mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-07 17:33:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c86f660f66 | ||
|
|
a2d181dd74 | ||
|
|
7411fdbb52 | ||
|
|
e7a8ce8eb7 | ||
|
|
61fb094306 | ||
|
|
018e8c78ab | ||
|
|
f0f0f653fe | ||
|
|
98a4ff8450 | ||
|
|
d6333aaf33 | ||
|
|
8005094267 | ||
|
|
08b21dabe3 | ||
|
|
8c70d840df | ||
|
|
d8e944c1d3 | ||
|
|
79b2b88f72 | ||
|
|
f5dfbd5670 | ||
|
|
aa6aa5933d | ||
|
|
ff3a4f2016 | ||
|
|
0d5444fc23 | ||
|
|
d7d5dab17b | ||
|
|
05678394e6 | ||
|
|
54cddc09b4 | ||
|
|
9cbab01785 | ||
|
|
de9d4bd3c6 | ||
|
|
aba2265e5e | ||
|
|
ebb9b24bff | ||
|
|
759ef2c655 | ||
|
|
cb4318f6e8 | ||
|
|
d9bb869d5e | ||
|
|
34b6aeb76b | ||
|
|
078b7801e1 | ||
|
|
a970e849b4 | ||
|
|
8e68f4e71c | ||
|
|
308758cdc2 | ||
|
|
f8fa61b082 | ||
|
|
955d236924 | ||
|
|
d9d63c3a13 | ||
|
|
5bee6eb470 | ||
|
|
7cb1676b7c | ||
|
|
e2e9c3f9ee | ||
|
|
cbe8beeca6 | ||
|
|
f7b66a52b7 | ||
|
|
6dbf194312 | ||
|
|
335e8d7e47 |
No files matched your search
+3
-1
@@ -80,4 +80,6 @@ CopilotKit*
|
||||
issue_details.json
|
||||
|
||||
# MCP Registry tokens
|
||||
.mcpregistry_*
|
||||
.mcpregistry_*
|
||||
|
||||
*.log
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.3.76" \
|
||||
"praisonai>=2.6.3" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.3.76" \
|
||||
"praisonai>=2.6.3" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.3.76" \
|
||||
"praisonai>=2.6.3" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Templates CLI
|
||||
|
||||
```bash
|
||||
praisonai templates list
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates search video
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates info ai-video-editor
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates run ai-video-editor --input video.mp4 --output edited.mp4
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates run ai-video-editor --input video.mp4 --output edited.mp4 --offline
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates run ai-video-editor --input video.mp4 --output edited.mp4 --strict-tools
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates run ai-video-editor --input video.mp4 --tools ./my_tools.py
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates run ai-video-editor --input video.mp4 --tools-dir ./tools
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates run ai-video-editor --input video.mp4 --tools-source praisonai_tools.video
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates run ai-video-editor --input video.mp4 --no-template-tools-py
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates init my-project --template ai-video-editor
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates cache clear
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai templates cache list
|
||||
```
|
||||
@@ -0,0 +1,104 @@
|
||||
# Templates Module
|
||||
|
||||
## Loading Templates
|
||||
|
||||
```python
|
||||
from praisonai.templates.loader import TemplateLoader
|
||||
|
||||
loader = TemplateLoader()
|
||||
|
||||
# Load from local path
|
||||
template = loader.load("./my-template")
|
||||
|
||||
# Load from URI
|
||||
template = loader.load("ai-video-editor")
|
||||
|
||||
# Load offline
|
||||
template = loader.load("ai-video-editor", offline=True)
|
||||
|
||||
# Check requirements
|
||||
missing = loader.check_requirements(template)
|
||||
```
|
||||
|
||||
## Template Discovery
|
||||
|
||||
```python
|
||||
from praisonai.templates.discovery import TemplateDiscovery
|
||||
|
||||
discovery = TemplateDiscovery(
|
||||
include_package=True,
|
||||
include_defaults=True,
|
||||
)
|
||||
|
||||
# Find template
|
||||
result = discovery.find_template("ai-video-editor")
|
||||
|
||||
# List all templates
|
||||
templates = discovery.list_templates()
|
||||
```
|
||||
|
||||
## Template Config
|
||||
|
||||
```python
|
||||
from praisonai.templates.loader import TemplateConfig
|
||||
|
||||
# Access template properties
|
||||
template.name
|
||||
template.description
|
||||
template.version
|
||||
template.author
|
||||
template.requires
|
||||
template.workflow_file
|
||||
template.agents_file
|
||||
template.path
|
||||
```
|
||||
|
||||
## Tool Override Integration
|
||||
|
||||
```python
|
||||
from praisonai.templates.tool_override import (
|
||||
create_tool_registry_with_overrides,
|
||||
resolve_tools,
|
||||
)
|
||||
|
||||
# Build registry with template sources
|
||||
tools_sources = template.requires.get("tools_sources", [])
|
||||
|
||||
registry = create_tool_registry_with_overrides(
|
||||
tools_sources=tools_sources,
|
||||
template_dir=str(template.path),
|
||||
include_defaults=True,
|
||||
)
|
||||
|
||||
# Resolve tool names
|
||||
tools = resolve_tools(["shell_tool"], registry=registry)
|
||||
```
|
||||
|
||||
## TEMPLATE.yaml Schema
|
||||
|
||||
```yaml
|
||||
name: my-template
|
||||
version: "1.0.0"
|
||||
description: Template description
|
||||
|
||||
requires:
|
||||
tools: [shell_tool]
|
||||
packages: [praisonai-tools]
|
||||
env: [OPENAI_API_KEY]
|
||||
tools_sources:
|
||||
- praisonai_tools.video
|
||||
- ./local_tools.py
|
||||
|
||||
workflow: workflow.yaml
|
||||
agents: agents.yaml
|
||||
|
||||
config:
|
||||
input:
|
||||
type: string
|
||||
required: true
|
||||
output:
|
||||
type: string
|
||||
|
||||
defaults:
|
||||
preset: podcast
|
||||
```
|
||||
@@ -0,0 +1,65 @@
|
||||
# Tools CLI
|
||||
|
||||
```bash
|
||||
praisonai tools list
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools info shell_tool
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools info internet_search
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools search "web"
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools search "file"
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools doctor
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools doctor --json
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools resolve shell_tool
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools resolve internet_search --template ai-video-editor
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools resolve my_tool --tools ./my_tools.py
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools resolve my_tool --tools-dir ./tools
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools discover
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools discover --include praisonai_tools.video
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools discover --entrypoints
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools show-sources
|
||||
```
|
||||
|
||||
```bash
|
||||
praisonai tools show-sources --template ai-video-editor
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
# Tools Module
|
||||
|
||||
## Tool Registry
|
||||
|
||||
```python
|
||||
from praisonai.templates.tool_override import (
|
||||
create_tool_registry_with_overrides,
|
||||
resolve_tools,
|
||||
)
|
||||
|
||||
# Create registry with defaults
|
||||
registry = create_tool_registry_with_overrides(include_defaults=True)
|
||||
|
||||
# Create registry with overrides
|
||||
registry = create_tool_registry_with_overrides(
|
||||
override_files=["my_tools.py"],
|
||||
override_dirs=["./tools"],
|
||||
include_defaults=True,
|
||||
)
|
||||
|
||||
# Create registry with tools_sources
|
||||
registry = create_tool_registry_with_overrides(
|
||||
tools_sources=["praisonai_tools.video"],
|
||||
template_dir="./my-template",
|
||||
include_defaults=True,
|
||||
)
|
||||
|
||||
# Resolve tool names to callables
|
||||
tools = resolve_tools(
|
||||
["shell_tool", "internet_search"],
|
||||
registry=registry,
|
||||
)
|
||||
```
|
||||
|
||||
## Tool Override Loader
|
||||
|
||||
```python
|
||||
from praisonai.templates.tool_override import ToolOverrideLoader
|
||||
|
||||
loader = ToolOverrideLoader()
|
||||
|
||||
# Load from file
|
||||
tools = loader.load_from_file("my_tools.py")
|
||||
|
||||
# Load from directory
|
||||
tools = loader.load_from_directory("./tools")
|
||||
|
||||
# Load from module
|
||||
tools = loader.load_from_module("praisonai_tools.video")
|
||||
|
||||
# Get default tool directories
|
||||
dirs = loader.get_default_tool_dirs()
|
||||
```
|
||||
|
||||
## Resolution Order
|
||||
|
||||
1. CLI `--tools` files (highest priority)
|
||||
2. CLI `--tools-dir` directories
|
||||
3. Template `tools_sources` (from TEMPLATE.yaml)
|
||||
4. Template-local `tools.py`
|
||||
5. Default dirs (`~/.praison/tools`, `~/.config/praison/tools`)
|
||||
6. Package discovery (`praisonai_tools`)
|
||||
7. Built-in tools (lowest priority)
|
||||
|
||||
## Creating Custom Tools
|
||||
|
||||
```python
|
||||
from praisonai_tools import tool
|
||||
|
||||
@tool
|
||||
def my_custom_tool(query: str) -> str:
|
||||
"""My custom tool description.
|
||||
|
||||
Args:
|
||||
query: The search query
|
||||
"""
|
||||
return f"Result for: {query}"
|
||||
```
|
||||
|
||||
## Template tools_sources
|
||||
|
||||
```yaml
|
||||
# TEMPLATE.yaml
|
||||
requires:
|
||||
tools: [shell_tool]
|
||||
tools_sources:
|
||||
- praisonai_tools.video
|
||||
- ./local_tools.py
|
||||
- ./tools_dir/
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
Async Runs Examples
|
||||
===================
|
||||
|
||||
This directory contains examples for the PraisonAI Async Jobs API.
|
||||
|
||||
Examples:
|
||||
---------
|
||||
1. sdk_submit_poll_result.py - Python SDK example for submit/poll/result
|
||||
2. cli_submit_poll_result.sh - Bash/curl example for submit/poll/result
|
||||
3. idempotency.sh - Demonstrates idempotency key usage
|
||||
4. cancel.sh - Demonstrates job cancellation
|
||||
5. sse_stream.sh - Demonstrates SSE streaming
|
||||
|
||||
Prerequisites:
|
||||
--------------
|
||||
1. Set OPENAI_API_KEY environment variable
|
||||
2. Start the jobs server:
|
||||
|
||||
cd /path/to/praisonai-package/src/praisonai
|
||||
python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory
|
||||
|
||||
3. Run examples:
|
||||
|
||||
python sdk_submit_poll_result.py
|
||||
bash cli_submit_poll_result.sh
|
||||
bash idempotency.sh
|
||||
bash cancel.sh
|
||||
bash sse_stream.sh
|
||||
|
||||
Environment Variables:
|
||||
----------------------
|
||||
- OPENAI_API_KEY: Required for agent execution
|
||||
- PRAISONAI_BASE_URL: API server URL (default: http://127.0.0.1:8005)
|
||||
"""
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# CLI Example: Cancel a Running Job
|
||||
#
|
||||
# This example demonstrates how to cancel a running job.
|
||||
#
|
||||
# Requirements:
|
||||
# - Jobs server running on http://127.0.0.1:8005
|
||||
#
|
||||
# Usage:
|
||||
# bash cancel.sh
|
||||
|
||||
set -e
|
||||
|
||||
API_URL="${PRAISONAI_BASE_URL:-http://127.0.0.1:8005}"
|
||||
|
||||
echo "============================================================"
|
||||
echo "PraisonAI Job Cancellation Example"
|
||||
echo "============================================================"
|
||||
|
||||
# Submit a long-running job
|
||||
echo ""
|
||||
echo "1. Submitting a job..."
|
||||
RESPONSE=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"prompt": "Write a detailed essay about the history of computing."}')
|
||||
|
||||
JOB_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID"
|
||||
|
||||
# Wait briefly
|
||||
echo ""
|
||||
echo "2. Waiting 1 second..."
|
||||
sleep 1
|
||||
|
||||
# Cancel the job
|
||||
echo ""
|
||||
echo "3. Cancelling job..."
|
||||
CANCEL_RESPONSE=$(curl -s -X POST "$API_URL/api/v1/runs/$JOB_ID/cancel")
|
||||
STATUS=$(echo "$CANCEL_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
|
||||
echo " Status after cancel: $STATUS"
|
||||
|
||||
# Verify cancellation
|
||||
echo ""
|
||||
echo "4. Verification:"
|
||||
if [ "$STATUS" = "cancelled" ]; then
|
||||
echo " ✓ SUCCESS: Job was cancelled"
|
||||
else
|
||||
echo " ℹ Job status: $STATUS (may have completed before cancel)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "Cancellation example completed!"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
# CLI Example: Submit, Poll, and Get Result for Async Jobs
|
||||
#
|
||||
# This example demonstrates how to use the PraisonAI CLI to:
|
||||
# 1. Submit an async job
|
||||
# 2. Check job status
|
||||
# 3. Get the result
|
||||
#
|
||||
# Requirements:
|
||||
# - OPENAI_API_KEY environment variable set
|
||||
# - Jobs server running on http://127.0.0.1:8005
|
||||
#
|
||||
# Usage:
|
||||
# bash cli_submit_poll_result.sh
|
||||
|
||||
set -e
|
||||
|
||||
API_URL="${PRAISONAI_BASE_URL:-http://127.0.0.1:8005}"
|
||||
|
||||
echo "============================================================"
|
||||
echo "PraisonAI Async Jobs CLI Example"
|
||||
echo "============================================================"
|
||||
|
||||
# Check if server is running
|
||||
echo ""
|
||||
echo "Checking server health..."
|
||||
if ! curl -s "$API_URL/health" > /dev/null 2>&1; then
|
||||
echo "✗ Server not available at $API_URL"
|
||||
echo " Start the server with: python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Server is healthy"
|
||||
|
||||
# 1. Submit a job
|
||||
echo ""
|
||||
echo "1. Submitting job..."
|
||||
RESPONSE=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"prompt": "What is 2+2? Answer with just the number."}')
|
||||
|
||||
JOB_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID"
|
||||
|
||||
# 2. Poll for status
|
||||
echo ""
|
||||
echo "2. Polling for status..."
|
||||
while true; do
|
||||
STATUS_RESPONSE=$(curl -s "$API_URL/api/v1/runs/$JOB_ID")
|
||||
STATUS=$(echo "$STATUS_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
|
||||
PROGRESS=$(echo "$STATUS_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['progress']['percentage'])")
|
||||
|
||||
echo " Status: $STATUS | Progress: ${PROGRESS}%"
|
||||
|
||||
if [ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "cancelled" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 3. Get result
|
||||
echo ""
|
||||
echo "3. Getting result..."
|
||||
if [ "$STATUS" = "succeeded" ]; then
|
||||
RESULT=$(curl -s "$API_URL/api/v1/runs/$JOB_ID/result")
|
||||
echo " Result: $(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['result'])")"
|
||||
echo " Duration: $(echo "$RESULT" | python3 -c "import sys,json; print(f\"{json.load(sys.stdin)['duration_seconds']:.2f}s\")")"
|
||||
else
|
||||
echo " Job did not succeed: $STATUS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "Example completed!"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# CLI Example: Idempotency Key Usage
|
||||
#
|
||||
# This example demonstrates how idempotency keys prevent duplicate job submissions.
|
||||
# Submitting the same request with the same idempotency key returns the existing job.
|
||||
#
|
||||
# Requirements:
|
||||
# - Jobs server running on http://127.0.0.1:8005
|
||||
#
|
||||
# Usage:
|
||||
# bash idempotency.sh
|
||||
|
||||
set -e
|
||||
|
||||
API_URL="${PRAISONAI_BASE_URL:-http://127.0.0.1:8005}"
|
||||
IDEM_KEY="example-idem-key-$(date +%s)"
|
||||
|
||||
echo "============================================================"
|
||||
echo "PraisonAI Idempotency Key Example"
|
||||
echo "============================================================"
|
||||
|
||||
# First submission
|
||||
echo ""
|
||||
echo "1. First submission with Idempotency-Key: $IDEM_KEY"
|
||||
RESPONSE1=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Idempotency-Key: $IDEM_KEY" \
|
||||
-d '{"prompt": "What is 1+1?"}')
|
||||
|
||||
JOB_ID1=$(echo "$RESPONSE1" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID1"
|
||||
|
||||
# Second submission with same key
|
||||
echo ""
|
||||
echo "2. Second submission with SAME Idempotency-Key: $IDEM_KEY"
|
||||
RESPONSE2=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Idempotency-Key: $IDEM_KEY" \
|
||||
-d '{"prompt": "Different prompt but same key"}')
|
||||
|
||||
JOB_ID2=$(echo "$RESPONSE2" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID2"
|
||||
|
||||
# Verify same job ID
|
||||
echo ""
|
||||
echo "3. Verification:"
|
||||
if [ "$JOB_ID1" = "$JOB_ID2" ]; then
|
||||
echo " ✓ SUCCESS: Same job ID returned (no duplicate created)"
|
||||
else
|
||||
echo " ✗ FAILURE: Different job IDs returned"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "Idempotency example completed!"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SDK Example: Submit, Poll, and Get Result for Async Jobs
|
||||
|
||||
This example demonstrates how to use the PraisonAI Jobs SDK to:
|
||||
1. Submit an async job
|
||||
2. Poll for status
|
||||
3. Get the result
|
||||
|
||||
Requirements:
|
||||
- OPENAI_API_KEY environment variable set
|
||||
- Jobs server running on http://127.0.0.1:8005
|
||||
|
||||
Usage:
|
||||
python sdk_submit_poll_result.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import httpx
|
||||
|
||||
# Configuration
|
||||
API_URL = os.getenv("PRAISONAI_BASE_URL", "http://127.0.0.1:8005")
|
||||
POLL_INTERVAL = 2 # seconds
|
||||
|
||||
|
||||
def submit_job(prompt: str, idempotency_key: str = None) -> dict:
|
||||
"""Submit a job and return the response."""
|
||||
headers = {}
|
||||
if idempotency_key:
|
||||
headers["Idempotency-Key"] = idempotency_key
|
||||
|
||||
with httpx.Client(timeout=30.0) as client:
|
||||
response = client.post(
|
||||
f"{API_URL}/api/v1/runs",
|
||||
json={"prompt": prompt},
|
||||
headers=headers if headers else None
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_status(job_id: str) -> dict:
|
||||
"""Get job status."""
|
||||
with httpx.Client(timeout=30.0) as client:
|
||||
response = client.get(f"{API_URL}/api/v1/runs/{job_id}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_result(job_id: str) -> dict:
|
||||
"""Get job result."""
|
||||
with httpx.Client(timeout=30.0) as client:
|
||||
response = client.get(f"{API_URL}/api/v1/runs/{job_id}/result")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def wait_for_completion(job_id: str, max_wait: int = 120) -> dict:
|
||||
"""Poll until job completes."""
|
||||
start = time.time()
|
||||
while time.time() - start < max_wait:
|
||||
status = get_status(job_id)
|
||||
print(f" Status: {status['status']} | Progress: {status['progress']['percentage']:.0f}%")
|
||||
|
||||
if status["status"] in ("succeeded", "failed", "cancelled"):
|
||||
return status
|
||||
|
||||
# Honor retry_after if present
|
||||
wait_time = status.get("retry_after") or POLL_INTERVAL
|
||||
time.sleep(wait_time)
|
||||
|
||||
raise TimeoutError(f"Job {job_id} did not complete within {max_wait}s")
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("PraisonAI Async Jobs SDK Example")
|
||||
print("=" * 60)
|
||||
|
||||
# Check if server is running
|
||||
try:
|
||||
with httpx.Client(timeout=5.0) as client:
|
||||
health = client.get(f"{API_URL}/health")
|
||||
health.raise_for_status()
|
||||
print(f"✓ Server healthy: {health.json()['status']}")
|
||||
except Exception as e:
|
||||
print(f"✗ Server not available at {API_URL}: {e}")
|
||||
print(" Start the server with: python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory")
|
||||
return
|
||||
|
||||
# 1. Submit a job
|
||||
print("\n1. Submitting job...")
|
||||
prompt = "What is the capital of France? Answer in one word."
|
||||
result = submit_job(prompt)
|
||||
job_id = result["job_id"]
|
||||
print(f" Job ID: {job_id}")
|
||||
print(f" Status: {result['status']}")
|
||||
print(f" Poll URL: {result['poll_url']}")
|
||||
|
||||
# 2. Wait for completion
|
||||
print("\n2. Waiting for completion...")
|
||||
final_status = wait_for_completion(job_id)
|
||||
print(f" Final status: {final_status['status']}")
|
||||
|
||||
# 3. Get result
|
||||
if final_status["status"] == "succeeded":
|
||||
print("\n3. Getting result...")
|
||||
result = get_result(job_id)
|
||||
print(f" Result: {result['result']}")
|
||||
print(f" Duration: {result['duration_seconds']:.2f}s")
|
||||
else:
|
||||
print(f"\n3. Job did not succeed: {final_status.get('error', 'Unknown error')}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Example completed successfully!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# CLI Example: SSE Streaming
|
||||
#
|
||||
# This example demonstrates how to stream job progress via Server-Sent Events.
|
||||
#
|
||||
# Requirements:
|
||||
# - Jobs server running on http://127.0.0.1:8005
|
||||
#
|
||||
# Usage:
|
||||
# bash sse_stream.sh
|
||||
|
||||
set -e
|
||||
|
||||
API_URL="${PRAISONAI_BASE_URL:-http://127.0.0.1:8005}"
|
||||
|
||||
echo "============================================================"
|
||||
echo "PraisonAI SSE Streaming Example"
|
||||
echo "============================================================"
|
||||
|
||||
# Submit a job
|
||||
echo ""
|
||||
echo "1. Submitting job..."
|
||||
RESPONSE=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"prompt": "Count from 1 to 5."}')
|
||||
|
||||
JOB_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID"
|
||||
|
||||
# Stream progress
|
||||
echo ""
|
||||
echo "2. Streaming progress (Ctrl+C to stop)..."
|
||||
echo " Connecting to: $API_URL/api/v1/runs/$JOB_ID/stream"
|
||||
echo ""
|
||||
|
||||
# Use curl to stream SSE events (timeout after 60s)
|
||||
curl -s -N --max-time 60 "$API_URL/api/v1/runs/$JOB_ID/stream" | while read -r line; do
|
||||
if [[ "$line" == data:* ]]; then
|
||||
data="${line#data: }"
|
||||
if [ "$data" = "[DONE]" ]; then
|
||||
echo " Stream completed"
|
||||
break
|
||||
fi
|
||||
echo " Event: $data"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "Streaming example completed!"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic Background Tasks Example for PraisonAI Agents.
|
||||
|
||||
This example demonstrates how to use the background task system to:
|
||||
1. Run tasks asynchronously
|
||||
2. Track task progress
|
||||
3. Cancel running tasks
|
||||
4. Handle task completion
|
||||
|
||||
Usage:
|
||||
python basic_background.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from praisonaiagents.background import BackgroundRunner, BackgroundConfig, TaskStatus
|
||||
|
||||
|
||||
async def main():
|
||||
print("=" * 60)
|
||||
print("Background Tasks Demo")
|
||||
print("=" * 60)
|
||||
|
||||
# Create a background runner with config
|
||||
config = BackgroundConfig(max_concurrent_tasks=3)
|
||||
runner = BackgroundRunner(config=config)
|
||||
|
||||
# Define some example tasks
|
||||
def slow_task(duration: float, name: str) -> str:
|
||||
"""A slow task that simulates work."""
|
||||
print(f" 🔄 Task '{name}' starting (will take {duration}s)")
|
||||
time.sleep(duration)
|
||||
return f"Task '{name}' completed after {duration}s"
|
||||
|
||||
async def async_task(duration: float, name: str) -> str:
|
||||
"""An async task that simulates work."""
|
||||
print(f" 🔄 Async task '{name}' starting (will take {duration}s)")
|
||||
await asyncio.sleep(duration)
|
||||
return f"Async task '{name}' completed after {duration}s"
|
||||
|
||||
# Submit tasks
|
||||
print("\n--- Submitting Tasks ---")
|
||||
|
||||
task1 = await runner.submit(slow_task, args=(1.0, "quick-job"), name="task1")
|
||||
print(f"✅ Submitted task 1: {task1.id[:8]}")
|
||||
|
||||
task2 = await runner.submit(slow_task, args=(2.0, "medium-job"), name="task2")
|
||||
print(f"✅ Submitted task 2: {task2.id[:8]}")
|
||||
|
||||
task3 = await runner.submit(async_task, args=(1.5, "async-job"), name="task3")
|
||||
print(f"✅ Submitted async task 3: {task3.id[:8]}")
|
||||
|
||||
# List all tasks
|
||||
print("\n--- Task Status ---")
|
||||
for task in runner.tasks:
|
||||
print(f" [{task.id[:8]}] {task.status.value}")
|
||||
|
||||
# Wait for task 1 to complete
|
||||
print("\n--- Waiting for Task 1 ---")
|
||||
await task1.wait(timeout=5.0)
|
||||
print(f"✅ Task 1 result: {task1.result}")
|
||||
|
||||
# Check status of all tasks
|
||||
print("\n--- Updated Task Status ---")
|
||||
for task in [task1, task2, task3]:
|
||||
status = "✅" if task.status == TaskStatus.COMPLETED else "🔄"
|
||||
print(f" {status} [{task.id[:8]}] {task.status.value}")
|
||||
|
||||
# Wait for all remaining tasks
|
||||
print("\n--- Waiting for All Tasks ---")
|
||||
await task2.wait(timeout=10.0)
|
||||
await task3.wait(timeout=10.0)
|
||||
|
||||
# Final status
|
||||
print("\n--- Final Results ---")
|
||||
for task in [task1, task2, task3]:
|
||||
if task.result:
|
||||
print(f" ✅ {task.result}")
|
||||
|
||||
# Cleanup
|
||||
runner.clear_completed()
|
||||
print("\n✅ Cleared completed tasks")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Demo Complete!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic Checkpoints Example for PraisonAI Agents.
|
||||
|
||||
This example demonstrates how to use the shadow git checkpointing system to:
|
||||
1. Save checkpoints before making changes
|
||||
2. List available checkpoints
|
||||
3. View diffs between checkpoints
|
||||
4. Restore to a previous checkpoint
|
||||
|
||||
Usage:
|
||||
python basic_checkpoints.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from praisonaiagents.checkpoints import CheckpointService
|
||||
|
||||
|
||||
async def main():
|
||||
# Create a temporary workspace for demonstration
|
||||
with tempfile.TemporaryDirectory() as workspace:
|
||||
print("=" * 60)
|
||||
print("Shadow Git Checkpointing Demo")
|
||||
print("=" * 60)
|
||||
print(f"\nWorkspace: {workspace}")
|
||||
|
||||
# Create some initial files
|
||||
test_file = Path(workspace) / "example.py"
|
||||
test_file.write_text("# Initial content\nprint('Hello, World!')\n")
|
||||
print(f"\n✅ Created initial file: {test_file.name}")
|
||||
|
||||
# Initialize the checkpoint service
|
||||
service = CheckpointService(workspace_dir=workspace)
|
||||
|
||||
try:
|
||||
# Initialize the shadow git repository
|
||||
print("\n--- Initializing Checkpoint Service ---")
|
||||
await service.initialize()
|
||||
print("✅ Shadow git repository initialized")
|
||||
|
||||
# Save first checkpoint
|
||||
print("\n--- Saving Checkpoint 1: Initial State ---")
|
||||
result1 = await service.save("Initial state")
|
||||
print(f"✅ Checkpoint saved: {result1.checkpoint.id[:8]}")
|
||||
print(f" Message: {result1.checkpoint.message}")
|
||||
|
||||
# Make some changes
|
||||
print("\n--- Making Changes ---")
|
||||
test_file.write_text("# Modified content\nprint('Hello, PraisonAI!')\nprint('Checkpoints are awesome!')\n")
|
||||
print("✅ Modified example.py")
|
||||
|
||||
# Create a new file
|
||||
new_file = Path(workspace) / "utils.py"
|
||||
new_file.write_text("def helper():\n return 'I help!'\n")
|
||||
print("✅ Created utils.py")
|
||||
|
||||
# Save second checkpoint
|
||||
print("\n--- Saving Checkpoint 2: After Changes ---")
|
||||
result2 = await service.save("Added features")
|
||||
print(f"✅ Checkpoint saved: {result2.checkpoint.id[:8]}")
|
||||
|
||||
# List all checkpoints
|
||||
print("\n--- Listing All Checkpoints ---")
|
||||
checkpoints = await service.list_checkpoints()
|
||||
for i, cp in enumerate(checkpoints, 1):
|
||||
print(f" {i}. [{cp.id[:8]}] {cp.message} ({cp.timestamp})")
|
||||
|
||||
# Show diff
|
||||
print("\n--- Current Diff (uncommitted changes) ---")
|
||||
diff = await service.diff()
|
||||
if diff.files:
|
||||
for f in diff.files:
|
||||
print(f" {f.status}: {f.path}")
|
||||
else:
|
||||
print(" No uncommitted changes")
|
||||
|
||||
# Make more changes to show diff
|
||||
test_file.write_text("# Even more changes\nprint('This will be reverted')\n")
|
||||
print("\n✅ Made more changes to example.py")
|
||||
|
||||
diff = await service.diff()
|
||||
print("\n--- New Diff ---")
|
||||
if diff.files:
|
||||
for f in diff.files:
|
||||
print(f" {f.status}: {f.path}")
|
||||
|
||||
# Restore to checkpoint 2
|
||||
print(f"\n--- Restoring to Checkpoint: {result2.checkpoint.id[:8]} ---")
|
||||
await service.restore(result2.checkpoint.id)
|
||||
print("✅ Restored successfully!")
|
||||
|
||||
# Verify restoration
|
||||
content = test_file.read_text()
|
||||
print(f"\n--- Verified Content After Restore ---")
|
||||
print(content)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
raise
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Demo Complete!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,93 @@
|
||||
# Persistence CLI Commands
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Check database connectivity
|
||||
praisonai persistence doctor \
|
||||
--conversation-url postgresql://localhost:5432/praisonai \
|
||||
--state-url redis://localhost:6379
|
||||
|
||||
# Run agent with persistence
|
||||
praisonai persistence run \
|
||||
--conversation-url postgresql://localhost:5432/praisonai \
|
||||
--session-id my-session
|
||||
|
||||
# Resume a session
|
||||
praisonai persistence resume \
|
||||
--conversation-url postgresql://localhost:5432/praisonai \
|
||||
--session-id my-session
|
||||
|
||||
# Export session to file
|
||||
praisonai persistence export \
|
||||
--conversation-url postgresql://localhost:5432/praisonai \
|
||||
--session-id my-session \
|
||||
--output session_backup.jsonl
|
||||
|
||||
# Import session from file
|
||||
praisonai persistence import \
|
||||
--conversation-url postgresql://localhost:5432/praisonai \
|
||||
--file session_backup.jsonl
|
||||
|
||||
# Check schema status
|
||||
praisonai persistence status \
|
||||
--conversation-url postgresql://localhost:5432/praisonai \
|
||||
--state-url redis://localhost:6379
|
||||
|
||||
# Apply migrations
|
||||
praisonai persistence migrate \
|
||||
--conversation-url postgresql://localhost:5432/praisonai \
|
||||
--state-url redis://localhost:6379
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Instead of CLI flags, you can use environment variables:
|
||||
|
||||
```bash
|
||||
export PRAISON_CONVERSATION_URL=postgresql://localhost:5432/praisonai
|
||||
export PRAISON_STATE_URL=redis://localhost:6379
|
||||
export PRAISON_KNOWLEDGE_URL=http://localhost:6333
|
||||
|
||||
# Then run without URL flags
|
||||
praisonai persistence doctor
|
||||
praisonai persistence status
|
||||
```
|
||||
|
||||
## Docker Setup
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker run -d --name praison-postgres \
|
||||
-e POSTGRES_PASSWORD=praison123 \
|
||||
-e POSTGRES_DB=praisonai \
|
||||
-p 5432:5432 postgres:16
|
||||
|
||||
docker run -d --name praison-redis \
|
||||
-p 6379:6379 redis:7
|
||||
|
||||
docker run -d --name praison-qdrant \
|
||||
-p 6333:6333 -p 6334:6334 qdrant/qdrant
|
||||
|
||||
# Verify
|
||||
docker ps
|
||||
```
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```bash
|
||||
# 1. Start services
|
||||
docker-compose up -d
|
||||
|
||||
# 2. Check connectivity
|
||||
praisonai persistence doctor
|
||||
|
||||
# 3. Run agent
|
||||
praisonai persistence run --session-id demo-session
|
||||
|
||||
# 4. Export for backup
|
||||
praisonai persistence export --session-id demo-session --output backup.jsonl
|
||||
|
||||
# 5. Import to another environment
|
||||
praisonai persistence import --file backup.jsonl
|
||||
```
|
||||
@@ -0,0 +1,124 @@
|
||||
# PraisonAI Persistence CLI - End-to-End Guide
|
||||
|
||||
This guide demonstrates the CLI commands for database persistence.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
# Start Docker containers
|
||||
docker run -d --name praison-postgres -p 5432:5432 \
|
||||
-e POSTGRES_PASSWORD=praison123 \
|
||||
-e POSTGRES_DB=praisonai \
|
||||
postgres:16
|
||||
|
||||
docker run -d --name praison-qdrant -p 6333:6333 qdrant/qdrant
|
||||
docker run -d --name praison-redis -p 6379:6379 redis:7
|
||||
```
|
||||
|
||||
## 1. Doctor Command - Validate Connectivity
|
||||
|
||||
```bash
|
||||
# Check all stores
|
||||
praisonai persistence doctor \
|
||||
--conversation-url "postgresql://postgres:praison123@localhost:5432/praisonai" \
|
||||
--knowledge-url "http://localhost:6333" \
|
||||
--state-url "redis://localhost:6379"
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
```
|
||||
==================================================
|
||||
PraisonAI Persistence Doctor
|
||||
==================================================
|
||||
|
||||
[Conversation Store] Testing: postgresql://postgres:****@localhost:5432/praisonai
|
||||
✅ Connected (postgres)
|
||||
|
||||
[Knowledge Store] Testing: http://localhost:6333
|
||||
✅ Connected (qdrant)
|
||||
|
||||
[State Store] Testing: redis://localhost:6379
|
||||
✅ Connected (redis)
|
||||
|
||||
==================================================
|
||||
Results: 3/3 stores connected successfully
|
||||
==================================================
|
||||
```
|
||||
|
||||
## 2. Run Command - Execute Agent with Persistence
|
||||
|
||||
```bash
|
||||
# Run agent with persistence (dry-run first)
|
||||
praisonai persistence run \
|
||||
--session-id "cli-demo-001" \
|
||||
--conversation-url "postgresql://postgres:praison123@localhost:5432/praisonai" \
|
||||
--dry-run \
|
||||
"Hello, my name is Bob"
|
||||
|
||||
# Actually run it
|
||||
praisonai persistence run \
|
||||
--session-id "cli-demo-001" \
|
||||
--conversation-url "postgresql://postgres:praison123@localhost:5432/praisonai" \
|
||||
"Hello, my name is Bob"
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
```
|
||||
[Session: cli-demo-001]
|
||||
Agent: Hello Bob! Nice to meet you. How can I help you today?
|
||||
```
|
||||
|
||||
## 3. Resume Command - Continue a Session
|
||||
|
||||
```bash
|
||||
# Show history
|
||||
praisonai persistence resume \
|
||||
--session-id "cli-demo-001" \
|
||||
--conversation-url "postgresql://postgres:praison123@localhost:5432/praisonai" \
|
||||
--show-history
|
||||
|
||||
# Continue conversation
|
||||
praisonai persistence resume \
|
||||
--session-id "cli-demo-001" \
|
||||
--conversation-url "postgresql://postgres:praison123@localhost:5432/praisonai" \
|
||||
--continue "What's my name?"
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
```
|
||||
[Session: cli-demo-001]
|
||||
Messages in history: 2
|
||||
|
||||
--- Conversation History ---
|
||||
[USER] Hello, my name is Bob
|
||||
[ASSISTANT] Hello Bob! Nice to meet you...
|
||||
--- End History ---
|
||||
|
||||
Agent: Your name is Bob!
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Instead of passing URLs on command line, use environment variables:
|
||||
|
||||
```bash
|
||||
export PRAISON_CONVERSATION_URL="postgresql://postgres:praison123@localhost:5432/praisonai"
|
||||
export PRAISON_KNOWLEDGE_URL="http://localhost:6333"
|
||||
export PRAISON_STATE_URL="redis://localhost:6379"
|
||||
export OPENAI_API_KEY="your-key"
|
||||
|
||||
# Now commands are simpler
|
||||
praisonai persistence doctor --all
|
||||
praisonai persistence run --session-id "my-session" "Hello!"
|
||||
```
|
||||
|
||||
## Full CLI Reference
|
||||
|
||||
```
|
||||
praisonai persistence --help
|
||||
praisonai persistence doctor --help
|
||||
praisonai persistence run --help
|
||||
praisonai persistence resume --help
|
||||
```
|
||||
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic Context Compaction Example for PraisonAI Agents.
|
||||
|
||||
This example demonstrates how to use context compaction to:
|
||||
1. Manage conversation context length
|
||||
2. Use different compaction strategies
|
||||
3. Track compaction results
|
||||
4. Preserve important messages
|
||||
|
||||
Usage:
|
||||
python basic_compaction.py
|
||||
"""
|
||||
|
||||
from praisonaiagents.compaction import (
|
||||
ContextCompactor, CompactionConfig, CompactionStrategy, CompactionResult
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Context Compaction Demo")
|
||||
print("=" * 60)
|
||||
|
||||
# ==========================================================================
|
||||
# Sample Messages
|
||||
# ==========================================================================
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a helpful AI assistant."},
|
||||
{"role": "user", "content": "Hello! Can you help me with Python?"},
|
||||
{"role": "assistant", "content": "Of course! I'd be happy to help you with Python. What would you like to know?"},
|
||||
{"role": "user", "content": "How do I read a file in Python?"},
|
||||
{"role": "assistant", "content": "To read a file in Python, you can use the built-in open() function. Here's an example:\n\nwith open('filename.txt', 'r') as f:\n content = f.read()\n\nThis opens the file, reads its content, and automatically closes it."},
|
||||
{"role": "user", "content": "What about writing to a file?"},
|
||||
{"role": "assistant", "content": "To write to a file, use the 'w' mode:\n\nwith open('filename.txt', 'w') as f:\n f.write('Hello, World!')\n\nUse 'a' mode to append instead of overwriting."},
|
||||
{"role": "user", "content": "Can you explain list comprehensions?"},
|
||||
{"role": "assistant", "content": "List comprehensions are a concise way to create lists. Instead of:\n\nresult = []\nfor x in range(10):\n result.append(x * 2)\n\nYou can write:\n\nresult = [x * 2 for x in range(10)]"},
|
||||
]
|
||||
|
||||
print(f"\n--- Original Messages ---")
|
||||
print(f" Message count: {len(messages)}")
|
||||
|
||||
# ==========================================================================
|
||||
# Basic Compaction
|
||||
# ==========================================================================
|
||||
print("\n--- Basic Compaction (Truncate Strategy) ---")
|
||||
|
||||
compactor = ContextCompactor(
|
||||
max_tokens=50, # Low limit to trigger compaction
|
||||
strategy=CompactionStrategy.TRUNCATE,
|
||||
preserve_system=True,
|
||||
preserve_recent=2
|
||||
)
|
||||
|
||||
# Check stats before
|
||||
stats = compactor.get_stats(messages)
|
||||
print(f" Total tokens: {stats['total_tokens']}")
|
||||
print(f" Max tokens: {stats['max_tokens']}")
|
||||
print(f" Needs compaction: {stats['needs_compaction']}")
|
||||
|
||||
# Compact
|
||||
compacted, result = compactor.compact(messages)
|
||||
|
||||
print(f"\n After compaction:")
|
||||
print(f" Messages: {len(messages)} -> {len(compacted)}")
|
||||
print(f" Tokens: {result.original_tokens} -> {result.compacted_tokens}")
|
||||
print(f" Saved: {result.tokens_saved} tokens")
|
||||
print(f" Compression: {result.compression_ratio:.1%}")
|
||||
|
||||
# ==========================================================================
|
||||
# Sliding Window Strategy
|
||||
# ==========================================================================
|
||||
print("\n--- Sliding Window Strategy ---")
|
||||
|
||||
compactor = ContextCompactor(
|
||||
max_tokens=50,
|
||||
strategy=CompactionStrategy.SLIDING,
|
||||
preserve_system=True,
|
||||
preserve_recent=3
|
||||
)
|
||||
|
||||
compacted, result = compactor.compact(messages)
|
||||
|
||||
print(f" Messages kept: {result.messages_kept}")
|
||||
print(f" Messages removed: {result.messages_removed}")
|
||||
print(f" Strategy: {result.strategy_used.value}")
|
||||
|
||||
# ==========================================================================
|
||||
# Summarize Strategy
|
||||
# ==========================================================================
|
||||
print("\n--- Summarize Strategy ---")
|
||||
|
||||
compactor = ContextCompactor(
|
||||
max_tokens=50,
|
||||
strategy=CompactionStrategy.SUMMARIZE,
|
||||
preserve_system=True,
|
||||
preserve_recent=2
|
||||
)
|
||||
|
||||
compacted, result = compactor.compact(messages)
|
||||
|
||||
print(f" Messages kept: {result.messages_kept}")
|
||||
print(f" Strategy: {result.strategy_used.value}")
|
||||
|
||||
# Show what messages remain
|
||||
print(f"\n Remaining message roles:")
|
||||
for msg in compacted:
|
||||
role = msg.get("role", "unknown")
|
||||
content_preview = msg.get("content", "")[:50]
|
||||
print(f" [{role}] {content_preview}...")
|
||||
|
||||
# ==========================================================================
|
||||
# No Compaction Needed
|
||||
# ==========================================================================
|
||||
print("\n--- No Compaction Needed ---")
|
||||
|
||||
compactor = ContextCompactor(
|
||||
max_tokens=10000, # High limit
|
||||
strategy=CompactionStrategy.TRUNCATE
|
||||
)
|
||||
|
||||
compacted, result = compactor.compact(messages)
|
||||
|
||||
print(f" Was compacted: {result.was_compacted}")
|
||||
print(f" Messages unchanged: {len(compacted) == len(messages)}")
|
||||
|
||||
# ==========================================================================
|
||||
# Result Serialization
|
||||
# ==========================================================================
|
||||
print("\n--- Result Serialization ---")
|
||||
|
||||
compactor = ContextCompactor(max_tokens=50)
|
||||
_, result = compactor.compact(messages)
|
||||
|
||||
data = result.to_dict()
|
||||
print(f" Original tokens: {data['original_tokens']}")
|
||||
print(f" Compacted tokens: {data['compacted_tokens']}")
|
||||
print(f" Compression ratio: {data['compression_ratio']:.2f}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Demo Complete!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic Hooks Example for PraisonAI Agents.
|
||||
|
||||
This example demonstrates how to use the hooks system to:
|
||||
1. Log tool calls before execution
|
||||
2. Block dangerous operations
|
||||
3. Add context to agent responses
|
||||
4. Monitor session lifecycle
|
||||
|
||||
Usage:
|
||||
python basic_hooks.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.hooks import (
|
||||
HookRegistry, HookRunner, HookEvent, HookResult,
|
||||
BeforeToolInput, AfterToolInput
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Create a hook registry
|
||||
registry = HookRegistry()
|
||||
|
||||
# ==========================================================================
|
||||
# Hook 1: Log all tool calls
|
||||
# ==========================================================================
|
||||
@registry.on(HookEvent.BEFORE_TOOL)
|
||||
def log_tool_calls(event_data: BeforeToolInput) -> HookResult:
|
||||
"""Log every tool call before execution."""
|
||||
print(f"\n📝 [LOG] Tool: {event_data.tool_name}")
|
||||
print(f" Input: {event_data.tool_input}")
|
||||
return HookResult.allow()
|
||||
|
||||
# ==========================================================================
|
||||
# Hook 2: Block dangerous file operations (using function logic)
|
||||
# ==========================================================================
|
||||
@registry.on(HookEvent.BEFORE_TOOL)
|
||||
def block_delete_operations(event_data: BeforeToolInput) -> HookResult:
|
||||
"""Block any delete or remove operations."""
|
||||
dangerous_keywords = ['delete', 'remove', 'rm', 'unlink']
|
||||
tool_lower = event_data.tool_name.lower()
|
||||
|
||||
if any(kw in tool_lower for kw in dangerous_keywords):
|
||||
print(f"\n🚫 [BLOCKED] Dangerous operation: {event_data.tool_name}")
|
||||
return HookResult.deny(
|
||||
f"Operation '{event_data.tool_name}' is blocked by security policy"
|
||||
)
|
||||
return HookResult.allow()
|
||||
|
||||
# ==========================================================================
|
||||
# Hook 3: Require confirmation for write operations
|
||||
# ==========================================================================
|
||||
@registry.on(HookEvent.BEFORE_TOOL)
|
||||
def confirm_write_operations(event_data: BeforeToolInput) -> HookResult:
|
||||
"""Request confirmation for write operations."""
|
||||
write_keywords = ['write', 'save', 'create', 'update']
|
||||
tool_lower = event_data.tool_name.lower()
|
||||
|
||||
if any(kw in tool_lower for kw in write_keywords):
|
||||
print(f"\n⚠️ [CONFIRM] Write operation: {event_data.tool_name}")
|
||||
# In a real scenario, you might prompt the user here
|
||||
# For this example, we'll auto-approve
|
||||
return HookResult.allow("Auto-approved for demo")
|
||||
return HookResult.allow()
|
||||
|
||||
# ==========================================================================
|
||||
# Hook 4: Add timing information after tool execution
|
||||
# ==========================================================================
|
||||
@registry.on(HookEvent.AFTER_TOOL)
|
||||
def log_tool_completion(event_data: AfterToolInput) -> HookResult:
|
||||
"""Log tool completion with timing."""
|
||||
print(f"\n✅ [DONE] Tool: {event_data.tool_name}")
|
||||
print(f" Duration: {event_data.execution_time_ms:.2f}ms")
|
||||
if event_data.tool_error:
|
||||
print(f" Error: {event_data.tool_error}")
|
||||
return HookResult.allow()
|
||||
|
||||
# ==========================================================================
|
||||
# Test the hooks
|
||||
# ==========================================================================
|
||||
print("=" * 60)
|
||||
print("Testing Hooks System")
|
||||
print("=" * 60)
|
||||
|
||||
runner = HookRunner(registry)
|
||||
|
||||
# Test 1: Normal tool call (should be logged and allowed)
|
||||
print("\n--- Test 1: Normal read operation ---")
|
||||
input1 = BeforeToolInput(
|
||||
session_id="test",
|
||||
cwd="/tmp",
|
||||
event_name="before_tool",
|
||||
timestamp="2024-01-01T00:00:00",
|
||||
tool_name="read_file",
|
||||
tool_input={"path": "/tmp/test.txt"}
|
||||
)
|
||||
results = asyncio.run(runner.execute(HookEvent.BEFORE_TOOL, input1))
|
||||
print(f"Result: {'ALLOWED' if not HookRunner.is_blocked(results) else 'BLOCKED'}")
|
||||
|
||||
# Test 2: Delete operation (should be blocked)
|
||||
print("\n--- Test 2: Delete operation ---")
|
||||
input2 = BeforeToolInput(
|
||||
session_id="test",
|
||||
cwd="/tmp",
|
||||
event_name="before_tool",
|
||||
timestamp="2024-01-01T00:00:00",
|
||||
tool_name="delete_file",
|
||||
tool_input={"path": "/important/data.txt"}
|
||||
)
|
||||
results = asyncio.run(runner.execute(HookEvent.BEFORE_TOOL, input2))
|
||||
print(f"Result: {'ALLOWED' if not HookRunner.is_blocked(results) else 'BLOCKED'}")
|
||||
if HookRunner.is_blocked(results):
|
||||
print(f"Reason: {HookRunner.get_blocking_reason(results)}")
|
||||
|
||||
# Test 3: Write operation (should be confirmed)
|
||||
print("\n--- Test 3: Write operation ---")
|
||||
input3 = BeforeToolInput(
|
||||
session_id="test",
|
||||
cwd="/tmp",
|
||||
event_name="before_tool",
|
||||
timestamp="2024-01-01T00:00:00",
|
||||
tool_name="write_file",
|
||||
tool_input={"path": "/tmp/output.txt", "content": "Hello"}
|
||||
)
|
||||
results = asyncio.run(runner.execute(HookEvent.BEFORE_TOOL, input3))
|
||||
print(f"Result: {'ALLOWED' if not HookRunner.is_blocked(results) else 'BLOCKED'}")
|
||||
|
||||
# Show registered hooks
|
||||
print("\n" + "=" * 60)
|
||||
print("Registered Hooks:")
|
||||
print("=" * 60)
|
||||
for event, hooks in registry.list_hooks().items():
|
||||
print(f"\n{event}:")
|
||||
for hook in hooks:
|
||||
print(f" - {hook['name']} (matcher: {hook['matcher'] or '*'})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Basic Agent Example
|
||||
* Demonstrates simple agent usage with OpenAI
|
||||
*/
|
||||
|
||||
import { Agent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const agent = new Agent({
|
||||
name: 'Assistant',
|
||||
instructions: 'You are a helpful assistant. Be concise and informative.',
|
||||
llm: 'openai/gpt-4o-mini'
|
||||
});
|
||||
|
||||
const response = await agent.chat('What is TypeScript?');
|
||||
console.log('Response:', response.text);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Context Agent Example
|
||||
* Demonstrates RAG-enabled agent with knowledge base
|
||||
*/
|
||||
|
||||
import { ContextAgent, createContextAgent, KnowledgeBase } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create knowledge base
|
||||
const kb = new KnowledgeBase();
|
||||
|
||||
// Add documents
|
||||
await kb.add({
|
||||
id: 'doc1',
|
||||
content: 'TypeScript is a strongly typed programming language that builds on JavaScript.',
|
||||
metadata: { topic: 'typescript' }
|
||||
});
|
||||
|
||||
await kb.add({
|
||||
id: 'doc2',
|
||||
content: 'TypeScript adds optional static typing and class-based object-oriented programming.',
|
||||
metadata: { topic: 'typescript' }
|
||||
});
|
||||
|
||||
await kb.add({
|
||||
id: 'doc3',
|
||||
content: 'PraisonAI is a framework for building AI agents in TypeScript and Python.',
|
||||
metadata: { topic: 'praisonai' }
|
||||
});
|
||||
|
||||
// Create context agent with knowledge base
|
||||
const agent = createContextAgent({
|
||||
name: 'KnowledgeAgent',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
knowledgeBase: kb,
|
||||
maxContextDocs: 3
|
||||
});
|
||||
|
||||
// Query with context
|
||||
const response = await agent.chat('What is TypeScript?');
|
||||
console.log('Response:', response.text);
|
||||
|
||||
// Check conversation history
|
||||
console.log('\nConversation history:', agent.getHistory().length, 'messages');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Image Agent Example
|
||||
* Demonstrates image analysis capabilities
|
||||
*/
|
||||
|
||||
import { ImageAgent, createImageAgent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const agent = createImageAgent({
|
||||
name: 'VisionAgent',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
verbose: true
|
||||
});
|
||||
|
||||
console.log('Image Agent created:', agent.name);
|
||||
|
||||
// Note: For actual image analysis, provide a real image URL
|
||||
// const analysis = await agent.analyze({
|
||||
// imageUrl: 'https://example.com/image.jpg',
|
||||
// prompt: 'Describe this image in detail'
|
||||
// });
|
||||
// console.log('Analysis:', analysis);
|
||||
|
||||
// Chat without image
|
||||
const response = await agent.chat('What types of images can you analyze?');
|
||||
console.log('Response:', response);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Multi-Agent Example
|
||||
* Demonstrates multiple agents working together
|
||||
*/
|
||||
|
||||
import { Agent, PraisonAIAgents, Task } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create specialized agents
|
||||
const researcher = new Agent({
|
||||
name: 'Researcher',
|
||||
role: 'Research Specialist',
|
||||
goal: 'Find accurate information on topics',
|
||||
instructions: 'You research topics thoroughly and provide factual information.'
|
||||
});
|
||||
|
||||
const writer = new Agent({
|
||||
name: 'Writer',
|
||||
role: 'Content Writer',
|
||||
goal: 'Create engaging content from research',
|
||||
instructions: 'You write clear, engaging content based on provided research.'
|
||||
});
|
||||
|
||||
// Create tasks
|
||||
const researchTask = new Task({
|
||||
description: 'Research the benefits of TypeScript over JavaScript',
|
||||
expectedOutput: 'A list of key benefits with explanations',
|
||||
agent: researcher
|
||||
});
|
||||
|
||||
const writeTask = new Task({
|
||||
description: 'Write a short article about TypeScript benefits',
|
||||
expectedOutput: 'A 200-word article',
|
||||
agent: writer
|
||||
});
|
||||
|
||||
// Run agents
|
||||
const agents = new PraisonAIAgents({
|
||||
agents: [researcher, writer],
|
||||
tasks: [researchTask, writeTask],
|
||||
process: 'sequential'
|
||||
});
|
||||
|
||||
const result = await agents.start();
|
||||
console.log('Result:', result);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Deep Research Agent Example
|
||||
* Demonstrates comprehensive research with citations
|
||||
*/
|
||||
|
||||
import { DeepResearchAgent, createDeepResearchAgent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const agent = createDeepResearchAgent({
|
||||
name: 'Scholar',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
maxIterations: 5,
|
||||
verbose: true
|
||||
});
|
||||
|
||||
console.log('Research Agent:', agent.name);
|
||||
|
||||
// Conduct research
|
||||
const result = await agent.research('What are the key benefits of TypeScript?');
|
||||
|
||||
console.log('\n=== Research Results ===');
|
||||
console.log('Answer:', result.answer.substring(0, 500) + '...');
|
||||
console.log('\nConfidence:', result.confidence);
|
||||
console.log('Reasoning steps:', result.reasoning.length);
|
||||
|
||||
// Show reasoning
|
||||
console.log('\n=== Reasoning Process ===');
|
||||
result.reasoning.forEach(step => {
|
||||
console.log(`Step ${step.step}: ${step.thought}`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Router Agent Example
|
||||
* Demonstrates routing requests to specialized agents
|
||||
*/
|
||||
|
||||
import { RouterAgent, createRouter, routeConditions, Agent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create specialized agents
|
||||
const mathAgent = {
|
||||
name: 'MathAgent',
|
||||
chat: async (input: string) => ({ text: `Math answer for: ${input}` })
|
||||
} as any;
|
||||
|
||||
const codeAgent = {
|
||||
name: 'CodeAgent',
|
||||
chat: async (input: string) => ({ text: `Code solution for: ${input}` })
|
||||
} as any;
|
||||
|
||||
const generalAgent = {
|
||||
name: 'GeneralAgent',
|
||||
chat: async (input: string) => ({ text: `General response: ${input}` })
|
||||
} as any;
|
||||
|
||||
// Create router with conditions
|
||||
const router = createRouter({
|
||||
name: 'SmartRouter',
|
||||
routes: [
|
||||
{
|
||||
agent: mathAgent,
|
||||
condition: routeConditions.keywords(['math', 'calculate', 'number', 'equation']),
|
||||
priority: 10
|
||||
},
|
||||
{
|
||||
agent: codeAgent,
|
||||
condition: routeConditions.keywords(['code', 'program', 'function', 'javascript']),
|
||||
priority: 10
|
||||
}
|
||||
],
|
||||
defaultAgent: generalAgent
|
||||
});
|
||||
|
||||
// Test routing
|
||||
const queries = [
|
||||
'Calculate 2 + 2',
|
||||
'Write a function to sort an array',
|
||||
'What is the weather today?'
|
||||
];
|
||||
|
||||
for (const query of queries) {
|
||||
const result = await router.route(query);
|
||||
console.log(`Query: "${query}"`);
|
||||
console.log(`Routed to: ${result?.agent.name || 'default'}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* AutoAgents Example
|
||||
* Demonstrates automatic agent generation from task descriptions
|
||||
*/
|
||||
|
||||
import { AutoAgents, createAutoAgents } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const auto = createAutoAgents({
|
||||
pattern: 'sequential',
|
||||
verbose: true
|
||||
});
|
||||
|
||||
// Analyze task complexity
|
||||
console.log('=== Complexity Analysis ===');
|
||||
const tasks = [
|
||||
'Write a hello world function',
|
||||
'Build a web scraper that extracts product data',
|
||||
'Create a multi-step data pipeline with validation, transformation, and multiple output formats'
|
||||
];
|
||||
|
||||
tasks.forEach(task => {
|
||||
const complexity = auto.analyzeComplexity(task);
|
||||
console.log(`"${task.substring(0, 40)}..." -> ${complexity}`);
|
||||
});
|
||||
|
||||
// Recommend patterns
|
||||
console.log('\n=== Pattern Recommendations ===');
|
||||
const patternTasks = [
|
||||
'Process items in parallel',
|
||||
'Route requests to different handlers',
|
||||
'Orchestrate multiple workers',
|
||||
'Evaluate and optimize results'
|
||||
];
|
||||
|
||||
patternTasks.forEach(task => {
|
||||
const pattern = auto.recommendPattern(task);
|
||||
console.log(`"${task}" -> ${pattern}`);
|
||||
});
|
||||
|
||||
// Generate agent configuration
|
||||
console.log('\n=== Generate Agent Config ===');
|
||||
console.log('Generating agents for: "Build a web scraper"');
|
||||
|
||||
const team = await auto.generate('Build a web scraper that extracts product prices from e-commerce sites');
|
||||
|
||||
console.log('\nGenerated Team:');
|
||||
console.log('Pattern:', team.pattern);
|
||||
console.log('Agents:', team.agents.length);
|
||||
team.agents.forEach(agent => {
|
||||
console.log(` - ${agent.name}: ${agent.role}`);
|
||||
});
|
||||
console.log('Tasks:', team.tasks.length);
|
||||
team.tasks.forEach(task => {
|
||||
console.log(` - ${task.description.substring(0, 50)}...`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Redis Database Example
|
||||
*/
|
||||
|
||||
import { createMemoryRedis } from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
// Use in-memory Redis for this example
|
||||
const redis = createMemoryRedis();
|
||||
await redis.connect();
|
||||
|
||||
console.log('Connected to Redis');
|
||||
|
||||
// Basic get/set
|
||||
await redis.set('user:123', { name: 'John', role: 'admin' });
|
||||
const user = await redis.get('user:123');
|
||||
console.log('User:', user);
|
||||
|
||||
// Set with TTL
|
||||
await redis.set('session:abc', { token: 'xyz' }, 60);
|
||||
const ttl = await redis.ttl('session:abc');
|
||||
console.log('Session TTL:', ttl, 'seconds');
|
||||
|
||||
// Hash operations
|
||||
await redis.hset('profile:123', 'email', '[email protected]');
|
||||
await redis.hset('profile:123', 'phone', '555-1234');
|
||||
const profile = await redis.hgetall('profile:123');
|
||||
console.log('Profile:', profile);
|
||||
|
||||
// List operations
|
||||
await redis.rpush('queue:tasks', { id: 1, type: 'process' });
|
||||
await redis.rpush('queue:tasks', { id: 2, type: 'notify' });
|
||||
const tasks = await redis.lrange('queue:tasks', 0, -1);
|
||||
console.log('Tasks:', tasks);
|
||||
|
||||
// Pub/Sub (memory only)
|
||||
await redis.subscribe('events', (message) => {
|
||||
console.log('Received event:', message);
|
||||
});
|
||||
await redis.publish('events', JSON.stringify({ type: 'user_joined' }));
|
||||
|
||||
// Cleanup
|
||||
await redis.disconnect();
|
||||
console.log('Done!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Evaluation Framework Example
|
||||
* Demonstrates accuracy, performance, and reliability evaluation
|
||||
*/
|
||||
|
||||
import { accuracyEval, performanceEval, reliabilityEval, EvalSuite } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Accuracy Evaluation ===');
|
||||
const accuracyResult = await accuracyEval({
|
||||
input: 'What is 2 + 2?',
|
||||
expectedOutput: '4',
|
||||
actualOutput: 'The answer is 4'
|
||||
});
|
||||
console.log('Passed:', accuracyResult.passed);
|
||||
console.log('Score:', accuracyResult.score);
|
||||
console.log('Duration:', accuracyResult.duration, 'ms');
|
||||
|
||||
console.log('\n=== Performance Evaluation ===');
|
||||
const perfResult = await performanceEval({
|
||||
func: async () => {
|
||||
// Simulate some work
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
return 'done';
|
||||
},
|
||||
iterations: 5,
|
||||
warmupRuns: 1
|
||||
});
|
||||
console.log('Iterations:', perfResult.times.length);
|
||||
console.log('Avg time:', perfResult.avgTime.toFixed(2), 'ms');
|
||||
console.log('Min time:', perfResult.minTime.toFixed(2), 'ms');
|
||||
console.log('Max time:', perfResult.maxTime.toFixed(2), 'ms');
|
||||
|
||||
console.log('\n=== Reliability Evaluation ===');
|
||||
const reliabilityResult = await reliabilityEval({
|
||||
expectedToolCalls: ['search', 'calculate'],
|
||||
actualToolCalls: ['search', 'calculate']
|
||||
});
|
||||
console.log('Passed:', reliabilityResult.passed);
|
||||
console.log('Score:', reliabilityResult.score);
|
||||
|
||||
console.log('\n=== Eval Suite ===');
|
||||
const suite = new EvalSuite();
|
||||
|
||||
await suite.runAccuracy('test1', {
|
||||
input: 'Hello',
|
||||
expectedOutput: 'Hi',
|
||||
actualOutput: 'Hi there!'
|
||||
});
|
||||
|
||||
await suite.runAccuracy('test2', {
|
||||
input: 'Goodbye',
|
||||
expectedOutput: 'Bye',
|
||||
actualOutput: 'Bye!'
|
||||
});
|
||||
|
||||
const summary = suite.getSummary();
|
||||
console.log('Total tests:', summary.total);
|
||||
console.log('Passed:', summary.passed);
|
||||
console.log('Failed:', summary.failed);
|
||||
console.log('Pass rate:', (summary.passRate * 100).toFixed(1) + '%');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Content Guardrails Example
|
||||
* Demonstrates input/output validation
|
||||
*/
|
||||
|
||||
import { guardrail, GuardrailManager, builtinGuardrails, LLMGuardrail } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create custom guardrail
|
||||
const noSpamGuardrail = guardrail({
|
||||
name: 'no_spam',
|
||||
check: (content) => {
|
||||
const spamWords = ['buy now', 'free money', 'click here'];
|
||||
const hasSpam = spamWords.some(word =>
|
||||
content.toLowerCase().includes(word)
|
||||
);
|
||||
return {
|
||||
status: hasSpam ? 'failed' : 'passed',
|
||||
message: hasSpam ? 'Content contains spam' : undefined
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Use built-in guardrails
|
||||
const maxLength = builtinGuardrails.maxLength(500);
|
||||
const minLength = builtinGuardrails.minLength(10);
|
||||
const blockedWords = builtinGuardrails.blockedWords(['badword1', 'badword2']);
|
||||
|
||||
// Create manager
|
||||
const manager = new GuardrailManager();
|
||||
manager.add(noSpamGuardrail);
|
||||
manager.add(maxLength);
|
||||
manager.add(minLength);
|
||||
manager.add(blockedWords);
|
||||
|
||||
// Test content
|
||||
const testCases = [
|
||||
'This is a normal, helpful response about programming.',
|
||||
'BUY NOW! Free money if you click here!',
|
||||
'Short',
|
||||
'This is a valid response that provides useful information about TypeScript and its benefits for developers.'
|
||||
];
|
||||
|
||||
console.log('=== Guardrail Tests ===\n');
|
||||
|
||||
for (const content of testCases) {
|
||||
console.log(`Content: "${content.substring(0, 40)}..."`);
|
||||
const result = await manager.runAll(content);
|
||||
console.log(` Passed: ${result.passed}`);
|
||||
if (!result.passed) {
|
||||
result.results
|
||||
.filter(r => r.status === 'failed')
|
||||
.forEach(r => console.log(` Failed: ${r.message}`));
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// LLM Guardrail example
|
||||
console.log('=== LLM Guardrail ===');
|
||||
const llmGuard = new LLMGuardrail({
|
||||
name: 'quality_check',
|
||||
criteria: 'Content must be helpful and professional'
|
||||
});
|
||||
console.log('LLM Guardrail created:', llmGuard.name);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Knowledge Base (RAG) Example
|
||||
* Demonstrates retrieval-augmented generation
|
||||
*/
|
||||
|
||||
import { KnowledgeBase, createKnowledgeBase, Chunking } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create knowledge base
|
||||
const kb = createKnowledgeBase();
|
||||
|
||||
// Add documents
|
||||
const documents = [
|
||||
{
|
||||
id: 'ts-intro',
|
||||
content: 'TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.',
|
||||
metadata: { category: 'introduction', language: 'typescript' }
|
||||
},
|
||||
{
|
||||
id: 'ts-types',
|
||||
content: 'TypeScript adds optional static typing and class-based object-oriented programming to the language.',
|
||||
metadata: { category: 'features', language: 'typescript' }
|
||||
},
|
||||
{
|
||||
id: 'ts-benefits',
|
||||
content: 'Benefits of TypeScript include catching errors early, better IDE support, and improved code maintainability.',
|
||||
metadata: { category: 'benefits', language: 'typescript' }
|
||||
},
|
||||
{
|
||||
id: 'js-intro',
|
||||
content: 'JavaScript is a lightweight, interpreted programming language with first-class functions.',
|
||||
metadata: { category: 'introduction', language: 'javascript' }
|
||||
}
|
||||
];
|
||||
|
||||
for (const doc of documents) {
|
||||
await kb.add(doc);
|
||||
}
|
||||
|
||||
console.log('Documents added:', kb.size);
|
||||
|
||||
// Search
|
||||
console.log('\n=== Search: "TypeScript benefits" ===');
|
||||
const results = await kb.search('TypeScript benefits', 3);
|
||||
results.forEach(r => {
|
||||
console.log(`Score: ${r.score.toFixed(2)} - ${r.document.content.substring(0, 50)}...`);
|
||||
});
|
||||
|
||||
// Build context
|
||||
console.log('\n=== Context for LLM ===');
|
||||
const context = kb.buildContext(results);
|
||||
console.log(context);
|
||||
|
||||
// Chunking example
|
||||
console.log('\n=== Chunking Example ===');
|
||||
const chunker = new Chunking({ chunkSize: 100, overlap: 20 });
|
||||
const longText = 'This is a long document. '.repeat(20);
|
||||
const chunks = chunker.chunk(longText);
|
||||
console.log(`Chunked into ${chunks.length} pieces`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Conversation Memory Example
|
||||
* Demonstrates memory management for conversations
|
||||
*/
|
||||
|
||||
import { Memory, createMemory } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const memory = createMemory({
|
||||
maxEntries: 100,
|
||||
maxTokens: 10000
|
||||
});
|
||||
|
||||
// Simulate a conversation
|
||||
await memory.add('What is TypeScript?', 'user');
|
||||
await memory.add('TypeScript is a strongly typed programming language that builds on JavaScript.', 'assistant');
|
||||
await memory.add('What are its main benefits?', 'user');
|
||||
await memory.add('Key benefits include type safety, better IDE support, and easier refactoring.', 'assistant');
|
||||
await memory.add('Can you give an example?', 'user');
|
||||
|
||||
console.log('Memory size:', memory.size, 'entries\n');
|
||||
|
||||
// Get recent messages
|
||||
console.log('=== Recent Messages ===');
|
||||
const recent = memory.getRecent(3);
|
||||
recent.forEach(entry => {
|
||||
console.log(`[${entry.role}]: ${entry.content.substring(0, 50)}...`);
|
||||
});
|
||||
|
||||
// Search memory
|
||||
console.log('\n=== Search Results for "TypeScript" ===');
|
||||
const results = await memory.search('TypeScript');
|
||||
results.forEach(r => {
|
||||
console.log(`Score: ${r.score.toFixed(2)} - ${r.entry.content.substring(0, 40)}...`);
|
||||
});
|
||||
|
||||
// Build context
|
||||
console.log('\n=== Full Context ===');
|
||||
console.log(memory.buildContext());
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Observability Example
|
||||
* Demonstrates tracing and monitoring
|
||||
*/
|
||||
|
||||
import { MemoryObservabilityAdapter, ConsoleObservabilityAdapter } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Memory adapter - stores traces in memory
|
||||
const memoryAdapter = new MemoryObservabilityAdapter();
|
||||
|
||||
// Start a trace
|
||||
const trace = memoryAdapter.startTrace('agent-execution');
|
||||
console.log('Trace started:', trace.traceId);
|
||||
|
||||
// Create spans for different operations
|
||||
const llmSpan = trace.startSpan('llm-call', 'llm');
|
||||
llmSpan.setAttribute('model', 'gpt-4o-mini');
|
||||
llmSpan.setAttribute('tokens', 150);
|
||||
await new Promise(r => setTimeout(r, 100)); // Simulate work
|
||||
llmSpan.end();
|
||||
|
||||
const toolSpan = trace.startSpan('tool-execution', 'tool');
|
||||
toolSpan.setAttribute('tool', 'calculator');
|
||||
toolSpan.addEvent('tool-started', { input: '2+2' });
|
||||
await new Promise(r => setTimeout(r, 50)); // Simulate work
|
||||
toolSpan.addEvent('tool-completed', { output: '4' });
|
||||
toolSpan.end();
|
||||
|
||||
// End trace
|
||||
trace.end();
|
||||
|
||||
// Retrieve trace data
|
||||
const traceData = memoryAdapter.getTrace(trace.traceId);
|
||||
console.log('\n=== Trace Data ===');
|
||||
console.log('Trace ID:', traceData?.traceId);
|
||||
console.log('Spans:', traceData?.spans.length);
|
||||
console.log('Duration:', traceData?.endTime! - traceData?.startTime!, 'ms');
|
||||
|
||||
// Show spans
|
||||
console.log('\n=== Spans ===');
|
||||
traceData?.spans.forEach(span => {
|
||||
console.log(`- ${span.name} (${span.kind}): ${span.endTime! - span.startTime}ms`);
|
||||
if (span.attributes) {
|
||||
Object.entries(span.attributes).forEach(([k, v]) => {
|
||||
console.log(` ${k}: ${v}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Console adapter example
|
||||
console.log('\n=== Console Adapter ===');
|
||||
const consoleAdapter = new ConsoleObservabilityAdapter();
|
||||
const trace2 = consoleAdapter.startTrace('demo-trace');
|
||||
const span = trace2.startSpan('demo-span', 'custom');
|
||||
span.end();
|
||||
trace2.end();
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Planning System Example
|
||||
* Demonstrates plans, steps, and todo lists
|
||||
*/
|
||||
|
||||
import { Plan, PlanStep, TodoList, TodoItem, PlanStorage, createPlan, createTodoList } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create a project plan
|
||||
console.log('=== Project Plan ===');
|
||||
const plan = createPlan({
|
||||
name: 'Build AI Agent',
|
||||
description: 'Create a fully functional AI agent'
|
||||
});
|
||||
|
||||
plan.addStep(new PlanStep({ description: 'Design agent architecture' }));
|
||||
plan.addStep(new PlanStep({ description: 'Implement core functionality' }));
|
||||
plan.addStep(new PlanStep({ description: 'Add tool support' }));
|
||||
plan.addStep(new PlanStep({ description: 'Write tests' }));
|
||||
plan.addStep(new PlanStep({ description: 'Deploy to production' }));
|
||||
|
||||
console.log('Plan:', plan.name);
|
||||
console.log('Steps:', plan.steps.length);
|
||||
|
||||
// Execute steps
|
||||
plan.start();
|
||||
plan.steps[0].start();
|
||||
plan.steps[0].complete();
|
||||
plan.steps[1].start();
|
||||
plan.steps[1].complete();
|
||||
|
||||
const progress = plan.getProgress();
|
||||
console.log(`Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`);
|
||||
|
||||
// Create todo list
|
||||
console.log('\n=== Todo List ===');
|
||||
const todos = createTodoList('Sprint Tasks');
|
||||
|
||||
todos.add(new TodoItem({ content: 'Review PR #123', priority: 'high' }));
|
||||
todos.add(new TodoItem({ content: 'Fix bug in router', priority: 'high' }));
|
||||
todos.add(new TodoItem({ content: 'Update documentation', priority: 'medium' }));
|
||||
todos.add(new TodoItem({ content: 'Refactor tests', priority: 'low' }));
|
||||
|
||||
console.log('Total items:', todos.items.length);
|
||||
console.log('High priority:', todos.getByPriority('high').length);
|
||||
|
||||
// Complete some items
|
||||
todos.items[0].complete();
|
||||
todos.items[1].start();
|
||||
|
||||
console.log('Pending:', todos.getPending().length);
|
||||
console.log('Completed:', todos.getCompleted().length);
|
||||
|
||||
const todoProgress = todos.getProgress();
|
||||
console.log(`Progress: ${todoProgress.completed}/${todoProgress.total} (${todoProgress.percentage}%)`);
|
||||
|
||||
// Use storage
|
||||
console.log('\n=== Plan Storage ===');
|
||||
const storage = new PlanStorage();
|
||||
storage.savePlan(plan);
|
||||
storage.saveTodoList(todos);
|
||||
|
||||
console.log('Saved plans:', storage.listPlans().length);
|
||||
console.log('Saved todo lists:', storage.listTodoLists().length);
|
||||
|
||||
// Retrieve
|
||||
const retrieved = storage.getPlan(plan.id);
|
||||
console.log('Retrieved plan:', retrieved?.name);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Skills System Example
|
||||
* Demonstrates skill management and parsing
|
||||
*/
|
||||
|
||||
import { SkillManager, parseSkillFile, createSkillManager } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Parse a skill file
|
||||
const skillContent = `---
|
||||
name: code-review
|
||||
description: Reviews code for best practices and potential issues
|
||||
license: MIT
|
||||
metadata:
|
||||
author: praisonai
|
||||
version: "1.0"
|
||||
allowed-tools: Read Grep
|
||||
---
|
||||
|
||||
# Code Review Skill
|
||||
|
||||
When reviewing code:
|
||||
1. Check for syntax errors
|
||||
2. Look for security vulnerabilities
|
||||
3. Suggest performance improvements
|
||||
4. Ensure proper error handling
|
||||
`;
|
||||
|
||||
console.log('=== Parse Skill File ===');
|
||||
const skill = parseSkillFile(skillContent);
|
||||
console.log('Name:', skill.metadata.name);
|
||||
console.log('Description:', skill.metadata.description);
|
||||
console.log('License:', skill.metadata.license);
|
||||
console.log('Instructions preview:', skill.instructions.substring(0, 100) + '...');
|
||||
|
||||
// Create skill manager
|
||||
console.log('\n=== Skill Manager ===');
|
||||
const manager = createSkillManager({ paths: [] });
|
||||
|
||||
// Register skills manually
|
||||
manager.register({
|
||||
metadata: {
|
||||
name: 'typescript-expert',
|
||||
description: 'Expert knowledge in TypeScript development'
|
||||
},
|
||||
instructions: 'You are a TypeScript expert. Help with type definitions, generics, and best practices.'
|
||||
});
|
||||
|
||||
manager.register({
|
||||
metadata: {
|
||||
name: 'api-designer',
|
||||
description: 'Designs RESTful APIs following best practices'
|
||||
},
|
||||
instructions: 'You design clean, RESTful APIs with proper versioning and documentation.'
|
||||
});
|
||||
|
||||
// List skills
|
||||
console.log('Registered skills:');
|
||||
manager.list().forEach(s => {
|
||||
console.log(`- ${s.metadata.name}: ${s.metadata.description}`);
|
||||
});
|
||||
|
||||
// Get specific skill
|
||||
const tsSkill = manager.get('typescript-expert');
|
||||
console.log('\n=== TypeScript Expert Skill ===');
|
||||
console.log('Instructions:', tsSkill?.instructions);
|
||||
|
||||
// Generate prompt
|
||||
console.log('\n=== Generated Prompt ===');
|
||||
const prompt = manager.generatePrompt(['typescript-expert']);
|
||||
console.log(prompt.substring(0, 200) + '...');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Basic Tools Example
|
||||
* Demonstrates tool creation and usage
|
||||
*/
|
||||
|
||||
import { tool, ToolRegistry, getRegistry } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create tools
|
||||
const calculator = tool({
|
||||
name: 'calculator',
|
||||
description: 'Perform basic math calculations',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
expression: { type: 'string', description: 'Math expression to evaluate' }
|
||||
},
|
||||
required: ['expression']
|
||||
},
|
||||
execute: async ({ expression }) => {
|
||||
try {
|
||||
const result = eval(expression);
|
||||
return `Result: ${result}`;
|
||||
} catch (e) {
|
||||
return 'Error: Invalid expression';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const greeter = tool({
|
||||
name: 'greeter',
|
||||
description: 'Generate a greeting',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name to greet' }
|
||||
},
|
||||
required: ['name']
|
||||
},
|
||||
execute: async ({ name }) => `Hello, ${name}! Welcome to PraisonAI.`
|
||||
});
|
||||
|
||||
// Create registry and register tools
|
||||
const registry = new ToolRegistry();
|
||||
registry.register(calculator);
|
||||
registry.register(greeter);
|
||||
|
||||
console.log('Registered tools:', registry.list().map(t => t.name));
|
||||
|
||||
// Execute tools
|
||||
const calcResult = await calculator.execute({ expression: '10 * 5 + 2' });
|
||||
console.log('\nCalculator:', calcResult);
|
||||
|
||||
const greetResult = await greeter.execute({ name: 'Developer' });
|
||||
console.log('Greeter:', greetResult);
|
||||
|
||||
// Get OpenAI format
|
||||
console.log('\nOpenAI tool format:', JSON.stringify(calculator.toOpenAITool(), null, 2));
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Custom Tool Example
|
||||
* Demonstrates creating custom tools using BaseTool class (plugin pattern)
|
||||
*/
|
||||
|
||||
import { BaseTool, createTool, tool, ToolRegistry } from 'praisonai';
|
||||
|
||||
// Method 1: Extend BaseTool class (recommended for complex tools)
|
||||
class WeatherTool extends BaseTool<{ location: string; units?: string }, { temp: number; condition: string }> {
|
||||
name = 'get_weather';
|
||||
description = 'Get current weather for a location';
|
||||
parameters = {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
location: { type: 'string', description: 'City name' },
|
||||
units: { type: 'string', description: 'celsius or fahrenheit', default: 'celsius' }
|
||||
},
|
||||
required: ['location']
|
||||
};
|
||||
|
||||
async run(params: { location: string; units?: string }): Promise<{ temp: number; condition: string }> {
|
||||
// Simulate API call
|
||||
console.log(`Getting weather for ${params.location} in ${params.units || 'celsius'}`);
|
||||
return { temp: 22, condition: 'sunny' };
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Use createTool function (quick inline tools)
|
||||
const calculatorTool = createTool({
|
||||
name: 'calculator',
|
||||
description: 'Evaluate a math expression',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
expression: { type: 'string', description: 'Math expression to evaluate' }
|
||||
},
|
||||
required: ['expression']
|
||||
},
|
||||
run: (params: { expression: string }) => {
|
||||
try {
|
||||
return eval(params.expression);
|
||||
} catch {
|
||||
return 'Error: Invalid expression';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Method 3: Use tool() function with config object
|
||||
const greeterTool = tool({
|
||||
name: 'greeter',
|
||||
description: 'Generate a personalized greeting',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name to greet' },
|
||||
style: { type: 'string', description: 'Greeting style: formal or casual' }
|
||||
},
|
||||
required: ['name']
|
||||
},
|
||||
execute: async (params: { name: string; style?: string }) => {
|
||||
if (params.style === 'formal') {
|
||||
return `Good day, ${params.name}. How may I assist you?`;
|
||||
}
|
||||
return `Hey ${params.name}! What's up?`;
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
console.log('=== Custom Tool Examples ===\n');
|
||||
|
||||
// Test WeatherTool (class-based)
|
||||
console.log('--- WeatherTool (class-based) ---');
|
||||
const weather = new WeatherTool();
|
||||
console.log('Name:', weather.name);
|
||||
console.log('Description:', weather.description);
|
||||
|
||||
const weatherResult = await weather.run({ location: 'New York' });
|
||||
console.log('Result:', weatherResult);
|
||||
|
||||
// Safe execution with error handling
|
||||
const safeResult = await weather.safeRun({ location: 'London' });
|
||||
console.log('Safe result:', safeResult);
|
||||
|
||||
// Get OpenAI schema
|
||||
console.log('OpenAI Schema:', JSON.stringify(weather.getSchema(), null, 2));
|
||||
|
||||
// Test calculatorTool (createTool)
|
||||
console.log('\n--- Calculator Tool (createTool) ---');
|
||||
const calcResult = await calculatorTool.run({ expression: '10 * 5 + 2' });
|
||||
console.log('10 * 5 + 2 =', calcResult);
|
||||
|
||||
// Test greeterTool (tool function)
|
||||
console.log('\n--- Greeter Tool (tool function) ---');
|
||||
const greeting1 = await greeterTool.execute({ name: 'Alice' });
|
||||
console.log('Casual:', greeting1);
|
||||
|
||||
const greeting2 = await greeterTool.execute({ name: 'Mr. Smith', style: 'formal' });
|
||||
console.log('Formal:', greeting2);
|
||||
|
||||
// Register tools in registry
|
||||
console.log('\n--- Tool Registry ---');
|
||||
const registry = new ToolRegistry();
|
||||
registry.register(greeterTool);
|
||||
|
||||
console.log('Registered tools:', registry.list().map(t => t.name));
|
||||
console.log('OpenAI tools format:', registry.toOpenAITools().length, 'tools');
|
||||
|
||||
// Validate tool
|
||||
console.log('\n--- Validation ---');
|
||||
console.log('WeatherTool valid:', weather.validate());
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Pinecone Vector Store Example
|
||||
*/
|
||||
|
||||
import { createPineconeStore } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const pinecone = createPineconeStore({
|
||||
apiKey: process.env.PINECONE_API_KEY!
|
||||
});
|
||||
|
||||
const indexName = 'test-index';
|
||||
|
||||
// Create index
|
||||
console.log('Creating index...');
|
||||
await pinecone.createIndex({
|
||||
indexName,
|
||||
dimension: 3,
|
||||
metric: 'cosine'
|
||||
});
|
||||
|
||||
// List indexes
|
||||
const indexes = await pinecone.listIndexes();
|
||||
console.log('Indexes:', indexes);
|
||||
|
||||
// Upsert vectors
|
||||
console.log('Upserting vectors...');
|
||||
await pinecone.upsert({
|
||||
indexName,
|
||||
vectors: [
|
||||
{ id: 'vec-1', vector: [0.1, 0.2, 0.3], metadata: { category: 'A' } },
|
||||
{ id: 'vec-2', vector: [0.4, 0.5, 0.6], metadata: { category: 'B' } },
|
||||
{ id: 'vec-3', vector: [0.7, 0.8, 0.9], metadata: { category: 'A' } }
|
||||
]
|
||||
});
|
||||
|
||||
// Query
|
||||
console.log('Querying...');
|
||||
const results = await pinecone.query({
|
||||
indexName,
|
||||
vector: [0.1, 0.2, 0.3],
|
||||
topK: 2,
|
||||
includeMetadata: true
|
||||
});
|
||||
|
||||
console.log('Results:');
|
||||
for (const r of results) {
|
||||
console.log(` ${r.id}: score=${r.score.toFixed(4)}, metadata=${JSON.stringify(r.metadata)}`);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
console.log('Deleting index...');
|
||||
await pinecone.deleteIndex(indexName);
|
||||
console.log('Done!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Voice/TTS Example
|
||||
*/
|
||||
|
||||
import { createOpenAIVoice } from '../../../src/praisonai-ts/src';
|
||||
import { writeFileSync } from 'fs';
|
||||
|
||||
async function main() {
|
||||
const voice = createOpenAIVoice({
|
||||
apiKey: process.env.OPENAI_API_KEY
|
||||
});
|
||||
|
||||
// Check available voices
|
||||
const speakers = await voice.getSpeakers();
|
||||
console.log('Available voices:', speakers.map(s => s.name).join(', '));
|
||||
|
||||
// Generate speech
|
||||
console.log('Generating speech...');
|
||||
const audio = await voice.speak('Hello! Welcome to PraisonAI TypeScript SDK.', {
|
||||
voice: 'nova',
|
||||
speed: 1.0,
|
||||
format: 'mp3'
|
||||
});
|
||||
|
||||
// Save to file
|
||||
writeFileSync('output.mp3', audio);
|
||||
console.log('Audio saved to output.mp3');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Basic Workflow Example
|
||||
* Demonstrates sequential workflow execution
|
||||
*/
|
||||
|
||||
import { Workflow, parallel, route, loop } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create a simple workflow
|
||||
const workflow = new Workflow<string, string>('DataProcessor')
|
||||
.step('validate', async (input) => {
|
||||
console.log('Step 1: Validating input...');
|
||||
return input.trim();
|
||||
})
|
||||
.step('transform', async (input) => {
|
||||
console.log('Step 2: Transforming...');
|
||||
return input.toUpperCase();
|
||||
})
|
||||
.step('format', async (input) => {
|
||||
console.log('Step 3: Formatting...');
|
||||
return `[PROCESSED] ${input}`;
|
||||
});
|
||||
|
||||
const result = await workflow.run(' hello world ');
|
||||
console.log('\nFinal output:', result.output);
|
||||
console.log('Steps executed:', result.steps.length);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Conditional Workflow Example
|
||||
* Demonstrates routing based on conditions
|
||||
*/
|
||||
|
||||
import { route } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const processRequest = async (type: string) => {
|
||||
console.log(`Processing request type: ${type}`);
|
||||
|
||||
const result = await route([
|
||||
{
|
||||
condition: () => type === 'urgent',
|
||||
execute: async () => {
|
||||
console.log(' -> Urgent handler activated');
|
||||
return 'Processed urgently';
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: () => type === 'normal',
|
||||
execute: async () => {
|
||||
console.log(' -> Normal handler activated');
|
||||
return 'Processed normally';
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: () => true, // Default
|
||||
execute: async () => {
|
||||
console.log(' -> Default handler activated');
|
||||
return 'Processed with default handler';
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Test different request types
|
||||
const types = ['urgent', 'normal', 'unknown'];
|
||||
|
||||
for (const type of types) {
|
||||
const result = await processRequest(type);
|
||||
console.log(`Result: ${result}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Parallel Workflow Example
|
||||
* Demonstrates parallel execution of tasks
|
||||
*/
|
||||
|
||||
import { parallel } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
console.log('Starting parallel tasks...\n');
|
||||
|
||||
const results = await parallel([
|
||||
async () => {
|
||||
console.log('Task 1: Starting...');
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log('Task 1: Complete');
|
||||
return 'Result 1';
|
||||
},
|
||||
async () => {
|
||||
console.log('Task 2: Starting...');
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
console.log('Task 2: Complete');
|
||||
return 'Result 2';
|
||||
},
|
||||
async () => {
|
||||
console.log('Task 3: Starting...');
|
||||
await new Promise(r => setTimeout(r, 750));
|
||||
console.log('Task 3: Complete');
|
||||
return 'Result 3';
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\nAll tasks complete!');
|
||||
console.log('Results:', results);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Basic Middleware Example - PraisonAI Agents
|
||||
|
||||
Demonstrates before/after hooks and wrap decorators for model and tool calls.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, tool
|
||||
from praisonaiagents.hooks import (
|
||||
before_model, after_model, wrap_model_call,
|
||||
before_tool, after_tool, wrap_tool_call,
|
||||
InvocationContext, ModelRequest, ModelResponse
|
||||
)
|
||||
|
||||
# Simple tool for demonstration
|
||||
@tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a city."""
|
||||
return f"Sunny, 22°C in {city}"
|
||||
|
||||
# Before model hook - adds context
|
||||
@before_model
|
||||
def add_context(request: ModelRequest) -> ModelRequest:
|
||||
print(f"[before_model] Adding context to request")
|
||||
return request
|
||||
|
||||
# After model hook - logs response
|
||||
@after_model
|
||||
def log_response(response: ModelResponse) -> ModelResponse:
|
||||
print(f"[after_model] Response received")
|
||||
return response
|
||||
|
||||
# Wrap tool call - retry on error
|
||||
@wrap_tool_call
|
||||
def retry_on_error(tool_call, call_next):
|
||||
print(f"[wrap_tool_call] Executing tool")
|
||||
try:
|
||||
return call_next(tool_call)
|
||||
except Exception as e:
|
||||
print(f"[wrap_tool_call] Retrying after error: {e}")
|
||||
return call_next(tool_call)
|
||||
|
||||
# Create agent with hooks
|
||||
agent = Agent(
|
||||
name="WeatherBot",
|
||||
instructions="You help with weather queries.",
|
||||
tools=[get_weather],
|
||||
hooks=[add_context, log_response, retry_on_error]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test the tool directly
|
||||
result = get_weather("London")
|
||||
print(f"Weather: {result}")
|
||||
|
||||
print("\n✓ Middleware example complete")
|
||||
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Configurable Model Example - PraisonAI Agents
|
||||
|
||||
Demonstrates runtime model switching without recreating the agent.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Create agent with configurable model enabled
|
||||
agent = Agent(
|
||||
name="FlexBot",
|
||||
instructions="You are a helpful assistant.",
|
||||
llm="gpt-4o-mini",
|
||||
llm_config={"configurable": True}
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Default model call
|
||||
print("Using default model (gpt-4o-mini)...")
|
||||
# response = agent.chat("Say hello in 5 words")
|
||||
|
||||
# Override model per-call
|
||||
print("\nOverriding to different model...")
|
||||
# response = agent.chat("Say hello in 5 words", config={"model": "gpt-4o"})
|
||||
|
||||
# Override temperature
|
||||
print("\nOverriding temperature...")
|
||||
# response = agent.chat("Say hello creatively", config={"temperature": 0.9})
|
||||
|
||||
print("\n✓ Configurable model example complete")
|
||||
print("Note: Uncomment agent.chat() calls to run with API key")
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Injected State Example - PraisonAI Agents
|
||||
|
||||
Demonstrates injecting agent state into tools without exposing it in the schema.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, tool
|
||||
from praisonaiagents.tools import Injected
|
||||
from praisonaiagents.tools.injected import AgentState, with_injection_context
|
||||
|
||||
# Tool with injected state - state param is NOT in the public schema
|
||||
@tool
|
||||
def show_context(query: str, state: Injected[dict]) -> str:
|
||||
"""Show the current agent context."""
|
||||
session_id = state.get('session_id', 'unknown')
|
||||
agent_id = state.get('agent_id', 'unknown')
|
||||
return f"Query: {query}, Session: {session_id}, Agent: {agent_id}"
|
||||
|
||||
# Create agent with the tool
|
||||
agent = Agent(
|
||||
name="ContextBot",
|
||||
instructions="You help show context information.",
|
||||
tools=[show_context],
|
||||
session_id="my-session-123"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Verify injected param is not in schema
|
||||
schema = show_context.get_schema()
|
||||
params = schema['function']['parameters']['properties']
|
||||
print(f"Schema params: {list(params.keys())}")
|
||||
assert 'state' not in params, "state should NOT be in schema"
|
||||
print("✓ 'state' correctly excluded from schema")
|
||||
|
||||
# Test with manual injection context
|
||||
mock_state = AgentState(
|
||||
agent_id="test-agent",
|
||||
run_id="run-1",
|
||||
session_id="session-abc"
|
||||
)
|
||||
|
||||
with with_injection_context(mock_state):
|
||||
result = show_context(query="hello")
|
||||
print(f"Result: {result}")
|
||||
|
||||
# Test via agent.execute_tool
|
||||
result = agent.execute_tool("show_context", {"query": "test"})
|
||||
print(f"Agent result: {result}")
|
||||
|
||||
print("\n✓ Injected state example complete")
|
||||
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Rate Limiter Example - PraisonAI Agents
|
||||
|
||||
Demonstrates token bucket rate limiting for LLM API calls.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.llm import RateLimiter
|
||||
|
||||
# Create rate limiter: 60 requests per minute
|
||||
limiter = RateLimiter(requests_per_minute=60, burst=5)
|
||||
|
||||
# Create agent with rate limiter
|
||||
agent = Agent(
|
||||
name="RateLimitedBot",
|
||||
instructions="You are a helpful assistant.",
|
||||
rate_limiter=limiter
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Rate limiter: {limiter}")
|
||||
print(f"Available tokens: {limiter.available_tokens}")
|
||||
|
||||
# Demonstrate rate limiting
|
||||
for i in range(3):
|
||||
limiter.acquire()
|
||||
print(f"Request {i+1} acquired, tokens left: {limiter.available_tokens:.1f}")
|
||||
|
||||
print("\n✓ Rate limiter example complete")
|
||||
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
Multi-Agent Persistence Example
|
||||
|
||||
Demonstrates how multiple agents can share or isolate conversation
|
||||
history using the persistence layer.
|
||||
"""
|
||||
|
||||
from praisonai.persistence.factory import create_conversation_store, create_state_store
|
||||
from praisonai.persistence.conversation.base import ConversationSession, ConversationMessage
|
||||
import uuid
|
||||
|
||||
|
||||
def main():
|
||||
# Create stores
|
||||
conv_store = create_conversation_store("sqlite", path=":memory:")
|
||||
state_store = create_state_store("memory")
|
||||
|
||||
print("=== Multi-Agent Persistence Demo ===\n")
|
||||
|
||||
# Scenario 1: Shared session (agents collaborate)
|
||||
print("--- Scenario 1: Shared Session ---")
|
||||
shared_session_id = f"shared-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
session = ConversationSession(
|
||||
session_id=shared_session_id,
|
||||
user_id="user-123",
|
||||
name="Shared Conversation"
|
||||
)
|
||||
conv_store.create_session(session)
|
||||
|
||||
# Agent 1 (Researcher) adds context
|
||||
conv_store.add_message(shared_session_id, ConversationMessage(
|
||||
role="assistant",
|
||||
content="[Researcher] I found relevant information about the topic.",
|
||||
metadata={"agent_id": "researcher", "agent_role": "research"}
|
||||
))
|
||||
|
||||
# Agent 2 (Writer) uses the context
|
||||
conv_store.add_message(shared_session_id, ConversationMessage(
|
||||
role="assistant",
|
||||
content="[Writer] Based on the research, here's a summary...",
|
||||
metadata={"agent_id": "writer", "agent_role": "writing"}
|
||||
))
|
||||
|
||||
# Agent 3 (Editor) reviews
|
||||
conv_store.add_message(shared_session_id, ConversationMessage(
|
||||
role="assistant",
|
||||
content="[Editor] I've reviewed and polished the content.",
|
||||
metadata={"agent_id": "editor", "agent_role": "editing"}
|
||||
))
|
||||
|
||||
messages = conv_store.get_messages(shared_session_id)
|
||||
print(f"Shared session has {len(messages)} messages from different agents")
|
||||
for msg in messages:
|
||||
agent = msg.metadata.get("agent_id", "unknown") if msg.metadata else "unknown"
|
||||
print(f" - {agent}: {msg.content[:50]}...")
|
||||
|
||||
# Scenario 2: Isolated sessions (agents work independently)
|
||||
print("\n--- Scenario 2: Isolated Sessions ---")
|
||||
|
||||
agent_sessions = {}
|
||||
for agent_name in ["planner", "executor", "validator"]:
|
||||
session_id = f"{agent_name}-{uuid.uuid4().hex[:8]}"
|
||||
agent_sessions[agent_name] = session_id
|
||||
|
||||
conv_store.create_session(ConversationSession(
|
||||
session_id=session_id,
|
||||
agent_id=agent_name,
|
||||
name=f"{agent_name.title()} Session"
|
||||
))
|
||||
|
||||
# Each agent has its own conversation
|
||||
conv_store.add_message(session_id, ConversationMessage(
|
||||
role="user",
|
||||
content=f"Task for {agent_name}"
|
||||
))
|
||||
conv_store.add_message(session_id, ConversationMessage(
|
||||
role="assistant",
|
||||
content=f"[{agent_name.title()}] Processing task..."
|
||||
))
|
||||
|
||||
print("Created isolated sessions for each agent:")
|
||||
for agent, sid in agent_sessions.items():
|
||||
msgs = conv_store.get_messages(sid)
|
||||
print(f" - {agent}: {len(msgs)} messages")
|
||||
|
||||
# Scenario 3: Shared state across agents
|
||||
print("\n--- Scenario 3: Shared State ---")
|
||||
|
||||
# Agents share state for coordination
|
||||
state_store.set("workflow:status", {"phase": "research", "progress": 0})
|
||||
state_store.set("workflow:context", {"topic": "AI Agents", "deadline": "2024-12-31"})
|
||||
|
||||
# Agent 1 updates progress
|
||||
state = state_store.get("workflow:status")
|
||||
state["progress"] = 33
|
||||
state["phase"] = "writing"
|
||||
state_store.set("workflow:status", state)
|
||||
print(f"Agent 1 updated state: {state}")
|
||||
|
||||
# Agent 2 reads and updates
|
||||
state = state_store.get("workflow:status")
|
||||
state["progress"] = 66
|
||||
state["phase"] = "editing"
|
||||
state_store.set("workflow:status", state)
|
||||
print(f"Agent 2 updated state: {state}")
|
||||
|
||||
# Agent 3 completes
|
||||
state = state_store.get("workflow:status")
|
||||
state["progress"] = 100
|
||||
state["phase"] = "complete"
|
||||
state_store.set("workflow:status", state)
|
||||
print(f"Agent 3 completed: {state}")
|
||||
|
||||
# Cleanup
|
||||
conv_store.close()
|
||||
state_store.close()
|
||||
print("\n✅ Multi-agent persistence demo complete!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Multi-Agent Shared Session - DB Persistence Test"""
|
||||
import sys
|
||||
sys.path.insert(0, 'src/praisonai')
|
||||
from praisonai.persistence import create_conversation_store
|
||||
from praisonai.persistence.conversation.base import ConversationSession, ConversationMessage
|
||||
|
||||
# Create shared session for multiple agents
|
||||
store = create_conversation_store("sqlite", path="/tmp/multi_agent.db")
|
||||
session_id = "shared-session-123"
|
||||
|
||||
# Create session
|
||||
session = ConversationSession(session_id=session_id, agent_id="multi")
|
||||
try:
|
||||
store.create_session(session)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Agent 1 writes
|
||||
msg1 = ConversationMessage(session_id=session_id, role="user", content="Agent1: Hello")
|
||||
store.add_message(session_id, msg1)
|
||||
msg2 = ConversationMessage(session_id=session_id, role="assistant", content="Agent1: Hi there!")
|
||||
store.add_message(session_id, msg2)
|
||||
|
||||
# Agent 2 writes to same session
|
||||
msg3 = ConversationMessage(session_id=session_id, role="user", content="Agent2: What did Agent1 say?")
|
||||
store.add_message(session_id, msg3)
|
||||
msg4 = ConversationMessage(session_id=session_id, role="assistant", content="Agent2: Agent1 said Hello")
|
||||
store.add_message(session_id, msg4)
|
||||
|
||||
# Verify both agents' messages are in session
|
||||
messages = store.get_messages(session_id)
|
||||
print(f"Total messages in shared session: {len(messages)}")
|
||||
assert len(messages) >= 4, "Should have messages from both agents"
|
||||
|
||||
# Verify content
|
||||
contents = [m.content for m in messages]
|
||||
assert any("Agent1" in c for c in contents), "Should have Agent1 messages"
|
||||
assert any("Agent2" in c for c in contents), "Should have Agent2 messages"
|
||||
|
||||
print("PASSED: Multi-agent shared session persistence")
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Multi-Agent Shared Session Demo"""
|
||||
from praisonaiagents import Agent
|
||||
from praisonai.persistence import create_conversation_store
|
||||
|
||||
store = create_conversation_store("sqlite", path="/tmp/multi_agent.db")
|
||||
|
||||
agent1 = Agent(name="Researcher", llm="gpt-4o-mini", db=store, session_id="shared-session")
|
||||
agent2 = Agent(name="Writer", llm="gpt-4o-mini", db=store, session_id="shared-session")
|
||||
|
||||
r1 = agent1.chat("Research topic: AI in healthcare. Key finding: reduces diagnosis time by 50%")
|
||||
print(f"Researcher: {r1[:50]}...")
|
||||
|
||||
r2 = agent2.chat("Based on the research, what was the key finding?")
|
||||
print(f"Writer: {r2[:50]}...")
|
||||
assert "50" in r2 or "diagnosis" in r2.lower(), "Writer should see Researcher's context"
|
||||
print("PASSED: Multi-agent shared session works")
|
||||
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Basic Observability Usage Example
|
||||
|
||||
This example demonstrates how to use the observability module
|
||||
with PraisonAI agents.
|
||||
|
||||
Usage:
|
||||
# Set your provider's API key first
|
||||
export LANGFUSE_PUBLIC_KEY=pk-lf-xxx
|
||||
export LANGFUSE_SECRET_KEY=sk-lf-xxx
|
||||
|
||||
python basic_usage.py
|
||||
"""
|
||||
|
||||
from praisonai_tools.observability import obs
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Initialize observability (auto-detects provider from env vars)
|
||||
obs.init()
|
||||
|
||||
# Or specify a provider explicitly
|
||||
# obs.init(provider="langfuse", project_name="my-project")
|
||||
|
||||
# Create an agent
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant.",
|
||||
model="gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Use trace context manager for complete workflow tracing
|
||||
with obs.trace("chat-session", session_id="user-123"):
|
||||
# Use span context manager for individual operations
|
||||
with obs.span("user-query", kind=obs.SpanKind.AGENT):
|
||||
response = agent.chat("What is the capital of France?")
|
||||
print(response)
|
||||
|
||||
# Log LLM calls manually if needed
|
||||
obs.log_llm_call(
|
||||
model="gpt-4o-mini",
|
||||
input_messages="What is 2+2?",
|
||||
output="4",
|
||||
input_tokens=10,
|
||||
output_tokens=1,
|
||||
)
|
||||
|
||||
# Check diagnostics
|
||||
print("\nDiagnostics:")
|
||||
print(obs.doctor())
|
||||
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Langfuse Integration Example
|
||||
|
||||
This example shows how to use Langfuse for LLM observability.
|
||||
|
||||
Setup:
|
||||
1. Sign up at https://langfuse.com/
|
||||
2. Get your API keys from the project settings
|
||||
3. Set environment variables:
|
||||
export LANGFUSE_PUBLIC_KEY=pk-lf-xxx
|
||||
export LANGFUSE_SECRET_KEY=sk-lf-xxx
|
||||
4. Install dependencies:
|
||||
pip install opentelemetry-sdk opentelemetry-exporter-otlp
|
||||
|
||||
Usage:
|
||||
python langfuse_example.py
|
||||
"""
|
||||
|
||||
import os
|
||||
from praisonai_tools.observability import obs
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Initialize Langfuse
|
||||
success = obs.init(
|
||||
provider="langfuse",
|
||||
project_name="praisonai-demo",
|
||||
)
|
||||
|
||||
if not success:
|
||||
print("Failed to initialize Langfuse. Check your API keys.")
|
||||
print("Required: LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY")
|
||||
exit(1)
|
||||
|
||||
print("Langfuse initialized successfully!")
|
||||
print(f"View traces at: https://cloud.langfuse.com/")
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
instructions="You are a helpful coding assistant.",
|
||||
model="gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Run with tracing
|
||||
with obs.trace("coding-session", user_id="developer-1"):
|
||||
response = agent.chat("Write a Python function to calculate fibonacci numbers")
|
||||
print(response)
|
||||
|
||||
print("\nCheck Langfuse dashboard for traces!")
|
||||
@@ -0,0 +1,22 @@
|
||||
"""MLflow Local Observability Demo (no API key needed)"""
|
||||
import mlflow
|
||||
import time
|
||||
|
||||
mlflow.set_tracking_uri("file:///tmp/mlflow_demo")
|
||||
mlflow.set_experiment("praisonai-demo")
|
||||
|
||||
with mlflow.start_run(run_name="agent-trace"):
|
||||
mlflow.log_param("agent_name", "Assistant")
|
||||
mlflow.log_param("model", "gpt-4o-mini")
|
||||
|
||||
# Simulate agent work
|
||||
start = time.time()
|
||||
time.sleep(0.1)
|
||||
latency = time.time() - start
|
||||
|
||||
mlflow.log_metric("latency_ms", latency * 1000)
|
||||
mlflow.log_metric("tokens_used", 150)
|
||||
|
||||
print(f"Logged run to MLflow: latency={latency*1000:.1f}ms")
|
||||
|
||||
print("PASSED: MLflow local observability works")
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Multi-Agent Tracing Example
|
||||
|
||||
This example shows how to trace multi-agent workflows with
|
||||
parent/child span relationships.
|
||||
|
||||
Usage:
|
||||
# Set your provider's API key
|
||||
export LANGFUSE_PUBLIC_KEY=pk-lf-xxx
|
||||
export LANGFUSE_SECRET_KEY=sk-lf-xxx
|
||||
|
||||
python multi_agent_tracing.py
|
||||
"""
|
||||
|
||||
from praisonai_tools.observability import obs
|
||||
from praisonai_tools.observability.base import SpanKind
|
||||
from praisonaiagents import Agent, PraisonAIAgents
|
||||
|
||||
# Initialize observability
|
||||
obs.init()
|
||||
|
||||
# Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
instructions="You research topics and provide detailed information.",
|
||||
model="gpt-4o-mini",
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
instructions="You write clear, engaging content based on research.",
|
||||
model="gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Trace the multi-agent workflow
|
||||
with obs.trace("content-creation", metadata={"workflow": "research-write"}):
|
||||
|
||||
# Research phase
|
||||
with obs.span("research-phase", kind=SpanKind.AGENT) as research_span:
|
||||
research_span.attributes["agent"] = "Researcher"
|
||||
research_result = researcher.chat("Research the benefits of AI in healthcare")
|
||||
|
||||
# Writing phase
|
||||
with obs.span("writing-phase", kind=SpanKind.AGENT) as write_span:
|
||||
write_span.attributes["agent"] = "Writer"
|
||||
write_span.attributes["input_length"] = len(str(research_result))
|
||||
final_content = writer.chat(f"Write a blog post based on: {research_result}")
|
||||
|
||||
print("Final Content:")
|
||||
print(final_content)
|
||||
|
||||
# Check trace info
|
||||
print("\nDiagnostics:", obs.doctor())
|
||||
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Qdrant Knowledge Store - Agent-First Example
|
||||
|
||||
Demonstrates using Qdrant for knowledge-based RAG with an Agent.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
Docker Setup:
|
||||
docker run -d --name praison-qdrant -p 6333:6333 qdrant/qdrant
|
||||
|
||||
Run:
|
||||
python knowledge_qdrant.py
|
||||
|
||||
Expected Output:
|
||||
Agent responds using knowledge from documents
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
|
||||
# Create sample knowledge document
|
||||
sample_doc = """
|
||||
# AI and Programming Guide
|
||||
|
||||
## Python Programming
|
||||
Python is a versatile programming language used for web development, data science, and AI.
|
||||
It has a simple syntax and extensive libraries.
|
||||
|
||||
## Machine Learning
|
||||
Machine learning enables computers to learn patterns from data without explicit programming.
|
||||
Common frameworks include TensorFlow, PyTorch, and scikit-learn.
|
||||
|
||||
## Databases
|
||||
PostgreSQL is a powerful open-source relational database system.
|
||||
It supports both SQL and JSON data types.
|
||||
"""
|
||||
|
||||
# Save to temp file
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
doc_path = os.path.join(temp_dir, "guide.txt")
|
||||
with open(doc_path, "w") as f:
|
||||
f.write(sample_doc)
|
||||
|
||||
print("=== Qdrant Knowledge Store Demo (Agent-First) ===")
|
||||
|
||||
# Agent-first approach: use knowledge parameter with Qdrant
|
||||
agent = Agent(
|
||||
name="KnowledgeAssistant",
|
||||
instructions="You are a helpful assistant with access to technical documentation.",
|
||||
knowledge=[doc_path],
|
||||
knowledge_config={
|
||||
"vector_store": "qdrant",
|
||||
"url": "http://localhost:6333"
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What programming language is good for AI?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
# Cleanup
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
print("\n=== Demo Complete ===")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence.factory import create_knowledge_store
|
||||
# store = create_knowledge_store("qdrant", url="http://localhost:6333")
|
||||
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
Minimal Agent with Database Persistence
|
||||
|
||||
This is the simplest way to add persistence to your agent.
|
||||
Messages, runs, and traces are automatically saved.
|
||||
|
||||
Run:
|
||||
python minimal_agent_db.py
|
||||
|
||||
Expected output:
|
||||
- Agent responds to your question
|
||||
- Data is persisted to PostgreSQL
|
||||
- Session can be resumed later with same session_id
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
# Create database connection (PostgreSQL + Redis)
|
||||
my_db = db(
|
||||
database_url="postgresql://postgres:praison123@localhost:5432/praisonai",
|
||||
state_url="redis://localhost:6379" # Optional: for state/tracing
|
||||
)
|
||||
|
||||
# Create agent with persistence
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. Be concise.",
|
||||
db=my_db,
|
||||
# session_id is optional - defaults to per-hour ID (YYYYMMDDHH-hash)
|
||||
# Set explicitly for session continuity across runs:
|
||||
session_id="my-persistent-session"
|
||||
)
|
||||
|
||||
# Chat - messages are automatically persisted
|
||||
response = agent.chat("What is the capital of France?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
# Verify persistence
|
||||
data = my_db.export_session("my-persistent-session")
|
||||
print(f"\nMessages stored: {len(data.get('messages', []))}")
|
||||
|
||||
# Clean up
|
||||
my_db.close()
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
PostgreSQL with Runs and Traces
|
||||
|
||||
Demonstrates full persistence with run tracking and tracing.
|
||||
|
||||
Run:
|
||||
python postgres_runs_traces.py
|
||||
|
||||
Expected output:
|
||||
- Agent responds with tool usage
|
||||
- Runs and traces are persisted
|
||||
- Export shows full session data
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a city."""
|
||||
return f"Weather in {city}: Sunny, 72°F"
|
||||
|
||||
# Create database with PostgreSQL + Redis
|
||||
my_db = db(
|
||||
database_url="postgresql://postgres:praison123@localhost:5432/praisonai",
|
||||
state_url="redis://localhost:6379"
|
||||
)
|
||||
|
||||
# Create agent with tools
|
||||
agent = Agent(
|
||||
name="WeatherBot",
|
||||
instructions="You help with weather. Use the get_weather tool when asked.",
|
||||
tools=[get_weather],
|
||||
db=my_db,
|
||||
session_id="weather-session-001"
|
||||
)
|
||||
|
||||
# First chat - triggers run tracking
|
||||
print("=== First Question ===")
|
||||
response1 = agent.chat("What's the weather in Tokyo?")
|
||||
print(f"Response: {response1}\n")
|
||||
|
||||
# Second chat - same session, new run
|
||||
print("=== Second Question ===")
|
||||
response2 = agent.chat("How about New York?")
|
||||
print(f"Response: {response2}\n")
|
||||
|
||||
# Export session data
|
||||
print("=== Session Data ===")
|
||||
data = my_db.export_session("weather-session-001")
|
||||
print(f"Session ID: {data.get('session_id')}")
|
||||
print(f"Messages: {len(data.get('messages', []))}")
|
||||
print(f"Agent: {data.get('agent_name')}")
|
||||
|
||||
# Show messages
|
||||
print("\n=== Messages ===")
|
||||
for msg in data.get('messages', []):
|
||||
role = msg.get('role', 'unknown')
|
||||
content = msg.get('content', '')[:60]
|
||||
print(f" [{role}] {content}...")
|
||||
|
||||
my_db.close()
|
||||
print("\n✅ Done")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Redis State Store - Agent-First Example
|
||||
|
||||
Use Redis for state management with an Agent.
|
||||
|
||||
Docker Setup:
|
||||
docker run -d --name redis -p 6379:6379 redis:7
|
||||
|
||||
Run:
|
||||
python redis_state.py
|
||||
|
||||
Expected output:
|
||||
Agent responds with state persisted to Redis
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
print("=== Redis State Store (Agent-First) ===")
|
||||
|
||||
# Agent-first approach: use db parameter with Redis state store
|
||||
my_db = db(
|
||||
database_url="sqlite:///conversations.db", # Conversations
|
||||
state_url="redis://localhost:6379" # State/tracing
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
db=my_db,
|
||||
session_id="redis-state-session"
|
||||
)
|
||||
|
||||
# Chat - state is automatically managed via Redis
|
||||
response = agent.chat("Hello! Remember my name is Alice.")
|
||||
print(f"Response: {response}")
|
||||
|
||||
# Second message to test state
|
||||
response2 = agent.chat("What is my name?")
|
||||
print(f"Response: {response2}")
|
||||
|
||||
my_db.close()
|
||||
print("\n✅ Done")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonaiagents import db
|
||||
# redis_db = db.RedisDB(host="localhost", port=6379)
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Session Resume Example
|
||||
|
||||
Demonstrates how to resume a conversation from a previous session.
|
||||
|
||||
Run:
|
||||
python session_resume.py
|
||||
|
||||
Expected output:
|
||||
- First run: Creates new session
|
||||
- Second run: Resumes with history
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
# Create database
|
||||
my_db = db(
|
||||
database_url="postgresql://postgres:praison123@localhost:5432/praisonai",
|
||||
state_url="redis://localhost:6379"
|
||||
)
|
||||
|
||||
SESSION_ID = "resume-demo-session"
|
||||
|
||||
# Create agent with explicit session_id
|
||||
agent = Agent(
|
||||
name="MemoryBot",
|
||||
instructions="You remember our conversation. Be helpful and concise.",
|
||||
db=my_db,
|
||||
session_id=SESSION_ID
|
||||
)
|
||||
|
||||
# Check if this is a new or resumed session
|
||||
data = my_db.export_session(SESSION_ID)
|
||||
existing_messages = len(data.get('messages', []))
|
||||
|
||||
if existing_messages == 0:
|
||||
print("=== NEW SESSION ===")
|
||||
print("This is a new conversation.\n")
|
||||
|
||||
response = agent.chat("My name is Alice and I love Python programming.")
|
||||
print(f"Response: {response}\n")
|
||||
|
||||
response = agent.chat("What's my favorite programming language?")
|
||||
print(f"Response: {response}\n")
|
||||
else:
|
||||
print(f"=== RESUMED SESSION ({existing_messages} previous messages) ===")
|
||||
print("Continuing previous conversation.\n")
|
||||
|
||||
# Show previous messages
|
||||
print("Previous messages:")
|
||||
for msg in data.get('messages', [])[:4]:
|
||||
role = msg.get('role', 'unknown')
|
||||
content = msg.get('content', '')[:60]
|
||||
print(f" [{role}] {content}...")
|
||||
print()
|
||||
|
||||
# Continue conversation
|
||||
response = agent.chat("What's my name and what do I like?")
|
||||
print(f"Response: {response}\n")
|
||||
|
||||
# Show final state
|
||||
final_data = my_db.export_session(SESSION_ID)
|
||||
print(f"Total messages now: {len(final_data.get('messages', []))}")
|
||||
|
||||
my_db.close()
|
||||
print("✅ Done")
|
||||
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
Simple Database Persistence Example for PraisonAI Agents.
|
||||
|
||||
This example shows how to enable automatic conversation persistence
|
||||
with just 2 lines of code.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
Docker Setup:
|
||||
docker run -d --name praison-postgres -p 5432:5432 \
|
||||
-e POSTGRES_PASSWORD=praison123 \
|
||||
-e POSTGRES_DB=praisonai \
|
||||
postgres:16
|
||||
|
||||
Environment:
|
||||
export OPENAI_API_KEY=your-key
|
||||
|
||||
Run:
|
||||
python simple_agent_postgres.py
|
||||
|
||||
Expected Output:
|
||||
=== First Run ===
|
||||
Agent: Nice to meet you, Alice! Python is a great choice...
|
||||
Session ID: demo-session-001
|
||||
Messages persisted: 2
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
# Create database adapter (simplified import)
|
||||
db_instance = db(
|
||||
database_url="postgresql://postgres:praison123@localhost:5432/praisonai"
|
||||
)
|
||||
|
||||
# Create agent with persistence enabled
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. Keep responses brief.",
|
||||
db=db_instance,
|
||||
session_id="demo-session-001", # Same session_id = resume conversation
|
||||
verbose=False
|
||||
)
|
||||
|
||||
# Chat with persistence
|
||||
print("=== First Run ===")
|
||||
response = agent.chat("My name is Alice and I love Python programming.")
|
||||
print(f"Agent: {response}\n")
|
||||
|
||||
# Show session info
|
||||
print(f"Session ID: {agent.session_id}")
|
||||
print(f"Messages in history: {len(agent.chat_history)}")
|
||||
|
||||
# Close DB connection
|
||||
db_instance.close()
|
||||
|
||||
print("\n=== Session persisted! ===")
|
||||
print("Run again with same session_id to resume.")
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
SQLite Local Database
|
||||
|
||||
Use SQLite for simple local persistence without external services.
|
||||
|
||||
Run:
|
||||
python sqlite_local.py
|
||||
|
||||
Expected output:
|
||||
- Agent responds
|
||||
- Data persisted to local SQLite file
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, db
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
# Create SQLite database (local file)
|
||||
db_path = os.path.join(tempfile.gettempdir(), "my_agent.db")
|
||||
my_db = db.SQLiteDB(path=db_path)
|
||||
|
||||
print(f"Database: {db_path}")
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="LocalBot",
|
||||
instructions="You are a helpful assistant.",
|
||||
db=my_db,
|
||||
session_id="local-session"
|
||||
)
|
||||
|
||||
# Chat
|
||||
response = agent.chat("Hello! What can you help me with?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
# Verify
|
||||
data = my_db.export_session("local-session")
|
||||
print(f"\nMessages stored: {len(data.get('messages', []))}")
|
||||
|
||||
my_db.close()
|
||||
print("✅ Done")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Redis State Store - Agent-First Example
|
||||
|
||||
Use Redis for state management with an Agent.
|
||||
|
||||
Docker Setup:
|
||||
docker run -d --name praison-redis -p 6379:6379 redis:7
|
||||
|
||||
Run:
|
||||
python state_redis.py
|
||||
|
||||
Expected Output:
|
||||
Agent responds with state persisted to Redis
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
print("=== Redis State Store (Agent-First) ===")
|
||||
|
||||
# Agent-first approach: use db parameter with Redis state store
|
||||
my_db = db(
|
||||
database_url="sqlite:///conversations.db", # Conversations
|
||||
state_url="redis://localhost:6379" # State/tracing
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
db=my_db,
|
||||
session_id="redis-state-example"
|
||||
)
|
||||
|
||||
# Chat - state is automatically managed via Redis
|
||||
response = agent.chat("Hello! Remember my favorite color is blue.")
|
||||
print(f"Response: {response}")
|
||||
|
||||
# Second message to test state
|
||||
response2 = agent.chat("What is my favorite color?")
|
||||
print(f"Response: {response2}")
|
||||
|
||||
my_db.close()
|
||||
print("\n=== Demo Complete ===")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence.factory import create_state_store
|
||||
# store = create_state_store("redis", url="redis://localhost:6379")
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic Policy Engine Example for PraisonAI Agents.
|
||||
|
||||
This example demonstrates how to use the policy engine to:
|
||||
1. Create policies with rules
|
||||
2. Check if actions are allowed
|
||||
3. Block dangerous operations
|
||||
4. Use convenience policy creators
|
||||
|
||||
Usage:
|
||||
python basic_policy.py
|
||||
"""
|
||||
|
||||
from praisonaiagents.policy import (
|
||||
PolicyEngine, Policy, PolicyRule, PolicyAction, PolicyConfig,
|
||||
create_deny_tools_policy, create_read_only_policy
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Policy Engine Demo")
|
||||
print("=" * 60)
|
||||
|
||||
# Create a policy engine
|
||||
engine = PolicyEngine()
|
||||
|
||||
# ==========================================================================
|
||||
# Policy 1: Block dangerous tools
|
||||
# ==========================================================================
|
||||
print("\n--- Creating Policies ---")
|
||||
|
||||
dangerous_tools_policy = Policy(
|
||||
name="block_dangerous",
|
||||
description="Block dangerous file operations",
|
||||
rules=[
|
||||
PolicyRule(
|
||||
action=PolicyAction.DENY,
|
||||
resource="tool:delete_*",
|
||||
reason="Delete operations are blocked",
|
||||
name="deny_delete"
|
||||
),
|
||||
PolicyRule(
|
||||
action=PolicyAction.DENY,
|
||||
resource="tool:rm_*",
|
||||
reason="Remove operations are blocked",
|
||||
name="deny_rm"
|
||||
),
|
||||
],
|
||||
priority=100
|
||||
)
|
||||
engine.add_policy(dangerous_tools_policy)
|
||||
print(f"✅ Added policy: {dangerous_tools_policy.name}")
|
||||
|
||||
# ==========================================================================
|
||||
# Policy 2: Allow read operations
|
||||
# ==========================================================================
|
||||
read_policy = Policy(
|
||||
name="allow_read",
|
||||
description="Allow read operations",
|
||||
rules=[
|
||||
PolicyRule(
|
||||
action=PolicyAction.ALLOW,
|
||||
resource="tool:read_*",
|
||||
name="allow_read"
|
||||
),
|
||||
PolicyRule(
|
||||
action=PolicyAction.ALLOW,
|
||||
resource="tool:list_*",
|
||||
name="allow_list"
|
||||
),
|
||||
]
|
||||
)
|
||||
engine.add_policy(read_policy)
|
||||
print(f"✅ Added policy: {read_policy.name}")
|
||||
|
||||
# ==========================================================================
|
||||
# Test policy checks
|
||||
# ==========================================================================
|
||||
print("\n--- Testing Policy Checks ---")
|
||||
|
||||
test_cases = [
|
||||
("tool:read_file", "Reading a file"),
|
||||
("tool:list_directory", "Listing directory"),
|
||||
("tool:delete_file", "Deleting a file"),
|
||||
("tool:rm_directory", "Removing directory"),
|
||||
("tool:write_file", "Writing a file"),
|
||||
]
|
||||
|
||||
for resource, description in test_cases:
|
||||
result = engine.check(resource, {})
|
||||
status = "✅ ALLOWED" if result.allowed else "🚫 DENIED"
|
||||
reason = f" ({result.reason})" if result.reason else ""
|
||||
print(f" {status}: {description}{reason}")
|
||||
|
||||
# ==========================================================================
|
||||
# Using convenience functions
|
||||
# ==========================================================================
|
||||
print("\n--- Using Convenience Functions ---")
|
||||
|
||||
# Create a read-only policy
|
||||
read_only = create_read_only_policy()
|
||||
print(f"✅ Created read-only policy with {len(read_only.rules)} rules")
|
||||
|
||||
# Create a deny tools policy
|
||||
deny_tools = create_deny_tools_policy(
|
||||
["execute_*", "shell_*", "system_*"],
|
||||
reason="System commands are blocked"
|
||||
)
|
||||
print(f"✅ Created deny-tools policy with {len(deny_tools.rules)} rules")
|
||||
|
||||
# ==========================================================================
|
||||
# Strict mode example
|
||||
# ==========================================================================
|
||||
print("\n--- Strict Mode Example ---")
|
||||
|
||||
strict_engine = PolicyEngine(PolicyConfig(strict_mode=True))
|
||||
|
||||
# In strict mode, unknown resources are denied
|
||||
result = strict_engine.check("tool:unknown_operation", {})
|
||||
print(f" Unknown operation in strict mode: {'DENIED' if not result.allowed else 'ALLOWED'}")
|
||||
|
||||
# ==========================================================================
|
||||
# Policy serialization
|
||||
# ==========================================================================
|
||||
print("\n--- Policy Serialization ---")
|
||||
|
||||
policy_dict = dangerous_tools_policy.to_dict()
|
||||
print(f" Serialized policy: {policy_dict['name']} with {len(policy_dict['rules'])} rules")
|
||||
|
||||
# Recreate from dict
|
||||
restored = Policy.from_dict(policy_dict)
|
||||
print(f" Restored policy: {restored.name}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Demo Complete!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -15,6 +15,7 @@ Features demonstrated:
|
||||
from praisonaiagents import Agent
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
|
||||
# Create a sample knowledge document for demonstration
|
||||
sample_document = """
|
||||
@@ -91,38 +92,15 @@ doc_path = os.path.join(temp_dir, "ai_healthcare_guide.txt")
|
||||
with open(doc_path, "w") as f:
|
||||
f.write(sample_document)
|
||||
|
||||
# Create an agent with advanced knowledge management configuration
|
||||
# Create an agent with knowledge management
|
||||
knowledge_agent = Agent(
|
||||
name="AdvancedKnowledgeAgent",
|
||||
role="Healthcare AI Knowledge Expert",
|
||||
goal="Provide comprehensive answers about AI in healthcare using advanced knowledge retrieval",
|
||||
backstory="You are an expert in healthcare AI with access to comprehensive knowledge bases and advanced retrieval capabilities.",
|
||||
|
||||
# Advanced knowledge configuration
|
||||
# Knowledge configuration - simple and working
|
||||
knowledge=[doc_path],
|
||||
knowledge_config={
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"collection_name": "healthcare_ai_knowledge"
|
||||
},
|
||||
"chunking": {
|
||||
"strategy": "semantic", # Advanced semantic chunking
|
||||
"chunk_size": 500, # Optimal chunk size for retrieval
|
||||
"chunk_overlap": 50, # Overlap for context preservation
|
||||
"separators": ["\n\n", "\n", ".", "!", "?"] # Smart separators
|
||||
},
|
||||
"retrieval": {
|
||||
"search_type": "similarity",
|
||||
"k": 5, # Retrieve top 5 most relevant chunks
|
||||
"score_threshold": 0.7, # Minimum relevance score
|
||||
"rerank": True # Enable reranking for better results
|
||||
},
|
||||
"embedding": {
|
||||
"provider": "openai",
|
||||
"model": "text-embedding-3-small", # Efficient embedding model
|
||||
"dimensions": 1536
|
||||
}
|
||||
},
|
||||
|
||||
instructions="""You are an expert in healthcare AI. Use the knowledge base to provide
|
||||
comprehensive, accurate answers. Always cite specific information from the documents
|
||||
@@ -180,7 +158,6 @@ relationship_result = knowledge_agent.start(
|
||||
print(f"Relationship analysis result:\n{relationship_result}\n")
|
||||
|
||||
# Clean up temporary files
|
||||
import shutil
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
print("="*80)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run all examples and collect PASS/SKIP/FAIL results."""
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
EXAMPLES_DIR = Path(__file__).parent
|
||||
RESULTS = []
|
||||
|
||||
def run_example(path: Path) -> tuple:
|
||||
"""Run an example and return (name, status, reason)."""
|
||||
name = f"{path.parent.name}/{path.name}"
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
env={**os.environ, "PYTHONPATH": str(EXAMPLES_DIR.parent / "src")}
|
||||
)
|
||||
output = result.stdout + result.stderr
|
||||
if "PASSED" in output:
|
||||
return (name, "PASS", "")
|
||||
elif "SKIPPED" in output:
|
||||
return (name, "SKIP", output.split("SKIPPED:")[-1].strip()[:50])
|
||||
elif result.returncode != 0:
|
||||
error = output.strip().split("\n")[-1][:80]
|
||||
return (name, "FAIL", error)
|
||||
else:
|
||||
return (name, "PASS", "")
|
||||
except subprocess.TimeoutExpired:
|
||||
return (name, "FAIL", "Timeout")
|
||||
except Exception as e:
|
||||
return (name, "FAIL", str(e)[:50])
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("EXAMPLE RUNNER - PASS/SKIP/FAIL SUMMARY")
|
||||
print("=" * 70)
|
||||
|
||||
# Find all example files
|
||||
examples = []
|
||||
for subdir in ["db", "vector", "observability", "multi_agent", "workflows"]:
|
||||
subpath = EXAMPLES_DIR / subdir
|
||||
if subpath.exists():
|
||||
examples.extend(sorted(subpath.glob("*_wow.py")))
|
||||
|
||||
print(f"\nFound {len(examples)} examples to run\n")
|
||||
|
||||
for ex in examples:
|
||||
name, status, reason = run_example(ex)
|
||||
RESULTS.append((name, status, reason))
|
||||
icon = {"PASS": "✅", "SKIP": "⏭️", "FAIL": "❌"}.get(status, "?")
|
||||
reason_str = f" ({reason})" if reason else ""
|
||||
print(f"{icon} {status:4} | {name:45} {reason_str}")
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 70)
|
||||
passed = sum(1 for _, s, _ in RESULTS if s == "PASS")
|
||||
skipped = sum(1 for _, s, _ in RESULTS if s == "SKIP")
|
||||
failed = sum(1 for _, s, _ in RESULTS if s == "FAIL")
|
||||
print(f"SUMMARY: {passed} PASS | {skipped} SKIP | {failed} FAIL | {len(RESULTS)} TOTAL")
|
||||
print("=" * 70)
|
||||
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example: Creating Skills with PraisonAI CLI
|
||||
|
||||
This example demonstrates how to create Agent Skills using the CLI.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def example_create_skill_with_template():
|
||||
"""Create a skill using template mode (no API key required)."""
|
||||
print("=" * 60)
|
||||
print("Example 1: Create skill with template")
|
||||
print("=" * 60)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
result = subprocess.run([
|
||||
"praisonai", "skills", "create",
|
||||
"--name", "my-template-skill",
|
||||
"--description", "A skill for processing text files",
|
||||
"--author", "example-team",
|
||||
"--license", "MIT",
|
||||
"--template",
|
||||
"--output-dir", tmpdir
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
if result.returncode == 0:
|
||||
skill_md = Path(tmpdir) / "my-template-skill" / "SKILL.md"
|
||||
print(f"\nGenerated SKILL.md:\n{skill_md.read_text()[:500]}...")
|
||||
else:
|
||||
print(f"Error: {result.stderr}")
|
||||
|
||||
|
||||
def example_create_skill_with_ai():
|
||||
"""Create a skill using AI generation (requires API key)."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 2: Create skill with AI generation")
|
||||
print("=" * 60)
|
||||
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Skipping: OPENAI_API_KEY not set")
|
||||
return
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
result = subprocess.run([
|
||||
"praisonai", "skills", "create",
|
||||
"--name", "csv-analyzer",
|
||||
"--description", "Analyze CSV files and generate statistical reports",
|
||||
"--output-dir", tmpdir
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
if result.returncode == 0:
|
||||
skill_md = Path(tmpdir) / "csv-analyzer" / "SKILL.md"
|
||||
print(f"\nGenerated SKILL.md:\n{skill_md.read_text()[:800]}...")
|
||||
|
||||
# Check if script was generated
|
||||
script_path = Path(tmpdir) / "csv-analyzer" / "scripts" / "skill.py"
|
||||
if script_path.exists():
|
||||
print(f"\nGenerated skill.py:\n{script_path.read_text()[:500]}...")
|
||||
|
||||
|
||||
def example_create_skill_with_script():
|
||||
"""Create a skill with auto-generated Python script."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 3: Create skill with script")
|
||||
print("=" * 60)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
result = subprocess.run([
|
||||
"praisonai", "skills", "create",
|
||||
"--name", "data-processor",
|
||||
"--description", "Process and transform data files",
|
||||
"--template",
|
||||
"--script",
|
||||
"--output-dir", tmpdir
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
if result.returncode == 0:
|
||||
script_path = Path(tmpdir) / "data-processor" / "scripts" / "skill.py"
|
||||
if script_path.exists():
|
||||
print(f"\nGenerated skill.py:\n{script_path.read_text()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
example_create_skill_with_template()
|
||||
example_create_skill_with_ai()
|
||||
example_create_skill_with_script()
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example: Multi-Agent CLI Usage
|
||||
|
||||
This example demonstrates how to use the praisonai agents command
|
||||
to define and run multiple agents from the command line.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
|
||||
def example_single_agent():
|
||||
"""Run a single agent with a simple task."""
|
||||
print("=" * 60)
|
||||
print("Example 1: Single Agent")
|
||||
print("=" * 60)
|
||||
|
||||
result = subprocess.run([
|
||||
"praisonai", "agents", "run",
|
||||
"--agent", "assistant:Helper",
|
||||
"--task", "What is the capital of France?"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
if result.returncode != 0:
|
||||
print(f"Error: {result.stderr}")
|
||||
|
||||
|
||||
def example_agent_with_tools():
|
||||
"""Run an agent with tools."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 2: Agent with Tools")
|
||||
print("=" * 60)
|
||||
|
||||
result = subprocess.run([
|
||||
"praisonai", "agents", "run",
|
||||
"--agent", "researcher:Research Analyst:internet_search",
|
||||
"--task", "Find the latest news about renewable energy"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
|
||||
|
||||
def example_multiple_agents_sequential():
|
||||
"""Run multiple agents in sequential mode."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 3: Multiple Agents (Sequential)")
|
||||
print("=" * 60)
|
||||
|
||||
result = subprocess.run([
|
||||
"praisonai", "agents", "run",
|
||||
"--agent", "researcher:Research Analyst:internet_search",
|
||||
"--agent", "writer:Content Writer:write_file",
|
||||
"--task", "Research AI trends and write a brief summary"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
|
||||
|
||||
def example_multiple_agents_parallel():
|
||||
"""Run multiple agents in parallel mode."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 4: Multiple Agents (Parallel)")
|
||||
print("=" * 60)
|
||||
|
||||
result = subprocess.run([
|
||||
"praisonai", "agents", "run",
|
||||
"--agent", "analyst1:Market Analyst",
|
||||
"--agent", "analyst2:Tech Analyst",
|
||||
"--process", "parallel",
|
||||
"--task", "Analyze the current state of AI industry"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
|
||||
|
||||
def example_with_custom_llm():
|
||||
"""Run agents with a custom LLM model."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 5: Custom LLM Model")
|
||||
print("=" * 60)
|
||||
|
||||
result = subprocess.run([
|
||||
"praisonai", "agents", "run",
|
||||
"--agent", "coder:Developer:execute_command",
|
||||
"--llm", "gpt-4o-mini",
|
||||
"--task", "Write a simple hello world Python script"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
|
||||
|
||||
def example_with_instructions():
|
||||
"""Run agents with additional instructions."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 6: With Additional Instructions")
|
||||
print("=" * 60)
|
||||
|
||||
result = subprocess.run([
|
||||
"praisonai", "agents", "run",
|
||||
"--agent", "analyst:Data Analyst",
|
||||
"--instructions", "Be concise and provide only key insights",
|
||||
"--task", "Explain the benefits of using AI in healthcare"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
|
||||
|
||||
def example_list_tools():
|
||||
"""List available tools for agents."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 7: List Available Tools")
|
||||
print("=" * 60)
|
||||
|
||||
result = subprocess.run([
|
||||
"praisonai", "agents", "list"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check for API key
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Warning: OPENAI_API_KEY not set. Some examples may fail.")
|
||||
print()
|
||||
|
||||
# Run examples
|
||||
example_list_tools()
|
||||
example_single_agent()
|
||||
# Uncomment to run more examples:
|
||||
# example_agent_with_tools()
|
||||
# example_multiple_agents_sequential()
|
||||
# example_multiple_agents_parallel()
|
||||
# example_with_custom_llm()
|
||||
# example_with_instructions()
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic Thinking Budgets Example for PraisonAI Agents.
|
||||
|
||||
This example demonstrates how to use thinking budgets to:
|
||||
1. Configure token budgets for extended thinking
|
||||
2. Use predefined budget levels
|
||||
3. Track thinking usage
|
||||
4. Adapt budgets based on complexity
|
||||
|
||||
Usage:
|
||||
python basic_thinking.py
|
||||
"""
|
||||
|
||||
from praisonaiagents.thinking import (
|
||||
ThinkingBudget, ThinkingConfig, ThinkingUsage, ThinkingTracker
|
||||
)
|
||||
from praisonaiagents.thinking.budget import BudgetLevel
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Thinking Budgets Demo")
|
||||
print("=" * 60)
|
||||
|
||||
# ==========================================================================
|
||||
# Budget Levels
|
||||
# ==========================================================================
|
||||
print("\n--- Budget Levels ---")
|
||||
|
||||
levels = [
|
||||
("Minimal", ThinkingBudget.minimal()),
|
||||
("Low", ThinkingBudget.low()),
|
||||
("Medium", ThinkingBudget.medium()),
|
||||
("High", ThinkingBudget.high()),
|
||||
("Maximum", ThinkingBudget.maximum()),
|
||||
]
|
||||
|
||||
for name, budget in levels:
|
||||
print(f" {name}: {budget.max_tokens:,} tokens")
|
||||
|
||||
# ==========================================================================
|
||||
# Custom Budget
|
||||
# ==========================================================================
|
||||
print("\n--- Custom Budget ---")
|
||||
|
||||
custom_budget = ThinkingBudget(
|
||||
max_tokens=12000,
|
||||
min_tokens=2000,
|
||||
adaptive=True,
|
||||
max_time_seconds=120.0
|
||||
)
|
||||
print(f" Max tokens: {custom_budget.max_tokens:,}")
|
||||
print(f" Min tokens: {custom_budget.min_tokens:,}")
|
||||
print(f" Adaptive: {custom_budget.adaptive}")
|
||||
print(f" Time limit: {custom_budget.max_time_seconds}s")
|
||||
|
||||
# ==========================================================================
|
||||
# Complexity-Based Scaling
|
||||
# ==========================================================================
|
||||
print("\n--- Complexity-Based Token Allocation ---")
|
||||
|
||||
budget = ThinkingBudget(
|
||||
max_tokens=16000,
|
||||
min_tokens=2000,
|
||||
adaptive=True
|
||||
)
|
||||
|
||||
complexities = [0.0, 0.25, 0.5, 0.75, 1.0]
|
||||
for complexity in complexities:
|
||||
tokens = budget.get_tokens_for_complexity(complexity)
|
||||
bar = "█" * int(tokens / 1000)
|
||||
print(f" Complexity {complexity:.2f}: {tokens:,} tokens {bar}")
|
||||
|
||||
# ==========================================================================
|
||||
# Usage Tracking
|
||||
# ==========================================================================
|
||||
print("\n--- Usage Tracking ---")
|
||||
|
||||
usage = ThinkingUsage(
|
||||
budget_tokens=8000,
|
||||
tokens_used=5000,
|
||||
time_seconds=30.0,
|
||||
budget_time=60.0
|
||||
)
|
||||
|
||||
print(f" Tokens used: {usage.tokens_used:,} / {usage.budget_tokens:,}")
|
||||
print(f" Tokens remaining: {usage.tokens_remaining:,}")
|
||||
print(f" Utilization: {usage.token_utilization:.1%}")
|
||||
print(f" Over budget: {usage.is_over_budget}")
|
||||
print(f" Time remaining: {usage.time_remaining}s")
|
||||
|
||||
# ==========================================================================
|
||||
# Session Tracking
|
||||
# ==========================================================================
|
||||
print("\n--- Session Tracking ---")
|
||||
|
||||
tracker = ThinkingTracker()
|
||||
|
||||
# Simulate multiple thinking sessions
|
||||
sessions = [
|
||||
(8000, 4000, 20.0),
|
||||
(8000, 6000, 35.0),
|
||||
(16000, 12000, 60.0),
|
||||
(8000, 9000, 45.0), # Over budget
|
||||
]
|
||||
|
||||
for budget_tokens, tokens_used, time_seconds in sessions:
|
||||
session = tracker.start_session(budget_tokens=budget_tokens)
|
||||
tracker.end_session(session, tokens_used=tokens_used, time_seconds=time_seconds)
|
||||
|
||||
summary = tracker.get_summary()
|
||||
print(f" Total sessions: {summary['session_count']}")
|
||||
print(f" Total tokens used: {summary['total_tokens_used']:,}")
|
||||
print(f" Average tokens/session: {summary['average_tokens_per_session']:,.0f}")
|
||||
print(f" Average utilization: {summary['average_utilization']:.1%}")
|
||||
print(f" Over-budget sessions: {summary['over_budget_count']}")
|
||||
|
||||
# ==========================================================================
|
||||
# Serialization
|
||||
# ==========================================================================
|
||||
print("\n--- Serialization ---")
|
||||
|
||||
budget = ThinkingBudget.from_level(BudgetLevel.HIGH)
|
||||
data = budget.to_dict()
|
||||
print(f" Serialized: level={data['level']}, max_tokens={data['max_tokens']}")
|
||||
|
||||
restored = ThinkingBudget.from_dict(data)
|
||||
print(f" Restored: {restored.max_tokens:,} tokens")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Demo Complete!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Example: Discover tools from installed packages."""
|
||||
|
||||
# Discover from praisonaiagents built-in tools
|
||||
try:
|
||||
from praisonaiagents.tools import TOOL_MAPPINGS
|
||||
print("Built-in tools from praisonaiagents:")
|
||||
for name in list(TOOL_MAPPINGS.keys())[:10]:
|
||||
print(f" - {name}")
|
||||
print(f" ... and {len(TOOL_MAPPINGS) - 10} more")
|
||||
except ImportError:
|
||||
print("praisonaiagents not installed")
|
||||
|
||||
# Discover from praisonai_tools
|
||||
try:
|
||||
import praisonai_tools
|
||||
print("\nTools from praisonai_tools:")
|
||||
|
||||
# Check for video module
|
||||
try:
|
||||
from praisonai_tools import video
|
||||
print(" - praisonai_tools.video (video editing)")
|
||||
except ImportError:
|
||||
pass
|
||||
except ImportError:
|
||||
print("praisonai_tools not installed")
|
||||
@@ -0,0 +1,18 @@
|
||||
"""Example: Resolve tool names to callable tools."""
|
||||
from praisonai.templates.tool_override import (
|
||||
create_tool_registry_with_overrides,
|
||||
resolve_tools,
|
||||
)
|
||||
|
||||
# Create registry with defaults
|
||||
registry = create_tool_registry_with_overrides(include_defaults=True)
|
||||
|
||||
print(f"Registry contains {len(registry)} tools")
|
||||
|
||||
# Resolve tool names
|
||||
tool_names = ["shell_tool", "internet_search"]
|
||||
resolved = resolve_tools(tool_names, registry=registry)
|
||||
|
||||
print(f"\nResolved {len(resolved)} tools:")
|
||||
for tool in resolved:
|
||||
print(f" - {getattr(tool, '__name__', type(tool).__name__)}")
|
||||
@@ -0,0 +1,14 @@
|
||||
"""Example: Configure tool sources for templates."""
|
||||
from praisonai.templates.tool_override import create_tool_registry_with_overrides
|
||||
|
||||
# Create registry with tools_sources
|
||||
registry = create_tool_registry_with_overrides(
|
||||
tools_sources=["praisonai_tools.video"],
|
||||
include_defaults=True,
|
||||
)
|
||||
|
||||
print(f"Registry with tools_sources: {len(registry)} tools")
|
||||
|
||||
# List some tools
|
||||
for name in list(registry.keys())[:10]:
|
||||
print(f" - {name}")
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
"""ArXiv Tool Example.
|
||||
|
||||
This example demonstrates how to use the ArXiv tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
|
||||
Note: No API key required!
|
||||
"""
|
||||
|
||||
from praisonai_tools import ArxivTool
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize ArXiv tool (no API key needed)
|
||||
arxiv = ArxivTool()
|
||||
|
||||
# Example 1: Search ArXiv
|
||||
print("=" * 60)
|
||||
print("Example 1: Search ArXiv")
|
||||
print("=" * 60)
|
||||
|
||||
results = arxiv.search("transformer neural networks", max_results=3)
|
||||
|
||||
if results and "error" in results[0]:
|
||||
print(f"Error: {results[0]['error']}")
|
||||
else:
|
||||
print(f"Found {len(results)} papers:")
|
||||
for paper in results:
|
||||
print(f"\n Title: {paper.get('title', 'N/A')[:60]}...")
|
||||
authors = paper.get('authors', [])
|
||||
if authors:
|
||||
print(f" Authors: {', '.join(authors[:2])}...")
|
||||
print(f" Published: {paper.get('published', 'N/A')}")
|
||||
print(f" PDF: {paper.get('pdf_url', 'N/A')}")
|
||||
|
||||
print("\n✅ ArXiv tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
"""Calculator Tool Example.
|
||||
|
||||
This example demonstrates how to use the Calculator tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
|
||||
Note: No API key required!
|
||||
"""
|
||||
|
||||
from praisonai_tools import CalculatorTool
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize Calculator tool
|
||||
calc = CalculatorTool()
|
||||
|
||||
# Example 1: Basic arithmetic
|
||||
print("=" * 60)
|
||||
print("Example 1: Basic Arithmetic")
|
||||
print("=" * 60)
|
||||
|
||||
expressions = [
|
||||
"2 + 2",
|
||||
"10 * 5",
|
||||
"100 / 4",
|
||||
"2 ** 10",
|
||||
"(10 + 5) * 2 - 3"
|
||||
]
|
||||
|
||||
for expr in expressions:
|
||||
result = calc.evaluate(expr)
|
||||
print(f" {expr} = {result.get('result', result)}")
|
||||
|
||||
# Example 2: Percentage calculation
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 2: Percentage Calculation")
|
||||
print("=" * 60)
|
||||
|
||||
result = calc.evaluate("250 * 0.15")
|
||||
print(f" 15% of 250 = {result.get('result', result)}")
|
||||
|
||||
result = calc.evaluate("1000 * (1 + 0.05) ** 3")
|
||||
print(f" $1000 at 5% for 3 years = ${result.get('result', 0):.2f}")
|
||||
|
||||
print("\n✅ Calculator tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
"""DuckDuckGo Search Tool Example.
|
||||
|
||||
This example demonstrates how to use the DuckDuckGo search tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
|
||||
Note: No API key required!
|
||||
"""
|
||||
|
||||
from praisonai_tools import DuckDuckGoTool
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize DuckDuckGo tool (no API key needed)
|
||||
ddg = DuckDuckGoTool()
|
||||
|
||||
# Example 1: Web search
|
||||
print("=" * 60)
|
||||
print("Example 1: Web Search")
|
||||
print("=" * 60)
|
||||
|
||||
results = ddg.search("Python programming best practices", max_results=3)
|
||||
|
||||
if results and "error" in results[0]:
|
||||
print(f"Error: {results[0]['error']}")
|
||||
else:
|
||||
print(f"Found {len(results)} results:")
|
||||
for r in results:
|
||||
print(f" - {r['title']}")
|
||||
print(f" URL: {r['url']}")
|
||||
print(f" Snippet: {r['snippet'][:100]}...")
|
||||
print()
|
||||
|
||||
# Example 2: News search
|
||||
print("=" * 60)
|
||||
print("Example 2: News Search")
|
||||
print("=" * 60)
|
||||
|
||||
news = ddg.news("artificial intelligence", max_results=3)
|
||||
|
||||
if news and "error" in news[0]:
|
||||
print(f"Error: {news[0]['error']}")
|
||||
else:
|
||||
print(f"Found {len(news)} news articles:")
|
||||
for n in news:
|
||||
print(f" - {n['title']}")
|
||||
print(f" Source: {n.get('source', 'N/A')}")
|
||||
print(f" Date: {n.get('date', 'N/A')}")
|
||||
print()
|
||||
|
||||
# Example 3: Image search
|
||||
print("=" * 60)
|
||||
print("Example 3: Image Search")
|
||||
print("=" * 60)
|
||||
|
||||
images = ddg.images("sunset landscape", max_results=3)
|
||||
|
||||
if images and "error" in images[0]:
|
||||
print(f"Error: {images[0]['error']}")
|
||||
else:
|
||||
print(f"Found {len(images)} images:")
|
||||
for img in images:
|
||||
print(f" - {img['title']}")
|
||||
print(f" Image URL: {img['image_url'][:60]}...")
|
||||
print()
|
||||
|
||||
print("✅ DuckDuckGo tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
"""Exa Search Tool Example.
|
||||
|
||||
This example demonstrates how to use the Exa search tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
export EXA_API_KEY=your_api_key
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
"""
|
||||
|
||||
import os
|
||||
from praisonai_tools import ExaTool
|
||||
|
||||
|
||||
def main():
|
||||
# Check for API key
|
||||
if not os.getenv("EXA_API_KEY"):
|
||||
print("Error: EXA_API_KEY environment variable not set")
|
||||
print("Get your API key from https://exa.ai/")
|
||||
return
|
||||
|
||||
# Initialize Exa tool
|
||||
exa = ExaTool()
|
||||
|
||||
# Example 1: Neural search
|
||||
print("=" * 60)
|
||||
print("Example 1: Neural Search")
|
||||
print("=" * 60)
|
||||
|
||||
results = exa.search("best practices for Python development", num_results=3)
|
||||
|
||||
if results and "error" in results[0]:
|
||||
print(f"Error: {results[0]['error']}")
|
||||
else:
|
||||
print(f"Found {len(results)} results:")
|
||||
for r in results:
|
||||
print(f" - {r.get('title', 'N/A')[:50]}...")
|
||||
print(f" URL: {r.get('url', 'N/A')[:60]}...")
|
||||
print(f" Score: {r.get('score', 'N/A')}")
|
||||
print()
|
||||
|
||||
print("✅ Exa tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
"""Firecrawl Tool Example.
|
||||
|
||||
This example demonstrates how to use the Firecrawl tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
export FIRECRAWL_API_KEY=your_api_key
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
"""
|
||||
|
||||
import os
|
||||
from praisonai_tools import FirecrawlTool
|
||||
|
||||
|
||||
def main():
|
||||
# Check for API key
|
||||
if not os.getenv("FIRECRAWL_API_KEY"):
|
||||
print("Error: FIRECRAWL_API_KEY environment variable not set")
|
||||
print("Get your API key from https://firecrawl.dev/")
|
||||
return
|
||||
|
||||
# Initialize Firecrawl tool
|
||||
firecrawl = FirecrawlTool()
|
||||
|
||||
# Example 1: Scrape a page
|
||||
print("=" * 60)
|
||||
print("Example 1: Scrape a Page")
|
||||
print("=" * 60)
|
||||
|
||||
result = firecrawl.scrape("https://example.com")
|
||||
|
||||
if "error" in result:
|
||||
print(f"Error: {result['error']}")
|
||||
else:
|
||||
print(f"URL: {result.get('url', 'N/A')}")
|
||||
markdown = result.get('markdown', '')
|
||||
print(f"Content preview: {markdown[:300]}...")
|
||||
|
||||
print("\n✅ Firecrawl tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
"""Hacker News Tool Example.
|
||||
|
||||
This example demonstrates how to use the Hacker News tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
|
||||
Note: No API key required!
|
||||
"""
|
||||
|
||||
from praisonai_tools import HackerNewsTool
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize Hacker News tool
|
||||
hn = HackerNewsTool()
|
||||
|
||||
# Example 1: Get top stories
|
||||
print("=" * 60)
|
||||
print("Example 1: Top Stories")
|
||||
print("=" * 60)
|
||||
|
||||
stories = hn.get_top_stories(limit=5)
|
||||
|
||||
if stories and "error" in stories[0]:
|
||||
print(f"Error: {stories[0]['error']}")
|
||||
else:
|
||||
print(f"Found {len(stories)} top stories:")
|
||||
for story in stories:
|
||||
print(f" - {story.get('title', 'N/A')[:50]}...")
|
||||
print(f" Score: {story.get('score', 'N/A')} | Comments: {story.get('descendants', 'N/A')}")
|
||||
print(f" URL: {story.get('url', 'N/A')[:60]}...")
|
||||
print()
|
||||
|
||||
print("✅ Hacker News tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
"""Jina Reader Tool Example.
|
||||
|
||||
This example demonstrates how to use the Jina Reader tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
|
||||
Note: Works without API key (with rate limits)
|
||||
"""
|
||||
|
||||
from praisonai_tools import JinaTool
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize Jina tool (works without API key)
|
||||
jina = JinaTool()
|
||||
|
||||
# Example 1: Read a URL
|
||||
print("=" * 60)
|
||||
print("Example 1: Read URL Content")
|
||||
print("=" * 60)
|
||||
|
||||
result = jina.read("https://example.com")
|
||||
|
||||
if "error" in result:
|
||||
print(f"Error: {result['error']}")
|
||||
else:
|
||||
print(f"URL: {result.get('url', 'N/A')}")
|
||||
print(f"Title: {result.get('title', 'N/A')}")
|
||||
content = result.get('content', '')
|
||||
print(f"Content preview: {content[:300]}...")
|
||||
|
||||
print("\n✅ Jina tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
"""PostgreSQL Tool Example.
|
||||
|
||||
This example demonstrates how to use the PostgreSQL tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
Docker: docker run -d --name postgres -e POSTGRES_PASSWORD=praison123 -e POSTGRES_DB=praisonai -p 5432:5432 postgres:16
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
"""
|
||||
|
||||
from praisonai_tools import PostgresTool
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize PostgreSQL tool
|
||||
pg = PostgresTool(
|
||||
host="localhost",
|
||||
port=5432,
|
||||
database="praisonai",
|
||||
user="postgres",
|
||||
password="praison123"
|
||||
)
|
||||
|
||||
# Example 1: List tables
|
||||
print("=" * 60)
|
||||
print("Example 1: List Tables")
|
||||
print("=" * 60)
|
||||
|
||||
tables = pg.list_tables()
|
||||
|
||||
if isinstance(tables, dict) and "error" in tables:
|
||||
print(f"Error: {tables['error']}")
|
||||
else:
|
||||
print(f"Found {len(tables)} tables:")
|
||||
for t in tables[:10]:
|
||||
print(f" - {t.get('table_name', t)}")
|
||||
|
||||
# Example 2: Query
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 2: Execute Query")
|
||||
print("=" * 60)
|
||||
|
||||
result = pg.query("SELECT version()")
|
||||
|
||||
if isinstance(result, dict) and "error" in result:
|
||||
print(f"Error: {result['error']}")
|
||||
else:
|
||||
print(f"PostgreSQL version: {result}")
|
||||
|
||||
print("\n✅ PostgreSQL tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
"""Serper Tool Example.
|
||||
|
||||
This example demonstrates how to use the Serper (Google Search) tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
export SERPER_API_KEY=your_api_key
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
"""
|
||||
|
||||
import os
|
||||
from praisonai_tools import SerperTool
|
||||
|
||||
|
||||
def main():
|
||||
# Check for API key
|
||||
if not os.getenv("SERPER_API_KEY"):
|
||||
print("Error: SERPER_API_KEY environment variable not set")
|
||||
print("Get your API key from https://serper.dev/")
|
||||
return
|
||||
|
||||
# Initialize Serper tool
|
||||
serper = SerperTool()
|
||||
|
||||
# Example 1: Web search
|
||||
print("=" * 60)
|
||||
print("Example 1: Google Web Search")
|
||||
print("=" * 60)
|
||||
|
||||
results = serper.search("Python programming tutorials", max_results=3)
|
||||
|
||||
if results and "error" in results[0]:
|
||||
print(f"Error: {results[0]['error']}")
|
||||
else:
|
||||
print(f"Found {len(results)} results:")
|
||||
for r in results:
|
||||
print(f" - {r.get('title', 'N/A')[:50]}...")
|
||||
print(f" URL: {r.get('link', 'N/A')[:60]}...")
|
||||
print()
|
||||
|
||||
print("✅ Serper tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
"""Tavily Search Tool Example.
|
||||
|
||||
This example demonstrates how to use the Tavily search tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
export TAVILY_API_KEY=your_api_key
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
"""
|
||||
|
||||
import os
|
||||
from praisonai_tools import TavilyTool
|
||||
|
||||
def main():
|
||||
# Check for API key
|
||||
if not os.getenv("TAVILY_API_KEY"):
|
||||
print("Error: TAVILY_API_KEY environment variable not set")
|
||||
print("Get your API key from https://tavily.com/")
|
||||
return
|
||||
|
||||
# Initialize Tavily tool
|
||||
tavily = TavilyTool()
|
||||
|
||||
# Example 1: Basic search
|
||||
print("=" * 60)
|
||||
print("Example 1: Basic Search")
|
||||
print("=" * 60)
|
||||
|
||||
results = tavily.search("What is quantum computing?", max_results=3)
|
||||
|
||||
if "error" in results:
|
||||
print(f"Error: {results['error']}")
|
||||
else:
|
||||
print(f"Query: {results.get('query')}")
|
||||
if "answer" in results:
|
||||
print(f"\nAI Answer: {results['answer'][:200]}...")
|
||||
print(f"\nResults ({len(results.get('results', []))}):")
|
||||
for r in results.get("results", []):
|
||||
print(f" - {r['title']}")
|
||||
print(f" URL: {r['url']}")
|
||||
print(f" Score: {r.get('score', 'N/A')}")
|
||||
|
||||
# Example 2: Search context for RAG
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 2: Search Context (for RAG)")
|
||||
print("=" * 60)
|
||||
|
||||
context = tavily.search_context("Python best practices 2024")
|
||||
print(f"Context (first 300 chars): {context[:300]}...")
|
||||
|
||||
print("\n✅ Tavily tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
"""Wikipedia Tool Example.
|
||||
|
||||
This example demonstrates how to use the Wikipedia tool with PraisonAI agents.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
|
||||
Usage:
|
||||
python tool.py
|
||||
|
||||
Note: No API key required!
|
||||
"""
|
||||
|
||||
from praisonai_tools import WikipediaTool
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize Wikipedia tool (no API key needed)
|
||||
wiki = WikipediaTool()
|
||||
|
||||
# Example 1: Search Wikipedia
|
||||
print("=" * 60)
|
||||
print("Example 1: Search Wikipedia")
|
||||
print("=" * 60)
|
||||
|
||||
results = wiki.search("Python programming", max_results=5)
|
||||
|
||||
if results and "error" in results[0]:
|
||||
print(f"Error: {results[0]['error']}")
|
||||
else:
|
||||
print(f"Found {len(results)} results:")
|
||||
for r in results:
|
||||
print(f" - {r['title']}")
|
||||
|
||||
# Example 2: Get page summary
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 2: Get Page Summary")
|
||||
print("=" * 60)
|
||||
|
||||
summary = wiki.summary("Artificial intelligence", sentences=3)
|
||||
|
||||
if "error" in summary:
|
||||
print(f"Error: {summary['error']}")
|
||||
else:
|
||||
print(f"Title: {summary['title']}")
|
||||
print(f"Summary: {summary['summary'][:300]}...")
|
||||
|
||||
# Example 3: Get full page
|
||||
print("\n" + "=" * 60)
|
||||
print("Example 3: Get Full Page")
|
||||
print("=" * 60)
|
||||
|
||||
page = wiki.get_page("Machine learning")
|
||||
|
||||
if "error" in page:
|
||||
print(f"Error: {page['error']}")
|
||||
if "options" in page:
|
||||
print(f"Options: {page['options'][:5]}")
|
||||
else:
|
||||
print(f"Title: {page['title']}")
|
||||
print(f"URL: {page['url']}")
|
||||
print(f"Categories: {page['categories'][:3]}")
|
||||
print(f"Content preview: {page['content'][:200]}...")
|
||||
|
||||
print("\n✅ Wikipedia tool working correctly!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,23 @@
|
||||
"""ChromaDB Vector Store - Agent-First Example"""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Agent-first approach: use knowledge parameter with ChromaDB
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "chroma",
|
||||
"path": "/tmp/chroma_demo"
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: ChromaDB with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence import create_knowledge_store
|
||||
# store = create_knowledge_store("chroma", path="/tmp/chroma_demo")
|
||||
@@ -0,0 +1,25 @@
|
||||
"""LanceDB Vector Store - Agent-First Example"""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Agent-first approach: use knowledge parameter with LanceDB
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "lancedb",
|
||||
"path": "/tmp/lancedb_test"
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: LanceDB with Agent")
|
||||
|
||||
# --- Advanced: Direct LanceDB Usage ---
|
||||
# import lancedb
|
||||
# db = lancedb.connect("/tmp/lancedb_test")
|
||||
# data = [{"id": "1", "text": "ML is AI", "vector": [0.1] * 128}]
|
||||
# table = db.create_table("demo", data)
|
||||
@@ -0,0 +1,23 @@
|
||||
"""LanceDB Vector Store - Agent-First Example"""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Agent-first approach: use knowledge parameter with LanceDB
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "lancedb",
|
||||
"path": "/tmp/lancedb_demo"
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: LanceDB with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence import create_knowledge_store
|
||||
# store = create_knowledge_store("lancedb", path="/tmp/lancedb_demo")
|
||||
@@ -0,0 +1,31 @@
|
||||
"""PGVector Vector Store - Agent-First Example
|
||||
|
||||
Docker: docker run -d --name pgvector -e POSTGRES_PASSWORD=postgres -p 5433:5432 pgvector/pgvector:pg16
|
||||
"""
|
||||
import os
|
||||
from praisonaiagents import Agent
|
||||
|
||||
url = os.getenv("PGVECTOR_URL", "postgresql://postgres:postgres@localhost:5433/postgres")
|
||||
|
||||
# Agent-first approach: use knowledge parameter with PGVector
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "pgvector",
|
||||
"url": url
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: PGVector with Agent")
|
||||
|
||||
# --- Advanced: Direct PGVector Usage ---
|
||||
# import psycopg2
|
||||
# conn = psycopg2.connect(url)
|
||||
# cur = conn.cursor()
|
||||
# cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
||||
@@ -0,0 +1,29 @@
|
||||
"""PGVector Knowledge Store - Agent-First Example (requires Docker)
|
||||
|
||||
Docker: docker run -d --name pgvector -e POSTGRES_PASSWORD=postgres -p 5433:5432 pgvector/pgvector:pg16
|
||||
"""
|
||||
import os
|
||||
from praisonaiagents import Agent
|
||||
|
||||
url = os.getenv("PGVECTOR_URL", "postgresql://postgres:postgres@localhost:5433/postgres")
|
||||
|
||||
# Agent-first approach: use knowledge parameter with PGVector
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "pgvector",
|
||||
"url": url
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: PGVector with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence import create_knowledge_store
|
||||
# store = create_knowledge_store("pgvector", url=url)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Pinecone Vector Store - Agent-First Example
|
||||
|
||||
Requires: export PINECONE_API_KEY=...
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from praisonaiagents import Agent
|
||||
|
||||
api_key = os.getenv("PINECONE_API_KEY")
|
||||
if not api_key:
|
||||
print("SKIPPED: Pinecone - PINECONE_API_KEY not set")
|
||||
sys.exit(0)
|
||||
|
||||
# Agent-first approach: use knowledge parameter with Pinecone
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "pinecone",
|
||||
"api_key": api_key,
|
||||
"environment": "us-east-1"
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: Pinecone with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from pinecone import Pinecone
|
||||
# pc = Pinecone(api_key=api_key)
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Qdrant Vector Store - Agent-First Example (requires Docker)
|
||||
|
||||
Docker: docker run -d --name qdrant -p 6333:6333 qdrant/qdrant
|
||||
"""
|
||||
import os
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Agent-first approach: use knowledge parameter with Qdrant
|
||||
url = os.getenv("QDRANT_URL", "http://localhost:6333")
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "qdrant",
|
||||
"url": url
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: Qdrant with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence import create_knowledge_store
|
||||
# store = create_knowledge_store("qdrant", url=url)
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Weaviate Cloud Vector Store - Agent-First Example
|
||||
|
||||
Requires: export WEAVIATE_URL=... and WEAVIATE_API_KEY=...
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from praisonaiagents import Agent
|
||||
|
||||
weaviate_url = os.getenv("WEAVIATE_URL", "https://your-cluster.weaviate.cloud")
|
||||
weaviate_key = os.getenv("WEAVIATE_API_KEY")
|
||||
if not weaviate_key:
|
||||
print("SKIPPED: Weaviate - WEAVIATE_API_KEY not set")
|
||||
sys.exit(0)
|
||||
|
||||
# Agent-first approach: use knowledge parameter with Weaviate
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "weaviate",
|
||||
"url": weaviate_url,
|
||||
"api_key": weaviate_key
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: Weaviate with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# import weaviate
|
||||
# from weaviate.classes.init import Auth
|
||||
# client = weaviate.connect_to_weaviate_cloud(cluster_url=weaviate_url, auth_credentials=Auth.api_key(weaviate_key))
|
||||
@@ -62,6 +62,8 @@ docs/generated/
|
||||
*.log
|
||||
*.sqlite
|
||||
*.db
|
||||
# But keep the db module directory
|
||||
!praisonaiagents/db/
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
|
||||
@@ -43,8 +43,7 @@ from .tools.base import BaseTool, ToolResult, ToolValidationError, validate_tool
|
||||
from .tools.decorator import tool, FunctionTool
|
||||
from .tools.registry import get_registry, register_tool, get_tool, ToolRegistry
|
||||
from .agents.autoagents import AutoAgents
|
||||
from .knowledge.knowledge import Knowledge
|
||||
from .knowledge.chunking import Chunking
|
||||
# Knowledge is lazy-loaded to avoid importing heavy deps (chromadb, mem0) at startup
|
||||
# MCP support (optional)
|
||||
try:
|
||||
from .mcp.mcp import MCP
|
||||
@@ -53,7 +52,9 @@ except ImportError:
|
||||
_mcp_available = False
|
||||
MCP = None
|
||||
from .session import Session
|
||||
from .memory.memory import Memory
|
||||
# Memory is lazy-loaded to avoid importing chromadb at startup
|
||||
from .db import db
|
||||
from .obs import obs
|
||||
# Workflows - import from dedicated workflows module
|
||||
from .workflows import (
|
||||
Workflow, WorkflowStep, WorkflowContext, StepResult,
|
||||
@@ -65,8 +66,16 @@ from .guardrails import GuardrailResult, LLMGuardrail
|
||||
|
||||
# Fast Context support (lazy loaded to avoid performance impact)
|
||||
def __getattr__(name):
|
||||
"""Lazy load FastContext to avoid impacting package load time."""
|
||||
if name == "FastContext":
|
||||
"""Lazy load heavy modules to avoid impacting package load time."""
|
||||
# Knowledge module (imports chromadb, mem0)
|
||||
if name == "Knowledge":
|
||||
from praisonaiagents.knowledge.knowledge import Knowledge
|
||||
return Knowledge
|
||||
elif name == "Chunking":
|
||||
from praisonaiagents.knowledge.chunking import Chunking
|
||||
return Chunking
|
||||
# FastContext support
|
||||
elif name == "FastContext":
|
||||
from praisonaiagents.context.fast import FastContext
|
||||
return FastContext
|
||||
elif name == "FastContextResult":
|
||||
@@ -91,6 +100,10 @@ def __getattr__(name):
|
||||
elif name == "SkillLoader":
|
||||
from praisonaiagents.skills import SkillLoader
|
||||
return SkillLoader
|
||||
# Memory module (imports chromadb)
|
||||
elif name == "Memory":
|
||||
from praisonaiagents.memory.memory import Memory
|
||||
return Memory
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
# Planning mode support
|
||||
@@ -257,6 +270,8 @@ __all__ = [
|
||||
'AutoAgents',
|
||||
'Session',
|
||||
'Memory',
|
||||
'db',
|
||||
'obs',
|
||||
'display_interaction',
|
||||
'display_self_reflection',
|
||||
'display_instruction',
|
||||
|
||||
@@ -61,6 +61,57 @@ class Agent:
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
|
||||
@classmethod
|
||||
def from_template(
|
||||
cls,
|
||||
uri: str,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
offline: bool = False,
|
||||
**kwargs
|
||||
) -> 'Agent':
|
||||
"""
|
||||
Create an Agent from a template.
|
||||
|
||||
Args:
|
||||
uri: Template URI (local path, package ref, or github ref)
|
||||
Examples:
|
||||
- "./my-template" (local path)
|
||||
- "transcript-generator" (default recipes repo)
|
||||
- "github:owner/repo/[email protected]" (GitHub with version)
|
||||
- "package:agent_recipes/transcript-generator" (installed package)
|
||||
config: Optional configuration overrides
|
||||
offline: If True, only use cached templates (no network)
|
||||
**kwargs: Additional Agent constructor arguments
|
||||
|
||||
Returns:
|
||||
Configured Agent instance
|
||||
|
||||
Example:
|
||||
```python
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# From default recipes repo
|
||||
agent = Agent.from_template("transcript-generator")
|
||||
result = agent.chat("Transcribe ./audio.mp3")
|
||||
|
||||
# With config overrides
|
||||
agent = Agent.from_template(
|
||||
"data-transformer",
|
||||
config={"output_format": "json"},
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
"""
|
||||
try:
|
||||
# Lazy import to avoid circular dependencies and keep core SDK lean
|
||||
from praisonai.templates.loader import create_agent_from_template
|
||||
return create_agent_from_template(uri, config=config, offline=offline, **kwargs)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Template support requires the 'praisonai' package. "
|
||||
"Install with: pip install praisonai"
|
||||
)
|
||||
|
||||
def _generate_tool_definition(self, function_name):
|
||||
"""
|
||||
Generate a tool definition from a function name by inspecting the function.
|
||||
@@ -252,7 +303,12 @@ class Agent:
|
||||
history_in_context: Optional[int] = None,
|
||||
auto_save: Optional[str] = None,
|
||||
skills: Optional[List[str]] = None,
|
||||
skills_dirs: Optional[List[str]] = None
|
||||
skills_dirs: Optional[List[str]] = None,
|
||||
db: Optional[Any] = None,
|
||||
session_id: Optional[str] = None,
|
||||
hooks: Optional[List[Any]] = None,
|
||||
llm_config: Optional[Dict[str, Any]] = None,
|
||||
rate_limiter: Optional[Any] = None
|
||||
):
|
||||
"""Initialize an Agent instance.
|
||||
|
||||
@@ -393,6 +449,17 @@ class Agent:
|
||||
# Flag to track if final result has been displayed to prevent duplicates
|
||||
self._final_display_shown = False
|
||||
|
||||
# Store hooks for middleware system (zero overhead when empty)
|
||||
self._hooks = hooks or []
|
||||
self._middleware_manager = None # Lazy init
|
||||
|
||||
# Store llm_config for configurable model switching
|
||||
self._llm_config = llm_config or {}
|
||||
self._llm_configurable = self._llm_config.get('configurable', False)
|
||||
|
||||
# Store rate limiter (optional, zero overhead when None)
|
||||
self._rate_limiter = rate_limiter
|
||||
|
||||
# Store OpenAI client parameters for lazy initialization
|
||||
self._openai_api_key = api_key
|
||||
self._openai_base_url = base_url
|
||||
@@ -615,6 +682,11 @@ Your Goal: {self.goal}
|
||||
self._skill_manager = None # Lazy loaded
|
||||
self._skills_initialized = False
|
||||
|
||||
# Database persistence (lazy - no imports until used)
|
||||
self._db = db
|
||||
self._session_id = session_id
|
||||
self._db_initialized = False
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
"""Lazily initialize Rich Console only when needed."""
|
||||
@@ -651,8 +723,45 @@ Your Goal: {self.goal}
|
||||
self._skill_manager.discover(self._skills_dirs, include_defaults=False)
|
||||
|
||||
self._skills_initialized = True
|
||||
|
||||
# Auto-add skill execution tools if not already present
|
||||
self._add_skill_tools()
|
||||
return self._skill_manager
|
||||
|
||||
def _add_skill_tools(self):
|
||||
"""Add tools required for skill execution (read_file, run_skill_script).
|
||||
|
||||
Uses lazy imports from praisonaiagents.tools to avoid performance impact
|
||||
when skills are not used.
|
||||
"""
|
||||
# Check if tools already include required capabilities
|
||||
tool_names = set()
|
||||
for tool in self.tools:
|
||||
if callable(tool) and hasattr(tool, '__name__'):
|
||||
tool_names.add(tool.__name__)
|
||||
elif hasattr(tool, 'name'):
|
||||
tool_names.add(tool.name)
|
||||
|
||||
# Add read_file if not present
|
||||
if 'read_file' not in tool_names:
|
||||
try:
|
||||
from ..tools import read_file
|
||||
self.tools.append(read_file)
|
||||
logging.debug("Added read_file tool for skill support")
|
||||
except ImportError:
|
||||
logging.warning("Could not import read_file tool for skills")
|
||||
|
||||
# Add run_skill_script from skill_tools module
|
||||
if 'run_skill_script' not in tool_names:
|
||||
try:
|
||||
from ..tools.skill_tools import create_skill_tools
|
||||
# Create skill tools with current working directory
|
||||
skill_tools = create_skill_tools()
|
||||
self.tools.append(skill_tools.run_skill_script)
|
||||
logging.debug("Added run_skill_script tool for skill support")
|
||||
except ImportError as e:
|
||||
logging.warning(f"Could not import skill_tools: {e}")
|
||||
|
||||
def get_skills_prompt(self) -> str:
|
||||
"""Get the XML prompt for available skills.
|
||||
|
||||
@@ -1321,6 +1430,13 @@ Your Goal: {self.goal}"""
|
||||
if self.verbose:
|
||||
self._display_memory_info()
|
||||
|
||||
# Add skills prompt if skills are configured
|
||||
if self._skills or self._skills_dirs:
|
||||
skills_prompt = self.get_skills_prompt()
|
||||
if skills_prompt:
|
||||
system_prompt += f"\n\n## Available Skills\n{skills_prompt}"
|
||||
system_prompt += "\n\nWhen a skill is relevant to the task, read its SKILL.md file to get detailed instructions. If the skill has scripts in its scripts/ directory, you can execute them using the execute_code or run_script tool."
|
||||
|
||||
# Add tool usage instructions if tools are available
|
||||
# Use provided tools or fall back to self.tools
|
||||
tools_to_use = tools if tools is not None else self.tools
|
||||
@@ -1571,8 +1687,32 @@ Your Goal: {self.goal}"""
|
||||
def execute_tool(self, function_name, arguments):
|
||||
"""
|
||||
Execute a tool dynamically based on the function name and arguments.
|
||||
Injects agent state for tools with Injected[T] parameters.
|
||||
"""
|
||||
logging.debug(f"{self.name} executing tool {function_name} with arguments: {arguments}")
|
||||
|
||||
# Set up injection context for tools with Injected parameters
|
||||
from ..tools.injected import AgentState, with_injection_context
|
||||
state = AgentState(
|
||||
agent_id=self.name,
|
||||
run_id=getattr(self, '_current_run_id', 'unknown'),
|
||||
session_id=getattr(self, '_session_id', None) or 'default',
|
||||
last_user_message=self.chat_history[-1].get('content') if self.chat_history else None,
|
||||
metadata={'agent_name': self.name}
|
||||
)
|
||||
|
||||
# Execute within injection context
|
||||
return self._execute_tool_with_context(function_name, arguments, state)
|
||||
|
||||
def _execute_tool_with_context(self, function_name, arguments, state):
|
||||
"""Execute tool within injection context."""
|
||||
from ..tools.injected import with_injection_context
|
||||
|
||||
with with_injection_context(state):
|
||||
return self._execute_tool_impl(function_name, arguments)
|
||||
|
||||
def _execute_tool_impl(self, function_name, arguments):
|
||||
"""Internal tool execution implementation."""
|
||||
|
||||
# Check if approval is required for this tool
|
||||
from ..approval import is_approval_required, console_approval_callback, get_risk_level, mark_approved, ApprovalDecision, get_approval_callback
|
||||
@@ -1605,7 +1745,11 @@ Your Goal: {self.goal}"""
|
||||
|
||||
# Special handling for MCP tools
|
||||
# Check if tools is an MCP instance with the requested function name
|
||||
from ..mcp.mcp import MCP
|
||||
MCP = None
|
||||
try:
|
||||
from ..mcp.mcp import MCP
|
||||
except ImportError:
|
||||
pass # MCP not available
|
||||
|
||||
# Helper function to execute MCP tool
|
||||
def _execute_mcp_tool(mcp_instance, func_name, args):
|
||||
@@ -1640,7 +1784,7 @@ Your Goal: {self.goal}"""
|
||||
return False, None
|
||||
|
||||
# Check if tools is a single MCP instance
|
||||
if isinstance(self.tools, MCP):
|
||||
if MCP is not None and isinstance(self.tools, MCP):
|
||||
logging.debug(f"Looking for MCP tool {function_name}")
|
||||
found, result = _execute_mcp_tool(self.tools, function_name, arguments)
|
||||
if found:
|
||||
@@ -1649,7 +1793,7 @@ Your Goal: {self.goal}"""
|
||||
# Check if tools is a list that may contain MCP instances
|
||||
if isinstance(self.tools, (list, tuple)):
|
||||
for tool in self.tools:
|
||||
if isinstance(tool, MCP):
|
||||
if MCP is not None and isinstance(tool, MCP):
|
||||
logging.debug(f"Looking for MCP tool {function_name} in MCP instance")
|
||||
found, result = _execute_mcp_tool(tool, function_name, arguments)
|
||||
if found:
|
||||
@@ -1913,7 +2057,112 @@ Your Goal: {self.goal}"""
|
||||
# expand=False
|
||||
# )
|
||||
|
||||
def chat(self, prompt, temperature=1.0, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False, stream=None, task_name=None, task_description=None, task_id=None):
|
||||
def _init_db_session(self):
|
||||
"""Initialize DB session if db adapter is provided (lazy, first chat only)."""
|
||||
if self._db is None or self._db_initialized:
|
||||
return
|
||||
|
||||
# Generate session_id if not provided: default to per-hour ID (YYYYMMDDHH-agentname)
|
||||
if self._session_id is None:
|
||||
import hashlib
|
||||
from datetime import datetime, timezone
|
||||
# Per-hour session ID: YYYYMMDDHH (UTC) + agent name hash for uniqueness
|
||||
hour_str = datetime.now(timezone.utc).strftime("%Y%m%d%H")
|
||||
agent_hash = hashlib.md5(self.name.encode()).hexdigest()[:6]
|
||||
self._session_id = f"{hour_str}-{agent_hash}"
|
||||
|
||||
# Call db adapter's on_agent_start to get previous messages
|
||||
try:
|
||||
history = self._db.on_agent_start(
|
||||
agent_name=self.name,
|
||||
session_id=self._session_id,
|
||||
user_id=self.user_id,
|
||||
metadata={"role": self.role, "goal": self.goal}
|
||||
)
|
||||
|
||||
# Restore chat history from previous session
|
||||
if history:
|
||||
for msg in history:
|
||||
self.chat_history.append({
|
||||
"role": msg.role,
|
||||
"content": msg.content
|
||||
})
|
||||
logging.info(f"Resumed session {self._session_id} with {len(history)} messages")
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to initialize DB session: {e}")
|
||||
|
||||
self._db_initialized = True
|
||||
self._current_run_id = None # Track current run
|
||||
|
||||
def _start_run(self, input_content: str):
|
||||
"""Start a new run (turn) for persistence tracking."""
|
||||
if self._db is None:
|
||||
return
|
||||
|
||||
import uuid
|
||||
self._current_run_id = f"run-{uuid.uuid4().hex[:12]}"
|
||||
|
||||
try:
|
||||
if hasattr(self._db, 'on_run_start'):
|
||||
self._db.on_run_start(
|
||||
session_id=self._session_id,
|
||||
run_id=self._current_run_id,
|
||||
input_content=input_content,
|
||||
metadata={"agent_name": self.name}
|
||||
)
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to start run: {e}")
|
||||
|
||||
def _end_run(self, output_content: str, status: str = "completed", metrics: dict = None):
|
||||
"""End the current run (turn)."""
|
||||
if self._db is None or self._current_run_id is None:
|
||||
return
|
||||
|
||||
try:
|
||||
if hasattr(self._db, 'on_run_end'):
|
||||
self._db.on_run_end(
|
||||
session_id=self._session_id,
|
||||
run_id=self._current_run_id,
|
||||
output_content=output_content,
|
||||
status=status,
|
||||
metrics=metrics or {},
|
||||
metadata={"agent_name": self.name}
|
||||
)
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to end run: {e}")
|
||||
|
||||
self._current_run_id = None
|
||||
|
||||
def _persist_message(self, role: str, content: str):
|
||||
"""Persist a message to the DB if adapter is provided."""
|
||||
if self._db is None:
|
||||
return
|
||||
|
||||
try:
|
||||
if role == "user":
|
||||
self._db.on_user_message(self._session_id, content)
|
||||
elif role == "assistant":
|
||||
self._db.on_agent_message(self._session_id, content)
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to persist message: {e}")
|
||||
|
||||
@property
|
||||
def session_id(self) -> Optional[str]:
|
||||
"""Get the current session ID."""
|
||||
return self._session_id
|
||||
|
||||
def chat(self, prompt, temperature=1.0, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False, stream=None, task_name=None, task_description=None, task_id=None, config=None):
|
||||
# Apply rate limiter if configured (before any LLM call)
|
||||
if self._rate_limiter is not None:
|
||||
self._rate_limiter.acquire()
|
||||
|
||||
# Initialize DB session on first chat (lazy)
|
||||
self._init_db_session()
|
||||
|
||||
# Start a new run for this chat turn
|
||||
prompt_str = prompt if isinstance(prompt, str) else str(prompt)
|
||||
self._start_run(prompt_str)
|
||||
|
||||
# Reset the final display flag for each new conversation
|
||||
self._final_display_shown = False
|
||||
|
||||
@@ -1966,8 +2215,12 @@ Your Goal: {self.goal}"""
|
||||
|
||||
# Convert MCP tool objects to OpenAI format if needed
|
||||
if tool_param is not None:
|
||||
from ..mcp.mcp import MCP
|
||||
if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
|
||||
MCP = None
|
||||
try:
|
||||
from ..mcp.mcp import MCP
|
||||
except ImportError:
|
||||
pass
|
||||
if MCP is not None and isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
|
||||
# Single MCP instance
|
||||
logging.debug("Converting single MCP tool to OpenAI format")
|
||||
openai_tool = tool_param.to_openai_tool()
|
||||
@@ -1982,7 +2235,7 @@ Your Goal: {self.goal}"""
|
||||
# List that may contain MCP instances - convert each MCP to OpenAI format
|
||||
converted_tools = []
|
||||
for t in tool_param:
|
||||
if isinstance(t, MCP) and hasattr(t, 'to_openai_tool'):
|
||||
if MCP is not None and isinstance(t, MCP) and hasattr(t, 'to_openai_tool'):
|
||||
logging.debug("Converting MCP instance in list to OpenAI format")
|
||||
openai_tools = t.to_openai_tool()
|
||||
if isinstance(openai_tools, list):
|
||||
@@ -2010,6 +2263,8 @@ Your Goal: {self.goal}"""
|
||||
self.chat_history[-1].get("content") == normalized_content):
|
||||
# Add user message to chat history BEFORE LLM call so handoffs can access it
|
||||
self.chat_history.append({"role": "user", "content": normalized_content})
|
||||
# Persist user message to DB
|
||||
self._persist_message("user", normalized_content)
|
||||
|
||||
try:
|
||||
# Pass everything to LLM class
|
||||
@@ -2039,6 +2294,8 @@ Your Goal: {self.goal}"""
|
||||
)
|
||||
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
# Persist assistant message to DB
|
||||
self._persist_message("assistant", response_text)
|
||||
|
||||
# Log completion time if in debug mode
|
||||
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
|
||||
@@ -2083,6 +2340,8 @@ Your Goal: {self.goal}"""
|
||||
self.chat_history[-1].get("content") == normalized_content):
|
||||
# Add user message to chat history BEFORE LLM call so handoffs can access it
|
||||
self.chat_history.append({"role": "user", "content": normalized_content})
|
||||
# Persist user message to DB (OpenAI path)
|
||||
self._persist_message("user", normalized_content)
|
||||
|
||||
reflection_count = 0
|
||||
start_time = time.time()
|
||||
@@ -2122,6 +2381,8 @@ Your Goal: {self.goal}"""
|
||||
# Add to chat history and return raw response
|
||||
# User message already added before LLM call via _build_messages
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
# Persist assistant message to DB
|
||||
self._persist_message("assistant", response_text)
|
||||
# Apply guardrail validation even for JSON output
|
||||
try:
|
||||
validated_response = self._apply_guardrail_with_retry(response_text, original_prompt, temperature, tools, task_name, task_description, task_id)
|
||||
@@ -2137,6 +2398,8 @@ Your Goal: {self.goal}"""
|
||||
if not self.self_reflect:
|
||||
# User message already added before LLM call via _build_messages
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
# Persist assistant message to DB (non-reflect path)
|
||||
self._persist_message("assistant", response_text)
|
||||
if self.verbose:
|
||||
logging.debug(f"Agent {self.name} final response: {response_text}")
|
||||
# Return only reasoning content if reasoning_steps is True
|
||||
@@ -2225,11 +2488,13 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
validated_response = self._apply_guardrail_with_retry(response_text, original_prompt, temperature, tools, task_name, task_description, task_id)
|
||||
# Execute callback after validation
|
||||
self._execute_callback_and_display(original_prompt, validated_response, time.time() - start_time, task_name, task_description, task_id)
|
||||
self._end_run(validated_response, "completed", {"duration_ms": (time.time() - start_time) * 1000})
|
||||
return validated_response
|
||||
except Exception as e:
|
||||
logging.error(f"Agent {self.name}: Guardrail validation failed after reflection: {e}")
|
||||
# Rollback chat history on guardrail failure
|
||||
self.chat_history = self.chat_history[:chat_history_length]
|
||||
self._end_run(None, "error", {"error": str(e)})
|
||||
return None
|
||||
|
||||
# Check if we've hit max reflections
|
||||
@@ -2905,8 +3170,12 @@ Write the complete compiled report:"""
|
||||
|
||||
# Convert MCP tools if needed
|
||||
if tool_param is not None:
|
||||
from ..mcp.mcp import MCP
|
||||
if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
|
||||
MCP = None
|
||||
try:
|
||||
from ..mcp.mcp import MCP
|
||||
except ImportError:
|
||||
pass
|
||||
if MCP is not None and isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
|
||||
openai_tool = tool_param.to_openai_tool()
|
||||
if openai_tool:
|
||||
if isinstance(openai_tool, list):
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Background Agents Module for PraisonAI Agents.
|
||||
|
||||
Provides the ability to run agents in the background, allowing:
|
||||
- Long-running tasks without blocking
|
||||
- Task queuing and management
|
||||
- Progress monitoring and notifications
|
||||
- Graceful cancellation
|
||||
|
||||
Zero Performance Impact:
|
||||
- All imports are lazy loaded via __getattr__
|
||||
- Background processing only when explicitly started
|
||||
- No overhead when not in use
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.background import BackgroundRunner, BackgroundTask
|
||||
|
||||
# Create a background runner
|
||||
runner = BackgroundRunner()
|
||||
|
||||
# Submit a task
|
||||
task = runner.submit(
|
||||
agent=my_agent,
|
||||
prompt="Research AI trends",
|
||||
callback=on_complete
|
||||
)
|
||||
|
||||
# Check status
|
||||
print(task.status) # "running", "completed", "failed"
|
||||
|
||||
# Wait for completion
|
||||
result = await task.wait()
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
# Core classes
|
||||
"BackgroundRunner",
|
||||
"BackgroundTask",
|
||||
# Status enum
|
||||
"TaskStatus",
|
||||
# Configuration
|
||||
"BackgroundConfig",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy load module components to avoid import overhead."""
|
||||
if name == "BackgroundRunner":
|
||||
from .runner import BackgroundRunner
|
||||
return BackgroundRunner
|
||||
|
||||
if name == "BackgroundTask":
|
||||
from .task import BackgroundTask
|
||||
return BackgroundTask
|
||||
|
||||
if name == "TaskStatus":
|
||||
from .task import TaskStatus
|
||||
return TaskStatus
|
||||
|
||||
if name == "BackgroundConfig":
|
||||
from .config import BackgroundConfig
|
||||
return BackgroundConfig
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Background Configuration for PraisonAI Agents.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class BackgroundConfig:
|
||||
"""Configuration for background task execution."""
|
||||
max_concurrent_tasks: int = 5
|
||||
default_timeout: Optional[float] = None # None = no timeout
|
||||
auto_cleanup: bool = True # Auto-remove completed tasks
|
||||
cleanup_delay: float = 300.0 # Seconds to keep completed tasks
|
||||
enable_notifications: bool = True
|
||||
log_progress: bool = True
|
||||
@@ -0,0 +1,314 @@
|
||||
"""
|
||||
Background Runner for PraisonAI Agents.
|
||||
|
||||
Manages background task execution.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Optional, List, Dict, Any, Callable, Union
|
||||
from datetime import datetime
|
||||
|
||||
from .task import BackgroundTask, TaskStatus
|
||||
from .config import BackgroundConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BackgroundRunner:
|
||||
"""
|
||||
Manages background task execution.
|
||||
|
||||
Provides:
|
||||
- Task submission and queuing
|
||||
- Concurrent execution management
|
||||
- Progress monitoring
|
||||
- Task cancellation
|
||||
|
||||
Example:
|
||||
runner = BackgroundRunner()
|
||||
|
||||
# Submit a task
|
||||
task = await runner.submit(
|
||||
func=my_agent.start,
|
||||
args=("Research AI trends",),
|
||||
name="research_task"
|
||||
)
|
||||
|
||||
# Check status
|
||||
print(task.status)
|
||||
|
||||
# Wait for result
|
||||
result = await task.wait()
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[BackgroundConfig] = None):
|
||||
"""
|
||||
Initialize the background runner.
|
||||
|
||||
Args:
|
||||
config: Configuration for background execution
|
||||
"""
|
||||
self.config = config or BackgroundConfig()
|
||||
self._tasks: Dict[str, BackgroundTask] = {}
|
||||
self._semaphore = asyncio.Semaphore(self.config.max_concurrent_tasks)
|
||||
self._running = False
|
||||
self._cleanup_task: Optional[asyncio.Task] = None
|
||||
|
||||
@property
|
||||
def tasks(self) -> List[BackgroundTask]:
|
||||
"""Get all tasks."""
|
||||
return list(self._tasks.values())
|
||||
|
||||
@property
|
||||
def running_tasks(self) -> List[BackgroundTask]:
|
||||
"""Get currently running tasks."""
|
||||
return [t for t in self._tasks.values() if t.is_running]
|
||||
|
||||
@property
|
||||
def pending_tasks(self) -> List[BackgroundTask]:
|
||||
"""Get pending tasks."""
|
||||
return [t for t in self._tasks.values() if t.status == TaskStatus.PENDING]
|
||||
|
||||
async def submit(
|
||||
self,
|
||||
func: Callable,
|
||||
args: tuple = (),
|
||||
kwargs: Optional[Dict[str, Any]] = None,
|
||||
name: Optional[str] = None,
|
||||
timeout: Optional[float] = None,
|
||||
on_complete: Optional[Callable[[BackgroundTask], None]] = None,
|
||||
on_progress: Optional[Callable[[BackgroundTask], None]] = None
|
||||
) -> BackgroundTask:
|
||||
"""
|
||||
Submit a task for background execution.
|
||||
|
||||
Args:
|
||||
func: Function to execute (can be sync or async)
|
||||
args: Positional arguments for the function
|
||||
kwargs: Keyword arguments for the function
|
||||
name: Optional task name
|
||||
timeout: Optional timeout in seconds
|
||||
on_complete: Callback when task completes
|
||||
on_progress: Callback for progress updates
|
||||
|
||||
Returns:
|
||||
BackgroundTask object for tracking
|
||||
"""
|
||||
kwargs = kwargs or {}
|
||||
|
||||
task = BackgroundTask(
|
||||
name=name or func.__name__,
|
||||
metadata={
|
||||
"func_name": func.__name__,
|
||||
"timeout": timeout or self.config.default_timeout
|
||||
}
|
||||
)
|
||||
|
||||
self._tasks[task.id] = task
|
||||
|
||||
# Create the execution coroutine
|
||||
async def execute():
|
||||
async with self._semaphore:
|
||||
task.start()
|
||||
logger.info(f"Background task started: {task.name} ({task.id})")
|
||||
|
||||
try:
|
||||
# Check if function is async
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
result = await asyncio.wait_for(
|
||||
func(*args, **kwargs),
|
||||
timeout=timeout or self.config.default_timeout
|
||||
)
|
||||
else:
|
||||
# Run sync function in thread pool
|
||||
loop = asyncio.get_event_loop()
|
||||
result = await asyncio.wait_for(
|
||||
loop.run_in_executor(None, lambda: func(*args, **kwargs)),
|
||||
timeout=timeout or self.config.default_timeout
|
||||
)
|
||||
|
||||
task.complete(result)
|
||||
logger.info(f"Background task completed: {task.name} ({task.id})")
|
||||
|
||||
if on_complete:
|
||||
try:
|
||||
on_complete(task)
|
||||
except Exception as e:
|
||||
logger.warning(f"on_complete callback error: {e}")
|
||||
|
||||
return result
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
task.fail(f"Task timed out after {timeout}s")
|
||||
logger.warning(f"Background task timed out: {task.name}")
|
||||
|
||||
except asyncio.CancelledError:
|
||||
task.cancel()
|
||||
logger.info(f"Background task cancelled: {task.name}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
task.fail(str(e))
|
||||
logger.error(f"Background task failed: {task.name} - {e}")
|
||||
|
||||
if on_complete:
|
||||
try:
|
||||
on_complete(task)
|
||||
except Exception as cb_error:
|
||||
logger.warning(f"on_complete callback error: {cb_error}")
|
||||
|
||||
# Create and store the future
|
||||
task._future = asyncio.create_task(execute())
|
||||
|
||||
return task
|
||||
|
||||
async def submit_agent(
|
||||
self,
|
||||
agent: Any,
|
||||
prompt: str,
|
||||
name: Optional[str] = None,
|
||||
timeout: Optional[float] = None,
|
||||
on_complete: Optional[Callable[[BackgroundTask], None]] = None
|
||||
) -> BackgroundTask:
|
||||
"""
|
||||
Submit an agent task for background execution.
|
||||
|
||||
Args:
|
||||
agent: Agent instance to run
|
||||
prompt: Prompt to send to the agent
|
||||
name: Optional task name
|
||||
timeout: Optional timeout in seconds
|
||||
on_complete: Callback when task completes
|
||||
|
||||
Returns:
|
||||
BackgroundTask object for tracking
|
||||
"""
|
||||
task_name = name or f"agent_{getattr(agent, 'name', 'unknown')}"
|
||||
|
||||
# Determine the method to call
|
||||
if hasattr(agent, 'start'):
|
||||
func = agent.start
|
||||
elif hasattr(agent, 'chat'):
|
||||
func = agent.chat
|
||||
elif hasattr(agent, 'run'):
|
||||
func = agent.run
|
||||
else:
|
||||
raise ValueError("Agent must have start(), chat(), or run() method")
|
||||
|
||||
return await self.submit(
|
||||
func=func,
|
||||
args=(prompt,),
|
||||
name=task_name,
|
||||
timeout=timeout,
|
||||
on_complete=on_complete
|
||||
)
|
||||
|
||||
def get_task(self, task_id: str) -> Optional[BackgroundTask]:
|
||||
"""Get a task by ID."""
|
||||
return self._tasks.get(task_id)
|
||||
|
||||
async def cancel_task(self, task_id: str) -> bool:
|
||||
"""
|
||||
Cancel a task.
|
||||
|
||||
Args:
|
||||
task_id: ID of task to cancel
|
||||
|
||||
Returns:
|
||||
True if cancelled, False if not found or already completed
|
||||
"""
|
||||
task = self._tasks.get(task_id)
|
||||
if task is None:
|
||||
return False
|
||||
|
||||
if task.is_completed:
|
||||
return False
|
||||
|
||||
task.cancel()
|
||||
|
||||
if task._future and not task._future.done():
|
||||
task._future.cancel()
|
||||
|
||||
return True
|
||||
|
||||
async def cancel_all(self):
|
||||
"""Cancel all running tasks."""
|
||||
for task in self.running_tasks:
|
||||
await self.cancel_task(task.id)
|
||||
|
||||
async def wait_all(self, timeout: Optional[float] = None) -> List[BackgroundTask]:
|
||||
"""
|
||||
Wait for all tasks to complete.
|
||||
|
||||
Args:
|
||||
timeout: Maximum time to wait
|
||||
|
||||
Returns:
|
||||
List of completed tasks
|
||||
"""
|
||||
futures = [t._future for t in self._tasks.values() if t._future]
|
||||
|
||||
if not futures:
|
||||
return list(self._tasks.values())
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.gather(*futures, return_exceptions=True),
|
||||
timeout=timeout
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
return list(self._tasks.values())
|
||||
|
||||
def clear_completed(self):
|
||||
"""Remove completed tasks from tracking."""
|
||||
completed_ids = [
|
||||
task_id for task_id, task in self._tasks.items()
|
||||
if task.is_completed
|
||||
]
|
||||
for task_id in completed_ids:
|
||||
del self._tasks[task_id]
|
||||
|
||||
def list_tasks(self, status: Optional[TaskStatus] = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
List tasks with optional status filter.
|
||||
|
||||
Args:
|
||||
status: Optional status to filter by
|
||||
|
||||
Returns:
|
||||
List of task dictionaries
|
||||
"""
|
||||
tasks = self._tasks.values()
|
||||
|
||||
if status:
|
||||
tasks = [t for t in tasks if t.status == status]
|
||||
|
||||
return [t.to_dict() for t in tasks]
|
||||
|
||||
async def start_cleanup_loop(self):
|
||||
"""Start the automatic cleanup loop."""
|
||||
if self._cleanup_task is not None:
|
||||
return
|
||||
|
||||
async def cleanup_loop():
|
||||
while True:
|
||||
await asyncio.sleep(self.config.cleanup_delay)
|
||||
if self.config.auto_cleanup:
|
||||
self.clear_completed()
|
||||
|
||||
self._cleanup_task = asyncio.create_task(cleanup_loop())
|
||||
|
||||
async def stop(self):
|
||||
"""Stop the runner and cancel all tasks."""
|
||||
await self.cancel_all()
|
||||
|
||||
if self._cleanup_task:
|
||||
self._cleanup_task.cancel()
|
||||
try:
|
||||
await self._cleanup_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._cleanup_task = None
|
||||
@@ -0,0 +1,154 @@
|
||||
"""
|
||||
Background Task for PraisonAI Agents.
|
||||
|
||||
Represents a task running in the background.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
import asyncio
|
||||
import logging
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Any, Callable, Dict
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TaskStatus(str, Enum):
|
||||
"""Status of a background task."""
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
@dataclass
|
||||
class BackgroundTask:
|
||||
"""
|
||||
Represents a background task.
|
||||
|
||||
Tracks the status, progress, and result of a task running
|
||||
in the background.
|
||||
"""
|
||||
id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
|
||||
name: str = ""
|
||||
status: TaskStatus = TaskStatus.PENDING
|
||||
progress: float = 0.0 # 0.0 to 1.0
|
||||
result: Any = None
|
||||
error: Optional[str] = None
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
started_at: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
# Internal
|
||||
_future: Optional[asyncio.Future] = field(default=None, repr=False)
|
||||
_cancel_event: Optional[asyncio.Event] = field(default=None, repr=False)
|
||||
|
||||
def __post_init__(self):
|
||||
self._cancel_event = asyncio.Event()
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""Check if task is currently running."""
|
||||
return self.status == TaskStatus.RUNNING
|
||||
|
||||
@property
|
||||
def is_completed(self) -> bool:
|
||||
"""Check if task has completed (success or failure)."""
|
||||
return self.status in (TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELLED)
|
||||
|
||||
@property
|
||||
def is_successful(self) -> bool:
|
||||
"""Check if task completed successfully."""
|
||||
return self.status == TaskStatus.COMPLETED
|
||||
|
||||
@property
|
||||
def duration_seconds(self) -> Optional[float]:
|
||||
"""Get task duration in seconds."""
|
||||
if self.started_at is None:
|
||||
return None
|
||||
end_time = self.completed_at or datetime.now()
|
||||
return (end_time - self.started_at).total_seconds()
|
||||
|
||||
def start(self):
|
||||
"""Mark task as started."""
|
||||
self.status = TaskStatus.RUNNING
|
||||
self.started_at = datetime.now()
|
||||
|
||||
def complete(self, result: Any = None):
|
||||
"""Mark task as completed."""
|
||||
self.status = TaskStatus.COMPLETED
|
||||
self.result = result
|
||||
self.progress = 1.0
|
||||
self.completed_at = datetime.now()
|
||||
|
||||
def fail(self, error: str):
|
||||
"""Mark task as failed."""
|
||||
self.status = TaskStatus.FAILED
|
||||
self.error = error
|
||||
self.completed_at = datetime.now()
|
||||
|
||||
def cancel(self):
|
||||
"""Request task cancellation."""
|
||||
if self._cancel_event:
|
||||
self._cancel_event.set()
|
||||
self.status = TaskStatus.CANCELLED
|
||||
self.completed_at = datetime.now()
|
||||
|
||||
def update_progress(self, progress: float, message: Optional[str] = None):
|
||||
"""Update task progress."""
|
||||
self.progress = min(max(progress, 0.0), 1.0)
|
||||
if message:
|
||||
self.metadata["last_message"] = message
|
||||
|
||||
async def wait(self, timeout: Optional[float] = None) -> Any:
|
||||
"""
|
||||
Wait for task completion.
|
||||
|
||||
Args:
|
||||
timeout: Maximum time to wait in seconds
|
||||
|
||||
Returns:
|
||||
Task result
|
||||
|
||||
Raises:
|
||||
asyncio.TimeoutError: If timeout exceeded
|
||||
RuntimeError: If task failed
|
||||
"""
|
||||
if self._future is None:
|
||||
if self.is_completed:
|
||||
if self.status == TaskStatus.FAILED:
|
||||
raise RuntimeError(self.error)
|
||||
return self.result
|
||||
raise RuntimeError("Task has no associated future")
|
||||
|
||||
try:
|
||||
result = await asyncio.wait_for(self._future, timeout=timeout)
|
||||
return result
|
||||
except asyncio.TimeoutError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise RuntimeError(str(e))
|
||||
|
||||
def should_cancel(self) -> bool:
|
||||
"""Check if cancellation was requested."""
|
||||
return self._cancel_event is not None and self._cancel_event.is_set()
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"status": self.status.value,
|
||||
"progress": self.progress,
|
||||
"result": str(self.result) if self.result else None,
|
||||
"error": self.error,
|
||||
"created_at": self.created_at.isoformat(),
|
||||
"started_at": self.started_at.isoformat() if self.started_at else None,
|
||||
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
||||
"duration_seconds": self.duration_seconds,
|
||||
"metadata": self.metadata
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
Checkpoints Module for PraisonAI Agents.
|
||||
|
||||
Provides file-level checkpointing using a shadow git repository to track
|
||||
and restore file changes made by agents. This enables:
|
||||
|
||||
- Automatic checkpointing before file modifications
|
||||
- Rewind to any previous checkpoint
|
||||
- Diff between checkpoints
|
||||
- Restore files and conversation state together
|
||||
|
||||
Zero Performance Impact:
|
||||
- All imports are lazy loaded via __getattr__
|
||||
- Checkpoints only created when enabled
|
||||
- No overhead when checkpoints are disabled
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.checkpoints import CheckpointService
|
||||
|
||||
# Create checkpoint service
|
||||
service = CheckpointService(
|
||||
workspace_dir="/path/to/project",
|
||||
storage_dir="~/.praison/checkpoints"
|
||||
)
|
||||
|
||||
# Initialize shadow git
|
||||
await service.initialize()
|
||||
|
||||
# Save a checkpoint
|
||||
checkpoint_id = await service.save("Before refactoring")
|
||||
|
||||
# Restore to a checkpoint
|
||||
await service.restore(checkpoint_id)
|
||||
|
||||
# Get diff between checkpoints
|
||||
diff = await service.diff(from_id, to_id)
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
# Core service
|
||||
"CheckpointService",
|
||||
# Data types
|
||||
"Checkpoint",
|
||||
"CheckpointDiff",
|
||||
"CheckpointConfig",
|
||||
# Events
|
||||
"CheckpointEvent",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy load module components to avoid import overhead."""
|
||||
if name == "CheckpointService":
|
||||
from .service import CheckpointService
|
||||
return CheckpointService
|
||||
|
||||
if name in ("Checkpoint", "CheckpointDiff", "CheckpointConfig"):
|
||||
from .types import Checkpoint, CheckpointDiff, CheckpointConfig
|
||||
return locals()[name]
|
||||
|
||||
if name == "CheckpointEvent":
|
||||
from .types import CheckpointEvent
|
||||
return CheckpointEvent
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
@@ -0,0 +1,497 @@
|
||||
"""
|
||||
Checkpoint Service for PraisonAI Agents.
|
||||
|
||||
Implements shadow git repository for file-level checkpointing.
|
||||
"""
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import logging
|
||||
import shutil
|
||||
from typing import Optional, List, Dict, Any, Callable
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from .types import (
|
||||
CheckpointConfig, Checkpoint, CheckpointDiff, FileDiff,
|
||||
CheckpointResult, CheckpointEvent
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Protected paths that should never be checkpointed
|
||||
PROTECTED_PATHS = [
|
||||
os.path.expanduser("~"),
|
||||
os.path.expanduser("~/Desktop"),
|
||||
os.path.expanduser("~/Documents"),
|
||||
os.path.expanduser("~/Downloads"),
|
||||
"/",
|
||||
"/tmp",
|
||||
"/var",
|
||||
"/etc",
|
||||
]
|
||||
|
||||
|
||||
class CheckpointService:
|
||||
"""
|
||||
Shadow Git Checkpoint Service.
|
||||
|
||||
Uses a separate git repository to track file changes in the workspace,
|
||||
enabling checkpoint creation, restoration, and diffing without
|
||||
interfering with the workspace's own git repository.
|
||||
|
||||
Example:
|
||||
service = CheckpointService(
|
||||
workspace_dir="/path/to/project",
|
||||
storage_dir="~/.praison/checkpoints"
|
||||
)
|
||||
|
||||
await service.initialize()
|
||||
|
||||
# Save checkpoint before making changes
|
||||
result = await service.save("Before refactoring")
|
||||
|
||||
# Make changes...
|
||||
|
||||
# Restore if needed
|
||||
await service.restore(result.checkpoint.id)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
workspace_dir: str,
|
||||
storage_dir: Optional[str] = None,
|
||||
enabled: bool = True,
|
||||
auto_checkpoint: bool = True,
|
||||
max_checkpoints: int = 100
|
||||
):
|
||||
"""
|
||||
Initialize the checkpoint service.
|
||||
|
||||
Args:
|
||||
workspace_dir: Directory to track
|
||||
storage_dir: Where to store checkpoint data
|
||||
enabled: Whether checkpoints are enabled
|
||||
auto_checkpoint: Auto-checkpoint before file modifications
|
||||
max_checkpoints: Maximum checkpoints to keep
|
||||
"""
|
||||
self.config = CheckpointConfig(
|
||||
workspace_dir=workspace_dir,
|
||||
storage_dir=storage_dir,
|
||||
enabled=enabled,
|
||||
auto_checkpoint=auto_checkpoint,
|
||||
max_checkpoints=max_checkpoints
|
||||
)
|
||||
|
||||
self._initialized = False
|
||||
self._checkpoints: List[Checkpoint] = []
|
||||
self._event_handlers: Dict[CheckpointEvent, List[Callable]] = {
|
||||
event: [] for event in CheckpointEvent
|
||||
}
|
||||
|
||||
@property
|
||||
def workspace_dir(self) -> str:
|
||||
"""Get the workspace directory."""
|
||||
return self.config.workspace_dir
|
||||
|
||||
@property
|
||||
def checkpoint_dir(self) -> str:
|
||||
"""Get the checkpoint storage directory."""
|
||||
return self.config.get_checkpoint_dir()
|
||||
|
||||
@property
|
||||
def git_dir(self) -> str:
|
||||
"""Get the shadow git directory."""
|
||||
return os.path.join(self.checkpoint_dir, ".git")
|
||||
|
||||
@property
|
||||
def is_initialized(self) -> bool:
|
||||
"""Check if the service is initialized."""
|
||||
return self._initialized
|
||||
|
||||
def on(self, event: CheckpointEvent, handler: Callable):
|
||||
"""Register an event handler."""
|
||||
self._event_handlers[event].append(handler)
|
||||
|
||||
def _emit(self, event: CheckpointEvent, data: Any = None):
|
||||
"""Emit an event to all handlers."""
|
||||
for handler in self._event_handlers[event]:
|
||||
try:
|
||||
handler(data)
|
||||
except Exception as e:
|
||||
logger.warning(f"Event handler error: {e}")
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""
|
||||
Initialize the shadow git repository.
|
||||
|
||||
Returns:
|
||||
True if initialization succeeded
|
||||
"""
|
||||
if not self.config.enabled:
|
||||
return False
|
||||
|
||||
# Check for protected paths
|
||||
workspace_real = os.path.realpath(self.config.workspace_dir)
|
||||
for protected in PROTECTED_PATHS:
|
||||
if workspace_real == os.path.realpath(protected):
|
||||
logger.warning(f"Cannot checkpoint protected path: {workspace_real}")
|
||||
return False
|
||||
|
||||
# Check if workspace exists
|
||||
if not os.path.isdir(self.config.workspace_dir):
|
||||
logger.error(f"Workspace does not exist: {self.config.workspace_dir}")
|
||||
return False
|
||||
|
||||
# Create checkpoint directory
|
||||
os.makedirs(self.checkpoint_dir, exist_ok=True)
|
||||
|
||||
# Initialize shadow git if needed
|
||||
if not os.path.exists(self.git_dir):
|
||||
success = await self._init_shadow_git()
|
||||
if not success:
|
||||
return False
|
||||
|
||||
# Load existing checkpoints
|
||||
await self._load_checkpoints()
|
||||
|
||||
self._initialized = True
|
||||
self._emit(CheckpointEvent.INITIALIZED, {"workspace": self.config.workspace_dir})
|
||||
|
||||
return True
|
||||
|
||||
async def _init_shadow_git(self) -> bool:
|
||||
"""Initialize the shadow git repository."""
|
||||
try:
|
||||
# Initialize git repo
|
||||
await self._run_git("init")
|
||||
|
||||
# Configure git
|
||||
await self._run_git("config", "user.name", "PraisonAI Checkpoints")
|
||||
await self._run_git("config", "user.email", "[email protected]")
|
||||
|
||||
# Set worktree to workspace
|
||||
await self._run_git("config", "core.worktree", self.config.workspace_dir)
|
||||
|
||||
# Create exclude file
|
||||
exclude_dir = os.path.join(self.git_dir, "info")
|
||||
os.makedirs(exclude_dir, exist_ok=True)
|
||||
|
||||
exclude_file = os.path.join(exclude_dir, "exclude")
|
||||
with open(exclude_file, "w") as f:
|
||||
for pattern in self.config.exclude_patterns:
|
||||
f.write(f"{pattern}\n")
|
||||
|
||||
# Also exclude the checkpoint directory itself
|
||||
f.write(f"{self.checkpoint_dir}\n")
|
||||
|
||||
# Create initial empty commit
|
||||
await self._run_git("commit", "--allow-empty", "-m", "Initial checkpoint")
|
||||
|
||||
logger.info(f"Initialized shadow git at {self.checkpoint_dir}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize shadow git: {e}")
|
||||
return False
|
||||
|
||||
async def _run_git(self, *args: str) -> str:
|
||||
"""Run a git command in the shadow repository."""
|
||||
env = self._get_sanitized_env()
|
||||
|
||||
cmd = ["git", f"--git-dir={self.git_dir}", f"--work-tree={self.config.workspace_dir}"]
|
||||
cmd.extend(args)
|
||||
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env=env,
|
||||
cwd=self.config.workspace_dir
|
||||
)
|
||||
|
||||
stdout, stderr = await process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
error_msg = stderr.decode().strip()
|
||||
raise RuntimeError(f"Git command failed: {' '.join(args)}\n{error_msg}")
|
||||
|
||||
return stdout.decode().strip()
|
||||
|
||||
def _get_sanitized_env(self) -> Dict[str, str]:
|
||||
"""Get environment with git variables sanitized."""
|
||||
env = os.environ.copy()
|
||||
|
||||
# Remove git environment variables that could interfere
|
||||
git_vars = [
|
||||
"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE",
|
||||
"GIT_OBJECT_DIRECTORY", "GIT_ALTERNATE_OBJECT_DIRECTORIES"
|
||||
]
|
||||
for var in git_vars:
|
||||
env.pop(var, None)
|
||||
|
||||
return env
|
||||
|
||||
async def save(self, message: str, allow_empty: bool = False) -> CheckpointResult:
|
||||
"""
|
||||
Save a checkpoint.
|
||||
|
||||
Args:
|
||||
message: Checkpoint message
|
||||
allow_empty: Allow checkpoint even if no changes
|
||||
|
||||
Returns:
|
||||
CheckpointResult with the created checkpoint
|
||||
"""
|
||||
if not self._initialized:
|
||||
return CheckpointResult.fail("Service not initialized")
|
||||
|
||||
try:
|
||||
# Stage all changes
|
||||
await self._run_git("add", "-A")
|
||||
|
||||
# Check if there are changes
|
||||
try:
|
||||
await self._run_git("diff", "--cached", "--quiet")
|
||||
has_changes = False
|
||||
except RuntimeError:
|
||||
has_changes = True
|
||||
|
||||
if not has_changes and not allow_empty:
|
||||
return CheckpointResult.fail("No changes to checkpoint")
|
||||
|
||||
# Create commit
|
||||
commit_args = ["commit", "-m", message]
|
||||
if allow_empty:
|
||||
commit_args.append("--allow-empty")
|
||||
|
||||
await self._run_git(*commit_args)
|
||||
|
||||
# Get commit info
|
||||
commit_hash = await self._run_git("rev-parse", "HEAD")
|
||||
timestamp = await self._run_git("log", "-1", "--format=%aI")
|
||||
|
||||
checkpoint = Checkpoint(
|
||||
id=commit_hash,
|
||||
short_id=commit_hash[:8],
|
||||
message=message,
|
||||
timestamp=datetime.fromisoformat(timestamp)
|
||||
)
|
||||
|
||||
self._checkpoints.append(checkpoint)
|
||||
|
||||
# Prune old checkpoints if needed
|
||||
await self._prune_checkpoints()
|
||||
|
||||
self._emit(CheckpointEvent.CHECKPOINT_CREATED, checkpoint)
|
||||
logger.info(f"Created checkpoint: {checkpoint.short_id} - {message}")
|
||||
|
||||
return CheckpointResult.ok(checkpoint)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
self._emit(CheckpointEvent.ERROR, {"error": error_msg})
|
||||
return CheckpointResult.fail(error_msg)
|
||||
|
||||
async def restore(self, checkpoint_id: str) -> CheckpointResult:
|
||||
"""
|
||||
Restore workspace to a checkpoint.
|
||||
|
||||
Args:
|
||||
checkpoint_id: Checkpoint ID (commit hash) to restore
|
||||
|
||||
Returns:
|
||||
CheckpointResult indicating success/failure
|
||||
"""
|
||||
if not self._initialized:
|
||||
return CheckpointResult.fail("Service not initialized")
|
||||
|
||||
try:
|
||||
# Clean untracked files
|
||||
await self._run_git("clean", "-f", "-d")
|
||||
|
||||
# Reset to checkpoint
|
||||
await self._run_git("reset", "--hard", checkpoint_id)
|
||||
|
||||
# Get checkpoint info
|
||||
message = await self._run_git("log", "-1", "--format=%s", checkpoint_id)
|
||||
timestamp = await self._run_git("log", "-1", "--format=%aI", checkpoint_id)
|
||||
|
||||
checkpoint = Checkpoint(
|
||||
id=checkpoint_id,
|
||||
short_id=checkpoint_id[:8],
|
||||
message=message,
|
||||
timestamp=datetime.fromisoformat(timestamp)
|
||||
)
|
||||
|
||||
self._emit(CheckpointEvent.CHECKPOINT_RESTORED, checkpoint)
|
||||
logger.info(f"Restored to checkpoint: {checkpoint.short_id}")
|
||||
|
||||
return CheckpointResult.ok(checkpoint)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
self._emit(CheckpointEvent.ERROR, {"error": error_msg})
|
||||
return CheckpointResult.fail(error_msg)
|
||||
|
||||
async def diff(
|
||||
self,
|
||||
from_id: Optional[str] = None,
|
||||
to_id: Optional[str] = None
|
||||
) -> CheckpointDiff:
|
||||
"""
|
||||
Get diff between checkpoints.
|
||||
|
||||
Args:
|
||||
from_id: Starting checkpoint (default: previous checkpoint)
|
||||
to_id: Ending checkpoint (default: current working directory)
|
||||
|
||||
Returns:
|
||||
CheckpointDiff with file changes
|
||||
"""
|
||||
if not self._initialized:
|
||||
return CheckpointDiff(from_checkpoint="", to_checkpoint="", files=[])
|
||||
|
||||
try:
|
||||
# Default from_id to HEAD~1
|
||||
if from_id is None:
|
||||
try:
|
||||
from_id = await self._run_git("rev-parse", "HEAD~1")
|
||||
except RuntimeError:
|
||||
from_id = await self._run_git("rev-parse", "HEAD")
|
||||
|
||||
# Build diff command
|
||||
if to_id:
|
||||
diff_output = await self._run_git(
|
||||
"diff", "--stat", "--numstat", from_id, to_id
|
||||
)
|
||||
else:
|
||||
# Diff against working directory
|
||||
await self._run_git("add", "-A") # Stage to include untracked
|
||||
diff_output = await self._run_git(
|
||||
"diff", "--stat", "--numstat", "--cached", from_id
|
||||
)
|
||||
|
||||
# Parse diff output
|
||||
files = self._parse_diff_output(diff_output)
|
||||
|
||||
return CheckpointDiff(
|
||||
from_checkpoint=from_id[:8] if from_id else "",
|
||||
to_checkpoint=to_id[:8] if to_id else None,
|
||||
files=files
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get diff: {e}")
|
||||
return CheckpointDiff(from_checkpoint="", to_checkpoint="", files=[])
|
||||
|
||||
def _parse_diff_output(self, output: str) -> List[FileDiff]:
|
||||
"""Parse git diff --numstat output."""
|
||||
files = []
|
||||
|
||||
for line in output.strip().split("\n"):
|
||||
if not line or "\t" not in line:
|
||||
continue
|
||||
|
||||
parts = line.split("\t")
|
||||
if len(parts) >= 3:
|
||||
try:
|
||||
additions = int(parts[0]) if parts[0] != "-" else 0
|
||||
deletions = int(parts[1]) if parts[1] != "-" else 0
|
||||
path = parts[2]
|
||||
|
||||
# Determine status
|
||||
if additions > 0 and deletions == 0:
|
||||
status = "added"
|
||||
elif additions == 0 and deletions > 0:
|
||||
status = "deleted"
|
||||
else:
|
||||
status = "modified"
|
||||
|
||||
files.append(FileDiff(
|
||||
path=path,
|
||||
absolute_path=os.path.join(self.config.workspace_dir, path),
|
||||
status=status,
|
||||
additions=additions,
|
||||
deletions=deletions
|
||||
))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
return files
|
||||
|
||||
async def list_checkpoints(self, limit: int = 50) -> List[Checkpoint]:
|
||||
"""
|
||||
List all checkpoints.
|
||||
|
||||
Args:
|
||||
limit: Maximum number of checkpoints to return
|
||||
|
||||
Returns:
|
||||
List of checkpoints, newest first
|
||||
"""
|
||||
if not self._initialized:
|
||||
return []
|
||||
|
||||
try:
|
||||
# Get commit log
|
||||
log_output = await self._run_git(
|
||||
"log", f"-{limit}", "--format=%H|%s|%aI"
|
||||
)
|
||||
|
||||
checkpoints = []
|
||||
for line in log_output.strip().split("\n"):
|
||||
if not line or "|" not in line:
|
||||
continue
|
||||
|
||||
parts = line.split("|", 2)
|
||||
if len(parts) >= 3:
|
||||
checkpoints.append(Checkpoint(
|
||||
id=parts[0],
|
||||
short_id=parts[0][:8],
|
||||
message=parts[1],
|
||||
timestamp=datetime.fromisoformat(parts[2])
|
||||
))
|
||||
|
||||
return checkpoints
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to list checkpoints: {e}")
|
||||
return []
|
||||
|
||||
async def _load_checkpoints(self):
|
||||
"""Load existing checkpoints from git log."""
|
||||
self._checkpoints = await self.list_checkpoints()
|
||||
|
||||
async def _prune_checkpoints(self):
|
||||
"""Prune old checkpoints if over limit."""
|
||||
if len(self._checkpoints) <= self.config.max_checkpoints:
|
||||
return
|
||||
|
||||
# Keep only the most recent checkpoints
|
||||
# Note: This doesn't actually delete git history, just our tracking
|
||||
self._checkpoints = self._checkpoints[:self.config.max_checkpoints]
|
||||
|
||||
async def get_checkpoint(self, checkpoint_id: str) -> Optional[Checkpoint]:
|
||||
"""Get a specific checkpoint by ID."""
|
||||
for cp in self._checkpoints:
|
||||
if cp.id.startswith(checkpoint_id) or cp.short_id == checkpoint_id:
|
||||
return cp
|
||||
return None
|
||||
|
||||
async def cleanup(self):
|
||||
"""Clean up the checkpoint service."""
|
||||
# Nothing to clean up currently
|
||||
self._initialized = False
|
||||
|
||||
async def delete_all(self) -> bool:
|
||||
"""Delete all checkpoint data for this workspace."""
|
||||
try:
|
||||
if os.path.exists(self.checkpoint_dir):
|
||||
shutil.rmtree(self.checkpoint_dir)
|
||||
self._checkpoints = []
|
||||
self._initialized = False
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete checkpoints: {e}")
|
||||
return False
|
||||
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
Checkpoint Types for PraisonAI Agents.
|
||||
|
||||
Defines the core types, enums, and dataclasses for the checkpoint system.
|
||||
"""
|
||||
|
||||
import os
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class CheckpointEvent(str, Enum):
|
||||
"""Events emitted by the checkpoint service."""
|
||||
INITIALIZED = "initialized"
|
||||
CHECKPOINT_CREATED = "checkpoint_created"
|
||||
CHECKPOINT_RESTORED = "checkpoint_restored"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckpointConfig:
|
||||
"""Configuration for the checkpoint service."""
|
||||
workspace_dir: str
|
||||
storage_dir: Optional[str] = None
|
||||
enabled: bool = True
|
||||
auto_checkpoint: bool = True # Auto-checkpoint before file modifications
|
||||
max_checkpoints: int = 100 # Maximum checkpoints to keep
|
||||
exclude_patterns: List[str] = field(default_factory=lambda: [
|
||||
".git",
|
||||
".praison",
|
||||
"__pycache__",
|
||||
"*.pyc",
|
||||
".env",
|
||||
"node_modules",
|
||||
".venv",
|
||||
"venv",
|
||||
"*.log"
|
||||
])
|
||||
|
||||
def __post_init__(self):
|
||||
# Expand paths
|
||||
self.workspace_dir = os.path.expanduser(self.workspace_dir)
|
||||
if self.storage_dir:
|
||||
self.storage_dir = os.path.expanduser(self.storage_dir)
|
||||
else:
|
||||
# Default storage in user's home directory
|
||||
self.storage_dir = os.path.expanduser("~/.praison/checkpoints")
|
||||
|
||||
def get_checkpoint_dir(self) -> str:
|
||||
"""Get the checkpoint directory for this workspace."""
|
||||
# Hash the workspace path to create a unique directory
|
||||
import hashlib
|
||||
workspace_hash = hashlib.sha256(
|
||||
self.workspace_dir.encode()
|
||||
).hexdigest()[:12]
|
||||
return os.path.join(self.storage_dir, workspace_hash)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Checkpoint:
|
||||
"""Represents a single checkpoint."""
|
||||
id: str # Git commit hash
|
||||
short_id: str # Short hash (8 chars)
|
||||
message: str
|
||||
timestamp: datetime
|
||||
files_changed: int = 0
|
||||
insertions: int = 0
|
||||
deletions: int = 0
|
||||
|
||||
@classmethod
|
||||
def from_git_commit(cls, commit_hash: str, message: str, timestamp: str) -> "Checkpoint":
|
||||
"""Create a Checkpoint from git commit info."""
|
||||
return cls(
|
||||
id=commit_hash,
|
||||
short_id=commit_hash[:8],
|
||||
message=message,
|
||||
timestamp=datetime.fromisoformat(timestamp) if isinstance(timestamp, str) else timestamp
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"short_id": self.short_id,
|
||||
"message": self.message,
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"files_changed": self.files_changed,
|
||||
"insertions": self.insertions,
|
||||
"deletions": self.deletions
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileDiff:
|
||||
"""Represents a diff for a single file."""
|
||||
path: str
|
||||
absolute_path: str
|
||||
status: str # added, modified, deleted
|
||||
additions: int = 0
|
||||
deletions: int = 0
|
||||
before_content: Optional[str] = None
|
||||
after_content: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"path": self.path,
|
||||
"absolute_path": self.absolute_path,
|
||||
"status": self.status,
|
||||
"additions": self.additions,
|
||||
"deletions": self.deletions
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckpointDiff:
|
||||
"""Represents a diff between two checkpoints."""
|
||||
from_checkpoint: str
|
||||
to_checkpoint: Optional[str] # None means current working directory
|
||||
files: List[FileDiff] = field(default_factory=list)
|
||||
total_additions: int = 0
|
||||
total_deletions: int = 0
|
||||
|
||||
def __post_init__(self):
|
||||
# Calculate totals
|
||||
self.total_additions = sum(f.additions for f in self.files)
|
||||
self.total_deletions = sum(f.deletions for f in self.files)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"from_checkpoint": self.from_checkpoint,
|
||||
"to_checkpoint": self.to_checkpoint,
|
||||
"files": [f.to_dict() for f in self.files],
|
||||
"total_additions": self.total_additions,
|
||||
"total_deletions": self.total_deletions
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckpointResult:
|
||||
"""Result of a checkpoint operation."""
|
||||
success: bool
|
||||
checkpoint: Optional[Checkpoint] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def ok(cls, checkpoint: Checkpoint) -> "CheckpointResult":
|
||||
"""Create a successful result."""
|
||||
return cls(success=True, checkpoint=checkpoint)
|
||||
|
||||
@classmethod
|
||||
def fail(cls, error: str) -> "CheckpointResult":
|
||||
"""Create a failed result."""
|
||||
return cls(success=False, error=error)
|
||||
Loaded 100 of 531 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user