Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/en/framework/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,33 @@ class BaseAgent:
# Tools and streaming
toolkit: list[Type[BaseTool]] # Set of available tools
streaming_generator: OpenAIStreamingGenerator # Streaming generator

# Execution control
_execute_task: asyncio.Task | None # Internal execution task (for cancellation)
```

### Agent Control Methods

```py
async def execute(self) -> str | None:
"""Start agent execution and return the result.

Creates an asyncio task for the agent execution, stores it
in _execute_task for later cancellation, and awaits completion.
"""

async def cancel(self) -> None:
"""Cancel the agent execution.

Cancels the running execute task if it exists and sets the agent
state to CANCELLED.
"""
```

!!! Note "Agent Cancellation"
The `cancel()` method allows you to stop a running agent at any time.
When cancelled, the agent's state is set to `CANCELLED` and it becomes part of `FINISH_STATES`.

### Methods to Override

When creating custom solutions, pay attention first and foremost to these methods:
Expand Down
6 changes: 5 additions & 1 deletion docs/en/sgr-api/SGR-Agent's-Workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,13 @@ sequenceDiagram
Agent->>API: Finish streaming
API-->>Client: Close SSE stream

Note over Client, Tools: Agent remains accessible<br/>via agent_id for further clarifications
Note over Client, Tools: Agent remains accessible<br/>via agent_id for further clarifications<br/>or cancellation via DELETE /agents/{agent_id}
```

!!! Note "Agent Cancellation"
At any point during execution, the agent can be cancelled using the `DELETE /agents/{agent_id}` endpoint.
This will stop the execution task, set the agent state to `CANCELLED`, and remove it from storage.

## 🤖 Schema-Guided Reasoning Capabilities:

1. **🤔 Clarification** - clarifying questions when unclear
Expand Down
55 changes: 55 additions & 0 deletions docs/en/sgr-api/SGR-Description-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ Get a list of all active agents.
- `RESEARCHING` - Agent is actively researching
- `WAITING_FOR_CLARIFICATION` - Agent needs clarification
- `COMPLETED` - Research completed
- `CANCELLED` - Agent execution was cancelled
- `FAILED` - Agent execution failed
- `ERROR` - Agent execution error

**Example:**

Expand Down Expand Up @@ -310,3 +313,55 @@ curl -X POST "http://localhost:8010/agents/sgr_agent_12345-67890-abcdef/provide_
</details>

______________________________________________________________________

<details>
<summary><strong>⏹️ Cancel/Delete Agent</strong> - Cancel agent execution and remove from storage</summary>

## 🔍 DELETE `/agents/{agent_id}`

Cancel a running agent's execution and remove it from storage. If the agent is currently running, it will be cancelled first before removal.

**Parameters:**

- `agent_id` (string, required): Unique agent identifier

**Response:**

```json
{
"agent_id": "sgr_agent_12345-67890-abcdef",
"deleted": true,
"final_state": "cancelled"
}
```

**Response Fields:**

- `agent_id` (string): The ID of the deleted agent
- `deleted` (boolean): Whether the agent was successfully deleted
- `final_state` (string): Final state of the agent after deletion (e.g., "cancelled", "completed", "failed")

**Behavior:**

- If the agent is currently running, it will be cancelled first
- The agent's execution task will be stopped
- The agent state will be set to `CANCELLED` if it was running
- The agent will be removed from storage after cancellation/deletion
- Works for agents in any state (running, completed, failed, etc.)

**Example:**

```bash
curl -X DELETE "http://localhost:8010/agents/sgr_agent_12345-67890-abcdef"
```

**Use Cases:**

- Stop a long-running research task that is no longer needed
- Clean up completed agents from storage
- Cancel an agent that is stuck or taking too long
- Free up resources by removing inactive agents

</details>

______________________________________________________________________
25 changes: 25 additions & 0 deletions docs/ru/framework/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,33 @@ class BaseAgent:
# Инструменты и стриминг
toolkit: list[Type[BaseTool]] # Набор доступных инструментов
streaming_generator: OpenAIStreamingGenerator # Генератор стриминга

# Управление выполнением
_execute_task: asyncio.Task | None # Внутренняя задача выполнения (для отмены)
```

### Методы управления агентом

```py
async def execute(self) -> str | None:
"""Запустить выполнение агента и вернуть результат.

Создает asyncio задачу для выполнения агента, сохраняет её
в _execute_task для последующей отмены и ожидает завершения.
"""

async def cancel(self) -> None:
"""Отменить выполнение агента.

Отменяет выполняющуюся задачу execute, если она существует,
и устанавливает состояние агента в CANCELLED.
"""
```

!!! Note "Отмена выполнения агента"
Метод `cancel()` позволяет остановить выполняющегося агента в любой момент.
При отмене состояние агента устанавливается в `CANCELLED` и он становится частью `FINISH_STATES`.

### Методы для переопределения

При создании собственных решений стоит обратить внимание в первую очередь на эти методы:
Expand Down
6 changes: 5 additions & 1 deletion docs/ru/sgr-api/SGR-Agent's-Workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,13 @@ sequenceDiagram
Agent->>API: Завершить поток
API-->>Client: Закрыть SSE поток

Note over Client, Tools: Агент остается доступным<br/>через agent_id для дальнейших уточнений
Note over Client, Tools: Агент остается доступным<br/>через agent_id для дальнейших уточнений<br/>или отмены через DELETE /agents/{agent_id}
```

!!! Note "Отмена выполнения агента"
В любой момент во время выполнения агент может быть отменен с помощью endpoint `DELETE /agents/{agent_id}`.
Это остановит задачу выполнения, установит состояние агента в `CANCELLED` и удалит его из хранилища.

## 🤖 Возможности Schema-Guided Reasoning:

1. **🤔 Clarification** - уточняющие вопросы при неясности
Expand Down
55 changes: 55 additions & 0 deletions docs/ru/sgr-api/SGR-Description-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ ______________________________________________________________________
- `RESEARCHING` - Агент активно исследует
- `WAITING_FOR_CLARIFICATION` - Агент нуждается в уточнении
- `COMPLETED` - Исследование завершено
- `CANCELLED` - Выполнение агента было отменено
- `FAILED` - Выполнение агента завершилось с ошибкой
- `ERROR` - Ошибка выполнения агента

**Пример:**

Expand Down Expand Up @@ -310,3 +313,55 @@ curl -X POST "http://localhost:8010/agents/sgr_agent_12345-67890-abcdef/provide_
</details>

______________________________________________________________________

<details>
<summary><strong>⏹️ Отмена/Удаление агента</strong> - Отменить выполнение агента и удалить из хранилища</summary>

## 🔍 DELETE `/agents/{agent_id}`

Отменить выполнение запущенного агента и удалить его из хранилища. Если агент в данный момент выполняется, он будет сначала отменен, а затем удален.

**Параметры:**

- `agent_id` (string, обязательный): Уникальный идентификатор агента

**Ответ:**

```json
{
"agent_id": "sgr_agent_12345-67890-abcdef",
"deleted": true,
"final_state": "cancelled"
}
```

**Поля ответа:**

- `agent_id` (string): ID удаленного агента
- `deleted` (boolean): Был ли агент успешно удален
- `final_state` (string): Финальное состояние агента после удаления (например, "cancelled", "completed", "failed")

**Поведение:**

- Если агент в данный момент выполняется, он будет сначала отменен
- Задача выполнения агента будет остановлена
- Состояние агента будет установлено в `CANCELLED`, если он выполнялся
- Агент будет удален из хранилища после отмены/удаления
- Работает для агентов в любом состоянии (выполняется, завершен, ошибка и т.д.)

**Пример:**

```bash
curl -X DELETE "http://localhost:8010/agents/sgr_agent_12345-67890-abcdef"
```

**Случаи использования:**

- Остановить долго выполняющуюся исследовательскую задачу, которая больше не нужна
- Очистить хранилище от завершенных агентов
- Отменить агента, который завис или выполняется слишком долго
- Освободить ресурсы, удалив неактивных агентов

</details>

______________________________________________________________________
42 changes: 39 additions & 3 deletions sgr_agent_core/base_agent.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import json
import logging
import os
Expand Down Expand Up @@ -56,6 +57,8 @@ def __init__(
self.logger = logging.getLogger(f"sgr_agent_core.agents.{self.id}")
self.log = []

self._execute_task: asyncio.Task | None = None

async def provide_clarification(self, messages: list[ChatCompletionMessageParam]):
"""Receive clarification from an external source (e.g. user input) in
OpenAI messages format."""
Expand Down Expand Up @@ -206,9 +209,37 @@ async def _execution_step(self):
self._context.clarification_received.clear()
await self._context.clarification_received.wait()

async def execute(
self,
):
async def cancel(self) -> None:
"""Cancel the agent execution.

Cancels the running execute task if it exists and sets the agent
state to CANCELLED.
"""
if self._execute_task and not self._execute_task.done():
self._execute_task.cancel()
try:
await self._execute_task
except asyncio.CancelledError:
pass

async def execute(self) -> str | None:
"""Start agent execution and return the result.

Creates an asyncio task for the agent execution, stores it
in _execute_task for later cancellation, and awaits completion.

Returns:
The execution result (final answer) or None.
"""
self._execute_task = asyncio.create_task(self._execute())
return await self._execute_task

async def _execute(self):
"""Internal execution loop for the agent.

This method contains the main agent execution logic. It is
called by execute() which wraps it in an asyncio task.
"""
self.logger.info(f"🚀 User provided {len(self.task_messages)} messages.")
try:
while self._context.state not in AgentStatesEnum.FINISH_STATES.value:
Expand All @@ -217,6 +248,11 @@ async def execute(
await self._execution_step()
return self._context.execution_result

except asyncio.CancelledError:
self.logger.info("⏹️ Agent execution cancelled")
self._context.state = AgentStatesEnum.CANCELLED
raise

except Exception as e:
self.logger.error(f"❌ Agent execution error: {str(e)}")
self._context.state = AgentStatesEnum.FAILED
Expand Down
3 changes: 2 additions & 1 deletion sgr_agent_core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ class AgentStatesEnum(str, Enum):
COMPLETED = "completed"
ERROR = "error"
FAILED = "failed"
CANCELLED = "cancelled"

FINISH_STATES = {COMPLETED, FAILED, ERROR}
FINISH_STATES = {COMPLETED, FAILED, ERROR, CANCELLED}


class AgentContext(BaseModel):
Expand Down
41 changes: 40 additions & 1 deletion sgr_agent_core/server/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from sgr_agent_core import AgentFactory, AgentStatesEnum, BaseAgent
from sgr_agent_core.server.models import (
AgentDeleteResponse,
AgentListItem,
AgentListResponse,
AgentStateResponse,
Expand Down Expand Up @@ -42,6 +43,44 @@ async def get_agent_state(agent_id: str):
)


@router.delete("/agents/{agent_id}", response_model=AgentDeleteResponse)
async def delete_agent(agent_id: str):
"""Delete (cancel) an agent and remove it from storage.

If the agent is currently running, it will be cancelled first.
The agent is then removed from storage.

Args:
agent_id: The ID of the agent to delete

Returns:
AgentDeleteResponse with deletion status and final state

Raises:
HTTPException: 404 if agent not found
"""
if agent_id not in agents_storage:
raise HTTPException(status_code=404, detail="Agent not found")

agent = agents_storage[agent_id]

# Cancel the agent if it's running
await agent.cancel()

# Get final state before removing from storage
final_state = agent._context.state.value

# Remove from storage
del agents_storage[agent_id]
logger.info(f"Agent {agent_id} deleted with final state: {final_state}")

return AgentDeleteResponse(
agent_id=agent_id,
deleted=True,
final_state=final_state,
)


@router.get("/agents", response_model=AgentListResponse)
async def get_agents_list():
agents_list = [
Expand Down Expand Up @@ -134,7 +173,7 @@ async def create_chat_completion(request: ChatCompletionRequest):
logger.info(f"Created agent '{request.model}' with {len(request.messages)} messages")

agents_storage[agent.id] = agent
_ = asyncio.create_task(agent.execute())
asyncio.create_task(agent.execute()) # Starts execution, task stored in agent._execute_task
return StreamingResponse(
agent.streaming_generator.stream(),
media_type="text/event-stream",
Expand Down
8 changes: 8 additions & 0 deletions sgr_agent_core/server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,11 @@ class ClarificationRequest(BaseModel):
format."""

messages: list[ChatCompletionMessageParam] = Field(description="Clarification messages in OpenAI format")


class AgentDeleteResponse(BaseModel):
"""Response for deleting (cancelling) an agent."""

agent_id: str = Field(description="Agent ID that was deleted")
deleted: bool = Field(description="Whether the agent was successfully deleted")
final_state: str = Field(description="Final state of the agent after deletion")
Loading