Compare commits

...
109 Commits
Author SHA1 Message Date
Mervin Praison cb551debfe Merge pull request #582 from MervinPraison/develop
Develop
2025-06-03 07:55:41 +01:00
MervinPraison fabd852002 chore: update PraisonAI version to 2.2.25 across Dockerfiles and documentation
- Updated the version of PraisonAI from 2.2.24 to 2.2.25 in all relevant Dockerfiles.
- Adjusted version references in README.md and various documentation files.
- Ensured consistency in package versioning for improved stability and performance.
2025-06-03 07:55:27 +01:00
MervinPraison 99de9f4d4f refactor: replace AsyncAsyncWebCrawler with AsyncWebCrawler in chat.py and code.py
- Updated import statements in chat.py and code.py to use AsyncWebCrawler for consistency.
- Modified default model name in realtime.py to include a specific date for clarity.
- Enhanced connection handling in RealtimeAPI to improve websocket connection checks and error handling.
- Added ensure_connected method in RealtimeClient for better connection management.
- Removed outdated realtimedocs.txt file to streamline the codebase.
2025-06-03 07:53:44 +01:00
Mervin Praison daaf92cbeb Merge pull request #581 from MervinPraison/claude/issue-289-20250603_061712
feat: Add domain context solution using existing features
2025-06-03 07:27:29 +01:00
claude[bot]andMervinPraison d8375cb50b feat: Add domain context solution using existing PraisonAI features
Fixes #289 - Multi-agent context issue

This solution demonstrates how to solve the domain context problem where tools 
default to "example.com" instead of user-specified domains. Uses 4 existing 
PraisonAI features:

1. Custom tool wrappers with domain injection
2. Agent instructions with domain context
3. Task context parameter for domain info
4. Shared memory for context persistence

Key benefits:
- No core SDK changes required
- Uses only existing framework features  
- Multi-layer context reliability
- 100% backward compatible
- Works for hierarchical and other processes

Co-authored-by: MervinPraison <[email protected]>
2025-06-03 06:22:53 +00:00
Mervin Praison 36ea8d381e Merge pull request #577 from DhivyaBharathy-web/add-arxiv-agent-notebook
Add arxiv agent notebook example to cookbooks
2025-06-02 11:49:40 +01:00
DhivyaBharathy 8a8f96e5e5 Update arXiv_agent_notebook.ipynb 2025-06-02 15:53:11 +05:30
DhivyaBharathy cd09b2de96 Update arXiv_agent_notebook.ipynb 2025-06-02 15:24:29 +05:30
DhivyaBharathy-web 5e5b6672eb Add arxiv agent notebook example to cookbooks 2025-06-02 09:16:15 +00:00
Mervin Praison f7ce10b92e Merge pull request #566 from MervinPraison/claude/issue-12-20250601_032826
feat: Add human-in-the-loop approval system for dangerous tool operations
2025-06-02 09:42:59 +01:00
Mervin Praison eead19802d Merge pull request #576 from DhivyaBharathy-web/main
Add DuckDuckGo PraisonAI Agent Notebook
2025-06-02 09:40:25 +01:00
MervinPraison 208c8aad3b feat: enhance approval system with context-aware auto-approval
This commit introduces a context-aware auto-approval mechanism for the approval system, allowing non-interactive execution in CI environments. It modifies the test files to include a skip condition for interactive approvals and updates the approval callback logic to support automatic approvals when user input is not required.

Files modified:
- Updated test_approval_agent_integration.py to include auto-approval logic
- Updated test_approval_basic.py to include skip condition for interactive tests
- Updated test_approval_interactive.py to include auto-approval logic
- Enhanced approval.py with context management for approval tracking
- Modified agent.py to prevent double approval in context

This change improves the usability of the approval system in automated workflows while maintaining security.
2025-06-02 09:35:09 +01:00
DhivyaBharathy fb2b086dd5 Update DuckDuckGo_PraisonAI_Agent_Notebook.ipynb 2025-06-02 13:44:41 +05:30
DhivyaBharathy 75972372a6 Update DuckDuckGo_PraisonAI_Agent_Notebook.ipynb 2025-06-02 12:29:34 +05:30
DhivyaBharathy-web 108053bb32 Add DuckDuckGo PraisonAI Agent Notebook 2025-06-02 06:37:45 +00:00
MervinPraison fbddda54ba fix: update import paths in approval agent test files
This commit modifies the import paths in the test files for the approval agent to ensure they correctly reference the local development path. The changes streamline the path structure for better integration with the existing codebase.

Files modified:
- Updated import paths in test_approval_agent_integration.py
- Updated import paths in test_approval_basic.py
- Updated import paths in test_approval_interactive.py
2025-06-01 07:16:28 +01:00
MervinPraison 83427ca929 chore: remove obsolete test_approval_basic.py file
This commit deletes the outdated test_approval_basic.py file, which contained basic tests for the human approval system. The tests are no longer necessary as the functionality has been integrated into other components.

Files modified:
- Deleted test_approval_basic.py
2025-06-01 07:15:18 +01:00
Mervin Praison 36e5047e53 Merge pull request #574 from MervinPraison/claude/issue-444-20250601_050501
fix: resolve Knowledge RAG errors with mem0 API compatibility
2025-06-01 06:23:50 +01:00
claude[bot]andMervinPraison 05d8136433 fix: resolve Knowledge RAG errors with mem0 API compatibility
- Fix CustomMemory._add_to_vector_store method signature to support optional parameters with defaults
- Add backward compatibility for different message formats  
- Update memory.add() call to use correct messages format for mem0 API
- Add fallback mechanism to support both new and legacy API formats
- Resolves "takes 4 positional arguments but 5 were given" error
- Resolves "missing 1 required positional argument: 'infer'" error

Fixes #444

Co-authored-by: MervinPraison <[email protected]>
2025-06-01 05:09:01 +00:00
Mervin Praison af9b251b59 Merge pull request #573 from MervinPraison/claude/issue-107-20250601_045023
feat: integrate Mem0 graph memory support
2025-06-01 06:02:44 +01:00
Mervin Praison c8a6744469 Rename test_graph_memory.py to src/praisonai/tests/unit/test_graph_memory.py 2025-06-01 06:01:31 +01:00
claude[bot]andMervinPraison 678fd462c3 feat: integrate Mem0 graph memory support
- Add graph memory configuration support to Memory class
- Enhanced Knowledge class with graph store support  
- Added optional graph dependency group in pyproject.toml
- Comprehensive examples demonstrating graph memory usage
- Maintained full backward compatibility
- Added test scripts to verify implementation

Supports Neo4j and Memgraph as graph store providers.
Install with: pip install "praisonaiagents[graph]"

Co-authored-by: MervinPraison <[email protected]>
2025-06-01 04:56:24 +00:00
Mervin Praison ba03a0d893 Merge pull request #572 from MervinPraison/claude/issue-421-20250531_210257
feat: support LiteLLM provider-specific parameters
2025-06-01 05:42:39 +01:00
Mervin Praison 3be4c4c70e Merge pull request #571 from MervinPraison/develop
chore: update PraisonAI version to 2.2.24 across Dockerfiles and docu…
2025-06-01 05:38:39 +01:00
MervinPraison 709640240f chore: update PraisonAI version to 2.2.24 across Dockerfiles and documentation
- Updated the version of PraisonAI from 2.2.23 to 2.2.24 in all relevant Dockerfiles.
- Adjusted version references in README.md and various documentation files.
- Ensured consistency in package versioning for improved stability and performance.
2025-06-01 05:36:48 +01:00
Mervin Praison 93d526eb7b Merge pull request #570 from MervinPraison/claude/issue-122-20250601_041558
feat: add --merge CLI parameter for merging agents.yaml files
2025-06-01 05:33:20 +01:00
Mervin Praison 624a2cfa17 Merge pull request #567 from MervinPraison/claude/issue-121-20250531_210355
fix: prevent CLI conflicts when PraisonAI is used as library
2025-06-01 05:30:06 +01:00
Mervin Praison 4d2a6a2e2e Merge pull request #569 from MervinPraison/claude/issue-466-20250601_041120
feat: Add Mem0 built-in reranker support to Knowledge Agent
2025-06-01 05:29:01 +01:00
claude[bot]andMervinPraison a1bdb056a0 refactor: use argparse.Namespace instead of custom DefaultArgs class
Replace the custom DefaultArgs class with argparse.Namespace for better 
maintainability and consistency with argparse conventions. This addresses
the code review suggestion to use idiomatic Python patterns and avoid
duplication of argument handling logic.

Co-authored-by: MervinPraison <[email protected]>
2025-06-01 04:27:44 +00:00
Mervin Praison b4a6401bae Merge pull request #568 from MervinPraison/claude/issue-460-20250601_040421
fix: resolve MCP function signature parameter ordering issue
2025-06-01 05:25:20 +01:00
claude[bot]andMervinPraison 772ce7d2e2 feat: add --merge CLI parameter for merging agents.yaml files
- Add --merge flag to CLI with description for merging behavior
- Default behavior remains overwrite (backwards compatible)
- When --merge is used, preserves existing agents and adds new ones
- Implements intelligent conflict resolution by renaming duplicates
- Combines topics when both existing and new topics are present
- Added comprehensive error handling for invalid YAML files

Usage:
- praisonai --init "topic" (overwrites existing agents.yaml)
- praisonai --init "topic" --merge (merges with existing agents.yaml)

Co-authored-by: MervinPraison <[email protected]>
2025-06-01 04:19:11 +00:00
claude[bot]andMervinPraison 20c2d28f35 feat: Add Mem0 built-in reranker support to Knowledge Agent
- Add rerank parameter to Knowledge.search() method with backward compatibility
- Update memory search methods to pass rerank parameter to Mem0
- Add reranker configuration options with enabled/default_rerank settings
- Support all Mem0 advanced retrieval options (keyword_search, filter_memories)
- Create comprehensive example demonstrating reranker usage and performance
- Maintains full backward compatibility - existing code works unchanged
- Addresses issue #466 for improved RAG relevance with minimal latency impact

Co-authored-by: MervinPraison <[email protected]>
2025-06-01 04:14:24 +00:00
claude[bot]andMervinPraison f8589e4363 fix: resolve MCP function signature parameter ordering issue
Fix ValueError "non-default argument follows default argument" in MCP tool 
wrapper creation by ensuring required parameters come before optional 
parameters in function signatures.

- Separate required and optional parameters during signature creation
- Maintain backward compatibility and preserve all functionality
- Resolves GitHub MCP server initialization failures

Fixes #460

Co-authored-by: MervinPraison <[email protected]>
2025-06-01 04:05:36 +00:00
claude[bot]andMervinPraison 97d368d2e2 feat: Add human-in-the-loop approval system for dangerous tool operations
Implements human approval framework for dangerous operations like shell commands,
code execution, and file operations to enhance security and user control.

Key features:
- @require_approval decorator for marking dangerous tools  
- Risk-based approval (critical, high, medium, low)
- Console approval interface with rich formatting
- Custom approval callbacks for automated policies
- Async/sync support for all workflows
- Runtime configuration of approval requirements

Protected operations:
- Critical: execute_command, kill_process, execute_code
- High: write_file, delete_file, move_file, copy_file
- Medium: evaluate, crawl, scrape_page

Files modified:
- New approval.py module with core framework
- Enhanced main.py with approval callback support
- Modified agent.py to check approvals before tool execution
- Added @require_approval decorators to dangerous tools
- Comprehensive example and test implementations

Resolves: #12

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: MervinPraison <[email protected]>
2025-06-01 03:42:06 +00:00
Mervin Praison 49d9d40199 Merge pull request #565 from MervinPraison/claude/issue-97-20250531_205330
fix: implement litellm handling in code.py same as llm.py
2025-06-01 04:27:46 +01:00
claude[bot]andMervinPraison 445cc037ce fix: prevent CLI conflicts when PraisonAI is used as library
Adds minimal detection in parse_args() to skip CLI parsing when imported
as a library (e.g., by Fabric). Returns default args instead of trying
to parse external application's CLI arguments.

- Only 20 lines added to fix the specific Fabric compatibility issue
- Zero breaking changes - all existing CLI and library usage preserved
- Addresses the root cause from issue #121

Co-authored-by: MervinPraison <[email protected]>
2025-05-31 21:07:12 +00:00
claude[bot]andMervinPraison c4e7b7dc7f feat: support LiteLLM provider-specific parameters
- Add extra_settings to LLM completion parameters
- Enables passing Ollama num_ctx and other provider params
- Maintains backward compatibility
- Addresses issue #421

Co-authored-by: MervinPraison <[email protected]>
2025-05-31 21:05:41 +00:00
claude[bot]andMervinPraison 9e7c172f55 fix: implement litellm handling in code.py same as llm.py
Apply minimal changes to fix LiteLLM provider error:
- Add litellm configuration with drop_params and modify_params
- Add _build_completion_params() helper function
- Update acompletion calls to use proper parameter handling

This prevents "list index out of range" errors when using model names
like "gemini" without provider prefix by leveraging litellm's built-in
parameter validation and modification capabilities.

Fixes #97

Co-authored-by: MervinPraison <[email protected]>
2025-05-31 20:55:14 +00:00
Mervin Praison d89570b085 Merge pull request #564 from MervinPraison/develop
chore: update PraisonAI version to 2.2.23 across Dockerfiles and docu…
2025-05-31 17:09:32 +01:00
MervinPraison ee5dfddccd chore: update PraisonAI version to 2.2.23 across Dockerfiles and documentation
- Updated the PraisonAI version from 2.2.22 to 2.2.23 in all relevant Dockerfiles.
- Adjusted version references in README.md, local-development.mdx, and other documentation files.
- Ensured consistency in versioning for deployment scripts and project files.

This change maintains compatibility with the latest features and fixes in PraisonAI.
2025-05-31 17:08:37 +01:00
Mervin Praison a3bfc6f795 Merge pull request #563 from MervinPraison/claude/issue-459-20250531_155633
fix: resolve MCP tools not working in Streamlit applications (issue #459)
2025-05-31 17:05:57 +01:00
Mervin Praison ebc130854d Merge pull request #562 from MervinPraison/claude/issue-5-20250531_154718
feat: add deployment scheduling functionality
2025-05-31 17:04:49 +01:00
Mervin Praison f40a5c1359 Rename schedule_config.yaml.example to src/praisonai/schedule_config.yaml.example 2025-05-31 17:04:06 +01:00
Mervin Praison 3284f7bbc6 Rename test_scheduler.py to src/praisonai/tests/unit/test_scheduler.py 2025-05-31 17:03:34 +01:00
claude[bot]andMervinPraison 87928611f7 fix: resolve MCP tools not working in Streamlit applications (issue #459)
This commit provides working examples and comprehensive documentation for using MCP tools with Streamlit, without modifying core MCP implementation.

Key fixes:
- Proper session state management to prevent agent re-initialization
- Use standard LLM format instead of provider/model format
- Comprehensive error handling and user feedback
- Working examples with troubleshooting guidance

Files added:
- examples/python/ui/mcp-streamlit-airbnb.py - Complete working Streamlit app
- examples/python/ui/mcp-streamlit-simple.py - Simple fixed version of original code
- docs/ui/streamlit/mcp-streamlit.mdx - Comprehensive documentation and troubleshooting

The root issue was improper usage patterns in Streamlit, not the core MCP implementation.

Co-authored-by: MervinPraison <[email protected]>

🤖 Generated with [Claude Code](https://claude.ai/code)
2025-05-31 15:59:21 +00:00
Mervin Praison 2727bf5992 Merge pull request #561 from MervinPraison/claude/issue-170-20250531_154607
fix: resolve crawling NoneType concatenation error in realtime tools
2025-05-31 16:58:03 +01:00
claude[bot]andMervinPraison 45d04163c3 feat: add deployment scheduling functionality
- Add DeploymentScheduler with provider-agnostic design
- Support interval-based scheduling (daily, hourly, */6h, 3600s)
- CLI integration with --schedule and --schedule-config options
- Backward compatible with existing --deploy behavior
- Thread-safe scheduler with retry mechanism
- Example configuration file for scheduled deployments
- Test suite for validation

Addresses issue #5: --deploy schedule runs

Co-authored-by: MervinPraison <[email protected]>
2025-05-31 15:53:57 +00:00
claude[bot]andMervinPraison 477d0d2e26 fix: resolve crawling NoneType concatenation error in realtime tools
- Fix "can only concatenate str (not "NoneType") to str" error in web crawling
- Update process_tavily_results and fallback_to_duckduckgo to use proper async patterns
- Add null checks for crawl_result.markdown to prevent NoneType errors
- Use async context manager with AsyncWebCrawler and await crawler.arun()
- Match the working async pattern used in chat.py

Fixes #170

Co-authored-by: MervinPraison <[email protected]>
2025-05-31 15:48:02 +00:00
Mervin Praison 77394f5e5f Merge pull request #560 from MervinPraison/develop
Develop
2025-05-31 16:44:03 +01:00
MervinPraison 29e533c18c chore: remove unsloth training dependency from pyproject.toml
- Cleared the train dependencies list by removing unsloth[colab-new]>=2024.11.7.
- Commented out the unsloth dependency to maintain a record without affecting functionality.
2025-05-31 16:43:46 +01:00
MervinPraison 48dbea0566 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-31 12:42:45 +01:00
Mervin Praison 969d6697e1 Merge pull request #559 from MervinPraison/claude/issue-232-20250531_060542
fix: add dependency check for train command to prevent confusing errors
2025-05-31 12:42:22 +01:00
Mervin Praisonandcoderabbitai[bot] 787d2508b1 Update src/praisonai/pyproject.toml
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-05-31 12:41:18 +01:00
claude[bot]andMervinPraison ec42dc92e4 feat: add unsloth training dependencies to pyproject.toml
- Add train optional dependencies with unsloth[colab-new]>=2024.11.7
- Update Poetry extras to include unsloth for train command
- Enables pip install "praisonai[train]" for training dependencies
- Maintains backward compatibility

Co-authored-by: MervinPraison <[email protected]>
2025-05-31 10:45:40 +00:00
claude[bot]andMervinPraison 5b7bae2f43 fix: add dependency check for train command to prevent confusing errors
- Add TRAIN_AVAILABLE flag to track unsloth dependency availability
- Add dependency validation in both parse_args() and main() methods
- Show clear error message with installation instructions when dependencies missing
- Follow same pattern as other commands (chat, code, call, realtime) for consistency
- Fixes issue where train command would show "train not available" without helpful context

Resolves #232

Co-authored-by: MervinPraison <[email protected]>
2025-05-31 06:07:31 +00:00
MervinPraison 69a1efac47 fix(tests): update expected name in tool call parsing test for malformed JSON
- Changed the expected function name in the test for malformed JSON from 'hello_world' to 'unknown_function' to reflect correct error handling behaviour.
- Adjusted the Python path in the test file to point to the correct source directory for the praisonai-agents module.
2025-05-31 06:51:09 +01:00
Mervin Praison e6381139ec Merge pull request #558 from MervinPraison/develop
Develop
2025-05-30 18:10:59 +01:00
MervinPraison aca2a58971 chore: update PraisonAI version to 2.2.22 across Dockerfiles and documentation
- Updated the version of PraisonAI from 2.2.21 to 2.2.22 in all relevant Dockerfiles.
- Adjusted README and local development documentation to reflect the new version.
- Ensured consistency in versioning across all instances where PraisonAI is referenced.
2025-05-30 18:10:42 +01:00
MervinPraison 7a07ebd05e Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 18:06:46 +01:00
Mervin Praison 418d6c810b Merge pull request #557 from MervinPraison/claude/issue-471-20250530_151237
fix(ollama): resolve tool-call issues with local Ollama models
2025-05-30 18:04:17 +01:00
Mervin Praison 8bcfdfe713 Update and rename test_ollama_fix.py to src/praisonai/tests/unit/test_ollama_fix.py 2025-05-30 18:01:53 +01:00
claude[bot]andMervinPraison 57f2fa0471 fix(ollama): resolve tool-call issues with local Ollama models
- Fix tool parameter logic in agent.py to handle empty tools list properly
- Add comprehensive Ollama provider detection via model name and env vars
- Implement centralized tool call argument parsing with error handling
- Add consistent JSON error handling for all providers (sync and async)
- Users no longer need to pass tools to both agents AND tasks

Fixes #471

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 15:17:06 +00:00
MervinPraison 2fdffd2d8d playwright mcp 2025-05-30 15:56:30 +01:00
Mervin Praison 471a6a106c Update article_generation_agents.ipynb 2025-05-30 15:41:18 +01:00
Mervin Praison 7c51c3aad1 Update article_generation_agents.ipynb 2025-05-30 15:40:48 +01:00
Mervin Praison dfa261f5ef Merge pull request #556 from MervinPraison/develop
chore: update PraisonAI version to 2.2.21 across Dockerfiles and docu…
2025-05-30 15:36:44 +01:00
MervinPraison 9a97f0b80f chore: update PraisonAI version to 2.2.21 across Dockerfiles and documentation
- Updated the version of PraisonAI from 2.2.20 to 2.2.21 in all relevant Dockerfiles.
- Adjusted README and local development documentation to reflect the new version.
- Ensured consistency in versioning across all instances where PraisonAI is referenced.
2025-05-30 15:36:03 +01:00
Mervin Praison eef9a5f7ae Merge pull request #555 from MervinPraison/claude/issue-18-20250530_125354
feat: Add comprehensive secondary market research agent system
2025-05-30 14:24:29 +01:00
Mervin Praison 510e9b1797 Merge pull request #554 from MervinPraison/claude/issue-23-20250530_125338
Add CSV processing examples for issue #23
2025-05-30 14:22:23 +01:00
Mervin Praison 89f48cf7a2 Merge pull request #553 from MervinPraison/develop
chore: update PraisonAI version to 2.2.20 across Dockerfiles and docu…
2025-05-30 14:16:47 +01:00
MervinPraison 97fb7fdd52 chore: update PraisonAI version to 2.2.20 across Dockerfiles and documentation
- Updated the version of PraisonAI from 2.2.19 to 2.2.20 in all relevant Dockerfiles.
- Adjusted README and local development documentation to reflect the new version.
- Ensured consistency in versioning across all instances where PraisonAI is referenced.
2025-05-30 14:16:19 +01:00
Mervin Praison ea4cb42324 Merge pull request #552 from MervinPraison/claude/issue-115-20250530_125352
fix: resolve ForwardRef TypeError for praisonai code command
2025-05-30 14:14:11 +01:00
claude[bot]andMervinPraison 43da66d129 feat: Add comprehensive secondary market research agent system
- Multi-agent system with 6 specialized research agents
- Customizable parameters for company, geography, industry, and sections
- YAML configuration for easy setup and customization
- FastAPI integration with async job processing and REST API
- Professional report generation suitable for business decision-making
- Jupyter notebook with multiple implementation examples
- Complete documentation and usage guidelines

Addresses issue #18 - Secondary market research agent with customization

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 13:00:21 +00:00
claude[bot]andMervinPraison e1a61959ea feat: Add comprehensive CSV processing examples for issue #23
- Add simple CSV URL processor for basic use cases
- Add comprehensive CSV processing with 4 different methods
- Add complete documentation with setup and troubleshooting
- Address GitHub issue #23: "How to pass in a CSV file"
- Support sequential processing of URLs and other CSV data
- Include validation, error handling, and multiple processing patterns

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 12:58:39 +00:00
Mervin Praison 71c5b929b7 Merge pull request #551 from MervinPraison/develop
Develop
2025-05-30 13:57:04 +01:00
claude[bot]andMervinPraison c9edf19907 fix: resolve ForwardRef TypeError for praisonai code command
Fixes #115

Resolves TypeError: ForwardRef._evaluate() missing recursive_guard argument when running `praisonai code`.

## Changes
- Moved crewai import from module level to conditional import in handle_direct_prompt
- The `code` command doesn't need crewai, only chainlit and other UI dependencies
- Follows existing pattern for optional dependencies

## Testing
- `praisonai code` now works without requiring crewai installation
- Direct prompt functionality still works when crewai is available

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 12:56:57 +00:00
MervinPraison 6cdc79f4a2 fix: update import path for database_config module in unit tests
- Adjusted the import statement in test_database_config.py to reference the correct module path.
- Ensures that the tests can locate the database_config functionalities properly.
2025-05-30 13:56:49 +01:00
MervinPraison fdda350b72 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 13:33:56 +01:00
Mervin Praison 85f748d5d1 Merge pull request #549 from DhivyaBharathy-web/add-langchain-praisonai-agents-notebook
Add LangChain + PraisonAI agents Colab notebook
2025-05-30 13:33:50 +01:00
MervinPraison bf24548598 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 13:33:24 +01:00
Mervin Praison a1622a2a79 Merge pull request #550 from MervinPraison/claude/issue-17-20250530_113738
feat: add piped input support to CLI
2025-05-30 13:32:57 +01:00
DhivyaBharathy-web 209faff03c Add LangChain + PraisonAI agents Colab notebook 2025-05-30 11:49:03 +00:00
Mervin Praison ad67089813 Merge pull request #548 from MervinPraison/claude/issue-201-20250530_113510
fix: move OpenAI API key validation from import-time to runtime
2025-05-30 12:41:49 +01:00
claude[bot]andMervinPraison 3e44eadd37 feat: add piped input support to CLI
Implement piped command functionality similar to fabric project, allowing:
- echo "content" | praisonai "command"
- cat file.txt | praisonai --auto "task"
- echo "question" | praisonai

Added read_stdin_if_available() method to detect and read piped input,
and modified main() to handle piped content in three scenarios:
1. Command + piped input
2. Direct prompt + piped input  
3. Only piped input

Fixes #17

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:39:42 +00:00
claude[bot]andMervinPraison 764e9aa838 fix: move OpenAI API key validation from import-time to runtime in call.py
- Removes import-time API key requirement that prevented GUI startup
- Moves validation to run_server() function where it's actually needed
- Allows PraisonAI to start without OpenAI API key for non-voice features
- Users can now configure API key through existing GUI interfaces
- Follows existing codebase patterns for conditional feature validation

Fixes #201

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:39:28 +00:00
Mervin Praison ccfc92564e Merge pull request #547 from MervinPraison/claude/issue-140-20250529_041935
fix: add fallback mechanism for conda run failure in training
2025-05-30 12:33:06 +01:00
MervinPraison 1f61000922 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 12:30:26 +01:00
Mervin Praison 2164f3e658 Merge pull request #546 from MervinPraison/claude/issue-190-20250530_111525
fix: add console scripts to project.scripts section for pip compatibility
2025-05-30 12:30:18 +01:00
MervinPraison 739a9ff8b5 docker 2025-05-30 12:28:49 +01:00
MervinPraison dbe63cbbc9 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 12:27:33 +01:00
Mervin Praison f4bff11e83 Merge pull request #545 from MervinPraison/claude/issue-130-20250530_105428
feat: implement comprehensive Docker support and package publishing for issue #130
2025-05-30 12:27:23 +01:00
Mervin Praison 6552f61322 Update __init__.py 2025-05-30 12:26:57 +01:00
Mervin Praison d5d898ff1b Update __init__.py 2025-05-30 12:26:15 +01:00
Mervin Praison 4df8d122fc Update __init__.py 2025-05-30 12:25:51 +01:00
Mervin Praison 766e5789e3 Update __init__.py 2025-05-30 12:25:14 +01:00
Mervin Praison f9585e83cb Delete src/praisonai/praisonai/inc/directory_manager.py 2025-05-30 12:24:57 +01:00
claude[bot]andMervinPraison e019bab987 fix: address critical security and implementation issues
- Remove automatic directory initialization on package import to prevent unexpected file operations
- Add DOCKER_CONTAINER=true environment variable to all Dockerfiles
- Fix hardcoded password security issue in docker-compose.dev.yml
- Prevent api.py overwrite in main Dockerfile by checking if file exists
- Use non-editable pip install in production Dockerfile for praisonaiagents
- Move package installations from runtime commands to Dockerfile build stage for better efficiency

These fixes address the critical issues identified in the code review while maintaining the core functionality.

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:20:27 +00:00
claude[bot]andMervinPraison 54072a1d55 fix: add console scripts to project.scripts section for pip compatibility
- Add [project.scripts] section with praisonai, setup-conda-env, and praisonai-call entry points
- Fixes issue where praisonai command was not found when installing with pip instead of Poetry
- Entry points were only defined in [tool.poetry.scripts] which pip ignores
- Maintains compatibility with both pip and Poetry installation methods

Fixes #190

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:17:09 +00:00
Mervin Praison 16ddcb8644 Merge pull request #544 from MervinPraison/claude/issue-246-20250530_105609
fix: add targeted o1 model support for system messages
2025-05-30 12:06:16 +01:00
MervinPraison e0b9739b66 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 12:04:03 +01:00
claude[bot]andMervinPraison 1bb6c8ca61 fix: add targeted o1 model support for system messages
- Add `_needs_system_message_skip()` method to detect legacy o1 models
- Skip system messages only for specific models: o1-preview, o1-mini, o1-mini-2024-09-12
- Update all 4 system message handling locations in LLM class
- Maintains backward compatibility for all other models
- Fixes 400 error for legacy o1 models that don't support system messages

Fixes #246

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:03:18 +00:00
claude[bot]andMervinPraison a242a26353 feat: implement directory management solution for issue #130
- Add DirectoryManager class to organize files in ~/.praison/
- Automatic migration of existing configuration files from root
- Environment variable management for proper config paths
- Solves directory clutter issue (chainlit.md, .chainlit, public)

Co-Authored-By: MervinPraison <[email protected]>
2025-05-30 11:01:42 +00:00
claude[bot]andMervinPraison ff4f63fc54 feat: add Docker documentation and development setup
- Comprehensive Docker setup guide and documentation
- Environment template for easy configuration
- Development Docker Compose with hot reload
- Interactive quick-start script for user convenience

Co-Authored-By: MervinPraison <[email protected]>
2025-05-30 11:01:35 +00:00
claude[bot]andMervinPraison 48707686f0 feat: update Docker configurations for improved multi-service setup
- Add new praisonaiagents Dockerfile
- Update docker-compose.yml with multi-service orchestration
- Align versions across all Dockerfiles
- Add directory management environment variables
- Support for ~/.praison/ configuration directory

Co-Authored-By: MervinPraison <[email protected]>
2025-05-30 11:01:23 +00:00
MervinPraison 1a414989bf Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 11:59:17 +01:00
Mervin Praison 141535c160 Merge pull request #543 from MervinPraison/claude/issue-238-20250528_161801
feat: add camera integration examples and documentation
2025-05-30 11:58:29 +01:00
MervinPraison 6911d6ad64 fix: Update import path for database_config module in unit tests
- Adjusted the sys.path in test_database_config.py to correctly locate the database_config module.
- Ensured compatibility with the module's new location for improved test functionality.
2025-05-30 11:51:29 +01:00
claude[bot]andMervinPraison ba5af94dcb fix: add fallback mechanism for conda run failure in training
- Add robust conda environment detection and setup validation
- Implement fallback to direct Python execution when conda fails
- Maintain backward compatibility while improving reliability
- Handle cases where conda is not available or environment setup fails

Fixes #140

Co-authored-by: MervinPraison <[email protected]>
2025-05-29 04:20:59 +00:00
claude[bot]andMervinPraison 955a05be59 feat: add camera integration examples and documentation
- Add comprehensive camera integration examples in examples/python/camera/
  - camera-basic.py: Single frame capture and analysis
  - camera-continuous.py: Continuous monitoring system
  - camera-multi-agent.py: Multi-agent parallel analysis
  - camera-video-analysis.py: Video recording and temporal analysis
  - README.md: Complete documentation for all examples

- Add camera integration documentation in docs/features/camera-integration.mdx
  - Complete integration patterns and use cases
  - Configuration options and best practices
  - Troubleshooting guide and security considerations
  - Links to example code

- Update docs/mint.json to include camera integration in navigation

Addresses issue #238 regarding multimodel agent camera input

Co-authored-by: MervinPraison <[email protected]>
2025-05-28 16:22:02 +00:00
89 changed files with 13821 additions and 1755 deletions

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
# PraisonAI Docker Environment Configuration
# Copy this file to .env and update with your values
# Required: OpenAI API Key
OPENAI_API_KEY=your_openai_api_key_here
# Required: Chainlit Authentication Secret (generate a random string)
CHAINLIT_AUTH_SECRET=your_chainlit_secret_here
# Optional: Additional API Keys
ANTHROPIC_API_KEY=your_anthropic_api_key_here
GOOGLE_API_KEY=your_google_api_key_here
GROQ_API_KEY=your_groq_api_key_here
COHERE_API_KEY=your_cohere_api_key_here
# Optional: Service Configuration
FLASK_ENV=development
CHAINLIT_HOST=0.0.0.0
# Optional: Port Configuration (modify if you have conflicts)
UI_PORT=8082
CHAT_PORT=8083
API_PORT=8080
# Optional: Database Configuration
DATABASE_URL=sqlite:///root/.praison/database.sqlite
# Optional: Logging Configuration
LOG_LEVEL=INFO
PYTHONPATH=/app
# Optional: Development Settings
DEV_MODE=false
DEBUG=false
# Optional: Model Configuration
DEFAULT_MODEL=gpt-4
DEFAULT_TEMPERATURE=0.7
MAX_TOKENS=4000
# Optional: Security Settings
CORS_ORIGINS=http://localhost:3000,http://localhost:8082,http://localhost:8083
RATE_LIMIT=100
# Optional: Feature Flags
ENABLE_CHAT=true
ENABLE_UI=true
ENABLE_API=true
ENABLE_REALTIME=true
# Optional: Volume Paths (for custom mounting)
PRAISON_DATA_PATH=~/.praison
EXAMPLES_PATH=./examples
+40 -1
View File
@@ -1,6 +1,45 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
flask \
"praisonai>=2.2.25" \
"praisonai[api]" \
gunicorn \
markdown
# Copy application code
COPY . .
RUN pip install flask praisonai==2.2.19 gunicorn markdown
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Create health check API if api.py doesn't exist
RUN if [ ! -f api.py ]; then \
echo "from flask import Flask\n\
app = Flask(__name__)\n\
\n\
@app.route('/health')\n\
def health():\n\
return {'status': 'healthy'}, 200\n\
\n\
if __name__ == '__main__':\n\
app.run()" > api.py; \
fi
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
+12 -3
View File
@@ -7,18 +7,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Install Python packages
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.19" \
"praisonai>=2.2.25" \
"praisonai[chat]" \
"embedchain[github,youtube]"
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "chat"]
+23 -4
View File
@@ -9,20 +9,39 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
portaudio19-dev \
python3-pyaudio \
curl \
git \
vim \
nano \
&& rm -rf /var/lib/apt/lists/*
# Install Python packages
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.19" \
"praisonai>=2.2.25" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
"embedchain[github,youtube]"
"praisonai[code]" \
"embedchain[github,youtube]" \
jupyter \
jupyterlab \
notebook \
pytest \
pytest-asyncio \
mkdocs \
mkdocs-material
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "ui"]
+32
View File
@@ -0,0 +1,32 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Copy praisonaiagents source
COPY src/praisonai-agents/ /app/praisonaiagents/
WORKDIR /app/praisonaiagents
# Install praisonaiagents with all features (non-editable for production)
RUN pip install --no-cache-dir ".[all]"
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
WORKDIR /app
# Default command
CMD ["python", "-c", "import praisonaiagents; print('PraisonAI Agents installed successfully')"]
+12 -3
View File
@@ -7,18 +7,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Install Python packages
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.19" \
"praisonai>=2.2.25" \
"praisonai[ui]" \
"praisonai[crewai]"
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "ui"]
+265
View File
@@ -0,0 +1,265 @@
# PraisonAI Docker Setup
This directory contains Docker configurations for running PraisonAI services in containerized environments. The setup addresses directory management issues and provides comprehensive multi-service deployment options.
## 🐳 Available Services
### Core Services
- **UI Service** (`port 8082`) - Chainlit-based web interface
- **Chat Service** (`port 8083`) - Dedicated chat interface
- **API Service** (`port 8080`) - REST API endpoint
- **Agents Service** - Standalone PraisonAI Agents runtime
### Docker Files
- `Dockerfile` - Basic API service
- `Dockerfile.ui` - UI service with web interface
- `Dockerfile.chat` - Chat-focused service
- `Dockerfile.dev` - Development environment with tools
- `Dockerfile.praisonaiagents` - Standalone agents framework
- `docker-compose.yml` - Multi-service orchestration
## 🚀 Quick Start
### Single Service
```bash
# Run UI service
docker run -p 8082:8082 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:ui
# Run Chat service
docker run -p 8083:8083 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:chat
# Run API service
docker run -p 8080:8080 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:api
```
### Multi-Service with Docker Compose
```bash
# Create environment file
cat > .env << EOF
OPENAI_API_KEY=your_openai_api_key_here
CHAINLIT_AUTH_SECRET=your_secret_here
EOF
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f
# Stop services
docker-compose down
```
## 📁 Directory Management
### Problem Solved
The original issue was that files like `chainlit.md`, `.chainlit` directory, and `public` folder were cluttering the root directory.
### Solution Implemented
All PraisonAI configuration and runtime files are now stored in `~/.praison/`:
```bash
~/.praison/
├── database.sqlite # Chainlit database
├── chainlit.md # Chainlit configuration
├── .chainlit/ # Chainlit runtime files
└── config/ # PraisonAI configuration
```
### Environment Variables
```bash
PRAISON_CONFIG_DIR=/root/.praison # Main config directory
CHAINLIT_CONFIG_DIR=/root/.praison # Chainlit config location
CHAINLIT_DB_DIR=/root/.praison # Database location
```
## 🔧 Service Configuration
### UI Service (Port 8082)
```yaml
environment:
- CHAINLIT_PORT=8082
- CHAINLIT_HOST=0.0.0.0
- OPENAI_API_KEY=${OPENAI_API_KEY}
- CHAINLIT_AUTH_SECRET=${CHAINLIT_AUTH_SECRET}
```
### Chat Service (Port 8083)
```yaml
environment:
- CHAINLIT_PORT=8083
- CHAINLIT_HOST=0.0.0.0
- OPENAI_API_KEY=${OPENAI_API_KEY}
```
### API Service (Port 8080)
```yaml
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
```
## 🎯 Service Endpoints
| Service | Port | Endpoint | Description |
|---------|------|----------|-------------|
| UI | 8082 | http://localhost:8082 | Web interface |
| Chat | 8083 | http://localhost:8083 | Chat interface |
| API | 8080 | http://localhost:8080 | REST API |
| API Health | 8080 | http://localhost:8080/health | Health check |
## 🔍 Health Checks
All services include health checks:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:PORT"]
interval: 10s
timeout: 5s
retries: 3
```
## 📦 Package Versions
All Docker images use consistent, up-to-date versions:
- PraisonAI: `>=2.2.25`
- PraisonAI Agents: `>=0.0.92`
- Python: `3.11-slim`
## 🔒 Security Features
- Non-root user execution where possible
- Minimal base image (python:3.11-slim)
- No unnecessary packages installed
- Environment variable-based configuration
- Volume mounting for persistent data
## 🛠 Development
### Development Environment
```bash
# Use development Dockerfile with additional tools
docker build -f Dockerfile.dev -t praisonai:dev .
docker run -it -v $(pwd):/app praisonai:dev bash
```
### Custom Configuration
```bash
# Mount custom config directory
docker run -v ~/.praison:/root/.praison praisonai:ui
```
## 📊 Monitoring
### Docker Compose Monitoring
```bash
# View service status
docker-compose ps
# View resource usage
docker-compose top
# View logs for specific service
docker-compose logs ui
docker-compose logs chat
docker-compose logs api
```
## 🚨 Troubleshooting
### Common Issues
1. **Port conflicts**
```bash
# Check port usage
netstat -tlnp | grep :8082
# Use different ports
docker run -p 9082:8082 praisonai:ui
```
2. **Environment variables not loading**
```bash
# Verify .env file
cat .env
# Set variables directly
docker run -e OPENAI_API_KEY=your_key praisonai:ui
```
3. **Permission issues**
```bash
# Check volume permissions
ls -la ~/.praison/
# Fix permissions
sudo chown -R $(id -u):$(id -g) ~/.praison/
```
4. **Service won't start**
```bash
# Check logs
docker-compose logs service_name
# Restart service
docker-compose restart service_name
```
## 🔄 Updates
### Pulling Latest Images
```bash
# Pull latest images
docker-compose pull
# Restart with new images
docker-compose up -d
```
### Version Pinning
To use specific versions, update the Dockerfile:
```dockerfile
RUN pip install "praisonai==2.2.25" "praisonaiagents==0.0.92"
```
## 🌐 Production Deployment
### Recommended Production Setup
```yaml
# docker-compose.prod.yml
version: '3.8'
services:
ui:
image: ghcr.io/mervinpraison/praisonai:ui
restart: unless-stopped
environment:
- CHAINLIT_HOST=0.0.0.0
- CHAINLIT_PORT=8082
volumes:
- praison_data:/root/.praison
networks:
- praison_network
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
```
### Load Balancer Configuration
For production environments, consider using nginx or similar:
```nginx
upstream praisonai {
server localhost:8082;
server localhost:8083;
}
server {
listen 80;
location / {
proxy_pass http://praisonai;
}
}
```
This Docker setup provides a clean, organized, and scalable way to deploy PraisonAI services while solving the directory management issues mentioned in the original request.
+184
View File
@@ -0,0 +1,184 @@
version: '3.8'
# Development Docker Compose Configuration
# Extends the main docker-compose.yml with development-specific settings
services:
# Development UI Service with hot reload
ui-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
- ../docs:/app/docs:ro
ports:
- "${UI_PORT:-8082}:8082"
environment:
FLASK_ENV: development
CHAINLIT_PORT: "8082"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: praisonai ui --host 0.0.0.0 --reload
networks:
- praison_network
depends_on:
- database
# Development Chat Service
chat-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
ports:
- "${CHAT_PORT:-8083}:8083"
environment:
CHAINLIT_PORT: "8083"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: praisonai chat --host 0.0.0.0 --port 8083 --reload
networks:
- praison_network
depends_on:
- database
# Development API Service with hot reload
api-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
ports:
- "${API_PORT:-8080}:8080"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
FLASK_ENV: development
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: python -m flask --app api run --host 0.0.0.0 --port 8080 --debug
networks:
- praison_network
depends_on:
- database
# Development Jupyter Notebook Service
jupyter:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
- ../docs:/app/docs:rw
ports:
- "8888:8888"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
JUPYTER_ENABLE_LAB: "yes"
PYTHONPATH: /app/src
command: jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --allow-root --LabApp.token='' --LabApp.password=''
networks:
- praison_network
# Development Testing Service
test:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- ../src:/app/src:rw
- ../tests:/app/tests:rw
- praison_data:/root/.praison
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
PYTHONPATH: /app/src
PYTEST_CURRENT_TEST: "true"
command: python -m pytest tests/ -v
networks:
- praison_network
profiles:
- test
# Standalone database for development
database:
image: postgres:15-alpine
environment:
POSTGRES_DB: praisonai_dev
POSTGRES_USER: praisonai
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-praisonai_dev_pass}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- praison_network
profiles:
- database
# Redis for caching and session management
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- praison_network
profiles:
- cache
# Development documentation server
docs:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- ../docs:/app/docs:rw
- ../README.md:/app/README.md:ro
ports:
- "8000:8000"
command: sh -c "cd docs && mkdocs serve --dev-addr 0.0.0.0:8000"
networks:
- praison_network
profiles:
- docs
volumes:
praison_data:
driver: local
postgres_data:
driver: local
redis_data:
driver: local
networks:
praison_network:
driver: bridge
+81 -3
View File
@@ -1,9 +1,13 @@
version: '3.8'
services:
# PraisonAI UI Service
ui:
build:
context: .
dockerfile: Dockerfile.ui
context: ..
dockerfile: docker/Dockerfile.ui
volumes:
- praison_data:/root/.praison
- .:/app
ports:
- "8082:8082"
@@ -13,9 +17,83 @@ services:
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
command: praisonai ui --host 0.0.0.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8082"]
interval: 10s
timeout: 5s
retries: 3
retries: 3
networks:
- praison_network
# PraisonAI Chat Service
chat:
build:
context: ..
dockerfile: docker/Dockerfile.chat
volumes:
- praison_data:/root/.praison
- .:/app
ports:
- "8083:8083"
environment:
CHAINLIT_PORT: "8083"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
command: praisonai chat --host 0.0.0.0 --port 8083
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8083"]
interval: 10s
timeout: 5s
retries: 3
networks:
- praison_network
# PraisonAI API Service
api:
build:
context: ..
dockerfile: docker/Dockerfile
volumes:
- praison_data:/root/.praison
ports:
- "8080:8080"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3
networks:
- praison_network
# PraisonAI Agents Service (standalone)
agents:
build:
context: ..
dockerfile: docker/Dockerfile.praisonaiagents
volumes:
- praison_data:/root/.praison
- ./examples:/app/examples
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
networks:
- praison_network
volumes:
praison_data:
driver: local
networks:
praison_network:
driver: bridge
+289
View File
@@ -0,0 +1,289 @@
#!/bin/bash
# PraisonAI Docker Quick Start Script
# This script helps users quickly get started with PraisonAI using Docker
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if Docker is installed
check_docker() {
if ! command -v docker &> /dev/null; then
print_error "Docker is not installed. Please install Docker first:"
echo " - Visit: https://docs.docker.com/get-docker/"
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
print_error "Docker Compose is not available. Please install Docker Compose:"
echo " - Visit: https://docs.docker.com/compose/install/"
exit 1
fi
print_success "Docker and Docker Compose are available"
}
# Check if .env file exists, create from template if not
setup_environment() {
if [ ! -f ".env" ]; then
print_status "Creating .env file from template..."
if [ -f ".env.template" ]; then
cp .env.template .env
else
cat > .env << EOF
# PraisonAI Docker Environment Configuration
OPENAI_API_KEY=your_openai_api_key_here
CHAINLIT_AUTH_SECRET=$(openssl rand -hex 32 2>/dev/null || echo "your_chainlit_secret_here")
FLASK_ENV=development
CHAINLIT_HOST=0.0.0.0
UI_PORT=8082
CHAT_PORT=8083
API_PORT=8080
EOF
fi
print_warning "Please edit .env file and add your API keys before continuing"
print_status "Required: OPENAI_API_KEY"
read -p "Press Enter to continue after updating .env file..."
else
print_success ".env file already exists"
fi
}
# Function to show menu
show_menu() {
echo ""
echo "=== PraisonAI Docker Quick Start ==="
echo ""
echo "1) Start UI Service (Web Interface) - Port 8082"
echo "2) Start Chat Service - Port 8083"
echo "3) Start API Service - Port 8080"
echo "4) Start All Services (Recommended)"
echo "5) Start Development Environment"
echo "6) View Service Status"
echo "7) View Logs"
echo "8) Stop All Services"
echo "9) Pull Latest Images"
echo "10) Clean Up (Remove containers and volumes)"
echo "11) Show Service URLs"
echo "0) Exit"
echo ""
}
# Start specific service
start_service() {
local service=$1
print_status "Starting $service service..."
docker-compose up -d $service
print_success "$service service started"
}
# Start all services
start_all_services() {
print_status "Starting all PraisonAI services..."
docker-compose up -d
print_success "All services started"
show_service_urls
}
# Start development environment
start_dev_environment() {
print_status "Starting development environment..."
if [ -f "docker-compose.dev.yml" ]; then
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d ui-dev chat-dev api-dev
print_success "Development environment started"
echo ""
echo "Development services available:"
echo " - UI (with hot reload): http://localhost:${UI_PORT:-8082}"
echo " - Chat (with hot reload): http://localhost:${CHAT_PORT:-8083}"
echo " - API (with hot reload): http://localhost:${API_PORT:-8080}"
echo " - Jupyter Lab: http://localhost:8888"
echo ""
else
print_warning "Development configuration not found, starting regular services"
start_all_services
fi
}
# Show service status
show_status() {
print_status "Service Status:"
docker-compose ps
}
# Show logs
show_logs() {
echo ""
echo "Which service logs would you like to view?"
echo "1) All services"
echo "2) UI service"
echo "3) Chat service"
echo "4) API service"
echo "5) Agents service"
read -p "Enter choice (1-5): " log_choice
case $log_choice in
1) docker-compose logs -f ;;
2) docker-compose logs -f ui ;;
3) docker-compose logs -f chat ;;
4) docker-compose logs -f api ;;
5) docker-compose logs -f agents ;;
*) print_error "Invalid choice" ;;
esac
}
# Stop all services
stop_services() {
print_status "Stopping all services..."
docker-compose down
print_success "All services stopped"
}
# Pull latest images
pull_images() {
print_status "Pulling latest images..."
docker-compose pull
print_success "Images updated"
}
# Clean up
cleanup() {
print_warning "This will remove all containers, networks, and volumes"
read -p "Are you sure? (y/N): " confirm
if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then
print_status "Cleaning up..."
docker-compose down -v --remove-orphans
docker system prune -f
print_success "Cleanup completed"
fi
}
# Show service URLs
show_service_urls() {
local ui_port=${UI_PORT:-8082}
local chat_port=${CHAT_PORT:-8083}
local api_port=${API_PORT:-8080}
echo ""
echo "=== Service URLs ==="
echo "UI Service: http://localhost:$ui_port"
echo "Chat Service: http://localhost:$chat_port"
echo "API Service: http://localhost:$api_port"
echo "API Health: http://localhost:$api_port/health"
echo ""
}
# Check system requirements
check_requirements() {
print_status "Checking system requirements..."
# Check available memory
if command -v free &> /dev/null; then
available_ram=$(free -m | awk 'NR==2{printf "%.1f", $7/1024}')
print_status "Available RAM: ${available_ram}GB"
if (( $(echo "$available_ram < 2.0" | bc -l 2>/dev/null || echo "1") )); then
print_warning "Low available memory. Recommended: 2GB+ available RAM"
fi
fi
# Check disk space
if command -v df &> /dev/null; then
available_space=$(df -BG . | awk 'NR==2{print $4}' | sed 's/G//')
print_status "Available disk space: ${available_space}GB"
if [ "$available_space" -lt 5 ]; then
print_warning "Low disk space. Recommended: 5GB+ available space"
fi
fi
}
# Main function
main() {
clear
echo "🤖 Welcome to PraisonAI Docker Setup!"
echo ""
# Check prerequisites
check_docker
check_requirements
setup_environment
# Main loop
while true; do
show_menu
read -p "Enter your choice (0-11): " choice
case $choice in
1)
start_service "ui"
echo "UI Service: http://localhost:${UI_PORT:-8082}"
;;
2)
start_service "chat"
echo "Chat Service: http://localhost:${CHAT_PORT:-8083}"
;;
3)
start_service "api"
echo "API Service: http://localhost:${API_PORT:-8080}"
;;
4)
start_all_services
;;
5)
start_dev_environment
;;
6)
show_status
;;
7)
show_logs
;;
8)
stop_services
;;
9)
pull_images
;;
10)
cleanup
;;
11)
show_service_urls
;;
0)
print_success "Thank you for using PraisonAI!"
exit 0
;;
*)
print_error "Invalid choice. Please try again."
;;
esac
echo ""
read -p "Press Enter to continue..."
done
}
# Run main function
main
+1 -1
View File
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
file.write(&#34;FROM python:3.11-slim\n&#34;)
file.write(&#34;WORKDIR /app\n&#34;)
file.write(&#34;COPY . .\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.2.19 gunicorn markdown\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.2.25 gunicorn markdown\n&#34;)
file.write(&#34;EXPOSE 8080\n&#34;)
file.write(&#39;CMD [&#34;gunicorn&#34;, &#34;-b&#34;, &#34;0.0.0.0:8080&#34;, &#34;api:app&#34;]\n&#39;)
+1 -1
View File
@@ -27,7 +27,7 @@ WORKDIR /app
COPY . .
RUN pip install flask praisonai==2.2.19 watchdog
RUN pip install flask praisonai==2.2.25 watchdog
EXPOSE 5555
+423
View File
@@ -0,0 +1,423 @@
---
title: Camera Integration
description: How to integrate camera feeds with PraisonAI multimodal agents for real-time visual analysis
---
# Camera Integration
PraisonAI supports camera integration for real-time visual analysis through multimodal agents. While there's no built-in camera capture, you can easily integrate camera feeds by capturing frames or videos and passing them to vision agents.
## Overview
Camera integration works by:
1. **Capturing frames/videos** from camera using OpenCV
2. **Saving temporarily** to disk
3. **Passing file paths** to agents via the `images` parameter
4. **Cleaning up** temporary files after analysis
## Quick Start
### Prerequisites
```bash
pip install praisonaiagents opencv-python
export OPENAI_API_KEY=your_openai_api_key
```
### Basic Camera Capture
```python
import cv2
from praisonaiagents import Agent, Task, PraisonAIAgents
def capture_and_analyze():
# Create vision agent
vision_agent = Agent(
name="CameraAnalyst",
role="Camera Feed Analyzer",
goal="Analyze camera captures in real-time",
backstory="Expert in real-time visual analysis",
llm="gpt-4o-mini"
)
# Capture from camera
cap = cv2.VideoCapture(0) # 0 for default camera
ret, frame = cap.read()
if ret:
# Save frame temporarily
cv2.imwrite("temp_capture.jpg", frame)
cap.release()
# Create analysis task
task = Task(
description="Analyze what you see in this camera feed",
expected_output="Detailed analysis of camera content",
agent=vision_agent,
images=["temp_capture.jpg"]
)
# Run analysis
agents = PraisonAIAgents(
agents=[vision_agent],
tasks=[task]
)
return agents.start()
# Run analysis
result = capture_and_analyze()
```
## Integration Patterns
### 1. Single Frame Analysis
Perfect for quick snapshots and one-time analysis:
```python
def single_frame_analysis():
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
if ret:
image_path = "snapshot.jpg"
cv2.imwrite(image_path, frame)
# Analyze with your agent
# ... (agent setup and task creation)
cap.release()
```
### 2. Continuous Monitoring
Ideal for security systems and real-time monitoring:
```python
def continuous_monitoring(interval=10):
vision_agent = Agent(
name="SecurityMonitor",
role="Security Camera Analyst",
goal="Monitor for security events",
backstory="Expert security analyst",
llm="gpt-4o-mini"
)
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if ret:
timestamp = int(time.time())
filename = f"capture_{timestamp}.jpg"
cv2.imwrite(filename, frame)
# Analyze frame
task = Task(
description="Monitor for unusual activities",
agent=vision_agent,
images=[filename]
)
agents = PraisonAIAgents(
agents=[vision_agent],
tasks=[task]
)
result = agents.start()
print(f"Analysis: {result}")
time.sleep(interval)
```
### 3. Multi-Agent Analysis
Use multiple specialized agents for comprehensive analysis:
```python
def multi_agent_camera_analysis():
# Create specialized agents
security_agent = Agent(
name="SecurityExpert",
role="Security Specialist",
goal="Identify security threats",
backstory="Expert in surveillance and threat detection",
llm="gpt-4o-mini"
)
object_detector = Agent(
name="ObjectDetector",
role="Object Recognition Specialist",
goal="Identify and catalog objects",
backstory="Computer vision expert",
llm="gpt-4o-mini"
)
scene_analyst = Agent(
name="SceneAnalyst",
role="Scene Understanding Expert",
goal="Provide comprehensive scene analysis",
backstory="Environmental analysis expert",
llm="gpt-4o-mini"
)
# Capture frame
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
cap.release()
if ret:
cv2.imwrite("analysis_frame.jpg", frame)
# Create specialized tasks
security_task = Task(
description="Analyze for security threats",
agent=security_agent,
images=["analysis_frame.jpg"]
)
object_task = Task(
description="Identify all objects in scene",
agent=object_detector,
images=["analysis_frame.jpg"]
)
scene_task = Task(
description="Provide comprehensive scene analysis",
agent=scene_analyst,
images=["analysis_frame.jpg"]
)
# Run parallel analysis
agents = PraisonAIAgents(
agents=[security_agent, object_detector, scene_analyst],
tasks=[security_task, object_task, scene_task],
process="parallel"
)
return agents.start()
```
### 4. Video Recording & Analysis
Analyze temporal events and activities:
```python
def record_and_analyze_video(duration=10):
cap = cv2.VideoCapture(0)
# Setup video writer
fps = int(cap.get(cv2.CAP_PROP_FPS)) or 30
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('recording.mp4', fourcc, fps, (width, height))
# Record video
start_time = time.time()
while (time.time() - start_time) < duration:
ret, frame = cap.read()
if ret:
out.write(frame)
cap.release()
out.release()
# Analyze video
video_agent = Agent(
name="VideoAnalyst",
role="Video Content Analyzer",
goal="Analyze video content for activities",
backstory="Expert in video analysis",
llm="gpt-4o-mini"
)
task = Task(
description="Analyze this video for activities and events",
agent=video_agent,
images=["recording.mp4"] # Video files work too!
)
agents = PraisonAIAgents(
agents=[video_agent],
tasks=[task]
)
return agents.start()
```
## Supported Input Types
PraisonAI accepts various visual input formats:
- ✅ **Local Images**: `"camera_shot.jpg"`, `"webcam_capture.png"`
- ✅ **Local Videos**: `"security_feed.mp4"`, `"recording.avi"`
- ✅ **Image URLs**: `"https://example.com/live_feed.jpg"`
- ✅ **Multiple Sources**: `["cam1.jpg", "cam2.jpg", "video.mp4"]`
## Configuration Options
### Camera Selection
```python
# Different camera sources
camera_id = 0 # Default camera
camera_id = 1 # External USB camera
camera_id = "rtsp://camera-ip/stream" # IP camera
```
### Analysis Intervals
```python
# For continuous monitoring
analysis_interval = 5 # Every 5 seconds (high frequency)
analysis_interval = 30 # Every 30 seconds (moderate)
analysis_interval = 300 # Every 5 minutes (low frequency)
```
### Video Recording
```python
# Recording duration
duration = 10 # 10 seconds
duration = 60 # 1 minute
duration = 300 # 5 minutes
```
## Use Cases
### Security Monitoring
- Real-time threat detection
- Unauthorized access alerts
- Suspicious activity identification
- Perimeter monitoring
### Retail Analytics
- Customer behavior analysis
- Inventory monitoring
- Queue management
- Theft prevention
### Industrial Automation
- Quality control inspection
- Safety compliance monitoring
- Equipment status verification
- Process optimization
### Smart Home
- Activity recognition
- Elderly care monitoring
- Pet monitoring
- Energy usage optimization
## Best Practices
### Performance Optimization
1. **Frame Rate Management**
```python
# Reduce analysis frequency for better performance
time.sleep(5) # Analyze every 5 seconds instead of continuously
```
2. **Image Size Optimization**
```python
# Resize frames for faster processing
frame = cv2.resize(frame, (640, 480))
```
3. **Parallel Processing**
```python
# Use parallel process for multiple agents
process="parallel"
```
### Memory Management
1. **Clean Up Temporary Files**
```python
import os
if os.path.exists(image_path):
os.remove(image_path)
```
2. **Limit Video Duration**
```python
# Keep recordings short to avoid large files
max_duration = 30 # seconds
```
### Security Considerations
1. **Camera Permissions**
- Ensure application has camera access
- Handle permission errors gracefully
2. **Privacy Protection**
- Implement data retention policies
- Secure transmission of camera data
- User consent for recording
3. **Access Control**
- Authenticate camera access
- Implement role-based permissions
## Troubleshooting
### Camera Not Found
```python
# Check available cameras
for i in range(4):
cap = cv2.VideoCapture(i)
if cap.isOpened():
print(f"Camera {i} is available")
cap.release()
else:
print(f"Camera {i} not available")
```
### Permission Issues
**Linux:**
```bash
# Add user to video group
sudo usermod -a -G video $USER
```
**macOS:**
- Grant camera permissions in System Preferences > Security & Privacy
**Windows:**
- Check camera privacy settings in Windows Settings
### Performance Issues
```python
# Reduce frame size
frame = cv2.resize(frame, (320, 240))
# Lower analysis frequency
time.sleep(10) # Analyze every 10 seconds
# Use threading for non-blocking capture
import threading
```
## Examples
Complete working examples are available in the repository:
- [`camera-basic.py`](../../examples/python/camera/camera-basic.py) - Basic single frame capture
- [`camera-continuous.py`](../../examples/python/camera/camera-continuous.py) - Continuous monitoring
- [`camera-multi-agent.py`](../../examples/python/camera/camera-multi-agent.py) - Multi-agent analysis
- [`camera-video-analysis.py`](../../examples/python/camera/camera-video-analysis.py) - Video recording & analysis
## Related Features
- [Multimodal Agents](./multimodal) - Core multimodal capabilities
- [Image Generation](./image-generation) - Creating images with AI
- [Agent Memory](../concepts/memory) - Persistent agent memory
- [Task Management](../concepts/tasks) - Task configuration and execution
+54
View File
@@ -0,0 +1,54 @@
---
title: 'Playwright'
description: 'Using Playwright MCP with PraisonAI'
---
# Playwright MCP
[Playwright](https://playwright.dev/) is a powerful browser automation tool that can be used with the Model Context Protocol (MCP) to enable agents to interact with web browsers.
## Installation
To use Playwright with MCP, you'll need to install the Playwright MCP package:
```bash
npm install @playwright/mcp
```
## Running the Playwright MCP Server
You need to run the Playwright MCP server in a separate terminal before your agent can use it:
```bash
npx @playwright/mcp@latest --port 8931
```
This will start a Playwright MCP server on port 8931 that your agent can connect to.
## Using Playwright MCP with PraisonAI
Once the Playwright MCP server is running, you can create an agent that uses it as a tool:
```python
from praisonaiagents import Agent, MCP
search_agent = Agent(
instructions="""You help search the web.""",
llm="gpt-4o-mini",
tools=MCP("http://localhost:8931/sse")
)
search_agent.start("Find about Praison AI")
```
The agent will be able to use Playwright to automate browser interactions and search the web.
## Advanced Configuration
You can customize the Playwright MCP server by passing additional options:
```bash
npx @playwright/mcp@latest --port 8931 --browser chromium
```
For more information on available options, refer to the [Playwright MCP documentation](https://github.com/microsoft/playwright/tree/main/packages/mcp).
+3 -1
View File
@@ -143,7 +143,8 @@
"features/mathagent",
"features/structured",
"features/callbacks",
"features/chat-with-pdf"
"features/chat-with-pdf",
"features/camera-integration"
]
},
{
@@ -250,6 +251,7 @@
"mcp/anthropic",
"mcp/gemini",
"mcp/mistral",
"mcp/playwright",
"mcp/whatsapp",
"mcp/perplexity",
"mcp/ollama-python",
+2 -2
View File
@@ -31,7 +31,7 @@ export OPENAI_API_KEY=xxxxxxxxx
```python
import os
from crawl4ai import WebCrawler
from crawl4ai import AsyncWebCrawler
from crawl4ai.extraction_strategy import LLMExtractionStrategy
from pydantic import BaseModel, Field
@@ -41,7 +41,7 @@ class OpenAIModelFee(BaseModel):
output_fee: str = Field(..., description="Fee for output token ßfor the OpenAI model.")
url = 'https://openai.com/api/pricing/'
crawler = WebCrawler()
crawler = AsyncWebCrawler()
crawler.warmup()
result = crawler.run(
+1 -1
View File
@@ -120,7 +120,7 @@ from praisonaiagents.tools import scrape_page, extract_links, crawl, extract_tex
# Create search agent
agent = Agent(
name="WebCrawler",
name="AsyncWebCrawler",
role="Web Scraping Specialist",
goal="Extract and analyze web content efficiently.",
backstory="Expert in web scraping and content extraction.",
+1 -1
View File
@@ -155,7 +155,7 @@ To facilitate local development with live reload, you can use Docker. Follow the
COPY . .
RUN pip install flask praisonai==2.2.19 watchdog
RUN pip install flask praisonai==2.2.25 watchdog
EXPOSE 5555
+1 -1
View File
@@ -208,7 +208,7 @@ To facilitate local development with live reload, you can use Docker. Follow the
COPY . .
RUN pip install flask praisonai==2.2.19 watchdog
RUN pip install flask praisonai==2.2.25 watchdog
EXPOSE 5555
+253
View File
@@ -0,0 +1,253 @@
---
title: "MCP with Streamlit"
description: "How to properly integrate MCP (Model Context Protocol) tools with PraisonAI Agents in Streamlit applications"
---
# MCP with Streamlit
This guide explains how to properly integrate MCP (Model Context Protocol) tools with PraisonAI Agents in Streamlit applications, addressing common issues and providing working solutions.
## Common Issues
When integrating MCP tools with Streamlit, users often encounter these problems:
### Issue #1: Agent Re-initialization
**Problem**: Agent gets re-initialized on every Streamlit interaction, causing MCP tools to fail.
**Solution**: Use Streamlit's session state to initialize the agent only once.
### Issue #2: LLM Provider Format
**Problem**: Using provider/model format like `"ollama/llama3.2"` can cause tool calling issues in Streamlit environments.
**Solution**: Use standard model names like `"gpt-4o-mini"` instead of provider/model format.
### Issue #3: Missing Error Handling
**Problem**: No user feedback when MCP initialization fails.
**Solution**: Implement comprehensive error handling with user-friendly messages.
## Working Example
Here's a complete working example that demonstrates the correct approach:
```python
import streamlit as st
from praisonaiagents import Agent
from praisonaiagents.mcp import MCP
import traceback
st.title("🏠 AI Airbnb Assistant")
# Configuration in sidebar
with st.sidebar:
st.header("⚙️ Configuration")
# Use standard model names, not provider/model format
llm_model = st.selectbox(
"Choose LLM Model",
options=[
"gpt-4o-mini", # ✅ Correct format
"gpt-4o",
"claude-3-5-sonnet-20241022"
],
index=0
)
debug_mode = st.checkbox("Enable Debug Mode", value=False)
# Initialize session state (CRITICAL for Streamlit)
if "agent_initialized" not in st.session_state:
st.session_state.agent_initialized = False
st.session_state.agent = None
st.session_state.init_error = None
# Function to initialize agent with proper error handling
def initialize_agent():
try:
with st.spinner("🔄 Initializing AI agent..."):
# Create MCP tools
mcp_tools = MCP(
"npx -y @openbnb/mcp-server-airbnb --ignore-robots-txt",
timeout=60,
debug=debug_mode
)
# Create agent - this should only happen ONCE
agent = Agent(
instructions="You are a helpful Airbnb assistant...",
llm=llm_model, # Use standard format
tools=mcp_tools,
verbose=debug_mode
)
return agent, None
except Exception as e:
error_msg = f"Failed to initialize agent: {str(e)}"
if debug_mode:
error_msg += f"\n\nFull traceback:\n{traceback.format_exc()}"
return None, error_msg
# Agent initialization (only runs once)
if not st.session_state.agent_initialized:
if st.button("🚀 Initialize AI Assistant", type="primary"):
agent, error = initialize_agent()
if agent:
st.session_state.agent = agent
st.session_state.agent_initialized = True
st.session_state.init_error = None
st.success("✅ AI Assistant initialized successfully!")
st.rerun()
else:
st.session_state.init_error = error
st.error(f"❌ Initialization failed: {error}")
# Main interface (only show if agent is ready)
if st.session_state.agent_initialized and st.session_state.agent:
query = st.text_input("🔍 What are you looking for?")
if st.button("Search") and query:
try:
with st.spinner("🔍 Searching..."):
# Use the SAME agent instance from session state
result = st.session_state.agent.start(query)
st.write(result)
except Exception as e:
st.error(f"❌ Search failed: {str(e)}")
```
## Best Practices
### 1. Session State Management
Always use Streamlit's session state to manage agent lifecycle:
```python
# ✅ Correct - Initialize once in session state
if "agent" not in st.session_state:
st.session_state.agent = Agent(...)
# ❌ Wrong - Re-initializes on every interaction
agent = Agent(...)
```
### 2. LLM Model Selection
Use standard model names, avoid provider/model format in Streamlit:
```python
# ✅ Correct - Standard model names
llm="gpt-4o-mini"
llm="claude-3-5-sonnet-20241022"
# ❌ Problematic in Streamlit - Provider/model format
llm="ollama/llama3.2"
llm="anthropic/claude-3-5-sonnet"
```
### 3. Error Handling
Implement comprehensive error handling:
```python
try:
# MCP initialization
mcp_tools = MCP("your-mcp-command")
agent = Agent(tools=mcp_tools, ...)
except Exception as e:
st.error(f"Initialization failed: {str(e)}")
# Provide troubleshooting tips
```
### 4. User Feedback
Provide clear feedback during initialization:
```python
with st.spinner("🔄 Initializing AI agent and MCP tools..."):
# Initialization code here
pass
if initialization_successful:
st.success("✅ AI Assistant initialized successfully!")
else:
st.error("❌ Initialization failed")
```
## Troubleshooting
### MCP Tools Not Found
If you get "MCP tool cannot be found" errors:
1. **Check MCP Server Command**: Ensure the MCP server command is correct and the server starts successfully
2. **Verify Dependencies**: Make sure all required dependencies (Node.js, npm, etc.) are installed
3. **Test Manually**: Try running the MCP server command manually first
4. **Enable Debug Mode**: Set `debug=True` in MCP constructor for detailed logs
### Tool Calling Issues
If tools aren't being called properly:
1. **Use Standard LLM Format**: Avoid provider/model format like `"ollama/llama3.2"`
2. **Check API Keys**: Ensure proper environment variables are set for your chosen LLM
3. **Session State**: Verify agent is properly stored in session state
4. **Timeout Settings**: Increase timeout if needed: `MCP(command, timeout=120)`
### Threading Conflicts
If you encounter threading-related errors:
1. **Single Agent Instance**: Use only one agent instance per session
2. **Proper Cleanup**: Let Streamlit handle cleanup naturally
3. **Avoid Manual Threading**: Don't create additional threads in Streamlit
## Complete Working Example
For a complete, production-ready example, see:
- [`examples/python/ui/mcp-streamlit-airbnb.py`](https://github.com/MervinPraison/PraisonAI/blob/main/examples/python/ui/mcp-streamlit-airbnb.py)
This example includes:
- ✅ Proper session state management
- ✅ Comprehensive error handling
- ✅ User-friendly interface
- ✅ Debug mode support
- ✅ Troubleshooting guidance
- ✅ Configuration options
## Environment Setup
Make sure your environment has the required dependencies:
```bash
# Install PraisonAI Agents
pip install praisonaiagents
# Install Streamlit
pip install streamlit
# For Airbnb MCP server example
npm install -g @openbnb/mcp-server-airbnb
# Set environment variables for your chosen LLM
export OPENAI_API_KEY="your-key"
# or
export ANTHROPIC_API_KEY="your-key"
```
## Running the Example
```bash
# Run the working example
streamlit run examples/python/ui/mcp-streamlit-airbnb.py
# Or create your own based on the patterns above
streamlit run your-mcp-app.py
```
## Summary
The key to successfully using MCP with Streamlit is:
1. **Proper session state management** - Initialize agent only once
2. **Standard LLM format** - Avoid provider/model format in Streamlit
3. **Comprehensive error handling** - Provide user feedback
4. **Correct usage patterns** - Follow Streamlit best practices
By following these guidelines, MCP tools will work reliably in your Streamlit applications without requiring any modifications to the core MCP implementation.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,373 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "M_G-1ig6-__z"
},
"source": [
"# LangChain Agent with PraisonAI\n",
"This notebook demonstrates how to use LangChain-compatible tools and PraisonAI agents to research AI-related topics using Wikipedia and YouTube.\n",
"\n",
"---"
],
"id": "M_G-1ig6-__z"
},
{
"cell_type": "markdown",
"metadata": {
"id": "ZVBvL3zK-__3"
},
"source": [
"## Dependencies"
],
"id": "ZVBvL3zK-__3"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "d7aUlkgM-__5"
},
"outputs": [],
"source": [
"!pip install praisonaiagents langchain-community wikipedia youtube-search"
],
"id": "d7aUlkgM-__5"
},
{
"cell_type": "markdown",
"metadata": {
"id": "sHNzy00Q-__6"
},
"source": [
"## Set API Keys"
],
"id": "sHNzy00Q-__6"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "xCQvYxjK-__6"
},
"outputs": [],
"source": [
"import os\n",
"os.environ['OPENAI_API_KEY'] = 'your_api_key_here'"
],
"id": "xCQvYxjK-__6"
},
{
"cell_type": "markdown",
"metadata": {
"id": "1CDN1gXJ-__7"
},
"source": [
"## Tools and Agents Setup"
],
"id": "1CDN1gXJ-__7"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "pj6Djfhd-__7"
},
"outputs": [],
"source": [
"from praisonaiagents import Agent, Task, PraisonAIAgents\n",
"from langchain_community.utilities import WikipediaAPIWrapper\n",
"from langchain_community.tools import YouTubeSearchTool\n",
"\n",
"wiki_agent = Agent(\n",
" name=\"WikiAgent\",\n",
" role=\"Research Assistant\",\n",
" goal=\"Search Wikipedia for accurate information\",\n",
" backstory=\"I am an AI assistant specialized in Wikipedia research\",\n",
" tools=[WikipediaAPIWrapper],\n",
" self_reflect=False\n",
")\n",
"\n",
"youtube_agent = Agent(\n",
" name=\"SearchAgent\",\n",
" role=\"Research Assistant\",\n",
" goal=\"Search for information from YouTube\",\n",
" backstory=\"I am an AI assistant that can search YouTube for relevant videos.\",\n",
" tools=[YouTubeSearchTool],\n",
" self_reflect=False\n",
")"
],
"id": "pj6Djfhd-__7"
},
{
"cell_type": "markdown",
"metadata": {
"id": "X5YJSOPO-__8"
},
"source": [
"## Prompt and Tasks"
],
"id": "X5YJSOPO-__8"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dC2fi8ZF-__9"
},
"outputs": [],
"source": [
"task1 = Task(\n",
" name=\"wiki_task\",\n",
" description=\"Research 'Artificial Intelligence' on Wikipedia\",\n",
" expected_output=\"Comprehensive information from Wikipedia articles\",\n",
" agent=wiki_agent\n",
")\n",
"\n",
"task2 = Task(\n",
" name=\"search_task\",\n",
" description=\"Search for information about 'AI advancements' on YouTube\",\n",
" expected_output=\"Relevant information from YouTube videos\",\n",
" agent=youtube_agent\n",
")"
],
"id": "dC2fi8ZF-__9"
},
{
"cell_type": "markdown",
"metadata": {
"id": "YMFbVVTF-__-"
},
"source": [
"## Main Agent Execution"
],
"id": "YMFbVVTF-__-"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "80jMkenz-__-"
},
"outputs": [],
"source": [
"agents = PraisonAIAgents(\n",
" agents=[wiki_agent, youtube_agent],\n",
" tasks=[task1, task2],\n",
" verbose=True\n",
")\n",
"agents.start()"
],
"id": "80jMkenz-__-"
},
{
"cell_type": "markdown",
"metadata": {
"id": "O13iiPNE-__-"
},
"source": [
"# LangChain Agent Outputs"
],
"id": "O13iiPNE-__-"
},
{
"cell_type": "markdown",
"metadata": {
"id": "6bL4CxKD-___"
},
"source": [
"## 1. Wikipedia Agent Output\n",
"\n",
"### Artificial Intelligence (AI)\n",
"\n",
"Artificial intelligence (AI) refers to the capability of computational systems to perform tasks typically associated with human intelligence, such as learning, reasoning, problem-solving, perception, and decision-making. It is a field of research in computer science focused on developing methods and software that enable machines to perceive their environment and use learning and intelligence to take actions that maximize their chances of achieving defined goals.\n",
"\n",
"**Applications of AI:**\n",
"- Advanced web search engines (e.g., Google Search)\n",
"- Recommendation systems (e.g., YouTube, Amazon, Netflix)\n",
"- Virtual assistants (e.g., Google Assistant, Siri, Alexa)\n",
"- Autonomous vehicles (e.g., Waymo)\n",
"- Generative and creative tools (e.g., ChatGPT, AI art)\n",
"- Superhuman play and analysis in strategy games (e.g., chess, Go)\n",
"\n",
"**Subfields and Techniques:**\n",
"AI research is centered around goals such as learning, reasoning, knowledge representation, planning, natural language processing, perception, and robotics support. Techniques include search and mathematical optimization, formal logic, artificial neural networks, and methods based on statistics, operations research, and economics. AI also draws from psychology, linguistics, philosophy, neuroscience, and other fields.\n",
"\n",
"**History and Development:**\n",
"AI was founded as an academic discipline in 1956. The field has experienced cycles of optimism and disappointment, known as AI winters. Interest and funding increased significantly after 2012 with the use of graphics processing units to accelerate neural networks and the success of deep learning. The AI boom in the 2020s was marked by advanced generative AI, raising ethical concerns and discussions about regulatory policies.\n",
"\n",
"**Artificial General Intelligence (AGI):**\n",
"AGI, or human-level intelligence AI, would match or surpass human capabilities across virtually all cognitive tasks. Some researchers believe that current large language models show early signs of AGI-level capability, while others argue that genuine AGI has not yet been achieved. AGI is distinct from artificial superintelligence (ASI), which would outperform human abilities across every domain. Unlike artificial narrow intelligence (ANI), AGI can generalize knowledge, transfer skills between domains, and solve novel problems without task-specific reprogramming.\n",
"\n",
"Creating AGI is a major goal for companies like OpenAI, Google DeepMind, and Meta, aiming to develop AI that can complete virtually any cognitive task as well as a human."
],
"id": "6bL4CxKD-___"
},
{
"cell_type": "markdown",
"metadata": {
"id": "lS0gvGjK-___"
},
"source": [
"## 2. Web Search Agent Output\n",
"\n",
"### Latest AI Developments\n",
"\n",
"New Technologies in AI: Chatbots and Virtual Assistants are personalizing user experiences by analyzing user data and behavior, recommending products, content, and services, which increases engagement and sales."
],
"id": "lS0gvGjK-___"
},
{
"cell_type": "markdown",
"metadata": {
"id": "yJqv-2Vf-___"
},
"source": [
"## 3. Agent Interaction Details\n",
"\n",
"**Wikipedia Agent Info:** \n",
"👤 Agent: WikiAgent \n",
"Role: Research Assistant \n",
"Tools: WikipediaAPIWrapper\n",
"\n",
"**Web Search Agent Info:** \n",
"👤 Agent: SearchAgent \n",
"Role: Research Assistant \n",
"Tools: YouTubeSearchTool\n",
"\n",
"**Performance Metrics:**\n",
"- Wikipedia Agent Response Time: 15.4 seconds\n",
"- Web Search Agent Response Time: 5.7 seconds"
],
"id": "yJqv-2Vf-___"
},
{
"cell_type": "markdown",
"metadata": {
"id": "Yv5abx7e-___"
},
"source": [
"## LangChain Tools with Wrappers"
],
"id": "Yv5abx7e-___"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "3UywakPF-___"
},
"outputs": [],
"source": [
"!pip install langchain-community google-search-results"
],
"id": "3UywakPF-___"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "otWS3SV0_AAA"
},
"outputs": [],
"source": [
"os.environ['SERPAPI_API_KEY'] = 'your_serpapi_key_here'"
],
"id": "otWS3SV0_AAA"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "TE46xk8A_AAB"
},
"outputs": [],
"source": [
"from langchain_community.utilities import SerpAPIWrapper\n",
"\n",
"data_agent = Agent(\n",
" instructions=\"Search about AI job trends in 2025\",\n",
" tools=[SerpAPIWrapper]\n",
")\n",
"editor_agent = Agent(instructions=\"Write a blog article\")\n",
"\n",
"agents = PraisonAIAgents(agents=[data_agent, editor_agent])\n",
"agents.start()"
],
"id": "TE46xk8A_AAB"
},
{
"cell_type": "markdown",
"metadata": {
"id": "2BqJBqaU_AAB"
},
"source": [
"## Example Without Wrapper (Brave Search)"
],
"id": "2BqJBqaU_AAB"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "MxGyDSqY_AAC"
},
"outputs": [],
"source": [
"!pip install langchain-community"
],
"id": "MxGyDSqY_AAC"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ZoD_erO__AAC"
},
"outputs": [],
"source": [
"os.environ['BRAVE_SEARCH_API'] = 'your_brave_api_key_here'"
],
"id": "ZoD_erO__AAC"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "-TQfJWzw_AAC"
},
"outputs": [],
"source": [
"from langchain_community.tools import BraveSearch\n",
"\n",
"def search_brave(query: str):\n",
" api_key = os.environ['BRAVE_SEARCH_API']\n",
" tool = BraveSearch.from_api_key(api_key=api_key, search_kwargs={\"count\": 3})\n",
" return tool.run(query)\n",
"\n",
"data_agent = Agent(instructions=\"Search about AI job trends in 2025\", tools=[search_brave])\n",
"editor_agent = Agent(instructions=\"Write a blog article\")\n",
"\n",
"agents = PraisonAIAgents(agents=[data_agent, editor_agent])\n",
"agents.start()"
],
"id": "-TQfJWzw_AAC"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"colab": {
"provenance": []
}
},
"nbformat": 4,
"nbformat_minor": 5
}
File diff suppressed because it is too large. Load diff
@@ -7,7 +7,7 @@
},
"source": [
"# Article Generation Agents\n",
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/notebooks/article_generation_agents.ipynb)"
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/examples/cookbooks/yaml/article_generation_agents.ipynb)"
]
},
{
@@ -609,4 +609,4 @@
},
"nbformat": 4,
"nbformat_minor": 0
}
}
@@ -51,7 +51,7 @@
},
"outputs": [],
"source": [
"from crawl4ai import WebCrawler\n",
"from crawl4ai import AsyncWebCrawler\n",
"from crawl4ai.extraction_strategy import LLMExtractionStrategy\n",
"from pydantic import BaseModel, Field\n",
"from praisonai_tools import BaseTool\n",
@@ -66,7 +66,7 @@
" description: str = \"Extracts model fees for input and output tokens from the given pricing page.\"\n",
"\n",
" def _run(self, url: str):\n",
" crawler = WebCrawler()\n",
" crawler = AsyncWebCrawler()\n",
" crawler.warmup()\n",
"\n",
" result = crawler.run(\n",
@@ -196,8 +196,8 @@
"output_type": "stream",
"text": [
"[LOG] 🚀 Initializing LocalSeleniumCrawlerStrategy\n",
"[LOG] 🌤️ Warming up the WebCrawler\n",
"[LOG] 🌞 WebCrawler is ready to crawl\n",
"[LOG] 🌤️ Warming up the AsyncWebCrawler\n",
"[LOG] 🌞 AsyncWebCrawler is ready to crawl\n",
"[LOG] 🚀 Crawling done for https://openai.com/api/pricing/, success: True, time taken: 0.86 seconds\n",
"[LOG] 🚀 Content extracted for https://openai.com/api/pricing/, success: True, time taken: 0.01 seconds\n",
"[LOG] 🔥 Extracting semantic blocks for https://openai.com/api/pricing/, Strategy: LLMExtractionStrategy\n",
@@ -245,8 +245,8 @@
"output_type": "stream",
"text": [
"[LOG] 🚀 Initializing LocalSeleniumCrawlerStrategy\n",
"[LOG] 🌤️ Warming up the WebCrawler\n",
"[LOG] 🌞 WebCrawler is ready to crawl\n",
"[LOG] 🌤️ Warming up the AsyncWebCrawler\n",
"[LOG] 🌞 AsyncWebCrawler is ready to crawl\n",
"[LOG] 🚀 Crawling done for https://www.anthropic.com/pricing, success: True, time taken: 2.93 seconds\n",
"[LOG] 🚀 Content extracted for https://www.anthropic.com/pricing, success: True, time taken: 0.06 seconds\n",
"[LOG] 🔥 Extracting semantic blocks for https://www.anthropic.com/pricing, Strategy: LLMExtractionStrategy\n",
@@ -323,8 +323,8 @@
"output_type": "stream",
"text": [
"[LOG] 🚀 Initializing LocalSeleniumCrawlerStrategy\n",
"[LOG] 🌤️ Warming up the WebCrawler\n",
"[LOG] 🌞 WebCrawler is ready to crawl\n",
"[LOG] 🌤️ Warming up the AsyncWebCrawler\n",
"[LOG] 🌞 AsyncWebCrawler is ready to crawl\n",
"[LOG] 🚀 Crawling done for https://cohere.com/pricing, success: True, time taken: 6.34 seconds\n",
"[LOG] 🚀 Content extracted for https://cohere.com/pricing, success: True, time taken: 0.14 seconds\n",
"[LOG] 🔥 Extracting semantic blocks for https://cohere.com/pricing, Strategy: LLMExtractionStrategy\n",
@@ -0,0 +1,640 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "1",
"metadata": {},
"source": [
"# Secondary Market Research Agents\n",
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/notebooks/secondary_market_research_agents.ipynb)\n",
"\n",
"This notebook demonstrates how to create a comprehensive secondary market research system using PraisonAI agents. The system generates detailed reports with customizable parameters for company, geography, and industry analysis."
]
},
{
"cell_type": "markdown",
"id": "2",
"metadata": {},
"source": [
"## Features\n",
"\n",
"- **Multi-agent Research System**: Specialized agents for different research areas\n",
"- **Customizable Parameters**: Company, geography, industry, and research sections\n",
"- **Comprehensive Reports**: Market overview, competitive analysis, financial performance, growth opportunities, and risk assessment\n",
"- **FastAPI Integration**: Ready for production deployment\n",
"- **Structured Output**: Professional report format suitable for business decision-making"
]
},
{
"cell_type": "markdown",
"id": "3",
"metadata": {},
"source": [
"## Installation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"# Install required dependencies\n",
"%pip install praisonai[crewai] > /dev/null\n",
"%pip install fastapi uvicorn > /dev/null\n",
"%pip install requests > /dev/null"
]
},
{
"cell_type": "markdown",
"id": "5",
"metadata": {},
"source": [
"## Setup API Key"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from google.colab import userdata\n",
"\n",
"# Set up OpenAI API key (add to Google Colab secrets)\n",
"os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY') or \"ENTER OPENAI_API_KEY HERE\"\n",
"os.environ[\"OPENAI_MODEL_NAME\"] = \"gpt-4o-mini\""
]
},
{
"cell_type": "markdown",
"id": "7",
"metadata": {},
"source": [
"## Method 1: Using YAML Configuration"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8",
"metadata": {},
"outputs": [],
"source": [
"# YAML configuration for market research agents\n",
"agent_yaml = \"\"\"\n",
"framework: \"crewai\"\n",
"topic: \"Secondary Market Research Analysis\"\n",
"\n",
"# Configuration variables\n",
"variables:\n",
" company: \"Tesla\"\n",
" geography: \"North America\" \n",
" industry: \"Electric Vehicles\"\n",
"\n",
"roles:\n",
" market_overview_specialist:\n",
" role: \"Market Overview Specialist\" \n",
" backstory: |\n",
" You are a seasoned market research analyst with 10+ years of experience \n",
" in analyzing market dynamics, trends, and growth patterns.\n",
" goal: |\n",
" Analyze the {industry} market in {geography} to provide comprehensive \n",
" market overview including size, trends, and growth drivers.\n",
" verbose: true\n",
" allow_delegation: false\n",
" tools:\n",
" - \"InternetSearchTool\"\n",
" tasks:\n",
" market_overview_research:\n",
" description: |\n",
" Conduct comprehensive market overview research for {industry} in {geography}:\n",
" 1. Market size and valuation analysis\n",
" 2. Key growth trends and patterns\n",
" 3. Primary market drivers\n",
" 4. Market segmentation\n",
" expected_output: |\n",
" A comprehensive market overview report with quantitative data and insights.\n",
"\n",
" competitive_intelligence_analyst:\n",
" role: \"Competitive Intelligence Analyst\"\n",
" backstory: |\n",
" You are a competitive intelligence expert specializing in analyzing \n",
" competitive landscapes and market positioning.\n",
" goal: |\n",
" Analyze {company}'s competitive landscape in the {industry} market.\n",
" verbose: true\n",
" allow_delegation: false\n",
" tools:\n",
" - \"InternetSearchTool\"\n",
" tasks:\n",
" competitive_analysis:\n",
" description: |\n",
" Conduct detailed competitive analysis for {company}:\n",
" 1. Identify top 5-7 competitors\n",
" 2. Market share analysis\n",
" 3. Competitive positioning\n",
" 4. Strengths and weaknesses\n",
" expected_output: |\n",
" A comprehensive competitive intelligence report.\n",
"\n",
" financial_performance_analyst:\n",
" role: \"Financial Performance Analyst\"\n",
" backstory: |\n",
" You are a financial analyst with expertise in corporate finance \n",
" and industry benchmarking.\n",
" goal: |\n",
" Analyze {company}'s financial performance and industry benchmarks.\n",
" verbose: true\n",
" allow_delegation: false\n",
" tools:\n",
" - \"InternetSearchTool\"\n",
" tasks:\n",
" financial_analysis:\n",
" description: |\n",
" Conduct financial performance analysis for {company}:\n",
" 1. Revenue trends and growth\n",
" 2. Profitability metrics\n",
" 3. Financial ratios\n",
" 4. Industry benchmarking\n",
" expected_output: |\n",
" A detailed financial performance analysis report.\n",
"\n",
" research_report_synthesizer:\n",
" role: \"Research Report Synthesizer\"\n",
" backstory: |\n",
" You are an expert business report writer specializing in \n",
" synthesizing complex research into actionable insights.\n",
" goal: |\n",
" Create a comprehensive secondary market research report for {company}.\n",
" verbose: true\n",
" allow_delegation: false\n",
" tasks:\n",
" report_synthesis:\n",
" description: |\n",
" Synthesize all research findings into a comprehensive report:\n",
" 1. Executive Summary\n",
" 2. Market Overview\n",
" 3. Competitive Analysis\n",
" 4. Financial Performance\n",
" 5. Conclusions and Recommendations\n",
" expected_output: |\n",
" A complete, professionally formatted secondary market research report.\n",
" context:\n",
" - \"market_overview_research\"\n",
" - \"competitive_analysis\" \n",
" - \"financial_analysis\"\n",
"\n",
"dependencies:\n",
" - market_overview_specialist\n",
" - competitive_intelligence_analyst\n",
" - financial_performance_analyst\n",
" - research_report_synthesizer\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {},
"outputs": [],
"source": [
"# Import required libraries\n",
"from praisonai import PraisonAI\n",
"from praisonai_tools import BaseTool\n",
"from duckduckgo_search import DDGS\n",
"import requests\n",
"from bs4 import BeautifulSoup\n",
"\n",
"# Create internet search tool\n",
"class InternetSearchTool(BaseTool):\n",
" name: str = \"InternetSearchTool\"\n",
" description: str = \"Search Internet for relevant information\"\n",
"\n",
" def _run(self, query: str):\n",
" ddgs = DDGS()\n",
" results = ddgs.text(keywords=query, region='wt-wt', safesearch='moderate', max_results=5)\n",
" return results\n",
"\n",
"# Create PraisonAI instance\n",
"praisonai = PraisonAI(agent_yaml=agent_yaml, tools=[InternetSearchTool])\n",
"\n",
"# Run the market research\n",
"print(\"🔍 Starting Secondary Market Research for Tesla...\")\n",
"result = praisonai.run()\n",
"\n",
"print(\"\\n📋 Market Research Complete!\")\n",
"print(\"=\" * 60)\n",
"print(result)"
]
},
{
"cell_type": "markdown",
"id": "10",
"metadata": {},
"source": [
"## Method 2: Using Python API with Custom Parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"metadata": {},
"outputs": [],
"source": [
"# Import the market research system\n",
"from praisonaiagents import Agent, Task, PraisonAIAgents, Tools\n",
"from typing import Dict, List, Any\n",
"import asyncio\n",
"from datetime import datetime\n",
"\n",
"class MarketResearchConfig:\n",
" \"\"\"Configuration for market research parameters\"\"\"\n",
" \n",
" def __init__(self, company: str, geography: str, industry: str, sections: List[str]):\n",
" self.company = company\n",
" self.geography = geography\n",
" self.industry = industry\n",
" self.sections = sections\n",
" self.timestamp = datetime.now().isoformat()\n",
"\n",
"def create_market_research_agents(config: MarketResearchConfig):\n",
" \"\"\"Create specialized market research agents\"\"\"\n",
" \n",
" agents = {}\n",
" \n",
" if \"market_overview\" in config.sections:\n",
" agents[\"market_overview\"] = Agent(\n",
" name=\"Market Overview Specialist\",\n",
" role=\"Market Analysis Expert\",\n",
" goal=f\"Analyze the {config.industry} market in {config.geography}\",\n",
" instructions=f\"\"\"\n",
" Analyze market size, trends, growth drivers for {config.industry} in {config.geography}.\n",
" Provide quantitative data and cite reliable sources.\n",
" \"\"\",\n",
" tools=[Tools.internet_search],\n",
" verbose=True\n",
" )\n",
" \n",
" if \"competitive_analysis\" in config.sections:\n",
" agents[\"competitive\"] = Agent(\n",
" name=\"Competitive Intelligence Analyst\",\n",
" role=\"Competition Research Expert\", \n",
" goal=f\"Analyze {config.company}'s competitive landscape\",\n",
" instructions=f\"\"\"\n",
" Analyze {config.company}'s competitive position in {config.industry}.\n",
" Identify key competitors, market share, and competitive advantages.\n",
" \"\"\",\n",
" tools=[Tools.internet_search],\n",
" verbose=True\n",
" )\n",
" \n",
" if \"financial_performance\" in config.sections:\n",
" agents[\"financial\"] = Agent(\n",
" name=\"Financial Performance Analyst\", \n",
" role=\"Financial Research Expert\",\n",
" goal=f\"Analyze {config.company}'s financial performance\",\n",
" instructions=f\"\"\"\n",
" Research {config.company}'s financial performance, revenue trends,\n",
" profitability, and compare with industry benchmarks.\n",
" \"\"\",\n",
" tools=[Tools.internet_search],\n",
" verbose=True\n",
" )\n",
" \n",
" if \"growth_opportunities\" in config.sections:\n",
" agents[\"growth\"] = Agent(\n",
" name=\"Growth Opportunities Researcher\",\n",
" role=\"Strategic Growth Expert\",\n",
" goal=f\"Identify growth opportunities for {config.company}\",\n",
" instructions=f\"\"\"\n",
" Identify emerging opportunities, market gaps, expansion possibilities\n",
" for {config.company} in {config.industry}.\n",
" \"\"\",\n",
" tools=[Tools.internet_search],\n",
" verbose=True\n",
" )\n",
" \n",
" # Always include synthesizer\n",
" agents[\"synthesizer\"] = Agent(\n",
" name=\"Research Report Synthesizer\",\n",
" role=\"Report Writing Expert\",\n",
" goal=\"Synthesize research into comprehensive report\",\n",
" instructions=f\"\"\"\n",
" Create a professional secondary market research report for {config.company} \n",
" in {config.industry} with clear structure and actionable recommendations.\n",
" \"\"\",\n",
" verbose=True\n",
" )\n",
" \n",
" return agents\n",
"\n",
"def create_research_tasks(agents: Dict[str, Agent], config: MarketResearchConfig):\n",
" \"\"\"Create research tasks for the agents\"\"\"\n",
" \n",
" tasks = []\n",
" \n",
" if \"market_overview\" in config.sections and \"market_overview\" in agents:\n",
" market_task = Task(\n",
" name=\"market_overview_research\",\n",
" description=f\"Research {config.industry} market overview in {config.geography}\",\n",
" expected_output=\"Market overview analysis with key metrics and trends\",\n",
" agent=agents[\"market_overview\"]\n",
" )\n",
" tasks.append(market_task)\n",
" \n",
" if \"competitive_analysis\" in config.sections and \"competitive\" in agents:\n",
" competitive_task = Task(\n",
" name=\"competitive_analysis\",\n",
" description=f\"Analyze competitive landscape for {config.company}\",\n",
" expected_output=\"Competitive analysis with key competitors and positioning\",\n",
" agent=agents[\"competitive\"]\n",
" )\n",
" tasks.append(competitive_task)\n",
" \n",
" if \"financial_performance\" in config.sections and \"financial\" in agents:\n",
" financial_task = Task(\n",
" name=\"financial_analysis\",\n",
" description=f\"Analyze {config.company}'s financial performance\",\n",
" expected_output=\"Financial performance analysis with key metrics\",\n",
" agent=agents[\"financial\"]\n",
" )\n",
" tasks.append(financial_task)\n",
" \n",
" if \"growth_opportunities\" in config.sections and \"growth\" in agents:\n",
" growth_task = Task(\n",
" name=\"growth_opportunities\",\n",
" description=f\"Identify growth opportunities for {config.company}\",\n",
" expected_output=\"Growth opportunities with strategic recommendations\",\n",
" agent=agents[\"growth\"]\n",
" )\n",
" tasks.append(growth_task)\n",
" \n",
" # Synthesis task\n",
" synthesis_task = Task(\n",
" name=\"synthesis\",\n",
" description=\"Synthesize all findings into comprehensive report\",\n",
" expected_output=\"Complete secondary market research report\",\n",
" agent=agents[\"synthesizer\"],\n",
" context=tasks\n",
" )\n",
" tasks.append(synthesis_task)\n",
" \n",
" return tasks\n",
"\n",
"async def run_market_research(config: MarketResearchConfig):\n",
" \"\"\"Run the complete market research workflow\"\"\"\n",
" \n",
" print(f\"🔍 Starting Market Research for {config.company}\")\n",
" print(f\"📍 Geography: {config.geography}\")\n",
" print(f\"🏭 Industry: {config.industry}\")\n",
" print(f\"📊 Sections: {', '.join(config.sections)}\")\n",
" print(\"=\" * 60)\n",
" \n",
" # Create agents and tasks\n",
" agents = create_market_research_agents(config)\n",
" tasks = create_research_tasks(agents, config)\n",
" \n",
" # Create workflow\n",
" workflow = PraisonAIAgents(\n",
" agents=list(agents.values()),\n",
" tasks=tasks,\n",
" process=\"workflow\",\n",
" verbose=True\n",
" )\n",
" \n",
" # Execute research\n",
" results = await workflow.astart()\n",
" \n",
" return results\n",
"\n",
"# Example with custom parameters\n",
"custom_config = MarketResearchConfig(\n",
" company=\"Apple\",\n",
" geography=\"Global\", \n",
" industry=\"Consumer Electronics\",\n",
" sections=[\"market_overview\", \"competitive_analysis\", \"financial_performance\"]\n",
")\n",
"\n",
"# Run the research\n",
"print(\"🚀 Running Custom Market Research...\")\n",
"custom_results = await run_market_research(custom_config)\n",
"\n",
"print(\"\\n📋 Research Complete!\")\n",
"print(\"=\" * 60)"
]
},
{
"cell_type": "markdown",
"id": "12",
"metadata": {},
"source": [
"## Method 3: FastAPI Integration Example"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13",
"metadata": {},
"outputs": [],
"source": [
"# Example of using the FastAPI endpoint\n",
"import requests\n",
"import json\n",
"import time\n",
"\n",
"# Note: This requires the FastAPI server to be running\n",
"# Run: uvicorn secondary-market-research-api:app --reload --port 8000\n",
"\n",
"API_BASE_URL = \"http://localhost:8000\" # Change if running elsewhere\n",
"\n",
"def generate_report_via_api(company, geography, industry, sections):\n",
" \"\"\"Generate market research report via API\"\"\"\n",
" \n",
" # Prepare request\n",
" request_data = {\n",
" \"company\": company,\n",
" \"geography\": geography,\n",
" \"industry\": industry,\n",
" \"sections\": sections,\n",
" \"format\": \"json\"\n",
" }\n",
" \n",
" print(f\"📤 Submitting research request for {company}...\")\n",
" \n",
" # Submit research request\n",
" response = requests.post(f\"{API_BASE_URL}/research/generate\", json=request_data)\n",
" \n",
" if response.status_code == 200:\n",
" job_data = response.json()\n",
" job_id = job_data[\"job_id\"]\n",
" print(f\"✅ Job submitted successfully. Job ID: {job_id}\")\n",
" \n",
" # Poll for completion\n",
" print(\"⏳ Waiting for completion...\")\n",
" while True:\n",
" status_response = requests.get(f\"{API_BASE_URL}/research/status/{job_id}\")\n",
" if status_response.status_code == 200:\n",
" status_data = status_response.json()\n",
" print(f\"📊 Progress: {status_data['progress']}% - {status_data['message']}\")\n",
" \n",
" if status_data[\"status\"] == \"completed\":\n",
" print(\"🎉 Research completed!\")\n",
" \n",
" # Download report\n",
" report_response = requests.get(f\"{API_BASE_URL}/research/reports/{job_id}\")\n",
" if report_response.status_code == 200:\n",
" return report_response.json()\n",
" else:\n",
" print(\"❌ Failed to download report\")\n",
" return None\n",
" \n",
" elif status_data[\"status\"] == \"failed\":\n",
" print(f\"❌ Research failed: {status_data.get('error', 'Unknown error')}\")\n",
" return None\n",
" \n",
" time.sleep(5) # Wait 5 seconds before checking again\n",
" else:\n",
" print(\"❌ Failed to check status\")\n",
" return None\n",
" else:\n",
" print(f\"❌ Failed to submit request: {response.status_code}\")\n",
" return None\n",
"\n",
"# Example API usage (uncomment to run with API server)\n",
"# report = generate_report_via_api(\n",
"# company=\"Microsoft\",\n",
"# geography=\"Global\",\n",
"# industry=\"Cloud Computing\",\n",
"# sections=[\"market_overview\", \"competitive_analysis\"]\n",
"# )\n",
"# \n",
"# if report:\n",
"# print(\"\\n📋 Generated Report:\")\n",
"# print(json.dumps(report, indent=2)[:1000] + \"...\")\n",
"\n",
"print(\"💡 API integration example ready. Start the FastAPI server to test.\")"
]
},
{
"cell_type": "markdown",
"id": "14",
"metadata": {},
"source": [
"## Customization Examples"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15",
"metadata": {},
"outputs": [],
"source": [
"# Example configurations for different industries and regions\n",
"\n",
"research_examples = {\n",
" \"tech_startup\": {\n",
" \"company\": \"OpenAI\",\n",
" \"geography\": \"United States\",\n",
" \"industry\": \"Artificial Intelligence\",\n",
" \"sections\": [\"market_overview\", \"competitive_analysis\", \"growth_opportunities\"]\n",
" },\n",
" \"automotive\": {\n",
" \"company\": \"BMW\",\n",
" \"geography\": \"Europe\",\n",
" \"industry\": \"Luxury Automobiles\",\n",
" \"sections\": [\"market_overview\", \"competitive_analysis\", \"financial_performance\", \"risk_assessment\"]\n",
" },\n",
" \"healthcare\": {\n",
" \"company\": \"Pfizer\",\n",
" \"geography\": \"Global\",\n",
" \"industry\": \"Pharmaceuticals\",\n",
" \"sections\": [\"market_overview\", \"competitive_analysis\", \"growth_opportunities\", \"risk_assessment\"]\n",
" },\n",
" \"fintech\": {\n",
" \"company\": \"Square\",\n",
" \"geography\": \"North America\",\n",
" \"industry\": \"Financial Technology\",\n",
" \"sections\": [\"market_overview\", \"competitive_analysis\", \"financial_performance\", \"growth_opportunities\"]\n",
" },\n",
" \"retail\": {\n",
" \"company\": \"Alibaba\",\n",
" \"geography\": \"Asia Pacific\",\n",
" \"industry\": \"E-commerce\",\n",
" \"sections\": [\"market_overview\", \"competitive_analysis\", \"financial_performance\", \"growth_opportunities\", \"risk_assessment\"]\n",
" }\n",
"}\n",
"\n",
"print(\"📚 Research Configuration Examples:\")\n",
"print(\"=\" * 50)\n",
"\n",
"for example_name, config in research_examples.items():\n",
" print(f\"\\n🔍 {example_name.upper()}:\")\n",
" print(f\" Company: {config['company']}\")\n",
" print(f\" Geography: {config['geography']}\")\n",
" print(f\" Industry: {config['industry']}\")\n",
" print(f\" Sections: {', '.join(config['sections'])}\")\n",
"\n",
"print(\"\\n💡 Use any of these configurations as templates for your research!\")"
]
},
{
"cell_type": "markdown",
"id": "16",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"This notebook demonstrates three different ways to use the Secondary Market Research Agent system:\n",
"\n",
"1. **YAML Configuration**: Easy to customize and modify research parameters\n",
"2. **Python API**: Programmatic control with custom configurations\n",
"3. **FastAPI Integration**: Production-ready REST API for web applications\n",
"\n",
"### Key Features:\n",
"- **Customizable Research Sections**: Choose from market overview, competitive analysis, financial performance, growth opportunities, and risk assessment\n",
"- **Geographic Flexibility**: Research any geographic region\n",
"- **Industry Agnostic**: Works across different industries and sectors\n",
"- **Professional Reports**: Generate business-ready research reports\n",
"- **Scalable Architecture**: Multi-agent system that can be extended\n",
"\n",
"### Use Cases:\n",
"- Market entry analysis\n",
"- Competitive intelligence\n",
"- Investment research\n",
"- Strategic planning\n",
"- Due diligence\n",
"\n",
"The system provides a comprehensive foundation for secondary market research that can be customized and extended based on specific business needs."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,369 @@
framework: "crewai"
topic: "Secondary Market Research Analysis with Customizable Parameters"
# Configuration variables for customization
variables:
company: "Tesla"
geography: "North America"
industry: "Electric Vehicles"
research_focus: "comprehensive market analysis"
roles:
market_overview_specialist:
role: "Market Overview Specialist"
backstory: |
You are a seasoned market research analyst with 10+ years of experience
in analyzing market dynamics, trends, and growth patterns across various
industries. You excel at synthesizing complex market data into clear,
actionable insights for business decision-makers.
goal: |
Analyze the {industry} market in {geography} to provide comprehensive
market overview including size, trends, growth drivers, and future projections.
verbose: true
allow_delegation: false
llm:
model: "gpt-4o-mini"
temperature: 0.3
tools:
- "InternetSearchTool"
tasks:
market_overview_research:
description: |
Conduct comprehensive market overview research for {industry} in {geography}:
1. Market Size & Valuation:
- Current market size and historical growth
- Market valuation and revenue projections
- Geographic market distribution
2. Market Trends:
- Key growth trends and patterns
- Emerging market developments
- Technology adoption rates
3. Market Drivers:
- Primary growth drivers and catalysts
- Consumer behavior shifts
- Regulatory and policy impacts
4. Market Segments:
- Key market segments and sub-sectors
- Segment performance and potential
- Customer demographics and preferences
Research should include quantitative data, statistics, and cite reliable sources.
expected_output: |
A comprehensive market overview report containing:
- Executive summary of market status
- Detailed market size and growth analysis
- Key trends and drivers identification
- Market segmentation analysis
- Future market projections and opportunities
Include relevant charts, statistics, and data sources.
competitive_intelligence_analyst:
role: "Competitive Intelligence Analyst"
backstory: |
You are a competitive intelligence expert specializing in analyzing
competitive landscapes, market positioning, and strategic intelligence.
You have deep expertise in competitor analysis, market share assessment,
and competitive strategy evaluation.
goal: |
Analyze {company}'s competitive landscape in the {industry} market
within {geography} to identify key competitors, market positioning,
and competitive dynamics.
verbose: true
allow_delegation: false
llm:
model: "gpt-4o-mini"
temperature: 0.3
tools:
- "InternetSearchTool"
tasks:
competitive_analysis:
description: |
Conduct detailed competitive analysis for {company} in {industry}:
1. Competitor Identification:
- Identify top 5-7 direct competitors
- Include emerging and indirect competitors
- Analyze competitor profiles and backgrounds
2. Market Share Analysis:
- Market share distribution among competitors
- {company}'s market position and ranking
- Market concentration and fragmentation
3. Competitive Positioning:
- Value proposition comparison
- Product/service differentiation
- Pricing strategies and positioning
4. Competitor Strengths & Weaknesses:
- SWOT analysis for key competitors
- Competitive advantages and disadvantages
- Performance comparison metrics
5. Competitive Strategies:
- Go-to-market strategies
- Innovation and R&D approaches
- Partnership and acquisition strategies
expected_output: |
A comprehensive competitive intelligence report including:
- Competitive landscape overview
- Detailed competitor profiles (top 5-7)
- Market share analysis and positioning
- Competitive strengths/weaknesses matrix
- Strategic implications and recommendations
financial_performance_analyst:
role: "Financial Performance Analyst"
backstory: |
You are a financial analyst with expertise in corporate finance,
financial statement analysis, and industry benchmarking. You specialize
in analyzing financial performance metrics, trends, and comparative
analysis within specific industry contexts.
goal: |
Analyze {company}'s financial performance, key metrics, and compare
with industry benchmarks to assess financial health and performance trends.
verbose: true
allow_delegation: false
llm:
model: "gpt-4o-mini"
temperature: 0.3
tools:
- "InternetSearchTool"
tasks:
financial_analysis:
description: |
Conduct comprehensive financial performance analysis for {company}:
1. Revenue Analysis:
- Revenue trends over last 3-5 years
- Revenue growth rates and patterns
- Revenue by segment/geography
2. Profitability Metrics:
- Gross, operating, and net profit margins
- EBITDA and EBITDA margins
- Return on assets (ROA) and equity (ROE)
3. Financial Ratios:
- Liquidity ratios (current, quick)
- Leverage ratios (debt-to-equity, interest coverage)
- Efficiency ratios (asset turnover, inventory turnover)
4. Industry Benchmarking:
- Compare key metrics with industry averages
- Position against top competitors
- Identify performance gaps and advantages
5. Financial Health Assessment:
- Cash flow analysis
- Debt levels and structure
- Financial stability indicators
expected_output: |
A detailed financial performance report containing:
- Financial performance summary and trends
- Key financial metrics and ratios analysis
- Industry benchmark comparison
- Financial strengths and areas for improvement
- Financial health assessment and outlook
growth_opportunities_researcher:
role: "Growth Opportunities Researcher"
backstory: |
You are a strategic business analyst specializing in identifying growth
opportunities, market expansion strategies, and emerging business models.
You excel at spotting trends, gaps, and untapped potential in markets
and industries.
goal: |
Identify and analyze growth opportunities for {company} in {industry}
within {geography}, including market expansion, innovation, and
strategic development possibilities.
verbose: true
allow_delegation: false
llm:
model: "gpt-4o-mini"
temperature: 0.4
tools:
- "InternetSearchTool"
tasks:
growth_opportunities_analysis:
description: |
Identify comprehensive growth opportunities for {company}:
1. Market Expansion:
- Geographic expansion opportunities
- New customer segments and demographics
- Untapped market niches
2. Product/Service Innovation:
- New product development opportunities
- Service enhancement possibilities
- Technology integration opportunities
3. Strategic Partnerships:
- Potential partnership opportunities
- Joint venture possibilities
- Acquisition targets and synergies
4. Technology Advancement:
- Emerging technology adoption
- Digital transformation opportunities
- Innovation and R&D investments
5. Market Trends Alignment:
- Emerging industry trends to leverage
- Consumer behavior shifts to capitalize on
- Regulatory changes creating opportunities
expected_output: |
A strategic growth opportunities report including:
- Prioritized growth opportunity matrix
- Market expansion analysis and recommendations
- Innovation and development opportunities
- Strategic partnership possibilities
- Implementation roadmap and timeline
risk_assessment_specialist:
role: "Risk Assessment Specialist"
backstory: |
You are a risk management expert with extensive experience in identifying,
analyzing, and assessing business risks across various industries. You
specialize in market risks, competitive threats, regulatory challenges,
and operational risk assessment.
goal: |
Assess and analyze potential risks, challenges, and threats facing
{company} in the {industry} market within {geography}.
verbose: true
allow_delegation: false
llm:
model: "gpt-4o-mini"
temperature: 0.3
tools:
- "InternetSearchTool"
tasks:
risk_assessment_analysis:
description: |
Conduct comprehensive risk assessment for {company}:
1. Competitive Risks:
- New entrant threats
- Competitor aggressive strategies
- Market share erosion risks
2. Market Risks:
- Market volatility and cyclicality
- Demand fluctuation risks
- Economic downturn impacts
3. Regulatory Risks:
- Regulatory changes and compliance
- Policy shifts and government intervention
- Environmental and safety regulations
4. Operational Risks:
- Supply chain vulnerabilities
- Technology and cybersecurity risks
- Talent and human capital risks
5. Financial Risks:
- Currency and exchange rate risks
- Interest rate and inflation impacts
- Credit and liquidity risks
6. Technology Disruption:
- Emerging technology threats
- Digital transformation challenges
- Innovation obsolescence risks
expected_output: |
A comprehensive risk assessment report including:
- Risk identification and categorization
- Risk impact and probability analysis
- Risk mitigation strategies and recommendations
- Risk monitoring and management framework
- Priority risk action plan
research_report_synthesizer:
role: "Research Report Synthesizer"
backstory: |
You are an expert business report writer and analyst with extensive
experience in synthesizing complex research findings into clear,
actionable business intelligence reports. You excel at creating
comprehensive, well-structured reports for executive decision-making.
goal: |
Synthesize all research findings into a comprehensive, professional
secondary market research report for {company} in {industry} that
provides actionable insights for business decision-making.
verbose: true
allow_delegation: false
llm:
model: "gpt-4o-mini"
temperature: 0.3
tasks:
report_synthesis:
description: |
Create a comprehensive secondary market research report synthesizing
all research findings:
Report Structure:
1. Executive Summary
- Key findings overview
- Critical insights and implications
- Primary recommendations
2. Market Overview
- Market size, trends, and dynamics
- Growth drivers and opportunities
- Market segmentation analysis
3. Competitive Landscape
- Competitive positioning analysis
- Key competitor profiles
- Market share and dynamics
4. Financial Performance
- Financial health assessment
- Performance benchmarking
- Financial trends and projections
5. Growth Opportunities
- Strategic growth vectors
- Market expansion possibilities
- Innovation and development opportunities
6. Risk Assessment
- Key risk factors and mitigation
- Challenge identification
- Risk management recommendations
7. Conclusions and Recommendations
- Strategic recommendations
- Action priorities
- Implementation considerations
Use professional formatting with clear headings, bullet points, and data visualization suggestions.
expected_output: |
A complete, professionally formatted secondary market research report containing:
- Executive summary with key insights
- Detailed analysis across all research areas
- Data-driven findings and conclusions
- Strategic recommendations and action items
- Professional formatting suitable for executive presentation
The report should be 8-12 pages in length with clear structure and actionable intelligence.
context:
- "market_overview_research"
- "competitive_analysis"
- "financial_analysis"
- "growth_opportunities_analysis"
- "risk_assessment_analysis"
dependencies:
- market_overview_specialist
- competitive_intelligence_analyst
- financial_performance_analyst
- growth_opportunities_researcher
- risk_assessment_specialist
- research_report_synthesizer
@@ -0,0 +1,528 @@
"""
Secondary Market Research FastAPI Application
This FastAPI application provides a REST API for generating customized
secondary market research reports. Users can specify company, geography,
industry, and research sections to generate tailored reports.
Features:
- RESTful API endpoints for market research
- Customizable research parameters
- Async report generation
- JSON and PDF report formats
- Real-time progress tracking
- Report history and caching
Usage:
uvicorn secondary-market-research-api:app --reload --port 8000
Then visit: http://localhost:8000/docs for API documentation
"""
from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends
from fastapi.responses import JSONResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
import asyncio
import json
import uuid
from datetime import datetime
import os
from pathlib import Path
# Import our market research system
from praisonaiagents import Agent, Task, PraisonAIAgents, Tools
# Create FastAPI app
app = FastAPI(
title="Secondary Market Research API",
description="Generate customized secondary market research reports",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc"
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Create directories for storing reports
REPORTS_DIR = Path("generated_reports")
REPORTS_DIR.mkdir(exist_ok=True)
# In-memory storage for job status (in production, use Redis or database)
job_status = {}
# Pydantic models for request/response
class MarketResearchRequest(BaseModel):
"""Request model for market research generation"""
company: str = Field(..., description="Company name to research", example="Tesla")
geography: str = Field(..., description="Geographic region", example="North America")
industry: str = Field(..., description="Industry sector", example="Electric Vehicles")
sections: Optional[List[str]] = Field(
default=["market_overview", "competitive_analysis", "financial_performance", "growth_opportunities", "risk_assessment"],
description="Research sections to include",
example=["market_overview", "competitive_analysis", "financial_performance"]
)
format: Optional[str] = Field(default="json", description="Output format: json or pdf", example="json")
email: Optional[str] = Field(None, description="Email for notification when complete", example="[email protected]")
class MarketResearchResponse(BaseModel):
"""Response model for market research request"""
job_id: str = Field(..., description="Unique job identifier")
status: str = Field(..., description="Job status: pending, running, completed, failed")
message: str = Field(..., description="Status message")
estimated_completion: Optional[str] = Field(None, description="Estimated completion time")
class JobStatusResponse(BaseModel):
"""Response model for job status check"""
job_id: str
status: str
progress: int = Field(..., description="Progress percentage (0-100)")
message: str
created_at: str
updated_at: str
result_url: Optional[str] = None
error: Optional[str] = None
class MarketResearchConfig:
"""Configuration class for market research parameters"""
def __init__(self, company: str, geography: str, industry: str, sections: List[str]):
self.company = company
self.geography = geography
self.industry = industry
self.sections = sections
self.timestamp = datetime.now().isoformat()
def create_market_research_agents(config: MarketResearchConfig):
"""Create specialized market research agents"""
agents = {}
if "market_overview" in config.sections:
agents["market_overview"] = Agent(
name="Market Overview Specialist",
role="Market Analysis Expert",
goal=f"Analyze the {config.industry} market in {config.geography}",
instructions=f"""
Analyze market size, trends, growth drivers, and overall market dynamics for
{config.industry} in {config.geography}. Provide quantitative data and cite sources.
""",
tools=[Tools.internet_search],
verbose=False
)
if "competitive_analysis" in config.sections:
agents["competitive"] = Agent(
name="Competitive Intelligence Analyst",
role="Competition Research Expert",
goal=f"Analyze {config.company}'s competitive landscape",
instructions=f"""
Analyze {config.company}'s competitive position in {config.industry} within {config.geography}.
Identify key competitors, market share, and competitive advantages.
""",
tools=[Tools.internet_search],
verbose=False
)
if "financial_performance" in config.sections:
agents["financial"] = Agent(
name="Financial Performance Analyst",
role="Financial Research Expert",
goal=f"Analyze {config.company}'s financial performance",
instructions=f"""
Research {config.company}'s financial performance, revenue trends, profitability,
and compare with industry benchmarks.
""",
tools=[Tools.internet_search],
verbose=False
)
if "growth_opportunities" in config.sections:
agents["growth"] = Agent(
name="Growth Opportunities Researcher",
role="Strategic Growth Expert",
goal=f"Identify growth opportunities for {config.company}",
instructions=f"""
Identify emerging opportunities, market gaps, expansion possibilities for {config.company}
in {config.industry} within {config.geography}.
""",
tools=[Tools.internet_search],
verbose=False
)
if "risk_assessment" in config.sections:
agents["risk"] = Agent(
name="Risk Assessment Specialist",
role="Risk Analysis Expert",
goal=f"Assess risks for {config.company}",
instructions=f"""
Identify and analyze potential risks, challenges, and threats facing {config.company}
in {config.geography}. Consider regulatory, competitive, and market risks.
""",
tools=[Tools.internet_search],
verbose=False
)
# Always include synthesizer
agents["synthesizer"] = Agent(
name="Research Report Synthesizer",
role="Report Writing Expert",
goal="Synthesize research into comprehensive report",
instructions=f"""
Create a professional secondary market research report for {config.company}
in {config.industry} with clear structure, insights, and recommendations.
""",
verbose=False
)
return agents
def create_research_tasks(agents: Dict[str, Agent], config: MarketResearchConfig):
"""Create research tasks for the agents"""
tasks = []
# Create tasks based on available sections
if "market_overview" in config.sections and "market_overview" in agents:
market_task = Task(
name="market_overview_research",
description=f"Research {config.industry} market overview in {config.geography}",
expected_output="Market overview analysis with key metrics and trends",
agent=agents["market_overview"]
)
tasks.append(market_task)
if "competitive_analysis" in config.sections and "competitive" in agents:
competitive_task = Task(
name="competitive_analysis",
description=f"Analyze competitive landscape for {config.company}",
expected_output="Competitive analysis with key competitors and positioning",
agent=agents["competitive"]
)
tasks.append(competitive_task)
if "financial_performance" in config.sections and "financial" in agents:
financial_task = Task(
name="financial_analysis",
description=f"Analyze {config.company}'s financial performance",
expected_output="Financial performance analysis with key metrics",
agent=agents["financial"]
)
tasks.append(financial_task)
if "growth_opportunities" in config.sections and "growth" in agents:
growth_task = Task(
name="growth_opportunities",
description=f"Identify growth opportunities for {config.company}",
expected_output="Growth opportunities analysis with strategic recommendations",
agent=agents["growth"]
)
tasks.append(growth_task)
if "risk_assessment" in config.sections and "risk" in agents:
risk_task = Task(
name="risk_assessment",
description=f"Assess risks for {config.company}",
expected_output="Risk assessment with mitigation strategies",
agent=agents["risk"]
)
tasks.append(risk_task)
# Synthesis task
synthesis_task = Task(
name="synthesis",
description=f"Synthesize all findings into comprehensive report",
expected_output="Complete secondary market research report",
agent=agents["synthesizer"],
context=tasks
)
tasks.append(synthesis_task)
return tasks
async def generate_market_research_report(job_id: str, config: MarketResearchConfig):
"""Background task to generate market research report"""
try:
# Update job status
job_status[job_id]["status"] = "running"
job_status[job_id]["progress"] = 10
job_status[job_id]["message"] = "Creating research agents..."
job_status[job_id]["updated_at"] = datetime.now().isoformat()
# Create agents and tasks
agents = create_market_research_agents(config)
tasks = create_research_tasks(agents, config)
job_status[job_id]["progress"] = 20
job_status[job_id]["message"] = "Setting up research workflow..."
# Create workflow
workflow = PraisonAIAgents(
agents=list(agents.values()),
tasks=tasks,
process="workflow",
verbose=False
)
job_status[job_id]["progress"] = 30
job_status[job_id]["message"] = "Executing research workflow..."
# Execute research
results = await workflow.astart()
job_status[job_id]["progress"] = 90
job_status[job_id]["message"] = "Generating final report..."
# Prepare final report
report_data = {
"metadata": {
"job_id": job_id,
"company": config.company,
"geography": config.geography,
"industry": config.industry,
"sections": config.sections,
"generated_at": datetime.now().isoformat()
},
"executive_summary": "Executive summary would be generated here...",
"research_findings": {}
}
# Extract results from each task
if "task_results" in results:
for task_name, result in results["task_results"].items():
if result:
report_data["research_findings"][task_name] = {
"content": result.raw,
"agent": result.agent if hasattr(result, 'agent') else "Unknown"
}
# Save report to file
report_filename = f"market_research_{job_id}.json"
report_path = REPORTS_DIR / report_filename
with open(report_path, "w") as f:
json.dump(report_data, f, indent=2, default=str)
# Update job status to completed
job_status[job_id]["status"] = "completed"
job_status[job_id]["progress"] = 100
job_status[job_id]["message"] = "Report generation completed successfully"
job_status[job_id]["result_url"] = f"/reports/{job_id}"
job_status[job_id]["updated_at"] = datetime.now().isoformat()
except Exception as e:
# Update job status to failed
job_status[job_id]["status"] = "failed"
job_status[job_id]["error"] = str(e)
job_status[job_id]["message"] = f"Report generation failed: {str(e)}"
job_status[job_id]["updated_at"] = datetime.now().isoformat()
# API Endpoints
@app.get("/", summary="API Health Check")
async def root():
"""Health check endpoint"""
return {
"service": "Secondary Market Research API",
"status": "active",
"version": "1.0.0",
"timestamp": datetime.now().isoformat()
}
@app.post("/research/generate", response_model=MarketResearchResponse, summary="Generate Market Research Report")
async def generate_research(
request: MarketResearchRequest,
background_tasks: BackgroundTasks
):
"""
Generate a secondary market research report with customizable parameters.
This endpoint starts an async job to generate a comprehensive market research
report based on the provided parameters. The report generation runs in the
background and can be tracked using the returned job_id.
"""
# Generate unique job ID
job_id = str(uuid.uuid4())
# Initialize job status
job_status[job_id] = {
"job_id": job_id,
"status": "pending",
"progress": 0,
"message": "Report generation queued",
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat(),
"config": request.dict()
}
# Create config object
config = MarketResearchConfig(
company=request.company,
geography=request.geography,
industry=request.industry,
sections=request.sections
)
# Start background task
background_tasks.add_task(generate_market_research_report, job_id, config)
return MarketResearchResponse(
job_id=job_id,
status="pending",
message="Market research report generation started",
estimated_completion=datetime.now().isoformat()
)
@app.get("/research/status/{job_id}", response_model=JobStatusResponse, summary="Check Job Status")
async def check_job_status(job_id: str):
"""
Check the status of a market research report generation job.
Returns the current status, progress percentage, and any results or errors.
"""
if job_id not in job_status:
raise HTTPException(status_code=404, detail="Job ID not found")
return JobStatusResponse(**job_status[job_id])
@app.get("/research/reports/{job_id}", summary="Download Research Report")
async def download_report(job_id: str):
"""
Download the generated market research report.
Returns the complete report in JSON format once generation is complete.
"""
if job_id not in job_status:
raise HTTPException(status_code=404, detail="Job ID not found")
if job_status[job_id]["status"] != "completed":
raise HTTPException(status_code=400, detail="Report not ready. Check job status first.")
report_path = REPORTS_DIR / f"market_research_{job_id}.json"
if not report_path.exists():
raise HTTPException(status_code=404, detail="Report file not found")
return FileResponse(
path=report_path,
media_type="application/json",
filename=f"market_research_{job_id}.json"
)
@app.get("/research/jobs", summary="List All Jobs")
async def list_jobs():
"""
List all market research jobs and their current status.
Useful for monitoring and administrative purposes.
"""
return {
"total_jobs": len(job_status),
"jobs": [
{
"job_id": job_id,
"status": status["status"],
"progress": status["progress"],
"created_at": status["created_at"],
"company": status["config"]["company"] if "config" in status else "Unknown"
}
for job_id, status in job_status.items()
]
}
@app.delete("/research/jobs/{job_id}", summary="Cancel Job")
async def cancel_job(job_id: str):
"""
Cancel a pending or running market research job.
"""
if job_id not in job_status:
raise HTTPException(status_code=404, detail="Job ID not found")
if job_status[job_id]["status"] in ["completed", "failed"]:
raise HTTPException(status_code=400, detail="Cannot cancel completed or failed job")
job_status[job_id]["status"] = "cancelled"
job_status[job_id]["message"] = "Job cancelled by user"
job_status[job_id]["updated_at"] = datetime.now().isoformat()
return {"message": "Job cancelled successfully", "job_id": job_id}
@app.get("/research/templates", summary="Get Research Templates")
async def get_research_templates():
"""
Get predefined research templates for common use cases.
"""
templates = {
"technology_company": {
"sections": ["market_overview", "competitive_analysis", "financial_performance", "growth_opportunities"],
"example": {
"company": "Tesla",
"geography": "North America",
"industry": "Electric Vehicles"
}
},
"financial_services": {
"sections": ["market_overview", "competitive_analysis", "risk_assessment", "financial_performance"],
"example": {
"company": "Goldman Sachs",
"geography": "Global",
"industry": "Investment Banking"
}
},
"healthcare": {
"sections": ["market_overview", "competitive_analysis", "growth_opportunities", "risk_assessment"],
"example": {
"company": "Johnson & Johnson",
"geography": "North America",
"industry": "Pharmaceuticals"
}
},
"retail": {
"sections": ["market_overview", "competitive_analysis", "financial_performance", "growth_opportunities"],
"example": {
"company": "Amazon",
"geography": "Global",
"industry": "E-commerce"
}
}
}
return {
"templates": templates,
"available_sections": [
"market_overview",
"competitive_analysis",
"financial_performance",
"growth_opportunities",
"risk_assessment"
]
}
if __name__ == "__main__":
import uvicorn
print("🚀 Starting Secondary Market Research API...")
print("📚 API Documentation: http://localhost:8000/docs")
print("🔄 ReDoc Documentation: http://localhost:8000/redoc")
uvicorn.run(
"secondary-market-research-api:app",
host="0.0.0.0",
port=8000,
reload=True
)
+173
View File
@@ -0,0 +1,173 @@
# Camera Integration Examples
This directory contains examples demonstrating how to integrate camera feeds with PraisonAI multimodal agents for real-time visual analysis.
## 📋 Overview
PraisonAI supports visual input through the `images` parameter in Tasks. While there's no built-in camera capture, you can integrate camera feeds by capturing frames/videos and passing them to vision agents.
## 🛠️ Setup
### Requirements
```bash
pip install praisonaiagents opencv-python
```
### Environment Variables
```bash
export OPENAI_API_KEY=your_openai_api_key
```
## 📁 Examples
### 1. `camera-basic.py` - Basic Camera Capture
- **Purpose**: Capture a single frame and analyze it
- **Use Case**: Quick analysis, testing camera setup
- **Features**:
- Single frame capture
- Basic object and scene analysis
- Automatic cleanup
**Usage:**
```bash
python camera-basic.py
```
### 2. `camera-continuous.py` - Continuous Monitoring
- **Purpose**: Continuous camera monitoring with periodic analysis
- **Use Case**: Security monitoring, surveillance systems
- **Features**:
- Configurable analysis intervals
- Real-time monitoring
- Security-focused analysis
- Graceful shutdown with Ctrl+C
**Usage:**
```bash
python camera-continuous.py
```
### 3. `camera-multi-agent.py` - Multi-Agent Analysis
- **Purpose**: Multiple specialized agents analyzing the same camera feed
- **Use Case**: Comprehensive analysis from different perspectives
- **Features**:
- Security analysis agent
- Object detection agent
- Scene analysis agent
- Parallel processing
**Usage:**
```bash
python camera-multi-agent.py
```
### 4. `camera-video-analysis.py` - Video Recording & Analysis
- **Purpose**: Record video segments and analyze temporal events
- **Use Case**: Activity analysis, event detection
- **Features**:
- Video recording with configurable duration
- Temporal event analysis
- Timeline extraction
- Automatic cleanup
**Usage:**
```bash
python camera-video-analysis.py
```
## 🎯 Supported Input Types
- ✅ **Local Images**: `"camera_shot.jpg"`, `"webcam_capture.png"`
- ✅ **Local Videos**: `"security_feed.mp4"`, `"recording.avi"`
- ✅ **Image URLs**: `"https://example.com/live_feed.jpg"`
- ✅ **Multiple Sources**: `["cam1.jpg", "cam2.jpg", "video.mp4"]`
## 🔧 Configuration Options
### Camera Settings
```python
camera_id = 0 # Default camera (change to 1, 2, etc. for other cameras)
```
### Analysis Intervals
```python
analysis_interval = 10 # Seconds between analyses for continuous monitoring
```
### Recording Duration
```python
recording_duration = 15 # Seconds for video recording
```
## 💡 Integration Patterns
### 1. Real-time Processing
```python
# Capture frames periodically and process them as separate tasks
def capture_and_analyze():
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
if ret:
cv2.imwrite("temp_capture.jpg", frame)
# Process with PraisonAI agent
cap.release()
```
### 2. Batch Analysis
```python
# Process multiple camera angles simultaneously
task = Task(
description="Analyze multiple camera feeds",
agent=vision_agent,
images=["cam1.jpg", "cam2.jpg", "cam3.jpg"]
)
```
### 3. Video Analysis
```python
# Save video segments and analyze them
task = Task(
description="Analyze this video for activities",
agent=vision_agent,
images=["security_footage.mp4"] # Video files work too
)
```
## 🔒 Security Considerations
- **Camera Permissions**: Ensure your application has camera access
- **Privacy**: Be mindful of privacy when recording/analyzing
- **Storage**: Clean up temporary files to avoid storage issues
- **Access Control**: Implement proper access controls for camera systems
## 🐛 Troubleshooting
### Camera Not Found
```python
# Check available cameras
for i in range(4): # Check first 4 camera indices
cap = cv2.VideoCapture(i)
if cap.isOpened():
print(f"Camera {i} is available")
cap.release()
```
### Permission Issues
- On Linux: Add user to `video` group
- On macOS: Grant camera permissions in System Preferences
- On Windows: Check camera privacy settings
### Performance Optimization
- Reduce frame size for faster processing
- Adjust analysis intervals based on requirements
- Use parallel processing for multiple agents
## 📚 Related Documentation
- [Multimodal Features](../../docs/features/multimodal.mdx)
- [Agent Documentation](../../docs/concepts/agents.mdx)
- [Task Documentation](../../docs/concepts/tasks.mdx)
## 🤝 Contributing
Feel free to contribute additional camera integration examples or improvements to existing ones!
+102
View File
@@ -0,0 +1,102 @@
"""
Basic Camera Integration Example
This example demonstrates how to capture a single frame from a camera
and analyze it using a PraisonAI vision agent.
Requirements:
pip install praisonaiagents opencv-python
Usage:
python camera-basic.py
Environment Variables:
OPENAI_API_KEY=your_openai_api_key
"""
import cv2
import os
from praisonaiagents import Agent, Task, PraisonAIAgents
def capture_and_analyze():
"""Capture a single frame from camera and analyze it"""
# Create vision agent
vision_agent = Agent(
name="CameraAnalyst",
role="Camera Feed Analyzer",
goal="Analyze camera captures in real-time",
backstory="Expert in real-time visual analysis with computer vision skills",
llm="gpt-4o-mini"
)
# Initialize camera (0 for default camera)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Error: Could not open camera")
return None
try:
# Capture frame
ret, frame = cap.read()
if ret:
# Save frame temporarily
image_path = "temp_camera_capture.jpg"
cv2.imwrite(image_path, frame)
print(f"Frame captured and saved as {image_path}")
# Create analysis task
task = Task(
name="analyze_camera_feed",
description="Analyze what you see in this camera capture. Identify objects, people, and activities.",
expected_output="Detailed analysis of the camera content including objects, people, activities, and overall scene description",
agent=vision_agent,
images=[image_path]
)
# Run analysis
agents = PraisonAIAgents(
agents=[vision_agent],
tasks=[task],
process="sequential",
verbose=1
)
result = agents.start()
# Clean up temporary file
if os.path.exists(image_path):
os.remove(image_path)
return result
else:
print("Error: Could not capture frame")
return None
finally:
cap.release()
cv2.destroyAllWindows()
def main():
"""Main function"""
print("📸 Starting basic camera analysis...")
print("Make sure your camera is connected and accessible")
result = capture_and_analyze()
if result:
print("\n🔍 Analysis Results:")
for task_id, task_result in result["task_results"].items():
print(f"\nTask {task_id}:")
print(task_result.raw)
else:
print("❌ Failed to capture and analyze camera feed")
if __name__ == "__main__":
main()
+142
View File
@@ -0,0 +1,142 @@
"""
Continuous Camera Monitoring Example
This example demonstrates continuous camera monitoring with periodic analysis
using PraisonAI vision agents. Perfect for security monitoring or real-time surveillance.
Requirements:
pip install praisonaiagents opencv-python
Usage:
python camera-continuous.py
Environment Variables:
OPENAI_API_KEY=your_openai_api_key
"""
import cv2
import time
import os
from datetime import datetime
from praisonaiagents import Agent, Task, PraisonAIAgents
class ContinuousCameraMonitor:
"""Continuous camera monitoring system"""
def __init__(self, camera_id=0, analysis_interval=10):
"""
Initialize camera monitor
Args:
camera_id (int): Camera ID (0 for default)
analysis_interval (int): Seconds between analyses
"""
self.camera_id = camera_id
self.analysis_interval = analysis_interval
self.running = False
# Create vision agent
self.vision_agent = Agent(
name="SecurityMonitor",
role="Security Camera Analyst",
goal="Monitor camera feeds for activities and security events",
backstory="Expert security analyst with computer vision skills specialized in real-time monitoring",
llm="gpt-4o-mini"
)
def capture_frame(self, cap):
"""Capture a single frame from camera"""
ret, frame = cap.read()
if ret:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"camera_capture_{timestamp}.jpg"
cv2.imwrite(filename, frame)
return filename
return None
def analyze_frame(self, image_path):
"""Analyze captured frame"""
task = Task(
name="monitor_camera_feed",
description="""Monitor this camera feed for:
1. Any people or unusual activities
2. Objects that seem out of place
3. Movement or changes in the scene
4. Security-relevant events
Provide a brief but detailed security report.""",
expected_output="Security analysis report with timestamp and observations",
agent=self.vision_agent,
images=[image_path]
)
agents = PraisonAIAgents(
agents=[self.vision_agent],
tasks=[task],
process="sequential",
verbose=0 # Reduced verbosity for continuous monitoring
)
return agents.start()
def start_monitoring(self):
"""Start continuous monitoring"""
print(f"🎥 Starting continuous camera monitoring...")
print(f"📊 Analysis interval: {self.analysis_interval} seconds")
print("🛑 Press Ctrl+C to stop")
cap = cv2.VideoCapture(self.camera_id)
if not cap.isOpened():
print(f"❌ Error: Could not open camera {self.camera_id}")
return
self.running = True
try:
while self.running:
# Capture frame
image_path = self.capture_frame(cap)
if image_path:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"\n⏰ {timestamp} - Analyzing camera feed...")
# Analyze frame
result = self.analyze_frame(image_path)
# Display results
if result and "task_results" in result:
for task_id, task_result in result["task_results"].items():
print(f"📋 Security Report: {task_result.raw}")
# Clean up temporary file
if os.path.exists(image_path):
os.remove(image_path)
# Wait for next analysis
time.sleep(self.analysis_interval)
except KeyboardInterrupt:
print("\n🛑 Stopping camera monitoring...")
finally:
self.running = False
cap.release()
cv2.destroyAllWindows()
print("✅ Camera monitoring stopped")
def main():
"""Main function"""
# Configuration
camera_id = 0 # Default camera
analysis_interval = 15 # Analyze every 15 seconds
# Create and start monitor
monitor = ContinuousCameraMonitor(camera_id, analysis_interval)
monitor.start_monitoring()
if __name__ == "__main__":
main()
@@ -0,0 +1,164 @@
"""
Multi-Agent Camera Analysis Example
This example demonstrates using multiple specialized agents to analyze
camera feeds from different perspectives (security, object detection, etc.).
Requirements:
pip install praisonaiagents opencv-python
Usage:
python camera-multi-agent.py
Environment Variables:
OPENAI_API_KEY=your_openai_api_key
"""
import cv2
import os
from datetime import datetime
from praisonaiagents import Agent, Task, PraisonAIAgents
def capture_camera_frame(camera_id=0):
"""Capture a frame from camera"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"Error: Could not open camera {camera_id}")
return None
ret, frame = cap.read()
cap.release()
if ret:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
image_path = f"camera_frame_{timestamp}.jpg"
cv2.imwrite(image_path, frame)
return image_path
return None
def analyze_with_multiple_agents():
"""Analyze camera feed using multiple specialized agents"""
# Capture frame
image_path = capture_camera_frame()
if not image_path:
print("Failed to capture camera frame")
return None
print(f"📸 Captured frame: {image_path}")
# Create specialized agents
security_agent = Agent(
name="SecurityExpert",
role="Security Specialist",
goal="Identify security threats and suspicious activities",
backstory="Expert security analyst with years of experience in surveillance and threat detection",
llm="gpt-4o-mini"
)
object_detector = Agent(
name="ObjectDetector",
role="Object Recognition Specialist",
goal="Identify and catalog all objects in the scene",
backstory="Computer vision expert specializing in object detection and classification",
llm="gpt-4o-mini"
)
scene_analyst = Agent(
name="SceneAnalyst",
role="Scene Understanding Expert",
goal="Provide comprehensive scene analysis and context",
backstory="Environmental analyst expert in understanding spatial relationships and scene context",
llm="gpt-4o-mini"
)
# Create specialized tasks
security_task = Task(
name="security_analysis",
description="""Analyze this camera feed for security concerns:
1. Identify any people and their activities
2. Look for suspicious behavior or unauthorized access
3. Check for potential security threats
4. Assess overall security status of the area""",
expected_output="Detailed security assessment with threat level and recommendations",
agent=security_agent,
images=[image_path]
)
object_task = Task(
name="object_detection",
description="""Identify and catalog all objects in this image:
1. List all visible objects with their approximate locations
2. Identify any vehicles, electronics, or valuable items
3. Note any objects that seem out of place
4. Provide a comprehensive inventory""",
expected_output="Detailed object inventory with locations and descriptions",
agent=object_detector,
images=[image_path]
)
scene_task = Task(
name="scene_analysis",
description="""Provide comprehensive scene analysis:
1. Describe the overall environment and setting
2. Analyze lighting conditions and time of day
3. Assess the general activity level and atmosphere
4. Provide context about what type of location this appears to be""",
expected_output="Complete scene description with environmental context",
agent=scene_analyst,
images=[image_path]
)
# Run multi-agent analysis
agents = PraisonAIAgents(
agents=[security_agent, object_detector, scene_analyst],
tasks=[security_task, object_task, scene_task],
process="parallel", # Run all analyses in parallel
verbose=1
)
result = agents.start()
# Clean up temporary file
if os.path.exists(image_path):
os.remove(image_path)
return result
def main():
"""Main function"""
print("🎯 Starting multi-agent camera analysis...")
print("📋 This will analyze the camera feed from multiple perspectives")
result = analyze_with_multiple_agents()
if result:
print("\n" + "="*60)
print("📊 MULTI-AGENT ANALYSIS RESULTS")
print("="*60)
# Display results by agent
agent_names = {
"security_analysis": "🛡️ SECURITY ANALYSIS",
"object_detection": "🔍 OBJECT DETECTION",
"scene_analysis": "🌍 SCENE ANALYSIS"
}
for task_id, task_result in result["task_results"].items():
print(f"\n{agent_names.get(task_id, f'Task {task_id}')}:")
print("-" * 40)
print(task_result.raw)
print("\n" + "="*60)
print("✅ Multi-agent analysis complete!")
else:
print("❌ Failed to complete camera analysis")
if __name__ == "__main__":
main()
@@ -0,0 +1,206 @@
"""
Camera Video Recording and Analysis Example
This example demonstrates recording video segments from camera
and analyzing them using PraisonAI vision agents.
Requirements:
pip install praisonaiagents opencv-python
Usage:
python camera-video-analysis.py
Environment Variables:
OPENAI_API_KEY=your_openai_api_key
"""
import cv2
import os
import time
from datetime import datetime
from praisonaiagents import Agent, Task, PraisonAIAgents
class CameraVideoAnalyzer:
"""Camera video recording and analysis system"""
def __init__(self, camera_id=0):
"""
Initialize video analyzer
Args:
camera_id (int): Camera ID (0 for default)
"""
self.camera_id = camera_id
# Create video analysis agent
self.video_agent = Agent(
name="VideoAnalyst",
role="Video Content Analyzer",
goal="Analyze video content for activities, objects, and events",
backstory="Expert in video analysis and temporal event detection with computer vision expertise",
llm="gpt-4o-mini"
)
def record_video_segment(self, duration_seconds=10):
"""
Record a video segment from camera
Args:
duration_seconds (int): Length of video to record
Returns:
str: Path to recorded video file or None if failed
"""
cap = cv2.VideoCapture(self.camera_id)
if not cap.isOpened():
print(f"Error: Could not open camera {self.camera_id}")
return None
# Get video properties
fps = int(cap.get(cv2.CAP_PROP_FPS)) or 30
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Setup video writer
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
video_path = f"camera_recording_{timestamp}.mp4"
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(video_path, fourcc, fps, (width, height))
print(f"🎬 Recording video for {duration_seconds} seconds...")
print(f"📹 Resolution: {width}x{height}, FPS: {fps}")
start_time = time.time()
frame_count = 0
try:
while (time.time() - start_time) < duration_seconds:
ret, frame = cap.read()
if ret:
out.write(frame)
frame_count += 1
# Show recording indicator
if frame_count % fps == 0: # Update every second
elapsed = int(time.time() - start_time)
remaining = duration_seconds - elapsed
print(f"⏱️ Recording... {remaining}s remaining")
else:
break
finally:
cap.release()
out.release()
cv2.destroyAllWindows()
if frame_count > 0:
print(f"✅ Video recorded: {video_path} ({frame_count} frames)")
return video_path
else:
print("❌ Failed to record video")
if os.path.exists(video_path):
os.remove(video_path)
return None
def analyze_video(self, video_path):
"""
Analyze recorded video
Args:
video_path (str): Path to video file
Returns:
dict: Analysis results
"""
task = Task(
name="analyze_video_content",
description="""Analyze this video recording and provide:
1. Summary of main activities and events observed
2. Timeline of key moments or changes
3. Identification of people, objects, and movements
4. Any notable interactions or behaviors
5. Overall assessment of what happened in the video
Focus on providing a comprehensive temporal analysis.""",
expected_output="Detailed video analysis with timeline and event description",
agent=self.video_agent,
images=[video_path] # PraisonAI supports video files in images parameter
)
agents = PraisonAIAgents(
agents=[self.video_agent],
tasks=[task],
process="sequential",
verbose=1
)
return agents.start()
def record_and_analyze(self, duration=10):
"""
Record video and analyze it
Args:
duration (int): Recording duration in seconds
"""
# Record video
video_path = self.record_video_segment(duration)
if not video_path:
return None
print(f"\n🔍 Analyzing recorded video...")
# Analyze video
result = self.analyze_video(video_path)
# Clean up video file
if os.path.exists(video_path):
os.remove(video_path)
print(f"🗑️ Cleaned up temporary video file")
return result
def main():
"""Main function"""
print("🎥 Camera Video Recording and Analysis")
print("=" * 50)
# Configuration
camera_id = 0
recording_duration = 15 # seconds
print(f"📹 Camera: {camera_id}")
print(f"⏱️ Recording Duration: {recording_duration} seconds")
print("\nMake sure your camera is connected and accessible")
print("The system will record a video segment and then analyze it")
input("\nPress Enter to start recording...")
# Create analyzer and run
analyzer = CameraVideoAnalyzer(camera_id)
result = analyzer.record_and_analyze(recording_duration)
if result:
print("\n" + "="*60)
print("📊 VIDEO ANALYSIS RESULTS")
print("="*60)
for task_id, task_result in result["task_results"].items():
print(f"\nTask: {task_id}")
print("-" * 40)
print(task_result.raw)
print("\n" + "="*60)
print("✅ Video analysis complete!")
else:
print("❌ Failed to record and analyze video")
if __name__ == "__main__":
main()
@@ -0,0 +1,205 @@
# CSV Processing with PraisonAI Agents
This document answers GitHub Issue #23: "How to pass in a CSV file" and provides multiple solutions for processing CSV files with PraisonAI agents.
## Quick Answer
**Yes, it's absolutely possible!** PraisonAI provides multiple ways to process CSV files with agents working through the list sequentially.
## The Simplest Solution
For the exact use case mentioned in the issue (CSV list of URLs processed sequentially):
```python
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create your agent
agent = Agent(
name="URLProcessor",
instructions="Analyze each URL from the CSV file"
)
# Create a loop task
task = Task(
description="Process each URL from the CSV",
agent=agent,
task_type="loop", # Enable CSV loop processing
input_file="your_urls.csv" # Your CSV file
)
# Run the agents
agents = PraisonAIAgents(
agents=[agent],
tasks=[task],
process="workflow"
)
agents.start()
```
## CSV File Format
Your CSV file should have a simple structure. For URLs:
```csv
url
https://example.com
https://github.com
https://stackoverflow.com
```
Or with additional columns:
```csv
url,description
https://example.com,Example website
https://github.com,GitHub platform
https://stackoverflow.com,Q&A site
```
## Available Methods
### Method 1: Simple Loop Processing (Recommended)
- **File**: `simple-csv-url-processor.py`
- **Best for**: Beginners, simple use cases
- **How it works**: Uses `task_type="loop"` to automatically process each CSV row
### Method 2: Manual CSV Processing
- **File**: `csv-processing-agents.py` (Method 2)
- **Best for**: When you need more control over validation and processing
- **How it works**: Uses CSV tools (`read_csv`, `write_csv`) for manual processing
### Method 3: URL-Specific Processing
- **File**: `csv-processing-agents.py` (Method 3)
- **Best for**: Specifically processing URLs with detailed analysis
- **How it works**: Combines CSV tools with URL analysis logic
### Method 4: Advanced Processing with Validation
- **File**: `csv-processing-agents.py` (Method 4)
- **Best for**: Production use cases requiring data validation
- **How it works**: Validates data before processing, handles errors gracefully
## Built-in CSV Tools
PraisonAI includes powerful CSV tools:
```python
from praisonaiagents.tools import read_csv, write_csv, merge_csv
# Read CSV files
data = read_csv("input.csv")
# Write CSV files
write_csv("output.csv", data)
# Merge multiple CSV files
merge_csv(["file1.csv", "file2.csv"], "merged.csv")
```
## Examples for Different Use Cases
### Processing URLs from CSV
```python
# Your CSV: urls.csv
# url,priority
# https://example.com,high
# https://github.com,medium
agent = Agent(
name="URLAnalyzer",
tools=[read_csv, write_csv],
instructions="Analyze URLs based on priority"
)
```
### Processing Any Text File
```python
# For text files, you can convert to CSV format first
# Or process line by line using file tools
```
### Batch Processing Tasks
```python
# Your CSV: tasks.csv
# task
# "Analyze competitor website"
# "Research market trends"
# "Generate report summary"
task = Task(
description="Complete each task from the CSV",
task_type="loop",
input_file="tasks.csv"
)
```
## Prerequisites
1. **Install PraisonAI Agents**:
```bash
pip install praisonaiagents
```
2. **Set API Key**:
```bash
export OPENAI_API_KEY=your_api_key_here
```
3. **Install pandas** (for advanced CSV operations):
```bash
pip install pandas
```
## Running the Examples
1. **Simple URL Processing**:
```bash
python simple-csv-url-processor.py
```
2. **Comprehensive Examples**:
```bash
python csv-processing-agents.py
```
## Troubleshooting
### Common Issues:
1. **"CSV file not found"**
- Make sure your CSV file is in the same directory as your script
- Use absolute paths if needed: `/full/path/to/your/file.csv`
2. **"pandas not available"**
- Install pandas: `pip install pandas`
3. **"API key not set"**
- Set your OpenAI API key: `export OPENAI_API_KEY=your_key`
4. **Processing stops early**
- Increase `max_iter` parameter in PraisonAIAgents
- Check for errors in your CSV format
### CSV Format Tips:
- Use UTF-8 encoding
- Ensure consistent column headers
- Handle special characters properly
- Keep URLs properly formatted (http:// or https://)
## Related Documentation
- [CSV Tools Documentation](../../../docs/tools/csv_tools.mdx)
- [Repetitive Agents](../../../docs/features/repetitive.mdx)
- [Agent Examples](../../README.md)
## Support
If you encounter issues:
1. Check the troubleshooting section above
2. Review the example files for working implementations
3. Open an issue on the GitHub repository with your specific use case
---
*This solution was generated in response to GitHub Issue #23*
@@ -0,0 +1,369 @@
"""
CSV Processing with PraisonAI Agents
This example demonstrates multiple ways to process CSV files with agents:
1. Simple CSV Loop Processing (easiest)
2. Manual CSV Processing with read_csv tools
3. URL Processing from CSV files
4. Custom CSV Processing with validation
Author: Generated for GitHub Issue #23
"""
import os
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import read_csv, write_csv
def create_sample_urls_csv():
"""Create a sample CSV file with URLs for testing"""
urls_data = [
{"id": 1, "url": "https://example.com", "description": "Example website"},
{"id": 2, "url": "https://github.com", "description": "GitHub platform"},
{"id": 3, "url": "https://stackoverflow.com", "description": "Stack Overflow Q&A"},
{"id": 4, "url": "https://python.org", "description": "Python official site"},
{"id": 5, "url": "https://docs.python.org", "description": "Python documentation"}
]
# Ensure the CSV file exists
write_csv("sample_urls.csv", urls_data)
print("✅ Created sample_urls.csv with 5 URLs")
return "sample_urls.csv"
def create_sample_tasks_csv():
"""Create a sample CSV file with tasks for loop processing"""
tasks_data = [
{"task": "Analyze the homepage of example.com and summarize its content"},
{"task": "Check if github.com is accessible and describe its main features"},
{"task": "Research what Stack Overflow is used for"},
{"task": "Summarize the key features of Python programming language"},
{"task": "Describe what you can find in Python documentation"}
]
write_csv("sample_tasks.csv", tasks_data)
print("✅ Created sample_tasks.csv with 5 tasks")
return "sample_tasks.csv"
# ==============================================================================
# METHOD 1: SIMPLE CSV LOOP PROCESSING (RECOMMENDED FOR BEGINNERS)
# ==============================================================================
def method_1_simple_loop():
"""
The easiest way to process CSV files with PraisonAI agents.
This method automatically loops through each row in your CSV file.
Each row becomes input for the agent to process.
"""
print("\n" + "="*60)
print("METHOD 1: Simple CSV Loop Processing")
print("="*60)
# Create sample CSV file
csv_file = create_sample_tasks_csv()
# Create an agent that will process each row
loop_agent = Agent(
name="CSVLoopProcessor",
role="CSV Task Processor",
goal="Process each task from the CSV file efficiently",
backstory="Expert at handling repetitive tasks from CSV data",
instructions="Process each task thoroughly and provide detailed output",
llm="gpt-4o-mini"
)
# Create a loop task that will automatically process each CSV row
loop_task = Task(
description="Process each task from the CSV file",
expected_output="Completed task with detailed results",
agent=loop_agent,
task_type="loop", # This enables CSV loop processing
input_file=csv_file # The CSV file to process
)
# Initialize the agents system
agents = PraisonAIAgents(
agents=[loop_agent],
tasks=[loop_task],
process="workflow",
max_iter=10 # Maximum iterations to prevent infinite loops
)
print(f"🚀 Starting loop processing of {csv_file}")
print("Each row will be processed as a separate task...")
# Start processing
agents.start()
# ==============================================================================
# METHOD 2: MANUAL CSV PROCESSING WITH TOOLS
# ==============================================================================
def method_2_manual_csv():
"""
Manual CSV processing using built-in CSV tools.
This gives you more control over how the CSV is processed,
including validation, filtering, and custom logic.
"""
print("\n" + "="*60)
print("METHOD 2: Manual CSV Processing with Tools")
print("="*60)
# Create sample CSV file
csv_file = create_sample_urls_csv()
# Create an agent with CSV tools
csv_agent = Agent(
name="CSVAnalyzer",
role="CSV Data Processor",
goal="Read, validate, and process CSV data manually",
backstory="Specialist in data validation and processing",
tools=[read_csv, write_csv],
instructions="""
You are an expert at processing CSV files. When given a CSV file:
1. Read the CSV file using read_csv tool
2. Validate the data structure
3. Process each row according to the requirements
4. Create a summary of the processing results
5. Save results to a new CSV file if needed
""",
llm="gpt-4o-mini"
)
# Create a task for manual CSV processing
csv_task = Task(
description=f"""
Process the CSV file '{csv_file}' manually:
1. Read the CSV file using the read_csv tool
2. Validate that it contains 'url' and 'description' columns
3. For each URL, describe what type of website it is
4. Create a summary report of all URLs processed
5. Save the results to 'processed_urls.csv'
""",
expected_output="A detailed report of CSV processing with validation results",
agent=csv_agent
)
# Initialize and run
agents = PraisonAIAgents(
agents=[csv_agent],
tasks=[csv_task],
process="sequential"
)
print(f"🚀 Starting manual processing of {csv_file}")
agents.start()
# ==============================================================================
# METHOD 3: URL PROCESSING FROM CSV (SPECIFIC TO ISSUE REQUEST)
# ==============================================================================
def method_3_url_processing():
"""
Specific example for processing URLs from CSV files.
This addresses the original question about processing a CSV list of URLs
where agents work through the list sequentially.
"""
print("\n" + "="*60)
print("METHOD 3: URL Processing from CSV Files")
print("="*60)
# Create sample CSV file with URLs
csv_file = create_sample_urls_csv()
# Create a URL processing agent
url_agent = Agent(
name="URLProcessor",
role="Website Analyzer",
goal="Analyze websites from CSV URL list",
backstory="Expert web analyst who can evaluate websites",
tools=[read_csv, write_csv],
instructions="""
You are a website analysis expert. When given a CSV with URLs:
1. Read the CSV file to get the list of URLs
2. For each URL, analyze what kind of website it is
3. Determine the purpose and main features of each site
4. Create a detailed analysis report
5. Save results with analysis data
""",
llm="gpt-4o-mini"
)
# Create the URL processing task
url_task = Task(
description=f"""
Process URLs from '{csv_file}':
1. Read the CSV file containing URLs and descriptions
2. For each URL in the list, provide an analysis of:
- What type of website it is
- Its main purpose and features
- Target audience
- Key characteristics
3. Work through the URLs sequentially (one by one)
4. Create a comprehensive report with all analyses
5. Save the results to 'url_analysis_results.csv'
Make sure to process each URL individually and thoroughly.
""",
expected_output="Detailed analysis of each URL with comprehensive results saved to CSV",
agent=url_agent
)
# Initialize and run
agents = PraisonAIAgents(
agents=[url_agent],
tasks=[url_task],
process="sequential"
)
print(f"🚀 Starting URL processing from {csv_file}")
print("URLs will be processed sequentially...")
agents.start()
# ==============================================================================
# METHOD 4: ADVANCED CSV PROCESSING WITH VALIDATION
# ==============================================================================
def method_4_advanced_processing():
"""
Advanced CSV processing with data validation and error handling.
This method shows how to handle complex CSV processing scenarios
with validation, error handling, and structured outputs.
"""
print("\n" + "="*60)
print("METHOD 4: Advanced CSV Processing with Validation")
print("="*60)
# Create a more complex CSV file
complex_data = [
{"id": 1, "url": "https://example.com", "priority": "high", "category": "test"},
{"id": 2, "url": "https://github.com", "priority": "medium", "category": "development"},
{"id": 3, "url": "invalid-url", "priority": "low", "category": "test"}, # Invalid URL for testing
{"id": 4, "url": "https://stackoverflow.com", "priority": "high", "category": "development"},
{"id": 5, "url": "", "priority": "medium", "category": "empty"} # Empty URL for testing
]
csv_file = "complex_urls.csv"
write_csv(csv_file, complex_data)
print(f"✅ Created {csv_file} with complex data including invalid entries")
# Create a validation agent
validator_agent = Agent(
name="DataValidator",
role="Data Quality Specialist",
goal="Validate and clean CSV data before processing",
backstory="Expert in data validation and quality assurance",
tools=[read_csv, write_csv],
instructions="""
You are a data validation expert. Your job is to:
1. Read CSV files and validate data quality
2. Identify invalid or problematic entries
3. Clean and standardize data where possible
4. Create separate files for valid and invalid data
5. Provide detailed validation reports
""",
llm="gpt-4o-mini"
)
# Create a processing agent
processor_agent = Agent(
name="ValidatedProcessor",
role="Validated Data Processor",
goal="Process only validated and clean data",
backstory="Specialist in processing pre-validated data efficiently",
tools=[read_csv, write_csv],
instructions="""
You process validated data efficiently:
1. Read validated CSV data
2. Process each valid entry according to requirements
3. Generate comprehensive results
4. Create detailed output reports
""",
llm="gpt-4o-mini"
)
# Create validation task
validation_task = Task(
description=f"""
Validate the data in '{csv_file}':
1. Read the CSV file
2. Check each row for:
- Valid URL format (must start with http:// or https://)
- Non-empty required fields
- Valid priority values (high, medium, low)
- Valid category values
3. Separate valid and invalid entries
4. Save valid entries to 'valid_urls.csv'
5. Save invalid entries to 'invalid_urls.csv' with error descriptions
6. Create a validation summary report
""",
expected_output="Validation complete with separate files for valid/invalid data and summary report",
agent=validator_agent
)
# Create processing task (depends on validation)
processing_task = Task(
description="""
Process the validated data from 'valid_urls.csv':
1. Read the validated CSV file
2. For each valid URL, provide analysis based on priority and category
3. High priority items get detailed analysis
4. Medium priority items get standard analysis
5. Low priority items get basic analysis
6. Save results to 'final_results.csv'
""",
expected_output="Processed results for all valid entries saved to final CSV",
agent=processor_agent
)
# Initialize and run with sequential processing
agents = PraisonAIAgents(
agents=[validator_agent, processor_agent],
tasks=[validation_task, processing_task],
process="sequential" # Validation must complete before processing
)
print(f"🚀 Starting advanced processing with validation")
agents.start()
# ==============================================================================
# MAIN EXECUTION
# ==============================================================================
if __name__ == "__main__":
print("CSV Processing with PraisonAI Agents - Multiple Methods Demo")
print("=" * 70)
print("\nThis demo shows 4 different ways to process CSV files with agents:")
print("1. Simple CSV Loop Processing (easiest)")
print("2. Manual CSV Processing with tools")
print("3. URL Processing from CSV (addresses the GitHub issue)")
print("4. Advanced processing with validation")
# You can run all methods or comment out ones you don't need
try:
# Method 1: Simple loop processing
method_1_simple_loop()
# Method 2: Manual CSV processing
method_2_manual_csv()
# Method 3: URL processing (specific to the GitHub issue)
method_3_url_processing()
# Method 4: Advanced processing with validation
method_4_advanced_processing()
print("\n" + "="*70)
print("✅ All CSV processing methods completed!")
print("Check the generated CSV files for results.")
print("="*70)
except Exception as e:
print(f"❌ Error during processing: {e}")
print("Make sure you have:")
print("- OpenAI API key set (export OPENAI_API_KEY=your_key)")
print("- praisonaiagents package installed (pip install praisonaiagents)")
print("- pandas package installed (pip install pandas)")
@@ -0,0 +1,159 @@
"""
Knowledge Agent with Reranker Example
===================================
This example demonstrates how to use Mem0's built-in reranking feature
with the Knowledge Agent for improved search result relevance.
"""
import os
from praisonaiagents import Agent
from praisonaiagents.knowledge import Knowledge
def main():
"""
Demonstrates knowledge search with and without reranking.
"""
# Create sample content for knowledge base
sample_documents = [
"Artificial Intelligence is a field of computer science that aims to create machines that can perform tasks that typically require human intelligence.",
"Machine Learning is a subset of AI that enables computers to learn and improve from experience without being explicitly programmed.",
"Deep Learning is a type of machine learning that uses neural networks with multiple layers to model and understand complex patterns.",
"Natural Language Processing (NLP) is a branch of AI that helps computers understand, interpret and manipulate human language.",
"Computer Vision is an AI field that trains computers to interpret and understand the visual world from digital images and videos.",
"Reinforcement Learning is a type of machine learning where an agent learns to make decisions by taking actions in an environment.",
"Neural Networks are computing systems inspired by biological neural networks that constitute animal brains.",
"Data Science combines domain expertise, programming skills, and knowledge of mathematics and statistics to extract insights from data."
]
print("=== Knowledge Agent Reranker Example ===\n")
# Example 1: Basic Knowledge without reranking
print("1. Creating Knowledge base without reranking...")
basic_config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "knowledge_basic",
"path": ".praison_basic"
}
},
"reranker": {
"enabled": True,
"default_rerank": False # Disabled by default
}
}
basic_knowledge = Knowledge(config=basic_config, verbose=1)
# Add documents to knowledge base
for i, doc in enumerate(sample_documents):
basic_knowledge.store(doc, metadata={"doc_id": f"doc_{i}", "category": "AI"})
# Example 2: Knowledge with reranking enabled by default
print("\n2. Creating Knowledge base with reranking enabled...")
rerank_config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "knowledge_rerank",
"path": ".praison_rerank"
}
},
"reranker": {
"enabled": True,
"default_rerank": True # Enabled by default
}
}
rerank_knowledge = Knowledge(config=rerank_config, verbose=1)
# Add same documents to reranking knowledge base
for i, doc in enumerate(sample_documents):
rerank_knowledge.store(doc, metadata={"doc_id": f"doc_{i}", "category": "AI"})
print("\n=== Search Results Comparison ===\n")
query = "What is machine learning and how does it work?"
# Search without reranking
print("3. Search WITHOUT reranking:")
basic_results = basic_knowledge.search(query, rerank=False)
print(f"Query: {query}")
print(f"Results (limit=3):")
for i, result in enumerate(basic_results[:3]):
text = result.get('memory', result.get('text', str(result)))
score = result.get('score', 'N/A')
print(f" {i+1}. Score: {score}")
print(f" Text: {text[:100]}...")
print()
# Search with reranking explicitly enabled
print("4. Search WITH reranking:")
rerank_results = basic_knowledge.search(query, rerank=True)
print(f"Query: {query}")
print(f"Results (limit=3):")
for i, result in enumerate(rerank_results[:3]):
text = result.get('memory', result.get('text', str(result)))
score = result.get('score', 'N/A')
print(f" {i+1}. Score: {score}")
print(f" Text: {text[:100]}...")
print()
# Search using config default (reranking enabled)
print("5. Search using config default (reranking enabled):")
default_results = rerank_knowledge.search(query) # Will use default_rerank=True
print(f"Query: {query}")
print(f"Results (limit=3):")
for i, result in enumerate(default_results[:3]):
text = result.get('memory', result.get('text', str(result)))
score = result.get('score', 'N/A')
print(f" {i+1}. Score: {score}")
print(f" Text: {text[:100]}...")
print()
print("=== Advanced Search Options ===\n")
# Example with additional Mem0 advanced retrieval options
print("6. Search with keyword search + reranking:")
advanced_results = basic_knowledge.search(
query,
rerank=True,
keyword_search=True, # Enable keyword search for better recall
filter_memories=True # Enable filtering for better precision
)
print(f"Query: {query}")
print(f"Advanced results with keyword_search=True, filter_memories=True:")
for i, result in enumerate(advanced_results[:3]):
text = result.get('memory', result.get('text', str(result)))
score = result.get('score', 'N/A')
print(f" {i+1}. Score: {score}")
print(f" Text: {text[:100]}...")
print()
# Example with Agent using reranking knowledge
print("\n=== Agent with Reranking Knowledge ===\n")
agent = Agent(
name="AI Research Assistant",
instructions="You are an AI research assistant. Use your knowledge base to answer questions about artificial intelligence topics.",
knowledge=sample_documents, # Will use default knowledge config
knowledge_config=rerank_config, # Use reranking config
llm="gpt-4o-mini"
)
print("7. Agent with reranking knowledge:")
response = agent.run("Explain the difference between machine learning and deep learning, and how they relate to AI.")
print(f"Agent response: {response}")
print("\n=== Performance Notes ===")
print("- Basic search: ~10ms latency")
print("- Reranking: 150-200ms additional latency")
print("- Reranking improves relevance ordering significantly")
print("- Use rerank=True for better results when query relevance is critical")
print("- Use keyword_search=True for better recall")
print("- Use filter_memories=True for better precision")
if __name__ == "__main__":
main()
@@ -0,0 +1,77 @@
"""
Simple CSV URL Processor
This example directly addresses the GitHub issue #23:
"Is it possible to pass in a CSV list of URLs and have agents work through the list each in turn?"
This is the simplest possible implementation for processing URLs from a CSV file.
Author: Generated for GitHub Issue #23
"""
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import write_csv
def create_url_csv():
"""Create a CSV file with URLs to process"""
# Example URLs - replace with your own
urls = [
{"url": "https://example.com"},
{"url": "https://github.com"},
{"url": "https://stackoverflow.com"},
{"url": "https://python.org"}
]
write_csv("urls_to_process.csv", urls)
print("✅ Created urls_to_process.csv")
return "urls_to_process.csv"
def main():
"""
Simple CSV URL processing example
This shows the easiest way to process a CSV list of URLs
where agents work through the list sequentially.
"""
print("Simple CSV URL Processor")
print("=" * 40)
# Step 1: Create the CSV file (or use your existing one)
csv_file = create_url_csv()
# Step 2: Create an agent to process URLs
url_agent = Agent(
name="URLProcessor",
role="URL Analyzer",
goal="Analyze each URL from the CSV list",
backstory="Expert at analyzing websites and URLs",
instructions="Analyze each URL and provide insights about the website",
llm="gpt-4o-mini" # You can change this to any supported model
)
# Step 3: Create a task that will loop through the CSV
url_task = Task(
description="Analyze each URL from the CSV file and provide insights",
expected_output="Analysis of the website at each URL",
agent=url_agent,
task_type="loop", # This tells PraisonAI to loop through CSV rows
input_file=csv_file # Your CSV file with URLs
)
# Step 4: Run the agents
agents = PraisonAIAgents(
agents=[url_agent],
tasks=[url_task],
process="workflow",
max_iter=10 # Adjust based on how many URLs you have
)
print(f"🚀 Processing URLs from {csv_file}")
print("The agent will work through each URL in turn...")
agents.start()
print("✅ Finished processing all URLs!")
if __name__ == "__main__":
main()
@@ -0,0 +1,210 @@
#!/usr/bin/env python3
"""
Human Approval Example for PraisonAI Agents
This example demonstrates the human-in-the-loop approval system for dangerous operations.
When agents attempt to execute high-risk tools (like shell commands or file operations),
the system will prompt the user for approval before proceeding.
Usage:
python human_approval_example.py
Features:
- Automatic approval prompts for dangerous tools
- Risk level classification (critical, high, medium, low)
- User can approve, deny, or modify tool arguments
- Console-based approval interface with rich formatting
"""
import asyncio
import logging
import os
import sys
# Add the local development path to use the current implementation
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src', 'praisonai-agents'))
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import python_tools, file_tools, shell_tools
from praisonaiagents.main import register_approval_callback
from praisonaiagents.approval import (
console_approval_callback,
ApprovalDecision,
add_approval_requirement,
remove_approval_requirement
)
# Configure logging
logging.basicConfig(level=logging.INFO)
def custom_approval_callback(function_name: str, arguments: dict, risk_level: str) -> ApprovalDecision:
"""
Custom approval callback that demonstrates how to implement
custom approval logic beyond the default console prompts.
"""
print(f"\n🔒 CUSTOM APPROVAL REQUIRED")
print(f"Function: {function_name}")
print(f"Risk Level: {risk_level}")
print(f"Arguments: {arguments}")
# Example: Auto-approve low-risk operations
if risk_level == "low":
print("✅ Auto-approving low-risk operation")
return ApprovalDecision(approved=True, reason="Auto-approved low risk")
# Example: Always deny certain dangerous commands
if function_name == "execute_command" and any(
dangerous in str(arguments.get("command", ""))
for dangerous in ["rm -rf", "del /f", "format", "shutdown"]
):
print("❌ Automatically denying dangerous command")
return ApprovalDecision(approved=False, reason="Dangerous command blocked")
# For other cases, use the default console approval
return console_approval_callback(function_name, arguments, risk_level)
def main():
"""Demonstrate human approval system with various dangerous operations."""
print("🤖 PraisonAI Human Approval System Demo")
print("=" * 50)
print("This demo will show approval prompts for dangerous operations.")
print("You can approve or deny each operation as it's requested.")
print()
# Option 1: Use default console approval callback (automatic)
# Option 2: Use custom approval callback
# Uncomment the line below to use custom approval logic:
# register_approval_callback(custom_approval_callback)
# Create agent with dangerous tools
agent = Agent(
name="Security Demo Agent",
role="System Administrator",
goal="Demonstrate human approval for dangerous operations",
tools=[python_tools, file_tools, shell_tools],
verbose=True
)
# Define tasks that will trigger approval prompts
tasks = [
Task(
description="Execute a simple Python print statement",
agent=agent,
expected_output="Python code execution result"
),
Task(
description="Create a test file with some content",
agent=agent,
expected_output="File creation confirmation"
),
Task(
description="List the current directory contents using shell command",
agent=agent,
expected_output="Directory listing"
),
Task(
description="Delete the test file that was created",
agent=agent,
expected_output="File deletion confirmation"
)
]
# Create and run process
process = PraisonAIAgents(
agents=[agent],
tasks=tasks,
verbose=True
)
print("\n🚀 Starting process with approval-required operations...")
print("You will be prompted to approve each dangerous operation.")
print()
try:
result = process.start()
print(f"\n✅ Process completed successfully!")
print(f"Final result: {result}")
except KeyboardInterrupt:
print("\n❌ Process cancelled by user")
except Exception as e:
print(f"\n❌ Process failed: {e}")
def demo_manual_approval_configuration():
"""Demonstrate how to manually configure approval requirements."""
print("\n📋 Manual Approval Configuration Demo")
print("=" * 40)
# Add approval requirement for a normally safe operation
add_approval_requirement("list_files", "medium")
print("✅ Added approval requirement for 'list_files' (medium risk)")
# Remove approval requirement for a normally dangerous operation
remove_approval_requirement("write_file")
print("✅ Removed approval requirement for 'write_file'")
# Show current approval requirements
from praisonaiagents.approval import APPROVAL_REQUIRED_TOOLS, TOOL_RISK_LEVELS
print(f"\nCurrent approval-required tools: {list(APPROVAL_REQUIRED_TOOLS)}")
print(f"Tool risk levels: {TOOL_RISK_LEVELS}")
async def async_demo():
"""Demonstrate approval system with async operations."""
print("\n🔄 Async Approval Demo")
print("=" * 25)
# Create async agent
agent = Agent(
name="Async Demo Agent",
role="Async Operations Specialist",
goal="Demonstrate async approval workflow",
tools=[python_tools],
verbose=True
)
# This would trigger approval in async context
task = Task(
description="Execute async Python code that prints 'Hello from async!'",
agent=agent,
expected_output="Async execution result"
)
process = PraisonAIAgents(
agents=[agent],
tasks=[task],
verbose=True
)
try:
result = await process.astart()
print(f"✅ Async process completed: {result}")
except Exception as e:
print(f"❌ Async process failed: {e}")
if __name__ == "__main__":
print(__doc__)
# Run the main demo
main()
# Show manual configuration options
demo_manual_approval_configuration()
# Demonstrate async approval (optional)
print("\n" + "=" * 50)
print("Would you like to run the async demo as well? (y/n): ", end="")
try:
if input().lower().startswith('y'):
asyncio.run(async_demo())
except KeyboardInterrupt:
print("\nDemo cancelled.")
print("\n🎉 Human approval demo completed!")
print("\nKey takeaways:")
print("- Dangerous operations automatically prompt for approval")
print("- Risk levels help prioritize approval decisions")
print("- Custom approval callbacks allow for automated policies")
print("- Both sync and async operations are supported")
print("- Approval requirements can be configured at runtime")
+9
View File
@@ -0,0 +1,9 @@
from praisonaiagents import Agent, MCP
search_agent = Agent(
instructions="""You help search the web.""",
llm="gpt-4o-mini",
tools=MCP("http://localhost:8931/sse")
)
search_agent.start("Find about Praison AI")
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""
Graph Knowledge Example
This example shows how to use graph memory with the Knowledge class
for enhanced document processing and relationship extraction.
Requirements:
pip install "praisonaiagents[graph]"
"""
import os
from praisonaiagents.knowledge import Knowledge
def main():
print("📚 Graph Knowledge Example")
print("=" * 40)
# Configuration with graph memory support
config_with_graph = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "graph_knowledge_test",
"path": ".praison/graph_knowledge"
}
},
"graph_store": {
"provider": "memgraph", # or "neo4j"
"config": {
"url": "bolt://localhost:7687",
"username": "memgraph",
"password": ""
}
},
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini"
}
},
"embedder": {
"provider": "openai",
"config": {
"model": "text-embedding-3-small"
}
},
"reranker": {
"enabled": True,
"default_rerank": True
}
}
# Fallback configuration (without graph store)
config_basic = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "basic_knowledge_test",
"path": ".praison/basic_knowledge"
}
},
"reranker": {
"enabled": True,
"default_rerank": True
}
}
try:
# Try graph-enabled knowledge first
knowledge = Knowledge(config=config_with_graph, verbose=1)
print("✅ Graph memory enabled!")
graph_mode = True
except Exception as e:
print(f"⚠️ Graph memory not available: {e}")
print("📦 Falling back to vector-only memory...")
knowledge = Knowledge(config=config_basic, verbose=1)
graph_mode = False
# Sample documents with rich relationship information
documents = [
"""
OpenAI is an artificial intelligence research company founded in 2015.
The company was co-founded by Sam Altman, Greg Brockman, and Elon Musk,
among others. Sam Altman serves as the CEO, while Greg Brockman is the
President and Chairman. Elon Musk was initially involved but left the
board in 2018.
""",
"""
GPT-4 is OpenAI's most advanced language model, released in March 2023.
It powers ChatGPT Plus and is available through OpenAI's API. GPT-4
demonstrates improved reasoning and reduced hallucinations compared to
its predecessor GPT-3.5. The model uses transformer architecture and
was trained using reinforcement learning from human feedback (RLHF).
""",
"""
Microsoft has a significant partnership with OpenAI, having invested
billions of dollars in the company. This partnership allows Microsoft
to integrate OpenAI's models into products like Microsoft Copilot,
which is built into Office 365 and Windows. The collaboration gives
Microsoft exclusive access to GPT models for certain use cases.
""",
"""
Anthropic is a major competitor to OpenAI, founded by former OpenAI
researchers including Dario Amodei and Daniela Amodei. Anthropic
created Claude, an AI assistant that competes with ChatGPT. The company
focuses on AI safety research and constitutional AI training methods.
"""
]
print(f"\n📥 Adding {len(documents)} documents to knowledge base...")
# Add documents to knowledge base
for i, doc in enumerate(documents, 1):
try:
result = knowledge.add(doc, user_id="demo_user")
print(f"✓ Document {i} added successfully")
if graph_mode:
print(f" Graph relationships extracted and stored")
except Exception as e:
print(f"❌ Error adding document {i}: {e}")
print("\n🔍 Testing relationship-aware queries...")
# Test queries that benefit from graph relationships
queries = [
"Who founded OpenAI?",
"What is the relationship between OpenAI and Microsoft?",
"How does GPT-4 compare to other models?",
"Who are OpenAI's competitors?",
"What role does Sam Altman play at OpenAI?",
"How is Anthropic related to OpenAI?"
]
for query in queries:
print(f"\n❓ Query: {query}")
try:
results = knowledge.search(query, user_id="demo_user", limit=2)
if results and hasattr(results, '__iter__'):
for i, result in enumerate(results, 1):
if hasattr(result, 'get'):
content = result.get('memory', result.get('text', str(result)))
else:
content = str(result)
# Truncate long responses
if len(content) > 200:
content = content[:197] + "..."
print(f" {i}. {content}")
else:
print(" No results found")
except Exception as e:
print(f" ❌ Search error: {e}")
print(f"\n{'✅ Graph Knowledge Example Complete!' if graph_mode else '✅ Basic Knowledge Example Complete!'}")
print("=" * 40)
if graph_mode:
print("🧠 Graph memory successfully captured entity relationships!")
print(" Queries benefit from understanding connections between entities.")
else:
print("📦 Vector memory provided semantic search capabilities.")
print("💡 To enable graph memory, set up Neo4j or Memgraph and install:")
print(" pip install \"mem0ai[graph]\"")
if __name__ == "__main__":
if not os.getenv("OPENAI_API_KEY"):
print("⚠️ Warning: OPENAI_API_KEY not set.")
print(" Export your key: export OPENAI_API_KEY='your-key-here'")
print(" Some features may not work without it.\n")
try:
main()
except KeyboardInterrupt:
print("\n👋 Example interrupted by user")
except Exception as e:
print(f"❌ Error: {e}")
print("💡 Make sure you have the required dependencies installed:")
print(" pip install \"praisonaiagents[graph]\"")
@@ -0,0 +1,251 @@
#!/usr/bin/env python3
"""
Graph Memory Agent Example
This example demonstrates how to use Mem0's graph memory capabilities
with PraisonAI agents to create and utilize complex relationships
between pieces of information.
Requirements:
pip install "praisonaiagents[graph]"
Setup:
- For Neo4j: Set up a Neo4j instance (local or AuraDB)
- For Memgraph: Run with Docker:
docker run -p 7687:7687 memgraph/memgraph-mage:latest --schema-info-enabled=True
"""
import os
from praisonaiagents import Agent, Task, PraisonAIAgents
def main():
# Example with Neo4j (uncomment and configure as needed)
neo4j_config = {
"provider": "mem0",
"config": {
"graph_store": {
"provider": "neo4j",
"config": {
"url": "neo4j+s://your-instance.databases.neo4j.io",
"username": "neo4j",
"password": "your-password"
}
},
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
},
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini"
}
}
}
}
# Example with Memgraph (local setup)
memgraph_config = {
"provider": "mem0",
"config": {
"graph_store": {
"provider": "memgraph",
"config": {
"url": "bolt://localhost:7687",
"username": "memgraph",
"password": ""
}
},
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
},
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini"
}
}
}
}
# Simple local configuration (fallback to vector-only memory)
local_config = {
"provider": "mem0",
"config": {
"vector_store": {
"provider": "chroma",
"config": {
"path": ".praison/graph_memory"
}
}
}
}
# Use local config by default (can be switched to neo4j_config or memgraph_config)
memory_config = local_config
print("🧠 Graph Memory Agent Example")
print("=" * 50)
# Define research agent
researcher = Agent(
name="Knowledge Researcher",
role="AI Research Specialist",
goal="Research and build knowledge graphs about AI and technology relationships",
backstory="""You are an expert at identifying relationships between concepts,
people, technologies, and ideas. You excel at creating structured knowledge
that captures complex interconnections.""",
verbose=True,
memory=True
)
# Task 1: Build initial knowledge graph
build_knowledge_task = Task(
description="""Research and store information about the relationships between:
1. OpenAI and its key products (GPT-4, ChatGPT, DALL-E)
2. Key people: Sam Altman (CEO), Greg Brockman (Co-founder)
3. Partnerships: Microsoft partnership and investment
4. Competitors: Anthropic, Google, Meta
5. Technologies: Transformer architecture, reinforcement learning
Focus on capturing the relationships and connections between these entities.
Store comprehensive information about how these elements relate to each other.""",
expected_output="Detailed knowledge graph with entities and relationships about OpenAI ecosystem",
agent=researcher
)
# Task 2: Query and expand knowledge
query_knowledge_task = Task(
description="""Based on the previously stored knowledge, answer these questions:
1. Who are the key leaders at OpenAI and what are their roles?
2. What are the main products and how do they relate to each other?
3. Who are OpenAI's main competitors and how do they compare?
4. What is the relationship between OpenAI and Microsoft?
Use the stored memory to provide comprehensive answers that leverage
the relationship information.""",
expected_output="Comprehensive answers utilizing relationship-aware memory retrieval",
agent=researcher
)
# Task 3: Add new connections
expand_knowledge_task = Task(
description="""Add new information to the knowledge graph:
1. Elon Musk was a co-founder of OpenAI but left the board
2. OpenAI started as a non-profit but created a capped-profit subsidiary
3. GPT-4 powers ChatGPT and is also available via API
4. DALL-E uses diffusion models for image generation
5. OpenAI's research influences the broader AI field
Ensure these new facts are connected to the existing knowledge graph.""",
expected_output="Updated knowledge graph with new relationships and connections",
agent=researcher
)
# Run the multi-agent system with graph memory
agents_system = PraisonAIAgents(
agents=[researcher],
tasks=[build_knowledge_task, query_knowledge_task, expand_knowledge_task],
verbose=1,
memory=True,
memory_config=memory_config
)
print("\n🚀 Starting graph memory demonstration...")
result = agents_system.start()
print("\n✅ Graph Memory Example Complete!")
print("=" * 50)
print("The agent has built and queried a knowledge graph that captures")
print("complex relationships between entities, demonstrating how graph memory")
print("enhances traditional vector-based memory with relationship awareness.")
return result
def test_direct_memory_api():
"""
Direct example using the Memory class with graph support
"""
print("\n🔬 Direct Memory API Test")
print("=" * 30)
from praisonaiagents.memory import Memory
# Configure memory with graph support (using local fallback)
memory_config = {
"provider": "mem0",
"config": {
"vector_store": {
"provider": "chroma",
"config": {
"path": ".praison/test_graph_memory"
}
}
}
}
memory = Memory(config=memory_config, verbose=5)
# Test storing user memories with relationships
user_id = "alice"
memories = [
"I love hiking in the mountains",
"My friend John also loves hiking",
"John has a dog named Max",
"Max loves to go on hikes with us",
"We often hike in Yosemite National Park",
"Yosemite has beautiful waterfalls"
]
print("Storing memories with potential relationships...")
for memory_text in memories:
memory.store_user_memory(user_id, memory_text)
print(f"✓ Stored: {memory_text}")
print("\nSearching for relationship-aware memories...")
# Test relationship-aware searches
queries = [
"Who likes hiking?",
"Tell me about John",
"What do we know about dogs?",
"Where do they go hiking?"
]
for query in queries:
print(f"\n🔍 Query: {query}")
results = memory.search_user_memory(user_id, query, limit=3, rerank=True)
for i, result in enumerate(results, 1):
if isinstance(result, dict):
print(f" {i}. {result.get('memory', result.get('text', str(result)))}")
else:
print(f" {i}. {result}")
if __name__ == "__main__":
# Check if required environment variables are set
if not os.getenv("OPENAI_API_KEY"):
print("⚠️ Warning: OPENAI_API_KEY not set. Some features may not work.")
print(" Set your API key: export OPENAI_API_KEY='your-key-here'")
try:
# Run the main agent example
main()
# Run direct API test
test_direct_memory_api()
except ImportError as e:
print(f"❌ Import Error: {e}")
print("💡 Try installing graph memory support:")
print(" pip install \"praisonaiagents[graph]\"")
except Exception as e:
print(f"❌ Error: {e}")
print("💡 Make sure you have the required dependencies and configuration.")
+252
View File
@@ -0,0 +1,252 @@
import streamlit as st
from praisonaiagents import Agent
from praisonaiagents.mcp import MCP
import logging
import traceback
# Configure page
st.set_page_config(
page_title="AI Airbnb Assistant",
page_icon="🏠",
layout="wide"
)
st.title("🏠 AI Airbnb Assistant")
st.write("Enter your accommodation search query below to get started!")
# Add configuration options in sidebar
with st.sidebar:
st.header("⚙️ Configuration")
# LLM Model Selection
llm_model = st.selectbox(
"Choose LLM Model",
options=[
"gpt-4o-mini",
"gpt-4o",
"gpt-3.5-turbo",
"claude-3-5-sonnet-20241022",
"claude-3-5-haiku-20241022"
],
index=0,
help="Select the language model to use. Avoid provider/model format like 'ollama/llama3.2' in Streamlit."
)
# Debug Mode
debug_mode = st.checkbox(
"Enable Debug Mode",
value=False,
help="Enable detailed logging for troubleshooting"
)
# MCP Server Command
mcp_command = st.text_input(
"MCP Server Command",
value="npx -y @openbnb/mcp-server-airbnb --ignore-robots-txt",
help="The command to start the MCP server"
)
# Initialize session state
if "agent_initialized" not in st.session_state:
st.session_state.agent_initialized = False
st.session_state.agent = None
st.session_state.init_error = None
# Function to initialize agent
def initialize_agent():
"""Initialize the agent with proper error handling."""
try:
with st.spinner("🔄 Initializing AI agent and MCP tools..."):
if debug_mode:
st.info("🐛 Debug mode enabled - check console for detailed logs")
# Create MCP tools
mcp_tools = MCP(
mcp_command,
timeout=60,
debug=debug_mode
)
# Create agent with session state management
agent = Agent(
instructions="""You are a helpful Airbnb assistant. Help users find and book apartments.
Use the available tools to search for accommodations, get property details, and provide helpful recommendations.
Always be polite and provide comprehensive information about properties.""",
llm=llm_model,
tools=mcp_tools,
verbose=debug_mode
)
return agent, None
except Exception as e:
error_msg = f"Failed to initialize agent: {str(e)}"
if debug_mode:
error_msg += f"\n\nFull traceback:\n{traceback.format_exc()}"
return None, error_msg
# Agent initialization section
if not st.session_state.agent_initialized:
if st.button("🚀 Initialize AI Assistant", type="primary"):
agent, error = initialize_agent()
if agent:
st.session_state.agent = agent
st.session_state.agent_initialized = True
st.session_state.init_error = None
st.success("✅ AI Assistant initialized successfully!")
st.rerun()
else:
st.session_state.init_error = error
st.error(f"❌ Initialization failed: {error}")
# Show initialization status
if st.session_state.init_error:
st.error(f"❌ Initialization Error: {st.session_state.init_error}")
with st.expander("🔧 Troubleshooting Tips"):
st.markdown("""
**Common issues and solutions:**
1. **MCP Server Issues:**
- Ensure Node.js and npm are installed
- Check if the MCP server command is correct
- Try running the MCP command manually first
2. **LLM Model Issues:**
- Make sure you have API keys set as environment variables
- Avoid provider/model formats (e.g., "ollama/llama3.2") in Streamlit
- Use standard model names (e.g., "gpt-4o-mini")
3. **Network/Timeout Issues:**
- Check your internet connection
- Increase timeout if needed
- Enable debug mode for more details
4. **Environment Issues:**
- Make sure all required packages are installed
- Check Python version compatibility
""")
# Main chat interface (only show if agent is initialized)
if st.session_state.agent_initialized and st.session_state.agent:
st.success("✅ AI Assistant is ready!")
# Create two columns for better layout
col1, col2 = st.columns([2, 1])
with col1:
# Query input
query = st.text_area(
"🔍 What are you looking for?",
placeholder="e.g., 'Find a 2-bedroom apartment in Paris for next weekend' or 'Show me luxury stays in Tokyo under $200/night'",
height=100
)
# Search button
search_col1, search_col2 = st.columns([1, 4])
with search_col1:
search_button = st.button("🔍 Search", type="primary", use_container_width=True)
with col2:
# Tips and examples
with st.container():
st.markdown("### 💡 Tips")
st.markdown("""
- Be specific about location and dates
- Mention budget preferences
- Include number of guests
- Ask about amenities
""")
with st.expander("📝 Example Queries"):
st.markdown("""
- "Find a beachfront apartment in Barcelona for 4 people"
- "Show me pet-friendly stays in London under £150/night"
- "I need a place with a kitchen near downtown Tokyo"
- "Find luxury accommodations in NYC for a business trip"
""")
# Handle search
if search_button and query:
if not query.strip():
st.warning("⚠️ Please enter a search query")
else:
try:
with st.spinner("🔍 Searching for accommodations..."):
# Add query to chat history
if "chat_history" not in st.session_state:
st.session_state.chat_history = []
st.session_state.chat_history.append({"role": "user", "content": query})
# Get response from agent
result = st.session_state.agent.start(query)
st.session_state.chat_history.append({"role": "assistant", "content": result})
# Display result
st.markdown("### 🏠 Search Results")
st.markdown(result)
except Exception as e:
error_msg = f"Search failed: {str(e)}"
st.error(f"❌ {error_msg}")
if debug_mode:
st.error(f"Full traceback:\n{traceback.format_exc()}")
# Chat history section
if "chat_history" in st.session_state and st.session_state.chat_history:
st.markdown("---")
st.markdown("### 💬 Conversation History")
for i, message in enumerate(st.session_state.chat_history):
if message["role"] == "user":
st.markdown(f"**🙋 You:** {message['content']}")
else:
st.markdown(f"**🤖 Assistant:** {message['content']}")
if i < len(st.session_state.chat_history) - 1:
st.markdown("---")
# Clear history button
if st.button("🗑️ Clear History"):
st.session_state.chat_history = []
st.rerun()
else:
# Show instructions for first-time users
st.info("👆 Please initialize the AI Assistant first using the button above.")
with st.expander("ℹ️ About this Application"):
st.markdown("""
This Streamlit application demonstrates how to properly integrate MCP (Model Context Protocol)
tools with PraisonAI Agents in a Streamlit environment.
**Key Features:**
- ✅ Proper session state management for Streamlit
- ✅ Comprehensive error handling and user feedback
- ✅ Debug mode for troubleshooting
- ✅ Flexible LLM model selection
- ✅ Clean and intuitive user interface
**Technical Implementation:**
- Uses session state to prevent agent re-initialization
- Implements proper error handling for MCP setup
- Provides detailed troubleshooting guidance
- Demonstrates best practices for Streamlit + MCP integration
""")
# Footer
st.markdown("---")
st.markdown(
"""
<div style='text-align: center; color: gray;'>
Built with PraisonAI Agents + MCP •
<a href='https://github.com/MervinPraison/PraisonAI' target='_blank'>GitHub</a> •
<a href='https://docs.praisonai.com' target='_blank'>Documentation</a>
</div>
""",
unsafe_allow_html=True
)
@@ -0,0 +1,54 @@
import streamlit as st
from praisonaiagents import Agent
from praisonaiagents.mcp import MCP
st.title("AI Airbnb")
st.write("Enter your research query below to get started!")
# CRITICAL: Initialize agent in session state to prevent re-initialization
if "agent" not in st.session_state:
try:
with st.spinner("Initializing AI agent..."):
# Initialize agent only once and store in session state
st.session_state.agent = Agent(
instructions="You help book apartments on Airbnb.",
llm="gpt-4o-mini", # ✅ Use standard format instead of "ollama/llama3.2"
tools=MCP("npx -y @openbnb/mcp-server-airbnb --ignore-robots-txt"),
verbose=True # Enable debugging
)
st.success("✅ Agent initialized successfully!")
except Exception as e:
st.error(f"❌ Failed to initialize agent: {str(e)}")
st.stop()
# Create the input field
query = st.text_input("Query")
# Add a search button
if st.button("Search"):
if query:
try:
with st.spinner("Researching..."):
# Use the agent from session state (already initialized)
result = st.session_state.agent.start(query)
st.write(result)
except Exception as e:
st.error(f"❌ Search failed: {str(e)}")
else:
st.warning("Please enter a query")
# Add troubleshooting information
with st.expander("🔧 Troubleshooting"):
st.markdown("""
**Key differences from problematic code:**
1. **Session State**: Agent is initialized once in `st.session_state.agent`
2. **LLM Format**: Using `"gpt-4o-mini"` instead of `"ollama/llama3.2"`
3. **Error Handling**: Proper try-catch blocks with user feedback
4. **Initialization Check**: Agent only created once, not on every interaction
**If you still have issues:**
- Make sure you have Node.js installed for the MCP server
- Set your OpenAI API key: `export OPENAI_API_KEY="your-key"`
- Try the debug mode by setting `verbose=True`
""")
@@ -0,0 +1,81 @@
# Domain Context Solution
This example demonstrates how to solve domain context issues in multi-agent systems using existing PraisonAI features.
## Problem
In hierarchical multi-agent workflows, tools often default to generic values like "example.com" instead of using user-specified domains. This happens because domain context doesn't automatically reach the tool execution level.
## Solution: 4-Layer Context Approach
This solution uses four existing PraisonAI features to ensure domain context is properly maintained:
### 1. **Custom Tool Wrappers**
```python
def create_domain_aware_tools(target_domain: str):
def query_fofa(query: str = None) -> dict:
if query is None or query == "example.com":
query = target_domain
# Tool implementation with domain injection
```
### 2. **Agent Instructions**
```python
agent = Agent(
instructions=f"""
CRITICAL DOMAIN CONTEXT: You are working exclusively with the domain '{domain}'.
All operations, tool calls, and analysis must focus on this specific domain.
Never use example.com or generic examples - always use {domain}.
""",
tools=domain_tools
)
```
### 3. **Task Context Parameter**
```python
task = Task(
description=f"Analyze domain {domain}",
context=[domain_context.get_context()], # Pass domain context
agent=agent
)
```
### 4. **Shared Memory**
```python
agents = PraisonAIAgents(
agents=[agent1, agent2],
tasks=[task1, task2],
memory=True, # Enable shared memory for context
memory_config={
"provider": "rag",
"config": {"collection_name": f"{domain}_analysis"}
}
)
```
## Usage
```python
from domain_context_solution import main
# Set your target domain
domain = "your-domain.com"
# Run the hierarchical workflow
main()
```
## Key Benefits
- ✅ **No Core SDK Changes** - Uses only existing framework features
- ✅ **Multi-Layer Reliability** - Context maintained through multiple mechanisms
- ✅ **Domain-Specific Results** - Tools focus on specified domain
- ✅ **Backward Compatible** - Doesn't break existing functionality
- ✅ **Scalable Pattern** - Works for any domain or context type
## Files
- `domain-context-solution.py` - Complete working solution
- `README-domain-context.md` - This documentation
This pattern can be adapted for any context passing scenario in PraisonAI multi-agent systems.
@@ -0,0 +1,234 @@
# Secondary Market Research Agent System
A comprehensive multi-agent system for generating customized secondary market research reports with FastAPI integration.
## 🎯 Features
- **Multi-Agent Research System**: Specialized agents for different research areas
- **Customizable Parameters**: Company, geography, industry, and research sections
- **Comprehensive Reports**: Market overview, competitive analysis, financial performance, growth opportunities, and risk assessment
- **FastAPI Integration**: Production-ready REST API
- **Professional Output**: Business-ready reports suitable for decision-making
## 📁 Files
- `secondary-market-research.py` - Main Python implementation
- `secondary-market-research-api.py` - FastAPI web service
- `secondary_market_research_agents.yaml` - YAML configuration
- `secondary_market_research_agents.ipynb` - Jupyter notebook examples
## 🚀 Quick Start
### 1. Python Implementation
```python
from praisonaiagents import Agent, Task, PraisonAIAgents, Tools
import asyncio
# Configure research parameters
config = MarketResearchConfig(
company="Tesla",
geography="North America",
industry="Electric Vehicles",
sections=["market_overview", "competitive_analysis", "financial_performance"]
)
# Run research
results = await run_market_research(config)
```
### 2. FastAPI Web Service
```bash
# Start the API server
uvicorn secondary-market-research-api:app --reload --port 8000
# Visit API documentation
http://localhost:8000/docs
```
### 3. YAML Configuration
```yaml
framework: "crewai"
topic: "Secondary Market Research Analysis"
variables:
company: "Tesla"
geography: "North America"
industry: "Electric Vehicles"
roles:
market_overview_specialist:
role: "Market Overview Specialist"
# ... configuration details
```
## 📊 Research Sections
Choose from the following research sections:
- **market_overview**: Market size, trends, and growth drivers
- **competitive_analysis**: Competitor analysis and market positioning
- **financial_performance**: Financial metrics and benchmarking
- **growth_opportunities**: Strategic growth vectors and opportunities
- **risk_assessment**: Risk factors and mitigation strategies
## 🌍 Use Cases
### Technology Companies
```python
config = MarketResearchConfig(
company="OpenAI",
geography="United States",
industry="Artificial Intelligence",
sections=["market_overview", "competitive_analysis", "growth_opportunities"]
)
```
### Automotive Industry
```python
config = MarketResearchConfig(
company="BMW",
geography="Europe",
industry="Luxury Automobiles",
sections=["market_overview", "competitive_analysis", "financial_performance", "risk_assessment"]
)
```
### Healthcare/Pharmaceuticals
```python
config = MarketResearchConfig(
company="Pfizer",
geography="Global",
industry="Pharmaceuticals",
sections=["market_overview", "competitive_analysis", "growth_opportunities", "risk_assessment"]
)
```
## 🔧 API Endpoints
### Generate Research Report
```http
POST /research/generate
Content-Type: application/json
{
"company": "Tesla",
"geography": "North America",
"industry": "Electric Vehicles",
"sections": ["market_overview", "competitive_analysis"],
"format": "json"
}
```
### Check Job Status
```http
GET /research/status/{job_id}
```
### Download Report
```http
GET /research/reports/{job_id}
```
### Get Templates
```http
GET /research/templates
```
## 📈 Output Format
The system generates comprehensive reports with the following structure:
```json
{
"metadata": {
"job_id": "uuid",
"company": "Tesla",
"geography": "North America",
"industry": "Electric Vehicles",
"generated_at": "2024-01-01T00:00:00Z"
},
"executive_summary": "...",
"research_findings": {
"market_overview_research": {
"content": "Market analysis...",
"agent": "Market Overview Specialist"
},
"competitive_analysis": {
"content": "Competitive intelligence...",
"agent": "Competitive Intelligence Analyst"
}
}
}
```
## 🛠️ Installation
```bash
pip install praisonai[crewai]
pip install fastapi uvicorn
```
## ⚙️ Configuration
Set your API keys:
```bash
export OPENAI_API_KEY="your-api-key"
export OPENAI_MODEL_NAME="gpt-4o-mini"
```
## 📝 Customization
### Adding New Research Sections
1. Create a new agent in `create_market_research_agents()`
2. Add corresponding task in `create_research_tasks()`
3. Update the sections list in configuration
### Geographic Regions
Supported regions include:
- North America
- Europe
- Asia Pacific
- Global
- Custom regions
### Industries
The system works across industries:
- Technology
- Automotive
- Healthcare/Pharmaceuticals
- Financial Services
- Retail/E-commerce
- Energy
- Manufacturing
- And more...
## 🎓 Examples
See the Jupyter notebook `secondary_market_research_agents.ipynb` for detailed examples including:
- YAML-based configuration
- Python API usage
- FastAPI integration
- Multiple industry examples
- Customization patterns
## 🚀 Production Deployment
For production use:
1. Deploy FastAPI with proper ASGI server (Gunicorn + Uvicorn)
2. Use Redis or database for job status storage
3. Implement proper error handling and logging
4. Add authentication and rate limiting
5. Set up monitoring and alerting
## 📄 License
This project is part of PraisonAI and follows the same licensing terms.
@@ -0,0 +1,321 @@
"""
SOLUTION: Domain Context Issue Fix using Existing PraisonAI Features
This solution fixes the issue where agents default to "example.com" instead of using
the specified domain "eenadu.net" by leveraging existing PraisonAI context features.
Key improvements:
1. Domain context passed through task context parameter
2. Agent instructions include domain-specific guidance
3. Custom tool wrappers that inject domain context
4. Shared memory for consistent domain context
No core SDK changes required - uses only existing framework features.
"""
import os
import requests
import json
import base64
from dotenv import load_dotenv
from qdrant_client import QdrantClient
from praisonaiagents import Agent, Task, PraisonAIAgents, Memory
# Load environment variables from .env.local
if not load_dotenv(dotenv_path=".env.local"):
raise FileNotFoundError(".env.local file not found. Please ensure it exists in the working directory.")
# API Keys
api_keys = {
"WHOXY_API_KEY": os.getenv("WHOXY_API_KEY"),
"FOFA_API_KEY": os.getenv("FOFA_API_KEY"),
"API_NINJAS_KEY": os.getenv("API_NINJAS_KEY"),
"QDRANT_API_KEY": os.getenv("QDRANT_API_KEY"),
}
# Debug API Keys
for key, value in api_keys.items():
if not value:
raise ValueError(f"Missing API key: {key}. Ensure it is set in the environment variables or .env.local.")
# Qdrant Configuration
qdrant_url = "https://d32d17dc-6b1d-4e13-9bb5-94a4d9f4b457.europe-west3-0.gcp.cloud.qdrant.io:6333"
qdrant_client = QdrantClient(url=qdrant_url, api_key=api_keys["QDRANT_API_KEY"])
# Domain Configuration - This is the key to solving the context issue
class DomainContext:
def __init__(self, target_domain: str):
self.target_domain = target_domain
self.context_description = f"DOMAIN CONTEXT: All tool operations must focus exclusively on the domain '{target_domain}'. Never use example.com or any other domain."
def get_context(self):
return {
"domain": self.target_domain,
"instructions": self.context_description,
"constraints": [f"Only analyze {self.target_domain}", "No generic examples", "Domain-specific results only"]
}
# Initialize domain context
domain = "eenadu.net"
domain_context = DomainContext(domain)
# SOLUTION 1: Custom Tool Wrappers with Domain Context Injection
def create_domain_aware_tools(target_domain: str):
"""Create domain-aware versions of tools that inject the target domain"""
def query_fofa(query: str = None) -> dict:
# Use target domain if no specific query provided
if query is None or query == "example.com":
query = target_domain
# Ensure domain context is considered
enhanced_query = f"{target_domain} {query}" if target_domain not in query else query
base64_query = base64.b64encode(enhanced_query.encode("utf-8")).decode("utf-8")
response = requests.get(
f"https://fofa.info/api/v1/search/all?key={api_keys['FOFA_API_KEY']}&qbase64={base64_query}&size=100&fields=jarm,host,domain,title,ip,port,protocol,base_protocol,country,country_name,region,city,longitude,latitude,as_number,as_organization,os,server,icp,header,banner,cert,link,certs_issuer_org,certs_issuer_cn,certs_subject_org,certs_subject_cn,tls_ja3s,tls_version"
)
return response.json()
def query_crtsh(domain_param: str = None) -> list:
# Use target domain if no specific domain provided
if domain_param is None or domain_param == "example.com":
domain_param = target_domain
response = requests.get(f"https://crt.sh/?q={domain_param}&output=json")
return response.json()
def query_whoxy(email_or_org: str = None) -> dict:
# Use target domain if no specific parameter provided
if email_or_org is None or email_or_org == "example.com":
email_or_org = target_domain
response = requests.get(
f"https://api.whoxy.com/?key={api_keys['WHOXY_API_KEY']}&reverse=whois&search={email_or_org}"
)
return response.json()
def query_api_ninjas(domain_param: str = None) -> dict:
# Use target domain if no specific domain provided
if domain_param is None or domain_param == "example.com":
domain_param = target_domain
response = requests.get(
f"https://api.api-ninjas.com/v1/dnslookup?domain={domain_param}",
headers={"X-Api-Key": api_keys["API_NINJAS_KEY"]},
)
return response.json()
def query_networkcalc(domain_param: str = None) -> dict:
# Use target domain if no specific domain provided
if domain_param is None or domain_param == "example.com":
domain_param = target_domain
response = requests.get(f"https://networkcalc.com/api/dns/lookup/{domain_param}")
return response.json()
return [query_fofa, query_crtsh, query_whoxy, query_api_ninjas, query_networkcalc]
# Create domain-aware tools
domain_tools = create_domain_aware_tools(domain)
def perform_reverse_lookups(domain: str, manager_memory: dict):
"""Perform WHOXY lookups and store related domains."""
reverse_data = domain_tools[2](domain) # Use domain-aware whoxy tool
email = reverse_data.get("email_address", "")
organization = reverse_data.get("organization_name", "")
owner = reverse_data.get("owner_name", "")
# Perform additional reverse lookups
related_by_email = domain_tools[2](email) if email else {}
related_by_org = domain_tools[2](organization) if organization else {}
related_by_owner = domain_tools[2](owner) if owner else {}
# Filter out unrelated domains
in_scope_domains = set()
for lookup_data in [related_by_email, related_by_org, related_by_owner]:
for domain_info in lookup_data.get("searchResult", {}).get("domains", []):
if domain in domain_info or is_in_scope(domain_info):
in_scope_domains.add(domain_info)
# Store results in memory
manager_memory["related_domains"] = list(in_scope_domains)
def is_in_scope(domain: str) -> bool:
"""Filter domains based on scope criteria."""
out_of_scope_keywords = ["example.com", "google.com", "microsoft.com", "cloudflare.com"]
return not any(keyword in domain for keyword in out_of_scope_keywords)
# SOLUTION 2: Agents with Domain-Specific Instructions
manager_agent = Agent(
name="Manager Agent",
role="Coordinate data collection and refinement",
goal=f"Collect user input, oversee tasks, and perform WHOXY lookups for related domains specifically for {domain}.",
backstory=f"Strategically manages all agents and ensures focused execution on the domain {domain}.",
instructions=f"""
CRITICAL DOMAIN CONTEXT: You are working exclusively with the domain '{domain}'.
All operations, tool calls, and analysis must focus on this specific domain.
When coordinating with other agents, ensure they understand the domain context.
Never use example.com or generic examples - always use {domain}.
Your primary responsibility is to ensure all agents maintain focus on {domain} throughout the workflow.
""",
tools=[],
verbose=True,
llm="gpt-4o",
markdown=True,
memory={"short_term": {"target_domain": domain}, "long_term": qdrant_client},
)
tool_agent = Agent(
name="Tool Agent",
role="Run initial tool queries",
goal=f"Gather subdomains, DNS records, reverse WHOIS data, and FOFA data specifically for {domain}.",
backstory=f"Tool specialist focused exclusively on analyzing the domain {domain}. Expert in running information-gathering queries using all available tools.",
instructions=f"""
CRITICAL DOMAIN CONTEXT: Your exclusive focus is the domain '{domain}'.
When using any tool:
1. Always use '{domain}' as the target domain
2. Never use example.com or any other domain
3. If a tool asks for a domain parameter, provide '{domain}'
4. If a tool asks for a query, include '{domain}' in the query
5. All searches and analysis must be domain-specific to '{domain}'
Remember: You are a specialist for {domain} only. No generic examples or other domains.
""",
tools=domain_tools, # Use domain-aware tools
verbose=True,
llm="gpt-4o",
markdown=True,
memory={"short_term": {"target_domain": domain}, "long_term": qdrant_client},
)
sr_pentester_agent = Agent(
name="Senior Pentester",
role="Analyze tool results for vulnerabilities",
goal=f"Investigate vulnerabilities and organize data for deep dives specifically for {domain}.",
backstory=f"An expert in penetration testing and data correlation with deep expertise in analyzing {domain}.",
instructions=f"""
CRITICAL DOMAIN CONTEXT: You are analyzing security findings for the domain '{domain}' exclusively.
When reviewing tool results:
1. Focus only on findings related to '{domain}'
2. Filter out any results from other domains
3. Prioritize vulnerabilities specific to '{domain}'
4. Organize findings with '{domain}' as the central focus
Your expertise is domain-specific to {domain}. All analysis must be relevant to this domain only.
""",
tools=[],
verbose=True,
llm="gpt-4o",
markdown=True,
memory={"short_term": {"target_domain": domain}, "long_term": qdrant_client},
)
ciso_agent = Agent(
name="CISO Agent",
role="Executive Security Analysis",
goal=f"Review findings and provide high-level recommendations specifically for {domain}.",
backstory=f"Provides strategic insights and ensures alignment with organizational goals for {domain} security posture.",
instructions=f"""
CRITICAL DOMAIN CONTEXT: You are providing executive security analysis for the domain '{domain}' exclusively.
In your strategic recommendations:
1. Focus on security posture of '{domain}' only
2. Provide recommendations specific to '{domain}'
3. Consider business impact on '{domain}' operations
4. Align security strategy with '{domain}' organizational goals
Your strategic insights must be domain-specific to {domain}. No generic recommendations.
""",
tools=[],
verbose=True,
llm="gpt-4o",
markdown=True,
memory={"short_term": {"target_domain": domain}, "long_term": qdrant_client},
)
# SOLUTION 3: Tasks with Domain Context Parameter
tool_query_task = Task(
name="tool_query_task",
description=f"Run all tools to gather initial domain data for {domain}. Use the domain {domain} exclusively for all tool operations.",
expected_output=f"Raw data from all tools for the domain {domain} stored in Qdrant.",
agent=tool_agent,
context=[domain_context.get_context()] # Pass domain context to task
)
pentester_analysis_task = Task(
name="pentester_analysis_task",
description=f"Analyze tool output for vulnerabilities and prioritize areas of focus for the domain {domain}.",
expected_output=f"Organized and prioritized security findings for {domain} stored in Qdrant.",
agent=sr_pentester_agent,
context=[domain_context.get_context(), tool_query_task] # Include domain context and previous task
)
ciso_review_task = Task(
name="ciso_review_task",
description=f"Review pentester findings and provide strategic recommendations for the domain {domain}.",
expected_output=f"High-level executive security recommendations for {domain} stored in Qdrant.",
agent=ciso_agent,
context=[domain_context.get_context(), pentester_analysis_task] # Include domain context and previous task
)
# SOLUTION 4: Agents with Shared Memory Configuration
agents = PraisonAIAgents(
agents=[manager_agent, tool_agent, sr_pentester_agent, ciso_agent],
tasks=[tool_query_task, pentester_analysis_task, ciso_review_task],
verbose=True,
process="hierarchical",
manager_llm="gpt-4o",
memory=True, # Enable shared memory for domain context
memory_config={
"provider": "rag",
"config": {
"collection_name": f"{domain.replace('.', '_')}_security_analysis"
}
}
)
# Main Execution Workflow
def main():
print(f"Welcome to the Hierarchical Pentesting Workflow for {domain}!")
# Store domain context in memory for all agents
domain_info = f"Target domain for analysis: {domain}. All operations must focus on this domain exclusively."
# Initialize shared context for all agents
for agent in [manager_agent, tool_agent, sr_pentester_agent, ciso_agent]:
agent.memory["short_term"]["domain_context"] = domain_context.get_context()
agent.memory["short_term"]["target_domain"] = domain
print(f"Domain context initialized: {domain}")
print(f"All agents configured to focus exclusively on: {domain}")
# Execute the workflow
result = agents.start()
# Serialize TaskOutput to JSON-serializable format
serialized_result = []
for task in agents.tasks:
if hasattr(task, "name") and hasattr(task, "output"):
serialized_result.append({
"task_name": task.name,
"domain": domain,
"output": task.output.to_dict() if hasattr(task.output, "to_dict") else str(task.output),
})
else:
print(f"Skipping task: {task} due to missing attributes.")
# Output results
print(f"Workflow Results for {domain}:")
print(json.dumps(serialized_result, indent=4))
# Verify domain context was maintained
print(f"\nDomain Context Verification:")
print(f"Target Domain: {domain}")
print(f"Context properly maintained: {'YES' if all('target_domain' in agent.memory['short_term'] for agent in agents.agents) else 'NO'}")
if __name__ == "__main__":
main()
@@ -0,0 +1,334 @@
"""
Secondary Market Research Agent System
This system creates a comprehensive market research solution with customizable
inputs for company, geography, and industry sections. It generates detailed
reports suitable for secondary market research.
Features:
- Multi-agent system with specialized research roles
- Customizable geography and company focus
- Structured report generation
- Market analysis and competitive intelligence
- FastAPI integration ready
"""
from praisonaiagents import Agent, Task, PraisonAIAgents, Tools
from typing import Dict, List, Any, Optional
import asyncio
import json
from datetime import datetime
class MarketResearchConfig:
"""Configuration class for market research parameters"""
def __init__(self,
company: str = "Tesla",
geography: str = "North America",
industry: str = "Electric Vehicles",
sections: List[str] = None):
self.company = company
self.geography = geography
self.industry = industry
self.sections = sections or [
"market_overview",
"competitive_analysis",
"financial_performance",
"growth_opportunities",
"risk_assessment"
]
self.timestamp = datetime.now().isoformat()
def create_market_research_agents(config: MarketResearchConfig):
"""Create specialized market research agents"""
# Market Overview Agent
market_overview_agent = Agent(
name="Market Overview Specialist",
role="Market Analysis Expert",
goal=f"Analyze the {config.industry} market in {config.geography}",
instructions=f"""
You are a market research specialist focused on {config.industry} in {config.geography}.
Analyze market size, trends, growth drivers, and overall market dynamics.
Focus on current market conditions and future projections.
Provide quantitative data where possible and cite reliable sources.
""",
tools=[Tools.internet_search],
verbose=True
)
# Competitive Intelligence Agent
competitive_agent = Agent(
name="Competitive Intelligence Analyst",
role="Competition Research Expert",
goal=f"Analyze {config.company}'s competitive landscape",
instructions=f"""
You are a competitive intelligence specialist analyzing {config.company}'s position
in the {config.industry} market within {config.geography}.
Identify key competitors, market share, competitive advantages, and positioning.
Analyze competitor strategies, strengths, weaknesses, and market positioning.
""",
tools=[Tools.internet_search],
verbose=True
)
# Financial Performance Agent
financial_agent = Agent(
name="Financial Performance Analyst",
role="Financial Research Expert",
goal=f"Analyze {config.company}'s financial performance and metrics",
instructions=f"""
You are a financial analyst specializing in {config.industry} companies.
Research {config.company}'s financial performance, revenue trends, profitability,
key financial metrics, and compare with industry benchmarks.
Focus on recent financial data and performance indicators.
""",
tools=[Tools.internet_search],
verbose=True
)
# Growth Opportunities Agent
growth_agent = Agent(
name="Growth Opportunities Researcher",
role="Strategic Growth Expert",
goal=f"Identify growth opportunities for {config.company}",
instructions=f"""
You are a strategic growth analyst focusing on {config.industry} in {config.geography}.
Identify emerging opportunities, market gaps, expansion possibilities,
new product/service opportunities, and strategic growth vectors for {config.company}.
Consider technological trends, regulatory changes, and market evolution.
""",
tools=[Tools.internet_search],
verbose=True
)
# Risk Assessment Agent
risk_agent = Agent(
name="Risk Assessment Specialist",
role="Risk Analysis Expert",
goal=f"Assess risks and challenges for {config.company}",
instructions=f"""
You are a risk assessment specialist for {config.industry} companies.
Identify and analyze potential risks, challenges, and threats facing {config.company}
in {config.geography}. Consider regulatory, competitive, technological,
economic, and operational risks.
""",
tools=[Tools.internet_search],
verbose=True
)
# Report Synthesizer Agent
synthesizer_agent = Agent(
name="Research Report Synthesizer",
role="Report Writing Expert",
goal="Synthesize research into comprehensive market research report",
instructions=f"""
You are an expert report writer specializing in market research reports.
Synthesize all research findings into a comprehensive, well-structured
secondary market research report for {config.company} in {config.industry}.
Create a professional report with:
- Executive Summary
- Market Overview
- Competitive Analysis
- Financial Performance Analysis
- Growth Opportunities
- Risk Assessment
- Conclusions and Recommendations
Use clear headings, bullet points, and structured formatting.
Include key insights and actionable recommendations.
""",
verbose=True
)
return {
"market_overview": market_overview_agent,
"competitive": competitive_agent,
"financial": financial_agent,
"growth": growth_agent,
"risk": risk_agent,
"synthesizer": synthesizer_agent
}
def create_research_tasks(agents: Dict[str, Agent], config: MarketResearchConfig):
"""Create research tasks for the agents"""
tasks = []
# Market Overview Task
if "market_overview" in config.sections:
market_task = Task(
name="market_overview_research",
description=f"""
Conduct comprehensive market overview research for {config.industry}
in {config.geography}. Include:
- Market size and valuation
- Growth trends and projections
- Key market drivers
- Market segments
- Regulatory environment
""",
expected_output="Detailed market overview analysis with quantitative data",
agent=agents["market_overview"],
is_start=True,
next_tasks=["competitive_analysis"] if "competitive_analysis" in config.sections else ["synthesis"]
)
tasks.append(market_task)
# Competitive Analysis Task
if "competitive_analysis" in config.sections:
competitive_task = Task(
name="competitive_analysis",
description=f"""
Analyze competitive landscape for {config.company} in {config.industry}:
- Identify top 5-7 competitors
- Market share analysis
- Competitive positioning
- Competitor strengths and weaknesses
- Competitive strategies
""",
expected_output="Comprehensive competitive intelligence report",
agent=agents["competitive"],
next_tasks=["financial_analysis"] if "financial_performance" in config.sections else ["synthesis"]
)
tasks.append(competitive_task)
# Financial Performance Task
if "financial_performance" in config.sections:
financial_task = Task(
name="financial_analysis",
description=f"""
Analyze {config.company}'s financial performance:
- Revenue trends (last 3-5 years)
- Profitability metrics
- Key financial ratios
- Industry benchmark comparison
- Financial health assessment
""",
expected_output="Detailed financial performance analysis",
agent=agents["financial"],
next_tasks=["growth_opportunities"] if "growth_opportunities" in config.sections else ["synthesis"]
)
tasks.append(financial_task)
# Growth Opportunities Task
if "growth_opportunities" in config.sections:
growth_task = Task(
name="growth_opportunities",
description=f"""
Identify growth opportunities for {config.company}:
- Market expansion opportunities
- New product/service opportunities
- Strategic partnerships potential
- Technology advancement opportunities
- Emerging market trends
""",
expected_output="Strategic growth opportunities analysis",
agent=agents["growth"],
next_tasks=["risk_assessment"] if "risk_assessment" in config.sections else ["synthesis"]
)
tasks.append(growth_task)
# Risk Assessment Task
if "risk_assessment" in config.sections:
risk_task = Task(
name="risk_assessment",
description=f"""
Assess risks and challenges for {config.company}:
- Competitive threats
- Regulatory risks
- Market risks
- Operational challenges
- Technology disruption risks
""",
expected_output="Comprehensive risk assessment report",
agent=agents["risk"],
next_tasks=["synthesis"]
)
tasks.append(risk_task)
# Synthesis Task
synthesis_task = Task(
name="synthesis",
description=f"""
Synthesize all research findings into a comprehensive secondary market
research report for {config.company} in {config.industry} ({config.geography}).
Create a professional report with clear sections, insights, and recommendations.
Focus on actionable intelligence for business decision-making.
""",
expected_output="Complete secondary market research report",
agent=agents["synthesizer"],
context=tasks # Use all previous tasks as context
)
tasks.append(synthesis_task)
return tasks
async def run_market_research(config: MarketResearchConfig) -> Dict[str, Any]:
"""Run the complete market research workflow"""
print(f"\n🔍 Starting Secondary Market Research for {config.company}")
print(f"📍 Geography: {config.geography}")
print(f"🏭 Industry: {config.industry}")
print(f"📊 Sections: {', '.join(config.sections)}")
print("=" * 60)
# Create agents and tasks
agents = create_market_research_agents(config)
tasks = create_research_tasks(agents, config)
# Create workflow
workflow = PraisonAIAgents(
agents=list(agents.values()),
tasks=tasks,
process="workflow",
verbose=True
)
# Execute research
results = await workflow.astart()
return {
"config": config.__dict__,
"results": results,
"timestamp": datetime.now().isoformat()
}
# Example usage
async def main():
"""Main function demonstrating the market research system"""
# Example 1: Tesla Electric Vehicle Research
tesla_config = MarketResearchConfig(
company="Tesla",
geography="North America",
industry="Electric Vehicles",
sections=["market_overview", "competitive_analysis", "financial_performance", "growth_opportunities"]
)
tesla_results = await run_market_research(tesla_config)
print("\n📋 Research Complete!")
print("=" * 60)
# Display results
if "task_results" in tesla_results["results"]:
for task_name, result in tesla_results["results"]["task_results"].items():
if result:
print(f"\n📌 {task_name.upper()}")
print("-" * 40)
print(result.raw[:500] + "..." if len(result.raw) > 500 else result.raw)
return tesla_results
if __name__ == "__main__":
# Run the market research
results = asyncio.run(main())
# Save results to file
with open("market_research_results.json", "w") as f:
json.dump(results, f, indent=2, default=str)
print(f"\n💾 Results saved to market_research_results.json")
@@ -16,7 +16,8 @@ from ..main import (
display_self_reflection,
ReflectionOutput,
client,
adisplay_instruction
adisplay_instruction,
approval_callback
)
import inspect
import uuid
@@ -570,6 +571,35 @@ Your Goal: {self.goal}
"""
logging.debug(f"{self.name} executing tool {function_name} with arguments: {arguments}")
# Check if approval is required for this tool
from ..approval import is_approval_required, console_approval_callback, get_risk_level, mark_approved, ApprovalDecision
if is_approval_required(function_name):
risk_level = get_risk_level(function_name)
logging.info(f"Tool {function_name} requires approval (risk level: {risk_level})")
# Use global approval callback or default console callback
callback = approval_callback or console_approval_callback
try:
decision = callback(function_name, arguments, risk_level)
if not decision.approved:
error_msg = f"Tool execution denied: {decision.reason}"
logging.warning(error_msg)
return {"error": error_msg, "approval_denied": True}
# Mark as approved in context to prevent double approval in decorator
mark_approved(function_name)
# Use modified arguments if provided
if decision.modified_args:
arguments = decision.modified_args
logging.info(f"Using modified arguments: {arguments}")
except Exception as e:
error_msg = f"Error during approval process: {str(e)}"
logging.error(error_msg)
return {"error": error_msg, "approval_error": True}
# Special handling for MCP tools
# Check if tools is an MCP instance with the requested function name
from ..mcp.mcp import MCP
@@ -864,7 +894,11 @@ Your Goal: {self.goal}
if self._using_custom_llm:
try:
# Special handling for MCP tools when using provider/model format
tool_param = self.tools if tools is None else tools
# Fix: Handle empty tools list properly - use self.tools if tools is None or empty
if tools is None or (isinstance(tools, list) and len(tools) == 0):
tool_param = self.tools
else:
tool_param = tools
# Convert MCP tool objects to OpenAI format if needed
if tool_param is not None:
@@ -1414,6 +1448,21 @@ Your Goal: {self.goal}
"""Async version of execute_tool"""
try:
logging.info(f"Executing async tool: {function_name} with arguments: {arguments}")
# Check if approval is required for this tool
from ..approval import is_approval_required, request_approval
if is_approval_required(function_name):
decision = await request_approval(function_name, arguments)
if not decision.approved:
error_msg = f"Tool execution denied: {decision.reason}"
logging.warning(error_msg)
return {"error": error_msg, "approval_denied": True}
# Use modified arguments if provided
if decision.modified_args:
arguments = decision.modified_args
logging.info(f"Using modified arguments: {arguments}")
# Try to find the function in the agent's tools list first
func = None
for tool in self.tools:
@@ -0,0 +1,263 @@
"""
Human Approval Framework for PraisonAI Agents
This module provides a minimal human-in-the-loop approval system for dangerous tool operations.
It extends the existing callback system to require human approval before executing high-risk tools.
"""
import logging
import asyncio
from typing import Dict, Set, Optional, Callable, Any, Literal
from functools import wraps
from contextvars import ContextVar
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from rich.prompt import Confirm
# Global registries for approval requirements
APPROVAL_REQUIRED_TOOLS: Set[str] = set()
TOOL_RISK_LEVELS: Dict[str, str] = {}
# Risk levels
RiskLevel = Literal["critical", "high", "medium", "low"]
# Global approval callback
approval_callback: Optional[Callable] = None
# Context variable to track if we're in an approved execution context
_approved_context: ContextVar[Set[str]] = ContextVar('approved_context', default=set())
class ApprovalDecision:
"""Result of an approval request"""
def __init__(self, approved: bool, modified_args: Optional[Dict[str, Any]] = None, reason: str = ""):
self.approved = approved
self.modified_args = modified_args or {}
self.reason = reason
def set_approval_callback(callback_fn: Callable):
"""Set a custom approval callback function.
The callback should accept (function_name, arguments, risk_level) and return ApprovalDecision.
"""
global approval_callback
approval_callback = callback_fn
def mark_approved(tool_name: str):
"""Mark a tool as approved in the current context."""
approved = _approved_context.get(set())
approved.add(tool_name)
_approved_context.set(approved)
def is_already_approved(tool_name: str) -> bool:
"""Check if a tool is already approved in the current context."""
approved = _approved_context.get(set())
return tool_name in approved
def clear_approval_context():
"""Clear the approval context."""
_approved_context.set(set())
def require_approval(risk_level: RiskLevel = "high"):
"""Decorator to mark a tool as requiring human approval.
Args:
risk_level: The risk level of the tool ("critical", "high", "medium", "low")
"""
def decorator(func):
tool_name = getattr(func, '__name__', str(func))
APPROVAL_REQUIRED_TOOLS.add(tool_name)
TOOL_RISK_LEVELS[tool_name] = risk_level
@wraps(func)
def wrapper(*args, **kwargs):
# Skip approval if already approved in current context
if is_already_approved(tool_name):
return func(*args, **kwargs)
# Request approval before executing the function
try:
# Try to check if we're in an async context
try:
asyncio.get_running_loop()
# We're in an async context, but this is a sync function
# Fall back to sync approval to avoid loop conflicts
raise RuntimeError("Use sync fallback in async context")
except RuntimeError:
# Either no running loop or we want sync fallback
# Use asyncio.run for clean async execution
decision = asyncio.run(request_approval(tool_name, kwargs))
except Exception as e:
# Fallback to sync approval if async fails
logging.warning(f"Async approval failed, using sync fallback: {e}")
callback = approval_callback or console_approval_callback
decision = callback(tool_name, kwargs, risk_level)
if not decision.approved:
raise PermissionError(f"Execution of {tool_name} denied: {decision.reason}")
# Mark as approved and merge modified args
mark_approved(tool_name)
kwargs.update(decision.modified_args)
return func(*args, **kwargs)
@wraps(func)
async def async_wrapper(*args, **kwargs):
# Skip approval if already approved in current context
if is_already_approved(tool_name):
return await func(*args, **kwargs)
# Request approval before executing the function
decision = await request_approval(tool_name, kwargs)
if not decision.approved:
raise PermissionError(f"Execution of {tool_name} denied: {decision.reason}")
# Mark as approved and merge modified args
mark_approved(tool_name)
kwargs.update(decision.modified_args)
return await func(*args, **kwargs)
# Return the appropriate wrapper based on function type
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return wrapper
return decorator
def console_approval_callback(function_name: str, arguments: Dict[str, Any], risk_level: str) -> ApprovalDecision:
"""Default console-based approval callback.
Displays tool information and prompts user for approval via console.
"""
console = Console()
# Create risk level styling
risk_colors = {
"critical": "bold red",
"high": "red",
"medium": "yellow",
"low": "blue"
}
risk_color = risk_colors.get(risk_level, "white")
# Display tool information
tool_info = f"[bold]Function:[/] {function_name}\n"
tool_info += f"[bold]Risk Level:[/] [{risk_color}]{risk_level.upper()}[/{risk_color}]\n"
tool_info += f"[bold]Arguments:[/]\n"
for key, value in arguments.items():
# Truncate long values for display
str_value = str(value)
if len(str_value) > 100:
str_value = str_value[:97] + "..."
tool_info += f" {key}: {str_value}\n"
console.print(Panel(
tool_info.strip(),
title="🔒 Tool Approval Required",
border_style=risk_color,
title_align="left"
))
# Get user approval
try:
approved = Confirm.ask(
f"[{risk_color}]Do you want to execute this {risk_level} risk tool?[/{risk_color}]",
default=False
)
if approved:
console.print("[green]✅ Tool execution approved[/green]")
return ApprovalDecision(approved=True, reason="User approved")
else:
console.print("[red]❌ Tool execution denied[/red]")
return ApprovalDecision(approved=False, reason="User denied")
except KeyboardInterrupt:
console.print("\n[red]❌ Tool execution cancelled by user[/red]")
return ApprovalDecision(approved=False, reason="User cancelled")
except Exception as e:
console.print(f"[red]Error during approval: {e}[/red]")
return ApprovalDecision(approved=False, reason=f"Approval error: {e}")
async def request_approval(function_name: str, arguments: Dict[str, Any]) -> ApprovalDecision:
"""Request approval for a tool execution.
Args:
function_name: Name of the function to execute
arguments: Arguments to pass to the function
Returns:
ApprovalDecision with approval status and any modifications
"""
# Check if approval is required
if function_name not in APPROVAL_REQUIRED_TOOLS:
return ApprovalDecision(approved=True, reason="No approval required")
risk_level = TOOL_RISK_LEVELS.get(function_name, "medium")
# Use custom callback if set, otherwise use console callback
callback = approval_callback or console_approval_callback
try:
# Handle async callbacks
if asyncio.iscoroutinefunction(callback):
decision = await callback(function_name, arguments, risk_level)
else:
# Run sync callback in executor to avoid blocking
loop = asyncio.get_event_loop()
decision = await loop.run_in_executor(None, callback, function_name, arguments, risk_level)
return decision
except Exception as e:
logging.error(f"Error in approval callback: {e}")
return ApprovalDecision(approved=False, reason=f"Approval callback error: {e}")
# Default dangerous tools - can be configured at runtime
DEFAULT_DANGEROUS_TOOLS = {
# Critical risk tools
"execute_command": "critical",
"kill_process": "critical",
"execute_code": "critical",
# High risk tools
"write_file": "high",
"delete_file": "high",
"move_file": "high",
"copy_file": "high",
"execute_query": "high",
# Medium risk tools
"evaluate": "medium",
"crawl": "medium",
"scrape_page": "medium",
}
def configure_default_approvals():
"""Configure default dangerous tools to require approval."""
for tool_name, risk_level in DEFAULT_DANGEROUS_TOOLS.items():
APPROVAL_REQUIRED_TOOLS.add(tool_name)
TOOL_RISK_LEVELS[tool_name] = risk_level
def add_approval_requirement(tool_name: str, risk_level: RiskLevel = "high"):
"""Dynamically add approval requirement for a tool."""
APPROVAL_REQUIRED_TOOLS.add(tool_name)
TOOL_RISK_LEVELS[tool_name] = risk_level
def remove_approval_requirement(tool_name: str):
"""Remove approval requirement for a tool."""
APPROVAL_REQUIRED_TOOLS.discard(tool_name)
TOOL_RISK_LEVELS.pop(tool_name, None)
def is_approval_required(tool_name: str) -> bool:
"""Check if a tool requires approval."""
return tool_name in APPROVAL_REQUIRED_TOOLS
def get_risk_level(tool_name: str) -> Optional[str]:
"""Get the risk level of a tool."""
return TOOL_RISK_LEVELS.get(tool_name)
# Initialize with defaults
configure_default_approvals()
@@ -17,9 +17,13 @@ class CustomMemory:
}).from_config(config)
@staticmethod
def _add_to_vector_store(self, messages, metadata, filters, infer):
def _add_to_vector_store(self, messages, metadata=None, filters=None, infer=None):
# Custom implementation that doesn't use LLM
parsed_messages = "\n".join([msg["content"] for msg in messages])
# Handle different message formats for backward compatibility
if isinstance(messages, list):
parsed_messages = "\n".join([msg.get("content", str(msg)) if isinstance(msg, dict) else str(msg) for msg in messages])
else:
parsed_messages = str(messages)
# Create a simple fact without using LLM
new_retrieved_facts = [parsed_messages]
@@ -34,7 +38,7 @@ class CustomMemory:
memory_id = self._create_memory(
data=parsed_messages,
existing_embeddings=new_message_embeddings,
metadata=metadata
metadata=metadata or {}
)
return [{
@@ -101,7 +105,11 @@ class Knowledge:
}
},
"version": "v1.1",
"custom_prompt": "Return {{\"facts\": [text]}} where text is the exact input provided and json response"
"custom_prompt": "Return {{\"facts\": [text]}} where text is the exact input provided and json response",
"reranker": {
"enabled": False,
"default_rerank": False
}
}
# If config is provided, merge it with base config
@@ -129,6 +137,14 @@ class Knowledge:
# Merge llm config if provided
if "llm" in self._config:
base_config["llm"] = self._config["llm"]
# Merge reranker config if provided
if "reranker" in self._config:
base_config["reranker"].update(self._config["reranker"])
# Merge graph_store config if provided (for graph memory support)
if "graph_store" in self._config:
base_config["graph_store"] = self._config["graph_store"]
return base_config
@cached_property
@@ -176,7 +192,22 @@ class Knowledge:
if not content:
return []
result = self.memory.add(content, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
# Try new API format first, fall back to old format for backward compatibility
try:
# Convert content to messages format for mem0 API compatibility
if isinstance(content, str):
messages = [{"role": "user", "content": content}]
else:
messages = content if isinstance(content, list) else [{"role": "user", "content": str(content)}]
result = self.memory.add(messages=messages, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
except TypeError as e:
# Fallback to old API format if messages parameter is not supported
if "unexpected keyword argument" in str(e) or "positional argument" in str(e):
self._log(f"Falling back to legacy API format due to: {e}")
result = self.memory.add(content, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
else:
raise
self._log(f"Store operation result: {result}")
return result
except Exception as e:
@@ -191,9 +222,25 @@ class Knowledge:
"""Retrieve a specific memory by ID."""
return self.memory.get(memory_id)
def search(self, query, user_id=None, agent_id=None, run_id=None):
"""Search for memories related to a query."""
return self.memory.search(query, user_id=user_id, agent_id=agent_id, run_id=run_id)
def search(self, query, user_id=None, agent_id=None, run_id=None, rerank=None, **kwargs):
"""Search for memories related to a query.
Args:
query: The search query string
user_id: Optional user ID for user-specific search
agent_id: Optional agent ID for agent-specific search
run_id: Optional run ID for run-specific search
rerank: Whether to use Mem0's advanced reranking. If None, uses config default
**kwargs: Additional search parameters to pass to Mem0 (keyword_search, filter_memories, etc.)
Returns:
List of search results, reranked if rerank=True
"""
# Use config default if rerank not explicitly specified
if rerank is None:
rerank = self.config.get("reranker", {}).get("default_rerank", False)
return self.memory.search(query, user_id=user_id, agent_id=agent_id, run_id=run_id, rerank=rerank, **kwargs)
def update(self, memory_id, data):
"""Update a memory."""
+107 -55
View File
@@ -205,6 +205,72 @@ class LLM:
}
logging.debug(f"LLM instance initialized with: {json.dumps(debug_info, indent=2, default=str)}")
def _is_ollama_provider(self) -> bool:
"""Detect if this is an Ollama provider regardless of naming convention"""
if not self.model:
return False
# Direct ollama/ prefix
if self.model.startswith("ollama/"):
return True
# Check environment variables for Ollama base URL
base_url = os.getenv("OPENAI_BASE_URL", "")
api_base = os.getenv("OPENAI_API_BASE", "")
# Common Ollama endpoints
ollama_endpoints = ["localhost:11434", "127.0.0.1:11434", ":11434"]
return any(endpoint in base_url or endpoint in api_base for endpoint in ollama_endpoints)
def _parse_tool_call_arguments(self, tool_call: Dict, is_ollama: bool = False) -> tuple:
"""
Safely parse tool call arguments with proper error handling
Returns:
tuple: (function_name, arguments, tool_call_id)
"""
try:
if is_ollama:
# Special handling for Ollama provider which may have different structure
if "function" in tool_call and isinstance(tool_call["function"], dict):
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
else:
# Try alternative format that Ollama might return
function_name = tool_call.get("name", "unknown_function")
arguments_str = tool_call.get("arguments", "{}")
arguments = json.loads(arguments_str) if arguments_str else {}
tool_call_id = tool_call.get("id", f"tool_{id(tool_call)}")
else:
# Standard format for other providers with error handling
function_name = tool_call["function"]["name"]
arguments_str = tool_call["function"]["arguments"]
arguments = json.loads(arguments_str) if arguments_str else {}
tool_call_id = tool_call["id"]
except (KeyError, json.JSONDecodeError, TypeError) as e:
logging.error(f"Error parsing tool call arguments: {e}")
function_name = tool_call.get("name", "unknown_function")
arguments = {}
tool_call_id = tool_call.get("id", f"tool_{id(tool_call)}")
return function_name, arguments, tool_call_id
def _needs_system_message_skip(self) -> bool:
"""Check if this model requires skipping system messages"""
if not self.model:
return False
# Only skip for specific legacy o1 models that don't support system messages
legacy_o1_models = [
"o1-preview", # 2024-09-12 version
"o1-mini", # 2024-09-12 version
"o1-mini-2024-09-12" # Explicit dated version
]
return self.model in legacy_o1_models
def get_response(
self,
prompt: Union[str, List[Dict]],
@@ -320,7 +386,9 @@ class LLM:
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(output_json.model_json_schema())}"
elif output_pydantic:
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(output_pydantic.model_json_schema())}"
messages.append({"role": "system", "content": system_prompt})
# Skip system messages for legacy o1 models as they don't support them
if not self._needs_system_message_skip():
messages.append({"role": "system", "content": system_prompt})
if chat_history:
messages.extend(chat_history)
@@ -470,32 +538,19 @@ class LLM:
for tool_call in tool_calls:
# Handle both object and dict access patterns
if isinstance(tool_call, dict):
# Special handling for Ollama provider which may have a different structure
if self.model and self.model.startswith("ollama/"):
try:
# Try standard format first
if "function" in tool_call and isinstance(tool_call["function"], dict):
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
else:
# Try alternative format that Ollama might return
function_name = tool_call.get("name", "unknown_function")
arguments = json.loads(tool_call.get("arguments", "{}"))
tool_call_id = tool_call.get("id", f"tool_{id(tool_call)}")
except Exception as e:
logging.error(f"Error processing Ollama tool call: {e}")
function_name = "unknown_function"
arguments = {}
tool_call_id = f"tool_{id(tool_call)}"
else:
# Standard format for other providers
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
tool_call_id = tool_call["id"]
is_ollama = self._is_ollama_provider()
function_name, arguments, tool_call_id = self._parse_tool_call_arguments(tool_call, is_ollama)
else:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
tool_call_id = tool_call.id
# Handle object-style tool calls
try:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments) if tool_call.function.arguments else {}
tool_call_id = tool_call.id
except (json.JSONDecodeError, AttributeError) as e:
logging.error(f"Error parsing object-style tool call: {e}")
function_name = "unknown_function"
arguments = {}
tool_call_id = f"tool_{id(tool_call)}"
logging.debug(f"[TOOL_EXEC_DEBUG] About to execute tool {function_name} with args: {arguments}")
tool_result = execute_tool_fn(function_name, arguments)
@@ -867,7 +922,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(output_json.model_json_schema())}"
elif output_pydantic:
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(output_pydantic.model_json_schema())}"
messages.append({"role": "system", "content": system_prompt})
# Skip system messages for legacy o1 models as they don't support them
if not self._needs_system_message_skip():
messages.append({"role": "system", "content": system_prompt})
if chat_history:
messages.extend(chat_history)
@@ -1065,32 +1122,19 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
for tool_call in tool_calls:
# Handle both object and dict access patterns
if isinstance(tool_call, dict):
# Special handling for Ollama provider which may have a different structure
if self.model and self.model.startswith("ollama/"):
try:
# Try standard format first
if "function" in tool_call and isinstance(tool_call["function"], dict):
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
else:
# Try alternative format that Ollama might return
function_name = tool_call.get("name", "unknown_function")
arguments = json.loads(tool_call.get("arguments", "{}"))
tool_call_id = tool_call.get("id", f"tool_{id(tool_call)}")
except Exception as e:
logging.error(f"Error processing Ollama tool call: {e}")
function_name = "unknown_function"
arguments = {}
tool_call_id = f"tool_{id(tool_call)}"
else:
# Standard format for other providers
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
tool_call_id = tool_call["id"]
is_ollama = self._is_ollama_provider()
function_name, arguments, tool_call_id = self._parse_tool_call_arguments(tool_call, is_ollama)
else:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
tool_call_id = tool_call.id
# Handle object-style tool calls
try:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments) if tool_call.function.arguments else {}
tool_call_id = tool_call.id
except (json.JSONDecodeError, AttributeError) as e:
logging.error(f"Error parsing object-style tool call: {e}")
function_name = "unknown_function"
arguments = {}
tool_call_id = f"tool_{id(tool_call)}"
tool_result = await execute_tool_fn(function_name, arguments)
@@ -1111,7 +1155,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
response_text = ""
# Special handling for Ollama models that don't automatically process tool results
if self.model and self.model.startswith("ollama/") and tool_result:
if self._is_ollama_provider() and tool_result:
# For Ollama models, we need to explicitly ask the model to process the tool results
# First, check if the response is just a JSON tool call
try:
@@ -1468,6 +1512,10 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
if self.stop_phrases:
params["stop"] = self.stop_phrases
# Add extra settings for provider-specific parameters (e.g., num_ctx for Ollama)
if self.extra_settings:
params.update(self.extra_settings)
# Override with any provided parameters
params.update(override_params)
@@ -1517,7 +1565,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
# Build messages list
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
# Skip system messages for legacy o1 models as they don't support them
if not self._needs_system_message_skip():
messages.append({"role": "system", "content": system_prompt})
# Add prompt to messages
if isinstance(prompt, list):
@@ -1623,7 +1673,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
# Build messages list
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
# Skip system messages for legacy o1 models as they don't support them
if not self._needs_system_message_skip():
messages.append({"role": "system", "content": system_prompt})
# Add prompt to messages
if isinstance(prompt, list):
@@ -43,12 +43,18 @@ error_logs = []
sync_display_callbacks = {}
async_display_callbacks = {}
# Global approval callback registry
approval_callback = None
# At the top of the file, add display_callbacks to __all__
__all__ = [
'error_logs',
'register_display_callback',
'register_approval_callback',
'sync_display_callbacks',
'async_display_callbacks',
'execute_callback',
'approval_callback',
# ... other exports
]
@@ -65,6 +71,15 @@ def register_display_callback(display_type: str, callback_fn, is_async: bool = F
else:
sync_display_callbacks[display_type] = callback_fn
def register_approval_callback(callback_fn):
"""Register a global approval callback function for dangerous tool operations.
Args:
callback_fn: Function that takes (function_name, arguments, risk_level) and returns ApprovalDecision
"""
global approval_callback
approval_callback = callback_fn
async def execute_callback(display_type: str, **kwargs):
"""Execute both sync and async callbacks for a given display type.
@@ -299,7 +299,11 @@ class MCP:
required_params.append(name)
# Create the function signature
params = []
# Separate required and optional parameters to ensure proper ordering
# (required parameters must come before optional parameters)
required_param_objects = []
optional_param_objects = []
for name in param_names:
is_required = name in required_params
param = inspect.Parameter(
@@ -308,7 +312,14 @@ class MCP:
default=inspect.Parameter.empty if is_required else None,
annotation=param_annotations.get(name, Any)
)
params.append(param)
if is_required:
required_param_objects.append(param)
else:
optional_param_objects.append(param)
# Combine parameters with required first, then optional
params = required_param_objects + optional_param_objects
# Create function template to be properly decorated
def template_function(*args, **kwargs):
@@ -41,6 +41,7 @@ class Memory:
- User memory (preferences/history for each user)
- Quality score logic for deciding which data to store in LTM
- Context building from multiple memory sources
- Graph memory support for complex relationship storage (via Mem0)
Config example:
{
@@ -53,9 +54,35 @@ class Memory:
"api_key": "...", # if mem0 usage
"org_id": "...",
"project_id": "...",
...
# Graph memory configuration (optional)
"graph_store": {
"provider": "neo4j" or "memgraph",
"config": {
"url": "neo4j+s://xxx" or "bolt://localhost:7687",
"username": "neo4j" or "memgraph",
"password": "xxx"
}
},
# Optional additional configurations for graph memory
"vector_store": {
"provider": "qdrant",
"config": {"host": "localhost", "port": 6333}
},
"llm": {
"provider": "openai",
"config": {"model": "gpt-4o", "api_key": "..."}
},
"embedder": {
"provider": "openai",
"config": {"model": "text-embedding-3-small", "api_key": "..."}
}
}
}
Note: Graph memory requires "mem0ai[graph]" installation and works alongside
vector-based memory for enhanced relationship-aware retrieval.
"""
def __init__(self, config: Dict[str, Any], verbose: int = 0):
@@ -78,6 +105,7 @@ class Memory:
self.provider = self.cfg.get("provider", "rag")
self.use_mem0 = (self.provider.lower() == "mem0") and MEM0_AVAILABLE
self.use_rag = (self.provider.lower() == "rag") and CHROMADB_AVAILABLE and self.cfg.get("use_embedding", False)
self.graph_enabled = False # Initialize graph support flag
# Create .praison directory if it doesn't exist
os.makedirs(".praison", exist_ok=True)
@@ -137,16 +165,49 @@ class Memory:
conn.close()
def _init_mem0(self):
"""Initialize Mem0 client for agent or user memory."""
from mem0 import MemoryClient
"""Initialize Mem0 client for agent or user memory with optional graph support."""
mem_cfg = self.cfg.get("config", {})
api_key = mem_cfg.get("api_key", os.getenv("MEM0_API_KEY"))
org_id = mem_cfg.get("org_id")
proj_id = mem_cfg.get("project_id")
if org_id and proj_id:
self.mem0_client = MemoryClient(api_key=api_key, org_id=org_id, project_id=proj_id)
# Check if graph memory is enabled
graph_config = mem_cfg.get("graph_store")
use_graph = graph_config is not None
if use_graph:
# Initialize with graph memory support
from mem0 import Memory
self._log_verbose("Initializing Mem0 with graph memory support")
# Build Mem0 config with graph store
mem0_config = {}
# Add graph store configuration
mem0_config["graph_store"] = graph_config
# Add other configurations if provided
if "vector_store" in mem_cfg:
mem0_config["vector_store"] = mem_cfg["vector_store"]
if "llm" in mem_cfg:
mem0_config["llm"] = mem_cfg["llm"]
if "embedder" in mem_cfg:
mem0_config["embedder"] = mem_cfg["embedder"]
# Initialize Memory with graph support
self.mem0_client = Memory.from_config(config_dict=mem0_config)
self.graph_enabled = True
self._log_verbose("Graph memory initialized successfully")
else:
self.mem0_client = MemoryClient(api_key=api_key)
# Use traditional MemoryClient
from mem0 import MemoryClient
self._log_verbose("Initializing Mem0 with traditional memory client")
if org_id and proj_id:
self.mem0_client = MemoryClient(api_key=api_key, org_id=org_id, project_id=proj_id)
else:
self.mem0_client = MemoryClient(api_key=api_key)
self.graph_enabled = False
def _init_chroma(self):
"""Initialize a local Chroma client for embedding-based search."""
@@ -262,13 +323,18 @@ class Memory:
query: str,
limit: int = 5,
min_quality: float = 0.0,
relevance_cutoff: float = 0.0
relevance_cutoff: float = 0.0,
rerank: bool = False,
**kwargs
) -> List[Dict[str, Any]]:
"""Search short-term memory with optional quality filter"""
self._log_verbose(f"Searching short memory for: {query}")
if self.use_mem0 and hasattr(self, "mem0_client"):
results = self.mem0_client.search(query=query, limit=limit)
# Pass rerank and other kwargs to Mem0 search
search_params = {"query": query, "limit": limit, "rerank": rerank}
search_params.update(kwargs)
results = self.mem0_client.search(**search_params)
filtered = [r for r in results if r.get("score", 1.0) >= relevance_cutoff]
return filtered
@@ -439,7 +505,9 @@ class Memory:
query: str,
limit: int = 5,
relevance_cutoff: float = 0.0,
min_quality: float = 0.0
min_quality: float = 0.0,
rerank: bool = False,
**kwargs
) -> List[Dict[str, Any]]:
"""Search long-term memory with optional quality filter"""
self._log_verbose(f"Searching long memory for: {query}")
@@ -448,7 +516,10 @@ class Memory:
found = []
if self.use_mem0 and hasattr(self, "mem0_client"):
results = self.mem0_client.search(query=query, limit=limit)
# Pass rerank and other kwargs to Mem0 search
search_params = {"query": query, "limit": limit, "rerank": rerank}
search_params.update(kwargs)
results = self.mem0_client.search(**search_params)
# Filter by quality
filtered = [r for r in results if r.get("metadata", {}).get("quality", 0.0) >= min_quality]
logger.info(f"Found {len(filtered)} results in Mem0")
@@ -595,12 +666,15 @@ class Memory:
else:
self.store_long_term(text, metadata=meta)
def search_user_memory(self, user_id: str, query: str, limit: int = 5) -> List[Dict[str, Any]]:
def search_user_memory(self, user_id: str, query: str, limit: int = 5, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]:
"""
If mem0 is used, pass user_id in. Otherwise fallback to local filter on user in metadata.
"""
if self.use_mem0 and hasattr(self, "mem0_client"):
return self.mem0_client.search(query=query, limit=limit, user_id=user_id)
# Pass rerank and other kwargs to Mem0 search
search_params = {"query": query, "limit": limit, "user_id": user_id, "rerank": rerank}
search_params.update(kwargs)
return self.mem0_client.search(**search_params)
else:
hits = self.search_long_term(query, limit=20)
filtered = []
@@ -16,6 +16,7 @@ from typing import List, Dict, Union, Optional
from pathlib import Path
import shutil
import logging
from ..approval import require_approval
class FileTools:
"""Tools for file operations including read, write, list, and information."""
@@ -41,6 +42,7 @@ class FileTools:
return error_msg
@staticmethod
@require_approval(risk_level="high")
def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> bool:
"""
Write content to a file.
@@ -134,6 +136,7 @@ class FileTools:
return {'error': error_msg}
@staticmethod
@require_approval(risk_level="high")
def copy_file(src: str, dst: str) -> bool:
"""
Copy a file from source to destination.
@@ -156,6 +159,7 @@ class FileTools:
return False
@staticmethod
@require_approval(risk_level="high")
def move_file(src: str, dst: str) -> bool:
"""
Move a file from source to destination.
@@ -178,6 +182,7 @@ class FileTools:
return False
@staticmethod
@require_approval(risk_level="high")
def delete_file(filepath: str) -> bool:
"""
Delete a file.
@@ -15,6 +15,7 @@ from importlib import util
import io
from contextlib import redirect_stdout, redirect_stderr
import traceback
from ..approval import require_approval
class PythonTools:
"""Tools for Python code execution and analysis."""
@@ -36,6 +37,7 @@ class PythonTools:
f"Run: pip install {' '.join(missing)}"
)
@require_approval(risk_level="critical")
def execute_code(
self,
code: str,
@@ -13,6 +13,7 @@ import logging
import os
import time
from typing import Dict, List, Optional, Union
from ..approval import require_approval
class ShellTools:
"""Tools for executing shell commands safely."""
@@ -31,6 +32,7 @@ class ShellTools:
"Run: pip install psutil"
)
@require_approval(risk_level="critical")
def execute_command(
self,
command: str,
@@ -146,6 +148,7 @@ class ShellTools:
logging.error(error_msg)
return []
@require_approval(risk_level="critical")
def kill_process(
self,
pid: int,
+8 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.92"
version = "0.0.96"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
requires-python = ">=3.10"
authors = [
@@ -35,6 +35,12 @@ knowledge = [
"chonkie>=1.0.2"
]
# Graph memory support (includes Mem0 with graph capabilities)
graph = [
"mem0ai[graph]>=0.1.0",
"chromadb>=1.0.0"
]
# Add LLM dependencies
llm = [
"litellm>=1.50.0",
@@ -51,6 +57,7 @@ api = [
all = [
"praisonaiagents[memory]",
"praisonaiagents[knowledge]",
"praisonaiagents[graph]",
"praisonaiagents[llm]",
"praisonaiagents[mcp]",
"praisonaiagents[api]"
+4
View File
@@ -11,5 +11,9 @@ mem0ai>=0.1.0
markitdown
chonkie>=1.0.2
# Graph memory dependencies (optional)
# Uncomment for graph memory support:
# mem0ai[graph]>=0.1.0
# Development dependencies
python-dotenv>=0.19.0
Binary file not shown.
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""
Test script for graph memory functionality
"""
from praisonaiagents.memory import Memory
from praisonaiagents.knowledge import Knowledge
def test_memory_graph_config():
"""Test memory with graph configuration"""
print("Testing Memory with graph configuration...")
# Configuration with graph store
config = {
"provider": "mem0",
"config": {
"graph_store": {
"provider": "memgraph",
"config": {
"url": "bolt://localhost:7687",
"username": "memgraph",
"password": ""
}
},
"vector_store": {
"provider": "chroma",
"config": {"path": ".test_graph_memory"}
}
}
}
try:
memory = Memory(config=config, verbose=1)
print(f"✅ Memory initialized. Graph enabled: {getattr(memory, 'graph_enabled', False)}")
# Test basic memory operations
memory.store_short_term("Alice loves hiking", {"category": "preference"})
memory.store_short_term("Alice's friend John also loves hiking", {"category": "social"})
results = memory.search_short_term("hiking", limit=2)
print(f"✅ Short-term search returned {len(results)} results")
return True
except Exception as e:
print(f"⚠️ Graph memory test failed (expected if dependencies missing): {e}")
return False
def test_knowledge_graph_config():
"""Test Knowledge with graph configuration"""
print("\nTesting Knowledge with graph configuration...")
config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "test_graph_knowledge",
"path": ".test_graph_knowledge"
}
},
"graph_store": {
"provider": "memgraph",
"config": {
"url": "bolt://localhost:7687",
"username": "memgraph",
"password": ""
}
}
}
try:
knowledge = Knowledge(config=config, verbose=0)
final_config = knowledge.config
if "graph_store" in final_config:
print("✅ Graph store configuration preserved in Knowledge")
else:
print("❌ Graph store configuration missing")
return True
except Exception as e:
print(f"⚠️ Knowledge graph test failed: {e}")
return False
def test_backward_compatibility():
"""Test that existing configurations still work"""
print("\nTesting backward compatibility...")
# Old-style configuration without graph
old_config = {
"provider": "mem0",
"config": {
"vector_store": {
"provider": "chroma",
"config": {"path": ".test_backward_compat"}
}
}
}
try:
memory = Memory(config=old_config, verbose=0)
print("✅ Backward compatibility maintained")
print(f" Graph enabled: {getattr(memory, 'graph_enabled', False)}")
return True
except Exception as e:
print(f"❌ Backward compatibility broken: {e}")
return False
if __name__ == "__main__":
print("🧪 Graph Memory Integration Tests")
print("=" * 50)
tests = [
test_memory_graph_config,
test_knowledge_graph_config,
test_backward_compatibility
]
passed = 0
for test in tests:
if test():
passed += 1
print(f"\n📊 Results: {passed}/{len(tests)} tests passed")
if passed == len(tests):
print("🎉 All tests passed!")
else:
print("⚠️ Some tests failed. This is expected if graph dependencies are not installed.")
print("\n💡 To enable full graph memory functionality:")
print(" pip install \"mem0ai[graph]\"")
print(" docker run -p 7687:7687 memgraph/memgraph-mage:latest")
+1 -1
View File
@@ -2233,7 +2233,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.92"
version = "0.0.96"
source = { editable = "." }
dependencies = [
{ name = "mcp" },
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.19.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.19.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.25.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.25.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+1 -1
View File
@@ -3,4 +3,4 @@ import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["EC_TELEMETRY"] = "false"
from .cli import PraisonAI
from .version import __version__
from .version import __version__
+3 -3
View File
@@ -39,9 +39,6 @@ LOG_EVENT_TYPES = [
app = FastAPI()
if not OPENAI_API_KEY:
raise ValueError('Missing the OpenAI API key. Please set it in the .env file.')
# Set up logging
logger = logging.getLogger(__name__)
log_level = os.getenv("LOGLEVEL", "INFO").upper()
@@ -266,6 +263,9 @@ def setup_public_url(port):
def run_server(port: int, use_public: bool = False):
"""Run the FastAPI server using uvicorn."""
if not OPENAI_API_KEY:
raise ValueError('Missing the OpenAI API key. Please set it in the .env file or configure it through the GUI.')
if use_public:
setup_public_url(port)
else:
+98 -25
View File
@@ -121,12 +121,12 @@ Tools are not available for {framework}. To use tools, install:
mode=instructor.Mode.JSON,
)
def generate(self):
def generate(self, merge=False):
"""
Generates a team structure for the specified topic.
Args:
None
merge (bool): Whether to merge with existing agents.yaml file instead of overwriting.
Returns:
str: The full path of the YAML file containing the generated team structure.
@@ -149,46 +149,119 @@ Tools are not available for {framework}. To use tools, install:
]
)
json_data = json.loads(response.model_dump_json())
self.convert_and_save(json_data)
self.convert_and_save(json_data, merge=merge)
full_path = os.path.abspath(self.agent_file)
return full_path
def convert_and_save(self, json_data):
def convert_and_save(self, json_data, merge=False):
"""Converts the provided JSON data into the desired YAML format and saves it to a file.
Args:
json_data (dict): The JSON data representing the team structure.
topic (str, optional): The topic to be inserted into the YAML. Defaults to "Artificial Intelligence".
agent_file (str, optional): The name of the YAML file to save. Defaults to "test.yaml".
merge (bool): Whether to merge with existing agents.yaml file instead of overwriting.
"""
yaml_data = {
"framework": self.framework,
"topic": self.topic,
"roles": {},
"dependencies": []
}
for role_id, role_details in json_data['roles'].items():
yaml_data['roles'][role_id] = {
"backstory": "" + role_details['backstory'],
"goal": role_details['goal'],
"role": role_details['role'],
"tasks": {},
# "tools": role_details.get('tools', []),
"tools": ['']
# Handle merge functionality
if merge and os.path.exists(self.agent_file):
yaml_data = self.merge_with_existing_agents(json_data)
else:
# Original behavior: create new yaml_data structure
yaml_data = {
"framework": self.framework,
"topic": self.topic,
"roles": {},
"dependencies": []
}
for task_id, task_details in role_details['tasks'].items():
yaml_data['roles'][role_id]['tasks'][task_id] = {
"description": "" + task_details['description'],
"expected_output": "" + task_details['expected_output']
for role_id, role_details in json_data['roles'].items():
yaml_data['roles'][role_id] = {
"backstory": "" + role_details['backstory'],
"goal": role_details['goal'],
"role": role_details['role'],
"tasks": {},
# "tools": role_details.get('tools', []),
"tools": ['']
}
for task_id, task_details in role_details['tasks'].items():
yaml_data['roles'][role_id]['tasks'][task_id] = {
"description": "" + task_details['description'],
"expected_output": "" + task_details['expected_output']
}
# Save to YAML file, maintaining the order
with open(self.agent_file, 'w') as f:
yaml.dump(yaml_data, f, allow_unicode=True, sort_keys=False)
def merge_with_existing_agents(self, new_json_data):
"""
Merge existing agents.yaml with new auto-generated agents.
Args:
new_json_data (dict): The JSON data representing the new team structure.
Returns:
dict: The merged YAML data structure.
"""
try:
# Load existing agents.yaml
with open(self.agent_file, 'r') as f:
existing_data = yaml.safe_load(f)
if not existing_data:
# If existing file is empty, treat as new file
existing_data = {"roles": {}, "dependencies": []}
except (yaml.YAMLError, FileNotFoundError) as e:
logging.warning(f"Could not load existing agents file {self.agent_file}: {e}")
logging.warning("Creating new file instead of merging")
existing_data = {"roles": {}, "dependencies": []}
# Start with existing data structure
merged_data = existing_data.copy()
# Ensure required fields exist
if 'roles' not in merged_data:
merged_data['roles'] = {}
if 'dependencies' not in merged_data:
merged_data['dependencies'] = []
if 'framework' not in merged_data:
merged_data['framework'] = self.framework
# Handle topic merging
existing_topic = merged_data.get('topic', '')
new_topic = self.topic
if existing_topic and existing_topic != new_topic:
merged_data['topic'] = f"{existing_topic} + {new_topic}"
else:
merged_data['topic'] = new_topic
# Merge new roles with existing ones
for role_id, role_details in new_json_data['roles'].items():
# Check for conflicts and rename if necessary
final_role_id = role_id
counter = 1
while final_role_id in merged_data['roles']:
final_role_id = f"{role_id}_auto_{counter}"
counter += 1
# Add the new role
merged_data['roles'][final_role_id] = {
"backstory": "" + role_details['backstory'],
"goal": role_details['goal'],
"role": role_details['role'],
"tasks": {},
"tools": ['']
}
# Add tasks for this role
for task_id, task_details in role_details['tasks'].items():
merged_data['roles'][final_role_id]['tasks'][task_id] = {
"description": "" + task_details['description'],
"expected_output": "" + task_details['expected_output']
}
return merged_data
def get_user_content(self):
"""
Generates a prompt for the OpenAI API to generate a team structure.
+184 -15
View File
@@ -5,6 +5,7 @@ import argparse
from .version import __version__
import yaml
import os
import time
from rich import print
from dotenv import load_dotenv
load_dotenv()
@@ -25,6 +26,7 @@ CALL_MODULE_AVAILABLE = False
CREWAI_AVAILABLE = False
AUTOGEN_AVAILABLE = False
PRAISONAI_AVAILABLE = False
TRAIN_AVAILABLE = False
try:
# Create necessary directories and set CHAINLIT_APP_ROOT
if "CHAINLIT_APP_ROOT" not in os.environ:
@@ -54,7 +56,7 @@ except ImportError:
pass
try:
from crewai import Agent, Task, Crew
import crewai
CREWAI_AVAILABLE = True
except ImportError:
pass
@@ -71,6 +73,12 @@ try:
except ImportError:
pass
try:
from unsloth import FastLanguageModel
TRAIN_AVAILABLE = True
except ImportError:
pass
logging.basicConfig(level=os.environ.get('LOGLEVEL', 'INFO'), format='%(asctime)s - %(levelname)s - %(message)s')
logging.getLogger('alembic').setLevel(logging.ERROR)
logging.getLogger('gradio').setLevel(logging.ERROR)
@@ -147,6 +155,21 @@ class PraisonAI:
"""
return self.main()
def read_stdin_if_available(self):
"""
Read from stdin if it's available (when data is piped in).
Returns the stdin content or None if no piped input is available.
"""
try:
# Check if stdin is not a terminal (i.e., has piped input)
if not sys.stdin.isatty():
stdin_content = sys.stdin.read().strip()
return stdin_content if stdin_content else None
except Exception:
# If there's any error reading stdin, ignore it
pass
return None
def main(self):
"""
The main function of the PraisonAI object. It parses the command-line arguments,
@@ -164,27 +187,104 @@ class PraisonAI:
self.framework = args.framework or self.framework
# Check for piped input from stdin
stdin_input = self.read_stdin_if_available()
if args.command:
if args.command.startswith("tests.test") or args.command.startswith("tests/test"): # Argument used for testing purposes
print("test")
return "test"
else:
self.agent_file = args.command
# If stdin input is available, append it to the command
if stdin_input:
combined_prompt = f"{args.command} {stdin_input}"
result = self.handle_direct_prompt(combined_prompt)
print(result)
return result
else:
self.agent_file = args.command
elif hasattr(args, 'direct_prompt') and args.direct_prompt:
# Only handle direct prompt if agent_file wasn't explicitly set in constructor
if original_agent_file == "agents.yaml": # Default value, so safe to use direct prompt
result = self.handle_direct_prompt(args.direct_prompt)
# If stdin input is available, append it to the direct prompt
prompt = args.direct_prompt
if stdin_input:
prompt = f"{args.direct_prompt} {stdin_input}"
result = self.handle_direct_prompt(prompt)
print(result)
return result
else:
# Agent file was explicitly set, ignore direct prompt and use the file
pass
elif stdin_input:
# If only stdin input is provided (no command), use it as direct prompt
if original_agent_file == "agents.yaml": # Default value, so safe to use stdin as prompt
result = self.handle_direct_prompt(stdin_input)
print(result)
return result
# If no command or direct_prompt, preserve agent_file from constructor (don't overwrite)
if args.deploy:
from .deploy import CloudDeployer
deployer = CloudDeployer()
deployer.run_commands()
if args.schedule or args.schedule_config:
# Scheduled deployment
from .scheduler import create_scheduler
# Load configuration from file if provided
config = {"max_retries": args.max_retries}
schedule_expr = args.schedule
provider = args.provider
if args.schedule_config:
try:
with open(args.schedule_config, 'r') as f:
file_config = yaml.safe_load(f)
# Extract deployment config
deploy_config = file_config.get('deployment', {})
schedule_expr = schedule_expr or deploy_config.get('schedule')
provider = deploy_config.get('provider', provider)
config['max_retries'] = deploy_config.get('max_retries', config['max_retries'])
# Apply environment variables if specified
env_vars = file_config.get('environment', {})
for key, value in env_vars.items():
os.environ[key] = str(value)
except FileNotFoundError:
print(f"Configuration file not found: {args.schedule_config}")
sys.exit(1)
except yaml.YAMLError as e:
print(f"Error parsing configuration file: {e}")
sys.exit(1)
if not schedule_expr:
print("Error: Schedule expression required. Use --schedule or specify in config file.")
sys.exit(1)
scheduler = create_scheduler(provider=provider, config=config)
print(f"Starting scheduled deployment with schedule: {schedule_expr}")
print(f"Provider: {provider}")
print(f"Max retries: {config['max_retries']}")
print("Press Ctrl+C to stop the scheduler")
if scheduler.start(schedule_expr, config['max_retries']):
try:
# Keep the main thread alive
while scheduler.is_running:
time.sleep(1)
except KeyboardInterrupt:
print("\nStopping scheduler...")
scheduler.stop()
print("Scheduler stopped successfully")
else:
print("Failed to start scheduler")
sys.exit(1)
else:
# One-time deployment (backward compatible)
from .deploy import CloudDeployer
deployer = CloudDeployer()
deployer.run_commands()
return
if getattr(args, 'chat', False):
@@ -207,6 +307,11 @@ class PraisonAI:
return
if args.command == 'train':
if not TRAIN_AVAILABLE:
print("[red]ERROR: Training dependencies not installed. Install with:[/red]")
print("\npip install \"praisonai[train]\"")
print("Or run: praisonai train init\n")
sys.exit(1)
package_root = os.path.dirname(os.path.abspath(__file__))
config_yaml_destination = os.path.join(os.getcwd(), 'config.yaml')
@@ -248,17 +353,29 @@ class PraisonAI:
print("All packages installed")
return
# Check if conda is available and environment exists
conda_available = True
conda_env_exists = False
try:
result = subprocess.check_output(['conda', 'env', 'list'])
if 'praison_env' in result.decode('utf-8'):
print("Conda environment 'praison_env' found.")
conda_env_exists = True
else:
raise subprocess.CalledProcessError(1, 'grep')
except subprocess.CalledProcessError:
print("Conda environment 'praison_env' not found. Setting it up...")
from praisonai.setup.setup_conda_env import main as setup_conda_main
setup_conda_main()
print("All packages installed.")
print("Conda environment 'praison_env' not found. Setting it up...")
from praisonai.setup.setup_conda_env import main as setup_conda_main
setup_conda_main()
print("All packages installed.")
# Check again if environment was created successfully
try:
result = subprocess.check_output(['conda', 'env', 'list'])
conda_env_exists = 'praison_env' in result.decode('utf-8')
except subprocess.CalledProcessError:
conda_env_exists = False
except (subprocess.CalledProcessError, FileNotFoundError):
print("Conda not available or failed to check environment.")
conda_available = False
train_args = sys.argv[2:] # Get all arguments after 'train'
@@ -278,7 +395,18 @@ class PraisonAI:
env = os.environ.copy()
env['PYTHONUNBUFFERED'] = '1'
stream_subprocess(['conda', 'run', '--no-capture-output', '--name', 'praison_env', 'python', '-u', train_script_path, 'train'], env=env)
# Try conda run first, fallback to direct Python execution
if conda_available and conda_env_exists:
try:
print("Attempting to run training using conda environment...")
stream_subprocess(['conda', 'run', '--no-capture-output', '--name', 'praison_env', 'python', '-u', train_script_path, 'train'], env=env)
except subprocess.CalledProcessError as e:
print(f"Conda run failed with error: {e}")
print("Falling back to direct Python execution...")
stream_subprocess([sys.executable, '-u', train_script_path, 'train'], env=env)
else:
print("Conda environment not available, using direct Python execution...")
stream_subprocess([sys.executable, '-u', train_script_path, 'train'], env=env)
return
if args.auto or self.auto:
@@ -289,7 +417,7 @@ class PraisonAI:
self.agent_file = "test.yaml"
generator = AutoGenerator(topic=self.topic, framework=self.framework, agent_file=self.agent_file)
self.agent_file = generator.generate()
self.agent_file = generator.generate(merge=getattr(args, 'merge', False))
agents_generator = AgentsGenerator(self.agent_file, self.framework, self.config_list)
result = agents_generator.generate_crew_and_kickoff()
print(result)
@@ -302,7 +430,7 @@ class PraisonAI:
self.agent_file = "agents.yaml"
generator = AutoGenerator(topic=self.topic, framework=self.framework, agent_file=self.agent_file)
self.agent_file = generator.generate()
self.agent_file = generator.generate(merge=getattr(args, 'merge', False))
print(f"File {self.agent_file} created successfully")
return f"File {self.agent_file} created successfully"
@@ -348,6 +476,36 @@ class PraisonAI:
'unittest' in sys.modules
)
# Check if we're being used as a library (not from praisonai CLI)
# Skip CLI parsing to avoid conflicts with applications like Fabric
is_library_usage = (
'praisonai' not in sys.argv[0] and
not in_test_env
)
if is_library_usage:
# Return default args when used as library to prevent CLI conflicts
default_args = argparse.Namespace()
default_args.framework = None
default_args.ui = None
default_args.auto = None
default_args.init = None
default_args.command = None
default_args.deploy = False
default_args.schedule = None
default_args.schedule_config = None
default_args.provider = "gcp"
default_args.max_retries = 3
default_args.model = None
default_args.llm = None
default_args.hf = None
default_args.ollama = None
default_args.dataset = "yahma/alpaca-cleaned"
default_args.realtime = False
default_args.call = False
default_args.public = False
return default_args
# Define special commands
special_commands = ['chat', 'code', 'call', 'realtime', 'train', 'ui']
@@ -358,6 +516,10 @@ class PraisonAI:
parser.add_argument("--init", nargs=argparse.REMAINDER, help="Initialize agents with optional topic")
parser.add_argument("command", nargs="?", help="Command to run or direct prompt")
parser.add_argument("--deploy", action="store_true", help="Deploy the application")
parser.add_argument("--schedule", type=str, help="Schedule deployment (e.g., 'daily', 'hourly', '*/6h', '3600')")
parser.add_argument("--schedule-config", type=str, help="Path to scheduling configuration file")
parser.add_argument("--provider", type=str, default="gcp", help="Deployment provider (gcp, aws, azure)")
parser.add_argument("--max-retries", type=int, default=3, help="Maximum retry attempts for scheduled deployments")
parser.add_argument("--model", type=str, help="Model name")
parser.add_argument("--llm", type=str, help="LLM model to use for direct prompts")
parser.add_argument("--hf", type=str, help="Hugging Face model name")
@@ -366,6 +528,7 @@ class PraisonAI:
parser.add_argument("--realtime", action="store_true", help="Start the realtime voice interaction interface")
parser.add_argument("--call", action="store_true", help="Start the PraisonAI Call server")
parser.add_argument("--public", action="store_true", help="Use ngrok to expose the server publicly (only with --call)")
parser.add_argument("--merge", action="store_true", help="Merge existing agents.yaml with auto-generated agents instead of overwriting")
# If we're in a test environment, parse with empty args to avoid pytest interference
if in_test_env:
@@ -451,6 +614,11 @@ class PraisonAI:
sys.exit(0)
elif args.command == 'train':
if not TRAIN_AVAILABLE:
print("[red]ERROR: Training dependencies not installed. Install with:[/red]")
print("\npip install \"praisonai[train]\"")
print("Or run: praisonai train init\n")
sys.exit(1)
package_root = os.path.dirname(os.path.abspath(__file__))
config_yaml_destination = os.path.join(os.getcwd(), 'config.yaml')
@@ -500,6 +668,7 @@ class PraisonAI:
result = agent.start(prompt)
return result
elif CREWAI_AVAILABLE:
from crewai import Agent, Task, Crew
agent_config = {
"name": "DirectAgent",
"role": "Assistant",
+1 -1
View File
@@ -56,7 +56,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.2.19 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.2.25 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+215
View File
@@ -0,0 +1,215 @@
import re
import threading
import time
import logging
from datetime import datetime, timedelta
from typing import Union, Optional, Callable, Dict, Any
from abc import ABC, abstractmethod
logger = logging.getLogger(__name__)
class DeployerInterface(ABC):
"""Abstract interface for deployers to ensure provider compatibility."""
@abstractmethod
def deploy(self) -> bool:
"""Execute deployment. Returns True on success, False on failure."""
pass
class CloudDeployerAdapter(DeployerInterface):
"""Adapter for existing CloudDeployer to match interface."""
def __init__(self):
from .deploy import CloudDeployer
self._deployer = CloudDeployer()
def deploy(self) -> bool:
"""Execute deployment using CloudDeployer."""
try:
self._deployer.run_commands()
return True
except Exception as e:
logger.error(f"Deployment failed: {e}")
return False
class ScheduleParser:
"""Parse schedule expressions into intervals."""
@staticmethod
def parse(schedule_expr: str) -> int:
"""
Parse schedule expression and return interval in seconds.
Supported formats:
- "daily" -> 86400 seconds
- "hourly" -> 3600 seconds
- "*/30m" -> 1800 seconds (every 30 minutes)
- "*/6h" -> 21600 seconds (every 6 hours)
- "60" -> 60 seconds (plain number)
"""
schedule_expr = schedule_expr.strip().lower()
if schedule_expr == "daily":
return 86400
elif schedule_expr == "hourly":
return 3600
elif schedule_expr.isdigit():
return int(schedule_expr)
elif schedule_expr.startswith("*/"):
# Handle */30m, */6h patterns
interval_part = schedule_expr[2:]
if interval_part.endswith("m"):
minutes = int(interval_part[:-1])
return minutes * 60
elif interval_part.endswith("h"):
hours = int(interval_part[:-1])
return hours * 3600
elif interval_part.endswith("s"):
return int(interval_part[:-1])
else:
return int(interval_part)
else:
raise ValueError(f"Unsupported schedule format: {schedule_expr}")
class DeploymentScheduler:
"""
Minimal deployment scheduler with provider-agnostic design.
Features:
- Simple interval-based scheduling
- Thread-safe operation
- Extensible deployer factory pattern
- Minimal dependencies
"""
def __init__(self, schedule_config: Optional[Dict[str, Any]] = None):
self.config = schedule_config or {}
self.is_running = False
self._stop_event = threading.Event()
self._thread = None
self._deployer = None
def set_deployer(self, deployer: DeployerInterface):
"""Set custom deployer implementation."""
self._deployer = deployer
def _get_deployer(self) -> DeployerInterface:
"""Get deployer instance using factory pattern."""
if self._deployer:
return self._deployer
# Default to CloudDeployer for backward compatibility
return CloudDeployerAdapter()
def start(self, schedule_expr: str, max_retries: int = 3) -> bool:
"""
Start scheduled deployment.
Args:
schedule_expr: Schedule expression (e.g., "daily", "*/6h", "3600")
max_retries: Maximum retry attempts on failure
Returns:
True if scheduler started successfully
"""
if self.is_running:
logger.warning("Scheduler is already running")
return False
try:
interval = ScheduleParser.parse(schedule_expr)
self.is_running = True
self._stop_event.clear()
self._thread = threading.Thread(
target=self._run_schedule,
args=(interval, max_retries),
daemon=True
)
self._thread.start()
logger.info(f"Deployment scheduler started with {interval}s interval")
return True
except Exception as e:
logger.error(f"Failed to start scheduler: {e}")
self.is_running = False
return False
def stop(self) -> bool:
"""Stop the scheduler."""
if not self.is_running:
return True
self._stop_event.set()
if self._thread and self._thread.is_alive():
self._thread.join(timeout=5)
self.is_running = False
logger.info("Deployment scheduler stopped")
return True
def _run_schedule(self, interval: int, max_retries: int):
"""Internal method to run scheduled deployments."""
deployer = self._get_deployer()
while not self._stop_event.is_set():
logger.info("Starting scheduled deployment")
success = False
for attempt in range(max_retries):
try:
if deployer.deploy():
logger.info(f"Deployment successful on attempt {attempt + 1}")
success = True
break
else:
logger.warning(f"Deployment failed on attempt {attempt + 1}")
except Exception as e:
logger.error(f"Deployment error on attempt {attempt + 1}: {e}")
if attempt < max_retries - 1:
time.sleep(30) # Wait before retry
if not success:
logger.error(f"Deployment failed after {max_retries} attempts")
# Wait for next scheduled time
self._stop_event.wait(interval)
def deploy_once(self) -> bool:
"""Execute a single deployment immediately."""
deployer = self._get_deployer()
try:
return deployer.deploy()
except Exception as e:
logger.error(f"One-time deployment failed: {e}")
return False
def create_scheduler(provider: str = "gcp", config: Optional[Dict[str, Any]] = None) -> DeploymentScheduler:
"""
Factory function to create scheduler for different providers.
Args:
provider: Deployment provider ("gcp", "aws", "azure", etc.)
config: Optional configuration dict
Returns:
Configured DeploymentScheduler instance
"""
scheduler = DeploymentScheduler(config)
# Provider-specific deployer setup can be added here
if provider == "gcp":
# Default CloudDeployer for GCP
pass
elif provider == "aws":
# Future: AWS deployer implementation
logger.warning("AWS provider not yet implemented, using default")
elif provider == "azure":
# Future: Azure deployer implementation
logger.warning("Azure provider not yet implemented, using default")
else:
logger.warning(f"Unknown provider {provider}, using default")
return scheduler
+1 -1
View File
@@ -72,7 +72,7 @@ async def tavily_web_search(query):
response = tavily_client.search(query)
logger.debug(f"Tavily search response: {response}")
async with AsyncWebCrawler() as crawler:
async with AsyncAsyncWebCrawler() as crawler:
results = []
for result in response.get('results', []):
url = result.get('url')
+34 -11
View File
@@ -20,6 +20,7 @@ from chainlit.input_widget import TextInput
from chainlit.types import ThreadDict
import chainlit.data as cl_data
from litellm import acompletion
import litellm
from db import DatabaseManager
# Load environment variables
@@ -40,6 +41,15 @@ logger.addHandler(console_handler)
# Set the logging level for the logger
logger.setLevel(log_level)
# Configure litellm same as in llm.py
litellm.set_verbose = False
litellm.success_callback = []
litellm._async_success_callback = []
litellm.callbacks = []
litellm.drop_params = True
litellm.modify_params = True
litellm.suppress_debug_messages = True
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
if not CHAINLIT_AUTH_SECRET:
@@ -55,6 +65,17 @@ db_manager.initialize()
deleted_thread_ids = [] # type: List[str]
def _build_completion_params(model_name, **override_params):
"""Build parameters for litellm completion calls with proper model handling"""
params = {
"model": model_name,
}
# Override with any provided parameters
params.update(override_params)
return params
def save_setting(key: str, value: str):
"""Saves a setting to the database.
@@ -153,8 +174,8 @@ async def tavily_web_search(query):
response = tavily_client.search(query)
logger.debug(f"Tavily search response: {response}")
# Create an instance of AsyncWebCrawler
async with AsyncWebCrawler() as crawler:
# Create an instance of AsyncAsyncWebCrawler
async with AsyncAsyncWebCrawler() as crawler:
# Prepare the results
results = []
for result in response.get('results', []):
@@ -237,12 +258,12 @@ Context:
msg = cl.Message(content="")
await msg.send()
# Prepare the completion parameters
completion_params = {
"model": model_name,
"messages": message_history,
"stream": True,
}
# Prepare the completion parameters using the helper function
completion_params = _build_completion_params(
model_name,
messages=message_history,
stream=True,
)
# If an image is uploaded, include it in the message
if image:
@@ -344,9 +365,11 @@ Context:
logger.error(f"Failed to parse function arguments: {function_args}")
second_response = await acompletion(
model=model_name,
stream=True,
messages=messages,
**_build_completion_params(
model_name,
stream=True,
messages=messages,
)
)
logger.debug(f"Second LLM response: {second_response}")
@@ -7,7 +7,7 @@ from litellm import acompletion
import json
import dotenv
from tavily import TavilyClient
from crawl4ai import AsyncWebCrawler
from crawl4ai import AsyncAsyncWebCrawler
dotenv.load_dotenv()
@@ -144,7 +144,7 @@ class AICoder:
})
response = self.tavily_client.search(query)
results = []
async with AsyncWebCrawler() as crawler:
async with AsyncAsyncWebCrawler() as crawler:
for result in response.get('results', []):
url = result.get('url')
if url:
+20 -8
View File
@@ -229,7 +229,7 @@ except Exception as e:
@cl.on_chat_start
async def start():
initialize_db()
model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-4o-mini-realtime-preview")
model_name = os.getenv("MODEL_NAME", "gpt-4o-mini-realtime-preview-2024-12-17")
cl.user_session.set("model_name", model_name)
cl.user_session.set("message_history", []) # Initialize message history
logger.debug(f"Model name: {model_name}")
@@ -238,7 +238,7 @@ async def start():
# TextInput(
# id="model_name",
# label="Enter the Model Name",
# placeholder="e.g., gpt-4o-mini-realtime-preview",
# placeholder="e.g., gpt-4o-mini-realtime-preview-2024-12-17",
# initial=model_name
# )
# ]
@@ -382,7 +382,8 @@ async def on_audio_start():
openai_realtime = cl.user_session.get("openai_realtime")
if not openai_realtime.is_connected():
await openai_realtime.connect()
model_name = cl.user_session.get("model_name", "gpt-4o-mini-realtime-preview-2024-12-17")
await openai_realtime.connect(model_name)
logger.info("Connected to OpenAI realtime")
return True
@@ -394,11 +395,22 @@ async def on_audio_start():
@cl.on_audio_chunk
async def on_audio_chunk(chunk: cl.InputAudioChunk):
openai_realtime: RealtimeClient = cl.user_session.get("openai_realtime")
openai_realtime: RealtimeClient = cl.user_session.get("openai_realtime")
if not openai_realtime:
logger.debug("No realtime client available")
return
if openai_realtime.is_connected():
await openai_realtime.append_input_audio(chunk.data)
try:
success = await openai_realtime.append_input_audio(chunk.data)
if not success:
logger.debug("Failed to append audio data - connection may be lost")
except Exception as e:
logger.debug(f"Error processing audio chunk: {e}")
# Optionally try to reconnect here if needed
else:
logger.info("RealtimeClient is not connected")
logger.debug("RealtimeClient is not connected - audio chunk ignored")
@cl.on_audio_end
@cl.on_chat_end
@@ -423,14 +435,14 @@ def auth_callback(username: str, password: str):
@cl.on_chat_resume
async def on_chat_resume(thread: ThreadDict):
logger.info(f"Resuming chat: {thread['id']}")
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini-realtime-preview"
model_name = os.getenv("MODEL_NAME") or "gpt-4o-mini-realtime-preview-2024-12-17"
logger.debug(f"Model name: {model_name}")
settings = cl.ChatSettings(
[
TextInput(
id="model_name",
label="Enter the Model Name",
placeholder="e.g., gpt-4o-mini-realtime-preview",
placeholder="e.g., gpt-4o-mini-realtime-preview-2024-12-17",
initial=model_name
)
]
@@ -6,6 +6,7 @@ import inspect
import numpy as np
import json
import websockets
from websockets.exceptions import ConnectionClosed
from datetime import datetime
from collections import defaultdict
import base64
@@ -97,29 +98,66 @@ class RealtimeAPI(RealtimeEventHandler):
self.ws = None
def is_connected(self):
return self.ws is not None
if self.ws is None:
return False
# Some websockets versions don't have a closed attribute
try:
return not self.ws.closed
except AttributeError:
# Fallback: check if websocket is still alive by checking state
try:
return hasattr(self.ws, 'state') and self.ws.state.name == 'OPEN'
except:
# Last fallback: assume connected if ws exists
return True
def log(self, *args):
logger.debug(f"[Websocket/{datetime.utcnow().isoformat()}]", *args)
async def connect(self, model='gpt-4o-realtime-preview-2024-10-01'):
async def connect(self, model='gpt-4o-mini-realtime-preview-2024-12-17'):
if self.is_connected():
raise Exception("Already connected")
self.ws = await websockets.connect(f"{self.url}?model={model}", extra_headers={
headers = {
'Authorization': f'Bearer {self.api_key}',
'OpenAI-Beta': 'realtime=v1'
})
}
# Try different header parameter names for compatibility
try:
self.ws = await websockets.connect(f"{self.url}?model={model}", additional_headers=headers)
except TypeError:
# Fallback to older websockets versions
try:
self.ws = await websockets.connect(f"{self.url}?model={model}", extra_headers=headers)
except TypeError:
# Last fallback - some versions might not support headers parameter
raise Exception("Websockets library version incompatible. Please update websockets to version 11.0 or higher.")
self.log(f"Connected to {self.url}")
asyncio.create_task(self._receive_messages())
async def _receive_messages(self):
async for message in self.ws:
event = json.loads(message)
if event['type'] == "error":
logger.error("ERROR", event)
self.log("received:", event)
self.dispatch(f"server.{event['type']}", event)
self.dispatch("server.*", event)
try:
async for message in self.ws:
event = json.loads(message)
if event['type'] == "error":
logger.error(f"OpenAI Realtime API Error: {event}")
self.log("received:", event)
self.dispatch(f"server.{event['type']}", event)
self.dispatch("server.*", event)
except ConnectionClosed as e:
logger.info(f"WebSocket connection closed normally: {e}")
# Mark connection as closed
self.ws = None
# Dispatch disconnection event
self.dispatch("disconnected", {"reason": str(e)})
except Exception as e:
logger.warning(f"WebSocket receive loop ended: {e}")
# Mark connection as closed
self.ws = None
# Dispatch disconnection event
self.dispatch("disconnected", {"reason": str(e)})
async def send(self, event_name, data=None):
if not self.is_connected():
@@ -135,16 +173,33 @@ class RealtimeAPI(RealtimeEventHandler):
self.dispatch(f"client.{event_name}", event)
self.dispatch("client.*", event)
self.log("sent:", event)
await self.ws.send(json.dumps(event))
try:
await self.ws.send(json.dumps(event))
except ConnectionClosed as e:
logger.info(f"WebSocket connection closed during send: {e}")
# Mark connection as closed if send fails
self.ws = None
raise Exception(f"WebSocket connection lost: {e}")
except Exception as e:
logger.error(f"Failed to send WebSocket message: {e}")
# Mark connection as closed if send fails
self.ws = None
raise Exception(f"WebSocket connection lost: {e}")
def _generate_id(self, prefix):
return f"{prefix}{int(datetime.utcnow().timestamp() * 1000)}"
async def disconnect(self):
if self.ws:
await self.ws.close()
self.ws = None
self.log(f"Disconnected from {self.url}")
try:
await self.ws.close()
logger.info(f"Disconnected from {self.url}")
except Exception as e:
logger.warning(f"Error during WebSocket close: {e}")
finally:
self.ws = None
self.log(f"WebSocket connection cleaned up")
class RealtimeConversation:
default_frequency = config.features.audio.sample_rate
@@ -341,8 +396,7 @@ class RealtimeConversation:
return None, None
array_buffer = base64_to_array_buffer(delta)
append_values = array_buffer.tobytes()
# TODO: make it work
# item['formatted']['audio'] = merge_int16_arrays(item['formatted']['audio'], append_values)
item['formatted']['audio'].append(append_values)
return item, {'audio': append_values}
def _process_text_delta(self, event):
@@ -381,7 +435,6 @@ class RealtimeClient(RealtimeEventHandler):
"tools": [],
"tool_choice": "auto",
"temperature": 0.8,
"max_response_output_tokens": 4096,
}
self.session_config = {}
self.transcription_models = [{"model": "whisper-1"}]
@@ -431,8 +484,13 @@ class RealtimeClient(RealtimeEventHandler):
self.dispatch("realtime.event", realtime_event)
def _on_session_created(self, event):
print(f"Session created: {event}")
logger.debug(f"Session created: {event}")
try:
session_id = event.get('session', {}).get('id', 'unknown')
model = event.get('session', {}).get('model', 'unknown')
logger.info(f"OpenAI Realtime session created - ID: {session_id}, Model: {model}")
except Exception as e:
logger.warning(f"Error processing session created event: {e}")
logger.debug(f"Session event details: {event}")
self.session_created = True
def _process_event(self, event, *args):
@@ -497,10 +555,15 @@ class RealtimeClient(RealtimeEventHandler):
self._add_api_event_handlers()
return True
async def connect(self):
async def connect(self, model=None):
if self.is_connected():
raise Exception("Already connected, use .disconnect() first")
await self.realtime.connect()
# Use provided model or default
if model is None:
model = 'gpt-4o-mini-realtime-preview-2024-12-17'
await self.realtime.connect(model)
await self.update_session()
return True
@@ -516,6 +579,7 @@ class RealtimeClient(RealtimeEventHandler):
self.conversation.clear()
if self.realtime.is_connected():
await self.realtime.disconnect()
logger.info("RealtimeClient disconnected")
def get_turn_detection_type(self):
return self.session_config.get("turn_detection", {}).get("type")
@@ -579,11 +643,22 @@ class RealtimeClient(RealtimeEventHandler):
return True
async def append_input_audio(self, array_buffer):
if not self.is_connected():
logger.warning("Cannot append audio: RealtimeClient is not connected")
return False
if len(array_buffer) > 0:
await self.realtime.send("input_audio_buffer.append", {
"audio": array_buffer_to_base64(np.array(array_buffer)),
})
self.input_audio_buffer.extend(array_buffer)
try:
await self.realtime.send("input_audio_buffer.append", {
"audio": array_buffer_to_base64(np.array(array_buffer)),
})
self.input_audio_buffer.extend(array_buffer)
except Exception as e:
logger.error(f"Failed to append input audio: {e}")
# Connection might be lost, mark as disconnected
if "connection" in str(e).lower() or "closed" in str(e).lower():
logger.warning("WebSocket connection appears to be lost. Audio input will be queued until reconnection.")
return False
return True
async def create_response(self):
@@ -650,4 +725,17 @@ class RealtimeClient(RealtimeEventHandler):
logger.debug(f"Unhandled item type:\n{json.dumps(item, indent=2)}")
# Additional debug logging
logger.debug(f"Processed Chainlit message for item: {item.get('id', 'unknown')}")
logger.debug(f"Processed Chainlit message for item: {item.get('id', 'unknown')}")
async def ensure_connected(self):
"""Check connection health and attempt reconnection if needed"""
if not self.is_connected():
try:
logger.info("Attempting to reconnect to OpenAI Realtime API...")
model = 'gpt-4o-mini-realtime-preview-2024-12-17'
await self.connect(model)
return True
except Exception as e:
logger.error(f"Failed to reconnect: {e}")
return False
return True
File diff suppressed because it is too large. Load diff
@@ -3,7 +3,7 @@ import chainlit as cl
import plotly
import json
from tavily import TavilyClient
from crawl4ai import WebCrawler
from crawl4ai import AsyncWebCrawler
import os
import logging
import asyncio
@@ -108,7 +108,7 @@ async def tavily_web_search_handler(query):
try:
response = tavily_client.search(query_with_date)
logger.debug(f"Tavily search response: {response}")
results = process_tavily_results(response)
results = await process_tavily_results(response)
except Exception as e:
logger.error(f"Error in Tavily search: {str(e)}")
results = await fallback_to_duckduckgo(query_with_date)
@@ -121,27 +121,27 @@ async def tavily_web_search_handler(query):
"results": results
})
def process_tavily_results(response):
crawler = WebCrawler()
crawler.warmup()
results = []
for result in response.get('results', []):
url = result.get('url')
if url:
try:
crawl_result = crawler.run(url=url)
results.append({
"content": result.get('content'),
"url": url,
"full_content": crawl_result.markdown
})
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
results.append({
"content": result.get('content'),
"url": url,
"full_content": "Error: Unable to crawl this URL"
})
async def process_tavily_results(response):
async with AsyncWebCrawler() as crawler:
results = []
for result in response.get('results', []):
url = result.get('url')
if url:
try:
crawl_result = await crawler.arun(url=url)
full_content = crawl_result.markdown if crawl_result and hasattr(crawl_result, 'markdown') and crawl_result.markdown else "No content available"
results.append({
"content": result.get('content'),
"url": url,
"full_content": full_content
})
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
results.append({
"content": result.get('content'),
"url": url,
"full_content": "Error: Unable to crawl this URL"
})
return results
async def fallback_to_duckduckgo(query):
@@ -151,33 +151,33 @@ async def fallback_to_duckduckgo(query):
logger.debug(f"DuckDuckGo search results: {ddg_results}")
crawler = WebCrawler()
crawler.warmup()
results = []
for result in ddg_results:
url = result.get('href')
if url:
try:
crawl_result = crawler.run(url=url)
async with AsyncWebCrawler() as crawler:
results = []
for result in ddg_results:
url = result.get('href')
if url:
try:
crawl_result = await crawler.arun(url=url)
full_content = crawl_result.markdown if crawl_result and hasattr(crawl_result, 'markdown') and crawl_result.markdown else "No content available"
results.append({
"content": result.get('body'),
"url": url,
"full_content": full_content
})
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
results.append({
"content": result.get('body'),
"url": url,
"full_content": "Error: Unable to crawl this URL"
})
else:
results.append({
"content": result.get('body'),
"url": url,
"full_content": crawl_result.markdown
"url": "N/A",
"full_content": "No URL provided for crawling"
})
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
results.append({
"content": result.get('body'),
"url": url,
"full_content": "Error: Unable to crawl this URL"
})
else:
results.append({
"content": result.get('body'),
"url": "N/A",
"full_content": "No URL provided for crawling"
})
return results
except Exception as e:
+12 -5
View File
@@ -1,6 +1,6 @@
[project]
name = "PraisonAI"
version = "2.2.19"
version = "2.2.25"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
readme = "README.md"
license = ""
@@ -12,13 +12,18 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.0.91",
"praisonaiagents>=0.0.96",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
"mcp>=1.6.0",
]
[project.scripts]
praisonai = "praisonai.__main__:main"
setup-conda-env = "praisonai.setup.setup_conda_env:main"
praisonai-call = "praisonai.api.call:main"
[project.optional-dependencies]
ui = [
"chainlit==2.5.5",
@@ -84,12 +89,13 @@ call = [
"rich",
"openai>=1.54.0",
]
train = []
crewai = ["crewai>=0.32.0", "praisonai-tools>=0.0.15"]
autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.15", "crewai"]
[tool.poetry]
name = "PraisonAI"
version = "2.2.19"
version = "2.2.25"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
authors = ["Mervin Praison"]
license = ""
@@ -107,7 +113,7 @@ python = ">=3.10,<3.13"
rich = ">=13.7"
markdown = ">=3.5"
pyparsing = ">=3.0.0"
praisonaiagents = ">=0.0.91"
praisonaiagents = ">=0.0.96"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
@@ -141,6 +147,7 @@ sqlalchemy = {version = ">=2.0.36", optional = true}
playwright = {version = ">=1.47.0", optional = true}
openai = {version = ">=1.54.0", optional = true}
pydantic = {version = "<=2.10.1", optional = true}
# unsloth = {version = ">=2024.11.7", extras = ["colab-new"], optional = true}
[tool.poetry.group.docs.dependencies]
mkdocs = "*"
@@ -244,7 +251,7 @@ code = [
"playwright",
"pydantic"
]
train = ["setup-conda-env"]
train = []
realtime = [
"chainlit",
"litellm",
@@ -0,0 +1,30 @@
# PraisonAI Deployment Scheduler Configuration
# This is an example configuration file for scheduled deployments
deployment:
# Schedule expression (e.g., "daily", "hourly", "*/6h", "3600")
schedule: "daily"
# Deployment provider (gcp, aws, azure)
provider: "gcp"
# Maximum retry attempts on deployment failure
max_retries: 3
# Optional: timezone for scheduling (future feature)
# timezone: "UTC"
# Optional: retry delay in seconds
# retry_delay: 30
# Optional: Environment variables for deployment
# environment:
# OPENAI_MODEL_NAME: "gpt-4o"
# OPENAI_API_KEY: "your-api-key"
# OPENAI_API_BASE: "https://api.openai.com/v1"
# Optional: Notification settings (future feature)
# notifications:
# on_success: true
# on_failure: true
# webhook_url: "https://your-webhook-url.com"
+5 -2
View File
@@ -43,7 +43,7 @@ class TestPraisonAIFramework(unittest.TestCase):
@pytest.mark.real
def test_main_with_built_in_tool(self):
praisonai = PraisonAI(agent_file="tests/built-in-tools-agents.yaml")
praisonai = PraisonAI(agent_file="tests/inbuilt-tool-agents.yaml")
result = praisonai.run()
print(f"Result: {result}")
self.assertIsNotNone(result)
@@ -77,7 +77,10 @@ class TestPraisonAICommandLine(unittest.TestCase):
command = "praisonai --framework autogen --init \"create a 2-agent team to write a simple python game\""
result = self.run_command(command)
print(f"Result: {result}")
self.assertIn('created successfully', result)
# Check for success indicator in the output
success_phrases = ['created successfully', 'agents.yaml created', 'File created']
found_success = any(phrase in result for phrase in success_phrases)
self.assertTrue(found_success, f"No success message found. Expected one of {success_phrases}. Last 1000 chars: {result[-1000:]}")
class TestExamples(unittest.TestCase):
def test_advanced_example(self):
@@ -0,0 +1,318 @@
#!/usr/bin/env python3
"""
Agent Integration Test for Human Approval System
This test demonstrates the approval system working through the agent's execute_tool method,
which is where the approval checks are actually implemented.
"""
import sys
import os
import asyncio
import pytest
# Add the praisonai-agents module to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'praisonai-agents'))
# Run interactively only when ASK_USER=1 is set
@pytest.mark.skipif(os.getenv("ASK_USER") != "1", reason="interactive approval requires user input")
def test_agent_tool_execution_with_approval():
"""Test that agent tool execution triggers approval prompts."""
print("\n🤖 Testing Agent Tool Execution with Approval")
print("=" * 50)
try:
from praisonaiagents import Agent
from praisonaiagents.tools.shell_tools import ShellTools
from praisonaiagents.approval import set_approval_callback, console_approval_callback, ApprovalDecision
# Use auto-approval when running non-interactive
if os.getenv("ASK_USER") == "1":
set_approval_callback(console_approval_callback)
else:
# Auto-approve for CI
def auto_approve_callback(function_name, arguments, risk_level):
return ApprovalDecision(approved=True, reason="Auto-approved for CI")
set_approval_callback(auto_approve_callback)
# Create agent with dangerous tools
agent = Agent(
name="Test Agent",
role="Security Tester",
goal="Test the human approval system",
tools=[ShellTools()],
verbose=True
)
print("About to execute a shell command through the agent...")
print("This should trigger an approval prompt.")
# Execute tool through agent - this should trigger approval
result = agent.execute_tool("execute_command", {"command": "echo 'Hello from agent-executed command!'"})
if result.get('success'):
print(f"✅ Command executed successfully: {result['stdout']}")
elif result.get('approval_denied'):
print(f"❌ Command was denied by approval system: {result['error']}")
else:
print(f"⚠️ Command failed for other reasons: {result}")
return True
except Exception as e:
print(f"❌ Agent tool execution test failed: {e}")
return False
def test_agent_with_auto_approval():
"""Test agent tool execution with auto-approval callback."""
print("\n🤖 Testing Agent with Auto-Approval")
print("=" * 40)
try:
from praisonaiagents import Agent
from praisonaiagents.tools.shell_tools import ShellTools
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
# Create auto-approval callback
def auto_approve_callback(function_name, arguments, risk_level):
print(f"🤖 Auto-approving {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=True, reason="Auto-approved for testing")
set_approval_callback(auto_approve_callback)
# Create agent
agent = Agent(
name="Auto-Approve Agent",
role="Automated Tester",
goal="Test auto-approval",
tools=[ShellTools()],
verbose=False
)
print("Executing command with auto-approval...")
result = agent.execute_tool("execute_command", {"command": "echo 'Auto-approved command executed!'"})
if result.get('success'):
print(f"✅ Auto-approved command executed: {result['stdout']}")
else:
print(f"❌ Auto-approved command failed: {result}")
return True
except Exception as e:
print(f"❌ Auto-approval test failed: {e}")
return False
def test_agent_with_auto_denial():
"""Test agent tool execution with auto-denial callback."""
print("\n🚫 Testing Agent with Auto-Denial")
print("=" * 40)
try:
from praisonaiagents import Agent
from praisonaiagents.tools.shell_tools import ShellTools
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
# Create auto-denial callback
def auto_deny_callback(function_name, arguments, risk_level):
print(f"🚫 Auto-denying {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=False, reason="Auto-denied for testing")
set_approval_callback(auto_deny_callback)
# Create agent
agent = Agent(
name="Auto-Deny Agent",
role="Security Tester",
goal="Test auto-denial",
tools=[ShellTools()],
verbose=False
)
print("Executing command with auto-denial...")
result = agent.execute_tool("execute_command", {"command": "echo 'This should be denied'"})
if result.get('approval_denied'):
print("✅ Command was correctly denied by approval system")
elif result.get('success'):
print("❌ Command executed when it should have been denied")
else:
print(f"⚠️ Command failed for other reasons: {result}")
return True
except Exception as e:
print(f"❌ Auto-denial test failed: {e}")
return False
def test_agent_python_code_execution():
"""Test Python code execution through agent with approval."""
print("\n🐍 Testing Agent Python Code Execution")
print("=" * 45)
try:
from praisonaiagents import Agent
from praisonaiagents.tools.python_tools import PythonTools
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
# Create auto-approval for this test
def auto_approve_callback(function_name, arguments, risk_level):
print(f"🤖 Auto-approving {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=True, reason="Auto-approved for testing")
set_approval_callback(auto_approve_callback)
# Create agent
agent = Agent(
name="Python Agent",
role="Code Executor",
goal="Test Python code execution",
tools=[PythonTools()],
verbose=False
)
code = """
print("Hello from agent-executed Python code!")
result = 3 * 7
print(f"3 * 7 = {result}")
"""
print("Executing Python code through agent...")
result = agent.execute_tool("execute_code", {"code": code})
if result.get('success'):
print(f"✅ Python code executed successfully: {result['output']}")
else:
print(f"❌ Python code execution failed: {result}")
return True
except Exception as e:
print(f"❌ Python code execution test failed: {e}")
return False
def test_agent_file_operations():
"""Test file operations through agent with approval."""
print("\n📁 Testing Agent File Operations")
print("=" * 35)
try:
from praisonaiagents import Agent
from praisonaiagents.tools.file_tools import FileTools
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
# Create auto-approval for this test
def auto_approve_callback(function_name, arguments, risk_level):
print(f"🤖 Auto-approving {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=True, reason="Auto-approved for testing")
set_approval_callback(auto_approve_callback)
# Create agent
agent = Agent(
name="File Agent",
role="File Manager",
goal="Test file operations",
tools=[FileTools()],
verbose=False
)
# Test file creation
print("Creating file through agent...")
result = agent.execute_tool("write_file", {
"file_path": "test_agent_file.txt",
"content": "This file was created through agent with approval!"
})
if result.get('success'):
print(f"✅ File created successfully: {result['message']}")
# Test file deletion
print("Deleting file through agent...")
delete_result = agent.execute_tool("delete_file", {"file_path": "test_agent_file.txt"})
if delete_result.get('success'):
print(f"✅ File deleted successfully: {delete_result['message']}")
else:
print(f"❌ File deletion failed: {delete_result}")
else:
print(f"❌ File creation failed: {result}")
return True
except Exception as e:
print(f"❌ File operations test failed: {e}")
return False
def main():
"""Run agent integration tests for the approval system."""
print("🧪 PraisonAI Human Approval System - Agent Integration Tests")
print("=" * 65)
print("These tests demonstrate the approval system working through agent tool execution.")
print()
# Ask user which tests to run
print("Available tests:")
print("1. Agent Tool Execution with Interactive Approval")
print("2. Agent with Auto-Approval")
print("3. Agent with Auto-Denial")
print("4. Agent Python Code Execution")
print("5. Agent File Operations")
print("6. Run all tests")
print()
try:
choice = input("Enter your choice (1-6): ").strip()
if choice == "1":
test_agent_tool_execution_with_approval()
elif choice == "2":
test_agent_with_auto_approval()
elif choice == "3":
test_agent_with_auto_denial()
elif choice == "4":
test_agent_python_code_execution()
elif choice == "5":
test_agent_file_operations()
elif choice == "6":
print("\n🚀 Running all tests...")
# Run non-interactive tests first
print("\n" + "=" * 65)
print("PART 1: NON-INTERACTIVE TESTS")
print("=" * 65)
test_agent_with_auto_approval()
test_agent_with_auto_denial()
test_agent_python_code_execution()
test_agent_file_operations()
# Ask if user wants to run interactive test
print("\n" + "=" * 65)
print("PART 2: INTERACTIVE TEST")
print("=" * 65)
print("The following test requires human interaction.")
run_interactive = input("Run interactive approval test? (y/n): ").strip().lower()
if run_interactive.startswith('y'):
test_agent_tool_execution_with_approval()
else:
print("Skipping interactive test.")
else:
print("Invalid choice. Exiting.")
return
print("\n🎉 Test completed!")
print("\nKey observations:")
print("- Approval system works when tools are executed through agent.execute_tool()")
print("- Direct tool calls bypass the approval system")
print("- Risk levels are correctly identified and displayed")
print("- Auto-approval and auto-denial callbacks work as expected")
print("- The approval system integrates properly with the agent architecture")
except KeyboardInterrupt:
print("\n\n❌ Test cancelled by user.")
except Exception as e:
print(f"\n❌ Test failed with error: {e}")
if __name__ == "__main__":
main()
@@ -0,0 +1,265 @@
#!/usr/bin/env python3
"""
Basic test to verify the human approval system implementation.
This test focuses on ensuring the approval decorators and callback system work correctly.
"""
import sys
import os
import asyncio
# Add the praisonai-agents module to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'praisonai-agents'))
def test_imports():
"""Test that all the new approval imports work correctly."""
try:
from praisonaiagents.approval import (
require_approval,
ApprovalDecision,
console_approval_callback,
request_approval,
add_approval_requirement,
remove_approval_requirement,
is_approval_required,
get_risk_level,
APPROVAL_REQUIRED_TOOLS,
TOOL_RISK_LEVELS
)
print("✅ All approval imports successful")
return True
except ImportError as e:
print(f"❌ Import failed: {e}")
return False
def test_approval_configuration():
"""Test approval requirement configuration."""
from praisonaiagents.approval import (
add_approval_requirement,
remove_approval_requirement,
is_approval_required,
get_risk_level,
APPROVAL_REQUIRED_TOOLS,
TOOL_RISK_LEVELS
)
print("\n🔧 Testing approval configuration...")
# Test adding requirement
add_approval_requirement("test_tool", "medium")
assert is_approval_required("test_tool"), "Tool should require approval after adding"
assert get_risk_level("test_tool") == "medium", "Risk level should be medium"
print("✅ Add approval requirement works")
# Test removing requirement
remove_approval_requirement("test_tool")
assert not is_approval_required("test_tool"), "Tool should not require approval after removing"
assert get_risk_level("test_tool") is None, "Risk level should be None after removal"
print("✅ Remove approval requirement works")
# Test default dangerous tools are configured
assert is_approval_required("execute_command"), "execute_command should require approval by default"
assert get_risk_level("execute_command") == "critical", "execute_command should be critical risk"
print("✅ Default dangerous tools are configured")
print(f"✅ Current approval-required tools: {len(APPROVAL_REQUIRED_TOOLS)} configured")
return True
def test_approval_decorator():
"""Test the require_approval decorator."""
from praisonaiagents.approval import require_approval, is_approval_required, get_risk_level
print("\n🎯 Testing approval decorator...")
# Test decorator on a test function
@require_approval(risk_level="high")
def test_dangerous_function(param1, param2="default"):
"""A test function that requires approval."""
return f"Executed with {param1} and {param2}"
# Check if the function is marked as requiring approval
assert is_approval_required("test_dangerous_function"), "Decorated function should require approval"
assert get_risk_level("test_dangerous_function") == "high", "Risk level should match decorator"
print("✅ Approval decorator works correctly")
# Test that the function still executes normally (approval will be checked at agent level)
result = test_dangerous_function("test", param2="value")
assert "Executed with test and value" in result, "Function should execute normally"
print("✅ Decorated function executes correctly")
return True
def test_tool_integration():
"""Test that dangerous tools have approval decorators."""
print("\n🛠️ Testing tool integration...")
# Test shell tools
try:
from praisonaiagents.tools.shell_tools import ShellTools
shell_tools = ShellTools()
# Check if execute_command requires approval
from praisonaiagents.approval import is_approval_required
assert is_approval_required("execute_command"), "execute_command should require approval"
print("✅ ShellTools.execute_command requires approval")
# Check if kill_process requires approval
assert is_approval_required("kill_process"), "kill_process should require approval"
print("✅ ShellTools.kill_process requires approval")
except Exception as e:
print(f"⚠️ Shell tools test failed: {e}")
# Test python tools
try:
from praisonaiagents.tools.python_tools import PythonTools
python_tools = PythonTools()
# Check if execute_code requires approval
assert is_approval_required("execute_code"), "execute_code should require approval"
print("✅ PythonTools.execute_code requires approval")
except Exception as e:
print(f"⚠️ Python tools test failed: {e}")
# Test file tools
try:
from praisonaiagents.tools.file_tools import FileTools
file_tools = FileTools()
# Check if write_file requires approval
assert is_approval_required("write_file"), "write_file should require approval"
print("✅ FileTools.write_file requires approval")
# Check if delete_file requires approval
assert is_approval_required("delete_file"), "delete_file should require approval"
print("✅ FileTools.delete_file requires approval")
except Exception as e:
print(f"⚠️ File tools test failed: {e}")
return True
async def test_approval_callback():
"""Test the approval callback system."""
print("\n📞 Testing approval callback system...")
from praisonaiagents.approval import request_approval, ApprovalDecision
# Mock approval that auto-denies
def mock_denial_callback(function_name, arguments, risk_level):
print(f"🔒 Mock callback: Denying {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=False, reason="Test denial")
# Mock approval that auto-approves
def mock_approval_callback(function_name, arguments, risk_level):
print(f"✅ Mock callback: Approving {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=True, reason="Test approval")
# Test with denial callback
from praisonaiagents.approval import set_approval_callback
set_approval_callback(mock_denial_callback)
decision = await request_approval("execute_command", {"command": "ls"})
assert not decision.approved, "Should be denied by mock callback"
assert "Test denial" in decision.reason, "Should have denial reason"
print("✅ Denial callback works")
# Test with approval callback
set_approval_callback(mock_approval_callback)
decision = await request_approval("execute_command", {"command": "ls"})
assert decision.approved, "Should be approved by mock callback"
assert "Test approval" in decision.reason, "Should have approval reason"
print("✅ Approval callback works")
# Test non-dangerous tool (should auto-approve)
decision = await request_approval("safe_function", {})
assert decision.approved, "Non-dangerous tools should auto-approve"
assert "No approval required" in decision.reason, "Should indicate no approval needed"
print("✅ Non-dangerous tools auto-approve")
return True
def test_agent_integration():
"""Test that agents properly integrate with the approval system."""
print("\n🤖 Testing agent integration...")
try:
from praisonaiagents import Agent
from praisonaiagents.tools.shell_tools import ShellTools
# Create agent with dangerous tools
agent = Agent(
name="Test Agent",
role="Tester",
goal="Test approval integration",
tools=[ShellTools()],
verbose=False
)
print("✅ Agent created with dangerous tools")
# Check that agent has the approval callback configured
from praisonaiagents.main import approval_callback
print(f"✅ Global approval callback configured: {approval_callback is not None}")
return True
except Exception as e:
print(f"⚠️ Agent integration test failed: {e}")
return False
def main():
"""Run all approval system tests."""
print("🧪 PraisonAI Human Approval System Test Suite")
print("=" * 50)
test_results = []
# Run synchronous tests
test_results.append(("Imports", test_imports()))
test_results.append(("Configuration", test_approval_configuration()))
test_results.append(("Decorator", test_approval_decorator()))
test_results.append(("Tool Integration", test_tool_integration()))
test_results.append(("Agent Integration", test_agent_integration()))
# Run async tests
try:
async_result = asyncio.run(test_approval_callback())
test_results.append(("Approval Callback", async_result))
except Exception as e:
print(f"❌ Async test failed: {e}")
test_results.append(("Approval Callback", False))
# Print summary
print("\n" + "=" * 50)
print("📊 TEST SUMMARY")
print("=" * 50)
passed = 0
failed = 0
for test_name, result in test_results:
status = "✅ PASSED" if result else "❌ FAILED"
print(f"{test_name:<20} {status}")
if result:
passed += 1
else:
failed += 1
print(f"\nTotal: {passed + failed} tests")
print(f"Passed: {passed}")
print(f"Failed: {failed}")
if failed == 0:
print("\n🎉 All tests passed! Human approval system is working correctly.")
return True
else:
print(f"\n❌ {failed} test(s) failed. Please check the implementation.")
return False
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)
@@ -0,0 +1,287 @@
#!/usr/bin/env python3
"""
Interactive test for the human approval system.
This test actually calls dangerous tools to trigger approval prompts,
allowing you to test the human-in-the-loop functionality.
"""
import sys
import os
import asyncio
import pytest
# Add the praisonai-agents module to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'praisonai-agents'))
@pytest.mark.skipif(os.getenv("ASK_USER") != "1", reason="interactive approval requires user input")
def test_shell_command_approval():
"""Test shell command execution with approval prompts."""
print("\n🐚 Testing Shell Command Approval")
print("=" * 40)
try:
from praisonaiagents.tools.shell_tools import ShellTools
from praisonaiagents.approval import set_approval_callback, console_approval_callback, ApprovalDecision
# Use auto-approval when running non-interactive
if os.getenv("ASK_USER") == "1":
set_approval_callback(console_approval_callback)
else:
# Auto-approve for CI
def auto_approve_callback(function_name, arguments, risk_level):
return ApprovalDecision(approved=True, reason="Auto-approved for CI")
set_approval_callback(auto_approve_callback)
shell_tools = ShellTools()
print("About to execute a shell command that requires approval...")
print("You should see an approval prompt.")
# This should trigger an approval prompt
result = shell_tools.execute_command("echo 'Hello from approved shell command!'")
if result.get('success'):
print(f"✅ Command executed successfully: {result['stdout']}")
else:
print(f"❌ Command failed or was denied: {result.get('stderr', 'Unknown error')}")
return True
except Exception as e:
print(f"❌ Shell command test failed: {e}")
return False
@pytest.mark.skipif(os.getenv("ASK_USER") != "1", reason="interactive approval requires user input")
def test_python_code_approval():
"""Test Python code execution with approval prompts."""
print("\n🐍 Testing Python Code Approval")
print("=" * 40)
try:
from praisonaiagents.tools.python_tools import PythonTools
from praisonaiagents.approval import set_approval_callback, console_approval_callback, ApprovalDecision
# Use auto-approval when running non-interactive
if os.getenv("ASK_USER") == "1":
set_approval_callback(console_approval_callback)
else:
# Auto-approve for CI
def auto_approve_callback(function_name, arguments, risk_level):
return ApprovalDecision(approved=True, reason="Auto-approved for CI")
set_approval_callback(auto_approve_callback)
python_tools = PythonTools()
print("About to execute Python code that requires approval...")
print("You should see an approval prompt.")
# This should trigger an approval prompt
code = """
print("Hello from approved Python code!")
result = 2 + 2
print(f"2 + 2 = {result}")
"""
result = python_tools.execute_code(code)
if result.get('success'):
print(f"✅ Code executed successfully: {result['output']}")
else:
print(f"❌ Code failed or was denied: {result.get('error', 'Unknown error')}")
return True
except Exception as e:
print(f"❌ Python code test failed: {e}")
return False
@pytest.mark.skipif(os.getenv("ASK_USER") != "1", reason="interactive approval requires user input")
def test_file_operation_approval():
"""Test file operations with approval prompts."""
print("\n📁 Testing File Operation Approval")
print("=" * 40)
try:
from praisonaiagents.tools.file_tools import FileTools
from praisonaiagents.approval import set_approval_callback, console_approval_callback, ApprovalDecision
# Use auto-approval when running non-interactive
if os.getenv("ASK_USER") == "1":
set_approval_callback(console_approval_callback)
else:
# Auto-approve for CI
def auto_approve_callback(function_name, arguments, risk_level):
return ApprovalDecision(approved=True, reason="Auto-approved for CI")
set_approval_callback(auto_approve_callback)
file_tools = FileTools()
print("About to create a file that requires approval...")
print("You should see an approval prompt.")
# This should trigger an approval prompt
result = file_tools.write_file(
file_path="test_approval_file.txt",
content="This file was created with human approval!"
)
if result.get('success'):
print(f"✅ File created successfully: {result['message']}")
# Now test deletion (also requires approval)
print("\nAbout to delete the file (also requires approval)...")
delete_result = file_tools.delete_file("test_approval_file.txt")
if delete_result.get('success'):
print(f"✅ File deleted successfully: {delete_result['message']}")
else:
print(f"❌ File deletion failed or was denied: {delete_result.get('error', 'Unknown error')}")
else:
print(f"❌ File creation failed or was denied: {result.get('error', 'Unknown error')}")
return True
except Exception as e:
print(f"❌ File operation test failed: {e}")
return False
def test_auto_approval_callback():
"""Test with an auto-approval callback for non-interactive testing."""
print("\n🤖 Testing Auto-Approval Callback")
print("=" * 40)
try:
from praisonaiagents.tools.shell_tools import ShellTools
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
# Create an auto-approval callback
def auto_approve_callback(function_name, arguments, risk_level):
print(f"🤖 Auto-approving {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=True, reason="Auto-approved for testing")
set_approval_callback(auto_approve_callback)
shell_tools = ShellTools()
print("Executing command with auto-approval...")
result = shell_tools.execute_command("echo 'Auto-approved command executed!'")
if result.get('success'):
print(f"✅ Auto-approved command executed: {result['stdout']}")
else:
print(f"❌ Auto-approved command failed: {result.get('stderr', 'Unknown error')}")
return True
except Exception as e:
print(f"❌ Auto-approval test failed: {e}")
return False
def test_auto_denial_callback():
"""Test with an auto-denial callback."""
print("\n🚫 Testing Auto-Denial Callback")
print("=" * 40)
try:
from praisonaiagents.tools.shell_tools import ShellTools
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
# Create an auto-denial callback
def auto_deny_callback(function_name, arguments, risk_level):
print(f"🚫 Auto-denying {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=False, reason="Auto-denied for testing")
set_approval_callback(auto_deny_callback)
shell_tools = ShellTools()
print("Executing command with auto-denial...")
result = shell_tools.execute_command("echo 'This should be denied'")
if result.get('approval_denied'):
print("✅ Command was correctly denied by approval system")
elif result.get('success'):
print("❌ Command executed when it should have been denied")
else:
print(f"⚠️ Command failed for other reasons: {result}")
return True
except Exception as e:
print(f"❌ Auto-denial test failed: {e}")
return False
def main():
"""Run interactive approval tests."""
print("🧪 PraisonAI Human Approval System - Interactive Tests")
print("=" * 60)
print("This test will demonstrate the human approval system in action.")
print("You will be prompted to approve or deny dangerous operations.")
print()
# Ask user which tests to run
print("Available tests:")
print("1. Shell Command Approval (interactive)")
print("2. Python Code Approval (interactive)")
print("3. File Operation Approval (interactive)")
print("4. Auto-Approval Test (non-interactive)")
print("5. Auto-Denial Test (non-interactive)")
print("6. Run all tests")
print()
try:
choice = input("Enter your choice (1-6): ").strip()
if choice == "1":
test_shell_command_approval()
elif choice == "2":
test_python_code_approval()
elif choice == "3":
test_file_operation_approval()
elif choice == "4":
test_auto_approval_callback()
elif choice == "5":
test_auto_denial_callback()
elif choice == "6":
print("\n🚀 Running all tests...")
# Run non-interactive tests first
print("\n" + "=" * 60)
print("PART 1: NON-INTERACTIVE TESTS")
print("=" * 60)
test_auto_approval_callback()
test_auto_denial_callback()
# Ask if user wants to run interactive tests
print("\n" + "=" * 60)
print("PART 2: INTERACTIVE TESTS")
print("=" * 60)
print("The following tests require human interaction.")
run_interactive = input("Run interactive tests? (y/n): ").strip().lower()
if run_interactive.startswith('y'):
test_shell_command_approval()
test_python_code_approval()
test_file_operation_approval()
else:
print("Skipping interactive tests.")
else:
print("Invalid choice. Exiting.")
return
print("\n🎉 Test completed!")
print("\nKey observations:")
print("- Dangerous operations should trigger approval prompts")
print("- Risk levels should be displayed correctly")
print("- Approved operations should execute normally")
print("- Denied operations should be blocked")
print("- Auto-approval/denial callbacks should work as expected")
except KeyboardInterrupt:
print("\n\n❌ Test cancelled by user.")
except Exception as e:
print(f"\n❌ Test failed with error: {e}")
if __name__ == "__main__":
main()
@@ -5,10 +5,9 @@ Test script for database_config module functionality.
import sys
import os
sys.path.insert(0, 'src/praisonai/praisonai/ui')
# Test the database_config module
from database_config import should_force_sqlite, get_database_url_with_sqlite_override, get_database_config_for_sqlalchemy
# Import from the proper module path
from praisonai.ui.database_config import should_force_sqlite, get_database_url_with_sqlite_override, get_database_config_for_sqlalchemy
def test_database_config():
print("Testing database_config utilities...")
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""
Test script to verify that the require_approval decorator now enforces approval
even when tools are called directly (not through agent.execute_tool).
"""
import sys
import os
# Add the praisonai-agents module to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))
def test_decorator_enforcement():
"""Test that the decorator actually enforces approval."""
print("🧪 Testing Decorator Enforcement")
print("=" * 35)
try:
from praisonaiagents.tools.shell_tools import ShellTools
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
# Set auto-denial to test enforcement
def auto_deny(function_name, arguments, risk_level):
print(f"🚫 Denying {function_name} (risk: {risk_level})")
return ApprovalDecision(approved=False, reason='Test denial')
set_approval_callback(auto_deny)
shell_tools = ShellTools()
print("Attempting to execute command directly (should be blocked)...")
try:
# This should now be blocked by the decorator
result = shell_tools.execute_command('echo "This should be denied"')
print('❌ Command executed when it should have been denied!')
return False
except PermissionError as e:
print(f'✅ Decorator enforcement working: {e}')
return True
except Exception as e:
print(f'❌ Unexpected error: {e}')
return False
except Exception as e:
print(f"❌ Test setup failed: {e}")
return False
if __name__ == "__main__":
success = test_decorator_enforcement()
if success:
print("\n🎉 Decorator enforcement is working correctly!")
else:
print("\n❌ Decorator enforcement test failed!")
sys.exit(0 if success else 1)
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
Simple test to verify the improved require_approval decorator.
"""
import sys
import os
# Add the praisonai-agents module to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))
def test_improved_decorator():
"""Test the improved decorator with context management."""
print("🧪 Testing Improved Decorator with Context Management")
print("=" * 55)
try:
from praisonaiagents.approval import (
require_approval, set_approval_callback, ApprovalDecision,
mark_approved, is_already_approved, clear_approval_context
)
# Clear any previous context
clear_approval_context()
# Create a test function
@require_approval(risk_level="critical")
def test_function(message="test"):
return f"Function executed: {message}"
print("✅ Test function decorated successfully")
# Test 1: Direct call without approval (should fail)
print("\n1. Testing direct call without approval (should fail)...")
def auto_deny(function_name, arguments, risk_level):
return ApprovalDecision(approved=False, reason='Test denial')
set_approval_callback(auto_deny)
try:
result = test_function("direct call")
print(f"❌ Function executed when it should have been denied: {result}")
except PermissionError as e:
print(f"✅ Correctly denied: {e}")
# Test 2: Mark as approved and call (should succeed)
print("\n2. Testing with approval context (should succeed)...")
mark_approved("test_function")
try:
result = test_function("approved context")
print(f"✅ Function executed with approved context: {result}")
except Exception as e:
print(f"❌ Function failed in approved context: {e}")
# Test 3: Clear context and test auto-approval
print("\n3. Testing auto-approval callback...")
clear_approval_context()
def auto_approve(function_name, arguments, risk_level):
return ApprovalDecision(approved=True, reason='Test approval')
set_approval_callback(auto_approve)
try:
result = test_function("auto approved")
print(f"✅ Function executed with auto-approval: {result}")
except Exception as e:
print(f"❌ Function failed with auto-approval: {e}")
# Test 4: Verify context is working
print("\n4. Testing context persistence...")
if is_already_approved("test_function"):
print("✅ Context correctly shows function as approved")
else:
print("❌ Context not working correctly")
return True
except Exception as e:
print(f"❌ Test failed: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = test_improved_decorator()
if success:
print("\n🎉 Improved decorator approach is working correctly!")
print("\nKey improvements:")
print("- ✅ Context management prevents double approval")
print("- ✅ Proper async handling")
print("- ✅ Decorator actually enforces approval")
print("- ✅ Agent integration marks tools as approved")
else:
print("\n❌ Improved decorator test failed!")
sys.exit(0 if success else 1)
@@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""
Quick test script to verify graph memory implementation
"""
import sys
import os
# Add the source directory to Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))
def test_memory_import():
"""Test that Memory class can be imported and initialized"""
try:
from praisonaiagents.memory import Memory
print("✅ Memory class imported successfully")
return True
except ImportError as e:
print(f"❌ Failed to import Memory: {e}")
return False
def test_knowledge_import():
"""Test that Knowledge class can be imported"""
try:
from praisonaiagents.knowledge import Knowledge
print("✅ Knowledge class imported successfully")
return True
except ImportError as e:
print(f"❌ Failed to import Knowledge: {e}")
return False
def test_memory_config():
"""Test memory configuration with graph support"""
try:
from praisonaiagents.memory import Memory
# Test basic configuration
basic_config = {
"provider": "mem0",
"config": {
"vector_store": {
"provider": "chroma",
"config": {"path": ".test_memory"}
}
}
}
memory = Memory(config=basic_config, verbose=1)
print("✅ Basic memory configuration works")
# Test graph configuration (will fallback gracefully if dependencies missing)
graph_config = {
"provider": "mem0",
"config": {
"graph_store": {
"provider": "memgraph",
"config": {
"url": "bolt://localhost:7687",
"username": "memgraph",
"password": ""
}
}
}
}
try:
memory_graph = Memory(config=graph_config, verbose=1)
print("✅ Graph memory configuration initialized")
print(f" Graph enabled: {getattr(memory_graph, 'graph_enabled', False)}")
except Exception as e:
print(f"⚠️ Graph memory not available (expected): {e}")
print(" This is normal if graph dependencies are not installed")
return True
except Exception as e:
print(f"❌ Memory configuration test failed: {e}")
return False
def test_knowledge_config():
"""Test knowledge configuration with graph support"""
try:
from praisonaiagents.knowledge import Knowledge
# Test basic knowledge config
basic_config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "test_collection",
"path": ".test_knowledge"
}
}
}
knowledge = Knowledge(config=basic_config, verbose=0)
print("✅ Basic knowledge configuration works")
# Test knowledge with graph config
graph_config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "test_graph_collection",
"path": ".test_graph_knowledge"
}
},
"graph_store": {
"provider": "memgraph",
"config": {
"url": "bolt://localhost:7687",
"username": "memgraph",
"password": ""
}
}
}
knowledge_graph = Knowledge(config=graph_config, verbose=0)
print("✅ Graph knowledge configuration created")
# Check if config contains graph_store
final_config = knowledge_graph.config
if "graph_store" in final_config:
print("✅ Graph store configuration preserved in knowledge config")
else:
print("❌ Graph store configuration not found in knowledge config")
return True
except Exception as e:
print(f"❌ Knowledge configuration test failed: {e}")
return False
def main():
print("🧪 Testing Graph Memory Implementation")
print("=" * 50)
tests = [
("Memory Import", test_memory_import),
("Knowledge Import", test_knowledge_import),
("Memory Configuration", test_memory_config),
("Knowledge Configuration", test_knowledge_config)
]
passed = 0
total = len(tests)
for test_name, test_func in tests:
print(f"\n🔬 Testing {test_name}...")
try:
if test_func():
passed += 1
except Exception as e:
print(f"❌ Test {test_name} crashed: {e}")
print(f"\n📊 Test Results: {passed}/{total} tests passed")
if passed == total:
print("🎉 All tests passed! Graph memory implementation is working correctly.")
else:
print("⚠️ Some tests failed. This might be expected if dependencies are missing.")
print("\n💡 To fully test graph memory, install dependencies:")
print(" pip install \"mem0ai[graph]\"")
print(" docker run -p 7687:7687 memgraph/memgraph-mage:latest")
if __name__ == "__main__":
main()
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""
Test script to verify Ollama tool-call fixes
"""
import sys
import os
# Add the source directory to Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'praisonai-agents'))
from praisonaiagents.llm.llm import LLM
from praisonaiagents.agent.agent import Agent
def test_ollama_provider_detection():
"""Test the new Ollama provider detection logic"""
print("Testing Ollama provider detection...")
# Test 1: Direct ollama/ prefix
llm1 = LLM(model="ollama/qwen3:32b")
assert llm1._is_ollama_provider() == True, "Should detect ollama/ prefix"
print("✓ Direct ollama/ prefix detection works")
# Test 2: Environment variable detection
os.environ["OPENAI_BASE_URL"] = "http://localhost:11434/v1"
llm2 = LLM(model="qwen3:32b")
assert llm2._is_ollama_provider() == True, "Should detect via env var"
print("✓ Environment variable detection works")
# Test 3: Non-Ollama model
os.environ.pop("OPENAI_BASE_URL", None)
llm3 = LLM(model="gpt-4o-mini")
assert llm3._is_ollama_provider() == False, "Should not detect non-Ollama"
print("✓ Non-Ollama detection works")
def test_tool_call_parsing():
"""Test the new tool call parsing logic"""
print("\nTesting tool call parsing...")
llm = LLM(model="ollama/qwen3:32b")
# Test 1: Standard format
tool_call_std = {
"id": "call_123",
"function": {
"name": "hello_world",
"arguments": '{"name": "test"}'
}
}
name, args, id = llm._parse_tool_call_arguments(tool_call_std, is_ollama=True)
assert name == "hello_world", f"Expected 'hello_world', got '{name}'"
assert args == {"name": "test"}, f"Expected {{'name': 'test'}}, got {args}"
print("✓ Standard format parsing works")
# Test 2: Ollama alternative format
tool_call_alt = {
"name": "hello_world",
"arguments": '{"name": "test"}'
}
name, args, id = llm._parse_tool_call_arguments(tool_call_alt, is_ollama=True)
assert name == "hello_world", f"Expected 'hello_world', got '{name}'"
assert args == {"name": "test"}, f"Expected {{'name': 'test'}}, got {args}"
print("✓ Alternative format parsing works")
# Test 3: Error handling - malformed JSON
tool_call_bad = {
"function": {
"name": "hello_world",
"arguments": 'invalid json'
}
}
name, args, id = llm._parse_tool_call_arguments(tool_call_bad, is_ollama=False)
assert name == "unknown_function", f"Expected 'unknown_function' for malformed JSON, got '{name}'"
assert args == {}, f"Expected empty dict, got {args}"
print("✓ Error handling works")
def test_agent_tool_parameter_logic():
"""Test the fixed tool parameter logic in Agent"""
print("\nTesting agent tool parameter logic...")
def dummy_tool():
"""Dummy tool for testing"""
return "test"
# Test 1: None tools should use agent tools
agent = Agent(name="test", tools=[dummy_tool])
# Simulate the fixed logic
tools = None
if tools is None or (isinstance(tools, list) and len(tools) == 0):
tool_param = agent.tools
else:
tool_param = tools
assert tool_param == [dummy_tool], "Should use agent tools when tools=None"
print("✓ None tools handling works")
# Test 2: Empty list should use agent tools
tools = []
if tools is None or (isinstance(tools, list) and len(tools) == 0):
tool_param = agent.tools
else:
tool_param = tools
assert tool_param == [dummy_tool], "Should use agent tools when tools=[]"
print("✓ Empty list handling works")
# Test 3: Non-empty list should use provided tools
custom_tools = [lambda: "custom"]
tools = custom_tools
if tools is None or (isinstance(tools, list) and len(tools) == 0):
tool_param = agent.tools
else:
tool_param = tools
assert tool_param == custom_tools, "Should use provided tools when not empty"
print("✓ Non-empty list handling works")
if __name__ == "__main__":
print("Running Ollama tool-call fix tests...\n")
try:
test_ollama_provider_detection()
test_tool_call_parsing()
test_agent_tool_parameter_logic()
print("\n🎉 All tests passed! The Ollama tool-call fixes are working correctly.")
sys.exit(0)
except Exception as e:
print(f"\n❌ Test failed: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
Test script for the deployment scheduler functionality.
Tests the core components without actually deploying.
"""
import sys
import os
import tempfile
import yaml
# Add the src directory to the path so we can import modules
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src/praisonai'))
def test_schedule_parser():
"""Test the schedule parser functionality."""
try:
from praisonai.scheduler import ScheduleParser
# Test various schedule expressions
test_cases = [
("daily", 86400),
("hourly", 3600),
("*/30m", 1800),
("*/6h", 21600),
("60", 60),
("3600", 3600)
]
print("Testing ScheduleParser...")
for expr, expected in test_cases:
result = ScheduleParser.parse(expr)
assert result == expected, f"Expected {expected}, got {result} for '{expr}'"
print(f" ✓ '{expr}' -> {result} seconds")
print("ScheduleParser tests passed!")
return True
except Exception as e:
print(f"ScheduleParser test failed: {e}")
return False
def test_scheduler_creation():
"""Test scheduler creation and basic functionality."""
try:
from praisonai.scheduler import create_scheduler, DeploymentScheduler
print("Testing scheduler creation...")
# Test default scheduler
scheduler = create_scheduler()
assert isinstance(scheduler, DeploymentScheduler)
print(" ✓ Default scheduler created")
# Test with different providers
for provider in ["gcp", "aws", "azure"]:
scheduler = create_scheduler(provider=provider)
assert isinstance(scheduler, DeploymentScheduler)
print(f" ✓ {provider} scheduler created")
print("Scheduler creation tests passed!")
return True
except Exception as e:
print(f"Scheduler creation test failed: {e}")
return False
def test_config_file_parsing():
"""Test configuration file parsing."""
try:
# Create a temporary config file
config_data = {
'deployment': {
'schedule': 'daily',
'provider': 'gcp',
'max_retries': 5
},
'environment': {
'TEST_VAR': 'test_value'
}
}
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
yaml.dump(config_data, f)
config_file = f.name
print("Testing config file parsing...")
# Test loading the config
with open(config_file, 'r') as f:
loaded_config = yaml.safe_load(f)
assert loaded_config['deployment']['schedule'] == 'daily'
assert loaded_config['deployment']['provider'] == 'gcp'
assert loaded_config['deployment']['max_retries'] == 5
print(" ✓ Config file parsed correctly")
# Clean up
os.unlink(config_file)
print("Config file parsing tests passed!")
return True
except Exception as e:
print(f"Config file parsing test failed: {e}")
return False
def test_cli_argument_parsing():
"""Test CLI argument parsing for scheduling options."""
try:
from praisonai.cli import PraisonAI
print("Testing CLI argument parsing...")
# Test basic CLI instantiation
praison = PraisonAI()
assert praison is not None
print(" ✓ PraisonAI CLI instance created")
print("CLI argument parsing tests passed!")
return True
except Exception as e:
print(f"CLI argument parsing test failed: {e}")
return False
def main():
"""Run all tests."""
print("Running PraisonAI Scheduler Tests")
print("=" * 40)
tests = [
test_schedule_parser,
test_scheduler_creation,
test_config_file_parsing,
test_cli_argument_parsing
]
passed = 0
total = len(tests)
for test in tests:
print()
if test():
passed += 1
else:
print("❌ Test failed")
print()
print("=" * 40)
print(f"Tests passed: {passed}/{total}")
if passed == total:
print("🎉 All tests passed!")
return 0
else:
print("❌ Some tests failed")
return 1
if __name__ == "__main__":
sys.exit(main())
+5 -5
View File
@@ -3931,7 +3931,7 @@ wheels = [
[[package]]
name = "praisonai"
version = "2.2.19"
version = "2.2.25"
source = { editable = "." }
dependencies = [
{ name = "instructor" },
@@ -4073,7 +4073,7 @@ requires-dist = [
{ name = "plotly", marker = "extra == 'realtime'", specifier = ">=5.24.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.15" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.15" },
{ name = "praisonaiagents", specifier = ">=0.0.91" },
{ name = "praisonaiagents", specifier = ">=0.0.96" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = ">=0.2.19" },
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
@@ -4130,7 +4130,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.91"
version = "0.0.96"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mcp" },
@@ -4138,9 +4138,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/63/a26fe7ae31191ed5998fabe6cb13f779728caf3123ac8c8e8f4707a99e58/praisonaiagents-0.0.91.tar.gz", hash = "sha256:ec0409591f9278b766c4c6b3876ed142cfc217b1545c1b9809494f8a826b55fa", size = 125710 }
sdist = { url = "https://files.pythonhosted.org/packages/10/53/2e1facaed976a6c428b67756108d2370b5ca9249356c7c71b7d6e1a28421/praisonaiagents-0.0.96.tar.gz", hash = "sha256:96ff46fbdb54162cf721c94fb04e95eae3f0d9305fd1939bad31fa1aca7b4231", size = 127366 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/2b/2e2d29ceb21a8e3af15beaf60955b85a9373ee6164d2d76f649a3e533786/praisonaiagents-0.0.91-py3-none-any.whl", hash = "sha256:aaaa51370a81aae29500168678d4b7208845890485f0a13da5367f1210dfaa3c", size = 143898 },
{ url = "https://files.pythonhosted.org/packages/4c/d1/89935abb42bd1c1cda1981583e4b6a2058b5c79e157a062489f581dba9b2/praisonaiagents-0.0.96-py3-none-any.whl", hash = "sha256:6d70884a7033e124510c13c80eb1e19dbad6c04d58eb8f0cfceb30fb9c91f85f", size = 145812 },
]
[[package]]