mirror of
https://github.com/RightNow-AI/openfang.git
synced 2026-07-30 06:32:17 +00:00
driver resilience
This commit is contained in:
Generated
+14
-14
@@ -3866,7 +3866,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-api"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3903,7 +3903,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-channels"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3934,7 +3934,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-cli"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
@@ -3961,7 +3961,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-desktop"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"open",
|
||||
@@ -3987,7 +3987,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-extensions"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2",
|
||||
@@ -4015,7 +4015,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-hands"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dashmap",
|
||||
@@ -4032,7 +4032,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-kernel"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4068,7 +4068,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-memory"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4087,7 +4087,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-migrate"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dirs 6.0.0",
|
||||
@@ -4106,7 +4106,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-runtime"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4138,7 +4138,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-skills"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
@@ -4161,7 +4161,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-types"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4180,7 +4180,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-wire"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -8802,7 +8802,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.20"
|
||||
version = "0.3.21"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
repository = "https://github.com/RightNow-AI/openfang"
|
||||
|
||||
@@ -7575,10 +7575,16 @@ pub async fn update_agent_identity(
|
||||
};
|
||||
|
||||
match state.kernel.registry.update_identity(agent_id, identity) {
|
||||
Ok(()) => (
|
||||
StatusCode::OK,
|
||||
Json(serde_json::json!({"status": "ok", "agent_id": id})),
|
||||
),
|
||||
Ok(()) => {
|
||||
// Persist identity to SQLite
|
||||
if let Some(entry) = state.kernel.registry.get(agent_id) {
|
||||
let _ = state.kernel.memory.save_agent(&entry);
|
||||
}
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(serde_json::json!({"status": "ok", "agent_id": id})),
|
||||
)
|
||||
}
|
||||
Err(_) => (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(serde_json::json!({"error": "Agent not found"})),
|
||||
|
||||
@@ -130,11 +130,19 @@ impl StructuredStore {
|
||||
"ALTER TABLE agents ADD COLUMN session_id TEXT DEFAULT ''",
|
||||
[],
|
||||
);
|
||||
// Add identity column (migration compat)
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE agents ADD COLUMN identity TEXT DEFAULT '{}'",
|
||||
[],
|
||||
);
|
||||
|
||||
let identity_json = serde_json::to_string(&entry.identity)
|
||||
.map_err(|e| OpenFangError::Serialization(e.to_string()))?;
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO agents (id, name, manifest, state, created_at, updated_at, session_id)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
||||
ON CONFLICT(id) DO UPDATE SET name = ?2, manifest = ?3, state = ?4, updated_at = ?6, session_id = ?7",
|
||||
"INSERT INTO agents (id, name, manifest, state, created_at, updated_at, session_id, identity)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
|
||||
ON CONFLICT(id) DO UPDATE SET name = ?2, manifest = ?3, state = ?4, updated_at = ?6, session_id = ?7, identity = ?8",
|
||||
rusqlite::params![
|
||||
entry.id.0.to_string(),
|
||||
entry.name,
|
||||
@@ -143,6 +151,7 @@ impl StructuredStore {
|
||||
entry.created_at.to_rfc3339(),
|
||||
now,
|
||||
entry.session_id.0.to_string(),
|
||||
identity_json,
|
||||
],
|
||||
)
|
||||
.map_err(|e| OpenFangError::Memory(e.to_string()))?;
|
||||
@@ -157,10 +166,13 @@ impl StructuredStore {
|
||||
.map_err(|e| OpenFangError::Internal(e.to_string()))?;
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT id, name, manifest, state, created_at, updated_at, session_id FROM agents WHERE id = ?1")
|
||||
.prepare("SELECT id, name, manifest, state, created_at, updated_at, session_id, identity FROM agents WHERE id = ?1")
|
||||
.or_else(|_| {
|
||||
// Fallback without session_id column for old DBs
|
||||
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at FROM agents WHERE id = ?1")
|
||||
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at, session_id FROM agents WHERE id = ?1")
|
||||
.or_else(|_| {
|
||||
// Fallback without session_id column for old DBs
|
||||
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at FROM agents WHERE id = ?1")
|
||||
})
|
||||
})
|
||||
.map_err(|e| OpenFangError::Memory(e.to_string()))?;
|
||||
|
||||
@@ -175,11 +187,16 @@ impl StructuredStore {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((name, manifest_blob, state_str, created_str, session_id_str))
|
||||
let identity_str: Option<String> = if col_count >= 8 {
|
||||
row.get(7).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((name, manifest_blob, state_str, created_str, session_id_str, identity_str))
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok((name, manifest_blob, state_str, created_str, session_id_str)) => {
|
||||
Ok((name, manifest_blob, state_str, created_str, session_id_str, identity_str)) => {
|
||||
let manifest = rmp_serde::from_slice(&manifest_blob)
|
||||
.map_err(|e| OpenFangError::Serialization(e.to_string()))?;
|
||||
let state = serde_json::from_str(&state_str)
|
||||
@@ -191,6 +208,9 @@ impl StructuredStore {
|
||||
.and_then(|s| uuid::Uuid::parse_str(&s).ok())
|
||||
.map(openfang_types::agent::SessionId)
|
||||
.unwrap_or_else(openfang_types::agent::SessionId::new);
|
||||
let identity = identity_str
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_default();
|
||||
Ok(Some(AgentEntry {
|
||||
id: agent_id,
|
||||
name,
|
||||
@@ -203,7 +223,7 @@ impl StructuredStore {
|
||||
children: vec![],
|
||||
session_id,
|
||||
tags: vec![],
|
||||
identity: Default::default(),
|
||||
identity,
|
||||
onboarding_completed: false,
|
||||
onboarding_completed_at: None,
|
||||
}))
|
||||
@@ -239,11 +259,14 @@ impl StructuredStore {
|
||||
.lock()
|
||||
.map_err(|e| OpenFangError::Internal(e.to_string()))?;
|
||||
|
||||
// Try with session_id column first, fall back without
|
||||
// Try with identity+session_id columns first, fall back gracefully
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT id, name, manifest, state, created_at, updated_at, session_id FROM agents",
|
||||
"SELECT id, name, manifest, state, created_at, updated_at, session_id, identity FROM agents",
|
||||
)
|
||||
.or_else(|_| {
|
||||
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at, session_id FROM agents")
|
||||
})
|
||||
.or_else(|_| {
|
||||
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at FROM agents")
|
||||
})
|
||||
@@ -262,6 +285,11 @@ impl StructuredStore {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let identity_str: Option<String> = if col_count >= 8 {
|
||||
row.get(7).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((
|
||||
id_str,
|
||||
name,
|
||||
@@ -269,6 +297,7 @@ impl StructuredStore {
|
||||
state_str,
|
||||
created_str,
|
||||
session_id_str,
|
||||
identity_str,
|
||||
))
|
||||
})
|
||||
.map_err(|e| OpenFangError::Memory(e.to_string()))?;
|
||||
@@ -278,7 +307,7 @@ impl StructuredStore {
|
||||
let mut repair_queue: Vec<(String, Vec<u8>, String)> = Vec::new();
|
||||
|
||||
for row in rows {
|
||||
let (id_str, name, manifest_blob, state_str, created_str, session_id_str) = match row {
|
||||
let (id_str, name, manifest_blob, state_str, created_str, session_id_str, identity_str) = match row {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
tracing::warn!("Skipping agent row with read error: {e}");
|
||||
@@ -342,6 +371,10 @@ impl StructuredStore {
|
||||
.map(openfang_types::agent::SessionId)
|
||||
.unwrap_or_else(openfang_types::agent::SessionId::new);
|
||||
|
||||
let identity = identity_str
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
agents.push(AgentEntry {
|
||||
id: agent_id,
|
||||
name,
|
||||
@@ -354,7 +387,7 @@ impl StructuredStore {
|
||||
children: vec![],
|
||||
session_id,
|
||||
tags: vec![],
|
||||
identity: Default::default(),
|
||||
identity,
|
||||
onboarding_completed: false,
|
||||
onboarding_completed_at: None,
|
||||
});
|
||||
|
||||
@@ -47,7 +47,8 @@ struct OaiRequest {
|
||||
/// New token limit field required by GPT-5 and o-series reasoning models.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
max_completion_tokens: Option<u32>,
|
||||
temperature: f32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
temperature: Option<f32>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
tools: Vec<OaiTool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -309,7 +310,7 @@ impl LlmDriver for OpenAIDriver {
|
||||
messages: oai_messages,
|
||||
max_tokens: mt,
|
||||
max_completion_tokens: mct,
|
||||
temperature: request.temperature,
|
||||
temperature: Some(request.temperature),
|
||||
tools: oai_tools,
|
||||
tool_choice,
|
||||
stream: false,
|
||||
@@ -399,6 +400,28 @@ impl LlmDriver for OpenAIDriver {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Model doesn't support function calling — retry without tools
|
||||
// (e.g. GLM-5 on DashScope returns 500 "internal error" when tools are sent)
|
||||
let body_lower = body.to_lowercase();
|
||||
if !oai_request.tools.is_empty()
|
||||
&& attempt < max_retries
|
||||
&& (status == 500
|
||||
|| body_lower.contains("internal error")
|
||||
|| (status == 400
|
||||
&& (body_lower.contains("does not support tools")
|
||||
|| body_lower.contains("tool")
|
||||
&& body_lower.contains("not supported"))))
|
||||
{
|
||||
warn!(
|
||||
model = %oai_request.model,
|
||||
status,
|
||||
"Model may not support tools, retrying without tools"
|
||||
);
|
||||
oai_request.tools.clear();
|
||||
oai_request.tool_choice = None;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(LlmError::Api {
|
||||
status,
|
||||
message: body,
|
||||
@@ -612,7 +635,7 @@ impl LlmDriver for OpenAIDriver {
|
||||
messages: oai_messages,
|
||||
max_tokens: mt,
|
||||
max_completion_tokens: mct,
|
||||
temperature: request.temperature,
|
||||
temperature: Some(request.temperature),
|
||||
tools: oai_tools,
|
||||
tool_choice,
|
||||
stream: true,
|
||||
@@ -704,6 +727,27 @@ impl LlmDriver for OpenAIDriver {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Model doesn't support function calling — retry without tools
|
||||
let body_lower = body.to_lowercase();
|
||||
if !oai_request.tools.is_empty()
|
||||
&& attempt < max_retries
|
||||
&& (status == 500
|
||||
|| body_lower.contains("internal error")
|
||||
|| (status == 400
|
||||
&& (body_lower.contains("does not support tools")
|
||||
|| body_lower.contains("tool")
|
||||
&& body_lower.contains("not supported"))))
|
||||
{
|
||||
warn!(
|
||||
model = %oai_request.model,
|
||||
status,
|
||||
"Model may not support tools (stream), retrying without tools"
|
||||
);
|
||||
oai_request.tools.clear();
|
||||
oai_request.tool_choice = None;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(LlmError::Api {
|
||||
status,
|
||||
message: body,
|
||||
|
||||
Reference in New Issue
Block a user