feat(assistant): #91 steps humains en direct + outils web_search/fetch_url (labels backend, SSE step, garde SSRF)
CI / lint (push) Successful in 1m22s
CI / security (push) Successful in 55s
CI / test (push) Successful in 3m0s
CI / build (push) Successful in 51s
CI / e2e (push) Successful in 10m41s

This commit is contained in:
2026-09-15 17:46:51 -04:00
parent 18b7dee2f6
commit 19d5b931c4
14 changed files with 745 additions and 32 deletions
+33 -2
View File
@@ -30,6 +30,7 @@ from backend.tools.api import (
call_tool,
get_tool_schemas,
)
from backend.tools.labels import thought_step_label, tool_step_label
logger = logging.getLogger("obsigate.agent.loop")
@@ -54,6 +55,9 @@ class ToolCallRecord:
arguments: dict[str, Any]
ok: bool
result: Any
# Human-readable « step » label for the Notion-style UI
# ({key, params} — see backend.tools.labels).
step: dict[str, Any] = field(default_factory=dict)
@dataclass
@@ -63,6 +67,9 @@ class AgentResult:
content: str = ""
messages: list[dict[str, Any]] = field(default_factory=list)
tool_calls: list[ToolCallRecord] = field(default_factory=list)
# Ordered Notion-style step descriptors ({key, params}); tool steps and
# intermediate reasoning notes interleaved by execution order.
steps: list[dict[str, Any]] = field(default_factory=list)
iterations: int = 0
stopped: str = STOP_DONE
pending: dict[str, Any] | None = None
@@ -142,7 +149,10 @@ def _execute_confirmed(
payload = e.to_dict()
ok = False
record = ToolCallRecord(name=name, arguments=arguments, ok=ok, result=payload)
record = ToolCallRecord(
name=name, arguments=arguments, ok=ok, result=payload,
step=tool_step_label(name, arguments),
)
executed.append(record)
if on_tool_call is not None:
on_tool_call(record)
@@ -164,6 +174,7 @@ async def run_agent(
max_iterations: int = DEFAULT_MAX_ITERATIONS,
max_tool_calls: int | None = None,
on_tool_call: Callable[[ToolCallRecord], None] | None = None,
on_thought: Callable[[dict[str, Any]], None] | None = None,
resume_messages: list[dict[str, Any]] | None = None,
confirm_pending: dict[str, Any] | None = None,
) -> AgentResult:
@@ -195,6 +206,15 @@ async def run_agent(
if tools is None:
tools = get_tool_schemas(scope=ToolScope.IN_APP)
quota = DEFAULT_MAX_TOOL_CALLS if max_tool_calls is None else max_tool_calls
steps: list[dict[str, Any]] = []
def _emit_note(text: str) -> None:
"""Record an intermediate reasoning note as a visible step."""
note = thought_step_label(text)
if note["params"]["value"]:
steps.append(note)
if on_thought is not None:
on_thought(note)
convo = [dict(m) for m in (resume_messages if resume_messages is not None else messages)]
executed: list[ToolCallRecord] = []
@@ -205,6 +225,7 @@ async def run_agent(
content="",
messages=convo,
tool_calls=executed,
steps=steps,
iterations=0,
stopped=STOP_QUOTA_EXCEEDED,
)
@@ -218,10 +239,13 @@ async def run_agent(
content=response.content or "",
messages=convo,
tool_calls=executed,
steps=steps,
iterations=iteration,
stopped=STOP_DONE,
)
# Intermediate reasoning shown alongside tool calls → a "thought" step.
_emit_note(response.content or "")
convo.append(_assistant_tool_message(response.content, response.tool_calls))
for call in response.tool_calls:
@@ -231,6 +255,7 @@ async def run_agent(
content=response.content or "",
messages=convo,
tool_calls=executed,
steps=steps,
iterations=iteration,
stopped=STOP_QUOTA_EXCEEDED,
)
@@ -247,6 +272,7 @@ async def run_agent(
content=response.content or "",
messages=convo,
tool_calls=executed,
steps=steps,
iterations=iteration,
stopped=STOP_CONFIRMATION_REQUIRED,
pending=pending,
@@ -255,8 +281,12 @@ async def run_agent(
payload = e.to_dict()
ok = False
record = ToolCallRecord(name=call.name, arguments=call.arguments, ok=ok, result=payload)
record = ToolCallRecord(
name=call.name, arguments=call.arguments, ok=ok, result=payload,
step=tool_step_label(call.name, call.arguments),
)
executed.append(record)
steps.append(record.step)
if on_tool_call is not None:
on_tool_call(record)
@@ -272,6 +302,7 @@ async def run_agent(
content="",
messages=convo,
tool_calls=executed,
steps=steps,
iterations=max_iterations,
stopped=STOP_MAX_ITERATIONS,
)