Spaces:
Runtime error
Runtime error
| import json | |
| import time | |
| from collections.abc import Iterator | |
| from _core.llm import LLMClient | |
| from _core.models import estimate_cost | |
| from _core.tools import ToolRegistry | |
| from _core.tracer import Step | |
| SYSTEM_PROMPT = ( | |
| "You are a ReAct agent. Reason step by step about the user's question. " | |
| "Use a tool when you need external information or computation. " | |
| "When you can answer directly, respond with the final answer and do NOT call a tool." | |
| ) | |
| class ReActAgent: | |
| def __init__(self, llm: LLMClient, tools: ToolRegistry, max_steps: int = 6): | |
| self.llm = llm | |
| self.tools = tools | |
| self.max_steps = max_steps | |
| def run(self, question: str) -> Iterator[Step]: | |
| messages: list[dict] = [ | |
| {"role": "system", "content": SYSTEM_PROMPT}, | |
| {"role": "user", "content": question}, | |
| ] | |
| for _ in range(self.max_steps): | |
| start = time.monotonic() | |
| resp = self.llm.chat(messages, tools=self.tools.to_openai_schema()) | |
| latency = int((time.monotonic() - start) * 1000) | |
| cost = estimate_cost(self.llm.model, resp.prompt_tokens, resp.completion_tokens) | |
| step_tokens = resp.prompt_tokens + resp.completion_tokens | |
| if resp.tool_calls: | |
| messages.append( | |
| { | |
| "role": "assistant", | |
| "content": resp.content or "", | |
| "tool_calls": [ | |
| { | |
| "id": tc["id"], | |
| "type": "function", | |
| "function": {"name": tc["name"], "arguments": tc["arguments"]}, | |
| } | |
| for tc in resp.tool_calls | |
| ], | |
| } | |
| ) | |
| if resp.content: | |
| yield Step( | |
| kind="thought", | |
| content=resp.content, | |
| tokens=step_tokens, | |
| cost_usd=cost, | |
| latency_ms=latency, | |
| ) | |
| for tc in resp.tool_calls: | |
| yield Step(kind="action", content=f"{tc['name']}({tc['arguments']})") | |
| try: | |
| args = json.loads(tc["arguments"]) if tc["arguments"] else {} | |
| except json.JSONDecodeError as e: | |
| # Thread the error back as an observation so the model can | |
| # recover, rather than aborting the whole run. | |
| result = f"Error: invalid tool arguments (not valid JSON): {e}" | |
| else: | |
| result = self.tools.execute(tc["name"], args) | |
| yield Step(kind="observation", content=result) | |
| messages.append({"role": "tool", "tool_call_id": tc["id"], "content": result}) | |
| continue | |
| yield Step( | |
| kind="final", | |
| content=resp.content or "", | |
| tokens=step_tokens, | |
| cost_usd=cost, | |
| latency_ms=latency, | |
| ) | |
| return | |
| yield Step(kind="final", content="Reached max steps without a final answer.") | |