Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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