Skip to content

Commit 9fc0fe7

Browse files
committed
driver resilience
1 parent 06df079 commit 9fc0fe7

File tree

5 files changed

+118
-35
lines changed

5 files changed

+118
-35
lines changed

Cargo.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ members = [
1818
]
1919

2020
[workspace.package]
21-
version = "0.3.20"
21+
version = "0.3.21"
2222
edition = "2021"
2323
license = "Apache-2.0 OR MIT"
2424
repository = "https://github.com/RightNow-AI/openfang"

crates/openfang-api/src/routes.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7575,10 +7575,16 @@ pub async fn update_agent_identity(
75757575
};
75767576

75777577
match state.kernel.registry.update_identity(agent_id, identity) {
7578-
Ok(()) => (
7579-
StatusCode::OK,
7580-
Json(serde_json::json!({"status": "ok", "agent_id": id})),
7581-
),
7578+
Ok(()) => {
7579+
// Persist identity to SQLite
7580+
if let Some(entry) = state.kernel.registry.get(agent_id) {
7581+
let _ = state.kernel.memory.save_agent(&entry);
7582+
}
7583+
(
7584+
StatusCode::OK,
7585+
Json(serde_json::json!({"status": "ok", "agent_id": id})),
7586+
)
7587+
}
75827588
Err(_) => (
75837589
StatusCode::NOT_FOUND,
75847590
Json(serde_json::json!({"error": "Agent not found"})),

crates/openfang-memory/src/structured.rs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,19 @@ impl StructuredStore {
130130
"ALTER TABLE agents ADD COLUMN session_id TEXT DEFAULT ''",
131131
[],
132132
);
133+
// Add identity column (migration compat)
134+
let _ = conn.execute(
135+
"ALTER TABLE agents ADD COLUMN identity TEXT DEFAULT '{}'",
136+
[],
137+
);
138+
139+
let identity_json = serde_json::to_string(&entry.identity)
140+
.map_err(|e| OpenFangError::Serialization(e.to_string()))?;
133141

134142
conn.execute(
135-
"INSERT INTO agents (id, name, manifest, state, created_at, updated_at, session_id)
136-
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
137-
ON CONFLICT(id) DO UPDATE SET name = ?2, manifest = ?3, state = ?4, updated_at = ?6, session_id = ?7",
143+
"INSERT INTO agents (id, name, manifest, state, created_at, updated_at, session_id, identity)
144+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
145+
ON CONFLICT(id) DO UPDATE SET name = ?2, manifest = ?3, state = ?4, updated_at = ?6, session_id = ?7, identity = ?8",
138146
rusqlite::params![
139147
entry.id.0.to_string(),
140148
entry.name,
@@ -143,6 +151,7 @@ impl StructuredStore {
143151
entry.created_at.to_rfc3339(),
144152
now,
145153
entry.session_id.0.to_string(),
154+
identity_json,
146155
],
147156
)
148157
.map_err(|e| OpenFangError::Memory(e.to_string()))?;
@@ -157,10 +166,13 @@ impl StructuredStore {
157166
.map_err(|e| OpenFangError::Internal(e.to_string()))?;
158167

159168
let mut stmt = conn
160-
.prepare("SELECT id, name, manifest, state, created_at, updated_at, session_id FROM agents WHERE id = ?1")
169+
.prepare("SELECT id, name, manifest, state, created_at, updated_at, session_id, identity FROM agents WHERE id = ?1")
161170
.or_else(|_| {
162-
// Fallback without session_id column for old DBs
163-
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at FROM agents WHERE id = ?1")
171+
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at, session_id FROM agents WHERE id = ?1")
172+
.or_else(|_| {
173+
// Fallback without session_id column for old DBs
174+
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at FROM agents WHERE id = ?1")
175+
})
164176
})
165177
.map_err(|e| OpenFangError::Memory(e.to_string()))?;
166178

@@ -175,11 +187,16 @@ impl StructuredStore {
175187
} else {
176188
None
177189
};
178-
Ok((name, manifest_blob, state_str, created_str, session_id_str))
190+
let identity_str: Option<String> = if col_count >= 8 {
191+
row.get(7).ok()
192+
} else {
193+
None
194+
};
195+
Ok((name, manifest_blob, state_str, created_str, session_id_str, identity_str))
179196
});
180197

181198
match result {
182-
Ok((name, manifest_blob, state_str, created_str, session_id_str)) => {
199+
Ok((name, manifest_blob, state_str, created_str, session_id_str, identity_str)) => {
183200
let manifest = rmp_serde::from_slice(&manifest_blob)
184201
.map_err(|e| OpenFangError::Serialization(e.to_string()))?;
185202
let state = serde_json::from_str(&state_str)
@@ -191,6 +208,9 @@ impl StructuredStore {
191208
.and_then(|s| uuid::Uuid::parse_str(&s).ok())
192209
.map(openfang_types::agent::SessionId)
193210
.unwrap_or_else(openfang_types::agent::SessionId::new);
211+
let identity = identity_str
212+
.and_then(|s| serde_json::from_str(&s).ok())
213+
.unwrap_or_default();
194214
Ok(Some(AgentEntry {
195215
id: agent_id,
196216
name,
@@ -203,7 +223,7 @@ impl StructuredStore {
203223
children: vec![],
204224
session_id,
205225
tags: vec![],
206-
identity: Default::default(),
226+
identity,
207227
onboarding_completed: false,
208228
onboarding_completed_at: None,
209229
}))
@@ -239,11 +259,14 @@ impl StructuredStore {
239259
.lock()
240260
.map_err(|e| OpenFangError::Internal(e.to_string()))?;
241261

242-
// Try with session_id column first, fall back without
262+
// Try with identity+session_id columns first, fall back gracefully
243263
let mut stmt = conn
244264
.prepare(
245-
"SELECT id, name, manifest, state, created_at, updated_at, session_id FROM agents",
265+
"SELECT id, name, manifest, state, created_at, updated_at, session_id, identity FROM agents",
246266
)
267+
.or_else(|_| {
268+
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at, session_id FROM agents")
269+
})
247270
.or_else(|_| {
248271
conn.prepare("SELECT id, name, manifest, state, created_at, updated_at FROM agents")
249272
})
@@ -262,13 +285,19 @@ impl StructuredStore {
262285
} else {
263286
None
264287
};
288+
let identity_str: Option<String> = if col_count >= 8 {
289+
row.get(7).ok()
290+
} else {
291+
None
292+
};
265293
Ok((
266294
id_str,
267295
name,
268296
manifest_blob,
269297
state_str,
270298
created_str,
271299
session_id_str,
300+
identity_str,
272301
))
273302
})
274303
.map_err(|e| OpenFangError::Memory(e.to_string()))?;
@@ -278,7 +307,7 @@ impl StructuredStore {
278307
let mut repair_queue: Vec<(String, Vec<u8>, String)> = Vec::new();
279308

280309
for row in rows {
281-
let (id_str, name, manifest_blob, state_str, created_str, session_id_str) = match row {
310+
let (id_str, name, manifest_blob, state_str, created_str, session_id_str, identity_str) = match row {
282311
Ok(r) => r,
283312
Err(e) => {
284313
tracing::warn!("Skipping agent row with read error: {e}");
@@ -342,6 +371,10 @@ impl StructuredStore {
342371
.map(openfang_types::agent::SessionId)
343372
.unwrap_or_else(openfang_types::agent::SessionId::new);
344373

374+
let identity = identity_str
375+
.and_then(|s| serde_json::from_str(&s).ok())
376+
.unwrap_or_default();
377+
345378
agents.push(AgentEntry {
346379
id: agent_id,
347380
name,
@@ -354,7 +387,7 @@ impl StructuredStore {
354387
children: vec![],
355388
session_id,
356389
tags: vec![],
357-
identity: Default::default(),
390+
identity,
358391
onboarding_completed: false,
359392
onboarding_completed_at: None,
360393
});

crates/openfang-runtime/src/drivers/openai.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ struct OaiRequest {
4747
/// New token limit field required by GPT-5 and o-series reasoning models.
4848
#[serde(skip_serializing_if = "Option::is_none")]
4949
max_completion_tokens: Option<u32>,
50-
temperature: f32,
50+
#[serde(skip_serializing_if = "Option::is_none")]
51+
temperature: Option<f32>,
5152
#[serde(skip_serializing_if = "Vec::is_empty")]
5253
tools: Vec<OaiTool>,
5354
#[serde(skip_serializing_if = "Option::is_none")]
@@ -309,7 +310,7 @@ impl LlmDriver for OpenAIDriver {
309310
messages: oai_messages,
310311
max_tokens: mt,
311312
max_completion_tokens: mct,
312-
temperature: request.temperature,
313+
temperature: Some(request.temperature),
313314
tools: oai_tools,
314315
tool_choice,
315316
stream: false,
@@ -399,6 +400,28 @@ impl LlmDriver for OpenAIDriver {
399400
continue;
400401
}
401402

403+
// Model doesn't support function calling — retry without tools
404+
// (e.g. GLM-5 on DashScope returns 500 "internal error" when tools are sent)
405+
let body_lower = body.to_lowercase();
406+
if !oai_request.tools.is_empty()
407+
&& attempt < max_retries
408+
&& (status == 500
409+
|| body_lower.contains("internal error")
410+
|| (status == 400
411+
&& (body_lower.contains("does not support tools")
412+
|| body_lower.contains("tool")
413+
&& body_lower.contains("not supported"))))
414+
{
415+
warn!(
416+
model = %oai_request.model,
417+
status,
418+
"Model may not support tools, retrying without tools"
419+
);
420+
oai_request.tools.clear();
421+
oai_request.tool_choice = None;
422+
continue;
423+
}
424+
402425
return Err(LlmError::Api {
403426
status,
404427
message: body,
@@ -612,7 +635,7 @@ impl LlmDriver for OpenAIDriver {
612635
messages: oai_messages,
613636
max_tokens: mt,
614637
max_completion_tokens: mct,
615-
temperature: request.temperature,
638+
temperature: Some(request.temperature),
616639
tools: oai_tools,
617640
tool_choice,
618641
stream: true,
@@ -704,6 +727,27 @@ impl LlmDriver for OpenAIDriver {
704727
continue;
705728
}
706729

730+
// Model doesn't support function calling — retry without tools
731+
let body_lower = body.to_lowercase();
732+
if !oai_request.tools.is_empty()
733+
&& attempt < max_retries
734+
&& (status == 500
735+
|| body_lower.contains("internal error")
736+
|| (status == 400
737+
&& (body_lower.contains("does not support tools")
738+
|| body_lower.contains("tool")
739+
&& body_lower.contains("not supported"))))
740+
{
741+
warn!(
742+
model = %oai_request.model,
743+
status,
744+
"Model may not support tools (stream), retrying without tools"
745+
);
746+
oai_request.tools.clear();
747+
oai_request.tool_choice = None;
748+
continue;
749+
}
750+
707751
return Err(LlmError::Api {
708752
status,
709753
message: body,

0 commit comments

Comments
 (0)