|
from textwrap import dedent |
|
from typing import Any, Dict, List, Optional |
|
|
|
from smolagents.tools import Tool |
|
|
|
|
|
|
|
|
|
|
|
class NextAction: |
|
CONTINUE = "continue" |
|
VALIDATE = "validate" |
|
FINAL_ANSWER = "final_answer" |
|
|
|
|
|
|
|
|
|
|
|
class ThinkTool(Tool): |
|
name = "think" |
|
description = ( |
|
"Internal scratch‑pad. Use this to reason step‑by‑step before " |
|
"calling other tools or replying to the user." |
|
) |
|
inputs = { |
|
"title": {"type": "string", "description": "Concise title"}, |
|
"thought": {"type": "string", "description": "Detailed reasoning"}, |
|
"action": { |
|
"type": "string", |
|
"description": "Intended next action", |
|
"nullable": True, |
|
}, |
|
"confidence": { |
|
"type": "number", |
|
"description": "Confidence 0–1", |
|
"nullable": True, |
|
}, |
|
"run_id": { |
|
"type": "string", |
|
"description": "Execution identifier", |
|
"nullable": True, |
|
}, |
|
} |
|
output_type = "string" |
|
|
|
def __init__(self): |
|
super().__init__() |
|
self._history: Dict[str, List[Dict[str, Any]]] = {} |
|
|
|
def forward( |
|
self, |
|
title: str, |
|
thought: str, |
|
action: Optional[str] = None, |
|
confidence: float = 0.8, |
|
run_id: str = "default", |
|
) -> str: |
|
"""Store and pretty‑print reasoning history.""" |
|
step = { |
|
"title": title, |
|
"reasoning": thought, |
|
"action": action, |
|
"confidence": confidence, |
|
} |
|
self._history.setdefault(run_id, []).append(step) |
|
|
|
|
|
formatted = "" |
|
for idx, s in enumerate(self._history[run_id], 1): |
|
formatted += ( |
|
dedent( |
|
f"""\ |
|
Step {idx}: |
|
Title: {s["title"]} |
|
Reasoning: {s["reasoning"]} |
|
Action: {s["action"]} |
|
Confidence: {s["confidence"]} |
|
""" |
|
) |
|
+ "\n" |
|
) |
|
return formatted.strip() |
|
|
|
|
|
|
|
|
|
|
|
class AnalyzeTool(Tool): |
|
name = "analyze" |
|
description = ( |
|
"Evaluate the result of previous actions and decide whether to " |
|
"continue, validate, or provide a final answer. " |
|
) |
|
inputs = { |
|
"title": {"type": "string", "description": "Concise title"}, |
|
"result": {"type": "string", "description": "Outcome being analysed"}, |
|
"analysis": {"type": "string", "description": "Your analysis"}, |
|
"next_action": { |
|
"type": "string", |
|
"description": "'continue' | 'validate' | 'final_answer'", |
|
"nullable": True, |
|
}, |
|
"confidence": { |
|
"type": "number", |
|
"description": "Confidence 0–1", |
|
"nullable": True, |
|
}, |
|
"run_id": { |
|
"type": "string", |
|
"description": "Execution identifier", |
|
"nullable": True, |
|
}, |
|
} |
|
output_type = "string" |
|
|
|
def __init__(self): |
|
super().__init__() |
|
self._history: Dict[str, List[Dict[str, Any]]] = {} |
|
|
|
def forward( |
|
self, |
|
title: str, |
|
result: str, |
|
analysis: str, |
|
next_action: str = NextAction.CONTINUE, |
|
confidence: float = 0.8, |
|
run_id: str = "default", |
|
) -> str: |
|
if next_action not in { |
|
NextAction.CONTINUE, |
|
NextAction.VALIDATE, |
|
NextAction.FINAL_ANSWER, |
|
}: |
|
raise ValueError( |
|
f"next_action must be one of " |
|
f"{NextAction.CONTINUE}, {NextAction.VALIDATE}, " |
|
f"{NextAction.FINAL_ANSWER}" |
|
) |
|
|
|
step = { |
|
"title": title, |
|
"result": result, |
|
"reasoning": analysis, |
|
"next_action": next_action, |
|
"confidence": confidence, |
|
} |
|
self._history.setdefault(run_id, []).append(step) |
|
|
|
formatted = "" |
|
for idx, s in enumerate(self._history[run_id], 1): |
|
formatted += ( |
|
dedent( |
|
f"""\ |
|
Step {idx}: |
|
Title: {s["title"]} |
|
Result: {s.get("result")} |
|
Reasoning: {s["reasoning"]} |
|
Next action: {s.get("next_action")} |
|
Confidence: {s["confidence"]} |
|
""" |
|
) |
|
+ "\n" |
|
) |
|
return formatted.strip() |
|
|
|
|
|
|
|
|
|
|
|
class ReasoningToolkit: |
|
""" |
|
Convenience wrapper so you can write: |
|
|
|
from reasoning_tools import ReasoningToolkit |
|
toolkit = ReasoningToolkit() |
|
agent = CodeAgent(tools=toolkit.tools, model=...) |
|
""" |
|
|
|
DEFAULT_INSTRUCTIONS = dedent( |
|
"""\ |
|
You have access to two internal tools – **think** and **analyze** – |
|
for chain‑of‑thought reasoning. **Always** call `think` before |
|
external tool calls or final answers, then call `analyze` to |
|
decide whether to continue, validate, or finish.""" |
|
) |
|
|
|
def __init__(self, think: bool = True, analyze: bool = True): |
|
self.tools: List[Tool] = [] |
|
if think: |
|
self.tools.append(ThinkTool()) |
|
if analyze: |
|
self.tools.append(AnalyzeTool()) |
|
|
|
def with_instructions(self, extra: str | None = None) -> str: |
|
return self.DEFAULT_INSTRUCTIONS + ("\n" + extra if extra else "") |
|
|