shenmali's picture
Upload folder using huggingface_hub
44de0e7 verified
Raw
History Blame Contribute Delete
4.22 kB
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
PLANNER_PROMPT = (
"You are a planner. Given a task, output a JSON array of 2-5 short step "
"strings that would accomplish it. Output ONLY the JSON array."
)
EXECUTOR_PROMPT = "You are an executor. Complete the given step concisely. Use a tool if it helps."
REFLECTOR_PROMPT = (
"You are a reviewer. Given the task and the results so far, decide if the task "
'is complete. Reply with JSON {"done": bool, "feedback": str}. Output ONLY JSON.'
)
class PlanExecuteAgent:
def __init__(self, llm: LLMClient, tools: ToolRegistry, max_rounds: int = 2):
self.llm = llm
self.tools = tools
self.max_rounds = max_rounds
def _ask(self, system: str, user: str, use_tools: bool = False):
start = time.monotonic()
resp = self.llm.chat(
[{"role": "system", "content": system}, {"role": "user", "content": user}],
tools=self.tools.to_openai_schema() if use_tools else None,
)
latency = int((time.monotonic() - start) * 1000)
cost = estimate_cost(self.llm.model, resp.prompt_tokens, resp.completion_tokens)
return resp, latency, cost
def run(self, task: str) -> Iterator[Step]:
feedback = ""
results: list[str] = []
for _ in range(self.max_rounds):
planner_input = f"Task: {task}" + (f"\nPrior feedback: {feedback}" if feedback else "")
resp, lat, cost = self._ask(PLANNER_PROMPT, planner_input)
try:
plan = json.loads(resp.content or "[]")
except json.JSONDecodeError:
plan = [resp.content or ""]
yield Step(
kind="thought",
content="Plan:\n" + "\n".join(f"{i + 1}. {s}" for i, s in enumerate(plan)),
tokens=resp.prompt_tokens + resp.completion_tokens,
cost_usd=cost,
latency_ms=lat,
)
results = []
for s in plan:
eresp, elat, ecost = self._ask(
EXECUTOR_PROMPT, f"Task: {task}\nStep: {s}", use_tools=True
)
if eresp.tool_calls:
for tc in eresp.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:
obs = f"Error: invalid tool arguments JSON: {e}"
else:
obs = self.tools.execute(tc["name"], args)
yield Step(kind="observation", content=obs)
results.append(f"{s}: {obs}")
else:
yield Step(kind="action", content=f"Execute: {s}")
yield Step(
kind="observation",
content=eresp.content or "",
tokens=eresp.prompt_tokens + eresp.completion_tokens,
cost_usd=ecost,
latency_ms=elat,
)
results.append(f"{s}: {eresp.content}")
rresp, rlat, rcost = self._ask(
REFLECTOR_PROMPT, f"Task: {task}\nResults:\n" + "\n".join(results)
)
try:
verdict = json.loads(rresp.content or "{}")
except json.JSONDecodeError:
verdict = {"done": True, "feedback": ""}
yield Step(
kind="thought",
content=f"Reflection (done={verdict.get('done')}): {verdict.get('feedback', '')}",
tokens=rresp.prompt_tokens + rresp.completion_tokens,
cost_usd=rcost,
latency_ms=rlat,
)
if verdict.get("done"):
break
feedback = verdict.get("feedback", "")
yield Step(kind="final", content="\n".join(results))