diff --git a/src/agent/service.py b/src/agent/service.py index 0967b4c..ef8760e 100644 --- a/src/agent/service.py +++ b/src/agent/service.py @@ -8,7 +8,6 @@ from lambda_agent_api.server import ( MsgEventToolCallChunk, MsgEventToolResult, MsgEventSendFile, - MsgEventEnd, ) @@ -99,23 +98,23 @@ class AgentService: attachments_description = await self.__describe_attachments(attachments) new_message += "\n" + attachments_description - # Используем astream_events для перехвата детальных событий (инструменты, чанки и т.д.) + # astream_events, чтобы выходили промежуточные ивенты по мере их генерации async for event in self._agent.astream_events( {"messages": [{"role": "user", "content": new_message}]}, config=config, - version="v2", # Обязательно v2 для современных версий LangChain + version="v2", ): kind = event["event"] - # 1. Агент генерирует токены (текст или аргументы для инструмента) + # пришли чанки от LLM if kind == "on_chat_model_stream": chunk = event["data"]["chunk"] - # Если генерируется обычный текст + # обычный текст if chunk.content: yield MsgEventTextChunk(text=chunk.content) - # Если агент решил использовать инструмент (Langchain выдает tool_call_chunks) + # если вернулся tool_call if hasattr(chunk, "tool_call_chunks") and chunk.tool_call_chunks: for tool_chunk in chunk.tool_call_chunks: yield MsgEventToolCallChunk( @@ -123,28 +122,20 @@ class AgentService: args_chunk=tool_chunk.get("args"), ) - # 2. Инструмент завершил работу и вернул результат + # завершилось выполнение тула elif kind == "on_tool_end": 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( tool_name=event["name"], - result=str(result) # Страховка от ошибки сериализации JSON + result=str(result) # переводим в строку, потому что иногда приходит кривой json ) - # 3. Кастомные события (send_file и др.) + # события, которые мы вызвали (например внутри тулов) через adispatch_custom_event elif kind == "on_custom_event": if event["name"] == "send_file": yield MsgEventSendFile(path=event["data"]["path"]) - # 3. В конце генерации отправляем событие завершения - yield MsgEventEnd(tokens_used=0) # потом заменить на метадату - async def __describe_attachments(self, raw_paths: list[str]) -> str: lines = [] for raw_path in raw_paths: diff --git a/src/api/external.py b/src/api/external.py index 3b74e27..3021d3f 100644 --- a/src/api/external.py +++ b/src/api/external.py @@ -4,7 +4,6 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends from lambda_agent_api.server import ( MsgStatus, - MsgEventTextChunk, MsgEventEnd, MsgError, ) @@ -45,3 +44,4 @@ async def process_message(ws: WebSocket, chat: AgentChat, msg): case MsgUserMessage(): async for chunk in chat.astream(msg.text, msg.attachments): await ws.send_text(chunk.model_dump_json()) + await ws.send_text(MsgEventEnd(tokens_used=0).model_dump_json()) # TODO: подставить реальное потребление токенов