Skip to content

Commit dc0183e

Browse files
committed
v0.0.16: Fix Kimi multi-turn — reasoning_content + content in tool call messages
Kimi's API requires reasoning_content and content to be present in assistant messages when thinking mode is active. Added: - reasoning_content field to ChatMessage struct - Stream parser captures reasoning_content from deltas - Always serialize content (even empty) and reasoning_content - Debug request logging via AO_DEBUG_REQUESTS env var
1 parent 22f473e commit dc0183e

7 files changed

Lines changed: 32 additions & 15 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oai-runner/src/api/client.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ impl ApiClient {
157157
request: &ChatRequest,
158158
on_text_chunk: &mut dyn FnMut(&str),
159159
) -> Result<(ChatMessage, Option<UsageInfo>)> {
160+
if std::env::var("AO_DEBUG_REQUESTS").is_ok() {
161+
eprintln!("[oai-runner] Request body: {}", serde_json::to_string(request).unwrap_or_default());
162+
}
160163
let resp = self
161164
.http
162165
.post(url)
@@ -183,6 +186,7 @@ impl ApiClient {
183186
}
184187

185188
let mut content = String::new();
189+
let mut reasoning_content = String::new();
186190
let mut tool_calls: Vec<ToolCall> = Vec::new();
187191
let mut usage: Option<UsageInfo> = None;
188192

@@ -201,7 +205,8 @@ impl ApiClient {
201205
std::io::stdout().flush().ok();
202206
let msg = ChatMessage {
203207
role: "assistant".to_string(),
204-
content: if content.is_empty() { None } else { Some(content) },
208+
content: Some(content),
209+
reasoning_content: Some(reasoning_content),
205210
tool_calls: if tool_calls.is_empty() { None } else { Some(tool_calls) },
206211
tool_call_id: None,
207212
};
@@ -223,6 +228,9 @@ impl ApiClient {
223228
content.push_str(text);
224229
on_text_chunk(text);
225230
}
231+
if let Some(text) = &choice.delta.reasoning_content {
232+
reasoning_content.push_str(text);
233+
}
226234

227235
if let Some(tc_deltas) = &choice.delta.tool_calls {
228236
for tc_delta in tc_deltas {
@@ -256,6 +264,7 @@ impl ApiClient {
256264
let msg = ChatMessage {
257265
role: "assistant".to_string(),
258266
content: if content.is_empty() { None } else { Some(content) },
267+
reasoning_content: Some(reasoning_content),
259268
tool_calls: if tool_calls.is_empty() { None } else { Some(tool_calls) },
260269
tool_call_id: None,
261270
};

crates/oai-runner/src/api/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ pub struct ChatMessage {
66
pub role: String,
77
#[serde(skip_serializing_if = "Option::is_none")]
88
pub content: Option<String>,
9+
#[serde(default, skip_serializing_if = "Option::is_none")]
10+
pub reasoning_content: Option<String>,
911
#[serde(skip_serializing_if = "Option::is_none")]
1012
pub tool_calls: Option<Vec<ToolCall>>,
1113
#[serde(skip_serializing_if = "Option::is_none")]
@@ -57,6 +59,8 @@ pub struct StreamDelta {
5759
#[serde(default)]
5860
pub content: Option<String>,
5961
#[serde(default)]
62+
pub reasoning_content: Option<String>,
63+
#[serde(default)]
6064
pub tool_calls: Option<Vec<StreamToolCall>>,
6165
}
6266

crates/oai-runner/src/runner/agent_loop.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ pub async fn run_agent_loop(
119119
sys.push_str(&build_schema_injection(response_schema.unwrap()));
120120
}
121121
if !sys.is_empty() {
122-
messages.push(ChatMessage {
122+
messages.push(ChatMessage { reasoning_content: None,
123123
role: "system".to_string(),
124124
content: Some(sys),
125125
tool_calls: None,
@@ -128,7 +128,7 @@ pub async fn run_agent_loop(
128128
}
129129
}
130130

131-
messages.push(ChatMessage {
131+
messages.push(ChatMessage { reasoning_content: None,
132132
role: "user".to_string(),
133133
content: Some(user_prompt.to_string()),
134134
tool_calls: None,
@@ -268,7 +268,7 @@ pub async fn run_agent_loop(
268268
}
269269
};
270270

271-
messages.push(ChatMessage {
271+
messages.push(ChatMessage { reasoning_content: None,
272272
role: "tool".to_string(),
273273
content: Some(result),
274274
tool_calls: None,
@@ -333,13 +333,13 @@ async fn retry_schema_validation(
333333
if let Some(sys) = system_msg {
334334
retry_messages.push(sys.clone());
335335
}
336-
retry_messages.push(ChatMessage {
336+
retry_messages.push(ChatMessage { reasoning_content: None,
337337
role: "assistant".to_string(),
338338
content: Some(last_assistant_content.clone()),
339339
tool_calls: None,
340340
tool_call_id: None,
341341
});
342-
retry_messages.push(ChatMessage {
342+
retry_messages.push(ChatMessage { reasoning_content: None,
343343
role: "user".to_string(),
344344
content: Some(correction.clone()),
345345
tool_calls: None,
@@ -375,7 +375,7 @@ async fn retry_schema_validation(
375375
}
376376

377377
let content = retry_msg.content.clone().unwrap_or_default();
378-
messages.push(ChatMessage {
378+
messages.push(ChatMessage { reasoning_content: None,
379379
role: "user".to_string(),
380380
content: Some(correction),
381381
tool_calls: None,
@@ -666,19 +666,19 @@ mod tests {
666666

667667
let sid = "test-session-round-trip";
668668
let messages = vec![
669-
ChatMessage {
669+
ChatMessage { reasoning_content: None,
670670
role: "system".to_string(),
671671
content: Some("You are helpful.".to_string()),
672672
tool_calls: None,
673673
tool_call_id: None,
674674
},
675-
ChatMessage {
675+
ChatMessage { reasoning_content: None,
676676
role: "user".to_string(),
677677
content: Some("Hello".to_string()),
678678
tool_calls: None,
679679
tool_call_id: None,
680680
},
681-
ChatMessage {
681+
ChatMessage { reasoning_content: None,
682682
role: "assistant".to_string(),
683683
content: Some("Hi there!".to_string()),
684684
tool_calls: None,

crates/oai-runner/src/runner/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ mod tests {
133133
use super::*;
134134

135135
fn msg(role: &str, content: &str) -> ChatMessage {
136-
ChatMessage { role: role.to_string(), content: Some(content.to_string()), tool_calls: None, tool_call_id: None }
136+
ChatMessage { reasoning_content: None, role: role.to_string(), content: Some(content.to_string()), tool_calls: None, tool_call_id: None }
137137
}
138138

139139
#[test]

crates/orchestrator-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "orchestrator-cli"
3-
version = "0.0.15"
3+
version = "0.0.16"
44
edition = "2021"
55

66
[[bin]]

crates/orchestrator-core/src/services/daemon_impl.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ impl DaemonServiceApi for InMemoryServiceHub {
6666
let pool_size = config.pool_size;
6767
let mut lock = self.state.write().await;
6868
lock.daemon_status = DaemonStatus::Running;
69-
lock.daemon_pool_size = pool_size;
69+
if let Some(ps) = pool_size {
70+
lock.daemon_pool_size = Some(ps);
71+
}
7072
lock.logs.push(LogEntry {
7173
timestamp: Utc::now(),
7274
level: LogLevel::Info,
@@ -171,7 +173,9 @@ impl DaemonServiceApi for FileServiceHub {
171173

172174
mutate_daemon_state(self, |state| {
173175
state.daemon_status = DaemonStatus::Running;
174-
state.daemon_pool_size = pool_size;
176+
if let Some(ps) = pool_size {
177+
state.daemon_pool_size = Some(ps);
178+
}
175179
state.runner_pid = runner_pid;
176180
state.logs.push(LogEntry {
177181
timestamp: Utc::now(),

0 commit comments

Comments
 (0)