Compare commits

...
3 Commits
Author SHA1 Message Date
Dark-Alex-17 e606eb7c49 fix: removed temperature modifier in librarian agent to mitigate invisible errors
CI / All (ubuntu-latest) (push) Failing after 27s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled
2026-08-05 16:10:19 -06:00
Dark-Alex-17 a8fb32b6bd fix: strip reasoning blocks for structured LLM output in graph agents 2026-08-05 16:10:03 -06:00
Dark-Alex-17 1f7b8417fa fix: prevent rare duplicate tool call IDs in long running claude prompts 2026-08-05 16:01:40 -06:00
4 changed files with 61 additions and 13 deletions
-4
View File
@@ -88,7 +88,6 @@ nodes:
prompt: |
Research prompt: {{initial_prompt}}
tools: []
temperature: 0.1
output_schema:
type: object
properties:
@@ -180,7 +179,6 @@ nodes:
tools:
- mcp:ddg-search
max_iterations: 15
temperature: 0.1
state_updates:
search_output: "{{output}}"
fallback: synthesize
@@ -251,7 +249,6 @@ nodes:
tools:
- mcp:personal-github
max_iterations: 15
temperature: 0.1
state_updates:
oss_output: "{{output}}"
fallback: synthesize
@@ -338,7 +335,6 @@ nodes:
tools:
- fetch_url_via_curl
max_iterations: 20
temperature: 0.1
state_updates:
findings: "{{output}}"
fallback: final_format
+10 -3
View File
@@ -1,3 +1,5 @@
use std::mem;
use super::*;
use crate::utils::{base64_decode, encode_uri, hex_encode, hmac_sha256, sha256, strip_think_tag};
@@ -275,10 +277,11 @@ async fn chat_completions_streaming(
format!("Tool call '{function_name}' has non-JSON arguments '{function_arguments}'")
})?;
handler.tool_call(ToolCall::new(
function_name.clone(),
mem::take(&mut function_name),
arguments,
Some(function_id.clone()),
Some(mem::take(&mut function_id)),
))?;
function_arguments.clear();
}
}
_ => {}
@@ -529,7 +532,11 @@ fn extract_chat_completions(data: &Value) -> Result<ChatCompletionsOutput> {
bail!("Invalid response data: {data}");
}
let output = ChatCompletionsOutput { text, tool_calls, ..Default::default() };
let output = ChatCompletionsOutput {
text,
tool_calls,
..Default::default()
};
Ok(output)
}
+7 -4
View File
@@ -1,3 +1,5 @@
use std::mem;
use super::access_token::get_access_token;
use super::claude_oauth::ClaudeOAuthProvider;
use super::oauth::{self, OAuthProvider};
@@ -232,8 +234,8 @@ pub async fn claude_chat_completions_streaming(
handler.text("\n</think>\n\n")?;
reasoning_state = 0;
handler.thinking_block(ThinkingBlock::Thinking {
thinking: std::mem::take(&mut thinking_text),
signature: std::mem::take(&mut thinking_signature),
thinking: mem::take(&mut thinking_text),
signature: mem::take(&mut thinking_signature),
});
}
if !function_name.is_empty() {
@@ -245,10 +247,11 @@ pub async fn claude_chat_completions_streaming(
})?
};
handler.tool_call(ToolCall::new(
function_name.clone(),
mem::take(&mut function_name),
arguments,
Some(function_id.clone()),
Some(mem::take(&mut function_id)),
))?;
function_arguments.clear();
}
}
_ => {}
+44 -2
View File
@@ -88,11 +88,21 @@ async fn run_one_shot(prompt: &str, ctx: &mut RequestContext) -> Result<String>
}
fn try_parse_json(raw: &str) -> Option<Value> {
let cleaned = strip_code_fences(raw.trim());
let cleaned = strip_code_fences(strip_thinking_blocks(raw.trim()));
serde_json::from_str(cleaned).ok()
}
fn strip_thinking_blocks(s: &str) -> &str {
let mut s = s.trim_start();
while s.starts_with("<think>") {
match s.find("</think>") {
Some(end) => s = s[end + "</think>".len()..].trim_start(),
None => break,
}
}
s
}
fn strip_code_fences(s: &str) -> &str {
let after_open = s
.strip_prefix("```json")
@@ -148,6 +158,38 @@ mod tests {
assert_eq!(v, json!({"x": true}));
}
#[test]
fn try_parse_json_strips_thinking_blocks() {
let raw = "<think>\nsome reasoning\n</think>\n{\"a\": 1}";
let v = try_parse_json(raw).unwrap();
assert_eq!(v, json!({"a": 1}));
}
#[test]
fn try_parse_json_strips_empty_thinking_block() {
let raw = "<think>\n\n</think>\n{\"a\": 1}";
let v = try_parse_json(raw).unwrap();
assert_eq!(v, json!({"a": 1}));
}
#[test]
fn try_parse_json_strips_multiple_thinking_blocks() {
let raw = "<think>first</think>\n<think>second</think>\n{\"a\": 1}";
let v = try_parse_json(raw).unwrap();
assert_eq!(v, json!({"a": 1}));
}
#[test]
fn try_parse_json_unclosed_think_tag_returns_none() {
assert!(try_parse_json("<think>unclosed {\"a\": 1}").is_none());
}
#[test]
fn try_parse_json_returns_none_on_prose() {
assert!(try_parse_json("Here is the result: it's good").is_none());