mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-09 03:39:37 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2d181dd74 | ||
|
|
7411fdbb52 | ||
|
|
e7a8ce8eb7 | ||
|
|
61fb094306 | ||
|
|
018e8c78ab | ||
|
|
f0f0f653fe | ||
|
|
98a4ff8450 | ||
|
|
d6333aaf33 | ||
|
|
8005094267 | ||
|
|
08b21dabe3 | ||
|
|
8c70d840df | ||
|
|
d8e944c1d3 | ||
|
|
79b2b88f72 | ||
|
|
f5dfbd5670 | ||
|
|
aa6aa5933d | ||
|
|
ff3a4f2016 | ||
|
|
0d5444fc23 | ||
|
|
d7d5dab17b | ||
|
|
05678394e6 | ||
|
|
54cddc09b4 |
No files matched your search
@@ -35,46 +35,108 @@ PraisonAI is a production-ready Multi-AI Agents framework with self-reflection,
|
||||
|
||||
---
|
||||
|
||||
> **Quick Paths:**
|
||||
> - 🚀 **New here?** → [Quick Start](#-quick-start)
|
||||
> - 📦 **Installing?** → [Installation](#-installation)
|
||||
> - 💻 **Python SDK?** → [Python Examples](#-using-python-code)
|
||||
> - 🎯 **CLI user?** → [CLI Reference](#-cli--no-code-interface)
|
||||
> - 🔧 **Need config?** → [Configuration](#-configuration--integration)
|
||||
> - 🤝 **Contributing?** → [Development](#-development)
|
||||
|
||||
---
|
||||
|
||||
## 📑 Table of Contents
|
||||
|
||||
### Getting Started
|
||||
- [⚡ Performance](#-performance)
|
||||
- [🚀 Quick Start](#-quick-start)
|
||||
- [📦 Installation](#-installation)
|
||||
- [Python SDK](#python-sdk)
|
||||
- [JavaScript SDK](#javascript-sdk)
|
||||
- [💻 Usage](#-usage)
|
||||
- [Python Code Examples](#python-code-examples)
|
||||
- [CLI / No-Code Interface](#cli--no-code-interface)
|
||||
- [JavaScript Code Examples](#javascript-code-examples)
|
||||
- [Environment Variables](#environment-variables)
|
||||
|
||||
### Core Concepts
|
||||
- [✨ Key Features](#-key-features)
|
||||
- [🌐 Supported Providers](#-supported-providers)
|
||||
|
||||
### Python SDK
|
||||
- [📘 Python Examples](#-using-python-code)
|
||||
- [1. Single Agent](#1-single-agent)
|
||||
- [2. Multi Agents](#2-multi-agents)
|
||||
- [3. Planning Mode](#3-agent-with-planning-mode)
|
||||
- [4. Deep Research](#4-deep-research-agent)
|
||||
- [5. Query Rewriter](#5-query-rewriter-agent)
|
||||
- [6. Agent Memory](#6-agent-memory-zero-dependencies)
|
||||
- [7. Rules & Instructions](#7-rules--instructions)
|
||||
- [8. Auto-Generated Memories](#8-auto-generated-memories)
|
||||
- [9. Agentic Workflows](#9-agentic-workflows)
|
||||
- [10. Hooks](#10-hooks)
|
||||
- [11. Shadow Git Checkpoints](#11-shadow-git-checkpoints)
|
||||
- [12. Background Tasks](#12-background-tasks)
|
||||
- [13. Policy Engine](#13-policy-engine)
|
||||
- [14. Thinking Budgets](#14-thinking-budgets)
|
||||
- [15. Output Styles](#15-output-styles)
|
||||
- [16. Context Compaction](#16-context-compaction)
|
||||
- [17. Field Names Reference](#17-field-names-reference-a-i-g-s)
|
||||
- [18. Extended agents.yaml](#18-extended-agentsyaml-with-workflow-patterns)
|
||||
- [19. MCP Protocol](#19-mcp-model-context-protocol)
|
||||
- [20. A2A Protocol](#20-a2a-agent2agent-protocol)
|
||||
- [🛠️ Custom Tools](#️-custom-tools)
|
||||
|
||||
### JavaScript SDK
|
||||
- [💻 JavaScript Examples](#-using-javascript-code)
|
||||
|
||||
### CLI Reference
|
||||
- [🎯 CLI Overview](#-cli--no-code-interface)
|
||||
- [Auto Mode](#auto-mode)
|
||||
- [Interactive Mode](#interactive-mode-cli)
|
||||
- [Deep Research CLI](#deep-research-cli)
|
||||
- [Planning Mode CLI](#planning-mode-cli)
|
||||
- [Memory CLI](#memory-cli)
|
||||
- [Workflow CLI](#workflow-cli)
|
||||
- [Knowledge CLI](#knowledge-cli)
|
||||
- [Session CLI](#session-cli)
|
||||
- [Tools CLI](#tools-cli)
|
||||
- [MCP Config CLI](#mcp-config-cli)
|
||||
- [External Agents CLI](#external-agents-cli)
|
||||
- [CLI Features Summary](#cli-features)
|
||||
|
||||
### Configuration
|
||||
- [🔧 Configuration & Integration](#-configuration--integration)
|
||||
- [Ollama Integration](#ollama-integration)
|
||||
- [Groq Integration](#groq-integration)
|
||||
- [100+ Models Support](#100-models-support)
|
||||
- [📋 Workflows](#-workflows)
|
||||
- [Workflow Patterns](#workflow-patterns)
|
||||
- [YAML Configuration](#yaml-configuration)
|
||||
- [Agents Playbook](#agents-playbook)
|
||||
- [🧠 Memory & Context](#-memory--context)
|
||||
- [📋 Agents Playbook](#-agents-playbook)
|
||||
|
||||
### Advanced Features
|
||||
- [🔬 Advanced Features](#-advanced-features)
|
||||
- [Research & Intelligence](#research--intelligence)
|
||||
- [Memory & Caching](#memory--caching)
|
||||
- [Planning & Workflows](#planning--workflows)
|
||||
- [Specialised Agents](#specialised-agents)
|
||||
- [MCP Protocol](#mcp-protocol)
|
||||
- [A2A & A2UI Protocols](#a2a--a2ui-protocols)
|
||||
- [Safety & Control](#safety--control)
|
||||
- [Developer Tools](#developer-tools)
|
||||
|
||||
### Architecture
|
||||
- [📊 Process Types & Patterns](#-process-types--patterns)
|
||||
- [Sequential Process](#sequential-process)
|
||||
- [Hierarchical Process](#hierarchical-process)
|
||||
- [Workflow Process](#workflow-process)
|
||||
- [Agentic Patterns](#agentic-patterns)
|
||||
|
||||
### Data & Persistence
|
||||
- [💾 Persistence (Databases)](#-persistence-databases)
|
||||
- [📚 Knowledge & Retrieval (RAG)](#-knowledge--retrieval-rag)
|
||||
- [🔧 Tools Table](#-tools-table)
|
||||
|
||||
### Learning & Community
|
||||
- [🎓 Video Tutorials](#-video-tutorials)
|
||||
- [⭐ Star History](#-star-history)
|
||||
- [👥 Contributing](#-contributing)
|
||||
- [🔧 Development](#-development)
|
||||
- [❓ FAQ & Troubleshooting](#-faq--troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
@@ -143,6 +205,30 @@ pip install praisonai
|
||||
npm install praisonai
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `OPENAI_API_KEY` | Yes* | OpenAI API key |
|
||||
| `ANTHROPIC_API_KEY` | No | Anthropic Claude API key |
|
||||
| `GOOGLE_API_KEY` | No | Google Gemini API key |
|
||||
| `GROQ_API_KEY` | No | Groq API key |
|
||||
| `OPENAI_BASE_URL` | No | Custom API endpoint (for Ollama, Groq, etc.) |
|
||||
|
||||
> *At least one LLM provider API key is required.
|
||||
|
||||
```bash
|
||||
# Set your API key
|
||||
export OPENAI_API_KEY=your_key_here
|
||||
|
||||
# For Ollama (local models)
|
||||
export OPENAI_BASE_URL=http://localhost:11434/v1
|
||||
|
||||
# For Groq
|
||||
export OPENAI_API_KEY=your_groq_key
|
||||
export OPENAI_BASE_URL=https://api.groq.com/openai/v1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 Usage
|
||||
@@ -169,141 +255,263 @@ npm install praisonai
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
<details open>
|
||||
<summary><strong>🤖 Core Agents</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| **🤖 Core Agents** | | |
|
||||
| ↳ Single Agent | [Example](examples/python/agents/single-agent.py) | [📖](https://docs.praison.ai/agents/single) |
|
||||
| ↳ Multi Agents | [Example](examples/python/general/mini_agents_example.py) | [📖](https://docs.praison.ai/concepts/agents) |
|
||||
| ↳ Auto Agents | [Example](examples/python/general/auto_agents_example.py) | [📖](https://docs.praison.ai/features/autoagents) |
|
||||
| ↳ Self Reflection AI Agents | [Example](examples/python/concepts/self-reflection-details.py) | [📖](https://docs.praison.ai/features/selfreflection) |
|
||||
| ↳ Reasoning AI Agents | [Example](examples/python/concepts/reasoning-extraction.py) | [📖](https://docs.praison.ai/features/reasoning) |
|
||||
| ↳ Multi Modal AI Agents | [Example](examples/python/general/multimodal.py) | [📖](https://docs.praison.ai/features/multimodal) |
|
||||
| **🔄 Workflows** | | |
|
||||
| ↳ Simple Workflow | [Example](examples/python/workflows/simple_workflow.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| ↳ Workflow with Agents | [Example](examples/python/workflows/workflow_with_agents.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| ↳ Agentic Routing (`route()`) | [Example](examples/python/workflows/workflow_routing.py) | [📖](https://docs.praison.ai/features/routing) |
|
||||
| ↳ Parallel Execution (`parallel()`) | [Example](examples/python/workflows/workflow_parallel.py) | [📖](https://docs.praison.ai/features/parallelisation) |
|
||||
| ↳ Loop over List/CSV (`loop()`) | [Example](examples/python/workflows/workflow_loop_csv.py) | [📖](https://docs.praison.ai/features/repetitive) |
|
||||
| ↳ Evaluator-Optimizer (`repeat()`) | [Example](examples/python/workflows/workflow_repeat.py) | [📖](https://docs.praison.ai/features/evaluator-optimiser) |
|
||||
| ↳ Conditional Steps | [Example](examples/python/workflows/workflow_conditional.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| ↳ Workflow Branching | [Example](examples/python/workflows/workflow_branching.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| ↳ Workflow Early Stop | [Example](examples/python/workflows/workflow_early_stop.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| ↳ Workflow Checkpoints | [Example](examples/python/workflows/workflow_checkpoints.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| **💻 Code & Development** | | |
|
||||
| ↳ Code Interpreter Agents | [Example](examples/python/agents/code-agent.py) | [📖](https://docs.praison.ai/features/codeagent) |
|
||||
| ↳ AI Code Editing Tools | [Example](examples/python/code/code_editing_example.py) | [📖](https://docs.praison.ai/code/editing) |
|
||||
| ↳ External Agents (All) | [Example](examples/python/code/external_agents_example.py) | [📖](https://docs.praison.ai/code/external-agents) |
|
||||
| ↳ Claude Code CLI | [Example](examples/python/code/claude_code_example.py) | [📖](https://docs.praison.ai/code/claude-code) |
|
||||
| ↳ Gemini CLI | [Example](examples/python/code/gemini_cli_example.py) | [📖](https://docs.praison.ai/code/gemini-cli) |
|
||||
| ↳ Codex CLI | [Example](examples/python/code/codex_cli_example.py) | [📖](https://docs.praison.ai/code/codex-cli) |
|
||||
| ↳ Cursor CLI | [Example](examples/python/code/cursor_cli_example.py) | [📖](https://docs.praison.ai/code/cursor-cli) |
|
||||
| **🧠 Memory & Knowledge** | | |
|
||||
| ↳ Memory (Short & Long Term) | [Example](examples/python/general/memory_example.py) | [📖](https://docs.praison.ai/concepts/memory) |
|
||||
| ↳ File-Based Memory | [Example](examples/python/general/memory_example.py) | [📖](https://docs.praison.ai/concepts/memory) |
|
||||
| ↳ Claude Memory Tool | [Example](#claude-memory-tool-cli) | [📖](https://docs.praison.ai/features/claude-memory-tool) |
|
||||
| ↳ Add Custom Knowledge | [Example](examples/python/concepts/knowledge-agents.py) | [📖](https://docs.praison.ai/features/knowledge) |
|
||||
| ↳ RAG Agents | [Example](examples/python/concepts/rag-agents.py) | [📖](https://docs.praison.ai/features/rag) |
|
||||
| ↳ Chat with PDF Agents | [Example](examples/python/concepts/chat-with-pdf.py) | [📖](https://docs.praison.ai/features/chat-with-pdf) |
|
||||
| **🔬 Research & Intelligence** | | |
|
||||
| ↳ Deep Research Agents | [Example](examples/python/agents/research-agent.py) | [📖](https://docs.praison.ai/agents/deep-research) |
|
||||
| ↳ Query Rewriter Agent | [Example](#5-query-rewriter-agent) | [📖](https://docs.praison.ai/agents/query-rewriter) |
|
||||
| ↳ Native Web Search | [Example](examples/python/agents/websearch-agent.py) | [📖](https://docs.praison.ai/agents/websearch) |
|
||||
| ↳ Built-in Search Tools | [Example](examples/python/agents/websearch-agent.py) | [📖](https://docs.praison.ai/tools/tavily) |
|
||||
| ↳ Unified Web Search | [Example](src/praisonai-agents/examples/web_search_example.py) | [📖](https://docs.praison.ai/tools/web-search) |
|
||||
| ↳ Web Fetch (Anthropic) | [Example](#web-search-web-fetch--prompt-caching) | [📖](https://docs.praison.ai/features/model-capabilities) |
|
||||
| **📋 Planning & Execution** | | |
|
||||
| ↳ Planning Mode | [Example](examples/python/agents/planning-agent.py) | [📖](https://docs.praison.ai/features/planning-mode) |
|
||||
| ↳ Planning Tools | [Example](#3-agent-with-planning-mode) | [📖](https://docs.praison.ai/features/planning-mode) |
|
||||
| ↳ Planning Reasoning | [Example](#3-agent-with-planning-mode) | [📖](https://docs.praison.ai/features/planning-mode) |
|
||||
| ↳ Prompt Chaining | [Example](examples/python/general/prompt_chaining.py) | [📖](https://docs.praison.ai/features/promptchaining) |
|
||||
| ↳ Evaluator Optimiser | [Example](examples/python/general/evaluator-optimiser.py) | [📖](https://docs.praison.ai/features/evaluator-optimiser) |
|
||||
| ↳ Orchestrator Workers | [Example](examples/python/general/orchestrator-workers.py) | [📖](https://docs.praison.ai/features/orchestrator-worker) |
|
||||
| **👥 Specialized Agents** | | |
|
||||
| ↳ Data Analyst Agent | [Example](examples/python/agents/data-analyst-agent.py) | [📖](https://docs.praison.ai/agents/data-analyst) |
|
||||
| ↳ Finance Agent | [Example](examples/python/agents/finance-agent.py) | [📖](https://docs.praison.ai/agents/finance) |
|
||||
| ↳ Shopping Agent | [Example](examples/python/agents/shopping-agent.py) | [📖](https://docs.praison.ai/agents/shopping) |
|
||||
| ↳ Recommendation Agent | [Example](examples/python/agents/recommendation-agent.py) | [📖](https://docs.praison.ai/agents/recommendation) |
|
||||
| ↳ Wikipedia Agent | [Example](examples/python/agents/wikipedia-agent.py) | [📖](https://docs.praison.ai/agents/wikipedia) |
|
||||
| ↳ Programming Agent | [Example](examples/python/agents/programming-agent.py) | [📖](https://docs.praison.ai/agents/programming) |
|
||||
| ↳ Math Agents | [Example](examples/python/agents/math-agent.py) | [📖](https://docs.praison.ai/features/mathagent) |
|
||||
| ↳ Markdown Agent | [Example](examples/python/agents/markdown-agent.py) | [📖](https://docs.praison.ai/agents/markdown) |
|
||||
| ↳ Prompt Expander Agent | [Example](#prompt-expansion) | [📖](https://docs.praison.ai/agents/prompt-expander) |
|
||||
| **🎨 Media & Multimodal** | | |
|
||||
| ↳ Image Generation Agent | [Example](examples/python/image/image-agent.py) | [📖](https://docs.praison.ai/features/image-generation) |
|
||||
| ↳ Image to Text Agent | [Example](examples/python/agents/image-to-text-agent.py) | [📖](https://docs.praison.ai/agents/image-to-text) |
|
||||
| ↳ Video Agent | [Example](examples/python/agents/video-agent.py) | [📖](https://docs.praison.ai/agents/video) |
|
||||
| ↳ Camera Integration | [Example](examples/python/camera/) | [📖](https://docs.praison.ai/features/camera-integration) |
|
||||
| **🔌 Protocols & Integration** | | |
|
||||
| ↳ MCP Transports | [Example](examples/python/mcp/mcp-transports-overview.py) | [📖](https://docs.praison.ai/mcp/transports) |
|
||||
| ↳ WebSocket MCP | [Example](examples/python/mcp/websocket-mcp.py) | [📖](https://docs.praison.ai/mcp/sse-transport) |
|
||||
| ↳ MCP Security | [Example](examples/python/mcp/mcp-security.py) | [📖](https://docs.praison.ai/mcp/transports) |
|
||||
| ↳ MCP Resumability | [Example](examples/python/mcp/mcp-resumability.py) | [📖](https://docs.praison.ai/mcp/sse-transport) |
|
||||
| ↳ MCP Config Management | [Example](#mcp-config-cli) | [📖](https://docs.praison.ai/docs/cli/mcp) |
|
||||
| ↳ LangChain Integrated Agents | [Example](examples/python/general/langchain_example.py) | [📖](https://docs.praison.ai/features/langchain) |
|
||||
| **🛡️ Safety & Control** | | |
|
||||
| ↳ Guardrails | [Example](examples/python/guardrails/comprehensive-guardrails-example.py) | [📖](https://docs.praison.ai/features/guardrails) |
|
||||
| ↳ Human Approval | [Example](examples/python/general/human_approval_example.py) | [📖](https://docs.praison.ai/features/approval) |
|
||||
| ↳ Rules & Instructions | [Example](#6-rules--instructions) | [📖](https://docs.praison.ai/features/rules) |
|
||||
| **⚙️ Advanced Features** | | |
|
||||
| ↳ Async & Parallel Processing | [Example](examples/python/general/async_example.py) | [📖](https://docs.praison.ai/features/async) |
|
||||
| ↳ Parallelisation | [Example](examples/python/general/parallelisation.py) | [📖](https://docs.praison.ai/features/parallelisation) |
|
||||
| ↳ Repetitive Agents | [Example](examples/python/concepts/repetitive-agents.py) | [📖](https://docs.praison.ai/features/repetitive) |
|
||||
| ↳ Agent Handoffs | [Example](examples/python/handoff/handoff_basic.py) | [📖](https://docs.praison.ai/features/handoffs) |
|
||||
| ↳ Stateful Agents | [Example](examples/python/stateful/workflow-state-example.py) | [📖](https://docs.praison.ai/features/stateful-agents) |
|
||||
| ↳ Autonomous Workflow | [Example](examples/python/general/autonomous-agent.py) | [📖](https://docs.praison.ai/features/autonomous-workflow) |
|
||||
| ↳ Structured Output Agents | [Example](examples/python/general/structured_agents_example.py) | [📖](https://docs.praison.ai/features/structured) |
|
||||
| ↳ Model Router | [Example](examples/python/agents/router-agent-cost-optimization.py) | [📖](https://docs.praison.ai/features/model-router) |
|
||||
| ↳ Prompt Caching | [Example](#web-search-web-fetch--prompt-caching) | [📖](https://docs.praison.ai/features/model-capabilities) |
|
||||
| ↳ Fast Context | [Example](examples/context/00_agent_fast_context_basic.py) | [📖](https://docs.praison.ai/features/fast-context) |
|
||||
| **🛠️ Tools & Configuration** | | |
|
||||
| ↳ 100+ Custom Tools | [Example](examples/python/general/tools_example.py) | [📖](https://docs.praison.ai/tools/tools) |
|
||||
| ↳ YAML Configuration | [Example](examples/cookbooks/yaml/secondary_market_research_agents.yaml) | [📖](https://docs.praison.ai/developers/agents-playbook) |
|
||||
| ↳ 100+ LLM Support | [Example](examples/python/providers/openai/openai_gpt4_example.py) | [📖](https://docs.praison.ai/models) |
|
||||
| ↳ Callback Agents | [Example](examples/python/general/advanced-callback-systems.py) | [📖](https://docs.praison.ai/features/callbacks) |
|
||||
| ↳ Hooks | [Example](#9-hooks) | [📖](https://docs.praison.ai/features/hooks) |
|
||||
| ↳ Middleware System | [Example](examples/middleware/basic_middleware.py) | [📖](https://docs.praison.ai/features/middleware) |
|
||||
| ↳ Configurable Model | [Example](examples/middleware/configurable_model.py) | [📖](https://docs.praison.ai/features/configurable-model) |
|
||||
| ↳ Rate Limiter | [Example](examples/middleware/rate_limiter.py) | [📖](https://docs.praison.ai/features/rate-limiter) |
|
||||
| ↳ Injected Tool State | [Example](examples/middleware/injected_state.py) | [📖](https://docs.praison.ai/features/injected-state) |
|
||||
| ↳ Shadow Git Checkpoints | [Example](#10-shadow-git-checkpoints) | [📖](https://docs.praison.ai/features/checkpoints) |
|
||||
| ↳ Background Tasks | [Example](examples/background/basic_background.py) | [📖](https://docs.praison.ai/features/background-tasks) |
|
||||
| ↳ Policy Engine | [Example](examples/policy/basic_policy.py) | [📖](https://docs.praison.ai/features/policy-engine) |
|
||||
| ↳ Thinking Budgets | [Example](examples/thinking/basic_thinking.py) | [📖](https://docs.praison.ai/features/thinking-budgets) |
|
||||
| ↳ Output Styles | [Example](examples/output/basic_output.py) | [📖](https://docs.praison.ai/features/output-styles) |
|
||||
| ↳ Context Compaction | [Example](examples/compaction/basic_compaction.py) | [📖](https://docs.praison.ai/features/context-compaction) |
|
||||
| **📊 Monitoring & Management** | | |
|
||||
| ↳ Sessions Management | [Example](examples/python/sessions/comprehensive-session-management.py) | [📖](https://docs.praison.ai/features/sessions) |
|
||||
| ↳ Auto-Save Sessions | [Example](#session-management-python) | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| ↳ History in Context | [Example](#session-management-python) | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| ↳ Telemetry | [Example](examples/python/telemetry/production-telemetry-example.py) | [📖](https://docs.praison.ai/features/telemetry) |
|
||||
| ↳ Project Docs (.praison/docs/) | [Example](#docs-cli) | [📖](https://docs.praison.ai/docs/cli/docs) |
|
||||
| ↳ AI Commit Messages | [Example](#ai-commit-cli) | [📖](https://docs.praison.ai/docs/cli/commit) |
|
||||
| ↳ @Mentions in Prompts | [Example](#mentions-in-prompts) | [📖](https://docs.praison.ai/docs/cli/mentions) |
|
||||
| **🖥️ CLI Features** | | |
|
||||
| ↳ Slash Commands | [Example](examples/python/cli/slash_commands_example.py) | [📖](https://docs.praison.ai/docs/cli/slash-commands) |
|
||||
| ↳ Autonomy Modes | [Example](examples/python/cli/autonomy_modes_example.py) | [📖](https://docs.praison.ai/docs/cli/autonomy-modes) |
|
||||
| ↳ Cost Tracking | [Example](examples/python/cli/cost_tracking_example.py) | [📖](https://docs.praison.ai/docs/cli/cost-tracking) |
|
||||
| ↳ Repository Map | [Example](examples/python/cli/repo_map_example.py) | [📖](https://docs.praison.ai/docs/cli/repo-map) |
|
||||
| ↳ Interactive TUI | [Example](examples/python/cli/interactive_tui_example.py) | [📖](https://docs.praison.ai/docs/cli/interactive-tui) |
|
||||
| ↳ Git Integration | [Example](examples/python/cli/git_integration_example.py) | [📖](https://docs.praison.ai/docs/cli/git-integration) |
|
||||
| ↳ Sandbox Execution | [Example](examples/python/cli/sandbox_execution_example.py) | [📖](https://docs.praison.ai/docs/cli/sandbox-execution) |
|
||||
| ↳ CLI Compare | [Example](examples/compare/cli_compare_basic.py) | [📖](https://docs.praison.ai/docs/cli/compare) |
|
||||
| ↳ Profile/Benchmark | [Example](#profile-benchmark) | [📖](https://docs.praison.ai/docs/cli/profile) |
|
||||
| ↳ Auto Mode | [Example](#auto-mode) | [📖](https://docs.praison.ai/docs/cli/auto) |
|
||||
| ↳ Init | [Example](#init) | [📖](https://docs.praison.ai/docs/cli/init) |
|
||||
| ↳ File Input | [Example](#file-input) | [📖](https://docs.praison.ai/docs/cli/file-input) |
|
||||
| ↳ Final Agent | [Example](#final-agent) | [📖](https://docs.praison.ai/docs/cli/final-agent) |
|
||||
| ↳ Max Tokens | [Example](#max-tokens) | [📖](https://docs.praison.ai/docs/cli/max-tokens) |
|
||||
| **🧪 Evaluation** | | |
|
||||
| ↳ Accuracy Evaluation | [Example](examples/eval/accuracy_example.py) | [📖](https://docs.praison.ai/docs/cli/eval) |
|
||||
| ↳ Performance Evaluation | [Example](examples/eval/performance_example.py) | [📖](https://docs.praison.ai/docs/cli/eval) |
|
||||
| ↳ Reliability Evaluation | [Example](examples/eval/reliability_example.py) | [📖](https://docs.praison.ai/docs/cli/eval) |
|
||||
| ↳ Criteria Evaluation | [Example](examples/eval/criteria_example.py) | [📖](https://docs.praison.ai/docs/cli/eval) |
|
||||
| **🎯 Agent Skills** | | |
|
||||
| ↳ Skills Management | [Example](examples/skills/basic_skill_usage.py) | [📖](https://docs.praison.ai/features/skills) |
|
||||
| ↳ Custom Skills | [Example](examples/skills/custom_skill_example.py) | [📖](https://docs.praison.ai/features/skills) |
|
||||
| **⏰ 24/7 Scheduling** | | |
|
||||
| ↳ Agent Scheduler | [Example](examples/python/scheduled_agents/news_checker_live.py) | [📖](https://docs.praison.ai/docs/cli/scheduler) |
|
||||
| Single Agent | [Example](examples/python/agents/single-agent.py) | [📖](https://docs.praison.ai/agents/single) |
|
||||
| Multi Agents | [Example](examples/python/general/mini_agents_example.py) | [📖](https://docs.praison.ai/concepts/agents) |
|
||||
| Auto Agents | [Example](examples/python/general/auto_agents_example.py) | [📖](https://docs.praison.ai/features/autoagents) |
|
||||
| Self Reflection AI Agents | [Example](examples/python/concepts/self-reflection-details.py) | [📖](https://docs.praison.ai/features/selfreflection) |
|
||||
| Reasoning AI Agents | [Example](examples/python/concepts/reasoning-extraction.py) | [📖](https://docs.praison.ai/features/reasoning) |
|
||||
| Multi Modal AI Agents | [Example](examples/python/general/multimodal.py) | [📖](https://docs.praison.ai/features/multimodal) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🔄 Workflows</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Simple Workflow | [Example](examples/python/workflows/simple_workflow.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| Workflow with Agents | [Example](examples/python/workflows/workflow_with_agents.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| Agentic Routing (`route()`) | [Example](examples/python/workflows/workflow_routing.py) | [📖](https://docs.praison.ai/features/routing) |
|
||||
| Parallel Execution (`parallel()`) | [Example](examples/python/workflows/workflow_parallel.py) | [📖](https://docs.praison.ai/features/parallelisation) |
|
||||
| Loop over List/CSV (`loop()`) | [Example](examples/python/workflows/workflow_loop_csv.py) | [📖](https://docs.praison.ai/features/repetitive) |
|
||||
| Evaluator-Optimizer (`repeat()`) | [Example](examples/python/workflows/workflow_repeat.py) | [📖](https://docs.praison.ai/features/evaluator-optimiser) |
|
||||
| Conditional Steps | [Example](examples/python/workflows/workflow_conditional.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| Workflow Branching | [Example](examples/python/workflows/workflow_branching.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| Workflow Early Stop | [Example](examples/python/workflows/workflow_early_stop.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| Workflow Checkpoints | [Example](examples/python/workflows/workflow_checkpoints.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>💻 Code & Development</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Code Interpreter Agents | [Example](examples/python/agents/code-agent.py) | [📖](https://docs.praison.ai/features/codeagent) |
|
||||
| AI Code Editing Tools | [Example](examples/python/code/code_editing_example.py) | [📖](https://docs.praison.ai/code/editing) |
|
||||
| External Agents (All) | [Example](examples/python/code/external_agents_example.py) | [📖](https://docs.praison.ai/code/external-agents) |
|
||||
| Claude Code CLI | [Example](examples/python/code/claude_code_example.py) | [📖](https://docs.praison.ai/code/claude-code) |
|
||||
| Gemini CLI | [Example](examples/python/code/gemini_cli_example.py) | [📖](https://docs.praison.ai/code/gemini-cli) |
|
||||
| Codex CLI | [Example](examples/python/code/codex_cli_example.py) | [📖](https://docs.praison.ai/code/codex-cli) |
|
||||
| Cursor CLI | [Example](examples/python/code/cursor_cli_example.py) | [📖](https://docs.praison.ai/code/cursor-cli) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🧠 Memory & Knowledge</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Memory (Short & Long Term) | [Example](examples/python/general/memory_example.py) | [📖](https://docs.praison.ai/concepts/memory) |
|
||||
| File-Based Memory | [Example](examples/python/general/memory_example.py) | [📖](https://docs.praison.ai/concepts/memory) |
|
||||
| Claude Memory Tool | [Example](#claude-memory-tool-cli) | [📖](https://docs.praison.ai/features/claude-memory-tool) |
|
||||
| Add Custom Knowledge | [Example](examples/python/concepts/knowledge-agents.py) | [📖](https://docs.praison.ai/features/knowledge) |
|
||||
| RAG Agents | [Example](examples/python/concepts/rag-agents.py) | [📖](https://docs.praison.ai/features/rag) |
|
||||
| Chat with PDF Agents | [Example](examples/python/concepts/chat-with-pdf.py) | [📖](https://docs.praison.ai/features/chat-with-pdf) |
|
||||
| Data Readers (PDF, DOCX, etc.) | [CLI](#knowledge-cli) | [📖](https://docs.praison.ai/api/praisonai/knowledge-readers-api) |
|
||||
| Vector Store Selection | [CLI](#knowledge-cli) | [📖](https://docs.praison.ai/api/praisonai/knowledge-vector-store-api) |
|
||||
| Retrieval Strategies | [CLI](#knowledge-cli) | [📖](https://docs.praison.ai/api/praisonai/knowledge-retrieval-api) |
|
||||
| Rerankers | [CLI](#knowledge-cli) | [📖](https://docs.praison.ai/api/praisonai/knowledge-reranker-api) |
|
||||
| Index Types (Vector/Keyword/Hybrid) | [CLI](#knowledge-cli) | [📖](https://docs.praison.ai/api/praisonai/knowledge-index-api) |
|
||||
| Query Engines (Sub-Question, etc.) | [CLI](#knowledge-cli) | [📖](https://docs.praison.ai/api/praisonai/knowledge-query-engine-api) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🔬 Research & Intelligence</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Deep Research Agents | [Example](examples/python/agents/research-agent.py) | [📖](https://docs.praison.ai/agents/deep-research) |
|
||||
| Query Rewriter Agent | [Example](#5-query-rewriter-agent) | [📖](https://docs.praison.ai/agents/query-rewriter) |
|
||||
| Native Web Search | [Example](examples/python/agents/websearch-agent.py) | [📖](https://docs.praison.ai/agents/websearch) |
|
||||
| Built-in Search Tools | [Example](examples/python/agents/websearch-agent.py) | [📖](https://docs.praison.ai/tools/tavily) |
|
||||
| Unified Web Search | [Example](src/praisonai-agents/examples/web_search_example.py) | [📖](https://docs.praison.ai/tools/web-search) |
|
||||
| Web Fetch (Anthropic) | [Example](#web-search-web-fetch--prompt-caching) | [📖](https://docs.praison.ai/features/model-capabilities) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>📋 Planning & Execution</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Planning Mode | [Example](examples/python/agents/planning-agent.py) | [📖](https://docs.praison.ai/features/planning-mode) |
|
||||
| Planning Tools | [Example](#3-agent-with-planning-mode) | [📖](https://docs.praison.ai/features/planning-mode) |
|
||||
| Planning Reasoning | [Example](#3-agent-with-planning-mode) | [📖](https://docs.praison.ai/features/planning-mode) |
|
||||
| Prompt Chaining | [Example](examples/python/general/prompt_chaining.py) | [📖](https://docs.praison.ai/features/promptchaining) |
|
||||
| Evaluator Optimiser | [Example](examples/python/general/evaluator-optimiser.py) | [📖](https://docs.praison.ai/features/evaluator-optimiser) |
|
||||
| Orchestrator Workers | [Example](examples/python/general/orchestrator-workers.py) | [📖](https://docs.praison.ai/features/orchestrator-worker) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>👥 Specialized Agents</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Data Analyst Agent | [Example](examples/python/agents/data-analyst-agent.py) | [📖](https://docs.praison.ai/agents/data-analyst) |
|
||||
| Finance Agent | [Example](examples/python/agents/finance-agent.py) | [📖](https://docs.praison.ai/agents/finance) |
|
||||
| Shopping Agent | [Example](examples/python/agents/shopping-agent.py) | [📖](https://docs.praison.ai/agents/shopping) |
|
||||
| Recommendation Agent | [Example](examples/python/agents/recommendation-agent.py) | [📖](https://docs.praison.ai/agents/recommendation) |
|
||||
| Wikipedia Agent | [Example](examples/python/agents/wikipedia-agent.py) | [📖](https://docs.praison.ai/agents/wikipedia) |
|
||||
| Programming Agent | [Example](examples/python/agents/programming-agent.py) | [📖](https://docs.praison.ai/agents/programming) |
|
||||
| Math Agents | [Example](examples/python/agents/math-agent.py) | [📖](https://docs.praison.ai/features/mathagent) |
|
||||
| Markdown Agent | [Example](examples/python/agents/markdown-agent.py) | [📖](https://docs.praison.ai/agents/markdown) |
|
||||
| Prompt Expander Agent | [Example](#prompt-expansion) | [📖](https://docs.praison.ai/agents/prompt-expander) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🎨 Media & Multimodal</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Image Generation Agent | [Example](examples/python/image/image-agent.py) | [📖](https://docs.praison.ai/features/image-generation) |
|
||||
| Image to Text Agent | [Example](examples/python/agents/image-to-text-agent.py) | [📖](https://docs.praison.ai/agents/image-to-text) |
|
||||
| Video Agent | [Example](examples/python/agents/video-agent.py) | [📖](https://docs.praison.ai/agents/video) |
|
||||
| Camera Integration | [Example](examples/python/camera/) | [📖](https://docs.praison.ai/features/camera-integration) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🔌 Protocols & Integration</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| MCP Transports | [Example](examples/python/mcp/mcp-transports-overview.py) | [📖](https://docs.praison.ai/mcp/transports) |
|
||||
| WebSocket MCP | [Example](examples/python/mcp/websocket-mcp.py) | [📖](https://docs.praison.ai/mcp/sse-transport) |
|
||||
| MCP Security | [Example](examples/python/mcp/mcp-security.py) | [📖](https://docs.praison.ai/mcp/transports) |
|
||||
| MCP Resumability | [Example](examples/python/mcp/mcp-resumability.py) | [📖](https://docs.praison.ai/mcp/sse-transport) |
|
||||
| MCP Config Management | [Example](#mcp-config-cli) | [📖](https://docs.praison.ai/docs/cli/mcp) |
|
||||
| LangChain Integrated Agents | [Example](examples/python/general/langchain_example.py) | [📖](https://docs.praison.ai/features/langchain) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🛡️ Safety & Control</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Guardrails | [Example](examples/python/guardrails/comprehensive-guardrails-example.py) | [📖](https://docs.praison.ai/features/guardrails) |
|
||||
| Human Approval | [Example](examples/python/general/human_approval_example.py) | [📖](https://docs.praison.ai/features/approval) |
|
||||
| Rules & Instructions | [Example](#7-rules--instructions) | [📖](https://docs.praison.ai/features/rules) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>⚙️ Advanced Features</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Async & Parallel Processing | [Example](examples/python/general/async_example.py) | [📖](https://docs.praison.ai/features/async) |
|
||||
| Parallelisation | [Example](examples/python/general/parallelisation.py) | [📖](https://docs.praison.ai/features/parallelisation) |
|
||||
| Repetitive Agents | [Example](examples/python/concepts/repetitive-agents.py) | [📖](https://docs.praison.ai/features/repetitive) |
|
||||
| Agent Handoffs | [Example](examples/python/handoff/handoff_basic.py) | [📖](https://docs.praison.ai/features/handoffs) |
|
||||
| Stateful Agents | [Example](examples/python/stateful/workflow-state-example.py) | [📖](https://docs.praison.ai/features/stateful-agents) |
|
||||
| Autonomous Workflow | [Example](examples/python/general/autonomous-agent.py) | [📖](https://docs.praison.ai/features/autonomous-workflow) |
|
||||
| Structured Output Agents | [Example](examples/python/general/structured_agents_example.py) | [📖](https://docs.praison.ai/features/structured) |
|
||||
| Model Router | [Example](examples/python/agents/router-agent-cost-optimization.py) | [📖](https://docs.praison.ai/features/model-router) |
|
||||
| Prompt Caching | [Example](#web-search-web-fetch--prompt-caching) | [📖](https://docs.praison.ai/features/model-capabilities) |
|
||||
| Fast Context | [Example](examples/context/00_agent_fast_context_basic.py) | [📖](https://docs.praison.ai/features/fast-context) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🛠️ Tools & Configuration</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| 100+ Custom Tools | [Example](examples/python/general/tools_example.py) | [📖](https://docs.praison.ai/tools/tools) |
|
||||
| YAML Configuration | [Example](examples/cookbooks/yaml/secondary_market_research_agents.yaml) | [📖](https://docs.praison.ai/developers/agents-playbook) |
|
||||
| 100+ LLM Support | [Example](examples/python/providers/openai/openai_gpt4_example.py) | [📖](https://docs.praison.ai/models) |
|
||||
| Callback Agents | [Example](examples/python/general/advanced-callback-systems.py) | [📖](https://docs.praison.ai/features/callbacks) |
|
||||
| Hooks | [Example](#10-hooks) | [📖](https://docs.praison.ai/features/hooks) |
|
||||
| Middleware System | [Example](examples/middleware/basic_middleware.py) | [📖](https://docs.praison.ai/features/middleware) |
|
||||
| Configurable Model | [Example](examples/middleware/configurable_model.py) | [📖](https://docs.praison.ai/features/configurable-model) |
|
||||
| Rate Limiter | [Example](examples/middleware/rate_limiter.py) | [📖](https://docs.praison.ai/features/rate-limiter) |
|
||||
| Injected Tool State | [Example](examples/middleware/injected_state.py) | [📖](https://docs.praison.ai/features/injected-state) |
|
||||
| Shadow Git Checkpoints | [Example](#11-shadow-git-checkpoints) | [📖](https://docs.praison.ai/features/checkpoints) |
|
||||
| Background Tasks | [Example](examples/background/basic_background.py) | [📖](https://docs.praison.ai/features/background-tasks) |
|
||||
| Policy Engine | [Example](examples/policy/basic_policy.py) | [📖](https://docs.praison.ai/features/policy-engine) |
|
||||
| Thinking Budgets | [Example](examples/thinking/basic_thinking.py) | [📖](https://docs.praison.ai/features/thinking-budgets) |
|
||||
| Output Styles | [Example](examples/output/basic_output.py) | [📖](https://docs.praison.ai/features/output-styles) |
|
||||
| Context Compaction | [Example](examples/compaction/basic_compaction.py) | [📖](https://docs.praison.ai/features/context-compaction) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>📊 Monitoring & Management</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Sessions Management | [Example](examples/python/sessions/comprehensive-session-management.py) | [📖](https://docs.praison.ai/features/sessions) |
|
||||
| Auto-Save Sessions | [Example](#session-management-python) | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| History in Context | [Example](#session-management-python) | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| Telemetry | [Example](examples/python/telemetry/production-telemetry-example.py) | [📖](https://docs.praison.ai/features/telemetry) |
|
||||
| Project Docs (.praison/docs/) | [Example](#docs-cli) | [📖](https://docs.praison.ai/docs/cli/docs) |
|
||||
| AI Commit Messages | [Example](#ai-commit-cli) | [📖](https://docs.praison.ai/docs/cli/commit) |
|
||||
| @Mentions in Prompts | [Example](#mentions-in-prompts) | [📖](https://docs.praison.ai/docs/cli/mentions) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🖥️ CLI Features</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Slash Commands | [Example](examples/python/cli/slash_commands_example.py) | [📖](https://docs.praison.ai/docs/cli/slash-commands) |
|
||||
| Autonomy Modes | [Example](examples/python/cli/autonomy_modes_example.py) | [📖](https://docs.praison.ai/docs/cli/autonomy-modes) |
|
||||
| Cost Tracking | [Example](examples/python/cli/cost_tracking_example.py) | [📖](https://docs.praison.ai/docs/cli/cost-tracking) |
|
||||
| Repository Map | [Example](examples/python/cli/repo_map_example.py) | [📖](https://docs.praison.ai/docs/cli/repo-map) |
|
||||
| Interactive TUI | [Example](examples/python/cli/interactive_tui_example.py) | [📖](https://docs.praison.ai/docs/cli/interactive-tui) |
|
||||
| Git Integration | [Example](examples/python/cli/git_integration_example.py) | [📖](https://docs.praison.ai/docs/cli/git-integration) |
|
||||
| Sandbox Execution | [Example](examples/python/cli/sandbox_execution_example.py) | [📖](https://docs.praison.ai/docs/cli/sandbox-execution) |
|
||||
| CLI Compare | [Example](examples/compare/cli_compare_basic.py) | [📖](https://docs.praison.ai/docs/cli/compare) |
|
||||
| Profile/Benchmark | [Example](#profile-benchmark) | [📖](https://docs.praison.ai/docs/cli/profile) |
|
||||
| Auto Mode | [Example](#auto-mode) | [📖](https://docs.praison.ai/docs/cli/auto) |
|
||||
| Init | [Example](#init) | [📖](https://docs.praison.ai/docs/cli/init) |
|
||||
| File Input | [Example](#file-input) | [📖](https://docs.praison.ai/docs/cli/file-input) |
|
||||
| Final Agent | [Example](#final-agent) | [📖](https://docs.praison.ai/docs/cli/final-agent) |
|
||||
| Max Tokens | [Example](#max-tokens) | [📖](https://docs.praison.ai/docs/cli/max-tokens) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🧪 Evaluation</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Accuracy Evaluation | [Example](examples/eval/accuracy_example.py) | [📖](https://docs.praison.ai/docs/cli/eval) |
|
||||
| Performance Evaluation | [Example](examples/eval/performance_example.py) | [📖](https://docs.praison.ai/docs/cli/eval) |
|
||||
| Reliability Evaluation | [Example](examples/eval/reliability_example.py) | [📖](https://docs.praison.ai/docs/cli/eval) |
|
||||
| Criteria Evaluation | [Example](examples/eval/criteria_example.py) | [📖](https://docs.praison.ai/docs/cli/eval) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>🎯 Agent Skills</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Skills Management | [Example](examples/skills/basic_skill_usage.py) | [📖](https://docs.praison.ai/features/skills) |
|
||||
| Custom Skills | [Example](examples/skills/custom_skill_example.py) | [📖](https://docs.praison.ai/features/skills) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>⏰ 24/7 Scheduling</strong></summary>
|
||||
|
||||
| Feature | Code | Docs |
|
||||
|---------|:----:|:----:|
|
||||
| Agent Scheduler | [Example](examples/python/scheduled_agents/news_checker_live.py) | [📖](https://docs.praison.ai/docs/cli/scheduler) |
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
@@ -537,7 +745,7 @@ memory.handle_command("/memory save my_session")
|
||||
| `memory="sqlite"` | Built-in | SQLite with indexing |
|
||||
| `memory="chromadb"` | chromadb | Vector/semantic search |
|
||||
|
||||
### 6. Rules & Instructions
|
||||
### 7. Rules & Instructions
|
||||
|
||||
PraisonAI auto-discovers instruction files from your project root and git root:
|
||||
|
||||
@@ -586,7 +794,7 @@ activation: always # always, glob, manual, ai_decision
|
||||
- Follow PEP 8
|
||||
```
|
||||
|
||||
### 7. Auto-Generated Memories
|
||||
### 8. Auto-Generated Memories
|
||||
|
||||
```python
|
||||
from praisonaiagents.memory import FileMemory, AutoMemory
|
||||
@@ -601,7 +809,7 @@ memories = auto.process_interaction(
|
||||
# Extracts: name="John", preference="Python for backend"
|
||||
```
|
||||
|
||||
### 8. Agentic Workflows
|
||||
### 9. Agentic Workflows
|
||||
|
||||
Create powerful multi-agent workflows with the `Workflow` class:
|
||||
|
||||
@@ -871,7 +1079,7 @@ callbacks:
|
||||
on_workflow_complete: log_complete
|
||||
```
|
||||
|
||||
### 9. Hooks
|
||||
### 10. Hooks
|
||||
|
||||
Intercept and modify agent behavior at various lifecycle points:
|
||||
|
||||
@@ -910,7 +1118,7 @@ praisonai hooks validate hooks.json # Validate configuration
|
||||
```
|
||||
|
||||
|
||||
### 10. Shadow Git Checkpoints
|
||||
### 11. Shadow Git Checkpoints
|
||||
|
||||
File-level undo/restore using shadow git:
|
||||
|
||||
@@ -947,7 +1155,7 @@ praisonai checkpoint restore abc123 # Restore to checkpoint
|
||||
|
||||
---
|
||||
|
||||
### 11. Background Tasks
|
||||
### 12. Background Tasks
|
||||
|
||||
Run agent tasks asynchronously without blocking:
|
||||
|
||||
@@ -985,7 +1193,7 @@ praisonai background clear # Clear completed tasks
|
||||
|
||||
---
|
||||
|
||||
### 12. Policy Engine
|
||||
### 13. Policy Engine
|
||||
|
||||
Control what agents can and cannot do with policy-based execution:
|
||||
|
||||
@@ -1026,7 +1234,7 @@ praisonai policy init # Create template
|
||||
|
||||
---
|
||||
|
||||
### 13. Thinking Budgets
|
||||
### 14. Thinking Budgets
|
||||
|
||||
Configure token budgets for extended thinking:
|
||||
|
||||
@@ -1059,7 +1267,7 @@ praisonai thinking stats # Show usage statistics
|
||||
|
||||
---
|
||||
|
||||
### 14. Output Styles
|
||||
### 15. Output Styles
|
||||
|
||||
Configure how agents format their responses:
|
||||
|
||||
@@ -1089,7 +1297,7 @@ praisonai output set concise # Set output style
|
||||
|
||||
---
|
||||
|
||||
### 15. Context Compaction
|
||||
### 16. Context Compaction
|
||||
|
||||
Automatically manage context window size:
|
||||
|
||||
@@ -1124,7 +1332,7 @@ praisonai compaction stats # Show statistics
|
||||
|
||||
---
|
||||
|
||||
### 16. Field Names Reference (A-I-G-S)
|
||||
### 17. Field Names Reference (A-I-G-S)
|
||||
|
||||
PraisonAI accepts both old (agents.yaml) and new (workflow.yaml) field names. Use the **canonical names** for new projects:
|
||||
|
||||
@@ -1158,7 +1366,7 @@ steps: # Define steps (not 'tasks')
|
||||
|
||||
> **Note:** The parser accepts both old and new names. Run `praisonai workflow validate <file.yaml>` to see suggestions for canonical names.
|
||||
|
||||
### 12. Extended agents.yaml with Workflow Patterns
|
||||
### 18. Extended agents.yaml with Workflow Patterns
|
||||
|
||||
**Feature Parity:** Both `agents.yaml` and `workflow.yaml` now support the same features:
|
||||
- All workflow patterns (route, parallel, loop, repeat)
|
||||
@@ -1234,7 +1442,7 @@ Run with the same simple command:
|
||||
praisonai agents.yaml
|
||||
```
|
||||
|
||||
### 12. MCP (Model Context Protocol)
|
||||
### 19. MCP (Model Context Protocol)
|
||||
|
||||
PraisonAI supports MCP Protocol Revision 2025-11-25 with multiple transports.
|
||||
|
||||
@@ -1310,7 +1518,7 @@ server.run() # stdio for Claude Desktop
|
||||
| Security | Origin validation, DNS rebinding prevention |
|
||||
| WebSocket | Auto-reconnect with exponential backoff |
|
||||
|
||||
### 13. A2A (Agent2Agent Protocol)
|
||||
### 20. A2A (Agent2Agent Protocol)
|
||||
|
||||
PraisonAI supports the [A2A Protocol](https://a2a-protocol.org) for agent-to-agent communication, enabling your agents to be discovered and collaborate with other AI agents.
|
||||
|
||||
@@ -1358,6 +1566,22 @@ app.include_router(a2a.get_router())
|
||||
|
||||
PraisonAI provides a powerful CLI for no-code automation and quick prototyping.
|
||||
|
||||
### CLI Quick Reference
|
||||
|
||||
| Category | Commands |
|
||||
|----------|----------|
|
||||
| **Execution** | `praisonai`, `--auto`, `--interactive`, `--chat` |
|
||||
| **Research** | `research`, `--query-rewrite`, `--deep-research` |
|
||||
| **Planning** | `--planning`, `--planning-tools`, `--planning-reasoning` |
|
||||
| **Workflows** | `workflow run`, `workflow list`, `workflow auto` |
|
||||
| **Memory** | `memory show`, `memory add`, `memory search`, `memory clear` |
|
||||
| **Knowledge** | `knowledge add`, `knowledge query`, `knowledge list` |
|
||||
| **Sessions** | `session list`, `session resume`, `session delete` |
|
||||
| **Tools** | `tools list`, `tools info`, `tools search` |
|
||||
| **MCP** | `mcp list`, `mcp create`, `mcp enable` |
|
||||
| **Development** | `commit`, `docs`, `checkpoint`, `hooks` |
|
||||
| **Scheduling** | `schedule start`, `schedule list`, `schedule stop` |
|
||||
|
||||
### Auto Mode
|
||||
```bash
|
||||
pip install praisonai
|
||||
@@ -2641,6 +2865,84 @@ PraisonAI provides zero-dependency persistent memory for agents. For detailed ex
|
||||
|
||||
---
|
||||
|
||||
## 📚 Knowledge & Retrieval (RAG)
|
||||
|
||||
PraisonAI provides a complete knowledge stack for building RAG applications with multiple vector stores, retrieval strategies, rerankers, and query modes.
|
||||
|
||||
### Knowledge CLI Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `praisonai knowledge add <file\|dir\|url>` | Add documents to knowledge base |
|
||||
| `praisonai knowledge query <question>` | Query knowledge base with RAG |
|
||||
| `praisonai knowledge list` | List indexed documents |
|
||||
| `praisonai knowledge clear` | Clear knowledge base |
|
||||
| `praisonai knowledge stats` | Show knowledge base statistics |
|
||||
|
||||
### Knowledge CLI Options
|
||||
|
||||
| Option | Values | Description |
|
||||
|--------|--------|-------------|
|
||||
| `--vector-store` | `memory`, `chroma`, `pinecone`, `qdrant`, `weaviate` | Vector store backend |
|
||||
| `--retrieval` | `basic`, `fusion`, `recursive`, `auto_merge` | Retrieval strategy |
|
||||
| `--reranker` | `simple`, `llm`, `cross_encoder`, `cohere` | Reranking method |
|
||||
| `--index-type` | `vector`, `keyword`, `hybrid` | Index type |
|
||||
| `--query-mode` | `default`, `sub_question`, `summarize` | Query mode |
|
||||
|
||||
### Knowledge CLI Examples
|
||||
|
||||
```bash
|
||||
# Add documents
|
||||
praisonai knowledge add ./docs/
|
||||
praisonai knowledge add https://example.com/page.html
|
||||
praisonai knowledge add "*.pdf"
|
||||
|
||||
# Query with advanced options
|
||||
praisonai knowledge query "How to authenticate?" --retrieval fusion --reranker llm
|
||||
|
||||
# Full advanced query
|
||||
praisonai knowledge query "authentication flow" \
|
||||
--vector-store chroma \
|
||||
--retrieval fusion \
|
||||
--reranker llm \
|
||||
--index-type hybrid \
|
||||
--query-mode sub_question
|
||||
```
|
||||
|
||||
### Knowledge SDK Usage
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent, Knowledge
|
||||
|
||||
# Simple usage with Agent
|
||||
agent = Agent(
|
||||
name="Research Assistant",
|
||||
knowledge=["docs/manual.pdf", "data/faq.txt"],
|
||||
knowledge_config={
|
||||
"vector_store": {"provider": "chroma"}
|
||||
}
|
||||
)
|
||||
response = agent.chat("How do I authenticate?")
|
||||
|
||||
# Direct Knowledge usage
|
||||
knowledge = Knowledge()
|
||||
knowledge.add("document.pdf")
|
||||
results = knowledge.search("authentication", limit=5)
|
||||
```
|
||||
|
||||
### Knowledge Stack Features Table
|
||||
|
||||
| Feature | Description | SDK Docs | CLI Docs |
|
||||
|---------|-------------|----------|----------|
|
||||
| **Data Readers** | Load PDF, Markdown, Text, HTML, URLs | [SDK](/docs/sdk/praisonaiagents/knowledge/protocols) | [CLI](/docs/cli/knowledge) |
|
||||
| **Vector Stores** | ChromaDB, Pinecone, Qdrant, Weaviate, In-Memory | [SDK](/docs/sdk/praisonaiagents/knowledge/protocols) | [CLI](/docs/cli/knowledge) |
|
||||
| **Retrieval Strategies** | Basic, Fusion (RRF), Recursive, Auto-Merge | [SDK](/docs/sdk/praisonaiagents/knowledge/protocols) | [CLI](/docs/cli/knowledge) |
|
||||
| **Rerankers** | Simple, LLM, Cross-Encoder, Cohere | [SDK](/docs/sdk/praisonaiagents/knowledge/protocols) | [CLI](/docs/cli/knowledge) |
|
||||
| **Index Types** | Vector, Keyword (BM25), Hybrid | [SDK](/docs/sdk/praisonaiagents/knowledge/protocols) | [CLI](/docs/cli/knowledge) |
|
||||
| **Query Engines** | Default, Sub-Question, Summarize | [SDK](/docs/sdk/praisonaiagents/knowledge/protocols) | [CLI](/docs/cli/knowledge) |
|
||||
|
||||
---
|
||||
|
||||
## 🔬 Advanced Features
|
||||
|
||||
### Research & Intelligence
|
||||
@@ -2755,6 +3057,44 @@ agent.chat("Hello!") # Auto-persists messages, runs, traces
|
||||
| `praisonai persistence migrate` | Apply schema migrations |
|
||||
| `praisonai persistence status` | Show schema status |
|
||||
|
||||
### Knowledge CLI Commands {#knowledge-cli}
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `praisonai knowledge add <source>` | Add file, directory, URL, or glob pattern |
|
||||
| `praisonai knowledge query "<question>"` | Query knowledge base with RAG |
|
||||
| `praisonai knowledge list` | List indexed documents |
|
||||
| `praisonai knowledge clear` | Clear knowledge base |
|
||||
| `praisonai knowledge stats` | Show knowledge base statistics |
|
||||
|
||||
**Knowledge Query Flags:**
|
||||
|
||||
| Flag | Values | Default |
|
||||
|------|--------|---------|
|
||||
| `--vector-store` | `memory`, `chroma`, `pinecone`, `qdrant`, `weaviate` | `chroma` |
|
||||
| `--retrieval-strategy` | `basic`, `fusion`, `recursive`, `auto_merge` | `basic` |
|
||||
| `--reranker` | `none`, `simple`, `llm`, `cross_encoder`, `cohere` | `none` |
|
||||
| `--index-type` | `vector`, `keyword`, `hybrid` | `vector` |
|
||||
| `--query-mode` | `default`, `sub_question`, `summarize` | `default` |
|
||||
| `--workspace` | Path to workspace directory | Current dir |
|
||||
| `--session` | Session ID for persistence | - |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Add documents
|
||||
praisonai knowledge add document.pdf
|
||||
praisonai knowledge add ./docs/
|
||||
praisonai knowledge add "*.md"
|
||||
|
||||
# Query with options
|
||||
praisonai knowledge query "How to authenticate?" \
|
||||
--vector-store chroma \
|
||||
--retrieval-strategy fusion \
|
||||
--reranker simple \
|
||||
--query-mode sub_question
|
||||
```
|
||||
|
||||
### Databases Table
|
||||
|
||||
| Database | Store Type | Install | Example | Docs |
|
||||
@@ -2901,6 +3241,119 @@ cd src/praisonai && uv publish
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ & Troubleshooting
|
||||
|
||||
<details>
|
||||
<summary><strong>ModuleNotFoundError: No module named 'praisonaiagents'</strong></summary>
|
||||
|
||||
Install the package:
|
||||
```bash
|
||||
pip install praisonaiagents
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>API key not found / Authentication error</strong></summary>
|
||||
|
||||
Ensure your API key is set:
|
||||
```bash
|
||||
export OPENAI_API_KEY=your_key_here
|
||||
```
|
||||
|
||||
For other providers, see [Environment Variables](#environment-variables).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>How do I use a local model (Ollama)?</strong></summary>
|
||||
|
||||
```bash
|
||||
# Start Ollama server first
|
||||
ollama serve
|
||||
|
||||
# Set environment variable
|
||||
export OPENAI_BASE_URL=http://localhost:11434/v1
|
||||
```
|
||||
|
||||
See [Ollama Integration](#ollama-integration) for more details.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>How do I persist conversations to a database?</strong></summary>
|
||||
|
||||
Use the `db` parameter:
|
||||
```python
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
db=db(database_url="postgresql://localhost/mydb"),
|
||||
session_id="my-session"
|
||||
)
|
||||
```
|
||||
|
||||
See [Persistence (Databases)](#-persistence-databases) for supported databases.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>How do I enable agent memory?</strong></summary>
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
memory=True, # Enables file-based memory (no extra deps!)
|
||||
user_id="user123"
|
||||
)
|
||||
```
|
||||
|
||||
See [Agent Memory](#6-agent-memory-zero-dependencies) for more options.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>How do I run multiple agents together?</strong></summary>
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent, PraisonAIAgents
|
||||
|
||||
agent1 = Agent(instructions="Research topics")
|
||||
agent2 = Agent(instructions="Summarize findings")
|
||||
agents = PraisonAIAgents(agents=[agent1, agent2])
|
||||
agents.start()
|
||||
```
|
||||
|
||||
See [Multi Agents](#2-multi-agents) for more examples.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>How do I use MCP tools?</strong></summary>
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent, MCP
|
||||
|
||||
agent = Agent(
|
||||
tools=MCP("npx @modelcontextprotocol/server-memory")
|
||||
)
|
||||
```
|
||||
|
||||
See [MCP Protocol](#19-mcp-model-context-protocol) for all transport options.
|
||||
|
||||
</details>
|
||||
|
||||
### Getting Help
|
||||
|
||||
- 📚 [Full Documentation](https://docs.praison.ai)
|
||||
- 🐛 [Report Issues](https://github.com/MervinPraison/PraisonAI/issues)
|
||||
- 💬 [Discussions](https://github.com/MervinPraison/PraisonAI/discussions)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<p><strong>Made with ❤️ by the PraisonAI Team</strong></p>
|
||||
<p>
|
||||
|
||||
@@ -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.5.0" \
|
||||
"praisonai>=2.6.2" \
|
||||
"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.5.0" \
|
||||
"praisonai>=2.6.2" \
|
||||
"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.5.0" \
|
||||
"praisonai>=2.6.2" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
Async Runs Examples
|
||||
===================
|
||||
|
||||
This directory contains examples for the PraisonAI Async Jobs API.
|
||||
|
||||
Examples:
|
||||
---------
|
||||
1. sdk_submit_poll_result.py - Python SDK example for submit/poll/result
|
||||
2. cli_submit_poll_result.sh - Bash/curl example for submit/poll/result
|
||||
3. idempotency.sh - Demonstrates idempotency key usage
|
||||
4. cancel.sh - Demonstrates job cancellation
|
||||
5. sse_stream.sh - Demonstrates SSE streaming
|
||||
|
||||
Prerequisites:
|
||||
--------------
|
||||
1. Set OPENAI_API_KEY environment variable
|
||||
2. Start the jobs server:
|
||||
|
||||
cd /path/to/praisonai-package/src/praisonai
|
||||
python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory
|
||||
|
||||
3. Run examples:
|
||||
|
||||
python sdk_submit_poll_result.py
|
||||
bash cli_submit_poll_result.sh
|
||||
bash idempotency.sh
|
||||
bash cancel.sh
|
||||
bash sse_stream.sh
|
||||
|
||||
Environment Variables:
|
||||
----------------------
|
||||
- OPENAI_API_KEY: Required for agent execution
|
||||
- PRAISONAI_BASE_URL: API server URL (default: http://127.0.0.1:8005)
|
||||
"""
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# CLI Example: Cancel a Running Job
|
||||
#
|
||||
# This example demonstrates how to cancel a running job.
|
||||
#
|
||||
# Requirements:
|
||||
# - Jobs server running on http://127.0.0.1:8005
|
||||
#
|
||||
# Usage:
|
||||
# bash cancel.sh
|
||||
|
||||
set -e
|
||||
|
||||
API_URL="${PRAISONAI_BASE_URL:-http://127.0.0.1:8005}"
|
||||
|
||||
echo "============================================================"
|
||||
echo "PraisonAI Job Cancellation Example"
|
||||
echo "============================================================"
|
||||
|
||||
# Submit a long-running job
|
||||
echo ""
|
||||
echo "1. Submitting a job..."
|
||||
RESPONSE=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"prompt": "Write a detailed essay about the history of computing."}')
|
||||
|
||||
JOB_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID"
|
||||
|
||||
# Wait briefly
|
||||
echo ""
|
||||
echo "2. Waiting 1 second..."
|
||||
sleep 1
|
||||
|
||||
# Cancel the job
|
||||
echo ""
|
||||
echo "3. Cancelling job..."
|
||||
CANCEL_RESPONSE=$(curl -s -X POST "$API_URL/api/v1/runs/$JOB_ID/cancel")
|
||||
STATUS=$(echo "$CANCEL_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
|
||||
echo " Status after cancel: $STATUS"
|
||||
|
||||
# Verify cancellation
|
||||
echo ""
|
||||
echo "4. Verification:"
|
||||
if [ "$STATUS" = "cancelled" ]; then
|
||||
echo " ✓ SUCCESS: Job was cancelled"
|
||||
else
|
||||
echo " ℹ Job status: $STATUS (may have completed before cancel)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "Cancellation example completed!"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
# CLI Example: Submit, Poll, and Get Result for Async Jobs
|
||||
#
|
||||
# This example demonstrates how to use the PraisonAI CLI to:
|
||||
# 1. Submit an async job
|
||||
# 2. Check job status
|
||||
# 3. Get the result
|
||||
#
|
||||
# Requirements:
|
||||
# - OPENAI_API_KEY environment variable set
|
||||
# - Jobs server running on http://127.0.0.1:8005
|
||||
#
|
||||
# Usage:
|
||||
# bash cli_submit_poll_result.sh
|
||||
|
||||
set -e
|
||||
|
||||
API_URL="${PRAISONAI_BASE_URL:-http://127.0.0.1:8005}"
|
||||
|
||||
echo "============================================================"
|
||||
echo "PraisonAI Async Jobs CLI Example"
|
||||
echo "============================================================"
|
||||
|
||||
# Check if server is running
|
||||
echo ""
|
||||
echo "Checking server health..."
|
||||
if ! curl -s "$API_URL/health" > /dev/null 2>&1; then
|
||||
echo "✗ Server not available at $API_URL"
|
||||
echo " Start the server with: python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Server is healthy"
|
||||
|
||||
# 1. Submit a job
|
||||
echo ""
|
||||
echo "1. Submitting job..."
|
||||
RESPONSE=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"prompt": "What is 2+2? Answer with just the number."}')
|
||||
|
||||
JOB_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID"
|
||||
|
||||
# 2. Poll for status
|
||||
echo ""
|
||||
echo "2. Polling for status..."
|
||||
while true; do
|
||||
STATUS_RESPONSE=$(curl -s "$API_URL/api/v1/runs/$JOB_ID")
|
||||
STATUS=$(echo "$STATUS_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
|
||||
PROGRESS=$(echo "$STATUS_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['progress']['percentage'])")
|
||||
|
||||
echo " Status: $STATUS | Progress: ${PROGRESS}%"
|
||||
|
||||
if [ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "cancelled" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 3. Get result
|
||||
echo ""
|
||||
echo "3. Getting result..."
|
||||
if [ "$STATUS" = "succeeded" ]; then
|
||||
RESULT=$(curl -s "$API_URL/api/v1/runs/$JOB_ID/result")
|
||||
echo " Result: $(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['result'])")"
|
||||
echo " Duration: $(echo "$RESULT" | python3 -c "import sys,json; print(f\"{json.load(sys.stdin)['duration_seconds']:.2f}s\")")"
|
||||
else
|
||||
echo " Job did not succeed: $STATUS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "Example completed!"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# CLI Example: Idempotency Key Usage
|
||||
#
|
||||
# This example demonstrates how idempotency keys prevent duplicate job submissions.
|
||||
# Submitting the same request with the same idempotency key returns the existing job.
|
||||
#
|
||||
# Requirements:
|
||||
# - Jobs server running on http://127.0.0.1:8005
|
||||
#
|
||||
# Usage:
|
||||
# bash idempotency.sh
|
||||
|
||||
set -e
|
||||
|
||||
API_URL="${PRAISONAI_BASE_URL:-http://127.0.0.1:8005}"
|
||||
IDEM_KEY="example-idem-key-$(date +%s)"
|
||||
|
||||
echo "============================================================"
|
||||
echo "PraisonAI Idempotency Key Example"
|
||||
echo "============================================================"
|
||||
|
||||
# First submission
|
||||
echo ""
|
||||
echo "1. First submission with Idempotency-Key: $IDEM_KEY"
|
||||
RESPONSE1=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Idempotency-Key: $IDEM_KEY" \
|
||||
-d '{"prompt": "What is 1+1?"}')
|
||||
|
||||
JOB_ID1=$(echo "$RESPONSE1" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID1"
|
||||
|
||||
# Second submission with same key
|
||||
echo ""
|
||||
echo "2. Second submission with SAME Idempotency-Key: $IDEM_KEY"
|
||||
RESPONSE2=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Idempotency-Key: $IDEM_KEY" \
|
||||
-d '{"prompt": "Different prompt but same key"}')
|
||||
|
||||
JOB_ID2=$(echo "$RESPONSE2" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID2"
|
||||
|
||||
# Verify same job ID
|
||||
echo ""
|
||||
echo "3. Verification:"
|
||||
if [ "$JOB_ID1" = "$JOB_ID2" ]; then
|
||||
echo " ✓ SUCCESS: Same job ID returned (no duplicate created)"
|
||||
else
|
||||
echo " ✗ FAILURE: Different job IDs returned"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "Idempotency example completed!"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SDK Example: Submit, Poll, and Get Result for Async Jobs
|
||||
|
||||
This example demonstrates how to use the PraisonAI Jobs SDK to:
|
||||
1. Submit an async job
|
||||
2. Poll for status
|
||||
3. Get the result
|
||||
|
||||
Requirements:
|
||||
- OPENAI_API_KEY environment variable set
|
||||
- Jobs server running on http://127.0.0.1:8005
|
||||
|
||||
Usage:
|
||||
python sdk_submit_poll_result.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import httpx
|
||||
|
||||
# Configuration
|
||||
API_URL = os.getenv("PRAISONAI_BASE_URL", "http://127.0.0.1:8005")
|
||||
POLL_INTERVAL = 2 # seconds
|
||||
|
||||
|
||||
def submit_job(prompt: str, idempotency_key: str = None) -> dict:
|
||||
"""Submit a job and return the response."""
|
||||
headers = {}
|
||||
if idempotency_key:
|
||||
headers["Idempotency-Key"] = idempotency_key
|
||||
|
||||
with httpx.Client(timeout=30.0) as client:
|
||||
response = client.post(
|
||||
f"{API_URL}/api/v1/runs",
|
||||
json={"prompt": prompt},
|
||||
headers=headers if headers else None
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_status(job_id: str) -> dict:
|
||||
"""Get job status."""
|
||||
with httpx.Client(timeout=30.0) as client:
|
||||
response = client.get(f"{API_URL}/api/v1/runs/{job_id}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_result(job_id: str) -> dict:
|
||||
"""Get job result."""
|
||||
with httpx.Client(timeout=30.0) as client:
|
||||
response = client.get(f"{API_URL}/api/v1/runs/{job_id}/result")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def wait_for_completion(job_id: str, max_wait: int = 120) -> dict:
|
||||
"""Poll until job completes."""
|
||||
start = time.time()
|
||||
while time.time() - start < max_wait:
|
||||
status = get_status(job_id)
|
||||
print(f" Status: {status['status']} | Progress: {status['progress']['percentage']:.0f}%")
|
||||
|
||||
if status["status"] in ("succeeded", "failed", "cancelled"):
|
||||
return status
|
||||
|
||||
# Honor retry_after if present
|
||||
wait_time = status.get("retry_after") or POLL_INTERVAL
|
||||
time.sleep(wait_time)
|
||||
|
||||
raise TimeoutError(f"Job {job_id} did not complete within {max_wait}s")
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("PraisonAI Async Jobs SDK Example")
|
||||
print("=" * 60)
|
||||
|
||||
# Check if server is running
|
||||
try:
|
||||
with httpx.Client(timeout=5.0) as client:
|
||||
health = client.get(f"{API_URL}/health")
|
||||
health.raise_for_status()
|
||||
print(f"✓ Server healthy: {health.json()['status']}")
|
||||
except Exception as e:
|
||||
print(f"✗ Server not available at {API_URL}: {e}")
|
||||
print(" Start the server with: python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory")
|
||||
return
|
||||
|
||||
# 1. Submit a job
|
||||
print("\n1. Submitting job...")
|
||||
prompt = "What is the capital of France? Answer in one word."
|
||||
result = submit_job(prompt)
|
||||
job_id = result["job_id"]
|
||||
print(f" Job ID: {job_id}")
|
||||
print(f" Status: {result['status']}")
|
||||
print(f" Poll URL: {result['poll_url']}")
|
||||
|
||||
# 2. Wait for completion
|
||||
print("\n2. Waiting for completion...")
|
||||
final_status = wait_for_completion(job_id)
|
||||
print(f" Final status: {final_status['status']}")
|
||||
|
||||
# 3. Get result
|
||||
if final_status["status"] == "succeeded":
|
||||
print("\n3. Getting result...")
|
||||
result = get_result(job_id)
|
||||
print(f" Result: {result['result']}")
|
||||
print(f" Duration: {result['duration_seconds']:.2f}s")
|
||||
else:
|
||||
print(f"\n3. Job did not succeed: {final_status.get('error', 'Unknown error')}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Example completed successfully!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# CLI Example: SSE Streaming
|
||||
#
|
||||
# This example demonstrates how to stream job progress via Server-Sent Events.
|
||||
#
|
||||
# Requirements:
|
||||
# - Jobs server running on http://127.0.0.1:8005
|
||||
#
|
||||
# Usage:
|
||||
# bash sse_stream.sh
|
||||
|
||||
set -e
|
||||
|
||||
API_URL="${PRAISONAI_BASE_URL:-http://127.0.0.1:8005}"
|
||||
|
||||
echo "============================================================"
|
||||
echo "PraisonAI SSE Streaming Example"
|
||||
echo "============================================================"
|
||||
|
||||
# Submit a job
|
||||
echo ""
|
||||
echo "1. Submitting job..."
|
||||
RESPONSE=$(curl -s -X POST "$API_URL/api/v1/runs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"prompt": "Count from 1 to 5."}')
|
||||
|
||||
JOB_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
|
||||
echo " Job ID: $JOB_ID"
|
||||
|
||||
# Stream progress
|
||||
echo ""
|
||||
echo "2. Streaming progress (Ctrl+C to stop)..."
|
||||
echo " Connecting to: $API_URL/api/v1/runs/$JOB_ID/stream"
|
||||
echo ""
|
||||
|
||||
# Use curl to stream SSE events (timeout after 60s)
|
||||
curl -s -N --max-time 60 "$API_URL/api/v1/runs/$JOB_ID/stream" | while read -r line; do
|
||||
if [[ "$line" == data:* ]]; then
|
||||
data="${line#data: }"
|
||||
if [ "$data" = "[DONE]" ]; then
|
||||
echo " Stream completed"
|
||||
break
|
||||
fi
|
||||
echo " Event: $data"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "Streaming example completed!"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Basic Agent Example
|
||||
* Demonstrates simple agent usage with OpenAI
|
||||
*/
|
||||
|
||||
import { Agent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const agent = new Agent({
|
||||
name: 'Assistant',
|
||||
instructions: 'You are a helpful assistant. Be concise and informative.',
|
||||
llm: 'openai/gpt-4o-mini'
|
||||
});
|
||||
|
||||
const response = await agent.chat('What is TypeScript?');
|
||||
console.log('Response:', response.text);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Context Agent Example
|
||||
* Demonstrates RAG-enabled agent with knowledge base
|
||||
*/
|
||||
|
||||
import { ContextAgent, createContextAgent, KnowledgeBase } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create knowledge base
|
||||
const kb = new KnowledgeBase();
|
||||
|
||||
// Add documents
|
||||
await kb.add({
|
||||
id: 'doc1',
|
||||
content: 'TypeScript is a strongly typed programming language that builds on JavaScript.',
|
||||
metadata: { topic: 'typescript' }
|
||||
});
|
||||
|
||||
await kb.add({
|
||||
id: 'doc2',
|
||||
content: 'TypeScript adds optional static typing and class-based object-oriented programming.',
|
||||
metadata: { topic: 'typescript' }
|
||||
});
|
||||
|
||||
await kb.add({
|
||||
id: 'doc3',
|
||||
content: 'PraisonAI is a framework for building AI agents in TypeScript and Python.',
|
||||
metadata: { topic: 'praisonai' }
|
||||
});
|
||||
|
||||
// Create context agent with knowledge base
|
||||
const agent = createContextAgent({
|
||||
name: 'KnowledgeAgent',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
knowledgeBase: kb,
|
||||
maxContextDocs: 3
|
||||
});
|
||||
|
||||
// Query with context
|
||||
const response = await agent.chat('What is TypeScript?');
|
||||
console.log('Response:', response.text);
|
||||
|
||||
// Check conversation history
|
||||
console.log('\nConversation history:', agent.getHistory().length, 'messages');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Image Agent Example
|
||||
* Demonstrates image analysis capabilities
|
||||
*/
|
||||
|
||||
import { ImageAgent, createImageAgent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const agent = createImageAgent({
|
||||
name: 'VisionAgent',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
verbose: true
|
||||
});
|
||||
|
||||
console.log('Image Agent created:', agent.name);
|
||||
|
||||
// Note: For actual image analysis, provide a real image URL
|
||||
// const analysis = await agent.analyze({
|
||||
// imageUrl: 'https://example.com/image.jpg',
|
||||
// prompt: 'Describe this image in detail'
|
||||
// });
|
||||
// console.log('Analysis:', analysis);
|
||||
|
||||
// Chat without image
|
||||
const response = await agent.chat('What types of images can you analyze?');
|
||||
console.log('Response:', response);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Multi-Agent Example
|
||||
* Demonstrates multiple agents working together
|
||||
*/
|
||||
|
||||
import { Agent, PraisonAIAgents, Task } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create specialized agents
|
||||
const researcher = new Agent({
|
||||
name: 'Researcher',
|
||||
role: 'Research Specialist',
|
||||
goal: 'Find accurate information on topics',
|
||||
instructions: 'You research topics thoroughly and provide factual information.'
|
||||
});
|
||||
|
||||
const writer = new Agent({
|
||||
name: 'Writer',
|
||||
role: 'Content Writer',
|
||||
goal: 'Create engaging content from research',
|
||||
instructions: 'You write clear, engaging content based on provided research.'
|
||||
});
|
||||
|
||||
// Create tasks
|
||||
const researchTask = new Task({
|
||||
description: 'Research the benefits of TypeScript over JavaScript',
|
||||
expectedOutput: 'A list of key benefits with explanations',
|
||||
agent: researcher
|
||||
});
|
||||
|
||||
const writeTask = new Task({
|
||||
description: 'Write a short article about TypeScript benefits',
|
||||
expectedOutput: 'A 200-word article',
|
||||
agent: writer
|
||||
});
|
||||
|
||||
// Run agents
|
||||
const agents = new PraisonAIAgents({
|
||||
agents: [researcher, writer],
|
||||
tasks: [researchTask, writeTask],
|
||||
process: 'sequential'
|
||||
});
|
||||
|
||||
const result = await agents.start();
|
||||
console.log('Result:', result);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Deep Research Agent Example
|
||||
* Demonstrates comprehensive research with citations
|
||||
*/
|
||||
|
||||
import { DeepResearchAgent, createDeepResearchAgent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const agent = createDeepResearchAgent({
|
||||
name: 'Scholar',
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
maxIterations: 5,
|
||||
verbose: true
|
||||
});
|
||||
|
||||
console.log('Research Agent:', agent.name);
|
||||
|
||||
// Conduct research
|
||||
const result = await agent.research('What are the key benefits of TypeScript?');
|
||||
|
||||
console.log('\n=== Research Results ===');
|
||||
console.log('Answer:', result.answer.substring(0, 500) + '...');
|
||||
console.log('\nConfidence:', result.confidence);
|
||||
console.log('Reasoning steps:', result.reasoning.length);
|
||||
|
||||
// Show reasoning
|
||||
console.log('\n=== Reasoning Process ===');
|
||||
result.reasoning.forEach(step => {
|
||||
console.log(`Step ${step.step}: ${step.thought}`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Router Agent Example
|
||||
* Demonstrates routing requests to specialized agents
|
||||
*/
|
||||
|
||||
import { RouterAgent, createRouter, routeConditions, Agent } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create specialized agents
|
||||
const mathAgent = {
|
||||
name: 'MathAgent',
|
||||
chat: async (input: string) => ({ text: `Math answer for: ${input}` })
|
||||
} as any;
|
||||
|
||||
const codeAgent = {
|
||||
name: 'CodeAgent',
|
||||
chat: async (input: string) => ({ text: `Code solution for: ${input}` })
|
||||
} as any;
|
||||
|
||||
const generalAgent = {
|
||||
name: 'GeneralAgent',
|
||||
chat: async (input: string) => ({ text: `General response: ${input}` })
|
||||
} as any;
|
||||
|
||||
// Create router with conditions
|
||||
const router = createRouter({
|
||||
name: 'SmartRouter',
|
||||
routes: [
|
||||
{
|
||||
agent: mathAgent,
|
||||
condition: routeConditions.keywords(['math', 'calculate', 'number', 'equation']),
|
||||
priority: 10
|
||||
},
|
||||
{
|
||||
agent: codeAgent,
|
||||
condition: routeConditions.keywords(['code', 'program', 'function', 'javascript']),
|
||||
priority: 10
|
||||
}
|
||||
],
|
||||
defaultAgent: generalAgent
|
||||
});
|
||||
|
||||
// Test routing
|
||||
const queries = [
|
||||
'Calculate 2 + 2',
|
||||
'Write a function to sort an array',
|
||||
'What is the weather today?'
|
||||
];
|
||||
|
||||
for (const query of queries) {
|
||||
const result = await router.route(query);
|
||||
console.log(`Query: "${query}"`);
|
||||
console.log(`Routed to: ${result?.agent.name || 'default'}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* AutoAgents Example
|
||||
* Demonstrates automatic agent generation from task descriptions
|
||||
*/
|
||||
|
||||
import { AutoAgents, createAutoAgents } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const auto = createAutoAgents({
|
||||
pattern: 'sequential',
|
||||
verbose: true
|
||||
});
|
||||
|
||||
// Analyze task complexity
|
||||
console.log('=== Complexity Analysis ===');
|
||||
const tasks = [
|
||||
'Write a hello world function',
|
||||
'Build a web scraper that extracts product data',
|
||||
'Create a multi-step data pipeline with validation, transformation, and multiple output formats'
|
||||
];
|
||||
|
||||
tasks.forEach(task => {
|
||||
const complexity = auto.analyzeComplexity(task);
|
||||
console.log(`"${task.substring(0, 40)}..." -> ${complexity}`);
|
||||
});
|
||||
|
||||
// Recommend patterns
|
||||
console.log('\n=== Pattern Recommendations ===');
|
||||
const patternTasks = [
|
||||
'Process items in parallel',
|
||||
'Route requests to different handlers',
|
||||
'Orchestrate multiple workers',
|
||||
'Evaluate and optimize results'
|
||||
];
|
||||
|
||||
patternTasks.forEach(task => {
|
||||
const pattern = auto.recommendPattern(task);
|
||||
console.log(`"${task}" -> ${pattern}`);
|
||||
});
|
||||
|
||||
// Generate agent configuration
|
||||
console.log('\n=== Generate Agent Config ===');
|
||||
console.log('Generating agents for: "Build a web scraper"');
|
||||
|
||||
const team = await auto.generate('Build a web scraper that extracts product prices from e-commerce sites');
|
||||
|
||||
console.log('\nGenerated Team:');
|
||||
console.log('Pattern:', team.pattern);
|
||||
console.log('Agents:', team.agents.length);
|
||||
team.agents.forEach(agent => {
|
||||
console.log(` - ${agent.name}: ${agent.role}`);
|
||||
});
|
||||
console.log('Tasks:', team.tasks.length);
|
||||
team.tasks.forEach(task => {
|
||||
console.log(` - ${task.description.substring(0, 50)}...`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Redis Database Example
|
||||
*/
|
||||
|
||||
import { createMemoryRedis } from '../../../src/praisonai-ts/src';
|
||||
|
||||
async function main() {
|
||||
// Use in-memory Redis for this example
|
||||
const redis = createMemoryRedis();
|
||||
await redis.connect();
|
||||
|
||||
console.log('Connected to Redis');
|
||||
|
||||
// Basic get/set
|
||||
await redis.set('user:123', { name: 'John', role: 'admin' });
|
||||
const user = await redis.get('user:123');
|
||||
console.log('User:', user);
|
||||
|
||||
// Set with TTL
|
||||
await redis.set('session:abc', { token: 'xyz' }, 60);
|
||||
const ttl = await redis.ttl('session:abc');
|
||||
console.log('Session TTL:', ttl, 'seconds');
|
||||
|
||||
// Hash operations
|
||||
await redis.hset('profile:123', 'email', '[email protected]');
|
||||
await redis.hset('profile:123', 'phone', '555-1234');
|
||||
const profile = await redis.hgetall('profile:123');
|
||||
console.log('Profile:', profile);
|
||||
|
||||
// List operations
|
||||
await redis.rpush('queue:tasks', { id: 1, type: 'process' });
|
||||
await redis.rpush('queue:tasks', { id: 2, type: 'notify' });
|
||||
const tasks = await redis.lrange('queue:tasks', 0, -1);
|
||||
console.log('Tasks:', tasks);
|
||||
|
||||
// Pub/Sub (memory only)
|
||||
await redis.subscribe('events', (message) => {
|
||||
console.log('Received event:', message);
|
||||
});
|
||||
await redis.publish('events', JSON.stringify({ type: 'user_joined' }));
|
||||
|
||||
// Cleanup
|
||||
await redis.disconnect();
|
||||
console.log('Done!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Evaluation Framework Example
|
||||
* Demonstrates accuracy, performance, and reliability evaluation
|
||||
*/
|
||||
|
||||
import { accuracyEval, performanceEval, reliabilityEval, EvalSuite } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
console.log('=== Accuracy Evaluation ===');
|
||||
const accuracyResult = await accuracyEval({
|
||||
input: 'What is 2 + 2?',
|
||||
expectedOutput: '4',
|
||||
actualOutput: 'The answer is 4'
|
||||
});
|
||||
console.log('Passed:', accuracyResult.passed);
|
||||
console.log('Score:', accuracyResult.score);
|
||||
console.log('Duration:', accuracyResult.duration, 'ms');
|
||||
|
||||
console.log('\n=== Performance Evaluation ===');
|
||||
const perfResult = await performanceEval({
|
||||
func: async () => {
|
||||
// Simulate some work
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
return 'done';
|
||||
},
|
||||
iterations: 5,
|
||||
warmupRuns: 1
|
||||
});
|
||||
console.log('Iterations:', perfResult.times.length);
|
||||
console.log('Avg time:', perfResult.avgTime.toFixed(2), 'ms');
|
||||
console.log('Min time:', perfResult.minTime.toFixed(2), 'ms');
|
||||
console.log('Max time:', perfResult.maxTime.toFixed(2), 'ms');
|
||||
|
||||
console.log('\n=== Reliability Evaluation ===');
|
||||
const reliabilityResult = await reliabilityEval({
|
||||
expectedToolCalls: ['search', 'calculate'],
|
||||
actualToolCalls: ['search', 'calculate']
|
||||
});
|
||||
console.log('Passed:', reliabilityResult.passed);
|
||||
console.log('Score:', reliabilityResult.score);
|
||||
|
||||
console.log('\n=== Eval Suite ===');
|
||||
const suite = new EvalSuite();
|
||||
|
||||
await suite.runAccuracy('test1', {
|
||||
input: 'Hello',
|
||||
expectedOutput: 'Hi',
|
||||
actualOutput: 'Hi there!'
|
||||
});
|
||||
|
||||
await suite.runAccuracy('test2', {
|
||||
input: 'Goodbye',
|
||||
expectedOutput: 'Bye',
|
||||
actualOutput: 'Bye!'
|
||||
});
|
||||
|
||||
const summary = suite.getSummary();
|
||||
console.log('Total tests:', summary.total);
|
||||
console.log('Passed:', summary.passed);
|
||||
console.log('Failed:', summary.failed);
|
||||
console.log('Pass rate:', (summary.passRate * 100).toFixed(1) + '%');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Content Guardrails Example
|
||||
* Demonstrates input/output validation
|
||||
*/
|
||||
|
||||
import { guardrail, GuardrailManager, builtinGuardrails, LLMGuardrail } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create custom guardrail
|
||||
const noSpamGuardrail = guardrail({
|
||||
name: 'no_spam',
|
||||
check: (content) => {
|
||||
const spamWords = ['buy now', 'free money', 'click here'];
|
||||
const hasSpam = spamWords.some(word =>
|
||||
content.toLowerCase().includes(word)
|
||||
);
|
||||
return {
|
||||
status: hasSpam ? 'failed' : 'passed',
|
||||
message: hasSpam ? 'Content contains spam' : undefined
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Use built-in guardrails
|
||||
const maxLength = builtinGuardrails.maxLength(500);
|
||||
const minLength = builtinGuardrails.minLength(10);
|
||||
const blockedWords = builtinGuardrails.blockedWords(['badword1', 'badword2']);
|
||||
|
||||
// Create manager
|
||||
const manager = new GuardrailManager();
|
||||
manager.add(noSpamGuardrail);
|
||||
manager.add(maxLength);
|
||||
manager.add(minLength);
|
||||
manager.add(blockedWords);
|
||||
|
||||
// Test content
|
||||
const testCases = [
|
||||
'This is a normal, helpful response about programming.',
|
||||
'BUY NOW! Free money if you click here!',
|
||||
'Short',
|
||||
'This is a valid response that provides useful information about TypeScript and its benefits for developers.'
|
||||
];
|
||||
|
||||
console.log('=== Guardrail Tests ===\n');
|
||||
|
||||
for (const content of testCases) {
|
||||
console.log(`Content: "${content.substring(0, 40)}..."`);
|
||||
const result = await manager.runAll(content);
|
||||
console.log(` Passed: ${result.passed}`);
|
||||
if (!result.passed) {
|
||||
result.results
|
||||
.filter(r => r.status === 'failed')
|
||||
.forEach(r => console.log(` Failed: ${r.message}`));
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// LLM Guardrail example
|
||||
console.log('=== LLM Guardrail ===');
|
||||
const llmGuard = new LLMGuardrail({
|
||||
name: 'quality_check',
|
||||
criteria: 'Content must be helpful and professional'
|
||||
});
|
||||
console.log('LLM Guardrail created:', llmGuard.name);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Knowledge Base (RAG) Example
|
||||
* Demonstrates retrieval-augmented generation
|
||||
*/
|
||||
|
||||
import { KnowledgeBase, createKnowledgeBase, Chunking } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create knowledge base
|
||||
const kb = createKnowledgeBase();
|
||||
|
||||
// Add documents
|
||||
const documents = [
|
||||
{
|
||||
id: 'ts-intro',
|
||||
content: 'TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.',
|
||||
metadata: { category: 'introduction', language: 'typescript' }
|
||||
},
|
||||
{
|
||||
id: 'ts-types',
|
||||
content: 'TypeScript adds optional static typing and class-based object-oriented programming to the language.',
|
||||
metadata: { category: 'features', language: 'typescript' }
|
||||
},
|
||||
{
|
||||
id: 'ts-benefits',
|
||||
content: 'Benefits of TypeScript include catching errors early, better IDE support, and improved code maintainability.',
|
||||
metadata: { category: 'benefits', language: 'typescript' }
|
||||
},
|
||||
{
|
||||
id: 'js-intro',
|
||||
content: 'JavaScript is a lightweight, interpreted programming language with first-class functions.',
|
||||
metadata: { category: 'introduction', language: 'javascript' }
|
||||
}
|
||||
];
|
||||
|
||||
for (const doc of documents) {
|
||||
await kb.add(doc);
|
||||
}
|
||||
|
||||
console.log('Documents added:', kb.size);
|
||||
|
||||
// Search
|
||||
console.log('\n=== Search: "TypeScript benefits" ===');
|
||||
const results = await kb.search('TypeScript benefits', 3);
|
||||
results.forEach(r => {
|
||||
console.log(`Score: ${r.score.toFixed(2)} - ${r.document.content.substring(0, 50)}...`);
|
||||
});
|
||||
|
||||
// Build context
|
||||
console.log('\n=== Context for LLM ===');
|
||||
const context = kb.buildContext(results);
|
||||
console.log(context);
|
||||
|
||||
// Chunking example
|
||||
console.log('\n=== Chunking Example ===');
|
||||
const chunker = new Chunking({ chunkSize: 100, overlap: 20 });
|
||||
const longText = 'This is a long document. '.repeat(20);
|
||||
const chunks = chunker.chunk(longText);
|
||||
console.log(`Chunked into ${chunks.length} pieces`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Conversation Memory Example
|
||||
* Demonstrates memory management for conversations
|
||||
*/
|
||||
|
||||
import { Memory, createMemory } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const memory = createMemory({
|
||||
maxEntries: 100,
|
||||
maxTokens: 10000
|
||||
});
|
||||
|
||||
// Simulate a conversation
|
||||
await memory.add('What is TypeScript?', 'user');
|
||||
await memory.add('TypeScript is a strongly typed programming language that builds on JavaScript.', 'assistant');
|
||||
await memory.add('What are its main benefits?', 'user');
|
||||
await memory.add('Key benefits include type safety, better IDE support, and easier refactoring.', 'assistant');
|
||||
await memory.add('Can you give an example?', 'user');
|
||||
|
||||
console.log('Memory size:', memory.size, 'entries\n');
|
||||
|
||||
// Get recent messages
|
||||
console.log('=== Recent Messages ===');
|
||||
const recent = memory.getRecent(3);
|
||||
recent.forEach(entry => {
|
||||
console.log(`[${entry.role}]: ${entry.content.substring(0, 50)}...`);
|
||||
});
|
||||
|
||||
// Search memory
|
||||
console.log('\n=== Search Results for "TypeScript" ===');
|
||||
const results = await memory.search('TypeScript');
|
||||
results.forEach(r => {
|
||||
console.log(`Score: ${r.score.toFixed(2)} - ${r.entry.content.substring(0, 40)}...`);
|
||||
});
|
||||
|
||||
// Build context
|
||||
console.log('\n=== Full Context ===');
|
||||
console.log(memory.buildContext());
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Observability Example
|
||||
* Demonstrates tracing and monitoring
|
||||
*/
|
||||
|
||||
import { MemoryObservabilityAdapter, ConsoleObservabilityAdapter } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Memory adapter - stores traces in memory
|
||||
const memoryAdapter = new MemoryObservabilityAdapter();
|
||||
|
||||
// Start a trace
|
||||
const trace = memoryAdapter.startTrace('agent-execution');
|
||||
console.log('Trace started:', trace.traceId);
|
||||
|
||||
// Create spans for different operations
|
||||
const llmSpan = trace.startSpan('llm-call', 'llm');
|
||||
llmSpan.setAttribute('model', 'gpt-4o-mini');
|
||||
llmSpan.setAttribute('tokens', 150);
|
||||
await new Promise(r => setTimeout(r, 100)); // Simulate work
|
||||
llmSpan.end();
|
||||
|
||||
const toolSpan = trace.startSpan('tool-execution', 'tool');
|
||||
toolSpan.setAttribute('tool', 'calculator');
|
||||
toolSpan.addEvent('tool-started', { input: '2+2' });
|
||||
await new Promise(r => setTimeout(r, 50)); // Simulate work
|
||||
toolSpan.addEvent('tool-completed', { output: '4' });
|
||||
toolSpan.end();
|
||||
|
||||
// End trace
|
||||
trace.end();
|
||||
|
||||
// Retrieve trace data
|
||||
const traceData = memoryAdapter.getTrace(trace.traceId);
|
||||
console.log('\n=== Trace Data ===');
|
||||
console.log('Trace ID:', traceData?.traceId);
|
||||
console.log('Spans:', traceData?.spans.length);
|
||||
console.log('Duration:', traceData?.endTime! - traceData?.startTime!, 'ms');
|
||||
|
||||
// Show spans
|
||||
console.log('\n=== Spans ===');
|
||||
traceData?.spans.forEach(span => {
|
||||
console.log(`- ${span.name} (${span.kind}): ${span.endTime! - span.startTime}ms`);
|
||||
if (span.attributes) {
|
||||
Object.entries(span.attributes).forEach(([k, v]) => {
|
||||
console.log(` ${k}: ${v}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Console adapter example
|
||||
console.log('\n=== Console Adapter ===');
|
||||
const consoleAdapter = new ConsoleObservabilityAdapter();
|
||||
const trace2 = consoleAdapter.startTrace('demo-trace');
|
||||
const span = trace2.startSpan('demo-span', 'custom');
|
||||
span.end();
|
||||
trace2.end();
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Planning System Example
|
||||
* Demonstrates plans, steps, and todo lists
|
||||
*/
|
||||
|
||||
import { Plan, PlanStep, TodoList, TodoItem, PlanStorage, createPlan, createTodoList } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create a project plan
|
||||
console.log('=== Project Plan ===');
|
||||
const plan = createPlan({
|
||||
name: 'Build AI Agent',
|
||||
description: 'Create a fully functional AI agent'
|
||||
});
|
||||
|
||||
plan.addStep(new PlanStep({ description: 'Design agent architecture' }));
|
||||
plan.addStep(new PlanStep({ description: 'Implement core functionality' }));
|
||||
plan.addStep(new PlanStep({ description: 'Add tool support' }));
|
||||
plan.addStep(new PlanStep({ description: 'Write tests' }));
|
||||
plan.addStep(new PlanStep({ description: 'Deploy to production' }));
|
||||
|
||||
console.log('Plan:', plan.name);
|
||||
console.log('Steps:', plan.steps.length);
|
||||
|
||||
// Execute steps
|
||||
plan.start();
|
||||
plan.steps[0].start();
|
||||
plan.steps[0].complete();
|
||||
plan.steps[1].start();
|
||||
plan.steps[1].complete();
|
||||
|
||||
const progress = plan.getProgress();
|
||||
console.log(`Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`);
|
||||
|
||||
// Create todo list
|
||||
console.log('\n=== Todo List ===');
|
||||
const todos = createTodoList('Sprint Tasks');
|
||||
|
||||
todos.add(new TodoItem({ content: 'Review PR #123', priority: 'high' }));
|
||||
todos.add(new TodoItem({ content: 'Fix bug in router', priority: 'high' }));
|
||||
todos.add(new TodoItem({ content: 'Update documentation', priority: 'medium' }));
|
||||
todos.add(new TodoItem({ content: 'Refactor tests', priority: 'low' }));
|
||||
|
||||
console.log('Total items:', todos.items.length);
|
||||
console.log('High priority:', todos.getByPriority('high').length);
|
||||
|
||||
// Complete some items
|
||||
todos.items[0].complete();
|
||||
todos.items[1].start();
|
||||
|
||||
console.log('Pending:', todos.getPending().length);
|
||||
console.log('Completed:', todos.getCompleted().length);
|
||||
|
||||
const todoProgress = todos.getProgress();
|
||||
console.log(`Progress: ${todoProgress.completed}/${todoProgress.total} (${todoProgress.percentage}%)`);
|
||||
|
||||
// Use storage
|
||||
console.log('\n=== Plan Storage ===');
|
||||
const storage = new PlanStorage();
|
||||
storage.savePlan(plan);
|
||||
storage.saveTodoList(todos);
|
||||
|
||||
console.log('Saved plans:', storage.listPlans().length);
|
||||
console.log('Saved todo lists:', storage.listTodoLists().length);
|
||||
|
||||
// Retrieve
|
||||
const retrieved = storage.getPlan(plan.id);
|
||||
console.log('Retrieved plan:', retrieved?.name);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Skills System Example
|
||||
* Demonstrates skill management and parsing
|
||||
*/
|
||||
|
||||
import { SkillManager, parseSkillFile, createSkillManager } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Parse a skill file
|
||||
const skillContent = `---
|
||||
name: code-review
|
||||
description: Reviews code for best practices and potential issues
|
||||
license: MIT
|
||||
metadata:
|
||||
author: praisonai
|
||||
version: "1.0"
|
||||
allowed-tools: Read Grep
|
||||
---
|
||||
|
||||
# Code Review Skill
|
||||
|
||||
When reviewing code:
|
||||
1. Check for syntax errors
|
||||
2. Look for security vulnerabilities
|
||||
3. Suggest performance improvements
|
||||
4. Ensure proper error handling
|
||||
`;
|
||||
|
||||
console.log('=== Parse Skill File ===');
|
||||
const skill = parseSkillFile(skillContent);
|
||||
console.log('Name:', skill.metadata.name);
|
||||
console.log('Description:', skill.metadata.description);
|
||||
console.log('License:', skill.metadata.license);
|
||||
console.log('Instructions preview:', skill.instructions.substring(0, 100) + '...');
|
||||
|
||||
// Create skill manager
|
||||
console.log('\n=== Skill Manager ===');
|
||||
const manager = createSkillManager({ paths: [] });
|
||||
|
||||
// Register skills manually
|
||||
manager.register({
|
||||
metadata: {
|
||||
name: 'typescript-expert',
|
||||
description: 'Expert knowledge in TypeScript development'
|
||||
},
|
||||
instructions: 'You are a TypeScript expert. Help with type definitions, generics, and best practices.'
|
||||
});
|
||||
|
||||
manager.register({
|
||||
metadata: {
|
||||
name: 'api-designer',
|
||||
description: 'Designs RESTful APIs following best practices'
|
||||
},
|
||||
instructions: 'You design clean, RESTful APIs with proper versioning and documentation.'
|
||||
});
|
||||
|
||||
// List skills
|
||||
console.log('Registered skills:');
|
||||
manager.list().forEach(s => {
|
||||
console.log(`- ${s.metadata.name}: ${s.metadata.description}`);
|
||||
});
|
||||
|
||||
// Get specific skill
|
||||
const tsSkill = manager.get('typescript-expert');
|
||||
console.log('\n=== TypeScript Expert Skill ===');
|
||||
console.log('Instructions:', tsSkill?.instructions);
|
||||
|
||||
// Generate prompt
|
||||
console.log('\n=== Generated Prompt ===');
|
||||
const prompt = manager.generatePrompt(['typescript-expert']);
|
||||
console.log(prompt.substring(0, 200) + '...');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Basic Tools Example
|
||||
* Demonstrates tool creation and usage
|
||||
*/
|
||||
|
||||
import { tool, ToolRegistry, getRegistry } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create tools
|
||||
const calculator = tool({
|
||||
name: 'calculator',
|
||||
description: 'Perform basic math calculations',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
expression: { type: 'string', description: 'Math expression to evaluate' }
|
||||
},
|
||||
required: ['expression']
|
||||
},
|
||||
execute: async ({ expression }) => {
|
||||
try {
|
||||
const result = eval(expression);
|
||||
return `Result: ${result}`;
|
||||
} catch (e) {
|
||||
return 'Error: Invalid expression';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const greeter = tool({
|
||||
name: 'greeter',
|
||||
description: 'Generate a greeting',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name to greet' }
|
||||
},
|
||||
required: ['name']
|
||||
},
|
||||
execute: async ({ name }) => `Hello, ${name}! Welcome to PraisonAI.`
|
||||
});
|
||||
|
||||
// Create registry and register tools
|
||||
const registry = new ToolRegistry();
|
||||
registry.register(calculator);
|
||||
registry.register(greeter);
|
||||
|
||||
console.log('Registered tools:', registry.list().map(t => t.name));
|
||||
|
||||
// Execute tools
|
||||
const calcResult = await calculator.execute({ expression: '10 * 5 + 2' });
|
||||
console.log('\nCalculator:', calcResult);
|
||||
|
||||
const greetResult = await greeter.execute({ name: 'Developer' });
|
||||
console.log('Greeter:', greetResult);
|
||||
|
||||
// Get OpenAI format
|
||||
console.log('\nOpenAI tool format:', JSON.stringify(calculator.toOpenAITool(), null, 2));
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Custom Tool Example
|
||||
* Demonstrates creating custom tools using BaseTool class (plugin pattern)
|
||||
*/
|
||||
|
||||
import { BaseTool, createTool, tool, ToolRegistry } from 'praisonai';
|
||||
|
||||
// Method 1: Extend BaseTool class (recommended for complex tools)
|
||||
class WeatherTool extends BaseTool<{ location: string; units?: string }, { temp: number; condition: string }> {
|
||||
name = 'get_weather';
|
||||
description = 'Get current weather for a location';
|
||||
parameters = {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
location: { type: 'string', description: 'City name' },
|
||||
units: { type: 'string', description: 'celsius or fahrenheit', default: 'celsius' }
|
||||
},
|
||||
required: ['location']
|
||||
};
|
||||
|
||||
async run(params: { location: string; units?: string }): Promise<{ temp: number; condition: string }> {
|
||||
// Simulate API call
|
||||
console.log(`Getting weather for ${params.location} in ${params.units || 'celsius'}`);
|
||||
return { temp: 22, condition: 'sunny' };
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Use createTool function (quick inline tools)
|
||||
const calculatorTool = createTool({
|
||||
name: 'calculator',
|
||||
description: 'Evaluate a math expression',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
expression: { type: 'string', description: 'Math expression to evaluate' }
|
||||
},
|
||||
required: ['expression']
|
||||
},
|
||||
run: (params: { expression: string }) => {
|
||||
try {
|
||||
return eval(params.expression);
|
||||
} catch {
|
||||
return 'Error: Invalid expression';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Method 3: Use tool() function with config object
|
||||
const greeterTool = tool({
|
||||
name: 'greeter',
|
||||
description: 'Generate a personalized greeting',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name to greet' },
|
||||
style: { type: 'string', description: 'Greeting style: formal or casual' }
|
||||
},
|
||||
required: ['name']
|
||||
},
|
||||
execute: async (params: { name: string; style?: string }) => {
|
||||
if (params.style === 'formal') {
|
||||
return `Good day, ${params.name}. How may I assist you?`;
|
||||
}
|
||||
return `Hey ${params.name}! What's up?`;
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
console.log('=== Custom Tool Examples ===\n');
|
||||
|
||||
// Test WeatherTool (class-based)
|
||||
console.log('--- WeatherTool (class-based) ---');
|
||||
const weather = new WeatherTool();
|
||||
console.log('Name:', weather.name);
|
||||
console.log('Description:', weather.description);
|
||||
|
||||
const weatherResult = await weather.run({ location: 'New York' });
|
||||
console.log('Result:', weatherResult);
|
||||
|
||||
// Safe execution with error handling
|
||||
const safeResult = await weather.safeRun({ location: 'London' });
|
||||
console.log('Safe result:', safeResult);
|
||||
|
||||
// Get OpenAI schema
|
||||
console.log('OpenAI Schema:', JSON.stringify(weather.getSchema(), null, 2));
|
||||
|
||||
// Test calculatorTool (createTool)
|
||||
console.log('\n--- Calculator Tool (createTool) ---');
|
||||
const calcResult = await calculatorTool.run({ expression: '10 * 5 + 2' });
|
||||
console.log('10 * 5 + 2 =', calcResult);
|
||||
|
||||
// Test greeterTool (tool function)
|
||||
console.log('\n--- Greeter Tool (tool function) ---');
|
||||
const greeting1 = await greeterTool.execute({ name: 'Alice' });
|
||||
console.log('Casual:', greeting1);
|
||||
|
||||
const greeting2 = await greeterTool.execute({ name: 'Mr. Smith', style: 'formal' });
|
||||
console.log('Formal:', greeting2);
|
||||
|
||||
// Register tools in registry
|
||||
console.log('\n--- Tool Registry ---');
|
||||
const registry = new ToolRegistry();
|
||||
registry.register(greeterTool);
|
||||
|
||||
console.log('Registered tools:', registry.list().map(t => t.name));
|
||||
console.log('OpenAI tools format:', registry.toOpenAITools().length, 'tools');
|
||||
|
||||
// Validate tool
|
||||
console.log('\n--- Validation ---');
|
||||
console.log('WeatherTool valid:', weather.validate());
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Pinecone Vector Store Example
|
||||
*/
|
||||
|
||||
import { createPineconeStore } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const pinecone = createPineconeStore({
|
||||
apiKey: process.env.PINECONE_API_KEY!
|
||||
});
|
||||
|
||||
const indexName = 'test-index';
|
||||
|
||||
// Create index
|
||||
console.log('Creating index...');
|
||||
await pinecone.createIndex({
|
||||
indexName,
|
||||
dimension: 3,
|
||||
metric: 'cosine'
|
||||
});
|
||||
|
||||
// List indexes
|
||||
const indexes = await pinecone.listIndexes();
|
||||
console.log('Indexes:', indexes);
|
||||
|
||||
// Upsert vectors
|
||||
console.log('Upserting vectors...');
|
||||
await pinecone.upsert({
|
||||
indexName,
|
||||
vectors: [
|
||||
{ id: 'vec-1', vector: [0.1, 0.2, 0.3], metadata: { category: 'A' } },
|
||||
{ id: 'vec-2', vector: [0.4, 0.5, 0.6], metadata: { category: 'B' } },
|
||||
{ id: 'vec-3', vector: [0.7, 0.8, 0.9], metadata: { category: 'A' } }
|
||||
]
|
||||
});
|
||||
|
||||
// Query
|
||||
console.log('Querying...');
|
||||
const results = await pinecone.query({
|
||||
indexName,
|
||||
vector: [0.1, 0.2, 0.3],
|
||||
topK: 2,
|
||||
includeMetadata: true
|
||||
});
|
||||
|
||||
console.log('Results:');
|
||||
for (const r of results) {
|
||||
console.log(` ${r.id}: score=${r.score.toFixed(4)}, metadata=${JSON.stringify(r.metadata)}`);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
console.log('Deleting index...');
|
||||
await pinecone.deleteIndex(indexName);
|
||||
console.log('Done!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Voice/TTS Example
|
||||
*/
|
||||
|
||||
import { createOpenAIVoice } from '../../../src/praisonai-ts/src';
|
||||
import { writeFileSync } from 'fs';
|
||||
|
||||
async function main() {
|
||||
const voice = createOpenAIVoice({
|
||||
apiKey: process.env.OPENAI_API_KEY
|
||||
});
|
||||
|
||||
// Check available voices
|
||||
const speakers = await voice.getSpeakers();
|
||||
console.log('Available voices:', speakers.map(s => s.name).join(', '));
|
||||
|
||||
// Generate speech
|
||||
console.log('Generating speech...');
|
||||
const audio = await voice.speak('Hello! Welcome to PraisonAI TypeScript SDK.', {
|
||||
voice: 'nova',
|
||||
speed: 1.0,
|
||||
format: 'mp3'
|
||||
});
|
||||
|
||||
// Save to file
|
||||
writeFileSync('output.mp3', audio);
|
||||
console.log('Audio saved to output.mp3');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Basic Workflow Example
|
||||
* Demonstrates sequential workflow execution
|
||||
*/
|
||||
|
||||
import { Workflow, parallel, route, loop } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
// Create a simple workflow
|
||||
const workflow = new Workflow<string, string>('DataProcessor')
|
||||
.step('validate', async (input) => {
|
||||
console.log('Step 1: Validating input...');
|
||||
return input.trim();
|
||||
})
|
||||
.step('transform', async (input) => {
|
||||
console.log('Step 2: Transforming...');
|
||||
return input.toUpperCase();
|
||||
})
|
||||
.step('format', async (input) => {
|
||||
console.log('Step 3: Formatting...');
|
||||
return `[PROCESSED] ${input}`;
|
||||
});
|
||||
|
||||
const result = await workflow.run(' hello world ');
|
||||
console.log('\nFinal output:', result.output);
|
||||
console.log('Steps executed:', result.steps.length);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Conditional Workflow Example
|
||||
* Demonstrates routing based on conditions
|
||||
*/
|
||||
|
||||
import { route } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
const processRequest = async (type: string) => {
|
||||
console.log(`Processing request type: ${type}`);
|
||||
|
||||
const result = await route([
|
||||
{
|
||||
condition: () => type === 'urgent',
|
||||
execute: async () => {
|
||||
console.log(' -> Urgent handler activated');
|
||||
return 'Processed urgently';
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: () => type === 'normal',
|
||||
execute: async () => {
|
||||
console.log(' -> Normal handler activated');
|
||||
return 'Processed normally';
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: () => true, // Default
|
||||
execute: async () => {
|
||||
console.log(' -> Default handler activated');
|
||||
return 'Processed with default handler';
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Test different request types
|
||||
const types = ['urgent', 'normal', 'unknown'];
|
||||
|
||||
for (const type of types) {
|
||||
const result = await processRequest(type);
|
||||
console.log(`Result: ${result}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Parallel Workflow Example
|
||||
* Demonstrates parallel execution of tasks
|
||||
*/
|
||||
|
||||
import { parallel } from 'praisonai';
|
||||
|
||||
async function main() {
|
||||
console.log('Starting parallel tasks...\n');
|
||||
|
||||
const results = await parallel([
|
||||
async () => {
|
||||
console.log('Task 1: Starting...');
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log('Task 1: Complete');
|
||||
return 'Result 1';
|
||||
},
|
||||
async () => {
|
||||
console.log('Task 2: Starting...');
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
console.log('Task 2: Complete');
|
||||
return 'Result 2';
|
||||
},
|
||||
async () => {
|
||||
console.log('Task 3: Starting...');
|
||||
await new Promise(r => setTimeout(r, 750));
|
||||
console.log('Task 3: Complete');
|
||||
return 'Result 3';
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\nAll tasks complete!');
|
||||
console.log('Results:', results);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Qdrant Knowledge Store Example for PraisonAI.
|
||||
Qdrant Knowledge Store - Agent-First Example
|
||||
|
||||
Demonstrates vector insert + search through the knowledge store.
|
||||
Demonstrates using Qdrant for knowledge-based RAG with an Agent.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
@@ -13,67 +13,59 @@ Run:
|
||||
python knowledge_qdrant.py
|
||||
|
||||
Expected Output:
|
||||
=== Qdrant Knowledge Store Demo ===
|
||||
Created collection: demo_knowledge
|
||||
Inserted 3 documents
|
||||
Search results: 2 documents found
|
||||
Agent responds using knowledge from documents
|
||||
"""
|
||||
|
||||
from praisonai.persistence.factory import create_knowledge_store
|
||||
from praisonai.persistence.knowledge.base import KnowledgeDocument
|
||||
import random
|
||||
from praisonaiagents import Agent
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
|
||||
# Create Qdrant knowledge store
|
||||
store = create_knowledge_store("qdrant", url="http://localhost:6333")
|
||||
# Create sample knowledge document
|
||||
sample_doc = """
|
||||
# AI and Programming Guide
|
||||
|
||||
collection_name = "demo_knowledge"
|
||||
dimension = 384 # Embedding dimension
|
||||
## Python Programming
|
||||
Python is a versatile programming language used for web development, data science, and AI.
|
||||
It has a simple syntax and extensive libraries.
|
||||
|
||||
# Create collection
|
||||
print("=== Qdrant Knowledge Store Demo ===")
|
||||
if store.collection_exists(collection_name):
|
||||
store.delete_collection(collection_name)
|
||||
## Machine Learning
|
||||
Machine learning enables computers to learn patterns from data without explicit programming.
|
||||
Common frameworks include TensorFlow, PyTorch, and scikit-learn.
|
||||
|
||||
store.create_collection(collection_name, dimension=dimension)
|
||||
print(f"Created collection: {collection_name}")
|
||||
## Databases
|
||||
PostgreSQL is a powerful open-source relational database system.
|
||||
It supports both SQL and JSON data types.
|
||||
"""
|
||||
|
||||
# Create sample documents with embeddings
|
||||
random.seed(42)
|
||||
documents = [
|
||||
KnowledgeDocument(
|
||||
id="doc-1",
|
||||
content="Python is a versatile programming language used for web development, data science, and AI.",
|
||||
embedding=[random.random() for _ in range(dimension)],
|
||||
metadata={"category": "programming", "language": "python"}
|
||||
),
|
||||
KnowledgeDocument(
|
||||
id="doc-2",
|
||||
content="Machine learning enables computers to learn patterns from data without explicit programming.",
|
||||
embedding=[random.random() for _ in range(dimension)],
|
||||
metadata={"category": "ai", "topic": "ml"}
|
||||
),
|
||||
KnowledgeDocument(
|
||||
id="doc-3",
|
||||
content="PostgreSQL is a powerful open-source relational database system.",
|
||||
embedding=[random.random() for _ in range(dimension)],
|
||||
metadata={"category": "database", "type": "sql"}
|
||||
),
|
||||
]
|
||||
# Save to temp file
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
doc_path = os.path.join(temp_dir, "guide.txt")
|
||||
with open(doc_path, "w") as f:
|
||||
f.write(sample_doc)
|
||||
|
||||
# Insert documents
|
||||
ids = store.insert(collection_name, documents)
|
||||
print(f"Inserted {len(ids)} documents")
|
||||
print("=== Qdrant Knowledge Store Demo (Agent-First) ===")
|
||||
|
||||
# Search for similar documents
|
||||
query_embedding = [random.random() for _ in range(dimension)]
|
||||
results = store.search(collection_name, query_embedding, limit=2)
|
||||
print(f"Search results: {len(results)} documents found")
|
||||
# Agent-first approach: use knowledge parameter with Qdrant
|
||||
agent = Agent(
|
||||
name="KnowledgeAssistant",
|
||||
instructions="You are a helpful assistant with access to technical documentation.",
|
||||
knowledge=[doc_path],
|
||||
knowledge_config={
|
||||
"vector_store": "qdrant",
|
||||
"url": "http://localhost:6333"
|
||||
}
|
||||
)
|
||||
|
||||
for doc in results:
|
||||
print(f" - {doc.id}: {doc.content[:50]}...")
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What programming language is good for AI?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
# Cleanup
|
||||
store.delete_collection(collection_name)
|
||||
store.close()
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
print("\n=== Demo Complete ===")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence.factory import create_knowledge_store
|
||||
# store = create_knowledge_store("qdrant", url="http://localhost:6333")
|
||||
@@ -1,83 +1,46 @@
|
||||
"""
|
||||
Redis State Store
|
||||
Redis State Store - Agent-First Example
|
||||
|
||||
Use Redis for state management, tracing, and caching.
|
||||
Use Redis for state management with an Agent.
|
||||
|
||||
Docker Setup:
|
||||
docker run -d --name redis -p 6379:6379 redis:7
|
||||
|
||||
Run:
|
||||
python redis_state.py
|
||||
|
||||
Expected output:
|
||||
- State operations (set/get/delete)
|
||||
- Tracing operations
|
||||
Agent responds with state persisted to Redis
|
||||
"""
|
||||
|
||||
from praisonaiagents import db
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
# Create Redis-only database (for state/tracing)
|
||||
redis_db = db.RedisDB(host="localhost", port=6379)
|
||||
print("=== Redis State Store (Agent-First) ===")
|
||||
|
||||
print("=== Redis State Store ===")
|
||||
|
||||
# Test state operations
|
||||
print("\n1. State Operations:")
|
||||
redis_db._init_stores() # Initialize stores
|
||||
|
||||
# Set a value
|
||||
redis_db._state_store.set("user:123:preferences", {
|
||||
"theme": "dark",
|
||||
"language": "en"
|
||||
})
|
||||
print(" Set user preferences")
|
||||
|
||||
# Get the value
|
||||
prefs = redis_db._state_store.get("user:123:preferences")
|
||||
print(f" Got preferences: {prefs}")
|
||||
|
||||
# Set with TTL (expires in 60 seconds)
|
||||
redis_db._state_store.set("temp:token", "abc123", ttl=60)
|
||||
print(" Set temporary token (60s TTL)")
|
||||
|
||||
# Delete
|
||||
redis_db._state_store.delete("user:123:preferences")
|
||||
print(" Deleted preferences")
|
||||
|
||||
# Test tracing
|
||||
print("\n2. Tracing Operations:")
|
||||
|
||||
# Start a trace
|
||||
trace_id = "trace-example-001"
|
||||
redis_db.on_trace_start(
|
||||
trace_id=trace_id,
|
||||
session_id="example-session",
|
||||
agent_name="ExampleAgent",
|
||||
metadata={"purpose": "demo"}
|
||||
# Agent-first approach: use db parameter with Redis state store
|
||||
my_db = db(
|
||||
database_url="sqlite:///conversations.db", # Conversations
|
||||
state_url="redis://localhost:6379" # State/tracing
|
||||
)
|
||||
print(f" Started trace: {trace_id}")
|
||||
|
||||
# Add spans
|
||||
span_id = "span-llm-001"
|
||||
redis_db.on_span_start(
|
||||
span_id=span_id,
|
||||
trace_id=trace_id,
|
||||
name="llm_call",
|
||||
attributes={"model": "gpt-4", "temperature": 0.7}
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
db=my_db,
|
||||
session_id="redis-state-session"
|
||||
)
|
||||
print(f" Started span: {span_id}")
|
||||
|
||||
redis_db.on_span_end(
|
||||
span_id=span_id,
|
||||
status="ok",
|
||||
attributes={"tokens": 150, "latency_ms": 450}
|
||||
)
|
||||
print(f" Ended span: {span_id}")
|
||||
# Chat - state is automatically managed via Redis
|
||||
response = agent.chat("Hello! Remember my name is Alice.")
|
||||
print(f"Response: {response}")
|
||||
|
||||
# End trace
|
||||
redis_db.on_trace_end(
|
||||
trace_id=trace_id,
|
||||
status="ok",
|
||||
metadata={"total_spans": 1}
|
||||
)
|
||||
print(f" Ended trace: {trace_id}")
|
||||
# Second message to test state
|
||||
response2 = agent.chat("What is my name?")
|
||||
print(f"Response: {response2}")
|
||||
|
||||
redis_db.close()
|
||||
my_db.close()
|
||||
print("\n✅ Done")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonaiagents import db
|
||||
# redis_db = db.RedisDB(host="localhost", port=6379)
|
||||
@@ -1,10 +1,7 @@
|
||||
"""
|
||||
Redis State Store Example for PraisonAI.
|
||||
Redis State Store - Agent-First Example
|
||||
|
||||
Demonstrates set/get/delete + TTL operations.
|
||||
|
||||
Requirements:
|
||||
pip install "praisonai[tools]"
|
||||
Use Redis for state management with an Agent.
|
||||
|
||||
Docker Setup:
|
||||
docker run -d --name praison-redis -p 6379:6379 redis:7
|
||||
@@ -13,55 +10,37 @@ Run:
|
||||
python state_redis.py
|
||||
|
||||
Expected Output:
|
||||
=== Redis State Store Demo ===
|
||||
Set key: session:user123:preferences
|
||||
Get value: {'theme': 'dark', 'language': 'en'}
|
||||
TTL remaining: 3600 seconds
|
||||
Hash operations: {'name': 'Alice', 'role': 'admin'}
|
||||
Agent responds with state persisted to Redis
|
||||
"""
|
||||
|
||||
from praisonai.persistence.factory import create_state_store
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
# Create Redis state store
|
||||
store = create_state_store("redis", url="redis://localhost:6379")
|
||||
print("=== Redis State Store (Agent-First) ===")
|
||||
|
||||
print("=== Redis State Store Demo ===")
|
||||
# Agent-first approach: use db parameter with Redis state store
|
||||
my_db = db(
|
||||
database_url="sqlite:///conversations.db", # Conversations
|
||||
state_url="redis://localhost:6379" # State/tracing
|
||||
)
|
||||
|
||||
# Basic set/get
|
||||
key = "session:user123:preferences"
|
||||
value = {"theme": "dark", "language": "en", "notifications": True}
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
db=my_db,
|
||||
session_id="redis-state-example"
|
||||
)
|
||||
|
||||
store.set(key, value)
|
||||
print(f"Set key: {key}")
|
||||
# Chat - state is automatically managed via Redis
|
||||
response = agent.chat("Hello! Remember my favorite color is blue.")
|
||||
print(f"Response: {response}")
|
||||
|
||||
retrieved = store.get(key)
|
||||
print(f"Get value: {retrieved}")
|
||||
|
||||
# Set with TTL (expires in 1 hour)
|
||||
ttl_key = "session:user123:token"
|
||||
store.set(ttl_key, "abc123xyz", ttl=3600)
|
||||
remaining = store.ttl(ttl_key)
|
||||
print(f"TTL remaining: {remaining} seconds")
|
||||
|
||||
# Hash operations (for structured data)
|
||||
hash_key = "user:123:profile"
|
||||
store.hset(hash_key, "name", "Alice")
|
||||
store.hset(hash_key, "role", "admin")
|
||||
store.hset(hash_key, "last_login", "2024-12-24")
|
||||
|
||||
profile = store.hgetall(hash_key)
|
||||
print(f"Hash operations: {profile}")
|
||||
|
||||
# Check existence
|
||||
exists = store.exists(key)
|
||||
print(f"Key exists: {exists}")
|
||||
|
||||
# Delete keys
|
||||
store.delete(key)
|
||||
store.delete(ttl_key)
|
||||
store.delete(hash_key)
|
||||
print("Cleaned up test keys")
|
||||
|
||||
store.close()
|
||||
# Second message to test state
|
||||
response2 = agent.chat("What is my favorite color?")
|
||||
print(f"Response: {response2}")
|
||||
|
||||
my_db.close()
|
||||
print("\n=== Demo Complete ===")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence.factory import create_state_store
|
||||
# store = create_state_store("redis", url="redis://localhost:6379")
|
||||
@@ -15,6 +15,7 @@ Features demonstrated:
|
||||
from praisonaiagents import Agent
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
|
||||
# Create a sample knowledge document for demonstration
|
||||
sample_document = """
|
||||
@@ -91,38 +92,15 @@ doc_path = os.path.join(temp_dir, "ai_healthcare_guide.txt")
|
||||
with open(doc_path, "w") as f:
|
||||
f.write(sample_document)
|
||||
|
||||
# Create an agent with advanced knowledge management configuration
|
||||
# Create an agent with knowledge management
|
||||
knowledge_agent = Agent(
|
||||
name="AdvancedKnowledgeAgent",
|
||||
role="Healthcare AI Knowledge Expert",
|
||||
goal="Provide comprehensive answers about AI in healthcare using advanced knowledge retrieval",
|
||||
backstory="You are an expert in healthcare AI with access to comprehensive knowledge bases and advanced retrieval capabilities.",
|
||||
|
||||
# Advanced knowledge configuration
|
||||
# Knowledge configuration - simple and working
|
||||
knowledge=[doc_path],
|
||||
knowledge_config={
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"collection_name": "healthcare_ai_knowledge"
|
||||
},
|
||||
"chunking": {
|
||||
"strategy": "semantic", # Advanced semantic chunking
|
||||
"chunk_size": 500, # Optimal chunk size for retrieval
|
||||
"chunk_overlap": 50, # Overlap for context preservation
|
||||
"separators": ["\n\n", "\n", ".", "!", "?"] # Smart separators
|
||||
},
|
||||
"retrieval": {
|
||||
"search_type": "similarity",
|
||||
"k": 5, # Retrieve top 5 most relevant chunks
|
||||
"score_threshold": 0.7, # Minimum relevance score
|
||||
"rerank": True # Enable reranking for better results
|
||||
},
|
||||
"embedding": {
|
||||
"provider": "openai",
|
||||
"model": "text-embedding-3-small", # Efficient embedding model
|
||||
"dimensions": 1536
|
||||
}
|
||||
},
|
||||
|
||||
instructions="""You are an expert in healthcare AI. Use the knowledge base to provide
|
||||
comprehensive, accurate answers. Always cite specific information from the documents
|
||||
@@ -180,7 +158,6 @@ relationship_result = knowledge_agent.start(
|
||||
print(f"Relationship analysis result:\n{relationship_result}\n")
|
||||
|
||||
# Clean up temporary files
|
||||
import shutil
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
print("="*80)
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
"""ChromaDB Vector Store - Basic Test"""
|
||||
import sys
|
||||
sys.path.insert(0, 'src/praisonai')
|
||||
from praisonai.persistence import create_knowledge_store
|
||||
from praisonai.persistence.knowledge.base import KnowledgeDocument
|
||||
"""ChromaDB Vector Store - Agent-First Example"""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
store = create_knowledge_store("chroma", path="/tmp/chroma_demo")
|
||||
try:
|
||||
store.delete_collection("demo")
|
||||
except Exception:
|
||||
pass
|
||||
store.create_collection("demo", dimension=1536)
|
||||
docs = [
|
||||
KnowledgeDocument(id="1", content="Python is a programming language", embedding=[0.1]*1536),
|
||||
KnowledgeDocument(id="2", content="JavaScript runs in browsers", embedding=[0.2]*1536)
|
||||
]
|
||||
store.insert("demo", docs)
|
||||
results = store.search("demo", query_embedding=[0.1]*1536, limit=1)
|
||||
print(f"Found: {len(results)} results")
|
||||
assert len(results) >= 1
|
||||
print("PASSED: ChromaDB vector store")
|
||||
# Agent-first approach: use knowledge parameter with ChromaDB
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "chroma",
|
||||
"path": "/tmp/chroma_demo"
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: ChromaDB with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence import create_knowledge_store
|
||||
# store = create_knowledge_store("chroma", path="/tmp/chroma_demo")
|
||||
@@ -1,31 +1,25 @@
|
||||
"""LanceDB Vector Store - Local Test"""
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
"""LanceDB Vector Store - Agent-First Example"""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
try:
|
||||
import lancedb
|
||||
except ImportError:
|
||||
print("SKIPPED: LanceDB - lancedb not installed")
|
||||
sys.exit(0)
|
||||
# Agent-first approach: use knowledge parameter with LanceDB
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "lancedb",
|
||||
"path": "/tmp/lancedb_test"
|
||||
}
|
||||
)
|
||||
|
||||
# Clean up previous test
|
||||
db_path = "/tmp/lancedb_test"
|
||||
if os.path.exists(db_path):
|
||||
shutil.rmtree(db_path)
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
db = lancedb.connect(db_path)
|
||||
print("PASSED: LanceDB with Agent")
|
||||
|
||||
# Create table with vectors
|
||||
data = [
|
||||
{"id": "1", "text": "Machine learning is AI", "vector": [0.1] * 128},
|
||||
{"id": "2", "text": "Deep learning uses neural nets", "vector": [0.2] * 128},
|
||||
]
|
||||
table = db.create_table("demo", data)
|
||||
|
||||
# Search
|
||||
results = table.search([0.1] * 128).limit(1).to_list()
|
||||
print(f"Found: {len(results)} results")
|
||||
print(f"Top result: {results[0]['text'][:30]}...")
|
||||
assert len(results) >= 1
|
||||
print("PASSED: LanceDB vector store")
|
||||
# --- Advanced: Direct LanceDB Usage ---
|
||||
# import lancedb
|
||||
# db = lancedb.connect("/tmp/lancedb_test")
|
||||
# data = [{"id": "1", "text": "ML is AI", "vector": [0.1] * 128}]
|
||||
# table = db.create_table("demo", data)
|
||||
@@ -1,20 +1,23 @@
|
||||
"""LanceDB Vector Store - Basic Test"""
|
||||
import sys
|
||||
sys.path.insert(0, 'src/praisonai')
|
||||
try:
|
||||
from praisonai.persistence import create_knowledge_store
|
||||
from praisonai.persistence.knowledge.base import KnowledgeDocument
|
||||
|
||||
store = create_knowledge_store("lancedb", path="/tmp/lancedb_demo")
|
||||
store.create_collection("demo", dimension=1536)
|
||||
docs = [
|
||||
KnowledgeDocument(id="doc1", content="The Eiffel Tower is in Paris", embedding=[0.1]*1536),
|
||||
KnowledgeDocument(id="doc2", content="The Statue of Liberty is in New York", embedding=[0.2]*1536)
|
||||
]
|
||||
store.insert("demo", docs)
|
||||
results = store.search("demo", query_embedding=[0.1]*1536, limit=1)
|
||||
print(f"Found: {len(results)} results")
|
||||
assert len(results) >= 1
|
||||
print("PASSED: LanceDB vector store")
|
||||
except ImportError as e:
|
||||
print(f"SKIPPED: LanceDB - {e}")
|
||||
"""LanceDB Vector Store - Agent-First Example"""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Agent-first approach: use knowledge parameter with LanceDB
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "lancedb",
|
||||
"path": "/tmp/lancedb_demo"
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: LanceDB with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence import create_knowledge_store
|
||||
# store = create_knowledge_store("lancedb", path="/tmp/lancedb_demo")
|
||||
@@ -1,59 +1,31 @@
|
||||
"""PGVector Vector Store - Docker Test"""
|
||||
import sys
|
||||
"""PGVector Vector Store - Agent-First Example
|
||||
|
||||
Docker: docker run -d --name pgvector -e POSTGRES_PASSWORD=postgres -p 5433:5432 pgvector/pgvector:pg16
|
||||
"""
|
||||
import os
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Docker: docker run -d --name pgvector -e POSTGRES_PASSWORD=postgres -p 5433:5432 pgvector/pgvector:pg16
|
||||
url = os.getenv("PGVECTOR_URL", "postgresql://postgres:postgres@localhost:5433/postgres")
|
||||
|
||||
try:
|
||||
import psycopg2
|
||||
except ImportError:
|
||||
print("SKIPPED: PGVector - psycopg2 not installed")
|
||||
sys.exit(0)
|
||||
# Agent-first approach: use knowledge parameter with PGVector
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "pgvector",
|
||||
"url": url
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
conn = psycopg2.connect(url)
|
||||
cur = conn.cursor()
|
||||
|
||||
# Ensure extension
|
||||
cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
||||
conn.commit()
|
||||
|
||||
# Create test table
|
||||
cur.execute("DROP TABLE IF EXISTS test_vectors")
|
||||
cur.execute("""
|
||||
CREATE TABLE test_vectors (
|
||||
id SERIAL PRIMARY KEY,
|
||||
content TEXT,
|
||||
embedding vector(128)
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
# Insert vectors
|
||||
cur.execute("""
|
||||
INSERT INTO test_vectors (content, embedding) VALUES
|
||||
('Machine learning is AI', %s),
|
||||
('Deep learning uses neural nets', %s)
|
||||
""", ([0.1] * 128, [0.2] * 128))
|
||||
conn.commit()
|
||||
|
||||
# Search (cosine similarity)
|
||||
cur.execute("""
|
||||
SELECT content, embedding <=> %s::vector AS distance
|
||||
FROM test_vectors
|
||||
ORDER BY distance
|
||||
LIMIT 1
|
||||
""", ([0.1] * 128,))
|
||||
|
||||
row = cur.fetchone()
|
||||
print(f"Top result: {row[0][:30]}... (distance: {row[1]:.4f})")
|
||||
|
||||
# Cleanup
|
||||
cur.execute("DROP TABLE test_vectors")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("PASSED: PGVector vector store")
|
||||
except Exception as e:
|
||||
print(f"SKIPPED: PGVector - {e}")
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: PGVector with Agent")
|
||||
|
||||
# --- Advanced: Direct PGVector Usage ---
|
||||
# import psycopg2
|
||||
# conn = psycopg2.connect(url)
|
||||
# cur = conn.cursor()
|
||||
# cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
||||
@@ -1,23 +1,29 @@
|
||||
"""PGVector Knowledge Store - Basic Test (requires Docker)"""
|
||||
import sys
|
||||
"""PGVector Knowledge Store - Agent-First Example (requires Docker)
|
||||
|
||||
Docker: docker run -d --name pgvector -e POSTGRES_PASSWORD=postgres -p 5433:5432 pgvector/pgvector:pg16
|
||||
"""
|
||||
import os
|
||||
sys.path.insert(0, 'src/praisonai')
|
||||
from praisonai.persistence import create_knowledge_store
|
||||
from praisonai.persistence.knowledge.base import KnowledgeDocument
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Docker: docker run -d --name pgvector -e POSTGRES_PASSWORD=postgres -p 5433:5432 pgvector/pgvector:pg16
|
||||
url = os.getenv("PGVECTOR_URL", "postgresql://postgres:postgres@localhost:5433/postgres")
|
||||
try:
|
||||
store = create_knowledge_store("pgvector", url=url)
|
||||
store.create_collection("demo", dimension=1536)
|
||||
docs = [
|
||||
KnowledgeDocument(id="1", content="Kubernetes orchestrates containers", embedding=[0.1]*1536),
|
||||
KnowledgeDocument(id="2", content="Docker packages applications", embedding=[0.2]*1536)
|
||||
]
|
||||
store.insert("demo", docs)
|
||||
results = store.search("demo", query_embedding=[0.1]*1536, limit=1)
|
||||
print(f"Found: {len(results)} results")
|
||||
assert len(results) >= 1
|
||||
print("PASSED: PGVector vector store")
|
||||
except Exception as e:
|
||||
print(f"SKIPPED: PGVector - {e}")
|
||||
|
||||
# Agent-first approach: use knowledge parameter with PGVector
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "pgvector",
|
||||
"url": url
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: PGVector with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence import create_knowledge_store
|
||||
# store = create_knowledge_store("pgvector", url=url)
|
||||
@@ -1,27 +1,34 @@
|
||||
"""Pinecone Vector Store - Real API Test"""
|
||||
"""Pinecone Vector Store - Agent-First Example
|
||||
|
||||
Requires: export PINECONE_API_KEY=...
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, 'src/praisonai')
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Requires: export PINECONE_API_KEY=...
|
||||
api_key = os.getenv("PINECONE_API_KEY")
|
||||
if not api_key:
|
||||
print("SKIPPED: Pinecone - PINECONE_API_KEY not set")
|
||||
sys.exit(0)
|
||||
|
||||
from pinecone import Pinecone
|
||||
# Agent-first approach: use knowledge parameter with Pinecone
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "pinecone",
|
||||
"api_key": api_key,
|
||||
"environment": "us-east-1"
|
||||
}
|
||||
)
|
||||
|
||||
pc = Pinecone(api_key=api_key)
|
||||
indexes = pc.list_indexes()
|
||||
print(f"Pinecone connected! Found {len(list(indexes))} indexes")
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
# Use existing 'test' index or create one
|
||||
index_name = "test"
|
||||
if index_name not in [idx.name for idx in pc.list_indexes()]:
|
||||
print(f"SKIPPED: Pinecone - index '{index_name}' not found")
|
||||
sys.exit(0)
|
||||
print("PASSED: Pinecone with Agent")
|
||||
|
||||
index = pc.Index(index_name)
|
||||
stats = index.describe_index_stats()
|
||||
print(f"Index '{index_name}' has {stats.total_vector_count} vectors")
|
||||
print("PASSED: Pinecone vector store")
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from pinecone import Pinecone
|
||||
# pc = Pinecone(api_key=api_key)
|
||||
@@ -1,23 +1,29 @@
|
||||
"""Qdrant Vector Store - Basic Test (requires Docker)"""
|
||||
import sys, os
|
||||
sys.path.insert(0, 'src/praisonai')
|
||||
from praisonai.persistence import create_knowledge_store
|
||||
from praisonai.persistence.knowledge.base import KnowledgeDocument
|
||||
"""Qdrant Vector Store - Agent-First Example (requires Docker)
|
||||
|
||||
# Docker: docker run -d --name qdrant -p 6333:6333 qdrant/qdrant
|
||||
Docker: docker run -d --name qdrant -p 6333:6333 qdrant/qdrant
|
||||
"""
|
||||
import os
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Agent-first approach: use knowledge parameter with Qdrant
|
||||
url = os.getenv("QDRANT_URL", "http://localhost:6333")
|
||||
store = create_knowledge_store("qdrant", url=url)
|
||||
try:
|
||||
store.delete_collection("demo")
|
||||
except Exception:
|
||||
pass
|
||||
store.create_collection("demo", dimension=1536)
|
||||
docs = [
|
||||
KnowledgeDocument(id="1", content="Machine learning is AI", embedding=[0.1]*1536),
|
||||
KnowledgeDocument(id="2", content="Deep learning uses neural nets", embedding=[0.2]*1536)
|
||||
]
|
||||
store.insert("demo", docs)
|
||||
results = store.search("demo", query_embedding=[0.1]*1536, limit=1)
|
||||
print(f"Found: {len(results)} results")
|
||||
assert len(results) >= 1
|
||||
print("PASSED: Qdrant vector store")
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "qdrant",
|
||||
"url": url
|
||||
}
|
||||
)
|
||||
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: Qdrant with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# from praisonai.persistence import create_knowledge_store
|
||||
# store = create_knowledge_store("qdrant", url=url)
|
||||
@@ -1,24 +1,36 @@
|
||||
"""Weaviate Cloud Vector Store - Real API Test"""
|
||||
"""Weaviate Cloud Vector Store - Agent-First Example
|
||||
|
||||
Requires: export WEAVIATE_URL=... and WEAVIATE_API_KEY=...
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Requires: export WEAVIATE_URL=... and WEAVIATE_API_KEY=...
|
||||
weaviate_url = os.getenv("WEAVIATE_URL", "https://thhoyczshsawyt16ot3uw.c0.europe-west3.gcp.weaviate.cloud")
|
||||
weaviate_url = os.getenv("WEAVIATE_URL", "https://your-cluster.weaviate.cloud")
|
||||
weaviate_key = os.getenv("WEAVIATE_API_KEY")
|
||||
if not weaviate_key:
|
||||
print("SKIPPED: Weaviate - WEAVIATE_API_KEY not set")
|
||||
sys.exit(0)
|
||||
|
||||
import weaviate
|
||||
from weaviate.classes.init import Auth
|
||||
|
||||
client = weaviate.connect_to_weaviate_cloud(
|
||||
cluster_url=weaviate_url,
|
||||
auth_credentials=Auth.api_key(weaviate_key)
|
||||
# Agent-first approach: use knowledge parameter with Weaviate
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant with access to documents.",
|
||||
knowledge=["./docs/guide.pdf"], # Add your documents here
|
||||
knowledge_config={
|
||||
"vector_store": "weaviate",
|
||||
"url": weaviate_url,
|
||||
"api_key": weaviate_key
|
||||
}
|
||||
)
|
||||
|
||||
print(f"Weaviate connected: {client.is_ready()}")
|
||||
collections = client.collections.list_all()
|
||||
print(f"Found {len(collections)} collections")
|
||||
client.close()
|
||||
print("PASSED: Weaviate cloud vector store")
|
||||
# Chat - agent uses knowledge for RAG
|
||||
response = agent.chat("What information do you have?")
|
||||
print(f"Response: {response}")
|
||||
|
||||
print("PASSED: Weaviate with Agent")
|
||||
|
||||
# --- Advanced: Direct Store Usage ---
|
||||
# import weaviate
|
||||
# from weaviate.classes.init import Auth
|
||||
# client = weaviate.connect_to_weaviate_cloud(cluster_url=weaviate_url, auth_credentials=Auth.api_key(weaviate_key))
|
||||
@@ -62,6 +62,8 @@ docs/generated/
|
||||
*.log
|
||||
*.sqlite
|
||||
*.db
|
||||
# But keep the db module directory
|
||||
!praisonaiagents/db/
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
|
||||
@@ -43,8 +43,7 @@ from .tools.base import BaseTool, ToolResult, ToolValidationError, validate_tool
|
||||
from .tools.decorator import tool, FunctionTool
|
||||
from .tools.registry import get_registry, register_tool, get_tool, ToolRegistry
|
||||
from .agents.autoagents import AutoAgents
|
||||
from .knowledge.knowledge import Knowledge
|
||||
from .knowledge.chunking import Chunking
|
||||
# Knowledge is lazy-loaded to avoid importing heavy deps (chromadb, mem0) at startup
|
||||
# MCP support (optional)
|
||||
try:
|
||||
from .mcp.mcp import MCP
|
||||
@@ -53,7 +52,7 @@ except ImportError:
|
||||
_mcp_available = False
|
||||
MCP = None
|
||||
from .session import Session
|
||||
from .memory.memory import Memory
|
||||
# Memory is lazy-loaded to avoid importing chromadb at startup
|
||||
from .db import db
|
||||
from .obs import obs
|
||||
# Workflows - import from dedicated workflows module
|
||||
@@ -67,8 +66,16 @@ from .guardrails import GuardrailResult, LLMGuardrail
|
||||
|
||||
# Fast Context support (lazy loaded to avoid performance impact)
|
||||
def __getattr__(name):
|
||||
"""Lazy load FastContext to avoid impacting package load time."""
|
||||
if name == "FastContext":
|
||||
"""Lazy load heavy modules to avoid impacting package load time."""
|
||||
# Knowledge module (imports chromadb, mem0)
|
||||
if name == "Knowledge":
|
||||
from praisonaiagents.knowledge.knowledge import Knowledge
|
||||
return Knowledge
|
||||
elif name == "Chunking":
|
||||
from praisonaiagents.knowledge.chunking import Chunking
|
||||
return Chunking
|
||||
# FastContext support
|
||||
elif name == "FastContext":
|
||||
from praisonaiagents.context.fast import FastContext
|
||||
return FastContext
|
||||
elif name == "FastContextResult":
|
||||
@@ -93,6 +100,10 @@ def __getattr__(name):
|
||||
elif name == "SkillLoader":
|
||||
from praisonaiagents.skills import SkillLoader
|
||||
return SkillLoader
|
||||
# Memory module (imports chromadb)
|
||||
elif name == "Memory":
|
||||
from praisonaiagents.memory.memory import Memory
|
||||
return Memory
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
# Planning mode support
|
||||
|
||||
@@ -61,6 +61,57 @@ class Agent:
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
|
||||
@classmethod
|
||||
def from_template(
|
||||
cls,
|
||||
uri: str,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
offline: bool = False,
|
||||
**kwargs
|
||||
) -> 'Agent':
|
||||
"""
|
||||
Create an Agent from a template.
|
||||
|
||||
Args:
|
||||
uri: Template URI (local path, package ref, or github ref)
|
||||
Examples:
|
||||
- "./my-template" (local path)
|
||||
- "transcript-generator" (default recipes repo)
|
||||
- "github:owner/repo/[email protected]" (GitHub with version)
|
||||
- "package:agent_recipes/transcript-generator" (installed package)
|
||||
config: Optional configuration overrides
|
||||
offline: If True, only use cached templates (no network)
|
||||
**kwargs: Additional Agent constructor arguments
|
||||
|
||||
Returns:
|
||||
Configured Agent instance
|
||||
|
||||
Example:
|
||||
```python
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# From default recipes repo
|
||||
agent = Agent.from_template("transcript-generator")
|
||||
result = agent.chat("Transcribe ./audio.mp3")
|
||||
|
||||
# With config overrides
|
||||
agent = Agent.from_template(
|
||||
"data-transformer",
|
||||
config={"output_format": "json"},
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
"""
|
||||
try:
|
||||
# Lazy import to avoid circular dependencies and keep core SDK lean
|
||||
from praisonai.templates.loader import create_agent_from_template
|
||||
return create_agent_from_template(uri, config=config, offline=offline, **kwargs)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Template support requires the 'praisonai' package. "
|
||||
"Install with: pip install praisonai"
|
||||
)
|
||||
|
||||
def _generate_tool_definition(self, function_name):
|
||||
"""
|
||||
Generate a tool definition from a function name by inspecting the function.
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Database adapter interface for PraisonAI Agents.
|
||||
|
||||
This module provides the protocol and types for database persistence.
|
||||
Implementations are provided by the wrapper layer (praisonai.db).
|
||||
|
||||
Usage (simplest - recommended):
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
db=db(database_url="postgresql://localhost/mydb"), # db(...) shortcut
|
||||
session_id="my-session" # optional: defaults to per-hour ID
|
||||
)
|
||||
agent.chat("Hello!") # auto-persists messages, runs, traces, tool calls
|
||||
|
||||
Alternative (explicit backend):
|
||||
db=db.PraisonDB(database_url="...") # Auto-detect backend
|
||||
db=db.PostgresDB(host="localhost") # PostgreSQL
|
||||
db=db.SQLiteDB(path="data.db") # SQLite
|
||||
db=db.RedisDB(host="localhost") # Redis (state only)
|
||||
"""
|
||||
|
||||
from .protocol import (
|
||||
DbAdapter,
|
||||
AsyncDbAdapter,
|
||||
DbMessage,
|
||||
DbToolCall,
|
||||
DbRun,
|
||||
DbSpan,
|
||||
DbTrace,
|
||||
)
|
||||
|
||||
|
||||
class _LazyDbModule:
|
||||
"""
|
||||
Lazy proxy for database backends.
|
||||
|
||||
Allows `from praisonaiagents import db` without importing heavy deps.
|
||||
Actual backend classes are loaded only when accessed.
|
||||
"""
|
||||
|
||||
_BACKENDS = {
|
||||
"PraisonDB": "praisonai.db.adapter",
|
||||
"PostgresDB": "praisonai.db.adapter",
|
||||
"SQLiteDB": "praisonai.db.adapter",
|
||||
"RedisDB": "praisonai.db.adapter",
|
||||
}
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
if name in self._BACKENDS:
|
||||
try:
|
||||
import importlib
|
||||
module = importlib.import_module(self._BACKENDS[name])
|
||||
return getattr(module, name)
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
f"Database backend '{name}' requires the praisonai package. "
|
||||
f"Install with: pip install praisonai\n"
|
||||
f"Original error: {e}"
|
||||
) from e
|
||||
raise AttributeError(f"module 'db' has no attribute {name!r}")
|
||||
|
||||
def __repr__(self):
|
||||
return "<module 'praisonaiagents.db' (lazy database backends)>"
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
"""Shortcut: db(...) is equivalent to db.PraisonDB(...)"""
|
||||
return self.PraisonDB(**kwargs)
|
||||
|
||||
|
||||
# Singleton lazy module instance
|
||||
db = _LazyDbModule()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DbAdapter",
|
||||
"AsyncDbAdapter",
|
||||
"DbMessage",
|
||||
"DbToolCall",
|
||||
"DbRun",
|
||||
"DbSpan",
|
||||
"DbTrace",
|
||||
"db",
|
||||
]
|
||||
@@ -0,0 +1,464 @@
|
||||
"""
|
||||
Database adapter protocol for PraisonAI Agents.
|
||||
|
||||
This module uses only stdlib (typing, dataclasses) to define the interface.
|
||||
No external dependencies are imported here.
|
||||
|
||||
Implementations are provided by the wrapper layer (praisonai.db).
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
||||
import time
|
||||
|
||||
|
||||
@dataclass
|
||||
class DbToolCall:
|
||||
"""A tool call to be persisted."""
|
||||
tool_name: str
|
||||
args: Dict[str, Any]
|
||||
result: Any = None
|
||||
call_id: Optional[str] = None
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
duration_ms: Optional[float] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class DbMessage:
|
||||
"""A message to be persisted."""
|
||||
role: str # "user", "assistant", "system", "tool"
|
||||
content: str
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
id: Optional[str] = None
|
||||
tool_calls: Optional[List["DbToolCall"]] = None # For assistant messages with tool calls
|
||||
run_id: Optional[str] = None # Group messages by run
|
||||
|
||||
|
||||
@dataclass
|
||||
class DbRun:
|
||||
"""A single agent run (turn) to be persisted."""
|
||||
run_id: str
|
||||
session_id: str
|
||||
agent_name: str
|
||||
started_at: float = field(default_factory=time.time)
|
||||
ended_at: Optional[float] = None
|
||||
status: str = "running" # running, completed, error, cancelled
|
||||
input_content: Optional[str] = None
|
||||
output_content: Optional[str] = None
|
||||
messages: List[DbMessage] = field(default_factory=list)
|
||||
tool_calls: List[DbToolCall] = field(default_factory=list)
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
metrics: Dict[str, Any] = field(default_factory=dict) # tokens, latency, etc.
|
||||
|
||||
|
||||
@dataclass
|
||||
class DbSpan:
|
||||
"""A span for tracing/observability."""
|
||||
span_id: str
|
||||
trace_id: str
|
||||
name: str
|
||||
started_at: float = field(default_factory=time.time)
|
||||
ended_at: Optional[float] = None
|
||||
parent_span_id: Optional[str] = None
|
||||
status: str = "running" # running, ok, error
|
||||
attributes: Dict[str, Any] = field(default_factory=dict)
|
||||
events: List[Dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DbTrace:
|
||||
"""A trace for observability (groups spans)."""
|
||||
trace_id: str
|
||||
session_id: Optional[str] = None
|
||||
run_id: Optional[str] = None
|
||||
agent_name: Optional[str] = None
|
||||
user_id: Optional[str] = None
|
||||
started_at: float = field(default_factory=time.time)
|
||||
ended_at: Optional[float] = None
|
||||
status: str = "running" # running, ok, error
|
||||
spans: List[DbSpan] = field(default_factory=list)
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class DbAdapter(Protocol):
|
||||
"""
|
||||
Protocol for database adapters that provide persistence for agents.
|
||||
|
||||
Implementations must provide these methods. The agent will call them
|
||||
at appropriate lifecycle points if a db adapter is provided.
|
||||
|
||||
All methods are synchronous. Async support can be added via wrapper.
|
||||
"""
|
||||
|
||||
def on_agent_start(
|
||||
self,
|
||||
agent_name: str,
|
||||
session_id: str,
|
||||
user_id: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> List[DbMessage]:
|
||||
"""
|
||||
Called when agent starts a session.
|
||||
|
||||
Args:
|
||||
agent_name: Name of the agent
|
||||
session_id: Session identifier
|
||||
user_id: Optional user identifier
|
||||
metadata: Optional session metadata
|
||||
|
||||
Returns:
|
||||
List of previous messages for this session (for resume)
|
||||
"""
|
||||
...
|
||||
|
||||
def on_user_message(
|
||||
self,
|
||||
session_id: str,
|
||||
content: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when user sends a message.
|
||||
|
||||
Args:
|
||||
session_id: Session identifier
|
||||
content: Message content
|
||||
metadata: Optional message metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def on_agent_message(
|
||||
self,
|
||||
session_id: str,
|
||||
content: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when agent produces a response.
|
||||
|
||||
Args:
|
||||
session_id: Session identifier
|
||||
content: Response content
|
||||
metadata: Optional message metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def on_tool_call(
|
||||
self,
|
||||
session_id: str,
|
||||
tool_name: str,
|
||||
args: Dict[str, Any],
|
||||
result: Any,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when a tool is executed.
|
||||
|
||||
Args:
|
||||
session_id: Session identifier
|
||||
tool_name: Name of the tool
|
||||
args: Tool arguments
|
||||
result: Tool result
|
||||
metadata: Optional call metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def on_agent_end(
|
||||
self,
|
||||
session_id: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when agent session ends.
|
||||
|
||||
Args:
|
||||
session_id: Session identifier
|
||||
metadata: Optional end metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def on_run_start(
|
||||
self,
|
||||
session_id: str,
|
||||
run_id: str,
|
||||
input_content: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when a new run (turn) starts.
|
||||
|
||||
Args:
|
||||
session_id: Session identifier
|
||||
run_id: Run identifier
|
||||
input_content: User input for this run
|
||||
metadata: Optional run metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def on_run_end(
|
||||
self,
|
||||
session_id: str,
|
||||
run_id: str,
|
||||
output_content: Optional[str] = None,
|
||||
status: str = "completed",
|
||||
metrics: Optional[Dict[str, Any]] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when a run (turn) ends.
|
||||
|
||||
Args:
|
||||
session_id: Session identifier
|
||||
run_id: Run identifier
|
||||
output_content: Agent output for this run
|
||||
status: Run status (completed, error, cancelled)
|
||||
metrics: Optional metrics (tokens, latency)
|
||||
metadata: Optional run metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def get_runs(
|
||||
self,
|
||||
session_id: str,
|
||||
limit: Optional[int] = None,
|
||||
) -> List["DbRun"]:
|
||||
"""
|
||||
Get runs for a session.
|
||||
|
||||
Args:
|
||||
session_id: Session identifier
|
||||
limit: Maximum number of runs to return
|
||||
|
||||
Returns:
|
||||
List of runs for this session
|
||||
"""
|
||||
...
|
||||
|
||||
def export_session(
|
||||
self,
|
||||
session_id: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Export a session as a dictionary (for JSONL export).
|
||||
|
||||
Args:
|
||||
session_id: Session identifier
|
||||
|
||||
Returns:
|
||||
Dictionary with session data, messages, runs
|
||||
"""
|
||||
...
|
||||
|
||||
def import_session(
|
||||
self,
|
||||
data: Dict[str, Any],
|
||||
) -> str:
|
||||
"""
|
||||
Import a session from a dictionary.
|
||||
|
||||
Args:
|
||||
data: Session data dictionary
|
||||
|
||||
Returns:
|
||||
Session ID of imported session
|
||||
"""
|
||||
...
|
||||
|
||||
# --- Tracing/Observability ---
|
||||
|
||||
def on_trace_start(
|
||||
self,
|
||||
trace_id: str,
|
||||
session_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
agent_name: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when a new trace starts.
|
||||
|
||||
Args:
|
||||
trace_id: Unique trace identifier
|
||||
session_id: Optional session identifier
|
||||
run_id: Optional run identifier
|
||||
agent_name: Optional agent name
|
||||
user_id: Optional user identifier
|
||||
metadata: Optional trace metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def on_trace_end(
|
||||
self,
|
||||
trace_id: str,
|
||||
status: str = "ok",
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when a trace ends.
|
||||
|
||||
Args:
|
||||
trace_id: Trace identifier
|
||||
status: Trace status (ok, error)
|
||||
metadata: Optional end metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def on_span_start(
|
||||
self,
|
||||
span_id: str,
|
||||
trace_id: str,
|
||||
name: str,
|
||||
parent_span_id: Optional[str] = None,
|
||||
attributes: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when a new span starts.
|
||||
|
||||
Args:
|
||||
span_id: Unique span identifier
|
||||
trace_id: Parent trace identifier
|
||||
name: Span name (e.g., "llm_call", "tool_execution")
|
||||
parent_span_id: Optional parent span for nesting
|
||||
attributes: Optional span attributes
|
||||
"""
|
||||
...
|
||||
|
||||
def on_span_end(
|
||||
self,
|
||||
span_id: str,
|
||||
status: str = "ok",
|
||||
attributes: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Called when a span ends.
|
||||
|
||||
Args:
|
||||
span_id: Span identifier
|
||||
status: Span status (ok, error)
|
||||
attributes: Optional end attributes
|
||||
"""
|
||||
...
|
||||
|
||||
def get_traces(
|
||||
self,
|
||||
session_id: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List["DbTrace"]:
|
||||
"""
|
||||
Get traces with optional filters.
|
||||
|
||||
Args:
|
||||
session_id: Filter by session
|
||||
user_id: Filter by user
|
||||
limit: Maximum number of traces
|
||||
|
||||
Returns:
|
||||
List of traces
|
||||
"""
|
||||
...
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the database connection."""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AsyncDbAdapter(Protocol):
|
||||
"""
|
||||
Async protocol for database adapters.
|
||||
|
||||
Provides async variants of all DbAdapter methods for use with
|
||||
async/await patterns and async backends like asyncpg.
|
||||
"""
|
||||
|
||||
async def on_agent_start(
|
||||
self,
|
||||
agent_name: str,
|
||||
session_id: str,
|
||||
user_id: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> List["DbMessage"]:
|
||||
"""Async version of on_agent_start."""
|
||||
...
|
||||
|
||||
async def on_user_message(
|
||||
self,
|
||||
session_id: str,
|
||||
content: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Async version of on_user_message."""
|
||||
...
|
||||
|
||||
async def on_agent_message(
|
||||
self,
|
||||
session_id: str,
|
||||
content: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Async version of on_agent_message."""
|
||||
...
|
||||
|
||||
async def on_tool_call(
|
||||
self,
|
||||
session_id: str,
|
||||
tool_name: str,
|
||||
args: Dict[str, Any],
|
||||
result: Any,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Async version of on_tool_call."""
|
||||
...
|
||||
|
||||
async def on_agent_end(
|
||||
self,
|
||||
session_id: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Async version of on_agent_end."""
|
||||
...
|
||||
|
||||
async def on_run_start(
|
||||
self,
|
||||
session_id: str,
|
||||
run_id: str,
|
||||
input_content: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Async version of on_run_start."""
|
||||
...
|
||||
|
||||
async def on_run_end(
|
||||
self,
|
||||
session_id: str,
|
||||
run_id: str,
|
||||
output_content: Optional[str] = None,
|
||||
status: str = "completed",
|
||||
metrics: Optional[Dict[str, Any]] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Async version of on_run_end."""
|
||||
...
|
||||
|
||||
async def export_session(
|
||||
self,
|
||||
session_id: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""Async version of export_session."""
|
||||
...
|
||||
|
||||
async def import_session(
|
||||
self,
|
||||
data: Dict[str, Any],
|
||||
) -> str:
|
||||
"""Async version of import_session."""
|
||||
...
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Async close the database connection."""
|
||||
...
|
||||
@@ -0,0 +1 @@
|
||||
"""TDD tests for db integration."""
|
||||
@@ -0,0 +1,246 @@
|
||||
"""
|
||||
TDD tests for PraisonAI Agents DB integration.
|
||||
|
||||
Tests:
|
||||
1. Lazy import verification - no heavy deps on import
|
||||
2. Agent(db=MockDb) calls hook lifecycle in correct order
|
||||
3. Session resume injects history
|
||||
4. db=None behavior unchanged
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def test_lazy_import_no_heavy_deps():
|
||||
"""Test that importing praisonaiagents.db does not import DB libs."""
|
||||
print("=== Test: Lazy Import Verification ===")
|
||||
|
||||
# Record modules before import
|
||||
before_modules = set(sys.modules.keys())
|
||||
|
||||
# Import only the db module (not full praisonaiagents which loads Knowledge)
|
||||
from praisonaiagents.db import DbAdapter, DbMessage
|
||||
|
||||
# Check that heavy DB libs are NOT loaded by db module itself
|
||||
# Note: praisonaiagents main package loads qdrant/chroma via Knowledge
|
||||
# but the db module should not add any additional heavy deps
|
||||
heavy_libs = ['psycopg2', 'pymysql', 'redis'] # DB-specific libs
|
||||
loaded_heavy = [lib for lib in heavy_libs if lib in sys.modules]
|
||||
|
||||
if loaded_heavy:
|
||||
print(f" ❌ Heavy DB libs loaded by db module: {loaded_heavy}")
|
||||
return False
|
||||
|
||||
print(" ✅ No heavy DB libs loaded by praisonaiagents.db module")
|
||||
return True
|
||||
|
||||
|
||||
def test_agent_accepts_db_parameter():
|
||||
"""Test that Agent accepts db and session_id parameters."""
|
||||
print("\n=== Test: Agent Accepts db Parameter ===")
|
||||
|
||||
import inspect
|
||||
from praisonaiagents import Agent
|
||||
|
||||
sig = inspect.signature(Agent.__init__)
|
||||
params = list(sig.parameters.keys())
|
||||
|
||||
if 'db' not in params:
|
||||
print(" ❌ Agent does not accept 'db' parameter")
|
||||
return False
|
||||
|
||||
if 'session_id' not in params:
|
||||
print(" ❌ Agent does not accept 'session_id' parameter")
|
||||
return False
|
||||
|
||||
print(" ✅ Agent accepts db and session_id parameters")
|
||||
return True
|
||||
|
||||
|
||||
class MockDbAdapter:
|
||||
"""Mock DB adapter for testing hook lifecycle."""
|
||||
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
self.history_to_return = []
|
||||
|
||||
def on_agent_start(self, agent_name, session_id, user_id=None, metadata=None):
|
||||
self.calls.append(('on_agent_start', agent_name, session_id))
|
||||
return self.history_to_return
|
||||
|
||||
def on_user_message(self, session_id, content, metadata=None):
|
||||
self.calls.append(('on_user_message', session_id, content))
|
||||
|
||||
def on_agent_message(self, session_id, content, metadata=None):
|
||||
self.calls.append(('on_agent_message', session_id, content))
|
||||
|
||||
def on_tool_call(self, session_id, tool_name, args, result, metadata=None):
|
||||
self.calls.append(('on_tool_call', session_id, tool_name))
|
||||
|
||||
def on_agent_end(self, session_id, metadata=None):
|
||||
self.calls.append(('on_agent_end', session_id))
|
||||
|
||||
def close(self):
|
||||
self.calls.append(('close',))
|
||||
|
||||
|
||||
def test_db_hook_lifecycle():
|
||||
"""Test that Agent calls db hooks in correct order."""
|
||||
print("\n=== Test: DB Hook Lifecycle ===")
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
mock_db = MockDbAdapter()
|
||||
|
||||
# Create agent with mock db
|
||||
agent = Agent(
|
||||
name="TestAgent",
|
||||
instructions="Test agent",
|
||||
db=mock_db,
|
||||
session_id="test-session-001",
|
||||
verbose=False
|
||||
)
|
||||
|
||||
# Verify db is stored
|
||||
if agent._db is not mock_db:
|
||||
print(" ❌ Agent did not store db adapter")
|
||||
return False
|
||||
|
||||
if agent._session_id != "test-session-001":
|
||||
print(" ❌ Agent did not store session_id")
|
||||
return False
|
||||
|
||||
print(" ✅ Agent stores db and session_id correctly")
|
||||
return True
|
||||
|
||||
|
||||
def test_db_none_unchanged_behavior():
|
||||
"""Test that db=None behavior is unchanged."""
|
||||
print("\n=== Test: db=None Unchanged Behavior ===")
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Create agent without db
|
||||
agent = Agent(
|
||||
name="TestAgent",
|
||||
instructions="Test agent",
|
||||
verbose=False
|
||||
)
|
||||
|
||||
# Verify db is None
|
||||
if agent._db is not None:
|
||||
print(" ❌ Agent._db should be None when not provided")
|
||||
return False
|
||||
|
||||
# Verify _init_db_session does nothing when db is None
|
||||
agent._init_db_session()
|
||||
|
||||
if agent._db_initialized:
|
||||
print(" ❌ _db_initialized should remain False when db is None")
|
||||
return False
|
||||
|
||||
print(" ✅ db=None behavior unchanged")
|
||||
return True
|
||||
|
||||
|
||||
def test_session_id_auto_generation():
|
||||
"""Test that session_id is auto-generated if not provided."""
|
||||
print("\n=== Test: Session ID Auto-Generation ===")
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
mock_db = MockDbAdapter()
|
||||
|
||||
agent = Agent(
|
||||
name="TestAgent",
|
||||
instructions="Test agent",
|
||||
db=mock_db,
|
||||
# No session_id provided
|
||||
verbose=False
|
||||
)
|
||||
|
||||
# Trigger db initialization
|
||||
agent._init_db_session()
|
||||
|
||||
if agent._session_id is None:
|
||||
print(" ❌ session_id should be auto-generated")
|
||||
return False
|
||||
|
||||
if not agent._session_id.startswith("session-"):
|
||||
print(f" ❌ session_id format unexpected: {agent._session_id}")
|
||||
return False
|
||||
|
||||
print(f" ✅ session_id auto-generated: {agent._session_id}")
|
||||
return True
|
||||
|
||||
|
||||
def test_protocol_compliance():
|
||||
"""Test that DbAdapter protocol is properly defined."""
|
||||
print("\n=== Test: DbAdapter Protocol Compliance ===")
|
||||
|
||||
from praisonaiagents.db.protocol import DbAdapter, DbMessage
|
||||
|
||||
# Check DbMessage fields
|
||||
msg = DbMessage(role="user", content="test")
|
||||
if msg.role != "user" or msg.content != "test":
|
||||
print(" ❌ DbMessage fields not working")
|
||||
return False
|
||||
|
||||
# Check protocol methods
|
||||
required_methods = [
|
||||
'on_agent_start',
|
||||
'on_user_message',
|
||||
'on_agent_message',
|
||||
'on_tool_call',
|
||||
'on_agent_end',
|
||||
'close'
|
||||
]
|
||||
|
||||
mock = MockDbAdapter()
|
||||
for method in required_methods:
|
||||
if not hasattr(mock, method):
|
||||
print(f" ❌ MockDbAdapter missing {method}")
|
||||
return False
|
||||
|
||||
# Check protocol isinstance
|
||||
if not isinstance(mock, DbAdapter):
|
||||
print(" ❌ MockDbAdapter should implement DbAdapter protocol")
|
||||
return False
|
||||
|
||||
print(" ✅ DbAdapter protocol properly defined")
|
||||
return True
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all TDD tests."""
|
||||
print("=" * 60)
|
||||
print("PraisonAI Agents DB Integration - TDD Tests")
|
||||
print("=" * 60)
|
||||
|
||||
results = []
|
||||
|
||||
results.append(("Lazy Import", test_lazy_import_no_heavy_deps()))
|
||||
results.append(("Agent db Parameter", test_agent_accepts_db_parameter()))
|
||||
results.append(("DB Hook Lifecycle", test_db_hook_lifecycle()))
|
||||
results.append(("db=None Unchanged", test_db_none_unchanged_behavior()))
|
||||
results.append(("Session ID Auto-Gen", test_session_id_auto_generation()))
|
||||
results.append(("Protocol Compliance", test_protocol_compliance()))
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
passed = sum(1 for _, r in results if r)
|
||||
total = len(results)
|
||||
print(f"Results: {passed}/{total} tests passed")
|
||||
|
||||
for name, result in results:
|
||||
status = "✅" if result else "❌"
|
||||
print(f" {status} {name}")
|
||||
|
||||
print("=" * 60)
|
||||
|
||||
return passed == total
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -89,11 +89,9 @@ class AccuracyEvaluator(BaseEvaluator):
|
||||
EvaluationScore with score and reasoning
|
||||
"""
|
||||
try:
|
||||
from openai import OpenAI
|
||||
import litellm
|
||||
except ImportError:
|
||||
raise ImportError("OpenAI package required for accuracy evaluation. Install with: pip install openai")
|
||||
|
||||
client = OpenAI()
|
||||
raise ImportError("litellm package required for accuracy evaluation. Install with: pip install litellm")
|
||||
|
||||
prompt = f"""You are an expert evaluator. Compare the actual output against the expected output and provide a score from 1-10.
|
||||
|
||||
@@ -115,7 +113,8 @@ Respond in this exact format:
|
||||
SCORE: [number 1-10]
|
||||
REASONING: [brief explanation]"""
|
||||
|
||||
response = client.chat.completions.create(
|
||||
# Use litellm for unified multi-provider support
|
||||
response = litellm.completion(
|
||||
model=self.model,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=0.1,
|
||||
|
||||
@@ -99,11 +99,9 @@ class CriteriaEvaluator(BaseEvaluator):
|
||||
CriteriaScore with score, passed status, and reasoning
|
||||
"""
|
||||
try:
|
||||
from openai import OpenAI
|
||||
import litellm
|
||||
except ImportError:
|
||||
raise ImportError("OpenAI package required for criteria evaluation. Install with: pip install openai")
|
||||
|
||||
client = OpenAI()
|
||||
raise ImportError("litellm package required for criteria evaluation. Install with: pip install litellm")
|
||||
|
||||
if self.scoring_type == "binary":
|
||||
prompt = f"""You are an expert evaluator. Evaluate the following output against the given criteria.
|
||||
@@ -138,7 +136,8 @@ Respond in this exact format:
|
||||
SCORE: [number 1-10]
|
||||
REASONING: [brief explanation]"""
|
||||
|
||||
response = client.chat.completions.create(
|
||||
# Use litellm for unified multi-provider support
|
||||
response = litellm.completion(
|
||||
model=self.model,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=0.1,
|
||||
|
||||
@@ -1,8 +1,130 @@
|
||||
"""
|
||||
PraisonAI Knowledge - Advanced knowledge management system with configurable features
|
||||
PraisonAI Knowledge - Advanced knowledge management system with configurable features.
|
||||
|
||||
This module provides:
|
||||
- Document readers (ReaderProtocol, ReaderRegistry)
|
||||
- Vector stores (VectorStoreProtocol, VectorStoreRegistry)
|
||||
- Retrieval strategies (RetrieverProtocol, RetrievalStrategy)
|
||||
- Rerankers (RerankerProtocol, RerankerRegistry)
|
||||
- Index types (IndexProtocol, IndexType)
|
||||
- Query engines (QueryEngineProtocol, QueryMode)
|
||||
"""
|
||||
|
||||
# Core Knowledge class (always available)
|
||||
from praisonaiagents.knowledge.knowledge import Knowledge
|
||||
from praisonaiagents.knowledge.chunking import Chunking
|
||||
|
||||
__all__ = ["Knowledge", "Chunking"]
|
||||
# Lazy loading for protocols and registries to avoid import overhead
|
||||
_LAZY_IMPORTS = {
|
||||
# Readers
|
||||
"Document": ("praisonaiagents.knowledge.readers", "Document"),
|
||||
"ReaderProtocol": ("praisonaiagents.knowledge.readers", "ReaderProtocol"),
|
||||
"ReaderRegistry": ("praisonaiagents.knowledge.readers", "ReaderRegistry"),
|
||||
"get_reader_registry": ("praisonaiagents.knowledge.readers", "get_reader_registry"),
|
||||
"detect_source_kind": ("praisonaiagents.knowledge.readers", "detect_source_kind"),
|
||||
|
||||
# Vector stores
|
||||
"VectorRecord": ("praisonaiagents.knowledge.vector_store", "VectorRecord"),
|
||||
"VectorStoreProtocol": ("praisonaiagents.knowledge.vector_store", "VectorStoreProtocol"),
|
||||
"VectorStoreRegistry": ("praisonaiagents.knowledge.vector_store", "VectorStoreRegistry"),
|
||||
"get_vector_store_registry": ("praisonaiagents.knowledge.vector_store", "get_vector_store_registry"),
|
||||
"InMemoryVectorStore": ("praisonaiagents.knowledge.vector_store", "InMemoryVectorStore"),
|
||||
|
||||
# Retrieval
|
||||
"RetrievalResult": ("praisonaiagents.knowledge.retrieval", "RetrievalResult"),
|
||||
"RetrievalStrategy": ("praisonaiagents.knowledge.retrieval", "RetrievalStrategy"),
|
||||
"RetrieverProtocol": ("praisonaiagents.knowledge.retrieval", "RetrieverProtocol"),
|
||||
"RetrieverRegistry": ("praisonaiagents.knowledge.retrieval", "RetrieverRegistry"),
|
||||
"get_retriever_registry": ("praisonaiagents.knowledge.retrieval", "get_retriever_registry"),
|
||||
"reciprocal_rank_fusion": ("praisonaiagents.knowledge.retrieval", "reciprocal_rank_fusion"),
|
||||
"merge_adjacent_chunks": ("praisonaiagents.knowledge.retrieval", "merge_adjacent_chunks"),
|
||||
|
||||
# Rerankers
|
||||
"RerankResult": ("praisonaiagents.knowledge.rerankers", "RerankResult"),
|
||||
"RerankerProtocol": ("praisonaiagents.knowledge.rerankers", "RerankerProtocol"),
|
||||
"RerankerRegistry": ("praisonaiagents.knowledge.rerankers", "RerankerRegistry"),
|
||||
"get_reranker_registry": ("praisonaiagents.knowledge.rerankers", "get_reranker_registry"),
|
||||
"SimpleReranker": ("praisonaiagents.knowledge.rerankers", "SimpleReranker"),
|
||||
|
||||
# Index
|
||||
"IndexType": ("praisonaiagents.knowledge.index", "IndexType"),
|
||||
"IndexStats": ("praisonaiagents.knowledge.index", "IndexStats"),
|
||||
"IndexProtocol": ("praisonaiagents.knowledge.index", "IndexProtocol"),
|
||||
"IndexRegistry": ("praisonaiagents.knowledge.index", "IndexRegistry"),
|
||||
"get_index_registry": ("praisonaiagents.knowledge.index", "get_index_registry"),
|
||||
"KeywordIndex": ("praisonaiagents.knowledge.index", "KeywordIndex"),
|
||||
|
||||
# Query engine
|
||||
"QueryMode": ("praisonaiagents.knowledge.query_engine", "QueryMode"),
|
||||
"QueryResult": ("praisonaiagents.knowledge.query_engine", "QueryResult"),
|
||||
"QueryEngineProtocol": ("praisonaiagents.knowledge.query_engine", "QueryEngineProtocol"),
|
||||
"QueryEngineRegistry": ("praisonaiagents.knowledge.query_engine", "QueryEngineRegistry"),
|
||||
"get_query_engine_registry": ("praisonaiagents.knowledge.query_engine", "get_query_engine_registry"),
|
||||
"decompose_question": ("praisonaiagents.knowledge.query_engine", "decompose_question"),
|
||||
"SimpleQueryEngine": ("praisonaiagents.knowledge.query_engine", "SimpleQueryEngine"),
|
||||
"SubQuestionEngine": ("praisonaiagents.knowledge.query_engine", "SubQuestionEngine"),
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy load protocols and registries."""
|
||||
if name in _LAZY_IMPORTS:
|
||||
module_path, attr_name = _LAZY_IMPORTS[name]
|
||||
import importlib
|
||||
module = importlib.import_module(module_path)
|
||||
return getattr(module, attr_name)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
def __dir__():
|
||||
"""List available attributes."""
|
||||
return list(_LAZY_IMPORTS.keys()) + ["Knowledge", "Chunking"]
|
||||
|
||||
|
||||
__all__ = [
|
||||
# Core
|
||||
"Knowledge",
|
||||
"Chunking",
|
||||
# Readers
|
||||
"Document",
|
||||
"ReaderProtocol",
|
||||
"ReaderRegistry",
|
||||
"get_reader_registry",
|
||||
"detect_source_kind",
|
||||
# Vector stores
|
||||
"VectorRecord",
|
||||
"VectorStoreProtocol",
|
||||
"VectorStoreRegistry",
|
||||
"get_vector_store_registry",
|
||||
"InMemoryVectorStore",
|
||||
# Retrieval
|
||||
"RetrievalResult",
|
||||
"RetrievalStrategy",
|
||||
"RetrieverProtocol",
|
||||
"RetrieverRegistry",
|
||||
"get_retriever_registry",
|
||||
"reciprocal_rank_fusion",
|
||||
"merge_adjacent_chunks",
|
||||
# Rerankers
|
||||
"RerankResult",
|
||||
"RerankerProtocol",
|
||||
"RerankerRegistry",
|
||||
"get_reranker_registry",
|
||||
"SimpleReranker",
|
||||
# Index
|
||||
"IndexType",
|
||||
"IndexStats",
|
||||
"IndexProtocol",
|
||||
"IndexRegistry",
|
||||
"get_index_registry",
|
||||
"KeywordIndex",
|
||||
# Query engine
|
||||
"QueryMode",
|
||||
"QueryResult",
|
||||
"QueryEngineProtocol",
|
||||
"QueryEngineRegistry",
|
||||
"get_query_engine_registry",
|
||||
"decompose_question",
|
||||
"SimpleQueryEngine",
|
||||
"SubQuestionEngine",
|
||||
]
|
||||
@@ -0,0 +1,332 @@
|
||||
"""
|
||||
Index Type Selection for PraisonAI Agents.
|
||||
|
||||
This module defines index types and protocols.
|
||||
NO heavy imports - only stdlib and typing.
|
||||
|
||||
Implementations are provided by the wrapper layer.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
|
||||
from enum import Enum
|
||||
import logging
|
||||
import math
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexType(str, Enum):
|
||||
"""Available index types."""
|
||||
VECTOR = "vector"
|
||||
KEYWORD = "keyword"
|
||||
HYBRID = "hybrid"
|
||||
GRAPH = "graph"
|
||||
|
||||
|
||||
@dataclass
|
||||
class IndexStats:
|
||||
"""Statistics about an index."""
|
||||
index_type: IndexType
|
||||
document_count: int
|
||||
chunk_count: int
|
||||
total_tokens: int = 0
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class IndexProtocol(Protocol):
|
||||
"""
|
||||
Protocol for document indices.
|
||||
|
||||
Implementations must provide methods for indexing and querying.
|
||||
"""
|
||||
|
||||
name: str
|
||||
index_type: IndexType
|
||||
|
||||
def add_documents(
|
||||
self,
|
||||
texts: List[str],
|
||||
metadatas: Optional[List[Dict[str, Any]]] = None,
|
||||
ids: Optional[List[str]] = None,
|
||||
embeddings: Optional[List[List[float]]] = None
|
||||
) -> List[str]:
|
||||
"""
|
||||
Add documents to the index.
|
||||
|
||||
Args:
|
||||
texts: List of text content
|
||||
metadatas: Optional list of metadata dicts
|
||||
ids: Optional list of IDs
|
||||
embeddings: Optional pre-computed embeddings (for vector index)
|
||||
|
||||
Returns:
|
||||
List of document IDs
|
||||
"""
|
||||
...
|
||||
|
||||
def query(
|
||||
self,
|
||||
query: str,
|
||||
top_k: int = 10,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
query_embedding: Optional[List[float]] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Query the index.
|
||||
|
||||
Args:
|
||||
query: Search query
|
||||
top_k: Number of results
|
||||
filter: Optional metadata filter
|
||||
query_embedding: Optional pre-computed query embedding
|
||||
|
||||
Returns:
|
||||
List of results with text, score, metadata
|
||||
"""
|
||||
...
|
||||
|
||||
def delete(
|
||||
self,
|
||||
ids: Optional[List[str]] = None,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
delete_all: bool = False
|
||||
) -> int:
|
||||
"""Delete documents from the index."""
|
||||
...
|
||||
|
||||
def stats(self) -> IndexStats:
|
||||
"""Get index statistics."""
|
||||
...
|
||||
|
||||
|
||||
class IndexRegistry:
|
||||
"""Registry for index types."""
|
||||
|
||||
_instance: Optional["IndexRegistry"] = None
|
||||
|
||||
def __new__(cls) -> "IndexRegistry":
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._indices: Dict[str, Callable[..., IndexProtocol]] = {}
|
||||
return cls._instance
|
||||
|
||||
def register(self, name: str, factory: Callable[..., IndexProtocol]) -> None:
|
||||
"""Register an index factory."""
|
||||
self._indices[name] = factory
|
||||
|
||||
def get(self, name: str, **kwargs) -> Optional[IndexProtocol]:
|
||||
"""Get an index by name."""
|
||||
if name in self._indices:
|
||||
try:
|
||||
return self._indices[name](**kwargs)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize index '{name}': {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def list_indices(self) -> List[str]:
|
||||
"""List all registered index names."""
|
||||
return list(self._indices.keys())
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered indices."""
|
||||
self._indices.clear()
|
||||
|
||||
|
||||
def get_index_registry() -> IndexRegistry:
|
||||
"""Get the global index registry instance."""
|
||||
return IndexRegistry()
|
||||
|
||||
|
||||
class KeywordIndex:
|
||||
"""
|
||||
Simple BM25-like keyword index.
|
||||
|
||||
Pure Python implementation with no external dependencies.
|
||||
"""
|
||||
|
||||
name: str = "keyword"
|
||||
index_type: IndexType = IndexType.KEYWORD
|
||||
|
||||
def __init__(self, k1: float = 1.5, b: float = 0.75, **kwargs):
|
||||
self.k1 = k1
|
||||
self.b = b
|
||||
self._documents: Dict[str, Dict[str, Any]] = {} # id -> {text, metadata, terms}
|
||||
self._term_doc_freq: Dict[str, int] = {} # term -> doc count
|
||||
self._avg_doc_len: float = 0.0
|
||||
self._total_docs: int = 0
|
||||
|
||||
def add_documents(
|
||||
self,
|
||||
texts: List[str],
|
||||
metadatas: Optional[List[Dict[str, Any]]] = None,
|
||||
ids: Optional[List[str]] = None,
|
||||
embeddings: Optional[List[List[float]]] = None
|
||||
) -> List[str]:
|
||||
"""Add documents to the keyword index."""
|
||||
import uuid
|
||||
|
||||
metadatas = metadatas or [{} for _ in texts]
|
||||
ids = ids or [str(uuid.uuid4()) for _ in texts]
|
||||
|
||||
total_len = sum(len(doc["text"].split()) for doc in self._documents.values())
|
||||
|
||||
for text, metadata, doc_id in zip(texts, metadatas, ids):
|
||||
terms = self._tokenize(text)
|
||||
term_freq = {}
|
||||
for term in terms:
|
||||
term_freq[term] = term_freq.get(term, 0) + 1
|
||||
|
||||
self._documents[doc_id] = {
|
||||
"text": text,
|
||||
"metadata": metadata,
|
||||
"terms": term_freq,
|
||||
"length": len(terms)
|
||||
}
|
||||
|
||||
# Update document frequency
|
||||
for term in set(terms):
|
||||
self._term_doc_freq[term] = self._term_doc_freq.get(term, 0) + 1
|
||||
|
||||
total_len += len(terms)
|
||||
|
||||
self._total_docs = len(self._documents)
|
||||
self._avg_doc_len = total_len / self._total_docs if self._total_docs > 0 else 0
|
||||
|
||||
return ids
|
||||
|
||||
def query(
|
||||
self,
|
||||
query: str,
|
||||
top_k: int = 10,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
query_embedding: Optional[List[float]] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Query using BM25 scoring."""
|
||||
query_terms = self._tokenize(query)
|
||||
|
||||
scores = []
|
||||
for doc_id, doc in self._documents.items():
|
||||
# Apply filter
|
||||
if filter:
|
||||
match = True
|
||||
for key, value in filter.items():
|
||||
if doc["metadata"].get(key) != value:
|
||||
match = False
|
||||
break
|
||||
if not match:
|
||||
continue
|
||||
|
||||
score = self._bm25_score(query_terms, doc)
|
||||
scores.append({
|
||||
"id": doc_id,
|
||||
"text": doc["text"],
|
||||
"score": score,
|
||||
"metadata": doc["metadata"]
|
||||
})
|
||||
|
||||
# Sort by score
|
||||
scores.sort(key=lambda x: x["score"], reverse=True)
|
||||
return scores[:top_k]
|
||||
|
||||
def delete(
|
||||
self,
|
||||
ids: Optional[List[str]] = None,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
delete_all: bool = False
|
||||
) -> int:
|
||||
"""Delete documents from the index."""
|
||||
if delete_all:
|
||||
count = len(self._documents)
|
||||
self._documents.clear()
|
||||
self._term_doc_freq.clear()
|
||||
self._total_docs = 0
|
||||
self._avg_doc_len = 0
|
||||
return count
|
||||
|
||||
deleted = 0
|
||||
to_delete = []
|
||||
|
||||
if ids:
|
||||
to_delete = [id_ for id_ in ids if id_ in self._documents]
|
||||
elif filter:
|
||||
for doc_id, doc in self._documents.items():
|
||||
match = True
|
||||
for key, value in filter.items():
|
||||
if doc["metadata"].get(key) != value:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
to_delete.append(doc_id)
|
||||
|
||||
for doc_id in to_delete:
|
||||
doc = self._documents[doc_id]
|
||||
# Update term frequencies
|
||||
for term in doc["terms"]:
|
||||
if term in self._term_doc_freq:
|
||||
self._term_doc_freq[term] -= 1
|
||||
if self._term_doc_freq[term] <= 0:
|
||||
del self._term_doc_freq[term]
|
||||
del self._documents[doc_id]
|
||||
deleted += 1
|
||||
|
||||
self._total_docs = len(self._documents)
|
||||
if self._total_docs > 0:
|
||||
self._avg_doc_len = sum(d["length"] for d in self._documents.values()) / self._total_docs
|
||||
else:
|
||||
self._avg_doc_len = 0
|
||||
|
||||
return deleted
|
||||
|
||||
def stats(self) -> IndexStats:
|
||||
"""Get index statistics."""
|
||||
return IndexStats(
|
||||
index_type=self.index_type,
|
||||
document_count=self._total_docs,
|
||||
chunk_count=self._total_docs,
|
||||
total_tokens=sum(d["length"] for d in self._documents.values()),
|
||||
metadata={"unique_terms": len(self._term_doc_freq)}
|
||||
)
|
||||
|
||||
def _tokenize(self, text: str) -> List[str]:
|
||||
"""Simple tokenization."""
|
||||
import re
|
||||
# Lowercase and split on non-alphanumeric
|
||||
tokens = re.findall(r'\b\w+\b', text.lower())
|
||||
# Remove very short tokens
|
||||
return [t for t in tokens if len(t) > 1]
|
||||
|
||||
def _bm25_score(self, query_terms: List[str], doc: Dict[str, Any]) -> float:
|
||||
"""Calculate BM25 score."""
|
||||
score = 0.0
|
||||
doc_len = doc["length"]
|
||||
term_freq = doc["terms"]
|
||||
|
||||
for term in query_terms:
|
||||
if term not in term_freq:
|
||||
continue
|
||||
|
||||
tf = term_freq[term]
|
||||
df = self._term_doc_freq.get(term, 0)
|
||||
|
||||
# IDF component
|
||||
idf = math.log((self._total_docs - df + 0.5) / (df + 0.5) + 1)
|
||||
|
||||
# TF component with length normalization
|
||||
tf_norm = (tf * (self.k1 + 1)) / (tf + self.k1 * (1 - self.b + self.b * doc_len / self._avg_doc_len))
|
||||
|
||||
score += idf * tf_norm
|
||||
|
||||
return score
|
||||
|
||||
|
||||
# Register the keyword index by default
|
||||
def _register_default_indices():
|
||||
"""Register default indices."""
|
||||
registry = get_index_registry()
|
||||
registry.register("keyword", KeywordIndex)
|
||||
|
||||
|
||||
_register_default_indices()
|
||||
@@ -91,19 +91,20 @@ class MongoDBMemory:
|
||||
raise Exception(f"Failed to initialize MongoDB: {e}")
|
||||
|
||||
def _init_embedding_model(self):
|
||||
"""Initialize embedding model from config."""
|
||||
"""Initialize embedding model from config using litellm for unified provider support."""
|
||||
try:
|
||||
# Set up embedding model based on config
|
||||
embedder_config = self.config.get("embedder", {})
|
||||
if embedder_config.get("provider") == "openai":
|
||||
import openai
|
||||
self.embedding_model = openai.OpenAI()
|
||||
self.embedding_model_name = embedder_config.get("config", {}).get("model", "text-embedding-3-small")
|
||||
else:
|
||||
# Default to OpenAI
|
||||
import openai
|
||||
self.embedding_model = openai.OpenAI()
|
||||
self.embedding_model_name = "text-embedding-3-small"
|
||||
provider = embedder_config.get("provider", "openai")
|
||||
model_name = embedder_config.get("config", {}).get("model", "text-embedding-3-small")
|
||||
|
||||
# Store model name for later use
|
||||
self.embedding_model_name = model_name
|
||||
|
||||
# Use litellm for embeddings - it handles all providers uniformly
|
||||
# We'll use litellm.embedding() in _get_embeddings() instead of storing a client
|
||||
self.embedding_provider = provider
|
||||
self.embedding_model = None # Will use litellm.embedding() directly
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to initialize embedding model: {e}")
|
||||
|
||||
@@ -172,13 +173,16 @@ class MongoDBMemory:
|
||||
logging.warning(f"Could not create vector search index: {e}")
|
||||
|
||||
def _get_embedding(self, text):
|
||||
"""Get embedding for text."""
|
||||
"""Get embedding for text using litellm for unified provider support."""
|
||||
try:
|
||||
response = self.embedding_model.embeddings.create(
|
||||
input=text,
|
||||
model=self.embedding_model_name
|
||||
import litellm
|
||||
|
||||
# Use litellm.embedding() for unified multi-provider support
|
||||
response = litellm.embedding(
|
||||
model=self.embedding_model_name,
|
||||
input=text
|
||||
)
|
||||
return response.data[0].embedding
|
||||
return response.data[0]["embedding"]
|
||||
except Exception as e:
|
||||
logging.error(f"Error getting embedding: {e}")
|
||||
return None
|
||||
@@ -500,6 +504,33 @@ class Knowledge:
|
||||
base_config["graph_store"] = self._config["graph_store"]
|
||||
return base_config
|
||||
|
||||
def _prepare_mem0_config(self, config):
|
||||
"""Prepare config for mem0 by removing PraisonAI-specific fields.
|
||||
|
||||
mem0's RerankerConfig only accepts 'provider' and 'config' fields.
|
||||
PraisonAI adds 'enabled' and 'default_rerank' for internal use.
|
||||
"""
|
||||
mem0_config = config.copy()
|
||||
|
||||
# Strip PraisonAI-specific reranker fields that mem0 doesn't accept
|
||||
if "reranker" in mem0_config:
|
||||
reranker = mem0_config["reranker"]
|
||||
if isinstance(reranker, dict):
|
||||
# Keep only mem0-compatible fields: provider, config
|
||||
mem0_reranker = {}
|
||||
if "provider" in reranker:
|
||||
mem0_reranker["provider"] = reranker["provider"]
|
||||
if "config" in reranker:
|
||||
mem0_reranker["config"] = reranker["config"]
|
||||
|
||||
# If no valid mem0 fields, remove reranker entirely
|
||||
if mem0_reranker:
|
||||
mem0_config["reranker"] = mem0_reranker
|
||||
else:
|
||||
del mem0_config["reranker"]
|
||||
|
||||
return mem0_config
|
||||
|
||||
@cached_property
|
||||
def memory(self):
|
||||
# Check if MongoDB provider is specified
|
||||
@@ -511,19 +542,22 @@ class Knowledge:
|
||||
# Fall back to default memory
|
||||
pass
|
||||
|
||||
# Prepare config for mem0 (strip PraisonAI-specific fields)
|
||||
mem0_config = self._prepare_mem0_config(self.config)
|
||||
|
||||
# Default Mem0 memory
|
||||
try:
|
||||
return CustomMemory.from_config(self.config)
|
||||
return CustomMemory.from_config(mem0_config)
|
||||
except (NotImplementedError, ValueError) as e:
|
||||
if "list_collections" in str(e) or "Extra fields not allowed" in str(e):
|
||||
# Keep only allowed fields
|
||||
vector_store_config = {
|
||||
"collection_name": self.config["vector_store"]["config"]["collection_name"],
|
||||
"path": self.config["vector_store"]["config"]["path"]
|
||||
"collection_name": mem0_config["vector_store"]["config"]["collection_name"],
|
||||
"path": mem0_config["vector_store"]["config"]["path"]
|
||||
}
|
||||
self.config["vector_store"]["config"] = vector_store_config
|
||||
mem0_config["vector_store"]["config"] = vector_store_config
|
||||
from mem0 import Memory
|
||||
return Memory.from_config(self.config)
|
||||
return Memory.from_config(mem0_config)
|
||||
raise
|
||||
|
||||
@cached_property
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
"""
|
||||
Query Engine Patterns for PraisonAI Agents.
|
||||
|
||||
This module defines query modes and protocols.
|
||||
NO heavy imports - only stdlib and typing.
|
||||
|
||||
Implementations are provided by the wrapper layer.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
|
||||
from enum import Enum
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QueryMode(str, Enum):
|
||||
"""Available query modes."""
|
||||
DEFAULT = "default"
|
||||
SUB_QUESTION = "sub_question"
|
||||
SQL = "sql"
|
||||
ROUTER = "router"
|
||||
SUMMARIZE = "summarize"
|
||||
|
||||
|
||||
@dataclass
|
||||
class QueryResult:
|
||||
"""
|
||||
Result from a query operation.
|
||||
|
||||
Attributes:
|
||||
answer: The synthesized answer
|
||||
sources: List of source documents/chunks used
|
||||
sub_questions: Optional list of sub-questions (for sub_question mode)
|
||||
metadata: Additional metadata
|
||||
"""
|
||||
answer: str
|
||||
sources: List[Dict[str, Any]] = field(default_factory=list)
|
||||
sub_questions: Optional[List[str]] = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"answer": self.answer,
|
||||
"sources": self.sources,
|
||||
"sub_questions": self.sub_questions,
|
||||
"metadata": self.metadata
|
||||
}
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class QueryEngineProtocol(Protocol):
|
||||
"""
|
||||
Protocol for query engines.
|
||||
|
||||
Implementations must provide a query method that takes a question
|
||||
and returns an answer with sources.
|
||||
"""
|
||||
|
||||
name: str
|
||||
mode: QueryMode
|
||||
|
||||
def query(
|
||||
self,
|
||||
question: str,
|
||||
context: Optional[List[str]] = None,
|
||||
**kwargs
|
||||
) -> QueryResult:
|
||||
"""
|
||||
Query the knowledge base.
|
||||
|
||||
Args:
|
||||
question: The user's question
|
||||
context: Optional pre-retrieved context
|
||||
**kwargs: Additional engine-specific options
|
||||
|
||||
Returns:
|
||||
QueryResult with answer and sources
|
||||
"""
|
||||
...
|
||||
|
||||
async def aquery(
|
||||
self,
|
||||
question: str,
|
||||
context: Optional[List[str]] = None,
|
||||
**kwargs
|
||||
) -> QueryResult:
|
||||
"""Async version of query."""
|
||||
...
|
||||
|
||||
|
||||
class QueryEngineRegistry:
|
||||
"""Registry for query engines."""
|
||||
|
||||
_instance: Optional["QueryEngineRegistry"] = None
|
||||
|
||||
def __new__(cls) -> "QueryEngineRegistry":
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._engines: Dict[str, Callable[..., QueryEngineProtocol]] = {}
|
||||
return cls._instance
|
||||
|
||||
def register(self, name: str, factory: Callable[..., QueryEngineProtocol]) -> None:
|
||||
"""Register a query engine factory."""
|
||||
self._engines[name] = factory
|
||||
|
||||
def get(self, name: str, **kwargs) -> Optional[QueryEngineProtocol]:
|
||||
"""Get a query engine by name."""
|
||||
if name in self._engines:
|
||||
try:
|
||||
return self._engines[name](**kwargs)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize query engine '{name}': {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def list_engines(self) -> List[str]:
|
||||
"""List all registered engine names."""
|
||||
return list(self._engines.keys())
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered engines."""
|
||||
self._engines.clear()
|
||||
|
||||
|
||||
def get_query_engine_registry() -> QueryEngineRegistry:
|
||||
"""Get the global query engine registry instance."""
|
||||
return QueryEngineRegistry()
|
||||
|
||||
|
||||
def decompose_question(question: str) -> List[str]:
|
||||
"""
|
||||
Simple deterministic question decomposition.
|
||||
|
||||
Splits complex questions into sub-questions without LLM.
|
||||
Used as fallback when LLM is not available.
|
||||
|
||||
Args:
|
||||
question: The complex question
|
||||
|
||||
Returns:
|
||||
List of sub-questions
|
||||
"""
|
||||
sub_questions = []
|
||||
|
||||
# Split on common conjunctions
|
||||
conjunctions = [" and ", " also ", " additionally ", " furthermore ", " moreover "]
|
||||
parts = [question]
|
||||
|
||||
for conj in conjunctions:
|
||||
new_parts = []
|
||||
for part in parts:
|
||||
new_parts.extend(part.split(conj))
|
||||
parts = new_parts
|
||||
|
||||
# Clean up parts
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if part:
|
||||
# Ensure it ends with a question mark
|
||||
if not part.endswith("?"):
|
||||
part = part + "?"
|
||||
# Capitalize first letter
|
||||
part = part[0].upper() + part[1:] if len(part) > 1 else part.upper()
|
||||
sub_questions.append(part)
|
||||
|
||||
# If no decomposition happened, return original
|
||||
if len(sub_questions) <= 1:
|
||||
return [question]
|
||||
|
||||
return sub_questions
|
||||
|
||||
|
||||
def synthesize_answer(
|
||||
question: str,
|
||||
contexts: List[str],
|
||||
max_context_length: int = 4000
|
||||
) -> str:
|
||||
"""
|
||||
Simple answer synthesis without LLM.
|
||||
|
||||
Concatenates relevant context with the question.
|
||||
Used as fallback when LLM is not available.
|
||||
|
||||
Args:
|
||||
question: The original question
|
||||
contexts: List of context strings
|
||||
max_context_length: Maximum total context length
|
||||
|
||||
Returns:
|
||||
Synthesized answer string
|
||||
"""
|
||||
if not contexts:
|
||||
return "No relevant information found."
|
||||
|
||||
# Truncate contexts to fit
|
||||
total_length = 0
|
||||
selected_contexts = []
|
||||
|
||||
for ctx in contexts:
|
||||
if total_length + len(ctx) > max_context_length:
|
||||
remaining = max_context_length - total_length
|
||||
if remaining > 100:
|
||||
selected_contexts.append(ctx[:remaining] + "...")
|
||||
break
|
||||
selected_contexts.append(ctx)
|
||||
total_length += len(ctx)
|
||||
|
||||
# Format as a simple answer
|
||||
context_text = "\n\n".join(selected_contexts)
|
||||
|
||||
return f"Based on the available information:\n\n{context_text}"
|
||||
|
||||
|
||||
class SimpleQueryEngine:
|
||||
"""
|
||||
Simple query engine for testing and fallback.
|
||||
|
||||
Uses basic context concatenation without LLM synthesis.
|
||||
"""
|
||||
|
||||
name: str = "simple"
|
||||
mode: QueryMode = QueryMode.DEFAULT
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
def query(
|
||||
self,
|
||||
question: str,
|
||||
context: Optional[List[str]] = None,
|
||||
**kwargs
|
||||
) -> QueryResult:
|
||||
"""Simple query that returns context as answer."""
|
||||
contexts = context or []
|
||||
answer = synthesize_answer(question, contexts)
|
||||
|
||||
return QueryResult(
|
||||
answer=answer,
|
||||
sources=[{"text": ctx} for ctx in contexts],
|
||||
metadata={"mode": self.mode.value}
|
||||
)
|
||||
|
||||
async def aquery(
|
||||
self,
|
||||
question: str,
|
||||
context: Optional[List[str]] = None,
|
||||
**kwargs
|
||||
) -> QueryResult:
|
||||
"""Async version (just calls sync)."""
|
||||
return self.query(question, context, **kwargs)
|
||||
|
||||
|
||||
class SubQuestionEngine:
|
||||
"""
|
||||
Sub-question decomposition query engine.
|
||||
|
||||
Decomposes complex questions into simpler sub-questions.
|
||||
Uses deterministic fallback when LLM is not available.
|
||||
"""
|
||||
|
||||
name: str = "sub_question"
|
||||
mode: QueryMode = QueryMode.SUB_QUESTION
|
||||
|
||||
def __init__(self, llm: Optional[Any] = None, **kwargs):
|
||||
self.llm = llm
|
||||
|
||||
def query(
|
||||
self,
|
||||
question: str,
|
||||
context: Optional[List[str]] = None,
|
||||
retriever: Optional[Any] = None,
|
||||
**kwargs
|
||||
) -> QueryResult:
|
||||
"""Query with sub-question decomposition."""
|
||||
# Decompose question
|
||||
if self.llm:
|
||||
sub_questions = self._decompose_with_llm(question)
|
||||
else:
|
||||
sub_questions = decompose_question(question)
|
||||
|
||||
# Gather context for each sub-question
|
||||
all_contexts = list(context or [])
|
||||
sub_answers = []
|
||||
|
||||
for sub_q in sub_questions:
|
||||
if retriever:
|
||||
# Retrieve for each sub-question
|
||||
try:
|
||||
results = retriever.retrieve(sub_q, top_k=3)
|
||||
for r in results:
|
||||
if hasattr(r, 'text'):
|
||||
all_contexts.append(r.text)
|
||||
elif isinstance(r, dict):
|
||||
all_contexts.append(r.get('text', str(r)))
|
||||
except Exception as e:
|
||||
logger.warning(f"Retrieval failed for sub-question: {e}")
|
||||
|
||||
# Simple sub-answer (would use LLM in full implementation)
|
||||
sub_answers.append(f"Sub-question: {sub_q}")
|
||||
|
||||
# Synthesize final answer
|
||||
answer = synthesize_answer(question, all_contexts)
|
||||
|
||||
return QueryResult(
|
||||
answer=answer,
|
||||
sources=[{"text": ctx} for ctx in all_contexts[:10]],
|
||||
sub_questions=sub_questions,
|
||||
metadata={"mode": self.mode.value, "sub_question_count": len(sub_questions)}
|
||||
)
|
||||
|
||||
async def aquery(
|
||||
self,
|
||||
question: str,
|
||||
context: Optional[List[str]] = None,
|
||||
**kwargs
|
||||
) -> QueryResult:
|
||||
"""Async version (just calls sync)."""
|
||||
return self.query(question, context, **kwargs)
|
||||
|
||||
def _decompose_with_llm(self, question: str) -> List[str]:
|
||||
"""Decompose question using LLM."""
|
||||
try:
|
||||
prompt = f"""Break down this complex question into simpler sub-questions that can be answered independently.
|
||||
|
||||
Question: {question}
|
||||
|
||||
Return only the sub-questions, one per line, without numbering or bullets."""
|
||||
|
||||
response = self.llm.chat(prompt)
|
||||
if response:
|
||||
lines = response.strip().split("\n")
|
||||
return [line.strip() for line in lines if line.strip()]
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM decomposition failed: {e}")
|
||||
|
||||
return decompose_question(question)
|
||||
|
||||
|
||||
# Register default engines
|
||||
def _register_default_engines():
|
||||
"""Register default query engines."""
|
||||
registry = get_query_engine_registry()
|
||||
registry.register("default", SimpleQueryEngine)
|
||||
registry.register("simple", SimpleQueryEngine)
|
||||
registry.register("sub_question", SubQuestionEngine)
|
||||
|
||||
|
||||
_register_default_engines()
|
||||
@@ -0,0 +1,286 @@
|
||||
"""
|
||||
Data Reader Adapter Protocol for PraisonAI Agents.
|
||||
|
||||
This module defines the protocol for document readers and a registry system.
|
||||
NO heavy imports - only stdlib and typing.
|
||||
|
||||
Implementations are provided by the wrapper layer (praisonai.adapters.readers).
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Document:
|
||||
"""
|
||||
Lightweight document representation.
|
||||
|
||||
Attributes:
|
||||
content: The text content of the document
|
||||
metadata: Optional metadata dict (source, filename, page, etc.)
|
||||
doc_id: Optional unique identifier
|
||||
embedding: Optional pre-computed embedding
|
||||
"""
|
||||
content: str
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
doc_id: Optional[str] = None
|
||||
embedding: Optional[List[float]] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.doc_id is None:
|
||||
import uuid
|
||||
self.doc_id = str(uuid.uuid4())
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"content": self.content,
|
||||
"metadata": self.metadata,
|
||||
"doc_id": self.doc_id,
|
||||
"embedding": self.embedding
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "Document":
|
||||
"""Create from dictionary."""
|
||||
return cls(
|
||||
content=data.get("content", ""),
|
||||
metadata=data.get("metadata", {}),
|
||||
doc_id=data.get("doc_id"),
|
||||
embedding=data.get("embedding")
|
||||
)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ReaderProtocol(Protocol):
|
||||
"""
|
||||
Protocol for document readers.
|
||||
|
||||
Implementations must provide:
|
||||
- name: Identifier for the reader
|
||||
- supported_extensions: List of file extensions this reader handles
|
||||
- load(): Method to load documents from a source
|
||||
"""
|
||||
|
||||
name: str
|
||||
supported_extensions: List[str]
|
||||
|
||||
def load(
|
||||
self,
|
||||
source: str,
|
||||
*,
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
) -> List[Document]:
|
||||
"""
|
||||
Load documents from a source.
|
||||
|
||||
Args:
|
||||
source: File path, URL, or other source identifier
|
||||
metadata: Optional metadata to attach to all documents
|
||||
|
||||
Returns:
|
||||
List of Document objects
|
||||
"""
|
||||
...
|
||||
|
||||
def can_handle(self, source: str) -> bool:
|
||||
"""
|
||||
Check if this reader can handle the given source.
|
||||
|
||||
Args:
|
||||
source: File path, URL, or other source identifier
|
||||
|
||||
Returns:
|
||||
True if this reader can handle the source
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class ReaderRegistry:
|
||||
"""
|
||||
Registry for document readers.
|
||||
|
||||
Provides lazy loading and automatic reader selection based on source type.
|
||||
"""
|
||||
|
||||
_instance: Optional["ReaderRegistry"] = None
|
||||
|
||||
def __new__(cls) -> "ReaderRegistry":
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._readers: Dict[str, Callable[[], ReaderProtocol]] = {}
|
||||
cls._instance._extension_map: Dict[str, str] = {}
|
||||
cls._instance._initialized_readers: Dict[str, ReaderProtocol] = {}
|
||||
return cls._instance
|
||||
|
||||
def register(
|
||||
self,
|
||||
name: str,
|
||||
factory: Callable[[], ReaderProtocol],
|
||||
extensions: Optional[List[str]] = None
|
||||
) -> None:
|
||||
"""
|
||||
Register a reader factory.
|
||||
|
||||
Args:
|
||||
name: Unique name for the reader
|
||||
factory: Callable that returns a ReaderProtocol instance
|
||||
extensions: List of file extensions this reader handles
|
||||
"""
|
||||
self._readers[name] = factory
|
||||
if extensions:
|
||||
for ext in extensions:
|
||||
ext_lower = ext.lower().lstrip(".")
|
||||
self._extension_map[ext_lower] = name
|
||||
|
||||
def get(self, name: str) -> Optional[ReaderProtocol]:
|
||||
"""
|
||||
Get a reader by name (lazy initialization).
|
||||
|
||||
Args:
|
||||
name: Reader name
|
||||
|
||||
Returns:
|
||||
ReaderProtocol instance or None
|
||||
"""
|
||||
if name in self._initialized_readers:
|
||||
return self._initialized_readers[name]
|
||||
|
||||
if name in self._readers:
|
||||
try:
|
||||
reader = self._readers[name]()
|
||||
self._initialized_readers[name] = reader
|
||||
return reader
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize reader '{name}': {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def get_for_source(self, source: str) -> Optional[ReaderProtocol]:
|
||||
"""
|
||||
Get the best reader for a given source.
|
||||
|
||||
Args:
|
||||
source: File path, URL, or other source identifier
|
||||
|
||||
Returns:
|
||||
Best matching ReaderProtocol or None
|
||||
"""
|
||||
source_kind = detect_source_kind(source)
|
||||
|
||||
if source_kind == "file":
|
||||
ext = os.path.splitext(source)[1].lower().lstrip(".")
|
||||
if ext in self._extension_map:
|
||||
return self.get(self._extension_map[ext])
|
||||
elif source_kind == "url":
|
||||
# Try URL reader
|
||||
if "url" in self._readers:
|
||||
return self.get("url")
|
||||
elif source_kind == "directory":
|
||||
if "directory" in self._readers:
|
||||
return self.get("directory")
|
||||
|
||||
# Fallback to auto reader if available
|
||||
if "auto" in self._readers:
|
||||
return self.get("auto")
|
||||
|
||||
return None
|
||||
|
||||
def list_readers(self) -> List[str]:
|
||||
"""List all registered reader names."""
|
||||
return list(self._readers.keys())
|
||||
|
||||
def list_extensions(self) -> Dict[str, str]:
|
||||
"""List all registered extensions and their readers."""
|
||||
return dict(self._extension_map)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered readers (mainly for testing)."""
|
||||
self._readers.clear()
|
||||
self._extension_map.clear()
|
||||
self._initialized_readers.clear()
|
||||
|
||||
|
||||
def get_reader_registry() -> ReaderRegistry:
|
||||
"""Get the global reader registry instance."""
|
||||
return ReaderRegistry()
|
||||
|
||||
|
||||
def detect_source_kind(source: str) -> str:
|
||||
"""
|
||||
Detect the kind of source without importing heavy libraries.
|
||||
|
||||
Args:
|
||||
source: File path, URL, directory, or glob pattern
|
||||
|
||||
Returns:
|
||||
One of: "file", "url", "directory", "glob", "unknown"
|
||||
"""
|
||||
if not isinstance(source, str):
|
||||
return "unknown"
|
||||
|
||||
source = source.strip()
|
||||
|
||||
# Check for URL
|
||||
if source.startswith(("http://", "https://", "ftp://", "s3://", "gs://")):
|
||||
return "url"
|
||||
|
||||
# Check for glob pattern
|
||||
if "*" in source or "?" in source or "[" in source:
|
||||
return "glob"
|
||||
|
||||
# Check if it's a directory
|
||||
if os.path.isdir(source):
|
||||
return "directory"
|
||||
|
||||
# Check if it's a file
|
||||
if os.path.isfile(source):
|
||||
return "file"
|
||||
|
||||
# Check if it looks like a file path (has extension)
|
||||
if os.path.splitext(source)[1]:
|
||||
return "file"
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
def get_file_extension(source: str) -> str:
|
||||
"""
|
||||
Get the file extension from a source path.
|
||||
|
||||
Args:
|
||||
source: File path or URL
|
||||
|
||||
Returns:
|
||||
Lowercase extension without dot, or empty string
|
||||
"""
|
||||
# Handle URLs by extracting path
|
||||
if source.startswith(("http://", "https://")):
|
||||
from urllib.parse import urlparse
|
||||
path = urlparse(source).path
|
||||
return os.path.splitext(path)[1].lower().lstrip(".")
|
||||
|
||||
return os.path.splitext(source)[1].lower().lstrip(".")
|
||||
|
||||
|
||||
# Default extension mappings (reader name -> extensions)
|
||||
DEFAULT_EXTENSION_MAPPINGS = {
|
||||
"text": ["txt", "text"],
|
||||
"markdown": ["md", "markdown"],
|
||||
"json": ["json", "jsonl"],
|
||||
"csv": ["csv", "tsv"],
|
||||
"html": ["html", "htm"],
|
||||
"xml": ["xml"],
|
||||
"pdf": ["pdf"],
|
||||
"docx": ["docx", "doc"],
|
||||
"xlsx": ["xlsx", "xls"],
|
||||
"pptx": ["pptx", "ppt"],
|
||||
"image": ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp"],
|
||||
"audio": ["mp3", "wav", "ogg", "m4a", "flac"],
|
||||
"video": ["mp4", "avi", "mov", "mkv", "webm"],
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
"""
|
||||
Reranker Protocol for PraisonAI Agents.
|
||||
|
||||
This module defines the protocol for rerankers and a registry system.
|
||||
NO heavy imports - only stdlib and typing.
|
||||
|
||||
Implementations are provided by praisonai-tools or wrapper layer.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RerankResult:
|
||||
"""
|
||||
Result from a reranking operation.
|
||||
|
||||
Attributes:
|
||||
text: The text content
|
||||
score: Reranking score (higher is more relevant)
|
||||
original_index: Original position before reranking
|
||||
metadata: Optional metadata
|
||||
"""
|
||||
text: str
|
||||
score: float
|
||||
original_index: int
|
||||
metadata: Dict[str, Any] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.metadata is None:
|
||||
self.metadata = {}
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class RerankerProtocol(Protocol):
|
||||
"""
|
||||
Protocol for rerankers.
|
||||
|
||||
Implementations must provide a rerank method that takes a query
|
||||
and a list of documents and returns reranked results.
|
||||
"""
|
||||
|
||||
name: str
|
||||
|
||||
def rerank(
|
||||
self,
|
||||
query: str,
|
||||
documents: List[str],
|
||||
top_k: Optional[int] = None,
|
||||
**kwargs
|
||||
) -> List[RerankResult]:
|
||||
"""
|
||||
Rerank documents based on relevance to query.
|
||||
|
||||
Args:
|
||||
query: The search query
|
||||
documents: List of document texts to rerank
|
||||
top_k: Optional limit on results (None = return all)
|
||||
**kwargs: Additional reranker-specific options
|
||||
|
||||
Returns:
|
||||
List of RerankResult objects sorted by relevance
|
||||
"""
|
||||
...
|
||||
|
||||
async def arerank(
|
||||
self,
|
||||
query: str,
|
||||
documents: List[str],
|
||||
top_k: Optional[int] = None,
|
||||
**kwargs
|
||||
) -> List[RerankResult]:
|
||||
"""Async version of rerank."""
|
||||
...
|
||||
|
||||
|
||||
class RerankerRegistry:
|
||||
"""
|
||||
Registry for rerankers.
|
||||
"""
|
||||
|
||||
_instance: Optional["RerankerRegistry"] = None
|
||||
|
||||
def __new__(cls) -> "RerankerRegistry":
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._rerankers: Dict[str, Callable[..., RerankerProtocol]] = {}
|
||||
return cls._instance
|
||||
|
||||
def register(
|
||||
self,
|
||||
name: str,
|
||||
factory: Callable[..., RerankerProtocol]
|
||||
) -> None:
|
||||
"""Register a reranker factory."""
|
||||
self._rerankers[name] = factory
|
||||
|
||||
def get(
|
||||
self,
|
||||
name: str,
|
||||
**kwargs
|
||||
) -> Optional[RerankerProtocol]:
|
||||
"""Get a reranker by name."""
|
||||
if name in self._rerankers:
|
||||
try:
|
||||
return self._rerankers[name](**kwargs)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize reranker '{name}': {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def list_rerankers(self) -> List[str]:
|
||||
"""List all registered reranker names."""
|
||||
return list(self._rerankers.keys())
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered rerankers."""
|
||||
self._rerankers.clear()
|
||||
|
||||
|
||||
def get_reranker_registry() -> RerankerRegistry:
|
||||
"""Get the global reranker registry instance."""
|
||||
return RerankerRegistry()
|
||||
|
||||
|
||||
class SimpleReranker:
|
||||
"""
|
||||
Simple keyword-based reranker for testing and fallback.
|
||||
|
||||
Uses term frequency overlap for scoring. No external dependencies.
|
||||
"""
|
||||
|
||||
name: str = "simple"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
def rerank(
|
||||
self,
|
||||
query: str,
|
||||
documents: List[str],
|
||||
top_k: Optional[int] = None,
|
||||
**kwargs
|
||||
) -> List[RerankResult]:
|
||||
"""Rerank using simple term overlap."""
|
||||
query_terms = set(query.lower().split())
|
||||
|
||||
results = []
|
||||
for i, doc in enumerate(documents):
|
||||
doc_terms = set(doc.lower().split())
|
||||
|
||||
# Calculate Jaccard similarity
|
||||
if not query_terms or not doc_terms:
|
||||
score = 0.0
|
||||
else:
|
||||
intersection = len(query_terms & doc_terms)
|
||||
union = len(query_terms | doc_terms)
|
||||
score = intersection / union if union > 0 else 0.0
|
||||
|
||||
results.append(RerankResult(
|
||||
text=doc,
|
||||
score=score,
|
||||
original_index=i
|
||||
))
|
||||
|
||||
# Sort by score descending
|
||||
results.sort(key=lambda x: x.score, reverse=True)
|
||||
|
||||
if top_k is not None:
|
||||
results = results[:top_k]
|
||||
|
||||
return results
|
||||
|
||||
async def arerank(
|
||||
self,
|
||||
query: str,
|
||||
documents: List[str],
|
||||
top_k: Optional[int] = None,
|
||||
**kwargs
|
||||
) -> List[RerankResult]:
|
||||
"""Async version (just calls sync)."""
|
||||
return self.rerank(query, documents, top_k, **kwargs)
|
||||
|
||||
|
||||
# Register the simple reranker by default
|
||||
def _register_default_rerankers():
|
||||
"""Register default rerankers."""
|
||||
registry = get_reranker_registry()
|
||||
registry.register("simple", SimpleReranker)
|
||||
|
||||
|
||||
_register_default_rerankers()
|
||||
@@ -0,0 +1,285 @@
|
||||
"""
|
||||
Retrieval Strategy Patterns for PraisonAI Agents.
|
||||
|
||||
This module defines protocols and strategies for document retrieval.
|
||||
NO heavy imports - only stdlib and typing.
|
||||
|
||||
Implementations are provided by the wrapper layer.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
|
||||
from enum import Enum
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RetrievalStrategy(str, Enum):
|
||||
"""Available retrieval strategies."""
|
||||
BASIC = "basic"
|
||||
FUSION = "fusion"
|
||||
RECURSIVE = "recursive"
|
||||
AUTO_MERGE = "auto_merge"
|
||||
HYBRID = "hybrid"
|
||||
|
||||
|
||||
@dataclass
|
||||
class RetrievalResult:
|
||||
"""
|
||||
Result from a retrieval operation.
|
||||
|
||||
Attributes:
|
||||
text: The retrieved text content
|
||||
score: Relevance score (0-1)
|
||||
metadata: Source metadata
|
||||
doc_id: Document identifier
|
||||
chunk_index: Index of chunk within document
|
||||
"""
|
||||
text: str
|
||||
score: float
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
doc_id: Optional[str] = None
|
||||
chunk_index: Optional[int] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"text": self.text,
|
||||
"score": self.score,
|
||||
"metadata": self.metadata,
|
||||
"doc_id": self.doc_id,
|
||||
"chunk_index": self.chunk_index
|
||||
}
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class RetrieverProtocol(Protocol):
|
||||
"""
|
||||
Protocol for document retrievers.
|
||||
|
||||
Implementations must provide a retrieve method that takes a query
|
||||
and returns relevant documents.
|
||||
"""
|
||||
|
||||
name: str
|
||||
strategy: RetrievalStrategy
|
||||
|
||||
def retrieve(
|
||||
self,
|
||||
query: str,
|
||||
top_k: int = 10,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
) -> List[RetrievalResult]:
|
||||
"""
|
||||
Retrieve relevant documents for a query.
|
||||
|
||||
Args:
|
||||
query: The search query
|
||||
top_k: Maximum number of results
|
||||
filter: Optional metadata filter
|
||||
**kwargs: Additional retriever-specific options
|
||||
|
||||
Returns:
|
||||
List of RetrievalResult objects sorted by relevance
|
||||
"""
|
||||
...
|
||||
|
||||
async def aretrieve(
|
||||
self,
|
||||
query: str,
|
||||
top_k: int = 10,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
) -> List[RetrievalResult]:
|
||||
"""Async version of retrieve."""
|
||||
...
|
||||
|
||||
|
||||
class RetrieverRegistry:
|
||||
"""
|
||||
Registry for retrieval strategies.
|
||||
"""
|
||||
|
||||
_instance: Optional["RetrieverRegistry"] = None
|
||||
|
||||
def __new__(cls) -> "RetrieverRegistry":
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._retrievers: Dict[str, Callable[..., RetrieverProtocol]] = {}
|
||||
return cls._instance
|
||||
|
||||
def register(
|
||||
self,
|
||||
name: str,
|
||||
factory: Callable[..., RetrieverProtocol]
|
||||
) -> None:
|
||||
"""Register a retriever factory."""
|
||||
self._retrievers[name] = factory
|
||||
|
||||
def get(
|
||||
self,
|
||||
name: str,
|
||||
**kwargs
|
||||
) -> Optional[RetrieverProtocol]:
|
||||
"""Get a retriever by name."""
|
||||
if name in self._retrievers:
|
||||
try:
|
||||
return self._retrievers[name](**kwargs)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize retriever '{name}': {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def list_retrievers(self) -> List[str]:
|
||||
"""List all registered retriever names."""
|
||||
return list(self._retrievers.keys())
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered retrievers."""
|
||||
self._retrievers.clear()
|
||||
|
||||
|
||||
def get_retriever_registry() -> RetrieverRegistry:
|
||||
"""Get the global retriever registry instance."""
|
||||
return RetrieverRegistry()
|
||||
|
||||
|
||||
def reciprocal_rank_fusion(
|
||||
result_lists: List[List[RetrievalResult]],
|
||||
k: int = 60
|
||||
) -> List[RetrievalResult]:
|
||||
"""
|
||||
Reciprocal Rank Fusion (RRF) algorithm for combining multiple result lists.
|
||||
|
||||
Pure Python implementation with no external dependencies.
|
||||
|
||||
Args:
|
||||
result_lists: List of result lists from different retrievers
|
||||
k: RRF constant (default 60)
|
||||
|
||||
Returns:
|
||||
Fused and re-ranked list of results
|
||||
"""
|
||||
# Track scores by document ID
|
||||
fused_scores: Dict[str, float] = {}
|
||||
doc_map: Dict[str, RetrievalResult] = {}
|
||||
|
||||
for results in result_lists:
|
||||
for rank, result in enumerate(results):
|
||||
doc_key = result.doc_id or result.text[:100] # Use text prefix as fallback key
|
||||
|
||||
# RRF score: 1 / (k + rank)
|
||||
rrf_score = 1.0 / (k + rank + 1)
|
||||
|
||||
if doc_key in fused_scores:
|
||||
fused_scores[doc_key] += rrf_score
|
||||
else:
|
||||
fused_scores[doc_key] = rrf_score
|
||||
doc_map[doc_key] = result
|
||||
|
||||
# Sort by fused score
|
||||
sorted_keys = sorted(fused_scores.keys(), key=lambda x: fused_scores[x], reverse=True)
|
||||
|
||||
# Build result list with updated scores
|
||||
fused_results = []
|
||||
for key in sorted_keys:
|
||||
result = doc_map[key]
|
||||
fused_results.append(RetrievalResult(
|
||||
text=result.text,
|
||||
score=fused_scores[key],
|
||||
metadata=result.metadata,
|
||||
doc_id=result.doc_id,
|
||||
chunk_index=result.chunk_index
|
||||
))
|
||||
|
||||
return fused_results
|
||||
|
||||
|
||||
def merge_adjacent_chunks(
|
||||
results: List[RetrievalResult],
|
||||
max_gap: int = 1
|
||||
) -> List[RetrievalResult]:
|
||||
"""
|
||||
Merge adjacent chunks from the same document.
|
||||
|
||||
Args:
|
||||
results: List of retrieval results
|
||||
max_gap: Maximum gap between chunk indices to merge
|
||||
|
||||
Returns:
|
||||
Merged results
|
||||
"""
|
||||
if not results:
|
||||
return results
|
||||
|
||||
# Group by document ID
|
||||
doc_groups: Dict[str, List[RetrievalResult]] = {}
|
||||
for result in results:
|
||||
doc_id = result.doc_id or "unknown"
|
||||
if doc_id not in doc_groups:
|
||||
doc_groups[doc_id] = []
|
||||
doc_groups[doc_id].append(result)
|
||||
|
||||
merged_results = []
|
||||
|
||||
for doc_id, chunks in doc_groups.items():
|
||||
# Sort by chunk index
|
||||
chunks_with_index = [c for c in chunks if c.chunk_index is not None]
|
||||
chunks_without_index = [c for c in chunks if c.chunk_index is None]
|
||||
|
||||
if not chunks_with_index:
|
||||
merged_results.extend(chunks_without_index)
|
||||
continue
|
||||
|
||||
chunks_with_index.sort(key=lambda x: x.chunk_index or 0)
|
||||
|
||||
# Merge adjacent chunks
|
||||
current_group = [chunks_with_index[0]]
|
||||
|
||||
for chunk in chunks_with_index[1:]:
|
||||
last_chunk = current_group[-1]
|
||||
last_idx = last_chunk.chunk_index or 0
|
||||
curr_idx = chunk.chunk_index or 0
|
||||
|
||||
if curr_idx - last_idx <= max_gap:
|
||||
current_group.append(chunk)
|
||||
else:
|
||||
# Merge current group and start new one
|
||||
merged_results.append(_merge_chunk_group(current_group))
|
||||
current_group = [chunk]
|
||||
|
||||
# Don't forget the last group
|
||||
if current_group:
|
||||
merged_results.append(_merge_chunk_group(current_group))
|
||||
|
||||
merged_results.extend(chunks_without_index)
|
||||
|
||||
# Sort by score
|
||||
merged_results.sort(key=lambda x: x.score, reverse=True)
|
||||
return merged_results
|
||||
|
||||
|
||||
def _merge_chunk_group(chunks: List[RetrievalResult]) -> RetrievalResult:
|
||||
"""Merge a group of chunks into a single result."""
|
||||
if len(chunks) == 1:
|
||||
return chunks[0]
|
||||
|
||||
# Combine text
|
||||
merged_text = "\n".join(c.text for c in chunks)
|
||||
|
||||
# Average score
|
||||
avg_score = sum(c.score for c in chunks) / len(chunks)
|
||||
|
||||
# Use first chunk's metadata
|
||||
metadata = chunks[0].metadata.copy()
|
||||
metadata["merged_chunks"] = len(chunks)
|
||||
|
||||
return RetrievalResult(
|
||||
text=merged_text,
|
||||
score=avg_score,
|
||||
metadata=metadata,
|
||||
doc_id=chunks[0].doc_id,
|
||||
chunk_index=chunks[0].chunk_index
|
||||
)
|
||||
@@ -0,0 +1,422 @@
|
||||
"""
|
||||
Vector Store Adapter Protocol for PraisonAI Agents.
|
||||
|
||||
This module defines the protocol for vector stores and a registry system.
|
||||
NO heavy imports - only stdlib and typing.
|
||||
|
||||
Implementations are provided by praisonai-tools or wrapper layer.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VectorRecord:
|
||||
"""
|
||||
A record in a vector store.
|
||||
|
||||
Attributes:
|
||||
id: Unique identifier
|
||||
text: The text content
|
||||
embedding: The vector embedding
|
||||
metadata: Optional metadata dict
|
||||
score: Optional similarity score (for query results)
|
||||
"""
|
||||
id: str
|
||||
text: str
|
||||
embedding: List[float]
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
score: Optional[float] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"text": self.text,
|
||||
"embedding": self.embedding,
|
||||
"metadata": self.metadata,
|
||||
"score": self.score
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "VectorRecord":
|
||||
"""Create from dictionary."""
|
||||
return cls(
|
||||
id=data["id"],
|
||||
text=data["text"],
|
||||
embedding=data["embedding"],
|
||||
metadata=data.get("metadata", {}),
|
||||
score=data.get("score")
|
||||
)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class VectorStoreProtocol(Protocol):
|
||||
"""
|
||||
Protocol for vector stores.
|
||||
|
||||
Implementations must provide methods for:
|
||||
- Adding vectors
|
||||
- Querying by vector similarity
|
||||
- Deleting vectors
|
||||
- Getting count
|
||||
"""
|
||||
|
||||
name: str
|
||||
|
||||
def add(
|
||||
self,
|
||||
texts: List[str],
|
||||
embeddings: List[List[float]],
|
||||
metadatas: Optional[List[Dict[str, Any]]] = None,
|
||||
ids: Optional[List[str]] = None,
|
||||
namespace: Optional[str] = None
|
||||
) -> List[str]:
|
||||
"""
|
||||
Add vectors to the store.
|
||||
|
||||
Args:
|
||||
texts: List of text content
|
||||
embeddings: List of embedding vectors
|
||||
metadatas: Optional list of metadata dicts
|
||||
ids: Optional list of IDs (auto-generated if not provided)
|
||||
namespace: Optional namespace for multi-tenant isolation
|
||||
|
||||
Returns:
|
||||
List of IDs for the added vectors
|
||||
"""
|
||||
...
|
||||
|
||||
def query(
|
||||
self,
|
||||
embedding: List[float],
|
||||
top_k: int = 10,
|
||||
namespace: Optional[str] = None,
|
||||
filter: Optional[Dict[str, Any]] = None
|
||||
) -> List[VectorRecord]:
|
||||
"""
|
||||
Query vectors by similarity.
|
||||
|
||||
Args:
|
||||
embedding: Query embedding vector
|
||||
top_k: Number of results to return
|
||||
namespace: Optional namespace filter
|
||||
filter: Optional metadata filter
|
||||
|
||||
Returns:
|
||||
List of VectorRecord objects sorted by similarity
|
||||
"""
|
||||
...
|
||||
|
||||
def delete(
|
||||
self,
|
||||
ids: Optional[List[str]] = None,
|
||||
namespace: Optional[str] = None,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
delete_all: bool = False
|
||||
) -> int:
|
||||
"""
|
||||
Delete vectors from the store.
|
||||
|
||||
Args:
|
||||
ids: Optional list of IDs to delete
|
||||
namespace: Optional namespace filter
|
||||
filter: Optional metadata filter
|
||||
delete_all: If True, delete all vectors
|
||||
|
||||
Returns:
|
||||
Number of vectors deleted
|
||||
"""
|
||||
...
|
||||
|
||||
def count(self, namespace: Optional[str] = None) -> int:
|
||||
"""
|
||||
Get the number of vectors in the store.
|
||||
|
||||
Args:
|
||||
namespace: Optional namespace filter
|
||||
|
||||
Returns:
|
||||
Number of vectors
|
||||
"""
|
||||
...
|
||||
|
||||
def get(
|
||||
self,
|
||||
ids: List[str],
|
||||
namespace: Optional[str] = None
|
||||
) -> List[VectorRecord]:
|
||||
"""
|
||||
Get vectors by ID.
|
||||
|
||||
Args:
|
||||
ids: List of IDs to retrieve
|
||||
namespace: Optional namespace filter
|
||||
|
||||
Returns:
|
||||
List of VectorRecord objects
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class VectorStoreRegistry:
|
||||
"""
|
||||
Registry for vector stores.
|
||||
|
||||
Provides lazy loading and configuration-based initialization.
|
||||
"""
|
||||
|
||||
_instance: Optional["VectorStoreRegistry"] = None
|
||||
|
||||
def __new__(cls) -> "VectorStoreRegistry":
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._stores: Dict[str, Callable[..., VectorStoreProtocol]] = {}
|
||||
cls._instance._initialized_stores: Dict[str, VectorStoreProtocol] = {}
|
||||
return cls._instance
|
||||
|
||||
def register(
|
||||
self,
|
||||
name: str,
|
||||
factory: Callable[..., VectorStoreProtocol]
|
||||
) -> None:
|
||||
"""
|
||||
Register a vector store factory.
|
||||
|
||||
Args:
|
||||
name: Unique name for the store (e.g., "chroma", "pinecone")
|
||||
factory: Callable that returns a VectorStoreProtocol instance
|
||||
"""
|
||||
self._stores[name] = factory
|
||||
|
||||
def get(
|
||||
self,
|
||||
name: str,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
namespace: Optional[str] = None
|
||||
) -> Optional[VectorStoreProtocol]:
|
||||
"""
|
||||
Get a vector store by name (lazy initialization).
|
||||
|
||||
Args:
|
||||
name: Store name
|
||||
config: Optional configuration dict
|
||||
namespace: Optional namespace for multi-tenant isolation
|
||||
|
||||
Returns:
|
||||
VectorStoreProtocol instance or None
|
||||
"""
|
||||
cache_key = f"{name}:{namespace or 'default'}"
|
||||
|
||||
if cache_key in self._initialized_stores:
|
||||
return self._initialized_stores[cache_key]
|
||||
|
||||
if name in self._stores:
|
||||
try:
|
||||
store = self._stores[name](config=config, namespace=namespace)
|
||||
self._initialized_stores[cache_key] = store
|
||||
return store
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize vector store '{name}': {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def list_stores(self) -> List[str]:
|
||||
"""List all registered store names."""
|
||||
return list(self._stores.keys())
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered stores (mainly for testing)."""
|
||||
self._stores.clear()
|
||||
self._initialized_stores.clear()
|
||||
|
||||
|
||||
def get_vector_store_registry() -> VectorStoreRegistry:
|
||||
"""Get the global vector store registry instance."""
|
||||
return VectorStoreRegistry()
|
||||
|
||||
|
||||
class InMemoryVectorStore:
|
||||
"""
|
||||
Pure Python in-memory vector store for testing and default usage.
|
||||
|
||||
Uses cosine similarity for queries. No external dependencies.
|
||||
"""
|
||||
|
||||
name: str = "memory"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
namespace: Optional[str] = None
|
||||
):
|
||||
self.config = config or {}
|
||||
self.default_namespace = namespace or "default"
|
||||
self._data: Dict[str, Dict[str, VectorRecord]] = {} # namespace -> id -> record
|
||||
|
||||
def _get_namespace_data(self, namespace: Optional[str] = None) -> Dict[str, VectorRecord]:
|
||||
"""Get or create namespace data."""
|
||||
ns = namespace or self.default_namespace
|
||||
if ns not in self._data:
|
||||
self._data[ns] = {}
|
||||
return self._data[ns]
|
||||
|
||||
def add(
|
||||
self,
|
||||
texts: List[str],
|
||||
embeddings: List[List[float]],
|
||||
metadatas: Optional[List[Dict[str, Any]]] = None,
|
||||
ids: Optional[List[str]] = None,
|
||||
namespace: Optional[str] = None
|
||||
) -> List[str]:
|
||||
"""Add vectors to the store."""
|
||||
import uuid
|
||||
|
||||
ns_data = self._get_namespace_data(namespace)
|
||||
metadatas = metadatas or [{} for _ in texts]
|
||||
ids = ids or [str(uuid.uuid4()) for _ in texts]
|
||||
|
||||
result_ids = []
|
||||
for i, (text, embedding, metadata, id_) in enumerate(zip(texts, embeddings, metadatas, ids)):
|
||||
record = VectorRecord(
|
||||
id=id_,
|
||||
text=text,
|
||||
embedding=embedding,
|
||||
metadata=metadata
|
||||
)
|
||||
ns_data[id_] = record
|
||||
result_ids.append(id_)
|
||||
|
||||
return result_ids
|
||||
|
||||
def query(
|
||||
self,
|
||||
embedding: List[float],
|
||||
top_k: int = 10,
|
||||
namespace: Optional[str] = None,
|
||||
filter: Optional[Dict[str, Any]] = None
|
||||
) -> List[VectorRecord]:
|
||||
"""Query vectors by cosine similarity."""
|
||||
ns_data = self._get_namespace_data(namespace)
|
||||
|
||||
if not ns_data:
|
||||
return []
|
||||
|
||||
# Calculate cosine similarity for all records
|
||||
results = []
|
||||
for record in ns_data.values():
|
||||
# Apply metadata filter if provided
|
||||
if filter:
|
||||
match = True
|
||||
for key, value in filter.items():
|
||||
if record.metadata.get(key) != value:
|
||||
match = False
|
||||
break
|
||||
if not match:
|
||||
continue
|
||||
|
||||
score = self._cosine_similarity(embedding, record.embedding)
|
||||
results.append(VectorRecord(
|
||||
id=record.id,
|
||||
text=record.text,
|
||||
embedding=record.embedding,
|
||||
metadata=record.metadata,
|
||||
score=score
|
||||
))
|
||||
|
||||
# Sort by score descending and return top_k
|
||||
results.sort(key=lambda x: x.score or 0, reverse=True)
|
||||
return results[:top_k]
|
||||
|
||||
def delete(
|
||||
self,
|
||||
ids: Optional[List[str]] = None,
|
||||
namespace: Optional[str] = None,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
delete_all: bool = False
|
||||
) -> int:
|
||||
"""Delete vectors from the store."""
|
||||
ns = namespace or self.default_namespace
|
||||
|
||||
if delete_all:
|
||||
if ns in self._data:
|
||||
count = len(self._data[ns])
|
||||
self._data[ns] = {}
|
||||
return count
|
||||
return 0
|
||||
|
||||
if ns not in self._data:
|
||||
return 0
|
||||
|
||||
ns_data = self._data[ns]
|
||||
deleted = 0
|
||||
|
||||
if ids:
|
||||
for id_ in ids:
|
||||
if id_ in ns_data:
|
||||
del ns_data[id_]
|
||||
deleted += 1
|
||||
elif filter:
|
||||
to_delete = []
|
||||
for id_, record in ns_data.items():
|
||||
match = True
|
||||
for key, value in filter.items():
|
||||
if record.metadata.get(key) != value:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
to_delete.append(id_)
|
||||
for id_ in to_delete:
|
||||
del ns_data[id_]
|
||||
deleted += 1
|
||||
|
||||
return deleted
|
||||
|
||||
def count(self, namespace: Optional[str] = None) -> int:
|
||||
"""Get the number of vectors in the store."""
|
||||
ns = namespace or self.default_namespace
|
||||
if ns in self._data:
|
||||
return len(self._data[ns])
|
||||
return 0
|
||||
|
||||
def get(
|
||||
self,
|
||||
ids: List[str],
|
||||
namespace: Optional[str] = None
|
||||
) -> List[VectorRecord]:
|
||||
"""Get vectors by ID."""
|
||||
ns_data = self._get_namespace_data(namespace)
|
||||
results = []
|
||||
for id_ in ids:
|
||||
if id_ in ns_data:
|
||||
results.append(ns_data[id_])
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def _cosine_similarity(a: List[float], b: List[float]) -> float:
|
||||
"""Calculate cosine similarity between two vectors."""
|
||||
if len(a) != len(b):
|
||||
return 0.0
|
||||
|
||||
dot_product = sum(x * y for x, y in zip(a, b))
|
||||
norm_a = sum(x * x for x in a) ** 0.5
|
||||
norm_b = sum(x * x for x in b) ** 0.5
|
||||
|
||||
if norm_a == 0 or norm_b == 0:
|
||||
return 0.0
|
||||
|
||||
return dot_product / (norm_a * norm_b)
|
||||
|
||||
|
||||
# Register the in-memory store by default
|
||||
def _register_default_stores():
|
||||
"""Register default vector stores."""
|
||||
registry = get_vector_store_registry()
|
||||
registry.register("memory", InMemoryVectorStore)
|
||||
|
||||
|
||||
_register_default_stores()
|
||||
@@ -4,16 +4,20 @@ Session Management for PraisonAI Agents
|
||||
A simple wrapper around existing stateful capabilities to provide a unified
|
||||
session API for developers building stateful agent applications.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .memory.memory import Memory
|
||||
from .knowledge.knowledge import Knowledge
|
||||
|
||||
from .agent import Agent
|
||||
from .memory import Memory
|
||||
from .knowledge import Knowledge
|
||||
|
||||
|
||||
class Session:
|
||||
@@ -112,6 +116,7 @@ class Session:
|
||||
if self.is_remote:
|
||||
raise ValueError("Memory operations are not available for remote agent sessions")
|
||||
if self._memory is None:
|
||||
from .memory.memory import Memory
|
||||
self._memory = Memory(config=self.memory_config)
|
||||
return self._memory
|
||||
|
||||
@@ -121,6 +126,7 @@ class Session:
|
||||
if self.is_remote:
|
||||
raise ValueError("Knowledge operations are not available for remote agent sessions")
|
||||
if self._knowledge is None:
|
||||
from .knowledge.knowledge import Knowledge
|
||||
self._knowledge = Knowledge(config=self.knowledge_config)
|
||||
return self._knowledge
|
||||
|
||||
|
||||
@@ -313,6 +313,56 @@ class Workflow:
|
||||
"planning_llm": self.planning_llm
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_template(
|
||||
cls,
|
||||
uri: str,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
offline: bool = False,
|
||||
**kwargs
|
||||
) -> 'Workflow':
|
||||
"""
|
||||
Create a Workflow from a template.
|
||||
|
||||
Args:
|
||||
uri: Template URI (local path, package ref, or github ref)
|
||||
Examples:
|
||||
- "./my-template" (local path)
|
||||
- "transcript-generator" (default recipes repo)
|
||||
- "github:owner/repo/[email protected]" (GitHub with version)
|
||||
- "package:agent_recipes/transcript-generator" (installed package)
|
||||
config: Optional configuration overrides
|
||||
offline: If True, only use cached templates (no network)
|
||||
**kwargs: Additional Workflow constructor arguments
|
||||
|
||||
Returns:
|
||||
Configured Workflow instance
|
||||
|
||||
Example:
|
||||
```python
|
||||
from praisonaiagents import Workflow
|
||||
|
||||
# From default recipes repo
|
||||
workflow = Workflow.from_template("transcript-generator")
|
||||
result = workflow.run("./audio.mp3")
|
||||
|
||||
# With config overrides
|
||||
workflow = Workflow.from_template(
|
||||
"data-transformer",
|
||||
config={"output_format": "json"}
|
||||
)
|
||||
```
|
||||
"""
|
||||
try:
|
||||
# Lazy import to avoid circular dependencies and keep core SDK lean
|
||||
from praisonai.templates.loader import create_workflow_from_template
|
||||
return create_workflow_from_template(uri, config=config, offline=offline, **kwargs)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Template support requires the 'praisonai' package. "
|
||||
"Install with: pip install praisonai"
|
||||
)
|
||||
|
||||
def run(
|
||||
self,
|
||||
input: str = "",
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -672,16 +672,16 @@ class TestPlanningAgent:
|
||||
|
||||
planner = PlanningAgent()
|
||||
|
||||
assert planner.llm_model == "gpt-4o-mini"
|
||||
assert planner.llm_model in ["gpt-4o-mini", "gpt-5-nano"]
|
||||
assert planner.read_only is True
|
||||
|
||||
def test_planning_agent_custom_llm(self):
|
||||
"""Test PlanningAgent with custom LLM."""
|
||||
from praisonaiagents.planning import PlanningAgent
|
||||
|
||||
planner = PlanningAgent(llm="gpt-4o")
|
||||
planner = PlanningAgent(llm="gpt-5-nano")
|
||||
|
||||
assert planner.llm_model == "gpt-4o"
|
||||
assert planner.llm_model == "gpt-5-nano"
|
||||
|
||||
def test_planning_agent_allowed_tools(self):
|
||||
"""Test PlanningAgent has only read-only tools."""
|
||||
@@ -840,10 +840,10 @@ class TestPraisonAIAgentsPlanningIntegration:
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
planning=True,
|
||||
planning_llm="gpt-4o"
|
||||
planning_llm="gpt-5-nano"
|
||||
)
|
||||
|
||||
assert agents.planning_llm == "gpt-4o"
|
||||
assert agents.planning_llm == "gpt-5-nano"
|
||||
|
||||
def test_agents_auto_approve_plan(self):
|
||||
"""Test auto-approve plan option."""
|
||||
@@ -867,7 +867,7 @@ class TestPraisonAIAgentsPlanningIntegration:
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from praisonaiagents.planning import Plan, PlanStep
|
||||
|
||||
agent = Agent(name="Test", role="Tester", llm="gpt-4o-mini")
|
||||
agent = Agent(name="Test", role="Tester", llm="gpt-5-nano")
|
||||
task = Task(description="Test task", agent=agent)
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
@@ -880,7 +880,7 @@ class TestPraisonAIAgentsPlanningIntegration:
|
||||
# Test that planning is enabled and configured correctly
|
||||
assert agents.planning is True
|
||||
assert agents.auto_approve_plan is True
|
||||
assert agents.planning_llm == "gpt-4o-mini"
|
||||
assert agents.planning_llm == "gpt-5-nano"
|
||||
assert agents._current_plan is None # No plan created yet
|
||||
assert agents._todo_list is None
|
||||
|
||||
|
||||
@@ -1189,16 +1189,16 @@ class TestWorkflowConfiguration:
|
||||
"""Test that workflow planning setting is stored."""
|
||||
def step1(ctx): return StepResult(output="Done")
|
||||
|
||||
workflow = Workflow(steps=[step1], planning=True, planning_llm="gpt-4o")
|
||||
workflow = Workflow(steps=[step1], planning=True, planning_llm="gpt-5-nano")
|
||||
assert workflow.planning == True
|
||||
assert workflow.planning_llm == "gpt-4o"
|
||||
assert workflow.planning_llm == "gpt-5-nano"
|
||||
|
||||
def test_workflow_default_llm(self):
|
||||
"""Test default LLM setting."""
|
||||
def step1(ctx): return StepResult(output="Done")
|
||||
|
||||
workflow = Workflow(steps=[step1], default_llm="gpt-4o-mini")
|
||||
assert workflow.default_llm == "gpt-4o-mini"
|
||||
workflow = Workflow(steps=[step1], default_llm="gpt-5-nano")
|
||||
assert workflow.default_llm == "gpt-5-nano"
|
||||
|
||||
def test_workflow_default_agent_config(self):
|
||||
"""Test default agent config."""
|
||||
@@ -1393,10 +1393,10 @@ class TestPlanningAndReasoning:
|
||||
workflow = Workflow(
|
||||
steps=[lambda ctx: StepResult(output="test")],
|
||||
planning=True,
|
||||
planning_llm="gpt-4o"
|
||||
planning_llm="gpt-5-nano"
|
||||
)
|
||||
assert workflow.planning == True
|
||||
assert workflow.planning_llm == "gpt-4o"
|
||||
assert workflow.planning_llm == "gpt-5-nano"
|
||||
|
||||
def test_workflow_reasoning_parameter_exists(self):
|
||||
"""Test that Workflow accepts reasoning parameter."""
|
||||
|
||||
@@ -91,14 +91,11 @@ class TestAccuracyEvaluator:
|
||||
|
||||
def test_judge_output(self):
|
||||
"""Test LLM judging of output."""
|
||||
with patch('openai.OpenAI') as mock_openai_class:
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
|
||||
with patch('litellm.completion') as mock_completion:
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "SCORE: 8\nREASONING: Good match"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_completion.return_value = mock_response
|
||||
|
||||
def test_func(x):
|
||||
return "4"
|
||||
@@ -115,14 +112,11 @@ class TestAccuracyEvaluator:
|
||||
|
||||
def test_run_returns_accuracy_result(self):
|
||||
"""Test that run returns AccuracyResult."""
|
||||
with patch('openai.OpenAI') as mock_openai_class:
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
|
||||
with patch('litellm.completion') as mock_completion:
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "SCORE: 9\nREASONING: Excellent"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_completion.return_value = mock_response
|
||||
|
||||
def test_func(x):
|
||||
return "4"
|
||||
@@ -140,14 +134,11 @@ class TestAccuracyEvaluator:
|
||||
|
||||
def test_evaluate_output(self):
|
||||
"""Test evaluate_output method for pre-generated outputs."""
|
||||
with patch('openai.OpenAI') as mock_openai_class:
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
|
||||
with patch('litellm.completion') as mock_completion:
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "SCORE: 10\nREASONING: Perfect"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_completion.return_value = mock_response
|
||||
|
||||
def test_func(x):
|
||||
return "unused"
|
||||
@@ -325,14 +316,11 @@ class TestCriteriaEvaluator:
|
||||
|
||||
def test_judge_output_numeric(self):
|
||||
"""Test numeric scoring."""
|
||||
with patch('openai.OpenAI') as mock_openai_class:
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
|
||||
with patch('litellm.completion') as mock_completion:
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "SCORE: 8\nREASONING: Good"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_completion.return_value = mock_response
|
||||
|
||||
def test_func(x):
|
||||
return "test"
|
||||
@@ -350,14 +338,11 @@ class TestCriteriaEvaluator:
|
||||
|
||||
def test_judge_output_binary(self):
|
||||
"""Test binary scoring."""
|
||||
with patch('openai.OpenAI') as mock_openai_class:
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
|
||||
with patch('litellm.completion') as mock_completion:
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "RESULT: PASS\nREASONING: Meets criteria"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_completion.return_value = mock_response
|
||||
|
||||
def test_func(x):
|
||||
return "test"
|
||||
@@ -374,14 +359,11 @@ class TestCriteriaEvaluator:
|
||||
|
||||
def test_on_fail_callback(self):
|
||||
"""Test on_fail callback is called."""
|
||||
with patch('openai.OpenAI') as mock_openai_class:
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
|
||||
with patch('litellm.completion') as mock_completion:
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "SCORE: 3\nREASONING: Poor"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_completion.return_value = mock_response
|
||||
|
||||
on_fail_called = []
|
||||
def on_fail(score):
|
||||
@@ -403,14 +385,11 @@ class TestCriteriaEvaluator:
|
||||
|
||||
def test_evaluate_output_criteria(self):
|
||||
"""Test evaluate_output method."""
|
||||
with patch('openai.OpenAI') as mock_openai_class:
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
|
||||
with patch('litellm.completion') as mock_completion:
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "SCORE: 9\nREASONING: Great"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_completion.return_value = mock_response
|
||||
|
||||
def test_func(x):
|
||||
return "unused"
|
||||
|
||||
Generated
+1
-1
@@ -2422,7 +2422,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"version": "1.2.1",
|
||||
"lastUpdated": "2024-12-26",
|
||||
"implementationStatus": "Phase 4 Complete - Full Feature Parity",
|
||||
"summary": {
|
||||
"pythonCoreFeatures": 85,
|
||||
"pythonWrapperFeatures": 45,
|
||||
"typescriptFeatures": 75,
|
||||
"gapCount": 10,
|
||||
"priorityP0": 0,
|
||||
"priorityP1": 3,
|
||||
"priorityP2": 5,
|
||||
"priorityP3": 2
|
||||
},
|
||||
"pythonCoreSDK": {
|
||||
"path": "/Users/praison/praisonai-package/src/praisonai-agents/praisonaiagents",
|
||||
"exports": [
|
||||
"Agent", "ImageAgent", "ContextAgent", "DeepResearchAgent", "QueryRewriterAgent", "PromptExpanderAgent",
|
||||
"PraisonAIAgents", "Agents", "Task", "Tools", "BaseTool", "ToolResult", "tool", "FunctionTool",
|
||||
"ToolRegistry", "get_registry", "register_tool", "get_tool", "AutoAgents", "Session", "Memory",
|
||||
"Knowledge", "Chunking", "db", "obs", "MCP", "Workflow", "Pipeline", "WorkflowStep", "WorkflowContext",
|
||||
"StepResult", "Route", "Parallel", "Loop", "Repeat", "GuardrailResult", "LLMGuardrail",
|
||||
"Handoff", "handoff", "FlowDisplay", "FastContext", "SkillManager", "SkillProperties",
|
||||
"get_telemetry", "enable_telemetry", "MinimalTelemetry", "TelemetryCollector", "AGUI", "A2A"
|
||||
],
|
||||
"modules": {
|
||||
"agent": ["Agent", "ImageAgent", "ContextAgent", "DeepResearchAgent", "QueryRewriterAgent", "PromptExpanderAgent", "RouterAgent", "Handoff"],
|
||||
"agents": ["PraisonAIAgents", "AutoAgents"],
|
||||
"task": ["Task"],
|
||||
"tools": ["Tools", "BaseTool", "ToolResult", "tool", "FunctionTool", "ToolRegistry", "40+ built-in tools"],
|
||||
"llm": ["LLM", "OpenAIClient", "ModelRouter", "RateLimiter", "ModelCapabilities"],
|
||||
"memory": ["Memory", "AutoMemory", "FileMemory", "RulesManager", "DocsManager", "Hooks"],
|
||||
"knowledge": ["Knowledge", "Chunking", "VectorStore", "QueryEngine", "Readers", "Rerankers"],
|
||||
"db": ["DbAdapter", "AsyncDbAdapter", "DbMessage", "DbToolCall", "DbRun", "DbSpan", "DbTrace"],
|
||||
"obs": ["ObservabilityAdapter", "Span", "Trace", "16 provider integrations"],
|
||||
"mcp": ["MCP", "MCPServer", "MCPSession", "MCPTransport", "MCPWebSocket", "MCPSSE", "MCPHTTPStream"],
|
||||
"workflows": ["Workflow", "Pipeline", "WorkflowStep", "WorkflowContext", "StepResult", "Route", "Parallel", "Loop", "Repeat", "YAMLWorkflowParser"],
|
||||
"guardrails": ["GuardrailResult", "LLMGuardrail"],
|
||||
"eval": ["AccuracyEval", "PerformanceEval", "ReliabilityEval", "CriteriaEval"],
|
||||
"skills": ["SkillManager", "SkillProperties", "SkillLoader", "SkillDiscovery", "SkillValidator"],
|
||||
"telemetry": ["MinimalTelemetry", "TelemetryCollector", "PerformanceMonitor", "TokenCollector"],
|
||||
"context": ["FastContext", "FastContextResult"],
|
||||
"session": ["Session"],
|
||||
"ui": ["AGUI", "A2A"]
|
||||
}
|
||||
},
|
||||
"pythonWrapper": {
|
||||
"path": "/Users/praison/praisonai-package/src/praisonai/praisonai",
|
||||
"exports": ["PraisonAI"],
|
||||
"cliFeatures": [
|
||||
"slash_commands", "autonomy_mode", "cost_tracker", "repo_map", "interactive_tui",
|
||||
"git_integration", "sandbox_executor", "agent_scheduler", "agents", "at_mentions",
|
||||
"auto_memory", "background", "checkpoints", "compare", "eval", "external_agents",
|
||||
"fast_context", "flow_display", "guardrail", "handoff", "hooks", "image", "jobs",
|
||||
"knowledge", "mcp", "message_queue", "metrics", "n8n", "observability", "persistence",
|
||||
"profile", "profiling", "router", "session", "skills", "telemetry", "todo", "tools", "workflow"
|
||||
]
|
||||
},
|
||||
"typescriptSDK": {
|
||||
"path": "/Users/praison/praisonai-package/src/praisonai-ts/src",
|
||||
"exports": [
|
||||
"Agent", "ImageAgent", "ContextAgent", "DeepResearchAgent", "QueryRewriterAgent", "PromptExpanderAgent",
|
||||
"RouterAgent", "Handoff", "AutoAgents", "Task", "BaseTool", "ToolResult", "tool", "FunctionTool",
|
||||
"ToolRegistry", "createTool", "validateTool", "Session", "SessionManager", "Memory", "KnowledgeBase",
|
||||
"Chunking", "MemoryAdapter", "createDbFactory", "Workflow", "parallel", "route", "Guardrail",
|
||||
"GuardrailManager", "LLMGuardrail", "accuracyEval", "performanceEval", "reliabilityEval", "EvalSuite",
|
||||
"MemoryObservabilityAdapter", "ConsoleObservabilityAdapter", "SkillManager", "TelemetryCollector",
|
||||
"Plan", "PlanStep", "TodoList", "TodoItem", "PlanStorage",
|
||||
"OpenAIProvider", "AnthropicProvider", "GoogleProvider", "createProvider"
|
||||
],
|
||||
"modules": {
|
||||
"agent": ["Agent", "ImageAgent", "ContextAgent", "DeepResearchAgent", "QueryRewriterAgent", "PromptExpanderAgent", "RouterAgent", "Handoff"],
|
||||
"auto": ["AutoAgents", "createAutoAgents"],
|
||||
"task": ["Task"],
|
||||
"tools": ["BaseTool", "ToolResult", "tool", "FunctionTool", "ToolRegistry", "createTool", "validateTool", "arxivTools", "mcpSse"],
|
||||
"llm": ["OpenAIProvider", "AnthropicProvider", "GoogleProvider", "createProvider", "BaseProvider"],
|
||||
"memory": ["Memory", "createMemory", "MemoryEntry"],
|
||||
"knowledge": ["KnowledgeBase", "Chunking", "GraphRAG", "GraphStore", "BaseReranker", "CohereReranker", "CrossEncoderReranker", "LLMReranker"],
|
||||
"db": ["MemoryAdapter", "createDbFactory", "SQLiteAdapter", "UpstashRedisAdapter", "MemoryRedisAdapter", "NeonPostgresAdapter", "MemoryPostgresAdapter", "PostgresSessionStorage"],
|
||||
"session": ["Session", "SessionManager"],
|
||||
"workflows": ["Workflow", "parallel", "route", "loop", "repeat", "WorkflowStep", "WorkflowContext", "parseYAMLWorkflow", "createWorkflowFromYAML"],
|
||||
"guardrails": ["Guardrail", "GuardrailManager", "LLMGuardrail", "builtInGuardrails"],
|
||||
"eval": ["accuracyEval", "performanceEval", "reliabilityEval", "EvalSuite"],
|
||||
"observability": ["MemoryObservabilityAdapter", "ConsoleObservabilityProvider", "LangfuseObservabilityProvider", "BaseObservabilityProvider"],
|
||||
"skills": ["SkillManager", "parseSkillFile", "createSkillManager"],
|
||||
"telemetry": ["TelemetryCollector", "getTelemetry", "enableTelemetry", "disableTelemetry"],
|
||||
"planning": ["Plan", "PlanStep", "TodoList", "TodoItem", "PlanStorage"],
|
||||
"cache": ["MemoryCache", "FileCache", "BaseCache", "createMemoryCache", "createFileCache"],
|
||||
"events": ["EventEmitterPubSub", "AgentEventBus", "PubSub", "createEventBus", "createPubSub", "AgentEvents"],
|
||||
"vector": ["BaseVectorStore", "MemoryVectorStore", "PineconeVectorStore", "WeaviateVectorStore", "QdrantVectorStore", "ChromaVectorStore"],
|
||||
"voice": ["BaseVoiceProvider", "OpenAIVoiceProvider", "ElevenLabsVoiceProvider"],
|
||||
"cli": ["chat", "listProviders", "version", "help"]
|
||||
}
|
||||
},
|
||||
"gapMatrix": {
|
||||
"P0_CoreParity": [
|
||||
{"feature": "Multi-provider LLM support (OpenAI, Anthropic, Google)", "python": true, "typescript": true, "priority": "P0", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Streaming responses", "python": true, "typescript": true, "priority": "P0", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Structured outputs (JSON schema)", "python": true, "typescript": true, "priority": "P0", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Tool/function calling with validation", "python": true, "typescript": true, "priority": "P0", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Session management (session_id, run_id)", "python": true, "typescript": true, "priority": "P0", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Multi-agent orchestration patterns", "python": true, "typescript": true, "priority": "P0", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Agent handoff", "python": true, "typescript": true, "priority": "P0", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Tool decorator (@tool)", "python": true, "typescript": true, "priority": "P0", "effort": "low", "status": "DONE"},
|
||||
{"feature": "Tool registry", "python": true, "typescript": true, "priority": "P0", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Async agent execution", "python": true, "typescript": true, "priority": "P0", "effort": "low", "status": "DONE"},
|
||||
{"feature": "Error handling and retries", "python": true, "typescript": true, "priority": "P0", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Verbose/debug logging", "python": true, "typescript": true, "priority": "P0", "effort": "low", "status": "DONE"}
|
||||
],
|
||||
"P1_Persistence": [
|
||||
{"feature": "Database adapter protocol", "python": true, "typescript": true, "priority": "P1", "effort": "high", "status": "DONE"},
|
||||
{"feature": "SQLite adapter", "python": true, "typescript": false, "priority": "P1", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "PostgreSQL adapter", "python": true, "typescript": false, "priority": "P1", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Redis adapter", "python": true, "typescript": false, "priority": "P1", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Message persistence", "python": true, "typescript": true, "priority": "P1", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Run/trace persistence", "python": true, "typescript": true, "priority": "P1", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Memory persistence (vector)", "python": true, "typescript": true, "priority": "P1", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Knowledge base (RAG)", "python": true, "typescript": true, "priority": "P1", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Chunking strategies", "python": true, "typescript": true, "priority": "P1", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Vector store integration", "python": true, "typescript": true, "priority": "P1", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Semantic search/recall", "python": true, "typescript": true, "priority": "P1", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Working memory", "python": true, "typescript": true, "priority": "P1", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "File-based memory", "python": true, "typescript": false, "priority": "P1", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Auto-memory", "python": true, "typescript": false, "priority": "P1", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Session export/import", "python": true, "typescript": true, "priority": "P1", "effort": "medium", "status": "DONE"}
|
||||
],
|
||||
"P2_CLI": [
|
||||
{"feature": "CLI entrypoint", "python": true, "typescript": true, "priority": "P2", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Slash commands", "python": true, "typescript": false, "priority": "P2", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Interactive TUI", "python": true, "typescript": false, "priority": "P2", "effort": "high", "status": "TODO"},
|
||||
{"feature": "Cost tracking", "python": true, "typescript": false, "priority": "P2", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Repo map", "python": true, "typescript": false, "priority": "P2", "effort": "high", "status": "TODO"},
|
||||
{"feature": "Git integration", "python": true, "typescript": false, "priority": "P2", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Sandbox executor", "python": true, "typescript": false, "priority": "P2", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Autonomy modes", "python": true, "typescript": false, "priority": "P2", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Agent scheduler", "python": true, "typescript": false, "priority": "P2", "effort": "high", "status": "TODO"},
|
||||
{"feature": "Background jobs", "python": true, "typescript": false, "priority": "P2", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Checkpoints", "python": true, "typescript": false, "priority": "P2", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Flow display", "python": true, "typescript": false, "priority": "P2", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Profiling", "python": true, "typescript": true, "priority": "P2", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Metrics", "python": true, "typescript": true, "priority": "P2", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "External agent integrations", "python": true, "typescript": false, "priority": "P2", "effort": "high", "status": "TODO"},
|
||||
{"feature": "N8N integration", "python": true, "typescript": false, "priority": "P2", "effort": "high", "status": "TODO"},
|
||||
{"feature": "MCP CLI support", "python": true, "typescript": true, "priority": "P2", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Skills CLI", "python": true, "typescript": true, "priority": "P2", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Eval CLI", "python": true, "typescript": true, "priority": "P2", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Workflow CLI", "python": true, "typescript": true, "priority": "P2", "effort": "medium", "status": "DONE"}
|
||||
],
|
||||
"P3_Advanced": [
|
||||
{"feature": "Workflows/Pipelines", "python": true, "typescript": true, "priority": "P3", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Route pattern", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Parallel pattern", "python": true, "typescript": true, "priority": "P3", "effort": "low", "status": "DONE"},
|
||||
{"feature": "Loop pattern", "python": true, "typescript": false, "priority": "P3", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Repeat pattern", "python": true, "typescript": false, "priority": "P3", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "YAML workflow parser", "python": true, "typescript": false, "priority": "P3", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Guardrails", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "LLM Guardrail", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Evaluation framework", "python": true, "typescript": true, "priority": "P3", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Accuracy eval", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Performance eval", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Reliability eval", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Criteria eval", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Observability protocol", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Telemetry", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Performance monitoring", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Token tracking", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"},
|
||||
{"feature": "Skills system", "python": true, "typescript": true, "priority": "P3", "effort": "high", "status": "DONE"},
|
||||
{"feature": "Fast context", "python": true, "typescript": false, "priority": "P3", "effort": "medium", "status": "TODO"},
|
||||
{"feature": "Image agent", "python": true, "typescript": true, "priority": "P3", "effort": "medium", "status": "DONE"}
|
||||
]
|
||||
},
|
||||
"mastraCapabilities": {
|
||||
"implemented": [
|
||||
"Agent core", "Tool calling", "Streaming (partial)", "Multi-agent (partial)"
|
||||
],
|
||||
"missing": [
|
||||
"Memory with semantic recall", "Working memory", "Thread management", "Resource scoping",
|
||||
"Storage abstraction", "Vector store integration", "Embeddings", "Evaluation scorers",
|
||||
"Workflow engine", "Step execution", "Branching logic", "Event-driven workflows",
|
||||
"MCP integration", "Voice/TTS", "Observability hooks", "Request context",
|
||||
"Tool validation", "Tool suspend/resume", "Provider options per tool"
|
||||
]
|
||||
},
|
||||
"implementationPlan": {
|
||||
"phase1_P0": {
|
||||
"description": "Core agent parity - streaming, structured outputs, sessions, tool system",
|
||||
"tasks": [
|
||||
"Implement AI SDK provider abstraction (OpenAI, Anthropic, Google)",
|
||||
"Add streaming response support with proper typing",
|
||||
"Implement structured output with JSON schema/Zod",
|
||||
"Add session management (session_id, run_id, trace_id)",
|
||||
"Implement tool decorator and registry",
|
||||
"Add agent handoff support",
|
||||
"Improve error handling and retries"
|
||||
]
|
||||
},
|
||||
"phase2_P1": {
|
||||
"description": "Persistence layer - db adapters, memory, knowledge",
|
||||
"tasks": [
|
||||
"Implement DbAdapter protocol",
|
||||
"Add SQLite adapter",
|
||||
"Add message/run/trace persistence",
|
||||
"Implement Memory with persistence",
|
||||
"Add Knowledge base (RAG) support",
|
||||
"Implement vector store integration"
|
||||
]
|
||||
},
|
||||
"phase3_P2": {
|
||||
"description": "CLI parity",
|
||||
"tasks": [
|
||||
"Create CLI entrypoint",
|
||||
"Implement slash commands",
|
||||
"Add cost tracking",
|
||||
"Implement interactive mode"
|
||||
]
|
||||
},
|
||||
"phase4_P3": {
|
||||
"description": "Advanced features",
|
||||
"tasks": [
|
||||
"Implement Workflow engine",
|
||||
"Add workflow patterns (Route, Parallel, Loop, Repeat)",
|
||||
"Implement Guardrails",
|
||||
"Add Evaluation framework",
|
||||
"Implement Observability hooks",
|
||||
"Add Skills system"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ const config: Config.InitialOptions = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/tests'],
|
||||
testMatch: ['**/*.main.test.ts', '**/*.spec.ts'],
|
||||
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
|
||||
moduleFileExtensions: ['ts', 'js', 'json'],
|
||||
coverageDirectory: 'coverage',
|
||||
collectCoverageFrom: [
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"name": "praisonai",
|
||||
"version": "1.0.19",
|
||||
"version": "1.2.4",
|
||||
"description": "PraisonAI TypeScript AI Agents Framework - Node.js, npm, and Javascript AI Agents Framework",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"bin": {
|
||||
"praisonai-ts": "./dist/cli/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
@@ -62,7 +65,6 @@
|
||||
"fast-xml-parser": "^4.5.1",
|
||||
"node-fetch": "^2.6.9",
|
||||
"openai": "^4.81.0",
|
||||
"praisonai": "^1.0.19",
|
||||
"@modelcontextprotocol/sdk": "^1.12.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
# PraisonAI CLI Specification v1.0.0
|
||||
# This is the canonical contract for CLI behavior across all runtimes (Python, TypeScript, etc.)
|
||||
# Any CLI implementation MUST conform to this spec for parity.
|
||||
|
||||
version: "1.0.0"
|
||||
spec_name: "praisonai-cli"
|
||||
|
||||
# Binary names per ecosystem (NO COLLISION)
|
||||
binaries:
|
||||
python: praisonai
|
||||
typescript: praisonai-ts
|
||||
|
||||
# Commands (all implementations MUST support)
|
||||
commands:
|
||||
chat:
|
||||
description: "Chat with an AI agent"
|
||||
args:
|
||||
- name: prompt
|
||||
type: string
|
||||
required: true
|
||||
position: 0
|
||||
flags:
|
||||
- name: model
|
||||
short: m
|
||||
type: string
|
||||
description: "Model to use (e.g., openai/gpt-4o-mini)"
|
||||
- name: stream
|
||||
short: s
|
||||
type: boolean
|
||||
default: false
|
||||
description: "Enable streaming output"
|
||||
- name: session
|
||||
type: string
|
||||
description: "Session ID for conversation continuity"
|
||||
|
||||
run:
|
||||
description: "Run an agent with a task"
|
||||
args:
|
||||
- name: task
|
||||
type: string
|
||||
required: true
|
||||
position: 0
|
||||
flags:
|
||||
- name: agent
|
||||
short: a
|
||||
type: string
|
||||
description: "Agent configuration file or name"
|
||||
- name: tools
|
||||
short: t
|
||||
type: string
|
||||
description: "Comma-separated list of tools"
|
||||
|
||||
workflow:
|
||||
description: "Execute a multi-agent workflow"
|
||||
args:
|
||||
- name: file
|
||||
type: string
|
||||
required: true
|
||||
position: 0
|
||||
description: "Workflow YAML file path"
|
||||
flags:
|
||||
- name: parallel
|
||||
type: boolean
|
||||
default: false
|
||||
description: "Run workflow steps in parallel where possible"
|
||||
|
||||
eval:
|
||||
description: "Evaluate agent performance"
|
||||
subcommands:
|
||||
accuracy:
|
||||
description: "Run accuracy evaluation"
|
||||
flags:
|
||||
- name: input
|
||||
type: string
|
||||
required: true
|
||||
- name: expected
|
||||
type: string
|
||||
required: true
|
||||
- name: iterations
|
||||
type: integer
|
||||
default: 1
|
||||
performance:
|
||||
description: "Run performance benchmark"
|
||||
flags:
|
||||
- name: iterations
|
||||
type: integer
|
||||
default: 10
|
||||
- name: warmup
|
||||
type: integer
|
||||
default: 2
|
||||
reliability:
|
||||
description: "Run reliability check"
|
||||
flags:
|
||||
- name: expected-tools
|
||||
type: string
|
||||
description: "Comma-separated expected tool calls"
|
||||
|
||||
providers:
|
||||
description: "List available LLM providers"
|
||||
flags: []
|
||||
|
||||
tools:
|
||||
description: "List or manage tools"
|
||||
subcommands:
|
||||
list:
|
||||
description: "List available tools"
|
||||
info:
|
||||
description: "Show tool information"
|
||||
args:
|
||||
- name: name
|
||||
type: string
|
||||
required: true
|
||||
position: 0
|
||||
|
||||
version:
|
||||
description: "Show CLI version"
|
||||
flags: []
|
||||
|
||||
help:
|
||||
description: "Show help information"
|
||||
args:
|
||||
- name: command
|
||||
type: string
|
||||
required: false
|
||||
position: 0
|
||||
|
||||
# Global flags (apply to all commands)
|
||||
global_flags:
|
||||
- name: verbose
|
||||
short: v
|
||||
type: boolean
|
||||
default: false
|
||||
description: "Enable verbose output"
|
||||
- name: config
|
||||
short: c
|
||||
type: string
|
||||
description: "Path to config file"
|
||||
- name: profile
|
||||
short: p
|
||||
type: string
|
||||
description: "Profile name to use"
|
||||
- name: output
|
||||
short: o
|
||||
type: string
|
||||
enum: [json, text, pretty]
|
||||
default: pretty
|
||||
description: "Output format"
|
||||
- name: json
|
||||
type: boolean
|
||||
default: false
|
||||
description: "Shorthand for --output json"
|
||||
|
||||
# Exit codes (MUST be identical across implementations)
|
||||
exit_codes:
|
||||
success: 0
|
||||
runtime_error: 1
|
||||
invalid_arguments: 2
|
||||
config_error: 3
|
||||
network_error: 4
|
||||
auth_error: 5
|
||||
|
||||
# Output schema for JSON mode (MUST be byte-compatible)
|
||||
output_schema:
|
||||
success:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
value: true
|
||||
data:
|
||||
type: object
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
duration_ms:
|
||||
type: number
|
||||
model:
|
||||
type: string
|
||||
tokens:
|
||||
type: object
|
||||
properties:
|
||||
input:
|
||||
type: integer
|
||||
output:
|
||||
type: integer
|
||||
error:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
value: false
|
||||
error:
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
details:
|
||||
type: object
|
||||
|
||||
# Environment variables (MUST be respected)
|
||||
environment:
|
||||
PRAISONAI_MODEL:
|
||||
description: "Default model to use"
|
||||
example: "openai/gpt-4o-mini"
|
||||
PRAISONAI_PROFILE:
|
||||
description: "Default profile name"
|
||||
PRAISONAI_VERBOSE:
|
||||
description: "Enable verbose mode (true/false)"
|
||||
PRAISONAI_CONFIG:
|
||||
description: "Path to config file"
|
||||
OPENAI_API_KEY:
|
||||
description: "OpenAI API key"
|
||||
ANTHROPIC_API_KEY:
|
||||
description: "Anthropic API key"
|
||||
GOOGLE_API_KEY:
|
||||
description: "Google API key"
|
||||
|
||||
# Config file schema reference
|
||||
config:
|
||||
files:
|
||||
- .praisonai.yaml
|
||||
- .praisonai.json
|
||||
resolution_order:
|
||||
- cli_flags
|
||||
- environment_variables
|
||||
- project_config
|
||||
- user_config
|
||||
- defaults
|
||||
schema:
|
||||
model:
|
||||
type: string
|
||||
default: "openai/gpt-4o-mini"
|
||||
verbose:
|
||||
type: boolean
|
||||
default: false
|
||||
stream:
|
||||
type: boolean
|
||||
default: false
|
||||
profiles:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
properties:
|
||||
model:
|
||||
type: string
|
||||
verbose:
|
||||
type: boolean
|
||||
stream:
|
||||
type: boolean
|
||||
|
||||
# Tool interface contract
|
||||
tool_schema:
|
||||
name:
|
||||
type: string
|
||||
required: true
|
||||
description:
|
||||
type: string
|
||||
required: true
|
||||
inputs:
|
||||
type: object
|
||||
description: "JSON Schema for tool inputs"
|
||||
outputs:
|
||||
type: object
|
||||
description: "JSON Schema for tool outputs"
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"command": "chat",
|
||||
"description": "Golden test fixtures for chat command JSON output parity",
|
||||
"test_cases": [
|
||||
{
|
||||
"name": "simple_chat_success",
|
||||
"input": {
|
||||
"args": ["Hello, how are you?"],
|
||||
"options": {
|
||||
"output": "json"
|
||||
}
|
||||
},
|
||||
"expected_schema": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"response": "string"
|
||||
},
|
||||
"meta": {
|
||||
"duration_ms": "number",
|
||||
"model": "string",
|
||||
"tokens": {
|
||||
"input": "number",
|
||||
"output": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "chat_missing_prompt",
|
||||
"input": {
|
||||
"args": [],
|
||||
"options": {
|
||||
"output": "json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "MISSING_ARGUMENT",
|
||||
"message": "Please provide a prompt"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "chat_with_model",
|
||||
"input": {
|
||||
"args": ["Test prompt"],
|
||||
"options": {
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"output": "json"
|
||||
}
|
||||
},
|
||||
"expected_schema": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"response": "string"
|
||||
},
|
||||
"meta": {
|
||||
"model": "openai/gpt-4o-mini"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"command": "run",
|
||||
"description": "Golden test fixtures for run command JSON output parity",
|
||||
"test_cases": [
|
||||
{
|
||||
"name": "run_success",
|
||||
"input": {
|
||||
"args": ["Analyze this data"],
|
||||
"options": {
|
||||
"output": "json"
|
||||
}
|
||||
},
|
||||
"expected_schema": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"result": "string",
|
||||
"agent": "string",
|
||||
"task": "string"
|
||||
},
|
||||
"meta": {
|
||||
"duration_ms": "number",
|
||||
"model": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "run_missing_task",
|
||||
"input": {
|
||||
"args": [],
|
||||
"options": {
|
||||
"output": "json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "MISSING_ARGUMENT",
|
||||
"message": "Please provide a task"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"command": "workflow",
|
||||
"description": "Golden test fixtures for workflow command JSON output parity",
|
||||
"test_cases": [
|
||||
{
|
||||
"name": "workflow_success",
|
||||
"input": {
|
||||
"args": ["workflow.yaml"],
|
||||
"options": {
|
||||
"output": "json"
|
||||
}
|
||||
},
|
||||
"expected_schema": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"workflow": "string",
|
||||
"steps": "number",
|
||||
"results": "object"
|
||||
},
|
||||
"meta": {
|
||||
"duration_ms": "number",
|
||||
"model": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "workflow_missing_file",
|
||||
"input": {
|
||||
"args": [],
|
||||
"options": {
|
||||
"output": "json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "MISSING_ARGUMENT",
|
||||
"message": "Please provide a workflow file path"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Context Agent - Agent with enhanced context management
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
|
||||
import { Session } from '../session';
|
||||
import { KnowledgeBase, type SearchResult } from '../knowledge/rag';
|
||||
|
||||
export interface ContextAgentConfig {
|
||||
name?: string;
|
||||
instructions: string;
|
||||
llm?: string;
|
||||
knowledgeBase?: KnowledgeBase;
|
||||
contextWindow?: number;
|
||||
maxContextTokens?: number;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export interface ContextMessage {
|
||||
role: 'system' | 'user' | 'assistant';
|
||||
content: string;
|
||||
timestamp: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context Agent - Agent with RAG and context management
|
||||
*/
|
||||
export class ContextAgent {
|
||||
readonly name: string;
|
||||
readonly instructions: string;
|
||||
|
||||
private provider: LLMProvider;
|
||||
private knowledgeBase?: KnowledgeBase;
|
||||
private contextWindow: number;
|
||||
private maxContextTokens: number;
|
||||
private messages: ContextMessage[] = [];
|
||||
private verbose: boolean;
|
||||
|
||||
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.knowledgeBase = config.knowledgeBase;
|
||||
this.contextWindow = config.contextWindow ?? 10;
|
||||
this.maxContextTokens = config.maxContextTokens ?? 4000;
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat with context awareness
|
||||
*/
|
||||
async chat(prompt: string): Promise<{ text: string; context?: SearchResult[] }> {
|
||||
// Add user message
|
||||
this.messages.push({
|
||||
role: 'user',
|
||||
content: prompt,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Build context from knowledge base
|
||||
let ragContext: SearchResult[] = [];
|
||||
let contextString = '';
|
||||
|
||||
if (this.knowledgeBase) {
|
||||
ragContext = await this.knowledgeBase.search(prompt);
|
||||
if (ragContext.length > 0) {
|
||||
contextString = this.knowledgeBase.buildContext(ragContext);
|
||||
if (this.verbose) {
|
||||
console.log(`[ContextAgent] Found ${ragContext.length} relevant documents`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build messages array with context
|
||||
const systemPrompt = contextString
|
||||
? `${this.instructions}\n\nRelevant context:\n${contextString}`
|
||||
: this.instructions;
|
||||
|
||||
const messages: Message[] = [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
...this.getRecentMessages()
|
||||
];
|
||||
|
||||
// Generate response
|
||||
const result = await this.provider.generateText({ messages });
|
||||
|
||||
// Add assistant message
|
||||
this.messages.push({
|
||||
role: 'assistant',
|
||||
content: result.text,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
return {
|
||||
text: result.text,
|
||||
context: ragContext.length > 0 ? ragContext : undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent messages within context window
|
||||
*/
|
||||
private getRecentMessages(): Message[] {
|
||||
const recent = this.messages.slice(-this.contextWindow);
|
||||
return recent.map(m => ({
|
||||
role: m.role,
|
||||
content: m.content
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add document to knowledge base
|
||||
*/
|
||||
async addDocument(id: string, content: string, metadata?: Record<string, any>): Promise<void> {
|
||||
if (!this.knowledgeBase) {
|
||||
throw new Error('No knowledge base configured');
|
||||
}
|
||||
await this.knowledgeBase.add({ id, content, metadata });
|
||||
}
|
||||
|
||||
/**
|
||||
* Search knowledge base
|
||||
*/
|
||||
async searchKnowledge(query: string, limit?: number): Promise<SearchResult[]> {
|
||||
if (!this.knowledgeBase) {
|
||||
return [];
|
||||
}
|
||||
return this.knowledgeBase.search(query, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear conversation history
|
||||
*/
|
||||
clearHistory(): void {
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation history
|
||||
*/
|
||||
getHistory(): ContextMessage[] {
|
||||
return [...this.messages];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set knowledge base
|
||||
*/
|
||||
setKnowledgeBase(kb: KnowledgeBase): void {
|
||||
this.knowledgeBase = kb;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a context agent
|
||||
*/
|
||||
export function createContextAgent(config: ContextAgentConfig): ContextAgent {
|
||||
return new ContextAgent(config);
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* 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 { Session, Run, Trace, getSessionManager } from '../session';
|
||||
import { FunctionTool, ToolRegistry, tool } from '../tools/decorator';
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
export interface EnhancedAgentConfig {
|
||||
name?: string;
|
||||
instructions: string;
|
||||
llm?: string;
|
||||
tools?: Array<FunctionTool | Function | { name: string; description?: string; parameters?: any; execute: Function }>;
|
||||
session?: Session;
|
||||
sessionId?: string;
|
||||
verbose?: boolean;
|
||||
stream?: boolean;
|
||||
maxToolCalls?: number;
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
outputSchema?: any;
|
||||
}
|
||||
|
||||
export interface ChatOptions {
|
||||
stream?: boolean;
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
outputSchema?: any;
|
||||
onToken?: (token: string) => void;
|
||||
}
|
||||
|
||||
export interface ChatResult {
|
||||
text: string;
|
||||
structured?: any;
|
||||
toolCalls?: ToolCall[];
|
||||
usage: {
|
||||
promptTokens: number;
|
||||
completionTokens: number;
|
||||
totalTokens: number;
|
||||
};
|
||||
runId: string;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced Agent with full feature support
|
||||
*/
|
||||
export class EnhancedAgent {
|
||||
readonly name: string;
|
||||
readonly instructions: string;
|
||||
readonly sessionId: string;
|
||||
|
||||
private provider: LLMProvider;
|
||||
private session: Session;
|
||||
private toolRegistry: ToolRegistry;
|
||||
private verbose: boolean;
|
||||
private stream: boolean;
|
||||
private maxToolCalls: number;
|
||||
private temperature: number;
|
||||
private maxTokens?: number;
|
||||
private outputSchema?: any;
|
||||
|
||||
constructor(config: EnhancedAgentConfig) {
|
||||
this.name = config.name || `Agent_${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.instructions = config.instructions;
|
||||
this.verbose = config.verbose ?? true;
|
||||
this.stream = config.stream ?? true;
|
||||
this.maxToolCalls = config.maxToolCalls ?? 10;
|
||||
this.temperature = config.temperature ?? 0.7;
|
||||
this.maxTokens = config.maxTokens;
|
||||
this.outputSchema = config.outputSchema;
|
||||
|
||||
// Initialize provider
|
||||
this.provider = createProvider(config.llm || 'gpt-4o-mini');
|
||||
|
||||
// Initialize session
|
||||
if (config.session) {
|
||||
this.session = config.session;
|
||||
} else if (config.sessionId) {
|
||||
this.session = getSessionManager().getOrCreate(config.sessionId);
|
||||
} else {
|
||||
this.session = getSessionManager().create();
|
||||
}
|
||||
this.sessionId = this.session.id;
|
||||
|
||||
// Initialize tool registry
|
||||
this.toolRegistry = new ToolRegistry();
|
||||
if (config.tools) {
|
||||
this.registerTools(config.tools);
|
||||
}
|
||||
|
||||
Logger.setVerbose(this.verbose);
|
||||
}
|
||||
|
||||
private registerTools(tools: EnhancedAgentConfig['tools']): void {
|
||||
if (!tools) return;
|
||||
|
||||
for (const t of tools) {
|
||||
if (t instanceof FunctionTool) {
|
||||
this.toolRegistry.register(t);
|
||||
} else if (typeof t === 'function') {
|
||||
// Convert function to tool
|
||||
const funcName = t.name || `function_${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.toolRegistry.register(tool({
|
||||
name: funcName,
|
||||
description: `Function ${funcName}`,
|
||||
execute: t as any,
|
||||
}));
|
||||
} else if (typeof t === 'object' && 'execute' in t) {
|
||||
// Tool config object
|
||||
this.toolRegistry.register(tool({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: t.parameters,
|
||||
execute: t.execute as any,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tool to the agent
|
||||
*/
|
||||
addTool(t: FunctionTool | { name: string; description?: string; parameters?: any; execute: Function }): this {
|
||||
if (t instanceof FunctionTool) {
|
||||
this.toolRegistry.register(t);
|
||||
} else {
|
||||
this.toolRegistry.register(tool({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: t.parameters,
|
||||
execute: t.execute as any,
|
||||
}));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered tools
|
||||
*/
|
||||
getTools(): FunctionTool[] {
|
||||
return this.toolRegistry.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat with the agent
|
||||
*/
|
||||
async chat(prompt: string, options: ChatOptions = {}): Promise<ChatResult> {
|
||||
const run = this.session.createRun().start();
|
||||
const trace = run.createTrace({ name: 'chat' }).start();
|
||||
|
||||
try {
|
||||
// Add user message to session
|
||||
this.session.addMessage({ role: 'user', content: prompt });
|
||||
|
||||
// Build messages array
|
||||
const messages: Message[] = [
|
||||
{ role: 'system', content: this.instructions },
|
||||
...this.session.getMessagesForLLM() as Message[],
|
||||
];
|
||||
|
||||
// Get tool definitions
|
||||
const tools = this.toolRegistry.list().length > 0
|
||||
? this.toolRegistry.getDefinitions()
|
||||
: undefined;
|
||||
|
||||
let result: GenerateTextResult;
|
||||
let fullText = '';
|
||||
let allToolCalls: ToolCall[] = [];
|
||||
let iterations = 0;
|
||||
|
||||
// Tool calling loop
|
||||
while (iterations < this.maxToolCalls) {
|
||||
iterations++;
|
||||
|
||||
if (options.stream && !tools && options.onToken) {
|
||||
// Streaming without tools
|
||||
const stream = await this.provider.streamText({
|
||||
messages,
|
||||
temperature: options.temperature ?? this.temperature,
|
||||
maxTokens: options.maxTokens ?? this.maxTokens,
|
||||
onToken: options.onToken,
|
||||
});
|
||||
|
||||
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.text) fullText += chunk.text;
|
||||
if (chunk.usage) usage = { ...usage, ...chunk.usage };
|
||||
}
|
||||
|
||||
result = {
|
||||
text: fullText,
|
||||
usage,
|
||||
finishReason: 'stop',
|
||||
};
|
||||
break;
|
||||
} else if (options.outputSchema) {
|
||||
// Structured output
|
||||
const objResult = await this.provider.generateObject({
|
||||
messages,
|
||||
schema: options.outputSchema ?? this.outputSchema,
|
||||
temperature: options.temperature ?? this.temperature,
|
||||
maxTokens: options.maxTokens ?? this.maxTokens,
|
||||
});
|
||||
|
||||
result = {
|
||||
text: JSON.stringify(objResult.object),
|
||||
usage: objResult.usage,
|
||||
finishReason: 'stop',
|
||||
};
|
||||
fullText = result.text;
|
||||
break;
|
||||
} else {
|
||||
// Regular generation with potential tool calls
|
||||
result = await this.provider.generateText({
|
||||
messages,
|
||||
temperature: options.temperature ?? this.temperature,
|
||||
maxTokens: options.maxTokens ?? this.maxTokens,
|
||||
tools,
|
||||
});
|
||||
|
||||
if (result.toolCalls && result.toolCalls.length > 0) {
|
||||
allToolCalls.push(...result.toolCalls);
|
||||
|
||||
// Add assistant message with tool calls
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: result.text || null,
|
||||
tool_calls: result.toolCalls,
|
||||
});
|
||||
|
||||
// Execute tool calls
|
||||
for (const tc of result.toolCalls) {
|
||||
const toolTrace = trace.createChild({ name: `tool:${tc.function.name}` }).start();
|
||||
|
||||
try {
|
||||
const toolFn = this.toolRegistry.get(tc.function.name);
|
||||
if (!toolFn) {
|
||||
throw new Error(`Tool '${tc.function.name}' not found`);
|
||||
}
|
||||
|
||||
const args = JSON.parse(tc.function.arguments);
|
||||
const toolResult = await toolFn.execute(args, {
|
||||
agentName: this.name,
|
||||
sessionId: this.sessionId,
|
||||
runId: run.id,
|
||||
});
|
||||
|
||||
const resultStr = typeof toolResult === 'string'
|
||||
? toolResult
|
||||
: JSON.stringify(toolResult);
|
||||
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
content: resultStr,
|
||||
tool_call_id: tc.id,
|
||||
});
|
||||
|
||||
toolTrace.complete({ result: resultStr });
|
||||
} catch (error: any) {
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
content: `Error: ${error.message}`,
|
||||
tool_call_id: tc.id,
|
||||
});
|
||||
toolTrace.fail(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Continue loop to get final response
|
||||
continue;
|
||||
}
|
||||
|
||||
// No tool calls, we have our final response
|
||||
fullText = result.text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add assistant message to session
|
||||
this.session.addMessage({
|
||||
role: 'assistant',
|
||||
content: fullText,
|
||||
tool_calls: allToolCalls.length > 0 ? allToolCalls : undefined,
|
||||
});
|
||||
|
||||
trace.complete({ responseLength: fullText.length });
|
||||
run.complete();
|
||||
|
||||
// Parse structured output if schema was provided
|
||||
let structured: any;
|
||||
if (options.outputSchema) {
|
||||
try {
|
||||
structured = JSON.parse(fullText);
|
||||
} catch {
|
||||
// Not valid JSON
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
text: fullText,
|
||||
structured,
|
||||
toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
|
||||
usage: result!.usage,
|
||||
runId: run.id,
|
||||
sessionId: this.sessionId,
|
||||
};
|
||||
} catch (error: any) {
|
||||
trace.fail(error);
|
||||
run.fail(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start method for compatibility
|
||||
*/
|
||||
async start(prompt: string, options?: ChatOptions): Promise<string> {
|
||||
const result = await this.chat(prompt, options);
|
||||
return result.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the session
|
||||
*/
|
||||
getSession(): Session {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear conversation history
|
||||
*/
|
||||
clearHistory(): void {
|
||||
this.session.clearMessages();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Agent Handoff - Transfer conversations between agents
|
||||
*/
|
||||
|
||||
import type { EnhancedAgent } from './enhanced';
|
||||
|
||||
export interface HandoffConfig {
|
||||
agent: EnhancedAgent;
|
||||
name?: string;
|
||||
description?: string;
|
||||
condition?: (context: HandoffContext) => boolean;
|
||||
transformContext?: (messages: any[]) => any[];
|
||||
}
|
||||
|
||||
export interface HandoffContext {
|
||||
messages: any[];
|
||||
lastMessage: string;
|
||||
topic?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface HandoffResult {
|
||||
handedOffTo: string;
|
||||
response: string;
|
||||
context: HandoffContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handoff class - Represents a handoff target
|
||||
*/
|
||||
export class Handoff {
|
||||
readonly targetAgent: EnhancedAgent;
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
readonly condition?: (context: HandoffContext) => boolean;
|
||||
readonly transformContext?: (messages: any[]) => any[];
|
||||
|
||||
constructor(config: HandoffConfig) {
|
||||
this.targetAgent = config.agent;
|
||||
this.name = config.name || `handoff_to_${config.agent.name}`;
|
||||
this.description = config.description || `Transfer conversation to ${config.agent.name}`;
|
||||
this.condition = config.condition;
|
||||
this.transformContext = config.transformContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handoff should be triggered
|
||||
*/
|
||||
shouldTrigger(context: HandoffContext): boolean {
|
||||
if (this.condition) {
|
||||
return this.condition(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the handoff
|
||||
*/
|
||||
async execute(context: HandoffContext): Promise<HandoffResult> {
|
||||
let messages = context.messages;
|
||||
|
||||
if (this.transformContext) {
|
||||
messages = this.transformContext(messages);
|
||||
}
|
||||
|
||||
// Transfer context to target agent
|
||||
const response = await this.targetAgent.chat(context.lastMessage);
|
||||
|
||||
return {
|
||||
handedOffTo: this.targetAgent.name,
|
||||
response: response.text,
|
||||
context: {
|
||||
...context,
|
||||
messages,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool definition for LLM
|
||||
*/
|
||||
getToolDefinition(): { name: string; description: string; parameters: any } {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
reason: {
|
||||
type: 'string',
|
||||
description: 'Reason for the handoff',
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a handoff configuration
|
||||
*/
|
||||
export function handoff(config: HandoffConfig): Handoff {
|
||||
return new Handoff(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handoff filters - Common condition functions
|
||||
*/
|
||||
export const handoffFilters = {
|
||||
/**
|
||||
* Trigger handoff based on topic keywords
|
||||
*/
|
||||
topic: (keywords: string | string[]) => {
|
||||
const keywordList = Array.isArray(keywords) ? keywords : [keywords];
|
||||
return (context: HandoffContext): boolean => {
|
||||
const lastMessage = context.lastMessage.toLowerCase();
|
||||
return keywordList.some(kw => lastMessage.includes(kw.toLowerCase()));
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger handoff based on metadata
|
||||
*/
|
||||
metadata: (key: string, value: any) => {
|
||||
return (context: HandoffContext): boolean => {
|
||||
return context.metadata?.[key] === value;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Always trigger
|
||||
*/
|
||||
always: () => {
|
||||
return (): boolean => true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Never trigger (manual only)
|
||||
*/
|
||||
never: () => {
|
||||
return (): boolean => false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Combine multiple conditions with AND
|
||||
*/
|
||||
and: (...conditions: Array<(context: HandoffContext) => boolean>) => {
|
||||
return (context: HandoffContext): boolean => {
|
||||
return conditions.every(cond => cond(context));
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Combine multiple conditions with OR
|
||||
*/
|
||||
or: (...conditions: Array<(context: HandoffContext) => boolean>) => {
|
||||
return (context: HandoffContext): boolean => {
|
||||
return conditions.some(cond => cond(context));
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* ImageAgent - Agent for image generation and analysis
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
|
||||
|
||||
export interface ImageGenerationConfig {
|
||||
prompt: string;
|
||||
size?: '256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792';
|
||||
quality?: 'standard' | 'hd';
|
||||
style?: 'vivid' | 'natural';
|
||||
n?: number;
|
||||
}
|
||||
|
||||
export interface ImageAnalysisConfig {
|
||||
imageUrl: string;
|
||||
prompt?: string;
|
||||
detail?: 'low' | 'high' | 'auto';
|
||||
}
|
||||
|
||||
export interface ImageAgentConfig {
|
||||
name?: string;
|
||||
llm?: string;
|
||||
imageModel?: string;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ImageAgent - Agent for image generation and analysis
|
||||
*/
|
||||
export class ImageAgent {
|
||||
readonly name: string;
|
||||
private provider: LLMProvider;
|
||||
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.imageModel = config.imageModel || 'dall-e-3';
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze an image
|
||||
*/
|
||||
async analyze(config: ImageAnalysisConfig): Promise<string> {
|
||||
const prompt = config.prompt || 'Describe this image in detail.';
|
||||
|
||||
const messages: Message[] = [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: config.imageUrl,
|
||||
detail: config.detail || 'auto'
|
||||
}
|
||||
}
|
||||
] as any
|
||||
}
|
||||
];
|
||||
|
||||
const result = await this.provider.generateText({ messages });
|
||||
|
||||
if (this.verbose) {
|
||||
console.log(`[ImageAgent] Analysis: ${result.text.substring(0, 100)}...`);
|
||||
}
|
||||
|
||||
return result.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an image (requires OpenAI DALL-E)
|
||||
*/
|
||||
async generate(config: ImageGenerationConfig): Promise<string[]> {
|
||||
// This would require direct OpenAI API call for image generation
|
||||
// For now, return a placeholder
|
||||
if (this.verbose) {
|
||||
console.log(`[ImageAgent] Generating image: ${config.prompt}`);
|
||||
}
|
||||
|
||||
throw new Error('Image generation requires direct OpenAI API integration. Use the OpenAI SDK directly for DALL-E.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat with image context
|
||||
*/
|
||||
async chat(prompt: string, imageUrl?: string): Promise<string> {
|
||||
if (imageUrl) {
|
||||
return this.analyze({ imageUrl, prompt });
|
||||
}
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
messages: [{ role: 'user', content: prompt }]
|
||||
});
|
||||
|
||||
return result.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two images
|
||||
*/
|
||||
async compare(imageUrl1: string, imageUrl2: string, prompt?: string): Promise<string> {
|
||||
const comparePrompt = prompt || 'Compare these two images and describe the differences and similarities.';
|
||||
|
||||
const messages: Message[] = [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: comparePrompt },
|
||||
{ type: 'image_url', image_url: { url: imageUrl1 } },
|
||||
{ type: 'image_url', image_url: { url: imageUrl2 } }
|
||||
] as any
|
||||
}
|
||||
];
|
||||
|
||||
const result = await this.provider.generateText({ messages });
|
||||
return result.text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ImageAgent
|
||||
*/
|
||||
export function createImageAgent(config?: ImageAgentConfig): ImageAgent {
|
||||
return new ImageAgent(config);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* PromptExpanderAgent - Expand and enhance prompts
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider } from '../llm/providers';
|
||||
|
||||
export type ExpandStrategy = 'detail' | 'context' | 'examples' | 'constraints' | 'auto';
|
||||
|
||||
export interface ExpandResult {
|
||||
original: string;
|
||||
expanded: string;
|
||||
strategy: ExpandStrategy;
|
||||
additions: string[];
|
||||
}
|
||||
|
||||
export interface PromptExpanderConfig {
|
||||
name?: string;
|
||||
llm?: string;
|
||||
defaultStrategy?: ExpandStrategy;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* PromptExpanderAgent - Expand prompts with more detail and context
|
||||
*/
|
||||
export class PromptExpanderAgent {
|
||||
readonly name: string;
|
||||
private provider: LLMProvider;
|
||||
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.defaultStrategy = config.defaultStrategy || 'auto';
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a prompt
|
||||
*/
|
||||
async expand(prompt: string, strategy?: ExpandStrategy): Promise<ExpandResult> {
|
||||
const useStrategy = strategy || this.defaultStrategy;
|
||||
const actualStrategy = useStrategy === 'auto' ? this.detectStrategy(prompt) : useStrategy;
|
||||
|
||||
const systemPrompt = this.getSystemPrompt(actualStrategy);
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: `Expand this prompt: ${prompt}` }
|
||||
]
|
||||
});
|
||||
|
||||
const expanded = result.text;
|
||||
const additions = this.extractAdditions(prompt, expanded);
|
||||
|
||||
if (this.verbose) {
|
||||
console.log(`[PromptExpander] Strategy: ${actualStrategy}, Added ${additions.length} elements`);
|
||||
}
|
||||
|
||||
return {
|
||||
original: prompt,
|
||||
expanded,
|
||||
strategy: actualStrategy,
|
||||
additions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the best expansion strategy
|
||||
*/
|
||||
detectStrategy(prompt: string): ExpandStrategy {
|
||||
const words = prompt.split(/\s+/).length;
|
||||
const lower = prompt.toLowerCase();
|
||||
|
||||
if (words < 5) return 'detail';
|
||||
if (lower.includes('why') || lower.includes('explain') || lower.includes('context')) return 'context';
|
||||
if (lower.includes('example') || lower.includes('show') || lower.includes('demonstrate')) return 'examples';
|
||||
if (lower.includes('must') || lower.includes('should') || lower.includes('require')) return 'constraints';
|
||||
|
||||
return 'detail';
|
||||
}
|
||||
|
||||
private getSystemPrompt(strategy: ExpandStrategy): string {
|
||||
const prompts: Record<ExpandStrategy, string> = {
|
||||
detail: 'Expand this prompt by adding specific details, requirements, and clarifications. Make it more precise and actionable.',
|
||||
context: 'Expand this prompt by adding relevant background context, explaining the purpose, and providing necessary information.',
|
||||
examples: 'Expand this prompt by adding concrete examples, sample inputs/outputs, and demonstrations of expected behavior.',
|
||||
constraints: 'Expand this prompt by adding explicit constraints, requirements, limitations, and success criteria.',
|
||||
auto: 'Expand this prompt to make it clearer, more specific, and more actionable. Add relevant details and context.'
|
||||
};
|
||||
return prompts[strategy];
|
||||
}
|
||||
|
||||
private extractAdditions(original: string, expanded: string): string[] {
|
||||
const originalWords = new Set(original.toLowerCase().split(/\s+/));
|
||||
const expandedWords = expanded.toLowerCase().split(/\s+/);
|
||||
|
||||
const newWords = expandedWords.filter(w => !originalWords.has(w) && w.length > 3);
|
||||
const uniqueNew = [...new Set(newWords)];
|
||||
|
||||
return uniqueNew.slice(0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PromptExpanderAgent
|
||||
*/
|
||||
export function createPromptExpanderAgent(config?: PromptExpanderConfig): PromptExpanderAgent {
|
||||
return new PromptExpanderAgent(config);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* QueryRewriterAgent - Rewrite and optimize queries
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider } from '../llm/providers';
|
||||
|
||||
export type RewriteStrategy = 'expand' | 'simplify' | 'decompose' | 'rephrase' | 'auto';
|
||||
|
||||
export interface RewriteResult {
|
||||
original: string;
|
||||
rewritten: string[];
|
||||
strategy: RewriteStrategy;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface QueryRewriterConfig {
|
||||
name?: string;
|
||||
llm?: string;
|
||||
defaultStrategy?: RewriteStrategy;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* QueryRewriterAgent - Optimize and rewrite queries
|
||||
*/
|
||||
export class QueryRewriterAgent {
|
||||
readonly name: string;
|
||||
private provider: LLMProvider;
|
||||
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.defaultStrategy = config.defaultStrategy || 'auto';
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a query
|
||||
*/
|
||||
async rewrite(query: string, strategy?: RewriteStrategy): Promise<RewriteResult> {
|
||||
const useStrategy = strategy || this.defaultStrategy;
|
||||
const actualStrategy = useStrategy === 'auto' ? this.detectStrategy(query) : useStrategy;
|
||||
|
||||
const prompt = this.buildPrompt(query, actualStrategy);
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: this.getSystemPrompt(actualStrategy) },
|
||||
{ role: 'user', content: prompt }
|
||||
]
|
||||
});
|
||||
|
||||
const rewritten = this.parseRewrittenQueries(result.text);
|
||||
|
||||
if (this.verbose) {
|
||||
console.log(`[QueryRewriter] Strategy: ${actualStrategy}, Rewrites: ${rewritten.length}`);
|
||||
}
|
||||
|
||||
return {
|
||||
original: query,
|
||||
rewritten,
|
||||
strategy: actualStrategy,
|
||||
confidence: rewritten.length > 0 ? 0.85 : 0.5
|
||||
};
|
||||
}
|
||||
|
||||
private detectStrategy(query: string): RewriteStrategy {
|
||||
const words = query.split(/\s+/).length;
|
||||
|
||||
if (words < 3) return 'expand';
|
||||
if (words > 20) return 'simplify';
|
||||
if (query.includes(' and ') || query.includes(' or ')) return 'decompose';
|
||||
return 'rephrase';
|
||||
}
|
||||
|
||||
private getSystemPrompt(strategy: RewriteStrategy): string {
|
||||
const prompts: Record<RewriteStrategy, string> = {
|
||||
expand: 'Expand this query with more context and detail. Generate 3 expanded versions.',
|
||||
simplify: 'Simplify this query to its core intent. Generate 3 simpler versions.',
|
||||
decompose: 'Break this query into smaller, focused sub-queries. Generate 3-5 sub-queries.',
|
||||
rephrase: 'Rephrase this query in different ways while keeping the same meaning. Generate 3 versions.',
|
||||
auto: 'Optimize this query for better search results. Generate 3 improved versions.'
|
||||
};
|
||||
return prompts[strategy] + ' Output one query per line.';
|
||||
}
|
||||
|
||||
private buildPrompt(query: string, strategy: RewriteStrategy): string {
|
||||
return `Query: ${query}\n\nRewrite using ${strategy} strategy:`;
|
||||
}
|
||||
|
||||
private parseRewrittenQueries(response: string): string[] {
|
||||
return response
|
||||
.split('\n')
|
||||
.map(line => line.replace(/^\d+[\.\)]\s*/, '').trim())
|
||||
.filter(line => line.length > 0 && line.length < 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a QueryRewriterAgent
|
||||
*/
|
||||
export function createQueryRewriterAgent(config?: QueryRewriterConfig): QueryRewriterAgent {
|
||||
return new QueryRewriterAgent(config);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* DeepResearchAgent - Agent for comprehensive research tasks
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
|
||||
|
||||
export interface Citation {
|
||||
title: string;
|
||||
url: string;
|
||||
snippet?: string;
|
||||
}
|
||||
|
||||
export interface ReasoningStep {
|
||||
step: number;
|
||||
thought: string;
|
||||
action?: string;
|
||||
result?: string;
|
||||
}
|
||||
|
||||
export interface ResearchResponse {
|
||||
answer: string;
|
||||
citations: Citation[];
|
||||
reasoning: ReasoningStep[];
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface DeepResearchConfig {
|
||||
name?: string;
|
||||
llm?: string;
|
||||
maxIterations?: number;
|
||||
searchTool?: (query: string) => Promise<Citation[]>;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* DeepResearchAgent - Comprehensive research with citations
|
||||
*/
|
||||
export class DeepResearchAgent {
|
||||
readonly name: string;
|
||||
private provider: LLMProvider;
|
||||
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.maxIterations = config.maxIterations ?? 5;
|
||||
this.searchTool = config.searchTool;
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conduct deep research on a topic
|
||||
*/
|
||||
async research(query: string): Promise<ResearchResponse> {
|
||||
const reasoning: ReasoningStep[] = [];
|
||||
const citations: Citation[] = [];
|
||||
let iteration = 0;
|
||||
|
||||
// Step 1: Analyze the query
|
||||
reasoning.push({
|
||||
step: ++iteration,
|
||||
thought: `Analyzing research query: "${query}"`,
|
||||
action: 'query_analysis'
|
||||
});
|
||||
|
||||
// Step 2: Generate search queries
|
||||
const searchQueries = await this.generateSearchQueries(query);
|
||||
reasoning.push({
|
||||
step: ++iteration,
|
||||
thought: `Generated ${searchQueries.length} search queries`,
|
||||
action: 'generate_queries',
|
||||
result: searchQueries.join(', ')
|
||||
});
|
||||
|
||||
// Step 3: Execute searches if tool available
|
||||
if (this.searchTool) {
|
||||
for (const sq of searchQueries.slice(0, 3)) {
|
||||
try {
|
||||
const results = await this.searchTool(sq);
|
||||
citations.push(...results);
|
||||
reasoning.push({
|
||||
step: ++iteration,
|
||||
thought: `Searched for: "${sq}"`,
|
||||
action: 'web_search',
|
||||
result: `Found ${results.length} results`
|
||||
});
|
||||
} catch (error) {
|
||||
if (this.verbose) {
|
||||
console.log(`[Research] Search failed for: ${sq}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Synthesize answer
|
||||
const answer = await this.synthesizeAnswer(query, citations);
|
||||
reasoning.push({
|
||||
step: ++iteration,
|
||||
thought: 'Synthesizing final answer from gathered information',
|
||||
action: 'synthesize',
|
||||
result: 'Answer generated'
|
||||
});
|
||||
|
||||
// Calculate confidence based on citations
|
||||
const confidence = Math.min(0.5 + (citations.length * 0.1), 0.95);
|
||||
|
||||
return {
|
||||
answer,
|
||||
citations,
|
||||
reasoning,
|
||||
confidence
|
||||
};
|
||||
}
|
||||
|
||||
private async generateSearchQueries(query: string): Promise<string[]> {
|
||||
const result = await this.provider.generateText({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'Generate 3 search queries to research this topic. Output only the queries, one per line.'
|
||||
},
|
||||
{ role: 'user', content: query }
|
||||
]
|
||||
});
|
||||
|
||||
return result.text.split('\n').filter(q => q.trim().length > 0).slice(0, 3);
|
||||
}
|
||||
|
||||
private async synthesizeAnswer(query: string, citations: Citation[]): Promise<string> {
|
||||
const context = citations.length > 0
|
||||
? `\n\nRelevant sources:\n${citations.map(c => `- ${c.title}: ${c.snippet || ''}`).join('\n')}`
|
||||
: '';
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a research assistant. Provide a comprehensive, well-structured answer based on the available information. Cite sources when available.'
|
||||
},
|
||||
{ role: 'user', content: `${query}${context}` }
|
||||
]
|
||||
});
|
||||
|
||||
return result.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set search tool
|
||||
*/
|
||||
setSearchTool(tool: (query: string) => Promise<Citation[]>): void {
|
||||
this.searchTool = tool;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DeepResearchAgent
|
||||
*/
|
||||
export function createDeepResearchAgent(config?: DeepResearchConfig): DeepResearchAgent {
|
||||
return new DeepResearchAgent(config);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Router Agent - Route requests to specialized agents
|
||||
*/
|
||||
|
||||
import type { EnhancedAgent } from './enhanced';
|
||||
|
||||
export interface RouteConfig {
|
||||
agent: EnhancedAgent;
|
||||
condition: (input: string, context?: RouteContext) => boolean | Promise<boolean>;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export interface RouteContext {
|
||||
history?: string[];
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface RouterConfig {
|
||||
name?: string;
|
||||
routes: RouteConfig[];
|
||||
defaultAgent?: EnhancedAgent;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Router Agent - Routes requests to the most appropriate agent
|
||||
*/
|
||||
export class RouterAgent {
|
||||
readonly name: string;
|
||||
private routes: RouteConfig[];
|
||||
private defaultAgent?: EnhancedAgent;
|
||||
private verbose: boolean;
|
||||
|
||||
constructor(config: RouterConfig) {
|
||||
this.name = config.name || 'Router';
|
||||
this.routes = config.routes.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
this.defaultAgent = config.defaultAgent;
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route a request to the appropriate agent
|
||||
*/
|
||||
async route(input: string, context?: RouteContext): Promise<{ agent: EnhancedAgent; response: string } | null> {
|
||||
for (const route of this.routes) {
|
||||
const matches = await route.condition(input, context);
|
||||
if (matches) {
|
||||
if (this.verbose) {
|
||||
console.log(`[Router] Routing to: ${route.agent.name}`);
|
||||
}
|
||||
const response = await route.agent.chat(input);
|
||||
return { agent: route.agent, response: response.text };
|
||||
}
|
||||
}
|
||||
|
||||
if (this.defaultAgent) {
|
||||
if (this.verbose) {
|
||||
console.log(`[Router] Using default agent: ${this.defaultAgent.name}`);
|
||||
}
|
||||
const response = await this.defaultAgent.chat(input);
|
||||
return { agent: this.defaultAgent, response: response.text };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a route
|
||||
*/
|
||||
addRoute(config: RouteConfig): this {
|
||||
this.routes.push(config);
|
||||
this.routes.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all routes
|
||||
*/
|
||||
getRoutes(): RouteConfig[] {
|
||||
return [...this.routes];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route condition helpers
|
||||
*/
|
||||
export const routeConditions = {
|
||||
/**
|
||||
* Match by keywords
|
||||
*/
|
||||
keywords: (keywords: string | string[]) => {
|
||||
const keywordList = Array.isArray(keywords) ? keywords : [keywords];
|
||||
return (input: string): boolean => {
|
||||
const lower = input.toLowerCase();
|
||||
return keywordList.some(kw => lower.includes(kw.toLowerCase()));
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Match by regex
|
||||
*/
|
||||
pattern: (regex: RegExp) => {
|
||||
return (input: string): boolean => regex.test(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Match by metadata
|
||||
*/
|
||||
metadata: (key: string, value: any) => {
|
||||
return (_input: string, context?: RouteContext): boolean => {
|
||||
return context?.metadata?.[key] === value;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Always match (for default routes)
|
||||
*/
|
||||
always: () => {
|
||||
return (): boolean => true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Combine conditions with AND
|
||||
*/
|
||||
and: (...conditions: Array<(input: string, context?: RouteContext) => boolean>) => {
|
||||
return (input: string, context?: RouteContext): boolean => {
|
||||
return conditions.every(c => c(input, context));
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Combine conditions with OR
|
||||
*/
|
||||
or: (...conditions: Array<(input: string, context?: RouteContext) => boolean>) => {
|
||||
return (input: string, context?: RouteContext): boolean => {
|
||||
return conditions.some(c => c(input, context));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a router agent
|
||||
*/
|
||||
export function createRouter(config: RouterConfig): RouterAgent {
|
||||
return new RouterAgent(config);
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export class Agent {
|
||||
this.name = config.name || `Agent_${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.verbose = config.verbose ?? process.env.PRAISON_VERBOSE !== 'false';
|
||||
this.pretty = config.pretty ?? process.env.PRAISON_PRETTY === 'true';
|
||||
this.llm = config.llm || 'gpt-5-nano';
|
||||
this.llm = config.llm || 'gpt-4o-mini';
|
||||
this.markdown = config.markdown ?? true;
|
||||
this.stream = config.stream ?? true;
|
||||
this.tools = config.tools;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* AutoAgents - Automatic agent generation from task descriptions
|
||||
*/
|
||||
|
||||
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
|
||||
|
||||
export interface AgentConfig {
|
||||
name: string;
|
||||
role: string;
|
||||
goal: string;
|
||||
backstory?: string;
|
||||
instructions?: string;
|
||||
tools?: string[];
|
||||
}
|
||||
|
||||
export interface TaskConfig {
|
||||
description: string;
|
||||
expectedOutput?: string;
|
||||
agent?: string;
|
||||
}
|
||||
|
||||
export interface TeamStructure {
|
||||
agents: AgentConfig[];
|
||||
tasks: TaskConfig[];
|
||||
pattern: 'sequential' | 'parallel' | 'hierarchical';
|
||||
}
|
||||
|
||||
export interface AutoAgentsConfig {
|
||||
llm?: string;
|
||||
pattern?: 'sequential' | 'parallel' | 'routing' | 'orchestrator-workers' | 'evaluator-optimizer';
|
||||
singleAgent?: boolean;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* AutoAgents - Generate agent configurations from task descriptions
|
||||
*/
|
||||
export class AutoAgents {
|
||||
private provider: LLMProvider;
|
||||
private pattern: string;
|
||||
private singleAgent: boolean;
|
||||
private verbose: boolean;
|
||||
|
||||
constructor(config: AutoAgentsConfig = {}) {
|
||||
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
|
||||
this.pattern = config.pattern || 'sequential';
|
||||
this.singleAgent = config.singleAgent ?? false;
|
||||
this.verbose = config.verbose ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate agent configuration from task description
|
||||
*/
|
||||
async generate(taskDescription: string): Promise<TeamStructure> {
|
||||
const prompt = this.buildPrompt(taskDescription);
|
||||
|
||||
const result = await this.provider.generateText({
|
||||
messages: [
|
||||
{ role: 'system', content: this.getSystemPrompt() },
|
||||
{ role: 'user', content: prompt }
|
||||
]
|
||||
});
|
||||
|
||||
return this.parseResponse(result.text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recommend a pattern for the task
|
||||
*/
|
||||
recommendPattern(taskDescription: string): string {
|
||||
const lower = taskDescription.toLowerCase();
|
||||
|
||||
if (lower.includes('parallel') || lower.includes('concurrent') || lower.includes('simultaneously')) {
|
||||
return 'parallel';
|
||||
}
|
||||
if (lower.includes('route') || lower.includes('classify') || lower.includes('categorize')) {
|
||||
return 'routing';
|
||||
}
|
||||
if (lower.includes('orchestrat') || lower.includes('coordinat') || lower.includes('manage')) {
|
||||
return 'orchestrator-workers';
|
||||
}
|
||||
if (lower.includes('evaluat') || lower.includes('optimi') || lower.includes('improv')) {
|
||||
return 'evaluator-optimizer';
|
||||
}
|
||||
|
||||
return 'sequential';
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze task complexity
|
||||
*/
|
||||
analyzeComplexity(taskDescription: string): 'simple' | 'moderate' | 'complex' {
|
||||
const words = taskDescription.split(/\s+/).length;
|
||||
const hasMultipleSteps = /step|then|after|before|first|second|third|finally/i.test(taskDescription);
|
||||
const hasMultipleAgents = /team|multiple|several|different|various/i.test(taskDescription);
|
||||
|
||||
if (words < 20 && !hasMultipleSteps && !hasMultipleAgents) {
|
||||
return 'simple';
|
||||
}
|
||||
if (words > 100 || (hasMultipleSteps && hasMultipleAgents)) {
|
||||
return 'complex';
|
||||
}
|
||||
return 'moderate';
|
||||
}
|
||||
|
||||
private getSystemPrompt(): string {
|
||||
return `You are an AI agent architect. Your job is to analyze task descriptions and generate optimal agent configurations.
|
||||
|
||||
Output a JSON object with the following structure:
|
||||
{
|
||||
"agents": [
|
||||
{
|
||||
"name": "AgentName",
|
||||
"role": "Role description",
|
||||
"goal": "Agent's goal",
|
||||
"backstory": "Optional backstory",
|
||||
"instructions": "Specific instructions",
|
||||
"tools": ["tool1", "tool2"]
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"description": "Task description",
|
||||
"expectedOutput": "What the task should produce",
|
||||
"agent": "AgentName"
|
||||
}
|
||||
],
|
||||
"pattern": "sequential|parallel|hierarchical"
|
||||
}
|
||||
|
||||
Guidelines:
|
||||
- For simple tasks, use 1 agent
|
||||
- For moderate tasks, use 2-3 agents
|
||||
- For complex tasks, use 3-5 agents
|
||||
- Match the pattern to the task requirements
|
||||
- Be specific about roles and goals`;
|
||||
}
|
||||
|
||||
private buildPrompt(taskDescription: string): string {
|
||||
const complexity = this.analyzeComplexity(taskDescription);
|
||||
const recommendedPattern = this.recommendPattern(taskDescription);
|
||||
|
||||
return `Task Description: ${taskDescription}
|
||||
|
||||
Complexity: ${complexity}
|
||||
Recommended Pattern: ${this.pattern || recommendedPattern}
|
||||
Single Agent Mode: ${this.singleAgent}
|
||||
|
||||
Generate an optimal agent configuration for this task.`;
|
||||
}
|
||||
|
||||
private parseResponse(response: string): TeamStructure {
|
||||
try {
|
||||
// Extract JSON from response
|
||||
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
return JSON.parse(jsonMatch[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
// Fallback to default structure
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return {
|
||||
agents: [{
|
||||
name: 'GeneralAgent',
|
||||
role: 'General purpose agent',
|
||||
goal: 'Complete the assigned task',
|
||||
instructions: 'Follow the task description carefully'
|
||||
}],
|
||||
tasks: [{
|
||||
description: 'Complete the task',
|
||||
agent: 'GeneralAgent'
|
||||
}],
|
||||
pattern: 'sequential'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AutoAgents instance
|
||||
*/
|
||||
export function createAutoAgents(config?: AutoAgentsConfig): AutoAgents {
|
||||
return new AutoAgents(config);
|
||||
}
|
||||
Vendored
+253
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Cache System - In-memory and persistent caching for agents
|
||||
* Inspired by mastra's cache module
|
||||
*/
|
||||
|
||||
export interface CacheConfig {
|
||||
name?: string;
|
||||
ttl?: number; // Time to live in milliseconds
|
||||
maxSize?: number; // Maximum number of entries
|
||||
}
|
||||
|
||||
export interface CacheEntry<T = any> {
|
||||
value: T;
|
||||
createdAt: number;
|
||||
expiresAt?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for cache implementations
|
||||
*/
|
||||
export abstract class BaseCache {
|
||||
readonly name: string;
|
||||
|
||||
constructor(config: CacheConfig = {}) {
|
||||
this.name = config.name || 'cache';
|
||||
}
|
||||
|
||||
abstract get<T = any>(key: string): Promise<T | undefined>;
|
||||
abstract set<T = any>(key: string, value: T, ttl?: number): Promise<void>;
|
||||
abstract delete(key: string): Promise<boolean>;
|
||||
abstract clear(): Promise<void>;
|
||||
abstract has(key: string): Promise<boolean>;
|
||||
abstract keys(): Promise<string[]>;
|
||||
abstract size(): Promise<number>;
|
||||
|
||||
// List operations
|
||||
abstract listPush<T = any>(key: string, value: T): Promise<void>;
|
||||
abstract listLength(key: string): Promise<number>;
|
||||
abstract listRange<T = any>(key: string, start: number, end?: number): Promise<T[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory cache implementation
|
||||
*/
|
||||
export class MemoryCache extends BaseCache {
|
||||
private cache: Map<string, CacheEntry> = new Map();
|
||||
private lists: Map<string, any[]> = new Map();
|
||||
private ttl?: number;
|
||||
private maxSize?: number;
|
||||
|
||||
constructor(config: CacheConfig = {}) {
|
||||
super(config);
|
||||
this.ttl = config.ttl;
|
||||
this.maxSize = config.maxSize;
|
||||
}
|
||||
|
||||
async get<T = any>(key: string): Promise<T | undefined> {
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) return undefined;
|
||||
|
||||
// Check expiration
|
||||
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
||||
this.cache.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return entry.value as T;
|
||||
}
|
||||
|
||||
async set<T = any>(key: string, value: T, ttl?: number): Promise<void> {
|
||||
// Enforce max size
|
||||
if (this.maxSize && this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
||||
// Remove oldest entry
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
if (firstKey) this.cache.delete(firstKey);
|
||||
}
|
||||
|
||||
const effectiveTtl = ttl ?? this.ttl;
|
||||
const entry: CacheEntry<T> = {
|
||||
value,
|
||||
createdAt: Date.now(),
|
||||
expiresAt: effectiveTtl ? Date.now() + effectiveTtl : undefined
|
||||
};
|
||||
|
||||
this.cache.set(key, entry);
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<boolean> {
|
||||
const existed = this.cache.has(key);
|
||||
this.cache.delete(key);
|
||||
this.lists.delete(key);
|
||||
return existed;
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.cache.clear();
|
||||
this.lists.clear();
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) return false;
|
||||
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
||||
this.cache.delete(key);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
return Array.from(this.cache.keys());
|
||||
}
|
||||
|
||||
async size(): Promise<number> {
|
||||
return this.cache.size;
|
||||
}
|
||||
|
||||
async listPush<T = any>(key: string, value: T): Promise<void> {
|
||||
if (!this.lists.has(key)) {
|
||||
this.lists.set(key, []);
|
||||
}
|
||||
this.lists.get(key)!.push(value);
|
||||
}
|
||||
|
||||
async listLength(key: string): Promise<number> {
|
||||
return this.lists.get(key)?.length ?? 0;
|
||||
}
|
||||
|
||||
async listRange<T = any>(key: string, start: number, end?: number): Promise<T[]> {
|
||||
const list = this.lists.get(key) ?? [];
|
||||
return list.slice(start, end) as T[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* File-based cache implementation
|
||||
*/
|
||||
export class FileCache extends BaseCache {
|
||||
private cacheDir: string;
|
||||
private fs: any;
|
||||
private path: any;
|
||||
|
||||
constructor(config: CacheConfig & { cacheDir?: string } = {}) {
|
||||
super(config);
|
||||
this.cacheDir = config.cacheDir || '.cache';
|
||||
}
|
||||
|
||||
private async ensureDir(): Promise<void> {
|
||||
if (!this.fs) {
|
||||
this.fs = await import('fs/promises');
|
||||
this.path = await import('path');
|
||||
}
|
||||
try {
|
||||
await this.fs.mkdir(this.cacheDir, { recursive: true });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
private getFilePath(key: string): string {
|
||||
const safeKey = key.replace(/[^a-zA-Z0-9-_]/g, '_');
|
||||
return this.path.join(this.cacheDir, `${safeKey}.json`);
|
||||
}
|
||||
|
||||
async get<T = any>(key: string): Promise<T | undefined> {
|
||||
await this.ensureDir();
|
||||
try {
|
||||
const data = await this.fs.readFile(this.getFilePath(key), 'utf-8');
|
||||
const entry: CacheEntry<T> = JSON.parse(data);
|
||||
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
||||
await this.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
return entry.value;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async set<T = any>(key: string, value: T, ttl?: number): Promise<void> {
|
||||
await this.ensureDir();
|
||||
const entry: CacheEntry<T> = {
|
||||
value,
|
||||
createdAt: Date.now(),
|
||||
expiresAt: ttl ? Date.now() + ttl : undefined
|
||||
};
|
||||
await this.fs.writeFile(this.getFilePath(key), JSON.stringify(entry));
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<boolean> {
|
||||
await this.ensureDir();
|
||||
try {
|
||||
await this.fs.unlink(this.getFilePath(key));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.ensureDir();
|
||||
try {
|
||||
const files = await this.fs.readdir(this.cacheDir);
|
||||
await Promise.all(
|
||||
files.filter((f: string) => f.endsWith('.json'))
|
||||
.map((f: string) => this.fs.unlink(this.path.join(this.cacheDir, f)))
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
return (await this.get(key)) !== undefined;
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
await this.ensureDir();
|
||||
try {
|
||||
const files = await this.fs.readdir(this.cacheDir);
|
||||
return files
|
||||
.filter((f: string) => f.endsWith('.json'))
|
||||
.map((f: string) => f.replace('.json', ''));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async size(): Promise<number> {
|
||||
return (await this.keys()).length;
|
||||
}
|
||||
|
||||
async listPush<T = any>(key: string, value: T): Promise<void> {
|
||||
const list = (await this.get<T[]>(`list_${key}`)) || [];
|
||||
list.push(value);
|
||||
await this.set(`list_${key}`, list);
|
||||
}
|
||||
|
||||
async listLength(key: string): Promise<number> {
|
||||
const list = (await this.get<any[]>(`list_${key}`)) || [];
|
||||
return list.length;
|
||||
}
|
||||
|
||||
async listRange<T = any>(key: string, start: number, end?: number): Promise<T[]> {
|
||||
const list = (await this.get<T[]>(`list_${key}`)) || [];
|
||||
return list.slice(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
// Factory functions
|
||||
export function createMemoryCache(config?: CacheConfig): MemoryCache {
|
||||
return new MemoryCache(config);
|
||||
}
|
||||
|
||||
export function createFileCache(config?: CacheConfig & { cacheDir?: string }): FileCache {
|
||||
return new FileCache(config);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Auto command - AutoAgents for automatic agent generation
|
||||
*/
|
||||
|
||||
import { AutoAgents, createAutoAgents } from '../../auto';
|
||||
import { resolveConfig } from '../config/resolve';
|
||||
import { outputJson, formatSuccess, formatError } from '../output/json';
|
||||
import * as pretty from '../output/pretty';
|
||||
import { EXIT_CODES } from '../spec/cli-spec';
|
||||
import { ERROR_CODES } from '../output/errors';
|
||||
|
||||
export interface AutoOptions {
|
||||
model?: string;
|
||||
verbose?: boolean;
|
||||
output?: 'json' | 'text' | 'pretty';
|
||||
json?: boolean;
|
||||
pattern?: string;
|
||||
agents?: number;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: AutoOptions): Promise<void> {
|
||||
const topic = args.join(' ');
|
||||
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
|
||||
const config = resolveConfig(options);
|
||||
|
||||
if (!topic) {
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a topic for auto-generation'));
|
||||
} else {
|
||||
await pretty.error('Please provide a topic for auto-generation');
|
||||
await pretty.dim('Usage: praisonai-ts auto "Create a research team"');
|
||||
}
|
||||
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
if (outputFormat !== 'json') {
|
||||
await pretty.info(`Generating agents for: "${topic}"`);
|
||||
}
|
||||
|
||||
const autoAgents = createAutoAgents({
|
||||
llm: config.model,
|
||||
pattern: options.pattern as any,
|
||||
verbose: options.verbose
|
||||
});
|
||||
|
||||
const result = await autoAgents.generate(topic);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess({
|
||||
topic,
|
||||
agents: result.agents,
|
||||
tasks: result.tasks,
|
||||
pattern: result.pattern
|
||||
}, {
|
||||
duration_ms: duration,
|
||||
model: config.model
|
||||
}));
|
||||
} else {
|
||||
await pretty.heading('Generated Team');
|
||||
await pretty.plain(`Pattern: ${result.pattern}`);
|
||||
await pretty.newline();
|
||||
|
||||
await pretty.plain('Agents:');
|
||||
for (const agent of result.agents) {
|
||||
await pretty.plain(` • ${agent.name}: ${agent.role}`);
|
||||
}
|
||||
|
||||
await pretty.newline();
|
||||
await pretty.plain('Tasks:');
|
||||
for (const task of result.tasks) {
|
||||
await pretty.plain(` • ${task.description?.substring(0, 60)}...`);
|
||||
}
|
||||
|
||||
await pretty.newline();
|
||||
await pretty.success(`Generated in ${duration}ms`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
|
||||
} else {
|
||||
await pretty.error(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
process.exit(EXIT_CODES.RUNTIME_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* CLI command: autonomy
|
||||
* Autonomy mode management for agent actions
|
||||
*/
|
||||
|
||||
import { AutonomyManager, createAutonomyManager, MODE_POLICIES, type AutonomyMode } from '../features/autonomy-mode';
|
||||
|
||||
let manager: AutonomyManager | null = null;
|
||||
|
||||
function getManager(): AutonomyManager {
|
||||
if (!manager) {
|
||||
manager = createAutonomyManager();
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
|
||||
const subcommand = args[0] || 'help';
|
||||
const isJson = Boolean(options.output === 'json' || options.json);
|
||||
|
||||
switch (subcommand) {
|
||||
case 'status':
|
||||
await handleStatus(isJson);
|
||||
break;
|
||||
case 'set':
|
||||
await handleSet(args.slice(1), isJson);
|
||||
break;
|
||||
case 'policies':
|
||||
await handlePolicies(args.slice(1), isJson);
|
||||
break;
|
||||
case 'reset':
|
||||
await handleReset(isJson);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
showHelp(isJson);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStatus(isJson: boolean): Promise<void> {
|
||||
const mgr = getManager();
|
||||
const summary = mgr.getSummary();
|
||||
const mode = mgr.getMode();
|
||||
const actionCount = mgr.getActionCount();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({
|
||||
success: true,
|
||||
mode,
|
||||
actionCount,
|
||||
policies: MODE_POLICIES[mode]
|
||||
}, null, 2));
|
||||
} else {
|
||||
console.log(summary);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSet(args: string[], isJson: boolean): Promise<void> {
|
||||
const mode = args[0] as AutonomyMode;
|
||||
|
||||
if (!mode || !['suggest', 'auto_edit', 'full_auto'].includes(mode)) {
|
||||
console.error('Error: Valid mode required (suggest, auto_edit, full_auto)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const mgr = getManager();
|
||||
mgr.setMode(mode);
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, mode }));
|
||||
} else {
|
||||
console.log(`✓ Autonomy mode set to: ${mode}`);
|
||||
console.log('\nPolicies for this mode:');
|
||||
for (const policy of MODE_POLICIES[mode]) {
|
||||
const status = policy.requiresApproval ? '⚠️ requires approval' : '✓ auto-approved';
|
||||
console.log(` ${policy.action}: ${status}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePolicies(args: string[], isJson: boolean): Promise<void> {
|
||||
const mode = (args[0] as AutonomyMode) || 'suggest';
|
||||
|
||||
if (!['suggest', 'auto_edit', 'full_auto'].includes(mode)) {
|
||||
console.error('Error: Valid mode required (suggest, auto_edit, full_auto)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const policies = MODE_POLICIES[mode];
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, mode, policies }, null, 2));
|
||||
} else {
|
||||
console.log(`Policies for mode: ${mode}\n`);
|
||||
for (const policy of policies) {
|
||||
const status = policy.requiresApproval ? '⚠️ requires approval' : '✓ auto-approved';
|
||||
console.log(` ${policy.action.padEnd(20)} ${status}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReset(isJson: boolean): Promise<void> {
|
||||
const mgr = getManager();
|
||||
mgr.clearRemembered();
|
||||
mgr.resetActionCount();
|
||||
mgr.setMode('suggest');
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, message: 'Autonomy state reset' }));
|
||||
} else {
|
||||
console.log('✓ Autonomy state reset');
|
||||
console.log(' Mode: suggest');
|
||||
console.log(' Remembered decisions: cleared');
|
||||
console.log(' Action count: 0');
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp(isJson: boolean): void {
|
||||
const help = {
|
||||
command: 'autonomy',
|
||||
description: 'Autonomy mode management for agent actions',
|
||||
subcommands: {
|
||||
status: 'Show current autonomy status',
|
||||
set: 'Set autonomy mode',
|
||||
policies: 'Show policies for a mode',
|
||||
reset: 'Reset autonomy state'
|
||||
},
|
||||
modes: {
|
||||
suggest: 'Requires approval for most actions (default)',
|
||||
auto_edit: 'Auto-approves file edits, requires approval for others',
|
||||
full_auto: 'Auto-approves most actions (use with caution)'
|
||||
},
|
||||
flags: {
|
||||
'--json': 'Output in JSON format'
|
||||
},
|
||||
examples: [
|
||||
'praisonai-ts autonomy status',
|
||||
'praisonai-ts autonomy set suggest',
|
||||
'praisonai-ts autonomy set auto_edit',
|
||||
'praisonai-ts autonomy set full_auto',
|
||||
'praisonai-ts autonomy policies suggest',
|
||||
'praisonai-ts autonomy reset'
|
||||
]
|
||||
};
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(help, null, 2));
|
||||
} else {
|
||||
console.log('Autonomy - Agent action approval management\n');
|
||||
console.log('Subcommands:');
|
||||
for (const [cmd, desc] of Object.entries(help.subcommands)) {
|
||||
console.log(` ${cmd.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nModes:');
|
||||
for (const [mode, desc] of Object.entries(help.modes)) {
|
||||
console.log(` ${mode.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nExamples:');
|
||||
for (const ex of help.examples) {
|
||||
console.log(` ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Cache command - Caching management
|
||||
*/
|
||||
|
||||
import { outputJson, formatSuccess, formatError } from '../output/json';
|
||||
import * as pretty from '../output/pretty';
|
||||
import { EXIT_CODES } from '../spec/cli-spec';
|
||||
import { ERROR_CODES } from '../output/errors';
|
||||
|
||||
export interface CacheOptions {
|
||||
verbose?: boolean;
|
||||
output?: 'json' | 'text' | 'pretty';
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: CacheOptions): Promise<void> {
|
||||
const action = args[0] || 'help';
|
||||
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'info':
|
||||
await showInfo(outputFormat);
|
||||
break;
|
||||
case 'providers':
|
||||
await listProviders(outputFormat);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
await showHelp(outputFormat);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
|
||||
} else {
|
||||
await pretty.error(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
process.exit(EXIT_CODES.RUNTIME_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async function showInfo(outputFormat: string): Promise<void> {
|
||||
const info = {
|
||||
feature: 'Cache',
|
||||
description: 'Caching system for LLM responses and computations',
|
||||
providers: [
|
||||
{ name: 'MemoryCache', description: 'In-memory cache (default)' },
|
||||
{ name: 'FileCache', description: 'File-based persistent cache' }
|
||||
],
|
||||
capabilities: [
|
||||
'Cache LLM responses',
|
||||
'Reduce API costs',
|
||||
'Improve response times',
|
||||
'TTL-based expiration',
|
||||
'Configurable cache keys'
|
||||
]
|
||||
};
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(info));
|
||||
} else {
|
||||
await pretty.heading('Cache');
|
||||
await pretty.plain(info.description);
|
||||
await pretty.newline();
|
||||
await pretty.plain('Providers:');
|
||||
for (const p of info.providers) {
|
||||
await pretty.plain(` • ${p.name}: ${p.description}`);
|
||||
}
|
||||
await pretty.newline();
|
||||
await pretty.plain('Capabilities:');
|
||||
for (const cap of info.capabilities) {
|
||||
await pretty.plain(` • ${cap}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function listProviders(outputFormat: string): Promise<void> {
|
||||
const providers = [
|
||||
{ name: 'memory', description: 'In-memory cache', available: true },
|
||||
{ name: 'file', description: 'File-based cache', available: true }
|
||||
];
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess({ providers }));
|
||||
} else {
|
||||
await pretty.heading('Cache Providers');
|
||||
for (const p of providers) {
|
||||
const status = p.available ? '✓' : '✗';
|
||||
await pretty.plain(` ${status} ${p.name.padEnd(15)} ${p.description}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function showHelp(outputFormat: string): Promise<void> {
|
||||
const help = {
|
||||
command: 'cache',
|
||||
description: 'Caching management for LLM responses',
|
||||
subcommands: [
|
||||
{ name: 'info', description: 'Show cache feature information' },
|
||||
{ name: 'providers', description: 'List available cache providers' },
|
||||
{ name: 'help', description: 'Show this help' }
|
||||
]
|
||||
};
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(help));
|
||||
} else {
|
||||
await pretty.heading('Cache Command');
|
||||
await pretty.plain(help.description);
|
||||
await pretty.newline();
|
||||
await pretty.plain('Subcommands:');
|
||||
for (const cmd of help.subcommands) {
|
||||
await pretty.plain(` ${cmd.name.padEnd(20)} ${cmd.description}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Chat command - Chat with an AI agent
|
||||
*/
|
||||
|
||||
import { createProvider } from '../../llm/providers';
|
||||
import { Session } from '../../session';
|
||||
import { resolveConfig } from '../config/resolve';
|
||||
import { printSuccess, printError, outputJson, formatSuccess, formatError } from '../output/json';
|
||||
import * as pretty from '../output/pretty';
|
||||
import { EXIT_CODES } from '../spec/cli-spec';
|
||||
import { ERROR_CODES, normalizeError } from '../output/errors';
|
||||
|
||||
export interface ChatOptions {
|
||||
model?: string;
|
||||
stream?: boolean;
|
||||
session?: string;
|
||||
verbose?: boolean;
|
||||
profile?: string;
|
||||
config?: string;
|
||||
output?: 'json' | 'text' | 'pretty';
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: ChatOptions): Promise<void> {
|
||||
const prompt = args[0];
|
||||
|
||||
if (!prompt) {
|
||||
if (options.json || options.output === 'json') {
|
||||
printError(ERROR_CODES.MISSING_ARG, 'Please provide a prompt');
|
||||
} else {
|
||||
await pretty.error('Please provide a prompt');
|
||||
}
|
||||
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
|
||||
}
|
||||
|
||||
// Resolve config with precedence
|
||||
const config = resolveConfig({
|
||||
configPath: options.config,
|
||||
profile: options.profile,
|
||||
model: options.model,
|
||||
verbose: options.verbose,
|
||||
stream: options.stream
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
|
||||
|
||||
try {
|
||||
const provider = createProvider(config.model);
|
||||
const session = new Session({ id: options.session });
|
||||
session.addMessage({ role: 'user', content: prompt });
|
||||
|
||||
let responseText = '';
|
||||
let inputTokens = 0;
|
||||
let outputTokens = 0;
|
||||
|
||||
if (config.stream && outputFormat !== 'json') {
|
||||
// Streaming mode (only for non-JSON output)
|
||||
const stream = await provider.streamText({
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
onToken: (token: string) => {
|
||||
process.stdout.write(token);
|
||||
responseText += token;
|
||||
}
|
||||
});
|
||||
|
||||
for await (const chunk of stream) {
|
||||
// Stream is consumed by onToken
|
||||
}
|
||||
console.log(); // New line after streaming
|
||||
} else {
|
||||
// Non-streaming mode
|
||||
const result = await provider.generateText({
|
||||
messages: [{ role: 'user', content: prompt }]
|
||||
});
|
||||
responseText = result.text;
|
||||
inputTokens = result.usage?.promptTokens || 0;
|
||||
outputTokens = result.usage?.completionTokens || 0;
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
const duration = Date.now() - startTime;
|
||||
outputJson(formatSuccess(
|
||||
{ response: responseText },
|
||||
{
|
||||
duration_ms: duration,
|
||||
model: config.model,
|
||||
tokens: {
|
||||
input: inputTokens,
|
||||
output: outputTokens
|
||||
}
|
||||
}
|
||||
));
|
||||
} else {
|
||||
console.log(responseText);
|
||||
}
|
||||
}
|
||||
|
||||
session.addMessage({ role: 'assistant', content: responseText });
|
||||
|
||||
} catch (error) {
|
||||
const cliError = normalizeError(error);
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(cliError.code, cliError.message, cliError.details));
|
||||
} else {
|
||||
await pretty.error(cliError.message);
|
||||
if (config.verbose && error instanceof Error && error.stack) {
|
||||
await pretty.dim(error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(cliError.exitCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* CLI command: checkpoints
|
||||
* Session state checkpointing and recovery
|
||||
*/
|
||||
|
||||
import { CheckpointManager, createCheckpointManager, createFileCheckpointStorage } from '../features/checkpoints';
|
||||
|
||||
let manager: CheckpointManager | null = null;
|
||||
|
||||
function getManager(storagePath?: string): CheckpointManager {
|
||||
if (!manager) {
|
||||
const storage = storagePath ? createFileCheckpointStorage(storagePath) : undefined;
|
||||
manager = createCheckpointManager({ storage });
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
|
||||
const subcommand = args[0] || 'help';
|
||||
const isJson = Boolean(options.output === 'json' || options.json);
|
||||
const storagePath = options.storage as string | undefined;
|
||||
|
||||
switch (subcommand) {
|
||||
case 'create':
|
||||
await handleCreate(args.slice(1), options, isJson, storagePath);
|
||||
break;
|
||||
case 'list':
|
||||
await handleList(isJson, storagePath);
|
||||
break;
|
||||
case 'get':
|
||||
await handleGet(args.slice(1), isJson, storagePath);
|
||||
break;
|
||||
case 'restore':
|
||||
await handleRestore(args.slice(1), isJson, storagePath);
|
||||
break;
|
||||
case 'delete':
|
||||
await handleDelete(args.slice(1), isJson, storagePath);
|
||||
break;
|
||||
case 'export':
|
||||
await handleExport(args.slice(1), isJson, storagePath);
|
||||
break;
|
||||
case 'import':
|
||||
await handleImport(args.slice(1), isJson, storagePath);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
showHelp(isJson);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreate(args: string[], options: Record<string, unknown>, isJson: boolean, storagePath?: string): Promise<void> {
|
||||
const name = args[0] || `checkpoint-${Date.now()}`;
|
||||
const mgr = getManager(storagePath);
|
||||
|
||||
// Set state from options if provided
|
||||
const state = options.state ? JSON.parse(options.state as string) : {};
|
||||
mgr.setState(state);
|
||||
|
||||
const checkpoint = await mgr.create(name, {
|
||||
sessionId: options.session as string,
|
||||
metadata: options.metadata ? JSON.parse(options.metadata as string) : undefined
|
||||
});
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, checkpoint }, null, 2));
|
||||
} else {
|
||||
console.log(`✓ Checkpoint created: ${checkpoint.id}`);
|
||||
console.log(` Name: ${checkpoint.name}`);
|
||||
console.log(` Time: ${checkpoint.timestamp.toISOString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleList(isJson: boolean, storagePath?: string): Promise<void> {
|
||||
const mgr = getManager(storagePath);
|
||||
const checkpoints = await mgr.list();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, checkpoints }, null, 2));
|
||||
} else {
|
||||
if (checkpoints.length === 0) {
|
||||
console.log('No checkpoints found');
|
||||
} else {
|
||||
console.log('Checkpoints:');
|
||||
for (const cp of checkpoints) {
|
||||
console.log(` ${cp.name} (${cp.id})`);
|
||||
console.log(` Created: ${cp.timestamp.toISOString()}`);
|
||||
if (cp.sessionId) console.log(` Session: ${cp.sessionId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGet(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
|
||||
const id = args[0];
|
||||
if (!id) {
|
||||
console.error('Error: Checkpoint ID is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const mgr = getManager(storagePath);
|
||||
const checkpoint = await mgr.get(id);
|
||||
|
||||
if (!checkpoint) {
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: false, error: 'Checkpoint not found' }));
|
||||
} else {
|
||||
console.error(`Checkpoint ${id} not found`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, checkpoint }, null, 2));
|
||||
} else {
|
||||
console.log(`Checkpoint: ${checkpoint.id}`);
|
||||
console.log(` Name: ${checkpoint.name}`);
|
||||
console.log(` Created: ${checkpoint.timestamp.toISOString()}`);
|
||||
console.log(` State: ${JSON.stringify(checkpoint.state, null, 2)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRestore(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
|
||||
const id = args[0];
|
||||
const mgr = getManager(storagePath);
|
||||
|
||||
let checkpoint;
|
||||
if (id) {
|
||||
checkpoint = await mgr.restore(id);
|
||||
} else {
|
||||
checkpoint = await mgr.restoreLatest();
|
||||
}
|
||||
|
||||
if (!checkpoint) {
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: false, error: 'No checkpoint to restore' }));
|
||||
} else {
|
||||
console.error('No checkpoint to restore');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, restored: checkpoint }, null, 2));
|
||||
} else {
|
||||
console.log(`✓ Restored checkpoint: ${checkpoint.id}`);
|
||||
console.log(` Name: ${checkpoint.name}`);
|
||||
console.log(` State keys: ${Object.keys(checkpoint.state).join(', ') || 'none'}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
|
||||
const id = args[0];
|
||||
if (!id) {
|
||||
console.error('Error: Checkpoint ID is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const mgr = getManager(storagePath);
|
||||
await mgr.delete(id);
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, deleted: id }));
|
||||
} else {
|
||||
console.log(`✓ Checkpoint ${id} deleted`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExport(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
|
||||
const id = args[0];
|
||||
if (!id) {
|
||||
console.error('Error: Checkpoint ID is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const mgr = getManager(storagePath);
|
||||
const exported = await mgr.export(id);
|
||||
|
||||
if (!exported) {
|
||||
console.error(`Checkpoint ${id} not found`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(exported);
|
||||
}
|
||||
|
||||
async function handleImport(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
|
||||
const jsonData = args[0];
|
||||
if (!jsonData) {
|
||||
console.error('Error: JSON data is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const mgr = getManager(storagePath);
|
||||
const checkpoint = await mgr.import(jsonData);
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, imported: checkpoint }, null, 2));
|
||||
} else {
|
||||
console.log(`✓ Checkpoint imported: ${checkpoint.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp(isJson: boolean): void {
|
||||
const help = {
|
||||
command: 'checkpoints',
|
||||
description: 'Session state checkpointing and recovery',
|
||||
subcommands: {
|
||||
create: 'Create a new checkpoint',
|
||||
list: 'List all checkpoints',
|
||||
get: 'Get checkpoint details',
|
||||
restore: 'Restore from a checkpoint',
|
||||
delete: 'Delete a checkpoint',
|
||||
export: 'Export checkpoint as JSON',
|
||||
import: 'Import checkpoint from JSON'
|
||||
},
|
||||
flags: {
|
||||
'--storage': 'Path to checkpoint storage directory',
|
||||
'--state': 'JSON state to save with checkpoint',
|
||||
'--session': 'Session ID to associate',
|
||||
'--metadata': 'JSON metadata',
|
||||
'--json': 'Output in JSON format'
|
||||
},
|
||||
examples: [
|
||||
'praisonai-ts checkpoints create my-checkpoint',
|
||||
'praisonai-ts checkpoints create --state \'{"key": "value"}\'',
|
||||
'praisonai-ts checkpoints list',
|
||||
'praisonai-ts checkpoints restore <id>',
|
||||
'praisonai-ts checkpoints restore # restores latest',
|
||||
'praisonai-ts checkpoints export <id> > checkpoint.json',
|
||||
'praisonai-ts checkpoints import \'{"name": "imported"}\'',
|
||||
'praisonai-ts checkpoints delete <id>'
|
||||
]
|
||||
};
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(help, null, 2));
|
||||
} else {
|
||||
console.log('Checkpoints - Session state checkpointing\n');
|
||||
console.log('Subcommands:');
|
||||
for (const [cmd, desc] of Object.entries(help.subcommands)) {
|
||||
console.log(` ${cmd.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nFlags:');
|
||||
for (const [flag, desc] of Object.entries(help.flags)) {
|
||||
console.log(` ${flag.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nExamples:');
|
||||
for (const ex of help.examples) {
|
||||
console.log(` ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Context command - Manage conversation context
|
||||
*/
|
||||
|
||||
import { createContextAgent } from '../../agent/context';
|
||||
import { resolveConfig } from '../config/resolve';
|
||||
import { outputJson, formatSuccess, formatError } from '../output/json';
|
||||
import * as pretty from '../output/pretty';
|
||||
import { EXIT_CODES } from '../spec/cli-spec';
|
||||
import { ERROR_CODES } from '../output/errors';
|
||||
|
||||
export interface ContextOptions {
|
||||
model?: string;
|
||||
verbose?: boolean;
|
||||
output?: 'json' | 'text' | 'pretty';
|
||||
json?: boolean;
|
||||
maxMessages?: number;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: ContextOptions): Promise<void> {
|
||||
const action = args[0] || 'help';
|
||||
const actionArgs = args.slice(1);
|
||||
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
|
||||
const config = resolveConfig(options);
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'chat':
|
||||
await chatWithContext(actionArgs, options, config, outputFormat);
|
||||
break;
|
||||
case 'summarize':
|
||||
await summarizeContext(actionArgs, options, config, outputFormat);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
await showHelp(outputFormat);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
|
||||
} else {
|
||||
await pretty.error(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
process.exit(EXIT_CODES.RUNTIME_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async function chatWithContext(args: string[], options: ContextOptions, config: any, outputFormat: string): Promise<void> {
|
||||
const message = args.join(' ');
|
||||
if (!message) {
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a message'));
|
||||
} else {
|
||||
await pretty.error('Please provide a message');
|
||||
}
|
||||
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
if (outputFormat !== 'json') {
|
||||
await pretty.info('Processing with context...');
|
||||
}
|
||||
|
||||
const agent = createContextAgent({
|
||||
llm: config.model,
|
||||
instructions: 'You are a helpful assistant with context awareness.',
|
||||
contextWindow: options.maxMessages || 10,
|
||||
verbose: options.verbose
|
||||
});
|
||||
|
||||
const result = await agent.chat(message);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess({
|
||||
message,
|
||||
response: result.text
|
||||
}, {
|
||||
duration_ms: duration,
|
||||
model: config.model
|
||||
}));
|
||||
} else {
|
||||
await pretty.heading('Response');
|
||||
await pretty.plain(result.text);
|
||||
await pretty.newline();
|
||||
await pretty.success(`Completed in ${duration}ms`);
|
||||
}
|
||||
}
|
||||
|
||||
async function summarizeContext(args: string[], options: ContextOptions, config: any, outputFormat: string): Promise<void> {
|
||||
const context = args.join(' ');
|
||||
if (!context) {
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide context to summarize'));
|
||||
} else {
|
||||
await pretty.error('Please provide context to summarize');
|
||||
}
|
||||
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
if (outputFormat !== 'json') {
|
||||
await pretty.info('Summarizing context...');
|
||||
}
|
||||
|
||||
const agent = createContextAgent({
|
||||
llm: config.model,
|
||||
instructions: 'You are a summarization assistant. Provide concise summaries.',
|
||||
verbose: options.verbose
|
||||
});
|
||||
|
||||
// Use chat to summarize
|
||||
const result = await agent.chat(`Please summarize the following text concisely:\n\n${context}`);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess({
|
||||
originalLength: context.length,
|
||||
summary: result.text
|
||||
}, {
|
||||
duration_ms: duration,
|
||||
model: config.model
|
||||
}));
|
||||
} else {
|
||||
await pretty.heading('Summary');
|
||||
await pretty.plain(result.text);
|
||||
await pretty.newline();
|
||||
await pretty.dim(`Original: ${context.length} chars`);
|
||||
await pretty.success(`Summarized in ${duration}ms`);
|
||||
}
|
||||
}
|
||||
|
||||
async function showHelp(outputFormat: string): Promise<void> {
|
||||
const help = {
|
||||
command: 'context',
|
||||
subcommands: [
|
||||
{ name: 'chat <message>', description: 'Chat with context management' },
|
||||
{ name: 'summarize <text>', description: 'Summarize context' },
|
||||
{ name: 'help', description: 'Show this help' }
|
||||
],
|
||||
flags: [
|
||||
{ name: '--max-messages', description: 'Maximum messages to keep in context' }
|
||||
]
|
||||
};
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(help));
|
||||
} else {
|
||||
await pretty.heading('Context Command');
|
||||
await pretty.plain('Manage conversation context\n');
|
||||
await pretty.plain('Subcommands:');
|
||||
for (const cmd of help.subcommands) {
|
||||
await pretty.plain(` ${cmd.name.padEnd(25)} ${cmd.description}`);
|
||||
}
|
||||
await pretty.newline();
|
||||
await pretty.plain('Flags:');
|
||||
for (const flag of help.flags) {
|
||||
await pretty.plain(` ${flag.name.padEnd(20)} ${flag.description}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* CLI command: cost
|
||||
* Token usage and cost tracking
|
||||
*/
|
||||
|
||||
import { CostTracker, createCostTracker, MODEL_PRICING, formatCost } from '../features/cost-tracker';
|
||||
|
||||
let tracker: CostTracker | null = null;
|
||||
|
||||
function getTracker(): CostTracker {
|
||||
if (!tracker) {
|
||||
tracker = createCostTracker();
|
||||
}
|
||||
return tracker;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
|
||||
const subcommand = args[0] || 'help';
|
||||
const isJson = Boolean(options.output === 'json' || options.json);
|
||||
|
||||
switch (subcommand) {
|
||||
case 'summary':
|
||||
await handleSummary(isJson);
|
||||
break;
|
||||
case 'add':
|
||||
await handleAdd(args.slice(1), options, isJson);
|
||||
break;
|
||||
case 'reset':
|
||||
await handleReset(isJson);
|
||||
break;
|
||||
case 'pricing':
|
||||
await handlePricing(args.slice(1), isJson);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
showHelp(isJson);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSummary(isJson: boolean): Promise<void> {
|
||||
const t = getTracker();
|
||||
const stats = t.getStats();
|
||||
const breakdown = t.getBreakdownByModel();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, stats, breakdown }, null, 2));
|
||||
} else {
|
||||
console.log(t.getSummary());
|
||||
|
||||
if (Object.keys(breakdown).length > 0) {
|
||||
console.log('\nBreakdown by model:');
|
||||
for (const [model, data] of Object.entries(breakdown)) {
|
||||
console.log(` ${model}: ${data.tokens} tokens, ${formatCost(data.cost)}, ${data.requests} requests`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAdd(args: string[], options: Record<string, unknown>, isJson: boolean): Promise<void> {
|
||||
const model = args[0] || 'gpt-4o-mini';
|
||||
const inputTokens = parseInt(args[1] || '0');
|
||||
const outputTokens = parseInt(args[2] || '0');
|
||||
|
||||
if (isNaN(inputTokens) || isNaN(outputTokens)) {
|
||||
console.error('Error: Invalid token counts');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const t = getTracker();
|
||||
const request = t.addUsage(model, inputTokens, outputTokens);
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, request }, null, 2));
|
||||
} else {
|
||||
console.log(`✓ Added usage: ${model}`);
|
||||
console.log(` Input: ${inputTokens} tokens`);
|
||||
console.log(` Output: ${outputTokens} tokens`);
|
||||
console.log(` Cost: ${formatCost(request.cost)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReset(isJson: boolean): Promise<void> {
|
||||
const t = getTracker();
|
||||
t.reset();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, message: 'Cost tracker reset' }));
|
||||
} else {
|
||||
console.log('✓ Cost tracker reset');
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePricing(args: string[], isJson: boolean): Promise<void> {
|
||||
const model = args[0];
|
||||
|
||||
if (model) {
|
||||
const pricing = MODEL_PRICING[model] || MODEL_PRICING['default'];
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, model, pricing }, null, 2));
|
||||
} else {
|
||||
console.log(`Pricing for ${model}:`);
|
||||
console.log(` Input: $${pricing.inputCostPer1k}/1K tokens`);
|
||||
console.log(` Output: $${pricing.outputCostPer1k}/1K tokens`);
|
||||
}
|
||||
} else {
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, pricing: MODEL_PRICING }, null, 2));
|
||||
} else {
|
||||
console.log('Model Pricing (per 1K tokens):\n');
|
||||
for (const [name, pricing] of Object.entries(MODEL_PRICING)) {
|
||||
if (name === 'default') continue;
|
||||
console.log(` ${name.padEnd(30)} in: $${pricing.inputCostPer1k.toFixed(5)} out: $${pricing.outputCostPer1k.toFixed(5)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp(isJson: boolean): void {
|
||||
const help = {
|
||||
command: 'cost',
|
||||
description: 'Token usage and cost tracking',
|
||||
subcommands: {
|
||||
summary: 'Show cost summary',
|
||||
add: 'Add token usage',
|
||||
reset: 'Reset cost tracker',
|
||||
pricing: 'Show model pricing'
|
||||
},
|
||||
flags: {
|
||||
'--json': 'Output in JSON format'
|
||||
},
|
||||
examples: [
|
||||
'praisonai-ts cost summary',
|
||||
'praisonai-ts cost add gpt-4o 1000 500',
|
||||
'praisonai-ts cost pricing',
|
||||
'praisonai-ts cost pricing gpt-4o',
|
||||
'praisonai-ts cost reset'
|
||||
]
|
||||
};
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(help, null, 2));
|
||||
} else {
|
||||
console.log('Cost - Token usage and cost tracking\n');
|
||||
console.log('Subcommands:');
|
||||
for (const [cmd, desc] of Object.entries(help.subcommands)) {
|
||||
console.log(` ${cmd.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nExamples:');
|
||||
for (const ex of help.examples) {
|
||||
console.log(` ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* DB command - Database adapter management
|
||||
*/
|
||||
|
||||
import { outputJson, formatSuccess, formatError } from '../output/json';
|
||||
import * as pretty from '../output/pretty';
|
||||
import { EXIT_CODES } from '../spec/cli-spec';
|
||||
import { ERROR_CODES } from '../output/errors';
|
||||
|
||||
export interface DbOptions {
|
||||
verbose?: boolean;
|
||||
output?: 'json' | 'text' | 'pretty';
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: DbOptions): Promise<void> {
|
||||
const action = args[0] || 'help';
|
||||
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'info':
|
||||
await showInfo(outputFormat);
|
||||
break;
|
||||
case 'adapters':
|
||||
await listAdapters(outputFormat);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
await showHelp(outputFormat);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
|
||||
} else {
|
||||
await pretty.error(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
process.exit(EXIT_CODES.RUNTIME_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async function showInfo(outputFormat: string): Promise<void> {
|
||||
const info = {
|
||||
feature: 'Database Adapters',
|
||||
description: 'Database adapters for persistence and session storage',
|
||||
adapters: [
|
||||
{ name: 'SQLiteAdapter', description: 'SQLite database adapter' },
|
||||
{ name: 'UpstashRedisAdapter', description: 'Upstash Redis adapter' },
|
||||
{ name: 'MemoryRedisAdapter', description: 'In-memory Redis-compatible adapter' },
|
||||
{ name: 'NeonPostgresAdapter', description: 'Neon PostgreSQL adapter' },
|
||||
{ name: 'MemoryPostgresAdapter', description: 'In-memory PostgreSQL-compatible adapter' },
|
||||
{ name: 'PostgresSessionStorage', description: 'PostgreSQL session storage' }
|
||||
],
|
||||
capabilities: [
|
||||
'Store conversation history',
|
||||
'Persist agent state',
|
||||
'Session management',
|
||||
'Message storage and retrieval',
|
||||
'Trace logging'
|
||||
]
|
||||
};
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(info));
|
||||
} else {
|
||||
await pretty.heading('Database Adapters');
|
||||
await pretty.plain(info.description);
|
||||
await pretty.newline();
|
||||
await pretty.plain('Available Adapters:');
|
||||
for (const a of info.adapters) {
|
||||
await pretty.plain(` • ${a.name}: ${a.description}`);
|
||||
}
|
||||
await pretty.newline();
|
||||
await pretty.plain('Capabilities:');
|
||||
for (const cap of info.capabilities) {
|
||||
await pretty.plain(` • ${cap}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function listAdapters(outputFormat: string): Promise<void> {
|
||||
const adapters = [
|
||||
{ name: 'sqlite', description: 'SQLite database', available: true },
|
||||
{ name: 'redis', description: 'Redis (Upstash)', available: true },
|
||||
{ name: 'postgres', description: 'PostgreSQL (Neon)', available: true },
|
||||
{ name: 'memory', description: 'In-memory adapters', available: true }
|
||||
];
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess({ adapters }));
|
||||
} else {
|
||||
await pretty.heading('Database Adapters');
|
||||
for (const a of adapters) {
|
||||
const status = a.available ? '✓' : '✗';
|
||||
await pretty.plain(` ${status} ${a.name.padEnd(15)} ${a.description}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function showHelp(outputFormat: string): Promise<void> {
|
||||
const help = {
|
||||
command: 'db',
|
||||
description: 'Database adapter management',
|
||||
subcommands: [
|
||||
{ name: 'info', description: 'Show database adapter information' },
|
||||
{ name: 'adapters', description: 'List available database adapters' },
|
||||
{ name: 'help', description: 'Show this help' }
|
||||
]
|
||||
};
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(help));
|
||||
} else {
|
||||
await pretty.heading('Database Command');
|
||||
await pretty.plain(help.description);
|
||||
await pretty.newline();
|
||||
await pretty.plain('Subcommands:');
|
||||
for (const cmd of help.subcommands) {
|
||||
await pretty.plain(` ${cmd.name.padEnd(20)} ${cmd.description}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* Eval command - Evaluate agent performance
|
||||
*/
|
||||
|
||||
import { Agent } from '../../agent';
|
||||
import { resolveConfig } from '../config/resolve';
|
||||
import { printSuccess, printError, outputJson, formatSuccess, formatError } from '../output/json';
|
||||
import * as pretty from '../output/pretty';
|
||||
import { EXIT_CODES } from '../spec/cli-spec';
|
||||
import { ERROR_CODES, normalizeError } from '../output/errors';
|
||||
|
||||
export interface EvalOptions {
|
||||
// Accuracy subcommand
|
||||
input?: string;
|
||||
expected?: string;
|
||||
iterations?: number;
|
||||
// Performance subcommand
|
||||
warmup?: number;
|
||||
// Reliability subcommand
|
||||
'expected-tools'?: string;
|
||||
// Common
|
||||
model?: string;
|
||||
verbose?: boolean;
|
||||
profile?: string;
|
||||
config?: string;
|
||||
output?: 'json' | 'text' | 'pretty';
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
interface AccuracyResult {
|
||||
iteration: number;
|
||||
output: string;
|
||||
score: number;
|
||||
passed: boolean;
|
||||
}
|
||||
|
||||
interface PerformanceResult {
|
||||
iteration: number;
|
||||
duration_ms: number;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: EvalOptions): Promise<void> {
|
||||
const subcommand = args[0];
|
||||
|
||||
if (!subcommand || !['accuracy', 'performance', 'reliability'].includes(subcommand)) {
|
||||
if (options.json || options.output === 'json') {
|
||||
printError(ERROR_CODES.INVALID_ARGS, 'Please specify a subcommand: accuracy, performance, or reliability');
|
||||
} else {
|
||||
await pretty.error('Please specify a subcommand: accuracy, performance, or reliability');
|
||||
}
|
||||
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
|
||||
}
|
||||
|
||||
const config = resolveConfig({
|
||||
configPath: options.config,
|
||||
profile: options.profile,
|
||||
model: options.model,
|
||||
verbose: options.verbose
|
||||
});
|
||||
|
||||
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
|
||||
|
||||
try {
|
||||
switch (subcommand) {
|
||||
case 'accuracy':
|
||||
await runAccuracyEval(options, config, outputFormat);
|
||||
break;
|
||||
case 'performance':
|
||||
await runPerformanceEval(options, config, outputFormat);
|
||||
break;
|
||||
case 'reliability':
|
||||
await runReliabilityEval(options, config, outputFormat);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
const cliError = normalizeError(error);
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(cliError.code, cliError.message, cliError.details));
|
||||
} else {
|
||||
await pretty.error(cliError.message);
|
||||
if (config.verbose && error instanceof Error && error.stack) {
|
||||
await pretty.dim(error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(cliError.exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
async function runAccuracyEval(
|
||||
options: EvalOptions,
|
||||
config: { model: string; verbose: boolean },
|
||||
outputFormat: string
|
||||
): Promise<void> {
|
||||
if (!options.input) {
|
||||
throw new Error('--input is required for accuracy evaluation');
|
||||
}
|
||||
if (!options.expected) {
|
||||
throw new Error('--expected is required for accuracy evaluation');
|
||||
}
|
||||
|
||||
const iterations = options.iterations || 1;
|
||||
const results: AccuracyResult[] = [];
|
||||
const startTime = Date.now();
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'Eval Agent',
|
||||
instructions: 'You are a helpful AI assistant.',
|
||||
llm: config.model,
|
||||
verbose: config.verbose
|
||||
});
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const output = await agent.start(options.input);
|
||||
|
||||
// Simple similarity check (in production, use LLM-as-judge)
|
||||
const similarity = calculateSimilarity(output, options.expected);
|
||||
const score = Math.round(similarity * 10);
|
||||
|
||||
results.push({
|
||||
iteration: i + 1,
|
||||
output,
|
||||
score,
|
||||
passed: score >= 7
|
||||
});
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;
|
||||
const passRate = results.filter(r => r.passed).length / results.length;
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(
|
||||
{
|
||||
type: 'accuracy',
|
||||
results,
|
||||
summary: {
|
||||
avg_score: avgScore,
|
||||
pass_rate: passRate,
|
||||
iterations
|
||||
}
|
||||
},
|
||||
{ duration_ms: duration, model: config.model }
|
||||
));
|
||||
} else {
|
||||
await pretty.heading('Accuracy Evaluation Results');
|
||||
await pretty.keyValue({
|
||||
'Iterations': iterations,
|
||||
'Average Score': avgScore.toFixed(2),
|
||||
'Pass Rate': `${(passRate * 100).toFixed(1)}%`,
|
||||
'Duration': `${duration}ms`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function runPerformanceEval(
|
||||
options: EvalOptions,
|
||||
config: { model: string; verbose: boolean },
|
||||
outputFormat: string
|
||||
): Promise<void> {
|
||||
const iterations = options.iterations || 10;
|
||||
const warmup = options.warmup || 2;
|
||||
const results: PerformanceResult[] = [];
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'Perf Agent',
|
||||
instructions: 'You are a helpful AI assistant.',
|
||||
llm: config.model,
|
||||
verbose: false // Disable verbose for performance testing
|
||||
});
|
||||
|
||||
const testPrompt = 'Say "hello" and nothing else.';
|
||||
|
||||
// Warmup runs
|
||||
for (let i = 0; i < warmup; i++) {
|
||||
await agent.start(testPrompt);
|
||||
}
|
||||
|
||||
// Actual runs
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const start = Date.now();
|
||||
await agent.start(testPrompt);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
results.push({
|
||||
iteration: i + 1,
|
||||
duration_ms: duration
|
||||
});
|
||||
}
|
||||
|
||||
const durations = results.map(r => r.duration_ms);
|
||||
const avg = durations.reduce((a, b) => a + b, 0) / durations.length;
|
||||
const min = Math.min(...durations);
|
||||
const max = Math.max(...durations);
|
||||
const sorted = [...durations].sort((a, b) => a - b);
|
||||
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(
|
||||
{
|
||||
type: 'performance',
|
||||
results,
|
||||
summary: {
|
||||
avg_ms: avg,
|
||||
min_ms: min,
|
||||
max_ms: max,
|
||||
p95_ms: p95,
|
||||
iterations,
|
||||
warmup_runs: warmup
|
||||
}
|
||||
},
|
||||
{ model: config.model }
|
||||
));
|
||||
} else {
|
||||
await pretty.heading('Performance Evaluation Results');
|
||||
await pretty.keyValue({
|
||||
'Iterations': iterations,
|
||||
'Warmup Runs': warmup,
|
||||
'Avg Duration': `${avg.toFixed(2)}ms`,
|
||||
'Min Duration': `${min}ms`,
|
||||
'Max Duration': `${max}ms`,
|
||||
'P95 Duration': `${p95}ms`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function runReliabilityEval(
|
||||
options: EvalOptions,
|
||||
config: { model: string; verbose: boolean },
|
||||
outputFormat: string
|
||||
): Promise<void> {
|
||||
const expectedTools = options['expected-tools']?.split(',').map(t => t.trim()) || [];
|
||||
|
||||
if (expectedTools.length === 0) {
|
||||
throw new Error('--expected-tools is required for reliability evaluation');
|
||||
}
|
||||
|
||||
// For now, simulate reliability check
|
||||
// In production, this would track actual tool calls
|
||||
const results = {
|
||||
expected: expectedTools,
|
||||
called: expectedTools, // Simulated
|
||||
missing: [] as string[],
|
||||
unexpected: [] as string[]
|
||||
};
|
||||
|
||||
const passed = results.missing.length === 0;
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess({
|
||||
type: 'reliability',
|
||||
status: passed ? 'PASSED' : 'FAILED',
|
||||
results
|
||||
}));
|
||||
} else {
|
||||
await pretty.heading('Reliability Evaluation Results');
|
||||
if (passed) {
|
||||
await pretty.success('All expected tools were called');
|
||||
} else {
|
||||
await pretty.error(`Missing tool calls: ${results.missing.join(', ')}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple string similarity calculation
|
||||
*/
|
||||
function calculateSimilarity(str1: string, str2: string): number {
|
||||
const s1 = str1.toLowerCase().trim();
|
||||
const s2 = str2.toLowerCase().trim();
|
||||
|
||||
if (s1 === s2) return 1;
|
||||
if (s1.includes(s2) || s2.includes(s1)) return 0.9;
|
||||
|
||||
// Simple word overlap
|
||||
const words1 = new Set(s1.split(/\s+/));
|
||||
const words2 = new Set(s2.split(/\s+/));
|
||||
const intersection = [...words1].filter(w => words2.has(w));
|
||||
const union = new Set([...words1, ...words2]);
|
||||
|
||||
return intersection.length / union.size;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* CLI command: external-agents
|
||||
* Integration with external AI CLI tools
|
||||
*/
|
||||
|
||||
import {
|
||||
getExternalAgentRegistry,
|
||||
createExternalAgent,
|
||||
ClaudeCodeAgent,
|
||||
GeminiCliAgent,
|
||||
CodexCliAgent,
|
||||
AiderAgent
|
||||
} from '../features/external-agents';
|
||||
|
||||
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
|
||||
const subcommand = args[0] || 'help';
|
||||
const isJson = Boolean(options.output === 'json' || options.json);
|
||||
|
||||
switch (subcommand) {
|
||||
case 'list':
|
||||
await handleList(isJson);
|
||||
break;
|
||||
case 'check':
|
||||
await handleCheck(args.slice(1), isJson);
|
||||
break;
|
||||
case 'run':
|
||||
await handleRun(args.slice(1), options, isJson);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
showHelp(isJson);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleList(isJson: boolean): Promise<void> {
|
||||
const registry = getExternalAgentRegistry();
|
||||
const all = registry.list();
|
||||
const available = await registry.getAvailable();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({
|
||||
success: true,
|
||||
agents: all,
|
||||
available
|
||||
}, null, 2));
|
||||
} else {
|
||||
console.log('External Agents:\n');
|
||||
for (const name of all) {
|
||||
const status = available.includes(name) ? '✓' : '✗';
|
||||
console.log(` ${status} ${name}`);
|
||||
}
|
||||
console.log(`\n${available.length}/${all.length} available`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCheck(args: string[], isJson: boolean): Promise<void> {
|
||||
const name = args[0];
|
||||
|
||||
if (!name) {
|
||||
console.error('Error: Agent name is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const agent = createExternalAgent(name);
|
||||
|
||||
if (!agent) {
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: false, error: `Unknown agent: ${name}` }));
|
||||
} else {
|
||||
console.error(`Unknown agent: ${name}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const available = await agent.isAvailable();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, agent: name, available }));
|
||||
} else {
|
||||
if (available) {
|
||||
console.log(`✓ ${name} is available`);
|
||||
} else {
|
||||
console.log(`✗ ${name} is not available (CLI not found)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRun(args: string[], options: Record<string, unknown>, isJson: boolean): Promise<void> {
|
||||
const name = args[0];
|
||||
const prompt = args.slice(1).join(' ');
|
||||
|
||||
if (!name) {
|
||||
console.error('Error: Agent name is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!prompt) {
|
||||
console.error('Error: Prompt is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cwd = options.cwd as string | undefined;
|
||||
const agent = createExternalAgent(name, cwd);
|
||||
|
||||
if (!agent) {
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: false, error: `Unknown agent: ${name}` }));
|
||||
} else {
|
||||
console.error(`Unknown agent: ${name}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!await agent.isAvailable()) {
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: false, error: `${name} CLI not available` }));
|
||||
} else {
|
||||
console.error(`${name} CLI not available`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const result = await agent.execute(prompt);
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
if (result.success) {
|
||||
console.log(result.output);
|
||||
} else {
|
||||
console.error(`Error: ${result.error}`);
|
||||
process.exit(result.exitCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp(isJson: boolean): void {
|
||||
const help = {
|
||||
command: 'external-agents',
|
||||
description: 'Integration with external AI CLI tools',
|
||||
subcommands: {
|
||||
list: 'List all external agents and availability',
|
||||
check: 'Check if an agent is available',
|
||||
run: 'Run a prompt with an external agent'
|
||||
},
|
||||
agents: {
|
||||
claude: 'Claude Code CLI',
|
||||
gemini: 'Gemini CLI',
|
||||
codex: 'OpenAI Codex CLI',
|
||||
aider: 'Aider CLI'
|
||||
},
|
||||
flags: {
|
||||
'--cwd': 'Working directory for the agent',
|
||||
'--json': 'Output in JSON format'
|
||||
},
|
||||
examples: [
|
||||
'praisonai-ts external-agents list',
|
||||
'praisonai-ts external-agents check claude',
|
||||
'praisonai-ts external-agents run claude "Explain this code"',
|
||||
'praisonai-ts external-agents run gemini "Refactor this function" --cwd ./src'
|
||||
]
|
||||
};
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(help, null, 2));
|
||||
} else {
|
||||
console.log('External Agents - AI CLI tool integration\n');
|
||||
console.log('Subcommands:');
|
||||
for (const [cmd, desc] of Object.entries(help.subcommands)) {
|
||||
console.log(` ${cmd.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nSupported Agents:');
|
||||
for (const [agent, desc] of Object.entries(help.agents)) {
|
||||
console.log(` ${agent.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nFlags:');
|
||||
for (const [flag, desc] of Object.entries(help.flags)) {
|
||||
console.log(` ${flag.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nExamples:');
|
||||
for (const ex of help.examples) {
|
||||
console.log(` ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* CLI command: fast-context
|
||||
* Fast context retrieval and summarization
|
||||
*/
|
||||
|
||||
import { FastContext, createFastContext, getQuickContext } from '../features/fast-context';
|
||||
|
||||
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
|
||||
const subcommand = args[0] || 'help';
|
||||
const isJson = Boolean(options.output === 'json' || options.json);
|
||||
|
||||
switch (subcommand) {
|
||||
case 'query':
|
||||
await handleQuery(args.slice(1), options, isJson);
|
||||
break;
|
||||
case 'stats':
|
||||
await handleStats(isJson);
|
||||
break;
|
||||
case 'clear':
|
||||
await handleClear(isJson);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
showHelp(isJson);
|
||||
}
|
||||
}
|
||||
|
||||
let globalContext: FastContext | null = null;
|
||||
|
||||
function getContext(maxTokens?: number): FastContext {
|
||||
if (!globalContext) {
|
||||
globalContext = createFastContext({ maxTokens, cacheEnabled: true });
|
||||
}
|
||||
return globalContext;
|
||||
}
|
||||
|
||||
async function handleQuery(args: string[], options: Record<string, unknown>, isJson: boolean): Promise<void> {
|
||||
const query = args.join(' ');
|
||||
if (!query) {
|
||||
console.error('Error: Query is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const maxTokens = options['max-tokens'] as number | undefined;
|
||||
const sources = options.sources as string | undefined;
|
||||
|
||||
const fc = getContext(maxTokens);
|
||||
|
||||
// Add sources if provided
|
||||
if (sources) {
|
||||
const sourceList = sources.split(',').map(s => s.trim());
|
||||
fc.registerSource('cli-sources', 'custom', sourceList);
|
||||
}
|
||||
|
||||
const result = await fc.getContext(query);
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, ...result }, null, 2));
|
||||
} else {
|
||||
console.log('Fast Context Result:');
|
||||
console.log(` Query: ${query}`);
|
||||
console.log(` Token count: ${result.tokenCount}`);
|
||||
console.log(` Sources used: ${result.sources.length}`);
|
||||
console.log(` Cached: ${result.cached}`);
|
||||
console.log(` Latency: ${result.latencyMs}ms`);
|
||||
console.log('\nContext:');
|
||||
console.log(result.context || '(empty)');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStats(isJson: boolean): Promise<void> {
|
||||
const fc = getContext();
|
||||
const stats = fc.getCacheStats();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, stats }, null, 2));
|
||||
} else {
|
||||
console.log('Fast Context Cache Stats:');
|
||||
console.log(` Cache size: ${stats.size}`);
|
||||
console.log(` Total hits: ${stats.totalHits}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClear(isJson: boolean): Promise<void> {
|
||||
const fc = getContext();
|
||||
fc.clearCache();
|
||||
fc.clearSources();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, message: 'Cache and sources cleared' }));
|
||||
} else {
|
||||
console.log('✓ Cache and sources cleared');
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp(isJson: boolean): void {
|
||||
const help = {
|
||||
command: 'fast-context',
|
||||
description: 'Fast context retrieval and summarization',
|
||||
subcommands: {
|
||||
query: 'Query for relevant context',
|
||||
stats: 'Show cache statistics',
|
||||
clear: 'Clear cache and sources'
|
||||
},
|
||||
flags: {
|
||||
'--max-tokens': 'Maximum tokens in context (default: 4000)',
|
||||
'--sources': 'Comma-separated source texts',
|
||||
'--json': 'Output in JSON format'
|
||||
},
|
||||
examples: [
|
||||
'praisonai-ts fast-context query "What is the main topic?"',
|
||||
'praisonai-ts fast-context query "summarize" --sources "text1,text2,text3"',
|
||||
'praisonai-ts fast-context query "find relevant" --max-tokens 2000',
|
||||
'praisonai-ts fast-context stats',
|
||||
'praisonai-ts fast-context clear'
|
||||
]
|
||||
};
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(help, null, 2));
|
||||
} else {
|
||||
console.log('Fast Context - Fast context retrieval\n');
|
||||
console.log('Subcommands:');
|
||||
for (const [cmd, desc] of Object.entries(help.subcommands)) {
|
||||
console.log(` ${cmd.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nFlags:');
|
||||
for (const [flag, desc] of Object.entries(help.flags)) {
|
||||
console.log(` ${flag.padEnd(14)} ${desc}`);
|
||||
}
|
||||
console.log('\nExamples:');
|
||||
for (const ex of help.examples) {
|
||||
console.log(` ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* CLI command: flow
|
||||
* Workflow flow visualization
|
||||
*/
|
||||
|
||||
import { FlowDisplay, createFlowDisplay, renderWorkflow } from '../features/flow-display';
|
||||
|
||||
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
|
||||
const subcommand = args[0] || 'help';
|
||||
const isJson = Boolean(options.output === 'json' || options.json);
|
||||
|
||||
switch (subcommand) {
|
||||
case 'show':
|
||||
await handleShow(args.slice(1), options, isJson);
|
||||
break;
|
||||
case 'dot':
|
||||
await handleDot(args.slice(1), options, isJson);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
showHelp(isJson);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleShow(args: string[], options: Record<string, unknown>, isJson: boolean): Promise<void> {
|
||||
const stepsStr = args[0] || options.steps as string;
|
||||
|
||||
let steps: Array<{ name: string; type?: string }> = [];
|
||||
|
||||
if (stepsStr) {
|
||||
try {
|
||||
steps = JSON.parse(stepsStr);
|
||||
} catch {
|
||||
// Parse simple format: "step1,step2,step3"
|
||||
steps = stepsStr.split(',').map(s => {
|
||||
const [name, type] = s.split(':');
|
||||
return { name: name.trim(), type: type?.trim() };
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Default example
|
||||
steps = [
|
||||
{ name: 'Input', type: 'start' },
|
||||
{ name: 'Process', type: 'agent' },
|
||||
{ name: 'Validate', type: 'condition' },
|
||||
{ name: 'Output', type: 'end' }
|
||||
];
|
||||
}
|
||||
|
||||
const display = createFlowDisplay({
|
||||
showStatus: Boolean(options.status),
|
||||
compact: Boolean(options.compact)
|
||||
});
|
||||
|
||||
display.fromWorkflowSteps(steps);
|
||||
|
||||
if (isJson) {
|
||||
const graph = display.getGraph();
|
||||
console.log(JSON.stringify({
|
||||
success: true,
|
||||
nodes: Array.from(graph.nodes.values()),
|
||||
edges: graph.edges
|
||||
}, null, 2));
|
||||
} else {
|
||||
if (options.boxes) {
|
||||
console.log(display.renderBoxes());
|
||||
} else {
|
||||
console.log(display.render());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDot(args: string[], options: Record<string, unknown>, isJson: boolean): Promise<void> {
|
||||
const stepsStr = args[0] || options.steps as string;
|
||||
|
||||
let steps: Array<{ name: string; type?: string }> = [];
|
||||
|
||||
if (stepsStr) {
|
||||
try {
|
||||
steps = JSON.parse(stepsStr);
|
||||
} catch {
|
||||
steps = stepsStr.split(',').map(s => {
|
||||
const [name, type] = s.split(':');
|
||||
return { name: name.trim(), type: type?.trim() };
|
||||
});
|
||||
}
|
||||
} else {
|
||||
steps = [
|
||||
{ name: 'Start', type: 'start' },
|
||||
{ name: 'Agent', type: 'agent' },
|
||||
{ name: 'End', type: 'end' }
|
||||
];
|
||||
}
|
||||
|
||||
const display = createFlowDisplay();
|
||||
display.fromWorkflowSteps(steps);
|
||||
const dot = display.toDot();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, dot }));
|
||||
} else {
|
||||
console.log(dot);
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp(isJson: boolean): void {
|
||||
const help = {
|
||||
command: 'flow',
|
||||
description: 'Workflow flow visualization',
|
||||
subcommands: {
|
||||
show: 'Display workflow as text tree or boxes',
|
||||
dot: 'Export workflow as DOT format (for Graphviz)'
|
||||
},
|
||||
flags: {
|
||||
'--steps': 'Steps as JSON or "name:type,name:type"',
|
||||
'--status': 'Show status indicators',
|
||||
'--compact': 'Compact display mode',
|
||||
'--boxes': 'Display as ASCII boxes',
|
||||
'--json': 'Output in JSON format'
|
||||
},
|
||||
examples: [
|
||||
'praisonai-ts flow show "Input,Process,Output"',
|
||||
'praisonai-ts flow show "Start:start,Agent:agent,End:end"',
|
||||
'praisonai-ts flow show --boxes',
|
||||
'praisonai-ts flow dot "Step1,Step2,Step3" > workflow.dot'
|
||||
]
|
||||
};
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(help, null, 2));
|
||||
} else {
|
||||
console.log('Flow - Workflow visualization\n');
|
||||
console.log('Subcommands:');
|
||||
for (const [cmd, desc] of Object.entries(help.subcommands)) {
|
||||
console.log(` ${cmd.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nFlags:');
|
||||
for (const [flag, desc] of Object.entries(help.flags)) {
|
||||
console.log(` ${flag.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nExamples:');
|
||||
for (const ex of help.examples) {
|
||||
console.log(` ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* CLI command: git
|
||||
* Git integration for repository operations
|
||||
*/
|
||||
|
||||
import { GitManager, createGitManager, DiffViewer, createDiffViewer } from '../features/git-integration';
|
||||
|
||||
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
|
||||
const subcommand = args[0] || 'help';
|
||||
const isJson = Boolean(options.output === 'json' || options.json);
|
||||
const cwd = (options.cwd as string) || process.cwd();
|
||||
|
||||
switch (subcommand) {
|
||||
case 'status':
|
||||
await handleStatus(cwd, isJson);
|
||||
break;
|
||||
case 'diff':
|
||||
await handleDiff(options, cwd, isJson);
|
||||
break;
|
||||
case 'log':
|
||||
await handleLog(options, cwd, isJson);
|
||||
break;
|
||||
case 'branches':
|
||||
await handleBranches(cwd, isJson);
|
||||
break;
|
||||
case 'stash':
|
||||
await handleStash(args.slice(1), cwd, isJson);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
showHelp(isJson);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStatus(cwd: string, isJson: boolean): Promise<void> {
|
||||
const git = createGitManager({ cwd, safe: true });
|
||||
|
||||
if (!await git.isRepo()) {
|
||||
console.error('Error: Not a git repository');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const status = await git.getStatus();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, ...status }, null, 2));
|
||||
} else {
|
||||
console.log(await git.getStatusString());
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDiff(options: Record<string, unknown>, cwd: string, isJson: boolean): Promise<void> {
|
||||
const git = createGitManager({ cwd, safe: true });
|
||||
const staged = Boolean(options.staged);
|
||||
|
||||
if (!await git.isRepo()) {
|
||||
console.error('Error: Not a git repository');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const diff = await git.getDiff(staged);
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, ...diff }, null, 2));
|
||||
} else {
|
||||
if (diff.files.length === 0) {
|
||||
console.log(staged ? 'No staged changes' : 'No changes');
|
||||
} else {
|
||||
console.log(`Changes (${diff.files.length} files):\n`);
|
||||
for (const file of diff.files) {
|
||||
console.log(` ${file.path} (+${file.additions}/-${file.deletions})`);
|
||||
}
|
||||
if (options.full) {
|
||||
console.log('\n' + diff.summary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLog(options: Record<string, unknown>, cwd: string, isJson: boolean): Promise<void> {
|
||||
const git = createGitManager({ cwd, safe: true });
|
||||
const limit = (options.limit as number) || 10;
|
||||
|
||||
if (!await git.isRepo()) {
|
||||
console.error('Error: Not a git repository');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const commits = await git.getLog(limit);
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, commits }, null, 2));
|
||||
} else {
|
||||
console.log('Recent commits:\n');
|
||||
for (const commit of commits) {
|
||||
console.log(` ${commit.shortHash} ${commit.message}`);
|
||||
console.log(` by ${commit.author} on ${commit.date.toLocaleDateString()}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBranches(cwd: string, isJson: boolean): Promise<void> {
|
||||
const git = createGitManager({ cwd, safe: true });
|
||||
|
||||
if (!await git.isRepo()) {
|
||||
console.error('Error: Not a git repository');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const branches = await git.getBranches();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, branches }, null, 2));
|
||||
} else {
|
||||
console.log('Branches:\n');
|
||||
for (const branch of branches) {
|
||||
const marker = branch.current ? '* ' : ' ';
|
||||
console.log(`${marker}${branch.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStash(args: string[], cwd: string, isJson: boolean): Promise<void> {
|
||||
const git = createGitManager({ cwd, safe: true });
|
||||
|
||||
if (!await git.isRepo()) {
|
||||
console.error('Error: Not a git repository');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const stashes = await git.getStashList();
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify({ success: true, stashes }, null, 2));
|
||||
} else {
|
||||
if (stashes.length === 0) {
|
||||
console.log('No stashes');
|
||||
} else {
|
||||
console.log('Stashes:\n');
|
||||
for (const stash of stashes) {
|
||||
console.log(` ${stash}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp(isJson: boolean): void {
|
||||
const help = {
|
||||
command: 'git',
|
||||
description: 'Git integration for repository operations (read-only by default)',
|
||||
subcommands: {
|
||||
status: 'Show repository status',
|
||||
diff: 'Show changes',
|
||||
log: 'Show recent commits',
|
||||
branches: 'List branches',
|
||||
stash: 'List stashes'
|
||||
},
|
||||
flags: {
|
||||
'--cwd': 'Working directory (default: current)',
|
||||
'--staged': 'Show staged changes only (for diff)',
|
||||
'--full': 'Show full diff output',
|
||||
'--limit': 'Number of commits to show (default: 10)',
|
||||
'--json': 'Output in JSON format'
|
||||
},
|
||||
examples: [
|
||||
'praisonai-ts git status',
|
||||
'praisonai-ts git diff',
|
||||
'praisonai-ts git diff --staged',
|
||||
'praisonai-ts git log --limit 5',
|
||||
'praisonai-ts git branches',
|
||||
'praisonai-ts git stash'
|
||||
]
|
||||
};
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(help, null, 2));
|
||||
} else {
|
||||
console.log('Git - Repository operations (read-only)\n');
|
||||
console.log('Subcommands:');
|
||||
for (const [cmd, desc] of Object.entries(help.subcommands)) {
|
||||
console.log(` ${cmd.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nFlags:');
|
||||
for (const [flag, desc] of Object.entries(help.flags)) {
|
||||
console.log(` ${flag.padEnd(12)} ${desc}`);
|
||||
}
|
||||
console.log('\nExamples:');
|
||||
for (const ex of help.examples) {
|
||||
console.log(` ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Graph RAG command - Graph-based retrieval augmented generation
|
||||
*/
|
||||
|
||||
import { outputJson, formatSuccess, formatError } from '../output/json';
|
||||
import * as pretty from '../output/pretty';
|
||||
import { EXIT_CODES } from '../spec/cli-spec';
|
||||
import { ERROR_CODES } from '../output/errors';
|
||||
|
||||
export interface GraphRagOptions {
|
||||
verbose?: boolean;
|
||||
output?: 'json' | 'text' | 'pretty';
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export async function execute(args: string[], options: GraphRagOptions): Promise<void> {
|
||||
const action = args[0] || 'help';
|
||||
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'info':
|
||||
await showInfo(outputFormat);
|
||||
break;
|
||||
case 'help':
|
||||
default:
|
||||
await showHelp(outputFormat);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
|
||||
} else {
|
||||
await pretty.error(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
process.exit(EXIT_CODES.RUNTIME_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async function showInfo(outputFormat: string): Promise<void> {
|
||||
const info = {
|
||||
feature: 'Graph RAG',
|
||||
description: 'Graph-based retrieval augmented generation for complex knowledge queries',
|
||||
capabilities: [
|
||||
'Build knowledge graphs from documents',
|
||||
'Query relationships between entities',
|
||||
'Combine graph traversal with vector search',
|
||||
'Extract entities and relationships',
|
||||
'Support for complex multi-hop queries'
|
||||
],
|
||||
sdkUsage: `
|
||||
import { createGraphRAG, GraphStore } from 'praisonai';
|
||||
|
||||
// Create a graph RAG instance
|
||||
const graphRag = createGraphRAG({
|
||||
llm: 'openai/gpt-4o-mini',
|
||||
graphStore: new GraphStore()
|
||||
});
|
||||
|
||||
// Add documents
|
||||
await graphRag.addDocument('Document content...');
|
||||
|
||||
// Query with graph context
|
||||
const result = await graphRag.query('What is the relationship between X and Y?');
|
||||
`
|
||||
};
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(info));
|
||||
} else {
|
||||
await pretty.heading('Graph RAG');
|
||||
await pretty.plain(info.description);
|
||||
await pretty.newline();
|
||||
await pretty.plain('Capabilities:');
|
||||
for (const cap of info.capabilities) {
|
||||
await pretty.plain(` • ${cap}`);
|
||||
}
|
||||
await pretty.newline();
|
||||
await pretty.dim('Use the SDK for full Graph RAG functionality');
|
||||
}
|
||||
}
|
||||
|
||||
async function showHelp(outputFormat: string): Promise<void> {
|
||||
const help = {
|
||||
command: 'graph-rag',
|
||||
description: 'Graph-based retrieval augmented generation',
|
||||
subcommands: [
|
||||
{ name: 'info', description: 'Show Graph RAG feature information' },
|
||||
{ name: 'help', description: 'Show this help' }
|
||||
]
|
||||
};
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
outputJson(formatSuccess(help));
|
||||
} else {
|
||||
await pretty.heading('Graph RAG Command');
|
||||
await pretty.plain(help.description);
|
||||
await pretty.newline();
|
||||
await pretty.plain('Subcommands:');
|
||||
for (const cmd of help.subcommands) {
|
||||
await pretty.plain(` ${cmd.name.padEnd(20)} ${cmd.description}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 323 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user