Skip to content
Open
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
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 45
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-9ab4b375245291b8e37dd1cbc054fa65f17b7e7db28729126ea9f1289dc99214.yml
configured_endpoints: 63
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-5400cbfee7eab6b5ace17d760b4997fd68f8d169470ab5040cf268a185250a0b.yml
openapi_spec_hash: d31d828c46635cbc20165177c7187a70
config_hash: fb079ef7936611b032568661b8165f19
config_hash: 81470e0e689fe06fa3e013ec01a7f84f
77 changes: 70 additions & 7 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,60 @@ from agentex.types import (

Methods:

- <code title="get /agents/{agent_id}">client.agents.<a href="./src/agentex/resources/agents.py">retrieve</a>(agent_id) -> <a href="./src/agentex/types/agent.py">Agent</a></code>
- <code title="get /agents">client.agents.<a href="./src/agentex/resources/agents.py">list</a>(\*\*<a href="src/agentex/types/agent_list_params.py">params</a>) -> <a href="./src/agentex/types/agent_list_response.py">AgentListResponse</a></code>
- <code title="delete /agents/{agent_id}">client.agents.<a href="./src/agentex/resources/agents.py">delete</a>(agent_id) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="delete /agents/name/{agent_name}">client.agents.<a href="./src/agentex/resources/agents.py">delete_by_name</a>(agent_name) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="get /agents/name/{agent_name}">client.agents.<a href="./src/agentex/resources/agents.py">retrieve_by_name</a>(agent_name) -> <a href="./src/agentex/types/agent.py">Agent</a></code>
- <code title="post /agents/{agent_id}/rpc">client.agents.<a href="./src/agentex/resources/agents.py">rpc</a>(agent_id, \*\*<a href="src/agentex/types/agent_rpc_params.py">params</a>) -> <a href="./src/agentex/types/agent_rpc_response.py">AgentRpcResponse</a></code>
- <code title="post /agents/name/{agent_name}/rpc">client.agents.<a href="./src/agentex/resources/agents.py">rpc_by_name</a>(agent_name, \*\*<a href="src/agentex/types/agent_rpc_by_name_params.py">params</a>) -> <a href="./src/agentex/types/agent_rpc_response.py">AgentRpcResponse</a></code>
- <code title="get /agents/{agent_id}">client.agents.<a href="./src/agentex/resources/agents/agents.py">retrieve</a>(agent_id) -> <a href="./src/agentex/types/agent.py">Agent</a></code>
- <code title="get /agents">client.agents.<a href="./src/agentex/resources/agents/agents.py">list</a>(\*\*<a href="src/agentex/types/agent_list_params.py">params</a>) -> <a href="./src/agentex/types/agent_list_response.py">AgentListResponse</a></code>
- <code title="delete /agents/{agent_id}">client.agents.<a href="./src/agentex/resources/agents/agents.py">delete</a>(agent_id) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="delete /agents/name/{agent_name}">client.agents.<a href="./src/agentex/resources/agents/agents.py">delete_by_name</a>(agent_name) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="get /agents/name/{agent_name}">client.agents.<a href="./src/agentex/resources/agents/agents.py">retrieve_by_name</a>(agent_name) -> <a href="./src/agentex/types/agent.py">Agent</a></code>
- <code title="post /agents/{agent_id}/rpc">client.agents.<a href="./src/agentex/resources/agents/agents.py">rpc</a>(agent_id, \*\*<a href="src/agentex/types/agent_rpc_params.py">params</a>) -> <a href="./src/agentex/types/agent_rpc_response.py">AgentRpcResponse</a></code>
- <code title="post /agents/name/{agent_name}/rpc">client.agents.<a href="./src/agentex/resources/agents/agents.py">rpc_by_name</a>(agent_name, \*\*<a href="src/agentex/types/agent_rpc_by_name_params.py">params</a>) -> <a href="./src/agentex/types/agent_rpc_response.py">AgentRpcResponse</a></code>

## Deployments

Types:

```python
from agentex.types.agents import (
DeploymentCreateResponse,
DeploymentRetrieveResponse,
DeploymentListResponse,
DeploymentPromoteResponse,
)
```

Methods:

- <code title="post /agents/{agent_id}/deployments">client.agents.deployments.<a href="./src/agentex/resources/agents/deployments.py">create</a>(agent_id, \*\*<a href="src/agentex/types/agents/deployment_create_params.py">params</a>) -> <a href="./src/agentex/types/agents/deployment_create_response.py">DeploymentCreateResponse</a></code>
- <code title="get /agents/{agent_id}/deployments/{deployment_id}">client.agents.deployments.<a href="./src/agentex/resources/agents/deployments.py">retrieve</a>(deployment_id, \*, agent_id) -> <a href="./src/agentex/types/agents/deployment_retrieve_response.py">DeploymentRetrieveResponse</a></code>
- <code title="get /agents/{agent_id}/deployments">client.agents.deployments.<a href="./src/agentex/resources/agents/deployments.py">list</a>(agent_id, \*\*<a href="src/agentex/types/agents/deployment_list_params.py">params</a>) -> <a href="./src/agentex/types/agents/deployment_list_response.py">DeploymentListResponse</a></code>
- <code title="delete /agents/{agent_id}/deployments/{deployment_id}">client.agents.deployments.<a href="./src/agentex/resources/agents/deployments.py">delete</a>(deployment_id, \*, agent_id) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="post /agents/{agent_id}/deployments/{deployment_id}/rpc">client.agents.deployments.<a href="./src/agentex/resources/agents/deployments.py">preview_rpc</a>(deployment_id, \*, agent_id, \*\*<a href="src/agentex/types/agents/deployment_preview_rpc_params.py">params</a>) -> <a href="./src/agentex/types/agent_rpc_response.py">AgentRpcResponse</a></code>
- <code title="post /agents/{agent_id}/deployments/{deployment_id}/promote">client.agents.deployments.<a href="./src/agentex/resources/agents/deployments.py">promote</a>(deployment_id, \*, agent_id) -> <a href="./src/agentex/types/agents/deployment_promote_response.py">DeploymentPromoteResponse</a></code>

## Schedules

Types:

```python
from agentex.types.agents import (
ScheduleCreateResponse,
ScheduleRetrieveResponse,
ScheduleListResponse,
SchedulePauseResponse,
ScheduleTriggerResponse,
ScheduleUnpauseResponse,
)
```

Methods:

- <code title="post /agents/{agent_id}/schedules">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">create</a>(agent_id, \*\*<a href="src/agentex/types/agents/schedule_create_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_create_response.py">ScheduleCreateResponse</a></code>
- <code title="get /agents/{agent_id}/schedules/{schedule_name}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">retrieve</a>(schedule_name, \*, agent_id) -> <a href="./src/agentex/types/agents/schedule_retrieve_response.py">ScheduleRetrieveResponse</a></code>
- <code title="get /agents/{agent_id}/schedules">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">list</a>(agent_id, \*\*<a href="src/agentex/types/agents/schedule_list_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_list_response.py">ScheduleListResponse</a></code>
- <code title="delete /agents/{agent_id}/schedules/{schedule_name}">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">delete</a>(schedule_name, \*, agent_id) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_name}/pause">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">pause</a>(schedule_name, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_pause_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_pause_response.py">SchedulePauseResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_name}/trigger">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">trigger</a>(schedule_name, \*, agent_id) -> <a href="./src/agentex/types/agents/schedule_trigger_response.py">ScheduleTriggerResponse</a></code>
- <code title="post /agents/{agent_id}/schedules/{schedule_name}/unpause">client.agents.schedules.<a href="./src/agentex/resources/agents/schedules.py">unpause</a>(schedule_name, \*, agent_id, \*\*<a href="src/agentex/types/agents/schedule_unpause_params.py">params</a>) -> <a href="./src/agentex/types/agents/schedule_unpause_response.py">ScheduleUnpauseResponse</a></code>

# Tasks

Expand Down Expand Up @@ -181,3 +228,19 @@ Methods:

- <code title="get /deployment-history/{deployment_id}">client.deployment_history.<a href="./src/agentex/resources/deployment_history.py">retrieve</a>(deployment_id) -> <a href="./src/agentex/types/deployment_history.py">DeploymentHistory</a></code>
- <code title="get /deployment-history">client.deployment_history.<a href="./src/agentex/resources/deployment_history.py">list</a>(\*\*<a href="src/agentex/types/deployment_history_list_params.py">params</a>) -> <a href="./src/agentex/types/deployment_history_list_response.py">DeploymentHistoryListResponse</a></code>

# Checkpoints

Types:

```python
from agentex.types import CheckpointListResponse, CheckpointGetTupleResponse, CheckpointPutResponse
```

Methods:

- <code title="post /checkpoints/list">client.checkpoints.<a href="./src/agentex/resources/checkpoints.py">list</a>(\*\*<a href="src/agentex/types/checkpoint_list_params.py">params</a>) -> <a href="./src/agentex/types/checkpoint_list_response.py">CheckpointListResponse</a></code>
- <code title="post /checkpoints/delete-thread">client.checkpoints.<a href="./src/agentex/resources/checkpoints.py">delete_thread</a>(\*\*<a href="src/agentex/types/checkpoint_delete_thread_params.py">params</a>) -> None</code>
- <code title="post /checkpoints/get-tuple">client.checkpoints.<a href="./src/agentex/resources/checkpoints.py">get_tuple</a>(\*\*<a href="src/agentex/types/checkpoint_get_tuple_params.py">params</a>) -> <a href="./src/agentex/types/checkpoint_get_tuple_response.py">Optional[CheckpointGetTupleResponse]</a></code>
- <code title="post /checkpoints/put">client.checkpoints.<a href="./src/agentex/resources/checkpoints.py">put</a>(\*\*<a href="src/agentex/types/checkpoint_put_params.py">params</a>) -> <a href="./src/agentex/types/checkpoint_put_response.py">CheckpointPutResponse</a></code>
- <code title="post /checkpoints/put-writes">client.checkpoints.<a href="./src/agentex/resources/checkpoints.py">put_writes</a>(\*\*<a href="src/agentex/types/checkpoint_put_writes_params.py">params</a>) -> None</code>
43 changes: 43 additions & 0 deletions examples/tutorials/00_sync/040_pydantic_ai/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Environments
.env**
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Git
.git
.gitignore

# Misc
.DS_Store
50 changes: 50 additions & 0 deletions examples/tutorials/00_sync/040_pydantic_ai/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# syntax=docker/dockerfile:1.3
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/

# Install system dependencies
RUN apt-get update && apt-get install -y \
htop \
vim \
curl \
tar \
python3-dev \
postgresql-client \
build-essential \
libpq-dev \
gcc \
cmake \
netcat-openbsd \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN uv pip install --system --upgrade pip setuptools wheel

ENV UV_HTTP_TIMEOUT=1000

# Copy pyproject.toml and README.md to install dependencies
COPY 00_sync/040_pydantic_ai/pyproject.toml /app/040_pydantic_ai/pyproject.toml
COPY 00_sync/040_pydantic_ai/README.md /app/040_pydantic_ai/README.md

WORKDIR /app/040_pydantic_ai

# Copy the project code
COPY 00_sync/040_pydantic_ai/project /app/040_pydantic_ai/project

# Copy the test files
COPY 00_sync/040_pydantic_ai/tests /app/040_pydantic_ai/tests

# Copy shared test utilities
COPY test_utils /app/test_utils

# Install the required Python packages with dev dependencies
RUN uv pip install --system .[dev]

# Set environment variables
ENV PYTHONPATH=/app

# Set test environment variables
ENV AGENT_NAME=s040-pydantic-ai

# Run the agent using uvicorn
CMD ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]
46 changes: 46 additions & 0 deletions examples/tutorials/00_sync/040_pydantic_ai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Tutorial 040: Sync Pydantic AI Agent

This tutorial demonstrates how to build a **synchronous** Pydantic AI agent on AgentEx with:
- Tool calling (Pydantic AI handles the tool loop internally)
- Streaming token output (including token-by-token tool-call argument streaming)

## Key Concepts

### Sync ACP
The sync ACP model uses HTTP request/response for communication. The `@acp.on_message_send` handler receives a message and yields streaming events back to the client.

### Pydantic AI Integration
- **Agent**: A single `pydantic_ai.Agent` that owns the model and tools. No graph required — Pydantic AI runs its own tool-call loop until the model is done.
- **`@agent.tool_plain`**: Registers a Python function as a tool. Pydantic AI infers the schema from type hints and docstring.
- **`agent.run_stream_events(...)`**: Yields `AgentStreamEvent`s (PartStartEvent / PartDeltaEvent / PartEndEvent / FunctionToolResultEvent) as the model produces them.

### Streaming
The agent streams tokens and tool-call arguments as they're generated using `convert_pydantic_ai_to_agentex_events()`, which adapts Pydantic AI's stream into AgentEx `TaskMessageUpdate` events. Notably, **tool-call arguments stream as `ToolRequestDelta` tokens** rather than arriving as a single complete payload — a richer experience than what OpenAI Agents SDK currently exposes.

## Files

| File | Description |
|------|-------------|
| `project/acp.py` | ACP server and message handler |
| `project/agent.py` | Pydantic AI agent + tool registration |
| `project/tools.py` | Tool definitions (weather example) |
| `tests/test_agent.py` | Integration tests |
| `manifest.yaml` | Agent configuration |

## Running Locally

```bash
# From this directory
agentex agents run
```

## Running Tests

```bash
pytest tests/test_agent.py -v
```

## Notes

- Multi-turn conversation memory is not wired in this tutorial. Pydantic AI does not ship a checkpointer like LangGraph; to add memory, load prior messages via `adk.messages.list(task_id=...)` and pass them to `agent.run_stream_events(..., message_history=...)`.
- Reasoning/thinking tokens are not exercised here because `gpt-4o-mini` does not emit `ThinkingPart`s. Swap to a reasoning-capable model (e.g. `openai:o1-mini` via Pydantic AI's appropriate provider) if you want to test that branch end-to-end.
58 changes: 58 additions & 0 deletions examples/tutorials/00_sync/040_pydantic_ai/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
build:
context:
root: ../../
include_paths:
- 00_sync/040_pydantic_ai
- test_utils
dockerfile: 00_sync/040_pydantic_ai/Dockerfile
dockerignore: 00_sync/040_pydantic_ai/.dockerignore

local_development:
agent:
port: 8000
host_address: host.docker.internal
paths:
acp: project/acp.py

agent:
acp_type: sync
name: s040-pydantic-ai
description: A sync Pydantic AI agent with tool calling and streaming

temporal:
enabled: false

credentials:
- env_var_name: OPENAI_API_KEY
secret_name: openai-api-key
secret_key: api-key
- env_var_name: REDIS_URL
secret_name: redis-url-secret
secret_key: url
- env_var_name: SGP_API_KEY
secret_name: sgp-api-key
secret_key: api-key
- env_var_name: SGP_ACCOUNT_ID
secret_name: sgp-account-id
secret_key: account-id
- env_var_name: SGP_CLIENT_BASE_URL
secret_name: sgp-client-base-url
secret_key: url

deployment:
image:
repository: ""
tag: "latest"

global:
agent:
name: "s040-pydantic-ai"
description: "A sync Pydantic AI agent with tool calling and streaming"
replicaCount: 1
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1000m"
memory: "2Gi"
Empty file.
78 changes: 78 additions & 0 deletions examples/tutorials/00_sync/040_pydantic_ai/project/acp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""ACP (Agent Communication Protocol) handler for Agentex.

This is the API layer — it owns the agent lifecycle and streams tokens
and tool calls from the Pydantic AI agent to the Agentex frontend.
"""

from __future__ import annotations

import os
from typing import AsyncGenerator

from dotenv import load_dotenv

load_dotenv()

import agentex.lib.adk as adk
from project.agent import create_agent
from agentex.lib.adk import (
create_pydantic_ai_tracing_handler,
convert_pydantic_ai_to_agentex_events,
)
from agentex.lib.types.acp import SendMessageParams
from agentex.lib.types.tracing import SGPTracingProcessorConfig
from agentex.lib.utils.logging import make_logger
from agentex.lib.sdk.fastacp.fastacp import FastACP
from agentex.types.task_message_update import TaskMessageUpdate
from agentex.types.task_message_content import TaskMessageContent
from agentex.lib.core.tracing.tracing_processor_manager import add_tracing_processor_config

logger = make_logger(__name__)

add_tracing_processor_config(
SGPTracingProcessorConfig(
sgp_api_key=os.environ.get("SGP_API_KEY", ""),
sgp_account_id=os.environ.get("SGP_ACCOUNT_ID", ""),
sgp_base_url=os.environ.get("SGP_CLIENT_BASE_URL", ""),
)
)

acp = FastACP.create(acp_type="sync")

_agent = None


def get_agent():
"""Get or create the Pydantic AI agent instance."""
global _agent
if _agent is None:
_agent = create_agent()
return _agent


@acp.on_message_send
async def handle_message_send(
params: SendMessageParams,
) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]:
"""Handle incoming messages from Agentex, streaming tokens and tool calls."""
agent = get_agent()
task_id = params.task.id

user_message = params.content.content
logger.info(f"Processing message for task {task_id}")

async with adk.tracing.span(
trace_id=task_id,
task_id=task_id,
name="message",
input={"message": user_message},
data={"__span_type__": "AGENT_WORKFLOW"},
) as turn_span:
tracing_handler = create_pydantic_ai_tracing_handler(
trace_id=task_id,
parent_span_id=turn_span.id if turn_span else None,
task_id=task_id,
)
async with agent.run_stream_events(user_message) as stream:
async for event in convert_pydantic_ai_to_agentex_events(stream, tracing_handler=tracing_handler):
yield event
Loading
Loading