Fix agentic tool loop: El scoping rules, json_get_raw for array/object fields, result truncation
This commit is contained in:
@@ -265,12 +265,9 @@ fn handle_chat_agentic(body: String) -> String {
|
||||
|
||||
let api_key: String = agentic_api_key()
|
||||
let tools_json: String = agentic_tools_literal()
|
||||
|
||||
// Build initial messages array
|
||||
let safe_msg: String = json_safe(message)
|
||||
let safe_sys: String = json_safe(system)
|
||||
let messages: String = "[{\"role\":\"user\",\"content\":\"" + safe_msg + "\"}]"
|
||||
|
||||
let api_url: String = "https://api.anthropic.com/v1/messages"
|
||||
let h: Map = {}
|
||||
map_set(h, "x-api-key", api_key)
|
||||
@@ -299,47 +296,55 @@ fn handle_chat_agentic(body: String) -> String {
|
||||
}
|
||||
|
||||
let stop_reason: String = json_get(raw_resp, "stop_reason")
|
||||
let content_arr: String = json_get(raw_resp, "content")
|
||||
// json_get_raw needed — content is an array, json_get returns "" for non-strings
|
||||
let content_arr: String = json_get_raw(raw_resp, "content")
|
||||
let eff_content: String = if str_eq(content_arr, "") { "[]" } else { content_arr }
|
||||
|
||||
// Collect text and tool_use blocks from content
|
||||
// Walk content blocks. El rule: mutations must be at top level of while body
|
||||
// using if-expressions — mutations inside if *blocks* don't escape scope.
|
||||
let text_out: String = ""
|
||||
let tool_results: String = ""
|
||||
let has_tool_use: Bool = false
|
||||
let has_tool: Bool = false
|
||||
let tool_id: String = ""
|
||||
let tool_name: String = ""
|
||||
let tool_input: String = ""
|
||||
let ci: Int = 0
|
||||
let c_total: Int = json_array_len(content_arr)
|
||||
let c_total: Int = json_array_len(eff_content)
|
||||
while ci < c_total {
|
||||
let block: String = json_array_get(content_arr, ci)
|
||||
let block_type: String = json_get(block, "type")
|
||||
if str_eq(block_type, "text") {
|
||||
let block_text: String = json_get(block, "text")
|
||||
let text_out = text_out + block_text
|
||||
}
|
||||
if str_eq(block_type, "tool_use") {
|
||||
let has_tool_use = true
|
||||
let tool_id: String = json_get(block, "id")
|
||||
let tool_name: String = json_get(block, "name")
|
||||
let tool_input: String = json_get(block, "input")
|
||||
let tool_result: String = dispatch_tool(tool_name, tool_input)
|
||||
let sep: String = if str_eq(tool_results, "") { "" } else { "," }
|
||||
let tool_results = tool_results + sep
|
||||
+ "{\"type\":\"tool_result\",\"tool_use_id\":\"" + tool_id + "\",\"content\":\"" + tool_result + "\"}"
|
||||
}
|
||||
let block: String = json_array_get(eff_content, ci)
|
||||
let btype: String = json_get(block, "type")
|
||||
// Accumulate text at top level using if-expression
|
||||
let text_out = if str_eq(btype, "text") { text_out + json_get(block, "text") } else { text_out }
|
||||
// Capture first tool_use block only
|
||||
let is_new_tool: Bool = str_eq(btype, "tool_use") && !has_tool
|
||||
let has_tool = if is_new_tool { true } else { has_tool }
|
||||
let tool_id = if is_new_tool { json_get(block, "id") } else { tool_id }
|
||||
let tool_name = if is_new_tool { json_get(block, "name") } else { tool_name }
|
||||
// input is a JSON object — must use json_get_raw, not json_get
|
||||
let tool_input = if is_new_tool { json_get_raw(block, "input") } else { tool_input }
|
||||
let ci = ci + 1
|
||||
}
|
||||
|
||||
if str_eq(stop_reason, "tool_use") && has_tool_use {
|
||||
// Append assistant turn with its content blocks, then tool results
|
||||
let safe_content_arr: String = json_safe(content_arr)
|
||||
let inner_msgs: String = str_slice(messages, 1, str_len(messages) - 1)
|
||||
let messages = "[" + inner_msgs
|
||||
+ ",{\"role\":\"assistant\",\"content\":" + content_arr + "}"
|
||||
+ ",{\"role\":\"user\",\"content\":[" + tool_results + "]}"
|
||||
+ "]"
|
||||
let iteration = iteration + 1
|
||||
} else {
|
||||
let final_text = text_out
|
||||
let keep_going = false
|
||||
}
|
||||
// Dispatch tool and build result message
|
||||
let tool_result_raw: String = if has_tool { dispatch_tool(tool_name, tool_input) } else { "" }
|
||||
// Truncate large tool results (web pages etc) to avoid oversized requests
|
||||
let tool_result: String = if str_len(tool_result_raw) > 6000 {
|
||||
str_slice(tool_result_raw, 0, 6000) + "...[truncated]"
|
||||
} else { tool_result_raw }
|
||||
|
||||
let tool_msg: String = "{\"type\":\"tool_result\",\"tool_use_id\":\"" + tool_id + "\",\"content\":\"" + tool_result + "\"}"
|
||||
|
||||
// Update messages and loop state — all at top level using if-expressions
|
||||
let is_tool_turn: Bool = str_eq(stop_reason, "tool_use") && has_tool
|
||||
let inner: String = str_slice(messages, 1, str_len(messages) - 1)
|
||||
let messages = if is_tool_turn {
|
||||
"[" + inner
|
||||
+ ",{\"role\":\"assistant\",\"content\":" + eff_content + "}"
|
||||
+ ",{\"role\":\"user\",\"content\":[" + tool_msg + "]}"
|
||||
+ "]"
|
||||
} else { messages }
|
||||
let final_text = if !is_tool_turn { text_out } else { final_text }
|
||||
let keep_going = if !is_tool_turn { false } else { keep_going }
|
||||
let iteration = iteration + 1
|
||||
}
|
||||
|
||||
if str_eq(final_text, "") {
|
||||
|
||||
Reference in New Issue
Block a user