diff --git a/typescript-sdk/apps/dojo/package.json b/typescript-sdk/apps/dojo/package.json index 50b90a16..5efc52ab 100644 --- a/typescript-sdk/apps/dojo/package.json +++ b/typescript-sdk/apps/dojo/package.json @@ -14,6 +14,7 @@ "@ag-ui/llamaindex": "workspace:*", "@ag-ui/mastra": "workspace:*", "@ag-ui/middleware-starter": "workspace:*", + "@ag-ui/pydantic-ai": "workspace:*", "@ag-ui/server-starter": "workspace:*", "@ag-ui/server-starter-all-features": "workspace:*", "@ag-ui/vercel-ai-sdk": "workspace:*", diff --git a/typescript-sdk/apps/dojo/src/agents.ts b/typescript-sdk/apps/dojo/src/agents.ts index 0222e59c..b3437a94 100644 --- a/typescript-sdk/apps/dojo/src/agents.ts +++ b/typescript-sdk/apps/dojo/src/agents.ts @@ -10,6 +10,7 @@ import { LangGraphAgent } from "@ag-ui/langgraph"; import { AgnoAgent } from "@ag-ui/agno"; import { LlamaIndexAgent } from "@ag-ui/llamaindex"; import { CrewAIAgent } from "@ag-ui/crewai"; +import { PydanticAIAgent } from "@ag-ui/pydantic-ai"; export const agentsIntegrations: AgentIntegrationConfig[] = [ { @@ -20,6 +21,31 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ }; }, }, + { + id: "pydantic-ai", + agents: async () => { + return { + agentic_chat: new PydanticAIAgent({ + url: "http://localhost:9000/agentic_chat", + }), + agentic_generative_ui: new PydanticAIAgent({ + url: "http://localhost:9000/agentic_generative_ui", + }), + human_in_the_loop: new PydanticAIAgent({ + url: "http://localhost:9000/human_in_the_loop", + }), + predictive_state_updates: new PydanticAIAgent({ + url: "http://localhost:9000/predictive_state_updates", + }), + shared_state: new PydanticAIAgent({ + url: "http://localhost:9000/shared_state", + }), + tool_based_generative_ui: new PydanticAIAgent({ + url: "http://localhost:9000/tool_based_generative_ui", + }), + }; + }, + }, { id: "server-starter", agents: async () => { diff --git a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx index 164bc489..1eccbd55 100644 --- a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx +++ b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx @@ -70,6 +70,10 @@ const Chat = () => { ], handler: ({ background }) => { setBackground(background); + return { + status: "success", + message: `Background changed to ${background}`, + }; }, }); diff --git a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_generative_ui/page.tsx b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_generative_ui/page.tsx index 120e7223..c44cd0b1 100644 --- a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_generative_ui/page.tsx +++ b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_generative_ui/page.tsx @@ -42,7 +42,7 @@ const Chat = () => { return (
-
+
{state.steps.map((step, index) => { if (step.status === "completed") { return ( @@ -55,7 +55,7 @@ const Chat = () => { index === state.steps.findIndex((s) => s.status === "pending") ) { return ( -
+
{step.description}
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 5b88a130..0b674d10 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 78e95c5e..c96f1e95 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 @@ -128,23 +128,36 @@ const DocumentEditor = () => { }, [text]); useCopilotAction({ - name: "confirm_changes", - renderAndWaitForResponse: ({ args, respond, status }) => ( - { - editor?.commands.setContent(fromMarkdown(currentDocument)); - setAgentState({ document: currentDocument }); - }} - onConfirm={() => { - editor?.commands.setContent(fromMarkdown(agentState?.document || "")); - setCurrentDocument(agentState?.document || ""); - setAgentState({ document: agentState?.document || "" }); - }} - /> - ), + 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 ( diff --git a/typescript-sdk/apps/dojo/src/config.ts b/typescript-sdk/apps/dojo/src/config.ts index 14a90fa6..64b71921 100644 --- a/typescript-sdk/apps/dojo/src/config.ts +++ b/typescript-sdk/apps/dojo/src/config.ts @@ -23,12 +23,6 @@ export const featureConfig: FeatureConfig[] = [ description: "Chat with your Copilot and call frontend tools", tags: ["Chat", "Tools", "Streaming"], }), - createFeatureConfig({ - id: "human_in_the_loop", - name: "Human in the loop", - description: "Plan a task together and direct the Copilot to take the right steps", - tags: ["HITL", "Interactivity"], - }), createFeatureConfig({ id: "agentic_generative_ui", name: "Agentic Generative UI", @@ -36,10 +30,16 @@ export const featureConfig: FeatureConfig[] = [ tags: ["Generative ui (agent)", "Long running task"], }), createFeatureConfig({ - id: "tool_based_generative_ui", - name: "Tool Based Generative UI", - description: "Haiku generator that uses tool based generative UI.", - tags: ["Generative ui (action)", "Tools"], + id: "human_in_the_loop", + name: "Human in the loop", + description: "Plan a task together and direct the Copilot to take the right steps", + tags: ["HITL", "Interactivity"], + }), + createFeatureConfig({ + id: "predictive_state_updates", + name: "Predictive State Updates", + description: "Use collaboration to edit a document in real time with your Copilot", + tags: ["State", "Streaming", "Tools"], }), createFeatureConfig({ id: "shared_state", @@ -48,10 +48,10 @@ export const featureConfig: FeatureConfig[] = [ tags: ["Agent State", "Collaborating"], }), createFeatureConfig({ - id: "predictive_state_updates", - name: "Predictive State Updates", - description: "Use collaboration to edit a document in real time with your Copilot", - tags: ["State", "Streaming", "Tools"], + id: "tool_based_generative_ui", + name: "Tool Based Generative UI", + description: "Haiku generator that uses tool based generative UI.", + tags: ["Generative ui (action)", "Tools"], }), ]; diff --git a/typescript-sdk/apps/dojo/src/menu.ts b/typescript-sdk/apps/dojo/src/menu.ts index b9060d6d..3f73ba8f 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/langgraph/examples/agents/predictive_state_updates/agent.py b/typescript-sdk/integrations/langgraph/examples/agents/predictive_state_updates/agent.py index 325f0014..ffb10570 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 @@ -127,22 +127,9 @@ async def chat_node(state: AgentState, config: RunnableConfig): "content": "Document written.", "tool_call_id": tool_call_id } - - # Add confirmation tool call - confirm_tool_call = { - "role": "assistant", - "content": "", - "tool_calls": [{ - "id": str(uuid.uuid4()), - "function": { - "name": "confirm_changes", - "arguments": "{}" - } - }] - } - - messages = messages + [tool_response, confirm_tool_call] - + + messages = messages + [tool_response] + # Return Command to route to end return Command( goto=END, diff --git a/typescript-sdk/integrations/pydantic-ai/.env-sample b/typescript-sdk/integrations/pydantic-ai/.env-sample new file mode 100644 index 00000000..6d5290d1 --- /dev/null +++ b/typescript-sdk/integrations/pydantic-ai/.env-sample @@ -0,0 +1,4 @@ +## OpenAI API Settings +# Get your Open AI API Key by following these instructions - +# https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key +OPENAI_API_KEY="sk-proj-...." diff --git a/typescript-sdk/integrations/pydantic-ai/.npmignore b/typescript-sdk/integrations/pydantic-ai/.npmignore new file mode 100644 index 00000000..aaacf159 --- /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 00000000..099f8aa5 --- /dev/null +++ b/typescript-sdk/integrations/pydantic-ai/README.md @@ -0,0 +1,163 @@ +# PydanticAI + +Implementation of the AG-UI protocol for PydanticAI. + +## Prerequisites + +This example uses a PydanticAI agent using an OpenAI model and the AG-UI dojo. + +1. An [OpenAI API key](https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key) +2. A clone of the [AG-UI protocol repository](https://github.com/ag-ui-protocol/ag-ui) + +## Running + +To run this integration you need to: + +1. Make a copy of `.env-sample` as `.env` in the `typescript-sdk/integrations/pydantic-ai` directory +2. Open it in your editor and set `OPENAI_API_KEY` to a valid OpenAI key +3. Open terminal in the `typescript-sdk/integrations/pydantic-ai` of the `ag-ui` repo +4. Install the `pydantic-ai-examples` package, for example: + + ```shell + pip install pydantic-ai-examples + ``` + + or: + + ```shell + uv venv + uv add pydantic-ai-examples + ``` + +5. Run the example dojo server + + ```shell + python -m pydantic_ai_ag_ui_examples.dojo_server + ``` + +6. Open another terminal in root directory of the `ag-ui` repository clone +7. Start the integration ag-ui dojo: + + ```shell + cd typescript-sdk + pnpm install && pnpm run dev + ``` + +8. Finally visit [http://localhost:3000/pydantic-ai](http://localhost:3000/pydantic-ai) + +## Feature Demos + +### [Agentic Chat](http://localhost:3000/pydantic-ai/feature/agentic_chat) + +This demonstrates a basic agent interaction including PydanticAI server side +tools and AG-UI client side tools. + +#### Agent Tools + +- `time` - PydanticAI 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 PydanticAI 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](http://localhost:3000/pydantic-ai/feature/agentic_generative_ui) + +Demonstrates a long running task where the agent sends updates to the frontend +to let the user know what's happening. + +#### Plan Prompts + +```text +Create a plan for breakfast and execute it +``` + +### [Human in the Loop](http://localhost:3000/pydantic-ai/feature/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](http://localhost:3000/pydantic-ai/feature/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. + +#### Story Tools + +- `write_document` - AG-UI tool to write the document to a window +- `document_predict_state` - PydanticAI 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](http://localhost:3000/pydantic-ai/feature/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. + +#### 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](http://localhost:3000/pydantic-ai/feature/tool_based_generative_ui) + +Demonstrates customised rendering for tool output with used confirmation. + +#### 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 00000000..a0db5c6f --- /dev/null +++ b/typescript-sdk/integrations/pydantic-ai/jest.config.js @@ -0,0 +1,9 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/*.test.ts"], + 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 00000000..a5be4e33 --- /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 00000000..f3a3190a --- /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 00000000..d12ec063 --- /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 00000000..12b69b8f --- /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/pnpm-lock.yaml b/typescript-sdk/pnpm-lock.yaml index cf343ab1..a81c616b 100644 --- a/typescript-sdk/pnpm-lock.yaml +++ b/typescript-sdk/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: '@ag-ui/proto': specifier: workspace:* version: link:../../packages/proto + '@ag-ui/pydantic-ai': + specifier: workspace:* + version: link:../../integrations/pydantic-ai '@ag-ui/server-starter': specifier: workspace:* version: link:../../integrations/server-starter @@ -412,6 +415,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@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@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.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':