use pydantic

This commit is contained in:
2025-12-13 04:35:40 +01:00
parent 79fc89a7f4
commit 955f992f8e
23 changed files with 1340 additions and 5363 deletions

View File

@@ -1,9 +0,0 @@
Dockerfile
.dockerignore
node_modules
.next
.git
.gitignore
*.md
.env*.local
agent/

15
web/.gitignore vendored
View File

@@ -31,6 +31,7 @@ yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env
.env*
# vercel
@@ -40,5 +41,15 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
# LangGraph API
.langgraph_api
.mastra/
# lock files
package-lock.json
yarn.lock
pnpm-lock.yaml
bun.lockb
# python
venv
.venv
__pycache__

View File

@@ -1,42 +0,0 @@
FROM node:24-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --ignore-scripts
# Build the application
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Skip agent installation in Docker
ENV SKIP_AGENT_INSTALL=true
RUN npm run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

View File

@@ -1,119 +1,117 @@
# Cavepedia Web
# CopilotKit <> PydanticAI Starter
Next.js frontend with integrated PydanticAI agent for Cavepedia.
## Project Structure
```
web/
├── src/ # Next.js application
├── agent/ # PydanticAI agent (Python)
│ ├── main.py # Agent definition
│ ├── server.py # FastAPI server with AG-UI
│ └── pyproject.toml
└── ...
```
This is a starter template for building AI agents using [PydanticAI](https://ai.pydantic.dev/) 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.
## Prerequisites
- Node.js 24+
- Python 3.13
- npm
- Google AI API Key (for the PydanticAI agent)
- OpenAI API Key (for the PydanticAI agent)
- Python 3.12+
- uv
- Node.js 20+
- Any of the following package managers:
- pnpm (recommended)
- npm
- yarn
- bun
## Development
> **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.
### 1. Install dependencies
## Getting Started
1. Install dependencies using your preferred package manager:
```bash
# Using pnpm (recommended)
pnpm install
# Using npm
npm install
# Using yarn
yarn install
# Using bun
bun install
```
This also installs the agent's Python dependencies via the `install:agent` script.
> **Note:** This will automatically setup the Python environment as well.
>
> If you have manual isseus, you can run:
>
> ```sh
> npm run install:agent
> ```
### 2. Set up environment variables
```bash
# Agent environment
cp agent/.env.example agent/.env
# Edit agent/.env with your API keys
3. Set up your OpenAI API key:
Create a `.env` file inside the `agent` folder with the following content:
```
OPENAI_API_KEY=sk-...your-openai-key-here...
```
### 3. Start development servers
4. Start the development server:
```bash
# Using pnpm
pnpm dev
# Using npm
npm run dev
# Using yarn
yarn dev
# Using bun
bun run dev
```
This starts both the Next.js UI and PydanticAI agent servers concurrently.
## Agent Deployment
The agent can be containerized for production deployment.
### Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `GOOGLE_API_KEY` | Yes | Google AI API key for Gemini |
### Running in production
```bash
cd agent
uv run uvicorn server:app --host 0.0.0.0 --port 8000
```
## Web Deployment
### Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `LANGGRAPH_DEPLOYMENT_URL` | Yes | `http://localhost:8000` | URL to the agent |
| `AUTH0_SECRET` | Yes | - | Session encryption key (`openssl rand -hex 32`) |
| `AUTH0_DOMAIN` | Yes | - | Auth0 tenant domain |
| `AUTH0_CLIENT_ID` | Yes | - | Auth0 application client ID |
| `AUTH0_CLIENT_SECRET` | Yes | - | Auth0 application client secret |
| `APP_BASE_URL` | Yes | - | Public URL of the app |
### Docker Compose (Full Stack)
```yaml
services:
web:
image: git.seaturtle.pw/cavepedia/cavepediav2-web:latest
ports:
- "3000:3000"
environment:
LANGGRAPH_DEPLOYMENT_URL: http://agent:8000
AUTH0_SECRET: ${AUTH0_SECRET}
AUTH0_DOMAIN: ${AUTH0_DOMAIN}
AUTH0_CLIENT_ID: ${AUTH0_CLIENT_ID}
AUTH0_CLIENT_SECRET: ${AUTH0_CLIENT_SECRET}
APP_BASE_URL: ${APP_BASE_URL}
depends_on:
- agent
agent:
image: git.seaturtle.pw/cavepedia/cavepediav2-agent:latest
environment:
GOOGLE_API_KEY: ${GOOGLE_API_KEY}
```
This will start both the UI and agent servers concurrently.
## Available Scripts
The following scripts can also be run using your preferred package manager:
- `dev` - Starts both UI and agent servers in development mode
- `dev:debug` - Starts development servers with debug logging enabled
- `dev:ui` - Starts only the Next.js UI server
- `dev:agent` - Starts only the PydanticAI agent server
- `build` - Builds the Next.js application for production
- `start` - Starts the production server
- `lint` - Runs ESLint for code linting
- `install:agent` - Installs Python dependencies for the agent
- `dev` - Start both UI and agent servers
- `dev:ui` - Start only Next.js
- `dev:agent` - Start only PydanticAI agent
- `build` - Build Next.js for production
- `start` - Start production server
- `lint` - Run ESLint
- `install:agent` - Install agent Python dependencies
## Documentation
## References
The main UI component is in `src/app/page.tsx`. You can:
- Modify the theme colors and styling
- Add new frontend actions
- Customize the CopilotKit sidebar appearance
- [PydanticAI Documentation](https://ai.pydantic.dev/)
- [CopilotKit Documentation](https://docs.copilotkit.ai)
- [Next.js Documentation](https://nextjs.org/docs)
- [Auth0 Next.js SDK Examples](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md)
## 📚 Documentation
- [PydanticAI Documentation](https://ai.pydantic.dev) - Learn more about PydanticAI and its features
- [CopilotKit Documentation](https://docs.copilotkit.ai) - Explore CopilotKit's capabilities
- [Next.js Documentation](https://nextjs.org/docs) - Learn about Next.js features and API
## Contributing
Feel free to submit issues and enhancement requests! This starter is designed to be easily extensible.
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Troubleshooting
### Agent Connection Issues
If you see "I'm having trouble connecting to my tools", make sure:
1. The PydanticAI agent is running on port 8000
2. Your OpenAI API key is set correctly
3. Both servers started successfully
### Python Dependencies
If you encounter Python import errors:
```bash
cd agent
uv sync
uv run src/main.py
```

View File

@@ -1,14 +0,0 @@
Dockerfile
.dockerignore
.git
.gitignore
.env
.env.*
.venv/
venv/
__pycache__/
*.pyc
*.pyo
.langgraph_api/
.vercel/
*.md

View File

@@ -1,9 +0,0 @@
venv/
__pycache__/
*.pyc
.env
.vercel
# python
.venv/
.langgraph_api/

View File

@@ -0,0 +1 @@
3.12

View File

@@ -1,21 +0,0 @@
# syntax=docker/dockerfile:1
FROM python:3.13-slim
WORKDIR /app
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Copy dependency files
COPY pyproject.toml uv.lock ./
# Install dependencies
RUN uv sync --frozen --no-dev --no-install-project
# Copy application code
COPY main.py server.py ./
EXPOSE 8000
CMD ["uv", "run", "python", "server.py"]

View File

@@ -1,26 +0,0 @@
"""
PydanticAI agent with MCP tools from Cavepedia server.
"""
from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel
from pydantic_ai.mcp import MCPServerStreamableHTTP
# Create MCP server connection to Cavepedia
mcp_server = MCPServerStreamableHTTP(
url="https://mcp.caving.dev/mcp",
timeout=30.0,
)
# Create the agent with Google Gemini model
agent = Agent(
model=GoogleModel("gemini-2.5-pro"),
toolsets=[mcp_server],
instructions="""You are a helpful assistant with access to cave-related information through the Cavepedia MCP server. You can help users find information about caves, caving techniques, and related topics.
IMPORTANT RULES:
1. Always cite your sources at the end of each response. List the specific sources/documents you used.
2. If you cannot find information on a topic, say so clearly. Do NOT make up information or hallucinate facts.
3. If the MCP tools return no results, acknowledge that you couldn't find the information rather than guessing.""",
)

View File

@@ -1,11 +1,12 @@
[project]
name = "vpi-1000"
version = "1.0.0"
description = "VPI-1000"
requires-python = ">=3.13,<3.14"
name = "agent"
version = "0.1.0"
description = "Cavepedia AI Agent with MCP tools"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pydantic-ai>=0.1.0",
"fastapi>=0.115.5,<1.0.0",
"uvicorn>=0.29.0,<1.0.0",
"python-dotenv>=1.0.0,<2.0.0",
"uvicorn",
"pydantic-ai[google,mcp,ag-ui]",
"python-dotenv",
"logfire>=4.10.0",
]

View File

@@ -1,20 +0,0 @@
"""
Self-hosted PydanticAI agent server using AG-UI protocol.
"""
import os
import uvicorn
from dotenv import load_dotenv
from pydantic_ai.ui.ag_ui.app import AGUIApp
from main import agent
load_dotenv()
# Convert PydanticAI agent to ASGI app with AG-UI protocol
app = AGUIApp(agent)
if __name__ == "__main__":
port = int(os.getenv("PORT", "8000"))
uvicorn.run(app, host="0.0.0.0", port=port)

62
web/agent/src/agent.py Normal file
View File

@@ -0,0 +1,62 @@
"""
PydanticAI agent with MCP tools from Cavepedia server.
"""
import logging
import httpx
from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel
# Set up logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
MCP_URL = "https://mcp.caving.dev/mcp"
logger.info("Initializing Cavepedia agent...")
def check_mcp_available(url: str, timeout: float = 5.0) -> bool:
"""Check if MCP server is reachable."""
try:
# Just check if we can connect - don't need a full MCP handshake
response = httpx.get(url, timeout=timeout, follow_redirects=True)
# Any response (even 4xx/5xx) means server is reachable
# 502 means upstream is down, so treat as unavailable
if response.status_code == 502:
logger.warning(f"MCP server returned 502 Bad Gateway")
return False
return True
except Exception as e:
logger.warning(f"MCP server not reachable: {e}")
return False
# Try to configure MCP if server is available
toolsets = []
if check_mcp_available(MCP_URL):
try:
from pydantic_ai.mcp import MCPServerStreamableHTTP
mcp_server = MCPServerStreamableHTTP(
url=MCP_URL,
timeout=30.0,
)
toolsets.append(mcp_server)
logger.info(f"MCP server configured: {MCP_URL}")
except Exception as e:
logger.warning(f"Could not configure MCP server: {e}")
else:
logger.info("MCP server unavailable - running without MCP tools")
# Create the agent with Google Gemini model
agent = Agent(
model=GoogleModel("gemini-2.5-pro"),
toolsets=toolsets if toolsets else None,
instructions="""You are a helpful caving assistant. Help users with all aspects of caving including cave exploration, safety, surveying techniques, cave locations, geology, equipment, history, conservation, and any other caving-related topics.
IMPORTANT RULES:
1. Always cite your sources at the end of each response when possible.
2. If you're not certain about information, say so clearly. Do NOT make up information or hallucinate facts.
3. Provide accurate, helpful, and safety-conscious information.""",
)
logger.info(f"Agent initialized successfully (MCP: {'enabled' if toolsets else 'disabled'})")

32
web/agent/src/main.py Normal file
View File

@@ -0,0 +1,32 @@
"""
Self-hosted PydanticAI agent server using AG-UI protocol.
"""
import os
import logging
from dotenv import load_dotenv
# Set up logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Load environment variables BEFORE importing agent
load_dotenv()
import uvicorn
from src.agent import agent
logger.info("Creating AG-UI app...")
# Convert PydanticAI agent to ASGI app with AG-UI protocol
app = agent.to_ag_ui(debug=True)
logger.info("AG-UI app created successfully")
if __name__ == "__main__":
port = int(os.getenv("PORT", "8000"))
uvicorn.run(app, host="127.0.0.1", port=port)

882
web/agent/uv.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,6 @@ const eslintConfig = [
"out/**",
"build/**",
"next-env.d.ts",
"agent",
],
},
];

View File

@@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
serverExternalPackages: ["@copilotkit/runtime"],
};
export default nextConfig;

5335
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,13 +5,13 @@
"scripts": {
"dev": "concurrently \"npm run dev:ui\" \"npm run dev:agent\" --names ui,agent --prefix-colors blue,green --kill-others",
"dev:debug": "LOG_LEVEL=debug npm run dev",
"dev:agent": "cd agent && uv run uvicorn server:app --host 0.0.0.0 --port 8000 --reload",
"dev:agent": "./scripts/run-agent.sh || scripts/run-agent.bat",
"dev:ui": "next dev --turbopack -H 127.0.0.1",
"build": "next build --webpack",
"build": "next build",
"start": "next start -H 127.0.0.1",
"lint": "eslint .",
"install:agent": "sh ./scripts/setup-agent.sh || scripts\\setup-agent.bat",
"postinstall": "if [ -z \"$SKIP_AGENT_INSTALL\" ]; then npm run install:agent; fi"
"install:agent": "./scripts/setup-agent.sh || scripts\\setup-agent.bat",
"postinstall": "npm run install:agent"
},
"dependencies": {
"@ag-ui/client": "^0.0.42",

View File

@@ -0,0 +1,6 @@
@echo off
REM Navigate to the agent directory
cd /d %~dp0\..\agent
REM Run the agent using uv
uv run src/main.py

7
web/scripts/run-agent.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Navigate to the agent directory
cd "$(dirname "$0")/../agent" || exit 1
# Run the agent using uvicorn with reload for development
uv run uvicorn src.main:app --host 127.0.0.1 --port 8000 --reload

View File

@@ -2,5 +2,5 @@
REM Navigate to the agent directory
cd /d "%~dp0\..\agent" || exit /b 1
REM Install dependencies using uv
REM Install dependencies and create virtual environment using uv
uv sync

View File

@@ -3,5 +3,5 @@
# Navigate to the agent directory
cd "$(dirname "$0")/../agent" || exit 1
# Install dependencies using uv
# Install dependencies and create virtual environment using uv
uv sync

View File

@@ -12,7 +12,7 @@ const serviceAdapter = new ExperimentalEmptyAdapter();
const runtime = new CopilotRuntime({
agents: {
vpi_1000: new HttpAgent({
url: process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8000",
url: process.env.AGENT_URL || "http://localhost:8000/",
}),
},
});
@@ -25,4 +25,4 @@ export const POST = async (req: NextRequest) => {
});
return handleRequest(req);
};
};