Compare commits

...
26 Commits
Author SHA1 Message Date
Mervin Praison 9a5a432d96 Release v2.7.2 2025-12-29 11:40:55 +00:00
Mervin Praison 8cb5bbb605 Release v2.7.2 - update doctor examples with improved check listing and CI formatting
- Add doctor examples to main README with documentation link
- Sort checks by category in list_available_checks()
- Update registry method call from list_checks() to get_all_checks()
- Remove unused results variable in run_environment_checks()
- Fix f-string formatting in CI integration print statement
2025-12-29 02:19:16 +00:00
Mervin Praison 952514d73c Release v2.7.1 2025-12-29 02:15:51 +00:00
Mervin Praison 6e1dc34864 Release v2.7.0 2025-12-29 01:33:40 +00:00
Mervin Praison 820d5c2995 Release v2.6.8 2025-12-29 00:42:47 +00:00
Mervin Praison 0b1b3d3b3e Release v2.6.7 2025-12-29 00:22:52 +00:00
Mervin Praison 59107addc3 Release v2.6.6 2025-12-28 23:51:03 +00:00
Mervin Praison b460ded5e0 Release v2.6.5 2025-12-28 23:02:27 +00:00
Mervin Praison a8967ed200 Release v1.3.1 - add Router class with simplified keyword/pattern-based routing and agent session management
- Bump praisonai-ts version from 1.3.0 to 1.3.1
- Add Router class with simplified keyword/pattern-based routing API (5-line usage)
- Export Router, RouterAgent, createRouter, routeConditions from agent module
- Add SimpleRouterConfig and SimpleRouteConfig types for simplified configuration
- Support both Agent and EnhancedAgent types in router (AnyAgent union type)
- Add Router.chat() method for direct routing
2025-12-28 22:53:04 +00:00
Mervin Praison 9189f42470 Release v2.6.4 2025-12-28 21:47:32 +00:00
Mervin Praison c86f660f66 Release v2.6.3 2025-12-28 07:31:42 +00:00
Mervin Praison a2d181dd74 Release v2.6.2 2025-12-27 07:58:18 +00:00
Mervin Praison 7411fdbb52 Release v2.6.1 2025-12-26 19:41:45 +00:00
Mervin Praison e7a8ce8eb7 Release v2.6.0 2025-12-26 19:31:15 +00:00
Mervin Praison 61fb094306 Release v1.2.3 - add comprehensive CLI feature exports and command definitions
- Bump praisonai-ts version from 1.2.2 to 1.2.3
- Add 18 new command definitions to CLI spec: query-rewrite, prompt-expand, router, context, handoff, vector, observability, voice, reranker, graph-rag, cache, db, scheduler, jobs, checkpoints, fast-context, autonomy, sandbox, repo-map, git, n8n, external-agents, flow, cost, interactive
- Export FileMemory and AutoMemory classes with factory functions and types from memory module
- Export
2025-12-26 18:41:37 +00:00
Mervin Praison 018e8c78ab refactor(ts): update CLI to v1.0.0 spec and change default model to gpt-4o-mini
- Change default agent model from gpt-5-nano to gpt-4o-mini in simple.ts
- Refactor CLI to implement CLI Spec v1.0.0 with lazy command loading
- Add parseArgs() function for spec-compliant argument parsing
- Add executeCommand() for dynamic command module loading
- Add comprehensive command definitions: memory, session, knowledge, skills, mcp, auto, image, research, guardrail, telemetry, planning
- Export CLI_SPEC_VERSION,
2025-12-26 14:07:05 +00:00
Mervin Praison f0f0f653fe Release v2.5.7 2025-12-26 13:31:45 +00:00
Mervin Praison 98a4ff8450 Release v2.5.6 2025-12-26 13:11:09 +00:00
Mervin Praison d6333aaf33 Release v1.2.1 - refactor TypeScript tools to use generic BaseTool
- Bump praisonai-ts version from 1.2.0 to 1.2.1
- Refactor BaseTool to use generics for type-safe input/output parameters
- Update ArxivSearchTool and ArxivDownloadTool to extend generic BaseTool with typed params
- Update MCPTool to extend generic BaseTool
- Change tool method from execute() to run() for consistency
- Export ToolResult, ToolValidationError, validateTool, createTool from base module
- Remove Tool interface export
2025-12-26 11:47:17 +00:00
Mervin Praison 8005094267 update 2025-12-26 08:39:22 +00:00
Mervin Praison 08b21dabe3 Release v2.5.5 2025-12-26 08:16:54 +00:00
Mervin Praison 8c70d840df docs: add knowledge export/import CLI commands to README
- Add knowledge export/import row to features table with CLI link
- Document `praisonai knowledge export <file.json>` command
- Document `praisonai knowledge import <file.json>` command
2025-12-26 06:53:20 +00:00
Mervin Praison d8e944c1d3 Release v2.5.4 2025-12-26 06:49:33 +00:00
Mervin Praison 79b2b88f72 Release v2.5.3 2025-12-26 05:54:09 +00:00
Mervin Praison f5dfbd5670 fix: resolve Core Tests failures in knowledge/reranker integration
- Fix #1: MemoryConfig Pydantic validation - add _prepare_mem0_config() to strip
  PraisonAI-specific reranker fields (enabled, default_rerank) before passing to
  mem0, which only accepts provider and config fields

- Fix #2: LLM Reranker 'Chat' object not callable - check if client.chat is
  callable, not just if it exists. OpenAI client has chat as namespace object.
  Also handle max_completion_tokens vs max_tokens for different model versions.

- Fix #3: Chroma vector store metadata validation - ensure non-empty metadata
  dicts by adding placeholder key when metadata is empty or None

- Fix test file issues: add user_id parameter (required by mem0 API), handle
  dict return type from knowledge.search, use gpt-4o-mini for reranker test
  (gpt-5-nano returns empty responses with max_completion_tokens=10)
2025-12-26 05:52:46 +00:00
Mervin Praison aa6aa5933d fix: update test files to use gpt-5-nano for cheaper CI runs
- Update test_workflow_patterns.py to use gpt-5-nano instead of gpt-4o/gpt-4o-mini
- Also set OPENAI_MODEL_NAME=gpt-5-nano as CI secret
2025-12-26 05:26:40 +00:00
418 changed files with 74455 additions and 1412 deletions

No files matched your search

+523 -170
View File
@@ -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,147 +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) |
| ↳ 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) |
| **🔬 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>
---
@@ -543,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:
@@ -592,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
@@ -607,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:
@@ -877,7 +1079,7 @@ callbacks:
on_workflow_complete: log_complete
```
### 9. Hooks
### 10. Hooks
Intercept and modify agent behavior at various lifecycle points:
@@ -916,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:
@@ -953,7 +1155,7 @@ praisonai checkpoint restore abc123 # Restore to checkpoint
---
### 11. Background Tasks
### 12. Background Tasks
Run agent tasks asynchronously without blocking:
@@ -991,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:
@@ -1032,7 +1234,7 @@ praisonai policy init # Create template
---
### 13. Thinking Budgets
### 14. Thinking Budgets
Configure token budgets for extended thinking:
@@ -1065,7 +1267,7 @@ praisonai thinking stats # Show usage statistics
---
### 14. Output Styles
### 15. Output Styles
Configure how agents format their responses:
@@ -1095,7 +1297,7 @@ praisonai output set concise # Set output style
---
### 15. Context Compaction
### 16. Context Compaction
Automatically manage context window size:
@@ -1130,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:
@@ -1164,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)
@@ -1240,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.
@@ -1316,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.
@@ -1364,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
@@ -1776,19 +1994,23 @@ manager.delete_checkpoint("deploy-v1")
### Tools CLI:
```bash
# List all available tools
praisonai tools list
# Get info about a specific tool
praisonai tools info internet_search
# Search for tools
praisonai tools search "web"
# Show all commands
praisonai tools help
praisonai tools doctor
praisonai tools resolve shell_tool
praisonai tools discover
praisonai tools show-sources
praisonai tools show-sources --template ai-video-editor
```
| Command | Example | Docs |
|---------|---------|------|
| `tools list` | [example](examples/tools/) | [docs](https://docs.praison.ai/docs/cli/tools) |
| `tools resolve` | [example](examples/tools/example_tools_resolve.py) | [docs](https://docs.praison.ai/docs/cli/tools-resolve) |
| `tools discover` | [example](examples/tools/example_tools_discover.py) | [docs](https://docs.praison.ai/docs/cli/tools-discover) |
| `tools show-sources` | [example](examples/tools/example_tools_sources.py) | [docs](https://docs.praison.ai/docs/cli/tools-show-sources) |
### Handoff CLI:
```bash
# Enable agent-to-agent task delegation
@@ -2137,6 +2359,24 @@ agent = Agent(
| 🌐 Web Search - Real-time search | [📖](https://docs.praison.ai/docs/cli/web-search) |
| 📥 Web Fetch - URL content retrieval | [📖](https://docs.praison.ai/docs/cli/web-fetch) |
| 💾 Prompt Caching - Cost reduction | [📖](https://docs.praison.ai/docs/cli/prompt-caching) |
| 📦 Template Catalog - Browse & discover templates | [📖](https://docs.praison.ai/docs/cli/template-catalog) |
### Template Catalog CLI
| Command | Description |
|---------|-------------|
| `praisonai templates browse` | Open template catalog in browser |
| `praisonai templates browse --print` | Print catalog URL only |
| `praisonai templates validate` | Validate template YAML files |
| `praisonai templates validate --source <dir>` | Validate specific directory |
| `praisonai templates validate --strict` | Strict validation mode |
| `praisonai templates validate --json` | JSON output format |
| `praisonai templates catalog build` | Build catalog locally |
| `praisonai templates catalog build --out <dir>` | Build to specific directory |
| `praisonai templates catalog sync` | Sync template sources |
| `praisonai templates catalog sync --source <name>` | Sync specific source |
**Examples:** [examples/catalog/](examples/catalog/) | **Docs:** [Code](https://docs.praison.ai/docs/cli/template-catalog-code) | [CLI](https://docs.praison.ai/docs/cli/template-catalog)
---
@@ -3023,6 +3263,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>
+1 -1
View File
@@ -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.2" \
"praisonai>=2.7.2" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -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.2" \
"praisonai>=2.7.2" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -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.2" \
"praisonai>=2.7.2" \
"praisonai[ui]" \
"praisonai[crewai]"
+53
View File
@@ -0,0 +1,53 @@
# Templates CLI
```bash
praisonai templates list
```
```bash
praisonai templates search video
```
```bash
praisonai templates info ai-video-editor
```
```bash
praisonai templates run ai-video-editor --input video.mp4 --output edited.mp4
```
```bash
praisonai templates run ai-video-editor --input video.mp4 --output edited.mp4 --offline
```
```bash
praisonai templates run ai-video-editor --input video.mp4 --output edited.mp4 --strict-tools
```
```bash
praisonai templates run ai-video-editor --input video.mp4 --tools ./my_tools.py
```
```bash
praisonai templates run ai-video-editor --input video.mp4 --tools-dir ./tools
```
```bash
praisonai templates run ai-video-editor --input video.mp4 --tools-source praisonai_tools.video
```
```bash
praisonai templates run ai-video-editor --input video.mp4 --no-template-tools-py
```
```bash
praisonai templates init my-project --template ai-video-editor
```
```bash
praisonai templates cache clear
```
```bash
praisonai templates cache list
```
+104
View File
@@ -0,0 +1,104 @@
# Templates Module
## Loading Templates
```python
from praisonai.templates.loader import TemplateLoader
loader = TemplateLoader()
# Load from local path
template = loader.load("./my-template")
# Load from URI
template = loader.load("ai-video-editor")
# Load offline
template = loader.load("ai-video-editor", offline=True)
# Check requirements
missing = loader.check_requirements(template)
```
## Template Discovery
```python
from praisonai.templates.discovery import TemplateDiscovery
discovery = TemplateDiscovery(
include_package=True,
include_defaults=True,
)
# Find template
result = discovery.find_template("ai-video-editor")
# List all templates
templates = discovery.list_templates()
```
## Template Config
```python
from praisonai.templates.loader import TemplateConfig
# Access template properties
template.name
template.description
template.version
template.author
template.requires
template.workflow_file
template.agents_file
template.path
```
## Tool Override Integration
```python
from praisonai.templates.tool_override import (
create_tool_registry_with_overrides,
resolve_tools,
)
# Build registry with template sources
tools_sources = template.requires.get("tools_sources", [])
registry = create_tool_registry_with_overrides(
tools_sources=tools_sources,
template_dir=str(template.path),
include_defaults=True,
)
# Resolve tool names
tools = resolve_tools(["shell_tool"], registry=registry)
```
## TEMPLATE.yaml Schema
```yaml
name: my-template
version: "1.0.0"
description: Template description
requires:
tools: [shell_tool]
packages: [praisonai-tools]
env: [OPENAI_API_KEY]
tools_sources:
- praisonai_tools.video
- ./local_tools.py
workflow: workflow.yaml
agents: agents.yaml
config:
input:
type: string
required: true
output:
type: string
defaults:
preset: podcast
```
+65
View File
@@ -0,0 +1,65 @@
# Tools CLI
```bash
praisonai tools list
```
```bash
praisonai tools info shell_tool
```
```bash
praisonai tools info internet_search
```
```bash
praisonai tools search "web"
```
```bash
praisonai tools search "file"
```
```bash
praisonai tools doctor
```
```bash
praisonai tools doctor --json
```
```bash
praisonai tools resolve shell_tool
```
```bash
praisonai tools resolve internet_search --template ai-video-editor
```
```bash
praisonai tools resolve my_tool --tools ./my_tools.py
```
```bash
praisonai tools resolve my_tool --tools-dir ./tools
```
```bash
praisonai tools discover
```
```bash
praisonai tools discover --include praisonai_tools.video
```
```bash
praisonai tools discover --entrypoints
```
```bash
praisonai tools show-sources
```
```bash
praisonai tools show-sources --template ai-video-editor
```
+90
View File
@@ -0,0 +1,90 @@
# Tools Module
## Tool Registry
```python
from praisonai.templates.tool_override import (
create_tool_registry_with_overrides,
resolve_tools,
)
# Create registry with defaults
registry = create_tool_registry_with_overrides(include_defaults=True)
# Create registry with overrides
registry = create_tool_registry_with_overrides(
override_files=["my_tools.py"],
override_dirs=["./tools"],
include_defaults=True,
)
# Create registry with tools_sources
registry = create_tool_registry_with_overrides(
tools_sources=["praisonai_tools.video"],
template_dir="./my-template",
include_defaults=True,
)
# Resolve tool names to callables
tools = resolve_tools(
["shell_tool", "internet_search"],
registry=registry,
)
```
## Tool Override Loader
```python
from praisonai.templates.tool_override import ToolOverrideLoader
loader = ToolOverrideLoader()
# Load from file
tools = loader.load_from_file("my_tools.py")
# Load from directory
tools = loader.load_from_directory("./tools")
# Load from module
tools = loader.load_from_module("praisonai_tools.video")
# Get default tool directories
dirs = loader.get_default_tool_dirs()
```
## Resolution Order
1. CLI `--tools` files (highest priority)
2. CLI `--tools-dir` directories
3. Template `tools_sources` (from TEMPLATE.yaml)
4. Template-local `tools.py`
5. Default dirs (`~/.praison/tools`, `~/.config/praison/tools`)
6. Package discovery (`praisonai_tools`)
7. Built-in tools (lowest priority)
## Creating Custom Tools
```python
from praisonai_tools import tool
@tool
def my_custom_tool(query: str) -> str:
"""My custom tool description.
Args:
query: The search query
"""
return f"Result for: {query}"
```
## Template tools_sources
```yaml
# TEMPLATE.yaml
requires:
tools: [shell_tool]
tools_sources:
- praisonai_tools.video
- ./local_tools.py
- ./tools_dir/
```
+1
View File
@@ -30,6 +30,7 @@ examples/
| **Tools** | [python/tools/](python/tools/) | [📖](https://docs.praison.ai/tools) |
| **Code** | [python/code/](python/code/) | [📖](https://docs.praison.ai/code) |
| **YAML** | [yaml/](yaml/) | [📖](https://docs.praison.ai/features/yaml-workflows) |
| **Doctor** | [doctor/](doctor/) | [📖](https://docs.praison.ai/cli/doctor) |
## Running Examples
@@ -0,0 +1,24 @@
"""
Assistants Capabilities Example
Demonstrates OpenAI Assistants API using PraisonAI capabilities.
"""
from praisonai.capabilities import assistant_list
print("=== List Assistants ===")
try:
assistants = assistant_list()
print(f"Assistants found: {len(assistants)}")
for a in assistants[:5]: # Show first 5
print(f" - {a.get('name', 'unnamed')}: {a.get('id', 'unknown')}")
except Exception as e:
print(f"Note: {e}")
print("\n=== Assistant Operations ===")
print("Available functions:")
print(" assistant_create(name, instructions, model='gpt-4o-mini')")
print(" assistant_list()")
print("\nSee CLI: praisonai assistants list")
print("See CLI: praisonai assistants create --name 'My Assistant'")
+24
View File
@@ -0,0 +1,24 @@
"""
Audio Capabilities Example
Demonstrates transcription and text-to-speech using PraisonAI capabilities.
"""
from praisonai.capabilities import transcribe, speech
# Note: Audio transcription requires an audio file
# This example shows the API structure
print("=== Audio Transcription ===")
print("To transcribe audio, use:")
print(" result = transcribe('audio.mp3', model='whisper-1')")
print(" print(result.text)")
print("\n=== Text-to-Speech ===")
print("To generate speech, use:")
print(" audio_data = speech('Hello world', model='tts-1', voice='alloy')")
print(" # Save to file or play")
print("\nNote: These operations require audio files or generate audio output.")
print("See CLI: praisonai audio transcribe <file>")
print("See CLI: praisonai audio speech <text>")
+27
View File
@@ -0,0 +1,27 @@
"""
Batches Capabilities Example
Demonstrates batch processing using PraisonAI capabilities.
"""
from praisonai.capabilities import batch_list
print("=== Batch List ===")
try:
batches = batch_list()
print(f"Batches found: {len(batches)}")
for b in batches[:5]: # Show first 5
print(f" - {b.get('id', 'unknown')}: {b.get('status', 'unknown')}")
except Exception as e:
print(f"Note: {e}")
print("Batch operations require batch jobs to be created first.")
print("\n=== Batch Operations ===")
print("Available functions:")
print(" batch_create(input_file_id, endpoint, completion_window='24h')")
print(" batch_list()")
print(" batch_retrieve(batch_id)")
print(" batch_cancel(batch_id)")
print("\nSee CLI: praisonai batches list")
print("See CLI: praisonai batches create <input_file_id>")
@@ -0,0 +1,30 @@
"""
Completions Capability Example
Demonstrates chat and text completions using PraisonAI capabilities.
"""
from praisonai.capabilities import chat_completion, text_completion
# Chat completion example
print("=== Chat Completion ===")
result = chat_completion(
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is 2 + 2? Answer in one word."}
],
model="gpt-4o-mini",
max_tokens=10
)
print(f"Response: {result.content}")
print(f"Model: {result.model}")
print(f"Usage: {result.usage}")
# Text completion example (legacy)
print("\n=== Text Completion ===")
result = text_completion(
prompt="The capital of France is",
model="gpt-3.5-turbo-instruct",
max_tokens=10
)
print(f"Response: {result.content}")
@@ -0,0 +1,27 @@
"""
Embeddings Capability Example
Demonstrates text embedding generation using PraisonAI capabilities.
"""
from praisonai.capabilities import embed
# Single text embedding
print("=== Single Text Embedding ===")
result = embed(
input="Hello, world!",
model="text-embedding-3-small"
)
print(f"Embedding dimensions: {len(result.embeddings[0])}")
print(f"First 5 values: {result.embeddings[0][:5]}")
print(f"Usage: {result.usage}")
# Multiple text embeddings
print("\n=== Multiple Text Embeddings ===")
result = embed(
input=["Hello", "World", "AI"],
model="text-embedding-3-small"
)
print(f"Number of embeddings: {len(result.embeddings)}")
for i, emb in enumerate(result.embeddings):
print(f" Text {i+1}: {len(emb)} dimensions")
+28
View File
@@ -0,0 +1,28 @@
"""
Files Capabilities Example
Demonstrates file management using PraisonAI capabilities.
"""
from praisonai.capabilities import file_list
print("=== File List ===")
try:
files = file_list()
print(f"Files found: {len(files)}")
for f in files[:5]: # Show first 5
print(f" - {f.get('filename', 'unknown')}: {f.get('id', 'unknown')}")
except Exception as e:
print(f"Note: {e}")
print("File operations require files to be uploaded first.")
print("\n=== File Operations ===")
print("Available functions:")
print(" file_create(file_path, purpose='assistants')")
print(" file_list(purpose=None)")
print(" file_retrieve(file_id)")
print(" file_delete(file_id)")
print(" file_content(file_id)")
print("\nSee CLI: praisonai files list")
print("See CLI: praisonai files upload <file>")
+31
View File
@@ -0,0 +1,31 @@
"""
Images Capability Example
Demonstrates image generation using PraisonAI capabilities.
"""
from praisonai.capabilities import image_generate
# Generate an image with DALL-E 2
print("=== Image Generation (DALL-E 2) ===")
result = image_generate(
prompt="A simple blue circle on a white background",
model="dall-e-2",
size="256x256",
n=1
)
print(f"Images generated: {len(result)}")
if result:
print(f"URL: {result[0].url[:80]}...")
# Generate with DALL-E 3 (higher quality)
print("\n=== Image Generation (DALL-E 3) ===")
result = image_generate(
prompt="A serene mountain landscape at sunset",
model="dall-e-3",
size="1024x1024",
quality="standard"
)
print(f"Images generated: {len(result)}")
if result:
print(f"Revised prompt: {result[0].revised_prompt[:80]}..." if result[0].revised_prompt else "No revised prompt")
+33
View File
@@ -0,0 +1,33 @@
"""
Messages Capability Example
Demonstrates Anthropic-style messages API and token counting.
"""
from praisonai.capabilities import messages_create, count_tokens
# Create a message (Anthropic-style)
print("=== Messages Create ===")
result = messages_create(
messages=[{"role": "user", "content": "What is the meaning of life? Answer briefly."}],
model="gpt-4o-mini", # Works with any model via LiteLLM
max_tokens=100,
system="You are a philosophical assistant."
)
print(f"Message ID: {result.id}")
if result.content:
for block in result.content:
if block.get("type") == "text":
print(f"Response: {block.get('text')}")
print(f"Usage: {result.usage}")
# Count tokens
print("\n=== Token Counting ===")
result = count_tokens(
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello, how are you today?"}
],
model="gpt-4o-mini"
)
print(f"Token count: {result.input_tokens}")
@@ -0,0 +1,24 @@
"""
Moderations Capability Example
Demonstrates content moderation using PraisonAI capabilities.
"""
from praisonai.capabilities import moderate
# Check safe content
print("=== Safe Content Check ===")
result = moderate(
input="Hello, how are you today?"
)
print(f"Flagged: {result[0].flagged}")
print(f"Categories: {list(result[0].categories.keys())[:5]}...")
# Check multiple texts
print("\n=== Multiple Text Moderation ===")
result = moderate(
input=["Hello world", "Have a nice day", "This is a test"]
)
print(f"Number of results: {len(result)}")
for i, r in enumerate(result):
print(f" Text {i+1}: Flagged = {r.flagged}")
+23
View File
@@ -0,0 +1,23 @@
"""
Realtime Capability Example
Demonstrates realtime session creation for audio/video streaming.
"""
from praisonai.capabilities import realtime_connect
# Create a realtime session
print("=== Realtime Session ===")
session = realtime_connect(
model="gpt-4o-realtime-preview",
modalities=["text", "audio"],
voice="alloy"
)
print(f"Session ID: {session.id}")
print(f"Status: {session.status}")
print(f"URL: {session.url}")
print(f"Modalities: {session.metadata.get('modalities')}")
print(f"Voice: {session.metadata.get('voice')}")
print("\nNote: To use this session, connect via WebSocket to the URL above.")
print("See OpenAI Realtime API documentation for WebSocket protocol details.")
+32
View File
@@ -0,0 +1,32 @@
"""
Rerank Capabilities Example
Demonstrates document reranking using PraisonAI capabilities.
"""
from praisonai.capabilities import rerank
print("=== Document Reranking ===")
try:
documents = [
"Python is a programming language",
"Machine learning uses algorithms",
"Python is great for data science",
"JavaScript runs in browsers"
]
results = rerank(
query="Python programming",
documents=documents,
model="cohere/rerank-english-v3.0"
)
print(f"Query: 'Python programming'")
print(f"Reranked results:")
for r in results:
print(f" Score {r.get('relevance_score', 0):.4f}: {r.get('document', {}).get('text', '')[:50]}")
except Exception as e:
print(f"Note: Reranking requires Cohere API key")
print(f"Error: {e}")
print("\nSee CLI: praisonai rerank <query> --documents 'doc1' 'doc2'")
@@ -0,0 +1,22 @@
"""
Vector Stores Capabilities Example
Demonstrates vector store management using PraisonAI capabilities.
"""
from praisonai.capabilities import vector_store_create, vector_store_search
print("=== Vector Store Operations ===")
print("Available functions:")
print(" vector_store_create(name)")
print(" vector_store_search(vector_store_id, query)")
print("\n=== Create Vector Store ===")
try:
store = vector_store_create(name="test-store")
print(f"Created: {store}")
except Exception as e:
print(f"Note: {e}")
print("\nSee CLI: praisonai vector-stores create --name 'my-store'")
print("See CLI: praisonai vector-stores search <store_id> <query>")
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""
Example: Fetch and search PraisonAI templates from the catalog.
This example demonstrates how to:
- Fetch templates.json from the deployed catalog
- Search templates by keyword
- Filter by tags and requirements
- Generate CLI commands
"""
import json
import urllib.request
from typing import List, Dict, Any, Optional
CATALOG_URL = "https://mervinpraison.github.io/praisonai-template-catalog/data/templates.json"
def fetch_catalog(url: str = CATALOG_URL) -> Dict[str, Any]:
"""Fetch the template catalog from URL."""
try:
with urllib.request.urlopen(url, timeout=10) as response:
return json.loads(response.read().decode())
except Exception as e:
print(f"Failed to fetch catalog: {e}")
# Return empty catalog on failure
return {"version": "0.0.0", "count": 0, "templates": []}
def search_templates(templates: List[Dict], query: str) -> List[Dict]:
"""Search templates by name, description, or tags."""
query = query.lower()
results = []
for t in templates:
if (query in t.get("name", "").lower() or
query in t.get("description", "").lower() or
any(query in tag.lower() for tag in t.get("tags", []))):
results.append(t)
return results
def filter_by_tags(templates: List[Dict], tags: List[str]) -> List[Dict]:
"""Filter templates that have all specified tags."""
return [
t for t in templates
if all(tag in t.get("tags", []) for tag in tags)
]
def filter_by_tool(templates: List[Dict], tool_name: str) -> List[Dict]:
"""Filter templates that require a specific tool."""
return [
t for t in templates
if tool_name in t.get("requires", {}).get("tools", [])
]
def get_template(templates: List[Dict], name: str) -> Optional[Dict]:
"""Get a template by name."""
for t in templates:
if t.get("name") == name:
return t
return None
def generate_cli_command(template: Dict, action: str = "run") -> str:
"""Generate CLI command for a template."""
name = template.get("name", "unknown")
if action == "run":
return f"praisonai templates run {name}"
elif action == "info":
return f"praisonai templates info {name}"
elif action == "init":
return f"praisonai templates init my-project --template {name}"
return f"praisonai templates {action} {name}"
def print_template_summary(template: Dict):
"""Print a summary of a template."""
print(f"\n{'='*60}")
print(f"Name: {template.get('name')}")
print(f"Version: {template.get('version')}")
print(f"Description: {template.get('description', '')[:100]}...")
print(f"Tags: {', '.join(template.get('tags', []))}")
requires = template.get("requires", {})
if requires.get("tools"):
print(f"Tools: {', '.join(requires['tools'])}")
if requires.get("packages"):
print(f"Packages: {', '.join(requires['packages'])}")
if requires.get("env"):
print(f"Env vars: {', '.join(requires['env'])}")
print(f"\nRun: {generate_cli_command(template, 'run')}")
print(f"{'='*60}")
def main():
print("PraisonAI Template Catalog - Python Example")
print("-" * 45)
# Fetch catalog
print("\n1. Fetching template catalog...")
catalog = fetch_catalog()
templates = catalog.get("templates", [])
print(f" Catalog version: {catalog.get('version', 'unknown')}")
print(f" Total templates: {len(templates)}")
if not templates:
print(" No templates found. The catalog may not be deployed yet.")
print(" Try building locally: praisonai templates catalog build")
return
# List all templates
print("\n2. Available templates:")
for t in templates:
print(f" - {t.get('name')} (v{t.get('version')})")
# Search for video templates
print("\n3. Searching for 'video' templates...")
video_templates = search_templates(templates, "video")
print(f" Found {len(video_templates)} video templates:")
for t in video_templates:
print(f" - {t.get('name')}: {t.get('description', '')[:50]}...")
# Filter by tool
print("\n4. Templates using 'shell_tool':")
shell_templates = filter_by_tool(templates, "shell_tool")
for t in shell_templates:
print(f" - {t.get('name')}")
# Get specific template details
if templates:
first_template = templates[0]
print(f"\n5. Template details for '{first_template.get('name')}':")
print_template_summary(first_template)
# Generate CLI commands
print("\n6. CLI commands for all templates:")
for t in templates[:5]: # First 5 only
print(f" {generate_cli_command(t, 'run')}")
print("\nDone!")
if __name__ == "__main__":
main()
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env npx ts-node
/**
* Example: Fetch and search PraisonAI templates from the catalog.
*
* This TypeScript example demonstrates how to:
* - Fetch templates.json from the deployed catalog
* - Search templates by keyword
* - Filter by tags and requirements
* - Generate CLI commands
*/
const CATALOG_URL = "https://mervinpraison.github.io/praisonai-template-catalog/data/templates.json";
interface Template {
name: string;
version: string;
description: string;
author?: string;
license?: string;
tags?: string[];
category?: string;
difficulty?: "beginner" | "intermediate" | "advanced";
requires?: {
tools?: string[];
packages?: string[];
env?: string[];
};
}
interface Catalog {
version: string;
generated_at: string;
count: number;
templates: Template[];
}
async function fetchCatalog(url: string = CATALOG_URL): Promise<Catalog> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch catalog: ${response.statusText}`);
}
return response.json();
}
function searchTemplates(templates: Template[], query: string): Template[] {
const q = query.toLowerCase();
return templates.filter(t =>
t.name.toLowerCase().includes(q) ||
t.description?.toLowerCase().includes(q) ||
t.tags?.some(tag => tag.toLowerCase().includes(q))
);
}
function filterByTags(templates: Template[], tags: string[]): Template[] {
return templates.filter(t =>
tags.every(tag => t.tags?.includes(tag))
);
}
function filterByTool(templates: Template[], toolName: string): Template[] {
return templates.filter(t =>
t.requires?.tools?.includes(toolName)
);
}
function getTemplate(templates: Template[], name: string): Template | undefined {
return templates.find(t => t.name === name);
}
function generateCLICommand(template: Template, action: string = "run"): string {
const name = template.name;
switch (action) {
case "run":
return `praisonai templates run ${name}`;
case "info":
return `praisonai templates info ${name}`;
case "init":
return `praisonai templates init my-project --template ${name}`;
default:
return `praisonai templates ${action} ${name}`;
}
}
function printTemplateSummary(template: Template): void {
console.log("\n" + "=".repeat(60));
console.log(`Name: ${template.name}`);
console.log(`Version: ${template.version}`);
console.log(`Description: ${template.description?.slice(0, 100)}...`);
console.log(`Tags: ${template.tags?.join(", ") || "none"}`);
const requires = template.requires;
if (requires?.tools?.length) {
console.log(`Tools: ${requires.tools.join(", ")}`);
}
if (requires?.packages?.length) {
console.log(`Packages: ${requires.packages.join(", ")}`);
}
if (requires?.env?.length) {
console.log(`Env vars: ${requires.env.join(", ")}`);
}
console.log(`\nRun: ${generateCLICommand(template, "run")}`);
console.log("=".repeat(60));
}
async function main(): Promise<void> {
console.log("PraisonAI Template Catalog - TypeScript Example");
console.log("-".repeat(45));
// Fetch catalog
console.log("\n1. Fetching template catalog...");
let catalog: Catalog;
try {
catalog = await fetchCatalog();
} catch (error) {
console.log(` Failed to fetch: ${error}`);
console.log(" The catalog may not be deployed yet.");
return;
}
const templates = catalog.templates;
console.log(` Catalog version: ${catalog.version}`);
console.log(` Total templates: ${templates.length}`);
if (templates.length === 0) {
console.log(" No templates found.");
return;
}
// List all templates
console.log("\n2. Available templates:");
templates.forEach(t => {
console.log(` - ${t.name} (v${t.version})`);
});
// Search for video templates
console.log("\n3. Searching for 'video' templates...");
const videoTemplates = searchTemplates(templates, "video");
console.log(` Found ${videoTemplates.length} video templates:`);
videoTemplates.forEach(t => {
console.log(` - ${t.name}: ${t.description?.slice(0, 50)}...`);
});
// Filter by tool
console.log("\n4. Templates using 'shell_tool':");
const shellTemplates = filterByTool(templates, "shell_tool");
shellTemplates.forEach(t => {
console.log(` - ${t.name}`);
});
// Get specific template details
if (templates.length > 0) {
const firstTemplate = templates[0];
console.log(`\n5. Template details for '${firstTemplate.name}':`);
printTemplateSummary(firstTemplate);
}
// Generate CLI commands
console.log("\n6. CLI commands for all templates:");
templates.slice(0, 5).forEach(t => {
console.log(` ${generateCLICommand(t, "run")}`);
});
console.log("\nDone!");
}
main().catch(console.error);
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""
Run all catalog examples and report pass/fail status.
Usage:
python run_all_catalog_examples.py
Environment variables:
OPENAI_API_KEY or PRAISONAI_OPENAI_API_KEY - Required for agent example
PRAISONAI_RUN_INTEGRATION - Set to "1" to run integration tests
"""
import os
import subprocess
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
def run_example(name: str, script: str, requires_api_key: bool = False) -> bool:
"""Run an example script and return success status."""
print(f"\n{'='*60}")
print(f"Running: {name}")
print(f"Script: {script}")
print("="*60)
# Check API key requirement
if requires_api_key:
api_key = os.environ.get("OPENAI_API_KEY") or os.environ.get("PRAISONAI_OPENAI_API_KEY")
run_integration = os.environ.get("PRAISONAI_RUN_INTEGRATION") == "1"
if not api_key:
print(f"⏭️ SKIPPED: {name} (no API key)")
print(" Set OPENAI_API_KEY to run this example")
return True # Not a failure, just skipped
if not run_integration:
print(f"⏭️ SKIPPED: {name} (integration tests disabled)")
print(" Set PRAISONAI_RUN_INTEGRATION=1 to run")
return True
script_path = SCRIPT_DIR / script
if not script_path.exists():
print(f"❌ FAILED: Script not found: {script_path}")
return False
try:
result = subprocess.run(
[sys.executable, str(script_path)],
capture_output=True,
text=True,
timeout=60,
cwd=str(SCRIPT_DIR)
)
print(result.stdout)
if result.stderr:
print(f"STDERR: {result.stderr}")
if result.returncode == 0:
print(f"✅ PASSED: {name}")
return True
else:
print(f"❌ FAILED: {name} (exit code {result.returncode})")
return False
except subprocess.TimeoutExpired:
print(f"❌ FAILED: {name} (timeout)")
return False
except Exception as e:
print(f"❌ FAILED: {name} ({e})")
return False
def main():
print("PraisonAI Template Catalog - Example Runner")
print("=" * 50)
# Check environment
api_key = os.environ.get("OPENAI_API_KEY") or os.environ.get("PRAISONAI_OPENAI_API_KEY")
if api_key:
print(f"API key: present (...{api_key[-4:]})")
else:
print("API key: not set")
run_integration = os.environ.get("PRAISONAI_RUN_INTEGRATION") == "1"
print(f"Integration tests: {'enabled' if run_integration else 'disabled'}")
# Define examples
examples = [
("Fetch Templates (Python)", "fetch_templates.py", False),
("Template Finder Agent", "template_finder_agent.py", True),
]
# Run examples
results = []
for name, script, requires_key in examples:
success = run_example(name, script, requires_key)
results.append((name, success))
# Summary
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
passed = sum(1 for _, s in results if s)
failed = sum(1 for _, s in results if not s)
for name, success in results:
status = "✅ PASS" if success else "❌ FAIL"
print(f" {status}: {name}")
print(f"\nTotal: {passed} passed, {failed} failed")
# Exit with appropriate code
sys.exit(0 if failed == 0 else 1)
if __name__ == "__main__":
main()
+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""
Example: Template Finder Agent
An Agent()-centric example that uses an AI agent to find templates
based on natural language queries.
Requires: OPENAI_API_KEY environment variable
"""
import json
import os
import urllib.request
CATALOG_URL = "https://mervinpraison.github.io/praisonai-template-catalog/data/templates.json"
def fetch_templates() -> list:
"""Fetch templates from catalog."""
try:
with urllib.request.urlopen(CATALOG_URL, timeout=10) as response:
data = json.loads(response.read().decode())
return data.get("templates", [])
except Exception:
return []
def search_templates_tool(query: str) -> str:
"""
Search templates by keyword.
Args:
query: Search query (e.g., "video editing", "transcript", "data")
Returns:
JSON string with matching templates
"""
templates = fetch_templates()
query_lower = query.lower()
results = []
for t in templates:
if (query_lower in t.get("name", "").lower() or
query_lower in t.get("description", "").lower() or
any(query_lower in tag.lower() for tag in t.get("tags", []))):
results.append({
"name": t.get("name"),
"description": t.get("description", "")[:100],
"tags": t.get("tags", []),
"cli_command": f"praisonai templates run {t.get('name')}"
})
if not results:
return json.dumps({"message": "No templates found matching your query", "templates": []})
return json.dumps({"count": len(results), "templates": results})
def list_all_templates_tool() -> str:
"""
List all available templates.
Returns:
JSON string with all template names and descriptions
"""
templates = fetch_templates()
if not templates:
return json.dumps({"message": "No templates available", "templates": []})
results = [
{"name": t.get("name"), "description": t.get("description", "")[:80]}
for t in templates
]
return json.dumps({"count": len(results), "templates": results})
def get_template_details_tool(name: str) -> str:
"""
Get detailed information about a specific template.
Args:
name: Template name (e.g., "ai-video-editor")
Returns:
JSON string with template details
"""
templates = fetch_templates()
for t in templates:
if t.get("name") == name:
return json.dumps({
"name": t.get("name"),
"version": t.get("version"),
"description": t.get("description"),
"author": t.get("author"),
"tags": t.get("tags", []),
"requires": t.get("requires", {}),
"cli_commands": {
"run": f"praisonai templates run {name}",
"info": f"praisonai templates info {name}",
"init": f"praisonai templates init my-project --template {name}"
}
})
return json.dumps({"error": f"Template '{name}' not found"})
def check_api_key() -> bool:
"""Check if API key is available."""
key = os.environ.get("OPENAI_API_KEY") or os.environ.get("PRAISONAI_OPENAI_API_KEY")
if key:
print(f"API key present: ...{key[-4:]}")
return True
return False
def main():
print("PraisonAI Template Finder Agent")
print("=" * 40)
# Check for API key
if not check_api_key():
print("\n⚠️ No API key found.")
print("Set OPENAI_API_KEY or PRAISONAI_OPENAI_API_KEY to use the agent.")
print("\nFalling back to direct tool usage:\n")
# Demo the tools directly
print("1. Listing all templates:")
result = list_all_templates_tool()
data = json.loads(result)
for t in data.get("templates", [])[:5]:
print(f" - {t['name']}: {t['description'][:50]}...")
print("\n2. Searching for 'video' templates:")
result = search_templates_tool("video")
data = json.loads(result)
for t in data.get("templates", []):
print(f" - {t['name']}")
print(f" Run: {t['cli_command']}")
return
# Import Agent only if API key is available
try:
from praisonaiagents import Agent
except ImportError:
print("praisonaiagents not installed. Install with: pip install praisonaiagents")
return
# Create the Template Finder Agent
agent = Agent(
name="Template Finder",
role="PraisonAI Template Expert",
goal="Help users find the right template for their needs",
backstory="""You are an expert on PraisonAI templates. You help users
discover templates that match their requirements. You can search templates,
list all available templates, and provide detailed information about specific
templates. Always provide the CLI command to run the template.""",
tools=[
search_templates_tool,
list_all_templates_tool,
get_template_details_tool
],
verbose=True
)
# Example queries
queries = [
"Find me a template for editing videos",
"What templates are available for working with transcripts?",
"Show me details about the ai-video-editor template"
]
print("\nRunning Template Finder Agent...")
print("-" * 40)
for query in queries:
print(f"\n📝 Query: {query}")
print("-" * 40)
try:
result = agent.start(query)
print(f"\n🤖 Response:\n{result}")
except Exception as e:
print(f"Error: {e}")
print("\n" + "=" * 40)
if __name__ == "__main__":
main()
+65
View File
@@ -0,0 +1,65 @@
# PraisonAI Doctor Examples
Examples demonstrating the PraisonAI Doctor health check and diagnostics system.
## CLI Examples
| Command | Description |
|---------|-------------|
| `praisonai doctor` | Run all fast health checks |
| `praisonai doctor --version` | Show doctor version |
| `praisonai doctor --list-checks` | List all available checks |
| `praisonai doctor env` | Check environment configuration |
| `praisonai doctor config` | Validate configuration files |
| `praisonai doctor tools` | Check tool availability |
| `praisonai doctor db` | Check database drivers |
| `praisonai doctor mcp` | Check MCP configuration |
| `praisonai doctor obs` | Check observability providers |
| `praisonai doctor skills` | Check agent skills |
| `praisonai doctor memory` | Check memory storage |
| `praisonai doctor permissions` | Check filesystem permissions |
| `praisonai doctor network` | Check network connectivity |
| `praisonai doctor performance` | Check import times |
| `praisonai doctor ci` | CI mode with JSON output |
| `praisonai doctor selftest` | Test agent functionality |
## Global Flags
| Flag | Description |
|------|-------------|
| `--json` | Output in JSON format |
| `--format text\|json` | Output format |
| `--output PATH` | Write report to file |
| `--deep` | Enable deeper probes |
| `--timeout SEC` | Per-check timeout |
| `--strict` | Treat warnings as failures |
| `--quiet` | Minimal output |
| `--no-color` | Disable colors |
| `--only IDS` | Only run these checks |
| `--skip IDS` | Skip these checks |
## Python Examples
| File | Description |
|------|-------------|
| [basic_doctor.py](basic_doctor.py) | Programmatic health checks |
| [ci_integration.py](ci_integration.py) | CI/CD pipeline integration |
## Quick Start
```bash
# Run basic health checks
praisonai doctor
# Run with JSON output
praisonai doctor --json
# Run specific checks
praisonai doctor --only python_version,openai_api_key
# CI mode
praisonai doctor ci
# Save report to file
praisonai doctor --output report.json
```
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""
Basic Doctor Example - Programmatic health checks for PraisonAI
This example demonstrates how to use the Doctor module programmatically
to run health checks and diagnostics.
"""
import json
import sys
# Add the praisonai path for development
sys.path.insert(0, '/Users/praison/praisonai-package/src/praisonai')
from praisonai.cli.features.doctor import DoctorEngine
from praisonai.cli.features.doctor.models import DoctorConfig, CheckCategory
from praisonai.cli.features.doctor.registry import get_registry
from praisonai.cli.features.doctor.checks import register_all_checks
from praisonai.cli.features.doctor.formatters import get_formatter
def run_basic_checks():
"""Run basic health checks."""
print("=" * 60)
print("Running Basic Health Checks")
print("=" * 60)
# Register all checks
register_all_checks()
# Create config for fast checks only
config = DoctorConfig(
deep=False,
timeout=10.0,
strict=False,
quiet=False,
)
# Create and run engine
engine = DoctorEngine(config)
report = engine.run()
# Format and print results
formatter = get_formatter("text", no_color=True)
print(formatter.format_report(report))
return report.exit_code
def run_environment_checks():
"""Run only environment checks."""
print("\n" + "=" * 60)
print("Running Environment Checks Only")
print("=" * 60)
# Register all checks
register_all_checks()
config = DoctorConfig(deep=False)
engine = DoctorEngine(config)
# Run only environment category
engine.run_checks(categories=[CheckCategory.ENVIRONMENT])
report = engine.generate_report()
formatter = get_formatter("text", no_color=True)
print(formatter.format_report(report))
return report.exit_code
def run_specific_checks():
"""Run specific checks by ID."""
print("\n" + "=" * 60)
print("Running Specific Checks")
print("=" * 60)
# Register all checks
register_all_checks()
# Only run these specific checks
config = DoctorConfig(
only=["python_version", "openai_api_key", "os_info"]
)
engine = DoctorEngine(config)
report = engine.run()
formatter = get_formatter("text", no_color=True)
print(formatter.format_report(report))
return report.exit_code
def run_json_output():
"""Run checks and get JSON output."""
print("\n" + "=" * 60)
print("Running Checks with JSON Output")
print("=" * 60)
# Register all checks
register_all_checks()
config = DoctorConfig(
only=["python_version", "os_info"],
format="json"
)
engine = DoctorEngine(config)
report = engine.run()
# Get JSON output
formatter = get_formatter("json")
json_output = formatter.format_report(report)
# Parse and pretty print
data = json.loads(json_output)
print(json.dumps(data, indent=2))
return report.exit_code
def list_available_checks():
"""List all available checks."""
print("\n" + "=" * 60)
print("Available Checks")
print("=" * 60)
# Register all checks
register_all_checks()
registry = get_registry()
checks = registry.get_all_checks()
# Sort by category
checks.sort(key=lambda c: c.category.value)
current_category = None
for check in checks:
if check.category != current_category:
current_category = check.category
print(f"\n {current_category.value.upper()}:")
deep_marker = " [deep]" if check.requires_deep else ""
print(f" {check.id:30} {check.description}{deep_marker}")
if __name__ == "__main__":
print("PraisonAI Doctor - Programmatic Usage Examples\n")
# List available checks
list_available_checks()
# Run basic checks
exit_code = run_basic_checks()
print(f"\nBasic checks exit code: {exit_code}")
# Run environment checks only
exit_code = run_environment_checks()
print(f"\nEnvironment checks exit code: {exit_code}")
# Run specific checks
exit_code = run_specific_checks()
print(f"\nSpecific checks exit code: {exit_code}")
# Run with JSON output
exit_code = run_json_output()
print(f"\nJSON output exit code: {exit_code}")
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
CI Integration Example - Using Doctor in CI/CD pipelines
This example demonstrates how to integrate PraisonAI Doctor
into CI/CD pipelines for automated health checks.
"""
import json
import subprocess
import sys
def run_doctor_ci():
"""Run doctor in CI mode and parse results."""
print("Running PraisonAI Doctor in CI mode...")
result = subprocess.run(
[sys.executable, "-m", "praisonai", "doctor", "ci"],
capture_output=True,
text=True,
timeout=120
)
# Parse JSON output
try:
report = json.loads(result.stdout)
except json.JSONDecodeError:
print("Error: Could not parse doctor output")
print(result.stdout)
print(result.stderr)
return 1
# Print summary
summary = report.get("summary", {})
print("\nDoctor CI Results:")
print(f" Total checks: {summary.get('total', 0)}")
print(f" Passed: {summary.get('passed', 0)}")
print(f" Warnings: {summary.get('warnings', 0)}")
print(f" Failed: {summary.get('failed', 0)}")
print(f" Skipped: {summary.get('skipped', 0)}")
print(f" Errors: {summary.get('errors', 0)}")
print(f"\nExit code: {report.get('exit_code', -1)}")
# Check for failures
if summary.get('failed', 0) > 0:
print("\nFailed checks:")
for check in report.get("results", []):
if check.get("status") == "fail":
print(f" - {check.get('title')}: {check.get('message')}")
if check.get("remediation"):
print(f" Fix: {check.get('remediation')}")
return result.returncode
def run_specific_checks_ci():
"""Run specific checks for CI."""
print("\nRunning specific checks for CI...")
# Only run critical checks
critical_checks = [
"python_version",
"praisonai_package",
"openai_api_key",
]
result = subprocess.run(
[
sys.executable, "-m", "praisonai", "doctor",
"--json",
"--only", ",".join(critical_checks)
],
capture_output=True,
text=True,
timeout=60
)
try:
report = json.loads(result.stdout)
print(f"Critical checks: {report['summary']['passed']}/{report['summary']['total']} passed")
return result.returncode
except json.JSONDecodeError:
print("Error parsing output")
return 1
def save_report_to_file():
"""Save doctor report to a file for artifact upload."""
print("\nSaving doctor report to file...")
result = subprocess.run(
[
sys.executable, "-m", "praisonai", "doctor",
"--json",
"--output", "/tmp/doctor-report.json"
],
capture_output=True,
text=True,
timeout=120
)
if result.returncode in [0, 1]:
print("Report saved to /tmp/doctor-report.json")
# Read and display summary
with open("/tmp/doctor-report.json", "r") as f:
report = json.load(f)
print(f"Summary: {report['summary']}")
return result.returncode
if __name__ == "__main__":
print("PraisonAI Doctor - CI Integration Examples\n")
print("=" * 60)
# Run CI mode
exit_code = run_doctor_ci()
# Run specific checks
run_specific_checks_ci()
# Save report
save_report_to_file()
print(f"\nFinal exit code: {exit_code}")
sys.exit(exit_code)
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""
PraisonAI Endpoints Example
This example demonstrates how to:
1. Start the recipe server
2. Use the endpoints CLI to interact with it
3. Invoke recipes via HTTP
Prerequisites:
- pip install praisonai[serve]
- Set OPENAI_API_KEY environment variable
Usage:
# Terminal 1: Start server
praisonai recipe serve --port 8765
# Terminal 2: Run this example
python endpoints_example.py
"""
import json
import os
import subprocess
import sys
import urllib.request
import urllib.error
# Configuration
SERVER_URL = os.environ.get("PRAISONAI_ENDPOINTS_URL", "http://localhost:8765")
def check_server_health():
"""Check if the server is running."""
try:
req = urllib.request.Request(f"{SERVER_URL}/health")
with urllib.request.urlopen(req, timeout=5) as response:
data = json.loads(response.read().decode())
return data.get("status") == "healthy"
except Exception:
return False
def list_endpoints():
"""List available endpoints using CLI."""
print("\n=== Listing Endpoints (CLI) ===")
result = subprocess.run(
["python", "-m", "praisonai.cli.main", "endpoints", "list", "--format", "json"],
capture_output=True,
text=True,
)
if result.returncode == 0:
recipes = json.loads(result.stdout)
print(f"Found {len(recipes)} endpoints:")
for r in recipes:
print(f" - {r['name']} ({r['version']}): {r.get('description', '')[:50]}")
return recipes
else:
print(f"Error: {result.stderr}")
return []
def describe_endpoint(name: str):
"""Describe an endpoint using CLI."""
print(f"\n=== Describing Endpoint: {name} ===")
result = subprocess.run(
["python", "-m", "praisonai.cli.main", "endpoints", "describe", name, "--url", SERVER_URL],
capture_output=True,
text=True,
)
if result.returncode == 0:
info = json.loads(result.stdout)
print(f"Name: {info.get('name')}")
print(f"Version: {info.get('version')}")
print(f"Description: {info.get('description')}")
return info
else:
print(f"Error: {result.stderr}")
return None
def invoke_endpoint_cli(name: str, input_data: dict):
"""Invoke an endpoint using CLI."""
print(f"\n=== Invoking Endpoint (CLI): {name} ===")
result = subprocess.run(
[
"python", "-m", "praisonai.cli.main", "endpoints", "invoke", name,
"--url", SERVER_URL,
"--input-json", json.dumps(input_data),
"--json",
"--dry-run", # Use dry-run for example
],
capture_output=True,
text=True,
)
if result.returncode == 0:
response = json.loads(result.stdout)
print(f"Run ID: {response.get('run_id')}")
print(f"Status: {response.get('status')}")
return response
else:
print(f"Error: {result.stderr}")
return None
def invoke_endpoint_http(name: str, input_data: dict):
"""Invoke an endpoint using HTTP directly."""
print(f"\n=== Invoking Endpoint (HTTP): {name} ===")
body = json.dumps({
"recipe": name,
"input": input_data,
"options": {"dry_run": True},
}).encode()
req = urllib.request.Request(
f"{SERVER_URL}/v1/recipes/run",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as response:
data = json.loads(response.read().decode())
print(f"Run ID: {data.get('run_id')}")
print(f"Status: {data.get('status')}")
print(f"Output: {data.get('output')}")
return data
except urllib.error.HTTPError as e:
print(f"HTTP Error: {e.code} - {e.read().decode()}")
return None
def main():
"""Main example function."""
print("=" * 60)
print("PraisonAI Endpoints Example")
print("=" * 60)
# Check if server is running
print("\n=== Checking Server Health ===")
if not check_server_health():
print(f"Server not running at {SERVER_URL}")
print("\nPlease start the server first:")
print(" praisonai recipe serve --port 8765")
sys.exit(1)
print("✓ Server is healthy")
# List endpoints
recipes = list_endpoints()
if not recipes:
print("No recipes available")
sys.exit(1)
# Use first available recipe
recipe_name = recipes[0]["name"]
# Describe endpoint
describe_endpoint(recipe_name)
# Invoke via CLI
invoke_endpoint_cli(recipe_name, {"query": "Hello from CLI"})
# Invoke via HTTP
invoke_endpoint_http(recipe_name, {"query": "Hello from HTTP"})
print("\n" + "=" * 60)
print("Example completed successfully!")
print("=" * 60)
if __name__ == "__main__":
main()
+19
View File
@@ -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);
+47
View File
@@ -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);
+29
View File
@@ -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);
+48
View File
@@ -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);
+33
View File
@@ -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);
+57
View File
@@ -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);
+59
View File
@@ -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);
+47
View File
@@ -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);
+64
View File
@@ -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);
+62
View File
@@ -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);
+42
View File
@@ -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);
+60
View File
@@ -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);
+71
View File
@@ -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);
+74
View File
@@ -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);
+61
View File
@@ -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);
+113
View File
@@ -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);
+30
View File
@@ -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);
+29
View File
@@ -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);
+108
View File
@@ -0,0 +1,108 @@
"""
Performance Benchmark Example for PraisonAI.
This example demonstrates how to measure and verify performance metrics
for PraisonAI CLI and SDK imports.
Usage:
python benchmark_example.py
"""
import time
import tracemalloc
import sys
def measure_import_time(module_name: str) -> float:
"""Measure import time for a module."""
# Clear from cache if already imported
if module_name in sys.modules:
del sys.modules[module_name]
start = time.time()
__import__(module_name)
elapsed = time.time() - start
return elapsed * 1000 # Return milliseconds
def measure_memory_usage(import_func) -> tuple:
"""Measure memory usage during import."""
tracemalloc.start()
import_func()
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
return current / 1024 / 1024, peak / 1024 / 1024 # Return MB
def main():
print("=" * 60)
print("PraisonAI Performance Benchmark")
print("=" * 60)
# Test 1: CLI Import Time
print("\n1. CLI Import Time")
print("-" * 40)
import os
os.environ['LOGLEVEL'] = 'WARNING'
start = time.time()
from praisonai.cli.main import PraisonAI
cli_import_time = (time.time() - start) * 1000
print(f" CLI import time: {cli_import_time:.0f}ms")
print(f" Status: {'✓ FAST' if cli_import_time < 500 else '✗ SLOW'}")
# Test 2: Memory Usage
print("\n2. Memory Usage")
print("-" * 40)
tracemalloc.start()
# Force re-import by clearing cache
for mod in list(sys.modules.keys()):
if 'praisonai' in mod:
del sys.modules[mod]
from praisonai.cli.main import PraisonAI as PraisonAI2
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f" Current memory: {current/1024/1024:.1f}MB")
print(f" Peak memory: {peak/1024/1024:.1f}MB")
print(f" Status: {'✓ LOW' if peak/1024/1024 < 50 else '✗ HIGH'}")
# Test 3: Lazy Loading Verification
print("\n3. Lazy Loading Verification")
print("-" * 40)
heavy_modules = ['litellm', 'instructor', 'praisonai.auto']
loaded = [m for m in heavy_modules if m in sys.modules]
if loaded:
print(f" Heavy modules loaded: {loaded}")
print(" Status: ✗ NOT LAZY")
else:
print(" Heavy modules NOT loaded at startup")
print(" Status: ✓ LAZY")
# Summary
print("\n" + "=" * 60)
print("Summary")
print("=" * 60)
all_pass = (
cli_import_time < 500 and
peak/1024/1024 < 50 and
len(loaded) == 0
)
if all_pass:
print("✓ All performance benchmarks PASSED")
else:
print("✗ Some benchmarks FAILED")
return 0 if all_pass else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""
Example: Lazy Imports & Fast Startup
Demonstrates how PraisonAI Agents uses lazy imports to achieve
fast startup times and reduced memory usage.
"""
import sys
import time
def example_verify_lazy_imports():
"""Verify that heavy dependencies are not loaded at import time."""
print("=" * 60)
print("Lazy Imports Verification")
print("=" * 60)
# Clear any cached imports
heavy_deps = ['litellm', 'chromadb', 'mem0', 'requests']
for dep in heavy_deps:
for key in list(sys.modules.keys()):
if key.startswith(dep):
del sys.modules[key]
# Measure import time
start = time.perf_counter()
import praisonaiagents
elapsed = (time.perf_counter() - start) * 1000
print(f"\nImport time: {elapsed:.1f}ms")
print(f"Target: <200ms")
print(f"Status: {'PASS' if elapsed < 200 else 'FAIL'}")
# Check lazy imports
print("\nLazy Import Check:")
all_lazy = True
for dep in heavy_deps:
is_lazy = dep not in sys.modules
status = "LAZY (good)" if is_lazy else "EAGER (bad)"
print(f" {dep}: {status}")
if not is_lazy:
all_lazy = False
print(f"\nAll lazy: {'PASS' if all_lazy else 'FAIL'}")
return all_lazy
def example_measure_memory():
"""Measure memory usage after import."""
print("\n" + "=" * 60)
print("Memory Usage Measurement")
print("=" * 60)
import tracemalloc
# Clear modules
for key in list(sys.modules.keys()):
if key.startswith('praisonaiagents'):
del sys.modules[key]
tracemalloc.start()
import praisonaiagents
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
current_mb = current / 1024 / 1024
peak_mb = peak / 1024 / 1024
print(f"\nCurrent memory: {current_mb:.1f}MB")
print(f"Peak memory: {peak_mb:.1f}MB")
print(f"Target: <30MB")
print(f"Status: {'PASS' if current_mb < 30 else 'WARN' if current_mb < 45 else 'FAIL'}")
return current_mb < 45
def example_specific_imports():
"""Show how to use specific imports for best performance."""
print("\n" + "=" * 60)
print("Specific Imports Example")
print("=" * 60)
# Good - specific imports are fast
print("\nGood practice - specific imports:")
print(" from praisonaiagents import Agent, Task")
# Avoid - star imports load everything
print("\nAvoid - star imports load more:")
print(" from praisonaiagents import *")
# Example of specific import
from praisonaiagents import Agent
print(f"\nAgent class loaded: {Agent.__name__}")
if __name__ == "__main__":
print("PraisonAI Agents - Lazy Imports Example")
print("=" * 60)
# Run examples
lazy_ok = example_verify_lazy_imports()
memory_ok = example_measure_memory()
example_specific_imports()
print("\n" + "=" * 60)
print("Summary")
print("=" * 60)
print(f"Lazy imports: {'PASS' if lazy_ok else 'FAIL'}")
print(f"Memory usage: {'PASS' if memory_ok else 'FAIL'}")
print(f"Overall: {'PASS' if lazy_ok and memory_ok else 'FAIL'}")
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
Example: Lite Agent (BYO-LLM)
Demonstrates the lightweight praisonaiagents.lite subpackage
that allows you to bring your own LLM client.
"""
import os
def example_custom_llm():
"""Example using a custom LLM function."""
print("=" * 60)
print("Example 1: Custom LLM Function")
print("=" * 60)
from praisonaiagents.lite import LiteAgent
# Define a simple mock LLM
def mock_llm(messages):
"""A mock LLM that echoes the last message."""
last_msg = messages[-1]["content"] if messages else "No message"
return f"Echo: {last_msg}"
agent = LiteAgent(
name="EchoAgent",
llm_fn=mock_llm,
instructions="You are a simple echo agent."
)
response = agent.chat("Hello, world!")
print(f"Response: {response}")
print(f"Chat history length: {len(agent.chat_history)}")
def example_openai_adapter():
"""Example using the built-in OpenAI adapter."""
print("\n" + "=" * 60)
print("Example 2: OpenAI Adapter")
print("=" * 60)
if not os.environ.get("OPENAI_API_KEY"):
print("Skipping: OPENAI_API_KEY not set")
return
from praisonaiagents.lite import LiteAgent, create_openai_llm_fn
# Create LLM function using OpenAI SDK directly
llm_fn = create_openai_llm_fn(model="gpt-4o-mini")
agent = LiteAgent(
name="OpenAIAgent",
llm_fn=llm_fn,
instructions="You are a helpful assistant. Keep responses brief."
)
response = agent.chat("What is 2+2?")
print(f"Response: {response}")
def example_with_tools():
"""Example using tools with LiteAgent."""
print("\n" + "=" * 60)
print("Example 3: Tools with LiteAgent")
print("=" * 60)
from praisonaiagents.lite import LiteAgent, tool
@tool
def add_numbers(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b
@tool
def multiply_numbers(a: int, b: int) -> int:
"""Multiply two numbers together."""
return a * b
def mock_llm(messages):
return "The calculation result is ready."
agent = LiteAgent(
name="MathAgent",
llm_fn=mock_llm,
tools=[add_numbers, multiply_numbers]
)
# Execute tools directly
result1 = agent.execute_tool("add_numbers", a=5, b=3)
print(f"5 + 3 = {result1.output}")
print(f"Success: {result1.success}")
result2 = agent.execute_tool("multiply_numbers", a=4, b=7)
print(f"4 * 7 = {result2.output}")
print(f"Success: {result2.success}")
def example_thread_safety():
"""Example demonstrating thread-safe operations."""
print("\n" + "=" * 60)
print("Example 4: Thread Safety")
print("=" * 60)
import threading
from praisonaiagents.lite import LiteAgent
counter = [0]
lock = threading.Lock()
def counting_llm(messages):
with lock:
counter[0] += 1
return f"Response {counter[0]}"
agent = LiteAgent(
name="ThreadSafeAgent",
llm_fn=counting_llm
)
def worker(thread_id):
for i in range(5):
agent.chat(f"Message from thread {thread_id}, iteration {i}")
threads = [
threading.Thread(target=worker, args=(i,))
for i in range(3)
]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Total messages processed: {counter[0]}")
print(f"Chat history length: {len(agent.chat_history)}")
print("Thread safety test: PASS")
if __name__ == "__main__":
print("PraisonAI Agents - Lite Package Examples")
print("=" * 60)
example_custom_llm()
example_openai_adapter()
example_with_tools()
example_thread_safety()
print("\n" + "=" * 60)
print("All examples completed!")
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""
Runner for all performance examples.
Usage:
# Run without real API (mock only)
python run_all_examples.py
# Run with real API
RUN_REAL_KEY_TESTS=1 OPENAI_API_KEY=your-key python run_all_examples.py
"""
import os
import sys
import subprocess
def run_example(name, script_path):
"""Run an example script and report results."""
print(f"\n{'=' * 60}")
print(f"Running: {name}")
print("=" * 60)
result = subprocess.run(
[sys.executable, script_path],
capture_output=True,
text=True,
env=os.environ.copy()
)
print(result.stdout)
if result.stderr:
print(f"STDERR: {result.stderr}")
return result.returncode == 0
def main():
"""Run all performance examples."""
print("PraisonAI Agents - Performance Examples Runner")
print("=" * 60)
# Check environment
real_tests = os.environ.get("RUN_REAL_KEY_TESTS")
has_openai = bool(os.environ.get("OPENAI_API_KEY"))
print(f"RUN_REAL_KEY_TESTS: {real_tests or 'not set'}")
print(f"OPENAI_API_KEY: {'set' if has_openai else 'not set'}")
# Get script directory
script_dir = os.path.dirname(os.path.abspath(__file__))
# Define examples
examples = [
("Lazy Imports", os.path.join(script_dir, "lazy_imports_example.py")),
("Lite Agent", os.path.join(script_dir, "lite_agent_example.py")),
("Thread Safety", os.path.join(script_dir, "thread_safety_example.py")),
]
# Run examples
results = []
for name, path in examples:
if os.path.exists(path):
success = run_example(name, path)
results.append((name, success))
else:
print(f"Skipping {name}: {path} not found")
results.append((name, None))
# Summary
print("\n" + "=" * 60)
print("Summary")
print("=" * 60)
passed = 0
failed = 0
skipped = 0
for name, success in results:
if success is None:
status = "SKIPPED"
skipped += 1
elif success:
status = "PASS"
passed += 1
else:
status = "FAIL"
failed += 1
print(f" {name}: {status}")
print(f"\nTotal: {passed} passed, {failed} failed, {skipped} skipped")
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""
Example: Thread-Safe Agent State
Demonstrates thread-safe chat history and cache management
in PraisonAI Agents.
"""
import os
import threading
import time
def example_lite_agent_thread_safety():
"""Demonstrate thread safety with LiteAgent."""
print("=" * 60)
print("Example 1: LiteAgent Thread Safety")
print("=" * 60)
from praisonaiagents.lite import LiteAgent
# Counter for tracking responses
response_count = [0]
count_lock = threading.Lock()
def counting_llm(messages):
with count_lock:
response_count[0] += 1
return f"Response #{response_count[0]}"
agent = LiteAgent(
name="ThreadSafeAgent",
llm_fn=counting_llm
)
errors = []
def worker(thread_id):
try:
for i in range(10):
agent.chat(f"Thread {thread_id}, message {i}")
except Exception as e:
errors.append(e)
# Create and start threads
threads = [
threading.Thread(target=worker, args=(i,))
for i in range(5)
]
start = time.perf_counter()
for t in threads:
t.start()
for t in threads:
t.join()
elapsed = time.perf_counter() - start
print(f"Threads: 5")
print(f"Messages per thread: 10")
print(f"Total messages: {response_count[0]}")
print(f"Chat history length: {len(agent.chat_history)}")
print(f"Errors: {len(errors)}")
print(f"Time: {elapsed:.2f}s")
print(f"Status: {'PASS' if len(errors) == 0 else 'FAIL'}")
def example_agent_locks():
"""Verify Agent class has proper locks."""
print("\n" + "=" * 60)
print("Example 2: Agent Lock Verification")
print("=" * 60)
from praisonaiagents import Agent
agent = Agent(
name="LockTestAgent",
instructions="Test agent",
verbose=False
)
# Check for locks
has_history_lock = hasattr(agent, '_history_lock')
has_cache_lock = hasattr(agent, '_cache_lock')
print(f"Has _history_lock: {has_history_lock}")
print(f"Has _cache_lock: {has_cache_lock}")
# Verify lock types
if has_history_lock:
lock_type = type(agent._history_lock).__name__
print(f"_history_lock type: {lock_type}")
if has_cache_lock:
lock_type = type(agent._cache_lock).__name__
print(f"_cache_lock type: {lock_type}")
print(f"Status: {'PASS' if has_history_lock and has_cache_lock else 'FAIL'}")
def example_concurrent_real_api():
"""Demonstrate concurrent API calls (requires API key)."""
print("\n" + "=" * 60)
print("Example 3: Concurrent Real API Calls")
print("=" * 60)
if not os.environ.get("OPENAI_API_KEY"):
print("Skipping: OPENAI_API_KEY not set")
return
from praisonaiagents import Agent
agent = Agent(
name="ConcurrentAgent",
instructions="Reply with one word only.",
llm="gpt-4o-mini",
verbose=False
)
results = []
results_lock = threading.Lock()
errors = []
def worker(thread_id):
try:
response = agent.chat(f"Say hello {thread_id}")
with results_lock:
results.append((thread_id, response))
except Exception as e:
errors.append(e)
threads = [
threading.Thread(target=worker, args=(i,))
for i in range(3)
]
start = time.perf_counter()
for t in threads:
t.start()
for t in threads:
t.join()
elapsed = time.perf_counter() - start
print(f"Concurrent requests: 3")
print(f"Successful responses: {len(results)}")
print(f"Errors: {len(errors)}")
print(f"Time: {elapsed:.2f}s")
print(f"Status: {'PASS' if len(errors) == 0 else 'FAIL'}")
if __name__ == "__main__":
print("PraisonAI Agents - Thread Safety Examples")
print("=" * 60)
example_lite_agent_thread_safety()
example_agent_locks()
example_concurrent_real_api()
print("\n" + "=" * 60)
print("All examples completed!")
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/env python3
"""
Run All Recipes - Test harness for all Agent-Recipes.
Runs each recipe in dry-run mode to verify CLI parity and run.json generation.
Requires: OPENAI_API_KEY environment variable for integration tests.
"""
import json
import os
import subprocess
import sys
from dataclasses import dataclass
from typing import List
# Recipe definitions
RECIPES = [
"ai-audio-normalizer",
"ai-audio-splitter",
"ai-changelog-generator",
"ai-code-documenter",
"ai-csv-cleaner",
"ai-data-profiler",
"ai-dependency-auditor",
"ai-doc-translator",
"ai-folder-packager",
"ai-image-cataloger",
"ai-image-optimizer",
"ai-image-resizer",
"ai-json-to-csv",
"ai-markdown-to-pdf",
"ai-pdf-summarizer",
"ai-pdf-to-markdown",
"ai-podcast-cleaner",
"ai-repo-readme",
"ai-schema-generator",
"ai-screenshot-ocr",
"ai-sitemap-scraper",
"ai-slide-to-notes",
"ai-url-to-markdown",
"ai-video-editor",
"ai-video-thumbnails",
"ai-video-to-gif",
]
@dataclass
class TestResult:
"""Result of a recipe test."""
recipe: str
status: str # pass, fail, skip
reason: str = ""
duration: float = 0.0
def check_api_keys() -> dict:
"""Check available API keys."""
keys = {
"OPENAI_API_KEY": bool(os.environ.get("OPENAI_API_KEY")),
"ANTHROPIC_API_KEY": bool(os.environ.get("ANTHROPIC_API_KEY")),
"GOOGLE_API_KEY": bool(os.environ.get("GOOGLE_API_KEY")),
}
return keys
def check_external_deps() -> dict:
"""Check external dependencies."""
import shutil
deps = {
"ffmpeg": shutil.which("ffmpeg") is not None,
"ffprobe": shutil.which("ffprobe") is not None,
"pdftotext": shutil.which("pdftotext") is not None,
"pandoc": shutil.which("pandoc") is not None,
"git": shutil.which("git") is not None,
}
return deps
def run_recipe_dry_run(recipe: str, timeout: int = 30) -> TestResult:
"""Run a recipe in dry-run mode."""
import time
start = time.time()
try:
# Use praison templates run command with dry-run
cmd = ["praison", "templates", "run", recipe, "--dry-run", "dummy_input"]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
)
duration = time.time() - start
if result.returncode == 0:
return TestResult(recipe, "pass", "dry-run completed", duration)
else:
# Check if it's a missing dependency error
stderr = result.stderr.lower()
if "not found" in stderr or "missing" in stderr:
return TestResult(recipe, "skip", f"Missing dependency: {result.stderr[:100]}", duration)
return TestResult(recipe, "fail", f"Exit code {result.returncode}: {result.stderr[:100]}", duration)
except subprocess.TimeoutExpired:
return TestResult(recipe, "fail", "Timeout", timeout)
except FileNotFoundError:
return TestResult(recipe, "skip", "praison CLI not found", 0)
except Exception as e:
return TestResult(recipe, "fail", str(e), 0)
def run_all_recipes(dry_run_only: bool = True) -> List[TestResult]:
"""Run all recipes and collect results."""
results = []
print("=" * 60)
print("RECIPE TEST HARNESS")
print("=" * 60)
# Check prerequisites
keys = check_api_keys()
deps = check_external_deps()
print("\nAPI Keys:")
for key, present in keys.items():
status = "✓" if present else "✗"
print(f" {status} {key}")
print("\nExternal Dependencies:")
for dep, present in deps.items():
status = "✓" if present else "✗"
print(f" {status} {dep}")
print("\n" + "=" * 60)
print(f"Running {len(RECIPES)} recipes (dry-run={dry_run_only})")
print("=" * 60 + "\n")
for i, recipe in enumerate(RECIPES, 1):
print(f"[{i:2d}/{len(RECIPES)}] {recipe}...", end=" ", flush=True)
if dry_run_only:
result = run_recipe_dry_run(recipe)
else:
# Full run would go here
result = run_recipe_dry_run(recipe)
results.append(result)
if result.status == "pass":
print(f"✓ ({result.duration:.1f}s)")
elif result.status == "skip":
print(f"⊘ SKIP: {result.reason}")
else:
print(f"✗ FAIL: {result.reason}")
return results
def print_summary(results: List[TestResult]):
"""Print test summary."""
passed = sum(1 for r in results if r.status == "pass")
failed = sum(1 for r in results if r.status == "fail")
skipped = sum(1 for r in results if r.status == "skip")
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
print(f" Passed: {passed}")
print(f" Failed: {failed}")
print(f" Skipped: {skipped}")
print(f" Total: {len(results)}")
if failed > 0:
print("\nFailed recipes:")
for r in results:
if r.status == "fail":
print(f" - {r.recipe}: {r.reason}")
return failed == 0
def main():
"""Main entry point."""
import argparse
parser = argparse.ArgumentParser(description="Run all Agent-Recipes tests")
parser.add_argument("--full", action="store_true", help="Run full tests (not just dry-run)")
parser.add_argument("--recipe", help="Run specific recipe only")
parser.add_argument("--json", action="store_true", help="Output JSON results")
args = parser.parse_args()
if args.recipe:
results = [run_recipe_dry_run(args.recipe)]
else:
results = run_all_recipes(dry_run_only=not args.full)
if args.json:
output = [{"recipe": r.recipe, "status": r.status, "reason": r.reason} for r in results]
print(json.dumps(output, indent=2))
else:
success = print_summary(results)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
+128
View File
@@ -0,0 +1,128 @@
"""
Example: Using pandas as a custom tool with PraisonAI Agent
This example shows how to wrap pandas functions as tools
that can be used with Agent().
"""
import pandas as pd
from io import StringIO
# Define pandas wrapper tools
def analyze_csv_data(csv_data: str) -> str:
"""Analyze CSV data using pandas DataFrame.
Args:
csv_data: CSV-formatted string data
Returns:
Analysis summary including shape, columns, and statistics
"""
df = pd.read_csv(StringIO(csv_data))
summary = f"""DataFrame Analysis:
- Shape: {df.shape[0]} rows, {df.shape[1]} columns
- Columns: {list(df.columns)}
- Data Types: {dict(df.dtypes)}
- Summary Statistics:
{df.describe().to_string()}"""
return summary
def filter_dataframe(csv_data: str, column: str, condition: str, value: str) -> str:
"""Filter a DataFrame based on a condition.
Args:
csv_data: CSV-formatted string data
column: Column name to filter on
condition: Condition type ('equals', 'greater', 'less', 'contains')
value: Value to compare against
Returns:
Filtered data as CSV string
"""
df = pd.read_csv(StringIO(csv_data))
if condition == 'equals':
filtered = df[df[column] == value]
elif condition == 'greater':
filtered = df[df[column] > float(value)]
elif condition == 'less':
filtered = df[df[column] < float(value)]
elif condition == 'contains':
filtered = df[df[column].str.contains(value, na=False)]
else:
return f"Unknown condition: {condition}"
return filtered.to_csv(index=False)
def calculate_statistics(csv_data: str, column: str) -> str:
"""Calculate statistics for a specific column.
Args:
csv_data: CSV-formatted string data
column: Column name to analyze
Returns:
Statistics summary for the column
"""
df = pd.read_csv(StringIO(csv_data))
if column not in df.columns:
return f"Column '{column}' not found. Available: {list(df.columns)}"
col = df[column]
if pd.api.types.is_numeric_dtype(col):
stats = f"""Statistics for '{column}':
- Count: {col.count()}
- Mean: {col.mean():.2f}
- Std: {col.std():.2f}
- Min: {col.min()}
- Max: {col.max()}
- Median: {col.median()}"""
else:
stats = f"""Statistics for '{column}':
- Count: {col.count()}
- Unique: {col.nunique()}
- Top: {col.mode().iloc[0] if len(col.mode()) > 0 else 'N/A'}
- Sample values: {list(col.head(3))}"""
return stats
if __name__ == "__main__":
# Test data
test_csv = """name,age,salary,department
Alice,30,50000,Engineering
Bob,25,45000,Marketing
Charlie,35,60000,Engineering
Diana,28,52000,Sales
Eve,32,55000,Marketing"""
print("=== Test: analyze_csv_data ===")
print(analyze_csv_data(test_csv))
print()
print("=== Test: filter_dataframe ===")
print(filter_dataframe(test_csv, "age", "greater", "28"))
print()
print("=== Test: calculate_statistics ===")
print(calculate_statistics(test_csv, "salary"))
print()
# Example with Agent (requires praisonaiagents)
print("=== Agent Usage Example ===")
print("""
from praisonaiagents import Agent
agent = Agent(
name="data_analyst",
role="Data Analyst",
goal="Analyze data using pandas tools",
tools=[analyze_csv_data, filter_dataframe, calculate_statistics]
)
result = agent.start("Analyze this data and find the average salary")
""")
+25
View File
@@ -0,0 +1,25 @@
"""Example: Discover tools from installed packages."""
# Discover from praisonaiagents built-in tools
try:
from praisonaiagents.tools import TOOL_MAPPINGS
print("Built-in tools from praisonaiagents:")
for name in list(TOOL_MAPPINGS.keys())[:10]:
print(f" - {name}")
print(f" ... and {len(TOOL_MAPPINGS) - 10} more")
except ImportError:
print("praisonaiagents not installed")
# Discover from praisonai_tools
try:
import praisonai_tools
print("\nTools from praisonai_tools:")
# Check for video module
try:
from praisonai_tools import video
print(" - praisonai_tools.video (video editing)")
except ImportError:
pass
except ImportError:
print("praisonai_tools not installed")
+18
View File
@@ -0,0 +1,18 @@
"""Example: Resolve tool names to callable tools."""
from praisonai.templates.tool_override import (
create_tool_registry_with_overrides,
resolve_tools,
)
# Create registry with defaults
registry = create_tool_registry_with_overrides(include_defaults=True)
print(f"Registry contains {len(registry)} tools")
# Resolve tool names
tool_names = ["shell_tool", "internet_search"]
resolved = resolve_tools(tool_names, registry=registry)
print(f"\nResolved {len(resolved)} tools:")
for tool in resolved:
print(f" - {getattr(tool, '__name__', type(tool).__name__)}")
+14
View File
@@ -0,0 +1,14 @@
"""Example: Configure tool sources for templates."""
from praisonai.templates.tool_override import create_tool_registry_with_overrides
# Create registry with tools_sources
registry = create_tool_registry_with_overrides(
tools_sources=["praisonai_tools.video"],
include_defaults=True,
)
print(f"Registry with tools_sources: {len(registry)} tools")
# List some tools
for name in list(registry.keys())[:10]:
print(f" - {name}")
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""
Import time benchmark for praisonaiagents.
This benchmark measures the import time of the praisonaiagents package
and verifies that heavy dependencies are not loaded at import time.
Usage:
python benchmarks/import_time.py
CI Gate:
- PASS: import time < 200ms
- WARN: import time 200-300ms
- FAIL: import time > 300ms
"""
import sys
import time
import statistics
def clear_modules():
"""Clear all praisonai and litellm related modules from cache."""
to_remove = [m for m in sys.modules.keys()
if 'praison' in m or 'litellm' in m]
for mod in to_remove:
del sys.modules[mod]
def measure_import_time(runs: int = 5) -> dict:
"""
Measure import time over multiple runs.
Returns:
dict with min, max, median, mean times in milliseconds
"""
times = []
for _ in range(runs):
clear_modules()
start = time.perf_counter()
import praisonaiagents # noqa: F401
end = time.perf_counter()
times.append((end - start) * 1000)
# Clean up for next run
if 'praisonaiagents' in sys.modules:
del sys.modules['praisonaiagents']
times.sort()
return {
'times': times,
'min': min(times),
'max': max(times),
'median': statistics.median(times),
'mean': statistics.mean(times),
'stdev': statistics.stdev(times) if len(times) > 1 else 0,
}
def check_lazy_imports() -> dict:
"""
Check that heavy dependencies are NOT loaded after importing praisonaiagents.
Returns:
dict with module names and whether they are loaded
"""
clear_modules()
import praisonaiagents # noqa: F401
heavy_modules = [
'litellm',
'chromadb',
'mem0',
'requests',
]
results = {}
for mod in heavy_modules:
results[mod] = mod in sys.modules
return results
def main():
"""Run the benchmark and print results."""
print("=" * 60)
print("PraisonAI Agents Import Time Benchmark")
print("=" * 60)
# Measure import time
print("\n[1/2] Measuring import time (5 runs)...")
results = measure_import_time(runs=5)
print(f"\nImport times (ms): {[round(t, 1) for t in results['times']]}")
print(f" Min: {results['min']:.1f} ms")
print(f" Max: {results['max']:.1f} ms")
print(f" Median: {results['median']:.1f} ms")
print(f" Mean: {results['mean']:.1f} ms")
print(f" StdDev: {results['stdev']:.1f} ms")
# Check lazy imports
print("\n[2/2] Checking lazy imports...")
lazy_results = check_lazy_imports()
print("\nHeavy modules loaded at import time:")
all_lazy = True
for mod, loaded in lazy_results.items():
status = "LOADED (BAD)" if loaded else "NOT LOADED (GOOD)"
print(f" {mod}: {status}")
if loaded:
all_lazy = False
# Determine pass/fail
print("\n" + "=" * 60)
print("RESULTS")
print("=" * 60)
median_time = results['median']
if median_time < 200:
time_status = "PASS"
time_msg = f"Import time {median_time:.0f}ms < 200ms target"
elif median_time < 300:
time_status = "WARN"
time_msg = f"Import time {median_time:.0f}ms between 200-300ms"
else:
time_status = "FAIL"
time_msg = f"Import time {median_time:.0f}ms > 300ms limit"
lazy_status = "PASS" if all_lazy else "FAIL"
lazy_msg = "All heavy deps lazy loaded" if all_lazy else "Some heavy deps loaded eagerly"
print(f"\nImport Time: [{time_status}] {time_msg}")
print(f"Lazy Imports: [{lazy_status}] {lazy_msg}")
overall = "PASS" if time_status != "FAIL" and lazy_status == "PASS" else "FAIL"
print(f"\nOverall: [{overall}]")
# Exit code for CI
return 0 if overall == "PASS" else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""
Memory usage benchmark for praisonaiagents.
This benchmark measures the memory footprint of importing praisonaiagents
and verifies it stays within acceptable limits.
Usage:
python benchmarks/memory_usage.py
CI Gate:
- PASS: memory < 30MB
- WARN: memory 30-45MB
- FAIL: memory > 45MB
"""
import sys
import tracemalloc
def clear_modules():
"""Clear all praisonai and litellm related modules from cache."""
to_remove = [m for m in sys.modules.keys()
if 'praison' in m or 'litellm' in m]
for mod in to_remove:
del sys.modules[mod]
def measure_memory() -> dict:
"""
Measure memory usage after importing praisonaiagents.
Returns:
dict with current and peak memory in MB
"""
clear_modules()
tracemalloc.start()
import praisonaiagents # noqa: F401
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
return {
'current_bytes': current,
'peak_bytes': peak,
'current_mb': current / 1024 / 1024,
'peak_mb': peak / 1024 / 1024,
}
def main():
"""Run the benchmark and print results."""
print("=" * 60)
print("PraisonAI Agents Memory Usage Benchmark")
print("=" * 60)
print("\nMeasuring memory usage...")
results = measure_memory()
print(f"\nMemory Usage:")
print(f" Current: {results['current_mb']:.1f} MB")
print(f" Peak: {results['peak_mb']:.1f} MB")
# Determine pass/fail based on peak memory
peak_mb = results['peak_mb']
print("\n" + "=" * 60)
print("RESULTS")
print("=" * 60)
if peak_mb < 30:
status = "PASS"
msg = f"Memory {peak_mb:.1f}MB < 30MB target"
elif peak_mb < 45:
status = "WARN"
msg = f"Memory {peak_mb:.1f}MB between 30-45MB"
else:
status = "FAIL"
msg = f"Memory {peak_mb:.1f}MB > 45MB limit"
print(f"\nMemory: [{status}] {msg}")
# Exit code for CI
return 0 if status != "FAIL" else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Example: Using PraisonAI Agents Lite Package
The lite package provides a minimal agent framework without heavy dependencies
like litellm. It's ideal for users who want to bring their own LLM client.
This example shows:
1. Creating a LiteAgent with a custom LLM function
2. Using the built-in OpenAI adapter
3. Thread-safe chat history management
"""
import os
def example_custom_llm():
"""Example 1: Using a custom LLM function."""
from praisonaiagents.lite import LiteAgent
# Define your own LLM function
def my_simple_llm(messages):
"""A mock LLM that echoes back the last message."""
last_msg = messages[-1]["content"] if messages else "No message"
return f"Echo: {last_msg}"
agent = LiteAgent(
name="EchoAgent",
llm_fn=my_simple_llm,
instructions="You are a simple echo agent."
)
response = agent.chat("Hello, world!")
print(f"Response: {response}")
print(f"Chat history length: {len(agent.chat_history)}")
def example_openai_adapter():
"""Example 2: Using the built-in OpenAI adapter."""
from praisonaiagents.lite import LiteAgent, create_openai_llm_fn
# Check for API key
if not os.environ.get("OPENAI_API_KEY"):
print("Skipping OpenAI example - OPENAI_API_KEY not set")
return
# Create LLM function using OpenAI SDK directly (no litellm)
llm_fn = create_openai_llm_fn(model="gpt-4o-mini")
agent = LiteAgent(
name="OpenAIAgent",
llm_fn=llm_fn,
instructions="You are a helpful assistant. Keep responses brief."
)
response = agent.chat("What is 2+2?")
print(f"OpenAI Response: {response}")
def example_with_tools():
"""Example 3: Using tools with LiteAgent."""
from praisonaiagents.lite import LiteAgent, tool
@tool
def add_numbers(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b
def mock_llm(messages):
return "The sum is 8"
agent = LiteAgent(
name="MathAgent",
llm_fn=mock_llm,
tools=[add_numbers]
)
# Execute tool directly
result = agent.execute_tool("add_numbers", a=5, b=3)
print(f"Tool result: {result.output}")
print(f"Tool success: {result.success}")
def example_anthropic_adapter():
"""Example 4: Using the built-in Anthropic adapter."""
from praisonaiagents.lite import LiteAgent, create_anthropic_llm_fn
# Check for API key
if not os.environ.get("ANTHROPIC_API_KEY"):
print("Skipping Anthropic example - ANTHROPIC_API_KEY not set")
return
# Create LLM function using Anthropic SDK directly
llm_fn = create_anthropic_llm_fn(model="claude-3-5-sonnet-20241022")
agent = LiteAgent(
name="ClaudeAgent",
llm_fn=llm_fn,
instructions="You are a helpful assistant. Keep responses brief."
)
response = agent.chat("What is the capital of France?")
print(f"Claude Response: {response}")
if __name__ == "__main__":
print("=" * 60)
print("PraisonAI Agents Lite Examples")
print("=" * 60)
print("\n[1] Custom LLM Function:")
example_custom_llm()
print("\n[2] OpenAI Adapter:")
example_openai_adapter()
print("\n[3] Tools with LiteAgent:")
example_with_tools()
print("\n[4] Anthropic Adapter:")
example_anthropic_adapter()
print("\n" + "=" * 60)
print("Examples completed!")
+322 -234
View File
@@ -1,5 +1,8 @@
"""
Praison AI Agents - A package for hierarchical AI agent task execution
This module uses lazy loading to minimize import time and memory usage.
Heavy dependencies like litellm are only loaded when actually needed.
"""
# Apply warning patch BEFORE any imports to intercept warnings at the source
@@ -11,51 +14,18 @@ from . import _logging
# Configure root logger after logging is initialized
_logging.configure_root_logger()
# Now import everything else
from .agent.agent import Agent
from .agent.image_agent import ImageAgent
from .agent.context_agent import ContextAgent, create_context_agent
from .agent.deep_research_agent import (
DeepResearchAgent,
DeepResearchResponse,
Citation,
ReasoningStep,
WebSearchCall,
CodeExecutionStep,
MCPCall,
FileSearchCall,
Provider
)
from .agent.query_rewriter_agent import (
QueryRewriterAgent,
RewriteStrategy,
RewriteResult
)
from .agent.prompt_expander_agent import (
PromptExpanderAgent,
ExpandStrategy,
ExpandResult
)
from .agents.agents import PraisonAIAgents
from .task.task import Task
# Import configuration (lightweight, no heavy deps)
from . import _config
# Lightweight imports that don't trigger heavy dependency chains
from .tools.tools import Tools
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
# Knowledge is lazy-loaded to avoid importing heavy deps (chromadb, mem0) at startup
# MCP support (optional)
try:
from .mcp.mcp import MCP
_mcp_available = True
except ImportError:
_mcp_available = False
MCP = None
from .session import Session
# Memory is lazy-loaded to avoid importing chromadb at startup
from .db import db
from .obs import obs
# Workflows - import from dedicated workflows module
# Workflows - lightweight module
from .workflows import (
Workflow, WorkflowStep, WorkflowContext, StepResult,
Route, Parallel, Loop, Repeat,
@@ -64,80 +34,18 @@ from .workflows import (
)
from .guardrails import GuardrailResult, LLMGuardrail
# Fast Context support (lazy loaded to avoid performance impact)
def __getattr__(name):
"""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":
from praisonaiagents.context.fast import FastContextResult
return FastContextResult
elif name == "FileMatch":
from praisonaiagents.context.fast import FileMatch
return FileMatch
elif name == "LineRange":
from praisonaiagents.context.fast import LineRange
return LineRange
# Agent Skills support (lazy loaded for zero performance impact)
elif name == "SkillManager":
from praisonaiagents.skills import SkillManager
return SkillManager
elif name == "SkillProperties":
from praisonaiagents.skills import SkillProperties
return SkillProperties
elif name == "SkillMetadata":
from praisonaiagents.skills import SkillMetadata
return SkillMetadata
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
try:
from .planning import (
Plan,
PlanStep,
TodoList,
TodoItem,
PlanStorage,
PlanningAgent,
ApprovalCallback,
READ_ONLY_TOOLS,
RESTRICTED_TOOLS,
)
_planning_available = True
except ImportError:
_planning_available = False
Plan = None
PlanStep = None
TodoList = None
TodoItem = None
PlanStorage = None
PlanningAgent = None
ApprovalCallback = None
READ_ONLY_TOOLS = []
RESTRICTED_TOOLS = []
# Handoff - lightweight
from .agent.handoff import Handoff, handoff, handoff_filters, RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions
# Flow display
# Flow display (optional)
try:
from .flow_display import FlowDisplay, track_workflow
except ImportError:
FlowDisplay = None
track_workflow = None
# Main display utilities - these are used frequently so import directly
# but they don't pull in litellm
from .main import (
TaskOutput,
ReflectionOutput,
@@ -154,98 +62,296 @@ from .main import (
async_display_callbacks,
)
# Telemetry support (lazy loaded)
try:
from .telemetry import (
get_telemetry,
enable_telemetry,
disable_telemetry,
enable_performance_mode,
disable_performance_mode,
cleanup_telemetry_resources,
MinimalTelemetry,
TelemetryCollector
)
_telemetry_available = True
except ImportError:
# Telemetry not available - provide stub functions
_telemetry_available = False
def get_telemetry():
return None
def enable_telemetry(*args, **kwargs):
import logging
logging.warning(
"Telemetry not available. Install with: pip install praisonaiagents[telemetry]"
)
return None
def disable_telemetry():
pass
def enable_performance_mode():
pass
def disable_performance_mode():
pass
def cleanup_telemetry_resources():
pass
MinimalTelemetry = None
TelemetryCollector = None
# Module-level caches for lazy-loaded classes
_lazy_cache = {}
# Add Agents as an alias for PraisonAIAgents
Agents = PraisonAIAgents
# Flags for optional modules
_mcp_available = None
_planning_available = None
_telemetry_available = None
_agui_available = None
_a2a_available = None
# Enable PostHog telemetry by default with actual event posting
# PostHog events are posted by default unless explicitly disabled
# Users can:
# - Disable completely: PRAISONAI_DISABLE_TELEMETRY=true (or DO_NOT_TRACK=true)
# - Enable performance mode: PRAISONAI_PERFORMANCE_MODE=true (minimal overhead, limited events)
# - Enable full telemetry: PRAISONAI_FULL_TELEMETRY=true (detailed tracking)
# - Legacy opt-in mode: PRAISONAI_AUTO_INSTRUMENT=true
if _telemetry_available:
def __getattr__(name):
"""
Lazy load heavy modules to avoid impacting package load time.
This function is called when an attribute is not found in the module.
It enables deferred loading of heavy dependencies like litellm.
"""
global _mcp_available, _planning_available, _telemetry_available
global _agui_available, _a2a_available
# Return cached values if available
if name in _lazy_cache:
return _lazy_cache[name]
# Agent and related classes (triggers litellm import chain)
if name == "Agent":
from .agent.agent import Agent
_lazy_cache[name] = Agent
return Agent
elif name == "ImageAgent":
from .agent.image_agent import ImageAgent
_lazy_cache[name] = ImageAgent
return ImageAgent
elif name == "ContextAgent":
from .agent.context_agent import ContextAgent
_lazy_cache[name] = ContextAgent
return ContextAgent
elif name == "create_context_agent":
from .agent.context_agent import create_context_agent
_lazy_cache[name] = create_context_agent
return create_context_agent
elif name == "DeepResearchAgent":
from .agent.deep_research_agent import DeepResearchAgent
_lazy_cache[name] = DeepResearchAgent
return DeepResearchAgent
elif name == "DeepResearchResponse":
from .agent.deep_research_agent import DeepResearchResponse
_lazy_cache[name] = DeepResearchResponse
return DeepResearchResponse
elif name == "Citation":
from .agent.deep_research_agent import Citation
_lazy_cache[name] = Citation
return Citation
elif name == "ReasoningStep":
from .agent.deep_research_agent import ReasoningStep
_lazy_cache[name] = ReasoningStep
return ReasoningStep
elif name == "WebSearchCall":
from .agent.deep_research_agent import WebSearchCall
_lazy_cache[name] = WebSearchCall
return WebSearchCall
elif name == "CodeExecutionStep":
from .agent.deep_research_agent import CodeExecutionStep
_lazy_cache[name] = CodeExecutionStep
return CodeExecutionStep
elif name == "MCPCall":
from .agent.deep_research_agent import MCPCall
_lazy_cache[name] = MCPCall
return MCPCall
elif name == "FileSearchCall":
from .agent.deep_research_agent import FileSearchCall
_lazy_cache[name] = FileSearchCall
return FileSearchCall
elif name == "Provider":
from .agent.deep_research_agent import Provider
_lazy_cache[name] = Provider
return Provider
elif name == "QueryRewriterAgent":
from .agent.query_rewriter_agent import QueryRewriterAgent
_lazy_cache[name] = QueryRewriterAgent
return QueryRewriterAgent
elif name == "RewriteStrategy":
from .agent.query_rewriter_agent import RewriteStrategy
_lazy_cache[name] = RewriteStrategy
return RewriteStrategy
elif name == "RewriteResult":
from .agent.query_rewriter_agent import RewriteResult
_lazy_cache[name] = RewriteResult
return RewriteResult
elif name == "PromptExpanderAgent":
from .agent.prompt_expander_agent import PromptExpanderAgent
_lazy_cache[name] = PromptExpanderAgent
return PromptExpanderAgent
elif name == "ExpandStrategy":
from .agent.prompt_expander_agent import ExpandStrategy
_lazy_cache[name] = ExpandStrategy
return ExpandStrategy
elif name == "ExpandResult":
from .agent.prompt_expander_agent import ExpandResult
_lazy_cache[name] = ExpandResult
return ExpandResult
# PraisonAIAgents and Agents alias
elif name == "PraisonAIAgents":
from .agents.agents import PraisonAIAgents
_lazy_cache[name] = PraisonAIAgents
_lazy_cache["Agents"] = PraisonAIAgents # Also cache alias
return PraisonAIAgents
elif name == "Agents":
from .agents.agents import PraisonAIAgents
_lazy_cache["PraisonAIAgents"] = PraisonAIAgents
_lazy_cache[name] = PraisonAIAgents
return PraisonAIAgents
# Task
elif name == "Task":
from .task.task import Task
_lazy_cache[name] = Task
return Task
# AutoAgents
elif name == "AutoAgents":
from .agents.autoagents import AutoAgents
_lazy_cache[name] = AutoAgents
return AutoAgents
# Session (imports requests)
elif name == "Session":
from .session import Session
_lazy_cache[name] = Session
return Session
# MCP support (optional)
elif name == "MCP":
if _mcp_available is None:
try:
from .mcp.mcp import MCP as _MCP
_lazy_cache[name] = _MCP
return _MCP
except ImportError:
return None
return _lazy_cache.get(name)
# Knowledge module (imports chromadb, mem0)
elif name == "Knowledge":
from praisonaiagents.knowledge.knowledge import Knowledge
_lazy_cache[name] = Knowledge
return Knowledge
elif name == "Chunking":
from praisonaiagents.knowledge.chunking import Chunking
_lazy_cache[name] = Chunking
return Chunking
# FastContext support
elif name == "FastContext":
from praisonaiagents.context.fast import FastContext
_lazy_cache[name] = FastContext
return FastContext
elif name == "FastContextResult":
from praisonaiagents.context.fast import FastContextResult
_lazy_cache[name] = FastContextResult
return FastContextResult
elif name == "FileMatch":
from praisonaiagents.context.fast import FileMatch
_lazy_cache[name] = FileMatch
return FileMatch
elif name == "LineRange":
from praisonaiagents.context.fast import LineRange
_lazy_cache[name] = LineRange
return LineRange
# Agent Skills support (lazy loaded for zero performance impact)
elif name == "SkillManager":
from praisonaiagents.skills import SkillManager
_lazy_cache[name] = SkillManager
return SkillManager
elif name == "SkillProperties":
from praisonaiagents.skills import SkillProperties
_lazy_cache[name] = SkillProperties
return SkillProperties
elif name == "SkillMetadata":
from praisonaiagents.skills import SkillMetadata
_lazy_cache[name] = SkillMetadata
return SkillMetadata
elif name == "SkillLoader":
from praisonaiagents.skills import SkillLoader
_lazy_cache[name] = SkillLoader
return SkillLoader
# Memory module (imports chromadb)
elif name == "Memory":
from praisonaiagents.memory.memory import Memory
_lazy_cache[name] = Memory
return Memory
# Planning mode support (lazy)
elif name in ("Plan", "PlanStep", "TodoList", "TodoItem", "PlanStorage",
"PlanningAgent", "ApprovalCallback", "READ_ONLY_TOOLS",
"RESTRICTED_TOOLS"):
try:
from . import planning as _planning
result = getattr(_planning, name)
_lazy_cache[name] = result
return result
except ImportError:
if name in ("READ_ONLY_TOOLS", "RESTRICTED_TOOLS"):
return []
return None
# Telemetry support (lazy loaded)
elif name in ("get_telemetry", "enable_telemetry", "disable_telemetry",
"enable_performance_mode", "disable_performance_mode",
"cleanup_telemetry_resources", "MinimalTelemetry", "TelemetryCollector"):
try:
from . import telemetry as _telemetry
result = getattr(_telemetry, name)
_lazy_cache[name] = result
return result
except ImportError:
# Provide stub functions
if name == "get_telemetry":
return lambda: None
elif name == "enable_telemetry":
import logging
def _stub(*args, **kwargs):
logging.warning(
"Telemetry not available. Install with: pip install praisonaiagents[telemetry]"
)
return None
return _stub
elif name in ("disable_telemetry", "enable_performance_mode",
"disable_performance_mode", "cleanup_telemetry_resources"):
return lambda: None
return None
# AG-UI support (optional, lazy loaded)
elif name == "AGUI":
try:
from praisonaiagents.ui.agui import AGUI
_lazy_cache[name] = AGUI
return AGUI
except ImportError:
return None
# A2A support (optional, lazy loaded)
elif name == "A2A":
try:
from praisonaiagents.ui.a2a import A2A
_lazy_cache[name] = A2A
return A2A
except ImportError:
return None
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# Initialize telemetry only if explicitly enabled via config
def _init_telemetry():
"""Initialize telemetry if enabled via environment variable."""
if not _config.TELEMETRY_ENABLED:
return
try:
import os
from .telemetry import get_telemetry
from .telemetry.integration import auto_instrument_all
# Check for explicit disable (respects DO_NOT_TRACK and other disable flags)
telemetry_disabled = any([
os.environ.get('PRAISONAI_TELEMETRY_DISABLED', '').lower() in ('true', '1', 'yes'),
os.environ.get('PRAISONAI_DISABLE_TELEMETRY', '').lower() in ('true', '1', 'yes'),
os.environ.get('DO_NOT_TRACK', '').lower() in ('true', '1', 'yes'),
])
# Check for performance mode (minimal overhead with limited events)
performance_mode = os.environ.get('PRAISONAI_PERFORMANCE_MODE', '').lower() in ('true', '1', 'yes')
# Check for full telemetry mode (more detailed tracking)
full_telemetry = os.environ.get('PRAISONAI_FULL_TELEMETRY', '').lower() in ('true', '1', 'yes')
# Legacy explicit auto-instrument option
explicit_auto_instrument = os.environ.get('PRAISONAI_AUTO_INSTRUMENT', '').lower() in ('true', '1', 'yes')
# Enable PostHog by default unless explicitly disabled
if not telemetry_disabled:
_telemetry = get_telemetry()
if _telemetry and _telemetry.enabled:
from .telemetry.integration import auto_instrument_all
# Default: PostHog telemetry is enabled and events are posted
# Performance mode can be explicitly enabled for minimal overhead
use_performance_mode = performance_mode and not (full_telemetry or explicit_auto_instrument)
auto_instrument_all(_telemetry, performance_mode=use_performance_mode)
# Track package import for basic usage analytics
try:
_telemetry.track_feature_usage("package_import")
except Exception:
pass
_telemetry = get_telemetry()
if _telemetry and _telemetry.enabled:
use_performance_mode = _config.PERFORMANCE_MODE and not (
_config.FULL_TELEMETRY or _config.AUTO_INSTRUMENT
)
auto_instrument_all(_telemetry, performance_mode=use_performance_mode)
# Track package import for basic usage analytics
try:
_telemetry.track_feature_usage("package_import")
except Exception:
pass
except Exception:
# Silently fail if there are any issues - never break user applications
pass
# Only initialize telemetry if explicitly enabled
_init_telemetry()
__all__ = [
# Core classes (lazy loaded)
'Agent',
'ImageAgent',
'ContextAgent',
@@ -314,11 +420,8 @@ __all__ = [
'RewriteResult',
'PromptExpanderAgent',
'ExpandStrategy',
'ExpandResult'
]
# Add workflow exports
__all__.extend([
'ExpandResult',
# Workflows
'Workflow',
'WorkflowStep',
'WorkflowContext',
@@ -331,48 +434,33 @@ __all__.extend([
'parallel',
'loop',
'repeat',
'Pipeline'
])
# Add MCP to __all__ if available
if _mcp_available:
__all__.append('MCP')
# Add flow display if available
if FlowDisplay is not None:
__all__.extend(['FlowDisplay', 'track_workflow'])
# Add FastContext exports (lazy loaded)
__all__.extend([
'Pipeline',
# MCP
'MCP',
# Flow display
'FlowDisplay',
'track_workflow',
# FastContext
'FastContext',
'FastContextResult',
'FileMatch',
'LineRange'
])
# Add Agent Skills exports (lazy loaded)
__all__.extend([
'LineRange',
# Agent Skills
'SkillManager',
'SkillProperties',
'SkillMetadata',
'SkillLoader'
])
# AG-UI support (optional, lazy loaded)
try:
from praisonaiagents.ui.agui import AGUI
__all__.append('AGUI')
_agui_available = True
except ImportError:
_agui_available = False
AGUI = None
# A2A support (optional, lazy loaded)
try:
from praisonaiagents.ui.a2a import A2A
__all__.append('A2A')
_a2a_available = True
except ImportError:
_a2a_available = False
A2A = None
'SkillLoader',
# Planning
'Plan',
'PlanStep',
'TodoList',
'TodoItem',
'PlanStorage',
'PlanningAgent',
'ApprovalCallback',
'READ_ONLY_TOOLS',
'RESTRICTED_TOOLS',
# UI
'AGUI',
'A2A',
]
Whitespace-only changes.
@@ -0,0 +1,106 @@
"""
Configuration module for PraisonAI Agents.
Provides feature flags and configuration options that can be controlled
via environment variables for performance tuning and behavior control.
Environment Variables:
PRAISONAI_LAZY_IMPORTS: Enable lazy imports (default: true)
PRAISONAI_TELEMETRY_ENABLED: Enable telemetry (default: false, opt-in)
PRAISONAI_TELEMETRY_DISABLED: Disable telemetry (takes precedence)
DO_NOT_TRACK: Standard opt-out flag (takes precedence)
"""
import os
from typing import Optional
def _str_to_bool(value: Optional[str], default: bool = False) -> bool:
"""Convert string environment variable to boolean."""
if value is None:
return default
return value.lower() in ('true', '1', 'yes', 'on')
# Feature Flags
# -------------
# Lazy imports: When True (default), heavy dependencies like litellm are
# only imported when actually needed, reducing import time significantly.
LAZY_IMPORTS = _str_to_bool(
os.environ.get('PRAISONAI_LAZY_IMPORTS'),
default=True
)
# Telemetry: Opt-in by default (False). Users must explicitly enable.
# Precedence (highest to lowest):
# 1. DO_NOT_TRACK=true -> disabled
# 2. PRAISONAI_TELEMETRY_DISABLED=true -> disabled
# 3. PRAISONAI_DISABLE_TELEMETRY=true -> disabled
# 4. PRAISONAI_TELEMETRY_ENABLED=true -> enabled
# 5. Default: disabled (opt-in model)
def _get_telemetry_enabled() -> bool:
"""Determine if telemetry should be enabled based on env vars."""
# Check disable flags first (highest precedence)
if _str_to_bool(os.environ.get('DO_NOT_TRACK')):
return False
if _str_to_bool(os.environ.get('PRAISONAI_TELEMETRY_DISABLED')):
return False
if _str_to_bool(os.environ.get('PRAISONAI_DISABLE_TELEMETRY')):
return False
# Check explicit enable flag
if _str_to_bool(os.environ.get('PRAISONAI_TELEMETRY_ENABLED')):
return True
# Default: disabled (opt-in model)
return False
TELEMETRY_ENABLED = _get_telemetry_enabled()
# Performance mode: Minimal telemetry overhead when enabled
PERFORMANCE_MODE = _str_to_bool(
os.environ.get('PRAISONAI_PERFORMANCE_MODE'),
default=False
)
# Full telemetry mode: Detailed tracking when enabled
FULL_TELEMETRY = _str_to_bool(
os.environ.get('PRAISONAI_FULL_TELEMETRY'),
default=False
)
# Legacy auto-instrument flag
AUTO_INSTRUMENT = _str_to_bool(
os.environ.get('PRAISONAI_AUTO_INSTRUMENT'),
default=False
)
def missing_dependency_error(
package_name: str,
extra_name: Optional[str] = None,
feature_name: Optional[str] = None
) -> ImportError:
"""
Create a standardized ImportError for missing optional dependencies.
Args:
package_name: The name of the missing package (e.g., 'litellm')
extra_name: The pip extra to install (e.g., 'llm' for [llm])
feature_name: Human-readable feature name (e.g., 'LLM support')
Returns:
ImportError with helpful install instructions
"""
if extra_name:
install_cmd = f"pip install 'praisonaiagents[{extra_name}]'"
else:
install_cmd = f"pip install {package_name}"
feature_desc = f" for {feature_name}" if feature_name else ""
return ImportError(
f"{package_name} is required{feature_desc} but not installed. "
f"Please install with: {install_cmd}"
)
@@ -84,8 +84,18 @@ def _configure_loggers():
# ========================================================================
# LITELLM CONFIGURATION
# ========================================================================
def _configure_litellm():
"""Configure litellm after it's imported."""
_litellm_configured = False
def configure_litellm():
"""Configure litellm after it's imported.
This function should be called lazily when litellm is actually used,
not at module import time, to avoid importing litellm unnecessarily.
"""
global _litellm_configured
if _litellm_configured:
return
try:
import litellm
litellm.telemetry = False
@@ -97,11 +107,17 @@ def _configure_litellm():
if hasattr(litellm, 'set_verbose'):
litellm.set_verbose = False
_litellm_configured = True
except (ImportError, AttributeError):
pass
# Alias for backward compatibility
_configure_litellm = configure_litellm
# ========================================================================
# ROOT LOGGER CONFIGURATION
# ========================================================================
@@ -124,11 +140,16 @@ def configure_root_logger():
# INITIALIZATION
# ========================================================================
def initialize_logging():
"""Initialize all logging configuration."""
"""Initialize all logging configuration.
Note: litellm configuration is NOT done here to avoid importing litellm
at package import time. Call configure_litellm() when litellm is needed.
"""
_configure_environment()
_configure_loggers()
_configure_litellm()
# NOTE: _configure_litellm() is NOT called here to avoid importing litellm
# It will be called lazily when LLM class is instantiated
# Auto-initialize on import
# Auto-initialize on import (but NOT litellm)
initialize_logging()
@@ -4,6 +4,7 @@ import json
import copy
import logging
import asyncio
import threading
from typing import List, Optional, Any, Dict, Union, Literal, TYPE_CHECKING, Callable, Tuple, Generator
from rich.console import Console
from rich.live import Live
@@ -61,6 +62,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.
@@ -529,9 +581,9 @@ class Agent:
self.embedder_config = embedder_config
self.knowledge = knowledge
self.use_system_prompt = use_system_prompt
# NOTE: chat_history is not thread-safe. If concurrent access is needed,
# consider using threading.Lock or other synchronization mechanisms
# Thread-safe chat_history with lock for concurrent access
self.chat_history = []
self._history_lock = threading.Lock()
self.markdown = markdown
self.stream = stream
self.metrics = metrics
@@ -592,11 +644,11 @@ Your Goal: {self.goal}
self._guardrail_fn = None
self._setup_guardrail()
# Cache for system prompts and formatted tools
# Note: In single-threaded usage (common case), these are safe
# For multi-threaded usage, consider using threading.Lock
# Cache for system prompts and formatted tools with thread-safe lock
# RLock allows re-entrant access from the same thread
self._system_prompt_cache = {}
self._formatted_tools_cache = {}
self._cache_lock = threading.RLock()
# Limit cache size to prevent unbounded growth
self._max_cache_size = 100
@@ -1694,7 +1746,11 @@ Your Goal: {self.goal}"""
# Special handling for MCP tools
# Check if tools is an MCP instance with the requested function name
from ..mcp.mcp import MCP
MCP = None
try:
from ..mcp.mcp import MCP
except ImportError:
pass # MCP not available
# Helper function to execute MCP tool
def _execute_mcp_tool(mcp_instance, func_name, args):
@@ -1729,7 +1785,7 @@ Your Goal: {self.goal}"""
return False, None
# Check if tools is a single MCP instance
if isinstance(self.tools, MCP):
if MCP is not None and isinstance(self.tools, MCP):
logging.debug(f"Looking for MCP tool {function_name}")
found, result = _execute_mcp_tool(self.tools, function_name, arguments)
if found:
@@ -1738,7 +1794,7 @@ Your Goal: {self.goal}"""
# Check if tools is a list that may contain MCP instances
if isinstance(self.tools, (list, tuple)):
for tool in self.tools:
if isinstance(tool, MCP):
if MCP is not None and isinstance(tool, MCP):
logging.debug(f"Looking for MCP tool {function_name} in MCP instance")
found, result = _execute_mcp_tool(tool, function_name, arguments)
if found:
@@ -2160,8 +2216,12 @@ Your Goal: {self.goal}"""
# Convert MCP tool objects to OpenAI format if needed
if tool_param is not None:
from ..mcp.mcp import MCP
if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
MCP = None
try:
from ..mcp.mcp import MCP
except ImportError:
pass
if MCP is not None and isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
# Single MCP instance
logging.debug("Converting single MCP tool to OpenAI format")
openai_tool = tool_param.to_openai_tool()
@@ -2176,7 +2236,7 @@ Your Goal: {self.goal}"""
# List that may contain MCP instances - convert each MCP to OpenAI format
converted_tools = []
for t in tool_param:
if isinstance(t, MCP) and hasattr(t, 'to_openai_tool'):
if MCP is not None and isinstance(t, MCP) and hasattr(t, 'to_openai_tool'):
logging.debug("Converting MCP instance in list to OpenAI format")
openai_tools = t.to_openai_tool()
if isinstance(openai_tools, list):
@@ -3111,8 +3171,12 @@ Write the complete compiled report:"""
# Convert MCP tools if needed
if tool_param is not None:
from ..mcp.mcp import MCP
if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
MCP = None
try:
from ..mcp.mcp import MCP
except ImportError:
pass
if MCP is not None and isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
openai_tool = tool_param.to_openai_tool()
if openai_tool:
if isinstance(openai_tool, list):
@@ -5,13 +5,26 @@ 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).
Schema Versioning:
- SCHEMA_VERSION tracks the protocol version
- Adapters should implement get_schema_version() to report their version
- Adapters should implement migrate_schema() for upgrades
- Version format: MAJOR.MINOR (e.g., "1.0", "1.1", "2.0")
- MAJOR changes are breaking, MINOR changes are backward compatible
"""
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable, Tuple
import time
# Current schema version for the DbAdapter protocol
# Increment MINOR for backward-compatible additions
# Increment MAJOR for breaking changes
SCHEMA_VERSION = "1.0"
@dataclass
class DbToolCall:
"""A tool call to be persisted."""
@@ -365,6 +378,48 @@ class DbAdapter(Protocol):
def close(self) -> None:
"""Close the database connection."""
...
# --- Schema Versioning (Optional) ---
# These methods are optional for backward compatibility.
# Adapters that don't implement them will work but won't support migrations.
def get_schema_version(self) -> str:
"""
Get the current schema version of this adapter.
Returns:
Schema version string (e.g., "1.0")
Default implementation returns "1.0"
"""
...
def migrate_schema(
self,
from_version: str,
to_version: str,
) -> bool:
"""
Migrate the schema from one version to another.
Args:
from_version: Current schema version
to_version: Target schema version
Returns:
True if migration succeeded, False otherwise
"""
...
def check_schema_compatibility(self) -> Tuple[bool, str]:
"""
Check if the adapter's schema is compatible with the protocol.
Returns:
Tuple of (is_compatible, message)
- is_compatible: True if schema is compatible
- message: Description of compatibility status or required action
"""
...
@runtime_checkable
@@ -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,
@@ -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,327 @@
"""
PraisonAI Agents Lite - Minimal agent framework without heavy dependencies.
This subpackage provides a lightweight version of PraisonAI Agents that:
- Does NOT import litellm, chromadb, or other heavy dependencies at import time
- Provides core Agent/Task/Tools abstractions
- Allows users to bring their own LLM client (BYO-LLM)
- Has minimal import time (<50ms) and memory footprint (<10MB)
Usage:
from praisonaiagents.lite import LiteAgent, LiteTask, Tools
# Create a lite agent with your own LLM function
def my_llm(messages):
# Your LLM implementation here
return "response"
agent = LiteAgent(
name="Assistant",
llm_fn=my_llm,
tools=[my_tool]
)
response = agent.chat("Hello!")
For full functionality with litellm integration, use the main package:
from praisonaiagents import Agent
"""
from typing import Any, Callable, Dict, List, Optional
from dataclasses import dataclass, field
import threading
@dataclass
class LiteToolResult:
"""Result from a tool execution."""
output: Any
success: bool = True
error: Optional[str] = None
@dataclass
class LiteTask:
"""
Lightweight task definition without heavy dependencies.
Attributes:
description: What the task should accomplish
expected_output: Description of expected output format
agent: Optional agent assigned to this task
context: Additional context for the task
result: Result after task execution
"""
description: str
expected_output: str = ""
agent: Optional['LiteAgent'] = None
context: Dict[str, Any] = field(default_factory=dict)
result: Optional[str] = None
def execute(self, input_text: Optional[str] = None) -> str:
"""Execute the task using the assigned agent."""
if self.agent is None:
raise ValueError("No agent assigned to this task")
prompt = input_text or self.description
if self.expected_output:
prompt += f"\n\nExpected output format: {self.expected_output}"
self.result = self.agent.chat(prompt)
return self.result
class LiteAgent:
"""
Lightweight agent that works with any LLM function.
This agent does NOT import litellm or other heavy dependencies.
Users provide their own LLM function for chat completions.
Args:
name: Agent name
llm_fn: Function that takes messages list and returns response string.
Signature: (messages: List[Dict]) -> str
instructions: System instructions for the agent
tools: List of tool functions the agent can use
verbose: Enable verbose output
Example:
def my_openai_llm(messages):
import openai
client = openai.OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
return response.choices[0].message.content
agent = LiteAgent(
name="Assistant",
llm_fn=my_openai_llm,
instructions="You are a helpful assistant."
)
response = agent.chat("Hello!")
"""
def __init__(
self,
name: str = "LiteAgent",
llm_fn: Optional[Callable[[List[Dict]], str]] = None,
instructions: str = "You are a helpful assistant.",
tools: Optional[List[Callable]] = None,
verbose: bool = False,
):
self.name = name
self.llm_fn = llm_fn
self.instructions = instructions
self.tools = tools or []
self.verbose = verbose
# Thread-safe chat history
self.chat_history: List[Dict[str, str]] = []
self._history_lock = threading.Lock()
# Tool registry
self._tool_map: Dict[str, Callable] = {}
for tool_fn in self.tools:
tool_name = getattr(tool_fn, '__name__', str(tool_fn))
self._tool_map[tool_name] = tool_fn
def _build_messages(self, user_message: str) -> List[Dict[str, str]]:
"""Build messages list for LLM call."""
messages = [{"role": "system", "content": self.instructions}]
with self._history_lock:
messages.extend(self.chat_history.copy())
messages.append({"role": "user", "content": user_message})
return messages
def _add_to_history(self, role: str, content: str):
"""Thread-safe addition to chat history."""
with self._history_lock:
self.chat_history.append({"role": role, "content": content})
def chat(self, message: str) -> str:
"""
Send a message to the agent and get a response.
Args:
message: User message
Returns:
Agent's response string
"""
if self.llm_fn is None:
raise ValueError(
"No LLM function provided. Please provide an llm_fn when creating the agent.\n"
"Example:\n"
" def my_llm(messages):\n"
" # Your LLM implementation\n"
" return 'response'\n"
" agent = LiteAgent(llm_fn=my_llm)"
)
messages = self._build_messages(message)
if self.verbose:
print(f"[{self.name}] Processing: {message[:50]}...")
response = self.llm_fn(messages)
# Add to history
self._add_to_history("user", message)
self._add_to_history("assistant", response)
if self.verbose:
print(f"[{self.name}] Response: {response[:100]}...")
return response
def clear_history(self):
"""Clear chat history."""
with self._history_lock:
self.chat_history.clear()
def execute_tool(self, tool_name: str, **kwargs) -> LiteToolResult:
"""
Execute a tool by name.
Args:
tool_name: Name of the tool to execute
**kwargs: Arguments to pass to the tool
Returns:
LiteToolResult with output or error
"""
if tool_name not in self._tool_map:
return LiteToolResult(
output=None,
success=False,
error=f"Tool '{tool_name}' not found"
)
try:
tool_fn = self._tool_map[tool_name]
result = tool_fn(**kwargs)
return LiteToolResult(output=result, success=True)
except Exception as e:
return LiteToolResult(
output=None,
success=False,
error=str(e)
)
def create_openai_llm_fn(
model: str = "gpt-4o-mini",
api_key: Optional[str] = None,
**kwargs
) -> Callable[[List[Dict]], str]:
"""
Create an LLM function using the OpenAI SDK.
This is a convenience function for users who want to use OpenAI
without the full litellm dependency.
Args:
model: OpenAI model name
api_key: OpenAI API key (or use OPENAI_API_KEY env var)
**kwargs: Additional arguments to pass to OpenAI
Returns:
LLM function compatible with LiteAgent
Example:
llm_fn = create_openai_llm_fn(model="gpt-4o-mini")
agent = LiteAgent(llm_fn=llm_fn)
"""
def llm_fn(messages: List[Dict]) -> str:
try:
import openai
except ImportError:
raise ImportError(
"openai package is required for create_openai_llm_fn. "
"Install with: pip install openai"
)
client = openai.OpenAI(api_key=api_key)
response = client.chat.completions.create(
model=model,
messages=messages,
**kwargs
)
return response.choices[0].message.content or ""
return llm_fn
def create_anthropic_llm_fn(
model: str = "claude-3-5-sonnet-20241022",
api_key: Optional[str] = None,
max_tokens: int = 4096,
**kwargs
) -> Callable[[List[Dict]], str]:
"""
Create an LLM function using the Anthropic SDK.
Args:
model: Anthropic model name
api_key: Anthropic API key (or use ANTHROPIC_API_KEY env var)
max_tokens: Maximum tokens in response
**kwargs: Additional arguments to pass to Anthropic
Returns:
LLM function compatible with LiteAgent
"""
def llm_fn(messages: List[Dict]) -> str:
try:
import anthropic
except ImportError:
raise ImportError(
"anthropic package is required for create_anthropic_llm_fn. "
"Install with: pip install anthropic"
)
client = anthropic.Anthropic(api_key=api_key)
# Extract system message
system = ""
chat_messages = []
for msg in messages:
if msg["role"] == "system":
system = msg["content"]
else:
chat_messages.append(msg)
response = client.messages.create(
model=model,
max_tokens=max_tokens,
system=system,
messages=chat_messages,
**kwargs
)
return response.content[0].text
return llm_fn
# Re-export Tools from main package (lightweight, no heavy deps)
from ..tools.tools import Tools
from ..tools.base import BaseTool, ToolResult
from ..tools.decorator import tool
__all__ = [
"LiteAgent",
"LiteTask",
"LiteToolResult",
"Tools",
"BaseTool",
"ToolResult",
"tool",
"create_openai_llm_fn",
"create_anthropic_llm_fn",
]
@@ -1,33 +1,102 @@
"""
LLM Module for PraisonAI Agents.
This module provides language model integrations with lazy loading
to minimize import time when LLM functionality is not immediately needed.
"""
import os
# Ensure litellm telemetry is disabled before imports
# Ensure litellm telemetry is disabled before any imports
os.environ["LITELLM_TELEMETRY"] = "False"
# Import modules
from .llm import LLM, LLMContextLengthExceededException
from .openai_client import (
OpenAIClient,
get_openai_client,
ChatCompletionMessage,
Choice,
CompletionTokensDetails,
PromptTokensDetails,
CompletionUsage,
ChatCompletion,
ToolCall,
process_stream_chunks
)
from .model_capabilities import (
supports_structured_outputs,
supports_streaming_with_tools
)
from .model_router import (
ModelRouter,
ModelProfile,
TaskComplexity,
create_routing_agent
)
from .rate_limiter import RateLimiter
# Module-level cache for lazy-loaded classes
_lazy_cache = {}
def __getattr__(name):
"""Lazy load LLM classes to avoid importing litellm at module load time."""
if name in _lazy_cache:
return _lazy_cache[name]
if name == "LLM":
from .llm import LLM
_lazy_cache[name] = LLM
return LLM
elif name == "LLMContextLengthExceededException":
from .llm import LLMContextLengthExceededException
_lazy_cache[name] = LLMContextLengthExceededException
return LLMContextLengthExceededException
elif name == "OpenAIClient":
from .openai_client import OpenAIClient
_lazy_cache[name] = OpenAIClient
return OpenAIClient
elif name == "get_openai_client":
from .openai_client import get_openai_client
_lazy_cache[name] = get_openai_client
return get_openai_client
elif name == "ChatCompletionMessage":
from .openai_client import ChatCompletionMessage
_lazy_cache[name] = ChatCompletionMessage
return ChatCompletionMessage
elif name == "Choice":
from .openai_client import Choice
_lazy_cache[name] = Choice
return Choice
elif name == "CompletionTokensDetails":
from .openai_client import CompletionTokensDetails
_lazy_cache[name] = CompletionTokensDetails
return CompletionTokensDetails
elif name == "PromptTokensDetails":
from .openai_client import PromptTokensDetails
_lazy_cache[name] = PromptTokensDetails
return PromptTokensDetails
elif name == "CompletionUsage":
from .openai_client import CompletionUsage
_lazy_cache[name] = CompletionUsage
return CompletionUsage
elif name == "ChatCompletion":
from .openai_client import ChatCompletion
_lazy_cache[name] = ChatCompletion
return ChatCompletion
elif name == "ToolCall":
from .openai_client import ToolCall
_lazy_cache[name] = ToolCall
return ToolCall
elif name == "process_stream_chunks":
from .openai_client import process_stream_chunks
_lazy_cache[name] = process_stream_chunks
return process_stream_chunks
elif name == "supports_structured_outputs":
from .model_capabilities import supports_structured_outputs
_lazy_cache[name] = supports_structured_outputs
return supports_structured_outputs
elif name == "supports_streaming_with_tools":
from .model_capabilities import supports_streaming_with_tools
_lazy_cache[name] = supports_streaming_with_tools
return supports_streaming_with_tools
elif name == "ModelRouter":
from .model_router import ModelRouter
_lazy_cache[name] = ModelRouter
return ModelRouter
elif name == "ModelProfile":
from .model_router import ModelProfile
_lazy_cache[name] = ModelProfile
return ModelProfile
elif name == "TaskComplexity":
from .model_router import TaskComplexity
_lazy_cache[name] = TaskComplexity
return TaskComplexity
elif name == "create_routing_agent":
from .model_router import create_routing_agent
_lazy_cache[name] = create_routing_agent
return create_routing_agent
elif name == "RateLimiter":
from .rate_limiter import RateLimiter
_lazy_cache[name] = RateLimiter
return RateLimiter
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = [
"LLM",
@@ -49,4 +118,4 @@ __all__ = [
"TaskComplexity",
"create_routing_agent",
"RateLimiter"
]
]
Whitespace-only changes.
+223 -2
View File
@@ -104,12 +104,34 @@ class LLM:
OLLAMA_TOOL_USAGE_PROMPT = "Please analyze the request and use the available tools to help answer the question. Start by identifying what information you need."
OLLAMA_FINAL_ANSWER_PROMPT = "Based on the tool results above, please provide the final answer to the original question."
# Force tool usage prompt template (used when model ignores tools)
FORCE_TOOL_USAGE_PROMPT = """You MUST use one of the available tools to answer this question.
Do NOT provide a direct answer. Respond ONLY with a tool call.
Available tools: {tool_names}
Respond with a JSON object in this format:
{{"name": "tool_name", "arguments": {{"arg1": value1, "arg2": value2}}}}"""
# Tool call repair prompt template (used when tool call is malformed)
TOOL_CALL_REPAIR_PROMPT = """Your previous tool call was malformed: {error}
Please try again. Available tools:
{tool_schemas}
Respond with ONLY a valid JSON tool call in this format:
{{"name": "tool_name", "arguments": {{"arg1": value1, "arg2": value2}}}}"""
# Ollama iteration threshold for summary generation
OLLAMA_SUMMARY_ITERATION_THRESHOLD = 1
@classmethod
def _configure_logging(cls):
"""Configure logging settings once for all LLM instances."""
# First, configure litellm via the centralized logging module
from .._logging import configure_litellm
configure_litellm()
try:
import litellm
# Disable telemetry
@@ -294,6 +316,16 @@ class LLM:
# Auto-detect XML tool format for known models, or allow manual override
self.xml_tool_format = extra_settings.get('xml_tool_format', 'auto')
# Tool calling reliability settings (for weak models like Ollama)
# These are auto-configured for Ollama providers if not explicitly set
self._max_tool_repairs_explicit = 'max_tool_repairs' in extra_settings
self._force_tool_usage_explicit = 'force_tool_usage' in extra_settings
self.max_tool_repairs = extra_settings.get('max_tool_repairs', 0) # Will be set to 2 for Ollama if not explicit
self.force_tool_usage = extra_settings.get('force_tool_usage', 'never') # Will be set to 'auto' for Ollama if not explicit
# Apply Ollama-specific defaults after model is set
self._apply_ollama_defaults()
# Token tracking
self.last_token_metrics: Optional[TokenMetrics] = None
self.session_token_metrics: Optional[TokenMetrics] = None
@@ -347,6 +379,16 @@ class LLM:
self._console = Console()
return self._console
def _apply_ollama_defaults(self):
"""Apply Ollama-specific defaults for tool calling reliability."""
if self._is_ollama_provider():
# Apply defaults only if not explicitly set by user
if not self._max_tool_repairs_explicit:
self.max_tool_repairs = 2
if not self._force_tool_usage_explicit:
self.force_tool_usage = 'auto'
logging.debug(f"[OLLAMA_RELIABILITY] Applied Ollama defaults: max_tool_repairs={self.max_tool_repairs}, force_tool_usage={self.force_tool_usage}")
def _is_ollama_provider(self) -> bool:
"""Detect if this is an Ollama provider regardless of naming convention"""
if not self.model:
@@ -562,14 +604,150 @@ class LLM:
def _format_ollama_tool_result_message(self, function_name: str, tool_result: Any) -> Dict[str, str]:
"""
Format tool result message for Ollama provider.
Simplified approach without hardcoded regex extraction.
Enhanced to instruct model to use the result for final answer.
"""
tool_result_str = str(tool_result)
return {
"role": "user",
"content": f"The {function_name} function returned: {tool_result_str}"
"content": f"""Tool execution complete.
Function: {function_name}
Result: {tool_result_str}
Now provide your final answer using this result."""
}
def _get_tool_names_for_prompt(self, formatted_tools: Optional[List]) -> str:
"""Extract tool names from formatted tools for prompts."""
if not formatted_tools:
return "None"
names = []
for tool in formatted_tools:
if isinstance(tool, dict) and 'function' in tool:
names.append(tool['function'].get('name', 'unknown'))
return ', '.join(names) if names else "None"
def _get_tool_schemas_for_prompt(self, formatted_tools: Optional[List]) -> str:
"""Generate compact tool schemas for repair prompts."""
if not formatted_tools:
return "None"
schemas = []
for tool in formatted_tools:
if isinstance(tool, dict) and 'function' in tool:
func = tool['function']
name = func.get('name', 'unknown')
params = func.get('parameters', {})
required = params.get('required', [])
props = params.get('properties', {})
param_strs = []
for pname, pinfo in props.items():
ptype = pinfo.get('type', 'any')
req = '*' if pname in required else ''
param_strs.append(f"{pname}{req}: {ptype}")
schemas.append(f"- {name}({', '.join(param_strs)})")
return '\n'.join(schemas) if schemas else "None"
def _should_force_tool_usage(self, response_text: str, tool_calls: Optional[List], formatted_tools: Optional[List], iteration_count: int) -> bool:
"""
Determine if we should force tool usage based on settings and context.
Returns True if:
- force_tool_usage == 'always' and tools exist and no tool_calls
- force_tool_usage == 'auto' and is Ollama and first iteration and tools exist and no tool_calls
"""
if not formatted_tools:
return False
if tool_calls:
return False
if self.force_tool_usage == 'never':
return False
elif self.force_tool_usage == 'always':
return True
elif self.force_tool_usage == 'auto':
# Auto mode: only for Ollama on first iteration when model ignores tools
return self._is_ollama_provider() and iteration_count == 0
return False
def _try_parse_tool_call_json(self, response_text: str, iteration_count: int) -> tuple:
"""
Try to parse tool call from JSON response text.
Returns:
tuple: (tool_calls list or None, parse_error string or None)
"""
if not response_text or not response_text.strip():
return None, None
try:
response_json = json.loads(response_text.strip())
if isinstance(response_json, dict) and "name" in response_json:
tool_calls = [{
"id": f"tool_{iteration_count}",
"type": "function",
"function": {
"name": response_json["name"],
"arguments": json.dumps(response_json.get("arguments", {}))
}
}]
return tool_calls, None
elif isinstance(response_json, list):
tool_calls = []
for idx, tool_json in enumerate(response_json):
if isinstance(tool_json, dict) and "name" in tool_json:
tool_calls.append({
"id": f"tool_{iteration_count}_{idx}",
"type": "function",
"function": {
"name": tool_json["name"],
"arguments": json.dumps(tool_json.get("arguments", {}))
}
})
return tool_calls if tool_calls else None, None
else:
return None, "Response is not a valid tool call format (missing 'name' field)"
except json.JSONDecodeError as e:
return None, f"JSON parse error: {str(e)}"
def _validate_tool_call(self, tool_call: Dict, formatted_tools: Optional[List]) -> Optional[str]:
"""
Validate a tool call against available tools.
Returns:
Error message string if invalid, None if valid
"""
if not formatted_tools:
return None
try:
func_info = tool_call.get('function', {})
tool_name = func_info.get('name', '')
arguments_str = func_info.get('arguments', '{}')
# Check if tool name exists
available_names = [t['function']['name'] for t in formatted_tools if isinstance(t, dict) and 'function' in t]
if tool_name not in available_names:
return f"Unknown tool '{tool_name}'. Available: {', '.join(available_names)}"
# Try to parse arguments
try:
arguments = json.loads(arguments_str) if isinstance(arguments_str, str) else arguments_str
except json.JSONDecodeError as e:
return f"Invalid arguments JSON: {str(e)}"
# Find the tool schema and validate required args
for tool in formatted_tools:
if isinstance(tool, dict) and tool.get('function', {}).get('name') == tool_name:
params = tool['function'].get('parameters', {})
required = params.get('required', [])
missing = [r for r in required if r not in arguments]
if missing:
return f"Missing required arguments for '{tool_name}': {', '.join(missing)}"
break
return None # Valid
except Exception as e:
return f"Validation error: {str(e)}"
def _process_stream_delta(self, delta, response_text: str, tool_calls: List[Dict], formatted_tools: Optional[List] = None) -> tuple:
"""
Process a streaming delta chunk to extract content and tool calls.
@@ -1637,6 +1815,18 @@ class LLM:
if tool_calls:
logging.debug(f"Parsed {len(tool_calls)} tool call(s) from XML format")
# Force tool usage logic: if model ignores tools but should use them
if self._should_force_tool_usage(response_text, tool_calls, formatted_tools, iteration_count):
tool_names = self._get_tool_names_for_prompt(formatted_tools)
force_prompt = self.FORCE_TOOL_USAGE_PROMPT.format(tool_names=tool_names)
logging.debug(f"[OLLAMA_RELIABILITY] Force tool usage triggered. Adding prompt for tools: {tool_names}")
messages.append({
"role": "user",
"content": force_prompt
})
iteration_count += 1
continue
# For Ollama, if response is empty but we have tools, prompt for tool usage
if self._is_ollama_provider() and (not response_text or response_text.strip() == "") and formatted_tools and iteration_count == 0:
messages.append({
@@ -1646,6 +1836,37 @@ class LLM:
iteration_count += 1
continue
# Tool call repair logic: validate and repair malformed tool calls
repair_attempt_count = getattr(self, '_current_repair_count', 0)
if tool_calls and self.max_tool_repairs > 0 and repair_attempt_count < self.max_tool_repairs:
# Validate each tool call
validation_errors = []
for tc in tool_calls:
error = self._validate_tool_call(tc, formatted_tools)
if error:
validation_errors.append(error)
if validation_errors:
# Tool call is invalid, attempt repair
error_msg = "; ".join(validation_errors)
tool_schemas = self._get_tool_schemas_for_prompt(formatted_tools)
repair_prompt = self.TOOL_CALL_REPAIR_PROMPT.format(
error=error_msg,
tool_schemas=tool_schemas
)
logging.debug(f"[OLLAMA_RELIABILITY] Tool call repair attempt {repair_attempt_count + 1}/{self.max_tool_repairs}: {error_msg}")
messages.append({
"role": "user",
"content": repair_prompt
})
self._current_repair_count = repair_attempt_count + 1
iteration_count += 1
tool_calls = None # Clear invalid tool calls
continue
# Reset repair count on successful tool call
self._current_repair_count = 0
# Handle tool calls - Sequential tool calling logic
if tool_calls and execute_tool_fn:
# Convert tool_calls to a serializable format for all providers
@@ -580,7 +580,60 @@ class MCP:
return openai_tools
def __enter__(self):
"""Context manager entry - return self for use in 'with' statements."""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit - clean up resources."""
self.shutdown()
return False # Don't suppress exceptions
def shutdown(self):
"""Explicitly shut down MCP resources.
Call this method when done using the MCP instance to ensure
all background threads and connections are properly cleaned up.
"""
# Shutdown stdio runner if present
if hasattr(self, 'runner') and self.runner is not None:
try:
self.runner.shutdown()
except Exception:
pass # Best effort cleanup
# Shutdown SSE client if present
if hasattr(self, 'sse_client') and self.sse_client is not None:
try:
if hasattr(self.sse_client, 'shutdown'):
self.sse_client.shutdown()
except Exception:
pass
# Shutdown HTTP stream client if present
if hasattr(self, 'http_stream_client') and self.http_stream_client is not None:
try:
if hasattr(self.http_stream_client, 'shutdown'):
self.http_stream_client.shutdown()
except Exception:
pass
# Shutdown WebSocket client if present
if hasattr(self, 'websocket_client') and self.websocket_client is not None:
try:
if hasattr(self.websocket_client, 'shutdown'):
self.websocket_client.shutdown()
except Exception:
pass
def __del__(self):
"""Clean up resources when the object is garbage collected."""
if hasattr(self, 'runner'):
self.runner.shutdown()
"""Clean up resources when the object is garbage collected.
Note: __del__ is called during garbage collection and may not
always run. For reliable cleanup, use the context manager
pattern or call shutdown() explicitly.
"""
try:
self.shutdown()
except Exception:
pass # Best effort cleanup in __del__
@@ -27,13 +27,12 @@ Usage:
)
result = agents.start()
This module uses lazy loading to minimize import time.
"""
from .plan import Plan, PlanStep
from .todo import TodoList, TodoItem
from .storage import PlanStorage
from .planner import PlanningAgent
from .approval import ApprovalCallback
# Module-level cache for lazy-loaded classes
_lazy_cache = {}
# Read-only tools that are allowed in plan mode
READ_ONLY_TOOLS = [
@@ -90,6 +89,44 @@ RESEARCH_TOOLS = [
"find_files",
]
def __getattr__(name):
"""Lazy load planning classes to avoid importing heavy dependencies at module load time."""
if name in _lazy_cache:
return _lazy_cache[name]
if name == "Plan":
from .plan import Plan
_lazy_cache[name] = Plan
return Plan
elif name == "PlanStep":
from .plan import PlanStep
_lazy_cache[name] = PlanStep
return PlanStep
elif name == "TodoList":
from .todo import TodoList
_lazy_cache[name] = TodoList
return TodoList
elif name == "TodoItem":
from .todo import TodoItem
_lazy_cache[name] = TodoItem
return TodoItem
elif name == "PlanStorage":
from .storage import PlanStorage
_lazy_cache[name] = PlanStorage
return PlanStorage
elif name == "PlanningAgent":
from .planner import PlanningAgent
_lazy_cache[name] = PlanningAgent
return PlanningAgent
elif name == "ApprovalCallback":
from .approval import ApprovalCallback
_lazy_cache[name] = ApprovalCallback
return ApprovalCallback
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = [
"Plan",
"PlanStep",
Whitespace-only changes.
@@ -4,6 +4,7 @@ This module provides centralized tool management with support for:
- Manual registration
- Auto-discovery via entry_points
- Tool lookup by name
- Thread-safe operations for multi-agent scenarios
Usage:
from praisonaiagents.tools.registry import get_registry
@@ -14,6 +15,7 @@ Usage:
"""
import logging
import threading
from typing import Callable, Dict, List, Optional, Union
from importlib.metadata import entry_points
@@ -38,6 +40,7 @@ class ToolRegistry:
self._tools: Dict[str, BaseTool] = {}
self._functions: Dict[str, Callable] = {} # For backward compat with plain functions
self._discovered: bool = False
self._lock = threading.RLock() # Thread-safe operations for multi-agent scenarios
def register(
self,
@@ -47,6 +50,8 @@ class ToolRegistry:
) -> None:
"""Register a tool with the registry.
Thread-safe: Uses lock for concurrent access in multi-agent scenarios.
Args:
tool: BaseTool instance or callable function
name: Override name (default: tool.name or function.__name__)
@@ -55,83 +60,93 @@ class ToolRegistry:
Raises:
ValueError: If tool with same name exists and overwrite=False
"""
# Handle BaseTool instances
if isinstance(tool, BaseTool):
tool_name = name or tool.name
if tool_name in self._tools and not overwrite:
logging.debug(f"Tool '{tool_name}' already registered, skipping")
with self._lock:
# Handle BaseTool instances
if isinstance(tool, BaseTool):
tool_name = name or tool.name
if tool_name in self._tools and not overwrite:
logging.debug(f"Tool '{tool_name}' already registered, skipping")
return
self._tools[tool_name] = tool
logging.debug(f"Registered tool: {tool_name}")
return
self._tools[tool_name] = tool
logging.debug(f"Registered tool: {tool_name}")
return
# Handle plain callables
if callable(tool):
tool_name = name or getattr(tool, '__name__', str(id(tool)))
if tool_name in self._functions and not overwrite:
logging.debug(f"Function '{tool_name}' already registered, skipping")
# Handle plain callables
if callable(tool):
tool_name = name or getattr(tool, '__name__', str(id(tool)))
if tool_name in self._functions and not overwrite:
logging.debug(f"Function '{tool_name}' already registered, skipping")
return
self._functions[tool_name] = tool
logging.debug(f"Registered function: {tool_name}")
return
self._functions[tool_name] = tool
logging.debug(f"Registered function: {tool_name}")
return
raise TypeError(f"Cannot register {type(tool)}, expected BaseTool or callable")
raise TypeError(f"Cannot register {type(tool)}, expected BaseTool or callable")
def unregister(self, name: str) -> bool:
"""Remove a tool from the registry.
Thread-safe: Uses lock for concurrent access.
Args:
name: Tool name to remove
Returns:
True if tool was removed, False if not found
"""
if name in self._tools:
del self._tools[name]
return True
if name in self._functions:
del self._functions[name]
return True
return False
with self._lock:
if name in self._tools:
del self._tools[name]
return True
if name in self._functions:
del self._functions[name]
return True
return False
def get(self, name: str) -> Optional[Union[BaseTool, Callable]]:
"""Get a tool by name.
Thread-safe: Uses lock for concurrent access.
Args:
name: Tool name
Returns:
BaseTool instance, callable, or None if not found
"""
# Check BaseTool registry first
if name in self._tools:
return self._tools[name]
# Check functions registry
if name in self._functions:
return self._functions[name]
# Try auto-discovery if not found
if not self._discovered:
self.discover_plugins()
with self._lock:
# Check BaseTool registry first
if name in self._tools:
return self._tools[name]
return None
# Check functions registry
if name in self._functions:
return self._functions[name]
# Try auto-discovery if not found
if not self._discovered:
self.discover_plugins()
if name in self._tools:
return self._tools[name]
return None
def list_tools(self) -> List[str]:
"""List all registered tool names."""
return list(self._tools.keys()) + list(self._functions.keys())
"""List all registered tool names. Thread-safe."""
with self._lock:
return list(self._tools.keys()) + list(self._functions.keys())
def list_base_tools(self) -> List[BaseTool]:
"""List all registered BaseTool instances."""
return list(self._tools.values())
"""List all registered BaseTool instances. Thread-safe."""
with self._lock:
return list(self._tools.values())
def get_all(self) -> Dict[str, Union[BaseTool, Callable]]:
"""Get all registered tools as a dict."""
result = dict(self._tools)
result.update(self._functions)
return result
"""Get all registered tools as a dict. Thread-safe."""
with self._lock:
result = dict(self._tools)
result.update(self._functions)
return result
def discover_plugins(self) -> int:
"""Discover and register tools from entry_points.
@@ -185,16 +200,19 @@ class ToolRegistry:
return count
def clear(self) -> None:
"""Clear all registered tools."""
self._tools.clear()
self._functions.clear()
self._discovered = False
"""Clear all registered tools. Thread-safe."""
with self._lock:
self._tools.clear()
self._functions.clear()
self._discovered = False
def __contains__(self, name: str) -> bool:
return name in self._tools or name in self._functions
with self._lock:
return name in self._tools or name in self._functions
def __len__(self) -> int:
return len(self._tools) + len(self._functions)
with self._lock:
return len(self._tools) + len(self._functions)
def __repr__(self) -> str:
return f"ToolRegistry(tools={len(self._tools)}, functions={len(self._functions)})"
@@ -6,8 +6,35 @@ via various frontend protocols.
Available integrations:
- agui: AG-UI (Agent-User Interface) protocol for CopilotKit and compatible frontends
- a2a: Agent-to-Agent protocol
- a2ui: Agent-to-UI protocol
This module uses lazy loading to minimize import time.
"""
from praisonaiagents.ui.agui import AGUI
# Module-level cache for lazy-loaded classes
_lazy_cache = {}
__all__ = ["AGUI"]
def __getattr__(name):
"""Lazy load UI classes to avoid importing heavy dependencies at module load time."""
if name in _lazy_cache:
return _lazy_cache[name]
if name == "AGUI":
from praisonaiagents.ui.agui import AGUI
_lazy_cache[name] = AGUI
return AGUI
elif name == "A2A":
from praisonaiagents.ui.a2a import A2A
_lazy_cache[name] = A2A
return A2A
elif name == "A2UI":
from praisonaiagents.ui.a2ui import A2UI
_lazy_cache[name] = A2UI
return A2UI
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = ["AGUI", "A2A", "A2UI"]
Whitespace-only changes.
@@ -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 = "",
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.3.2"
version = "0.5.1"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
readme = "README.md"
requires-python = ">=3.10"
@@ -0,0 +1,243 @@
"""
Real API key integration tests for praisonaiagents.
These tests are gated by the RUN_REAL_KEY_TESTS environment variable.
They require valid API keys to be set in the environment.
Usage:
RUN_REAL_KEY_TESTS=1 pytest tests/integration/test_real_api.py -v
Required environment variables:
- OPENAI_API_KEY: OpenAI API key
Optional:
- ANTHROPIC_API_KEY: Anthropic API key (for Claude tests)
- GOOGLE_API_KEY: Google API key (for Gemini tests)
"""
import os
import sys
import pytest
# Gate all tests in this module
pytestmark = pytest.mark.skipif(
os.environ.get('RUN_REAL_KEY_TESTS', '').lower() not in ('1', 'true', 'yes'),
reason="Real API key tests disabled. Set RUN_REAL_KEY_TESTS=1 to enable."
)
def clear_modules():
"""Clear all praisonai and litellm related modules from cache."""
to_remove = [m for m in list(sys.modules.keys())
if 'praison' in m or 'litellm' in m]
for mod in to_remove:
del sys.modules[mod]
class TestAgentRealAPI:
"""Test Agent with real API calls."""
@pytest.fixture(autouse=True)
def setup(self):
"""Set up test fixtures."""
clear_modules()
# Verify API key is set (without printing it)
api_key = os.environ.get('OPENAI_API_KEY')
if not api_key:
pytest.skip("OPENAI_API_KEY not set")
yield
clear_modules()
def test_agent_simple_chat(self):
"""Test basic Agent.chat() with real API."""
from praisonaiagents import Agent
agent = Agent(
name="TestAgent",
instructions="You are a helpful assistant. Keep responses very brief.",
llm="gpt-4o-mini",
verbose=False
)
response = agent.chat("Say 'hello' and nothing else.")
assert response is not None
assert len(response) > 0
assert 'hello' in response.lower()
print(f"[PASS] Agent response received (length: {len(response)})")
def test_agent_with_tool(self):
"""Test Agent with a simple tool."""
from praisonaiagents import Agent
# Define a simple deterministic tool
def add_numbers(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b
agent = Agent(
name="MathAgent",
instructions="You are a math assistant. Use the add_numbers tool when asked to add.",
llm="gpt-4o-mini",
tools=[add_numbers],
verbose=False
)
response = agent.chat("What is 5 + 3? Use the add_numbers tool.")
assert response is not None
assert '8' in response
print(f"[PASS] Tool call successful, response contains '8'")
def test_agent_chat_history(self):
"""Test that chat history is maintained."""
from praisonaiagents import Agent
agent = Agent(
name="HistoryAgent",
instructions="You are a helpful assistant. Remember what the user tells you.",
llm="gpt-4o-mini",
verbose=False
)
# First message
agent.chat("My favorite color is blue.")
# Second message referencing first
response = agent.chat("What is my favorite color?")
assert response is not None
assert 'blue' in response.lower()
print(f"[PASS] Chat history maintained correctly")
class TestLiteAgentRealAPI:
"""Test LiteAgent with real API using OpenAI SDK directly."""
@pytest.fixture(autouse=True)
def setup(self):
"""Set up test fixtures."""
clear_modules()
api_key = os.environ.get('OPENAI_API_KEY')
if not api_key:
pytest.skip("OPENAI_API_KEY not set")
yield
clear_modules()
def test_lite_agent_with_openai(self):
"""Test LiteAgent with OpenAI SDK."""
from praisonaiagents.lite import LiteAgent, create_openai_llm_fn
# Create LLM function using OpenAI
llm_fn = create_openai_llm_fn(model="gpt-4o-mini")
agent = LiteAgent(
name="LiteTestAgent",
llm_fn=llm_fn,
instructions="You are a helpful assistant. Keep responses very brief."
)
response = agent.chat("Say 'hello' and nothing else.")
assert response is not None
assert len(response) > 0
print(f"[PASS] LiteAgent with OpenAI response received")
def test_lite_agent_no_litellm_loaded(self):
"""Verify LiteAgent doesn't load litellm."""
from praisonaiagents.lite import LiteAgent, create_openai_llm_fn
llm_fn = create_openai_llm_fn(model="gpt-4o-mini")
agent = LiteAgent(llm_fn=llm_fn)
response = agent.chat("Hi")
assert response is not None
assert 'litellm' not in sys.modules, \
"LiteAgent should not load litellm"
print(f"[PASS] LiteAgent works without litellm")
class TestAnthropicAPI:
"""Test with Anthropic API (optional)."""
@pytest.fixture(autouse=True)
def setup(self):
"""Set up test fixtures."""
clear_modules()
api_key = os.environ.get('ANTHROPIC_API_KEY')
if not api_key:
pytest.skip("ANTHROPIC_API_KEY not set")
yield
clear_modules()
def test_agent_with_claude(self):
"""Test Agent with Claude model."""
from praisonaiagents import Agent
agent = Agent(
name="ClaudeAgent",
instructions="You are a helpful assistant. Keep responses very brief.",
llm="claude-3-5-sonnet-20241022",
verbose=False
)
response = agent.chat("Say 'hello' and nothing else.")
assert response is not None
assert len(response) > 0
print(f"[PASS] Claude response received")
class TestGoogleAPI:
"""Test with Google API (optional)."""
@pytest.fixture(autouse=True)
def setup(self):
"""Set up test fixtures."""
clear_modules()
api_key = os.environ.get('GOOGLE_API_KEY')
if not api_key:
pytest.skip("GOOGLE_API_KEY not set")
yield
clear_modules()
def test_agent_with_gemini(self):
"""Test Agent with Gemini model."""
from praisonaiagents import Agent
agent = Agent(
name="GeminiAgent",
instructions="You are a helpful assistant. Keep responses very brief.",
llm="gemini/gemini-2.0-flash",
verbose=False
)
response = agent.chat("Say 'hello' and nothing else.")
assert response is not None
assert len(response) > 0
print(f"[PASS] Gemini response received")
if __name__ == "__main__":
# Run with real API keys
if os.environ.get('RUN_REAL_KEY_TESTS', '').lower() not in ('1', 'true', 'yes'):
print("Set RUN_REAL_KEY_TESTS=1 to run these tests")
sys.exit(1)
pytest.main([__file__, "-v", "-s"])
@@ -5,11 +5,16 @@ Basic test to verify HTTP Stream implementation is working correctly.
import sys
import os
import pytest
# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from praisonaiagents.mcp import MCP
# Skip entire module if mcp package is not installed
try:
from praisonaiagents.mcp import MCP
except ImportError:
pytest.skip("MCP package not installed", allow_module_level=True)
def test_transport_selection():
"""Test that URLs are correctly routed to appropriate transports."""
@@ -18,8 +18,12 @@ from unittest.mock import Mock
# Add the package to path for testing
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from praisonaiagents.mcp.mcp import MCP
from praisonaiagents.agent.agent import Agent
# Skip entire module if mcp package is not installed
try:
from praisonaiagents.mcp.mcp import MCP
from praisonaiagents.agent.agent import Agent
except ImportError:
pytest.skip("MCP package not installed", allow_module_level=True)
# ============================================================================
@@ -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"
@@ -0,0 +1,217 @@
"""
Tests for lazy import behavior in praisonaiagents.
These tests verify that heavy dependencies like litellm are NOT loaded
at package import time, ensuring fast startup.
"""
import sys
import pytest
def clear_modules():
"""Clear all praisonai and litellm related modules from cache."""
to_remove = [m for m in list(sys.modules.keys())
if 'praison' in m or 'litellm' in m]
for mod in to_remove:
del sys.modules[mod]
class TestLazyImports:
"""Test that heavy dependencies are lazily loaded."""
def setup_method(self):
"""Clear module cache before each test."""
clear_modules()
def teardown_method(self):
"""Clean up after each test."""
clear_modules()
def test_litellm_not_loaded_on_import(self):
"""litellm should NOT be loaded when importing praisonaiagents."""
import praisonaiagents # noqa: F401
assert 'litellm' not in sys.modules, \
"litellm should not be loaded at import time"
def test_requests_not_loaded_on_import(self):
"""requests should NOT be loaded when importing praisonaiagents."""
import praisonaiagents # noqa: F401
assert 'requests' not in sys.modules, \
"requests should not be loaded at import time"
def test_chromadb_not_loaded_on_import(self):
"""chromadb should NOT be loaded when importing praisonaiagents."""
import praisonaiagents # noqa: F401
assert 'chromadb' not in sys.modules, \
"chromadb should not be loaded at import time"
def test_agent_available_via_getattr(self):
"""Agent should be available via lazy loading."""
import praisonaiagents
# Access Agent - this triggers lazy loading
Agent = praisonaiagents.Agent
assert Agent is not None
assert hasattr(Agent, 'chat')
def test_session_available_via_getattr(self):
"""Session should be available via lazy loading."""
import praisonaiagents
# Access Session - this triggers lazy loading
Session = praisonaiagents.Session
assert Session is not None
def test_knowledge_available_via_getattr(self):
"""Knowledge should be available via lazy loading."""
import praisonaiagents
# Access Knowledge - this triggers lazy loading
Knowledge = praisonaiagents.Knowledge
assert Knowledge is not None
def test_memory_available_via_getattr(self):
"""Memory should be available via lazy loading."""
import praisonaiagents
# Access Memory - this triggers lazy loading
Memory = praisonaiagents.Memory
assert Memory is not None
def test_tools_loaded_eagerly(self):
"""Tools should be available immediately (lightweight)."""
import praisonaiagents
# Tools is lightweight and should be available
assert hasattr(praisonaiagents, 'Tools')
assert praisonaiagents.Tools is not None
def test_all_exports_accessible(self):
"""All items in __all__ should be accessible."""
import praisonaiagents
for name in praisonaiagents.__all__:
# Some items may be None if optional deps not installed
try:
attr = getattr(praisonaiagents, name)
# Just verify it's accessible, may be None
except AttributeError:
pytest.fail(f"'{name}' in __all__ but not accessible")
class TestLitePackage:
"""Test the lite subpackage."""
def setup_method(self):
"""Clear module cache before each test."""
clear_modules()
def test_lite_import_fast(self):
"""Lite package should import quickly without heavy deps."""
import time
start = time.perf_counter()
from praisonaiagents.lite import LiteAgent # noqa: F401
end = time.perf_counter()
import_time_ms = (end - start) * 1000
# Lite should import in under 200ms (allowing for system variance)
assert import_time_ms < 200, \
f"Lite import took {import_time_ms:.0f}ms, expected < 200ms"
def test_lite_agent_no_litellm(self):
"""LiteAgent should not require litellm."""
from praisonaiagents.lite import LiteAgent
# Create agent without LLM function
agent = LiteAgent(name="Test")
assert agent.name == "Test"
assert 'litellm' not in sys.modules
def test_lite_agent_with_custom_llm(self):
"""LiteAgent should work with custom LLM function."""
from praisonaiagents.lite import LiteAgent
# Create a mock LLM function
def mock_llm(messages):
return "Hello from mock LLM!"
agent = LiteAgent(
name="TestAgent",
llm_fn=mock_llm,
instructions="You are a test agent."
)
response = agent.chat("Hi")
assert response == "Hello from mock LLM!"
assert len(agent.chat_history) == 2 # user + assistant
class TestTelemetryConfig:
"""Test telemetry configuration."""
def setup_method(self):
"""Clear module cache and env vars before each test."""
clear_modules()
import os
# Save original env vars
self._orig_env = {}
for key in ['PRAISONAI_TELEMETRY_ENABLED', 'PRAISONAI_TELEMETRY_DISABLED',
'DO_NOT_TRACK', 'PRAISONAI_DISABLE_TELEMETRY']:
self._orig_env[key] = os.environ.get(key)
if key in os.environ:
del os.environ[key]
def teardown_method(self):
"""Restore env vars after each test."""
import os
for key, value in self._orig_env.items():
if value is None:
if key in os.environ:
del os.environ[key]
else:
os.environ[key] = value
clear_modules()
def test_telemetry_disabled_by_default(self):
"""Telemetry should be disabled by default (opt-in model)."""
from praisonaiagents._config import TELEMETRY_ENABLED
assert TELEMETRY_ENABLED is False, \
"Telemetry should be disabled by default"
def test_telemetry_enabled_via_env(self):
"""Telemetry can be enabled via environment variable."""
import os
os.environ['PRAISONAI_TELEMETRY_ENABLED'] = 'true'
# Re-import to pick up new env var
clear_modules()
from praisonaiagents._config import _get_telemetry_enabled
assert _get_telemetry_enabled() is True
def test_do_not_track_takes_precedence(self):
"""DO_NOT_TRACK should disable telemetry even if enabled."""
import os
os.environ['PRAISONAI_TELEMETRY_ENABLED'] = 'true'
os.environ['DO_NOT_TRACK'] = 'true'
clear_modules()
from praisonaiagents._config import _get_telemetry_enabled
assert _get_telemetry_enabled() is False
if __name__ == "__main__":
pytest.main([__file__, "-v"])
@@ -0,0 +1,180 @@
"""
Tests for thread safety in praisonaiagents.
These tests verify that Agent chat_history and caches are thread-safe
when accessed concurrently.
"""
import sys
import threading
import time
import pytest
def clear_modules():
"""Clear all praisonai and litellm related modules from cache."""
to_remove = [m for m in list(sys.modules.keys())
if 'praison' in m or 'litellm' in m]
for mod in to_remove:
del sys.modules[mod]
class TestLiteAgentThreadSafety:
"""Test thread safety of LiteAgent (no external deps)."""
def test_concurrent_chat_history_access(self):
"""Multiple threads should safely access chat_history."""
from praisonaiagents.lite import LiteAgent
# Create agent with mock LLM
call_count = [0]
lock = threading.Lock()
def mock_llm(messages):
with lock:
call_count[0] += 1
time.sleep(0.01) # Simulate some work
return f"Response {call_count[0]}"
agent = LiteAgent(llm_fn=mock_llm)
errors = []
def worker(thread_id):
try:
for i in range(5):
response = agent.chat(f"Message from thread {thread_id}, iteration {i}")
assert response is not None
except Exception as e:
errors.append(f"Thread {thread_id}: {e}")
# Start multiple threads
threads = []
for i in range(4):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
# Wait for all threads
for t in threads:
t.join()
# Check for errors
assert len(errors) == 0, f"Thread errors: {errors}"
# Verify history was updated (should have entries from all threads)
assert len(agent.chat_history) > 0
def test_concurrent_clear_history(self):
"""Clearing history while other threads are chatting should be safe."""
from praisonaiagents.lite import LiteAgent
def mock_llm(messages):
return "Response"
agent = LiteAgent(llm_fn=mock_llm)
errors = []
stop_flag = threading.Event()
def chat_worker():
try:
while not stop_flag.is_set():
agent.chat("Hello")
time.sleep(0.001)
except Exception as e:
errors.append(f"Chat error: {e}")
def clear_worker():
try:
for _ in range(10):
time.sleep(0.01)
agent.clear_history()
except Exception as e:
errors.append(f"Clear error: {e}")
# Start chat threads
chat_threads = []
for _ in range(2):
t = threading.Thread(target=chat_worker)
chat_threads.append(t)
t.start()
# Start clear thread
clear_thread = threading.Thread(target=clear_worker)
clear_thread.start()
# Wait for clear thread to finish
clear_thread.join()
# Stop chat threads
stop_flag.set()
for t in chat_threads:
t.join()
assert len(errors) == 0, f"Thread errors: {errors}"
class TestAgentThreadSafety:
"""Test thread safety of main Agent class.
Note: These tests use a mock LLM to avoid network calls.
"""
def setup_method(self):
"""Set up test fixtures."""
clear_modules()
def test_agent_has_history_lock(self):
"""Agent should have a history lock for thread safety."""
from praisonaiagents import Agent
agent = Agent(
name="Test",
instructions="Test agent",
llm="gpt-4o-mini" # Won't actually call
)
assert hasattr(agent, '_history_lock')
assert isinstance(agent._history_lock, type(threading.Lock()))
def test_agent_has_cache_lock(self):
"""Agent should have a cache lock for thread safety."""
from praisonaiagents import Agent
agent = Agent(
name="Test",
instructions="Test agent",
llm="gpt-4o-mini"
)
assert hasattr(agent, '_cache_lock')
# RLock is a different type than Lock
assert agent._cache_lock is not None
class TestMCPCleanup:
"""Test MCP cleanup and context manager support."""
def test_mcp_has_context_manager(self):
"""MCP should support context manager protocol."""
try:
from praisonaiagents.mcp.mcp import MCP
except ImportError:
pytest.skip("MCP package not installed")
assert hasattr(MCP, '__enter__')
assert hasattr(MCP, '__exit__')
assert hasattr(MCP, 'shutdown')
def test_mcp_shutdown_method(self):
"""MCP should have a shutdown method."""
try:
from praisonaiagents.mcp.mcp import MCP
except ImportError:
pytest.skip("MCP package not installed")
assert callable(getattr(MCP, 'shutdown', None))
if __name__ == "__main__":
pytest.main([__file__, "-v"])
+1 -1
View File
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.3.2"
version = "0.5.1"
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"
]
}
}
}
@@ -0,0 +1,52 @@
/**
* Evaluation Example - Testing agent accuracy and performance
*
* Run: npx ts-node examples/features/evaluation.ts
*/
import { Agent, accuracyEval, performanceEval } from '../../src';
async function main() {
// Create an agent to evaluate
const agent = new Agent({
instructions: "You are a math tutor. Answer math questions accurately.",
llm: "gpt-4o-mini",
verbose: false
});
console.log("=== Accuracy Evaluation ===");
// Test accuracy with expected outputs
const testCases = [
{ input: "What is 2 + 2?", expected: "4" },
{ input: "What is 10 * 5?", expected: "50" },
{ input: "What is 100 / 4?", expected: "25" }
];
for (const test of testCases) {
const response = await agent.chat(test.input);
const passed = response.includes(test.expected);
console.log(` ${passed ? '✅' : '❌'} "${test.input}" → Expected: ${test.expected}`);
}
console.log("\n=== Performance Evaluation ===");
// Measure response time
const iterations = 3;
const times: number[] = [];
for (let i = 0; i < iterations; i++) {
const start = Date.now();
await agent.chat("What is 5 + 5?");
const duration = Date.now() - start;
times.push(duration);
console.log(` Run ${i + 1}: ${duration}ms`);
}
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
console.log(`\n Average: ${avgTime.toFixed(0)}ms`);
console.log(` Min: ${Math.min(...times)}ms`);
console.log(` Max: ${Math.max(...times)}ms`);
}
main().catch(console.error);
@@ -0,0 +1,49 @@
/**
* Guardrails Example - Input/output validation for agents
*
* Run: npx ts-node examples/features/guardrails.ts
*/
import { Agent, LLMGuardrail } from '../../src';
async function main() {
// Create an LLM-based guardrail
const contentGuardrail = new LLMGuardrail({
name: "ContentSafety",
criteria: "Content must be professional, non-offensive, and factually accurate",
llm: "gpt-4o-mini",
threshold: 0.7,
verbose: true
});
// Create agent
const agent = new Agent({
instructions: "You are a helpful assistant that provides accurate information.",
verbose: true
});
// Example: Check content before responding
const userInput = "Tell me about artificial intelligence";
// Validate input using check()
const inputCheck = await contentGuardrail.check(userInput);
console.log("Input validation:", inputCheck);
if (inputCheck.status === 'passed') {
const response = await agent.chat(userInput);
// Validate output
const outputCheck = await contentGuardrail.check(response);
console.log("Output validation:", outputCheck);
if (outputCheck.status === 'passed') {
console.log("\n✅ Response passed guardrails");
} else {
console.log("\n❌ Response failed guardrails:", outputCheck.reasoning);
}
} else {
console.log("❌ Input failed guardrails:", inputCheck.reasoning);
}
}
main().catch(console.error);
@@ -0,0 +1,64 @@
/**
* Knowledge Base Example - RAG with agents
*
* Run: npx ts-node examples/features/knowledge.ts
*/
import { Agent, BaseKnowledgeBase, type Knowledge } from '../../src';
async function main() {
// Create knowledge base
const kb = new BaseKnowledgeBase();
// Add knowledge entries
kb.addKnowledge({
id: "overview",
type: "documentation",
content: `
PraisonAI is a TypeScript SDK for building AI agents.
It supports multiple LLM providers including OpenAI, Anthropic, and Google.
Key features include:
- Simple Agent API with instructions
- Multi-agent orchestration with Agents class
- Workflow-based execution
- Tool integration with auto-schema generation
- Session persistence with db() factory
`,
metadata: { source: "docs", topic: "overview" }
});
kb.addKnowledge({
id: "agent-usage",
type: "documentation",
content: `
The Agent class is the primary entry point.
Create an agent with: new Agent({ instructions: "..." })
Use agent.chat("message") to interact.
Tools can be plain functions passed in the tools array.
`,
metadata: { source: "docs", topic: "agent" }
});
// Search the knowledge base
console.log("=== Searching Knowledge Base ===");
const results = kb.searchKnowledge("agent");
console.log("Found", results.length, "relevant entries");
results.forEach((r: Knowledge, i: number) => {
console.log(`\n[${i + 1}] ID: ${r.id}`);
console.log(String(r.content).substring(0, 100) + "...");
});
// Create agent that uses knowledge
const knowledgeContext = results.map((r: Knowledge) => r.content).join('\n\n');
const agent = new Agent({
instructions: `You are a helpful assistant. Use the following knowledge to answer questions:
${knowledgeContext}`,
verbose: true
});
console.log("\n=== Agent Response ===");
await agent.chat("How do I create an agent in PraisonAI?");
}
main().catch(console.error);
@@ -0,0 +1,37 @@
/**
* Memory Example - Using memory for agent context
*
* Run: npx ts-node examples/features/memory.ts
*/
import { Agent, Memory } from '../../src';
async function main() {
// Create a memory instance
const memory = new Memory({
maxEntries: 100,
maxTokens: 50000
});
// Add some context to memory
await memory.add("User prefers concise answers", "system", { priority: "high" });
await memory.add("User is working on a TypeScript project", "system", { topic: "programming" });
// Search memory
const results = await memory.search("TypeScript");
console.log("Memory search results:", results);
// Get recent entries
const recent = memory.getRecent(5);
console.log("Recent entries:", recent);
// Create agent
const agent = new Agent({
instructions: "You are a helpful assistant. Use context from memory.",
verbose: true
});
await agent.chat("Help me with my project");
}
main().catch(console.error);
@@ -0,0 +1,31 @@
/**
* Observability Example - Tracing and monitoring agents
*
* Run: npx ts-node examples/features/observability.ts
*/
import { Agent, setObservabilityAdapter, ConsoleObservabilityAdapter } from '../../src';
async function main() {
// Set up console observability (logs all traces)
const adapter = new ConsoleObservabilityAdapter();
setObservabilityAdapter(adapter);
console.log("=== Observability Enabled ===");
console.log("All agent operations will be traced to console.\n");
// Create agent
const agent = new Agent({
instructions: "You are a helpful assistant.",
verbose: true
});
// Run some operations - they will be traced
console.log("=== Running Agent ===");
await agent.chat("Hello! Tell me a joke.");
console.log("\n=== Trace Complete ===");
console.log("Check console output above for trace information.");
}
main().catch(console.error);
@@ -0,0 +1,50 @@
/**
* Planning Example - Using planning mode for complex tasks
*
* Run: npx ts-node examples/features/planning.ts
*/
import { Agent, Plan, PlanStep, TodoList, TodoItem } from '../../src';
async function main() {
// Create a plan for a complex task
console.log("=== Creating a Plan ===");
const plan = new Plan({
name: "Build a Web App",
description: "Plan to build a simple web application"
});
// Add steps to the plan
plan.addStep(new PlanStep({ description: "Initialize project structure" }));
plan.addStep(new PlanStep({ description: "Build API endpoints" }));
plan.addStep(new PlanStep({ description: "Create UI components" }));
plan.addStep(new PlanStep({ description: "Write and run tests" }));
plan.addStep(new PlanStep({ description: "Deploy to production" }));
console.log("Plan created with", plan.steps.length, "steps");
// Create a todo list
console.log("\n=== Creating a Todo List ===");
const todos = new TodoList();
todos.add(new TodoItem({ content: "Research frameworks", priority: "high" }));
todos.add(new TodoItem({ content: "Design database schema", priority: "medium" }));
todos.add(new TodoItem({ content: "Write documentation", priority: "low" }));
console.log("Todo list has", todos.items.length, "items");
// Create agent with planning context
const agent = new Agent({
instructions: `You are a project planning assistant.
Current plan: ${plan.name}
Steps: ${plan.steps.map((s: PlanStep) => s.description).join(', ')}
Help the user execute this plan.`,
verbose: true
});
await agent.chat("What should I do first?");
}
main().catch(console.error);
Loaded 100 of 418 files, more files were not shown because too many files have changed in this diff. Show more