mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
The actual fix in #156 was adding chat(ChatRequest) to ReliableProvider. The prompt changes in instructions.rs and the bracket tool call parser in parse.rs were added during investigation but are not needed — the model uses native tool calls when ReliableProvider properly delegates to the inner provider.
This commit is contained in:
@@ -8,35 +8,14 @@ pub(crate) fn build_tool_instructions(tools_registry: &[Box<dyn Tool>]) -> Strin
|
||||
instructions.push_str("\n## Tool Use Protocol\n\n");
|
||||
instructions.push_str("To use a tool, wrap a JSON object in <tool_call></tool_call> tags:\n\n");
|
||||
instructions.push_str("```\n<tool_call>\n{\"name\": \"tool_name\", \"arguments\": {\"param\": \"value\"}}\n</tool_call>\n```\n\n");
|
||||
|
||||
// Explicit anti-hallucination rules
|
||||
instructions.push_str("### Rules (MUST follow)\n\n");
|
||||
instructions.push_str(
|
||||
"1. **ALWAYS use <tool_call> tags** when a task requires a tool. \
|
||||
NEVER narrate what you would do — emit the actual tags.\n",
|
||||
"CRITICAL: Output actual <tool_call> tags—never describe steps or give examples.\n\n",
|
||||
);
|
||||
instructions.push_str(
|
||||
"2. **NEVER describe a tool call in prose** (e.g. \"I'll run ls\" or \
|
||||
\"Let me check the file\") without also emitting the <tool_call> tags. \
|
||||
If you mention a tool, you must call it.\n",
|
||||
);
|
||||
instructions.push_str(
|
||||
"3. **Use the exact tool names** listed below. \
|
||||
Do not invent tool names that are not in the list.\n",
|
||||
);
|
||||
instructions.push_str("4. You may use **multiple tool calls** in a single response.\n");
|
||||
instructions.push_str(
|
||||
"5. After tool execution, results appear in <tool_result> tags. \
|
||||
Continue reasoning with the results until you can give a final answer.\n",
|
||||
);
|
||||
instructions.push_str(
|
||||
"6. Only respond **without** a tool call when the answer requires \
|
||||
no tool (e.g. general knowledge, math, or conversation).\n\n",
|
||||
);
|
||||
|
||||
instructions.push_str("### Example\n\n");
|
||||
instructions.push_str("User: \"what's the date?\"\nCorrect response:\n<tool_call>\n{\"name\":\"shell\",\"arguments\":{\"command\":\"date\"}}\n</tool_call>\n\n");
|
||||
|
||||
instructions.push_str("Example: User says \"what's the date?\". You MUST respond with:\n<tool_call>\n{\"name\":\"shell\",\"arguments\":{\"command\":\"date\"}}\n</tool_call>\n\n");
|
||||
instructions.push_str("You may use multiple tool calls in a single response. ");
|
||||
instructions.push_str("After tool execution, results appear in <tool_result> tags. ");
|
||||
instructions
|
||||
.push_str("Continue reasoning with the results until you can give a final answer.\n\n");
|
||||
instructions.push_str("### Available Tools\n\n");
|
||||
|
||||
for tool in tools_registry {
|
||||
|
||||
@@ -82,51 +82,7 @@ pub(crate) fn parse_tool_calls_from_json_value(value: &serde_json::Value) -> Vec
|
||||
calls
|
||||
}
|
||||
|
||||
/// Parse bracket-style pseudo-syntax emitted by some models:
|
||||
/// `{tool => "shell", args => { --command "ls" }}`
|
||||
///
|
||||
/// Extracts tool name and converts `--key "value"` args into a JSON object.
|
||||
fn parse_bracket_tool_call(inner: &str) -> Option<ParsedToolCall> {
|
||||
static BRACKET_TOOL_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r#"(?s)\{?\s*tool\s*=>\s*"([^"]+)"\s*,\s*args\s*=>\s*\{(.*?)\}\s*\}?"#).unwrap()
|
||||
});
|
||||
static ARG_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"--(\w+)\s+"([^"]*)"#).unwrap());
|
||||
|
||||
let cap = BRACKET_TOOL_RE.captures(inner.trim())?;
|
||||
let name = cap.get(1)?.as_str().to_string();
|
||||
let args_body = cap.get(2)?.as_str();
|
||||
|
||||
let mut args = serde_json::Map::new();
|
||||
for arg_cap in ARG_RE.captures_iter(args_body) {
|
||||
let key = arg_cap.get(1)?.as_str().to_string();
|
||||
let value = arg_cap.get(2)?.as_str().to_string();
|
||||
args.insert(key, serde_json::Value::String(value));
|
||||
}
|
||||
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
tool = name.as_str(),
|
||||
parse_mode = "bracket_syntax",
|
||||
"[parse] parsed bracket-style tool call"
|
||||
);
|
||||
|
||||
Some(ParsedToolCall {
|
||||
name,
|
||||
arguments: serde_json::Value::Object(args),
|
||||
})
|
||||
}
|
||||
|
||||
const TOOL_CALL_OPEN_TAGS: [&str; 6] = [
|
||||
"<tool_call>",
|
||||
"<toolcall>",
|
||||
"<tool-call>",
|
||||
"<invoke>",
|
||||
"[TOOL_CALL]",
|
||||
"[tool_call]",
|
||||
];
|
||||
const TOOL_CALL_OPEN_TAGS: [&str; 4] = ["<tool_call>", "<toolcall>", "<tool-call>", "<invoke>"];
|
||||
|
||||
pub(crate) fn find_first_tag<'a>(haystack: &str, tags: &'a [&'a str]) -> Option<(usize, &'a str)> {
|
||||
tags.iter()
|
||||
@@ -140,8 +96,6 @@ pub(crate) fn matching_tool_call_close_tag(open_tag: &str) -> Option<&'static st
|
||||
"<toolcall>" => Some("</toolcall>"),
|
||||
"<tool-call>" => Some("</tool-call>"),
|
||||
"<invoke>" => Some("</invoke>"),
|
||||
"[TOOL_CALL]" => Some("[/TOOL_CALL]"),
|
||||
"[tool_call]" => Some("[/tool_call]"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -422,16 +376,7 @@ pub(crate) fn parse_tool_calls(response: &str) -> (String, Vec<ParsedToolCall>)
|
||||
}
|
||||
|
||||
if !parsed_any {
|
||||
// Try parsing bracket pseudo-syntax:
|
||||
// {tool => "name", args => { --key "value" }}
|
||||
if let Some(call) = parse_bracket_tool_call(inner) {
|
||||
calls.push(call);
|
||||
parsed_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !parsed_any {
|
||||
tracing::warn!("Malformed tool_call body: could not parse tag content as JSON or bracket syntax");
|
||||
tracing::warn!("Malformed <tool_call> JSON: expected tool-call object in tag body");
|
||||
}
|
||||
|
||||
remaining = &after_open[close_idx + close_tag.len()..];
|
||||
|
||||
Reference in New Issue
Block a user