Compare commits

...
22 Commits
Author SHA1 Message Date
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
342 changed files with 57869 additions and 1071 deletions

No files matched your search

+505 -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
@@ -3023,6 +3245,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.6.8" \
"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.6.8" \
"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.6.8" \
"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/
```
@@ -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>")
+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);
+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}")
@@ -61,6 +61,57 @@ class Agent:
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
@classmethod
def from_template(
cls,
uri: str,
config: Optional[Dict[str, Any]] = None,
offline: bool = False,
**kwargs
) -> 'Agent':
"""
Create an Agent from a template.
Args:
uri: Template URI (local path, package ref, or github ref)
Examples:
- "./my-template" (local path)
- "transcript-generator" (default recipes repo)
- "github:owner/repo/[email protected]" (GitHub with version)
- "package:agent_recipes/transcript-generator" (installed package)
config: Optional configuration overrides
offline: If True, only use cached templates (no network)
**kwargs: Additional Agent constructor arguments
Returns:
Configured Agent instance
Example:
```python
from praisonaiagents import Agent
# From default recipes repo
agent = Agent.from_template("transcript-generator")
result = agent.chat("Transcribe ./audio.mp3")
# With config overrides
agent = Agent.from_template(
"data-transformer",
config={"output_format": "json"},
verbose=True
)
```
"""
try:
# Lazy import to avoid circular dependencies and keep core SDK lean
from praisonai.templates.loader import create_agent_from_template
return create_agent_from_template(uri, config=config, offline=offline, **kwargs)
except ImportError:
raise ImportError(
"Template support requires the 'praisonai' package. "
"Install with: pip install praisonai"
)
def _generate_tool_definition(self, function_name):
"""
Generate a tool definition from a function name by inspecting the function.
@@ -1694,7 +1745,11 @@ Your Goal: {self.goal}"""
# Special handling for MCP tools
# Check if tools is an MCP instance with the requested function name
from ..mcp.mcp import MCP
MCP = None
try:
from ..mcp.mcp import MCP
except ImportError:
pass # MCP not available
# Helper function to execute MCP tool
def _execute_mcp_tool(mcp_instance, func_name, args):
@@ -1729,7 +1784,7 @@ Your Goal: {self.goal}"""
return False, None
# Check if tools is a single MCP instance
if isinstance(self.tools, MCP):
if MCP is not None and isinstance(self.tools, MCP):
logging.debug(f"Looking for MCP tool {function_name}")
found, result = _execute_mcp_tool(self.tools, function_name, arguments)
if found:
@@ -1738,7 +1793,7 @@ Your Goal: {self.goal}"""
# Check if tools is a list that may contain MCP instances
if isinstance(self.tools, (list, tuple)):
for tool in self.tools:
if isinstance(tool, MCP):
if MCP is not None and isinstance(tool, MCP):
logging.debug(f"Looking for MCP tool {function_name} in MCP instance")
found, result = _execute_mcp_tool(tool, function_name, arguments)
if found:
@@ -2160,8 +2215,12 @@ Your Goal: {self.goal}"""
# Convert MCP tool objects to OpenAI format if needed
if tool_param is not None:
from ..mcp.mcp import MCP
if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
MCP = None
try:
from ..mcp.mcp import MCP
except ImportError:
pass
if MCP is not None and isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
# Single MCP instance
logging.debug("Converting single MCP tool to OpenAI format")
openai_tool = tool_param.to_openai_tool()
@@ -2176,7 +2235,7 @@ Your Goal: {self.goal}"""
# List that may contain MCP instances - convert each MCP to OpenAI format
converted_tools = []
for t in tool_param:
if isinstance(t, MCP) and hasattr(t, 'to_openai_tool'):
if MCP is not None and isinstance(t, MCP) and hasattr(t, 'to_openai_tool'):
logging.debug("Converting MCP instance in list to OpenAI format")
openai_tools = t.to_openai_tool()
if isinstance(openai_tools, list):
@@ -3111,8 +3170,12 @@ Write the complete compiled report:"""
# Convert MCP tools if needed
if tool_param is not None:
from ..mcp.mcp import MCP
if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
MCP = None
try:
from ..mcp.mcp import MCP
except ImportError:
pass
if MCP is not None and isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
openai_tool = tool_param.to_openai_tool()
if openai_tool:
if isinstance(openai_tool, list):
@@ -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
+219 -2
View File
@@ -104,6 +104,24 @@ 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
@@ -294,6 +312,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 +375,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 +600,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 +1811,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 +1832,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
@@ -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.4.1"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
readme = "README.md"
requires-python = ">=3.10"
@@ -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"
+1 -1
View File
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.3.2"
version = "0.4.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);
@@ -0,0 +1,45 @@
/**
* LLM Providers Example - Using different LLM providers
*
* Run: npx ts-node examples/features/providers.ts
*/
import { Agent } from '../../src';
async function main() {
// OpenAI (default)
console.log("=== OpenAI Agent ===");
const openaiAgent = new Agent({
instructions: "You are a helpful assistant.",
llm: "gpt-4o-mini" // or just omit for default
});
await openaiAgent.chat("Hello from OpenAI!");
// Anthropic Claude
console.log("\n=== Anthropic Agent ===");
const anthropicAgent = new Agent({
instructions: "You are a helpful assistant.",
llm: "anthropic/claude-3-haiku-20240307"
});
// Uncomment if you have ANTHROPIC_API_KEY set
// await anthropicAgent.chat("Hello from Anthropic!");
// Google Gemini
console.log("\n=== Google Agent ===");
const googleAgent = new Agent({
instructions: "You are a helpful assistant.",
llm: "google/gemini-1.5-flash"
});
// Uncomment if you have GOOGLE_API_KEY set
// await googleAgent.chat("Hello from Google!");
// Model string formats
console.log("\n=== Model String Formats ===");
console.log("Supported formats:");
console.log(" - 'gpt-4o-mini' (defaults to OpenAI)");
console.log(" - 'openai/gpt-4o'");
console.log(" - 'anthropic/claude-3-sonnet-20240229'");
console.log(" - 'google/gemini-1.5-pro'");
}
main().catch(console.error);
@@ -0,0 +1,31 @@
/**
* Sessions Example - Conversation persistence with session IDs
*
* Run: npx ts-node examples/features/sessions.ts
*/
import { Agent, db } from '../../src';
async function main() {
// Create an agent with session persistence
const agent = new Agent({
instructions: "You are a helpful assistant that remembers our conversation.",
db: db("sqlite:./sessions.db"),
sessionId: "user-alice-123"
});
// First conversation
console.log("=== First Conversation ===");
await agent.chat("My name is Alice and I love programming.");
// Continue the conversation - agent remembers context
console.log("\n=== Continuing Conversation ===");
await agent.chat("What's my name and what do I love?");
// Get session info
console.log("\n=== Session Info ===");
console.log("Session ID:", agent.getSessionId());
console.log("Run ID:", agent.getRunId());
}
main().catch(console.error);
@@ -0,0 +1,51 @@
/**
* Skills Example - Using agent skills
*
* Run: npx ts-node examples/features/skills.ts
*/
import { Agent, SkillManager, type Skill } from '../../src';
async function main() {
// Create a skill manager
const skillManager = new SkillManager({
paths: ['./.praison/skills'],
includeProject: true,
includeUser: true
});
// Discover available skills
console.log("=== Discovering Skills ===");
const skills = await skillManager.discover();
if (skills.length === 0) {
console.log("No skills found. Create a skill in .praison/skills/");
console.log("\nExample SKILL.md:");
console.log(`---
name: code-review
description: Reviews code for best practices
---
# Code Review Skill
When reviewing code:
1. Check for security issues
2. Verify error handling
3. Suggest improvements
`);
} else {
skills.forEach((skill: Skill) => {
console.log(` - ${skill.metadata.name}: ${skill.metadata.description}`);
});
}
// Create agent (skills would be loaded automatically if configured)
const agent = new Agent({
instructions: "You are a helpful coding assistant.",
verbose: true
});
await agent.chat("Help me write clean code");
}
main().catch(console.error);
@@ -0,0 +1,35 @@
/**
* Multi-Agent Pattern - Orchestrate multiple agents
*
* Run: npx ts-node examples/patterns/multi-agent.ts
*/
import { Agent, Agents } from '../../src';
// Create specialized agents
const researcher = new Agent({
instructions: "You are a research specialist. Thoroughly research the given topic and provide detailed findings.",
name: "Researcher"
});
const writer = new Agent({
instructions: "You are a professional writer. Take the research provided and write a clear, engaging summary.",
name: "Writer"
});
// Orchestrate agents - they run sequentially, passing output to the next
const agents = new Agents([researcher, writer]);
// Alternative: use config object for more control
// const agents = new Agents({
// agents: [researcher, writer],
// process: 'sequential', // or 'parallel'
// verbose: true
// });
agents.start().then(results => {
console.log("\n=== Research Agent Output ===");
console.log(results[0]);
console.log("\n=== Writer Agent Output ===");
console.log(results[1]);
});
@@ -0,0 +1,21 @@
/**
* Streaming Pattern - Real-time response streaming
*
* Run: npx ts-node examples/patterns/streaming.ts
*/
import { Agent } from '../../src';
// Create an agent with streaming enabled
const agent = new Agent({
instructions: "You are a creative storyteller. Write engaging, detailed stories.",
name: "Storyteller",
stream: true, // Enable streaming (default is true)
verbose: true
});
// The response will stream to stdout as it's generated
console.log("=== Streaming Story ===\n");
agent.chat("Write a short story about a robot learning to paint.").then(() => {
console.log("\n\n=== Story Complete ===");
});
@@ -0,0 +1,41 @@
/**
* Workflow Pattern - Step-based pipeline with agent integration
*
* Run: npx ts-node examples/patterns/workflow.ts
*/
import { Agent, Workflow } from '../../src';
// Create agents for the workflow
const researcher = new Agent({
instructions: "You are a research specialist. Research the given topic thoroughly.",
name: "Researcher"
});
const analyzer = new Agent({
instructions: "You are a data analyst. Analyze the research and extract key insights.",
name: "Analyzer"
});
const writer = new Agent({
instructions: "You are a professional writer. Write a clear report based on the analysis.",
name: "Writer"
});
// Create a workflow with agent steps
const workflow = new Workflow("Research Pipeline")
.agent(researcher, "Research the topic")
.agent(analyzer, "Analyze the research findings")
.agent(writer, "Write a summary report");
// Run the workflow
workflow.run("Artificial Intelligence trends in 2025").then(result => {
console.log("\n=== Workflow Results ===");
console.log(`Steps completed: ${result.results.length}`);
console.log(`Final output:\n${result.output}`);
// Access individual step results
result.results.forEach((step, i) => {
console.log(`\nStep ${i + 1} (${step.stepName}): ${step.status} in ${step.duration}ms`);
});
});
@@ -0,0 +1,10 @@
/**
* Hello Agent - The simplest possible PraisonAI example (3 lines)
*
* Run: npx ts-node examples/quickstart/hello-agent.ts
*/
import { Agent } from '../../src';
const agent = new Agent({ instructions: "You are a helpful assistant" });
agent.chat("Hello! Tell me a fun fact about AI.").then(console.log);
@@ -0,0 +1,15 @@
/**
* Agent with Persistence - Auto-save messages to database (4 lines)
*
* Run: npx ts-node examples/quickstart/with-persistence.ts
*/
import { Agent, db } from '../../src';
const agent = new Agent({
instructions: "You are a helpful assistant with memory",
db: db("memory:"), // Use in-memory db for demo (use "sqlite:./data.db" for file)
sessionId: "my-session"
});
agent.chat("Remember that my favorite color is blue.").then(console.log);
@@ -0,0 +1,17 @@
/**
* Agent with Tools - Pass plain functions as tools (5 lines)
*
* Run: npx ts-node examples/quickstart/with-tools.ts
*/
import { Agent } from '../../src';
// Define a simple tool as a plain function
const getWeather = (city: string) => `Weather in ${city}: 22°C, Sunny`;
const agent = new Agent({
instructions: "You are a weather assistant. Use the getWeather tool to answer questions.",
tools: [getWeather]
});
agent.chat("What's the weather like in Paris?").then(console.log);
@@ -0,0 +1,157 @@
/**
* Test file for simplified TS SDK APIs
* Run with: npx tsx examples/simplified-api-test.ts
*/
import { Agent, db, Router, PlanningAgent, TaskAgent, AgentTelemetry } from '../src';
async function testSimpleAgent() {
console.log('\n=== Test 1: Simple Agent ===');
const agent = new Agent({
instructions: 'You are a helpful assistant. Be brief.',
verbose: false
});
const response = await agent.chat('What is 2+2? Answer in one word.');
console.log('Response:', response.slice(0, 100));
console.log('✅ Simple Agent works');
}
async function testAgentWithTools() {
console.log('\n=== Test 2: Agent with Tools ===');
const getWeather = (city: string) => `Weather in ${city}: 22°C, sunny`;
const calculate = (expr: string) => {
try {
return String(eval(expr));
} catch {
return 'Error';
}
};
const agent = new Agent({
instructions: 'You help with weather and calculations. Be brief.',
tools: [getWeather, calculate],
verbose: false
});
const response = await agent.chat('What is 5 + 3?');
console.log('Response:', response.slice(0, 100));
console.log('✅ Agent with Tools works');
}
async function testAgentWithPersistence() {
console.log('\n=== Test 3: Agent with Persistence (Memory) ===');
const agent = new Agent({
instructions: 'You remember conversations. Be brief.',
db: db('memory:'), // In-memory for testing
sessionId: 'test-session-123',
verbose: false
});
await agent.chat('My name is Alice.');
const response = await agent.chat('What is my name?');
console.log('Response:', response.slice(0, 100));
console.log('History length:', agent.getHistory().length);
console.log('✅ Agent with Persistence works');
}
async function testRouter() {
console.log('\n=== Test 4: Simplified Router ===');
const mathAgent = new Agent({
instructions: 'You are a math expert. Answer math questions briefly.',
verbose: false
});
const codeAgent = new Agent({
instructions: 'You are a coding expert. Answer coding questions briefly.',
verbose: false
});
const router = new Router({
math: { agent: mathAgent, keywords: ['calculate', 'math', 'sum', '+', '-', '*', '/'] },
code: { agent: codeAgent, keywords: ['code', 'program', 'function', 'javascript'] }
});
const response = await router.chat('Calculate 10 + 5');
console.log('Response:', response.slice(0, 100));
console.log('✅ Simplified Router works');
}
async function testPlanningAgent() {
console.log('\n=== Test 5: Planning Agent ===');
const agent = new PlanningAgent({
verbose: false,
maxSteps: 3 // Limit steps for testing
});
const plan = await agent.createPlan('Make a cup of tea');
console.log('Plan created with', plan.steps.length, 'steps');
console.log('First step:', plan.steps[0]?.description || 'N/A');
console.log('✅ Planning Agent works');
}
async function testTaskAgent() {
console.log('\n=== Test 6: Task Agent ===');
const agent = new TaskAgent({ verbose: false });
agent.addTask('Fix critical bug', 'high');
agent.addTask('Write documentation', 'medium');
agent.addTask('Review PR', 'low');
console.log('Pending tasks:', agent.getPendingTasks().length);
agent.completeTask('bug');
console.log('After completing bug task:', agent.getPendingTasks().length);
console.log('Progress:', agent.getProgress().percentage + '%');
console.log('✅ Task Agent works');
}
async function testAgentTelemetry() {
console.log('\n=== Test 7: Agent Telemetry ===');
const agent = new Agent({
instructions: 'Be brief.',
verbose: false
});
const telemetry = new AgentTelemetry('TestAgent');
await telemetry.trackChat(async () => {
return await agent.chat('Hello!');
});
await telemetry.trackChat(async () => {
return await agent.chat('How are you?');
});
const stats = telemetry.getStats();
console.log('Total chats:', stats.totalChats);
console.log('Success rate:', telemetry.getSuccessRate() + '%');
console.log('✅ Agent Telemetry works');
}
async function main() {
console.log('🚀 Testing Simplified TS SDK APIs\n');
try {
await testSimpleAgent();
await testAgentWithTools();
await testAgentWithPersistence();
await testRouter();
await testPlanningAgent();
await testTaskAgent();
await testAgentTelemetry();
console.log('\n✅ All tests passed!');
} catch (error) {
console.error('\n❌ Test failed:', error);
process.exit(1);
}
}
main();
+1 -1
View File
@@ -4,7 +4,7 @@ const config: Config.InitialOptions = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/tests'],
testMatch: ['**/*.main.test.ts', '**/*.spec.ts'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
moduleFileExtensions: ['ts', 'js', 'json'],
coverageDirectory: 'coverage',
collectCoverageFrom: [
+4 -2
View File
@@ -1,9 +1,12 @@
{
"name": "praisonai",
"version": "1.0.19",
"version": "1.3.2",
"description": "PraisonAI TypeScript AI Agents Framework - Node.js, npm, and Javascript AI Agents Framework",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
"praisonai-ts": "./dist/cli/index.js"
},
"scripts": {
"build": "tsc",
"test": "jest",
@@ -62,7 +65,6 @@
"fast-xml-parser": "^4.5.1",
"node-fetch": "^2.6.9",
"openai": "^4.81.0",
"praisonai": "^1.0.19",
"@modelcontextprotocol/sdk": "^1.12.1"
},
"optionalDependencies": {
+266
View File
@@ -0,0 +1,266 @@
# PraisonAI CLI Specification v1.0.0
# This is the canonical contract for CLI behavior across all runtimes (Python, TypeScript, etc.)
# Any CLI implementation MUST conform to this spec for parity.
version: "1.0.0"
spec_name: "praisonai-cli"
# Binary names per ecosystem (NO COLLISION)
binaries:
python: praisonai
typescript: praisonai-ts
# Commands (all implementations MUST support)
commands:
chat:
description: "Chat with an AI agent"
args:
- name: prompt
type: string
required: true
position: 0
flags:
- name: model
short: m
type: string
description: "Model to use (e.g., openai/gpt-4o-mini)"
- name: stream
short: s
type: boolean
default: false
description: "Enable streaming output"
- name: session
type: string
description: "Session ID for conversation continuity"
run:
description: "Run an agent with a task"
args:
- name: task
type: string
required: true
position: 0
flags:
- name: agent
short: a
type: string
description: "Agent configuration file or name"
- name: tools
short: t
type: string
description: "Comma-separated list of tools"
workflow:
description: "Execute a multi-agent workflow"
args:
- name: file
type: string
required: true
position: 0
description: "Workflow YAML file path"
flags:
- name: parallel
type: boolean
default: false
description: "Run workflow steps in parallel where possible"
eval:
description: "Evaluate agent performance"
subcommands:
accuracy:
description: "Run accuracy evaluation"
flags:
- name: input
type: string
required: true
- name: expected
type: string
required: true
- name: iterations
type: integer
default: 1
performance:
description: "Run performance benchmark"
flags:
- name: iterations
type: integer
default: 10
- name: warmup
type: integer
default: 2
reliability:
description: "Run reliability check"
flags:
- name: expected-tools
type: string
description: "Comma-separated expected tool calls"
providers:
description: "List available LLM providers"
flags: []
tools:
description: "List or manage tools"
subcommands:
list:
description: "List available tools"
info:
description: "Show tool information"
args:
- name: name
type: string
required: true
position: 0
version:
description: "Show CLI version"
flags: []
help:
description: "Show help information"
args:
- name: command
type: string
required: false
position: 0
# Global flags (apply to all commands)
global_flags:
- name: verbose
short: v
type: boolean
default: false
description: "Enable verbose output"
- name: config
short: c
type: string
description: "Path to config file"
- name: profile
short: p
type: string
description: "Profile name to use"
- name: output
short: o
type: string
enum: [json, text, pretty]
default: pretty
description: "Output format"
- name: json
type: boolean
default: false
description: "Shorthand for --output json"
# Exit codes (MUST be identical across implementations)
exit_codes:
success: 0
runtime_error: 1
invalid_arguments: 2
config_error: 3
network_error: 4
auth_error: 5
# Output schema for JSON mode (MUST be byte-compatible)
output_schema:
success:
type: object
properties:
success:
type: boolean
value: true
data:
type: object
meta:
type: object
properties:
duration_ms:
type: number
model:
type: string
tokens:
type: object
properties:
input:
type: integer
output:
type: integer
error:
type: object
properties:
success:
type: boolean
value: false
error:
type: object
properties:
code:
type: string
message:
type: string
details:
type: object
# Environment variables (MUST be respected)
environment:
PRAISONAI_MODEL:
description: "Default model to use"
example: "openai/gpt-4o-mini"
PRAISONAI_PROFILE:
description: "Default profile name"
PRAISONAI_VERBOSE:
description: "Enable verbose mode (true/false)"
PRAISONAI_CONFIG:
description: "Path to config file"
OPENAI_API_KEY:
description: "OpenAI API key"
ANTHROPIC_API_KEY:
description: "Anthropic API key"
GOOGLE_API_KEY:
description: "Google API key"
# Config file schema reference
config:
files:
- .praisonai.yaml
- .praisonai.json
resolution_order:
- cli_flags
- environment_variables
- project_config
- user_config
- defaults
schema:
model:
type: string
default: "openai/gpt-4o-mini"
verbose:
type: boolean
default: false
stream:
type: boolean
default: false
profiles:
type: object
additionalProperties:
type: object
properties:
model:
type: string
verbose:
type: boolean
stream:
type: boolean
# Tool interface contract
tool_schema:
name:
type: string
required: true
description:
type: string
required: true
inputs:
type: object
description: "JSON Schema for tool inputs"
outputs:
type: object
description: "JSON Schema for tool outputs"
+64
View File
@@ -0,0 +1,64 @@
{
"command": "chat",
"description": "Golden test fixtures for chat command JSON output parity",
"test_cases": [
{
"name": "simple_chat_success",
"input": {
"args": ["Hello, how are you?"],
"options": {
"output": "json"
}
},
"expected_schema": {
"success": true,
"data": {
"response": "string"
},
"meta": {
"duration_ms": "number",
"model": "string",
"tokens": {
"input": "number",
"output": "number"
}
}
}
},
{
"name": "chat_missing_prompt",
"input": {
"args": [],
"options": {
"output": "json"
}
},
"expected": {
"success": false,
"error": {
"code": "MISSING_ARGUMENT",
"message": "Please provide a prompt"
}
}
},
{
"name": "chat_with_model",
"input": {
"args": ["Test prompt"],
"options": {
"model": "openai/gpt-4o-mini",
"output": "json"
}
},
"expected_schema": {
"success": true,
"data": {
"response": "string"
},
"meta": {
"model": "openai/gpt-4o-mini"
}
}
}
]
}
+43
View File
@@ -0,0 +1,43 @@
{
"command": "run",
"description": "Golden test fixtures for run command JSON output parity",
"test_cases": [
{
"name": "run_success",
"input": {
"args": ["Analyze this data"],
"options": {
"output": "json"
}
},
"expected_schema": {
"success": true,
"data": {
"result": "string",
"agent": "string",
"task": "string"
},
"meta": {
"duration_ms": "number",
"model": "string"
}
}
},
{
"name": "run_missing_task",
"input": {
"args": [],
"options": {
"output": "json"
}
},
"expected": {
"success": false,
"error": {
"code": "MISSING_ARGUMENT",
"message": "Please provide a task"
}
}
}
]
}
@@ -0,0 +1,43 @@
{
"command": "workflow",
"description": "Golden test fixtures for workflow command JSON output parity",
"test_cases": [
{
"name": "workflow_success",
"input": {
"args": ["workflow.yaml"],
"options": {
"output": "json"
}
},
"expected_schema": {
"success": true,
"data": {
"workflow": "string",
"steps": "number",
"results": "object"
},
"meta": {
"duration_ms": "number",
"model": "string"
}
}
},
{
"name": "workflow_missing_file",
"input": {
"args": [],
"options": {
"output": "json"
}
},
"expected": {
"success": false,
"error": {
"code": "MISSING_ARGUMENT",
"message": "Please provide a workflow file path"
}
}
}
]
}
+159
View File
@@ -0,0 +1,159 @@
/**
* Context Agent - Agent with enhanced context management
*/
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
import { Session } from '../session';
import { KnowledgeBase, type SearchResult } from '../knowledge/rag';
export interface ContextAgentConfig {
name?: string;
instructions: string;
llm?: string;
knowledgeBase?: KnowledgeBase;
contextWindow?: number;
maxContextTokens?: number;
verbose?: boolean;
}
export interface ContextMessage {
role: 'system' | 'user' | 'assistant';
content: string;
timestamp: number;
metadata?: Record<string, any>;
}
/**
* Context Agent - Agent with RAG and context management
*/
export class ContextAgent {
readonly name: string;
readonly instructions: string;
private provider: LLMProvider;
private knowledgeBase?: KnowledgeBase;
private contextWindow: number;
private maxContextTokens: number;
private messages: ContextMessage[] = [];
private verbose: boolean;
constructor(config: ContextAgentConfig) {
this.name = config.name || `ContextAgent_${Math.random().toString(36).substr(2, 9)}`;
this.instructions = config.instructions;
this.provider = createProvider(config.llm || 'gpt-4o-mini');
this.knowledgeBase = config.knowledgeBase;
this.contextWindow = config.contextWindow ?? 10;
this.maxContextTokens = config.maxContextTokens ?? 4000;
this.verbose = config.verbose ?? false;
}
/**
* Chat with context awareness
*/
async chat(prompt: string): Promise<{ text: string; context?: SearchResult[] }> {
// Add user message
this.messages.push({
role: 'user',
content: prompt,
timestamp: Date.now()
});
// Build context from knowledge base
let ragContext: SearchResult[] = [];
let contextString = '';
if (this.knowledgeBase) {
ragContext = await this.knowledgeBase.search(prompt);
if (ragContext.length > 0) {
contextString = this.knowledgeBase.buildContext(ragContext);
if (this.verbose) {
console.log(`[ContextAgent] Found ${ragContext.length} relevant documents`);
}
}
}
// Build messages array with context
const systemPrompt = contextString
? `${this.instructions}\n\nRelevant context:\n${contextString}`
: this.instructions;
const messages: Message[] = [
{ role: 'system', content: systemPrompt },
...this.getRecentMessages()
];
// Generate response
const result = await this.provider.generateText({ messages });
// Add assistant message
this.messages.push({
role: 'assistant',
content: result.text,
timestamp: Date.now()
});
return {
text: result.text,
context: ragContext.length > 0 ? ragContext : undefined
};
}
/**
* Get recent messages within context window
*/
private getRecentMessages(): Message[] {
const recent = this.messages.slice(-this.contextWindow);
return recent.map(m => ({
role: m.role,
content: m.content
}));
}
/**
* Add document to knowledge base
*/
async addDocument(id: string, content: string, metadata?: Record<string, any>): Promise<void> {
if (!this.knowledgeBase) {
throw new Error('No knowledge base configured');
}
await this.knowledgeBase.add({ id, content, metadata });
}
/**
* Search knowledge base
*/
async searchKnowledge(query: string, limit?: number): Promise<SearchResult[]> {
if (!this.knowledgeBase) {
return [];
}
return this.knowledgeBase.search(query, limit);
}
/**
* Clear conversation history
*/
clearHistory(): void {
this.messages = [];
}
/**
* Get conversation history
*/
getHistory(): ContextMessage[] {
return [...this.messages];
}
/**
* Set knowledge base
*/
setKnowledgeBase(kb: KnowledgeBase): void {
this.knowledgeBase = kb;
}
}
/**
* Create a context agent
*/
export function createContextAgent(config: ContextAgentConfig): ContextAgent {
return new ContextAgent(config);
}
+337
View File
@@ -0,0 +1,337 @@
/**
* Enhanced Agent - Agent with session management, provider abstraction, and tool support
*/
import { createProvider, type LLMProvider, type Message, type ToolCall, type GenerateTextResult } from '../llm/providers';
import { Session, Run, Trace, getSessionManager } from '../session';
import { FunctionTool, ToolRegistry, tool } from '../tools/decorator';
import { Logger } from '../utils/logger';
export interface EnhancedAgentConfig {
name?: string;
instructions: string;
llm?: string;
tools?: Array<FunctionTool | Function | { name: string; description?: string; parameters?: any; execute: Function }>;
session?: Session;
sessionId?: string;
verbose?: boolean;
stream?: boolean;
maxToolCalls?: number;
temperature?: number;
maxTokens?: number;
outputSchema?: any;
}
export interface ChatOptions {
stream?: boolean;
temperature?: number;
maxTokens?: number;
outputSchema?: any;
onToken?: (token: string) => void;
}
export interface ChatResult {
text: string;
structured?: any;
toolCalls?: ToolCall[];
usage: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
runId: string;
sessionId: string;
}
/**
* Enhanced Agent with full feature support
*/
export class EnhancedAgent {
readonly name: string;
readonly instructions: string;
readonly sessionId: string;
private provider: LLMProvider;
private session: Session;
private toolRegistry: ToolRegistry;
private verbose: boolean;
private stream: boolean;
private maxToolCalls: number;
private temperature: number;
private maxTokens?: number;
private outputSchema?: any;
constructor(config: EnhancedAgentConfig) {
this.name = config.name || `Agent_${Math.random().toString(36).substr(2, 9)}`;
this.instructions = config.instructions;
this.verbose = config.verbose ?? true;
this.stream = config.stream ?? true;
this.maxToolCalls = config.maxToolCalls ?? 10;
this.temperature = config.temperature ?? 0.7;
this.maxTokens = config.maxTokens;
this.outputSchema = config.outputSchema;
// Initialize provider
this.provider = createProvider(config.llm || 'gpt-4o-mini');
// Initialize session
if (config.session) {
this.session = config.session;
} else if (config.sessionId) {
this.session = getSessionManager().getOrCreate(config.sessionId);
} else {
this.session = getSessionManager().create();
}
this.sessionId = this.session.id;
// Initialize tool registry
this.toolRegistry = new ToolRegistry();
if (config.tools) {
this.registerTools(config.tools);
}
Logger.setVerbose(this.verbose);
}
private registerTools(tools: EnhancedAgentConfig['tools']): void {
if (!tools) return;
for (const t of tools) {
if (t instanceof FunctionTool) {
this.toolRegistry.register(t);
} else if (typeof t === 'function') {
// Convert function to tool
const funcName = t.name || `function_${Math.random().toString(36).substr(2, 9)}`;
this.toolRegistry.register(tool({
name: funcName,
description: `Function ${funcName}`,
execute: t as any,
}));
} else if (typeof t === 'object' && 'execute' in t) {
// Tool config object
this.toolRegistry.register(tool({
name: t.name,
description: t.description,
parameters: t.parameters,
execute: t.execute as any,
}));
}
}
}
/**
* Add a tool to the agent
*/
addTool(t: FunctionTool | { name: string; description?: string; parameters?: any; execute: Function }): this {
if (t instanceof FunctionTool) {
this.toolRegistry.register(t);
} else {
this.toolRegistry.register(tool({
name: t.name,
description: t.description,
parameters: t.parameters,
execute: t.execute as any,
}));
}
return this;
}
/**
* Get all registered tools
*/
getTools(): FunctionTool[] {
return this.toolRegistry.list();
}
/**
* Chat with the agent
*/
async chat(prompt: string, options: ChatOptions = {}): Promise<ChatResult> {
const run = this.session.createRun().start();
const trace = run.createTrace({ name: 'chat' }).start();
try {
// Add user message to session
this.session.addMessage({ role: 'user', content: prompt });
// Build messages array
const messages: Message[] = [
{ role: 'system', content: this.instructions },
...this.session.getMessagesForLLM() as Message[],
];
// Get tool definitions
const tools = this.toolRegistry.list().length > 0
? this.toolRegistry.getDefinitions()
: undefined;
let result: GenerateTextResult;
let fullText = '';
let allToolCalls: ToolCall[] = [];
let iterations = 0;
// Tool calling loop
while (iterations < this.maxToolCalls) {
iterations++;
if (options.stream && !tools && options.onToken) {
// Streaming without tools
const stream = await this.provider.streamText({
messages,
temperature: options.temperature ?? this.temperature,
maxTokens: options.maxTokens ?? this.maxTokens,
onToken: options.onToken,
});
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
for await (const chunk of stream) {
if (chunk.text) fullText += chunk.text;
if (chunk.usage) usage = { ...usage, ...chunk.usage };
}
result = {
text: fullText,
usage,
finishReason: 'stop',
};
break;
} else if (options.outputSchema) {
// Structured output
const objResult = await this.provider.generateObject({
messages,
schema: options.outputSchema ?? this.outputSchema,
temperature: options.temperature ?? this.temperature,
maxTokens: options.maxTokens ?? this.maxTokens,
});
result = {
text: JSON.stringify(objResult.object),
usage: objResult.usage,
finishReason: 'stop',
};
fullText = result.text;
break;
} else {
// Regular generation with potential tool calls
result = await this.provider.generateText({
messages,
temperature: options.temperature ?? this.temperature,
maxTokens: options.maxTokens ?? this.maxTokens,
tools,
});
if (result.toolCalls && result.toolCalls.length > 0) {
allToolCalls.push(...result.toolCalls);
// Add assistant message with tool calls
messages.push({
role: 'assistant',
content: result.text || null,
tool_calls: result.toolCalls,
});
// Execute tool calls
for (const tc of result.toolCalls) {
const toolTrace = trace.createChild({ name: `tool:${tc.function.name}` }).start();
try {
const toolFn = this.toolRegistry.get(tc.function.name);
if (!toolFn) {
throw new Error(`Tool '${tc.function.name}' not found`);
}
const args = JSON.parse(tc.function.arguments);
const toolResult = await toolFn.execute(args, {
agentName: this.name,
sessionId: this.sessionId,
runId: run.id,
});
const resultStr = typeof toolResult === 'string'
? toolResult
: JSON.stringify(toolResult);
messages.push({
role: 'tool',
content: resultStr,
tool_call_id: tc.id,
});
toolTrace.complete({ result: resultStr });
} catch (error: any) {
messages.push({
role: 'tool',
content: `Error: ${error.message}`,
tool_call_id: tc.id,
});
toolTrace.fail(error);
}
}
// Continue loop to get final response
continue;
}
// No tool calls, we have our final response
fullText = result.text;
break;
}
}
// Add assistant message to session
this.session.addMessage({
role: 'assistant',
content: fullText,
tool_calls: allToolCalls.length > 0 ? allToolCalls : undefined,
});
trace.complete({ responseLength: fullText.length });
run.complete();
// Parse structured output if schema was provided
let structured: any;
if (options.outputSchema) {
try {
structured = JSON.parse(fullText);
} catch {
// Not valid JSON
}
}
return {
text: fullText,
structured,
toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
usage: result!.usage,
runId: run.id,
sessionId: this.sessionId,
};
} catch (error: any) {
trace.fail(error);
run.fail(error);
throw error;
}
}
/**
* Start method for compatibility
*/
async start(prompt: string, options?: ChatOptions): Promise<string> {
const result = await this.chat(prompt, options);
return result.text;
}
/**
* Get the session
*/
getSession(): Session {
return this.session;
}
/**
* Clear conversation history
*/
clearHistory(): void {
this.session.clearMessages();
}
}
+162
View File
@@ -0,0 +1,162 @@
/**
* Agent Handoff - Transfer conversations between agents
*/
import type { EnhancedAgent } from './enhanced';
export interface HandoffConfig {
agent: EnhancedAgent;
name?: string;
description?: string;
condition?: (context: HandoffContext) => boolean;
transformContext?: (messages: any[]) => any[];
}
export interface HandoffContext {
messages: any[];
lastMessage: string;
topic?: string;
metadata?: Record<string, any>;
}
export interface HandoffResult {
handedOffTo: string;
response: string;
context: HandoffContext;
}
/**
* Handoff class - Represents a handoff target
*/
export class Handoff {
readonly targetAgent: EnhancedAgent;
readonly name: string;
readonly description: string;
readonly condition?: (context: HandoffContext) => boolean;
readonly transformContext?: (messages: any[]) => any[];
constructor(config: HandoffConfig) {
this.targetAgent = config.agent;
this.name = config.name || `handoff_to_${config.agent.name}`;
this.description = config.description || `Transfer conversation to ${config.agent.name}`;
this.condition = config.condition;
this.transformContext = config.transformContext;
}
/**
* Check if handoff should be triggered
*/
shouldTrigger(context: HandoffContext): boolean {
if (this.condition) {
return this.condition(context);
}
return true;
}
/**
* Execute the handoff
*/
async execute(context: HandoffContext): Promise<HandoffResult> {
let messages = context.messages;
if (this.transformContext) {
messages = this.transformContext(messages);
}
// Transfer context to target agent
const response = await this.targetAgent.chat(context.lastMessage);
return {
handedOffTo: this.targetAgent.name,
response: response.text,
context: {
...context,
messages,
},
};
}
/**
* Get tool definition for LLM
*/
getToolDefinition(): { name: string; description: string; parameters: any } {
return {
name: this.name,
description: this.description,
parameters: {
type: 'object',
properties: {
reason: {
type: 'string',
description: 'Reason for the handoff',
},
},
required: [],
},
};
}
}
/**
* Create a handoff configuration
*/
export function handoff(config: HandoffConfig): Handoff {
return new Handoff(config);
}
/**
* Handoff filters - Common condition functions
*/
export const handoffFilters = {
/**
* Trigger handoff based on topic keywords
*/
topic: (keywords: string | string[]) => {
const keywordList = Array.isArray(keywords) ? keywords : [keywords];
return (context: HandoffContext): boolean => {
const lastMessage = context.lastMessage.toLowerCase();
return keywordList.some(kw => lastMessage.includes(kw.toLowerCase()));
};
},
/**
* Trigger handoff based on metadata
*/
metadata: (key: string, value: any) => {
return (context: HandoffContext): boolean => {
return context.metadata?.[key] === value;
};
},
/**
* Always trigger
*/
always: () => {
return (): boolean => true;
},
/**
* Never trigger (manual only)
*/
never: () => {
return (): boolean => false;
},
/**
* Combine multiple conditions with AND
*/
and: (...conditions: Array<(context: HandoffContext) => boolean>) => {
return (context: HandoffContext): boolean => {
return conditions.every(cond => cond(context));
};
},
/**
* Combine multiple conditions with OR
*/
or: (...conditions: Array<(context: HandoffContext) => boolean>) => {
return (context: HandoffContext): boolean => {
return conditions.some(cond => cond(context));
};
},
};
+130
View File
@@ -0,0 +1,130 @@
/**
* ImageAgent - Agent for image generation and analysis
*/
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
export interface ImageGenerationConfig {
prompt: string;
size?: '256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792';
quality?: 'standard' | 'hd';
style?: 'vivid' | 'natural';
n?: number;
}
export interface ImageAnalysisConfig {
imageUrl: string;
prompt?: string;
detail?: 'low' | 'high' | 'auto';
}
export interface ImageAgentConfig {
name?: string;
llm?: string;
imageModel?: string;
verbose?: boolean;
}
/**
* ImageAgent - Agent for image generation and analysis
*/
export class ImageAgent {
readonly name: string;
private provider: LLMProvider;
private imageModel: string;
private verbose: boolean;
constructor(config: ImageAgentConfig = {}) {
this.name = config.name || `ImageAgent_${Math.random().toString(36).substr(2, 9)}`;
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
this.imageModel = config.imageModel || 'dall-e-3';
this.verbose = config.verbose ?? false;
}
/**
* Analyze an image
*/
async analyze(config: ImageAnalysisConfig): Promise<string> {
const prompt = config.prompt || 'Describe this image in detail.';
const messages: Message[] = [
{
role: 'user',
content: [
{ type: 'text', text: prompt },
{
type: 'image_url',
image_url: {
url: config.imageUrl,
detail: config.detail || 'auto'
}
}
] as any
}
];
const result = await this.provider.generateText({ messages });
if (this.verbose) {
console.log(`[ImageAgent] Analysis: ${result.text.substring(0, 100)}...`);
}
return result.text;
}
/**
* Generate an image (requires OpenAI DALL-E)
*/
async generate(config: ImageGenerationConfig): Promise<string[]> {
// This would require direct OpenAI API call for image generation
// For now, return a placeholder
if (this.verbose) {
console.log(`[ImageAgent] Generating image: ${config.prompt}`);
}
throw new Error('Image generation requires direct OpenAI API integration. Use the OpenAI SDK directly for DALL-E.');
}
/**
* Chat with image context
*/
async chat(prompt: string, imageUrl?: string): Promise<string> {
if (imageUrl) {
return this.analyze({ imageUrl, prompt });
}
const result = await this.provider.generateText({
messages: [{ role: 'user', content: prompt }]
});
return result.text;
}
/**
* Compare two images
*/
async compare(imageUrl1: string, imageUrl2: string, prompt?: string): Promise<string> {
const comparePrompt = prompt || 'Compare these two images and describe the differences and similarities.';
const messages: Message[] = [
{
role: 'user',
content: [
{ type: 'text', text: comparePrompt },
{ type: 'image_url', image_url: { url: imageUrl1 } },
{ type: 'image_url', image_url: { url: imageUrl2 } }
] as any
}
];
const result = await this.provider.generateText({ messages });
return result.text;
}
}
/**
* Create an ImageAgent
*/
export function createImageAgent(config?: ImageAgentConfig): ImageAgent {
return new ImageAgent(config);
}
+31 -7
View File
@@ -1,12 +1,36 @@
// Export implementation based on mode
/**
* Agent Module - Core agent classes for PraisonAI
*
* The primary exports are:
* - Agent: Single agent with instructions, tools, and optional persistence
* - Agents: Multi-agent orchestration (alias for PraisonAIAgents)
* - Router: Simplified keyword/pattern-based routing
* - Workflow: Step-based workflow execution (from workflows module)
*/
// Core exports - the main API surface
export { Agent, PraisonAIAgents, Agents } from './simple';
export type { SimpleAgentConfig, PraisonAIAgentsConfig } from './simple';
// Router exports
export { Router, RouterAgent, createRouter, routeConditions } from './router';
export type { RouterConfig, RouteConfig, RouteContext, SimpleRouterConfig, SimpleRouteConfig } from './router';
// Task support (for advanced use cases)
export { Task } from './types';
export type { TaskConfig, AgentConfig as TaskAgentConfig } from './types';
// Legacy compatibility - setTaskMode is deprecated but kept for backward compat
let useTaskMode = false;
/**
* @deprecated Task mode is no longer needed. Use Agent with role/goal/backstory instead.
*/
export function setTaskMode(enabled: boolean) {
if (enabled) {
console.warn(
'setTaskMode() is deprecated. Use Agent({ role, goal, backstory }) instead of task mode.'
);
}
useTaskMode = enabled;
}
export { Agent, PraisonAIAgents, Task } from './proxy';
export type { ProxyAgentConfig } from './proxy';
export type { AgentConfig } from './types';
export type { TaskConfig } from './types';
export type { PraisonAIAgentsConfig, SimpleAgentConfig } from './simple';
@@ -0,0 +1,112 @@
/**
* PromptExpanderAgent - Expand and enhance prompts
*/
import { createProvider, type LLMProvider } from '../llm/providers';
export type ExpandStrategy = 'detail' | 'context' | 'examples' | 'constraints' | 'auto';
export interface ExpandResult {
original: string;
expanded: string;
strategy: ExpandStrategy;
additions: string[];
}
export interface PromptExpanderConfig {
name?: string;
llm?: string;
defaultStrategy?: ExpandStrategy;
verbose?: boolean;
}
/**
* PromptExpanderAgent - Expand prompts with more detail and context
*/
export class PromptExpanderAgent {
readonly name: string;
private provider: LLMProvider;
private defaultStrategy: ExpandStrategy;
private verbose: boolean;
constructor(config: PromptExpanderConfig = {}) {
this.name = config.name || `PromptExpander_${Math.random().toString(36).substr(2, 9)}`;
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
this.defaultStrategy = config.defaultStrategy || 'auto';
this.verbose = config.verbose ?? false;
}
/**
* Expand a prompt
*/
async expand(prompt: string, strategy?: ExpandStrategy): Promise<ExpandResult> {
const useStrategy = strategy || this.defaultStrategy;
const actualStrategy = useStrategy === 'auto' ? this.detectStrategy(prompt) : useStrategy;
const systemPrompt = this.getSystemPrompt(actualStrategy);
const result = await this.provider.generateText({
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: `Expand this prompt: ${prompt}` }
]
});
const expanded = result.text;
const additions = this.extractAdditions(prompt, expanded);
if (this.verbose) {
console.log(`[PromptExpander] Strategy: ${actualStrategy}, Added ${additions.length} elements`);
}
return {
original: prompt,
expanded,
strategy: actualStrategy,
additions
};
}
/**
* Detect the best expansion strategy
*/
detectStrategy(prompt: string): ExpandStrategy {
const words = prompt.split(/\s+/).length;
const lower = prompt.toLowerCase();
if (words < 5) return 'detail';
if (lower.includes('why') || lower.includes('explain') || lower.includes('context')) return 'context';
if (lower.includes('example') || lower.includes('show') || lower.includes('demonstrate')) return 'examples';
if (lower.includes('must') || lower.includes('should') || lower.includes('require')) return 'constraints';
return 'detail';
}
private getSystemPrompt(strategy: ExpandStrategy): string {
const prompts: Record<ExpandStrategy, string> = {
detail: 'Expand this prompt by adding specific details, requirements, and clarifications. Make it more precise and actionable.',
context: 'Expand this prompt by adding relevant background context, explaining the purpose, and providing necessary information.',
examples: 'Expand this prompt by adding concrete examples, sample inputs/outputs, and demonstrations of expected behavior.',
constraints: 'Expand this prompt by adding explicit constraints, requirements, limitations, and success criteria.',
auto: 'Expand this prompt to make it clearer, more specific, and more actionable. Add relevant details and context.'
};
return prompts[strategy];
}
private extractAdditions(original: string, expanded: string): string[] {
const originalWords = new Set(original.toLowerCase().split(/\s+/));
const expandedWords = expanded.toLowerCase().split(/\s+/);
const newWords = expandedWords.filter(w => !originalWords.has(w) && w.length > 3);
const uniqueNew = [...new Set(newWords)];
return uniqueNew.slice(0, 10);
}
}
/**
* Create a PromptExpanderAgent
*/
export function createPromptExpanderAgent(config?: PromptExpanderConfig): PromptExpanderAgent {
return new PromptExpanderAgent(config);
}
@@ -0,0 +1,106 @@
/**
* QueryRewriterAgent - Rewrite and optimize queries
*/
import { createProvider, type LLMProvider } from '../llm/providers';
export type RewriteStrategy = 'expand' | 'simplify' | 'decompose' | 'rephrase' | 'auto';
export interface RewriteResult {
original: string;
rewritten: string[];
strategy: RewriteStrategy;
confidence: number;
}
export interface QueryRewriterConfig {
name?: string;
llm?: string;
defaultStrategy?: RewriteStrategy;
verbose?: boolean;
}
/**
* QueryRewriterAgent - Optimize and rewrite queries
*/
export class QueryRewriterAgent {
readonly name: string;
private provider: LLMProvider;
private defaultStrategy: RewriteStrategy;
private verbose: boolean;
constructor(config: QueryRewriterConfig = {}) {
this.name = config.name || `QueryRewriter_${Math.random().toString(36).substr(2, 9)}`;
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
this.defaultStrategy = config.defaultStrategy || 'auto';
this.verbose = config.verbose ?? false;
}
/**
* Rewrite a query
*/
async rewrite(query: string, strategy?: RewriteStrategy): Promise<RewriteResult> {
const useStrategy = strategy || this.defaultStrategy;
const actualStrategy = useStrategy === 'auto' ? this.detectStrategy(query) : useStrategy;
const prompt = this.buildPrompt(query, actualStrategy);
const result = await this.provider.generateText({
messages: [
{ role: 'system', content: this.getSystemPrompt(actualStrategy) },
{ role: 'user', content: prompt }
]
});
const rewritten = this.parseRewrittenQueries(result.text);
if (this.verbose) {
console.log(`[QueryRewriter] Strategy: ${actualStrategy}, Rewrites: ${rewritten.length}`);
}
return {
original: query,
rewritten,
strategy: actualStrategy,
confidence: rewritten.length > 0 ? 0.85 : 0.5
};
}
private detectStrategy(query: string): RewriteStrategy {
const words = query.split(/\s+/).length;
if (words < 3) return 'expand';
if (words > 20) return 'simplify';
if (query.includes(' and ') || query.includes(' or ')) return 'decompose';
return 'rephrase';
}
private getSystemPrompt(strategy: RewriteStrategy): string {
const prompts: Record<RewriteStrategy, string> = {
expand: 'Expand this query with more context and detail. Generate 3 expanded versions.',
simplify: 'Simplify this query to its core intent. Generate 3 simpler versions.',
decompose: 'Break this query into smaller, focused sub-queries. Generate 3-5 sub-queries.',
rephrase: 'Rephrase this query in different ways while keeping the same meaning. Generate 3 versions.',
auto: 'Optimize this query for better search results. Generate 3 improved versions.'
};
return prompts[strategy] + ' Output one query per line.';
}
private buildPrompt(query: string, strategy: RewriteStrategy): string {
return `Query: ${query}\n\nRewrite using ${strategy} strategy:`;
}
private parseRewrittenQueries(response: string): string[] {
return response
.split('\n')
.map(line => line.replace(/^\d+[\.\)]\s*/, '').trim())
.filter(line => line.length > 0 && line.length < 500);
}
}
/**
* Create a QueryRewriterAgent
*/
export function createQueryRewriterAgent(config?: QueryRewriterConfig): QueryRewriterAgent {
return new QueryRewriterAgent(config);
}
+162
View File
@@ -0,0 +1,162 @@
/**
* DeepResearchAgent - Agent for comprehensive research tasks
*/
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
export interface Citation {
title: string;
url: string;
snippet?: string;
}
export interface ReasoningStep {
step: number;
thought: string;
action?: string;
result?: string;
}
export interface ResearchResponse {
answer: string;
citations: Citation[];
reasoning: ReasoningStep[];
confidence: number;
}
export interface DeepResearchConfig {
name?: string;
llm?: string;
maxIterations?: number;
searchTool?: (query: string) => Promise<Citation[]>;
verbose?: boolean;
}
/**
* DeepResearchAgent - Comprehensive research with citations
*/
export class DeepResearchAgent {
readonly name: string;
private provider: LLMProvider;
private maxIterations: number;
private searchTool?: (query: string) => Promise<Citation[]>;
private verbose: boolean;
constructor(config: DeepResearchConfig = {}) {
this.name = config.name || `ResearchAgent_${Math.random().toString(36).substr(2, 9)}`;
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
this.maxIterations = config.maxIterations ?? 5;
this.searchTool = config.searchTool;
this.verbose = config.verbose ?? false;
}
/**
* Conduct deep research on a topic
*/
async research(query: string): Promise<ResearchResponse> {
const reasoning: ReasoningStep[] = [];
const citations: Citation[] = [];
let iteration = 0;
// Step 1: Analyze the query
reasoning.push({
step: ++iteration,
thought: `Analyzing research query: "${query}"`,
action: 'query_analysis'
});
// Step 2: Generate search queries
const searchQueries = await this.generateSearchQueries(query);
reasoning.push({
step: ++iteration,
thought: `Generated ${searchQueries.length} search queries`,
action: 'generate_queries',
result: searchQueries.join(', ')
});
// Step 3: Execute searches if tool available
if (this.searchTool) {
for (const sq of searchQueries.slice(0, 3)) {
try {
const results = await this.searchTool(sq);
citations.push(...results);
reasoning.push({
step: ++iteration,
thought: `Searched for: "${sq}"`,
action: 'web_search',
result: `Found ${results.length} results`
});
} catch (error) {
if (this.verbose) {
console.log(`[Research] Search failed for: ${sq}`);
}
}
}
}
// Step 4: Synthesize answer
const answer = await this.synthesizeAnswer(query, citations);
reasoning.push({
step: ++iteration,
thought: 'Synthesizing final answer from gathered information',
action: 'synthesize',
result: 'Answer generated'
});
// Calculate confidence based on citations
const confidence = Math.min(0.5 + (citations.length * 0.1), 0.95);
return {
answer,
citations,
reasoning,
confidence
};
}
private async generateSearchQueries(query: string): Promise<string[]> {
const result = await this.provider.generateText({
messages: [
{
role: 'system',
content: 'Generate 3 search queries to research this topic. Output only the queries, one per line.'
},
{ role: 'user', content: query }
]
});
return result.text.split('\n').filter(q => q.trim().length > 0).slice(0, 3);
}
private async synthesizeAnswer(query: string, citations: Citation[]): Promise<string> {
const context = citations.length > 0
? `\n\nRelevant sources:\n${citations.map(c => `- ${c.title}: ${c.snippet || ''}`).join('\n')}`
: '';
const result = await this.provider.generateText({
messages: [
{
role: 'system',
content: 'You are a research assistant. Provide a comprehensive, well-structured answer based on the available information. Cite sources when available.'
},
{ role: 'user', content: `${query}${context}` }
]
});
return result.text;
}
/**
* Set search tool
*/
setSearchTool(tool: (query: string) => Promise<Citation[]>): void {
this.searchTool = tool;
}
}
/**
* Create a DeepResearchAgent
*/
export function createDeepResearchAgent(config?: DeepResearchConfig): DeepResearchAgent {
return new DeepResearchAgent(config);
}
+267
View File
@@ -0,0 +1,267 @@
/**
* Router Agent - Route requests to specialized agents
*
* @example Simple usage (5 lines)
* ```typescript
* import { Agent, Router } from 'praisonai';
*
* const router = new Router({
* math: { agent: new Agent({ instructions: 'Math expert' }), keywords: ['calculate', 'math'] },
* code: { agent: new Agent({ instructions: 'Code expert' }), keywords: ['code', 'program'] }
* });
* await router.chat('Calculate 2+2'); // Routes to math agent
* ```
*/
import type { EnhancedAgent } from './enhanced';
import { Agent } from './simple';
// Support both Agent and EnhancedAgent
type AnyAgent = Agent | EnhancedAgent;
export interface RouteConfig {
agent: AnyAgent;
condition: (input: string, context?: RouteContext) => boolean | Promise<boolean>;
priority?: number;
}
/** Simplified route definition */
export interface SimpleRouteConfig {
agent: AnyAgent;
keywords?: string[];
pattern?: RegExp;
priority?: number;
}
export interface RouteContext {
history?: string[];
metadata?: Record<string, any>;
}
export interface RouterConfig {
name?: string;
routes: RouteConfig[];
defaultAgent?: AnyAgent;
verbose?: boolean;
}
/** Simplified router config - just a map of route name to config */
export type SimpleRouterConfig = Record<string, SimpleRouteConfig>;
/**
* Router Agent - Routes requests to the most appropriate agent
*/
export class RouterAgent {
readonly name: string;
private routes: RouteConfig[];
private defaultAgent?: AnyAgent;
private verbose: boolean;
constructor(config: RouterConfig) {
this.name = config.name || 'Router';
this.routes = config.routes.sort((a, b) => (b.priority || 0) - (a.priority || 0));
this.defaultAgent = config.defaultAgent;
this.verbose = config.verbose ?? false;
}
/**
* Route a request to the appropriate agent
*/
async route(input: string, context?: RouteContext): Promise<{ agent: AnyAgent; response: string } | null> {
for (const route of this.routes) {
const matches = await route.condition(input, context);
if (matches) {
if (this.verbose) {
console.log(`[Router] Routing to: ${route.agent.name}`);
}
const response = await route.agent.chat(input);
// Handle both Agent (returns string) and EnhancedAgent (returns ChatResult)
const responseText = typeof response === 'string' ? response : response.text;
return { agent: route.agent, response: responseText };
}
}
if (this.defaultAgent) {
if (this.verbose) {
console.log(`[Router] Using default agent: ${this.defaultAgent.name}`);
}
const response = await this.defaultAgent.chat(input);
const responseText = typeof response === 'string' ? response : response.text;
return { agent: this.defaultAgent, response: responseText };
}
return null;
}
/**
* Simplified chat method - routes and returns just the response
*/
async chat(input: string, context?: RouteContext): Promise<string> {
const result = await this.route(input, context);
if (!result) {
throw new Error('No matching route found and no default agent configured');
}
return result.response;
}
/**
* Add a route
*/
addRoute(config: RouteConfig): this {
this.routes.push(config);
this.routes.sort((a, b) => (b.priority || 0) - (a.priority || 0));
return this;
}
/**
* Get all routes
*/
getRoutes(): RouteConfig[] {
return [...this.routes];
}
}
/**
* Route condition helpers
*/
export const routeConditions = {
/**
* Match by keywords
*/
keywords: (keywords: string | string[]) => {
const keywordList = Array.isArray(keywords) ? keywords : [keywords];
return (input: string): boolean => {
const lower = input.toLowerCase();
return keywordList.some(kw => lower.includes(kw.toLowerCase()));
};
},
/**
* Match by regex
*/
pattern: (regex: RegExp) => {
return (input: string): boolean => regex.test(input);
},
/**
* Match by metadata
*/
metadata: (key: string, value: any) => {
return (_input: string, context?: RouteContext): boolean => {
return context?.metadata?.[key] === value;
};
},
/**
* Always match (for default routes)
*/
always: () => {
return (): boolean => true;
},
/**
* Combine conditions with AND
*/
and: (...conditions: Array<(input: string, context?: RouteContext) => boolean>) => {
return (input: string, context?: RouteContext): boolean => {
return conditions.every(c => c(input, context));
};
},
/**
* Combine conditions with OR
*/
or: (...conditions: Array<(input: string, context?: RouteContext) => boolean>) => {
return (input: string, context?: RouteContext): boolean => {
return conditions.some(c => c(input, context));
};
}
};
/**
* Create a router agent (legacy API)
*/
export function createRouter(config: RouterConfig): RouterAgent {
return new RouterAgent(config);
}
/**
* Simplified Router class - uses keyword/pattern-based routing
*
* @example Simple usage (5 lines)
* ```typescript
* import { Agent, Router } from 'praisonai';
*
* const router = new Router({
* math: { agent: new Agent({ instructions: 'Math expert' }), keywords: ['calculate', 'math'] },
* code: { agent: new Agent({ instructions: 'Code expert' }), keywords: ['code', 'program'] }
* });
* await router.chat('Calculate 2+2'); // Routes to math agent
* ```
*/
export class Router {
private routerAgent: RouterAgent;
private agentMap: Map<string, AnyAgent> = new Map();
constructor(config: SimpleRouterConfig, options?: { default?: string; verbose?: boolean }) {
const routes: RouteConfig[] = [];
let defaultAgent: AnyAgent | undefined;
for (const [name, routeConfig] of Object.entries(config)) {
this.agentMap.set(name, routeConfig.agent);
// Build condition from keywords or pattern
let condition: (input: string) => boolean;
if (routeConfig.keywords) {
condition = routeConditions.keywords(routeConfig.keywords);
} else if (routeConfig.pattern) {
condition = routeConditions.pattern(routeConfig.pattern);
} else {
condition = routeConditions.always();
}
routes.push({
agent: routeConfig.agent,
condition,
priority: routeConfig.priority
});
// Set default agent
if (options?.default === name) {
defaultAgent = routeConfig.agent;
}
}
// If no default specified, use first agent
if (!defaultAgent && routes.length > 0) {
defaultAgent = routes[0].agent;
}
this.routerAgent = new RouterAgent({
routes,
defaultAgent,
verbose: options?.verbose
});
}
/**
* Route and get response
*/
async chat(input: string): Promise<string> {
return this.routerAgent.chat(input);
}
/**
* Route and get full result with agent info
*/
async route(input: string): Promise<{ agent: AnyAgent; response: string } | null> {
return this.routerAgent.route(input);
}
/**
* Get agent by name
*/
getAgent(name: string): AnyAgent | undefined {
return this.agentMap.get(name);
}
}
+343 -14
View File
@@ -1,17 +1,96 @@
import { OpenAIService } from '../llm/openai';
import { Logger } from '../utils/logger';
import type { ChatCompletionTool } from 'openai/resources/chat/completions';
import type { DbAdapter, DbMessage, DbRun } from '../db/types';
import { randomUUID } from 'crypto';
/**
* Agent Configuration
*
* The Agent class is the primary entry point for PraisonAI.
* It supports both simple instruction-based agents and advanced configurations.
*
* @example Simple usage (3 lines)
* ```typescript
* import { Agent } from 'praisonai';
* const agent = new Agent({ instructions: "You are helpful" });
* await agent.chat("Hello!");
* ```
*
* @example With tools (5 lines)
* ```typescript
* const getWeather = (city: string) => `Weather in ${city}: 20°C`;
* const agent = new Agent({
* instructions: "You provide weather info",
* tools: [getWeather]
* });
* await agent.chat("Weather in Paris?");
* ```
*
* @example With persistence (4 lines)
* ```typescript
* import { Agent, db } from 'praisonai';
* const agent = new Agent({
* instructions: "You are helpful",
* db: db("sqlite:./data.db"),
* sessionId: "my-session"
* });
* await agent.chat("Hello!");
* ```
*/
export interface SimpleAgentConfig {
/** Agent instructions/system prompt (required) */
instructions: string;
/** Agent name (auto-generated if not provided) */
name?: string;
/** Enable verbose logging (default: true) */
verbose?: boolean;
/** Enable pretty output formatting */
pretty?: boolean;
/**
* LLM model to use. Accepts:
* - Model name: "gpt-4o-mini", "claude-3-sonnet"
* - Provider/model: "openai/gpt-4o", "anthropic/claude-3"
* Default: "gpt-4o-mini"
*/
llm?: string;
/** Enable markdown formatting in responses */
markdown?: boolean;
/** Enable streaming responses (default: true) */
stream?: boolean;
/**
* Tools available to the agent.
* Can be plain functions (auto-schema) or OpenAI tool definitions.
*/
tools?: any[] | Function[];
/** Map of tool function implementations */
toolFunctions?: Record<string, Function>;
/** Database adapter for persistence */
db?: DbAdapter;
/** Session ID for conversation persistence (auto-generated if not provided) */
sessionId?: string;
/** Run ID for tracing (auto-generated if not provided) */
runId?: string;
/** Max messages to restore from history (default: 100) */
historyLimit?: number;
/** Auto-restore conversation history from db (default: true) */
autoRestore?: boolean;
/** Auto-persist messages to db (default: true) */
autoPersist?: boolean;
/** Enable caching of responses */
cache?: boolean;
/** Cache TTL in seconds (default: 3600) */
cacheTTL?: number;
/** Enable telemetry tracking (default: false, opt-in) */
telemetry?: boolean;
// Advanced mode (role/goal/backstory) - for compatibility
/** Agent role (advanced mode) */
role?: string;
/** Agent goal (advanced mode) */
goal?: string;
/** Agent backstory (advanced mode) */
backstory?: string;
}
export class Agent {
@@ -25,16 +104,50 @@ export class Agent {
private llmService: OpenAIService;
private tools?: any[];
private toolFunctions: Record<string, Function> = {};
private dbAdapter?: DbAdapter;
private sessionId: string;
private runId: string;
private messages: Array<{ role: string; content: string | null; tool_calls?: any[]; tool_call_id?: string }> = [];
private dbInitialized: boolean = false;
private historyLimit: number;
private autoRestore: boolean;
private autoPersist: boolean;
private cache: boolean;
private cacheTTL: number;
private responseCache: Map<string, { response: string; timestamp: number }> = new Map();
private telemetryEnabled: boolean;
constructor(config: SimpleAgentConfig) {
this.instructions = config.instructions;
// Build instructions from either simple or advanced mode
if (config.instructions) {
this.instructions = config.instructions;
} else if (config.role || config.goal || config.backstory) {
// Advanced mode: construct instructions from role/goal/backstory
const parts: string[] = [];
if (config.role) parts.push(`You are a ${config.role}.`);
if (config.goal) parts.push(`Your goal is: ${config.goal}`);
if (config.backstory) parts.push(`Background: ${config.backstory}`);
this.instructions = parts.join('\n');
} else {
this.instructions = 'You are a helpful AI assistant.';
}
this.name = config.name || `Agent_${Math.random().toString(36).substr(2, 9)}`;
this.verbose = config.verbose ?? process.env.PRAISON_VERBOSE !== 'false';
this.pretty = config.pretty ?? process.env.PRAISON_PRETTY === 'true';
this.llm = config.llm || 'gpt-5-nano';
this.llm = config.llm || process.env.OPENAI_MODEL_NAME || process.env.PRAISONAI_MODEL || 'gpt-4o-mini';
this.markdown = config.markdown ?? true;
this.stream = config.stream ?? true;
this.tools = config.tools;
this.dbAdapter = config.db;
this.sessionId = config.sessionId || this.generateSessionId();
this.runId = config.runId || randomUUID();
this.historyLimit = config.historyLimit ?? 100;
this.autoRestore = config.autoRestore ?? true;
this.autoPersist = config.autoPersist ?? true;
this.cache = config.cache ?? false;
this.cacheTTL = config.cacheTTL ?? 3600;
this.telemetryEnabled = config.telemetry ?? false;
this.llmService = new OpenAIService(this.llm);
// Configure logging
@@ -93,6 +206,70 @@ export class Agent {
}
}
/**
* Generate a session ID based on current hour and agent name (like Python SDK)
*/
private generateSessionId(): string {
const now = new Date();
const hourStr = now.toISOString().slice(0, 13).replace(/[-T:]/g, '');
const hash = this.name ? this.name.slice(0, 6) : 'agent';
return `${hourStr}-${hash}`;
}
/**
* Initialize DB session - restore history on first chat (lazy)
*/
private async initDbSession(): Promise<void> {
if (this.dbInitialized || !this.dbAdapter || !this.autoRestore) return;
try {
// Restore previous messages from DB
const history = await this.dbAdapter.getMessages(this.sessionId, this.historyLimit);
if (history.length > 0) {
this.messages = history.map(m => ({
role: m.role,
content: m.content
}));
Logger.debug(`Restored ${history.length} messages from session ${this.sessionId}`);
}
} catch (error) {
Logger.warn('Failed to initialize DB session:', error);
}
this.dbInitialized = true;
}
/**
* Get cached response if available and not expired
*/
private getCachedResponse(prompt: string): string | null {
if (!this.cache) return null;
const cacheKey = `${this.sessionId}:${prompt}`;
const cached = this.responseCache.get(cacheKey);
if (cached) {
const age = (Date.now() - cached.timestamp) / 1000;
if (age < this.cacheTTL) {
Logger.debug('Cache hit for prompt');
return cached.response;
}
// Expired, remove it
this.responseCache.delete(cacheKey);
}
return null;
}
/**
* Cache a response
*/
private cacheResponse(prompt: string, response: string): void {
if (!this.cache) return;
const cacheKey = `${this.sessionId}:${prompt}`;
this.responseCache.set(cacheKey, { response, timestamp: Date.now() });
}
private createSystemPrompt(): string {
let prompt = this.instructions;
if (this.markdown) {
@@ -238,27 +415,32 @@ export class Agent {
prompt = prompt.replace('{{previous}}', previousResult);
}
// Initialize messages array
// Initialize messages array with system prompt and conversation history
const messages: Array<any> = [
{ role: 'system', content: this.createSystemPrompt() },
{ role: 'user', content: prompt }
{ role: 'system', content: this.createSystemPrompt() }
];
// Add conversation history (excluding the current prompt which will be added below)
for (const msg of this.messages) {
if (msg.role && msg.content) {
messages.push({ role: msg.role, content: msg.content });
}
}
// Add current user prompt
messages.push({ role: 'user', content: prompt });
let finalResponse = '';
if (this.stream && !this.tools) {
// Use streaming without tools
let fullResponse = '';
await this.llmService.streamText(
prompt,
this.createSystemPrompt(),
// Use streaming with full conversation history
finalResponse = await this.llmService.streamChat(
messages,
0.7,
(token: string) => {
process.stdout.write(token);
fullResponse += token;
}
);
finalResponse = fullResponse;
} else if (this.tools) {
// Use tools (non-streaming for now to simplify implementation)
let continueConversation = true;
@@ -315,7 +497,37 @@ export class Agent {
}
async chat(prompt: string, previousResult?: string): Promise<string> {
return this.start(prompt, previousResult);
// Lazy init: restore history on first chat (like Python SDK)
await this.initDbSession();
// Check cache first
const cached = this.getCachedResponse(prompt);
if (cached) {
return cached;
}
// Add user message to conversation history
this.messages.push({ role: 'user', content: prompt });
// Persist user message if db is configured and autoPersist is enabled
if (this.dbAdapter && this.autoPersist) {
await this.persistMessage('user', prompt);
}
const response = await this.start(prompt, previousResult);
// Add assistant response to history
this.messages.push({ role: 'assistant', content: response });
// Persist assistant response if db is configured and autoPersist is enabled
if (this.dbAdapter && this.autoPersist) {
await this.persistMessage('assistant', response);
}
// Cache the response
this.cacheResponse(prompt, response);
return response;
}
async execute(previousResult?: string): Promise<string> {
@@ -323,6 +535,41 @@ export class Agent {
return this.start(this.instructions, previousResult);
}
/**
* Persist a message to the database
*/
private async persistMessage(role: 'user' | 'assistant' | 'system' | 'tool', content: string): Promise<void> {
if (!this.dbAdapter) return;
try {
const message: DbMessage = {
id: randomUUID(),
sessionId: this.sessionId,
runId: this.runId,
role,
content,
createdAt: Date.now()
};
await this.dbAdapter.saveMessage(message);
} catch (error) {
await Logger.warn('Failed to persist message:', error);
}
}
/**
* Get the session ID for this agent
*/
getSessionId(): string {
return this.sessionId;
}
/**
* Get the run ID for this agent
*/
getRunId(): string {
return this.runId;
}
getResult(): string | null {
return null;
}
@@ -330,8 +577,40 @@ export class Agent {
getInstructions(): string {
return this.instructions;
}
/**
* Get conversation history
*/
getHistory(): Array<{ role: string; content: string | null }> {
return [...this.messages];
}
/**
* Clear conversation history (in memory and optionally in DB)
*/
async clearHistory(clearDb: boolean = true): Promise<void> {
this.messages = [];
if (clearDb && this.dbAdapter) {
try {
await this.dbAdapter.deleteMessages(this.sessionId);
Logger.debug(`Cleared history for session ${this.sessionId}`);
} catch (error) {
Logger.warn('Failed to clear DB history:', error);
}
}
}
/**
* Clear response cache
*/
clearCache(): void {
this.responseCache.clear();
}
}
/**
* Configuration for multi-agent orchestration
*/
export interface PraisonAIAgentsConfig {
agents: Agent[];
tasks?: string[];
@@ -340,6 +619,28 @@ export interface PraisonAIAgentsConfig {
process?: 'sequential' | 'parallel';
}
/**
* Multi-agent orchestration class
*
* @example Simple array syntax
* ```typescript
* import { Agent, Agents } from 'praisonai';
*
* const researcher = new Agent({ instructions: "Research the topic" });
* const writer = new Agent({ instructions: "Write based on research" });
*
* const agents = new Agents([researcher, writer]);
* await agents.start();
* ```
*
* @example Config object syntax
* ```typescript
* const agents = new Agents({
* agents: [researcher, writer],
* process: 'parallel'
* });
* ```
*/
export class PraisonAIAgents {
private agents: Agent[];
private tasks: string[];
@@ -347,7 +648,16 @@ export class PraisonAIAgents {
private pretty: boolean;
private process: 'sequential' | 'parallel';
constructor(config: PraisonAIAgentsConfig) {
/**
* Create a multi-agent orchestration
* @param configOrAgents - Either an array of agents or a config object
*/
constructor(configOrAgents: PraisonAIAgentsConfig | Agent[]) {
// Support array syntax: new Agents([a1, a2])
const config: PraisonAIAgentsConfig = Array.isArray(configOrAgents)
? { agents: configOrAgents }
: configOrAgents;
this.agents = config.agents;
this.verbose = config.verbose ?? process.env.PRAISON_VERBOSE !== 'false';
this.pretty = config.pretty ?? process.env.PRAISON_PRETTY === 'true';
@@ -425,3 +735,22 @@ export class PraisonAIAgents {
return this.start();
}
}
/**
* Agents - Alias for PraisonAIAgents
*
* This is the recommended class name for multi-agent orchestration.
* PraisonAIAgents is kept for backward compatibility.
*
* @example
* ```typescript
* import { Agent, Agents } from 'praisonai';
*
* const agents = new Agents([
* new Agent({ instructions: "Research the topic" }),
* new Agent({ instructions: "Write based on research" })
* ]);
* await agents.start();
* ```
*/
export const Agents = PraisonAIAgents;
+185
View File
@@ -0,0 +1,185 @@
/**
* AutoAgents - Automatic agent generation from task descriptions
*/
import { createProvider, type LLMProvider, type Message } from '../llm/providers';
export interface AgentConfig {
name: string;
role: string;
goal: string;
backstory?: string;
instructions?: string;
tools?: string[];
}
export interface TaskConfig {
description: string;
expectedOutput?: string;
agent?: string;
}
export interface TeamStructure {
agents: AgentConfig[];
tasks: TaskConfig[];
pattern: 'sequential' | 'parallel' | 'hierarchical';
}
export interface AutoAgentsConfig {
llm?: string;
pattern?: 'sequential' | 'parallel' | 'routing' | 'orchestrator-workers' | 'evaluator-optimizer';
singleAgent?: boolean;
verbose?: boolean;
}
/**
* AutoAgents - Generate agent configurations from task descriptions
*/
export class AutoAgents {
private provider: LLMProvider;
private pattern: string;
private singleAgent: boolean;
private verbose: boolean;
constructor(config: AutoAgentsConfig = {}) {
this.provider = createProvider(config.llm || 'openai/gpt-4o-mini');
this.pattern = config.pattern || 'sequential';
this.singleAgent = config.singleAgent ?? false;
this.verbose = config.verbose ?? false;
}
/**
* Generate agent configuration from task description
*/
async generate(taskDescription: string): Promise<TeamStructure> {
const prompt = this.buildPrompt(taskDescription);
const result = await this.provider.generateText({
messages: [
{ role: 'system', content: this.getSystemPrompt() },
{ role: 'user', content: prompt }
]
});
return this.parseResponse(result.text);
}
/**
* Recommend a pattern for the task
*/
recommendPattern(taskDescription: string): string {
const lower = taskDescription.toLowerCase();
if (lower.includes('parallel') || lower.includes('concurrent') || lower.includes('simultaneously')) {
return 'parallel';
}
if (lower.includes('route') || lower.includes('classify') || lower.includes('categorize')) {
return 'routing';
}
if (lower.includes('orchestrat') || lower.includes('coordinat') || lower.includes('manage')) {
return 'orchestrator-workers';
}
if (lower.includes('evaluat') || lower.includes('optimi') || lower.includes('improv')) {
return 'evaluator-optimizer';
}
return 'sequential';
}
/**
* Analyze task complexity
*/
analyzeComplexity(taskDescription: string): 'simple' | 'moderate' | 'complex' {
const words = taskDescription.split(/\s+/).length;
const hasMultipleSteps = /step|then|after|before|first|second|third|finally/i.test(taskDescription);
const hasMultipleAgents = /team|multiple|several|different|various/i.test(taskDescription);
if (words < 20 && !hasMultipleSteps && !hasMultipleAgents) {
return 'simple';
}
if (words > 100 || (hasMultipleSteps && hasMultipleAgents)) {
return 'complex';
}
return 'moderate';
}
private getSystemPrompt(): string {
return `You are an AI agent architect. Your job is to analyze task descriptions and generate optimal agent configurations.
Output a JSON object with the following structure:
{
"agents": [
{
"name": "AgentName",
"role": "Role description",
"goal": "Agent's goal",
"backstory": "Optional backstory",
"instructions": "Specific instructions",
"tools": ["tool1", "tool2"]
}
],
"tasks": [
{
"description": "Task description",
"expectedOutput": "What the task should produce",
"agent": "AgentName"
}
],
"pattern": "sequential|parallel|hierarchical"
}
Guidelines:
- For simple tasks, use 1 agent
- For moderate tasks, use 2-3 agents
- For complex tasks, use 3-5 agents
- Match the pattern to the task requirements
- Be specific about roles and goals`;
}
private buildPrompt(taskDescription: string): string {
const complexity = this.analyzeComplexity(taskDescription);
const recommendedPattern = this.recommendPattern(taskDescription);
return `Task Description: ${taskDescription}
Complexity: ${complexity}
Recommended Pattern: ${this.pattern || recommendedPattern}
Single Agent Mode: ${this.singleAgent}
Generate an optimal agent configuration for this task.`;
}
private parseResponse(response: string): TeamStructure {
try {
// Extract JSON from response
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
} catch (error) {
// Fallback to default structure
}
// Default fallback
return {
agents: [{
name: 'GeneralAgent',
role: 'General purpose agent',
goal: 'Complete the assigned task',
instructions: 'Follow the task description carefully'
}],
tasks: [{
description: 'Complete the task',
agent: 'GeneralAgent'
}],
pattern: 'sequential'
};
}
}
/**
* Create an AutoAgents instance
*/
export function createAutoAgents(config?: AutoAgentsConfig): AutoAgents {
return new AutoAgents(config);
}
+253
View File
@@ -0,0 +1,253 @@
/**
* Cache System - In-memory and persistent caching for agents
* Inspired by mastra's cache module
*/
export interface CacheConfig {
name?: string;
ttl?: number; // Time to live in milliseconds
maxSize?: number; // Maximum number of entries
}
export interface CacheEntry<T = any> {
value: T;
createdAt: number;
expiresAt?: number;
}
/**
* Abstract base class for cache implementations
*/
export abstract class BaseCache {
readonly name: string;
constructor(config: CacheConfig = {}) {
this.name = config.name || 'cache';
}
abstract get<T = any>(key: string): Promise<T | undefined>;
abstract set<T = any>(key: string, value: T, ttl?: number): Promise<void>;
abstract delete(key: string): Promise<boolean>;
abstract clear(): Promise<void>;
abstract has(key: string): Promise<boolean>;
abstract keys(): Promise<string[]>;
abstract size(): Promise<number>;
// List operations
abstract listPush<T = any>(key: string, value: T): Promise<void>;
abstract listLength(key: string): Promise<number>;
abstract listRange<T = any>(key: string, start: number, end?: number): Promise<T[]>;
}
/**
* In-memory cache implementation
*/
export class MemoryCache extends BaseCache {
private cache: Map<string, CacheEntry> = new Map();
private lists: Map<string, any[]> = new Map();
private ttl?: number;
private maxSize?: number;
constructor(config: CacheConfig = {}) {
super(config);
this.ttl = config.ttl;
this.maxSize = config.maxSize;
}
async get<T = any>(key: string): Promise<T | undefined> {
const entry = this.cache.get(key);
if (!entry) return undefined;
// Check expiration
if (entry.expiresAt && Date.now() > entry.expiresAt) {
this.cache.delete(key);
return undefined;
}
return entry.value as T;
}
async set<T = any>(key: string, value: T, ttl?: number): Promise<void> {
// Enforce max size
if (this.maxSize && this.cache.size >= this.maxSize && !this.cache.has(key)) {
// Remove oldest entry
const firstKey = this.cache.keys().next().value;
if (firstKey) this.cache.delete(firstKey);
}
const effectiveTtl = ttl ?? this.ttl;
const entry: CacheEntry<T> = {
value,
createdAt: Date.now(),
expiresAt: effectiveTtl ? Date.now() + effectiveTtl : undefined
};
this.cache.set(key, entry);
}
async delete(key: string): Promise<boolean> {
const existed = this.cache.has(key);
this.cache.delete(key);
this.lists.delete(key);
return existed;
}
async clear(): Promise<void> {
this.cache.clear();
this.lists.clear();
}
async has(key: string): Promise<boolean> {
const entry = this.cache.get(key);
if (!entry) return false;
if (entry.expiresAt && Date.now() > entry.expiresAt) {
this.cache.delete(key);
return false;
}
return true;
}
async keys(): Promise<string[]> {
return Array.from(this.cache.keys());
}
async size(): Promise<number> {
return this.cache.size;
}
async listPush<T = any>(key: string, value: T): Promise<void> {
if (!this.lists.has(key)) {
this.lists.set(key, []);
}
this.lists.get(key)!.push(value);
}
async listLength(key: string): Promise<number> {
return this.lists.get(key)?.length ?? 0;
}
async listRange<T = any>(key: string, start: number, end?: number): Promise<T[]> {
const list = this.lists.get(key) ?? [];
return list.slice(start, end) as T[];
}
}
/**
* File-based cache implementation
*/
export class FileCache extends BaseCache {
private cacheDir: string;
private fs: any;
private path: any;
constructor(config: CacheConfig & { cacheDir?: string } = {}) {
super(config);
this.cacheDir = config.cacheDir || '.cache';
}
private async ensureDir(): Promise<void> {
if (!this.fs) {
this.fs = await import('fs/promises');
this.path = await import('path');
}
try {
await this.fs.mkdir(this.cacheDir, { recursive: true });
} catch {}
}
private getFilePath(key: string): string {
const safeKey = key.replace(/[^a-zA-Z0-9-_]/g, '_');
return this.path.join(this.cacheDir, `${safeKey}.json`);
}
async get<T = any>(key: string): Promise<T | undefined> {
await this.ensureDir();
try {
const data = await this.fs.readFile(this.getFilePath(key), 'utf-8');
const entry: CacheEntry<T> = JSON.parse(data);
if (entry.expiresAt && Date.now() > entry.expiresAt) {
await this.delete(key);
return undefined;
}
return entry.value;
} catch {
return undefined;
}
}
async set<T = any>(key: string, value: T, ttl?: number): Promise<void> {
await this.ensureDir();
const entry: CacheEntry<T> = {
value,
createdAt: Date.now(),
expiresAt: ttl ? Date.now() + ttl : undefined
};
await this.fs.writeFile(this.getFilePath(key), JSON.stringify(entry));
}
async delete(key: string): Promise<boolean> {
await this.ensureDir();
try {
await this.fs.unlink(this.getFilePath(key));
return true;
} catch {
return false;
}
}
async clear(): Promise<void> {
await this.ensureDir();
try {
const files = await this.fs.readdir(this.cacheDir);
await Promise.all(
files.filter((f: string) => f.endsWith('.json'))
.map((f: string) => this.fs.unlink(this.path.join(this.cacheDir, f)))
);
} catch {}
}
async has(key: string): Promise<boolean> {
return (await this.get(key)) !== undefined;
}
async keys(): Promise<string[]> {
await this.ensureDir();
try {
const files = await this.fs.readdir(this.cacheDir);
return files
.filter((f: string) => f.endsWith('.json'))
.map((f: string) => f.replace('.json', ''));
} catch {
return [];
}
}
async size(): Promise<number> {
return (await this.keys()).length;
}
async listPush<T = any>(key: string, value: T): Promise<void> {
const list = (await this.get<T[]>(`list_${key}`)) || [];
list.push(value);
await this.set(`list_${key}`, list);
}
async listLength(key: string): Promise<number> {
const list = (await this.get<any[]>(`list_${key}`)) || [];
return list.length;
}
async listRange<T = any>(key: string, start: number, end?: number): Promise<T[]> {
const list = (await this.get<T[]>(`list_${key}`)) || [];
return list.slice(start, end);
}
}
// Factory functions
export function createMemoryCache(config?: CacheConfig): MemoryCache {
return new MemoryCache(config);
}
export function createFileCache(config?: CacheConfig & { cacheDir?: string }): FileCache {
return new FileCache(config);
}
+148
View File
@@ -0,0 +1,148 @@
/**
* Agent command - Run a single agent with instructions
*
* Usage:
* praisonai-ts agent chat "Hello, how are you?"
* praisonai-ts agent run --instructions "You are helpful" "Tell me a joke"
*/
import { Agent } from '../../agent';
import { resolveConfig } from '../config/resolve';
import { printSuccess, printError, outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
import { ERROR_CODES, normalizeError } from '../output/errors';
export interface AgentOptions {
instructions?: string;
model?: string;
stream?: boolean;
verbose?: boolean;
profile?: string;
config?: string;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
sessionId?: string;
}
/**
* Execute agent chat subcommand
*/
export async function executeChat(args: string[], options: AgentOptions): Promise<void> {
const prompt = args[0];
if (!prompt) {
if (options.json || options.output === 'json') {
printError(ERROR_CODES.MISSING_ARG, 'Please provide a prompt');
} else {
await pretty.error('Please provide a prompt');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
const config = resolveConfig({
configPath: options.config,
profile: options.profile,
model: options.model,
verbose: options.verbose,
stream: options.stream
});
const startTime = Date.now();
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
try {
const agent = new Agent({
instructions: options.instructions || 'You are a helpful AI assistant.',
llm: config.model,
stream: config.stream && outputFormat !== 'json',
verbose: config.verbose,
sessionId: options.sessionId
});
const response = await agent.chat(prompt);
const duration = Date.now() - startTime;
if (outputFormat === 'json') {
outputJson(formatSuccess(
{
response,
agent: agent.name,
sessionId: agent.getSessionId()
},
{
duration_ms: duration,
model: config.model
}
));
} else if (outputFormat === 'text') {
console.log(response);
} else {
// Pretty output - response already printed if streaming
if (!config.stream) {
console.log(response);
}
console.log(); // Newline
await pretty.dim(`Completed in ${duration}ms`);
}
} catch (error) {
const cliError = normalizeError(error);
if (outputFormat === 'json') {
outputJson(formatError(cliError.code, cliError.message, cliError.details));
} else {
await pretty.error(cliError.message);
if (config.verbose && error instanceof Error && error.stack) {
await pretty.dim(error.stack);
}
}
process.exit(cliError.exitCode);
}
}
/**
* Execute agent run subcommand (same as chat but requires instructions)
*/
export async function executeRun(args: string[], options: AgentOptions): Promise<void> {
if (!options.instructions) {
if (options.json || options.output === 'json') {
printError(ERROR_CODES.MISSING_ARG, 'Please provide --instructions');
} else {
await pretty.error('Please provide --instructions');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
return executeChat(args, options);
}
/**
* Main execute function - routes to subcommands
*/
export async function execute(args: string[], options: AgentOptions): Promise<void> {
const subcommand = args[0];
const subArgs = args.slice(1);
switch (subcommand) {
case 'chat':
return executeChat(subArgs, options);
case 'run':
return executeRun(subArgs, options);
default:
// If no subcommand, treat the first arg as a prompt for chat
if (subcommand) {
return executeChat(args, options);
}
if (options.json || options.output === 'json') {
printError(ERROR_CODES.MISSING_ARG, 'Please provide a subcommand (chat, run) or a prompt');
} else {
await pretty.error('Usage: praisonai-ts agent [chat|run] <prompt>');
await pretty.info(' agent chat "Hello" - Chat with default agent');
await pretty.info(' agent run --instructions "..." "Hello" - Run with custom instructions');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
}
+167
View File
@@ -0,0 +1,167 @@
/**
* Agents command - Run multi-agent orchestration
*
* Usage:
* praisonai-ts agents run --agents "researcher,writer" "Research AI trends"
*/
import { Agent, Agents } from '../../agent';
import { resolveConfig } from '../config/resolve';
import { printSuccess, printError, outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
import { ERROR_CODES, normalizeError } from '../output/errors';
export interface AgentsOptions {
agents?: string; // Comma-separated agent instructions
process?: 'sequential' | 'parallel';
model?: string;
verbose?: boolean;
profile?: string;
config?: string;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
}
/**
* Parse agent definitions from comma-separated string
*/
function parseAgentDefinitions(agentsStr: string): string[] {
return agentsStr.split(',').map(s => s.trim()).filter(s => s.length > 0);
}
/**
* Execute agents run subcommand
*/
export async function executeRun(args: string[], options: AgentsOptions): Promise<void> {
const prompt = args[0];
if (!prompt) {
if (options.json || options.output === 'json') {
printError(ERROR_CODES.MISSING_ARG, 'Please provide a prompt/task');
} else {
await pretty.error('Please provide a prompt/task');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
const config = resolveConfig({
configPath: options.config,
profile: options.profile,
model: options.model,
verbose: options.verbose
});
const startTime = Date.now();
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
try {
// Create agents from definitions or use defaults
let agentList: Agent[];
if (options.agents) {
const definitions = parseAgentDefinitions(options.agents);
agentList = definitions.map((instructions, i) =>
new Agent({
instructions,
name: `Agent_${i + 1}`,
llm: config.model,
verbose: config.verbose
})
);
} else {
// Default: create a researcher and writer
agentList = [
new Agent({
instructions: `Research the following topic thoroughly: ${prompt}`,
name: 'Researcher',
llm: config.model,
verbose: config.verbose
}),
new Agent({
instructions: 'Summarize and write a clear report based on the research provided.',
name: 'Writer',
llm: config.model,
verbose: config.verbose
})
];
}
if (outputFormat !== 'json') {
await pretty.heading(`Running ${agentList.length} agents (${options.process || 'sequential'})`);
}
const agents = new Agents({
agents: agentList,
process: options.process || 'sequential',
verbose: config.verbose
});
const results = await agents.start();
const duration = Date.now() - startTime;
if (outputFormat === 'json') {
outputJson(formatSuccess(
{
results,
agentCount: agentList.length,
process: options.process || 'sequential'
},
{
duration_ms: duration,
model: config.model
}
));
} else if (outputFormat === 'text') {
results.forEach((result, i) => {
console.log(`\n--- Agent ${i + 1} ---\n${result}`);
});
} else {
// Pretty output
console.log();
await pretty.success(`Completed ${agentList.length} agents in ${duration}ms`);
}
} catch (error) {
const cliError = normalizeError(error);
if (outputFormat === 'json') {
outputJson(formatError(cliError.code, cliError.message, cliError.details));
} else {
await pretty.error(cliError.message);
if (config.verbose && error instanceof Error && error.stack) {
await pretty.dim(error.stack);
}
}
process.exit(cliError.exitCode);
}
}
/**
* Main execute function - routes to subcommands
*/
export async function execute(args: string[], options: AgentsOptions): Promise<void> {
const subcommand = args[0];
const subArgs = args.slice(1);
switch (subcommand) {
case 'run':
return executeRun(subArgs, options);
default:
// If no subcommand, treat as run
if (subcommand) {
return executeRun(args, options);
}
if (options.json || options.output === 'json') {
printError(ERROR_CODES.MISSING_ARG, 'Please provide a task/prompt');
} else {
await pretty.error('Usage: praisonai-ts agents [run] <task>');
await pretty.info(' agents run "Research AI" - Run with default agents');
await pretty.info(' agents run --agents "..." "Research AI" - Run with custom agents');
await pretty.info(' agents run --process parallel "Research AI" - Run in parallel');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
}
+90
View File
@@ -0,0 +1,90 @@
/**
* Auto command - AutoAgents for automatic agent generation
*/
import { AutoAgents, createAutoAgents } from '../../auto';
import { resolveConfig } from '../config/resolve';
import { outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
import { ERROR_CODES } from '../output/errors';
export interface AutoOptions {
model?: string;
verbose?: boolean;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
pattern?: string;
agents?: number;
}
export async function execute(args: string[], options: AutoOptions): Promise<void> {
const topic = args.join(' ');
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
const config = resolveConfig(options);
if (!topic) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a topic for auto-generation'));
} else {
await pretty.error('Please provide a topic for auto-generation');
await pretty.dim('Usage: praisonai-ts auto "Create a research team"');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
const startTime = Date.now();
try {
if (outputFormat !== 'json') {
await pretty.info(`Generating agents for: "${topic}"`);
}
const autoAgents = createAutoAgents({
llm: config.model,
pattern: options.pattern as any,
verbose: options.verbose
});
const result = await autoAgents.generate(topic);
const duration = Date.now() - startTime;
if (outputFormat === 'json') {
outputJson(formatSuccess({
topic,
agents: result.agents,
tasks: result.tasks,
pattern: result.pattern
}, {
duration_ms: duration,
model: config.model
}));
} else {
await pretty.heading('Generated Team');
await pretty.plain(`Pattern: ${result.pattern}`);
await pretty.newline();
await pretty.plain('Agents:');
for (const agent of result.agents) {
await pretty.plain(` • ${agent.name}: ${agent.role}`);
}
await pretty.newline();
await pretty.plain('Tasks:');
for (const task of result.tasks) {
await pretty.plain(` • ${task.description?.substring(0, 60)}...`);
}
await pretty.newline();
await pretty.success(`Generated in ${duration}ms`);
}
} catch (error) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
} else {
await pretty.error(error instanceof Error ? error.message : String(error));
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
}
@@ -0,0 +1,163 @@
/**
* CLI command: autonomy
* Autonomy mode management for agent actions
*/
import { AutonomyManager, createAutonomyManager, MODE_POLICIES, type AutonomyMode } from '../features/autonomy-mode';
let manager: AutonomyManager | null = null;
function getManager(): AutonomyManager {
if (!manager) {
manager = createAutonomyManager();
}
return manager;
}
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
const subcommand = args[0] || 'help';
const isJson = Boolean(options.output === 'json' || options.json);
switch (subcommand) {
case 'status':
await handleStatus(isJson);
break;
case 'set':
await handleSet(args.slice(1), isJson);
break;
case 'policies':
await handlePolicies(args.slice(1), isJson);
break;
case 'reset':
await handleReset(isJson);
break;
case 'help':
default:
showHelp(isJson);
}
}
async function handleStatus(isJson: boolean): Promise<void> {
const mgr = getManager();
const summary = mgr.getSummary();
const mode = mgr.getMode();
const actionCount = mgr.getActionCount();
if (isJson) {
console.log(JSON.stringify({
success: true,
mode,
actionCount,
policies: MODE_POLICIES[mode]
}, null, 2));
} else {
console.log(summary);
}
}
async function handleSet(args: string[], isJson: boolean): Promise<void> {
const mode = args[0] as AutonomyMode;
if (!mode || !['suggest', 'auto_edit', 'full_auto'].includes(mode)) {
console.error('Error: Valid mode required (suggest, auto_edit, full_auto)');
process.exit(1);
}
const mgr = getManager();
mgr.setMode(mode);
if (isJson) {
console.log(JSON.stringify({ success: true, mode }));
} else {
console.log(`✓ Autonomy mode set to: ${mode}`);
console.log('\nPolicies for this mode:');
for (const policy of MODE_POLICIES[mode]) {
const status = policy.requiresApproval ? '⚠️ requires approval' : '✓ auto-approved';
console.log(` ${policy.action}: ${status}`);
}
}
}
async function handlePolicies(args: string[], isJson: boolean): Promise<void> {
const mode = (args[0] as AutonomyMode) || 'suggest';
if (!['suggest', 'auto_edit', 'full_auto'].includes(mode)) {
console.error('Error: Valid mode required (suggest, auto_edit, full_auto)');
process.exit(1);
}
const policies = MODE_POLICIES[mode];
if (isJson) {
console.log(JSON.stringify({ success: true, mode, policies }, null, 2));
} else {
console.log(`Policies for mode: ${mode}\n`);
for (const policy of policies) {
const status = policy.requiresApproval ? '⚠️ requires approval' : '✓ auto-approved';
console.log(` ${policy.action.padEnd(20)} ${status}`);
}
}
}
async function handleReset(isJson: boolean): Promise<void> {
const mgr = getManager();
mgr.clearRemembered();
mgr.resetActionCount();
mgr.setMode('suggest');
if (isJson) {
console.log(JSON.stringify({ success: true, message: 'Autonomy state reset' }));
} else {
console.log('✓ Autonomy state reset');
console.log(' Mode: suggest');
console.log(' Remembered decisions: cleared');
console.log(' Action count: 0');
}
}
function showHelp(isJson: boolean): void {
const help = {
command: 'autonomy',
description: 'Autonomy mode management for agent actions',
subcommands: {
status: 'Show current autonomy status',
set: 'Set autonomy mode',
policies: 'Show policies for a mode',
reset: 'Reset autonomy state'
},
modes: {
suggest: 'Requires approval for most actions (default)',
auto_edit: 'Auto-approves file edits, requires approval for others',
full_auto: 'Auto-approves most actions (use with caution)'
},
flags: {
'--json': 'Output in JSON format'
},
examples: [
'praisonai-ts autonomy status',
'praisonai-ts autonomy set suggest',
'praisonai-ts autonomy set auto_edit',
'praisonai-ts autonomy set full_auto',
'praisonai-ts autonomy policies suggest',
'praisonai-ts autonomy reset'
]
};
if (isJson) {
console.log(JSON.stringify(help, null, 2));
} else {
console.log('Autonomy - Agent action approval management\n');
console.log('Subcommands:');
for (const [cmd, desc] of Object.entries(help.subcommands)) {
console.log(` ${cmd.padEnd(12)} ${desc}`);
}
console.log('\nModes:');
for (const [mode, desc] of Object.entries(help.modes)) {
console.log(` ${mode.padEnd(12)} ${desc}`);
}
console.log('\nExamples:');
for (const ex of help.examples) {
console.log(` ${ex}`);
}
}
}
+117
View File
@@ -0,0 +1,117 @@
/**
* Cache command - Caching management
*/
import { outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
import { ERROR_CODES } from '../output/errors';
export interface CacheOptions {
verbose?: boolean;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
}
export async function execute(args: string[], options: CacheOptions): Promise<void> {
const action = args[0] || 'help';
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
try {
switch (action) {
case 'info':
await showInfo(outputFormat);
break;
case 'providers':
await listProviders(outputFormat);
break;
case 'help':
default:
await showHelp(outputFormat);
break;
}
} catch (error) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
} else {
await pretty.error(error instanceof Error ? error.message : String(error));
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
}
async function showInfo(outputFormat: string): Promise<void> {
const info = {
feature: 'Cache',
description: 'Caching system for LLM responses and computations',
providers: [
{ name: 'MemoryCache', description: 'In-memory cache (default)' },
{ name: 'FileCache', description: 'File-based persistent cache' }
],
capabilities: [
'Cache LLM responses',
'Reduce API costs',
'Improve response times',
'TTL-based expiration',
'Configurable cache keys'
]
};
if (outputFormat === 'json') {
outputJson(formatSuccess(info));
} else {
await pretty.heading('Cache');
await pretty.plain(info.description);
await pretty.newline();
await pretty.plain('Providers:');
for (const p of info.providers) {
await pretty.plain(` • ${p.name}: ${p.description}`);
}
await pretty.newline();
await pretty.plain('Capabilities:');
for (const cap of info.capabilities) {
await pretty.plain(` • ${cap}`);
}
}
}
async function listProviders(outputFormat: string): Promise<void> {
const providers = [
{ name: 'memory', description: 'In-memory cache', available: true },
{ name: 'file', description: 'File-based cache', available: true }
];
if (outputFormat === 'json') {
outputJson(formatSuccess({ providers }));
} else {
await pretty.heading('Cache Providers');
for (const p of providers) {
const status = p.available ? '✓' : '✗';
await pretty.plain(` ${status} ${p.name.padEnd(15)} ${p.description}`);
}
}
}
async function showHelp(outputFormat: string): Promise<void> {
const help = {
command: 'cache',
description: 'Caching management for LLM responses',
subcommands: [
{ name: 'info', description: 'Show cache feature information' },
{ name: 'providers', description: 'List available cache providers' },
{ name: 'help', description: 'Show this help' }
]
};
if (outputFormat === 'json') {
outputJson(formatSuccess(help));
} else {
await pretty.heading('Cache Command');
await pretty.plain(help.description);
await pretty.newline();
await pretty.plain('Subcommands:');
for (const cmd of help.subcommands) {
await pretty.plain(` ${cmd.name.padEnd(20)} ${cmd.description}`);
}
}
}
+114
View File
@@ -0,0 +1,114 @@
/**
* Chat command - Chat with an AI agent
*/
import { createProvider } from '../../llm/providers';
import { Session } from '../../session';
import { resolveConfig } from '../config/resolve';
import { printSuccess, printError, outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
import { ERROR_CODES, normalizeError } from '../output/errors';
export interface ChatOptions {
model?: string;
stream?: boolean;
session?: string;
verbose?: boolean;
profile?: string;
config?: string;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
}
export async function execute(args: string[], options: ChatOptions): Promise<void> {
const prompt = args[0];
if (!prompt) {
if (options.json || options.output === 'json') {
printError(ERROR_CODES.MISSING_ARG, 'Please provide a prompt');
} else {
await pretty.error('Please provide a prompt');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
// Resolve config with precedence
const config = resolveConfig({
configPath: options.config,
profile: options.profile,
model: options.model,
verbose: options.verbose,
stream: options.stream
});
const startTime = Date.now();
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
try {
const provider = createProvider(config.model);
const session = new Session({ id: options.session });
session.addMessage({ role: 'user', content: prompt });
let responseText = '';
let inputTokens = 0;
let outputTokens = 0;
if (config.stream && outputFormat !== 'json') {
// Streaming mode (only for non-JSON output)
const stream = await provider.streamText({
messages: [{ role: 'user', content: prompt }],
onToken: (token: string) => {
process.stdout.write(token);
responseText += token;
}
});
for await (const chunk of stream) {
// Stream is consumed by onToken
}
console.log(); // New line after streaming
} else {
// Non-streaming mode
const result = await provider.generateText({
messages: [{ role: 'user', content: prompt }]
});
responseText = result.text;
inputTokens = result.usage?.promptTokens || 0;
outputTokens = result.usage?.completionTokens || 0;
if (outputFormat === 'json') {
const duration = Date.now() - startTime;
outputJson(formatSuccess(
{ response: responseText },
{
duration_ms: duration,
model: config.model,
tokens: {
input: inputTokens,
output: outputTokens
}
}
));
} else {
console.log(responseText);
}
}
session.addMessage({ role: 'assistant', content: responseText });
} catch (error) {
const cliError = normalizeError(error);
if (outputFormat === 'json') {
outputJson(formatError(cliError.code, cliError.message, cliError.details));
} else {
await pretty.error(cliError.message);
if (config.verbose && error instanceof Error && error.stack) {
await pretty.dim(error.stack);
}
}
process.exit(cliError.exitCode);
}
}
@@ -0,0 +1,252 @@
/**
* CLI command: checkpoints
* Session state checkpointing and recovery
*/
import { CheckpointManager, createCheckpointManager, createFileCheckpointStorage } from '../features/checkpoints';
let manager: CheckpointManager | null = null;
function getManager(storagePath?: string): CheckpointManager {
if (!manager) {
const storage = storagePath ? createFileCheckpointStorage(storagePath) : undefined;
manager = createCheckpointManager({ storage });
}
return manager;
}
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
const subcommand = args[0] || 'help';
const isJson = Boolean(options.output === 'json' || options.json);
const storagePath = options.storage as string | undefined;
switch (subcommand) {
case 'create':
await handleCreate(args.slice(1), options, isJson, storagePath);
break;
case 'list':
await handleList(isJson, storagePath);
break;
case 'get':
await handleGet(args.slice(1), isJson, storagePath);
break;
case 'restore':
await handleRestore(args.slice(1), isJson, storagePath);
break;
case 'delete':
await handleDelete(args.slice(1), isJson, storagePath);
break;
case 'export':
await handleExport(args.slice(1), isJson, storagePath);
break;
case 'import':
await handleImport(args.slice(1), isJson, storagePath);
break;
case 'help':
default:
showHelp(isJson);
}
}
async function handleCreate(args: string[], options: Record<string, unknown>, isJson: boolean, storagePath?: string): Promise<void> {
const name = args[0] || `checkpoint-${Date.now()}`;
const mgr = getManager(storagePath);
// Set state from options if provided
const state = options.state ? JSON.parse(options.state as string) : {};
mgr.setState(state);
const checkpoint = await mgr.create(name, {
sessionId: options.session as string,
metadata: options.metadata ? JSON.parse(options.metadata as string) : undefined
});
if (isJson) {
console.log(JSON.stringify({ success: true, checkpoint }, null, 2));
} else {
console.log(`✓ Checkpoint created: ${checkpoint.id}`);
console.log(` Name: ${checkpoint.name}`);
console.log(` Time: ${checkpoint.timestamp.toISOString()}`);
}
}
async function handleList(isJson: boolean, storagePath?: string): Promise<void> {
const mgr = getManager(storagePath);
const checkpoints = await mgr.list();
if (isJson) {
console.log(JSON.stringify({ success: true, checkpoints }, null, 2));
} else {
if (checkpoints.length === 0) {
console.log('No checkpoints found');
} else {
console.log('Checkpoints:');
for (const cp of checkpoints) {
console.log(` ${cp.name} (${cp.id})`);
console.log(` Created: ${cp.timestamp.toISOString()}`);
if (cp.sessionId) console.log(` Session: ${cp.sessionId}`);
}
}
}
}
async function handleGet(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
const id = args[0];
if (!id) {
console.error('Error: Checkpoint ID is required');
process.exit(1);
}
const mgr = getManager(storagePath);
const checkpoint = await mgr.get(id);
if (!checkpoint) {
if (isJson) {
console.log(JSON.stringify({ success: false, error: 'Checkpoint not found' }));
} else {
console.error(`Checkpoint ${id} not found`);
}
process.exit(1);
}
if (isJson) {
console.log(JSON.stringify({ success: true, checkpoint }, null, 2));
} else {
console.log(`Checkpoint: ${checkpoint.id}`);
console.log(` Name: ${checkpoint.name}`);
console.log(` Created: ${checkpoint.timestamp.toISOString()}`);
console.log(` State: ${JSON.stringify(checkpoint.state, null, 2)}`);
}
}
async function handleRestore(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
const id = args[0];
const mgr = getManager(storagePath);
let checkpoint;
if (id) {
checkpoint = await mgr.restore(id);
} else {
checkpoint = await mgr.restoreLatest();
}
if (!checkpoint) {
if (isJson) {
console.log(JSON.stringify({ success: false, error: 'No checkpoint to restore' }));
} else {
console.error('No checkpoint to restore');
}
process.exit(1);
}
if (isJson) {
console.log(JSON.stringify({ success: true, restored: checkpoint }, null, 2));
} else {
console.log(`✓ Restored checkpoint: ${checkpoint.id}`);
console.log(` Name: ${checkpoint.name}`);
console.log(` State keys: ${Object.keys(checkpoint.state).join(', ') || 'none'}`);
}
}
async function handleDelete(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
const id = args[0];
if (!id) {
console.error('Error: Checkpoint ID is required');
process.exit(1);
}
const mgr = getManager(storagePath);
await mgr.delete(id);
if (isJson) {
console.log(JSON.stringify({ success: true, deleted: id }));
} else {
console.log(`✓ Checkpoint ${id} deleted`);
}
}
async function handleExport(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
const id = args[0];
if (!id) {
console.error('Error: Checkpoint ID is required');
process.exit(1);
}
const mgr = getManager(storagePath);
const exported = await mgr.export(id);
if (!exported) {
console.error(`Checkpoint ${id} not found`);
process.exit(1);
}
console.log(exported);
}
async function handleImport(args: string[], isJson: boolean, storagePath?: string): Promise<void> {
const jsonData = args[0];
if (!jsonData) {
console.error('Error: JSON data is required');
process.exit(1);
}
const mgr = getManager(storagePath);
const checkpoint = await mgr.import(jsonData);
if (isJson) {
console.log(JSON.stringify({ success: true, imported: checkpoint }, null, 2));
} else {
console.log(`✓ Checkpoint imported: ${checkpoint.id}`);
}
}
function showHelp(isJson: boolean): void {
const help = {
command: 'checkpoints',
description: 'Session state checkpointing and recovery',
subcommands: {
create: 'Create a new checkpoint',
list: 'List all checkpoints',
get: 'Get checkpoint details',
restore: 'Restore from a checkpoint',
delete: 'Delete a checkpoint',
export: 'Export checkpoint as JSON',
import: 'Import checkpoint from JSON'
},
flags: {
'--storage': 'Path to checkpoint storage directory',
'--state': 'JSON state to save with checkpoint',
'--session': 'Session ID to associate',
'--metadata': 'JSON metadata',
'--json': 'Output in JSON format'
},
examples: [
'praisonai-ts checkpoints create my-checkpoint',
'praisonai-ts checkpoints create --state \'{"key": "value"}\'',
'praisonai-ts checkpoints list',
'praisonai-ts checkpoints restore <id>',
'praisonai-ts checkpoints restore # restores latest',
'praisonai-ts checkpoints export <id> > checkpoint.json',
'praisonai-ts checkpoints import \'{"name": "imported"}\'',
'praisonai-ts checkpoints delete <id>'
]
};
if (isJson) {
console.log(JSON.stringify(help, null, 2));
} else {
console.log('Checkpoints - Session state checkpointing\n');
console.log('Subcommands:');
for (const [cmd, desc] of Object.entries(help.subcommands)) {
console.log(` ${cmd.padEnd(12)} ${desc}`);
}
console.log('\nFlags:');
for (const [flag, desc] of Object.entries(help.flags)) {
console.log(` ${flag.padEnd(12)} ${desc}`);
}
console.log('\nExamples:');
for (const ex of help.examples) {
console.log(` ${ex}`);
}
}
}
@@ -0,0 +1,164 @@
/**
* Context command - Manage conversation context
*/
import { createContextAgent } from '../../agent/context';
import { resolveConfig } from '../config/resolve';
import { outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
import { ERROR_CODES } from '../output/errors';
export interface ContextOptions {
model?: string;
verbose?: boolean;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
maxMessages?: number;
}
export async function execute(args: string[], options: ContextOptions): Promise<void> {
const action = args[0] || 'help';
const actionArgs = args.slice(1);
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
const config = resolveConfig(options);
try {
switch (action) {
case 'chat':
await chatWithContext(actionArgs, options, config, outputFormat);
break;
case 'summarize':
await summarizeContext(actionArgs, options, config, outputFormat);
break;
case 'help':
default:
await showHelp(outputFormat);
break;
}
} catch (error) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
} else {
await pretty.error(error instanceof Error ? error.message : String(error));
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
}
async function chatWithContext(args: string[], options: ContextOptions, config: any, outputFormat: string): Promise<void> {
const message = args.join(' ');
if (!message) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a message'));
} else {
await pretty.error('Please provide a message');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
const startTime = Date.now();
if (outputFormat !== 'json') {
await pretty.info('Processing with context...');
}
const agent = createContextAgent({
llm: config.model,
instructions: 'You are a helpful assistant with context awareness.',
contextWindow: options.maxMessages || 10,
verbose: options.verbose
});
const result = await agent.chat(message);
const duration = Date.now() - startTime;
if (outputFormat === 'json') {
outputJson(formatSuccess({
message,
response: result.text
}, {
duration_ms: duration,
model: config.model
}));
} else {
await pretty.heading('Response');
await pretty.plain(result.text);
await pretty.newline();
await pretty.success(`Completed in ${duration}ms`);
}
}
async function summarizeContext(args: string[], options: ContextOptions, config: any, outputFormat: string): Promise<void> {
const context = args.join(' ');
if (!context) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide context to summarize'));
} else {
await pretty.error('Please provide context to summarize');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
const startTime = Date.now();
if (outputFormat !== 'json') {
await pretty.info('Summarizing context...');
}
const agent = createContextAgent({
llm: config.model,
instructions: 'You are a summarization assistant. Provide concise summaries.',
verbose: options.verbose
});
// Use chat to summarize
const result = await agent.chat(`Please summarize the following text concisely:\n\n${context}`);
const duration = Date.now() - startTime;
if (outputFormat === 'json') {
outputJson(formatSuccess({
originalLength: context.length,
summary: result.text
}, {
duration_ms: duration,
model: config.model
}));
} else {
await pretty.heading('Summary');
await pretty.plain(result.text);
await pretty.newline();
await pretty.dim(`Original: ${context.length} chars`);
await pretty.success(`Summarized in ${duration}ms`);
}
}
async function showHelp(outputFormat: string): Promise<void> {
const help = {
command: 'context',
subcommands: [
{ name: 'chat <message>', description: 'Chat with context management' },
{ name: 'summarize <text>', description: 'Summarize context' },
{ name: 'help', description: 'Show this help' }
],
flags: [
{ name: '--max-messages', description: 'Maximum messages to keep in context' }
]
};
if (outputFormat === 'json') {
outputJson(formatSuccess(help));
} else {
await pretty.heading('Context Command');
await pretty.plain('Manage conversation context\n');
await pretty.plain('Subcommands:');
for (const cmd of help.subcommands) {
await pretty.plain(` ${cmd.name.padEnd(25)} ${cmd.description}`);
}
await pretty.newline();
await pretty.plain('Flags:');
for (const flag of help.flags) {
await pretty.plain(` ${flag.name.padEnd(20)} ${flag.description}`);
}
}
}
+153
View File
@@ -0,0 +1,153 @@
/**
* CLI command: cost
* Token usage and cost tracking
*/
import { CostTracker, createCostTracker, MODEL_PRICING, formatCost } from '../features/cost-tracker';
let tracker: CostTracker | null = null;
function getTracker(): CostTracker {
if (!tracker) {
tracker = createCostTracker();
}
return tracker;
}
export async function execute(args: string[], options: Record<string, unknown>): Promise<void> {
const subcommand = args[0] || 'help';
const isJson = Boolean(options.output === 'json' || options.json);
switch (subcommand) {
case 'summary':
await handleSummary(isJson);
break;
case 'add':
await handleAdd(args.slice(1), options, isJson);
break;
case 'reset':
await handleReset(isJson);
break;
case 'pricing':
await handlePricing(args.slice(1), isJson);
break;
case 'help':
default:
showHelp(isJson);
}
}
async function handleSummary(isJson: boolean): Promise<void> {
const t = getTracker();
const stats = t.getStats();
const breakdown = t.getBreakdownByModel();
if (isJson) {
console.log(JSON.stringify({ success: true, stats, breakdown }, null, 2));
} else {
console.log(t.getSummary());
if (Object.keys(breakdown).length > 0) {
console.log('\nBreakdown by model:');
for (const [model, data] of Object.entries(breakdown)) {
console.log(` ${model}: ${data.tokens} tokens, ${formatCost(data.cost)}, ${data.requests} requests`);
}
}
}
}
async function handleAdd(args: string[], options: Record<string, unknown>, isJson: boolean): Promise<void> {
const model = args[0] || 'gpt-4o-mini';
const inputTokens = parseInt(args[1] || '0');
const outputTokens = parseInt(args[2] || '0');
if (isNaN(inputTokens) || isNaN(outputTokens)) {
console.error('Error: Invalid token counts');
process.exit(1);
}
const t = getTracker();
const request = t.addUsage(model, inputTokens, outputTokens);
if (isJson) {
console.log(JSON.stringify({ success: true, request }, null, 2));
} else {
console.log(`✓ Added usage: ${model}`);
console.log(` Input: ${inputTokens} tokens`);
console.log(` Output: ${outputTokens} tokens`);
console.log(` Cost: ${formatCost(request.cost)}`);
}
}
async function handleReset(isJson: boolean): Promise<void> {
const t = getTracker();
t.reset();
if (isJson) {
console.log(JSON.stringify({ success: true, message: 'Cost tracker reset' }));
} else {
console.log('✓ Cost tracker reset');
}
}
async function handlePricing(args: string[], isJson: boolean): Promise<void> {
const model = args[0];
if (model) {
const pricing = MODEL_PRICING[model] || MODEL_PRICING['default'];
if (isJson) {
console.log(JSON.stringify({ success: true, model, pricing }, null, 2));
} else {
console.log(`Pricing for ${model}:`);
console.log(` Input: $${pricing.inputCostPer1k}/1K tokens`);
console.log(` Output: $${pricing.outputCostPer1k}/1K tokens`);
}
} else {
if (isJson) {
console.log(JSON.stringify({ success: true, pricing: MODEL_PRICING }, null, 2));
} else {
console.log('Model Pricing (per 1K tokens):\n');
for (const [name, pricing] of Object.entries(MODEL_PRICING)) {
if (name === 'default') continue;
console.log(` ${name.padEnd(30)} in: $${pricing.inputCostPer1k.toFixed(5)} out: $${pricing.outputCostPer1k.toFixed(5)}`);
}
}
}
}
function showHelp(isJson: boolean): void {
const help = {
command: 'cost',
description: 'Token usage and cost tracking',
subcommands: {
summary: 'Show cost summary',
add: 'Add token usage',
reset: 'Reset cost tracker',
pricing: 'Show model pricing'
},
flags: {
'--json': 'Output in JSON format'
},
examples: [
'praisonai-ts cost summary',
'praisonai-ts cost add gpt-4o 1000 500',
'praisonai-ts cost pricing',
'praisonai-ts cost pricing gpt-4o',
'praisonai-ts cost reset'
]
};
if (isJson) {
console.log(JSON.stringify(help, null, 2));
} else {
console.log('Cost - Token usage and cost tracking\n');
console.log('Subcommands:');
for (const [cmd, desc] of Object.entries(help.subcommands)) {
console.log(` ${cmd.padEnd(12)} ${desc}`);
}
console.log('\nExamples:');
for (const ex of help.examples) {
console.log(` ${ex}`);
}
}
}
Loaded 100 of 342 files, more files were not shown because too many files have changed in this diff. Show more