diff --git a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/human_in_the_loop/page.tsx b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/human_in_the_loop/page.tsx
index 5b88a1307..0b674d10b 100644
--- a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/human_in_the_loop/page.tsx
+++ b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/human_in_the_loop/page.tsx
@@ -99,6 +99,7 @@ const Chat = () => {
});
useCopilotAction({
name: "generate_task_steps",
+ description: "Generates a list of steps for the user to perform",
parameters: [
{
name: "steps",
diff --git a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/predictive_state_updates/page.tsx b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/predictive_state_updates/page.tsx
index 78e95c5ed..19061a067 100644
--- a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/predictive_state_updates/page.tsx
+++ b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/predictive_state_updates/page.tsx
@@ -127,6 +127,7 @@ const DocumentEditor = () => {
}
}, [text]);
+ // TODO(steve): Remove this when all agents have been updated to use write_document tool.
useCopilotAction({
name: "confirm_changes",
renderAndWaitForResponse: ({ args, respond, status }) => (
@@ -147,6 +148,40 @@ const DocumentEditor = () => {
),
});
+ // Action to write the document.
+ useCopilotAction({
+ name: "write_document",
+ description: `Present the proposed changes to the user for review`,
+ parameters: [
+ {
+ name: "document",
+ type: "string",
+ description: "The full updated document in markdown format",
+ },
+ ],
+ renderAndWaitForResponse({ args, status, respond }) {
+ if (status === "executing") {
+ return (
+
{
+ editor?.commands.setContent(fromMarkdown(currentDocument));
+ setAgentState({ document: currentDocument });
+ }}
+ onConfirm={() => {
+ editor?.commands.setContent(fromMarkdown(agentState?.document || ""));
+ setCurrentDocument(agentState?.document || "");
+ setAgentState({ document: agentState?.document || "" });
+ }}
+ />
+ );
+ }
+ return <>>;
+ },
+ });
+
return (
{placeholderVisible && (
diff --git a/typescript-sdk/apps/dojo/src/menu.ts b/typescript-sdk/apps/dojo/src/menu.ts
index 632c9d720..36a378c99 100644
--- a/typescript-sdk/apps/dojo/src/menu.ts
+++ b/typescript-sdk/apps/dojo/src/menu.ts
@@ -6,6 +6,18 @@ export const menuIntegrations: MenuIntegrationConfig[] = [
name: "Middleware Starter",
features: ["agentic_chat"],
},
+ {
+ id: "pydantic-ai",
+ name: "Pydantic AI",
+ features: [
+ "agentic_chat",
+ "human_in_the_loop",
+ "agentic_generative_ui",
+ "tool_based_generative_ui",
+ "shared_state",
+ "predictive_state_updates",
+ ],
+ },
{
id: "server-starter",
name: "Server Starter",
diff --git a/typescript-sdk/integrations/crewai/python/ag_ui_crewai/examples/predictive_state_updates.py b/typescript-sdk/integrations/crewai/python/ag_ui_crewai/examples/predictive_state_updates.py
index f933d52d6..011b399e0 100644
--- a/typescript-sdk/integrations/crewai/python/ag_ui_crewai/examples/predictive_state_updates.py
+++ b/typescript-sdk/integrations/crewai/python/ag_ui_crewai/examples/predictive_state_updates.py
@@ -16,7 +16,7 @@
WRITE_DOCUMENT_TOOL = {
"type": "function",
"function": {
- "name": "write_document",
+ "name": "write_document_local",
"description": " ".join("""
Write a document. Use markdown formatting to format the document.
It's good to format the document extensively so it's easy to read.
@@ -63,19 +63,19 @@ async def chat(self):
Standard chat node.
"""
system_prompt = f"""
- You are a helpful assistant for writing documents.
- To write the document, you MUST use the write_document tool.
+ You are a helpful assistant for writing documents.
+ To write the document, you MUST use the write_document_local tool.
You MUST write the full document, even when changing only a few words.
When you wrote the document, DO NOT repeat it as a message.
Just briefly summarize the changes you made. 2 sentences max.
This is the current state of the document: ----\n {self.state.document}\n-----
"""
- # 1. Here we specify that we want to stream the tool call to write_document
+ # 1. Here we specify that we want to stream the tool call to write_document_local
# to the frontend as state.
await copilotkit_predict_state({
"document": {
- "tool_name": "write_document",
+ "tool_name": "write_document_local",
"tool_argument": "document"
}
})
@@ -122,7 +122,7 @@ async def chat(self):
tool_call_name = tool_call["function"]["name"]
tool_call_args = json.loads(tool_call["function"]["arguments"])
- if tool_call_name == "write_document":
+ if tool_call_name == "write_document_local":
self.state.document = tool_call_args["document"]
# 4.1 Append the result to the messages in state
diff --git a/typescript-sdk/integrations/langgraph/examples/agents/predictive_state_updates/agent.py b/typescript-sdk/integrations/langgraph/examples/agents/predictive_state_updates/agent.py
index 325f0014e..8a270f6d1 100644
--- a/typescript-sdk/integrations/langgraph/examples/agents/predictive_state_updates/agent.py
+++ b/typescript-sdk/integrations/langgraph/examples/agents/predictive_state_updates/agent.py
@@ -18,7 +18,7 @@
WRITE_DOCUMENT_TOOL = {
"type": "function",
"function": {
- "name": "write_document",
+ "name": "write_document_local",
"description": " ".join("""
Write a document. Use markdown formatting to format the document.
It's good to format the document extensively so it's easy to read.
@@ -64,25 +64,25 @@ async def chat_node(state: AgentState, config: RunnableConfig):
"""
system_prompt = f"""
- You are a helpful assistant for writing documents.
- To write the document, you MUST use the write_document tool.
+ You are a helpful assistant for writing documents.
+ To write the document, you MUST use the write_document_local tool.
You MUST write the full document, even when changing only a few words.
- When you wrote the document, DO NOT repeat it as a message.
+ When you wrote the document, DO NOT repeat it as a message.
Just briefly summarize the changes you made. 2 sentences max.
This is the current state of the document: ----\n {state.get('document')}\n-----
"""
# Define the model
model = ChatOpenAI(model="gpt-4o")
-
+
# Define config for the model with emit_intermediate_state to stream tool calls to frontend
if config is None:
config = RunnableConfig(recursion_limit=25)
- # Use "predict_state" metadata to set up streaming for the write_document tool
+ # Use "predict_state" metadata to set up streaming for the write_document_local tool
config["metadata"]["predict_state"] = [{
"state_key": "document",
- "tool": "write_document",
+ "tool": "write_document_local",
"tool_argument": "document"
}]
@@ -104,11 +104,11 @@ async def chat_node(state: AgentState, config: RunnableConfig):
# Update messages with the response
messages = state["messages"] + [response]
-
+
# Extract any tool calls from the response
if hasattr(response, "tool_calls") and response.tool_calls:
tool_call = response.tool_calls[0]
-
+
# Handle tool_call as a dictionary or an object
if isinstance(tool_call, dict):
tool_call_id = tool_call["id"]
@@ -120,14 +120,14 @@ async def chat_node(state: AgentState, config: RunnableConfig):
tool_call_name = tool_call.name
tool_call_args = tool_call.args
- if tool_call_name == "write_document":
+ if tool_call_name == "write_document_local":
# Add the tool response to messages
tool_response = {
"role": "tool",
"content": "Document written.",
"tool_call_id": tool_call_id
}
-
+
# Add confirmation tool call
confirm_tool_call = {
"role": "assistant",
@@ -140,9 +140,9 @@ async def chat_node(state: AgentState, config: RunnableConfig):
}
}]
}
-
+
messages = messages + [tool_response, confirm_tool_call]
-
+
# Return Command to route to end
return Command(
goto=END,
@@ -151,7 +151,7 @@ async def chat_node(state: AgentState, config: RunnableConfig):
"document": tool_call_args["document"]
}
)
-
+
# If no tool was called, go to end
return Command(
goto=END,
diff --git a/typescript-sdk/integrations/langgraph/python/ag_ui_langgraph/examples/agents/predictive_state_updates.py b/typescript-sdk/integrations/langgraph/python/ag_ui_langgraph/examples/agents/predictive_state_updates.py
index a7b435741..6aaad5bcb 100644
--- a/typescript-sdk/integrations/langgraph/python/ag_ui_langgraph/examples/agents/predictive_state_updates.py
+++ b/typescript-sdk/integrations/langgraph/python/ag_ui_langgraph/examples/agents/predictive_state_updates.py
@@ -19,7 +19,7 @@
WRITE_DOCUMENT_TOOL = {
"type": "function",
"function": {
- "name": "write_document",
+ "name": "write_document_local",
"description": " ".join("""
Write a document. Use markdown formatting to format the document.
It's good to format the document extensively so it's easy to read.
@@ -66,7 +66,7 @@ async def chat_node(state: AgentState, config: RunnableConfig):
system_prompt = f"""
You are a helpful assistant for writing documents.
- To write the document, you MUST use the write_document tool.
+ To write the document, you MUST use the write_document_local tool.
You MUST write the full document, even when changing only a few words.
When you wrote the document, DO NOT repeat it as a message.
Just briefly summarize the changes you made. 2 sentences max.
@@ -80,10 +80,10 @@ async def chat_node(state: AgentState, config: RunnableConfig):
if config is None:
config = RunnableConfig(recursion_limit=25)
- # Use "predict_state" metadata to set up streaming for the write_document tool
+ # Use "predict_state" metadata to set up streaming for the write_document_local tool
config["metadata"]["predict_state"] = [{
"state_key": "document",
- "tool": "write_document",
+ "tool": "write_document_local",
"tool_argument": "document"
}]
@@ -121,7 +121,7 @@ async def chat_node(state: AgentState, config: RunnableConfig):
tool_call_name = tool_call.name
tool_call_args = tool_call.args
- if tool_call_name == "write_document":
+ if tool_call_name == "write_document_local":
# Add the tool response to messages
tool_response = {
"role": "tool",
diff --git a/typescript-sdk/integrations/pydantic-ai/.npmignore b/typescript-sdk/integrations/pydantic-ai/.npmignore
new file mode 100644
index 000000000..aaacf1596
--- /dev/null
+++ b/typescript-sdk/integrations/pydantic-ai/.npmignore
@@ -0,0 +1,12 @@
+.turbo
+.DS_Store
+.git
+.gitignore
+.idea
+.vscode
+.env
+__tests__
+src
+tsup.config.ts
+tsconfig.json
+jest.config.js
diff --git a/typescript-sdk/integrations/pydantic-ai/README.md b/typescript-sdk/integrations/pydantic-ai/README.md
new file mode 100644
index 000000000..695809424
--- /dev/null
+++ b/typescript-sdk/integrations/pydantic-ai/README.md
@@ -0,0 +1,194 @@
+# Pydantic AI
+
+Implementation of the AG-UI protocol for [Pydantic AI](https://ai.pydantic.dev/).
+
+For more information on the Pydantic AI implementation see
+the [Pydantic AI AG-UI docs](https://ai.pydantic.dev/ag-ui/).
+
+## Prerequisites
+
+This example uses a Pydantic AI agent using an OpenAI model and the AG-UI dojo.
+
+- An [OpenAI API key](https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key)
+
+## Running
+
+To run this integration you need to:
+
+1. Clone the [AG-UI repository](https://github.com/ag-ui-protocol/ag-ui)
+
+ ```shell
+ git clone https://github.com/ag-ui-protocol/ag-ui.git
+ ```
+
+2. Change into the `typescript-sdk/integrations/pydantic-ai` directory
+
+ ```shell
+ cd typescript-sdk/integrations/pydantic-ai
+ ```
+
+3. Install the `pydantic-ai-examples` package, for example:
+
+ ```shell
+ pip install pydantic-ai-examples
+ ```
+
+ or:
+
+ ```shell
+ uv venv
+ uv pip install pydantic-ai-examples
+ ```
+
+4. Run the example dojo server
+
+ ```shell
+ export OPENAI_API_KEY=
+ python -m pydantic_ai_examples.ag_ui
+ ```
+
+ or:
+
+ ```shell
+ export OPENAI_API_KEY=
+ uv run python -m pydantic_ai_examples.ag_ui
+ ```
+
+5. Open another terminal in root directory of the `ag-ui` repository clone
+6. Start the integration ag-ui dojo:
+
+ ```shell
+ cd typescript-sdk
+ pnpm install && pnpm run dev
+ ```
+
+7. Visit [http://localhost:3000/pydantic-ai](http://localhost:3000/pydantic-ai)
+8. Select View `Pydantic AI` from the sidebar
+
+
+## Feature Examples
+
+### Agentic Chat
+
+This demonstrates a basic agent interaction including Pydantic AI server side
+tools and AG-UI client side tools.
+
+View the [Agentic Chat example](http://localhost:3000/pydantic-ai/feature/agentic_chat).
+
+#### Agent Tools
+
+- `time` - Pydantic AI tool to check the current time for a time zone
+- `background` - AG-UI tool to set the background color of the client window
+
+#### Agent Prompts
+
+```text
+What is the time in New York?
+```
+
+```text
+Change the background to blue
+```
+
+A complex example which mixes both AG-UI and Pydantic AI tools:
+
+```text
+Perform the following steps, waiting for the response of each step before continuing:
+1. Get the time
+2. Set the background to red
+3. Get the time
+4. Report how long the background set took by diffing the two times
+```
+
+### Agentic Generative UI
+
+Demonstrates a long running task where the agent sends updates to the frontend
+to let the user know what's happening.
+
+View the [Agentic Generative UI example](http://localhost:3000/pydantic-ai/feature/agentic_generative_ui).
+
+#### Plan Prompts
+
+```text
+Create a plan for breakfast and execute it
+```
+
+### Human in the Loop
+
+Demonstrates simple human in the loop workflow where the agent comes up with a
+plan and the user can approve it using checkboxes.
+
+#### Task Planning Tools
+
+- `generate_task_steps` - AG-UI tool to generate and confirm steps
+
+#### Task Planning Prompt
+
+```text
+Generate a list of steps for cleaning a car for me to review
+```
+
+### Predictive State Updates
+
+Demonstrates how to use the predictive state updates feature to update the state
+of the UI based on agent responses, including user interaction via user
+confirmation.
+
+View the [Predictive State Updates example](http://localhost:3000/pydantic-ai/feature/predictive_state_updates).
+
+#### Story Tools
+
+- `write_document` - AG-UI tool to write the document to a window
+- `document_predict_state` - Pydantic AI tool that enables document state
+ prediction for the `write_document` tool
+
+This also shows how to use custom instructions based on shared state information.
+
+#### Story Example
+
+Starting document text
+
+```markdown
+Bruce was a good dog,
+```
+
+Agent prompt
+
+```text
+Help me complete my story about bruce the dog, is should be no longer than a sentence.
+```
+
+### Shared State
+
+Demonstrates how to use the shared state between the UI and the agent.
+
+State sent to the agent is detected by a function based instruction. This then
+validates the data using a custom pydantic model before using to create the
+instructions for the agent to follow and send to the client using a AG-UI tool.
+
+View the [Shared State example](http://localhost:3000/pydantic-ai/feature/shared_state).
+
+#### Recipe Tools
+
+- `display_recipe` - AG-UI tool to display the recipe in a graphical format
+
+#### Recipe Example
+
+1. Customise the basic settings of your recipe
+2. Click `Improve with AI`
+
+### Tool Based Generative UI
+
+Demonstrates customised rendering for tool output with used confirmation.
+
+View the [Tool Based Generative UI example](http://localhost:3000/pydantic-ai/feature/tool_based_generative_ui).
+
+#### Haiku Tools
+
+- `generate_haiku` - AG-UI tool to display a haiku in English and Japanese
+
+#### Haiku Prompt
+
+```text
+Generate a haiku about formula 1
+```
diff --git a/typescript-sdk/integrations/pydantic-ai/jest.config.js b/typescript-sdk/integrations/pydantic-ai/jest.config.js
new file mode 100644
index 000000000..0521f8d91
--- /dev/null
+++ b/typescript-sdk/integrations/pydantic-ai/jest.config.js
@@ -0,0 +1,10 @@
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+ preset: "ts-jest",
+ testEnvironment: "node",
+ testMatch: ["**/*.test.ts"],
+ passWithNoTests: true,
+ moduleNameMapper: {
+ "^@/(.*)$": "/src/$1",
+ },
+};
diff --git a/typescript-sdk/integrations/pydantic-ai/package.json b/typescript-sdk/integrations/pydantic-ai/package.json
new file mode 100644
index 000000000..a5be4e338
--- /dev/null
+++ b/typescript-sdk/integrations/pydantic-ai/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@ag-ui/pydantic-ai",
+ "author": "Steven Hartland ",
+ "version": "0.0.1",
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "sideEffects": false,
+ "files": [
+ "dist/**"
+ ],
+ "scripts": {
+ "build": "tsup",
+ "dev": "tsup --watch",
+ "clean": "rm -rf dist .turbo node_modules",
+ "typecheck": "tsc --noEmit",
+ "test": "jest",
+ "link:global": "pnpm link --global",
+ "unlink:global": "pnpm unlink --global"
+ },
+ "dependencies": {
+ "@ag-ui/client": "workspace:*"
+ },
+ "peerDependencies": {
+ "rxjs": "7.8.1"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.5.14",
+ "@types/node": "^20.11.19",
+ "jest": "^29.7.0",
+ "ts-jest": "^29.1.2",
+ "tsup": "^8.0.2",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/typescript-sdk/integrations/pydantic-ai/src/index.ts b/typescript-sdk/integrations/pydantic-ai/src/index.ts
new file mode 100644
index 000000000..f3a3190a8
--- /dev/null
+++ b/typescript-sdk/integrations/pydantic-ai/src/index.ts
@@ -0,0 +1,3 @@
+import { HttpAgent } from "@ag-ui/client";
+
+export class PydanticAIAgent extends HttpAgent {}
diff --git a/typescript-sdk/integrations/pydantic-ai/tsconfig.json b/typescript-sdk/integrations/pydantic-ai/tsconfig.json
new file mode 100644
index 000000000..d12ec063d
--- /dev/null
+++ b/typescript-sdk/integrations/pydantic-ai/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "es2017",
+ "module": "esnext",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "moduleResolution": "node",
+ "skipLibCheck": true,
+ "strict": true,
+ "jsx": "react-jsx",
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "stripInternal": true
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/typescript-sdk/integrations/pydantic-ai/tsup.config.ts b/typescript-sdk/integrations/pydantic-ai/tsup.config.ts
new file mode 100644
index 000000000..12b69b8fb
--- /dev/null
+++ b/typescript-sdk/integrations/pydantic-ai/tsup.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ format: ["cjs", "esm"],
+ dts: true,
+ splitting: false,
+ sourcemap: true,
+ clean: true,
+ minify: true,
+});
diff --git a/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/predictive_state_updates.py b/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/predictive_state_updates.py
index 2eeb072ff..6496d5d9c 100644
--- a/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/predictive_state_updates.py
+++ b/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/predictive_state_updates.py
@@ -80,7 +80,7 @@ def make_story(name: str) -> str:
async def send_tool_call_events():
"""Send tool call events with predictive state and incremental story generation"""
tool_call_id = str(uuid.uuid4())
- tool_call_name = "write_document"
+ tool_call_name = "write_document_local"
# Generate a random story
story = make_story(random.choice(dog_names))
@@ -93,13 +93,13 @@ async def send_tool_call_events():
value=[
{
"state_key": "document",
- "tool": "write_document",
+ "tool": "write_document_local",
"tool_argument": "document"
}
]
)
- # First tool call: write_document
+ # First tool call: write_document_local
yield ToolCallStartEvent(
type=EventType.TOOL_CALL_START,
tool_call_id=tool_call_id,
diff --git a/typescript-sdk/pnpm-lock.yaml b/typescript-sdk/pnpm-lock.yaml
index 22a2e7225..a42d98c4b 100644
--- a/typescript-sdk/pnpm-lock.yaml
+++ b/typescript-sdk/pnpm-lock.yaml
@@ -96,6 +96,9 @@ importers:
'@ag-ui/proto':
specifier: workspace:*
version: 0.0.30
+ '@ag-ui/pydantic-ai':
+ specifier: workspace:*
+ version: link:../../integrations/pydantic-ai
'@ag-ui/server-starter':
specifier: workspace:*
version: link:../../integrations/server-starter
@@ -473,6 +476,34 @@ importers:
specifier: ^5.3.3
version: 5.8.2
+ integrations/pydantic-ai:
+ dependencies:
+ '@ag-ui/client':
+ specifier: workspace:*
+ version: link:../../packages/client
+ rxjs:
+ specifier: 7.8.1
+ version: 7.8.1
+ devDependencies:
+ '@types/jest':
+ specifier: ^29.5.14
+ version: 29.5.14
+ '@types/node':
+ specifier: ^20.11.19
+ version: 20.17.50
+ jest:
+ specifier: ^29.7.0
+ version: 29.7.0(@types/node@20.17.50)
+ ts-jest:
+ specifier: ^29.1.2
+ version: 29.3.4(@babel/core@7.27.1)(@jest/transform@30.0.4)(@jest/types@30.0.1)(babel-jest@30.0.4(@babel/core@7.27.1))(esbuild@0.25.4)(jest@29.7.0(@types/node@20.17.50))(typescript@5.8.2)
+ tsup:
+ specifier: ^8.0.2
+ version: 8.5.0(jiti@2.4.2)(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.2)(yaml@2.8.0)
+ typescript:
+ specifier: ^5.3.3
+ version: 5.8.2
+
integrations/server-starter:
dependencies:
'@ag-ui/client':