diff --git a/src/agent/base.py b/src/agent/base.py index d85775f..fe08072 100644 --- a/src/agent/base.py +++ b/src/agent/base.py @@ -5,7 +5,6 @@ from langchain_openai import ChatOpenAI from langgraph.checkpoint.memory import MemorySaver from src.agent.backends import IsolatedShellBackend -from src.agent.tools import send_file def create_agent(): @@ -26,7 +25,6 @@ def create_agent(): return create_deep_agent( model=model, - tools=[send_file], system_prompt="You are a helpful assistant.", checkpointer=MemorySaver(), backend=backend, diff --git a/src/agent/service.py b/src/agent/service.py index 90c4663..170bb53 100644 --- a/src/agent/service.py +++ b/src/agent/service.py @@ -3,12 +3,8 @@ from abc import abstractmethod from src.agent.base import create_agent from lambda_agent_api.server import ( - AgentEventUnion, - MsgEventTextChunk, - MsgEventToolCallChunk, - MsgEventToolResult, - MsgEventSendFile, - MsgEventEnd, + AgentEventUnion, MsgEventTextChunk, MsgEventToolCallChunk, + MsgEventToolResult, MsgEventEnd ) @@ -16,7 +12,6 @@ class ChatBusyError(Exception): """ Чат занят в другом блоке ``with`` """ - pass @@ -29,15 +24,15 @@ class AgentChat(AsyncContextManager[Self]): Перед вызовом любых методов (``astream`` и т. д.) необходимо войти в блок ``with``. Объект получается из AgentService.chat(). """ - chat_id: int @abstractmethod - def astream(self, text: str) -> AsyncIterator[AgentEventUnion]: ... + def astream(self, text: str) -> AsyncIterator[AgentEventUnion]: + ... class AgentService: - _instance = None # синглтон + _instance = None # синглтон def __new__(cls): if cls._instance is None: @@ -50,7 +45,7 @@ class AgentService: Своеобразная реализация Mutex'а. Служит прослойкой до методов AgentService, но подставляет в них 'захваченный' chat_id. """ - __locks: set[int] = set() # чаты, которые уже "взяты" + __locks: set[int] = set() # чаты, которые уже "взяты" def __init__(self, service: AgentService, chat_id: int) -> None: self.__chat_id = chat_id @@ -83,23 +78,21 @@ class AgentService: """ return self.__AgentChat(self, chat_id) - async def __astream( - self, chat_id: int, text: str - ) -> AsyncIterator[AgentEventUnion]: + async def __astream(self, chat_id: int, text: str) -> AsyncIterator[AgentEventUnion]: config = {"configurable": {"thread_id": chat_id}} # Используем astream_events для перехвата детальных событий (инструменты, чанки и т.д.) async for event in self._agent.astream_events( {"messages": [{"role": "user", "content": text}]}, config=config, - version="v2", # Обязательно v2 для современных версий LangChain + version="v2" # Обязательно v2 для современных версий LangChain ): kind = event["event"] # 1. Агент генерирует токены (текст или аргументы для инструмента) if kind == "on_chat_model_stream": chunk = event["data"]["chunk"] - + # Если генерируется обычный текст if chunk.content: yield MsgEventTextChunk(text=chunk.content) @@ -109,19 +102,15 @@ class AgentService: for tool_chunk in chunk.tool_call_chunks: yield MsgEventToolCallChunk( tool_name=tool_chunk.get("name"), - args_chunk=tool_chunk.get("args"), + args_chunk=tool_chunk.get("args") ) # 2. Инструмент завершил работу и вернул результат elif kind == "on_tool_end": yield MsgEventToolResult( - tool_name=event["name"], result=event["data"].get("output") + tool_name=event["name"], + result=event["data"].get("output") ) - # 3. Кастомные события (send_file и др.) - elif kind == "on_custom_event": - if event["name"] == "send_file": - yield MsgEventSendFile(path=event["data"]["path"]) - # 3. В конце генерации отправляем событие завершения yield MsgEventEnd(tokens_used=0) # потом заменить на метадату diff --git a/src/agent/tools.py b/src/agent/tools.py deleted file mode 100644 index 4afd6e0..0000000 --- a/src/agent/tools.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -from pathlib import Path - -from langchain_core.callbacks import adispatch_custom_event -from langchain_core.tools import tool - - -@tool -async def send_file(path: str) -> str: - """Отправить файл пользователю. - - Используй этот инструмент, когда пользователь просит: - - скачать файл - - отправить файл - - прислать документ - - получить файл - - открыть/показать файл в чате - - Этот инструмент НЕ используется когда: - - пользователь просто хочет прочитать содержимое файла (используй read_file) - - нужно создать или изменить файл (используй write_file/edit_file) - - Пользователь не имеет доступа к файлам напрямую, ты ОБЯЗАН ему их отправлять. - Если пользователь просил сформировать файл - скорее всего, нужно его отправить. - Используй этот tool без явного запроса от пользователя. - - Args: - path: Путь к файлу относительно /workspace (например: 'report.pdf', 'docs/readme.txt', 'output/data.json') - - Returns: - Подтверждение отправки или сообщение об ошибке - """ - workspace = os.environ.get("WORKSPACE_DIR", "/workspace") - - input_path = Path(path).as_posix().lstrip("/") - if input_path.startswith("workspace/"): - input_path = input_path[len("workspace/"):] - - full_path = Path(workspace) / input_path - - if not full_path.exists(): - return f"Ошибка: файл '{path}' не найден в /workspace" - - if not full_path.is_file(): - return f"Ошибка: '{path}' не является файлом" - - await adispatch_custom_event(name="send_file", data={"path": path}) - - return f"Файл '{path}' отправлен пользователю"