driver resilience

This commit is contained in:
jaberjaber23
2026-03-05 20:21:03 +03:00
parent 06df0795c8
commit 9fc0fe71bf
5 changed files with 118 additions and 35 deletions
Generated
+14 -14
View File
@@ -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
View File
@@ -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"
+10 -4
View File
@@ -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"})),
+46 -13
View File
@@ -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 -3
View File
@@ -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,