Render plain Responses completions

When Responses mode accepts a plain-text final answer, reuse the active generating log item as a finished response entry so the WebUI does not remain on the Calling LLM step.

Skip normal tool JSON and existing live response-tool logs, and cover the fallback with focused regression tests.
This commit is contained in:
Alessandro 2026-06-26 15:00:02 +02:00
parent 272a0d8dfa
commit 48087de008
3 changed files with 111 additions and 0 deletions

View file

@ -15,6 +15,7 @@
- Do not flatten nested qualname paths into retired legacy folder names.
- Extension functions must match the implicit hook's supplied arguments.
- Preserve ordering prefixes where exception handling, watchdog registration, or cleanup depends on them.
- Hooks that mirror persisted AI responses into UI logs must reuse existing stream log items and avoid duplicating live response-tool logs.
## Work Guidance

View file

@ -0,0 +1,44 @@
from typing import Any
from helpers import extract_tools
from helpers.extension import Extension
class LogPlainResponses(Extension):
def execute(self, data: dict[str, Any] | None = None, **kwargs):
if not self.agent or not isinstance(data, dict):
return
call_kwargs = data.get("kwargs")
if not isinstance(call_kwargs, dict):
call_kwargs = {}
llm_result = call_kwargs.get("llm_result")
if getattr(llm_result, "mode", "") != "responses":
return
message = call_kwargs.get("message")
call_args = data.get("args")
if message is None and isinstance(call_args, tuple) and len(call_args) > 1:
message = call_args[1]
if not isinstance(message, str) or not message:
return
if extract_tools.json_parse_dirty(message) is not None:
return
params = getattr(getattr(self.agent, "loop_data", None), "params_temporary", None)
if not isinstance(params, dict) or "log_item_response" in params:
return
log_item = params.get("log_item_generating")
if log_item is None:
return
params["log_item_response"] = log_item
log_item.update(
type="response",
heading="",
content=message,
finished=True,
update_progress="none",
)

View file

@ -0,0 +1,66 @@
from types import SimpleNamespace
from helpers.log import Log
from extensions.python._functions.agent.Agent.hist_add_ai_response.end._10_log_plain_responses import (
LogPlainResponses,
)
def _agent_with_generating_log():
log = Log()
item = log.log(type="agent", heading="A0: Calling LLM...", id="msg-1")
agent = SimpleNamespace(
loop_data=SimpleNamespace(params_temporary={"log_item_generating": item})
)
return agent, item
def test_responses_plain_text_completion_finishes_generating_log_as_response():
agent, item = _agent_with_generating_log()
data = {
"args": (agent, "Plain final answer."),
"kwargs": {"id": "msg-1", "llm_result": SimpleNamespace(mode="responses")},
}
LogPlainResponses(agent=agent).execute(data=data)
assert item.type == "response"
assert item.heading == ""
assert item.content == "Plain final answer."
assert item.update_progress == "none"
assert item.kvps["finished"] is True
assert agent.loop_data.params_temporary["log_item_response"] is item
def test_responses_tool_json_keeps_generating_log_as_agent_step():
agent, item = _agent_with_generating_log()
data = {
"args": (
agent,
'{"tool_name":"search_engine","tool_args":{"query":"today news"}}',
),
"kwargs": {"id": "msg-1", "llm_result": SimpleNamespace(mode="responses")},
}
LogPlainResponses(agent=agent).execute(data=data)
assert item.type == "agent"
assert item.heading == "A0: Calling LLM..."
assert item.content == ""
assert "log_item_response" not in agent.loop_data.params_temporary
def test_responses_plain_text_completion_does_not_replace_live_response_log():
agent, item = _agent_with_generating_log()
live_response = Log().log(type="response", content="Already live")
agent.loop_data.params_temporary["log_item_response"] = live_response
data = {
"args": (agent, "Plain final answer."),
"kwargs": {"id": "msg-1", "llm_result": SimpleNamespace(mode="responses")},
}
LogPlainResponses(agent=agent).execute(data=data)
assert item.type == "agent"
assert item.content == ""
assert agent.loop_data.params_temporary["log_item_response"] is live_response