отправка END в API слое вместо service

This commit is contained in:
Егор Кандрушин 2026-04-27 13:54:55 +03:00
parent 3118e576da
commit 74e884179a
2 changed files with 9 additions and 18 deletions

View file

@ -8,7 +8,6 @@ from lambda_agent_api.server import (
MsgEventToolCallChunk, MsgEventToolCallChunk,
MsgEventToolResult, MsgEventToolResult,
MsgEventSendFile, MsgEventSendFile,
MsgEventEnd,
) )
@ -99,23 +98,23 @@ class AgentService:
attachments_description = await self.__describe_attachments(attachments) attachments_description = await self.__describe_attachments(attachments)
new_message += "\n" + attachments_description new_message += "\n" + attachments_description
# Используем astream_events для перехвата детальных событий (инструменты, чанки и т.д.) # astream_events, чтобы выходили промежуточные ивенты по мере их генерации
async for event in self._agent.astream_events( async for event in self._agent.astream_events(
{"messages": [{"role": "user", "content": new_message}]}, {"messages": [{"role": "user", "content": new_message}]},
config=config, config=config,
version="v2", # Обязательно v2 для современных версий LangChain version="v2",
): ):
kind = event["event"] kind = event["event"]
# 1. Агент генерирует токены (текст или аргументы для инструмента) # пришли чанки от LLM
if kind == "on_chat_model_stream": if kind == "on_chat_model_stream":
chunk = event["data"]["chunk"] chunk = event["data"]["chunk"]
# Если генерируется обычный текст # обычный текст
if chunk.content: if chunk.content:
yield MsgEventTextChunk(text=chunk.content) yield MsgEventTextChunk(text=chunk.content)
# Если агент решил использовать инструмент (Langchain выдает tool_call_chunks) # если вернулся tool_call
if hasattr(chunk, "tool_call_chunks") and chunk.tool_call_chunks: if hasattr(chunk, "tool_call_chunks") and chunk.tool_call_chunks:
for tool_chunk in chunk.tool_call_chunks: for tool_chunk in chunk.tool_call_chunks:
yield MsgEventToolCallChunk( yield MsgEventToolCallChunk(
@ -123,28 +122,20 @@ class AgentService:
args_chunk=tool_chunk.get("args"), args_chunk=tool_chunk.get("args"),
) )
# 2. Инструмент завершил работу и вернул результат # завершилось выполнение тула
elif kind == "on_tool_end": elif kind == "on_tool_end":
result = event["data"].get("output") result = event["data"].get("output")
"""# Перехватываем ссылку на авторизацию Composio v3
if result and "connect.composio.dev" in str(result):
yield MsgEventTextChunk(
text=f"\n⚠️ Для выполнения действия требуется авторизация. Перейдите по ссылке: {result}\n")
else:"""
yield MsgEventToolResult( yield MsgEventToolResult(
tool_name=event["name"], tool_name=event["name"],
result=str(result) # Страховка от ошибки сериализации JSON result=str(result) # переводим в строку, потому что иногда приходит кривой json
) )
# 3. Кастомные события (send_file и др.) # события, которые мы вызвали (например внутри тулов) через adispatch_custom_event
elif kind == "on_custom_event": elif kind == "on_custom_event":
if event["name"] == "send_file": if event["name"] == "send_file":
yield MsgEventSendFile(path=event["data"]["path"]) yield MsgEventSendFile(path=event["data"]["path"])
# 3. В конце генерации отправляем событие завершения
yield MsgEventEnd(tokens_used=0) # потом заменить на метадату
async def __describe_attachments(self, raw_paths: list[str]) -> str: async def __describe_attachments(self, raw_paths: list[str]) -> str:
lines = [] lines = []
for raw_path in raw_paths: for raw_path in raw_paths:

View file

@ -4,7 +4,6 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
from lambda_agent_api.server import ( from lambda_agent_api.server import (
MsgStatus, MsgStatus,
MsgEventTextChunk,
MsgEventEnd, MsgEventEnd,
MsgError, MsgError,
) )
@ -45,3 +44,4 @@ async def process_message(ws: WebSocket, chat: AgentChat, msg):
case MsgUserMessage(): case MsgUserMessage():
async for chunk in chat.astream(msg.text, msg.attachments): async for chunk in chat.astream(msg.text, msg.attachments):
await ws.send_text(chunk.model_dump_json()) await ws.send_text(chunk.model_dump_json())
await ws.send_text(MsgEventEnd(tokens_used=0).model_dump_json()) # TODO: подставить реальное потребление токенов