mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-08 01:08:39 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9815a9f8a8 | ||
|
|
2ae44a05b0 | ||
|
|
3386d25022 | ||
|
|
6dca2ace8a | ||
|
|
63548739e8 | ||
|
|
3e046537f4 | ||
|
|
4a34677567 | ||
|
|
19e91cb669 | ||
|
|
5a6125021a | ||
|
|
da26887f24 | ||
|
|
1b2f7139f9 | ||
|
|
aafbc86180 | ||
|
|
3d4ca1b7c0 | ||
|
|
1180aa38bd | ||
|
|
65f65e5c7b | ||
|
|
f81a12d5e8 | ||
|
|
cf6f5fbefe | ||
|
|
908d7710db | ||
|
|
7d5b41e31f | ||
|
|
e46a0bc9f3 | ||
|
|
911483c92c | ||
|
|
4ffb9d6e3f | ||
|
|
b686e5b00f | ||
|
|
d8d84ee3e3 | ||
|
|
9f2c4d10b5 | ||
|
|
42a86c831f | ||
|
|
48443b11d6 | ||
|
|
71745aa14a | ||
|
|
4f309ec16d | ||
|
|
e48afc8993 | ||
|
|
90950f8889 | ||
|
|
56f36e2593 | ||
|
|
7aa67ad27c | ||
|
|
307df443ef | ||
|
|
6434a87b60 | ||
|
|
97d5b6db0d | ||
|
|
4996a9f9fa | ||
|
|
54f4a96cba | ||
|
|
e59be8f0fd | ||
|
|
b52708f3f6 | ||
|
|
ba37ce39d3 | ||
|
|
98c5e8f175 |
No files matched your search
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Auto-generated API server for PraisonAI agents.
|
||||
"""
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
from praisonai import PraisonAI
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# CORS configuration
|
||||
CORS(app)
|
||||
|
||||
# Authentication
|
||||
AUTH_ENABLED = False
|
||||
AUTH_TOKEN = None
|
||||
|
||||
def check_auth():
|
||||
"""Check authentication if enabled."""
|
||||
if not AUTH_ENABLED:
|
||||
return True
|
||||
|
||||
token = request.headers.get('Authorization', '').replace('Bearer ', '')
|
||||
return token == AUTH_TOKEN
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health():
|
||||
"""Health check endpoint."""
|
||||
return jsonify({"status": "ok", "service": "praisonai-api"})
|
||||
|
||||
@app.route('/chat', methods=['POST'])
|
||||
def chat():
|
||||
"""Chat endpoint for agent interaction."""
|
||||
if not check_auth():
|
||||
return jsonify({"error": "Unauthorized"}), 401
|
||||
|
||||
data = request.get_json()
|
||||
if not data or 'message' not in data:
|
||||
return jsonify({"error": "Message required"}), 400
|
||||
|
||||
try:
|
||||
praisonai = PraisonAI(agent_file="agents.yaml")
|
||||
result = praisonai.run()
|
||||
|
||||
return jsonify({
|
||||
"response": result,
|
||||
"status": "success"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"error": str(e),
|
||||
"status": "error"
|
||||
}), 500
|
||||
|
||||
@app.route('/agents', methods=['GET'])
|
||||
def list_agents():
|
||||
"""List available agents."""
|
||||
if not check_auth():
|
||||
return jsonify({"error": "Unauthorized"}), 401
|
||||
|
||||
return jsonify({
|
||||
"agents": ["default"],
|
||||
"agent_file": "agents.yaml"
|
||||
})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(
|
||||
host='0.0.0.0',
|
||||
port=8080,
|
||||
debug=False
|
||||
)
|
||||
@@ -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.7.9" \
|
||||
"praisonai>=3.0.4" \
|
||||
"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.7.9" \
|
||||
"praisonai>=3.0.4" \
|
||||
"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.7.9" \
|
||||
"praisonai>=3.0.4" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# Profiling CLI
|
||||
|
||||
PraisonAI provides unified profiling capabilities for performance analysis and debugging, with **full visibility from ENTER to first response**.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Basic profiling
|
||||
praisonai "What is 2+2?" --profile
|
||||
|
||||
# Deep profiling with call graph, decision trace, and module breakdown
|
||||
praisonai "What is 2+2?" --profile --profile-deep
|
||||
|
||||
# JSON output format
|
||||
praisonai "What is 2+2?" --profile --profile-format json
|
||||
```
|
||||
|
||||
## Time to First Response (TTFR)
|
||||
|
||||
**TTFR** is the key user-perceived latency metric. It measures the time from when you press ENTER until the first visible output appears.
|
||||
|
||||
### What contributes to TTFR?
|
||||
|
||||
1. **CLI Entry** - Time to reach the CLI entry point
|
||||
2. **CLI Parse** - Argparse/Typer routing time
|
||||
3. **Routing** - Command routing to handler
|
||||
4. **Imports** - Loading required modules
|
||||
5. **Agent Init** - Initializing the agent
|
||||
6. **Network Request** - Sending request to LLM API
|
||||
7. **First Token** - Receiving first token (streaming)
|
||||
8. **First Output** - Rendering first visible output
|
||||
|
||||
### How to reduce TTFR
|
||||
|
||||
1. **Warm runs** - Imports are cached after first run
|
||||
2. **Streaming mode** - See output as tokens arrive
|
||||
3. **Lighter models** - Faster response times
|
||||
4. **Local models** - Eliminate network latency
|
||||
|
||||
## CLI Flags
|
||||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `--profile` | Enable profiling with timing breakdown | Disabled |
|
||||
| `--profile-deep` | Enable deep profiling with call graph (higher overhead) | Disabled |
|
||||
| `--profile-format` | Output format: `text` or `json` | `text` |
|
||||
|
||||
## Profile Command
|
||||
|
||||
For more detailed profiling options, use the `profile` subcommand:
|
||||
|
||||
```bash
|
||||
# Profile a query
|
||||
praisonai profile query "What is 2+2?"
|
||||
|
||||
# With deep call tracing
|
||||
praisonai profile query "Hello" --deep
|
||||
|
||||
# Limit functions shown
|
||||
praisonai profile query "Test" --limit 20
|
||||
|
||||
# Save artifacts to directory
|
||||
praisonai profile query "Test" --save ./profile_results
|
||||
|
||||
# JSON output
|
||||
praisonai profile query "Test" --format json
|
||||
```
|
||||
|
||||
### Profile Query Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--model, -m` | Model to use | Default model |
|
||||
| `--stream/--no-stream` | Use streaming mode | No stream |
|
||||
| `--deep` | Enable deep call tracing | Disabled |
|
||||
| `--limit, -n` | Top N functions to show | 30 |
|
||||
| `--sort, -s` | Sort by: `cumulative` or `tottime` | `cumulative` |
|
||||
| `--show-callers` | Show caller functions | Disabled |
|
||||
| `--show-callees` | Show callee functions | Disabled |
|
||||
| `--save` | Save artifacts to path | None |
|
||||
| `--format, -f` | Output format: `text` or `json` | `text` |
|
||||
|
||||
## Other Profile Commands
|
||||
|
||||
```bash
|
||||
# Profile module import times
|
||||
praisonai profile imports
|
||||
|
||||
# Profile CLI startup time
|
||||
praisonai profile startup
|
||||
|
||||
# Run comprehensive profiling suite
|
||||
praisonai profile suite
|
||||
|
||||
# Create performance snapshots for comparison
|
||||
praisonai profile snapshot --baseline
|
||||
praisonai profile snapshot current --compare
|
||||
|
||||
# Configure performance optimizations
|
||||
praisonai profile optimize --show
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
### Text Report
|
||||
|
||||
```
|
||||
======================================================================
|
||||
PraisonAI Profile Report
|
||||
======================================================================
|
||||
|
||||
Run ID: abc12345
|
||||
Timestamp: 2024-01-02T12:00:00.000000Z
|
||||
Method: cli_direct
|
||||
Version: 3.0.1
|
||||
|
||||
## Execution Timeline
|
||||
---------------------------------------------
|
||||
Imports : 500.00 ms
|
||||
Agent Init : 0.50 ms
|
||||
Execution : 2000.00 ms
|
||||
───────────────────────────────────────────
|
||||
⏱ Time to First Response : 2500.50 ms
|
||||
TOTAL : 2500.50 ms
|
||||
|
||||
## Timing Breakdown
|
||||
----------------------------------------
|
||||
Imports: 500.00 ms
|
||||
Agent Init: 0.50 ms
|
||||
Execution: 2000.00 ms
|
||||
─────────────────────────────────────
|
||||
TOTAL: 2500.50 ms
|
||||
|
||||
## Decision Trace (deep profile only)
|
||||
----------------------------------------
|
||||
Agent Config: default
|
||||
Model: gpt-4
|
||||
Streaming: disabled
|
||||
Profile Layer: 2
|
||||
|
||||
## Top Functions by Cumulative Time
|
||||
----------------------------------------------------------------------
|
||||
Function Calls Cumul (ms)
|
||||
----------------------------------------------------------------------
|
||||
request 1 1800.00
|
||||
send 1 1750.00
|
||||
...
|
||||
```
|
||||
|
||||
### JSON Report
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"run_id": "abc12345",
|
||||
"timestamp": "2024-01-02T12:00:00.000000Z",
|
||||
"invocation": {
|
||||
"method": "cli_direct",
|
||||
"flags": {"layer": 1, "stream": false, "model": null},
|
||||
"praisonai_version": "3.0.1",
|
||||
"python_version": "3.12.0"
|
||||
},
|
||||
"timing": {
|
||||
"total_ms": 2500.5,
|
||||
"imports_ms": 500.0,
|
||||
"agent_init_ms": 0.5,
|
||||
"execution_ms": 2000.0
|
||||
},
|
||||
"functions": [...],
|
||||
"response_preview": "four"
|
||||
}
|
||||
```
|
||||
|
||||
## Profiling Layers
|
||||
|
||||
| Layer | Overhead | Features |
|
||||
|-------|----------|----------|
|
||||
| 0 | < 1ms | Wall-clock phases only |
|
||||
| 1 | ~5% | + cProfile function stats |
|
||||
| 2 | ~15% | + Call graph (callers/callees) |
|
||||
|
||||
- `--profile` uses Layer 1
|
||||
- `--profile --profile-deep` uses Layer 2
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Use Layer 0 for production monitoring** - minimal overhead
|
||||
2. **Use Layer 1 for development profiling** - good balance
|
||||
3. **Use Layer 2 only for deep debugging** - higher overhead
|
||||
|
||||
## Observational Equivalence
|
||||
|
||||
The following commands produce equivalent profile reports:
|
||||
|
||||
```bash
|
||||
# CLI direct
|
||||
praisonai "prompt" --profile
|
||||
|
||||
# Profile command
|
||||
praisonai profile query "prompt"
|
||||
```
|
||||
|
||||
Both use the same unified profiling architecture and produce identical schema output.
|
||||
@@ -0,0 +1,257 @@
|
||||
# Profiling Module
|
||||
|
||||
PraisonAI provides a unified profiling module for programmatic performance analysis.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from praisonai.cli.execution import (
|
||||
ExecutionRequest,
|
||||
Profiler,
|
||||
ProfilerConfig,
|
||||
)
|
||||
|
||||
# Create execution request
|
||||
request = ExecutionRequest(prompt="What is 2+2?")
|
||||
|
||||
# Configure profiler
|
||||
config = ProfilerConfig(layer=1)
|
||||
|
||||
# Run with profiling
|
||||
profiler = Profiler(config)
|
||||
result, report = profiler.profile_sync(request)
|
||||
|
||||
# Access results
|
||||
print(f"Output: {result.output}")
|
||||
print(f"Total time: {report.timing.total_ms}ms")
|
||||
```
|
||||
|
||||
## ExecutionRequest
|
||||
|
||||
Immutable request descriptor for execution.
|
||||
|
||||
```python
|
||||
from praisonai.cli.execution import ExecutionRequest
|
||||
|
||||
# Basic request
|
||||
request = ExecutionRequest(prompt="Hello")
|
||||
|
||||
# With model
|
||||
request = ExecutionRequest(
|
||||
prompt="Hello",
|
||||
model="gpt-4",
|
||||
agent_name="MyAgent",
|
||||
)
|
||||
|
||||
# With streaming
|
||||
request = ExecutionRequest(
|
||||
prompt="Hello",
|
||||
stream=True,
|
||||
)
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `prompt` | str | Required | The prompt to execute |
|
||||
| `agent_name` | str | "Assistant" | Name of the agent |
|
||||
| `agent_instructions` | str | None | Custom instructions |
|
||||
| `model` | str | None | Model to use |
|
||||
| `stream` | bool | False | Enable streaming |
|
||||
| `tools` | tuple | () | Tools to provide |
|
||||
|
||||
## ProfilerConfig
|
||||
|
||||
Configuration for profiling behavior.
|
||||
|
||||
```python
|
||||
from praisonai.cli.execution import ProfilerConfig
|
||||
|
||||
# Layer 0: Wall-clock only (< 1ms overhead)
|
||||
config = ProfilerConfig(layer=0)
|
||||
|
||||
# Layer 1: + cProfile stats (~5% overhead)
|
||||
config = ProfilerConfig(layer=1)
|
||||
|
||||
# Layer 2: + Call graph (~15% overhead)
|
||||
config = ProfilerConfig(
|
||||
layer=2,
|
||||
show_callers=True,
|
||||
show_callees=True,
|
||||
)
|
||||
|
||||
# From CLI flags
|
||||
config = ProfilerConfig.from_flags(
|
||||
profile=True,
|
||||
deep=True,
|
||||
network=False,
|
||||
)
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `layer` | int | 1 | Profiling layer (0, 1, or 2) |
|
||||
| `limit` | int | 30 | Max functions to show |
|
||||
| `sort_by` | str | "cumulative" | Sort by: cumulative, time, calls |
|
||||
| `show_callers` | bool | False | Show caller functions |
|
||||
| `show_callees` | bool | False | Show callee functions |
|
||||
| `track_network` | bool | False | Track network requests |
|
||||
| `output_format` | str | "text" | Output format: text, json |
|
||||
| `save_path` | str | None | Path to save artifacts |
|
||||
|
||||
## Profiler
|
||||
|
||||
The main profiler class that wraps execution.
|
||||
|
||||
```python
|
||||
from praisonai.cli.execution import Profiler, ProfilerConfig, ExecutionRequest
|
||||
|
||||
config = ProfilerConfig(layer=1)
|
||||
profiler = Profiler(config)
|
||||
|
||||
request = ExecutionRequest(prompt="Hello")
|
||||
result, report = profiler.profile_sync(request, invocation_method="api")
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
#### `profile_sync(request, invocation_method="profile_command")`
|
||||
|
||||
Profile synchronous execution.
|
||||
|
||||
**Parameters:**
|
||||
- `request`: ExecutionRequest - The execution request
|
||||
- `invocation_method`: str - How this was invoked (for report)
|
||||
|
||||
**Returns:** Tuple of (ExecutionResult, ProfileReport)
|
||||
|
||||
## ProfileReport
|
||||
|
||||
The canonical profile report with timing and statistics.
|
||||
|
||||
```python
|
||||
# Access timing breakdown
|
||||
print(f"Total: {report.timing.total_ms}ms")
|
||||
print(f"Imports: {report.timing.imports_ms}ms")
|
||||
print(f"Agent Init: {report.timing.agent_init_ms}ms")
|
||||
print(f"Execution: {report.timing.execution_ms}ms")
|
||||
|
||||
# Access function stats (layer 1+)
|
||||
if report.functions:
|
||||
for func in report.functions[:5]:
|
||||
print(f"{func.name}: {func.cumulative_time_ms}ms")
|
||||
|
||||
# Access call graph (layer 2)
|
||||
if report.call_graph:
|
||||
print(f"Edges: {len(report.call_graph.edges)}")
|
||||
|
||||
# Export formats
|
||||
json_str = report.to_json()
|
||||
text_str = report.to_text()
|
||||
dict_data = report.to_dict()
|
||||
```
|
||||
|
||||
### Schema Version
|
||||
|
||||
The report uses schema version `1.0`. Schema is frozen after release - new fields are always optional with defaults.
|
||||
|
||||
### Mandatory Fields
|
||||
|
||||
- `schema_version`: str
|
||||
- `run_id`: str
|
||||
- `timestamp`: str (ISO 8601)
|
||||
- `invocation`: InvocationInfo
|
||||
- `timing`: TimingBreakdown
|
||||
- `response_preview`: str
|
||||
|
||||
### Optional Fields
|
||||
|
||||
- `functions`: List[FunctionStat] (layer 1+)
|
||||
- `call_graph`: CallGraph (layer 2)
|
||||
- `network`: List[RequestTiming] (if track_network)
|
||||
|
||||
## ExecutionResult
|
||||
|
||||
Result of execution with output and metadata.
|
||||
|
||||
```python
|
||||
# Access output
|
||||
print(result.output)
|
||||
print(result.success)
|
||||
print(result.run_id)
|
||||
|
||||
# Check for errors
|
||||
if result.error:
|
||||
print(f"Error: {result.error}")
|
||||
|
||||
# Duration
|
||||
print(f"Duration: {result.duration_ms}ms")
|
||||
```
|
||||
|
||||
## Data Bounds
|
||||
|
||||
Profile data is strictly bounded to prevent memory issues:
|
||||
|
||||
| Data Type | Maximum |
|
||||
|-----------|---------|
|
||||
| Function stats | 1000 |
|
||||
| Call graph edges | 5000 |
|
||||
| Network requests | 100 |
|
||||
|
||||
## Example: Full Profiling Workflow
|
||||
|
||||
```python
|
||||
from praisonai.cli.execution import (
|
||||
ExecutionRequest,
|
||||
Profiler,
|
||||
ProfilerConfig,
|
||||
)
|
||||
|
||||
# Configure deep profiling
|
||||
config = ProfilerConfig(
|
||||
layer=2,
|
||||
limit=50,
|
||||
show_callers=True,
|
||||
show_callees=True,
|
||||
)
|
||||
|
||||
# Create request
|
||||
request = ExecutionRequest(
|
||||
prompt="Explain quantum computing in simple terms",
|
||||
model="gpt-4",
|
||||
)
|
||||
|
||||
# Profile execution
|
||||
profiler = Profiler(config)
|
||||
result, report = profiler.profile_sync(request)
|
||||
|
||||
# Print text report
|
||||
print(report.to_text())
|
||||
|
||||
# Save JSON report
|
||||
with open("profile.json", "w") as f:
|
||||
f.write(report.to_json())
|
||||
|
||||
# Analyze timing
|
||||
print(f"\nTiming Analysis:")
|
||||
print(f" Import overhead: {report.timing.imports_ms / report.timing.total_ms * 100:.1f}%")
|
||||
print(f" Execution time: {report.timing.execution_ms / report.timing.total_ms * 100:.1f}%")
|
||||
|
||||
# Top 5 hotspots
|
||||
if report.functions:
|
||||
print(f"\nTop 5 Hotspots:")
|
||||
for func in report.functions[:5]:
|
||||
print(f" {func.name}: {func.cumulative_time_ms:.2f}ms ({func.calls} calls)")
|
||||
```
|
||||
|
||||
## Invariants
|
||||
|
||||
The profiling module guarantees:
|
||||
|
||||
1. **Output Equivalence**: Profiled execution produces identical output to non-profiled execution
|
||||
2. **Schema Stability**: Schema version 1.0 is frozen; new fields are always optional
|
||||
3. **Bounded Data**: All profile data respects maximum bounds
|
||||
4. **Zero Overhead When Disabled**: No profiling code runs unless explicitly enabled
|
||||
@@ -0,0 +1,137 @@
|
||||
# ACP (Agent Client Protocol) Examples
|
||||
|
||||
This directory contains examples for using PraisonAI with the Agent Client Protocol (ACP).
|
||||
|
||||
## What is ACP?
|
||||
|
||||
The Agent Client Protocol (ACP) is a standardized JSON-RPC 2.0 protocol that allows code editors and IDEs to communicate with AI coding agents. It enables seamless integration between PraisonAI and editors like:
|
||||
|
||||
- **Zed**
|
||||
- **JetBrains IDEs** (IntelliJ, PyCharm, WebStorm, etc.)
|
||||
- **VSCode** (via extensions)
|
||||
- **Toad**
|
||||
|
||||
## Quick Start
|
||||
|
||||
### CLI Usage
|
||||
|
||||
```bash
|
||||
# Start ACP server with defaults
|
||||
praisonai acp
|
||||
|
||||
# Enable debug logging
|
||||
praisonai acp --debug
|
||||
|
||||
# Allow file writes
|
||||
praisonai acp --allow-write
|
||||
|
||||
# Use a specific model
|
||||
praisonai acp -m gpt-4o
|
||||
|
||||
# Resume last session
|
||||
praisonai acp --resume --last
|
||||
```
|
||||
|
||||
### Python API
|
||||
|
||||
```python
|
||||
from praisonai.acp import serve
|
||||
|
||||
serve(
|
||||
workspace=".",
|
||||
debug=True,
|
||||
allow_write=True,
|
||||
)
|
||||
```
|
||||
|
||||
## Editor Configuration
|
||||
|
||||
### Zed
|
||||
|
||||
Add to `~/.config/zed/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"PraisonAI": {
|
||||
"command": "praisonai",
|
||||
"args": ["acp"],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JetBrains
|
||||
|
||||
Add to `~/.jetbrains/acp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"PraisonAI": {
|
||||
"command": "praisonai",
|
||||
"args": ["acp"],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Toad
|
||||
|
||||
```bash
|
||||
toad acp "praisonai acp"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
- `basic_acp_server.py` - Simple ACP server with default settings
|
||||
- `custom_agent_acp.py` - ACP server with a custom agent
|
||||
|
||||
## CLI Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-w, --workspace` | Workspace root directory |
|
||||
| `-a, --agent` | Agent name or config file |
|
||||
| `-m, --model` | LLM model to use |
|
||||
| `-r, --resume` | Resume session by ID |
|
||||
| `--last` | Resume the last session |
|
||||
| `--approve` | Approval mode: manual, auto, scoped |
|
||||
| `--read-only` | Read-only mode (default) |
|
||||
| `--allow-write` | Allow file writes |
|
||||
| `--allow-shell` | Allow shell commands |
|
||||
| `--debug` | Enable debug logging |
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install praisonai[acp]
|
||||
```
|
||||
|
||||
## Protocol Details
|
||||
|
||||
ACP uses JSON-RPC 2.0 over stdio:
|
||||
- **stdin**: Receives JSON-RPC requests from the client
|
||||
- **stdout**: Sends JSON-RPC responses to the client
|
||||
- **stderr**: Debug/log output (never pollutes stdout)
|
||||
|
||||
### Supported Methods
|
||||
|
||||
**Agent Methods (client → agent):**
|
||||
- `initialize` - Negotiate protocol version and capabilities
|
||||
- `authenticate` - Optional authentication
|
||||
- `session/new` - Create a new session
|
||||
- `session/load` - Load an existing session
|
||||
- `session/prompt` - Send user message
|
||||
- `session/cancel` - Cancel ongoing operations
|
||||
- `session/set_mode` - Change operating mode
|
||||
|
||||
**Client Methods (agent → client):**
|
||||
- `session/update` - Send progress updates
|
||||
- `session/request_permission` - Request user approval
|
||||
- `fs/read_text_file` - Read file contents
|
||||
- `fs/write_text_file` - Write file contents
|
||||
- `terminal/create` - Create terminal
|
||||
- `terminal/output` - Get terminal output
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic ACP Server Example
|
||||
|
||||
This example demonstrates how to run PraisonAI as an ACP server
|
||||
that can be connected to by IDEs like Zed, JetBrains, VSCode, or Toad.
|
||||
|
||||
Usage:
|
||||
# Run from command line
|
||||
praisonai acp
|
||||
|
||||
# Or run this script directly
|
||||
python basic_acp_server.py
|
||||
"""
|
||||
|
||||
from praisonai.acp import serve
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Start ACP server with default settings
|
||||
# This will listen on stdin/stdout for JSON-RPC messages
|
||||
serve(
|
||||
workspace=".", # Current directory as workspace
|
||||
agent="default", # Use default agent
|
||||
debug=False, # Set to True for debug logging to stderr
|
||||
read_only=True, # Safe by default
|
||||
approval_mode="manual", # Require approval for actions
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Custom Agent ACP Server Example
|
||||
|
||||
This example shows how to run a custom PraisonAI agent as an ACP server.
|
||||
|
||||
Usage:
|
||||
python custom_agent_acp.py
|
||||
"""
|
||||
|
||||
from praisonai.acp import serve, ACPServer, ACPConfig
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Create a custom agent
|
||||
agent = Agent(
|
||||
name="CodeAssistant",
|
||||
instructions="""You are an expert coding assistant. You help users:
|
||||
- Write clean, efficient code
|
||||
- Debug issues
|
||||
- Explain complex concepts
|
||||
- Review code for best practices
|
||||
|
||||
Always provide clear explanations with your code suggestions.""",
|
||||
model="gpt-4o-mini",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create config
|
||||
config = ACPConfig(
|
||||
workspace=".",
|
||||
debug=True,
|
||||
allow_write=True, # Allow file modifications
|
||||
approval_mode="manual",
|
||||
)
|
||||
|
||||
# Create server with custom agent
|
||||
server = ACPServer(config=config, agent=agent)
|
||||
|
||||
# Run the server
|
||||
serve(
|
||||
workspace=".",
|
||||
debug=True,
|
||||
allow_write=True,
|
||||
approval_mode="manual",
|
||||
)
|
||||
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
Agent-Centric Tools Example
|
||||
|
||||
This example demonstrates how to use LSP/ACP-powered tools that make
|
||||
the Agent the central orchestrator for file operations.
|
||||
|
||||
How to run:
|
||||
export OPENAI_API_KEY=your_key
|
||||
python examples/agent_tools/agent_centric_example.py
|
||||
|
||||
Prerequisites:
|
||||
pip install praisonai praisonaiagents
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
# Add paths for development
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src/praisonai'))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src/praisonai-agents'))
|
||||
|
||||
|
||||
async def main():
|
||||
print("=" * 60)
|
||||
print("AGENT-CENTRIC TOOLS EXAMPLE")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Create temporary workspace
|
||||
workspace = tempfile.mkdtemp(prefix="praisonai_example_")
|
||||
print(f"Workspace: {workspace}")
|
||||
print()
|
||||
|
||||
try:
|
||||
# Import after path setup
|
||||
from praisonai.cli.features import (
|
||||
create_agent_centric_tools,
|
||||
InteractiveRuntime,
|
||||
RuntimeConfig
|
||||
)
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# 1. Create runtime with ACP enabled
|
||||
print("1. Creating InteractiveRuntime with ACP...")
|
||||
config = RuntimeConfig(
|
||||
workspace=workspace,
|
||||
lsp_enabled=False, # Skip LSP for this example
|
||||
acp_enabled=True,
|
||||
approval_mode="auto" # Auto-approve for demo
|
||||
)
|
||||
runtime = InteractiveRuntime(config)
|
||||
status = await runtime.start()
|
||||
|
||||
print(f" ACP ready: {runtime.acp_ready}")
|
||||
print(f" Read-only: {runtime.read_only}")
|
||||
print()
|
||||
|
||||
# 2. Create agent-centric tools
|
||||
print("2. Creating agent-centric tools...")
|
||||
tools = create_agent_centric_tools(runtime)
|
||||
print(f" Tools created: {len(tools)}")
|
||||
for tool in tools:
|
||||
print(f" - {tool.__name__}")
|
||||
print()
|
||||
|
||||
# 3. Create Agent with ACP-powered tools
|
||||
print("3. Creating Agent with ACP-powered tools...")
|
||||
agent = Agent(
|
||||
name="FileAgent",
|
||||
instructions="""You help create and manage files.
|
||||
Use acp_create_file to create files.
|
||||
Use read_file to read files.
|
||||
Use list_files to list directory contents.""",
|
||||
tools=tools,
|
||||
verbose=True
|
||||
)
|
||||
print()
|
||||
|
||||
# 4. Ask agent to create a file
|
||||
print("4. Asking agent to create a Python file...")
|
||||
print("-" * 40)
|
||||
result = agent.start(
|
||||
"Create a Python file called calculator.py with add and subtract functions"
|
||||
)
|
||||
print("-" * 40)
|
||||
print(f"Agent response: {result[:200]}..." if len(str(result)) > 200 else f"Agent response: {result}")
|
||||
print()
|
||||
|
||||
# 5. Verify file was created
|
||||
print("5. Verifying file creation...")
|
||||
calc_path = os.path.join(workspace, "calculator.py")
|
||||
if os.path.exists(calc_path):
|
||||
print(f" ✓ File created at {calc_path}")
|
||||
with open(calc_path) as f:
|
||||
content = f.read()
|
||||
print(f" Content preview:")
|
||||
for line in content.split('\n')[:10]:
|
||||
print(f" {line}")
|
||||
else:
|
||||
# Check what files exist
|
||||
files = os.listdir(workspace)
|
||||
print(f" Files in workspace: {files}")
|
||||
for f in files:
|
||||
fp = os.path.join(workspace, f)
|
||||
if os.path.isfile(fp):
|
||||
print(f" ✓ Found: {f}")
|
||||
print()
|
||||
|
||||
# 6. Ask agent to read the file
|
||||
print("6. Asking agent to read the file...")
|
||||
print("-" * 40)
|
||||
result2 = agent.start("Read the calculator.py file and tell me what functions it has")
|
||||
print("-" * 40)
|
||||
print(f"Agent response: {result2[:300]}..." if len(str(result2)) > 300 else f"Agent response: {result2}")
|
||||
print()
|
||||
|
||||
# Cleanup
|
||||
await runtime.stop()
|
||||
|
||||
finally:
|
||||
# Clean up workspace
|
||||
shutil.rmtree(workspace, ignore_errors=True)
|
||||
print(f"Cleaned up workspace: {workspace}")
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("EXAMPLE COMPLETE")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
Debug Interactive Mode Example
|
||||
|
||||
This example demonstrates how to use the debug CLI commands to test
|
||||
the Interactive Coding Assistant without entering TUI mode.
|
||||
|
||||
Features demonstrated:
|
||||
- LSP symbol listing
|
||||
- LSP definition lookup
|
||||
- ACP action planning
|
||||
- ACP action execution
|
||||
- Trace capture and JSON output
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add the src directory to path for development
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src" / "praisonai"))
|
||||
|
||||
from praisonai.cli.features.interactive_runtime import create_runtime
|
||||
from praisonai.cli.features.code_intelligence import CodeIntelligenceRouter
|
||||
from praisonai.cli.features.action_orchestrator import ActionOrchestrator
|
||||
|
||||
|
||||
async def demo_lsp_queries(workspace: str):
|
||||
"""Demonstrate LSP-based code queries."""
|
||||
print("\n" + "=" * 60)
|
||||
print("LSP CODE QUERIES DEMO")
|
||||
print("=" * 60)
|
||||
|
||||
runtime = create_runtime(
|
||||
workspace=workspace,
|
||||
lsp=True,
|
||||
acp=False,
|
||||
trace=True
|
||||
)
|
||||
|
||||
try:
|
||||
await runtime.start()
|
||||
router = CodeIntelligenceRouter(runtime)
|
||||
|
||||
# Query 1: List symbols
|
||||
print("\n[Query 1] List all symbols in app.py")
|
||||
result = await router.handle_query("list all functions in app.py", "app.py")
|
||||
print(f" Success: {result.success}")
|
||||
print(f" LSP Used: {result.lsp_used}")
|
||||
print(f" Fallback Used: {result.fallback_used}")
|
||||
if result.data:
|
||||
print(f" Symbols found: {len(result.data)}")
|
||||
for symbol in result.data[:5]:
|
||||
print(f" - {symbol['name']} ({symbol['kind']}) at line {symbol['line']}")
|
||||
|
||||
# Query 2: Find definition
|
||||
print("\n[Query 2] Find definition of Calculator")
|
||||
result = await router.handle_query("where is Calculator defined", "app.py")
|
||||
print(f" Success: {result.success}")
|
||||
if result.citations:
|
||||
print(f" Found at: {result.citations[0]}")
|
||||
|
||||
# Get trace
|
||||
trace = runtime.get_trace()
|
||||
print(f"\n[Trace] {len(trace.entries)} entries captured")
|
||||
|
||||
finally:
|
||||
await runtime.stop()
|
||||
|
||||
|
||||
async def demo_acp_actions(workspace: str):
|
||||
"""Demonstrate ACP-based code modifications."""
|
||||
print("\n" + "=" * 60)
|
||||
print("ACP CODE MODIFICATIONS DEMO")
|
||||
print("=" * 60)
|
||||
|
||||
runtime = create_runtime(
|
||||
workspace=workspace,
|
||||
lsp=False,
|
||||
acp=True,
|
||||
approval="auto",
|
||||
trace=True
|
||||
)
|
||||
|
||||
try:
|
||||
await runtime.start()
|
||||
print(f"\n ACP Ready: {runtime.acp_ready}")
|
||||
print(f" Read Only: {runtime.read_only}")
|
||||
|
||||
orchestrator = ActionOrchestrator(runtime)
|
||||
|
||||
# Action 1: Create a plan
|
||||
print("\n[Action 1] Create plan for new file")
|
||||
result = await orchestrator.create_plan("create a new file called demo_output.py")
|
||||
print(f" Success: {result.success}")
|
||||
if result.plan:
|
||||
print(f" Plan ID: {result.plan.id}")
|
||||
print(f" Steps: {len(result.plan.steps)}")
|
||||
for step in result.plan.steps:
|
||||
print(f" - {step.action_type.value}: {step.target}")
|
||||
|
||||
# Action 2: Execute with auto-approve
|
||||
print("\n[Action 2] Execute file creation")
|
||||
exec_result = await orchestrator.execute(
|
||||
"create a new file called demo_output.py with a hello function",
|
||||
auto_approve=True
|
||||
)
|
||||
print(f" Success: {exec_result.success}")
|
||||
print(f" Applied Steps: {exec_result.applied_steps}")
|
||||
|
||||
# Verify file was created
|
||||
output_file = Path(workspace) / "demo_output.py"
|
||||
if output_file.exists():
|
||||
print(f" File created: {output_file}")
|
||||
print(f" Content preview: {output_file.read_text()[:100]}...")
|
||||
|
||||
# Get trace
|
||||
trace = runtime.get_trace()
|
||||
print(f"\n[Trace] {len(trace.entries)} entries captured")
|
||||
for entry in trace.entries:
|
||||
print(f" [{entry.category}] {entry.action}")
|
||||
|
||||
finally:
|
||||
await runtime.stop()
|
||||
|
||||
|
||||
async def demo_full_interactive_turn(workspace: str):
|
||||
"""Demonstrate full interactive turn simulation."""
|
||||
print("\n" + "=" * 60)
|
||||
print("FULL INTERACTIVE TURN SIMULATION")
|
||||
print("=" * 60)
|
||||
|
||||
runtime = create_runtime(
|
||||
workspace=workspace,
|
||||
lsp=True,
|
||||
acp=True,
|
||||
approval="auto",
|
||||
trace=True
|
||||
)
|
||||
|
||||
try:
|
||||
status = await runtime.start()
|
||||
print("\n Runtime Status:")
|
||||
print(f" LSP: {'ready' if status['lsp']['ready'] else 'fallback'}")
|
||||
print(f" ACP: {'ready' if status['acp']['ready'] else 'unavailable'}")
|
||||
|
||||
router = CodeIntelligenceRouter(runtime)
|
||||
orchestrator = ActionOrchestrator(runtime)
|
||||
|
||||
# Simulate user prompt: code query
|
||||
prompt1 = "What functions are in app.py?"
|
||||
print(f"\n[Turn 1] User: {prompt1}")
|
||||
|
||||
# Classify and route
|
||||
result = await router.handle_query(prompt1, "app.py")
|
||||
print(f" Intent: {result.intent.value}")
|
||||
print(f" Response: Found {len(result.data) if result.data else 0} symbols")
|
||||
|
||||
# Simulate user prompt: code modification
|
||||
prompt2 = "Create a new test file"
|
||||
print(f"\n[Turn 2] User: {prompt2}")
|
||||
|
||||
exec_result = await orchestrator.execute(
|
||||
"create a new file called test_demo.py",
|
||||
auto_approve=True
|
||||
)
|
||||
print(" Action: file_create")
|
||||
print(f" Result: {'success' if exec_result.success else 'failed'}")
|
||||
|
||||
# Export trace as JSON
|
||||
trace = runtime.get_trace()
|
||||
trace_json = trace.to_dict()
|
||||
print("[Trace Export]")
|
||||
print(json.dumps(trace_json, indent=2, default=str)[:500] + "...")
|
||||
|
||||
finally:
|
||||
await runtime.stop()
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the debug demo."""
|
||||
# Create a temporary workspace
|
||||
workspace = Path(__file__).parent / "_demo_workspace"
|
||||
workspace.mkdir(exist_ok=True)
|
||||
|
||||
# Create a sample app.py
|
||||
app_file = workspace / "app.py"
|
||||
app_file.write_text('''"""Sample application."""
|
||||
|
||||
def greet(name: str) -> str:
|
||||
"""Greet someone."""
|
||||
return f"Hello, {name}!"
|
||||
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
class Calculator:
|
||||
"""Simple calculator."""
|
||||
|
||||
def __init__(self):
|
||||
self.value = 0
|
||||
|
||||
def add(self, x):
|
||||
self.value += x
|
||||
return self.value
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(greet("World"))
|
||||
''')
|
||||
|
||||
print("=" * 60)
|
||||
print("PRAISONAI DEBUG MODE DEMO")
|
||||
print("=" * 60)
|
||||
print(f"Workspace: {workspace}")
|
||||
|
||||
# Run demos
|
||||
asyncio.run(demo_lsp_queries(str(workspace)))
|
||||
asyncio.run(demo_acp_actions(str(workspace)))
|
||||
asyncio.run(demo_full_interactive_turn(str(workspace)))
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("DEMO COMPLETE")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
Interactive Default Tools Example
|
||||
|
||||
This example demonstrates how PraisonAI's interactive modes (TUI and prompt)
|
||||
include ACP and LSP tools by default.
|
||||
|
||||
Tool Groups:
|
||||
- ACP: acp_create_file, acp_edit_file, acp_delete_file, acp_execute_command
|
||||
- LSP: lsp_list_symbols, lsp_find_definition, lsp_find_references, lsp_get_diagnostics
|
||||
- Basic: read_file, write_file, list_files, execute_command, internet_search
|
||||
|
||||
Usage:
|
||||
python interactive_tools_example.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add paths for development
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src/praisonai'))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src/praisonai-agents'))
|
||||
|
||||
|
||||
def example_get_default_tools():
|
||||
"""Example: Get all default interactive tools."""
|
||||
from praisonai.cli.features.interactive_tools import (
|
||||
get_interactive_tools,
|
||||
ToolConfig,
|
||||
TOOL_GROUPS,
|
||||
)
|
||||
|
||||
print("=== Default Interactive Tools ===\n")
|
||||
|
||||
# Show tool groups
|
||||
print("Tool Groups:")
|
||||
for group, tools in TOOL_GROUPS.items():
|
||||
if group != "interactive":
|
||||
print(f" {group}: {tools}")
|
||||
print()
|
||||
|
||||
# Get all tools
|
||||
config = ToolConfig(workspace=os.getcwd())
|
||||
tools = get_interactive_tools(config=config)
|
||||
|
||||
print(f"Total tools loaded: {len(tools)}")
|
||||
print(f"Tool names: {[t.__name__ for t in tools]}")
|
||||
print()
|
||||
|
||||
return tools
|
||||
|
||||
|
||||
def example_disable_acp():
|
||||
"""Example: Disable ACP tools."""
|
||||
from praisonai.cli.features.interactive_tools import (
|
||||
get_interactive_tools,
|
||||
ToolConfig,
|
||||
)
|
||||
|
||||
print("=== Disable ACP Tools (--no-acp) ===\n")
|
||||
|
||||
config = ToolConfig(workspace=os.getcwd(), enable_acp=False)
|
||||
tools = get_interactive_tools(config=config, disable=["acp"])
|
||||
|
||||
acp_tools = [t for t in tools if t.__name__.startswith("acp_")]
|
||||
print(f"ACP tools after --no-acp: {len(acp_tools)}")
|
||||
print(f"Remaining tools: {[t.__name__ for t in tools]}")
|
||||
print()
|
||||
|
||||
return tools
|
||||
|
||||
|
||||
def example_disable_lsp():
|
||||
"""Example: Disable LSP tools."""
|
||||
from praisonai.cli.features.interactive_tools import (
|
||||
get_interactive_tools,
|
||||
ToolConfig,
|
||||
)
|
||||
|
||||
print("=== Disable LSP Tools (--no-lsp) ===\n")
|
||||
|
||||
config = ToolConfig(workspace=os.getcwd(), enable_lsp=False)
|
||||
tools = get_interactive_tools(config=config, disable=["lsp"])
|
||||
|
||||
lsp_tools = [t for t in tools if t.__name__.startswith("lsp_")]
|
||||
print(f"LSP tools after --no-lsp: {len(lsp_tools)}")
|
||||
print(f"Remaining tools: {[t.__name__ for t in tools]}")
|
||||
print()
|
||||
|
||||
return tools
|
||||
|
||||
|
||||
def example_agent_with_default_tools():
|
||||
"""Example: Create an Agent with default interactive tools."""
|
||||
from praisonai.cli.features.interactive_tools import get_interactive_tools, ToolConfig
|
||||
from praisonaiagents import Agent
|
||||
|
||||
print("=== Agent with Default Interactive Tools ===\n")
|
||||
|
||||
# Get default tools
|
||||
config = ToolConfig(workspace=os.getcwd())
|
||||
tools = get_interactive_tools(config=config)
|
||||
|
||||
# Create agent with tools
|
||||
agent = Agent(
|
||||
name="InteractiveAgent",
|
||||
instructions="You are a helpful coding assistant with file and code analysis capabilities.",
|
||||
tools=tools,
|
||||
verbose=False
|
||||
)
|
||||
|
||||
print(f"Agent created with {len(tools)} tools")
|
||||
print(f"Tools: {[t.__name__ for t in tools]}")
|
||||
print()
|
||||
|
||||
# Simple test - list files
|
||||
print("Testing agent with list_files tool...")
|
||||
result = agent.start("List the Python files in the current directory using list_files tool.")
|
||||
print(f"Result: {result[:200]}..." if len(str(result)) > 200 else f"Result: {result}")
|
||||
print()
|
||||
|
||||
return agent
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("PraisonAI Interactive Default Tools Example")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Example 1: Get default tools
|
||||
example_get_default_tools()
|
||||
|
||||
# Example 2: Disable ACP
|
||||
example_disable_acp()
|
||||
|
||||
# Example 3: Disable LSP
|
||||
example_disable_lsp()
|
||||
|
||||
# Example 4: Agent with default tools (requires API key)
|
||||
if os.environ.get("OPENAI_API_KEY"):
|
||||
example_agent_with_default_tools()
|
||||
else:
|
||||
print("=== Agent Example Skipped (no OPENAI_API_KEY) ===")
|
||||
|
||||
print("=" * 60)
|
||||
print("Examples Complete")
|
||||
print("=" * 60)
|
||||
@@ -0,0 +1,41 @@
|
||||
# AI SDK Examples
|
||||
|
||||
Examples demonstrating the AI SDK integration in PraisonAI TypeScript.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
npm install praisonai ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google zod
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=sk-...
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
export GOOGLE_API_KEY=AIza...
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `basic-chat.ts` | Simple text generation |
|
||||
| `streaming.ts` | Streaming text output |
|
||||
| `tool-calling.ts` | Function/tool calling |
|
||||
| `multi-provider.ts` | Using multiple providers |
|
||||
| `structured-output.ts` | JSON schema output |
|
||||
| `multi-agent.ts` | Multi-agent attribution |
|
||||
|
||||
## Running Examples
|
||||
|
||||
```bash
|
||||
npx ts-node basic-chat.ts
|
||||
npx ts-node streaming.ts
|
||||
npx ts-node tool-calling.ts
|
||||
```
|
||||
|
||||
## More Information
|
||||
|
||||
- [AI SDK Documentation](https://docs.praisonai.com/docs/js/ai-sdk)
|
||||
- [AI SDK CLI](https://docs.praisonai.com/docs/js/ai-sdk-cli)
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* AI SDK Basic Chat Example
|
||||
*
|
||||
* Demonstrates simple text generation using the AI SDK backend.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node basic-chat.ts
|
||||
*
|
||||
* Environment:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create backend with model string
|
||||
const backend = createAISDKBackend('openai/gpt-4o-mini');
|
||||
|
||||
console.log('Sending message to OpenAI...\n');
|
||||
|
||||
// Generate text
|
||||
const result = await backend.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||
{ role: 'user', content: 'What are the three primary colors?' }
|
||||
],
|
||||
maxTokens: 200,
|
||||
temperature: 0.7,
|
||||
});
|
||||
|
||||
console.log('Response:', result.text);
|
||||
console.log('\nFinish reason:', result.finishReason);
|
||||
|
||||
if (result.usage) {
|
||||
console.log('Token usage:', result.usage);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Generate Text Example
|
||||
*
|
||||
* Demonstrates basic text generation using AI SDK v6 via praisonai-ts.
|
||||
*
|
||||
* Run: npx ts-node generate-text.ts
|
||||
* Required: OPENAI_API_KEY
|
||||
*/
|
||||
|
||||
// Import from local praisonai-ts package
|
||||
// In production: import { Agent } from 'praisonai';
|
||||
import { Agent } from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
// Simple text generation with Agent
|
||||
const agent = new Agent({
|
||||
instructions: 'You are a helpful assistant that provides concise answers.',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
});
|
||||
|
||||
console.log('=== Generate Text Example ===\n');
|
||||
|
||||
// Basic chat
|
||||
const response = await agent.chat('What is the capital of France?');
|
||||
console.log('Response:', response);
|
||||
|
||||
// With context
|
||||
const response2 = await agent.chat('What is its population?');
|
||||
console.log('\nFollow-up:', response2);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* MCP Client Example (AI SDK v6 Parity)
|
||||
*
|
||||
* Demonstrates Model Context Protocol client with HTTP/SSE transports.
|
||||
*
|
||||
* Run: npx ts-node mcp-client.ts
|
||||
* Note: Requires an MCP server running
|
||||
*/
|
||||
|
||||
import {
|
||||
createMCP,
|
||||
closeMCPClient,
|
||||
type MCPConfig,
|
||||
} from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
console.log('=== MCP Client Example ===\n');
|
||||
|
||||
// Example configurations for different transports
|
||||
const configs: Record<string, MCPConfig> = {
|
||||
// HTTP transport (recommended for production)
|
||||
http: {
|
||||
transport: {
|
||||
type: 'http',
|
||||
url: 'https://mcp-server.example.com/mcp',
|
||||
headers: { Authorization: 'Bearer token' },
|
||||
},
|
||||
name: 'http-client',
|
||||
},
|
||||
// SSE transport
|
||||
sse: {
|
||||
transport: {
|
||||
type: 'sse',
|
||||
url: 'https://mcp-server.example.com/sse',
|
||||
},
|
||||
name: 'sse-client',
|
||||
},
|
||||
// Stdio transport (for local servers)
|
||||
stdio: {
|
||||
transport: {
|
||||
type: 'stdio',
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-everything'],
|
||||
},
|
||||
name: 'stdio-client',
|
||||
},
|
||||
};
|
||||
|
||||
console.log('MCP Transport Configurations:');
|
||||
console.log('');
|
||||
console.log('1. HTTP Transport (recommended):');
|
||||
console.log(` ${JSON.stringify(configs.http.transport, null, 2)}`);
|
||||
console.log('');
|
||||
console.log('2. SSE Transport:');
|
||||
console.log(` ${JSON.stringify(configs.sse.transport, null, 2)}`);
|
||||
console.log('');
|
||||
console.log('3. Stdio Transport (local):');
|
||||
console.log(` ${JSON.stringify(configs.stdio.transport, null, 2)}`);
|
||||
|
||||
console.log('\n--- Example Usage ---\n');
|
||||
console.log(`
|
||||
// Create MCP client
|
||||
const client = await createMCP({
|
||||
transport: {
|
||||
type: 'http',
|
||||
url: 'https://your-mcp-server.com/mcp',
|
||||
headers: { Authorization: 'Bearer your-token' },
|
||||
}
|
||||
});
|
||||
|
||||
// Get available tools
|
||||
const tools = await client.tools();
|
||||
console.log('Available tools:', Object.keys(tools));
|
||||
|
||||
// List resources
|
||||
const resources = await client.listResources();
|
||||
console.log('Resources:', resources);
|
||||
|
||||
// List prompts
|
||||
const prompts = await client.listPrompts();
|
||||
console.log('Prompts:', prompts);
|
||||
|
||||
// Close when done
|
||||
await closeMCPClient('default');
|
||||
`);
|
||||
|
||||
console.log('\n✅ MCP Client demo completed!');
|
||||
console.log('Note: To run with a real MCP server, uncomment the connection code.');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* AI SDK Multi-Agent Attribution Example
|
||||
*
|
||||
* Demonstrates how to propagate attribution context (agentId, runId, traceId)
|
||||
* to LLM calls for multi-agent observability and safety.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node multi-agent.ts
|
||||
*
|
||||
* Environment:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from 'praisonai';
|
||||
|
||||
// Simulate a multi-agent system with attribution
|
||||
interface AgentContext {
|
||||
agentId: string;
|
||||
runId: string;
|
||||
traceId: string;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
async function runAgent(name: string, task: string, context: AgentContext) {
|
||||
console.log(`\n[${name}] Starting task: ${task}`);
|
||||
console.log(` Attribution: agentId=${context.agentId}, runId=${context.runId}`);
|
||||
|
||||
const backend = createAISDKBackend('openai/gpt-4o-mini', {
|
||||
attribution: context,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
const result = await backend.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: `You are ${name}, a specialized AI agent.` },
|
||||
{ role: 'user', content: task }
|
||||
],
|
||||
maxTokens: 100,
|
||||
});
|
||||
|
||||
console.log(` Response: ${result.text.slice(0, 100)}...`);
|
||||
return result.text;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Create a shared trace ID for the entire workflow
|
||||
const traceId = `trace-${Date.now().toString(36)}`;
|
||||
const sessionId = 'session-user123';
|
||||
|
||||
console.log('Multi-Agent Workflow with Attribution');
|
||||
console.log('=====================================');
|
||||
console.log(`Trace ID: ${traceId}`);
|
||||
console.log(`Session ID: ${sessionId}`);
|
||||
|
||||
// Agent 1: Research Agent
|
||||
const researchResult = await runAgent(
|
||||
'ResearchAgent',
|
||||
'What are the key benefits of TypeScript?',
|
||||
{
|
||||
agentId: 'agent-research',
|
||||
runId: `run-${Date.now().toString(36)}-1`,
|
||||
traceId,
|
||||
sessionId,
|
||||
}
|
||||
);
|
||||
|
||||
// Agent 2: Summary Agent
|
||||
const summaryResult = await runAgent(
|
||||
'SummaryAgent',
|
||||
`Summarize this in one sentence: ${researchResult}`,
|
||||
{
|
||||
agentId: 'agent-summary',
|
||||
runId: `run-${Date.now().toString(36)}-2`,
|
||||
traceId,
|
||||
sessionId,
|
||||
}
|
||||
);
|
||||
|
||||
// Agent 3: Review Agent
|
||||
await runAgent(
|
||||
'ReviewAgent',
|
||||
`Review this summary for accuracy: ${summaryResult}`,
|
||||
{
|
||||
agentId: 'agent-review',
|
||||
runId: `run-${Date.now().toString(36)}-3`,
|
||||
traceId,
|
||||
sessionId,
|
||||
}
|
||||
);
|
||||
|
||||
console.log('\n\nWorkflow complete!');
|
||||
console.log('All requests were tagged with the same trace ID for observability.');
|
||||
console.log('Check your LLM provider dashboard to see the correlated requests.');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* AI SDK Multi-Provider Example
|
||||
*
|
||||
* Demonstrates using multiple LLM providers with the same interface.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node multi-provider.ts
|
||||
*
|
||||
* Environment:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
* ANTHROPIC_API_KEY - Your Anthropic API key
|
||||
* GOOGLE_API_KEY - Your Google API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from 'praisonai';
|
||||
|
||||
const PROMPT = 'Explain what an API is in one sentence.';
|
||||
|
||||
async function testProvider(modelString: string) {
|
||||
try {
|
||||
const backend = createAISDKBackend(modelString, {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await backend.generateText({
|
||||
messages: [{ role: 'user', content: PROMPT }],
|
||||
maxTokens: 100,
|
||||
});
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
console.log(`\n${modelString}:`);
|
||||
console.log(` Response: ${result.text}`);
|
||||
console.log(` Duration: ${duration}ms`);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.log(`\n${modelString}:`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Testing multiple providers with the same prompt...');
|
||||
console.log(`Prompt: "${PROMPT}"`);
|
||||
|
||||
const providers = [
|
||||
'openai/gpt-4o-mini',
|
||||
'anthropic/claude-3-haiku-20240307',
|
||||
'google/gemini-1.5-flash',
|
||||
];
|
||||
|
||||
const results = await Promise.all(providers.map(testProvider));
|
||||
|
||||
const successful = results.filter(Boolean).length;
|
||||
console.log(`\n\nSummary: ${successful}/${providers.length} providers succeeded`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Speech & Transcription Example (AI SDK v6 Parity)
|
||||
*
|
||||
* Demonstrates speech generation and transcription wrappers.
|
||||
*
|
||||
* Run: npx ts-node speech-transcription.ts
|
||||
* Required: OPENAI_API_KEY or ELEVENLABS_API_KEY
|
||||
*/
|
||||
|
||||
import {
|
||||
generateSpeech,
|
||||
transcribe,
|
||||
SPEECH_MODELS,
|
||||
TRANSCRIPTION_MODELS,
|
||||
} from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Speech & Transcription Example ===\n');
|
||||
|
||||
// Show available models
|
||||
console.log('1. Available Speech Models:');
|
||||
Object.entries(SPEECH_MODELS).forEach(([key, value]) => {
|
||||
console.log(` ${key}: ${value}`);
|
||||
});
|
||||
|
||||
console.log('\n2. Available Transcription Models:');
|
||||
Object.entries(TRANSCRIPTION_MODELS).forEach(([key, value]) => {
|
||||
console.log(` ${key}: ${value}`);
|
||||
});
|
||||
|
||||
// Example: Generate speech (requires API key)
|
||||
console.log('\n3. Speech Generation Example:');
|
||||
console.log(`
|
||||
// Generate speech from text
|
||||
const result = await generateSpeech({
|
||||
text: 'Hello, this is a test of speech synthesis.',
|
||||
model: 'openai/tts-1',
|
||||
voice: 'alloy',
|
||||
});
|
||||
|
||||
// Save to file
|
||||
await fs.writeFile('output.mp3', result.audio);
|
||||
console.log('Audio saved to output.mp3');
|
||||
`);
|
||||
|
||||
// Example: Transcribe audio (requires API key)
|
||||
console.log('4. Transcription Example:');
|
||||
console.log(`
|
||||
// Transcribe audio file
|
||||
const result = await transcribe({
|
||||
audio: await fs.readFile('audio.mp3'),
|
||||
model: 'openai/whisper-1',
|
||||
});
|
||||
|
||||
console.log('Transcription:', result.text);
|
||||
console.log('Language:', result.language);
|
||||
console.log('Duration:', result.durationInSeconds, 'seconds');
|
||||
|
||||
// With segments
|
||||
if (result.segments) {
|
||||
result.segments.forEach(seg => {
|
||||
console.log(\`[\${seg.start}s - \${seg.end}s]: \${seg.text}\`);
|
||||
});
|
||||
}
|
||||
`);
|
||||
|
||||
// Live test if API key is available
|
||||
if (process.env.OPENAI_API_KEY) {
|
||||
console.log('\n5. Live Speech Generation Test:');
|
||||
try {
|
||||
const result = await generateSpeech({
|
||||
text: 'Hello from PraisonAI!',
|
||||
model: 'openai/tts-1',
|
||||
voice: 'alloy',
|
||||
});
|
||||
console.log(` ✅ Generated ${result.audio.length} bytes of audio`);
|
||||
} catch (error: any) {
|
||||
console.log(` ⚠️ Speech generation failed: ${error.message}`);
|
||||
}
|
||||
} else {
|
||||
console.log('\n5. Live Test: Skipped (OPENAI_API_KEY not set)');
|
||||
}
|
||||
|
||||
console.log('\n✅ Speech & Transcription demo completed!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Stream Text Example
|
||||
*
|
||||
* Demonstrates streaming text generation using AI SDK v6 via praisonai-ts.
|
||||
*
|
||||
* Run: npx ts-node stream-text.ts
|
||||
* Required: OPENAI_API_KEY
|
||||
*/
|
||||
|
||||
import { Agent } from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
const agent = new Agent({
|
||||
instructions: 'You are a helpful assistant that provides detailed answers.',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
stream: true,
|
||||
});
|
||||
|
||||
console.log('=== Stream Text Example ===\n');
|
||||
console.log('Streaming response:\n');
|
||||
|
||||
// Stream the response
|
||||
const response = await agent.chat('Explain quantum computing in 3 paragraphs.');
|
||||
console.log('\n\nFinal response:', response);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* AI SDK Streaming Example
|
||||
*
|
||||
* Demonstrates streaming text generation with real-time output.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node streaming.ts
|
||||
*
|
||||
* Environment:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const backend = createAISDKBackend('openai/gpt-4o-mini');
|
||||
|
||||
console.log('Streaming response from OpenAI...\n');
|
||||
|
||||
const stream = await backend.streamText({
|
||||
messages: [
|
||||
{ role: 'user', content: 'Write a short poem about coding.' }
|
||||
],
|
||||
maxTokens: 200,
|
||||
});
|
||||
|
||||
// Stream chunks as they arrive
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.text) {
|
||||
process.stdout.write(chunk.text);
|
||||
}
|
||||
|
||||
if (chunk.finishReason) {
|
||||
console.log('\n\nFinish reason:', chunk.finishReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* AI SDK Structured Output Example
|
||||
*
|
||||
* Demonstrates generating typed JSON objects with Zod schemas.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node structured-output.ts
|
||||
*
|
||||
* Environment:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { generateObject } from 'ai';
|
||||
import { openai } from '@ai-sdk/openai';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Define schema for a person profile
|
||||
const PersonSchema = z.object({
|
||||
name: z.string().describe('Full name'),
|
||||
age: z.number().describe('Age in years'),
|
||||
occupation: z.string().describe('Job title'),
|
||||
city: z.string().describe('City of residence'),
|
||||
interests: z.array(z.string()).describe('List of hobbies/interests'),
|
||||
bio: z.string().describe('Short biography'),
|
||||
});
|
||||
|
||||
// Define schema for a recipe
|
||||
const RecipeSchema = z.object({
|
||||
name: z.string().describe('Recipe name'),
|
||||
cuisine: z.string().describe('Type of cuisine'),
|
||||
prepTime: z.number().describe('Preparation time in minutes'),
|
||||
cookTime: z.number().describe('Cooking time in minutes'),
|
||||
servings: z.number().describe('Number of servings'),
|
||||
ingredients: z.array(z.object({
|
||||
item: z.string(),
|
||||
amount: z.string(),
|
||||
})).describe('List of ingredients'),
|
||||
instructions: z.array(z.string()).describe('Step-by-step instructions'),
|
||||
});
|
||||
|
||||
async function main() {
|
||||
console.log('Generating structured output with Zod schemas...\n');
|
||||
|
||||
// Generate a person profile
|
||||
console.log('1. Generating a fictional person profile...');
|
||||
const personResult = await generateObject({
|
||||
model: openai('gpt-4o-mini'),
|
||||
schema: PersonSchema,
|
||||
prompt: 'Generate a fictional software engineer profile.',
|
||||
});
|
||||
|
||||
console.log('Person:', JSON.stringify(personResult.object, null, 2));
|
||||
|
||||
// Generate a recipe
|
||||
console.log('\n2. Generating a recipe...');
|
||||
const recipeResult = await generateObject({
|
||||
model: openai('gpt-4o-mini'),
|
||||
schema: RecipeSchema,
|
||||
prompt: 'Generate a simple pasta recipe.',
|
||||
});
|
||||
|
||||
console.log('Recipe:', JSON.stringify(recipeResult.object, null, 2));
|
||||
|
||||
// Type-safe access
|
||||
console.log('\n3. Type-safe access:');
|
||||
console.log(` Person name: ${personResult.object.name}`);
|
||||
console.log(` Person age: ${personResult.object.age}`);
|
||||
console.log(` Recipe name: ${recipeResult.object.name}`);
|
||||
console.log(` Total time: ${recipeResult.object.prepTime + recipeResult.object.cookTime} minutes`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Telemetry Example (AI SDK v6 Parity)
|
||||
*
|
||||
* Demonstrates telemetry and tracing with optional OpenTelemetry integration.
|
||||
*
|
||||
* Run: npx ts-node telemetry.ts
|
||||
* Required: OPENAI_API_KEY
|
||||
*/
|
||||
|
||||
import {
|
||||
configureTelemetry,
|
||||
isTelemetryEnabled,
|
||||
createAISpan,
|
||||
recordEvent,
|
||||
getEvents,
|
||||
clearEvents,
|
||||
Agent,
|
||||
} from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Telemetry Example ===\n');
|
||||
|
||||
// Configure telemetry
|
||||
configureTelemetry({
|
||||
isEnabled: true,
|
||||
functionId: 'telemetry-demo',
|
||||
metadata: {
|
||||
version: '1.0.0',
|
||||
environment: 'development',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('1. Telemetry Status:');
|
||||
console.log(` Enabled: ${isTelemetryEnabled()}`);
|
||||
|
||||
// Create a span for an operation
|
||||
console.log('\n2. Creating AI Span:');
|
||||
const span = createAISpan('generateText', {
|
||||
model: 'gpt-4o-mini',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
try {
|
||||
// Simulate AI operation
|
||||
console.log(' Performing AI operation...');
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
span.setAttribute('tokens', 150);
|
||||
span.setStatus({ code: 'ok' });
|
||||
console.log(' Span completed successfully');
|
||||
} catch (error: any) {
|
||||
span.recordException(error);
|
||||
span.setStatus({ code: 'error', message: error.message });
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
|
||||
// Record custom events
|
||||
console.log('\n3. Recording Events:');
|
||||
recordEvent('agent.started', { agentName: 'demo-agent' });
|
||||
recordEvent('tool.called', { toolName: 'search', duration: 250 });
|
||||
recordEvent('agent.completed', { success: true });
|
||||
|
||||
const events = getEvents();
|
||||
console.log(` Recorded ${events.length} events:`);
|
||||
events.forEach(e => {
|
||||
console.log(` - ${e.name}: ${JSON.stringify(e.attributes)}`);
|
||||
});
|
||||
|
||||
// Clear events
|
||||
console.log('\n4. Clearing Events:');
|
||||
clearEvents();
|
||||
console.log(` Events after clear: ${getEvents().length}`);
|
||||
|
||||
console.log('\n✅ Telemetry demo completed!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Tool Approval Example (AI SDK v6 Parity)
|
||||
*
|
||||
* Demonstrates human-in-the-loop tool approval using needsApproval.
|
||||
*
|
||||
* Run: npx ts-node tool-approval.ts
|
||||
* Required: OPENAI_API_KEY
|
||||
*/
|
||||
|
||||
import {
|
||||
Agent,
|
||||
withApproval,
|
||||
getApprovalManager,
|
||||
createDangerousPatternChecker,
|
||||
} from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Tool Approval Example ===\n');
|
||||
|
||||
// Set up approval handler
|
||||
const manager = getApprovalManager();
|
||||
manager.onApprovalRequest(async (request) => {
|
||||
console.log(`\n🔐 APPROVAL REQUESTED`);
|
||||
console.log(` Tool: ${request.toolName}`);
|
||||
console.log(` Input: ${JSON.stringify(request.input)}`);
|
||||
console.log(` Reason: ${request.reason || 'No reason provided'}`);
|
||||
|
||||
// Auto-approve for demo (in production, prompt user)
|
||||
console.log(` ✅ Auto-approved for demo\n`);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Create a tool that always requires approval
|
||||
const deleteFile = withApproval({
|
||||
name: 'deleteFile',
|
||||
description: 'Delete a file from the filesystem',
|
||||
needsApproval: true, // Always require approval
|
||||
execute: async (input: { path: string }) => {
|
||||
console.log(` Executing: Would delete ${input.path}`);
|
||||
return { success: true, deleted: input.path };
|
||||
},
|
||||
});
|
||||
|
||||
// Create a tool with conditional approval (dangerous patterns only)
|
||||
const runCommand = withApproval({
|
||||
name: 'runCommand',
|
||||
description: 'Run a shell command',
|
||||
needsApproval: createDangerousPatternChecker(),
|
||||
execute: async (input: { command: string }) => {
|
||||
console.log(` Executing: ${input.command}`);
|
||||
return { output: 'command executed' };
|
||||
},
|
||||
});
|
||||
|
||||
// Test the tools
|
||||
console.log('1. Testing deleteFile (always requires approval):');
|
||||
const result1 = await deleteFile({ path: '/tmp/test.txt' });
|
||||
console.log(' Result:', result1);
|
||||
|
||||
console.log('\n2. Testing runCommand with safe command (no approval needed):');
|
||||
const result2 = await runCommand({ command: 'ls -la' });
|
||||
console.log(' Result:', result2);
|
||||
|
||||
console.log('\n3. Testing runCommand with dangerous command (requires approval):');
|
||||
const result3 = await runCommand({ command: 'rm -rf /tmp/test' });
|
||||
console.log(' Result:', result3);
|
||||
|
||||
console.log('\n✅ Tool approval demo completed!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* AI SDK Tool Calling Example
|
||||
*
|
||||
* Demonstrates function/tool calling with Zod schemas.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node tool-calling.ts
|
||||
*
|
||||
* Environment:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { generateText, tool } from 'ai';
|
||||
import { openai } from '@ai-sdk/openai';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Define tools with Zod schemas
|
||||
const tools = {
|
||||
get_weather: tool({
|
||||
description: 'Get the current weather for a city',
|
||||
inputSchema: z.object({
|
||||
city: z.string().describe('The city name'),
|
||||
unit: z.enum(['celsius', 'fahrenheit']).optional().describe('Temperature unit'),
|
||||
}),
|
||||
execute: async ({ city, unit = 'celsius' }) => {
|
||||
// Mock weather data
|
||||
const weather = {
|
||||
city,
|
||||
temperature: unit === 'celsius' ? 22 : 72,
|
||||
unit,
|
||||
condition: 'sunny',
|
||||
humidity: 45,
|
||||
};
|
||||
return weather;
|
||||
},
|
||||
}),
|
||||
|
||||
get_time: tool({
|
||||
description: 'Get the current time in a timezone',
|
||||
inputSchema: z.object({
|
||||
timezone: z.string().describe('Timezone like "America/New_York"'),
|
||||
}),
|
||||
execute: async ({ timezone }) => {
|
||||
const time = new Date().toLocaleString('en-US', { timeZone: timezone });
|
||||
return { timezone, time };
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
async function main() {
|
||||
console.log('Asking about weather with tool calling...\n');
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-4o-mini'),
|
||||
messages: [
|
||||
{ role: 'user', content: 'What is the weather in Paris and what time is it in New York?' }
|
||||
],
|
||||
tools,
|
||||
maxSteps: 5, // Allow multiple tool calls
|
||||
});
|
||||
|
||||
console.log('Tool Calls:');
|
||||
for (const toolCall of result.toolCalls) {
|
||||
console.log(` - ${toolCall.toolName}:`, toolCall.args);
|
||||
}
|
||||
|
||||
console.log('\nTool Results:');
|
||||
for (const toolResult of result.toolResults) {
|
||||
console.log(` - ${toolResult.toolName}:`, toolResult.result);
|
||||
}
|
||||
|
||||
console.log('\nFinal Response:', result.text);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* UI Message Example (AI SDK v6 Parity)
|
||||
*
|
||||
* Demonstrates UIMessage protocol for Next.js/React integration.
|
||||
*
|
||||
* Run: npx ts-node ui-message.ts
|
||||
* Required: OPENAI_API_KEY
|
||||
*/
|
||||
|
||||
import {
|
||||
createTextMessage,
|
||||
createSystemMessage,
|
||||
convertToModelMessages,
|
||||
convertToUIMessages,
|
||||
validateUIMessages,
|
||||
hasPendingApprovals,
|
||||
type UIMessage,
|
||||
} from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
console.log('=== UI Message Example ===\n');
|
||||
|
||||
// Create UI messages
|
||||
const messages: UIMessage[] = [
|
||||
createSystemMessage('You are a helpful assistant.'),
|
||||
createTextMessage('user', 'What is the capital of France?'),
|
||||
createTextMessage('assistant', 'The capital of France is Paris.'),
|
||||
createTextMessage('user', 'What is its population?'),
|
||||
];
|
||||
|
||||
console.log('1. Created UI Messages:');
|
||||
messages.forEach((msg, i) => {
|
||||
const text = msg.parts.find(p => p.type === 'text');
|
||||
console.log(` [${i}] ${msg.role}: ${text ? (text as any).text.substring(0, 50) : '...'}`);
|
||||
});
|
||||
|
||||
// Validate messages
|
||||
console.log('\n2. Validating messages:');
|
||||
const isValid = validateUIMessages(messages);
|
||||
console.log(` Valid: ${isValid}`);
|
||||
|
||||
// Convert to model messages (for AI SDK)
|
||||
console.log('\n3. Converting to Model Messages:');
|
||||
const modelMessages = await convertToModelMessages(messages);
|
||||
modelMessages.forEach((msg, i) => {
|
||||
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
|
||||
console.log(` [${i}] ${msg.role}: ${content.substring(0, 50)}...`);
|
||||
});
|
||||
|
||||
// Check for pending approvals
|
||||
console.log('\n4. Checking for pending approvals:');
|
||||
const hasPending = messages.some(m => hasPendingApprovals(m));
|
||||
console.log(` Has pending approvals: ${hasPending}`);
|
||||
|
||||
// Convert model messages back to UI messages
|
||||
console.log('\n5. Round-trip conversion:');
|
||||
const uiMessages = convertToUIMessages(modelMessages);
|
||||
console.log(` Converted ${uiMessages.length} messages back to UI format`);
|
||||
|
||||
console.log('\n✅ UI Message demo completed!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Basic Embedding Example
|
||||
*
|
||||
* Demonstrates using Agent.embed() for text embeddings
|
||||
*
|
||||
* Run: npx ts-node examples/js/embeddings/embed-basic.ts
|
||||
*/
|
||||
|
||||
import { Agent } from '../../../src/praisonai-ts/dist/agent/simple';
|
||||
import { embed, embedMany, cosineSimilarity } from '../../../src/praisonai-ts/dist/llm/embeddings';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Basic Embedding Example ===\n');
|
||||
|
||||
// Method 1: Using Agent.embed()
|
||||
console.log('1. Agent.embed() - Single text');
|
||||
const agent = new Agent({
|
||||
instructions: 'You are a helpful assistant',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
verbose: false
|
||||
});
|
||||
|
||||
const singleEmbedding = await agent.embed('Hello world');
|
||||
console.log(` Dimensions: ${(singleEmbedding as number[]).length}`);
|
||||
console.log(` First 5 values: [${(singleEmbedding as number[]).slice(0, 5).map(v => v.toFixed(4)).join(', ')}...]`);
|
||||
|
||||
// Method 2: Using Agent.embed() with multiple texts
|
||||
console.log('\n2. Agent.embed() - Multiple texts');
|
||||
const multiEmbeddings = await agent.embed(['Hello', 'World', 'How are you?']);
|
||||
console.log(` Count: ${(multiEmbeddings as number[][]).length}`);
|
||||
console.log(` Each has ${(multiEmbeddings as number[][])[0].length} dimensions`);
|
||||
|
||||
// Method 3: Direct embed function
|
||||
console.log('\n3. Direct embed() function');
|
||||
const result = await embed('PraisonAI is an AI agent framework');
|
||||
console.log(` Dimensions: ${result.embedding.length}`);
|
||||
console.log(` Tokens used: ${result.usage?.tokens || 'N/A'}`);
|
||||
|
||||
// Method 4: Batch embeddings
|
||||
console.log('\n4. Batch embedMany() function');
|
||||
const texts = [
|
||||
'Machine learning is a subset of AI',
|
||||
'Deep learning uses neural networks',
|
||||
'Natural language processing handles text'
|
||||
];
|
||||
const batchResult = await embedMany(texts);
|
||||
console.log(` Embedded ${batchResult.embeddings.length} texts`);
|
||||
console.log(` Total tokens: ${batchResult.usage?.tokens || 'N/A'}`);
|
||||
|
||||
// Method 5: Similarity comparison
|
||||
console.log('\n5. Cosine Similarity');
|
||||
const emb1 = await embed('I love programming');
|
||||
const emb2 = await embed('Coding is my passion');
|
||||
const emb3 = await embed('The weather is nice today');
|
||||
|
||||
const sim12 = cosineSimilarity(emb1.embedding, emb2.embedding);
|
||||
const sim13 = cosineSimilarity(emb1.embedding, emb3.embedding);
|
||||
|
||||
console.log(` "I love programming" vs "Coding is my passion": ${(sim12 * 100).toFixed(1)}%`);
|
||||
console.log(` "I love programming" vs "The weather is nice": ${(sim13 * 100).toFixed(1)}%`);
|
||||
|
||||
console.log('\n=== Complete ===');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Document Embedding Example
|
||||
*
|
||||
* Demonstrates chunking and embedding documents for retrieval
|
||||
*
|
||||
* Run: npx ts-node examples/js/embeddings/embed-docs.ts
|
||||
*/
|
||||
|
||||
import { embedMany, cosineSimilarity } from '../../../src/praisonai-ts/dist/llm/embeddings';
|
||||
|
||||
// Sample documents
|
||||
const documents = [
|
||||
{
|
||||
id: 'doc1',
|
||||
title: 'Introduction to AI',
|
||||
content: 'Artificial Intelligence (AI) is the simulation of human intelligence by machines. It includes machine learning, natural language processing, and computer vision.'
|
||||
},
|
||||
{
|
||||
id: 'doc2',
|
||||
title: 'Machine Learning Basics',
|
||||
content: 'Machine learning is a subset of AI that enables systems to learn from data. It includes supervised learning, unsupervised learning, and reinforcement learning.'
|
||||
},
|
||||
{
|
||||
id: 'doc3',
|
||||
title: 'Deep Learning',
|
||||
content: 'Deep learning uses neural networks with multiple layers. It excels at tasks like image recognition, speech processing, and natural language understanding.'
|
||||
},
|
||||
{
|
||||
id: 'doc4',
|
||||
title: 'Natural Language Processing',
|
||||
content: 'NLP enables computers to understand human language. Applications include chatbots, translation, sentiment analysis, and text summarization.'
|
||||
},
|
||||
{
|
||||
id: 'doc5',
|
||||
title: 'Computer Vision',
|
||||
content: 'Computer vision allows machines to interpret visual information. It powers applications like facial recognition, autonomous vehicles, and medical imaging.'
|
||||
}
|
||||
];
|
||||
|
||||
interface EmbeddedDocument {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
embedding: number[];
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('=== Document Embedding Example ===\n');
|
||||
|
||||
// Step 1: Embed all documents
|
||||
console.log('1. Embedding documents...');
|
||||
const contents = documents.map(d => d.content);
|
||||
const { embeddings } = await embedMany(contents);
|
||||
|
||||
const embeddedDocs: EmbeddedDocument[] = documents.map((doc, i) => ({
|
||||
...doc,
|
||||
embedding: embeddings[i]
|
||||
}));
|
||||
|
||||
console.log(` Embedded ${embeddedDocs.length} documents`);
|
||||
console.log(` Embedding dimensions: ${embeddings[0].length}`);
|
||||
|
||||
// Step 2: Search function
|
||||
async function search(query: string, topK: number = 3): Promise<Array<{ doc: EmbeddedDocument; score: number }>> {
|
||||
const { embedding: queryEmbedding } = await (await import('../../../src/praisonai-ts/dist/llm/embeddings')).embed(query);
|
||||
|
||||
const results = embeddedDocs.map(doc => ({
|
||||
doc,
|
||||
score: cosineSimilarity(queryEmbedding, doc.embedding)
|
||||
}));
|
||||
|
||||
return results
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, topK);
|
||||
}
|
||||
|
||||
// Step 3: Test queries
|
||||
console.log('\n2. Testing search queries...\n');
|
||||
|
||||
const queries = [
|
||||
'How do machines learn from data?',
|
||||
'What is image recognition?',
|
||||
'Tell me about chatbots and translation'
|
||||
];
|
||||
|
||||
for (const query of queries) {
|
||||
console.log(`Query: "${query}"`);
|
||||
const results = await search(query);
|
||||
|
||||
console.log('Top results:');
|
||||
results.forEach((r, i) => {
|
||||
console.log(` ${i + 1}. [${(r.score * 100).toFixed(1)}%] ${r.doc.title}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Step 4: Show document similarity matrix
|
||||
console.log('3. Document Similarity Matrix');
|
||||
console.log('');
|
||||
|
||||
// Header
|
||||
process.stdout.write(' ');
|
||||
documents.forEach((_, i) => process.stdout.write(`Doc${i + 1} `));
|
||||
console.log('');
|
||||
|
||||
// Matrix
|
||||
for (let i = 0; i < embeddedDocs.length; i++) {
|
||||
process.stdout.write(`Doc${i + 1} `);
|
||||
for (let j = 0; j < embeddedDocs.length; j++) {
|
||||
const sim = cosineSimilarity(embeddedDocs[i].embedding, embeddedDocs[j].embedding);
|
||||
process.stdout.write(`${(sim * 100).toFixed(0).padStart(3)}% `);
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
console.log('\n=== Complete ===');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Retrieval Example - Simple Vector Search
|
||||
*
|
||||
* Demonstrates building a simple retrieval system with embeddings
|
||||
*
|
||||
* Run: npx ts-node examples/js/embeddings/retrieval.ts
|
||||
*/
|
||||
|
||||
import { embed, embedMany, cosineSimilarity } from '../../../src/praisonai-ts/dist/llm/embeddings';
|
||||
|
||||
interface Document {
|
||||
id: string;
|
||||
content: string;
|
||||
embedding?: number[];
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
document: Document;
|
||||
score: number;
|
||||
}
|
||||
|
||||
// Simple in-memory vector store
|
||||
class SimpleVectorStore {
|
||||
private documents: Document[] = [];
|
||||
|
||||
async add(doc: Document): Promise<void> {
|
||||
const { embedding } = await embed(doc.content);
|
||||
this.documents.push({ ...doc, embedding });
|
||||
}
|
||||
|
||||
async addBatch(docs: Document[]): Promise<void> {
|
||||
const contents = docs.map(d => d.content);
|
||||
const { embeddings } = await embedMany(contents);
|
||||
docs.forEach((doc, i) => {
|
||||
this.documents.push({ ...doc, embedding: embeddings[i] });
|
||||
});
|
||||
}
|
||||
|
||||
async search(query: string, topK: number = 3): Promise<SearchResult[]> {
|
||||
const { embedding: queryEmb } = await embed(query);
|
||||
|
||||
const results = this.documents
|
||||
.filter(doc => doc.embedding)
|
||||
.map(doc => ({
|
||||
document: doc,
|
||||
score: cosineSimilarity(queryEmb, doc.embedding!)
|
||||
}))
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, topK);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
get(id: string): Document | undefined {
|
||||
return this.documents.find(d => d.id === id);
|
||||
}
|
||||
|
||||
delete(id: string): boolean {
|
||||
const idx = this.documents.findIndex(d => d.id === id);
|
||||
if (idx >= 0) {
|
||||
this.documents.splice(idx, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('=== Simple Vector Retrieval ===\n');
|
||||
|
||||
// Create vector store
|
||||
console.log('1. Creating vector store...');
|
||||
const store = new SimpleVectorStore();
|
||||
|
||||
// Add documents
|
||||
console.log('2. Adding documents...');
|
||||
const docs: Document[] = [
|
||||
{ id: '1', content: 'PraisonAI is an AI agent framework for building intelligent applications.' },
|
||||
{ id: '2', content: 'Agents can use tools to interact with external systems and APIs.' },
|
||||
{ id: '3', content: 'Workflows allow orchestrating multiple agents in sequence or parallel.' },
|
||||
{ id: '4', content: 'Memory systems help agents maintain context across conversations.' },
|
||||
{ id: '5', content: 'The AI SDK provides a unified interface for multiple LLM providers.' },
|
||||
{ id: '6', content: 'Embeddings enable semantic search and similarity matching.' },
|
||||
{ id: '7', content: 'Guardrails ensure agent outputs meet safety and quality standards.' },
|
||||
{ id: '8', content: 'Sessions persist conversation state for multi-turn interactions.' }
|
||||
];
|
||||
|
||||
await store.addBatch(docs);
|
||||
console.log(` Added ${docs.length} documents`);
|
||||
|
||||
// Search queries
|
||||
console.log('\n3. Running search queries...\n');
|
||||
|
||||
const queries = [
|
||||
'How do I build an AI application?',
|
||||
'Can agents call external APIs?',
|
||||
'How to maintain conversation context?',
|
||||
'What providers are supported?'
|
||||
];
|
||||
|
||||
for (const query of queries) {
|
||||
console.log(`Query: "${query}"`);
|
||||
const results = await store.search(query);
|
||||
|
||||
if (results.length > 0) {
|
||||
console.log('Results:');
|
||||
results.forEach((r, i) => {
|
||||
console.log(` ${i + 1}. [${(r.score * 100).toFixed(1)}%] ${r.document.content.substring(0, 60)}...`);
|
||||
});
|
||||
} else {
|
||||
console.log(' No results found');
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Get specific document
|
||||
console.log('4. Get document by ID...');
|
||||
const doc = store.get('5');
|
||||
if (doc) {
|
||||
console.log(` Found: ${doc.content}`);
|
||||
}
|
||||
|
||||
// Delete document
|
||||
console.log('\n5. Delete document...');
|
||||
const deleted = store.delete('8');
|
||||
console.log(` Deleted doc 8: ${deleted}`);
|
||||
|
||||
console.log('\n=== Complete ===');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* MCP Filesystem Server Example
|
||||
*
|
||||
* Demonstrates using MCP to connect to a filesystem server.
|
||||
*
|
||||
* Prerequisites:
|
||||
* npm install praisonai-ts
|
||||
* export OPENAI_API_KEY=your-api-key
|
||||
*
|
||||
* Run:
|
||||
* npx ts-node filesystem-mcp.ts
|
||||
*/
|
||||
|
||||
import { Agent, createMCP } from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
console.log('=== MCP Filesystem Server Example ===\n');
|
||||
|
||||
// Connect to MCP filesystem server
|
||||
const mcp = await createMCP({
|
||||
transport: {
|
||||
type: 'stdio',
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-filesystem', '.'],
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Connected to MCP filesystem server\n');
|
||||
|
||||
// Get tools from MCP server
|
||||
const tools = await mcp.tools();
|
||||
console.log('Available tools:', Object.keys(tools));
|
||||
|
||||
// Create agent with MCP tools
|
||||
const agent = new Agent({
|
||||
name: 'FileAgent',
|
||||
instructions: 'You help manage files using the filesystem tools.',
|
||||
tools: Object.values(tools),
|
||||
});
|
||||
|
||||
// Ask the agent to list files
|
||||
const response = await agent.chat('List all TypeScript files in the current directory');
|
||||
console.log('\nResponse:', response);
|
||||
|
||||
// Cleanup
|
||||
await mcp.close();
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Multimodal Image Analysis Example
|
||||
*
|
||||
* Demonstrates analyzing images with a vision-capable model.
|
||||
*
|
||||
* Prerequisites:
|
||||
* npm install praisonai-ts
|
||||
* export OPENAI_API_KEY=your-api-key
|
||||
*
|
||||
* Run:
|
||||
* npx ts-node image-analysis.ts
|
||||
*/
|
||||
|
||||
import { Agent } from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Multimodal Image Analysis Example ===\n');
|
||||
|
||||
// Create an agent with a vision-capable model
|
||||
const agent = new Agent({
|
||||
name: 'VisionAgent',
|
||||
llm: 'gpt-4o', // Vision-capable model
|
||||
instructions: 'You analyze images and describe what you see in detail.',
|
||||
});
|
||||
|
||||
// For multimodal, we describe the image in the prompt
|
||||
// The agent will use its vision capabilities
|
||||
const imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/300px-PNG_transparency_demonstration_1.png';
|
||||
|
||||
console.log('Analyzing image...\n');
|
||||
console.log('Image URL:', imageUrl, '\n');
|
||||
|
||||
// Ask the agent to analyze (in production, use multimodal message format)
|
||||
const response = await agent.chat(
|
||||
`Please analyze this image: ${imageUrl}\nDescribe what you see in detail.`
|
||||
);
|
||||
|
||||
console.log('Analysis:', response);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,50 @@
|
||||
# Observability Examples
|
||||
|
||||
Examples demonstrating how to use observability integrations with PraisonAI TypeScript.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
cd /path/to/praisonai-ts
|
||||
pnpm install
|
||||
|
||||
# Set API keys for observability tools
|
||||
export LANGFUSE_SECRET_KEY=sk-...
|
||||
export LANGFUSE_PUBLIC_KEY=pk-...
|
||||
# Or use built-in tools (no API key needed)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `basic-tracing.ts` | Basic tracing with memory adapter |
|
||||
| `langfuse-integration.ts` | Langfuse observability integration |
|
||||
| `multi-agent-attribution.ts` | Track agent_id/run_id across agents |
|
||||
| `switchable-tools.ts` | Switch between observability tools via env |
|
||||
|
||||
## Running Examples
|
||||
|
||||
```bash
|
||||
# Run with ts-node or tsx
|
||||
npx tsx examples/js/observability/basic-tracing.ts
|
||||
npx tsx examples/js/observability/multi-agent-attribution.ts
|
||||
|
||||
# With Langfuse (requires API keys)
|
||||
LANGFUSE_SECRET_KEY=sk-... npx tsx examples/js/observability/langfuse-integration.ts
|
||||
```
|
||||
|
||||
## Supported Tools
|
||||
|
||||
| Tool | Env Variable | Package |
|
||||
|------|--------------|---------|
|
||||
| Langfuse | `LANGFUSE_SECRET_KEY` | `langfuse` |
|
||||
| LangSmith | `LANGCHAIN_API_KEY` | `langsmith` |
|
||||
| LangWatch | `LANGWATCH_API_KEY` | `langwatch` |
|
||||
| Arize | `ARIZE_API_KEY` | `arize-phoenix` |
|
||||
| Axiom | `AXIOM_TOKEN` | `@axiomhq/js` |
|
||||
| Braintrust | `BRAINTRUST_API_KEY` | `braintrust` |
|
||||
| Helicone | `HELICONE_API_KEY` | `@helicone/helicone` |
|
||||
| Console | (none) | built-in |
|
||||
| Memory | (none) | built-in |
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Basic Tracing Example
|
||||
*
|
||||
* Demonstrates basic observability with the built-in memory adapter.
|
||||
* No external API keys required.
|
||||
*/
|
||||
|
||||
import { Agent, MemoryObservabilityAdapter, setObservabilityAdapter } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create memory adapter (stores traces in memory)
|
||||
const obs = new MemoryObservabilityAdapter();
|
||||
setObservabilityAdapter(obs);
|
||||
|
||||
// Create agent with observability enabled
|
||||
const agent = new Agent({
|
||||
name: 'TracedAgent',
|
||||
instructions: 'You are a helpful assistant. Answer questions concisely.'
|
||||
});
|
||||
|
||||
console.log('Running agent with tracing...\n');
|
||||
|
||||
// Run some interactions
|
||||
await agent.chat('What is 2 + 2?');
|
||||
await agent.chat('What is the capital of France?');
|
||||
|
||||
// View traces
|
||||
const traces = obs.getAllTraces();
|
||||
|
||||
console.log('\n=== Trace Summary ===');
|
||||
console.log(`Total traces: ${traces.length}`);
|
||||
|
||||
for (const trace of traces) {
|
||||
console.log(`\nTrace: ${trace.name}`);
|
||||
console.log(` Status: ${trace.status}`);
|
||||
console.log(` Spans: ${trace.spans.length}`);
|
||||
|
||||
for (const span of trace.spans) {
|
||||
const duration = span.endTime ? span.endTime - span.startTime : 'running';
|
||||
console.log(` - ${span.kind}: ${span.name} (${duration}ms)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Multi-Agent Attribution Example
|
||||
*
|
||||
* Demonstrates tracking agent_id, run_id, and trace_id across
|
||||
* multi-agent workflows for proper attribution.
|
||||
*/
|
||||
|
||||
import { Agent, Agents, MemoryObservabilityAdapter, setObservabilityAdapter } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create memory adapter
|
||||
const obs = new MemoryObservabilityAdapter();
|
||||
setObservabilityAdapter(obs);
|
||||
|
||||
// Create agents
|
||||
const researcher = new Agent({
|
||||
name: 'Researcher',
|
||||
instructions: 'Research topics and gather information.'
|
||||
});
|
||||
|
||||
const writer = new Agent({
|
||||
name: 'Writer',
|
||||
instructions: 'Write clear summaries based on research.'
|
||||
});
|
||||
|
||||
// Create multi-agent workflow
|
||||
const agents = new Agents({
|
||||
agents: [researcher, writer],
|
||||
tasks: [
|
||||
{ agent: researcher, description: 'Research: {topic}' },
|
||||
{ agent: writer, description: 'Summarize the research findings' }
|
||||
]
|
||||
});
|
||||
|
||||
console.log('Running multi-agent workflow with attribution...\n');
|
||||
|
||||
await agents.start({ topic: 'TypeScript best practices' });
|
||||
|
||||
// View traces with attribution
|
||||
const traces = obs.getAllTraces();
|
||||
|
||||
console.log('\n=== Attribution Report ===');
|
||||
for (const trace of traces) {
|
||||
console.log(`\nTrace ID: ${trace.id}`);
|
||||
console.log(` Name: ${trace.name}`);
|
||||
|
||||
if (trace.attribution) {
|
||||
console.log(` Agent ID: ${trace.attribution.agentId || 'N/A'}`);
|
||||
console.log(` Run ID: ${trace.attribution.runId || 'N/A'}`);
|
||||
console.log(` Session ID: ${trace.attribution.sessionId || 'N/A'}`);
|
||||
}
|
||||
|
||||
console.log(` Spans: ${trace.spans.length}`);
|
||||
for (const span of trace.spans) {
|
||||
console.log(` - [${span.kind}] ${span.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Switchable Observability Tools Example
|
||||
*
|
||||
* Demonstrates switching between observability tools via environment variable.
|
||||
* Set OBS_TOOL env var to: langfuse, langsmith, console, memory, noop
|
||||
*
|
||||
* Usage:
|
||||
* OBS_TOOL=console npx tsx switchable-tools.ts
|
||||
* OBS_TOOL=langfuse LANGFUSE_SECRET_KEY=sk-... npx tsx switchable-tools.ts
|
||||
*/
|
||||
|
||||
import { Agent, createObservabilityAdapter, setObservabilityAdapter } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Get tool from environment (default to memory)
|
||||
const toolName = process.env.OBS_TOOL || 'memory';
|
||||
|
||||
console.log(`Using observability tool: ${toolName}\n`);
|
||||
|
||||
// Create adapter dynamically
|
||||
const obs = await createObservabilityAdapter(toolName as any);
|
||||
setObservabilityAdapter(obs);
|
||||
|
||||
// Create agent
|
||||
const agent = new Agent({
|
||||
name: 'ObservedAgent',
|
||||
instructions: 'You are a helpful assistant.'
|
||||
});
|
||||
|
||||
// Run interaction
|
||||
console.log('Running agent...\n');
|
||||
const response = await agent.chat('Hello! What is 2 + 2?');
|
||||
console.log('Response:', response);
|
||||
|
||||
// Flush traces to backend
|
||||
await obs.flush();
|
||||
console.log('\nTraces flushed to', toolName);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,47 @@
|
||||
# Provider Examples
|
||||
|
||||
Examples demonstrating how to use different LLM providers with PraisonAI TypeScript.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
cd /path/to/praisonai-ts
|
||||
pnpm install
|
||||
|
||||
# Set API keys
|
||||
export OPENAI_API_KEY=sk-...
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
export GOOGLE_API_KEY=AIza...
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `multi-provider.ts` | Use multiple providers in one workflow |
|
||||
| `openai-compatible.ts` | Use OpenAI-compatible APIs (LM Studio, etc.) |
|
||||
| `local-ollama.ts` | Use local Ollama models |
|
||||
| `provider-selection.ts` | Dynamic provider selection based on task |
|
||||
|
||||
## Running Examples
|
||||
|
||||
```bash
|
||||
# Run with ts-node or tsx
|
||||
npx tsx examples/js/providers/multi-provider.ts
|
||||
npx tsx examples/js/providers/openai-compatible.ts
|
||||
npx tsx examples/js/providers/local-ollama.ts
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Provider | Variable | Example |
|
||||
|----------|----------|---------|
|
||||
| OpenAI | `OPENAI_API_KEY` | `sk-...` |
|
||||
| Anthropic | `ANTHROPIC_API_KEY` | `sk-ant-...` |
|
||||
| Google | `GOOGLE_API_KEY` | `AIza...` |
|
||||
| xAI | `XAI_API_KEY` | `xai-...` |
|
||||
| Groq | `GROQ_API_KEY` | `gsk_...` |
|
||||
| Mistral | `MISTRAL_API_KEY` | `...` |
|
||||
| DeepSeek | `DEEPSEEK_API_KEY` | `...` |
|
||||
| Ollama | `OLLAMA_BASE_URL` | `http://localhost:11434` |
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Local Ollama Provider Example
|
||||
*
|
||||
* Demonstrates using local Ollama models with PraisonAI.
|
||||
*
|
||||
* Setup:
|
||||
* 1. Install Ollama: https://ollama.ai
|
||||
* 2. Pull a model: ollama pull llama3.2
|
||||
* 3. Run this example
|
||||
*
|
||||
* Optional env var:
|
||||
* - OLLAMA_BASE_URL (default: http://localhost:11434)
|
||||
*/
|
||||
|
||||
import { Agent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Ollama with llama3.2
|
||||
const ollamaAgent = new Agent({
|
||||
name: 'LocalLlama',
|
||||
instructions: 'You are a helpful local AI assistant running on Ollama.',
|
||||
llm: 'ollama/llama3.2',
|
||||
llmConfig: {
|
||||
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Testing Ollama local model...\n');
|
||||
|
||||
try {
|
||||
const response = await ollamaAgent.chat('Hello! What model are you running?');
|
||||
console.log('Response:', response);
|
||||
} catch (error: any) {
|
||||
console.log('Note: Ollama not running or model not pulled.');
|
||||
console.log('Start Ollama and run: ollama pull llama3.2');
|
||||
console.log('Error:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Multi-Provider Example
|
||||
*
|
||||
* Demonstrates using multiple LLM providers in a single workflow.
|
||||
* Each agent uses a different provider optimized for its task.
|
||||
*
|
||||
* Required env vars:
|
||||
* - OPENAI_API_KEY
|
||||
* - ANTHROPIC_API_KEY
|
||||
* - GOOGLE_API_KEY
|
||||
*/
|
||||
|
||||
import { Agent, Agents } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Fast model for quick categorization
|
||||
const triageAgent = new Agent({
|
||||
name: 'Triage',
|
||||
instructions: 'Quickly categorize the user request into: technical, creative, or general.',
|
||||
llm: 'openai/gpt-4o-mini' // Fast and cheap
|
||||
});
|
||||
|
||||
// Powerful model for complex reasoning
|
||||
const analysisAgent = new Agent({
|
||||
name: 'Analyst',
|
||||
instructions: 'Analyze the request in depth and provide detailed insights.',
|
||||
llm: 'anthropic/claude-3-5-sonnet' // Best reasoning
|
||||
});
|
||||
|
||||
// Creative model for content generation
|
||||
const writerAgent = new Agent({
|
||||
name: 'Writer',
|
||||
instructions: 'Write a clear, engaging response based on the analysis.',
|
||||
llm: 'google/gemini-2.0-flash' // Good for creative tasks
|
||||
});
|
||||
|
||||
// Create multi-agent workflow
|
||||
const agents = new Agents({
|
||||
agents: [triageAgent, analysisAgent, writerAgent],
|
||||
tasks: [
|
||||
{ agent: triageAgent, description: 'Categorize: {input}' },
|
||||
{ agent: analysisAgent, description: 'Analyze the categorized request' },
|
||||
{ agent: writerAgent, description: 'Write the final response' }
|
||||
]
|
||||
});
|
||||
|
||||
console.log('Starting multi-provider workflow...\n');
|
||||
|
||||
const result = await agents.start({
|
||||
input: 'Explain how neural networks learn'
|
||||
});
|
||||
|
||||
console.log('\n=== Final Result ===');
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* OpenAI-Compatible Provider Example
|
||||
*
|
||||
* Demonstrates using any OpenAI-compatible API endpoint.
|
||||
* Works with LM Studio, vLLM, LocalAI, and other compatible servers.
|
||||
*
|
||||
* Required env vars:
|
||||
* - OPENAI_COMPATIBLE_BASE_URL (e.g., http://localhost:1234/v1)
|
||||
* - OPENAI_COMPATIBLE_API_KEY (optional, use 'not-needed' for local)
|
||||
*/
|
||||
|
||||
import { Agent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// LM Studio example (local)
|
||||
const lmStudioAgent = new Agent({
|
||||
name: 'LocalAssistant',
|
||||
instructions: 'You are a helpful local AI assistant.',
|
||||
llm: 'openai-compatible/local-model',
|
||||
llmConfig: {
|
||||
baseUrl: process.env.LM_STUDIO_BASE_URL || 'http://localhost:1234/v1',
|
||||
apiKey: 'not-needed'
|
||||
}
|
||||
});
|
||||
|
||||
// Custom OpenAI-compatible endpoint
|
||||
const customAgent = new Agent({
|
||||
name: 'CustomAssistant',
|
||||
instructions: 'You are a helpful assistant.',
|
||||
llm: 'openai-compatible/my-model',
|
||||
llmConfig: {
|
||||
baseUrl: process.env.OPENAI_COMPATIBLE_BASE_URL || 'http://localhost:8000/v1',
|
||||
apiKey: process.env.OPENAI_COMPATIBLE_API_KEY || 'not-needed'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Testing OpenAI-compatible endpoint...\n');
|
||||
|
||||
try {
|
||||
const response = await lmStudioAgent.chat('Hello! What model are you?');
|
||||
console.log('Response:', response);
|
||||
} catch (error: any) {
|
||||
console.log('Note: LM Studio not running. Start it with a model loaded.');
|
||||
console.log('Error:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Basic RAG Agent Example
|
||||
*
|
||||
* Demonstrates building a RAG agent with context injection.
|
||||
*
|
||||
* Prerequisites:
|
||||
* npm install praisonai-ts
|
||||
* export OPENAI_API_KEY=your-api-key
|
||||
*
|
||||
* Run:
|
||||
* npx ts-node basic-rag.ts
|
||||
*/
|
||||
|
||||
import { Agent } from '../../../src/praisonai-ts/src';
|
||||
|
||||
// Simulated knowledge base (in production, use a vector store)
|
||||
const knowledgeBase = [
|
||||
{ id: '1', content: 'PraisonAI is an AI agent framework for building intelligent applications.' },
|
||||
{ id: '2', content: 'Agents can use tools to accomplish tasks like web search and calculations.' },
|
||||
{ id: '3', content: 'RAG combines retrieval with generation for accurate, grounded responses.' },
|
||||
];
|
||||
|
||||
// Simple search function (in production, use vector similarity)
|
||||
function searchKnowledge(query: string): string[] {
|
||||
const queryLower = query.toLowerCase();
|
||||
return knowledgeBase
|
||||
.filter(doc => doc.content.toLowerCase().includes(queryLower.split(' ')[0]))
|
||||
.map(doc => doc.content);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('=== Basic RAG Agent Example ===\n');
|
||||
|
||||
// Create an agent with RAG-style instructions
|
||||
const agent = new Agent({
|
||||
name: 'RAGAgent',
|
||||
instructions: `You are a helpful assistant that answers questions using provided context.
|
||||
Always base your answers on the context provided.
|
||||
If the context doesn't contain relevant information, say so.`,
|
||||
});
|
||||
|
||||
// Query with context injection
|
||||
const query = 'What is PraisonAI?';
|
||||
console.log(`Q: ${query}`);
|
||||
|
||||
// Retrieve relevant context
|
||||
const context = searchKnowledge(query).join('\n\n');
|
||||
|
||||
// Ask the agent with context
|
||||
const response = await agent.chat(
|
||||
`Context:\n${context}\n\nQuestion: ${query}`
|
||||
);
|
||||
|
||||
console.log(`A: ${response}\n`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Express Server Adapter Example
|
||||
*
|
||||
* Demonstrates deploying an AI agent as an Express HTTP server.
|
||||
*
|
||||
* Prerequisites:
|
||||
* npm install praisonai-ts express
|
||||
* export OPENAI_API_KEY=your-api-key
|
||||
*
|
||||
* Run:
|
||||
* npx ts-node express-server.ts
|
||||
*
|
||||
* Test:
|
||||
* curl -X POST http://localhost:3000/api/chat \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -d '{"message": "Hello!"}'
|
||||
*/
|
||||
|
||||
import { Agent } from '../../../src/praisonai-ts/src';
|
||||
|
||||
// Simple HTTP server without Express dependency for this example
|
||||
import http from 'http';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Express Server Adapter Example ===\n');
|
||||
|
||||
// Create an agent
|
||||
const agent = new Agent({
|
||||
name: 'APIAgent',
|
||||
instructions: 'You are a helpful API assistant. Keep responses concise.',
|
||||
});
|
||||
|
||||
// Create HTTP server
|
||||
const server = http.createServer(async (req, res) => {
|
||||
// CORS headers
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url === '/api/chat') {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', async () => {
|
||||
try {
|
||||
const { message } = JSON.parse(body);
|
||||
const response = await agent.chat(message);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ response }));
|
||||
} catch (error: any) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('Not Found');
|
||||
}
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
console.log('\nTest with:');
|
||||
console.log(` curl -X POST http://localhost:${PORT}/api/chat \\`);
|
||||
console.log(' -H "Content-Type: application/json" \\');
|
||||
console.log(' -d \'{"message": "Hello!"}\'');
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
LSP Code Intelligence Example
|
||||
|
||||
This example demonstrates how to use LSP-powered tools through an Agent
|
||||
for semantic code analysis (listing symbols, finding definitions, etc.).
|
||||
|
||||
How to run:
|
||||
export OPENAI_API_KEY=your_key
|
||||
python examples/lsp/lsp_code_analysis_example.py
|
||||
|
||||
Prerequisites:
|
||||
pip install praisonai praisonaiagents
|
||||
pip install python-lsp-server # Optional, for full LSP support
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add paths for development
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src/praisonai'))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src/praisonai-agents'))
|
||||
|
||||
|
||||
async def main():
|
||||
print("=" * 60)
|
||||
print("LSP CODE INTELLIGENCE EXAMPLE")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Import after path setup
|
||||
from praisonai.cli.features import (
|
||||
create_agent_centric_tools,
|
||||
InteractiveRuntime,
|
||||
RuntimeConfig
|
||||
)
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Use the praisonaiagents source as workspace for analysis
|
||||
workspace = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'../../src/praisonai-agents/praisonaiagents'
|
||||
)
|
||||
workspace = os.path.abspath(workspace)
|
||||
|
||||
print(f"Workspace: {workspace}")
|
||||
print()
|
||||
|
||||
# 1. Create runtime with LSP enabled
|
||||
print("1. Creating InteractiveRuntime with LSP...")
|
||||
config = RuntimeConfig(
|
||||
workspace=workspace,
|
||||
lsp_enabled=True, # Enable LSP for code intelligence
|
||||
acp_enabled=False, # Disable ACP for this example
|
||||
approval_mode="auto"
|
||||
)
|
||||
runtime = InteractiveRuntime(config)
|
||||
status = await runtime.start()
|
||||
|
||||
print(f" LSP enabled: {status['lsp']['enabled']}")
|
||||
print(f" LSP ready: {runtime.lsp_ready}")
|
||||
print(f" LSP status: {status['lsp']['status']}")
|
||||
if not runtime.lsp_ready:
|
||||
print(" Note: LSP not available, will use regex fallback")
|
||||
print()
|
||||
|
||||
# 2. Create agent-centric tools
|
||||
print("2. Creating agent-centric tools...")
|
||||
tools = create_agent_centric_tools(runtime)
|
||||
|
||||
# Filter to show only LSP tools
|
||||
lsp_tools = [t for t in tools if t.__name__.startswith('lsp_')]
|
||||
print(f" LSP tools available:")
|
||||
for tool in lsp_tools:
|
||||
print(f" - {tool.__name__}")
|
||||
print()
|
||||
|
||||
# 3. Create Agent with LSP-powered tools
|
||||
print("3. Creating Agent with LSP tools...")
|
||||
agent = Agent(
|
||||
name="CodeAnalyzer",
|
||||
instructions="""You are a code analysis assistant.
|
||||
Use lsp_list_symbols to list functions and classes in files.
|
||||
Use lsp_find_definition to find where symbols are defined.
|
||||
Use lsp_find_references to find where symbols are used.
|
||||
Provide clear, organized summaries of code structure.""",
|
||||
tools=tools,
|
||||
verbose=True
|
||||
)
|
||||
print()
|
||||
|
||||
# 4. Test 1: List symbols in a file
|
||||
print("4. TEST 1: List symbols in agent/agent.py")
|
||||
print("-" * 50)
|
||||
result1 = agent.start("List all the main classes and public methods in agent/agent.py. Give me a brief summary.")
|
||||
print("-" * 50)
|
||||
print(f"Summary: {str(result1)[:300]}...")
|
||||
print()
|
||||
|
||||
# 5. Test 2: Find definition of a symbol
|
||||
print("5. TEST 2: Find where 'Agent' class is defined")
|
||||
print("-" * 50)
|
||||
result2 = agent.start("Where is the Agent class defined? Use lsp_find_definition to find it.")
|
||||
print("-" * 50)
|
||||
print(f"Result: {str(result2)[:300]}...")
|
||||
print()
|
||||
|
||||
# 6. Test 3: Analyze main.py
|
||||
print("6. TEST 3: Analyze main.py structure")
|
||||
print("-" * 50)
|
||||
result3 = agent.start("List all the functions in main.py and briefly describe what the file does.")
|
||||
print("-" * 50)
|
||||
print(f"Analysis: {str(result3)[:300]}...")
|
||||
print()
|
||||
|
||||
# Cleanup
|
||||
await runtime.stop()
|
||||
|
||||
print("=" * 60)
|
||||
print("EXAMPLE COMPLETE")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print("Key takeaways:")
|
||||
print("- LSP tools work through the Agent (agentic LSP)")
|
||||
print("- Falls back to regex if LSP server not available")
|
||||
print("- Returns structured data with citations")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
PraisonAI Output UX Examples
|
||||
|
||||
This example demonstrates the various output modes and configurations
|
||||
available in PraisonAI agents.
|
||||
|
||||
Features demonstrated:
|
||||
- verbose=True: Full output with agent info, task, and response panels
|
||||
- verbose=False: Clean output with just the result
|
||||
- Tool call display with consolidated output
|
||||
- Multi-agent output with clear attribution
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, '/Users/praison/praisonai-package/src/praisonai-agents')
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get the current weather for a city.
|
||||
|
||||
Args:
|
||||
city: The name of the city to get weather for
|
||||
|
||||
Returns:
|
||||
Weather information string
|
||||
"""
|
||||
return f"The weather in {city} is sunny and 72°F"
|
||||
|
||||
|
||||
def example_verbose_true():
|
||||
"""Example with verbose=True (default) - shows full output panels."""
|
||||
print("\n" + "=" * 60)
|
||||
print("EXAMPLE 1: verbose=True (Full Output)")
|
||||
print("=" * 60)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. Be concise.",
|
||||
verbose=True # Default - shows Agent Info, Task, and Response panels
|
||||
)
|
||||
result = agent.start("What is the capital of France? Answer in one sentence.")
|
||||
print(f"\nReturned value: {result}")
|
||||
|
||||
|
||||
def example_verbose_false():
|
||||
"""Example with verbose=False - clean output, just the result."""
|
||||
print("\n" + "=" * 60)
|
||||
print("EXAMPLE 2: verbose=False (Clean Output)")
|
||||
print("=" * 60)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. Be concise.",
|
||||
verbose=False # No panels, just returns the result
|
||||
)
|
||||
result = agent.start("What is 2+2? Answer in one word.")
|
||||
print(f"Result: {result}")
|
||||
|
||||
|
||||
def example_with_tools():
|
||||
"""Example showing tool call output."""
|
||||
print("\n" + "=" * 60)
|
||||
print("EXAMPLE 3: Agent with Tools")
|
||||
print("=" * 60)
|
||||
|
||||
agent = Agent(
|
||||
name="Weather Bot",
|
||||
instructions="You help users check the weather. Use the get_weather tool.",
|
||||
tools=[get_weather],
|
||||
verbose=True
|
||||
)
|
||||
result = agent.start("What's the weather in Tokyo?")
|
||||
print(f"\nReturned value: {result}")
|
||||
|
||||
|
||||
def example_multi_agent():
|
||||
"""Example showing multi-agent output with clear attribution."""
|
||||
print("\n" + "=" * 60)
|
||||
print("EXAMPLE 4: Multi-Agent Output")
|
||||
print("=" * 60)
|
||||
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Research Analyst",
|
||||
instructions="You research topics and provide factual information.",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
role="Content Writer",
|
||||
instructions="You write clear summaries based on research.",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
task1 = Task(
|
||||
description="Research what AI is in one sentence",
|
||||
expected_output="A brief factual description",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
description="Write a one-sentence summary about AI",
|
||||
expected_output="A clear summary",
|
||||
agent=writer
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task1, task2],
|
||||
process="sequential",
|
||||
verbose=1
|
||||
)
|
||||
|
||||
result = agents.start()
|
||||
print(f"\nFinal result: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("PRAISONAI OUTPUT UX EXAMPLES")
|
||||
print("=" * 60)
|
||||
|
||||
# Run examples
|
||||
example_verbose_true()
|
||||
example_verbose_false()
|
||||
example_with_tools()
|
||||
example_multi_agent()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ALL EXAMPLES COMPLETE")
|
||||
print("=" * 60)
|
||||
@@ -0,0 +1,197 @@
|
||||
"""
|
||||
Isolated Registry Example - Multi-Agent Safety
|
||||
|
||||
This example demonstrates how to use isolated registries to prevent
|
||||
provider registration collisions in multi-agent scenarios.
|
||||
|
||||
Key concepts:
|
||||
- Each agent/context can have its own registry
|
||||
- No global state mutation
|
||||
- Safe for concurrent/parallel runs
|
||||
- Different agents can use different provider implementations
|
||||
|
||||
Requirements:
|
||||
- pip install praisonai
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, '../../src/praisonai')
|
||||
|
||||
from praisonai.llm import (
|
||||
LLMProviderRegistry,
|
||||
create_llm_provider,
|
||||
register_llm_provider,
|
||||
get_default_llm_registry,
|
||||
_reset_default_registry
|
||||
)
|
||||
|
||||
|
||||
# Define two different provider implementations with the same name
|
||||
class Agent1Provider:
|
||||
"""Provider implementation for Agent 1."""
|
||||
provider_id = "custom"
|
||||
|
||||
def __init__(self, model_id, config=None):
|
||||
self.model_id = model_id
|
||||
self.config = config or {}
|
||||
self.agent_name = "Agent1"
|
||||
|
||||
def generate(self, prompt):
|
||||
return f"[{self.agent_name}] Response to: {prompt}"
|
||||
|
||||
|
||||
class Agent2Provider:
|
||||
"""Provider implementation for Agent 2 - different behavior."""
|
||||
provider_id = "custom"
|
||||
|
||||
def __init__(self, model_id, config=None):
|
||||
self.model_id = model_id
|
||||
self.config = config or {}
|
||||
self.agent_name = "Agent2"
|
||||
|
||||
def generate(self, prompt):
|
||||
return f"[{self.agent_name}] Different response to: {prompt}"
|
||||
|
||||
|
||||
def demonstrate_collision_problem():
|
||||
"""Show what happens with global registry collisions."""
|
||||
print("=== Problem: Global Registry Collision ===\n")
|
||||
|
||||
# Reset to clean state
|
||||
_reset_default_registry()
|
||||
|
||||
# Agent 1 registers "custom" provider
|
||||
register_llm_provider("custom", Agent1Provider)
|
||||
print("Agent 1 registered 'custom' provider")
|
||||
|
||||
# Agent 2 tries to register same name - ERROR!
|
||||
try:
|
||||
register_llm_provider("custom", Agent2Provider)
|
||||
print("Agent 2 registered 'custom' provider")
|
||||
except ValueError as e:
|
||||
print(f"Agent 2 FAILED: {e}")
|
||||
|
||||
print()
|
||||
print("Problem: Both agents want to use 'custom' but with different implementations.")
|
||||
print("Solution: Use isolated registries.\n")
|
||||
|
||||
|
||||
def demonstrate_isolated_solution():
|
||||
"""Show how isolated registries solve the collision problem."""
|
||||
print("=== Solution: Isolated Registries ===\n")
|
||||
|
||||
# Create isolated registries for each agent
|
||||
agent1_registry = LLMProviderRegistry()
|
||||
agent2_registry = LLMProviderRegistry()
|
||||
|
||||
# Each agent registers its own "custom" provider
|
||||
agent1_registry.register("custom", Agent1Provider)
|
||||
agent2_registry.register("custom", Agent2Provider)
|
||||
|
||||
print("Agent 1 registry providers:", agent1_registry.list())
|
||||
print("Agent 2 registry providers:", agent2_registry.list())
|
||||
print()
|
||||
|
||||
# Create providers from each registry
|
||||
provider1 = create_llm_provider("custom/model-v1", registry=agent1_registry)
|
||||
provider2 = create_llm_provider("custom/model-v1", registry=agent2_registry)
|
||||
|
||||
# They have different implementations!
|
||||
print("Provider 1 response:", provider1.generate("Hello"))
|
||||
print("Provider 2 response:", provider2.generate("Hello"))
|
||||
print()
|
||||
|
||||
# Verify isolation
|
||||
print("Verification:")
|
||||
print(f" provider1.agent_name = {provider1.agent_name}")
|
||||
print(f" provider2.agent_name = {provider2.agent_name}")
|
||||
print(f" Same class? {type(provider1) == type(provider2)}")
|
||||
print()
|
||||
|
||||
|
||||
def demonstrate_parallel_agents():
|
||||
"""Simulate parallel agent execution with isolated registries."""
|
||||
print("=== Parallel Agent Simulation ===\n")
|
||||
|
||||
def run_agent(agent_id, registry):
|
||||
"""Simulate an agent run with its own registry."""
|
||||
provider = create_llm_provider("custom/model", registry=registry)
|
||||
result = provider.generate(f"Query from agent {agent_id}")
|
||||
return result
|
||||
|
||||
# Setup registries
|
||||
registries = {}
|
||||
for i in range(3):
|
||||
reg = LLMProviderRegistry()
|
||||
# Each agent gets a slightly different provider
|
||||
class DynamicProvider:
|
||||
provider_id = "custom"
|
||||
def __init__(self, model_id, config=None):
|
||||
self.model_id = model_id
|
||||
self.agent_id = i # Capture agent ID
|
||||
def generate(self, prompt):
|
||||
return f"Agent-{self.agent_id} processed: {prompt}"
|
||||
|
||||
reg.register("custom", DynamicProvider)
|
||||
registries[i] = reg
|
||||
|
||||
# Simulate parallel execution
|
||||
print("Simulating parallel agent runs:")
|
||||
for agent_id, registry in registries.items():
|
||||
result = run_agent(agent_id, registry)
|
||||
print(f" {result}")
|
||||
|
||||
print()
|
||||
print("Each agent used its own isolated registry - no collisions!")
|
||||
print()
|
||||
|
||||
|
||||
def demonstrate_shared_vs_isolated():
|
||||
"""Compare shared (global) vs isolated registry patterns."""
|
||||
print("=== Shared vs Isolated Patterns ===\n")
|
||||
|
||||
# Reset global registry
|
||||
_reset_default_registry()
|
||||
|
||||
# Pattern 1: Shared (global) registry
|
||||
print("Pattern 1: Shared Registry (use for app-wide providers)")
|
||||
register_llm_provider("shared-provider", Agent1Provider)
|
||||
|
||||
# All code sees the same provider
|
||||
global_reg = get_default_llm_registry()
|
||||
print(f" Global registry has 'shared-provider': {global_reg.has('shared-provider')}")
|
||||
|
||||
# Pattern 2: Isolated registry
|
||||
print("\nPattern 2: Isolated Registry (use for per-agent/per-run providers)")
|
||||
isolated = LLMProviderRegistry()
|
||||
isolated.register("isolated-provider", Agent2Provider)
|
||||
|
||||
# Only code with reference to this registry sees it
|
||||
print(f" Isolated registry has 'isolated-provider': {isolated.has('isolated-provider')}")
|
||||
print(f" Global registry has 'isolated-provider': {global_reg.has('isolated-provider')}")
|
||||
|
||||
print()
|
||||
print("Recommendation:")
|
||||
print(" - Use global registry for app-wide, stable providers")
|
||||
print(" - Use isolated registries for per-agent or per-run customization")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Isolated Registry Example - Multi-Agent Safety")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
demonstrate_collision_problem()
|
||||
demonstrate_isolated_solution()
|
||||
demonstrate_parallel_agents()
|
||||
demonstrate_shared_vs_isolated()
|
||||
|
||||
print("=" * 60)
|
||||
print("Example Complete")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,83 @@
|
||||
# PraisonAI Profiling Examples
|
||||
|
||||
This directory contains examples for profiling PraisonAI agent performance.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
pip install praisonai
|
||||
export OPENAI_API_KEY=your_key_here
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### 1. Basic Profiling (`basic_profiling.py`)
|
||||
|
||||
Demonstrates programmatic profiling of a single query:
|
||||
|
||||
```bash
|
||||
python basic_profiling.py
|
||||
```
|
||||
|
||||
### 2. Suite Profiling (`suite_profiling.py`)
|
||||
|
||||
Runs a comprehensive profiling suite with multiple scenarios:
|
||||
|
||||
```bash
|
||||
python suite_profiling.py
|
||||
```
|
||||
|
||||
### 3. Optimization Demo (`optimization_example.py`)
|
||||
|
||||
Demonstrates Tier 0/1/2 performance optimizations:
|
||||
|
||||
```bash
|
||||
python optimization_example.py
|
||||
```
|
||||
|
||||
## CLI Commands
|
||||
|
||||
You can also profile directly from the command line:
|
||||
|
||||
```bash
|
||||
# Profile a query
|
||||
praisonai profile query "What is 2+2?"
|
||||
|
||||
# Profile with file grouping
|
||||
praisonai profile query "Hello" --show-files --limit 20
|
||||
|
||||
# Profile imports
|
||||
praisonai profile imports
|
||||
|
||||
# Profile startup time
|
||||
praisonai profile startup
|
||||
|
||||
# Run comprehensive suite
|
||||
praisonai profile suite --quick
|
||||
|
||||
# Create performance baseline
|
||||
praisonai profile snapshot --baseline
|
||||
|
||||
# Compare against baseline
|
||||
praisonai profile snapshot current --compare
|
||||
|
||||
# Show optimization status
|
||||
praisonai profile optimize --show
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Text output**: Human-readable timing breakdown
|
||||
- **JSON output**: Machine-readable for CI/CD (`--format json`)
|
||||
- **Artifacts**: Binary cProfile data and reports (`--save ./results`)
|
||||
- **Snapshots**: Performance baselines for regression detection
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
Enable opt-in optimizations via environment variables:
|
||||
|
||||
```bash
|
||||
export PRAISONAI_LITE_MODE=1
|
||||
export PRAISONAI_SKIP_TYPE_VALIDATION=1
|
||||
export PRAISONAI_MINIMAL_IMPORTS=1
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Basic Profiling Example for PraisonAI
|
||||
|
||||
This example demonstrates how to use the unified profiler programmatically
|
||||
to measure agent performance.
|
||||
|
||||
Run with:
|
||||
python basic_profiling.py
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# Ensure API key is set
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Please set OPENAI_API_KEY environment variable")
|
||||
exit(1)
|
||||
|
||||
from praisonai.cli.execution import (
|
||||
ExecutionRequest,
|
||||
Profiler,
|
||||
ProfilerConfig,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Run a profiled query and display results."""
|
||||
|
||||
# Configure profiler (Layer 1 = basic profiling with function stats)
|
||||
config = ProfilerConfig(
|
||||
layer=1, # 0=minimal, 1=basic, 2=deep
|
||||
limit=15, # Show top 15 functions
|
||||
show_callers=False, # Set True for caller info (layer 2)
|
||||
show_callees=False, # Set True for callee info (layer 2)
|
||||
)
|
||||
|
||||
# Create execution request
|
||||
request = ExecutionRequest(
|
||||
prompt="What is 2+2?",
|
||||
agent_name="ProfiledAgent",
|
||||
)
|
||||
|
||||
# Run profiled query
|
||||
print("🔬 Running profiled query...")
|
||||
profiler = Profiler(config)
|
||||
result, report = profiler.profile_sync(request)
|
||||
|
||||
# Print formatted report
|
||||
print("\n" + report.to_text())
|
||||
|
||||
# Access timing data programmatically
|
||||
print("\n📊 Timing Summary:")
|
||||
print(f" Imports: {report.timing.imports_ms:>10.2f} ms")
|
||||
print(f" Agent Init: {report.timing.agent_init_ms:>10.2f} ms")
|
||||
print(f" Execution: {report.timing.execution_ms:>10.2f} ms")
|
||||
print(f" Total: {report.timing.total_ms:>10.2f} ms")
|
||||
|
||||
# Show top 5 functions
|
||||
if report.functions:
|
||||
print("\n🔥 Top 5 Functions by Cumulative Time:")
|
||||
for i, func in enumerate(report.functions[:5], 1):
|
||||
print(f" {i}. {func.name}: {func.cumulative_time_ms:.2f}ms")
|
||||
|
||||
# Show response
|
||||
print(f"\n💬 Response: {result.output}")
|
||||
|
||||
# Export to JSON
|
||||
print("\n📄 JSON Export available via: report.to_json()")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
Performance Optimization Example for PraisonAI
|
||||
|
||||
This example demonstrates how to use the Tier 0/1/2 optimization features
|
||||
programmatically.
|
||||
|
||||
Run with:
|
||||
python optimization_example.py
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# Ensure API key is set
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Please set OPENAI_API_KEY environment variable")
|
||||
exit(1)
|
||||
|
||||
|
||||
def demo_provider_cache():
|
||||
"""Demonstrate provider caching (Tier 0)."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Tier 0: Provider Cache Demo")
|
||||
print("=" * 60)
|
||||
|
||||
from praisonai.cli.features.profiler import (
|
||||
get_provider_cache,
|
||||
resolve_model_provider,
|
||||
)
|
||||
|
||||
cache = get_provider_cache()
|
||||
cache.invalidate() # Start fresh
|
||||
|
||||
# Resolve some models
|
||||
models = ['gpt-4o', 'claude-3-opus', 'gemini-pro', 'gpt-4o-mini']
|
||||
|
||||
print("\nResolving models:")
|
||||
for model in models:
|
||||
provider, name = resolve_model_provider(model)
|
||||
print(f" {model} -> {provider}/{name}")
|
||||
|
||||
# Check cache stats
|
||||
stats = cache.get_stats()
|
||||
print("\nCache Stats:")
|
||||
print(f" Hits: {stats['hits']}")
|
||||
print(f" Misses: {stats['misses']}")
|
||||
print(f" Size: {stats['size']}")
|
||||
|
||||
|
||||
def demo_lazy_importer():
|
||||
"""Demonstrate lazy imports (Tier 0)."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Tier 0: Lazy Importer Demo")
|
||||
print("=" * 60)
|
||||
|
||||
from praisonai.cli.features.profiler import LazyImporter
|
||||
import time
|
||||
|
||||
LazyImporter.clear()
|
||||
|
||||
# Time lazy import
|
||||
start = time.perf_counter()
|
||||
json_module = LazyImporter.get('json')
|
||||
first_import = (time.perf_counter() - start) * 1000
|
||||
|
||||
# Second import (cached)
|
||||
start = time.perf_counter()
|
||||
json_module2 = LazyImporter.get('json')
|
||||
cached_import = (time.perf_counter() - start) * 1000
|
||||
|
||||
print(f"\nFirst import: {first_import:.3f}ms")
|
||||
print(f"Cached import: {cached_import:.3f}ms")
|
||||
print(f"Same object: {json_module is json_module2}")
|
||||
|
||||
|
||||
def demo_client_pool():
|
||||
"""Demonstrate client pooling (Tier 1)."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Tier 1: Client Pool Demo")
|
||||
print("=" * 60)
|
||||
|
||||
from praisonai.cli.features.profiler import ClientPool
|
||||
|
||||
pool = ClientPool(max_size=3)
|
||||
|
||||
# Create some mock clients
|
||||
def create_client(name):
|
||||
return {'name': name, 'created': True}
|
||||
|
||||
# Get or create clients
|
||||
client1 = pool.get_or_create('openai', lambda: create_client('openai'))
|
||||
_ = pool.get_or_create('anthropic', lambda: create_client('anthropic'))
|
||||
|
||||
# Reuse existing client
|
||||
client1_again = pool.get_or_create('openai', lambda: create_client('openai_new'))
|
||||
|
||||
print(f"\nPool size: {pool.size()}")
|
||||
print(f"Client reused: {client1 is client1_again}")
|
||||
print(f"Client1 name: {client1['name']}")
|
||||
|
||||
|
||||
def demo_prewarm():
|
||||
"""Demonstrate pre-warming (Tier 1)."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Tier 1: Pre-warming Demo")
|
||||
print("=" * 60)
|
||||
|
||||
from praisonai.cli.features.profiler import PrewarmManager
|
||||
import time
|
||||
|
||||
PrewarmManager.reset()
|
||||
|
||||
print(f"\nPre-warming enabled: {PrewarmManager.is_enabled()}")
|
||||
|
||||
# Enable and prewarm
|
||||
PrewarmManager.enable()
|
||||
print(f"After enable: {PrewarmManager.is_enabled()}")
|
||||
|
||||
# Prewarm OpenAI (runs in background)
|
||||
PrewarmManager.prewarm_provider('openai')
|
||||
print("Pre-warming OpenAI in background...")
|
||||
|
||||
# Wait a bit for background thread
|
||||
time.sleep(0.5)
|
||||
|
||||
print(f"OpenAI prewarmed: {PrewarmManager.is_prewarmed('openai')}")
|
||||
|
||||
PrewarmManager.reset()
|
||||
|
||||
|
||||
def demo_lite_mode():
|
||||
"""Demonstrate lite mode (Tier 2)."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Tier 2: Lite Mode Demo")
|
||||
print("=" * 60)
|
||||
|
||||
from praisonai.cli.features.profiler import (
|
||||
LiteModeConfig,
|
||||
is_lite_mode,
|
||||
)
|
||||
|
||||
# Default config
|
||||
config = LiteModeConfig()
|
||||
print(f"\nDefault lite mode: {config.enabled}")
|
||||
|
||||
# Config from env
|
||||
print("\nTo enable lite mode, set environment variables:")
|
||||
print(" export PRAISONAI_LITE_MODE=1")
|
||||
print(" export PRAISONAI_SKIP_TYPE_VALIDATION=1")
|
||||
|
||||
print(f"\nCurrent is_lite_mode(): {is_lite_mode()}")
|
||||
|
||||
|
||||
def demo_perf_snapshot():
|
||||
"""Demonstrate performance snapshots (Tier 2)."""
|
||||
print("\n" + "=" * 60)
|
||||
print("Tier 2: Performance Snapshot Demo")
|
||||
print("=" * 60)
|
||||
|
||||
from praisonai.cli.features.profiler import (
|
||||
PerfSnapshot,
|
||||
PerfComparison,
|
||||
PerfSnapshotManager,
|
||||
)
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# Create manager with temp directory
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
manager = PerfSnapshotManager(snapshot_dir=Path(tmpdir))
|
||||
|
||||
# Create baseline
|
||||
baseline = PerfSnapshot(
|
||||
timestamp='2025-01-01T00:00:00Z',
|
||||
name='baseline',
|
||||
startup_cold_ms=100.0,
|
||||
startup_warm_ms=80.0,
|
||||
import_time_ms=500.0,
|
||||
query_time_ms=2000.0,
|
||||
first_token_ms=300.0,
|
||||
)
|
||||
manager.save_baseline(baseline)
|
||||
print("\nBaseline saved")
|
||||
|
||||
# Create current snapshot (simulated improvement)
|
||||
current = PerfSnapshot(
|
||||
timestamp='2025-01-02T00:00:00Z',
|
||||
name='current',
|
||||
startup_cold_ms=95.0, # 5% faster
|
||||
startup_warm_ms=78.0,
|
||||
import_time_ms=480.0, # 4% faster
|
||||
query_time_ms=1900.0, # 5% faster
|
||||
first_token_ms=280.0,
|
||||
)
|
||||
|
||||
# Compare
|
||||
comparison = PerfComparison(baseline=baseline, current=current)
|
||||
|
||||
print(f"\nStartup improvement: {-comparison.startup_cold_diff_pct:.1f}%")
|
||||
print(f"Import improvement: {-comparison.import_time_diff_pct:.1f}%")
|
||||
print(f"Query improvement: {-comparison.query_time_diff_pct:.1f}%")
|
||||
print(f"Is regression: {comparison.is_regression()}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all demos."""
|
||||
print("=" * 60)
|
||||
print("PraisonAI Performance Optimization Demo")
|
||||
print("=" * 60)
|
||||
|
||||
demo_provider_cache()
|
||||
demo_lazy_importer()
|
||||
demo_client_pool()
|
||||
demo_prewarm()
|
||||
demo_lite_mode()
|
||||
demo_perf_snapshot()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("All demos complete!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"timestamp": "2025-12-31T18:45:38.187539Z",
|
||||
"metadata": {
|
||||
"python_version": "3.12.11",
|
||||
"platform": "macOS-15.7.4-arm64-arm-64bit",
|
||||
"praisonai_version": "2.9.2",
|
||||
"timestamp": "2025-12-31T18:45:38.226558Z"
|
||||
},
|
||||
"startup": {
|
||||
"cold_ms": 80.9305000002496,
|
||||
"warm_ms": 55.266541996388696
|
||||
},
|
||||
"import_analysis": [
|
||||
{
|
||||
"module": "praisonaiagents",
|
||||
"self_us": 2419,
|
||||
"cumulative_us": 1645390,
|
||||
"self_ms": 2.419,
|
||||
"cumulative_ms": 1645.39
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.workflows",
|
||||
"self_us": 670,
|
||||
"cumulative_us": 1375919,
|
||||
"self_ms": 0.67,
|
||||
"cumulative_ms": 1375.919
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.workflows.yaml_parser",
|
||||
"self_us": 775,
|
||||
"cumulative_us": 1369435,
|
||||
"self_ms": 0.775,
|
||||
"cumulative_ms": 1369.435
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.agent.agent",
|
||||
"self_us": 19,
|
||||
"cumulative_us": 1349174,
|
||||
"self_ms": 0.019,
|
||||
"cumulative_ms": 1349.174
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.agent",
|
||||
"self_us": 537,
|
||||
"cumulative_us": 1349155,
|
||||
"self_ms": 0.537,
|
||||
"cumulative_ms": 1349.155
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.agent.agent",
|
||||
"self_us": 1453,
|
||||
"cumulative_us": 1285299,
|
||||
"self_ms": 1.453,
|
||||
"cumulative_ms": 1285.299
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.llm.openai_client",
|
||||
"self_us": 7887,
|
||||
"cumulative_us": 1123578,
|
||||
"self_ms": 7.887,
|
||||
"cumulative_ms": 1123.578
|
||||
},
|
||||
{
|
||||
"module": "openai",
|
||||
"self_us": 1083,
|
||||
"cumulative_us": 1115692,
|
||||
"self_ms": 1.083,
|
||||
"cumulative_ms": 1115.692
|
||||
},
|
||||
{
|
||||
"module": "openai.types",
|
||||
"self_us": 2606,
|
||||
"cumulative_us": 957990,
|
||||
"self_ms": 2.606,
|
||||
"cumulative_ms": 957.99
|
||||
},
|
||||
{
|
||||
"module": "openai.types.batch",
|
||||
"self_us": 1787,
|
||||
"cumulative_us": 485667,
|
||||
"self_ms": 1.787,
|
||||
"cumulative_ms": 485.667
|
||||
},
|
||||
{
|
||||
"module": "openai._models",
|
||||
"self_us": 13423,
|
||||
"cumulative_us": 450798,
|
||||
"self_ms": 13.423,
|
||||
"cumulative_ms": 450.798
|
||||
},
|
||||
{
|
||||
"module": "openai.types.eval_create_params",
|
||||
"self_us": 644,
|
||||
"cumulative_us": 393275,
|
||||
"self_ms": 0.644,
|
||||
"cumulative_ms": 393.275
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders.grader_inputs_param",
|
||||
"self_us": 17,
|
||||
"cumulative_us": 359515,
|
||||
"self_ms": 0.017,
|
||||
"cumulative_ms": 359.515
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders",
|
||||
"self_us": 392,
|
||||
"cumulative_us": 359498,
|
||||
"self_ms": 0.392,
|
||||
"cumulative_ms": 359.498
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders.multi_grader",
|
||||
"self_us": 3847,
|
||||
"cumulative_us": 351251,
|
||||
"self_ms": 3.847,
|
||||
"cumulative_ms": 351.251
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders.label_model_grader",
|
||||
"self_us": 5450,
|
||||
"cumulative_us": 337291,
|
||||
"self_ms": 5.45,
|
||||
"cumulative_ms": 337.291
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders.grader_inputs",
|
||||
"self_us": 545,
|
||||
"cumulative_us": 331841,
|
||||
"self_ms": 0.545,
|
||||
"cumulative_ms": 331.841
|
||||
},
|
||||
{
|
||||
"module": "openai.types.responses.response_input_text",
|
||||
"self_us": 21,
|
||||
"cumulative_us": 331297,
|
||||
"self_ms": 0.021,
|
||||
"cumulative_ms": 331.297
|
||||
},
|
||||
{
|
||||
"module": "openai.types.responses",
|
||||
"self_us": 1301,
|
||||
"cumulative_us": 331277,
|
||||
"self_ms": 1.301,
|
||||
"cumulative_ms": 331.277
|
||||
},
|
||||
{
|
||||
"module": "rich.logging",
|
||||
"self_us": 1066,
|
||||
"cumulative_us": 238256,
|
||||
"self_ms": 1.066,
|
||||
"cumulative_ms": 238.256
|
||||
}
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "simple_query",
|
||||
"config": {
|
||||
"prompt": "What is 2+2?",
|
||||
"model": "default",
|
||||
"stream": false,
|
||||
"iterations": 2
|
||||
},
|
||||
"import_time_stats": {
|
||||
"mean": 0.008833496394800022,
|
||||
"min": 0.005916997906751931,
|
||||
"max": 0.011749994882848114,
|
||||
"stdev": 0.0041245517164382375,
|
||||
"median": 0.008833496394800022
|
||||
},
|
||||
"total_time_stats": {
|
||||
"mean": 1727.4090415012324,
|
||||
"min": 1367.2264170018025,
|
||||
"max": 2087.5916660006624,
|
||||
"stdev": 509.3751524982296,
|
||||
"median": 1727.4090415012324
|
||||
},
|
||||
"first_token_stats": null,
|
||||
"raw_import_times_ms": [
|
||||
0.011749994882848114,
|
||||
0.005916997906751931
|
||||
],
|
||||
"raw_total_times_ms": [
|
||||
1367.2264170018025,
|
||||
2087.5916660006624
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "simple_stream",
|
||||
"config": {
|
||||
"prompt": "What is 2+2?",
|
||||
"model": "default",
|
||||
"stream": true,
|
||||
"iterations": 2
|
||||
},
|
||||
"import_time_stats": {
|
||||
"mean": 0.008792001608526334,
|
||||
"min": 0.006375004886649549,
|
||||
"max": 0.01120899833040312,
|
||||
"stdev": 0.0034181495442894613,
|
||||
"median": 0.008792001608526334
|
||||
},
|
||||
"total_time_stats": {
|
||||
"mean": 2321.0982290001994,
|
||||
"min": 1697.1089580038097,
|
||||
"max": 2945.087499996589,
|
||||
"stdev": 882.4540898183949,
|
||||
"median": 2321.0982290001994
|
||||
},
|
||||
"first_token_stats": {
|
||||
"mean": 2312.8511880022415,
|
||||
"min": 1681.2440420035273,
|
||||
"max": 2944.458334000956,
|
||||
"stdev": 893.2273919631452,
|
||||
"median": 2312.8511880022415
|
||||
},
|
||||
"raw_import_times_ms": [
|
||||
0.01120899833040312,
|
||||
0.006375004886649549
|
||||
],
|
||||
"raw_total_times_ms": [
|
||||
2945.087499996589,
|
||||
1697.1089580038097
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Profile Suite Example for PraisonAI
|
||||
|
||||
This example demonstrates how to run a comprehensive profiling suite
|
||||
programmatically with multiple scenarios.
|
||||
|
||||
Run with:
|
||||
python suite_profiling.py
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# Ensure API key is set
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Please set OPENAI_API_KEY environment variable")
|
||||
exit(1)
|
||||
|
||||
from praisonai.cli.features.profiler import (
|
||||
ScenarioConfig,
|
||||
run_profile_suite,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Run a profiling suite with custom scenarios."""
|
||||
|
||||
# Define custom scenarios
|
||||
scenarios = [
|
||||
ScenarioConfig(
|
||||
name="simple_query",
|
||||
prompt="What is 2+2?",
|
||||
stream=False,
|
||||
iterations=2,
|
||||
warmup=1,
|
||||
),
|
||||
ScenarioConfig(
|
||||
name="simple_stream",
|
||||
prompt="What is 2+2?",
|
||||
stream=True,
|
||||
iterations=2,
|
||||
warmup=1,
|
||||
),
|
||||
]
|
||||
|
||||
# Run suite with custom output directory
|
||||
print("🔬 Running Profile Suite...")
|
||||
result = run_profile_suite(
|
||||
output_dir="./profile_results",
|
||||
scenarios=scenarios,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
# Print summary
|
||||
print("\n" + "=" * 60)
|
||||
print("Profile Suite Results")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n📊 Startup Times:")
|
||||
print(f" Cold Start: {result.startup_cold_ms:.2f}ms")
|
||||
print(f" Warm Start: {result.startup_warm_ms:.2f}ms")
|
||||
|
||||
if result.import_analysis:
|
||||
print("\n📦 Top Import:")
|
||||
print(f" {result.import_analysis[0]['module']}: {result.import_analysis[0]['cumulative_ms']:.2f}ms")
|
||||
|
||||
print("\n🎯 Scenario Results:")
|
||||
for scenario in result.scenarios:
|
||||
stats = scenario.get_stats(scenario.total_times)
|
||||
print(f" {scenario.name}:")
|
||||
print(f" Mean: {stats['mean']:.2f}ms")
|
||||
print(f" Min: {stats['min']:.2f}ms")
|
||||
print(f" Max: {stats['max']:.2f}ms")
|
||||
|
||||
print("\n✅ Results saved to: ./profile_results/")
|
||||
print(" - suite_results.json (machine-readable)")
|
||||
print(" - suite_report.txt (human-readable)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
Session Persistence Example
|
||||
|
||||
Demonstrates automatic session persistence with zero configuration.
|
||||
When you provide a session_id to an Agent, conversation history is
|
||||
automatically saved to disk and restored on subsequent runs.
|
||||
|
||||
Usage:
|
||||
python session_persistence_example.py
|
||||
|
||||
Requirements:
|
||||
- praisonaiagents package installed
|
||||
- OPENAI_API_KEY environment variable set
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
|
||||
def example_basic_persistence():
|
||||
"""Basic session persistence example."""
|
||||
print("=" * 60)
|
||||
print("EXAMPLE 1: Basic Session Persistence")
|
||||
print("=" * 60)
|
||||
|
||||
session_id = "demo-session-basic"
|
||||
|
||||
# Create agent with session_id
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. Be very brief.",
|
||||
session_id=session_id,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
# First message
|
||||
print("\n[User]: My favorite color is blue and my lucky number is 7.")
|
||||
response1 = agent.chat("My favorite color is blue and my lucky number is 7.")
|
||||
print(f"[Assistant]: {response1}")
|
||||
|
||||
# Second message - agent remembers
|
||||
print("\n[User]: What is my favorite color?")
|
||||
response2 = agent.chat("What is my favorite color?")
|
||||
print(f"[Assistant]: {response2}")
|
||||
|
||||
print(f"\n✅ Session saved to: ~/.praison/sessions/{session_id}.json")
|
||||
print(f" Chat history length: {len(agent.chat_history)} messages")
|
||||
|
||||
|
||||
def example_session_restoration():
|
||||
"""Demonstrates session restoration across Agent instances."""
|
||||
print("\n" + "=" * 60)
|
||||
print("EXAMPLE 2: Session Restoration")
|
||||
print("=" * 60)
|
||||
|
||||
session_id = "demo-session-restore"
|
||||
|
||||
# First agent instance
|
||||
print("\n--- First Agent Instance ---")
|
||||
agent1 = Agent(
|
||||
name="MemoryBot",
|
||||
instructions="You are a helpful assistant. Be very brief.",
|
||||
session_id=session_id,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
print("[User]: Remember that my pet's name is Max.")
|
||||
response1 = agent1.chat("Remember that my pet's name is Max.")
|
||||
print(f"[Assistant]: {response1}")
|
||||
|
||||
# Simulate new process by creating new agent instance
|
||||
print("\n--- New Agent Instance (simulating restart) ---")
|
||||
agent2 = Agent(
|
||||
name="MemoryBot",
|
||||
instructions="You are a helpful assistant. Be very brief.",
|
||||
session_id=session_id,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
print("[User]: What is my pet's name?")
|
||||
response2 = agent2.chat("What is my pet's name?")
|
||||
print(f"[Assistant]: {response2}")
|
||||
|
||||
if "max" in response2.lower():
|
||||
print("\n✅ Session restored successfully! Agent remembered 'Max'.")
|
||||
else:
|
||||
print("\n⚠️ Agent may not have remembered correctly.")
|
||||
|
||||
|
||||
def example_in_memory_only():
|
||||
"""Demonstrates in-memory memory without persistence."""
|
||||
print("\n" + "=" * 60)
|
||||
print("EXAMPLE 3: In-Memory Only (No session_id)")
|
||||
print("=" * 60)
|
||||
|
||||
# Agent without session_id - in-memory only
|
||||
agent = Agent(
|
||||
name="TempBot",
|
||||
instructions="You are a helpful assistant. Be very brief.",
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
print("\n[User]: My name is Alice.")
|
||||
response1 = agent.chat("My name is Alice.")
|
||||
print(f"[Assistant]: {response1}")
|
||||
|
||||
print("\n[User]: What is my name?")
|
||||
response2 = agent.chat("What is my name?")
|
||||
print(f"[Assistant]: {response2}")
|
||||
|
||||
print(f"\n✅ In-memory chat history: {len(agent.chat_history)} messages")
|
||||
print(" (This will be lost when the Agent instance is garbage collected)")
|
||||
|
||||
|
||||
def example_direct_store_access():
|
||||
"""Demonstrates direct access to the session store."""
|
||||
print("\n" + "=" * 60)
|
||||
print("EXAMPLE 4: Direct Session Store Access")
|
||||
print("=" * 60)
|
||||
|
||||
from praisonaiagents.session import get_default_session_store
|
||||
|
||||
store = get_default_session_store()
|
||||
session_id = "demo-direct-access"
|
||||
|
||||
# Add messages directly
|
||||
store.add_user_message(session_id, "Hello from direct access!")
|
||||
store.add_assistant_message(session_id, "Hi! I received your message.")
|
||||
|
||||
# Get history
|
||||
history = store.get_chat_history(session_id)
|
||||
print(f"\n✅ Added {len(history)} messages directly to session store")
|
||||
|
||||
for msg in history:
|
||||
print(f" [{msg['role']}]: {msg['content']}")
|
||||
|
||||
# List all sessions
|
||||
sessions = store.list_sessions()
|
||||
print(f"\n📋 Total sessions in store: {len(sessions)}")
|
||||
|
||||
# Clean up demo session
|
||||
store.delete_session(session_id)
|
||||
print(f" Deleted demo session: {session_id}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all examples."""
|
||||
print("\n" + "=" * 60)
|
||||
print("SESSION PERSISTENCE EXAMPLES")
|
||||
print("=" * 60)
|
||||
|
||||
# Run examples
|
||||
example_basic_persistence()
|
||||
example_session_restoration()
|
||||
example_in_memory_only()
|
||||
example_direct_store_access()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ALL EXAMPLES COMPLETE")
|
||||
print("=" * 60)
|
||||
print("\nSession files are stored in: ~/.praison/sessions/")
|
||||
print("You can inspect them with: cat ~/.praison/sessions/<session_id>.json")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Basic TUI Usage Example for PraisonAI.
|
||||
|
||||
This example demonstrates how to launch the interactive TUI.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# Ensure API key is set
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Please set OPENAI_API_KEY environment variable")
|
||||
exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Launch the TUI."""
|
||||
try:
|
||||
from praisonai.cli.features.tui import run_tui
|
||||
|
||||
# Launch with default settings
|
||||
run_tui()
|
||||
|
||||
except ImportError:
|
||||
print("Textual is required for TUI. Install with:")
|
||||
print(" pip install praisonai[tui]")
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
Headless TUI Simulation Example for PraisonAI.
|
||||
|
||||
This example demonstrates how to run TUI simulations without
|
||||
launching the interactive UI - useful for testing and CI.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from praisonai.cli.features.tui import (
|
||||
TuiOrchestrator,
|
||||
SimulationRunner,
|
||||
MockProvider,
|
||||
)
|
||||
from praisonai.cli.features.tui.orchestrator import OutputMode
|
||||
from praisonai.cli.features.queue import QueueConfig
|
||||
|
||||
|
||||
async def run_simulation():
|
||||
"""Run a headless simulation."""
|
||||
|
||||
# Create orchestrator with mock provider (no real API calls)
|
||||
config = QueueConfig(enable_persistence=False)
|
||||
orchestrator = TuiOrchestrator(
|
||||
queue_config=config,
|
||||
output_mode=OutputMode.PRETTY,
|
||||
debug=True,
|
||||
)
|
||||
|
||||
# Create simulation runner
|
||||
runner = SimulationRunner(orchestrator, assert_mode=True)
|
||||
|
||||
# Define simulation script
|
||||
script = {
|
||||
"session_id": "sim-example",
|
||||
"model": "gpt-4o-mini",
|
||||
"steps": [
|
||||
# Navigate to different screens
|
||||
{"action": "navigate", "args": {"screen": "main"}},
|
||||
{"action": "focus", "args": {"widget": "composer"}},
|
||||
|
||||
# Change model
|
||||
{
|
||||
"action": "model",
|
||||
"args": {"model": "gpt-4"},
|
||||
"expected": {"model": "gpt-4"}
|
||||
},
|
||||
|
||||
# Navigate to queue screen
|
||||
{"action": "navigate", "args": {"screen": "queue"}},
|
||||
|
||||
# Take a snapshot
|
||||
{"action": "snapshot"},
|
||||
|
||||
# Navigate back
|
||||
{"action": "navigate", "args": {"screen": "main"}},
|
||||
|
||||
# Small delay
|
||||
{"action": "sleep", "args": {"seconds": 0.5}},
|
||||
]
|
||||
}
|
||||
|
||||
# Run the simulation
|
||||
print("Running headless TUI simulation...")
|
||||
print("=" * 50)
|
||||
|
||||
success = await runner.run_script(script)
|
||||
|
||||
# Print results
|
||||
print("=" * 50)
|
||||
summary = runner.get_summary()
|
||||
|
||||
print(f"\nSimulation {'PASSED' if success else 'FAILED'}")
|
||||
print(f" Assertions passed: {summary['assertions_passed']}")
|
||||
print(f" Assertions failed: {summary['assertions_failed']}")
|
||||
|
||||
if summary['errors']:
|
||||
print("\nErrors:")
|
||||
for error in summary['errors']:
|
||||
print(f" - {error}")
|
||||
|
||||
return success
|
||||
|
||||
|
||||
async def run_with_events():
|
||||
"""Run simulation and capture all events."""
|
||||
|
||||
config = QueueConfig(enable_persistence=False)
|
||||
orchestrator = TuiOrchestrator(
|
||||
queue_config=config,
|
||||
output_mode=OutputMode.SILENT,
|
||||
)
|
||||
|
||||
# Capture events
|
||||
events = []
|
||||
orchestrator.add_event_callback(lambda e: events.append(e))
|
||||
|
||||
await orchestrator.start(session_id="event-capture")
|
||||
|
||||
# Perform some actions
|
||||
orchestrator.set_model("gpt-4")
|
||||
orchestrator.navigate_screen("queue")
|
||||
orchestrator.set_focus("queue-panel")
|
||||
orchestrator.navigate_screen("main")
|
||||
|
||||
await orchestrator.stop()
|
||||
|
||||
# Print captured events
|
||||
print("\nCaptured Events:")
|
||||
print("-" * 40)
|
||||
for event in events:
|
||||
print(f" {event.event_type.value}")
|
||||
|
||||
return events
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
print("PraisonAI TUI Headless Simulation Example")
|
||||
print("=" * 50)
|
||||
|
||||
# Run simulation
|
||||
success = asyncio.run(run_simulation())
|
||||
|
||||
# Run event capture
|
||||
print("\n" + "=" * 50)
|
||||
events = asyncio.run(run_with_events())
|
||||
|
||||
print(f"\nTotal events captured: {len(events)}")
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
Queue Management Example for PraisonAI.
|
||||
|
||||
This example demonstrates how to use the queue system programmatically.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from praisonai.cli.features.queue import (
|
||||
QueueManager,
|
||||
QueueConfig,
|
||||
QueuedRun,
|
||||
RunState,
|
||||
RunPriority,
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Demonstrate queue management."""
|
||||
|
||||
print("PraisonAI Queue Management Example")
|
||||
print("=" * 50)
|
||||
|
||||
# Create queue configuration
|
||||
config = QueueConfig(
|
||||
max_concurrent_global=2,
|
||||
max_concurrent_per_agent=1,
|
||||
max_queue_size=10,
|
||||
enable_persistence=False, # Use in-memory for demo
|
||||
)
|
||||
|
||||
# Track outputs
|
||||
outputs = {}
|
||||
|
||||
async def on_output(run_id: str, chunk: str):
|
||||
if run_id not in outputs:
|
||||
outputs[run_id] = ""
|
||||
outputs[run_id] += chunk
|
||||
print(f" [{run_id[:8]}] {chunk}", end="", flush=True)
|
||||
|
||||
async def on_complete(run_id: str, run: QueuedRun):
|
||||
print(f"\n [{run_id[:8]}] Completed: {run.state.value}")
|
||||
|
||||
async def on_error(run_id: str, error: Exception):
|
||||
print(f"\n [{run_id[:8]}] Error: {error}")
|
||||
|
||||
# Create queue manager
|
||||
manager = QueueManager(
|
||||
config=config,
|
||||
on_output=on_output,
|
||||
on_complete=on_complete,
|
||||
on_error=on_error,
|
||||
)
|
||||
|
||||
# Start the manager
|
||||
await manager.start(recover=False)
|
||||
print("Queue manager started")
|
||||
|
||||
# Submit some runs with different priorities
|
||||
print("\nSubmitting runs...")
|
||||
|
||||
run1 = await manager.submit(
|
||||
input_content="Low priority task",
|
||||
agent_name="Agent1",
|
||||
priority=RunPriority.LOW,
|
||||
)
|
||||
print(f" Submitted run1: {run1} (LOW priority)")
|
||||
|
||||
run2 = await manager.submit(
|
||||
input_content="High priority task",
|
||||
agent_name="Agent2",
|
||||
priority=RunPriority.HIGH,
|
||||
)
|
||||
print(f" Submitted run2: {run2} (HIGH priority)")
|
||||
|
||||
run3 = await manager.submit(
|
||||
input_content="Normal priority task",
|
||||
agent_name="Agent1",
|
||||
priority=RunPriority.NORMAL,
|
||||
)
|
||||
print(f" Submitted run3: {run3} (NORMAL priority)")
|
||||
|
||||
# Check queue status
|
||||
print(f"\nQueue status:")
|
||||
print(f" Queued: {manager.queued_count}")
|
||||
print(f" Running: {manager.running_count}")
|
||||
|
||||
# List runs
|
||||
runs = manager.list_runs()
|
||||
print(f"\nAll runs ({len(runs)}):")
|
||||
for run in runs:
|
||||
print(f" {run.run_id[:8]}: {run.agent_name} - {run.state.value} ({run.priority.name})")
|
||||
|
||||
# Wait a bit for processing
|
||||
print("\nWaiting for processing...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Cancel a run
|
||||
if runs:
|
||||
cancel_id = runs[-1].run_id
|
||||
print(f"\nCancelling run: {cancel_id[:8]}")
|
||||
await manager.cancel(cancel_id)
|
||||
|
||||
# Get stats
|
||||
stats = manager.get_stats()
|
||||
print(f"\nQueue statistics:")
|
||||
print(f" Total runs: {stats.total_runs}")
|
||||
print(f" Queued: {stats.queued_count}")
|
||||
print(f" Running: {stats.running_count}")
|
||||
print(f" Succeeded: {stats.succeeded_count}")
|
||||
print(f" Failed: {stats.failed_count}")
|
||||
print(f" Cancelled: {stats.cancelled_count}")
|
||||
|
||||
# Stop the manager
|
||||
await manager.stop()
|
||||
print("\nQueue manager stopped")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("Example complete!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Exa Web Search Tool Example
|
||||
*
|
||||
* Demonstrates semantic web search with Exa AI.
|
||||
*
|
||||
* Prerequisites:
|
||||
* npm install praisonai-ts @exalabs/ai-sdk
|
||||
* export EXA_API_KEY=your-api-key
|
||||
* export OPENAI_API_KEY=your-api-key
|
||||
*/
|
||||
|
||||
// For local development, use relative imports
|
||||
// For production, use: import { Agent, exaSearch } from 'praisonai-ts'
|
||||
import { Agent } from '../../../src/praisonai-ts/src';
|
||||
import { exaSearch } from '../../../src/praisonai-ts/src/tools/builtins';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Exa Web Search Tool Example ===\n');
|
||||
|
||||
// Example 1: Basic Semantic Search
|
||||
console.log('1. Basic Semantic Search');
|
||||
console.log('------------------------');
|
||||
|
||||
const searchAgent = new Agent({
|
||||
name: 'SemanticSearcher',
|
||||
instructions: 'You search the web using semantic search and provide detailed summaries.',
|
||||
tools: [exaSearch({ numResults: 5, type: 'auto' })],
|
||||
});
|
||||
|
||||
const searchResult = await searchAgent.chat('Find the latest AI research breakthroughs');
|
||||
console.log('Search Result:', searchResult);
|
||||
console.log();
|
||||
|
||||
// Example 2: Company Research
|
||||
console.log('2. Company Research');
|
||||
console.log('-------------------');
|
||||
|
||||
const companyAgent = new Agent({
|
||||
name: 'CompanyResearcher',
|
||||
instructions: 'You research companies and provide analysis.',
|
||||
tools: [
|
||||
exaSearch({
|
||||
type: 'auto',
|
||||
numResults: 5,
|
||||
category: 'company',
|
||||
contents: {
|
||||
text: { maxCharacters: 1000 },
|
||||
summary: true,
|
||||
livecrawl: 'preferred',
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const companyResult = await companyAgent.chat(
|
||||
'Find AI startups in Europe founded after 2020'
|
||||
);
|
||||
console.log('Company Result:', companyResult);
|
||||
console.log();
|
||||
|
||||
// Example 3: Academic Paper Search
|
||||
console.log('3. Academic Paper Search');
|
||||
console.log('------------------------');
|
||||
|
||||
const academicAgent = new Agent({
|
||||
name: 'AcademicResearcher',
|
||||
instructions: 'You search for academic papers and summarize findings.',
|
||||
tools: [
|
||||
exaSearch({
|
||||
type: 'neural',
|
||||
category: 'research paper',
|
||||
numResults: 5,
|
||||
includeDomains: ['arxiv.org', 'nature.com', 'science.org'],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const academicResult = await academicAgent.chat(
|
||||
'Find recent papers on transformer architectures and attention mechanisms'
|
||||
);
|
||||
console.log('Academic Result:', academicResult);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Tavily Search Tool Example
|
||||
*
|
||||
* Demonstrates web search, content extraction, and crawling with Tavily.
|
||||
*
|
||||
* Prerequisites:
|
||||
* npm install praisonai @tavily/ai-sdk
|
||||
* export TAVILY_API_KEY=your-api-key
|
||||
* export OPENAI_API_KEY=your-api-key
|
||||
*/
|
||||
|
||||
// For local development, use relative imports
|
||||
// For production, use: import { Agent, tavilySearch } from 'praisonai-ts'
|
||||
import { Agent } from '../../../src/praisonai-ts/src';
|
||||
import { tavilySearch, tavilyExtract, tavilyCrawl } from '../../../src/praisonai-ts/src/tools/builtins';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Tavily Search Tool Example ===\n');
|
||||
|
||||
// Example 1: Basic Web Search
|
||||
console.log('1. Basic Web Search');
|
||||
console.log('-------------------');
|
||||
|
||||
const searchAgent = new Agent({
|
||||
name: 'WebSearcher',
|
||||
instructions: 'You search the web for information and provide concise summaries with sources.',
|
||||
tools: [tavilySearch({ maxResults: 5, includeAnswer: true })],
|
||||
});
|
||||
|
||||
const searchResult = await searchAgent.chat('What are the latest developments in AI agents?');
|
||||
console.log('Search Result:', searchResult);
|
||||
console.log();
|
||||
|
||||
// Example 2: News Search with Time Filter
|
||||
console.log('2. News Search with Time Filter');
|
||||
console.log('-------------------------------');
|
||||
|
||||
const newsAgent = new Agent({
|
||||
name: 'NewsSearcher',
|
||||
instructions: 'You search for recent news and summarize key points.',
|
||||
tools: [
|
||||
tavilySearch({
|
||||
topic: 'news',
|
||||
timeRange: 'week',
|
||||
maxResults: 5,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const newsResult = await newsAgent.chat('Latest news about OpenAI');
|
||||
console.log('News Result:', newsResult);
|
||||
console.log();
|
||||
|
||||
// Example 3: Content Extraction
|
||||
console.log('3. Content Extraction');
|
||||
console.log('--------------------');
|
||||
|
||||
const extractAgent = new Agent({
|
||||
name: 'ContentExtractor',
|
||||
instructions: 'You extract and summarize content from web pages.',
|
||||
tools: [tavilyExtract()],
|
||||
});
|
||||
|
||||
const extractResult = await extractAgent.chat(
|
||||
'Extract the main content from https://docs.tavily.com and summarize it'
|
||||
);
|
||||
console.log('Extract Result:', extractResult);
|
||||
console.log();
|
||||
|
||||
// Example 4: Website Crawling
|
||||
console.log('4. Website Crawling');
|
||||
console.log('------------------');
|
||||
|
||||
const crawlAgent = new Agent({
|
||||
name: 'WebCrawler',
|
||||
instructions: 'You crawl websites and analyze their structure and content.',
|
||||
tools: [tavilyCrawl({ maxDepth: 2 })],
|
||||
});
|
||||
|
||||
const crawlResult = await crawlAgent.chat(
|
||||
'Crawl https://docs.tavily.com and list the main documentation sections'
|
||||
);
|
||||
console.log('Crawl Result:', crawlResult);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -687,6 +687,11 @@ Your Goal: {self.goal}
|
||||
self._db = db
|
||||
self._session_id = session_id
|
||||
self._db_initialized = False
|
||||
|
||||
# Session store for JSON-based persistence (lazy - no imports until used)
|
||||
# Used when session_id is provided but no DB adapter
|
||||
self._session_store = None
|
||||
self._session_store_initialized = False
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
@@ -2094,6 +2099,49 @@ Your Goal: {self.goal}"""
|
||||
|
||||
self._db_initialized = True
|
||||
self._current_run_id = None # Track current run
|
||||
|
||||
def _init_session_store(self):
|
||||
"""
|
||||
Initialize session store for JSON-based persistence (lazy, first chat only).
|
||||
|
||||
This is used when session_id is provided but no DB adapter.
|
||||
Enables automatic session persistence with zero configuration.
|
||||
"""
|
||||
if self._session_store_initialized:
|
||||
return
|
||||
|
||||
# Only initialize if session_id is provided and no DB adapter
|
||||
if self._session_id is None or self._db is not None:
|
||||
self._session_store_initialized = True
|
||||
return
|
||||
|
||||
try:
|
||||
from ..session import get_default_session_store
|
||||
self._session_store = get_default_session_store()
|
||||
|
||||
# Restore chat history from previous session
|
||||
history = self._session_store.get_chat_history(self._session_id)
|
||||
if history:
|
||||
# Only restore if chat_history is empty (avoid duplicates)
|
||||
if not self.chat_history:
|
||||
for msg in history:
|
||||
self.chat_history.append({
|
||||
"role": msg["role"],
|
||||
"content": msg["content"]
|
||||
})
|
||||
logging.info(f"Restored session {self._session_id} with {len(history)} messages from JSON store")
|
||||
|
||||
# Set agent info
|
||||
self._session_store.set_agent_info(
|
||||
self._session_id,
|
||||
agent_name=self.name,
|
||||
user_id=self.user_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to initialize session store: {e}")
|
||||
self._session_store = None
|
||||
|
||||
self._session_store_initialized = True
|
||||
|
||||
def _start_run(self, input_content: str):
|
||||
"""Start a new run (turn) for persistence tracking."""
|
||||
@@ -2135,17 +2183,27 @@ Your Goal: {self.goal}"""
|
||||
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:
|
||||
"""Persist a message to the DB or session store."""
|
||||
# Try DB adapter first
|
||||
if self._db is not None:
|
||||
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 to DB: {e}")
|
||||
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}")
|
||||
# Fall back to session store (JSON-based)
|
||||
if self._session_store is not None and self._session_id is not None:
|
||||
try:
|
||||
if role == "user":
|
||||
self._session_store.add_user_message(self._session_id, content)
|
||||
elif role == "assistant":
|
||||
self._session_store.add_assistant_message(self._session_id, content)
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to persist message to session store: {e}")
|
||||
|
||||
@property
|
||||
def session_id(self) -> Optional[str]:
|
||||
@@ -2160,6 +2218,10 @@ Your Goal: {self.goal}"""
|
||||
# Initialize DB session on first chat (lazy)
|
||||
self._init_db_session()
|
||||
|
||||
# Initialize session store for JSON-based persistence (lazy)
|
||||
# This enables automatic session persistence when session_id is provided
|
||||
self._init_session_store()
|
||||
|
||||
# Start a new run for this chat turn
|
||||
prompt_str = prompt if isinstance(prompt, str) else str(prompt)
|
||||
self._start_run(prompt_str)
|
||||
|
||||
@@ -58,6 +58,12 @@ class LLM:
|
||||
# Class-level flag for one-time logging configuration
|
||||
_logging_configured = False
|
||||
|
||||
# Class-level cache for LiteLLM module (avoids repeated import overhead)
|
||||
_litellm_module = None
|
||||
|
||||
# Class-level flag for LiteLLM global settings (set once)
|
||||
_litellm_configured = False
|
||||
|
||||
# Default window sizes for different models (75% of actual to be safe)
|
||||
MODEL_WINDOWS = {
|
||||
# OpenAI
|
||||
@@ -272,14 +278,16 @@ Respond with ONLY a valid JSON tool call in this format:
|
||||
if not LLM._logging_configured:
|
||||
LLM._configure_logging()
|
||||
|
||||
# Import litellm after logging is configured
|
||||
try:
|
||||
import litellm
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"LiteLLM is required but not installed. "
|
||||
"Please install with: pip install 'praisonaiagents[llm]'"
|
||||
)
|
||||
# Use cached LiteLLM module or import once (avoids repeated import overhead)
|
||||
if LLM._litellm_module is None:
|
||||
try:
|
||||
import litellm
|
||||
LLM._litellm_module = litellm
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"LiteLLM is required but not installed. "
|
||||
"Please install with: pip install 'praisonaiagents[llm]'"
|
||||
)
|
||||
|
||||
self.model = model
|
||||
self.timeout = timeout
|
||||
@@ -341,11 +349,12 @@ Respond with ONLY a valid JSON tool call in this format:
|
||||
self._formatted_tools_cache = {}
|
||||
self._max_cache_size = 100
|
||||
|
||||
# Enable error dropping for cleaner output
|
||||
import litellm
|
||||
litellm.drop_params = True
|
||||
# Enable parameter modification for providers like Anthropic
|
||||
litellm.modify_params = True
|
||||
# Configure LiteLLM global settings only once (avoids repeated setup cost)
|
||||
if not LLM._litellm_configured:
|
||||
litellm = LLM._litellm_module
|
||||
litellm.drop_params = True
|
||||
litellm.modify_params = True
|
||||
LLM._litellm_configured = True
|
||||
self._setup_event_tracking(events)
|
||||
|
||||
# Log all initialization parameters when in debug mode or verbose >= 10
|
||||
|
||||
@@ -150,7 +150,10 @@ def _clean_display_content(content: str, max_length: int = 20000) -> str:
|
||||
return content.strip()
|
||||
|
||||
def display_interaction(message, response, markdown=True, generation_time=None, console=None, agent_name=None, agent_role=None, agent_tools=None, task_name=None, task_description=None, task_id=None):
|
||||
"""Synchronous version of display_interaction."""
|
||||
"""Synchronous version of display_interaction.
|
||||
|
||||
Displays the task/message and response in clean panels with response time in title.
|
||||
"""
|
||||
if console is None:
|
||||
console = Console()
|
||||
|
||||
@@ -179,16 +182,18 @@ def display_interaction(message, response, markdown=True, generation_time=None,
|
||||
task_description=task_description,
|
||||
task_id=task_id
|
||||
)
|
||||
# Rest of the display logic...
|
||||
|
||||
# Build response title with time (Agno-style: "Response (1.2s)")
|
||||
response_title = "Response"
|
||||
if generation_time:
|
||||
console.print(Text(f"Response generated in {generation_time:.1f}s", style="dim"))
|
||||
response_title = f"Response ({generation_time:.1f}s)"
|
||||
|
||||
if markdown:
|
||||
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(response), title="Response", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(response), title=response_title, border_style="cyan"))
|
||||
else:
|
||||
console.print(Panel.fit(Text(message, style="bold green"), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Text(response, style="bold blue"), title="Response", border_style="cyan"))
|
||||
console.print(Panel.fit(Text(response, style="bold blue"), title=response_title, border_style="cyan"))
|
||||
|
||||
def display_self_reflection(message: str, console=None):
|
||||
if not message or not message.strip():
|
||||
@@ -226,7 +231,16 @@ def display_instruction(message: str, console=None, agent_name: str = None, agen
|
||||
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
|
||||
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
|
||||
|
||||
def display_tool_call(message: str, console=None):
|
||||
def display_tool_call(message: str, console=None, tool_name: str = None, tool_input: dict = None, tool_output: str = None):
|
||||
"""Display tool call information in a consolidated panel.
|
||||
|
||||
Args:
|
||||
message: The tool call message (legacy format)
|
||||
console: Rich console for output
|
||||
tool_name: Name of the tool being called
|
||||
tool_input: Input arguments to the tool
|
||||
tool_output: Output from the tool (if available)
|
||||
"""
|
||||
logging.debug(f"display_tool_call called with message: {repr(message)}")
|
||||
if not message or not message.strip():
|
||||
logging.debug("Empty message in display_tool_call, returning early")
|
||||
@@ -235,11 +249,21 @@ def display_tool_call(message: str, console=None):
|
||||
logging.debug(f"Cleaned message in display_tool_call: {repr(message)}")
|
||||
|
||||
# Execute synchronous callbacks (always, even when console is None)
|
||||
execute_sync_callback('tool_call', message=message)
|
||||
execute_sync_callback('tool_call', message=message, tool_name=tool_name, tool_input=tool_input, tool_output=tool_output)
|
||||
|
||||
# Only print panel if console is provided (verbose mode)
|
||||
if console is not None:
|
||||
console.print(Panel.fit(Text(message, style="bold cyan"), title="Tool Call", border_style="green"))
|
||||
# Build consolidated tool call display (Agno-style)
|
||||
if tool_name and tool_input is not None:
|
||||
# Format: tool_name(arg1=val1, arg2=val2)
|
||||
args_str = ", ".join(f"{k}={v}" for k, v in tool_input.items()) if tool_input else ""
|
||||
tool_display = f"• {tool_name}({args_str})"
|
||||
if tool_output:
|
||||
tool_display += f"\n → {tool_output}"
|
||||
console.print(Panel.fit(Text(tool_display, style="bold cyan"), title="Tool Call", border_style="green"))
|
||||
else:
|
||||
# Legacy format
|
||||
console.print(Panel.fit(Text(message, style="bold cyan"), title="Tool Call", border_style="green"))
|
||||
|
||||
def display_error(message: str, console=None):
|
||||
if not message or not message.strip():
|
||||
|
||||
@@ -11,8 +11,15 @@ import platform
|
||||
from typing import Any, List, Optional, Callable, Iterable, Union
|
||||
from functools import wraps, partial
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
try:
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
MCP_AVAILABLE = True
|
||||
except ImportError:
|
||||
MCP_AVAILABLE = False
|
||||
ClientSession = None
|
||||
StdioServerParameters = None
|
||||
stdio_client = None
|
||||
|
||||
class MCPToolRunner(threading.Thread):
|
||||
"""A dedicated thread for running MCP operations."""
|
||||
@@ -196,10 +203,17 @@ class MCP:
|
||||
debug: Enable debug logging for MCP operations (default: False)
|
||||
**kwargs: Additional parameters for StdioServerParameters
|
||||
"""
|
||||
# Check if MCP is available
|
||||
if not MCP_AVAILABLE:
|
||||
raise ImportError(
|
||||
"MCP (Model Context Protocol) package is not installed. "
|
||||
"Install it with: pip install praisonaiagents[mcp]"
|
||||
)
|
||||
|
||||
# Handle backward compatibility with named parameter 'command'
|
||||
if command_or_string is None and command is not None:
|
||||
command_or_string = command
|
||||
|
||||
|
||||
# Set up logging - default to WARNING level to hide INFO messages
|
||||
if debug:
|
||||
logging.getLogger("mcp-wrapper").setLevel(logging.DEBUG)
|
||||
|
||||
@@ -16,11 +16,17 @@ import weakref
|
||||
from typing import List, Dict, Any, Optional, Callable, Iterable, Union
|
||||
from urllib.parse import urlparse, urljoin
|
||||
|
||||
from mcp import ClientSession
|
||||
try:
|
||||
from mcp import ClientSession
|
||||
MCP_AVAILABLE = True
|
||||
except ImportError:
|
||||
MCP_AVAILABLE = False
|
||||
ClientSession = None
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
except ImportError:
|
||||
raise ImportError("aiohttp is required for HTTP Stream transport. Install with: pip install praisonaiagents[mcp]")
|
||||
aiohttp = None
|
||||
|
||||
logger = logging.getLogger("mcp-http-stream")
|
||||
|
||||
@@ -507,9 +513,23 @@ class HTTPStreamMCPClient:
|
||||
timeout: Timeout in seconds for operations (default: 60)
|
||||
options: Additional configuration options for the transport
|
||||
"""
|
||||
# Check if MCP is available
|
||||
if not MCP_AVAILABLE:
|
||||
raise ImportError(
|
||||
"MCP (Model Context Protocol) package is not installed. "
|
||||
"Install it with: pip install praisonaiagents[mcp]"
|
||||
)
|
||||
|
||||
# Check if aiohttp is available
|
||||
if aiohttp is None:
|
||||
raise ImportError(
|
||||
"aiohttp is required for HTTP Stream transport. "
|
||||
"Install it with: pip install praisonaiagents[mcp]"
|
||||
)
|
||||
|
||||
# Parse URL to extract base URL and endpoint
|
||||
parsed = urlparse(server_url)
|
||||
|
||||
|
||||
# If the URL already has a path, use it; otherwise use default /mcp endpoint
|
||||
if parsed.path and parsed.path != '/':
|
||||
self.base_url = server_url
|
||||
|
||||
@@ -11,8 +11,14 @@ import inspect
|
||||
import json
|
||||
from typing import List, Dict, Any, Optional, Callable, Iterable
|
||||
|
||||
from mcp import ClientSession
|
||||
from mcp.client.sse import sse_client
|
||||
try:
|
||||
from mcp import ClientSession
|
||||
from mcp.client.sse import sse_client
|
||||
MCP_AVAILABLE = True
|
||||
except ImportError:
|
||||
MCP_AVAILABLE = False
|
||||
ClientSession = None
|
||||
sse_client = None
|
||||
|
||||
logger = logging.getLogger("mcp-sse")
|
||||
|
||||
@@ -169,12 +175,19 @@ class SSEMCPClient:
|
||||
debug: Whether to enable debug logging
|
||||
timeout: Timeout in seconds for operations (default: 60)
|
||||
"""
|
||||
# Check if MCP is available
|
||||
if not MCP_AVAILABLE:
|
||||
raise ImportError(
|
||||
"MCP (Model Context Protocol) package is not installed. "
|
||||
"Install it with: pip install praisonaiagents[mcp]"
|
||||
)
|
||||
|
||||
self.server_url = server_url
|
||||
self.debug = debug
|
||||
self.timeout = timeout
|
||||
self.session = None
|
||||
self.tools = []
|
||||
|
||||
|
||||
# Set up logging
|
||||
if debug:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# Session Persistence for PraisonAI Agents
|
||||
|
||||
This module provides automatic session persistence with zero configuration.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# With session persistence (auto-enabled)
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
session_id="my-session-123"
|
||||
)
|
||||
agent.start("Hello, my name is Alice")
|
||||
|
||||
# Later, in a new process - history is restored automatically
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
session_id="my-session-123"
|
||||
)
|
||||
agent.start("What is my name?") # Agent remembers: "Alice"
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When you provide a `session_id` to an Agent:
|
||||
|
||||
1. **Automatic Persistence**: Conversation history is automatically saved to disk
|
||||
2. **Automatic Restoration**: When a new Agent is created with the same `session_id`, history is restored
|
||||
3. **Zero Configuration**: No database setup required - uses JSON files by default
|
||||
|
||||
### Default Storage Location
|
||||
|
||||
Sessions are stored in: `~/.praison/sessions/{session_id}.json`
|
||||
|
||||
### Session File Format
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "my-session-123",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Hello", "timestamp": 1704153600.0},
|
||||
{"role": "assistant", "content": "Hi there!", "timestamp": 1704153601.5}
|
||||
],
|
||||
"created_at": "2026-01-02T04:00:00+00:00",
|
||||
"updated_at": "2026-01-02T04:01:00+00:00",
|
||||
"agent_name": "Assistant"
|
||||
}
|
||||
```
|
||||
|
||||
## Behavior Matrix
|
||||
|
||||
| Scenario | Behavior |
|
||||
|----------|----------|
|
||||
| `session_id` provided, no DB | JSON persistence (auto) |
|
||||
| `session_id` provided, with DB | DB adapter used |
|
||||
| No `session_id`, same Agent instance | In-memory only |
|
||||
| No `session_id`, new Agent instance | No history |
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Direct Session Store Access
|
||||
|
||||
```python
|
||||
from praisonaiagents.session import get_default_session_store
|
||||
|
||||
store = get_default_session_store()
|
||||
|
||||
# Add messages
|
||||
store.add_user_message("session-123", "Hello")
|
||||
store.add_assistant_message("session-123", "Hi there!")
|
||||
|
||||
# Get history
|
||||
history = store.get_chat_history("session-123")
|
||||
# [{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi there!"}]
|
||||
|
||||
# List all sessions
|
||||
sessions = store.list_sessions()
|
||||
|
||||
# Delete a session
|
||||
store.delete_session("session-123")
|
||||
```
|
||||
|
||||
### Custom Session Directory
|
||||
|
||||
```python
|
||||
from praisonaiagents.session import DefaultSessionStore
|
||||
|
||||
store = DefaultSessionStore(
|
||||
session_dir="/custom/path/sessions",
|
||||
max_messages=200, # Default: 100
|
||||
lock_timeout=10.0, # Default: 5.0 seconds
|
||||
)
|
||||
```
|
||||
|
||||
### Using with DB Adapter
|
||||
|
||||
When a DB adapter is provided, it takes precedence over JSON persistence:
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent
|
||||
from praisonai.db import PostgresAdapter
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
session_id="my-session",
|
||||
db=PostgresAdapter(connection_string="...")
|
||||
)
|
||||
```
|
||||
|
||||
## Multi-Process Safety
|
||||
|
||||
The session store uses file locking to ensure safe concurrent access:
|
||||
|
||||
- **Unix**: Uses `fcntl.flock()` for file locking
|
||||
- **Windows**: Uses `msvcrt.locking()` for file locking
|
||||
- **Atomic Writes**: Uses temp file + rename to prevent corruption
|
||||
|
||||
Multiple processes can safely read/write to the same session.
|
||||
|
||||
## Context Caching
|
||||
|
||||
For cost optimization, use `prompt_caching=True` with Anthropic models:
|
||||
|
||||
```python
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
session_id="my-session",
|
||||
prompt_caching=True, # Enables Anthropic prompt caching
|
||||
)
|
||||
```
|
||||
|
||||
This caches the system prompt, reducing token costs for repeated conversations.
|
||||
|
||||
## API Reference
|
||||
|
||||
### DefaultSessionStore
|
||||
|
||||
```python
|
||||
class DefaultSessionStore:
|
||||
def __init__(
|
||||
self,
|
||||
session_dir: Optional[str] = None, # Default: ~/.praison/sessions/
|
||||
max_messages: int = 100,
|
||||
lock_timeout: float = 5.0,
|
||||
): ...
|
||||
|
||||
def add_message(self, session_id: str, role: str, content: str) -> bool: ...
|
||||
def add_user_message(self, session_id: str, content: str) -> bool: ...
|
||||
def add_assistant_message(self, session_id: str, content: str) -> bool: ...
|
||||
def get_chat_history(self, session_id: str, max_messages: int = None) -> List[Dict]: ...
|
||||
def get_session(self, session_id: str) -> SessionData: ...
|
||||
def clear_session(self, session_id: str) -> bool: ...
|
||||
def delete_session(self, session_id: str) -> bool: ...
|
||||
def list_sessions(self, limit: int = 50) -> List[Dict]: ...
|
||||
def session_exists(self, session_id: str) -> bool: ...
|
||||
```
|
||||
|
||||
### SessionData
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class SessionData:
|
||||
session_id: str
|
||||
messages: List[SessionMessage]
|
||||
created_at: str
|
||||
updated_at: str
|
||||
agent_name: Optional[str]
|
||||
user_id: Optional[str]
|
||||
metadata: Dict[str, Any]
|
||||
|
||||
def get_chat_history(self, max_messages: int = None) -> List[Dict[str, str]]: ...
|
||||
```
|
||||
|
||||
### SessionMessage
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class SessionMessage:
|
||||
role: str # "user", "assistant", "system"
|
||||
content: str
|
||||
timestamp: float
|
||||
metadata: Dict[str, Any]
|
||||
```
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Session persistence module for PraisonAI Agents.
|
||||
|
||||
Provides automatic session persistence with zero configuration.
|
||||
When a session_id is provided to an Agent, conversation history
|
||||
is automatically persisted to disk and restored on subsequent runs.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# With session persistence (auto-enabled)
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
session_id="my-session-123"
|
||||
)
|
||||
agent.start("Hello")
|
||||
|
||||
# Later, new process - history is restored
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
session_id="my-session-123"
|
||||
)
|
||||
agent.start("What did I say before?") # Remembers!
|
||||
|
||||
Default storage: ~/.praison/sessions/{session_id}.json
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .store import DefaultSessionStore, SessionMessage, SessionData
|
||||
|
||||
# Lazy loading for zero import overhead
|
||||
_module_cache = {}
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy load session components."""
|
||||
if name in _module_cache:
|
||||
return _module_cache[name]
|
||||
|
||||
if name == "DefaultSessionStore":
|
||||
from .store import DefaultSessionStore
|
||||
_module_cache[name] = DefaultSessionStore
|
||||
return DefaultSessionStore
|
||||
|
||||
if name == "SessionMessage":
|
||||
from .store import SessionMessage
|
||||
_module_cache[name] = SessionMessage
|
||||
return SessionMessage
|
||||
|
||||
if name == "SessionData":
|
||||
from .store import SessionData
|
||||
_module_cache[name] = SessionData
|
||||
return SessionData
|
||||
|
||||
if name == "get_default_session_store":
|
||||
from .store import get_default_session_store
|
||||
_module_cache[name] = get_default_session_store
|
||||
return get_default_session_store
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DefaultSessionStore",
|
||||
"SessionMessage",
|
||||
"SessionData",
|
||||
"get_default_session_store",
|
||||
]
|
||||
@@ -0,0 +1,521 @@
|
||||
"""
|
||||
Default Session Store for PraisonAI Agents.
|
||||
|
||||
JSON-based session persistence with file locking and atomic writes.
|
||||
Zero dependencies beyond stdlib.
|
||||
"""
|
||||
|
||||
import fcntl
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Default session directory
|
||||
DEFAULT_SESSION_DIR = os.path.expanduser("~/.praison/sessions")
|
||||
|
||||
# Default limits
|
||||
DEFAULT_MAX_MESSAGES = 100
|
||||
DEFAULT_LOCK_TIMEOUT = 5.0 # seconds
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionMessage:
|
||||
"""A single message in a session."""
|
||||
role: str # "user", "assistant", "system"
|
||||
content: str
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"role": self.role,
|
||||
"content": self.content,
|
||||
"timestamp": self.timestamp,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "SessionMessage":
|
||||
"""Create from dictionary."""
|
||||
return cls(
|
||||
role=data.get("role", "user"),
|
||||
content=data.get("content", ""),
|
||||
timestamp=data.get("timestamp", time.time()),
|
||||
metadata=data.get("metadata", {}),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionData:
|
||||
"""Complete session data structure."""
|
||||
session_id: str
|
||||
messages: List[SessionMessage] = field(default_factory=list)
|
||||
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
||||
updated_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
||||
agent_name: Optional[str] = None
|
||||
user_id: Optional[str] = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
"session_id": self.session_id,
|
||||
"messages": [m.to_dict() for m in self.messages],
|
||||
"created_at": self.created_at,
|
||||
"updated_at": self.updated_at,
|
||||
"agent_name": self.agent_name,
|
||||
"user_id": self.user_id,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "SessionData":
|
||||
"""Create from dictionary."""
|
||||
messages = [
|
||||
SessionMessage.from_dict(m)
|
||||
for m in data.get("messages", [])
|
||||
]
|
||||
return cls(
|
||||
session_id=data.get("session_id", ""),
|
||||
messages=messages,
|
||||
created_at=data.get("created_at", datetime.now(timezone.utc).isoformat()),
|
||||
updated_at=data.get("updated_at", datetime.now(timezone.utc).isoformat()),
|
||||
agent_name=data.get("agent_name"),
|
||||
user_id=data.get("user_id"),
|
||||
metadata=data.get("metadata", {}),
|
||||
)
|
||||
|
||||
def get_chat_history(self, max_messages: Optional[int] = None) -> List[Dict[str, str]]:
|
||||
"""
|
||||
Get chat history in LLM-compatible format.
|
||||
|
||||
Returns list of {"role": "user/assistant", "content": "..."} dicts.
|
||||
"""
|
||||
messages = self.messages
|
||||
if max_messages and len(messages) > max_messages:
|
||||
messages = messages[-max_messages:]
|
||||
return [{"role": m.role, "content": m.content} for m in messages]
|
||||
|
||||
|
||||
class FileLock:
|
||||
"""
|
||||
Cross-platform file locking.
|
||||
|
||||
Uses fcntl on Unix and msvcrt on Windows.
|
||||
"""
|
||||
|
||||
def __init__(self, filepath: str, timeout: float = DEFAULT_LOCK_TIMEOUT):
|
||||
self.filepath = filepath
|
||||
self.timeout = timeout
|
||||
self._lock_file = None
|
||||
self._lock_path = filepath + ".lock"
|
||||
|
||||
def __enter__(self):
|
||||
self.acquire()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.release()
|
||||
return False
|
||||
|
||||
def acquire(self) -> bool:
|
||||
"""Acquire the file lock."""
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(self._lock_path) or ".", exist_ok=True)
|
||||
|
||||
start_time = time.time()
|
||||
while True:
|
||||
try:
|
||||
self._lock_file = open(self._lock_path, "w")
|
||||
if sys.platform == "win32":
|
||||
# Windows locking
|
||||
import msvcrt
|
||||
msvcrt.locking(self._lock_file.fileno(), msvcrt.LK_NBLCK, 1)
|
||||
else:
|
||||
# Unix locking
|
||||
fcntl.flock(self._lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
return True
|
||||
except (IOError, OSError, BlockingIOError):
|
||||
if self._lock_file:
|
||||
self._lock_file.close()
|
||||
self._lock_file = None
|
||||
|
||||
if time.time() - start_time > self.timeout:
|
||||
logger.warning(f"Failed to acquire lock for {self.filepath} after {self.timeout}s")
|
||||
return False
|
||||
|
||||
time.sleep(0.05) # Wait 50ms before retry
|
||||
|
||||
def release(self) -> None:
|
||||
"""Release the file lock."""
|
||||
if self._lock_file:
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
import msvcrt
|
||||
msvcrt.locking(self._lock_file.fileno(), msvcrt.LK_UNLCK, 1)
|
||||
else:
|
||||
fcntl.flock(self._lock_file.fileno(), fcntl.LOCK_UN)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
finally:
|
||||
self._lock_file.close()
|
||||
self._lock_file = None
|
||||
# Clean up lock file
|
||||
try:
|
||||
os.remove(self._lock_path)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
|
||||
class DefaultSessionStore:
|
||||
"""
|
||||
JSON-based session persistence with file locking.
|
||||
|
||||
Features:
|
||||
- Zero configuration required
|
||||
- Automatic file locking for multi-process safety
|
||||
- Atomic writes to prevent corruption
|
||||
- Configurable message limits
|
||||
- Thread-safe operations
|
||||
|
||||
Usage:
|
||||
store = DefaultSessionStore()
|
||||
|
||||
# Add messages
|
||||
store.add_message("session-123", "user", "Hello")
|
||||
store.add_message("session-123", "assistant", "Hi there!")
|
||||
|
||||
# Get history
|
||||
history = store.get_chat_history("session-123")
|
||||
# [{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi there!"}]
|
||||
|
||||
# Restore in new process
|
||||
store2 = DefaultSessionStore()
|
||||
history = store2.get_chat_history("session-123") # Same history!
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session_dir: Optional[str] = None,
|
||||
max_messages: int = DEFAULT_MAX_MESSAGES,
|
||||
lock_timeout: float = DEFAULT_LOCK_TIMEOUT,
|
||||
):
|
||||
"""
|
||||
Initialize session store.
|
||||
|
||||
Args:
|
||||
session_dir: Directory for session files. Defaults to ~/.praison/sessions/
|
||||
max_messages: Maximum messages to keep per session.
|
||||
lock_timeout: Timeout for file lock acquisition.
|
||||
"""
|
||||
self.session_dir = session_dir or DEFAULT_SESSION_DIR
|
||||
self.max_messages = max_messages
|
||||
self.lock_timeout = lock_timeout
|
||||
self._lock = threading.RLock()
|
||||
self._cache: Dict[str, SessionData] = {}
|
||||
|
||||
# Ensure session directory exists
|
||||
os.makedirs(self.session_dir, exist_ok=True)
|
||||
|
||||
def _get_session_path(self, session_id: str) -> str:
|
||||
"""Get the file path for a session."""
|
||||
# Sanitize session_id for filesystem
|
||||
safe_id = "".join(c if c.isalnum() or c in "-_" else "_" for c in session_id)
|
||||
return os.path.join(self.session_dir, f"{safe_id}.json")
|
||||
|
||||
def _load_session(self, session_id: str) -> SessionData:
|
||||
"""Load session from disk with file locking."""
|
||||
filepath = self._get_session_path(session_id)
|
||||
|
||||
# Check cache first
|
||||
with self._lock:
|
||||
if session_id in self._cache:
|
||||
return self._cache[session_id]
|
||||
|
||||
# Load from disk
|
||||
if not os.path.exists(filepath):
|
||||
session = SessionData(session_id=session_id)
|
||||
with self._lock:
|
||||
self._cache[session_id] = session
|
||||
return session
|
||||
|
||||
with FileLock(filepath, self.lock_timeout):
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
session = SessionData.from_dict(data)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"Failed to load session {session_id}: {e}")
|
||||
session = SessionData(session_id=session_id)
|
||||
|
||||
with self._lock:
|
||||
self._cache[session_id] = session
|
||||
|
||||
return session
|
||||
|
||||
def _save_session(self, session: SessionData) -> bool:
|
||||
"""Save session to disk with atomic write."""
|
||||
filepath = self._get_session_path(session.session_id)
|
||||
session.updated_at = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Trim messages if over limit
|
||||
if len(session.messages) > self.max_messages:
|
||||
session.messages = session.messages[-self.max_messages:]
|
||||
|
||||
with FileLock(filepath, self.lock_timeout):
|
||||
try:
|
||||
# Atomic write: write to temp file, then rename
|
||||
dir_path = os.path.dirname(filepath)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
dir=dir_path,
|
||||
delete=False,
|
||||
suffix=".tmp"
|
||||
) as f:
|
||||
json.dump(session.to_dict(), f, indent=2, ensure_ascii=False)
|
||||
temp_path = f.name
|
||||
|
||||
# Atomic rename
|
||||
os.replace(temp_path, filepath)
|
||||
return True
|
||||
except (IOError, OSError) as e:
|
||||
logger.error(f"Failed to save session {session.session_id}: {e}")
|
||||
# Clean up temp file if it exists
|
||||
try:
|
||||
if 'temp_path' in locals():
|
||||
os.remove(temp_path)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
return False
|
||||
|
||||
def add_message(
|
||||
self,
|
||||
session_id: str,
|
||||
role: str,
|
||||
content: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Add a message to a session.
|
||||
|
||||
Args:
|
||||
session_id: The session ID.
|
||||
role: Message role ("user", "assistant", "system").
|
||||
content: Message content.
|
||||
metadata: Optional metadata.
|
||||
|
||||
Returns:
|
||||
True if saved successfully.
|
||||
"""
|
||||
filepath = self._get_session_path(session_id)
|
||||
|
||||
message = SessionMessage(
|
||||
role=role,
|
||||
content=content,
|
||||
timestamp=time.time(),
|
||||
metadata=metadata or {},
|
||||
)
|
||||
|
||||
# Use file lock for atomic read-modify-write
|
||||
with FileLock(filepath, self.lock_timeout):
|
||||
# Always reload from disk inside lock to avoid race conditions
|
||||
if os.path.exists(filepath):
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
session = SessionData.from_dict(data)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
session = SessionData(session_id=session_id)
|
||||
else:
|
||||
session = SessionData(session_id=session_id)
|
||||
|
||||
# Add message
|
||||
session.messages.append(message)
|
||||
session.updated_at = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Trim messages if over limit
|
||||
if len(session.messages) > self.max_messages:
|
||||
session.messages = session.messages[-self.max_messages:]
|
||||
|
||||
# Write atomically
|
||||
try:
|
||||
dir_path = os.path.dirname(filepath) or "."
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
dir=dir_path,
|
||||
delete=False,
|
||||
suffix=".tmp"
|
||||
) as f:
|
||||
json.dump(session.to_dict(), f, indent=2, ensure_ascii=False)
|
||||
temp_path = f.name
|
||||
|
||||
os.replace(temp_path, filepath)
|
||||
|
||||
# Update cache
|
||||
with self._lock:
|
||||
self._cache[session_id] = session
|
||||
|
||||
return True
|
||||
except (IOError, OSError) as e:
|
||||
logger.error(f"Failed to save session {session_id}: {e}")
|
||||
try:
|
||||
if 'temp_path' in locals():
|
||||
os.remove(temp_path)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
return False
|
||||
|
||||
def add_user_message(
|
||||
self,
|
||||
session_id: str,
|
||||
content: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> bool:
|
||||
"""Add a user message to a session."""
|
||||
return self.add_message(session_id, "user", content, metadata)
|
||||
|
||||
def add_assistant_message(
|
||||
self,
|
||||
session_id: str,
|
||||
content: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> bool:
|
||||
"""Add an assistant message to a session."""
|
||||
return self.add_message(session_id, "assistant", content, metadata)
|
||||
|
||||
def get_chat_history(
|
||||
self,
|
||||
session_id: str,
|
||||
max_messages: Optional[int] = None,
|
||||
) -> List[Dict[str, str]]:
|
||||
"""
|
||||
Get chat history for a session in LLM-compatible format.
|
||||
|
||||
Args:
|
||||
session_id: The session ID.
|
||||
max_messages: Maximum messages to return (defaults to store limit).
|
||||
|
||||
Returns:
|
||||
List of {"role": "user/assistant", "content": "..."} dicts.
|
||||
"""
|
||||
session = self._load_session(session_id)
|
||||
limit = max_messages or self.max_messages
|
||||
return session.get_chat_history(limit)
|
||||
|
||||
def get_session(self, session_id: str) -> SessionData:
|
||||
"""Get full session data."""
|
||||
return self._load_session(session_id)
|
||||
|
||||
def set_agent_info(
|
||||
self,
|
||||
session_id: str,
|
||||
agent_name: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Set agent info for a session."""
|
||||
session = self._load_session(session_id)
|
||||
|
||||
with self._lock:
|
||||
if agent_name:
|
||||
session.agent_name = agent_name
|
||||
if user_id:
|
||||
session.user_id = user_id
|
||||
self._cache[session_id] = session
|
||||
|
||||
return self._save_session(session)
|
||||
|
||||
def clear_session(self, session_id: str) -> bool:
|
||||
"""Clear all messages from a session."""
|
||||
session = self._load_session(session_id)
|
||||
|
||||
with self._lock:
|
||||
session.messages.clear()
|
||||
self._cache[session_id] = session
|
||||
|
||||
return self._save_session(session)
|
||||
|
||||
def delete_session(self, session_id: str) -> bool:
|
||||
"""Delete a session completely."""
|
||||
filepath = self._get_session_path(session_id)
|
||||
|
||||
with self._lock:
|
||||
if session_id in self._cache:
|
||||
del self._cache[session_id]
|
||||
|
||||
try:
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
return True
|
||||
except (IOError, OSError) as e:
|
||||
logger.error(f"Failed to delete session {session_id}: {e}")
|
||||
return False
|
||||
|
||||
def list_sessions(self, limit: int = 50) -> List[Dict[str, Any]]:
|
||||
"""List all sessions with metadata."""
|
||||
sessions = []
|
||||
|
||||
try:
|
||||
for filename in os.listdir(self.session_dir):
|
||||
if filename.endswith(".json"):
|
||||
filepath = os.path.join(self.session_dir, filename)
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
sessions.append({
|
||||
"session_id": data.get("session_id", filename[:-5]),
|
||||
"agent_name": data.get("agent_name"),
|
||||
"created_at": data.get("created_at"),
|
||||
"updated_at": data.get("updated_at"),
|
||||
"message_count": len(data.get("messages", [])),
|
||||
})
|
||||
except (json.JSONDecodeError, IOError):
|
||||
continue
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
# Sort by updated_at descending
|
||||
sessions.sort(key=lambda x: x.get("updated_at", ""), reverse=True)
|
||||
return sessions[:limit]
|
||||
|
||||
def session_exists(self, session_id: str) -> bool:
|
||||
"""Check if a session exists."""
|
||||
filepath = self._get_session_path(session_id)
|
||||
return os.path.exists(filepath)
|
||||
|
||||
def invalidate_cache(self, session_id: Optional[str] = None) -> None:
|
||||
"""Invalidate cache for a session or all sessions."""
|
||||
with self._lock:
|
||||
if session_id:
|
||||
self._cache.pop(session_id, None)
|
||||
else:
|
||||
self._cache.clear()
|
||||
|
||||
|
||||
# Global session store instance (lazy initialized)
|
||||
_default_store: Optional[DefaultSessionStore] = None
|
||||
_store_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_default_session_store() -> DefaultSessionStore:
|
||||
"""Get the global default session store instance."""
|
||||
global _default_store
|
||||
|
||||
if _default_store is None:
|
||||
with _store_lock:
|
||||
if _default_store is None:
|
||||
_default_store = DefaultSessionStore()
|
||||
|
||||
return _default_store
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "0.5.1"
|
||||
version = "0.6.2"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Test that MCP module imports are optional and don't break basic functionality.
|
||||
This test verifies the fix for issue #1091.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
class TestMCPOptionalImport:
|
||||
"""Test that MCP is properly optional."""
|
||||
|
||||
def test_praisonaiagents_import_without_mcp(self):
|
||||
"""Test that praisonaiagents can be imported without mcp installed."""
|
||||
# This should not raise any ImportError
|
||||
import praisonaiagents
|
||||
assert praisonaiagents is not None
|
||||
|
||||
def test_agent_import_without_mcp(self):
|
||||
"""Test that Agent can be imported without mcp installed."""
|
||||
from praisonaiagents import Agent
|
||||
assert Agent is not None
|
||||
|
||||
def test_mcp_lazy_loading(self):
|
||||
"""Test that MCP is lazily loaded."""
|
||||
from praisonaiagents import MCP # noqa: F401
|
||||
# MCP should either be available or None (not raise ImportError on access)
|
||||
# The actual ImportError should only be raised when trying to instantiate
|
||||
# if mcp package is not installed
|
||||
|
||||
def test_mcp_module_import_no_crash(self):
|
||||
"""Test that mcp module can be imported without crashing."""
|
||||
# This tests that the try/except in mcp.py works correctly
|
||||
try:
|
||||
from praisonaiagents.mcp import mcp as mcp_module
|
||||
assert hasattr(mcp_module, 'MCP_AVAILABLE')
|
||||
except ImportError:
|
||||
# If import fails, it should be because of missing mcp,
|
||||
# not because of a syntax error
|
||||
pass
|
||||
|
||||
def test_mcp_sse_module_import_no_crash(self):
|
||||
"""Test that mcp_sse module can be imported without crashing."""
|
||||
try:
|
||||
from praisonaiagents.mcp import mcp_sse
|
||||
assert hasattr(mcp_sse, 'MCP_AVAILABLE')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def test_mcp_http_stream_module_import_no_crash(self):
|
||||
"""Test that mcp_http_stream module can be imported without crashing."""
|
||||
try:
|
||||
from praisonaiagents.mcp import mcp_http_stream
|
||||
assert hasattr(mcp_http_stream, 'MCP_AVAILABLE')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def test_basic_agent_creation_without_mcp_tools(self):
|
||||
"""Test that agents can be created without MCP tools."""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Creating an agent without MCP tools should work
|
||||
agent = Agent(
|
||||
name="Test Agent",
|
||||
instructions="You are a test agent.",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
assert agent is not None
|
||||
|
||||
def test_mcp_raises_import_error_when_unavailable(self):
|
||||
"""Test that MCP raises ImportError with install instructions when mcp package unavailable."""
|
||||
import praisonaiagents.mcp.mcp as mcp_module
|
||||
|
||||
# Save original value
|
||||
original_available = mcp_module.MCP_AVAILABLE
|
||||
|
||||
# Simulate MCP not installed
|
||||
mcp_module.MCP_AVAILABLE = False
|
||||
|
||||
try:
|
||||
from praisonaiagents.mcp.mcp import MCP
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
MCP('test-command')
|
||||
|
||||
error_msg = str(exc_info.value)
|
||||
assert 'pip install praisonaiagents[mcp]' in error_msg
|
||||
assert 'MCP' in error_msg or 'Model Context Protocol' in error_msg
|
||||
finally:
|
||||
# Restore original value
|
||||
mcp_module.MCP_AVAILABLE = original_available
|
||||
|
||||
def test_sse_client_raises_import_error_when_unavailable(self):
|
||||
"""Test that SSEMCPClient raises ImportError with install instructions when mcp unavailable."""
|
||||
import praisonaiagents.mcp.mcp_sse as sse_module
|
||||
|
||||
original_available = sse_module.MCP_AVAILABLE
|
||||
sse_module.MCP_AVAILABLE = False
|
||||
|
||||
try:
|
||||
from praisonaiagents.mcp.mcp_sse import SSEMCPClient
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
SSEMCPClient('http://test.com/sse')
|
||||
|
||||
error_msg = str(exc_info.value)
|
||||
assert 'pip install praisonaiagents[mcp]' in error_msg
|
||||
finally:
|
||||
sse_module.MCP_AVAILABLE = original_available
|
||||
|
||||
def test_http_stream_client_raises_import_error_when_mcp_unavailable(self):
|
||||
"""Test that HTTPStreamMCPClient raises ImportError when mcp unavailable."""
|
||||
import praisonaiagents.mcp.mcp_http_stream as http_module
|
||||
|
||||
original_available = http_module.MCP_AVAILABLE
|
||||
http_module.MCP_AVAILABLE = False
|
||||
|
||||
try:
|
||||
from praisonaiagents.mcp.mcp_http_stream import HTTPStreamMCPClient
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
HTTPStreamMCPClient('http://test.com/mcp')
|
||||
|
||||
error_msg = str(exc_info.value)
|
||||
assert 'pip install praisonaiagents[mcp]' in error_msg
|
||||
finally:
|
||||
http_module.MCP_AVAILABLE = original_available
|
||||
|
||||
def test_http_stream_client_raises_import_error_when_aiohttp_unavailable(self):
|
||||
"""Test that HTTPStreamMCPClient raises ImportError when aiohttp unavailable."""
|
||||
import praisonaiagents.mcp.mcp_http_stream as http_module
|
||||
|
||||
# Ensure MCP is available but aiohttp is not
|
||||
original_available = http_module.MCP_AVAILABLE
|
||||
original_aiohttp = http_module.aiohttp
|
||||
|
||||
http_module.MCP_AVAILABLE = True
|
||||
http_module.aiohttp = None
|
||||
|
||||
try:
|
||||
from praisonaiagents.mcp.mcp_http_stream import HTTPStreamMCPClient
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
HTTPStreamMCPClient('http://test.com/mcp')
|
||||
|
||||
error_msg = str(exc_info.value)
|
||||
assert 'aiohttp' in error_msg.lower()
|
||||
assert 'pip install praisonaiagents[mcp]' in error_msg
|
||||
finally:
|
||||
http_module.MCP_AVAILABLE = original_available
|
||||
http_module.aiohttp = original_aiohttp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
@@ -0,0 +1 @@
|
||||
# Session module tests
|
||||
@@ -0,0 +1,404 @@
|
||||
"""
|
||||
TDD Tests for Session Store.
|
||||
|
||||
Tests for DefaultSessionStore with JSON-based persistence.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import pytest
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from praisonaiagents.session.store import (
|
||||
DefaultSessionStore,
|
||||
SessionMessage,
|
||||
SessionData,
|
||||
FileLock,
|
||||
DEFAULT_MAX_MESSAGES,
|
||||
)
|
||||
|
||||
|
||||
class TestSessionMessage:
|
||||
"""Tests for SessionMessage dataclass."""
|
||||
|
||||
def test_create_message(self):
|
||||
"""Test creating a session message."""
|
||||
msg = SessionMessage(role="user", content="Hello")
|
||||
assert msg.role == "user"
|
||||
assert msg.content == "Hello"
|
||||
assert msg.timestamp > 0
|
||||
assert msg.metadata == {}
|
||||
|
||||
def test_message_to_dict(self):
|
||||
"""Test converting message to dict."""
|
||||
msg = SessionMessage(role="assistant", content="Hi there!")
|
||||
d = msg.to_dict()
|
||||
assert d["role"] == "assistant"
|
||||
assert d["content"] == "Hi there!"
|
||||
assert "timestamp" in d
|
||||
assert "metadata" in d
|
||||
|
||||
def test_message_from_dict(self):
|
||||
"""Test creating message from dict."""
|
||||
d = {"role": "user", "content": "Test", "timestamp": 123.0, "metadata": {"key": "value"}}
|
||||
msg = SessionMessage.from_dict(d)
|
||||
assert msg.role == "user"
|
||||
assert msg.content == "Test"
|
||||
assert msg.timestamp == 123.0
|
||||
assert msg.metadata == {"key": "value"}
|
||||
|
||||
|
||||
class TestSessionData:
|
||||
"""Tests for SessionData dataclass."""
|
||||
|
||||
def test_create_session(self):
|
||||
"""Test creating session data."""
|
||||
session = SessionData(session_id="test-123")
|
||||
assert session.session_id == "test-123"
|
||||
assert session.messages == []
|
||||
assert session.created_at is not None
|
||||
assert session.updated_at is not None
|
||||
|
||||
def test_session_to_dict(self):
|
||||
"""Test converting session to dict."""
|
||||
session = SessionData(session_id="test-123", agent_name="Assistant")
|
||||
session.messages.append(SessionMessage(role="user", content="Hello"))
|
||||
d = session.to_dict()
|
||||
assert d["session_id"] == "test-123"
|
||||
assert d["agent_name"] == "Assistant"
|
||||
assert len(d["messages"]) == 1
|
||||
|
||||
def test_session_from_dict(self):
|
||||
"""Test creating session from dict."""
|
||||
d = {
|
||||
"session_id": "test-456",
|
||||
"messages": [{"role": "user", "content": "Hi"}],
|
||||
"agent_name": "Bot",
|
||||
}
|
||||
session = SessionData.from_dict(d)
|
||||
assert session.session_id == "test-456"
|
||||
assert session.agent_name == "Bot"
|
||||
assert len(session.messages) == 1
|
||||
|
||||
def test_get_chat_history(self):
|
||||
"""Test getting chat history in LLM format."""
|
||||
session = SessionData(session_id="test")
|
||||
session.messages.append(SessionMessage(role="user", content="Hello"))
|
||||
session.messages.append(SessionMessage(role="assistant", content="Hi!"))
|
||||
|
||||
history = session.get_chat_history()
|
||||
assert len(history) == 2
|
||||
assert history[0] == {"role": "user", "content": "Hello"}
|
||||
assert history[1] == {"role": "assistant", "content": "Hi!"}
|
||||
|
||||
def test_get_chat_history_with_limit(self):
|
||||
"""Test getting limited chat history."""
|
||||
session = SessionData(session_id="test")
|
||||
for i in range(10):
|
||||
session.messages.append(SessionMessage(role="user", content=f"Message {i}"))
|
||||
|
||||
history = session.get_chat_history(max_messages=3)
|
||||
assert len(history) == 3
|
||||
assert history[0]["content"] == "Message 7"
|
||||
assert history[2]["content"] == "Message 9"
|
||||
|
||||
|
||||
class TestFileLock:
|
||||
"""Tests for FileLock."""
|
||||
|
||||
def test_acquire_release(self):
|
||||
"""Test basic lock acquire and release."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
filepath = os.path.join(tmpdir, "test.json")
|
||||
lock = FileLock(filepath, timeout=1.0)
|
||||
|
||||
assert lock.acquire()
|
||||
lock.release()
|
||||
|
||||
def test_context_manager(self):
|
||||
"""Test lock as context manager."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
filepath = os.path.join(tmpdir, "test.json")
|
||||
|
||||
with FileLock(filepath, timeout=1.0):
|
||||
# Lock is held
|
||||
pass
|
||||
# Lock is released
|
||||
|
||||
def test_concurrent_locks(self):
|
||||
"""Test that concurrent locks are serialized."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
filepath = os.path.join(tmpdir, "test.json")
|
||||
results = []
|
||||
|
||||
def worker(worker_id):
|
||||
with FileLock(filepath, timeout=5.0):
|
||||
results.append(f"start-{worker_id}")
|
||||
time.sleep(0.1)
|
||||
results.append(f"end-{worker_id}")
|
||||
|
||||
threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
# Check that starts and ends are paired (no interleaving)
|
||||
for i in range(3):
|
||||
start_idx = results.index(f"start-{i}")
|
||||
end_idx = results.index(f"end-{i}")
|
||||
# No other start should be between this start and end
|
||||
for j in range(3):
|
||||
if i != j:
|
||||
other_start = results.index(f"start-{j}")
|
||||
if start_idx < other_start < end_idx:
|
||||
pytest.fail(f"Lock interleaving detected: {results}")
|
||||
|
||||
|
||||
class TestDefaultSessionStore:
|
||||
"""Tests for DefaultSessionStore."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_store(self):
|
||||
"""Create a temporary session store."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
store = DefaultSessionStore(session_dir=tmpdir)
|
||||
yield store
|
||||
|
||||
def test_add_message(self, temp_store):
|
||||
"""Test adding a message."""
|
||||
result = temp_store.add_message("session-1", "user", "Hello")
|
||||
assert result is True
|
||||
|
||||
history = temp_store.get_chat_history("session-1")
|
||||
assert len(history) == 1
|
||||
assert history[0]["role"] == "user"
|
||||
assert history[0]["content"] == "Hello"
|
||||
|
||||
def test_add_user_message(self, temp_store):
|
||||
"""Test adding a user message."""
|
||||
temp_store.add_user_message("session-1", "Hello")
|
||||
history = temp_store.get_chat_history("session-1")
|
||||
assert history[0]["role"] == "user"
|
||||
|
||||
def test_add_assistant_message(self, temp_store):
|
||||
"""Test adding an assistant message."""
|
||||
temp_store.add_assistant_message("session-1", "Hi there!")
|
||||
history = temp_store.get_chat_history("session-1")
|
||||
assert history[0]["role"] == "assistant"
|
||||
|
||||
def test_persistence_across_instances(self, temp_store):
|
||||
"""Test that sessions persist across store instances."""
|
||||
session_dir = temp_store.session_dir
|
||||
|
||||
# Add messages with first store
|
||||
temp_store.add_user_message("session-1", "Hello")
|
||||
temp_store.add_assistant_message("session-1", "Hi!")
|
||||
|
||||
# Create new store instance
|
||||
store2 = DefaultSessionStore(session_dir=session_dir)
|
||||
|
||||
# Verify messages are restored
|
||||
history = store2.get_chat_history("session-1")
|
||||
assert len(history) == 2
|
||||
assert history[0]["content"] == "Hello"
|
||||
assert history[1]["content"] == "Hi!"
|
||||
|
||||
def test_session_file_created(self, temp_store):
|
||||
"""Test that session file is created on disk."""
|
||||
temp_store.add_user_message("test-session", "Hello")
|
||||
|
||||
filepath = os.path.join(temp_store.session_dir, "test-session.json")
|
||||
assert os.path.exists(filepath)
|
||||
|
||||
with open(filepath, "r") as f:
|
||||
data = json.load(f)
|
||||
assert data["session_id"] == "test-session"
|
||||
assert len(data["messages"]) == 1
|
||||
|
||||
def test_max_messages_limit(self):
|
||||
"""Test that messages are trimmed to max limit."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
store = DefaultSessionStore(session_dir=tmpdir, max_messages=5)
|
||||
|
||||
for i in range(10):
|
||||
store.add_user_message("session-1", f"Message {i}")
|
||||
|
||||
history = store.get_chat_history("session-1")
|
||||
assert len(history) == 5
|
||||
assert history[0]["content"] == "Message 5"
|
||||
assert history[4]["content"] == "Message 9"
|
||||
|
||||
def test_clear_session(self, temp_store):
|
||||
"""Test clearing a session."""
|
||||
temp_store.add_user_message("session-1", "Hello")
|
||||
temp_store.add_assistant_message("session-1", "Hi!")
|
||||
|
||||
temp_store.clear_session("session-1")
|
||||
|
||||
history = temp_store.get_chat_history("session-1")
|
||||
assert len(history) == 0
|
||||
|
||||
def test_delete_session(self, temp_store):
|
||||
"""Test deleting a session."""
|
||||
temp_store.add_user_message("session-1", "Hello")
|
||||
filepath = os.path.join(temp_store.session_dir, "session-1.json")
|
||||
assert os.path.exists(filepath)
|
||||
|
||||
temp_store.delete_session("session-1")
|
||||
assert not os.path.exists(filepath)
|
||||
|
||||
def test_list_sessions(self, temp_store):
|
||||
"""Test listing sessions."""
|
||||
temp_store.add_user_message("session-1", "Hello")
|
||||
temp_store.add_user_message("session-2", "Hi")
|
||||
temp_store.add_user_message("session-3", "Hey")
|
||||
|
||||
sessions = temp_store.list_sessions()
|
||||
assert len(sessions) == 3
|
||||
session_ids = [s["session_id"] for s in sessions]
|
||||
assert "session-1" in session_ids
|
||||
assert "session-2" in session_ids
|
||||
assert "session-3" in session_ids
|
||||
|
||||
def test_session_exists(self, temp_store):
|
||||
"""Test checking if session exists."""
|
||||
assert not temp_store.session_exists("session-1")
|
||||
|
||||
temp_store.add_user_message("session-1", "Hello")
|
||||
assert temp_store.session_exists("session-1")
|
||||
|
||||
def test_set_agent_info(self, temp_store):
|
||||
"""Test setting agent info."""
|
||||
temp_store.add_user_message("session-1", "Hello")
|
||||
temp_store.set_agent_info("session-1", agent_name="TestBot", user_id="user-123")
|
||||
|
||||
session = temp_store.get_session("session-1")
|
||||
assert session.agent_name == "TestBot"
|
||||
assert session.user_id == "user-123"
|
||||
|
||||
def test_concurrent_writes(self, temp_store):
|
||||
"""Test concurrent writes to same session."""
|
||||
def writer(msg_id):
|
||||
temp_store.add_user_message("session-1", f"Message {msg_id}")
|
||||
|
||||
with ThreadPoolExecutor(max_workers=5) as executor:
|
||||
futures = [executor.submit(writer, i) for i in range(10)]
|
||||
for f in futures:
|
||||
f.result()
|
||||
|
||||
history = temp_store.get_chat_history("session-1")
|
||||
assert len(history) == 10
|
||||
|
||||
def test_sanitize_session_id(self, temp_store):
|
||||
"""Test that session IDs are sanitized for filesystem."""
|
||||
temp_store.add_user_message("session/with/slashes", "Hello")
|
||||
temp_store.add_user_message("session:with:colons", "Hi")
|
||||
|
||||
# Should not raise errors
|
||||
history1 = temp_store.get_chat_history("session/with/slashes")
|
||||
history2 = temp_store.get_chat_history("session:with:colons")
|
||||
assert len(history1) == 1
|
||||
assert len(history2) == 1
|
||||
|
||||
def test_empty_session_returns_empty_history(self, temp_store):
|
||||
"""Test that non-existent session returns empty history."""
|
||||
history = temp_store.get_chat_history("nonexistent")
|
||||
assert history == []
|
||||
|
||||
def test_invalidate_cache(self, temp_store):
|
||||
"""Test cache invalidation."""
|
||||
temp_store.add_user_message("session-1", "Hello")
|
||||
|
||||
# Modify file directly
|
||||
filepath = os.path.join(temp_store.session_dir, "session-1.json")
|
||||
with open(filepath, "r") as f:
|
||||
data = json.load(f)
|
||||
data["messages"].append({"role": "user", "content": "Direct edit", "timestamp": time.time(), "metadata": {}})
|
||||
with open(filepath, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
# Without invalidation, cache returns old data
|
||||
history = temp_store.get_chat_history("session-1")
|
||||
assert len(history) == 1 # Cached
|
||||
|
||||
# After invalidation, new data is loaded
|
||||
temp_store.invalidate_cache("session-1")
|
||||
history = temp_store.get_chat_history("session-1")
|
||||
assert len(history) == 2
|
||||
|
||||
|
||||
class TestAgentSessionIntegration:
|
||||
"""Tests for Agent integration with session store."""
|
||||
|
||||
def test_agent_session_persistence(self):
|
||||
"""Test that Agent persists session with session_id."""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Patch the default session directory
|
||||
import praisonaiagents.session.store as store_module
|
||||
original_dir = store_module.DEFAULT_SESSION_DIR
|
||||
store_module.DEFAULT_SESSION_DIR = tmpdir
|
||||
|
||||
# Reset global store
|
||||
store_module._default_store = None
|
||||
|
||||
try:
|
||||
# Create agent with session_id
|
||||
agent = Agent(
|
||||
name="TestAgent",
|
||||
instructions="You are a test agent.",
|
||||
session_id="test-session-123",
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
# Verify session store is initialized after first chat
|
||||
# (We can't actually call chat without API key, so we test init)
|
||||
agent._init_session_store()
|
||||
|
||||
assert agent._session_store is not None
|
||||
assert agent._session_store_initialized is True
|
||||
finally:
|
||||
store_module.DEFAULT_SESSION_DIR = original_dir
|
||||
store_module._default_store = None
|
||||
|
||||
def test_agent_no_session_id_no_persistence(self):
|
||||
"""Test that Agent without session_id doesn't use session store."""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
agent = Agent(
|
||||
name="TestAgent",
|
||||
instructions="You are a test agent.",
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
agent._init_session_store()
|
||||
|
||||
assert agent._session_store is None
|
||||
assert agent._session_store_initialized is True
|
||||
|
||||
def test_agent_with_db_no_session_store(self):
|
||||
"""Test that Agent with DB adapter doesn't use session store."""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Mock DB adapter
|
||||
class MockDbAdapter:
|
||||
def on_agent_start(self, **kwargs):
|
||||
return []
|
||||
|
||||
agent = Agent(
|
||||
name="TestAgent",
|
||||
instructions="You are a test agent.",
|
||||
session_id="test-session",
|
||||
db=MockDbAdapter(),
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
agent._init_session_store()
|
||||
|
||||
# Session store should not be used when DB is provided
|
||||
assert agent._session_store is None
|
||||
Generated
+1
-1
@@ -2422,7 +2422,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.5.1"
|
||||
version = "0.6.2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
{
|
||||
"version": "1.2.1",
|
||||
"lastUpdated": "2024-12-26",
|
||||
"implementationStatus": "Phase 4 Complete - Full Feature Parity",
|
||||
"version": "1.3.0",
|
||||
"lastUpdated": "2025-01-15",
|
||||
"implementationStatus": "Phase 5 Complete - AI SDK Wrapper + Integrations",
|
||||
"summary": {
|
||||
"pythonCoreFeatures": 85,
|
||||
"pythonWrapperFeatures": 45,
|
||||
"typescriptFeatures": 75,
|
||||
"gapCount": 10,
|
||||
"typescriptFeatures": 95,
|
||||
"gapCount": 5,
|
||||
"priorityP0": 0,
|
||||
"priorityP1": 3,
|
||||
"priorityP2": 5,
|
||||
"priorityP3": 2
|
||||
"priorityP1": 1,
|
||||
"priorityP2": 2,
|
||||
"priorityP3": 2,
|
||||
"newInV1_3": {
|
||||
"aiSdkWrappers": ["generateText", "streamText", "generateObject", "streamObject", "generateImage", "embed", "embedMany"],
|
||||
"serverAdapters": ["http", "express", "hono", "fastify", "nest"],
|
||||
"nextjsIntegration": ["createRouteHandler", "createPagesHandler"],
|
||||
"mcpClient": ["createMCP", "getMCPClient", "mcpToolsToAITools"],
|
||||
"integrations": ["createSlackBot", "createNLPostgres", "createComputerUse"],
|
||||
"agentLoop": ["createAgentLoop", "stopAfterSteps", "stopWhenNoToolCalls"],
|
||||
"multimodal": ["createImagePart", "createFilePart", "createPdfPart", "createMultimodalMessage"],
|
||||
"middleware": ["createCachingMiddleware", "createLoggingMiddleware", "wrapModel"]
|
||||
}
|
||||
},
|
||||
"pythonCoreSDK": {
|
||||
"path": "/Users/praison/praisonai-package/src/praisonai-agents/praisonaiagents",
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* AI SDK v6 Parity - Basic Usage Examples
|
||||
*
|
||||
* This example demonstrates the new AI SDK v6 compatible features in praisonai-ts.
|
||||
*/
|
||||
|
||||
import {
|
||||
// Core Agent
|
||||
Agent,
|
||||
// Tool Approval
|
||||
ApprovalManager,
|
||||
getApprovalManager,
|
||||
withApproval,
|
||||
createDangerousPatternChecker,
|
||||
// UI Messages
|
||||
UIMessage,
|
||||
convertToModelMessages,
|
||||
createTextMessage,
|
||||
toUIMessageStreamResponse,
|
||||
// Speech & Transcription
|
||||
generateSpeech,
|
||||
transcribe,
|
||||
// DevTools
|
||||
enableDevTools,
|
||||
isDevToolsEnabled,
|
||||
// Telemetry
|
||||
configureTelemetry,
|
||||
createAISpan,
|
||||
// MCP
|
||||
createMCP,
|
||||
} from '../../src';
|
||||
|
||||
// ============================================================================
|
||||
// Example 1: Tool with Approval
|
||||
// ============================================================================
|
||||
|
||||
async function toolApprovalExample() {
|
||||
console.log('\n=== Tool Approval Example ===\n');
|
||||
|
||||
// Create a tool that requires approval
|
||||
const deleteFile = withApproval({
|
||||
name: 'deleteFile',
|
||||
description: 'Delete a file from the filesystem',
|
||||
needsApproval: true, // Always require approval
|
||||
execute: async (input: { path: string }) => {
|
||||
console.log(`Would delete: ${input.path}`);
|
||||
return { success: true, path: input.path };
|
||||
},
|
||||
onDenied: (input) => {
|
||||
console.log(`Deletion denied for: ${input.path}`);
|
||||
return { success: false, path: input.path };
|
||||
},
|
||||
});
|
||||
|
||||
// Create a tool with conditional approval
|
||||
const runCommand = withApproval({
|
||||
name: 'runCommand',
|
||||
description: 'Run a shell command',
|
||||
needsApproval: createDangerousPatternChecker(), // Only approve dangerous commands
|
||||
execute: async (input: { command: string }) => {
|
||||
console.log(`Would run: ${input.command}`);
|
||||
return { output: 'command output' };
|
||||
},
|
||||
});
|
||||
|
||||
// Set up approval handler
|
||||
const manager = getApprovalManager();
|
||||
manager.onApprovalRequest(async (request) => {
|
||||
console.log(`\n🔐 Approval requested for: ${request.toolName}`);
|
||||
console.log(` Input: ${JSON.stringify(request.input)}`);
|
||||
// Auto-approve for demo
|
||||
return true;
|
||||
});
|
||||
|
||||
// Test the tools
|
||||
try {
|
||||
const result1 = await deleteFile({ path: '/tmp/test.txt' });
|
||||
console.log('Delete result:', result1);
|
||||
|
||||
const result2 = await runCommand({ command: 'ls -la' });
|
||||
console.log('Command result:', result2);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Example 2: UI Messages
|
||||
// ============================================================================
|
||||
|
||||
async function uiMessageExample() {
|
||||
console.log('\n=== UI Message Example ===\n');
|
||||
|
||||
// Create UI messages
|
||||
const messages: UIMessage[] = [
|
||||
createTextMessage('user', 'Hello, how are you?'),
|
||||
createTextMessage('assistant', 'I am doing well, thank you for asking!'),
|
||||
];
|
||||
|
||||
console.log('UI Messages:', JSON.stringify(messages, null, 2));
|
||||
|
||||
// Convert to model messages
|
||||
const modelMessages = await convertToModelMessages(messages);
|
||||
console.log('\nModel Messages:', JSON.stringify(modelMessages, null, 2));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Example 3: DevTools
|
||||
// ============================================================================
|
||||
|
||||
async function devToolsExample() {
|
||||
console.log('\n=== DevTools Example ===\n');
|
||||
|
||||
// Check if DevTools is enabled
|
||||
console.log('DevTools enabled:', isDevToolsEnabled());
|
||||
|
||||
// Enable DevTools (in development)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
await enableDevTools({
|
||||
port: 3001,
|
||||
projectName: 'PraisonAI Demo',
|
||||
});
|
||||
console.log('DevTools enabled:', isDevToolsEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Example 4: Telemetry
|
||||
// ============================================================================
|
||||
|
||||
async function telemetryExample() {
|
||||
console.log('\n=== Telemetry Example ===\n');
|
||||
|
||||
// Configure telemetry
|
||||
configureTelemetry({
|
||||
isEnabled: true,
|
||||
functionId: 'demo-app',
|
||||
metadata: {
|
||||
version: '1.0.0',
|
||||
environment: 'development',
|
||||
},
|
||||
});
|
||||
|
||||
// Create a span
|
||||
const span = createAISpan('generateText', {
|
||||
model: 'gpt-4o',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
try {
|
||||
// Simulate some work
|
||||
console.log('Performing AI operation...');
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
span.setAttribute('tokens', 150);
|
||||
span.setStatus({ code: 'ok' });
|
||||
} catch (error: any) {
|
||||
span.recordException(error);
|
||||
span.setStatus({ code: 'error', message: error.message });
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
|
||||
console.log('Telemetry span completed');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Example 5: MCP with HTTP Transport
|
||||
// ============================================================================
|
||||
|
||||
async function mcpExample() {
|
||||
console.log('\n=== MCP Example ===\n');
|
||||
|
||||
// Note: This requires an MCP server to be running
|
||||
console.log('MCP HTTP transport configuration example:');
|
||||
console.log(`
|
||||
const client = await createMCP({
|
||||
transport: {
|
||||
type: 'http',
|
||||
url: 'https://mcp-server.example.com/mcp',
|
||||
headers: { Authorization: 'Bearer token' },
|
||||
}
|
||||
});
|
||||
|
||||
const tools = await client.tools();
|
||||
const resources = await client.listResources();
|
||||
const prompts = await client.listPrompts();
|
||||
`);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main
|
||||
// ============================================================================
|
||||
|
||||
async function main() {
|
||||
console.log('🚀 AI SDK v6 Parity Examples\n');
|
||||
console.log('=' .repeat(50));
|
||||
|
||||
await toolApprovalExample();
|
||||
await uiMessageExample();
|
||||
await devToolsExample();
|
||||
await telemetryExample();
|
||||
await mcpExample();
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log('✅ All examples completed!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* AI SDK Wrapper Demo
|
||||
*
|
||||
* Demonstrates the new AI SDK wrapper functions in praisonai-ts.
|
||||
* These wrappers provide a stable interface over the AI SDK primitives.
|
||||
*/
|
||||
|
||||
import {
|
||||
// Core text generation
|
||||
aiGenerateText,
|
||||
aiStreamText,
|
||||
// Object generation
|
||||
aiGenerateObject,
|
||||
// Image generation
|
||||
aiGenerateImage,
|
||||
// Embeddings
|
||||
aiEmbed,
|
||||
// Tools
|
||||
defineTool,
|
||||
createToolSet,
|
||||
// Models
|
||||
getModel,
|
||||
MODEL_ALIASES,
|
||||
// Middleware
|
||||
createCachingMiddleware,
|
||||
// Multimodal
|
||||
createImagePart,
|
||||
createMultimodalMessage,
|
||||
// MCP
|
||||
createMCP,
|
||||
// Server adapters
|
||||
createExpressHandler,
|
||||
// Next.js
|
||||
createRouteHandler,
|
||||
// Agent loop
|
||||
createAgentLoop,
|
||||
stopWhenNoToolCalls,
|
||||
} from '../../src';
|
||||
|
||||
// Also import the Agent class for comparison
|
||||
import { Agent } from '../../src';
|
||||
|
||||
async function main() {
|
||||
console.log('=== PraisonAI TypeScript - AI SDK Wrapper Demo ===\n');
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 1. Simple Text Generation
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('1. Simple Text Generation');
|
||||
console.log('-------------------------');
|
||||
|
||||
try {
|
||||
const result = await aiGenerateText({
|
||||
model: 'gpt-4o-mini',
|
||||
prompt: 'What is 2 + 2? Answer in one word.',
|
||||
});
|
||||
console.log('Result:', result.text);
|
||||
console.log('Usage:', result.usage);
|
||||
} catch (error: any) {
|
||||
console.log('Skipped (AI SDK not installed):', error.message);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Streaming Text Generation
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n2. Streaming Text Generation');
|
||||
console.log('----------------------------');
|
||||
|
||||
try {
|
||||
const stream = await aiStreamText({
|
||||
model: 'gpt-4o-mini',
|
||||
prompt: 'Count from 1 to 5.',
|
||||
});
|
||||
|
||||
process.stdout.write('Streaming: ');
|
||||
for await (const chunk of stream.textStream) {
|
||||
process.stdout.write(chunk);
|
||||
}
|
||||
console.log('\n');
|
||||
} catch (error: any) {
|
||||
console.log('Skipped (AI SDK not installed):', error.message);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 3. Structured Object Generation
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('3. Structured Object Generation');
|
||||
console.log('-------------------------------');
|
||||
|
||||
try {
|
||||
// Using a simple JSON schema (Zod would be better in production)
|
||||
const result = await aiGenerateObject({
|
||||
model: 'gpt-4o-mini',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
age: { type: 'number' },
|
||||
city: { type: 'string' },
|
||||
},
|
||||
required: ['name', 'age', 'city'],
|
||||
},
|
||||
prompt: 'Generate a fictional person.',
|
||||
});
|
||||
console.log('Generated object:', result.object);
|
||||
} catch (error: any) {
|
||||
console.log('Skipped (AI SDK not installed):', error.message);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 4. Tool Definition
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n4. Tool Definition');
|
||||
console.log('------------------');
|
||||
|
||||
const weatherTool = defineTool({
|
||||
description: 'Get weather for a city',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
city: { type: 'string', description: 'City name' },
|
||||
},
|
||||
required: ['city'],
|
||||
},
|
||||
execute: async ({ city }) => {
|
||||
return { temperature: 20, unit: 'celsius', city };
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Weather tool defined:', weatherTool.description);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 5. Tool Set Creation
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n5. Tool Set Creation');
|
||||
console.log('--------------------');
|
||||
|
||||
const tools = createToolSet({
|
||||
weather: weatherTool,
|
||||
calculator: defineTool({
|
||||
description: 'Perform calculations',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
expression: { type: 'string' },
|
||||
},
|
||||
required: ['expression'],
|
||||
},
|
||||
execute: async ({ expression }) => {
|
||||
return { result: eval(expression) };
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
console.log('Tools created:', Object.keys(tools));
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 6. Model Aliases
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n6. Model Aliases');
|
||||
console.log('----------------');
|
||||
console.log('Available aliases:', Object.keys(MODEL_ALIASES).slice(0, 10), '...');
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 7. Multimodal Message Creation
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n7. Multimodal Message Creation');
|
||||
console.log('------------------------------');
|
||||
|
||||
const imagePart = createImagePart('https://example.com/image.png');
|
||||
console.log('Image part type:', imagePart.type);
|
||||
|
||||
const multimodalMsg = createMultimodalMessage(
|
||||
'What is in this image?',
|
||||
['https://example.com/image.png']
|
||||
);
|
||||
console.log('Multimodal message role:', multimodalMsg.role);
|
||||
console.log('Content parts:', multimodalMsg.content.length);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 8. Agent Loop (Manual Control)
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n8. Agent Loop (Manual Control)');
|
||||
console.log('------------------------------');
|
||||
|
||||
const loop = createAgentLoop({
|
||||
model: 'gpt-4o-mini',
|
||||
system: 'You are a helpful assistant.',
|
||||
tools: {
|
||||
greet: defineTool({
|
||||
description: 'Greet someone',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
required: ['name'],
|
||||
},
|
||||
execute: async ({ name }) => `Hello, ${name}!`,
|
||||
}),
|
||||
},
|
||||
maxSteps: 3,
|
||||
stopWhen: stopWhenNoToolCalls(),
|
||||
});
|
||||
|
||||
console.log('Agent loop created with max 3 steps');
|
||||
console.log('Stop condition: when no tool calls');
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 9. Server Handler Creation
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n9. Server Handler Creation');
|
||||
console.log('--------------------------');
|
||||
|
||||
const expressHandler = createExpressHandler({
|
||||
handler: async (input) => {
|
||||
return { text: `Echo: ${input.message}` };
|
||||
},
|
||||
streaming: false,
|
||||
cors: true,
|
||||
});
|
||||
console.log('Express handler created');
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 10. Next.js Route Handler
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n10. Next.js Route Handler');
|
||||
console.log('-------------------------');
|
||||
|
||||
const routeHandler = createRouteHandler({
|
||||
handler: async (input) => {
|
||||
return { text: `Response: ${input.prompt}` };
|
||||
},
|
||||
streaming: true,
|
||||
});
|
||||
console.log('Next.js route handler created');
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 11. Comparison with Agent Class
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n11. Comparison with Agent Class');
|
||||
console.log('-------------------------------');
|
||||
console.log('The Agent class provides a higher-level abstraction:');
|
||||
console.log('- Automatic tool calling');
|
||||
console.log('- Session persistence');
|
||||
console.log('- Multi-agent orchestration');
|
||||
console.log('');
|
||||
console.log('The AI SDK wrappers provide lower-level control:');
|
||||
console.log('- Direct access to generateText/streamText');
|
||||
console.log('- Manual agent loops');
|
||||
console.log('- Server adapters for various frameworks');
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Summary
|
||||
// -------------------------------------------------------------------------
|
||||
console.log('\n=== Summary ===');
|
||||
console.log('New AI SDK wrapper exports:');
|
||||
console.log('- aiGenerateText, aiStreamText');
|
||||
console.log('- aiGenerateObject, aiStreamObject');
|
||||
console.log('- aiGenerateImage');
|
||||
console.log('- aiEmbed, aiEmbedMany');
|
||||
console.log('- defineTool, createToolSet');
|
||||
console.log('- createAgentLoop');
|
||||
console.log('- createHttpHandler, createExpressHandler, createHonoHandler, createFastifyHandler, createNestHandler');
|
||||
console.log('- createRouteHandler (Next.js)');
|
||||
console.log('- createMCP (MCP client)');
|
||||
console.log('- createSlackBot, createNLPostgres, createComputerUse (integrations)');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* AI SDK Basic Chat Example
|
||||
*
|
||||
* Demonstrates basic text generation using the AI SDK backend.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node examples/ai-sdk/basic-chat.ts
|
||||
*
|
||||
* Required environment variables:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from '../../src/llm/providers/ai-sdk';
|
||||
|
||||
async function main() {
|
||||
console.log('AI SDK Basic Chat Example\n');
|
||||
|
||||
// Create a backend for OpenAI
|
||||
const backend = createAISDKBackend('openai/gpt-4o-mini', {
|
||||
timeout: 30000,
|
||||
maxRetries: 2,
|
||||
});
|
||||
|
||||
console.log(`Provider: ${backend.providerId}`);
|
||||
console.log(`Model: ${backend.modelId}\n`);
|
||||
|
||||
// Generate text
|
||||
const result = await backend.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||
{ role: 'user', content: 'What is the capital of France? Answer in one sentence.' }
|
||||
],
|
||||
temperature: 0.7,
|
||||
maxTokens: 100,
|
||||
});
|
||||
|
||||
console.log('Response:', result.text);
|
||||
console.log('\nUsage:', result.usage);
|
||||
console.log('Finish Reason:', result.finishReason);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* AI SDK Multi-Agent Example
|
||||
*
|
||||
* Demonstrates multi-agent safety with attribution context.
|
||||
* Each agent has its own ID and the context is propagated to LLM calls.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node examples/ai-sdk/multi-agent.ts
|
||||
*
|
||||
* Required environment variables:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from '../../src/llm/providers/ai-sdk';
|
||||
import type { AttributionContext } from '../../src/llm/providers/ai-sdk/types';
|
||||
|
||||
// Simulate multiple agents running in parallel
|
||||
async function runAgent(
|
||||
agentId: string,
|
||||
task: string,
|
||||
attribution: AttributionContext
|
||||
): Promise<{ agentId: string; result: string; duration: number }> {
|
||||
console.log(`[${agentId}] Starting task: "${task}"`);
|
||||
|
||||
// Create backend with attribution context
|
||||
const backend = createAISDKBackend('openai/gpt-4o-mini', {
|
||||
timeout: 30000,
|
||||
attribution,
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
const result = await backend.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: `You are agent ${agentId}. Be concise.` },
|
||||
{ role: 'user', content: task }
|
||||
],
|
||||
temperature: 0.7,
|
||||
maxTokens: 100,
|
||||
});
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
console.log(`[${agentId}] Completed in ${duration}ms`);
|
||||
|
||||
return {
|
||||
agentId,
|
||||
result: result.text,
|
||||
duration
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('AI SDK Multi-Agent Example\n');
|
||||
console.log('Running multiple agents in parallel with attribution tracking...\n');
|
||||
|
||||
// Shared run context
|
||||
const runId = `run-${Date.now()}`;
|
||||
const traceId = `trace-${Math.random().toString(36).slice(2, 10)}`;
|
||||
const sessionId = 'session-demo';
|
||||
|
||||
console.log(`Run ID: ${runId}`);
|
||||
console.log(`Trace ID: ${traceId}`);
|
||||
console.log(`Session ID: ${sessionId}\n`);
|
||||
console.log('---\n');
|
||||
|
||||
// Define agents with their tasks
|
||||
const agents = [
|
||||
{
|
||||
id: 'researcher',
|
||||
task: 'What is the population of Tokyo? Just the number.',
|
||||
attribution: {
|
||||
agentId: 'researcher',
|
||||
runId,
|
||||
traceId,
|
||||
sessionId,
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'analyst',
|
||||
task: 'What is 15% of 1000? Just the number.',
|
||||
attribution: {
|
||||
agentId: 'analyst',
|
||||
runId,
|
||||
traceId,
|
||||
sessionId,
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'writer',
|
||||
task: 'Write a one-sentence tagline for a coffee shop.',
|
||||
attribution: {
|
||||
agentId: 'writer',
|
||||
runId,
|
||||
traceId,
|
||||
sessionId,
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Run all agents in parallel
|
||||
const startTime = Date.now();
|
||||
const results = await Promise.all(
|
||||
agents.map(agent => runAgent(agent.id, agent.task, agent.attribution))
|
||||
);
|
||||
const totalDuration = Date.now() - startTime;
|
||||
|
||||
console.log('\n---\n');
|
||||
console.log('Results:\n');
|
||||
|
||||
for (const result of results) {
|
||||
console.log(`[${result.agentId}] (${result.duration}ms)`);
|
||||
console.log(` ${result.result.trim()}\n`);
|
||||
}
|
||||
|
||||
console.log('---');
|
||||
console.log(`Total parallel execution time: ${totalDuration}ms`);
|
||||
console.log(`Sum of individual times: ${results.reduce((sum, r) => sum + r.duration, 0)}ms`);
|
||||
console.log('\nNote: Attribution headers (X-Agent-Id, X-Run-Id, X-Trace-Id, X-Session-Id)');
|
||||
console.log('are automatically injected into each LLM request for tracing and debugging.');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* AI SDK Multi-Provider Example
|
||||
*
|
||||
* Demonstrates using multiple AI providers with the same interface.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node examples/ai-sdk/multi-provider.ts
|
||||
*
|
||||
* Required environment variables (set at least one):
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
* ANTHROPIC_API_KEY - Your Anthropic API key
|
||||
* GOOGLE_API_KEY - Your Google API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend, validateProviderApiKey } from '../../src/llm/providers/ai-sdk';
|
||||
|
||||
const PROVIDERS = [
|
||||
{ id: 'openai', model: 'gpt-4o-mini' },
|
||||
{ id: 'anthropic', model: 'claude-3-haiku-20240307' },
|
||||
{ id: 'google', model: 'gemini-1.5-flash' },
|
||||
];
|
||||
|
||||
async function main() {
|
||||
console.log('AI SDK Multi-Provider Example\n');
|
||||
console.log('Testing multiple providers with the same prompt...\n');
|
||||
|
||||
const prompt = 'What is 2 + 2? Answer with just the number.';
|
||||
console.log(`Prompt: "${prompt}"\n`);
|
||||
console.log('---\n');
|
||||
|
||||
for (const provider of PROVIDERS) {
|
||||
// Check if API key is available
|
||||
if (!validateProviderApiKey(provider.id)) {
|
||||
console.log(`${provider.id}: Skipped (no API key)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const modelString = `${provider.id}/${provider.model}`;
|
||||
console.log(`${provider.id} (${provider.model}):`);
|
||||
|
||||
const backend = createAISDKBackend(modelString, {
|
||||
timeout: 30000,
|
||||
maxRetries: 1,
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await backend.generateText({
|
||||
messages: [
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
temperature: 0,
|
||||
maxTokens: 10,
|
||||
});
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
console.log(` Response: ${result.text.trim()}`);
|
||||
console.log(` Duration: ${duration}ms`);
|
||||
console.log(` Tokens: ${result.usage?.totalTokens || 'N/A'}`);
|
||||
console.log();
|
||||
} catch (error) {
|
||||
console.log(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* AI SDK Streaming Example
|
||||
*
|
||||
* Demonstrates streaming text generation using the AI SDK backend.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node examples/ai-sdk/streaming.ts
|
||||
*
|
||||
* Required environment variables:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from '../../src/llm/providers/ai-sdk';
|
||||
|
||||
async function main() {
|
||||
console.log('AI SDK Streaming Example\n');
|
||||
|
||||
// Create a backend for OpenAI
|
||||
const backend = createAISDKBackend('openai/gpt-4o-mini', {
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
console.log(`Provider: ${backend.providerId}`);
|
||||
console.log(`Model: ${backend.modelId}\n`);
|
||||
console.log('Streaming response:\n');
|
||||
|
||||
// Stream text
|
||||
const stream = await backend.streamText({
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||
{ role: 'user', content: 'Write a short poem about programming.' }
|
||||
],
|
||||
temperature: 0.8,
|
||||
maxTokens: 200,
|
||||
onToken: (token) => {
|
||||
// This callback is called for each token
|
||||
process.stdout.write(token);
|
||||
},
|
||||
});
|
||||
|
||||
// Consume the stream
|
||||
let totalTokens = 0;
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.text) {
|
||||
// Text is already printed via onToken callback
|
||||
}
|
||||
if (chunk.usage) {
|
||||
totalTokens = chunk.usage.totalTokens || 0;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n\n---');
|
||||
console.log(`Total tokens: ${totalTokens}`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* AI SDK Structured Output Example
|
||||
*
|
||||
* Demonstrates generating structured JSON output using schemas.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node examples/ai-sdk/structured-output.ts
|
||||
*
|
||||
* Required environment variables:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from '../../src/llm/providers/ai-sdk';
|
||||
|
||||
// Define the schema for structured output
|
||||
const recipeSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Name of the recipe'
|
||||
},
|
||||
ingredients: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
amount: { type: 'string' },
|
||||
unit: { type: 'string' }
|
||||
},
|
||||
required: ['name', 'amount']
|
||||
},
|
||||
description: 'List of ingredients'
|
||||
},
|
||||
steps: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Cooking steps'
|
||||
},
|
||||
prepTime: {
|
||||
type: 'number',
|
||||
description: 'Preparation time in minutes'
|
||||
},
|
||||
cookTime: {
|
||||
type: 'number',
|
||||
description: 'Cooking time in minutes'
|
||||
},
|
||||
servings: {
|
||||
type: 'number',
|
||||
description: 'Number of servings'
|
||||
}
|
||||
},
|
||||
required: ['name', 'ingredients', 'steps']
|
||||
};
|
||||
|
||||
interface Recipe {
|
||||
name: string;
|
||||
ingredients: Array<{
|
||||
name: string;
|
||||
amount: string;
|
||||
unit?: string;
|
||||
}>;
|
||||
steps: string[];
|
||||
prepTime?: number;
|
||||
cookTime?: number;
|
||||
servings?: number;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('AI SDK Structured Output Example\n');
|
||||
|
||||
const backend = createAISDKBackend('openai/gpt-4o-mini', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
console.log(`Provider: ${backend.providerId}`);
|
||||
console.log(`Model: ${backend.modelId}\n`);
|
||||
|
||||
console.log('Generating a recipe in structured JSON format...\n');
|
||||
|
||||
const result = await backend.generateObject<Recipe>({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a helpful cooking assistant. Generate recipes in the requested format.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Give me a simple recipe for chocolate chip cookies.'
|
||||
}
|
||||
],
|
||||
schema: recipeSchema,
|
||||
temperature: 0.7,
|
||||
});
|
||||
|
||||
console.log('Generated Recipe:');
|
||||
console.log('================\n');
|
||||
|
||||
const recipe = result.object;
|
||||
|
||||
console.log(`📝 ${recipe.name}`);
|
||||
console.log();
|
||||
|
||||
if (recipe.prepTime || recipe.cookTime) {
|
||||
console.log(`⏱️ Prep: ${recipe.prepTime || 'N/A'} min | Cook: ${recipe.cookTime || 'N/A'} min`);
|
||||
}
|
||||
if (recipe.servings) {
|
||||
console.log(`🍽️ Servings: ${recipe.servings}`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
console.log('Ingredients:');
|
||||
for (const ing of recipe.ingredients) {
|
||||
const unit = ing.unit ? ` ${ing.unit}` : '';
|
||||
console.log(` • ${ing.amount}${unit} ${ing.name}`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
console.log('Steps:');
|
||||
recipe.steps.forEach((step, i) => {
|
||||
console.log(` ${i + 1}. ${step}`);
|
||||
});
|
||||
|
||||
console.log('\n---');
|
||||
console.log('Usage:', result.usage);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* AI SDK Tool Calling Example
|
||||
*
|
||||
* Demonstrates tool calling using the AI SDK backend.
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node examples/ai-sdk/tool-calling.ts
|
||||
*
|
||||
* Required environment variables:
|
||||
* OPENAI_API_KEY - Your OpenAI API key
|
||||
*/
|
||||
|
||||
import { createAISDKBackend } from '../../src/llm/providers/ai-sdk';
|
||||
import type { ToolDefinition, Message } from '../../src/llm/providers/types';
|
||||
|
||||
// Define tools
|
||||
const tools: ToolDefinition[] = [
|
||||
{
|
||||
name: 'get_weather',
|
||||
description: 'Get the current weather for a location',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
location: {
|
||||
type: 'string',
|
||||
description: 'The city and country, e.g., "London, UK"'
|
||||
},
|
||||
unit: {
|
||||
type: 'string',
|
||||
enum: ['celsius', 'fahrenheit'],
|
||||
description: 'Temperature unit'
|
||||
}
|
||||
},
|
||||
required: ['location']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_time',
|
||||
description: 'Get the current time for a timezone',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
timezone: {
|
||||
type: 'string',
|
||||
description: 'The timezone, e.g., "America/New_York"'
|
||||
}
|
||||
},
|
||||
required: ['timezone']
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Mock tool execution
|
||||
function executeTool(name: string, args: Record<string, unknown>): string {
|
||||
switch (name) {
|
||||
case 'get_weather':
|
||||
return JSON.stringify({
|
||||
location: args.location,
|
||||
temperature: 22,
|
||||
unit: args.unit || 'celsius',
|
||||
condition: 'Partly cloudy'
|
||||
});
|
||||
case 'get_time':
|
||||
return JSON.stringify({
|
||||
timezone: args.timezone,
|
||||
time: new Date().toLocaleTimeString('en-US', {
|
||||
timeZone: args.timezone as string
|
||||
})
|
||||
});
|
||||
default:
|
||||
return JSON.stringify({ error: 'Unknown tool' });
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('AI SDK Tool Calling Example\n');
|
||||
|
||||
const backend = createAISDKBackend('openai/gpt-4o-mini', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
console.log(`Provider: ${backend.providerId}`);
|
||||
console.log(`Model: ${backend.modelId}\n`);
|
||||
|
||||
// Initial conversation
|
||||
const messages: Message[] = [
|
||||
{ role: 'system', content: 'You are a helpful assistant with access to weather and time tools.' },
|
||||
{ role: 'user', content: 'What is the weather in London and what time is it in New York?' }
|
||||
];
|
||||
|
||||
console.log('User:', messages[1].content);
|
||||
console.log('\n--- First LLM Call ---\n');
|
||||
|
||||
// First call - model should request tool calls
|
||||
let result = await backend.generateText({
|
||||
messages,
|
||||
tools,
|
||||
toolChoice: 'auto',
|
||||
});
|
||||
|
||||
console.log('Finish Reason:', result.finishReason);
|
||||
|
||||
// Handle tool calls
|
||||
if (result.toolCalls && result.toolCalls.length > 0) {
|
||||
console.log(`\nTool Calls: ${result.toolCalls.length}`);
|
||||
|
||||
// Add assistant message with tool calls
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: null,
|
||||
tool_calls: result.toolCalls
|
||||
});
|
||||
|
||||
// Execute each tool and add results
|
||||
for (const toolCall of result.toolCalls) {
|
||||
const args = JSON.parse(toolCall.function.arguments);
|
||||
console.log(`\n Executing: ${toolCall.function.name}`);
|
||||
console.log(` Arguments: ${JSON.stringify(args)}`);
|
||||
|
||||
const toolResult = executeTool(toolCall.function.name, args);
|
||||
console.log(` Result: ${toolResult}`);
|
||||
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
content: toolResult,
|
||||
tool_call_id: toolCall.id,
|
||||
name: toolCall.function.name
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n--- Second LLM Call (with tool results) ---\n');
|
||||
|
||||
// Second call with tool results
|
||||
result = await backend.generateText({
|
||||
messages,
|
||||
tools,
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Final Response:', result.text);
|
||||
console.log('\nUsage:', result.usage);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* AI SDK Tools Registry Example
|
||||
*
|
||||
* Demonstrates how to use the built-in AI SDK tools with PraisonAI agents.
|
||||
*/
|
||||
|
||||
import { Agent } from '../../src/agent';
|
||||
import { tools, registerBuiltinTools } from '../../src/tools/tools';
|
||||
import { getToolsRegistry } from '../../src/tools/registry/registry';
|
||||
import { createLoggingMiddleware, createTimeoutMiddleware, createRedactionMiddleware } from '../../src/tools/registry/middleware';
|
||||
import type { ToolExecutionContext } from '../../src/tools/registry/types';
|
||||
|
||||
// Register all built-in tools
|
||||
registerBuiltinTools();
|
||||
|
||||
// Example 1: Web Research Agent with Tavily
|
||||
async function webResearchExample() {
|
||||
console.log('\n=== Web Research Agent ===\n');
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'WebResearcher',
|
||||
instructions: 'You are a web research assistant. Search the web to find information.',
|
||||
tools: [tools.tavily()],
|
||||
});
|
||||
|
||||
// Note: Requires TAVILY_API_KEY environment variable
|
||||
// const result = await agent.run('What are the latest developments in AI?');
|
||||
// console.log(result.text);
|
||||
|
||||
console.log('Web research agent created with Tavily search tool.');
|
||||
console.log('Set TAVILY_API_KEY to use this agent.');
|
||||
}
|
||||
|
||||
// Example 2: Code Execution Agent
|
||||
async function codeExecutionExample() {
|
||||
console.log('\n=== Code Execution Agent ===\n');
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'CodeRunner',
|
||||
instructions: 'You can execute Python code to solve computational problems.',
|
||||
tools: [tools.codeExecution()],
|
||||
});
|
||||
|
||||
// Note: Requires Vercel deployment or VERCEL_OIDC_TOKEN
|
||||
// const result = await agent.run('Calculate the first 10 Fibonacci numbers');
|
||||
// console.log(result.text);
|
||||
|
||||
console.log('Code execution agent created.');
|
||||
console.log('Deploy to Vercel or set VERCEL_OIDC_TOKEN to use.');
|
||||
}
|
||||
|
||||
// Example 3: Secure Agent with Guardrails
|
||||
async function secureAgentExample() {
|
||||
console.log('\n=== Secure Agent with Guardrails ===\n');
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'SecureAssistant',
|
||||
instructions: 'You are a secure assistant that protects user data.',
|
||||
tools: [
|
||||
tools.guard(), // Check for prompt injection
|
||||
tools.redact(), // Remove PII from responses
|
||||
tools.verify(), // Verify claims
|
||||
],
|
||||
});
|
||||
|
||||
// Note: Requires SUPERAGENT_API_KEY
|
||||
console.log('Secure agent created with guard, redact, and verify tools.');
|
||||
console.log('Set SUPERAGENT_API_KEY to use security features.');
|
||||
}
|
||||
|
||||
// Example 4: Finance Research Agent with Valyu
|
||||
async function financeResearchExample() {
|
||||
console.log('\n=== Finance Research Agent ===\n');
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'FinanceAnalyst',
|
||||
instructions: 'You are a financial analyst. Research stocks, SEC filings, and economic data.',
|
||||
tools: [
|
||||
tools.valyuFinanceSearch(),
|
||||
tools.valyuSecSearch(),
|
||||
tools.valyuEconomicsSearch(),
|
||||
],
|
||||
});
|
||||
|
||||
// Note: Requires VALYU_API_KEY
|
||||
console.log('Finance research agent created with Valyu tools.');
|
||||
console.log('Set VALYU_API_KEY to use finance search features.');
|
||||
}
|
||||
|
||||
// Example 5: Multi-Tool Agent
|
||||
async function multiToolExample() {
|
||||
console.log('\n=== Multi-Tool Research Agent ===\n');
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'ResearchAssistant',
|
||||
instructions: 'You are a comprehensive research assistant with access to multiple search tools.',
|
||||
tools: [
|
||||
tools.tavily(), // General web search
|
||||
tools.exa(), // Semantic search
|
||||
tools.valyuPaperSearch(), // Academic papers
|
||||
tools.firecrawl(), // Web scraping
|
||||
],
|
||||
});
|
||||
|
||||
console.log('Multi-tool agent created with 4 different search tools.');
|
||||
}
|
||||
|
||||
// Example 6: Using Middleware
|
||||
async function middlewareExample() {
|
||||
console.log('\n=== Registry with Middleware ===\n');
|
||||
|
||||
const registry = getToolsRegistry();
|
||||
|
||||
// Add logging middleware
|
||||
registry.use(createLoggingMiddleware());
|
||||
|
||||
// Add timeout middleware (30 second timeout)
|
||||
registry.use(createTimeoutMiddleware(30000));
|
||||
|
||||
// Add PII redaction middleware
|
||||
registry.use(createRedactionMiddleware());
|
||||
|
||||
// Set hooks for monitoring
|
||||
registry.setHooks({
|
||||
beforeToolCall: (name: string, input: unknown, ctx: ToolExecutionContext) => {
|
||||
console.log(`[Hook] Starting tool: ${name}`);
|
||||
},
|
||||
afterToolCall: (name: string, input: unknown, output: unknown, ctx: ToolExecutionContext) => {
|
||||
console.log(`[Hook] Completed tool: ${name}`);
|
||||
},
|
||||
onError: (name: string, error: Error, ctx: ToolExecutionContext) => {
|
||||
console.error(`[Hook] Error in tool ${name}:`, error.message);
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Registry configured with logging, timeout, and redaction middleware.');
|
||||
}
|
||||
|
||||
// Example 7: Custom Tool Registration
|
||||
async function customToolExample() {
|
||||
console.log('\n=== Custom Tool Registration ===\n');
|
||||
|
||||
// Register a custom tool
|
||||
const customTool = tools.custom({
|
||||
id: 'my-calculator',
|
||||
name: 'calculator',
|
||||
description: 'Perform basic arithmetic calculations',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
expression: {
|
||||
type: 'string',
|
||||
description: 'Mathematical expression to evaluate',
|
||||
},
|
||||
},
|
||||
required: ['expression'],
|
||||
},
|
||||
execute: async (input: { expression: string }) => {
|
||||
// Simple eval for demo (use a proper math parser in production)
|
||||
try {
|
||||
const result = Function(`"use strict"; return (${input.expression})`)();
|
||||
return { result, expression: input.expression };
|
||||
} catch (error) {
|
||||
return { error: 'Invalid expression', expression: input.expression };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'Calculator',
|
||||
instructions: 'You can perform calculations.',
|
||||
tools: [customTool],
|
||||
});
|
||||
|
||||
console.log('Custom calculator tool registered and agent created.');
|
||||
}
|
||||
|
||||
// Example 8: List Available Tools
|
||||
async function listToolsExample() {
|
||||
console.log('\n=== Available Tools ===\n');
|
||||
|
||||
const allTools = tools.list();
|
||||
|
||||
console.log(`Total registered tools: ${allTools.length}\n`);
|
||||
|
||||
for (const tool of allTools) {
|
||||
console.log(`• ${tool.id} (${tool.displayName})`);
|
||||
console.log(` Tags: ${tool.tags.join(', ')}`);
|
||||
console.log(` Package: ${tool.packageName}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
// Run all examples
|
||||
async function main() {
|
||||
console.log('='.repeat(60));
|
||||
console.log('AI SDK Tools Registry Examples');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
await webResearchExample();
|
||||
await codeExecutionExample();
|
||||
await secureAgentExample();
|
||||
await financeResearchExample();
|
||||
await multiToolExample();
|
||||
await middlewareExample();
|
||||
await customToolExample();
|
||||
await listToolsExample();
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('Examples completed!');
|
||||
console.log('='.repeat(60));
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Tavily Search Tool Example
|
||||
*
|
||||
* Demonstrates using Tavily search with PraisonAI agents.
|
||||
*
|
||||
* Run with:
|
||||
* TAVILY_API_KEY=your-key node examples/tools/tavily-search.mjs
|
||||
*/
|
||||
|
||||
import { tavilySearch } from '@tavily/ai-sdk';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Tavily Search Example ===\n');
|
||||
|
||||
// Check for API key
|
||||
if (!process.env.TAVILY_API_KEY) {
|
||||
console.error('ERROR: TAVILY_API_KEY environment variable is required');
|
||||
console.error('Get your API key at: https://tavily.com');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create the Tavily search tool
|
||||
const searchTool = tavilySearch({
|
||||
apiKey: process.env.TAVILY_API_KEY,
|
||||
searchDepth: 'basic',
|
||||
maxResults: 5,
|
||||
includeAnswer: true,
|
||||
});
|
||||
|
||||
console.log('Tool:', searchTool.description);
|
||||
console.log('\nSearching for: "latest AI agent frameworks 2024"\n');
|
||||
|
||||
try {
|
||||
// Execute the search
|
||||
const result = await searchTool.execute(
|
||||
{ query: 'latest AI agent frameworks 2024' },
|
||||
{ toolCallId: 'search-1', messages: [] }
|
||||
);
|
||||
|
||||
console.log('=== Results ===\n');
|
||||
console.log('Query:', result.query);
|
||||
console.log('Response Time:', result.responseTime, 'ms');
|
||||
|
||||
if (result.answer) {
|
||||
console.log('\nAI Answer:', result.answer);
|
||||
}
|
||||
|
||||
console.log('\nTop Results:');
|
||||
for (const r of result.results || []) {
|
||||
console.log(`\n• ${r.title}`);
|
||||
console.log(` URL: ${r.url}`);
|
||||
console.log(` Score: ${r.score?.toFixed(3)}`);
|
||||
console.log(` ${r.content.substring(0, 150)}...`);
|
||||
}
|
||||
|
||||
console.log('\n✅ Search completed successfully!');
|
||||
} catch (error) {
|
||||
console.error('❌ Search failed:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Tavily Search Tool Test
|
||||
*
|
||||
* Tests the Tavily search tool with a real API key.
|
||||
* Run with: TAVILY_API_KEY=your-key npx ts-node examples/tools/tavily-test.ts
|
||||
*/
|
||||
|
||||
import { tavilySearch } from '../../src/tools/builtins/tavily';
|
||||
|
||||
async function main() {
|
||||
console.log('Testing Tavily Search Tool...\n');
|
||||
|
||||
// Check for API key
|
||||
if (!process.env.TAVILY_API_KEY) {
|
||||
console.error('ERROR: TAVILY_API_KEY environment variable is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('API Key found: ' + process.env.TAVILY_API_KEY.substring(0, 10) + '...\n');
|
||||
|
||||
try {
|
||||
// Create the tool
|
||||
const tool = tavilySearch({ includeAnswer: true, maxResults: 3 });
|
||||
|
||||
console.log('Tool created:', tool.name);
|
||||
console.log('Description:', tool.description);
|
||||
console.log('\nExecuting search for "latest AI developments 2024"...\n');
|
||||
|
||||
// Execute the search
|
||||
const result = await tool.execute({ query: 'latest AI developments 2024' });
|
||||
|
||||
console.log('=== Search Results ===\n');
|
||||
|
||||
if (result.answer) {
|
||||
console.log('AI Answer:', result.answer.substring(0, 200) + '...\n');
|
||||
}
|
||||
|
||||
console.log(`Found ${result.results.length} results:\n`);
|
||||
|
||||
for (const r of result.results) {
|
||||
console.log(`- ${r.title}`);
|
||||
console.log(` URL: ${r.url}`);
|
||||
console.log(` Score: ${r.score}`);
|
||||
console.log(` Content: ${r.content.substring(0, 100)}...`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
console.log('Response time:', result.responseTime, 'ms');
|
||||
console.log('\n✅ Tavily search test PASSED!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Test FAILED:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "praisonai",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.0",
|
||||
"description": "PraisonAI TypeScript AI Agents Framework - Node.js, npm, and Javascript AI Agents Framework",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -46,11 +46,15 @@
|
||||
"author": "Mervin Praison",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.1",
|
||||
"@ai-sdk/google": "^3.0.1",
|
||||
"@ai-sdk/openai": "^3.0.1",
|
||||
"@types/figlet": "^1.7.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.22.0",
|
||||
"@typescript-eslint/parser": "^8.22.0",
|
||||
"ai": "^6.0.3",
|
||||
"eslint": "^9.19.0",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "^5.0.5",
|
||||
@@ -60,20 +64,43 @@
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||
"axios": "^1.7.9",
|
||||
"dotenv": "^16.4.7",
|
||||
"fast-xml-parser": "^4.5.1",
|
||||
"node-fetch": "^2.6.9",
|
||||
"openai": "^4.81.0",
|
||||
"@modelcontextprotocol/sdk": "^1.12.1"
|
||||
"openai": "^4.81.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@airweave/sdk": ">=0.1.0",
|
||||
"@mendable/firecrawl-js": ">=1.0.0",
|
||||
"bedrock-agentcore": ">=0.1.0",
|
||||
"boxen": "^7.1.1",
|
||||
"chalk": "^4.1.2",
|
||||
"cli-table3": "^0.6.3",
|
||||
"figlet": "^1.7.0",
|
||||
"ora": "^5.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ai-sdk/anthropic": ">=1.0.0",
|
||||
"@ai-sdk/google": ">=1.0.0",
|
||||
"@ai-sdk/openai": ">=1.0.0",
|
||||
"ai": ">=4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ai": {
|
||||
"optional": true
|
||||
},
|
||||
"@ai-sdk/openai": {
|
||||
"optional": true
|
||||
},
|
||||
"@ai-sdk/anthropic": {
|
||||
"optional": true
|
||||
},
|
||||
"@ai-sdk/google": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"whatwg-url": "^14.1.0",
|
||||
"node-fetch": "^2.6.9"
|
||||
|
||||
Generated
+1597
-26
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,8 @@
|
||||
* Context Agent - Agent with enhanced context management
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
|
||||
import { type LLMProvider, type Message } from '../llm/providers';
|
||||
import { resolveBackend } from '../llm/backend-resolver';
|
||||
import { Session } from '../session';
|
||||
import { KnowledgeBase, type SearchResult } from '../knowledge/rag';
|
||||
|
||||
@@ -30,7 +31,9 @@ export class ContextAgent {
|
||||
readonly name: string;
|
||||
readonly instructions: string;
|
||||
|
||||
private provider: LLMProvider;
|
||||
private provider: LLMProvider | null = null;
|
||||
private providerPromise: Promise<LLMProvider> | null = null;
|
||||
private llmModel: string;
|
||||
private knowledgeBase?: KnowledgeBase;
|
||||
private contextWindow: number;
|
||||
private maxContextTokens: number;
|
||||
@@ -40,13 +43,34 @@ export class ContextAgent {
|
||||
constructor(config: ContextAgentConfig) {
|
||||
this.name = config.name || `ContextAgent_${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.instructions = config.instructions;
|
||||
this.provider = createProvider(config.llm || 'gpt-4o-mini');
|
||||
this.llmModel = config.llm || 'openai/gpt-4o-mini';
|
||||
this.knowledgeBase = config.knowledgeBase;
|
||||
this.contextWindow = config.contextWindow ?? 10;
|
||||
this.maxContextTokens = config.maxContextTokens ?? 4000;
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LLM provider (lazy initialization with AI SDK backend)
|
||||
*/
|
||||
private async getProvider(): Promise<LLMProvider> {
|
||||
if (this.provider) {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
if (!this.providerPromise) {
|
||||
this.providerPromise = (async () => {
|
||||
const result = await resolveBackend(this.llmModel, {
|
||||
attribution: { agentId: this.name },
|
||||
});
|
||||
this.provider = result.provider;
|
||||
return result.provider;
|
||||
})();
|
||||
}
|
||||
|
||||
return this.providerPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat with context awareness
|
||||
*/
|
||||
@@ -83,7 +107,8 @@ export class ContextAgent {
|
||||
];
|
||||
|
||||
// Generate response
|
||||
const result = await this.provider.generateText({ messages });
|
||||
const provider = await this.getProvider();
|
||||
const result = await provider.generateText({ messages });
|
||||
|
||||
// Add assistant message
|
||||
this.messages.push({
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* Enhanced Agent - Agent with session management, provider abstraction, and tool support
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider, type Message, type ToolCall, type GenerateTextResult } from '../llm/providers';
|
||||
import { type LLMProvider, type Message, type ToolCall, type GenerateTextResult } from '../llm/providers';
|
||||
import { resolveBackend } from '../llm/backend-resolver';
|
||||
import { Session, Run, Trace, getSessionManager } from '../session';
|
||||
import { FunctionTool, ToolRegistry, tool } from '../tools/decorator';
|
||||
import { Logger } from '../utils/logger';
|
||||
@@ -51,7 +52,9 @@ export class EnhancedAgent {
|
||||
readonly instructions: string;
|
||||
readonly sessionId: string;
|
||||
|
||||
private provider: LLMProvider;
|
||||
private provider: LLMProvider | null = null;
|
||||
private providerPromise: Promise<LLMProvider> | null = null;
|
||||
private llmModel: string;
|
||||
private session: Session;
|
||||
private toolRegistry: ToolRegistry;
|
||||
private verbose: boolean;
|
||||
@@ -71,8 +74,8 @@ export class EnhancedAgent {
|
||||
this.maxTokens = config.maxTokens;
|
||||
this.outputSchema = config.outputSchema;
|
||||
|
||||
// Initialize provider
|
||||
this.provider = createProvider(config.llm || 'gpt-4o-mini');
|
||||
// Store model string for lazy provider initialization
|
||||
this.llmModel = config.llm || 'openai/gpt-4o-mini';
|
||||
|
||||
// Initialize session
|
||||
if (config.session) {
|
||||
@@ -93,6 +96,30 @@ export class EnhancedAgent {
|
||||
Logger.setVerbose(this.verbose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LLM provider (lazy initialization with AI SDK backend)
|
||||
*/
|
||||
private async getProvider(): Promise<LLMProvider> {
|
||||
if (this.provider) {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
if (!this.providerPromise) {
|
||||
this.providerPromise = (async () => {
|
||||
const result = await resolveBackend(this.llmModel, {
|
||||
attribution: {
|
||||
agentId: this.name,
|
||||
sessionId: this.sessionId,
|
||||
},
|
||||
});
|
||||
this.provider = result.provider;
|
||||
return result.provider;
|
||||
})();
|
||||
}
|
||||
|
||||
return this.providerPromise;
|
||||
}
|
||||
|
||||
private registerTools(tools: EnhancedAgentConfig['tools']): void {
|
||||
if (!tools) return;
|
||||
|
||||
@@ -176,7 +203,8 @@ export class EnhancedAgent {
|
||||
|
||||
if (options.stream && !tools && options.onToken) {
|
||||
// Streaming without tools
|
||||
const stream = await this.provider.streamText({
|
||||
const provider = await this.getProvider();
|
||||
const stream = await provider.streamText({
|
||||
messages,
|
||||
temperature: options.temperature ?? this.temperature,
|
||||
maxTokens: options.maxTokens ?? this.maxTokens,
|
||||
@@ -197,7 +225,8 @@ export class EnhancedAgent {
|
||||
break;
|
||||
} else if (options.outputSchema) {
|
||||
// Structured output
|
||||
const objResult = await this.provider.generateObject({
|
||||
const provider = await this.getProvider();
|
||||
const objResult = await provider.generateObject({
|
||||
messages,
|
||||
schema: options.outputSchema ?? this.outputSchema,
|
||||
temperature: options.temperature ?? this.temperature,
|
||||
@@ -213,7 +242,8 @@ export class EnhancedAgent {
|
||||
break;
|
||||
} else {
|
||||
// Regular generation with potential tool calls
|
||||
result = await this.provider.generateText({
|
||||
const provider = await this.getProvider();
|
||||
result = await provider.generateText({
|
||||
messages,
|
||||
temperature: options.temperature ?? this.temperature,
|
||||
maxTokens: options.maxTokens ?? this.maxTokens,
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* ImageAgent - Agent for image generation and analysis
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
|
||||
import { type LLMProvider, type Message } from '../llm/providers';
|
||||
import { resolveBackend } from '../llm/backend-resolver';
|
||||
|
||||
export interface ImageGenerationConfig {
|
||||
prompt: string;
|
||||
@@ -30,17 +31,40 @@ export interface ImageAgentConfig {
|
||||
*/
|
||||
export class ImageAgent {
|
||||
readonly name: string;
|
||||
private provider: LLMProvider;
|
||||
private provider: LLMProvider | null = null;
|
||||
private providerPromise: Promise<LLMProvider> | null = null;
|
||||
private llmModel: string;
|
||||
private imageModel: string;
|
||||
private verbose: boolean;
|
||||
|
||||
constructor(config: ImageAgentConfig = {}) {
|
||||
this.name = config.name || `ImageAgent_${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
|
||||
this.llmModel = config.llm || 'openai/gpt-4o-mini';
|
||||
this.imageModel = config.imageModel || 'dall-e-3';
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LLM provider (lazy initialization with AI SDK backend)
|
||||
*/
|
||||
private async getProvider(): Promise<LLMProvider> {
|
||||
if (this.provider) {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
if (!this.providerPromise) {
|
||||
this.providerPromise = (async () => {
|
||||
const result = await resolveBackend(this.llmModel, {
|
||||
attribution: { agentId: this.name },
|
||||
});
|
||||
this.provider = result.provider;
|
||||
return result.provider;
|
||||
})();
|
||||
}
|
||||
|
||||
return this.providerPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze an image
|
||||
*/
|
||||
@@ -63,7 +87,8 @@ export class ImageAgent {
|
||||
}
|
||||
];
|
||||
|
||||
const result = await this.provider.generateText({ messages });
|
||||
const provider = await this.getProvider();
|
||||
const result = await provider.generateText({ messages });
|
||||
|
||||
if (this.verbose) {
|
||||
console.log(`[ImageAgent] Analysis: ${result.text.substring(0, 100)}...`);
|
||||
@@ -93,7 +118,8 @@ export class ImageAgent {
|
||||
return this.analyze({ imageUrl, prompt });
|
||||
}
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
const provider = await this.getProvider();
|
||||
const result = await provider.generateText({
|
||||
messages: [{ role: 'user', content: prompt }]
|
||||
});
|
||||
|
||||
@@ -117,7 +143,8 @@ export class ImageAgent {
|
||||
}
|
||||
];
|
||||
|
||||
const result = await this.provider.generateText({ messages });
|
||||
const provider = await this.getProvider();
|
||||
const result = await provider.generateText({ messages });
|
||||
return result.text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* PromptExpanderAgent - Expand and enhance prompts
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider } from '../llm/providers';
|
||||
import { type LLMProvider } from '../llm/providers';
|
||||
import { resolveBackend } from '../llm/backend-resolver';
|
||||
|
||||
export type ExpandStrategy = 'detail' | 'context' | 'examples' | 'constraints' | 'auto';
|
||||
|
||||
@@ -25,17 +26,31 @@ export interface PromptExpanderConfig {
|
||||
*/
|
||||
export class PromptExpanderAgent {
|
||||
readonly name: string;
|
||||
private provider: LLMProvider;
|
||||
private provider: LLMProvider | null = null;
|
||||
private providerPromise: Promise<LLMProvider> | null = null;
|
||||
private llmModel: string;
|
||||
private defaultStrategy: ExpandStrategy;
|
||||
private verbose: boolean;
|
||||
|
||||
constructor(config: PromptExpanderConfig = {}) {
|
||||
this.name = config.name || `PromptExpander_${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
|
||||
this.llmModel = config.llm || 'openai/gpt-4o-mini';
|
||||
this.defaultStrategy = config.defaultStrategy || 'auto';
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
private async getProvider(): Promise<LLMProvider> {
|
||||
if (this.provider) return this.provider;
|
||||
if (!this.providerPromise) {
|
||||
this.providerPromise = (async () => {
|
||||
const result = await resolveBackend(this.llmModel, { attribution: { agentId: this.name } });
|
||||
this.provider = result.provider;
|
||||
return result.provider;
|
||||
})();
|
||||
}
|
||||
return this.providerPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a prompt
|
||||
*/
|
||||
@@ -45,7 +60,8 @@ export class PromptExpanderAgent {
|
||||
|
||||
const systemPrompt = this.getSystemPrompt(actualStrategy);
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
const provider = await this.getProvider();
|
||||
const result = await provider.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: `Expand this prompt: ${prompt}` }
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* QueryRewriterAgent - Rewrite and optimize queries
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider } from '../llm/providers';
|
||||
import { type LLMProvider } from '../llm/providers';
|
||||
import { resolveBackend } from '../llm/backend-resolver';
|
||||
|
||||
export type RewriteStrategy = 'expand' | 'simplify' | 'decompose' | 'rephrase' | 'auto';
|
||||
|
||||
@@ -25,17 +26,31 @@ export interface QueryRewriterConfig {
|
||||
*/
|
||||
export class QueryRewriterAgent {
|
||||
readonly name: string;
|
||||
private provider: LLMProvider;
|
||||
private provider: LLMProvider | null = null;
|
||||
private providerPromise: Promise<LLMProvider> | null = null;
|
||||
private llmModel: string;
|
||||
private defaultStrategy: RewriteStrategy;
|
||||
private verbose: boolean;
|
||||
|
||||
constructor(config: QueryRewriterConfig = {}) {
|
||||
this.name = config.name || `QueryRewriter_${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
|
||||
this.llmModel = config.llm || 'openai/gpt-4o-mini';
|
||||
this.defaultStrategy = config.defaultStrategy || 'auto';
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
private async getProvider(): Promise<LLMProvider> {
|
||||
if (this.provider) return this.provider;
|
||||
if (!this.providerPromise) {
|
||||
this.providerPromise = (async () => {
|
||||
const result = await resolveBackend(this.llmModel, { attribution: { agentId: this.name } });
|
||||
this.provider = result.provider;
|
||||
return result.provider;
|
||||
})();
|
||||
}
|
||||
return this.providerPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a query
|
||||
*/
|
||||
@@ -45,7 +60,8 @@ export class QueryRewriterAgent {
|
||||
|
||||
const prompt = this.buildPrompt(query, actualStrategy);
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
const provider = await this.getProvider();
|
||||
const result = await provider.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: this.getSystemPrompt(actualStrategy) },
|
||||
{ role: 'user', content: prompt }
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* DeepResearchAgent - Agent for comprehensive research tasks
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
|
||||
import { type LLMProvider, type Message } from '../llm/providers';
|
||||
import { resolveBackend } from '../llm/backend-resolver';
|
||||
|
||||
export interface Citation {
|
||||
title: string;
|
||||
@@ -37,19 +38,33 @@ export interface DeepResearchConfig {
|
||||
*/
|
||||
export class DeepResearchAgent {
|
||||
readonly name: string;
|
||||
private provider: LLMProvider;
|
||||
private provider: LLMProvider | null = null;
|
||||
private providerPromise: Promise<LLMProvider> | null = null;
|
||||
private llmModel: string;
|
||||
private maxIterations: number;
|
||||
private searchTool?: (query: string) => Promise<Citation[]>;
|
||||
private verbose: boolean;
|
||||
|
||||
constructor(config: DeepResearchConfig = {}) {
|
||||
this.name = config.name || `ResearchAgent_${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
|
||||
this.llmModel = config.llm || 'openai/gpt-4o-mini';
|
||||
this.maxIterations = config.maxIterations ?? 5;
|
||||
this.searchTool = config.searchTool;
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
private async getProvider(): Promise<LLMProvider> {
|
||||
if (this.provider) return this.provider;
|
||||
if (!this.providerPromise) {
|
||||
this.providerPromise = (async () => {
|
||||
const result = await resolveBackend(this.llmModel, { attribution: { agentId: this.name } });
|
||||
this.provider = result.provider;
|
||||
return result.provider;
|
||||
})();
|
||||
}
|
||||
return this.providerPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conduct deep research on a topic
|
||||
*/
|
||||
@@ -115,7 +130,8 @@ export class DeepResearchAgent {
|
||||
}
|
||||
|
||||
private async generateSearchQueries(query: string): Promise<string[]> {
|
||||
const result = await this.provider.generateText({
|
||||
const provider = await this.getProvider();
|
||||
const result = await provider.generateText({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -133,7 +149,8 @@ export class DeepResearchAgent {
|
||||
? `\n\nRelevant sources:\n${citations.map(c => `- ${c.title}: ${c.snippet || ''}`).join('\n')}`
|
||||
: '';
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
const provider = await this.getProvider();
|
||||
const result = await provider.generateText({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Logger } from '../utils/logger';
|
||||
import type { ChatCompletionTool } from 'openai/resources/chat/completions';
|
||||
import type { DbAdapter, DbMessage, DbRun } from '../db/types';
|
||||
import { randomUUID } from 'crypto';
|
||||
import type { LLMProvider } from '../llm/providers/types';
|
||||
import type { BackendResolutionResult } from '../llm/backend-resolver';
|
||||
|
||||
/**
|
||||
* Agent Configuration
|
||||
@@ -116,6 +118,12 @@ export class Agent {
|
||||
private cacheTTL: number;
|
||||
private responseCache: Map<string, { response: string; timestamp: number }> = new Map();
|
||||
private telemetryEnabled: boolean;
|
||||
|
||||
// AI SDK backend support
|
||||
private _backend: LLMProvider | null = null;
|
||||
private _backendPromise: Promise<BackendResolutionResult> | null = null;
|
||||
private _backendSource: 'ai-sdk' | 'native' | 'custom' | 'legacy' = 'legacy';
|
||||
private _useAISDKBackend: boolean = false;
|
||||
|
||||
constructor(config: SimpleAgentConfig) {
|
||||
// Build instructions from either simple or advanced mode
|
||||
@@ -148,7 +156,16 @@ export class Agent {
|
||||
this.cache = config.cache ?? false;
|
||||
this.cacheTTL = config.cacheTTL ?? 3600;
|
||||
this.telemetryEnabled = config.telemetry ?? false;
|
||||
this.llmService = new OpenAIService(this.llm);
|
||||
|
||||
// Parse model string to extract provider and model ID
|
||||
// Format: "provider/model" or just "model"
|
||||
const providerId = this.llm.includes('/') ? this.llm.split('/')[0] : 'openai';
|
||||
const modelId = this.llm.includes('/') ? this.llm.split('/').slice(1).join('/') : this.llm;
|
||||
|
||||
// For OpenAI, use OpenAIService directly for backward compatibility
|
||||
// For other providers, we'll use the AI SDK backend via getBackend()
|
||||
this._useAISDKBackend = providerId !== 'openai';
|
||||
this.llmService = new OpenAIService(modelId);
|
||||
|
||||
// Configure logging
|
||||
Logger.setVerbose(this.verbose);
|
||||
@@ -207,13 +224,14 @@ export class Agent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a session ID based on current hour and agent name (like Python SDK)
|
||||
* Generate a unique session ID based on current hour, agent name, and random suffix
|
||||
*/
|
||||
private generateSessionId(): string {
|
||||
const now = new Date();
|
||||
const hourStr = now.toISOString().slice(0, 13).replace(/[-T:]/g, '');
|
||||
const hash = this.name ? this.name.slice(0, 6) : 'agent';
|
||||
return `${hourStr}-${hash}`;
|
||||
const randomSuffix = randomUUID().slice(0, 8);
|
||||
return `${hourStr}-${hash}-${randomSuffix}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,8 +450,35 @@ export class Agent {
|
||||
|
||||
let finalResponse = '';
|
||||
|
||||
if (this.stream && !this.tools) {
|
||||
// Use streaming with full conversation history
|
||||
// Use AI SDK backend for non-OpenAI providers
|
||||
if (this._useAISDKBackend) {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (this.stream && !this.tools) {
|
||||
// Streaming with AI SDK backend
|
||||
const stream = await backend.streamText({
|
||||
messages,
|
||||
temperature: 0.7
|
||||
});
|
||||
|
||||
let accumulated = '';
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.text) {
|
||||
process.stdout.write(chunk.text);
|
||||
accumulated += chunk.text;
|
||||
}
|
||||
}
|
||||
finalResponse = accumulated;
|
||||
} else {
|
||||
// Non-streaming with AI SDK backend
|
||||
const result = await backend.generateText({
|
||||
messages,
|
||||
temperature: 0.7
|
||||
});
|
||||
finalResponse = result.text;
|
||||
}
|
||||
} else if (this.stream && !this.tools) {
|
||||
// Use streaming with full conversation history (OpenAI)
|
||||
finalResponse = await this.llmService.streamChat(
|
||||
messages,
|
||||
0.7,
|
||||
@@ -606,6 +651,79 @@ export class Agent {
|
||||
clearCache(): void {
|
||||
this.responseCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resolved backend (AI SDK preferred, native fallback)
|
||||
* Lazy initialization - backend is only resolved on first use
|
||||
*/
|
||||
async getBackend(): Promise<LLMProvider> {
|
||||
if (this._backend) {
|
||||
return this._backend;
|
||||
}
|
||||
|
||||
if (!this._backendPromise) {
|
||||
this._backendPromise = (async () => {
|
||||
const { resolveBackend } = await import('../llm/backend-resolver');
|
||||
const result = await resolveBackend(this.llm, {
|
||||
attribution: {
|
||||
agentId: this.name,
|
||||
runId: this.runId,
|
||||
sessionId: this.sessionId,
|
||||
},
|
||||
});
|
||||
this._backend = result.provider;
|
||||
this._backendSource = result.source;
|
||||
Logger.debug(`Agent ${this.name} using ${result.source} backend for ${this.llm}`);
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
|
||||
const result = await this._backendPromise;
|
||||
return result.provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the backend source (ai-sdk, native, custom, or legacy)
|
||||
*/
|
||||
getBackendSource(): 'ai-sdk' | 'native' | 'custom' | 'legacy' {
|
||||
return this._backendSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed text using AI SDK (preferred) or native provider
|
||||
*
|
||||
* @param text - Text to embed (string or array of strings)
|
||||
* @param options - Embedding options
|
||||
* @returns Embedding vector(s)
|
||||
*
|
||||
* @example Single text
|
||||
* ```typescript
|
||||
* const embedding = await agent.embed("Hello world");
|
||||
* ```
|
||||
*
|
||||
* @example Multiple texts
|
||||
* ```typescript
|
||||
* const embeddings = await agent.embed(["Hello", "World"]);
|
||||
* ```
|
||||
*/
|
||||
async embed(text: string | string[], options?: { model?: string }): Promise<number[] | number[][]> {
|
||||
const { embed, embedMany } = await import('../llm/embeddings');
|
||||
|
||||
if (Array.isArray(text)) {
|
||||
const result = await embedMany(text, { model: options?.model });
|
||||
return result.embeddings;
|
||||
} else {
|
||||
const result = await embed(text, { model: options?.model });
|
||||
return result.embedding;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model string for this agent
|
||||
*/
|
||||
getModel(): string {
|
||||
return this.llm;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* Agent Loop - Manual Agent Loop Control
|
||||
*
|
||||
* Provides utilities for manual control of agent execution loops.
|
||||
*/
|
||||
|
||||
import type { Message, ToolCallPart, ToolResultPart } from './types';
|
||||
|
||||
export interface AgentLoopConfig {
|
||||
/** Model to use */
|
||||
model: string;
|
||||
/** System prompt */
|
||||
system?: string;
|
||||
/** Available tools */
|
||||
tools?: Record<string, AgentTool>;
|
||||
/** Maximum steps (default: 10) */
|
||||
maxSteps?: number;
|
||||
/** Stop condition */
|
||||
stopWhen?: StopCondition;
|
||||
/** On step finish callback */
|
||||
onStepFinish?: (step: AgentStep) => void | Promise<void>;
|
||||
/** On tool call callback (for approval) */
|
||||
onToolCall?: (toolCall: ToolCallInfo) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface AgentTool {
|
||||
description: string;
|
||||
parameters: any;
|
||||
execute: (args: any) => Promise<any>;
|
||||
}
|
||||
|
||||
export interface AgentStep {
|
||||
stepNumber: number;
|
||||
text: string;
|
||||
toolCalls: ToolCallInfo[];
|
||||
toolResults: ToolResultInfo[];
|
||||
usage: { promptTokens: number; completionTokens: number; totalTokens: number };
|
||||
finishReason: string;
|
||||
}
|
||||
|
||||
export interface ToolCallInfo {
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
args: any;
|
||||
}
|
||||
|
||||
export interface ToolResultInfo {
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
result: any;
|
||||
isError?: boolean;
|
||||
}
|
||||
|
||||
export type StopCondition =
|
||||
| { type: 'stepCount'; count: number }
|
||||
| { type: 'noToolCalls' }
|
||||
| { type: 'custom'; check: (step: AgentStep) => boolean };
|
||||
|
||||
export interface AgentLoopResult {
|
||||
text: string;
|
||||
steps: AgentStep[];
|
||||
totalUsage: { promptTokens: number; completionTokens: number; totalTokens: number };
|
||||
finishReason: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a manual agent loop for fine-grained control.
|
||||
*
|
||||
* @example Basic usage
|
||||
* ```typescript
|
||||
* const loop = createAgentLoop({
|
||||
* model: 'gpt-4o',
|
||||
* system: 'You are a helpful assistant',
|
||||
* tools: {
|
||||
* search: {
|
||||
* description: 'Search the web',
|
||||
* parameters: z.object({ query: z.string() }),
|
||||
* execute: async ({ query }) => searchWeb(query)
|
||||
* }
|
||||
* },
|
||||
* maxSteps: 5
|
||||
* });
|
||||
*
|
||||
* const result = await loop.run('Find information about AI');
|
||||
* ```
|
||||
*
|
||||
* @example With approval
|
||||
* ```typescript
|
||||
* const loop = createAgentLoop({
|
||||
* model: 'gpt-4o',
|
||||
* tools: { ... },
|
||||
* onToolCall: async (toolCall) => {
|
||||
* const approved = await askUserForApproval(toolCall);
|
||||
* return approved;
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example Step-by-step control
|
||||
* ```typescript
|
||||
* const loop = createAgentLoop({ model: 'gpt-4o', tools: { ... } });
|
||||
*
|
||||
* // Initialize with a prompt
|
||||
* loop.addMessage({ role: 'user', content: 'Hello' });
|
||||
*
|
||||
* // Run one step at a time
|
||||
* while (!loop.isComplete()) {
|
||||
* const step = await loop.step();
|
||||
* console.log('Step:', step);
|
||||
*
|
||||
* // Optionally modify messages or tools between steps
|
||||
* if (needsMoreContext) {
|
||||
* loop.addMessage({ role: 'user', content: 'Additional context...' });
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* const result = loop.getResult();
|
||||
* ```
|
||||
*/
|
||||
export function createAgentLoop(config: AgentLoopConfig): AgentLoop {
|
||||
return new AgentLoop(config);
|
||||
}
|
||||
|
||||
export class AgentLoop {
|
||||
private config: AgentLoopConfig;
|
||||
private messages: Message[] = [];
|
||||
private steps: AgentStep[] = [];
|
||||
private currentStep = 0;
|
||||
private complete = false;
|
||||
private totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
||||
|
||||
constructor(config: AgentLoopConfig) {
|
||||
this.config = {
|
||||
maxSteps: 10,
|
||||
...config,
|
||||
};
|
||||
|
||||
// Add system message if provided
|
||||
if (config.system) {
|
||||
this.messages.push({ role: 'system', content: config.system });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message to the conversation.
|
||||
*/
|
||||
addMessage(message: Message): void {
|
||||
this.messages.push(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all messages in the conversation.
|
||||
*/
|
||||
getMessages(): Message[] {
|
||||
return [...this.messages];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the loop is complete.
|
||||
*/
|
||||
isComplete(): boolean {
|
||||
return this.complete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single step of the agent loop.
|
||||
*/
|
||||
async step(): Promise<AgentStep> {
|
||||
if (this.complete) {
|
||||
throw new Error('Agent loop is already complete');
|
||||
}
|
||||
|
||||
this.currentStep++;
|
||||
|
||||
// Check max steps
|
||||
if (this.currentStep > (this.config.maxSteps || 10)) {
|
||||
this.complete = true;
|
||||
throw new Error(`Maximum steps (${this.config.maxSteps}) reached`);
|
||||
}
|
||||
|
||||
// Import and call generateText
|
||||
const { generateText } = await import('./generate-text');
|
||||
|
||||
const result = await generateText({
|
||||
model: this.config.model,
|
||||
messages: this.messages as any,
|
||||
tools: this.config.tools,
|
||||
maxSteps: 1, // Single step
|
||||
});
|
||||
|
||||
// Build step result
|
||||
const step: AgentStep = {
|
||||
stepNumber: this.currentStep,
|
||||
text: result.text,
|
||||
toolCalls: result.toolCalls.map(tc => ({
|
||||
toolCallId: tc.toolCallId,
|
||||
toolName: tc.toolName,
|
||||
args: tc.args,
|
||||
})),
|
||||
toolResults: result.toolResults.map(tr => ({
|
||||
toolCallId: tr.toolCallId,
|
||||
toolName: tr.toolName,
|
||||
result: tr.result,
|
||||
})),
|
||||
usage: result.usage,
|
||||
finishReason: result.finishReason,
|
||||
};
|
||||
|
||||
// Update total usage
|
||||
this.totalUsage.promptTokens += result.usage.promptTokens;
|
||||
this.totalUsage.completionTokens += result.usage.completionTokens;
|
||||
this.totalUsage.totalTokens += result.usage.totalTokens;
|
||||
|
||||
// Add assistant message
|
||||
if (result.text) {
|
||||
this.messages.push({ role: 'assistant', content: result.text });
|
||||
}
|
||||
|
||||
// Handle tool calls
|
||||
if (step.toolCalls.length > 0) {
|
||||
// Check for approval if callback provided
|
||||
if (this.config.onToolCall) {
|
||||
for (const toolCall of step.toolCalls) {
|
||||
const approved = await this.config.onToolCall(toolCall);
|
||||
if (!approved) {
|
||||
this.complete = true;
|
||||
step.finishReason = 'tool_rejected';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add tool call message
|
||||
const toolCallParts: ToolCallPart[] = step.toolCalls.map(tc => ({
|
||||
type: 'tool-call',
|
||||
toolCallId: tc.toolCallId,
|
||||
toolName: tc.toolName,
|
||||
args: tc.args,
|
||||
}));
|
||||
this.messages.push({ role: 'assistant', content: '', toolCalls: toolCallParts });
|
||||
|
||||
// Add tool results
|
||||
for (const tr of step.toolResults) {
|
||||
const toolResultPart: ToolResultPart = {
|
||||
type: 'tool-result',
|
||||
toolCallId: tr.toolCallId,
|
||||
toolName: tr.toolName,
|
||||
result: tr.result,
|
||||
};
|
||||
this.messages.push({
|
||||
role: 'tool',
|
||||
content: [toolResultPart],
|
||||
toolCallId: tr.toolCallId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Store step
|
||||
this.steps.push(step);
|
||||
|
||||
// Call step finish callback
|
||||
if (this.config.onStepFinish) {
|
||||
await this.config.onStepFinish(step);
|
||||
}
|
||||
|
||||
// Check stop conditions
|
||||
if (this.shouldStop(step)) {
|
||||
this.complete = true;
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the full agent loop until completion.
|
||||
*/
|
||||
async run(prompt: string): Promise<AgentLoopResult> {
|
||||
// Add user message
|
||||
this.addMessage({ role: 'user', content: prompt });
|
||||
|
||||
// Run steps until complete
|
||||
while (!this.isComplete()) {
|
||||
try {
|
||||
await this.step();
|
||||
} catch (error: any) {
|
||||
if (error.message.includes('Maximum steps')) {
|
||||
break;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return this.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the final result.
|
||||
*/
|
||||
getResult(): AgentLoopResult {
|
||||
const lastStep = this.steps[this.steps.length - 1];
|
||||
return {
|
||||
text: lastStep?.text || '',
|
||||
steps: this.steps,
|
||||
totalUsage: this.totalUsage,
|
||||
finishReason: lastStep?.finishReason || 'unknown',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the loop should stop.
|
||||
*/
|
||||
private shouldStop(step: AgentStep): boolean {
|
||||
// Check custom stop condition
|
||||
if (this.config.stopWhen) {
|
||||
switch (this.config.stopWhen.type) {
|
||||
case 'stepCount':
|
||||
return this.currentStep >= this.config.stopWhen.count;
|
||||
case 'noToolCalls':
|
||||
return step.toolCalls.length === 0;
|
||||
case 'custom':
|
||||
return this.config.stopWhen.check(step);
|
||||
}
|
||||
}
|
||||
|
||||
// Default: stop when no tool calls
|
||||
return step.toolCalls.length === 0 && step.finishReason === 'stop';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the agent loop.
|
||||
*/
|
||||
reset(): void {
|
||||
this.messages = [];
|
||||
this.steps = [];
|
||||
this.currentStep = 0;
|
||||
this.complete = false;
|
||||
this.totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
||||
|
||||
if (this.config.system) {
|
||||
this.messages.push({ role: 'system', content: this.config.system });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stop condition that stops after N steps.
|
||||
*/
|
||||
export function stopAfterSteps(count: number): StopCondition {
|
||||
return { type: 'stepCount', count };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stop condition that stops when no tool calls are made.
|
||||
*/
|
||||
export function stopWhenNoToolCalls(): StopCondition {
|
||||
return { type: 'noToolCalls' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom stop condition.
|
||||
*/
|
||||
export function stopWhen(check: (step: AgentStep) => boolean): StopCondition {
|
||||
return { type: 'custom', check };
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* DevTools - AI SDK v6 DevTools Integration
|
||||
*
|
||||
* Provides optional DevTools integration for debugging AI SDK calls.
|
||||
* This is an optional dependency that provides visibility into LLM calls.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export interface DevToolsConfig {
|
||||
/** Enable DevTools (default: auto-detect from NODE_ENV) */
|
||||
enabled?: boolean;
|
||||
/** DevTools server port (default: 3001) */
|
||||
port?: number;
|
||||
/** DevTools server host (default: localhost) */
|
||||
host?: string;
|
||||
/** Project name for DevTools UI */
|
||||
projectName?: string;
|
||||
/** Custom metadata to include */
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DevToolsState {
|
||||
enabled: boolean;
|
||||
initialized: boolean;
|
||||
port?: number;
|
||||
host?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Global State
|
||||
// ============================================================================
|
||||
|
||||
let devToolsState: DevToolsState = {
|
||||
enabled: false,
|
||||
initialized: false,
|
||||
};
|
||||
|
||||
let devToolsInstance: unknown = null;
|
||||
|
||||
// ============================================================================
|
||||
// DevTools Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Enable AI SDK DevTools for debugging.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Enable in development
|
||||
* if (process.env.NODE_ENV === 'development') {
|
||||
* await enableDevTools();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example With configuration
|
||||
* ```typescript
|
||||
* await enableDevTools({
|
||||
* port: 3001,
|
||||
* projectName: 'My AI App'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function enableDevTools(config?: DevToolsConfig): Promise<void> {
|
||||
if (devToolsState.initialized && devToolsState.enabled) {
|
||||
return; // Already enabled
|
||||
}
|
||||
|
||||
// Check if we should enable
|
||||
const shouldEnable = config?.enabled ??
|
||||
(process.env.NODE_ENV === 'development' || process.env.PRAISONAI_DEVTOOLS === 'true');
|
||||
|
||||
if (!shouldEnable) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to import @ai-sdk/devtools (optional dependency)
|
||||
// @ts-ignore - Optional dependency
|
||||
const devtools = await import('@ai-sdk/devtools');
|
||||
|
||||
if ('enableDevTools' in devtools) {
|
||||
await (devtools as any).enableDevTools({
|
||||
port: config?.port,
|
||||
host: config?.host,
|
||||
projectName: config?.projectName,
|
||||
metadata: config?.metadata,
|
||||
});
|
||||
|
||||
devToolsInstance = devtools;
|
||||
devToolsState = {
|
||||
enabled: true,
|
||||
initialized: true,
|
||||
port: config?.port || 3001,
|
||||
host: config?.host || 'localhost',
|
||||
};
|
||||
|
||||
console.log(`🔧 AI SDK DevTools enabled at http://${devToolsState.host}:${devToolsState.port}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') {
|
||||
console.warn(
|
||||
'⚠️ @ai-sdk/devtools not installed. Install with:\n' +
|
||||
' npm install @ai-sdk/devtools\n' +
|
||||
' or: pnpm add @ai-sdk/devtools'
|
||||
);
|
||||
} else {
|
||||
console.warn('⚠️ Failed to enable DevTools:', error.message);
|
||||
}
|
||||
|
||||
devToolsState = {
|
||||
enabled: false,
|
||||
initialized: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable DevTools.
|
||||
*/
|
||||
export async function disableDevTools(): Promise<void> {
|
||||
if (!devToolsState.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (devToolsInstance && typeof (devToolsInstance as any).disableDevTools === 'function') {
|
||||
await (devToolsInstance as any).disableDevTools();
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors during disable
|
||||
}
|
||||
|
||||
devToolsState = {
|
||||
enabled: false,
|
||||
initialized: true,
|
||||
};
|
||||
devToolsInstance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if DevTools is enabled.
|
||||
*/
|
||||
export function isDevToolsEnabled(): boolean {
|
||||
return devToolsState.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DevTools state.
|
||||
*/
|
||||
export function getDevToolsState(): DevToolsState {
|
||||
return { ...devToolsState };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DevTools URL.
|
||||
*/
|
||||
export function getDevToolsUrl(): string | null {
|
||||
if (!devToolsState.enabled) {
|
||||
return null;
|
||||
}
|
||||
return `http://${devToolsState.host || 'localhost'}:${devToolsState.port || 3001}`;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Middleware for DevTools
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create middleware that logs to DevTools.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const model = wrapModel(openai('gpt-4o'), createDevToolsMiddleware());
|
||||
* ```
|
||||
*/
|
||||
export function createDevToolsMiddleware(): any {
|
||||
return {
|
||||
transformParams: async ({ params }: { params: any }) => {
|
||||
if (!devToolsState.enabled) {
|
||||
return params;
|
||||
}
|
||||
|
||||
// Add DevTools metadata
|
||||
return {
|
||||
...params,
|
||||
experimental_telemetry: {
|
||||
...params.experimental_telemetry,
|
||||
isEnabled: true,
|
||||
metadata: {
|
||||
...params.experimental_telemetry?.metadata,
|
||||
devtools: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Auto-enable in Development
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Auto-enable DevTools if in development mode.
|
||||
* Call this at app startup.
|
||||
*/
|
||||
export async function autoEnableDevTools(): Promise<void> {
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const isExplicitlyEnabled = process.env.PRAISONAI_DEVTOOLS === 'true';
|
||||
const isExplicitlyDisabled = process.env.PRAISONAI_DEVTOOLS === 'false';
|
||||
|
||||
if (isExplicitlyDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDev || isExplicitlyEnabled) {
|
||||
await enableDevTools();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Embed - AI SDK Wrapper
|
||||
*
|
||||
* Provides embed and embedMany functions for text embeddings.
|
||||
*/
|
||||
|
||||
// Lazy load AI SDK
|
||||
let aiSdk: any = null;
|
||||
let aiSdkLoaded = false;
|
||||
|
||||
async function loadAISDK() {
|
||||
if (aiSdkLoaded) return aiSdk;
|
||||
try {
|
||||
aiSdk = await import('ai');
|
||||
aiSdkLoaded = true;
|
||||
return aiSdk;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface EmbedOptions {
|
||||
/** Model to use (e.g., 'text-embedding-3-small', 'openai/text-embedding-3-large') */
|
||||
model: string;
|
||||
/** Text to embed */
|
||||
value: string;
|
||||
/** Maximum retries */
|
||||
maxRetries?: number;
|
||||
/** Abort signal */
|
||||
abortSignal?: AbortSignal;
|
||||
/** Additional headers */
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface EmbedResult {
|
||||
/** Embedding vector */
|
||||
embedding: number[];
|
||||
/** Token usage */
|
||||
usage: {
|
||||
tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EmbedManyOptions {
|
||||
/** Model to use */
|
||||
model: string;
|
||||
/** Texts to embed */
|
||||
values: string[];
|
||||
/** Maximum retries */
|
||||
maxRetries?: number;
|
||||
/** Abort signal */
|
||||
abortSignal?: AbortSignal;
|
||||
/** Additional headers */
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface EmbedManyResult {
|
||||
/** Embedding vectors */
|
||||
embeddings: number[][];
|
||||
/** Token usage */
|
||||
usage: {
|
||||
tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve model string to AI SDK embedding model instance
|
||||
*/
|
||||
async function resolveEmbeddingModel(modelString: string) {
|
||||
let provider = 'openai';
|
||||
let modelId = modelString;
|
||||
|
||||
if (modelString.includes('/')) {
|
||||
const parts = modelString.split('/');
|
||||
provider = parts[0].toLowerCase();
|
||||
modelId = parts.slice(1).join('/');
|
||||
} else {
|
||||
if (modelString.startsWith('text-embedding')) provider = 'openai';
|
||||
else if (modelString.startsWith('voyage')) provider = 'anthropic';
|
||||
else if (modelString.startsWith('embed-')) provider = 'cohere';
|
||||
}
|
||||
|
||||
let providerModule: any;
|
||||
try {
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai.embedding(modelId);
|
||||
case 'google':
|
||||
providerModule = await import('@ai-sdk/google');
|
||||
return providerModule.google.embedding(modelId);
|
||||
default:
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai.embedding(modelId);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to load embedding provider '${provider}': ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed a single text using an embedding model.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await embed({
|
||||
* model: 'text-embedding-3-small',
|
||||
* value: 'Hello, world!'
|
||||
* });
|
||||
* console.log(result.embedding); // [0.1, 0.2, ...]
|
||||
* ```
|
||||
*/
|
||||
export async function embed(options: EmbedOptions): Promise<EmbedResult> {
|
||||
const sdk = await loadAISDK();
|
||||
if (!sdk) {
|
||||
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
|
||||
}
|
||||
|
||||
const model = await resolveEmbeddingModel(options.model);
|
||||
|
||||
const result = await sdk.embed({
|
||||
model,
|
||||
value: options.value,
|
||||
maxRetries: options.maxRetries,
|
||||
abortSignal: options.abortSignal,
|
||||
headers: options.headers,
|
||||
});
|
||||
|
||||
return {
|
||||
embedding: result.embedding,
|
||||
usage: result.usage || { tokens: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed multiple texts using an embedding model.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await embedMany({
|
||||
* model: 'text-embedding-3-small',
|
||||
* values: ['Hello', 'World']
|
||||
* });
|
||||
* console.log(result.embeddings); // [[0.1, ...], [0.2, ...]]
|
||||
* ```
|
||||
*/
|
||||
export async function embedMany(options: EmbedManyOptions): Promise<EmbedManyResult> {
|
||||
const sdk = await loadAISDK();
|
||||
if (!sdk) {
|
||||
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
|
||||
}
|
||||
|
||||
const model = await resolveEmbeddingModel(options.model);
|
||||
|
||||
const result = await sdk.embedMany({
|
||||
model,
|
||||
values: options.values,
|
||||
maxRetries: options.maxRetries,
|
||||
abortSignal: options.abortSignal,
|
||||
headers: options.headers,
|
||||
});
|
||||
|
||||
return {
|
||||
embeddings: result.embeddings,
|
||||
usage: result.usage || { tokens: 0 },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Generate Image - AI SDK Wrapper
|
||||
*
|
||||
* Provides generateImage function for image generation.
|
||||
*/
|
||||
|
||||
// Lazy load AI SDK
|
||||
let aiSdk: any = null;
|
||||
let aiSdkLoaded = false;
|
||||
|
||||
async function loadAISDK() {
|
||||
if (aiSdkLoaded) return aiSdk;
|
||||
try {
|
||||
aiSdk = await import('ai');
|
||||
aiSdkLoaded = true;
|
||||
return aiSdk;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface GenerateImageOptions {
|
||||
/** Model to use (e.g., 'dall-e-3', 'google/imagen-3') */
|
||||
model: string;
|
||||
/** Text prompt for image generation */
|
||||
prompt: string;
|
||||
/** Number of images to generate */
|
||||
n?: number;
|
||||
/** Image size (e.g., '1024x1024') */
|
||||
size?: `${number}x${number}`;
|
||||
/** Aspect ratio (e.g., '16:9') */
|
||||
aspectRatio?: `${number}:${number}`;
|
||||
/** Seed for reproducibility */
|
||||
seed?: number;
|
||||
/** Maximum retries */
|
||||
maxRetries?: number;
|
||||
/** Abort signal */
|
||||
abortSignal?: AbortSignal;
|
||||
/** Additional headers */
|
||||
headers?: Record<string, string>;
|
||||
/** Provider-specific options */
|
||||
providerOptions?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface GeneratedImage {
|
||||
/** Base64-encoded image data */
|
||||
base64: string;
|
||||
/** Image as Uint8Array */
|
||||
uint8Array: Uint8Array;
|
||||
/** MIME type */
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
export interface GenerateImageResult {
|
||||
/** Generated images */
|
||||
images: GeneratedImage[];
|
||||
/** Warnings */
|
||||
warnings?: any[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve model string to AI SDK image model instance
|
||||
*/
|
||||
async function resolveImageModel(modelString: string) {
|
||||
let provider = 'openai';
|
||||
let modelId = modelString;
|
||||
|
||||
if (modelString.includes('/')) {
|
||||
const parts = modelString.split('/');
|
||||
provider = parts[0].toLowerCase();
|
||||
modelId = parts.slice(1).join('/');
|
||||
} else {
|
||||
if (modelString.startsWith('imagen')) provider = 'google';
|
||||
else if (modelString.startsWith('dall-e')) provider = 'openai';
|
||||
}
|
||||
|
||||
let providerModule: any;
|
||||
try {
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai.image(modelId);
|
||||
case 'google':
|
||||
providerModule = await import('@ai-sdk/google');
|
||||
return providerModule.google.image(modelId);
|
||||
default:
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai.image(modelId);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to load image provider '${provider}': ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate images using an image model.
|
||||
*
|
||||
* @example Generate with DALL-E
|
||||
* ```typescript
|
||||
* const result = await generateImage({
|
||||
* model: 'dall-e-3',
|
||||
* prompt: 'A futuristic city at sunset',
|
||||
* size: '1024x1024'
|
||||
* });
|
||||
*
|
||||
* // Save the image
|
||||
* fs.writeFileSync('image.png', result.images[0].uint8Array);
|
||||
* ```
|
||||
*
|
||||
* @example Generate with Google Imagen
|
||||
* ```typescript
|
||||
* const result = await generateImage({
|
||||
* model: 'google/imagen-3',
|
||||
* prompt: 'A beautiful landscape',
|
||||
* aspectRatio: '16:9'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function generateImage(options: GenerateImageOptions): Promise<GenerateImageResult> {
|
||||
const sdk = await loadAISDK();
|
||||
if (!sdk) {
|
||||
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
|
||||
}
|
||||
|
||||
const model = await resolveImageModel(options.model);
|
||||
|
||||
const result = await sdk.generateImage({
|
||||
model,
|
||||
prompt: options.prompt,
|
||||
n: options.n,
|
||||
size: options.size,
|
||||
aspectRatio: options.aspectRatio,
|
||||
seed: options.seed,
|
||||
maxRetries: options.maxRetries,
|
||||
abortSignal: options.abortSignal,
|
||||
headers: options.headers,
|
||||
providerOptions: options.providerOptions,
|
||||
});
|
||||
|
||||
return {
|
||||
images: result.images || [],
|
||||
warnings: result.warnings,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Generate Object - AI SDK Wrapper
|
||||
*
|
||||
* Provides generateObject and streamObject functions for structured output generation.
|
||||
*/
|
||||
|
||||
import type { Message } from './types';
|
||||
|
||||
// Lazy load AI SDK
|
||||
let aiSdk: any = null;
|
||||
let aiSdkLoaded = false;
|
||||
|
||||
async function loadAISDK() {
|
||||
if (aiSdkLoaded) return aiSdk;
|
||||
try {
|
||||
aiSdk = await import('ai');
|
||||
aiSdkLoaded = true;
|
||||
return aiSdk;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface GenerateObjectOptions<T = unknown> {
|
||||
/** Model to use */
|
||||
model: string;
|
||||
/** Schema for the output (Zod schema or JSON schema) */
|
||||
schema: any;
|
||||
/** Schema name for the output */
|
||||
schemaName?: string;
|
||||
/** Schema description */
|
||||
schemaDescription?: string;
|
||||
/** Output mode: 'object' | 'array' | 'enum' | 'no-schema' */
|
||||
mode?: 'object' | 'array' | 'enum' | 'no-schema';
|
||||
/** Simple text prompt */
|
||||
prompt?: string;
|
||||
/** Chat messages */
|
||||
messages?: Message[];
|
||||
/** System message */
|
||||
system?: string;
|
||||
/** Maximum tokens */
|
||||
maxTokens?: number;
|
||||
/** Temperature */
|
||||
temperature?: number;
|
||||
/** Top P */
|
||||
topP?: number;
|
||||
/** Maximum retries */
|
||||
maxRetries?: number;
|
||||
/** Abort signal */
|
||||
abortSignal?: AbortSignal;
|
||||
/** Additional headers */
|
||||
headers?: Record<string, string>;
|
||||
/** Callback on finish */
|
||||
onFinish?: (result: GenerateObjectResult<T>) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface GenerateObjectResult<T = unknown> {
|
||||
/** Generated object */
|
||||
object: T;
|
||||
/** Token usage */
|
||||
usage: {
|
||||
promptTokens: number;
|
||||
completionTokens: number;
|
||||
totalTokens: number;
|
||||
};
|
||||
/** Finish reason */
|
||||
finishReason: string;
|
||||
/** Warnings */
|
||||
warnings?: any[];
|
||||
}
|
||||
|
||||
export interface StreamObjectOptions<T = unknown> extends GenerateObjectOptions<T> {
|
||||
/** Callback for partial object updates */
|
||||
onPartialObject?: (partial: Partial<T>) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface StreamObjectResult<T = unknown> {
|
||||
/** Async iterator for partial objects */
|
||||
partialObjectStream: AsyncIterable<Partial<T>>;
|
||||
/** Promise that resolves to the final object */
|
||||
object: Promise<T>;
|
||||
/** Promise that resolves to usage */
|
||||
usage: Promise<{
|
||||
promptTokens: number;
|
||||
completionTokens: number;
|
||||
totalTokens: number;
|
||||
}>;
|
||||
/** Promise that resolves to finish reason */
|
||||
finishReason: Promise<string>;
|
||||
/** Convert to Response for streaming */
|
||||
toTextStreamResponse(options?: { headers?: Record<string, string> }): Response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve model string to AI SDK model instance
|
||||
*/
|
||||
async function resolveModel(modelString: string) {
|
||||
let provider = 'openai';
|
||||
let modelId = modelString;
|
||||
|
||||
if (modelString.includes('/')) {
|
||||
const parts = modelString.split('/');
|
||||
provider = parts[0].toLowerCase();
|
||||
modelId = parts.slice(1).join('/');
|
||||
} else {
|
||||
if (modelString.startsWith('claude')) provider = 'anthropic';
|
||||
else if (modelString.startsWith('gemini')) provider = 'google';
|
||||
}
|
||||
|
||||
let providerModule: any;
|
||||
try {
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai(modelId);
|
||||
case 'anthropic':
|
||||
providerModule = await import('@ai-sdk/anthropic');
|
||||
return providerModule.anthropic(modelId);
|
||||
case 'google':
|
||||
providerModule = await import('@ai-sdk/google');
|
||||
return providerModule.google(modelId);
|
||||
default:
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai(modelId);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to load provider '${provider}': ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a structured object using a language model.
|
||||
*
|
||||
* @example With Zod schema
|
||||
* ```typescript
|
||||
* import { z } from 'zod';
|
||||
*
|
||||
* const result = await generateObject({
|
||||
* model: 'gpt-4o',
|
||||
* schema: z.object({
|
||||
* name: z.string(),
|
||||
* age: z.number()
|
||||
* }),
|
||||
* prompt: 'Generate a person'
|
||||
* });
|
||||
* console.log(result.object); // { name: 'John', age: 30 }
|
||||
* ```
|
||||
*
|
||||
* @example With JSON schema
|
||||
* ```typescript
|
||||
* const result = await generateObject({
|
||||
* model: 'gpt-4o',
|
||||
* schema: {
|
||||
* type: 'object',
|
||||
* properties: {
|
||||
* name: { type: 'string' },
|
||||
* age: { type: 'number' }
|
||||
* },
|
||||
* required: ['name', 'age']
|
||||
* },
|
||||
* prompt: 'Generate a person'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function generateObject<T = unknown>(
|
||||
options: GenerateObjectOptions<T>
|
||||
): Promise<GenerateObjectResult<T>> {
|
||||
const sdk = await loadAISDK();
|
||||
if (!sdk) {
|
||||
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
|
||||
}
|
||||
|
||||
const model = await resolveModel(options.model);
|
||||
|
||||
const result = await sdk.generateObject({
|
||||
model,
|
||||
schema: options.schema,
|
||||
schemaName: options.schemaName,
|
||||
schemaDescription: options.schemaDescription,
|
||||
mode: options.mode,
|
||||
prompt: options.prompt,
|
||||
messages: options.messages,
|
||||
system: options.system,
|
||||
maxTokens: options.maxTokens,
|
||||
temperature: options.temperature,
|
||||
topP: options.topP,
|
||||
maxRetries: options.maxRetries,
|
||||
abortSignal: options.abortSignal,
|
||||
headers: options.headers,
|
||||
onFinish: options.onFinish,
|
||||
});
|
||||
|
||||
return {
|
||||
object: result.object as T,
|
||||
usage: result.usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
||||
finishReason: result.finishReason || 'stop',
|
||||
warnings: result.warnings,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream a structured object using a language model.
|
||||
*
|
||||
* @example Streaming partial objects
|
||||
* ```typescript
|
||||
* import { z } from 'zod';
|
||||
*
|
||||
* const result = await streamObject({
|
||||
* model: 'gpt-4o',
|
||||
* schema: z.object({
|
||||
* story: z.string(),
|
||||
* characters: z.array(z.string())
|
||||
* }),
|
||||
* prompt: 'Write a short story',
|
||||
* onPartialObject: (partial) => {
|
||||
* console.log('Partial:', partial);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* const finalObject = await result.object;
|
||||
* ```
|
||||
*/
|
||||
export async function streamObject<T = unknown>(
|
||||
options: StreamObjectOptions<T>
|
||||
): Promise<StreamObjectResult<T>> {
|
||||
const sdk = await loadAISDK();
|
||||
if (!sdk) {
|
||||
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
|
||||
}
|
||||
|
||||
const model = await resolveModel(options.model);
|
||||
|
||||
const result = sdk.streamObject({
|
||||
model,
|
||||
schema: options.schema,
|
||||
schemaName: options.schemaName,
|
||||
schemaDescription: options.schemaDescription,
|
||||
mode: options.mode,
|
||||
prompt: options.prompt,
|
||||
messages: options.messages,
|
||||
system: options.system,
|
||||
maxTokens: options.maxTokens,
|
||||
temperature: options.temperature,
|
||||
topP: options.topP,
|
||||
maxRetries: options.maxRetries,
|
||||
abortSignal: options.abortSignal,
|
||||
headers: options.headers,
|
||||
onFinish: options.onFinish,
|
||||
});
|
||||
|
||||
return result as StreamObjectResult<T>;
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* Generate Text - AI SDK Wrapper
|
||||
*
|
||||
* Provides generateText and streamText functions that wrap the AI SDK.
|
||||
* These are the primary text generation primitives.
|
||||
*/
|
||||
|
||||
import type { Message } from './types';
|
||||
|
||||
// Lazy load AI SDK to avoid import errors when not installed
|
||||
let aiSdk: any = null;
|
||||
let aiSdkLoaded = false;
|
||||
|
||||
async function loadAISDK() {
|
||||
if (aiSdkLoaded) return aiSdk;
|
||||
try {
|
||||
aiSdk = await import('ai');
|
||||
aiSdkLoaded = true;
|
||||
return aiSdk;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface GenerateTextOptions {
|
||||
/** Model to use (e.g., 'gpt-4o', 'claude-3-sonnet', 'openai/gpt-4o') */
|
||||
model: string;
|
||||
/** Simple text prompt */
|
||||
prompt?: string;
|
||||
/** Chat messages (alternative to prompt) */
|
||||
messages?: Message[];
|
||||
/** System message */
|
||||
system?: string;
|
||||
/** Tools available to the model */
|
||||
tools?: Record<string, any>;
|
||||
/** Tool choice strategy */
|
||||
toolChoice?: 'auto' | 'none' | 'required' | { type: 'tool'; toolName: string };
|
||||
/** Maximum tokens to generate */
|
||||
maxTokens?: number;
|
||||
/** Temperature (0-2) */
|
||||
temperature?: number;
|
||||
/** Top P (0-1) */
|
||||
topP?: number;
|
||||
/** Top K */
|
||||
topK?: number;
|
||||
/** Presence penalty */
|
||||
presencePenalty?: number;
|
||||
/** Frequency penalty */
|
||||
frequencyPenalty?: number;
|
||||
/** Stop sequences */
|
||||
stopSequences?: string[];
|
||||
/** Seed for reproducibility */
|
||||
seed?: number;
|
||||
/** Maximum retries */
|
||||
maxRetries?: number;
|
||||
/** Abort signal */
|
||||
abortSignal?: AbortSignal;
|
||||
/** Additional headers */
|
||||
headers?: Record<string, string>;
|
||||
/** Maximum steps for tool calling */
|
||||
maxSteps?: number;
|
||||
/** Callback on step finish */
|
||||
onStepFinish?: (step: StepResult) => void | Promise<void>;
|
||||
/** Callback on finish */
|
||||
onFinish?: (result: GenerateTextResult) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface StepResult {
|
||||
text: string;
|
||||
toolCalls: ToolCall[];
|
||||
toolResults: ToolResult[];
|
||||
usage: TokenUsage;
|
||||
finishReason: string;
|
||||
}
|
||||
|
||||
export interface ToolCall {
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
args: any;
|
||||
}
|
||||
|
||||
export interface ToolResult {
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
result: any;
|
||||
}
|
||||
|
||||
export interface TokenUsage {
|
||||
promptTokens: number;
|
||||
completionTokens: number;
|
||||
totalTokens: number;
|
||||
}
|
||||
|
||||
export interface GenerateTextResult {
|
||||
/** Generated text */
|
||||
text: string;
|
||||
/** Tool calls made */
|
||||
toolCalls: ToolCall[];
|
||||
/** Tool results */
|
||||
toolResults: ToolResult[];
|
||||
/** Token usage */
|
||||
usage: TokenUsage;
|
||||
/** Finish reason */
|
||||
finishReason: string;
|
||||
/** All steps (for multi-step) */
|
||||
steps: StepResult[];
|
||||
/** Response messages */
|
||||
responseMessages: Message[];
|
||||
/** Warnings */
|
||||
warnings?: any[];
|
||||
}
|
||||
|
||||
export interface StreamTextOptions extends GenerateTextOptions {
|
||||
/** Callback for each text chunk */
|
||||
onChunk?: (chunk: TextStreamPart) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface TextStreamPart {
|
||||
type: 'text-delta' | 'tool-call' | 'tool-result' | 'finish' | 'error';
|
||||
textDelta?: string;
|
||||
toolCall?: ToolCall;
|
||||
toolResult?: ToolResult;
|
||||
finishReason?: string;
|
||||
usage?: TokenUsage;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export interface StreamTextResult {
|
||||
/** Async iterator for text chunks */
|
||||
textStream: AsyncIterable<string>;
|
||||
/** Full text stream with all events */
|
||||
fullStream: AsyncIterable<TextStreamPart>;
|
||||
/** Promise that resolves to the final text */
|
||||
text: Promise<string>;
|
||||
/** Promise that resolves to tool calls */
|
||||
toolCalls: Promise<ToolCall[]>;
|
||||
/** Promise that resolves to tool results */
|
||||
toolResults: Promise<ToolResult[]>;
|
||||
/** Promise that resolves to usage */
|
||||
usage: Promise<TokenUsage>;
|
||||
/** Promise that resolves to finish reason */
|
||||
finishReason: Promise<string>;
|
||||
/** Promise that resolves to all steps */
|
||||
steps: Promise<StepResult[]>;
|
||||
/** Promise that resolves to response messages */
|
||||
responseMessages: Promise<Message[]>;
|
||||
/** Convert to Response object for streaming */
|
||||
toDataStreamResponse(options?: { headers?: Record<string, string> }): Response;
|
||||
/** Pipe to a writable stream */
|
||||
pipeDataStreamToResponse(response: any, options?: { headers?: Record<string, string> }): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve model string to AI SDK model instance
|
||||
*/
|
||||
async function resolveModel(modelString: string) {
|
||||
const sdk = await loadAISDK();
|
||||
if (!sdk) {
|
||||
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
|
||||
}
|
||||
|
||||
// Parse provider/model format
|
||||
let provider = 'openai';
|
||||
let modelId = modelString;
|
||||
|
||||
if (modelString.includes('/')) {
|
||||
const parts = modelString.split('/');
|
||||
provider = parts[0].toLowerCase();
|
||||
modelId = parts.slice(1).join('/');
|
||||
} else {
|
||||
// Infer provider from model name
|
||||
if (modelString.startsWith('claude')) {
|
||||
provider = 'anthropic';
|
||||
} else if (modelString.startsWith('gemini')) {
|
||||
provider = 'google';
|
||||
} else if (modelString.startsWith('gpt') || modelString.startsWith('o1') || modelString.startsWith('o3')) {
|
||||
provider = 'openai';
|
||||
}
|
||||
}
|
||||
|
||||
// Load provider
|
||||
let providerModule: any;
|
||||
try {
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai(modelId);
|
||||
case 'anthropic':
|
||||
providerModule = await import('@ai-sdk/anthropic');
|
||||
return providerModule.anthropic(modelId);
|
||||
case 'google':
|
||||
providerModule = await import('@ai-sdk/google');
|
||||
return providerModule.google(modelId);
|
||||
default:
|
||||
// Try OpenAI-compatible
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai(modelId);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to load provider '${provider}': ${error.message}. Install with: npm install @ai-sdk/${provider}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate text using a language model.
|
||||
*
|
||||
* @example Simple prompt
|
||||
* ```typescript
|
||||
* const result = await generateText({
|
||||
* model: 'gpt-4o',
|
||||
* prompt: 'What is the capital of France?'
|
||||
* });
|
||||
* console.log(result.text);
|
||||
* ```
|
||||
*
|
||||
* @example Chat messages
|
||||
* ```typescript
|
||||
* const result = await generateText({
|
||||
* model: 'claude-3-sonnet',
|
||||
* messages: [
|
||||
* { role: 'user', content: 'Hello!' }
|
||||
* ]
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example With tools
|
||||
* ```typescript
|
||||
* const result = await generateText({
|
||||
* model: 'gpt-4o',
|
||||
* prompt: 'What is the weather in Paris?',
|
||||
* tools: {
|
||||
* getWeather: {
|
||||
* description: 'Get weather for a city',
|
||||
* parameters: z.object({ city: z.string() }),
|
||||
* execute: async ({ city }) => `Weather in ${city}: 20°C`
|
||||
* }
|
||||
* },
|
||||
* maxSteps: 5
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function generateText(options: GenerateTextOptions): Promise<GenerateTextResult> {
|
||||
const sdk = await loadAISDK();
|
||||
if (!sdk) {
|
||||
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
|
||||
}
|
||||
|
||||
// Handle both string model names and pre-resolved model objects
|
||||
const model = typeof options.model === 'string'
|
||||
? await resolveModel(options.model)
|
||||
: options.model;
|
||||
|
||||
const result = await sdk.generateText({
|
||||
model,
|
||||
prompt: options.prompt,
|
||||
messages: options.messages,
|
||||
system: options.system,
|
||||
tools: options.tools,
|
||||
toolChoice: options.toolChoice,
|
||||
maxTokens: options.maxTokens,
|
||||
temperature: options.temperature,
|
||||
topP: options.topP,
|
||||
topK: options.topK,
|
||||
presencePenalty: options.presencePenalty,
|
||||
frequencyPenalty: options.frequencyPenalty,
|
||||
stopSequences: options.stopSequences,
|
||||
seed: options.seed,
|
||||
maxRetries: options.maxRetries,
|
||||
abortSignal: options.abortSignal,
|
||||
headers: options.headers,
|
||||
maxSteps: options.maxSteps,
|
||||
onStepFinish: options.onStepFinish,
|
||||
onFinish: options.onFinish,
|
||||
});
|
||||
|
||||
return {
|
||||
text: result.text,
|
||||
toolCalls: result.toolCalls || [],
|
||||
toolResults: result.toolResults || [],
|
||||
usage: result.usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
||||
finishReason: result.finishReason || 'stop',
|
||||
steps: result.steps || [],
|
||||
responseMessages: result.responseMessages || [],
|
||||
warnings: result.warnings,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream text using a language model.
|
||||
*
|
||||
* @example Simple streaming
|
||||
* ```typescript
|
||||
* const result = await streamText({
|
||||
* model: 'gpt-4o',
|
||||
* prompt: 'Write a poem about AI'
|
||||
* });
|
||||
*
|
||||
* for await (const chunk of result.textStream) {
|
||||
* process.stdout.write(chunk);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example With tools and multi-step
|
||||
* ```typescript
|
||||
* const result = await streamText({
|
||||
* model: 'gpt-4o',
|
||||
* prompt: 'Search for AI news and summarize',
|
||||
* tools: { webSearch },
|
||||
* maxSteps: 5,
|
||||
* onChunk: (chunk) => console.log(chunk)
|
||||
* });
|
||||
*
|
||||
* const text = await result.text;
|
||||
* ```
|
||||
*/
|
||||
export async function streamText(options: StreamTextOptions): Promise<StreamTextResult> {
|
||||
const sdk = await loadAISDK();
|
||||
if (!sdk) {
|
||||
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
|
||||
}
|
||||
|
||||
// Handle both string model names and pre-resolved model objects
|
||||
const model = typeof options.model === 'string'
|
||||
? await resolveModel(options.model)
|
||||
: options.model;
|
||||
|
||||
const result = sdk.streamText({
|
||||
model,
|
||||
prompt: options.prompt,
|
||||
messages: options.messages,
|
||||
system: options.system,
|
||||
tools: options.tools,
|
||||
toolChoice: options.toolChoice,
|
||||
maxTokens: options.maxTokens,
|
||||
temperature: options.temperature,
|
||||
topP: options.topP,
|
||||
topK: options.topK,
|
||||
presencePenalty: options.presencePenalty,
|
||||
frequencyPenalty: options.frequencyPenalty,
|
||||
stopSequences: options.stopSequences,
|
||||
seed: options.seed,
|
||||
maxRetries: options.maxRetries,
|
||||
abortSignal: options.abortSignal,
|
||||
headers: options.headers,
|
||||
maxSteps: options.maxSteps,
|
||||
onStepFinish: options.onStepFinish,
|
||||
onFinish: options.onFinish,
|
||||
onChunk: options.onChunk ? async (chunk: any) => {
|
||||
await options.onChunk!(chunk as TextStreamPart);
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
return result as StreamTextResult;
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* AI SDK Wrapper Module
|
||||
*
|
||||
* This module provides a stable wrapper over the AI SDK (aisdk) primitives.
|
||||
* It isolates aisdk imports and provides typed wrappers to ensure future
|
||||
* aisdk upgrades don't break praisonai-ts users.
|
||||
*
|
||||
* @module ai
|
||||
*/
|
||||
|
||||
// Re-export core execution functions
|
||||
export {
|
||||
generateText,
|
||||
streamText,
|
||||
type GenerateTextOptions,
|
||||
type GenerateTextResult,
|
||||
type StreamTextOptions,
|
||||
type StreamTextResult,
|
||||
type TextStreamPart,
|
||||
} from './generate-text';
|
||||
|
||||
export {
|
||||
generateObject,
|
||||
streamObject,
|
||||
type GenerateObjectOptions,
|
||||
type GenerateObjectResult,
|
||||
type StreamObjectOptions,
|
||||
type StreamObjectResult,
|
||||
} from './generate-object';
|
||||
|
||||
export {
|
||||
generateImage,
|
||||
type GenerateImageOptions,
|
||||
type GenerateImageResult,
|
||||
} from './generate-image';
|
||||
|
||||
export {
|
||||
embed,
|
||||
embedMany,
|
||||
type EmbedOptions,
|
||||
type EmbedResult,
|
||||
type EmbedManyResult,
|
||||
} from './embed';
|
||||
|
||||
// Re-export tool utilities
|
||||
export {
|
||||
defineTool,
|
||||
createToolSet,
|
||||
functionToTool,
|
||||
type ToolDefinition,
|
||||
type ToolExecuteFunction,
|
||||
type ToolInput,
|
||||
type ToolOutput,
|
||||
} from './tools';
|
||||
|
||||
// Re-export model utilities
|
||||
export {
|
||||
createModel,
|
||||
getModel,
|
||||
parseModel,
|
||||
MODEL_ALIASES,
|
||||
listModelAliases,
|
||||
hasModelAlias,
|
||||
resolveModelAlias,
|
||||
type ModelConfig,
|
||||
type ModelId,
|
||||
} from './models';
|
||||
|
||||
// Re-export middleware
|
||||
export {
|
||||
createCachingMiddleware,
|
||||
createLoggingMiddleware,
|
||||
wrapModel,
|
||||
applyMiddleware,
|
||||
clearCache,
|
||||
getCacheStats,
|
||||
type Middleware,
|
||||
type MiddlewareConfig,
|
||||
type MiddlewareRequest,
|
||||
type MiddlewareResponse,
|
||||
} from './middleware';
|
||||
|
||||
// Re-export multimodal utilities
|
||||
export {
|
||||
createImagePart,
|
||||
createFilePart,
|
||||
createPdfPart,
|
||||
createTextPart,
|
||||
createMultimodalMessage,
|
||||
toMessageContent,
|
||||
base64ToUint8Array,
|
||||
uint8ArrayToBase64,
|
||||
isUrl,
|
||||
isDataUrl,
|
||||
type InputPart,
|
||||
type ImagePart,
|
||||
type FilePart,
|
||||
type PdfPart,
|
||||
type TextPart,
|
||||
} from './multimodal';
|
||||
|
||||
// Re-export MCP client
|
||||
export {
|
||||
createMCP,
|
||||
getMCPClient,
|
||||
closeMCPClient,
|
||||
closeAllMCPClients,
|
||||
mcpToolsToAITools,
|
||||
type MCPConfig,
|
||||
type MCPClient,
|
||||
type MCPTool,
|
||||
type MCPResource,
|
||||
type MCPPrompt,
|
||||
} from './mcp';
|
||||
|
||||
// Re-export server adapters
|
||||
export {
|
||||
createHttpHandler,
|
||||
createExpressHandler,
|
||||
createHonoHandler,
|
||||
createFastifyHandler,
|
||||
createNestHandler,
|
||||
type ServerHandler,
|
||||
type ServerHandlerConfig,
|
||||
} from './server';
|
||||
|
||||
// Re-export React/Next.js utilities (lazy loaded)
|
||||
export {
|
||||
createRouteHandler,
|
||||
createPagesHandler,
|
||||
type RouteHandlerConfig,
|
||||
type UseChatConfig,
|
||||
} from './nextjs';
|
||||
|
||||
// Re-export agent loop utilities
|
||||
export {
|
||||
createAgentLoop,
|
||||
AgentLoop,
|
||||
stopAfterSteps,
|
||||
stopWhenNoToolCalls,
|
||||
stopWhen,
|
||||
type AgentLoopConfig,
|
||||
type AgentStep,
|
||||
type AgentLoopResult,
|
||||
type StopCondition,
|
||||
} from './agent-loop';
|
||||
|
||||
// Re-export UI message utilities
|
||||
export {
|
||||
convertToModelMessages,
|
||||
convertToUIMessages,
|
||||
validateUIMessages,
|
||||
safeValidateUIMessages,
|
||||
createTextMessage,
|
||||
createSystemMessage,
|
||||
hasPendingApprovals,
|
||||
getToolsNeedingApproval,
|
||||
createApprovalResponse,
|
||||
toUIMessageStreamResponse,
|
||||
pipeUIMessageStreamToResponse,
|
||||
type UIMessage,
|
||||
type UIMessagePart,
|
||||
type TextUIPart,
|
||||
type ReasoningUIPart,
|
||||
type ToolUIPart,
|
||||
type FileUIPart,
|
||||
type DataUIPart,
|
||||
type ModelMessage,
|
||||
type UIMessageStreamOptions,
|
||||
} from './ui-message';
|
||||
|
||||
// Re-export tool approval utilities
|
||||
export {
|
||||
ApprovalManager,
|
||||
getApprovalManager,
|
||||
setApprovalManager,
|
||||
withApproval,
|
||||
createCLIApprovalPrompt,
|
||||
ToolApprovalDeniedError,
|
||||
ToolApprovalTimeoutError,
|
||||
DANGEROUS_PATTERNS,
|
||||
isDangerous,
|
||||
createDangerousPatternChecker,
|
||||
type ToolApprovalConfig,
|
||||
type ToolApprovalRequest,
|
||||
type ToolApprovalResponse,
|
||||
type ApprovalState,
|
||||
type ApprovalHandler,
|
||||
} from './tool-approval';
|
||||
|
||||
// Re-export speech and transcription
|
||||
export {
|
||||
generateSpeech,
|
||||
transcribe,
|
||||
SPEECH_MODELS,
|
||||
TRANSCRIPTION_MODELS,
|
||||
type GenerateSpeechOptions,
|
||||
type GenerateSpeechResult,
|
||||
type TranscribeOptions,
|
||||
type TranscribeResult,
|
||||
type TranscriptionSegment,
|
||||
} from './speech';
|
||||
|
||||
// Re-export DevTools
|
||||
export {
|
||||
enableDevTools,
|
||||
disableDevTools,
|
||||
isDevToolsEnabled,
|
||||
getDevToolsState,
|
||||
getDevToolsUrl,
|
||||
createDevToolsMiddleware,
|
||||
autoEnableDevTools,
|
||||
type DevToolsConfig,
|
||||
type DevToolsState,
|
||||
} from './devtools';
|
||||
|
||||
// Re-export OAuth provider for MCP
|
||||
export type { OAuthClientProvider } from './mcp';
|
||||
|
||||
// Re-export telemetry utilities
|
||||
export {
|
||||
configureTelemetry,
|
||||
getTelemetrySettings,
|
||||
enableTelemetry as enableAITelemetry,
|
||||
disableTelemetry as disableAITelemetry,
|
||||
isTelemetryEnabled,
|
||||
initOpenTelemetry,
|
||||
getTracer,
|
||||
createAISpan,
|
||||
withSpan,
|
||||
createTelemetryMiddleware,
|
||||
recordEvent,
|
||||
getEvents,
|
||||
clearEvents,
|
||||
createTelemetrySettings,
|
||||
type TelemetrySettings,
|
||||
type Tracer,
|
||||
type Span,
|
||||
type SpanOptions,
|
||||
type SpanKind,
|
||||
type SpanStatus,
|
||||
type TelemetryEvent,
|
||||
} from './telemetry';
|
||||
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* MCP (Model Context Protocol) - AI SDK Wrapper
|
||||
*
|
||||
* Provides MCP client utilities for connecting to MCP servers and using their tools.
|
||||
*/
|
||||
|
||||
export interface MCPConfig {
|
||||
/** Transport configuration */
|
||||
transport: MCPTransportConfig;
|
||||
/** Client name */
|
||||
name?: string;
|
||||
/** Client version */
|
||||
version?: string;
|
||||
/** Callback for uncaught errors */
|
||||
onUncaughtError?: (error: unknown) => void;
|
||||
}
|
||||
|
||||
export type MCPTransportConfig =
|
||||
| { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
|
||||
| { type: 'sse'; url: string; headers?: Record<string, string>; authProvider?: OAuthClientProvider }
|
||||
| { type: 'http'; url: string; headers?: Record<string, string>; authProvider?: OAuthClientProvider }
|
||||
| { type: 'websocket'; url: string; headers?: Record<string, string> };
|
||||
|
||||
/**
|
||||
* OAuth client provider for MCP authentication.
|
||||
*/
|
||||
export interface OAuthClientProvider {
|
||||
/** Get the current access token */
|
||||
getAccessToken(): Promise<string | null>;
|
||||
/** Refresh the access token */
|
||||
refreshToken?(): Promise<string | null>;
|
||||
/** Handle OAuth redirect */
|
||||
handleRedirect?(url: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MCPClient {
|
||||
/** Get tools from the MCP server */
|
||||
tools(): Promise<Record<string, MCPTool>>;
|
||||
/** List available resources */
|
||||
listResources(): Promise<MCPResource[]>;
|
||||
/** Read a resource */
|
||||
readResource(uri: string): Promise<MCPResourceContent>;
|
||||
/** List available prompts */
|
||||
listPrompts(): Promise<MCPPrompt[]>;
|
||||
/** Get a prompt */
|
||||
getPrompt(name: string, args?: Record<string, string>): Promise<MCPPromptResult>;
|
||||
/** Close the connection */
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MCPTool {
|
||||
/** Tool name */
|
||||
name: string;
|
||||
/** Tool description */
|
||||
description?: string;
|
||||
/** Input schema */
|
||||
inputSchema: any;
|
||||
/** Execute the tool */
|
||||
execute: (args: any) => Promise<any>;
|
||||
}
|
||||
|
||||
export interface MCPResource {
|
||||
/** Resource URI */
|
||||
uri: string;
|
||||
/** Resource name */
|
||||
name: string;
|
||||
/** Resource description */
|
||||
description?: string;
|
||||
/** MIME type */
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
export interface MCPResourceContent {
|
||||
/** Content URI */
|
||||
uri: string;
|
||||
/** Content MIME type */
|
||||
mimeType?: string;
|
||||
/** Text content */
|
||||
text?: string;
|
||||
/** Binary content (base64) */
|
||||
blob?: string;
|
||||
}
|
||||
|
||||
export interface MCPPrompt {
|
||||
/** Prompt name */
|
||||
name: string;
|
||||
/** Prompt description */
|
||||
description?: string;
|
||||
/** Arguments */
|
||||
arguments?: Array<{
|
||||
name: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface MCPPromptResult {
|
||||
/** Prompt description */
|
||||
description?: string;
|
||||
/** Messages */
|
||||
messages: Array<{
|
||||
role: 'user' | 'assistant';
|
||||
content: { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string };
|
||||
}>;
|
||||
}
|
||||
|
||||
// Connection pool for MCP clients
|
||||
const clientPool = new Map<string, MCPClient>();
|
||||
|
||||
/**
|
||||
* Create an MCP client.
|
||||
*
|
||||
* @example Stdio transport (local server)
|
||||
* ```typescript
|
||||
* const client = await createMCP({
|
||||
* transport: {
|
||||
* type: 'stdio',
|
||||
* command: 'npx',
|
||||
* args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/dir']
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* const tools = await client.tools();
|
||||
* ```
|
||||
*
|
||||
* @example SSE transport (remote server)
|
||||
* ```typescript
|
||||
* const client = await createMCP({
|
||||
* transport: {
|
||||
* type: 'sse',
|
||||
* url: 'https://mcp-server.example.com/sse'
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function createMCP(config: MCPConfig): Promise<MCPClient> {
|
||||
// Try to use AI SDK MCP client (optional dependency)
|
||||
try {
|
||||
// @ts-ignore - Optional dependency
|
||||
const mcpModule = await import('@ai-sdk/mcp');
|
||||
|
||||
const transport = createTransport(config.transport);
|
||||
const client = await mcpModule.createMCPClient({
|
||||
transport,
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
onUncaughtError: config.onUncaughtError,
|
||||
});
|
||||
|
||||
return {
|
||||
tools: async () => {
|
||||
const toolSet = await client.tools();
|
||||
return toolSet as Record<string, MCPTool>;
|
||||
},
|
||||
listResources: async () => {
|
||||
const result = await client.listResources();
|
||||
return result.resources || [];
|
||||
},
|
||||
readResource: async (uri: string) => {
|
||||
const result = await client.readResource({ uri });
|
||||
return result.contents?.[0] || { uri };
|
||||
},
|
||||
listPrompts: async () => {
|
||||
const result = await client.listPrompts();
|
||||
return result.prompts || [];
|
||||
},
|
||||
getPrompt: async (name: string, args?: Record<string, string>) => {
|
||||
const result = await client.getPrompt({ name, arguments: args });
|
||||
return result as MCPPromptResult;
|
||||
},
|
||||
close: async () => {
|
||||
await client.close();
|
||||
},
|
||||
};
|
||||
} catch (error: any) {
|
||||
// Fall back to native implementation
|
||||
return createNativeMCPClient(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create transport configuration for AI SDK MCP.
|
||||
*/
|
||||
function createTransport(config: MCPTransportConfig): any {
|
||||
switch (config.type) {
|
||||
case 'stdio':
|
||||
return {
|
||||
type: 'stdio',
|
||||
command: config.command,
|
||||
args: config.args,
|
||||
env: config.env,
|
||||
};
|
||||
case 'sse':
|
||||
return {
|
||||
type: 'sse',
|
||||
url: config.url,
|
||||
headers: config.headers,
|
||||
authProvider: config.authProvider,
|
||||
};
|
||||
case 'http':
|
||||
return {
|
||||
type: 'http',
|
||||
url: config.url,
|
||||
headers: config.headers,
|
||||
authProvider: config.authProvider,
|
||||
};
|
||||
case 'websocket':
|
||||
return {
|
||||
type: 'websocket',
|
||||
url: config.url,
|
||||
headers: config.headers,
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unknown transport type: ${(config as any).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Native MCP client implementation (fallback).
|
||||
*/
|
||||
async function createNativeMCPClient(config: MCPConfig): Promise<MCPClient> {
|
||||
// This is a simplified native implementation
|
||||
// In production, this would use the @modelcontextprotocol/sdk directly
|
||||
|
||||
const tools: Record<string, MCPTool> = {};
|
||||
const resources: MCPResource[] = [];
|
||||
const prompts: MCPPrompt[] = [];
|
||||
|
||||
return {
|
||||
tools: async () => tools,
|
||||
listResources: async () => resources,
|
||||
readResource: async (uri: string) => ({ uri }),
|
||||
listPrompts: async () => prompts,
|
||||
getPrompt: async (name: string) => ({ messages: [] }),
|
||||
close: async () => {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a pooled MCP client.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const client = await getMCPClient('filesystem', {
|
||||
* transport: { type: 'stdio', command: 'npx', args: ['-y', '@mcp/server-fs'] }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function getMCPClient(key: string, config: MCPConfig): Promise<MCPClient> {
|
||||
if (clientPool.has(key)) {
|
||||
return clientPool.get(key)!;
|
||||
}
|
||||
|
||||
const client = await createMCP(config);
|
||||
clientPool.set(key, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close and remove a pooled MCP client.
|
||||
*/
|
||||
export async function closeMCPClient(key: string): Promise<void> {
|
||||
const client = clientPool.get(key);
|
||||
if (client) {
|
||||
await client.close();
|
||||
clientPool.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all pooled MCP clients.
|
||||
*/
|
||||
export async function closeAllMCPClients(): Promise<void> {
|
||||
for (const [key, client] of clientPool) {
|
||||
await client.close();
|
||||
clientPool.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert MCP tools to AI SDK tool format.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const client = await createMCP({ ... });
|
||||
* const mcpTools = await client.tools();
|
||||
* const aiTools = mcpToolsToAITools(mcpTools);
|
||||
*
|
||||
* const result = await generateText({
|
||||
* model: 'gpt-4o',
|
||||
* prompt: 'List files in the current directory',
|
||||
* tools: aiTools
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function mcpToolsToAITools(mcpTools: Record<string, MCPTool>): Record<string, any> {
|
||||
const aiTools: Record<string, any> = {};
|
||||
|
||||
for (const [name, tool] of Object.entries(mcpTools)) {
|
||||
aiTools[name] = {
|
||||
description: tool.description || `MCP tool: ${name}`,
|
||||
parameters: tool.inputSchema,
|
||||
execute: tool.execute,
|
||||
};
|
||||
}
|
||||
|
||||
return aiTools;
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Middleware - AI SDK Wrapper
|
||||
*
|
||||
* Provides middleware utilities for caching, logging, and model wrapping.
|
||||
*/
|
||||
|
||||
export interface Middleware {
|
||||
/** Middleware name */
|
||||
name: string;
|
||||
/** Transform request before sending */
|
||||
transformRequest?: (request: MiddlewareRequest) => Promise<MiddlewareRequest> | MiddlewareRequest;
|
||||
/** Transform response after receiving */
|
||||
transformResponse?: (response: MiddlewareResponse) => Promise<MiddlewareResponse> | MiddlewareResponse;
|
||||
/** Handle errors */
|
||||
onError?: (error: Error) => Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface MiddlewareRequest {
|
||||
model: string;
|
||||
prompt?: string;
|
||||
messages?: any[];
|
||||
system?: string;
|
||||
tools?: Record<string, any>;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface MiddlewareResponse {
|
||||
text?: string;
|
||||
object?: any;
|
||||
toolCalls?: any[];
|
||||
usage?: {
|
||||
promptTokens: number;
|
||||
completionTokens: number;
|
||||
totalTokens: number;
|
||||
};
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface MiddlewareConfig {
|
||||
/** Enable caching */
|
||||
cache?: boolean;
|
||||
/** Cache TTL in seconds */
|
||||
cacheTTL?: number;
|
||||
/** Enable logging */
|
||||
logging?: boolean;
|
||||
/** Log level */
|
||||
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
||||
/** Custom middlewares */
|
||||
middlewares?: Middleware[];
|
||||
}
|
||||
|
||||
// In-memory cache for middleware
|
||||
const responseCache = new Map<string, { response: any; timestamp: number }>();
|
||||
|
||||
/**
|
||||
* Create a caching middleware.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const cachingMiddleware = createCachingMiddleware({
|
||||
* ttl: 3600, // 1 hour
|
||||
* keyGenerator: (request) => `${request.model}:${request.prompt}`
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function createCachingMiddleware(options?: {
|
||||
ttl?: number;
|
||||
keyGenerator?: (request: MiddlewareRequest) => string;
|
||||
storage?: {
|
||||
get: (key: string) => Promise<any>;
|
||||
set: (key: string, value: any, ttl?: number) => Promise<void>;
|
||||
};
|
||||
}): Middleware {
|
||||
const ttl = options?.ttl ?? 3600;
|
||||
const keyGenerator = options?.keyGenerator ?? defaultKeyGenerator;
|
||||
const storage = options?.storage;
|
||||
|
||||
return {
|
||||
name: 'caching',
|
||||
transformRequest: async (request) => {
|
||||
const key = keyGenerator(request);
|
||||
|
||||
if (storage) {
|
||||
const cached = await storage.get(key);
|
||||
if (cached) {
|
||||
(request as any).__cached = cached;
|
||||
(request as any).__cacheHit = true;
|
||||
}
|
||||
} else {
|
||||
const cached = responseCache.get(key);
|
||||
if (cached && Date.now() - cached.timestamp < ttl * 1000) {
|
||||
(request as any).__cached = cached.response;
|
||||
(request as any).__cacheHit = true;
|
||||
}
|
||||
}
|
||||
|
||||
(request as any).__cacheKey = key;
|
||||
return request;
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const key = (response as any).__cacheKey;
|
||||
if (key && !(response as any).__cacheHit) {
|
||||
if (storage) {
|
||||
await storage.set(key, response, ttl);
|
||||
} else {
|
||||
responseCache.set(key, { response, timestamp: Date.now() });
|
||||
}
|
||||
}
|
||||
return response;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a logging middleware.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const loggingMiddleware = createLoggingMiddleware({
|
||||
* level: 'debug',
|
||||
* onRequest: (request) => console.log('Request:', request),
|
||||
* onResponse: (response) => console.log('Response:', response)
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function createLoggingMiddleware(options?: {
|
||||
level?: 'debug' | 'info' | 'warn' | 'error';
|
||||
onRequest?: (request: MiddlewareRequest) => void;
|
||||
onResponse?: (response: MiddlewareResponse) => void;
|
||||
onError?: (error: Error) => void;
|
||||
}): Middleware {
|
||||
const level = options?.level ?? 'info';
|
||||
const log = (msg: string, data?: any) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
console[level](`[${timestamp}] ${msg}`, data ?? '');
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'logging',
|
||||
transformRequest: async (request) => {
|
||||
log('AI Request', { model: request.model, hasPrompt: !!request.prompt, hasMessages: !!request.messages });
|
||||
options?.onRequest?.(request);
|
||||
return request;
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
log('AI Response', { hasText: !!response.text, hasObject: !!response.object, usage: response.usage });
|
||||
options?.onResponse?.(response);
|
||||
return response;
|
||||
},
|
||||
onError: async (error) => {
|
||||
log('AI Error', { message: error.message });
|
||||
options?.onError?.(error);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a model with middleware.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const wrappedModel = wrapModel(model, [
|
||||
* createCachingMiddleware(),
|
||||
* createLoggingMiddleware()
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
export function wrapModel(model: any, middlewares: Middleware[]): any {
|
||||
// This is a placeholder - actual implementation would wrap the model's
|
||||
// doGenerate and doStream methods with middleware transformations
|
||||
return {
|
||||
...model,
|
||||
__middlewares: middlewares,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply middleware to a request/response cycle.
|
||||
*/
|
||||
export async function applyMiddleware(
|
||||
middlewares: Middleware[],
|
||||
request: MiddlewareRequest,
|
||||
execute: (req: MiddlewareRequest) => Promise<MiddlewareResponse>
|
||||
): Promise<MiddlewareResponse> {
|
||||
// Transform request through all middlewares
|
||||
let transformedRequest = request;
|
||||
for (const middleware of middlewares) {
|
||||
if (middleware.transformRequest) {
|
||||
transformedRequest = await middleware.transformRequest(transformedRequest);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cache hit
|
||||
if ((transformedRequest as any).__cacheHit) {
|
||||
return (transformedRequest as any).__cached;
|
||||
}
|
||||
|
||||
// Execute the actual request
|
||||
let response: MiddlewareResponse;
|
||||
try {
|
||||
response = await execute(transformedRequest);
|
||||
(response as any).__cacheKey = (transformedRequest as any).__cacheKey;
|
||||
} catch (error: any) {
|
||||
// Handle errors through middlewares
|
||||
for (const middleware of middlewares) {
|
||||
if (middleware.onError) {
|
||||
await middleware.onError(error);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Transform response through all middlewares (in reverse order)
|
||||
let transformedResponse = response;
|
||||
for (const middleware of [...middlewares].reverse()) {
|
||||
if (middleware.transformResponse) {
|
||||
transformedResponse = await middleware.transformResponse(transformedResponse);
|
||||
}
|
||||
}
|
||||
|
||||
return transformedResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default cache key generator.
|
||||
*/
|
||||
function defaultKeyGenerator(request: MiddlewareRequest): string {
|
||||
const parts = [
|
||||
request.model,
|
||||
request.prompt || '',
|
||||
JSON.stringify(request.messages || []),
|
||||
request.system || '',
|
||||
];
|
||||
return parts.join(':').slice(0, 256);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the response cache.
|
||||
*/
|
||||
export function clearCache(): void {
|
||||
responseCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics.
|
||||
*/
|
||||
export function getCacheStats(): { size: number; keys: string[] } {
|
||||
return {
|
||||
size: responseCache.size,
|
||||
keys: Array.from(responseCache.keys()),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Models - AI SDK Wrapper
|
||||
*
|
||||
* Provides model aliases and utilities for model resolution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Model aliases for common models across providers.
|
||||
* Maps friendly names to provider/model format.
|
||||
*/
|
||||
export const MODEL_ALIASES: Record<string, string> = {
|
||||
// OpenAI
|
||||
'gpt-4o': 'openai/gpt-4o',
|
||||
'gpt-4o-mini': 'openai/gpt-4o-mini',
|
||||
'gpt-4-turbo': 'openai/gpt-4-turbo',
|
||||
'gpt-4': 'openai/gpt-4',
|
||||
'gpt-3.5-turbo': 'openai/gpt-3.5-turbo',
|
||||
'gpt-5': 'openai/gpt-5',
|
||||
'o1': 'openai/o1',
|
||||
'o1-mini': 'openai/o1-mini',
|
||||
'o1-preview': 'openai/o1-preview',
|
||||
'o3': 'openai/o3',
|
||||
'o3-mini': 'openai/o3-mini',
|
||||
|
||||
// Anthropic
|
||||
'claude-4': 'anthropic/claude-4-sonnet-20250514',
|
||||
'claude-4-opus': 'anthropic/claude-4-opus-20250514',
|
||||
'claude-4-sonnet': 'anthropic/claude-4-sonnet-20250514',
|
||||
'claude-3.7-sonnet': 'anthropic/claude-3-7-sonnet-20250219',
|
||||
'claude-3.5-sonnet': 'anthropic/claude-3-5-sonnet-20241022',
|
||||
'claude-3-sonnet': 'anthropic/claude-3-sonnet-20240229',
|
||||
'claude-3-opus': 'anthropic/claude-3-opus-20240229',
|
||||
'claude-3-haiku': 'anthropic/claude-3-haiku-20240307',
|
||||
|
||||
// Google
|
||||
'gemini-3': 'google/gemini-3.0-pro',
|
||||
'gemini-2.5-pro': 'google/gemini-2.5-pro-preview-06-05',
|
||||
'gemini-2.5-flash': 'google/gemini-2.5-flash-preview-05-20',
|
||||
'gemini-2.0-flash': 'google/gemini-2.0-flash',
|
||||
'gemini-1.5-pro': 'google/gemini-1.5-pro',
|
||||
'gemini-1.5-flash': 'google/gemini-1.5-flash',
|
||||
|
||||
// DeepSeek
|
||||
'deepseek-r1': 'deepseek/deepseek-reasoner',
|
||||
'deepseek-v3': 'deepseek/deepseek-chat',
|
||||
'deepseek-v3.2': 'deepseek/deepseek-chat-v3.2',
|
||||
|
||||
// Meta Llama
|
||||
'llama-3.1': 'groq/llama-3.1-70b-versatile',
|
||||
'llama-3.1-8b': 'groq/llama-3.1-8b-instant',
|
||||
'llama-3.1-70b': 'groq/llama-3.1-70b-versatile',
|
||||
'llama-3.1-405b': 'together/meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo',
|
||||
|
||||
// Embedding models
|
||||
'text-embedding-3-small': 'openai/text-embedding-3-small',
|
||||
'text-embedding-3-large': 'openai/text-embedding-3-large',
|
||||
'text-embedding-004': 'google/text-embedding-004',
|
||||
|
||||
// Image models
|
||||
'dall-e-3': 'openai/dall-e-3',
|
||||
'dall-e-2': 'openai/dall-e-2',
|
||||
'imagen-3': 'google/imagen-3.0-generate-002',
|
||||
};
|
||||
|
||||
export type ModelId = keyof typeof MODEL_ALIASES | string;
|
||||
|
||||
export interface ModelConfig {
|
||||
/** Provider name */
|
||||
provider: string;
|
||||
/** Model ID */
|
||||
modelId: string;
|
||||
/** Original model string */
|
||||
original: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a model string into provider and model ID.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* parseModel('gpt-4o') // { provider: 'openai', modelId: 'gpt-4o', original: 'gpt-4o' }
|
||||
* parseModel('anthropic/claude-3-sonnet') // { provider: 'anthropic', modelId: 'claude-3-sonnet', original: 'anthropic/claude-3-sonnet' }
|
||||
* ```
|
||||
*/
|
||||
export function parseModel(model: ModelId): ModelConfig {
|
||||
// Check aliases first
|
||||
const aliased = MODEL_ALIASES[model];
|
||||
const modelString = aliased || model;
|
||||
|
||||
if (modelString.includes('/')) {
|
||||
const parts = modelString.split('/');
|
||||
return {
|
||||
provider: parts[0].toLowerCase(),
|
||||
modelId: parts.slice(1).join('/'),
|
||||
original: model,
|
||||
};
|
||||
}
|
||||
|
||||
// Infer provider from model name
|
||||
let provider = 'openai';
|
||||
if (modelString.startsWith('claude')) provider = 'anthropic';
|
||||
else if (modelString.startsWith('gemini')) provider = 'google';
|
||||
else if (modelString.startsWith('deepseek')) provider = 'deepseek';
|
||||
else if (modelString.startsWith('llama')) provider = 'groq';
|
||||
|
||||
return {
|
||||
provider,
|
||||
modelId: modelString,
|
||||
original: model,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a model instance from a model string.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const model = await getModel('gpt-4o');
|
||||
* const result = await generateText({ model, prompt: 'Hello' });
|
||||
* ```
|
||||
*/
|
||||
export async function getModel(model: ModelId): Promise<any> {
|
||||
const config = parseModel(model);
|
||||
|
||||
let providerModule: any;
|
||||
try {
|
||||
switch (config.provider) {
|
||||
case 'openai':
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai(config.modelId);
|
||||
case 'anthropic':
|
||||
providerModule = await import('@ai-sdk/anthropic');
|
||||
return providerModule.anthropic(config.modelId);
|
||||
case 'google':
|
||||
providerModule = await import('@ai-sdk/google');
|
||||
return providerModule.google(config.modelId);
|
||||
case 'groq':
|
||||
// @ts-ignore - Optional dependency
|
||||
providerModule = await import('@ai-sdk/groq');
|
||||
return providerModule.groq(config.modelId);
|
||||
case 'deepseek':
|
||||
// @ts-ignore - Optional dependency
|
||||
providerModule = await import('@ai-sdk/deepseek');
|
||||
return providerModule.deepseek(config.modelId);
|
||||
default:
|
||||
// Try OpenAI-compatible
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
return providerModule.openai(config.modelId);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to load provider '${config.provider}': ${error.message}. ` +
|
||||
`Install with: npm install @ai-sdk/${config.provider}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a model with custom configuration.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const model = await createModel('gpt-4o', {
|
||||
* baseURL: 'https://custom-endpoint.com/v1',
|
||||
* apiKey: 'custom-key'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function createModel(
|
||||
model: ModelId,
|
||||
options?: {
|
||||
baseURL?: string;
|
||||
apiKey?: string;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
): Promise<any> {
|
||||
const config = parseModel(model);
|
||||
|
||||
let providerModule: any;
|
||||
try {
|
||||
switch (config.provider) {
|
||||
case 'openai':
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
const openaiProvider = providerModule.createOpenAI({
|
||||
baseURL: options?.baseURL,
|
||||
apiKey: options?.apiKey,
|
||||
headers: options?.headers,
|
||||
});
|
||||
return openaiProvider(config.modelId);
|
||||
case 'anthropic':
|
||||
providerModule = await import('@ai-sdk/anthropic');
|
||||
const anthropicProvider = providerModule.createAnthropic({
|
||||
baseURL: options?.baseURL,
|
||||
apiKey: options?.apiKey,
|
||||
headers: options?.headers,
|
||||
});
|
||||
return anthropicProvider(config.modelId);
|
||||
case 'google':
|
||||
providerModule = await import('@ai-sdk/google');
|
||||
const googleProvider = providerModule.createGoogleGenerativeAI({
|
||||
baseURL: options?.baseURL,
|
||||
apiKey: options?.apiKey,
|
||||
headers: options?.headers,
|
||||
});
|
||||
return googleProvider(config.modelId);
|
||||
default:
|
||||
providerModule = await import('@ai-sdk/openai');
|
||||
const defaultProvider = providerModule.createOpenAI({
|
||||
baseURL: options?.baseURL,
|
||||
apiKey: options?.apiKey,
|
||||
headers: options?.headers,
|
||||
});
|
||||
return defaultProvider(config.modelId);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to create model '${model}': ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available model aliases.
|
||||
*/
|
||||
export function listModelAliases(): string[] {
|
||||
return Object.keys(MODEL_ALIASES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model alias exists.
|
||||
*/
|
||||
export function hasModelAlias(alias: string): boolean {
|
||||
return alias in MODEL_ALIASES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a model alias to its full provider/model format.
|
||||
*/
|
||||
export function resolveModelAlias(alias: string): string {
|
||||
return MODEL_ALIASES[alias] || alias;
|
||||
}
|
||||
Loaded 100 of 316 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user