├── src └── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── api │ └── copilotkit │ │ └── route.ts │ └── page.tsx ├── postcss.config.mjs ├── agent ├── requirements.txt └── agent.py ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── next.config.ts ├── scripts ├── run-agent.bat ├── run-agent.sh ├── setup-agent.bat └── setup-agent.sh ├── eslint.config.mjs ├── tsconfig.json ├── .gitignore ├── LICENSE ├── package.json └── README.md /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/adk-ag-ui-demo/main/src/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /agent/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn[standard] 3 | python-dotenv 4 | pydantic 5 | google-adk 6 | google-genai 7 | ag-ui-adk -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /scripts/run-agent.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Navigate to the agent directory 3 | cd /d %~dp0\..\agent 4 | 5 | REM Activate the virtual environment 6 | call .venv\Scripts\activate.bat 7 | 8 | REM Run the agent 9 | .venv\Scripts\python.exe agent.py -------------------------------------------------------------------------------- /scripts/run-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Navigate to the agent directory 4 | cd "$(dirname "$0")/../agent" || exit 1 5 | 6 | # Activate the virtual environment 7 | source .venv/bin/activate 8 | 9 | # Run the agent 10 | .venv/bin/python agent.py 11 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/setup-agent.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Navigate to the agent directory 3 | cd /d "%~dp0\..\agent" || exit /b 1 4 | 5 | REM Create virtual environment if it doesn't exist 6 | if not exist ".venv" ( 7 | python -m venv .venv 8 | ) 9 | 10 | REM Activate the virtual environment 11 | call .venv\Scripts\activate.bat 12 | 13 | REM Install requirements using pip 14 | pip install -r requirements.txt -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | :root { 4 | --background: #ffffff; 5 | --foreground: #171717; 6 | } 7 | 8 | @media (prefers-color-scheme: dark) { 9 | :root { 10 | --background: #0a0a0a; 11 | --foreground: #ededed; 12 | } 13 | } 14 | 15 | body { 16 | background: var(--background); 17 | color: var(--foreground); 18 | font-family: Arial, Helvetica, sans-serif; 19 | } 20 | 21 | body, 22 | html { 23 | height: 100%; 24 | } 25 | -------------------------------------------------------------------------------- /scripts/setup-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Navigate to the agent directory 4 | cd "$(dirname "$0")/../agent" || exit 1 5 | 6 | # Create virtual environment if it doesn't exist 7 | if [ ! -d ".venv" ]; then 8 | python3 -m venv .venv || python -m venv .venv 9 | fi 10 | 11 | # Activate the virtual environment 12 | source .venv/bin/activate 13 | 14 | # Install requirements using pip3 or pip 15 | (pip3 install -r requirements.txt || pip install -r requirements.txt) 16 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | 3 | import { CopilotKit } from "@copilotkit/react-core"; 4 | import "./globals.css"; 5 | import "@copilotkit/react-ui/styles.css"; 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | 20 | 21 | {children} 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | .mastra/ 44 | 45 | # lock files 46 | package-lock.json 47 | yarn.lock 48 | pnpm-lock.yaml 49 | bun.lockb 50 | 51 | # python 52 | agent/venv/ 53 | agent/__pycache__/ 54 | agent/.venv/ -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/copilotkit/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CopilotRuntime, 3 | ExperimentalEmptyAdapter, 4 | copilotRuntimeNextJSAppRouterEndpoint, 5 | } from "@copilotkit/runtime"; 6 | import { HttpAgent } from "@ag-ui/client"; 7 | import { NextRequest } from "next/server"; 8 | 9 | // 1. You can use any service adapter here for multi-agent support. We use 10 | // the empty adapter since we're only using one agent. 11 | const serviceAdapter = new ExperimentalEmptyAdapter(); 12 | 13 | // 2. Create the CopilotRuntime instance and utilize the AG-UI client 14 | // to setup the connection with the ADK agent. 15 | const runtime = new CopilotRuntime({ 16 | agents: { 17 | // Our FastAPI endpoint URL 18 | "my_agent": new HttpAgent({url: "http://localhost:8000/"}), 19 | } 20 | }); 21 | 22 | // 3. Build a Next.js API route that handles the CopilotKit runtime requests. 23 | export const POST = async (req: NextRequest) => { 24 | const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ 25 | runtime, 26 | serviceAdapter, 27 | endpoint: "/api/copilotkit", 28 | }); 29 | 30 | return handleRequest(req); 31 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) Atai Barkai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adk-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "concurrently \"npm run dev:ui\" \"npm run dev:agent\" --names ui,agent --prefix-colors blue,green --kill-others", 7 | "dev:debug": "LOG_LEVEL=debug npm run dev", 8 | "dev:agent": "./scripts/run-agent.sh || scripts/run-agent.bat", 9 | "dev:ui": "next dev --turbopack", 10 | "build": "next build", 11 | "start": "next start", 12 | "lint": "next lint", 13 | "install:agent": "./scripts/setup-agent.sh || scripts\\setup-agent.bat", 14 | "postinstall": "npm run install:agent" 15 | }, 16 | "dependencies": { 17 | "@ag-ui/client": "^0.0.38", 18 | "@copilotkit/react-core": "1.10.4", 19 | "@copilotkit/react-ui": "1.10.4", 20 | "@copilotkit/runtime": "1.10.4", 21 | "next": "15.3.2", 22 | "react": "^19.0.0", 23 | "react-dom": "^19.0.0", 24 | "zod": "^3.24.4" 25 | }, 26 | "devDependencies": { 27 | "@eslint/eslintrc": "^3", 28 | "@tailwindcss/postcss": "^4", 29 | "@types/node": "^20", 30 | "@types/react": "^19", 31 | "@types/react-dom": "^19", 32 | "concurrently": "^9.1.2", 33 | "eslint": "^9", 34 | "eslint-config-next": "15.3.2", 35 | "tailwindcss": "^4", 36 | "typescript": "^5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CopilotKit <> ADK Starter 2 | 3 | This is a starter template for building AI agents using Google's [ADK](https://google.github.io/adk-docs/) and [CopilotKit](https://copilotkit.ai). It provides a modern Next.js application with an integrated investment analyst agent that can research stocks, analyze market data, and provide investment insights. 4 | 5 | ## Prerequisites 6 | 7 | - Node.js 18+ 8 | - Python 3.12+ 9 | - Google Makersuite API Key (for the ADK agent) (see https://makersuite.google.com/app/apikey) 10 | - Any of the following package managers: 11 | - pnpm (recommended) 12 | - npm 13 | - yarn 14 | - bun 15 | 16 | > **Note:** This repository ignores lock files (package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb) to avoid conflicts between different package managers. Each developer should generate their own lock file using their preferred package manager. After that, make sure to delete it from the .gitignore. 17 | 18 | ## Getting Started 19 | 20 | 1. Install dependencies using your preferred package manager: 21 | ```bash 22 | # Using pnpm (recommended) 23 | pnpm install 24 | 25 | # Using npm 26 | npm install 27 | 28 | # Using yarn 29 | yarn install 30 | 31 | # Using bun 32 | bun install 33 | ``` 34 | 35 | 2. Install Python dependencies for the ADK agent: 36 | ```bash 37 | # Using pnpm 38 | pnpm install:agent 39 | 40 | # Using npm 41 | npm run install:agent 42 | 43 | # Using yarn 44 | yarn install:agent 45 | 46 | # Using bun 47 | bun run install:agent 48 | ``` 49 | 50 | > **Note:** This will automatically setup a `.venv` (virtual environment) inside the `agent` directory. 51 | > 52 | > To activate the virtual environment manually, you can run: 53 | > ```bash 54 | > source agent/.venv/bin/activate 55 | > ``` 56 | 57 | 58 | 3. Set up your Google API key: 59 | ```bash 60 | export GOOGLE_API_KEY="your-google-api-key-here" 61 | ``` 62 | 63 | 4. Start the development server: 64 | ```bash 65 | # Using pnpm 66 | pnpm dev 67 | 68 | # Using npm 69 | npm run dev 70 | 71 | # Using yarn 72 | yarn dev 73 | 74 | # Using bun 75 | bun run dev 76 | ``` 77 | 78 | This will start both the UI and agent servers concurrently. 79 | 80 | ## Available Scripts 81 | The following scripts can also be run using your preferred package manager: 82 | - `dev` - Starts both UI and agent servers in development mode 83 | - `dev:debug` - Starts development servers with debug logging enabled 84 | - `dev:ui` - Starts only the Next.js UI server 85 | - `dev:agent` - Starts only the ADK agent server 86 | - `build` - Builds the Next.js application for production 87 | - `start` - Starts the production server 88 | - `lint` - Runs ESLint for code linting 89 | - `install:agent` - Installs Python dependencies for the agent 90 | 91 | ## Documentation 92 | 93 | The main UI component is in `src/app/page.tsx`. You can: 94 | - Modify the theme colors and styling 95 | - Add new frontend actions 96 | - Customize the CopilotKit sidebar appearance 97 | 98 | ## 📚 Documentation 99 | 100 | - [ADK Documentation](https://google.github.io/adk-docs/) - Learn more about the ADK and its features 101 | - [CopilotKit Documentation](https://docs.copilotkit.ai) - Explore CopilotKit's capabilities 102 | - [Next.js Documentation](https://nextjs.org/docs) - Learn about Next.js features and API 103 | 104 | 105 | ## Contributing 106 | 107 | Feel free to submit issues and enhancement requests! This starter is designed to be easily extensible. 108 | 109 | ## License 110 | 111 | This project is licensed under the MIT License - see the LICENSE file for details. 112 | 113 | ## Troubleshooting 114 | 115 | ### Agent Connection Issues 116 | If you see "I'm having trouble connecting to my tools", make sure: 117 | 1. The ADK agent is running on port 8000 118 | 2. Your Google API key is set correctly 119 | 3. Both servers started successfully 120 | 121 | ### Python Dependencies 122 | If you encounter Python import errors: 123 | ```bash 124 | cd agent 125 | pip install -r requirements.txt 126 | ``` -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useCoAgent, useCopilotAction } from "@copilotkit/react-core"; 4 | import { CopilotKitCSSProperties, CopilotSidebar } from "@copilotkit/react-ui"; 5 | import { useState } from "react"; 6 | 7 | export default function CopilotKitPage() { 8 | const [themeColor, setThemeColor] = useState("#6366f1"); 9 | 10 | // 🪁 Frontend Actions: https://docs.copilotkit.ai/guides/frontend-actions 11 | useCopilotAction({ 12 | name: "setThemeColor", 13 | parameters: [{ 14 | name: "themeColor", 15 | description: "The theme color to set. Make sure to pick nice colors.", 16 | required: true, 17 | }], 18 | handler({ themeColor }) { 19 | setThemeColor(themeColor); 20 | }, 21 | }); 22 | 23 | return ( 24 |
25 | 26 | 34 |
35 | ); 36 | } 37 | 38 | // State of the agent, make sure this aligns with your agent's state. 39 | type AgentState = { 40 | proverbs: string[]; 41 | } 42 | 43 | function YourMainContent({ themeColor }: { themeColor: string }) { 44 | // 🪁 Shared State: https://docs.copilotkit.ai/coagents/shared-state 45 | const { state, setState } = useCoAgent({ 46 | name: "my_agent", 47 | initialState: { 48 | proverbs: [ 49 | "CopilotKit may be new, but its the best thing since sliced bread.", 50 | ], 51 | }, 52 | }) 53 | 54 | //🪁 Generative UI: https://docs.copilotkit.ai/coagents/generative-ui 55 | useCopilotAction({ 56 | name: "get_weather", 57 | description: "Get the weather for a given location.", 58 | available: "disabled", 59 | parameters: [ 60 | { name: "location", type: "string", required: true }, 61 | ], 62 | render: ({ args }) => { 63 | return 64 | }, 65 | }); 66 | 67 | return ( 68 |
72 |
73 |

Proverbs

74 |

This is a demonstrative page, but it could be anything you want! 🪁

75 |
76 |
77 | {state.proverbs?.map((proverb, index) => ( 78 |
82 |

{proverb}

83 | 93 |
94 | ))} 95 |
96 | {state.proverbs?.length === 0 &&

97 | No proverbs yet. Ask the assistant to add some! 98 |

} 99 |
100 |
101 | ); 102 | } 103 | 104 | // Simple sun icon for the weather card 105 | function SunIcon() { 106 | return ( 107 | 108 | 109 | 110 | 111 | ); 112 | } 113 | 114 | // Weather card component where the location and themeColor are based on what the agent 115 | // sets via tool calls. 116 | function WeatherCard({ location, themeColor }: { location?: string, themeColor: string }) { 117 | return ( 118 |
122 |
123 |
124 |
125 |

{location}

126 |

Current Weather

127 |
128 | 129 |
130 | 131 |
132 |
70°
133 |
Clear skies
134 |
135 | 136 |
137 |
138 |
139 |

Humidity

140 |

45%

141 |
142 |
143 |

Wind

144 |

5 mph

145 |
146 |
147 |

Feels Like

148 |

72°

149 |
150 |
151 |
152 |
153 |
154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /agent/agent.py: -------------------------------------------------------------------------------- 1 | """Shared State feature.""" 2 | 3 | from __future__ import annotations 4 | 5 | from dotenv import load_dotenv 6 | load_dotenv() 7 | import json 8 | from enum import Enum 9 | from typing import Dict, List, Any, Optional 10 | from fastapi import FastAPI 11 | from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint 12 | 13 | # ADK imports 14 | from google.adk.agents import LlmAgent 15 | from google.adk.agents.callback_context import CallbackContext 16 | from google.adk.sessions import InMemorySessionService, Session 17 | from google.adk.runners import Runner 18 | from google.adk.events import Event, EventActions 19 | from google.adk.tools import FunctionTool, ToolContext 20 | from google.genai.types import Content, Part , FunctionDeclaration 21 | from google.adk.models import LlmResponse, LlmRequest 22 | from google.genai import types 23 | 24 | from pydantic import BaseModel, Field 25 | from typing import List, Optional 26 | from enum import Enum 27 | 28 | 29 | class ProverbsState(BaseModel): 30 | """List of the proverbs being written.""" 31 | proverbs: list[str] = Field( 32 | default_factory=list, 33 | description='The list of already written proverbs', 34 | ) 35 | 36 | 37 | def set_proverbs( 38 | tool_context: ToolContext, 39 | new_proverbs: list[str] 40 | ) -> Dict[str, str]: 41 | """ 42 | Set the list of provers using the provided new list. 43 | 44 | Args: 45 | "new_proverbs": { 46 | "type": "array", 47 | "items": {"type": "string"}, 48 | "description": "The new list of proverbs to maintain", 49 | } 50 | 51 | Returns: 52 | Dict indicating success status and message 53 | """ 54 | try: 55 | # Put this into a state object just to confirm the shape 56 | new_state = { "proverbs": new_proverbs} 57 | tool_context.state["proverbs"] = new_state["proverbs"] 58 | return {"status": "success", "message": "Proverbs updated successfully"} 59 | 60 | except Exception as e: 61 | return {"status": "error", "message": f"Error updating proverbs: {str(e)}"} 62 | 63 | 64 | 65 | def get_weather(tool_context: ToolContext, location: str) -> Dict[str, str]: 66 | """Get the weather for a given location. Ensure location is fully spelled out.""" 67 | return {"status": "success", "message": f"The weather in {location} is sunny."} 68 | 69 | 70 | 71 | def on_before_agent(callback_context: CallbackContext): 72 | """ 73 | Initialize proverbs state if it doesn't exist. 74 | """ 75 | 76 | if "proverbs" not in callback_context.state: 77 | # Initialize with default recipe 78 | default_proverbs = [] 79 | callback_context.state["proverbs"] = default_proverbs 80 | 81 | 82 | return None 83 | 84 | 85 | 86 | 87 | # --- Define the Callback Function --- 88 | # modifying the agent's system prompt to incude the current state of the proverbs list 89 | def before_model_modifier( 90 | callback_context: CallbackContext, llm_request: LlmRequest 91 | ) -> Optional[LlmResponse]: 92 | """Inspects/modifies the LLM request or skips the call.""" 93 | agent_name = callback_context.agent_name 94 | if agent_name == "ProverbsAgent": 95 | proverbs_json = "No proverbs yet" 96 | if "proverbs" in callback_context.state and callback_context.state["proverbs"] is not None: 97 | try: 98 | proverbs_json = json.dumps(callback_context.state["proverbs"], indent=2) 99 | except Exception as e: 100 | proverbs_json = f"Error serializing proverbs: {str(e)}" 101 | # --- Modification Example --- 102 | # Add a prefix to the system instruction 103 | original_instruction = llm_request.config.system_instruction or types.Content(role="system", parts=[]) 104 | prefix = f"""You are a helpful assistant for maintaining a list of proverbs. 105 | This is the current state of the list of proverbs: {proverbs_json} 106 | When you modify the list of proverbs (wether to add, remove, or modify one or more proverbs), use the set_proverbs tool to update the list.""" 107 | # Ensure system_instruction is Content and parts list exists 108 | if not isinstance(original_instruction, types.Content): 109 | # Handle case where it might be a string (though config expects Content) 110 | original_instruction = types.Content(role="system", parts=[types.Part(text=str(original_instruction))]) 111 | if not original_instruction.parts: 112 | original_instruction.parts.append(types.Part(text="")) # Add an empty part if none exist 113 | 114 | # Modify the text of the first part 115 | modified_text = prefix + (original_instruction.parts[0].text or "") 116 | original_instruction.parts[0].text = modified_text 117 | llm_request.config.system_instruction = original_instruction 118 | 119 | 120 | 121 | return None 122 | 123 | 124 | 125 | 126 | 127 | 128 | # --- Define the Callback Function --- 129 | def simple_after_model_modifier( 130 | callback_context: CallbackContext, llm_response: LlmResponse 131 | ) -> Optional[LlmResponse]: 132 | """Stop the consecutive tool calling of the agent""" 133 | agent_name = callback_context.agent_name 134 | # --- Inspection --- 135 | if agent_name == "ProverbsAgent": 136 | original_text = "" 137 | if llm_response.content and llm_response.content.parts: 138 | # Assuming simple text response for this example 139 | if llm_response.content.role=='model' and llm_response.content.parts[0].text: 140 | original_text = llm_response.content.parts[0].text 141 | callback_context._invocation_context.end_invocation = True 142 | 143 | elif llm_response.error_message: 144 | return None 145 | else: 146 | return None # Nothing to modify 147 | return None 148 | 149 | 150 | proverbs_agent = LlmAgent( 151 | name="ProverbsAgent", 152 | model="gemini-2.5-flash", 153 | instruction=f""" 154 | When a user asks you to do anything regarding proverbs, you MUST use the set_proverbs tool. 155 | 156 | IMPORTANT RULES ABOUT PROVERBS AND THE SET_PROVERBS TOOL: 157 | 1. Always use the set_proverbs tool for any proverbs-related requests 158 | 2. Always pass the COMPLETE LIST of proverbs to the set_proverbs tool. If the list had 5 proverbs and you removed one, you must pass the complete list of 4 remaining proverbs. 159 | 3. You can use existing proverbs if one is relevant to the user's request, but you can also create new proverbs as required. 160 | 4. Be creative and helpful in generating complete, practical proverbs 161 | 5. After using the tool, provide a brief summary of what you create, removed, or changed 7. 162 | 163 | Examples of when to use the set_proverbs tool: 164 | - "Add a proverb about soap" → Use tool with an array containing the existing list of proverbs with the new proverb about soap at the end. 165 | - "Remove the first proverb" → Use tool with an array containing the all of the existing proverbs except the first one" 166 | - "Change any proverbs about cats to mention that they have 18 lives" → If no proverbs mention cats, do not use the tool. If one or more proverbs do mention cats, change them to mention cats having 18 lives, and use the tool with an array of all of the proverbs, including ones that were changed and ones that did not require changes. 167 | 168 | Do your best to ensure proverbs plausibly make sense. 169 | 170 | 171 | IMPORTANT RULES ABOUT WEATHER AND THE GET_WEATHER TOOL: 172 | 1. Only call the get_weather tool if the user asks you for the weather in a given location. 173 | 2. If the user does not specify a location, you can use the location "Everywhere ever in the whole wide world" 174 | 175 | Examples of when to use the get_weather tool: 176 | - "What's the weather today in Tokyo?" → Use the tool with the location "Tokyo" 177 | - "Whats the weather right now" → Use the location "Everywhere ever in the whole wide world" 178 | - Is it raining in London? → Use the tool with the location "London" 179 | """, 180 | tools=[set_proverbs, get_weather], 181 | before_agent_callback=on_before_agent, 182 | before_model_callback=before_model_modifier, 183 | after_model_callback = simple_after_model_modifier 184 | ) 185 | 186 | # Create ADK middleware agent instance 187 | adk_proverbs_agent = ADKAgent( 188 | adk_agent=proverbs_agent, 189 | app_name="proverbs_app", 190 | user_id="demo_user", 191 | session_timeout_seconds=3600, 192 | use_in_memory_services=True 193 | ) 194 | 195 | # Create FastAPI app 196 | app = FastAPI(title="ADK Middleware Proverbs Agent") 197 | 198 | # Add the ADK endpoint 199 | add_adk_fastapi_endpoint(app, adk_proverbs_agent, path="/") 200 | 201 | if __name__ == "__main__": 202 | import os 203 | import uvicorn 204 | 205 | if not os.getenv("GOOGLE_API_KEY"): 206 | print("⚠️ Warning: GOOGLE_API_KEY environment variable not set!") 207 | print(" Set it with: export GOOGLE_API_KEY='your-key-here'") 208 | print(" Get a key from: https://makersuite.google.com/app/apikey") 209 | print() 210 | 211 | port = int(os.getenv("PORT", 8000)) 212 | uvicorn.run(app, host="0.0.0.0", port=port) 213 | --------------------------------------------------------------------------------