diff --git a/README.md b/README.md index 23841e6..496c876 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,9 @@ +from lambda_agent_api import AgentApi + # Lambda Agent API WebSocket API SDK для взаимодействия с AI-агентом. -## Release Notes -# v1.1 -- **CRITICAL**: `AgentAPI` вместо `url` принимает `base_url` и сам дописывает нужный эндпоинт. -Раньше: `AgentAPI(url="ws://localhost:8000/agent_ws/")`. Сейчас: `AgentAPI(base_url="ws://localhost:8000/")` -- Добавлен параметр `chat_id` в конструктор `AgentAPI`. Нужен для разделения истории сообщений по чатам/веткам. -- `AgentAPI.connect()` вызывает `AgentBusyException`, если выбранный чат уже занят другим API клиентом. - ## Установка В `master` всегда будет актуальная рабочая версия. ```bash @@ -19,10 +14,42 @@ pip install git+https://git.lambda.coredump.ru/platform/agent_api.git ## Быстрый старт (с использованием AgentApi) -**Рабочий REPL пример: [tests/manual.py](tests/manual.py).** +```python +import asyncio + +from lambda_agent_api.agent_api import AgentApi +from lambda_agent_api.server import MsgEventTextChunk -## Предполагаемое управление подключениями +async def main(): + api = AgentApi("agent-1", "ws://localhost:8000/ws") + + await api.connect() + try: + response = await api.send_message("Привет, агент!") + + async for chunk in response: + if isinstance(chunk, MsgEventTextChunk): + print(chunk.text, end="", flush=True) + elif isinstance(chunk, MsgEventToolCallChunk): + print(f"Tool call started: {chunk.tool_name}") + elif isinstance(chunk, MsgEventToolResult): + print(f"Tool result: {chunk.result}") + elif isinstance(chunk, MsgEventCustomUpdate): + print(f"Progress update: {chunk.payload}") + elif isinstance(chunk, MsgEventEnd): + print(f"Generation ended, tokens used: {chunk.tokens_used}") + + finally: + await api.close() + + +asyncio.run(main()) +``` + +> `AgentApi.send_message()` возвращает стриминг-итерируемый объект, который может выдавать не только текстовые чанки, но и события инструментов (`MsgEventToolCallChunk`, `MsgEventToolResult`, `MsgEventCustomUpdate`) и финальный `MsgEventEnd`. + +## Предполагаемое использование ```python from lambda_agent_api.agent_api import AgentApi diff --git a/lambda_agent_api/agent_api.py b/lambda_agent_api/agent_api.py index e22a0b8..d130bcf 100644 --- a/lambda_agent_api/agent_api.py +++ b/lambda_agent_api/agent_api.py @@ -2,9 +2,6 @@ import logging from typing import Callable, Optional, AsyncIterator import aiohttp import asyncio -from urllib.parse import urljoin - -from aiohttp import WSServerHandshakeError from lambda_agent_api.server import * from lambda_agent_api.client import * @@ -28,14 +25,12 @@ class AgentApi: def __init__( self, agent_id: str, - base_url: str, + url: str, callback: Optional[Callable[[ServerMessage], None]] = None, - on_disconnect: Optional[Callable[['AgentApi'], None]] = None, - chat_id: int = 0 # значение по умолчанию для обратной совместимости + on_disconnect: Optional[Callable[['AgentApi'], None]] = None ): self.id = agent_id # ID агента для словаря - self.chat_id = chat_id - self.url = urljoin(base_url, f"/v1/agent_ws/{chat_id}/") + self.url = url self.callback = callback self.on_disconnect = on_disconnect @@ -48,11 +43,7 @@ class AgentApi: self._listen_task: asyncio.Task | None = None async def connect(self): - """Явное подключение к агенту. - - :raise AgentBusyException: Чат занят другим клиентом. - :raise AgentException: Непредвиденная ошибка протокола, см. code и details - """ + """Явное подключение к агенту.""" self._session = aiohttp.ClientSession() try: self._ws = await self._session.ws_connect(self.url, heartbeat=30) @@ -94,25 +85,6 @@ class AgentApi: await self._session.close() raise - except WSServerHandshakeError as e: # если при открытии подключения сервер вернул какую-то ошибку - if self._session and not self._session.closed: - await self._session.close() - - # во-первых, aiohttp зачем-то приводит WS коды ошибок к HTTP. - # во-вторых, делает он это некорректно. Любой неизвестный код WS ошибки становится 403 HTTP - # в-третьих, он не передает оригинальное сообщение об ошибке. - # Т. е. какое бы сообщение сервер не отправлял, тут всегда будет "Invalid response status" - # см. site-packages\aiohttp\client.py, line 1104, in _ws_connect - # итого понять реальную причину ошибки почти невозможно. Нужно менять библиотеку - # сейчас сервер специально кидает только ошибку с WS кодом 1008 Policy Violation, когда внутри ловит ChatBusyError - # поэтому скорее всего, если мы получили WSServerHandshakeError с 403, то это внутренний ChatBusyError - if e.status != 403: - # обрабатываем как обычную ошибку WS по примеру except блока ниже - raise AgentException(code="CONNECTION_ERROR", - details=f"Failed to connect agent {self.id}: {e}") from e - - raise AgentBusyException(f"Chat {self.chat_id} is already in use by other client") - except Exception as e: # Обработка всех остальных ошибок (например, aiohttp.ClientConnectionError) if self._ws and not self._ws.closed: diff --git a/tests/manual.py b/tests/manual.py index 10f3005..a39802b 100644 --- a/tests/manual.py +++ b/tests/manual.py @@ -1,21 +1,15 @@ import asyncio import traceback -from lambda_agent_api.agent_api import AgentApi, AgentBusyException +from lambda_agent_api.agent_api import AgentApi from lambda_agent_api.server import MsgEventTextChunk, MsgEventToolCallChunk, MsgEventToolResult async def main(): - chat_id = input("Chat id: ") or 0 - api = AgentApi("agent-1", "ws://localhost:8000/", chat_id=chat_id) - - try: - await api.connect() - except AgentBusyException: - print(f"Чат {chat_id} занят другим клиентом") - return + api = AgentApi("agent-1", "ws://localhost:8000/agent_ws/") + await api.connect() while True: try: prompt = await asyncio.get_event_loop().run_in_executor(None, input, ">>> ")