feat: логи tool-calls для pipeline всех заданий

pipeline.py:
- _invoke_with_retry принимает callbacks и пробрасывает в agent.ainvoke()
- process_one_task принимает RunnableConfig и извлекает callbacks из него
- callbacks передаются при первой сдаче и при retry-исправлении

ui.py:
- Pipeline «все todo» запускается в потоке (не блокирует asyncio.run)
- AgentCallback собирает события и показывает их в реальном времени
- После завершения — раскрывающийся лог всего pipeline

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 17:31:05 +03:00
parent 2bfcf5f782
commit 101671ea6e
2 changed files with 78 additions and 26 deletions
+16 -5
View File
@@ -8,6 +8,7 @@ import re
from typing import TypedDict
from langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableConfig
from langgraph.graph import START, StateGraph
from src.agent.agent import homework_direct_agent, journal as _journal_toolsets, rework_agent
@@ -236,11 +237,16 @@ def _is_rate_limit(exc: Exception) -> bool:
return "429" in msg or "rate" in msg.lower() or "rate_limit" in msg.lower()
async def _invoke_with_retry(agent, messages, config):
"""Вызывает агента с автоматическим retry при 429."""
async def _invoke_with_retry(agent, messages, config, callbacks=None):
"""Вызывает агента с автоматическим retry при 429.
callbacks — список LangChain callback-объектов (например AgentCallback из UI).
"""
run_config = dict(config)
if callbacks:
run_config["callbacks"] = callbacks
for attempt in range(1, RATE_LIMIT_RETRIES + 1):
try:
return await agent.ainvoke(messages, config)
return await agent.ainvoke(messages, run_config)
except Exception as e:
if _is_rate_limit(e) and attempt < RATE_LIMIT_RETRIES:
wait = RATE_LIMIT_PAUSE * attempt
@@ -276,8 +282,8 @@ async def fetch_tasks(state: PipelineState) -> dict:
return {"tasks": coding, "current_index": 0, "results": [], "errors": []}
async def process_one_task(state: PipelineState) -> dict:
"""Выполняет одно задание."""
async def process_one_task(state: PipelineState, config: RunnableConfig | None = None) -> dict:
"""Выполняет одно задание. config может содержать callbacks из UI."""
if state["current_index"] >= len(state["tasks"]):
return state
@@ -316,6 +322,9 @@ async def process_one_task(state: PipelineState) -> dict:
)
agent_to_use = homework_direct_agent
# Извлекаем callbacks из LangGraph config (переданы из UI)
callbacks = (config or {}).get("callbacks") or []
try:
print(f"[pipeline] Задание {task_id[:8]}{'пересдача' if is_rework else 'первая сдача'}: "
f"{task.get('title','')[:50]}")
@@ -324,6 +333,7 @@ async def process_one_task(state: PipelineState) -> dict:
agent_to_use,
{"messages": [HumanMessage(content=prompt)]},
{"configurable": {"thread_id": f"pipeline-task-{task_id}"}},
callbacks=callbacks,
)
last = (result.get("messages") or [{}])[-1]
output = getattr(last, "content", str(last))
@@ -343,6 +353,7 @@ async def process_one_task(state: PipelineState) -> dict:
agent_to_use,
{"messages": [HumanMessage(content=fix_msg)]},
{"configurable": {"thread_id": f"pipeline-task-{task_id}-retry-{retries}"}},
callbacks=callbacks,
)
verification = await _verify_repo(repo_name)