sipe5001's picture
Add Hugging Face Docker Space configuration
d3d0e0e
Raw History Blame Contribute Delete
5.45 kB
from __future__ import annotations
import json
import re
from dataclasses import dataclass
from data_agent_baseline.agents.model import ModelAdapter, ModelMessage, ModelStep
from data_agent_baseline.agents.prompt import (
REACT_SYSTEM_PROMPT,
build_observation_prompt,
build_system_prompt,
build_task_prompt,
)
from data_agent_baseline.agents.runtime import AgentRunResult, AgentRuntimeState, StepRecord
from data_agent_baseline.benchmark.schema import PublicTask
from data_agent_baseline.tools.registry import ToolRegistry
@dataclass(frozen=True, slots=True)
class ReActAgentConfig:
max_steps: int = 16
def _strip_json_fence(raw_response: str) -> str:
text = raw_response.strip()
fence_match = re.search(r"```json\s*(.*?)\s*```", text, flags=re.IGNORECASE | re.DOTALL)
if fence_match is not None:
return fence_match.group(1).strip()
generic_fence_match = re.search(r"```\s*(.*?)\s*```", text, flags=re.DOTALL)
if generic_fence_match is not None:
return generic_fence_match.group(1).strip()
return text
def _load_single_json_object(text: str) -> dict[str, object]:
payload, end = json.JSONDecoder().raw_decode(text)
remainder = text[end:].strip()
if remainder:
cleaned_remainder = re.sub(r"(?:\\[nrt])+", "", remainder).strip()
if cleaned_remainder:
raise ValueError("Model response must contain only one JSON object.")
if not isinstance(payload, dict):
raise ValueError("Model response must be a JSON object.")
return payload
def parse_model_step(raw_response: str) -> ModelStep:
normalized = _strip_json_fence(raw_response)
payload = _load_single_json_object(normalized)
thought = payload.get("thought", "")
action = payload.get("action")
action_input = payload.get("action_input", {})
if not isinstance(thought, str):
raise ValueError("thought must be a string.")
if not isinstance(action, str) or not action:
raise ValueError("action must be a non-empty string.")
if not isinstance(action_input, dict):
raise ValueError("action_input must be a JSON object.")
return ModelStep(
thought=thought,
action=action,
action_input=action_input,
raw_response=raw_response,
)
class ReActAgent:
def __init__(
self,
*,
model: ModelAdapter,
tools: ToolRegistry,
config: ReActAgentConfig | None = None,
system_prompt: str | None = None,
) -> None:
self.model = model
self.tools = tools
self.config = config or ReActAgentConfig()
self.system_prompt = system_prompt or REACT_SYSTEM_PROMPT
def _build_messages(self, task: PublicTask, state: AgentRuntimeState) -> list[ModelMessage]:
system_content = build_system_prompt(
self.tools.describe_for_prompt(),
system_prompt=self.system_prompt,
)
messages = [ModelMessage(role="system", content=system_content)]
messages.append(ModelMessage(role="user", content=build_task_prompt(task)))
for step in state.steps:
messages.append(ModelMessage(role="assistant", content=step.raw_response))
messages.append(
ModelMessage(role="user", content=build_observation_prompt(step.observation))
)
return messages
def run(self, task: PublicTask) -> AgentRunResult:
state = AgentRuntimeState()
for step_index in range(1, self.config.max_steps + 1):
raw_response = self.model.complete(self._build_messages(task, state))
try:
model_step = parse_model_step(raw_response)
tool_result = self.tools.execute(task, model_step.action, model_step.action_input)
observation = {
"ok": tool_result.ok,
"tool": model_step.action,
"content": tool_result.content,
}
step_record = StepRecord(
step_index=step_index,
thought=model_step.thought,
action=model_step.action,
action_input=model_step.action_input,
raw_response=raw_response,
observation=observation,
ok=tool_result.ok,
)
state.steps.append(step_record)
if tool_result.is_terminal:
state.answer = tool_result.answer
break
except Exception as exc:
observation = {
"ok": False,
"error": str(exc),
}
state.steps.append(
StepRecord(
step_index=step_index,
thought="",
action="__error__",
action_input={},
raw_response=raw_response,
observation=observation,
ok=False,
)
)
if state.answer is None and state.failure_reason is None:
state.failure_reason = "Agent did not submit an answer within max_steps."
return AgentRunResult(
task_id=task.task_id,
answer=state.answer,
steps=list(state.steps),
failure_reason=state.failure_reason,
)