MeuAgente / src /agents /single_agent.py
gaiotto's picture
deploy: agente aula07 no HF Space
df1da34
Raw
History Blame Contribute Delete
4.23 kB
"""Agente single ReAct usando OpenAI tools nativo.
Implementação minimalista e didática do padrão ReAct: o LLM raciocina,
decide qual tool chamar, recebe o resultado, e itera até produzir resposta final.
Esta implementação NÃO depende de smolagents (que adiciona uma camada de
abstração e dependências pesadas). Aqui usamos diretamente o SDK OpenAI,
o que torna o código mais fácil de auditar e ensinar.
"""
import json
from dataclasses import dataclass, field
from typing import Any
from src.config import get_product_config, get_settings
from src.llm import chat_completion
from src.tools import execute_tool, get_tools_for_product
@dataclass
class AgentStep:
"""Um passo do loop ReAct."""
step_number: int
thought: str | None = None
tool_name: str | None = None
tool_args: dict | None = None
tool_result: str | None = None
is_final: bool = False
final_answer: str | None = None
@dataclass
class AgentRun:
"""Resultado de uma execução completa do agente."""
user_query: str
final_answer: str
steps: list[AgentStep] = field(default_factory=list)
total_tokens: int = 0
class SingleAgent:
"""Agente single ReAct com loop limitado.
Limite de iterações evita loops infinitos quando o LLM não converge.
"""
MAX_ITERATIONS = 8
def __init__(self, product_config: dict | None = None):
self.product_config: dict = product_config or get_product_config()
self.settings = get_settings()
self.system_prompt: str = self.product_config["persona"]
self.tools: list[dict] = get_tools_for_product(self.product_config)
def run(self, user_query: str, verbose: bool = False) -> AgentRun:
"""Executa o loop ReAct até resposta final ou MAX_ITERATIONS."""
messages: list[dict[str, Any]] = [
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": user_query},
]
run = AgentRun(user_query=user_query, final_answer="")
for i in range(1, self.MAX_ITERATIONS + 1):
step = AgentStep(step_number=i)
response_msg = chat_completion(
messages=messages,
tools=self.tools if self.tools else None,
temperature=0.7,
)
# Acumula no histórico (formato OpenAI espera tool_calls intactas)
messages.append(response_msg)
tool_calls = response_msg.get("tool_calls") or []
if not tool_calls:
# Sem mais ferramentas → resposta final
step.is_final = True
step.final_answer = response_msg.get("content") or ""
run.steps.append(step)
run.final_answer = step.final_answer
if verbose:
print(f"[passo {i}] FINAL: {step.final_answer[:200]}")
break
# Há chamadas de tools — execute cada uma e siga
for tool_call in tool_calls:
fn = tool_call["function"]
tool_name = fn["name"]
try:
tool_args = json.loads(fn.get("arguments") or "{}")
except json.JSONDecodeError:
tool_args = {}
step.tool_name = tool_name
step.tool_args = tool_args
tool_result = execute_tool(tool_name, tool_args)
step.tool_result = tool_result
messages.append(
{
"role": "tool",
"tool_call_id": tool_call["id"],
"content": tool_result,
}
)
if verbose:
print(f"[passo {i}] tool={tool_name} args={tool_args}")
print(f"[passo {i}] result={tool_result[:200]}")
run.steps.append(step)
if not run.final_answer:
# MAX_ITERATIONS atingido sem resposta final
run.final_answer = (
"(O agente atingiu o limite de iterações sem produzir resposta final. "
"Reformule sua pergunta de forma mais específica.)"
)
return run