инструмент для отправки файла пользователю
This commit is contained in:
parent
1c2f9495db
commit
2d4a5d73c7
3 changed files with 65 additions and 12 deletions
|
|
@ -5,6 +5,7 @@ from langchain_openai import ChatOpenAI
|
||||||
from langgraph.checkpoint.memory import MemorySaver
|
from langgraph.checkpoint.memory import MemorySaver
|
||||||
|
|
||||||
from src.agent.backends import IsolatedShellBackend
|
from src.agent.backends import IsolatedShellBackend
|
||||||
|
from src.agent.tools import send_file
|
||||||
|
|
||||||
|
|
||||||
def create_agent():
|
def create_agent():
|
||||||
|
|
@ -25,6 +26,7 @@ def create_agent():
|
||||||
|
|
||||||
return create_deep_agent(
|
return create_deep_agent(
|
||||||
model=model,
|
model=model,
|
||||||
|
tools=[send_file],
|
||||||
system_prompt="You are a helpful assistant.",
|
system_prompt="You are a helpful assistant.",
|
||||||
checkpointer=MemorySaver(),
|
checkpointer=MemorySaver(),
|
||||||
backend=backend,
|
backend=backend,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,12 @@ from abc import abstractmethod
|
||||||
|
|
||||||
from src.agent.base import create_agent
|
from src.agent.base import create_agent
|
||||||
from lambda_agent_api.server import (
|
from lambda_agent_api.server import (
|
||||||
AgentEventUnion, MsgEventTextChunk, MsgEventToolCallChunk,
|
AgentEventUnion,
|
||||||
MsgEventToolResult, MsgEventEnd
|
MsgEventTextChunk,
|
||||||
|
MsgEventToolCallChunk,
|
||||||
|
MsgEventToolResult,
|
||||||
|
MsgEventSendFile,
|
||||||
|
MsgEventEnd,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -12,6 +16,7 @@ class ChatBusyError(Exception):
|
||||||
"""
|
"""
|
||||||
Чат занят в другом блоке ``with``
|
Чат занят в другом блоке ``with``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,15 +29,15 @@ class AgentChat(AsyncContextManager[Self]):
|
||||||
Перед вызовом любых методов (``astream`` и т. д.) необходимо войти в блок ``with``.
|
Перед вызовом любых методов (``astream`` и т. д.) необходимо войти в блок ``with``.
|
||||||
Объект получается из AgentService.chat().
|
Объект получается из AgentService.chat().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
chat_id: int
|
chat_id: int
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def astream(self, text: str) -> AsyncIterator[AgentEventUnion]:
|
def astream(self, text: str) -> AsyncIterator[AgentEventUnion]: ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class AgentService:
|
class AgentService:
|
||||||
_instance = None # синглтон
|
_instance = None # синглтон
|
||||||
|
|
||||||
def __new__(cls):
|
def __new__(cls):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
|
|
@ -45,7 +50,7 @@ class AgentService:
|
||||||
Своеобразная реализация Mutex'а. Служит прослойкой до методов AgentService, но подставляет в них 'захваченный' chat_id.
|
Своеобразная реализация Mutex'а. Служит прослойкой до методов AgentService, но подставляет в них 'захваченный' chat_id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__locks: set[int] = set() # чаты, которые уже "взяты"
|
__locks: set[int] = set() # чаты, которые уже "взяты"
|
||||||
|
|
||||||
def __init__(self, service: AgentService, chat_id: int) -> None:
|
def __init__(self, service: AgentService, chat_id: int) -> None:
|
||||||
self.__chat_id = chat_id
|
self.__chat_id = chat_id
|
||||||
|
|
@ -78,21 +83,23 @@ class AgentService:
|
||||||
"""
|
"""
|
||||||
return self.__AgentChat(self, chat_id)
|
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}}
|
config = {"configurable": {"thread_id": chat_id}}
|
||||||
|
|
||||||
# Используем astream_events для перехвата детальных событий (инструменты, чанки и т.д.)
|
# Используем astream_events для перехвата детальных событий (инструменты, чанки и т.д.)
|
||||||
async for event in self._agent.astream_events(
|
async for event in self._agent.astream_events(
|
||||||
{"messages": [{"role": "user", "content": text}]},
|
{"messages": [{"role": "user", "content": text}]},
|
||||||
config=config,
|
config=config,
|
||||||
version="v2" # Обязательно v2 для современных версий LangChain
|
version="v2", # Обязательно v2 для современных версий LangChain
|
||||||
):
|
):
|
||||||
kind = event["event"]
|
kind = event["event"]
|
||||||
|
|
||||||
# 1. Агент генерирует токены (текст или аргументы для инструмента)
|
# 1. Агент генерирует токены (текст или аргументы для инструмента)
|
||||||
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)
|
||||||
|
|
@ -102,15 +109,19 @@ class AgentService:
|
||||||
for tool_chunk in chunk.tool_call_chunks:
|
for tool_chunk in chunk.tool_call_chunks:
|
||||||
yield MsgEventToolCallChunk(
|
yield MsgEventToolCallChunk(
|
||||||
tool_name=tool_chunk.get("name"),
|
tool_name=tool_chunk.get("name"),
|
||||||
args_chunk=tool_chunk.get("args")
|
args_chunk=tool_chunk.get("args"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Инструмент завершил работу и вернул результат
|
# 2. Инструмент завершил работу и вернул результат
|
||||||
elif kind == "on_tool_end":
|
elif kind == "on_tool_end":
|
||||||
yield MsgEventToolResult(
|
yield MsgEventToolResult(
|
||||||
tool_name=event["name"],
|
tool_name=event["name"], result=event["data"].get("output")
|
||||||
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. В конце генерации отправляем событие завершения
|
# 3. В конце генерации отправляем событие завершения
|
||||||
yield MsgEventEnd(tokens_used=0) # потом заменить на метадату
|
yield MsgEventEnd(tokens_used=0) # потом заменить на метадату
|
||||||
|
|
|
||||||
40
src/agent/tools.py
Normal file
40
src/agent/tools.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
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)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: Путь к файлу относительно /workspace (например: 'report.pdf', 'docs/readme.txt', 'output/data.json')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Подтверждение отправки или сообщение об ошибке
|
||||||
|
"""
|
||||||
|
workspace = os.environ.get("WORKSPACE_DIR", "/workspace")
|
||||||
|
full_path = Path(workspace) / 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}' отправлен пользователю"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue