Compare commits

...
103 Commits
Author SHA1 Message Date
Mervin Praison 8c0e4277a4 Merge pull request #313 from MervinPraison/develop
Update PraisonAI to version 2.0.53 and enhance agent functionality
2025-01-20 10:04:38 +00:00
MervinPraison 41448617d5 Update PraisonAI to version 2.0.53 and enhance agent functionality
- Bumped PraisonAI version from 2.0.52 to 2.0.53 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonai.rb` formula to point to the new version's tarball.
- Updated the `praisonaiagents` dependency version from `0.0.43` to `0.0.44` across relevant files for improved functionality.
- Enhanced the `PraisonAIAgents` class to include a `max_iter` parameter, allowing for more control over agent iterations.
- Added new tasks to `agents/tasks.csv` to expand the capabilities of the agents in handling various writing tasks.
- Updated documentation to reflect changes in agent functionality and new task examples.

These changes improve the overall functionality and usability of the PraisonAI framework, particularly in task execution and agent capabilities.
2025-01-20 10:04:20 +00:00
Mervin Praison 49ea23c676 Merge pull request #312 from MervinPraison/develop
Introducing Repetitive Agents
2025-01-20 07:18:19 +00:00
MervinPraison bb0dc63680 Update PraisonAI to version 2.0.52 and enhance repetitive agents functionality
- Bumped PraisonAI version from 2.0.51 to 2.0.52 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonai.rb` formula to point to the new version's tarball.
- Updated the `praisonaiagents` dependency version from `0.0.42` to `0.0.43` across relevant files for improved functionality.
- Introduced new files for repetitive agents in `agents/repetitive-agents.py` and `examples/concepts/repetitive-agents.py`, enabling automated task loops.
- Enhanced documentation with a new section on repetitive agents in `docs/features/repetitive.mdx` and updated `mint.json` to include the new feature.

These changes improve the overall functionality and usability of the PraisonAI framework, particularly in handling repetitive tasks and enhancing agent capabilities.
2025-01-20 07:17:43 +00:00
MervinPraison 7774f2c565 Add loop agent functionality and update task management in PraisonAI agents
- Introduced a new loop agent in `loop_agents.py` to create and manage a loop of tasks from a CSV file.
- Added a new file `repetitive-manual-agents.py` that implements task identification, status checking, and updating functionalities.
- Updated `process.py` to enhance task handling by allowing dynamic input files for loop tasks.
- Removed the obsolete `repeat_agents.py` file to streamline the codebase.
- Bumped the version of the `praisonaiagents` package from 0.0.42 to 0.0.43 in `pyproject.toml` and `uv.lock` to reflect these changes.

These updates improve the task execution capabilities of the PraisonAI framework, particularly in handling repetitive tasks and enhancing agent functionality.
2025-01-20 06:56:33 +00:00
MervinPraison 5533fd3476 Enhance logging and improve decision/loop task handling in PraisonAI agents
- Suppressed debug logs from the markdown parser to reduce noise in the logging output.
- Refactored decision-making logic to utilize Pydantic models for better type safety and clarity.
- Updated loop task handling to ensure proper context management and error handling.
- Introduced dynamic literal types for decision and loop tasks to enhance flexibility in task definitions.

These changes improve the overall robustness and usability of the PraisonAI framework, particularly in task execution and logging clarity.
2025-01-19 05:58:46 +00:00
Mervin Praison 262a90a235 Merge pull request #310 from MervinPraison/develop
2.0.51
2025-01-18 06:02:35 +00:00
MervinPraison 60928dcb90 Update PraisonAI to version 2.0.51 and enhance dependencies
- Bumped PraisonAI version from 2.0.50 to 2.0.51 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonai.rb` formula to point to the new version's tarball.
- Updated the `praisonaiagents` dependency version from `0.0.41` to `0.0.42` across relevant files for improved functionality.
- Enhanced documentation with updated installation instructions for various examples, ensuring users have access to the latest package versions.

These changes improve the overall functionality and usability of the PraisonAI framework, particularly in dependency management and documentation clarity.
2025-01-18 06:02:09 +00:00
MervinPraison c8552e1158 Update PraisonAI agents package version to 0.0.42 in pyproject.toml and uv.lock
- Bumped the version from 0.0.41 to 0.0.42 to reflect recent updates and ensure users have access to the latest features and improvements.
- This change maintains the integrity of the PraisonAI framework and prepares it for future enhancements.
2025-01-18 05:48:57 +00:00
Mervin Praison c57d7db9bb Merge pull request #309 from MervinPraison/develop
Enhance Streamlit UIs for Code Analysis and Hackathon Judging
2025-01-17 22:21:06 +00:00
MervinPraison 236abc2c83 Enhance Streamlit UIs for Code Analysis and Hackathon Judging
- Updated `mint.json` to group Streamlit pages under a new "Streamlit" section, improving organization.
- Renamed titles in `code-analysis.mdx` and `hackathon-judge.mdx` to include "Agent" for clarity.
- Added new Streamlit UI files: `code-analysis-streamlit.mdx` and `hackathon-judge-streamlit.mdx`, providing interactive interfaces for code analysis and hackathon project evaluation.
- Each new UI includes detailed descriptions, features, quick start guides, and visual flowcharts to enhance user experience and understanding.

These changes significantly improve the usability and accessibility of the PraisonAI framework's Streamlit applications, offering users powerful tools for code and project evaluation.
2025-01-17 22:20:51 +00:00
Mervin Praison 76c5f8fe3f Merge pull request #308 from MervinPraison/develop
Add new code analysis and hackathon judge agents with Streamlit integ…
2025-01-17 19:03:12 +00:00
MervinPraison 82750020d2 Add new code analysis and hackathon judge agents with Streamlit integration
- Introduced `code-analysis-agents.py` and `code-analysis-streamlit.py` for comprehensive code evaluation, including metrics on architecture, maintainability, performance, and security.
- Added `context-agents-code.py` and `context-agents.py` for CV analysis capabilities, providing detailed scoring and recommendations.
- Implemented `hackathon-judge-agent.py` and `hackathon-judge-streamlit.py` for evaluating hackathon projects through video demonstrations, offering scores and feedback on innovation, technical complexity, and presentation quality.
- Enhanced documentation with new examples for code analysis and hackathon judging, improving user guidance and showcasing agent capabilities.
- Updated `mint.json` to include new examples, enriching the available resources for users.

These additions significantly expand the functionality of the PraisonAI framework, providing users with powerful tools for code and project evaluation.
2025-01-17 19:02:56 +00:00
Mervin Praison f49ad9fbc2 Merge pull request #306 from MervinPraison/develop
Develop
2025-01-17 17:35:18 +00:00
MervinPraison c086c60fd6 Update PraisonAI to version 2.0.50 and enhance dependencies
- Bumped PraisonAI version from 2.0.49 to 2.0.50 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonai.rb` formula to point to the new version's tarball.
- Updated the `praisonaiagents` dependency version from `0.0.40` to `0.0.41` across relevant files for improved functionality.
- Added a new `context-agents.py` file to introduce a CV analysis agent with comprehensive evaluation capabilities.
- Modified deployment scripts to ensure compatibility with the updated package version.

These changes improve the overall functionality and usability of the PraisonAI framework, particularly in dependency management and the introduction of new agent capabilities.
2025-01-17 17:35:00 +00:00
MervinPraison 4f8f4f2036 Update package version to 0.0.41 in uv.lock
- Bumped the version of the PraisonAI agents package from 0.0.40 to 0.0.41 to reflect recent updates.
- This change ensures that the latest features and improvements are properly versioned and available for users.
2025-01-17 17:26:51 +00:00
MervinPraison 8e91c20967 Update package version to 0.0.41 in pyproject.toml
- Bumped the version of the PraisonAI agents package from 0.0.40 to 0.0.41 to reflect recent updates.
- This change ensures that the latest features and improvements are properly versioned and available for users.

These updates maintain the integrity of the PraisonAI framework and prepare it for future enhancements.
2025-01-17 17:26:34 +00:00
MervinPraison 6f092c1507 Refactor agent context handling and improve task prompt generation
- Updated `crewai-tools-example.py` to remove unnecessary whitespace.
- Modified `agent.py` to replace `schema_json()` with `model_json_schema()` for JSON output consistency.
- Enhanced context handling in `agents.py` to avoid duplicates and improve clarity in task prompts by using lists instead of strings.
- Added support for passing content to tasks in both `start` and `astart` methods, allowing for dynamic context management.
- Improved task prompt generation in `task.py` to handle various context types, ensuring better output formatting.

These changes enhance the functionality and usability of the PraisonAI agents, particularly in context management and task execution clarity.
2025-01-17 17:25:58 +00:00
Mervin Praison 8f553d3a54 Merge pull request #305 from MervinPraison/develop
2.0.49
2025-01-17 12:24:16 +00:00
MervinPraison 2d647e25b5 Update PraisonAI to version 2.0.49 and enhance dependencies
- Bumped PraisonAI version from 2.0.48 to 2.0.49 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonai.rb` formula to point to the new version's tarball.
- Updated the `praisonaiagents` dependency version from `0.0.39` to `0.0.40` across relevant files for improved functionality.
- Modified deployment scripts to ensure compatibility with the updated package version.
- Enhanced documentation by adding a new "Tools" section in `mint.json` and updating the title in `custom.mdx` for better clarity.

These changes improve the overall functionality and usability of the PraisonAI framework, particularly in dependency management and documentation clarity.
2025-01-17 12:23:44 +00:00
MervinPraison 039bb772f5 Update version to 0.0.40 and enhance agent functionality
- Bumped the package version in `pyproject.toml` and `uv.lock` from `0.0.39` to `0.0.40` to reflect recent updates.
- Improved the `Agent` class in `agent.py` to support both Langchain and CrewAI tools by adding checks for the presence of `run` and `_run` methods, allowing for more flexible function handling.

These changes enhance the functionality of the PraisonAI agents, ensuring compatibility with different toolsets and improving overall usability.
2025-01-17 12:13:08 +00:00
Mervin Praison 459c2f2b70 Merge pull request #304 from MervinPraison/develop
Add new examples and enhance documentation for AI agent use cases
2025-01-17 10:34:16 +00:00
MervinPraison ee59706357 Add new examples and enhance documentation for AI agent use cases
- Introduced multiple new example files for various AI agent applications, including Climate Impact, Crypto Validator, DeFi Market Maker, Medicine Protocol, Neural Architecture, Quantum Optimiser, Research Assistant, Space Mission, and Vulnerability Detection.
- Updated `mint.json` to include new examples, improving the breadth of use cases available for users.
- Enhanced the `examples.mdx` file to provide links and descriptions for the newly added examples, facilitating easier navigation and understanding of the capabilities of PraisonAI.

These changes significantly expand the documentation and practical examples available to users, showcasing the versatility of the PraisonAI framework in various domains.
2025-01-17 10:33:58 +00:00
Mervin Praison 2a51644773 Merge pull request #303 from MervinPraison/develop
Refactor documentation and enhance quickstart guide for PraisonAI
2025-01-16 11:45:30 +00:00
MervinPraison cce908fa22 Refactor documentation and enhance quickstart guide for PraisonAI
- Removed unnecessary note from the introduction section of `introduction.mdx` for improved clarity.
- Updated the `quickstart.mdx` to provide a more comprehensive and user-friendly guide, including detailed steps for both code and no-code approaches to creating AI agents.
- Enhanced the `knowledge.mdx` file by renaming the vector store from "Chroma" to "Vector DB" for better clarity in terminology.
- Added prerequisites and tips to the quickstart guide to assist users in setting up their environment effectively.

These changes improve the overall usability and clarity of the documentation, making it easier for users to get started with PraisonAI.
2025-01-16 11:45:01 +00:00
Mervin Praison acf476f715 Merge pull request #302 from MervinPraison/develop
Develop
2025-01-16 11:13:41 +00:00
MervinPraison e5d77de79c Update PraisonAI to version 2.0.48 and enhance dependencies
- Bumped PraisonAI version from 2.0.47 to 2.0.48 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonai.rb` formula to point to the new version's tarball.
- Updated the `praisonaiagents` dependency version from `0.0.38` to `0.0.39` across relevant files for improved functionality.
- Modified deployment scripts to ensure compatibility with the updated package version.
- Removed outdated references in documentation and improved clarity in agent-related concepts.

These changes enhance the overall functionality and usability of the PraisonAI framework, particularly in dependency management and deployment processes.
2025-01-16 11:13:22 +00:00
MervinPraison 9ab444b54c Update knowledge agent configuration and versioning
- Changed the knowledge source in `knowledge-agents.py` from `small.pdf` to `large.pdf` to utilize a more comprehensive dataset.
- Bumped the package version in `pyproject.toml` and `uv.lock` from `0.0.38` to `0.0.39` to reflect recent updates.
- Enhanced the `Knowledge` class in `knowledge.py` by adding a progress display for memory storage operations, improving user experience during knowledge updates.

These changes improve the functionality and usability of the knowledge management system within the PraisonAI framework.
2025-01-16 10:14:11 +00:00
Mervin Praison e13f543c6d Merge pull request #301 from MervinPraison/develop
v0.0.38
2025-01-16 08:27:58 +00:00
MervinPraison 4f235d358c Update PraisonAI to version 2.0.47 and enhance knowledge management features
- Bumped PraisonAI version from 2.0.46 to 2.0.47 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonai.rb` formula to point to the new version's tarball.
- Enhanced the knowledge management system by adding a new `knowledge.mdx` file and related examples for creating knowledge-based agents.
- Updated documentation to include new concepts and improve clarity on knowledge management functionalities.
- Refactored deployment scripts to ensure compatibility with the updated package version.

These changes improve the overall functionality and usability of the PraisonAI framework, particularly in knowledge management and deployment processes.
2025-01-16 08:27:33 +00:00
MervinPraison b7847feb1e Update package version and enhance logging in Knowledge class
- Bumped the package version in `pyproject.toml` and `uv.lock` from `0.0.37` to `0.0.38` to reflect recent changes.
- Modified the `Knowledge` class in `knowledge.py` to include a `verbose` parameter for improved logging control.
- Implemented an internal logging helper method to streamline log messages and manage logging levels based on the `verbose` setting.
- Updated various logging statements to utilize the new logging helper, enhancing clarity and consistency in log outputs.

These changes improve the maintainability and usability of the knowledge management system by providing better logging capabilities.
2025-01-16 08:14:21 +00:00
MervinPraison 7ac730462c Update knowledge agent configuration and enhance memory management
- Changed the knowledge source in `knowledge-agents.py` from `large.pdf` to `small.pdf` for improved testing consistency.
- Bumped the package version in `pyproject.toml` from `0.0.36` to `0.0.37` to reflect recent changes.
- Updated the `chromadb` dependency version in `pyproject.toml` to `0.5.23` for compatibility.
- Enhanced the `CustomMemory` class in `knowledge.py` to allow dynamic memory management without relying on LLM, improving performance and flexibility.
- Introduced cached properties in the `Knowledge` class for better configuration handling and memory management.

These changes improve the knowledge agent's functionality and ensure better performance in memory operations.
2025-01-16 08:07:08 +00:00
MervinPraison 9cbc8d082d Add chunking functionality and update knowledge management features
Knowledge working without lazy loading

- Introduced a new `chunking-direct-test.py` file to demonstrate the usage of the `Chunking` class for text chunking with various strategies.
- Updated `knowledge-agents.py` to change the knowledge source from `2409.13731v3.pdf` to `large.pdf` and modified the agent's query for clarity.
- Revised `knowledge-direct-test.py` to replace the reference to `sample.pdf` with `small.pdf`, ensuring consistency in test file usage.
- Added `knowledge-large-pdf-test.py` to test the handling of large PDF files, integrating logging for better traceability.
- Updated `pyproject.toml` to include the new `chonkie` dependency for chunking functionalities.
- Removed the deprecated `sample.pdf` and added `small.pdf` to the agents directory for testing purposes.
- Enhanced the `Knowledge` class in `knowledge.py` to utilize the new `Chunking` class for improved memory management and content processing.

These changes enhance the knowledge management system's capabilities by integrating chunking strategies and improving file handling processes.
2025-01-16 07:56:32 +00:00
MervinPraison 7fb0aed7ac Refactor memory handling in knowledge-direct-test.py and knowledge.py
- Updated memory retrieval, updating, and deletion methods in `knowledge-direct-test.py` to correctly access memory IDs from the new results structure.
- Enhanced logging to provide clearer information about memory operations, including warnings for unexpected memory formats.
- Modified the `Knowledge` class in `knowledge.py` to ensure all memory results are returned in a consistent format, improving the handling of stored memories.
- Improved error handling and added checks for valid memory formats when verifying stored content from text and PDF files.

These changes enhance the robustness and clarity of memory management within the knowledge management system.
2025-01-16 06:24:15 +00:00
MervinPraison 206f0d01e7 Add new knowledge management features and files
- Introduced a new `knowledge-agents-task.py` script to define and execute tasks for knowledge agents, enhancing the framework's capabilities for knowledge retrieval and management.
- Added `knowledge-agents.py` to create a knowledge agent with specific instructions and knowledge sources, allowing for more tailored interactions.
- Implemented `knowledge-direct-test.py` for testing memory storage and retrieval functionalities, showcasing the new capabilities of the knowledge management system.
- Created `md.py` for handling Markdown file conversions, integrating with the knowledge management features.
- Updated `pyproject.toml` to specify the exact version of the `chromadb` dependency, ensuring compatibility with the new features.
- Added new PDF and DOCX files to the agents directory for testing and demonstration purposes.
- Enhanced the `Knowledge` class in `knowledge.py` to support user and agent identifiers, improving memory management and retrieval processes.

This commit significantly expands the knowledge management functionalities within the PraisonAI framework, providing new tools and scripts for better handling of knowledge content.
2025-01-16 05:36:08 +00:00
MervinPraison e19a310314 Update dependencies and enhance knowledge management system
- Updated the `chainlit` dependency version from `2.0.1` to `2.0.3` across multiple configurations in `pyproject.toml` and `agents/pyproject.toml` to ensure compatibility with the latest features and fixes.
- Introduced a new optional dependency group `knowledge` in `agents/pyproject.toml` for enhanced knowledge management capabilities, including `mem0ai` and `markitdown`.
- Refactored the `Knowledge` class in `knowledge.py` to improve initialization and memory management, allowing for better handling of user and agent identifiers.
- Enhanced methods for storing, retrieving, and managing memories, including new functionalities for updating and deleting memories.
- Updated UI components in `mint.json` and `realtime.py` to reflect changes in model naming conventions, improving user experience and clarity.

This commit strengthens the overall functionality and usability of the PraisonAI framework, particularly in knowledge management and UI interactions.
2025-01-15 17:00:15 +00:00
MervinPraison 1ea572689e Refactor Knowledge Management System in knowledge.py
- Overhauled the `Knowledge` class to enhance the knowledge management system, introducing a more flexible initialization with user and agent identifiers, and improved configuration options.
- Streamlined methods for adding, searching, and deleting knowledge, integrating a new memory management system for better performance and organization.
- Updated vector store and embedding configurations to support a wider range of providers and models, including options for graph capabilities.
- Enhanced error handling and debugging features for improved troubleshooting.
- Removed deprecated methods and optimized existing functionality for clarity and efficiency.

This commit significantly improves the structure and capabilities of the knowledge management system within the PraisonAI framework, facilitating better content handling and retrieval.
2025-01-15 06:52:26 +00:00
MervinPraison 96df649b05 Add Knowledge Management System in knowledge.py
- Introduced a new `Knowledge` class for managing knowledge content, including methods for adding, updating, searching, and deleting knowledge.
- Implemented support for various vector store types (Chroma, Pinecone, PGVector, Qdrant, Milvus) for storing embeddings.
- Added functionality for chunking text, generating embeddings using different models (OpenAI, Cohere, SentenceTransformers, HuggingFace), and optional encryption of content.
- Enhanced caching mechanism for search results to improve performance.
- Included comprehensive error handling and debug mode for better troubleshooting.

This commit establishes a robust framework for knowledge management within the PraisonAI agents, enhancing the system's capabilities for handling and retrieving information efficiently.
2025-01-15 06:18:33 +00:00
Mervin Praison 5aed75837a Merge pull request #300 from MervinPraison/develop
Refactor UI documentation and update navigation structure
2025-01-15 05:18:38 +00:00
MervinPraison 9c8f68a05f Refactor UI documentation and update navigation structure
- Renamed and reorganized the UI documentation in `ui.mdx`, changing the title and sidebar title for clarity.
- Updated the installation instructions to provide a more streamlined setup process.
- Removed the previous table of available interfaces and replaced it with a concise description of features.
- Adjusted the `mint.json` file to reflect the new UI documentation structure, enhancing navigation for users.

This commit improves the clarity and accessibility of the user interface documentation, making it easier for users to understand and utilize the PraisonAI framework.
2025-01-15 05:18:07 +00:00
Mervin Praison 9778a69c4d Merge pull request #299 from MervinPraison/develop
Remove deprecated use case documentation and update navigation structure
2025-01-14 17:12:40 +00:00
MervinPraison 73c7994380 Remove deprecated use case documentation and update navigation structure
- Deleted multiple use case files including "Examples," "Adaptive Learning," "Code Review," "Customer Service," "Emergency Response," "Fraud Detection," "Healthcare Diagnosis," "Multilingual Content," "Predictive Maintenance," "Smart City," and "Supply Chain" to streamline documentation.
- Updated `mint.json` to replace "Use Cases" with "Examples" in the navigation, enhancing clarity and organization.
- Adjusted navigation groups to reflect the new structure, ensuring users can easily find relevant information.

This commit focuses on cleaning up outdated documentation and improving the overall user experience by refining the navigation structure.
2025-01-14 17:12:26 +00:00
Mervin Praison 82b83ded41 Merge pull request #298 from MervinPraison/develop
Update PraisonAI and praisonaiagents to versions 2.0.46 and 0.0.36 re…
2025-01-14 13:47:58 +00:00
MervinPraison fc80db32c2 Update PraisonAI and praisonaiagents to versions 2.0.46 and 0.0.36 respectively
- Bumped the PraisonAI package version from 2.0.45 to 2.0.46 in Dockerfile, pyproject.toml, and related files.
- Updated the installation command in Dockerfile and deployment scripts to reflect the new version.
- Updated the praisonaiagents package version from 0.0.35 to 0.0.36 in relevant files.
- Introduced new agents for data analysis, finance, image processing, and more, enhancing the framework's capabilities.
- Added new example scripts demonstrating the usage of the newly created agents.
- Updated documentation to include new agents and their functionalities, improving user guidance and accessibility.

This commit enhances the overall functionality of the PraisonAI framework while ensuring that all components are up-to-date.
2025-01-14 13:46:43 +00:00
Mervin Praison 3f115ca7ba Merge pull request #297 from MervinPraison/develop
Refactor multimodal agent execution and enhance documentation structure
2025-01-14 09:48:14 +00:00
MervinPraison a32d45e6da Refactor multimodal agent execution and enhance documentation structure
- Simplified the execution of the multimodal agent by removing unnecessary result printing, allowing for a cleaner start method call.
- Updated `mint.json` to include new tabs for "Documentation" and "Use Cases," improving navigation and accessibility of relevant information.
- Added a new "Usecases" group in the navigation section of `mint.json`, detailing various use cases for better user guidance.

This commit streamlines the multimodal agent's functionality and enhances the documentation for improved user experience.
2025-01-14 09:47:50 +00:00
Mervin Praison 8823df26ff Merge pull request #294 from MervinPraison/develop
Add EXASearchTool class and integrate with AI agents
2025-01-13 13:20:26 +00:00
MervinPraison 9c2126fbd6 Add EXASearchTool class and integrate with AI agents
- Introduced a new `EXASearchTool` class to interact with the EXA Search API, allowing AI agents to perform searches and retrieve results.
- Implemented methods for running queries and parsing results into a readable format.
- Created a `SearchAgent` to utilize the `EXASearchTool` for searching information about the 'AI Agents Framework'.
- Added a task definition for the `SearchAgent` to demonstrate the tool's functionality.
- Updated documentation to include the new `tools_class` and provide a comprehensive guide on using class-based tools with AI agents.
2025-01-13 13:20:00 +00:00
Mervin Praison 741b01bdf0 Merge pull request #293 from MervinPraison/develop
Refactor autonomous agent workflow and enhance documentation
2025-01-13 12:25:56 +00:00
MervinPraison 6ed9a69cd8 Refactor autonomous agent workflow and enhance documentation
- Renamed and refactored functions in `autonomous-agent.py` to improve clarity and functionality, including changing `get_environment_feedback` to `get_environment_state`.
- Introduced new agents: `Environment Monitor`, `Action Executor`, and `Feedback Processor` to better structure the workflow.
- Updated task definitions to create a more coherent feedback loop and action execution process.
- Added a `main` function to encapsulate the workflow execution and result printing.
- Updated `mint.json` to include a new "Workflows" group for better organization of documentation.
- Removed the outdated `agenticrouting.mdx` documentation file to streamline content.

This commit enhances the overall structure and readability of the autonomous agent system while improving documentation organization.
2025-01-13 12:24:51 +00:00
Mervin Praison 56a42e6d95 Merge pull request #292 from MervinPraison/develop
Update PraisonAI and praisonaiagents to version 2.0.45 and 0.0.35 res…
2025-01-13 11:49:53 +00:00
MervinPraison d2b4affdd1 Update PraisonAI and praisonaiagents to version 2.0.45 and 0.0.35 respectively
- Bumped the PraisonAI package version from 2.0.44 to 2.0.45 in Dockerfile, pyproject.toml, and related files.
- Updated the installation command in Dockerfile and deployment scripts to reflect the new version.
- Updated the praisonaiagents package version from 0.0.34 to 0.0.35 in relevant files.
- Introduced new evaluator-optimiser and parallelisation features with corresponding agent and task implementations.
- Added documentation for the evaluator-optimiser feature and updated existing documentation for parallelisation.
- Adjusted dependencies in uv.lock for compatibility with the new versions.
2025-01-13 11:49:32 +00:00
Mervin Praison 0d5c8b478f Merge pull request #291 from MervinPraison/develop
Add parallelisation feature and update documentation
2025-01-13 07:52:07 +00:00
MervinPraison fdfad70319 Update PraisonAI and praisonaiagents to version 2.0.44 and 0.0.34 respectively
- Bumped the PraisonAI package version from 2.0.43 to 2.0.44 in Dockerfile, pyproject.toml, and related files.
- Updated the installation command in Dockerfile and deployment scripts to reflect the new version.
- Updated the praisonaiagents package version from 0.0.33 to 0.0.34 in relevant files.
- Adjusted dependencies in uv.lock for compatibility with the new versions.
- Refactored agent definitions in parallelization scripts to remove unnecessary verbosity parameters for cleaner code.
2025-01-13 07:51:14 +00:00
MervinPraison fba6b2e2a0 Add parallelisation feature and update documentation
- Introduced a new `parallelisation.py` file to implement parallel processing using AI agents, allowing concurrent execution of tasks for improved performance.
- Created multiple agents and tasks to demonstrate parallel execution and result aggregation.
- Added a new documentation file `parallelisation.mdx` detailing the setup, usage, and benefits of the parallelisation feature.
- Updated `mint.json` to include the new parallelisation feature for better tracking and visibility.
2025-01-13 07:38:24 +00:00
Mervin Praison f9bfb85f44 Merge pull request #290 from MervinPraison/develop
2.0.43
2025-01-13 07:33:48 +00:00
MervinPraison aab4b5b466 Update PraisonAI to version 2.0.43 and praisonaiagents to version 0.0.33
- Bumped the PraisonAI package version from 2.0.42 to 2.0.43 in Dockerfile, pyproject.toml, and related files.
- Updated the installation command in Dockerfile and deployment scripts to reflect the new version.
- Updated the praisonaiagents package version from 0.0.32 to 0.0.33 in relevant files.
- Modified task descriptions in evaluator-optimizer.py to enhance clarity and functionality.
- Adjusted documentation to ensure consistency with the new version.
- Updated dependencies in uv.lock for compatibility with the new versions.
2025-01-13 07:33:15 +00:00
MervinPraison 7ccadde004 Update praisonaiagents to version 0.0.33 and enhance agent functionality
- Bumped the version of the praisonaiagents package from 0.0.32 to 0.0.33 in pyproject.toml and related files.
- Improved tool availability handling by ensuring tools from both tasks and agents are combined for better execution.
- Enhanced parallel task execution logic to efficiently manage asynchronous tasks within workflows.
- Updated callback execution to handle running event loops more gracefully, improving overall stability and error handling.
- Adjusted dependencies in uv.lock for compatibility with the new version.
2025-01-13 06:36:27 +00:00
Mervin Praison 22a9d5ff23 Merge pull request #287 from MervinPraison/develop
Refactor prompt chaining and update JSON configuration
2025-01-12 07:03:55 +00:00
MervinPraison 98a11209ea Refactor prompt chaining and update JSON configuration
- Simplified import statements in agents/prompt_chaining.py for better readability.
- Updated the condition handling in the initial_task to specify "exit" for odd conditions, clarifying task flow.
- Added "features/promptchaining" to the mint.json configuration, enhancing feature tracking for prompt chaining capabilities.
2025-01-12 07:03:32 +00:00
Mervin Praison df3c97e73a Merge pull request #286 from MervinPraison/develop
Update PraisonAI to version 2.0.42 and praisonaiagents to version 0.0.32
2025-01-12 06:40:44 +00:00
MervinPraison 27808964be Update PraisonAI to version 2.0.42 and praisonaiagents to version 0.0.32
- Updated the PraisonAI package version from 2.0.41 to 2.0.42 in Dockerfile, pyproject.toml, and related files.
- Adjusted installation command in Dockerfile and deploy scripts to reflect the new version.
- Updated the praisonaiagents package version from 0.0.31 to 0.0.32 in relevant files.
- Enhanced the prompt chaining implementation in agents/prompt_chaining.py for improved task management and workflow execution.
- Added new documentation on agentic prompt chaining to guide users in creating complex workflows.
- Updated dependencies in uv.lock for compatibility with the new versions.
2025-01-12 06:40:29 +00:00
Mervin Praison b5daf16e0d Merge pull request #285 from MervinPraison/develop
adding docs
2025-01-11 17:13:14 +00:00
MervinPraison a2cac05d16 adding docs 2025-01-11 17:12:56 +00:00
Mervin Praison ad9828d3bb Merge pull request #284 from MervinPraison/develop
Update PraisonAI to version 2.0.41 and enhance documentation
2025-01-11 16:13:02 +00:00
MervinPraison 85ddf04b47 Update PraisonAI to version 2.0.41 and enhance documentation
- Updated PraisonAI package version from 2.0.40 to 2.0.41 in Dockerfile, pyproject.toml, and related files.
- Adjusted installation command in Dockerfile and deploy scripts to reflect the new version.
- Improved logging in agents_generator.py to handle missing tools.py file gracefully.
- Updated deployment documentation to ensure consistency with the new version.
- Adjusted dependencies in uv.lock for compatibility with the updated PraisonAI version.
2025-01-11 16:12:41 +00:00
Mervin Praison 1c1a1b719b Merge pull request #283 from MervinPraison/develop
Update PraisonAI to version 2.0.40 and enhance logging
2025-01-11 16:02:16 +00:00
MervinPraison a8e0914e05 Update PraisonAI to version 2.0.40 and enhance logging
- Updated the PraisonAI package version from 2.0.39 to 2.0.40 in Dockerfile, pyproject.toml, and related files.
- Added a new memory configuration option in agents.yaml to control memory usage.
- Improved logging throughout the codebase by changing info logs to debug logs for better traceability.
- Updated .gitignore to include new files for better project management.
- Adjusted dependencies in pyproject.toml and uv.lock for compatibility with the new version.
2025-01-11 16:01:54 +00:00
Mervin Praison ec6d883f4f Merge pull request #282 from MervinPraison/develop
adding langchain agent doc
2025-01-11 08:21:48 +00:00
MervinPraison d094cbfac5 adding langchain agent doc 2025-01-11 08:21:25 +00:00
Mervin Praison 14e3724aed Merge pull request #281 from MervinPraison/develop
Update praisonaiagents to version 0.0.31 and enhance agent functionality
2025-01-11 07:51:59 +00:00
MervinPraison 92436277d9 Update praisonaiagents to version 0.0.31 and enhance agent functionality
- Updated the version of the praisonaiagents package from 0.0.30 to 0.0.31 in pyproject.toml and related files.
- Added a new Fact Agent to store and retrieve facts, improving the overall functionality of the agent system.
- Enhanced the Research Agent with multiple tasks focused on AI topics, including healthcare and education.
- Updated display functions to include agent information in the output, improving clarity during interactions.
- Improved logging for agent processing and responses, aiding in debugging and tracking agent activities.
- Adjusted dependencies in pyproject.toml and uv.lock for compatibility with the new version.
2025-01-11 07:51:05 +00:00
Mervin Praison 3e0fd298e2 Merge pull request #280 from MervinPraison/develop
AI Agents with memory
2025-01-10 17:21:44 +00:00
MervinPraison fad0b41b52 Update PraisonAI to version 2.0.38 and enhance memory management
- Updated PraisonAI package version from 2.0.37 to 2.0.38 in Dockerfile, pyproject.toml, and related files.
- Refactored memory handling in memory_example.py to improve database path management and logging.
- Introduced new memory management features in the PraisonAIAgents class, allowing for better context retention across tasks.
- Enhanced logging throughout the codebase for improved debugging and tracking of memory operations.
- Added comprehensive documentation on memory capabilities, including short-term and long-term memory management.
- Updated dependencies in pyproject.toml and uv.lock for compatibility with the new version.
2025-01-10 17:20:54 +00:00
MervinPraison 6c3e5ee187 Agents(memory=True) working
Refactor memory management and enhance agent initialization

- Removed direct memory initialization from memory_example.py and integrated shared memory handling into PraisonAIAgents class.
- Updated PraisonAIAgents constructor to accept a memory flag, initializing shared memory if enabled.
- Assigned shared memory to tasks dynamically, improving data sharing between agents.
- Enhanced logging for memory initialization and task assignment to facilitate debugging.
2025-01-10 06:11:35 +00:00
MervinPraison f842ee7048 Update chainlit dependency to version 2.0.1 and improve tool registration in realtime.py
- Updated chainlit version from 1.3.2 to 2.0.1 in pyproject.toml and uv.lock.
- Enhanced tool registration logic in realtime.py to ensure only valid tools are added, with improved error handling and logging for invalid tool definitions.
- Refactored custom tools processing to support both formatted and unformatted tool definitions.
2025-01-10 06:01:47 +00:00
MervinPraison b0ff6f3f60 Update .gitignore and add new ignore files; enhance memory_example.py and memory.py functionality
- Updated .gitignore to include chroma_db/ directory.
- Added .praisoncontext and .praisonignore files for better project structure.
- Refactored memory_example.py to improve memory retrieval and logging.
- Enhanced memory.py to handle exceptions during ChromaDB collection initialization and updated embedding model to 'text-embedding-3-small'.
- Improved logging for embedding operations and ensured API key is set correctly.
2025-01-10 05:40:32 +00:00
MervinPraison 798f6dad14 Refactor memory handling and enhance task execution flow
- Improved memory initialization and management in memory_example.py and Task class.
- Integrated shared memory for tasks to facilitate data sharing between agents.
- Enhanced error handling and logging for memory operations in the Memory class.
- Updated task execution to include memory context and quality metrics.
- Added new task for querying both short-term and long-term memory.
- Improved overall structure and clarity of code with better logging practices.
2025-01-09 10:24:53 +00:00
MervinPraison 629bf0d669 Refactor memory management and enhance search functionality
- Removed unused imports and improved logging in memory_example.py.
- Updated Task description and expected output for clarity.
- Enhanced Memory class to support OpenAI embeddings for improved search capabilities.
- Added quality and relevance filtering in search_short_term and search_long_term methods.
- Implemented deletion of existing ChromaDB collection before creating a new one.
- Improved error handling and logging for database operations.
2025-01-09 06:44:25 +00:00
MervinPraison eaca447b82 Enhance code structure and documentation:
- Updated cursor rules for minimal code changes and user-friendly function naming.
- Integrated Memory module into the PraisonAIAgents initialization.
- Modified Agent class parameters for improved flexibility and default values.
- Enhanced Task class with memory and quality check features, including logging for quality metrics during execution.
2025-01-09 06:23:00 +00:00
Mervin Praison bef3693627 Merge pull request #278 from MervinPraison/develop
updating docs
2025-01-08 16:43:47 +00:00
MervinPraison aa62babed6 updating docs 2025-01-08 16:43:30 +00:00
Mervin Praison b73f181200 Merge pull request #277 from MervinPraison/develop
Updating Docs
2025-01-08 13:42:39 +00:00
MervinPraison 66d2998047 Updating Docs 2025-01-08 13:42:00 +00:00
Mervin Praison c5b6f8b91a Merge pull request #276 from MervinPraison/develop
adding autoagents
2025-01-08 06:47:17 +00:00
MervinPraison 823e46ca84 adding autoagents 2025-01-08 06:47:02 +00:00
Mervin Praison 57040333a7 Merge pull request #275 from MervinPraison/develop
Develop
2025-01-07 18:19:17 +00:00
MervinPraison 8c6800f876 adding langchain with uv 2025-01-07 18:18:46 +00:00
MervinPraison 0d701ae43c adding langchain 2025-01-07 14:15:32 +00:00
Mervin Praison 7a8c25621c Merge pull request #273 from MervinPraison/develop
fixing internet search tool
2025-01-07 06:55:38 +00:00
MervinPraison 218c5443b1 fixing internet search tool 2025-01-07 06:55:17 +00:00
Mervin Praison f56810341c Merge pull request #272 from MervinPraison/develop
updating docs
2025-01-07 06:09:35 +00:00
MervinPraison 4f601a573f updating docs 2025-01-07 06:09:14 +00:00
Mervin Praison 43c03f4813 Merge pull request #271 from MervinPraison/develop
fixing tools
2025-01-07 05:46:04 +00:00
MervinPraison 03856b74bf fixing tools 2025-01-07 05:45:42 +00:00
Mervin Praison eadfc0cff0 Merge pull request #270 from MervinPraison/develop
adding tools
2025-01-07 04:54:36 +00:00
MervinPraison f33c44d4c5 adding tools 2025-01-07 04:54:05 +00:00
Mervin Praison 3f7885289d Merge pull request #269 from MervinPraison/develop
adding mini docs
2025-01-07 00:55:43 +00:00
MervinPraison 833c795e9a adding mini docs 2025-01-07 00:55:29 +00:00
Mervin Praison c460c9da60 Merge pull request #268 from MervinPraison/develop
adding structured agents and code agents
2025-01-06 18:38:51 +00:00
MervinPraison 656887c700 adding structured agents and code agents 2025-01-06 18:26:56 +00:00
Mervin Praison e0089cac41 Merge pull request #267 from MervinPraison/develop
fixing bug setting openai_model_name env variable
2025-01-06 10:06:33 +00:00
MervinPraison bf5e069c7b fixing bug setting openai_model_name env variable 2025-01-06 10:06:11 +00:00
303 changed files with 48348 additions and 11592 deletions

No files matched your search

+2 -1
View File
@@ -65,4 +65,5 @@ outputs
agents/praisonaiagents/build
agents/praisonaiagents/dist
agents/praisonaiagents/praisonaiagents.egg-info
.codegpt
.codegpt
.praison
+1 -1
View File
@@ -1,6 +1,6 @@
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install flask praisonai==2.0.30 gunicorn markdown
RUN pip install flask praisonai==2.0.53 gunicorn markdown
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
+1
View File
@@ -1,6 +1,7 @@
framework: praisonai
process: sequential
topic: create movie script about cat in mars
memory: false
roles:
researcher:
backstory: Skilled in research, with a focus on gathering accurate and relevant
+8
View File
@@ -0,0 +1,8 @@
1. Make minimal code changes as possible
2. if you are creating function, make sure to name it in more easy, user friendly , optimised manner.
3. Never copy the exact code if presented with a code from other location or file or codebase, just make it optimised
4. Always Follow the principle LESS IS MORE
5. Make it minimal change as possible
6. Firstly try not to make any modification to the existing code as possible
7. Only modify the existing code if its highly required, without that if it cant be done, then add new code section.
8. If you are adding new code, make sure to add it in a way that it can be easily integrated with the existing codebase.
+43
View File
@@ -0,0 +1,43 @@
FROM mcr.microsoft.com/devcontainers/python:3.11
# Prevent apt from showing prompts
ENV DEBIAN_FRONTEND=noninteractive
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Create a non-root user
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
# Create the user and group only if they don't exist
RUN if ! getent group $USER_GID > /dev/null 2>&1; then \
groupadd --gid $USER_GID $USERNAME; \
fi && \
if ! getent passwd $USER_UID > /dev/null 2>&1; then \
useradd --uid $USER_UID --gid $USER_GID -m $USERNAME; \
fi && \
apt-get update && \
apt-get install -y sudo && \
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME && \
chmod 0440 /etc/sudoers.d/$USERNAME
# Set Python environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Install Python dependencies
COPY requirements.txt /tmp/pip-tmp/
RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
&& rm -rf /tmp/pip-tmp
# Switch to non-root user
USER $USERNAME
# Set the default shell to bash
ENV SHELL=/bin/bash
+36
View File
@@ -0,0 +1,36 @@
{
"name": "PraisonAI Development",
"dockerFile": "Dockerfile",
"context": "..",
"runArgs": [
"--env-file",
".env",
"--network=host"
],
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.black-formatter",
"ms-toolsai.jupyter",
"yzhang.markdown-all-in-one",
"streetsidesoftware.code-spell-checker"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.formatting.provider": "black",
"editor.formatOnSave": true,
"editor.rulers": [88],
"files.trimTrailingWhitespace": true
}
}
},
"remoteUser": "vscode",
"updateRemoteUserUID": false,
"forwardPorts": [],
"appPort": ["56040:56040"],
"overrideCommand": false,
"init": true
}
+2 -1
View File
@@ -83,4 +83,5 @@ dist
build
*.mp4
*.png
graph.py
graph.py
chroma_db/
View File
Whitespace-only changes.
+32
View File
@@ -0,0 +1,32 @@
.cursorrules
.gitignore
.praisoncontext
.praisonignore
.windsurfrules
async_example.py
async_example_full.py
async_example_full_multigroups.py
auto_agents_example.py
code_agents_example.py
example_callback.py
example_custom_tools.py
example_sequential.py
image.jpg
langchain_example.py
langchain_logs.py
long_term.db
mini_agents_example.py
multimodal.py
praisonaiagents/tools/*
praisonaiagents/process/*
pyproject.toml
short_term.db
structured_agents_example.py
structured_response_example.py
test.py
tools_example.py
uv.lock
workflow_example_basic.py
workflow_example_detailed.py
__pycache__
__pycache__/*
+8
View File
@@ -0,0 +1,8 @@
1. Make minimal code changes as possible
2. if you are creating function, make sure to name it in more easy, user friendly , optimised manner.
3. Never copy the exact code if presented with a code from other location or file or codebase, just make it optimised
4. Always Follow the principle LESS IS MORE
5. Make it minimal change as possible
6. Firstly try not to make any modification to the existing code as possible
7. Only modify the existing code if its highly required, without that if it cant be done, then add new code section.
8. If you are adding new code, make sure to add it in a way that it can be easily integrated with the existing codebase.
+11
View File
@@ -0,0 +1,11 @@
from praisonaiagents import AutoAgents
from praisonaiagents.tools import duckduckgo
agents = AutoAgents(
instructions="Search for information about AI Agents",
tools=[duckduckgo],
process="sequential",
verbose=True
)
agents.start()
+114
View File
@@ -0,0 +1,114 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
import time
def get_environment_state():
"""Simulates getting current environment state"""
current_time = int(time.time())
states = ["normal", "critical", "optimal"]
state = states[current_time % 3]
print(f"Environment state: {state}")
return state
def perform_action(state: str):
"""Simulates performing an action based on state"""
actions = {
"normal": "maintain",
"critical": "fix",
"optimal": "enhance"
}
action = actions.get(state, "observe")
print(f"Performing action: {action} for state: {state}")
return action
def get_feedback():
"""Simulates environment feedback"""
current_time = int(time.time())
feedback = "positive" if current_time % 2 == 0 else "negative"
print(f"Feedback received: {feedback}")
return feedback
# Create specialized agents
llm_caller = Agent(
name="Environment Monitor",
role="State analyzer",
goal="Monitor environment and analyze state",
instructions="Check environment state and provide analysis",
tools=[get_environment_state]
)
action_agent = Agent(
name="Action Executor",
role="Action performer",
goal="Execute appropriate actions based on state",
instructions="Determine and perform actions based on environment state",
tools=[perform_action]
)
feedback_agent = Agent(
name="Feedback Processor",
role="Feedback analyzer",
goal="Process environment feedback and adapt strategy",
instructions="Analyze feedback and provide adaptation recommendations",
tools=[get_feedback]
)
# Create tasks for autonomous workflow
monitor_task = Task(
name="monitor_environment",
description="Monitor and analyze environment state",
expected_output="Current environment state analysis",
agent=llm_caller,
is_start=True,
task_type="decision",
next_tasks=["execute_action"],
condition={
"normal": ["execute_action"],
"critical": ["execute_action"],
"optimal": "exit"
}
)
action_task = Task(
name="execute_action",
description="Execute appropriate action based on state",
expected_output="Action execution result",
agent=action_agent,
next_tasks=["process_feedback"]
)
feedback_task = Task(
name="process_feedback",
description="Process feedback and adapt strategy",
expected_output="Strategy adaptation based on feedback",
agent=feedback_agent,
next_tasks=["monitor_environment"], # Create feedback loop
context=[monitor_task, action_task] # Access to previous states and actions
)
# Create workflow manager
workflow = PraisonAIAgents(
agents=[llm_caller, action_agent, feedback_agent],
tasks=[monitor_task, action_task, feedback_task],
process="workflow",
verbose=True
)
def main():
print("\nStarting Autonomous Agent Workflow...")
print("=" * 50)
# Run autonomous workflow
results = workflow.start()
# Print results
print("\nAutonomous Agent Results:")
print("=" * 50)
for task_id, result in results["task_results"].items():
if result:
task_name = result.description
print(f"\nTask: {task_name}")
print(f"Result: {result.raw}")
print("-" * 50)
if __name__ == "__main__":
main()
+14
View File
@@ -0,0 +1,14 @@
from praisonaiagents.knowledge import Chunking
# No heavy imports or initialization happens here
chunking = Chunking(chunker_type='token')
# Chunker is only initialized when first used
chunks = chunking.chunk("Your text here")
# Embedding model is only loaded if using semantic chunking
semantic_chunking = Chunking(chunker_type='recursive')
# Embedding model and chunker are loaded here on first use
semantic_chunks = semantic_chunking.chunk("Your text here")
print(semantic_chunks)
+97
View File
@@ -0,0 +1,97 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from pydantic import BaseModel
from typing import List, Dict
from gitingest import ingest
class CodeMetrics(BaseModel):
category: str
score: int
findings: List[str]
class CodeAnalysisReport(BaseModel):
overall_quality: int
code_metrics: List[CodeMetrics]
architecture_score: int
maintainability_score: int
performance_score: int
security_score: int
test_coverage: int
key_strengths: List[str]
improvement_areas: List[str]
tech_stack: List[str]
recommendations: List[str]
complexity_metrics: Dict[str, int]
best_practices: List[Dict[str, str]]
potential_risks: List[str]
documentation_quality: int
code_analyzer = Agent(
role="Code Analysis Expert",
goal="Provide comprehensive code evaluation and recommendations",
backstory="""Expert code analyst specializing in architecture review,
best practices, and technical debt assessment.""",
verbose=True
)
code_analysis_task = Task(
description="""Analyze code repository and provide structured evaluation:
1. Overall Quality (0-100)
2. Core Metrics Analysis:
- Architecture and Design
- Code Maintainability
- Performance Optimization
- Security Practices
- Test Coverage
3. Technical Assessment:
- Technology Stack Review
- Code Complexity Analysis
- Best Practices Adherence
- Risk Assessment
4. Recommendations:
- Key Improvements
- Architecture Suggestions
- Security Enhancements""",
expected_output="Detailed code analysis report with metrics and recommendations",
agent=code_analyzer,
output_pydantic=CodeAnalysisReport
)
def analyze_code(code_source: str) -> CodeAnalysisReport:
"""
Analyze code from directory path or GitHub URL
"""
# Ingest code content
summary, tree, content = ingest(code_source)
# Concatenate context into structured format
context_text = f"""
CODE REPOSITORY ANALYSIS
=======================
SUMMARY
-------
{summary}
REPOSITORY STRUCTURE
-------------------
{tree}
SOURCE CODE
-----------
{content}
"""
# Initialize and run analysis
agents = PraisonAIAgents(
agents=[code_analyzer],
tasks=[code_analysis_task]
)
return agents.start(context_text)
if __name__ == "__main__":
# Example usage
code_source = "https://github.com/openai/openai-python/tree/main/src/openai/cli/_api/chat" # GitHub URL or local directory
result = analyze_code(code_source)
print(result)
+217
View File
@@ -0,0 +1,217 @@
import streamlit as st
from praisonaiagents import Agent, Task, PraisonAIAgents
from pydantic import BaseModel
from typing import List, Dict, Any
from gitingest import ingest
# Import the same classes and code from code-analysis-agents.py
class CodeMetrics(BaseModel):
category: str
score: int
findings: List[str]
class CodeAnalysisReport(BaseModel):
overall_quality: int
code_metrics: List[CodeMetrics]
architecture_score: int
maintainability_score: int
performance_score: int
security_score: int
test_coverage: int
key_strengths: List[str]
improvement_areas: List[str]
tech_stack: List[str]
recommendations: List[str]
complexity_metrics: Dict[str, int]
best_practices: List[Dict[str, str]]
potential_risks: List[str]
documentation_quality: int
def analyze_code(code_source: str) -> CodeAnalysisReport:
"""
Analyze code from directory path or GitHub URL
"""
# Create code analyzer agent
code_analyzer = Agent(
role="Code Analysis Expert",
goal="Provide comprehensive code evaluation and recommendations",
backstory="""Expert code analyst specializing in architecture review,
best practices, and technical debt assessment.""",
verbose=True
)
# Create analysis task
code_analysis_task = Task(
description="""Analyze code repository and provide structured evaluation:
1. Overall Quality (0-100)
2. Core Metrics Analysis:
- Architecture and Design
- Code Maintainability
- Performance Optimization
- Security Practices
- Test Coverage
3. Technical Assessment:
- Technology Stack Review
- Code Complexity Analysis
- Best Practices Adherence
- Risk Assessment
4. Recommendations:
- Key Improvements
- Architecture Suggestions
- Security Enhancements""",
expected_output="Detailed code analysis report with metrics and recommendations",
agent=code_analyzer,
output_pydantic=CodeAnalysisReport
)
# Ingest code content
summary, tree, content = ingest(code_source)
# Concatenate context into structured format
context_text = f"""
CODE REPOSITORY ANALYSIS
=======================
SUMMARY
-------
{summary}
REPOSITORY STRUCTURE
-------------------
{tree}
SOURCE CODE
-----------
{content}
"""
# Initialize and run analysis
agents = PraisonAIAgents(
agents=[code_analyzer],
tasks=[code_analysis_task]
)
result = agents.start(context_text)
# Extract the Pydantic model from the result
if isinstance(result, dict) and 'task_results' in result:
# Get the first task result's pydantic output
analysis_result = result['task_results'][0].pydantic
if isinstance(analysis_result, CodeAnalysisReport):
return analysis_result
# If we can't get the Pydantic model, create one from the raw data
return CodeAnalysisReport(**result)
def display_code_metrics(metrics, cols):
"""Display code metrics in columns with color-coded scores"""
for i, metric in enumerate(metrics):
score = metric.score
color = "red" if score < 60 else "orange" if score < 75 else "green"
# Use modulo to alternate between columns
with cols[i % len(cols)]:
st.markdown(f"### {metric.category}")
st.markdown(f"**Score:** :{color}[{score}%]")
for finding in metric.findings:
st.markdown(f"• {finding}")
def display_section(title: str, items: list, icon: str = "•"):
"""Display a section with items in a consistent format"""
st.markdown(f"### {title}")
for item in items:
if isinstance(item, dict):
for key, value in item.items():
st.markdown(f"{icon} **{key}**: {value}")
else:
st.markdown(f"{icon} {item}")
def main():
st.set_page_config(
page_title="Code Analysis Agent",
layout="wide",
initial_sidebar_state="expanded"
)
st.title("Code Analysis Agent")
with st.sidebar:
st.header("Input")
code_source = st.text_input(
"GitHub URL or Local Path",
placeholder="https://github.com/username/repo or /path/to/directory"
)
analyze_button = st.button("Analyze Code", type="primary")
if analyze_button and code_source:
try:
with st.spinner("Analyzing code..."):
result = analyze_code(code_source)
# Overall Metrics
st.header("Overall Metrics")
cols = st.columns(6)
metrics = {
"Overall Quality": result.overall_quality,
"Architecture": result.architecture_score,
"Maintainability": result.maintainability_score,
"Performance": result.performance_score,
"Security": result.security_score,
"Test Coverage": result.test_coverage
}
for (metric, value), col in zip(metrics.items(), cols):
color = "red" if value < 60 else "orange" if value < 75 else "green"
col.metric(metric, f"{value}%")
col.markdown(f":{color}[{'●' * (value // 20)}]")
# Detailed Analysis
st.header("Detailed Analysis")
metric_cols = st.columns(2)
display_code_metrics(result.code_metrics, metric_cols)
# Technology Stack
col1, col2 = st.columns(2)
with col1:
st.header("Technology Stack")
for tech in result.tech_stack:
st.markdown(f"🔧 {tech}")
with col2:
st.header("Complexity Metrics")
for metric, value in result.complexity_metrics.items():
st.metric(metric.replace('_', ' ').title(), value)
# Key Findings
st.header("Key Findings")
cols = st.columns(2)
with cols[0]:
display_section("✅ Strengths", result.key_strengths)
display_section("🔄 Best Practices", result.best_practices)
with cols[1]:
display_section("⚠️ Areas for Improvement", result.improvement_areas)
display_section("❗ Potential Risks", result.potential_risks)
# Recommendations
st.header("Recommendations")
for i, rec in enumerate(result.recommendations, 1):
st.markdown(f"**{i}.** {rec}")
# Documentation Quality
st.header("Documentation")
doc_score = result.documentation_quality
color = "red" if doc_score < 60 else "orange" if doc_score < 75 else "green"
st.markdown(f"Documentation Quality: :{color}[{doc_score}%]")
except Exception as e:
st.error(f"Error analyzing code: {str(e)}")
st.exception(e)
elif analyze_button:
st.warning("Please enter a GitHub URL or local path")
if __name__ == "__main__":
main()
+50
View File
@@ -0,0 +1,50 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
import json
from e2b_code_interpreter import Sandbox
def code_interpreter(code: str):
"""
A function to demonstrate running Python code dynamically using e2b_code_interpreter.
"""
print(f"\n{'='*50}\n> Running following AI-generated code:\n{code}\n{'='*50}")
exec_result = Sandbox().run_code(code)
if exec_result.error:
print("[Code Interpreter error]", exec_result.error)
return {"error": str(exec_result.error)}
else:
results = []
for result in exec_result.results:
if hasattr(result, '__iter__'):
results.extend(list(result))
else:
results.append(str(result))
logs = {"stdout": list(exec_result.logs.stdout), "stderr": list(exec_result.logs.stderr)}
return json.dumps({"results": results, "logs": logs})
code_agent = Agent(
name="code_agent",
llm="gpt-4o-mini",
backstory="Expert in writing Python scripts",
self_reflect=False
)
execution_agent = Agent(
name="execution_agent",
llm="gpt-4o-mini",
backstory="Expert in executing Python scripts",
self_reflect=False,
tools=[code_interpreter]
)
code_agent_task = Task(
description="Write a simple Python script to print 'Hello, World!'",
expected_output="A Python script that prints 'Hello, World!'",
agent=code_agent
)
execution_agent_task = Task(
description="Execute the Python script",
expected_output="The output of the Python script",
agent=execution_agent
)
agents = PraisonAIAgents(agents=[code_agent, execution_agent], tasks=[code_agent_task, execution_agent_task])
agents.start()
+108
View File
@@ -0,0 +1,108 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
import time
from typing import List, Dict
def analyze_code_changes():
"""Simulates code analysis"""
issues = [
{"type": "style", "severity": "low", "file": "main.py"},
{"type": "security", "severity": "high", "file": "auth.py"},
{"type": "performance", "severity": "medium", "file": "data.py"}
]
return issues[int(time.time()) % 3]
def suggest_fixes(issue: Dict):
"""Simulates fix suggestions"""
fixes = {
"style": "Apply PEP 8 formatting",
"security": "Implement input validation",
"performance": "Use list comprehension"
}
return fixes.get(issue["type"], "Review manually")
def apply_automated_fix(fix: str):
"""Simulates applying automated fixes"""
success = int(time.time()) % 2 == 0
return "fixed" if success else "manual_review"
# Create specialized agents
analyzer = Agent(
name="Code Analyzer",
role="Code analysis",
goal="Analyze code changes and identify issues",
instructions="Review code changes and report issues",
tools=[analyze_code_changes]
)
fix_suggester = Agent(
name="Fix Suggester",
role="Solution provider",
goal="Suggest fixes for identified issues",
instructions="Provide appropriate fix suggestions",
tools=[suggest_fixes]
)
fix_applier = Agent(
name="Fix Applier",
role="Fix implementation",
goal="Apply suggested fixes automatically when possible",
instructions="Implement suggested fixes and report results",
tools=[apply_automated_fix]
)
# Create workflow tasks
analysis_task = Task(
name="analyze_code",
description="Analyze code changes for issues",
expected_output="Identified code issues",
agent=analyzer,
is_start=True,
next_tasks=["suggest_fixes"]
)
suggestion_task = Task(
name="suggest_fixes",
description="Suggest fixes for identified issues",
expected_output="Fix suggestions",
agent=fix_suggester,
next_tasks=["apply_fixes"]
)
fix_task = Task(
name="apply_fixes",
description="Apply suggested fixes",
expected_output="Fix application status",
agent=fix_applier,
task_type="decision",
condition={
"fixed": "",
"manual_review": ["suggest_fixes"] # Loop back for manual review
}
)
# Create workflow
workflow = PraisonAIAgents(
agents=[analyzer, fix_suggester, fix_applier],
tasks=[analysis_task, suggestion_task, fix_task],
process="workflow",
verbose=True
)
def main():
print("\nStarting Code Review Workflow...")
print("=" * 50)
# Run workflow
results = workflow.start()
# Print results
print("\nCode Review Results:")
print("=" * 50)
for task_id, result in results["task_results"].items():
if result:
print(f"\nTask: {task_id}")
print(f"Result: {result.raw}")
print("-" * 50)
if __name__ == "__main__":
main()
+98
View File
@@ -0,0 +1,98 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from pydantic import BaseModel
from typing import List, Dict
class SkillMetrics(BaseModel):
name: str
score: int
comments: str
class CVAnalysisReport(BaseModel):
overall_score: int
skill_metrics: List[SkillMetrics]
experience_score: int
education_score: int
presentation_score: int
achievement_impact_score: int
market_readiness: int
key_strengths: List[str]
areas_for_improvement: List[str]
market_fit: str
recommendations: List[str]
technical_skills: List[Dict[str, int]]
soft_skills: List[Dict[str, int]]
skill_gaps: List[str]
key_achievements: List[str]
impact_metrics: List[Dict[str, str]]
areas_of_expertise: List[str]
# Create single CV analysis agent
cv_analyzer = Agent(
role="CV Analysis Expert",
goal="Provide comprehensive CV evaluation and recommendations",
backstory="""You are an expert CV analyst with deep knowledge of industry requirements,
market trends, and talent assessment. You excel at evaluating skills, experience,
and providing actionable recommendations.""",
verbose=True
)
# Single comprehensive analysis task
cv_analysis_task = Task(
description="""Perform a detailed CV analysis with scoring metrics and provide output in the following format:
1. Overall Score (0-100):
- Calculate based on weighted average of all metrics
2. Individual Skill Metrics:
- For each major skill found in CV:
* Name of skill
* Score (0-100)
* Specific feedback/comments
3. Core Metrics (0-100 each):
- Experience Score: Based on relevance and years
- Education Score: Based on relevance and level
- Presentation Score: CV format, clarity, structure
- Achievement Impact Score: Measurable results
- Market Readiness: Current market fit
4. Technical Skills:
- List each skill with proficiency score (0-100)
- Example: {"Python": 85, "AWS": 70}
5. Soft Skills:
- List each skill with proficiency score (0-100)
- Example: {"Communication": 90, "Leadership": 75}
6. Detailed Analysis:
- Key Strengths: Top 3-5 standout points
- Areas for Improvement: 3-5 specific points
- Market Fit: Current industry alignment
- Skill Gaps: Missing critical skills for target role
- Key Achievements: Quantifiable accomplishments
- Impact Metrics: {"metric": "value"}
- Areas of Expertise: Primary domains of expertise
7. Recommendations:
- 3-5 actionable steps for improvement
Provide clear justification for all scores and ensure feedback is specific and actionable.""",
expected_output="Structured CV analysis report with detailed metrics and scoring justification",
agent=cv_analyzer,
output_pydantic=CVAnalysisReport
)
# Create and run the agent
agents = PraisonAIAgents(
agents=[cv_analyzer],
tasks=[cv_analysis_task]
)
# Read and pass CV content through start()
with open('cv.txt', 'r') as file:
cv_content = file.read()
# Start the analysis by passing CV content
result = agents.start(cv_content)
print(result)
+202
View File
@@ -0,0 +1,202 @@
import streamlit as st
from praisonaiagents import Agent, Task, PraisonAIAgents
from pydantic import BaseModel
from typing import List, Dict
import json
import asyncio
import nest_asyncio
from functools import partial
# Enable nested event loops
nest_asyncio.apply()
class SkillMetrics(BaseModel):
name: str
score: int
comments: str
class CVAnalysisReport(BaseModel):
overall_score: int
skill_metrics: List[SkillMetrics]
experience_score: int
education_score: int
presentation_score: int
achievement_impact_score: int
market_readiness: int
key_strengths: List[str]
areas_for_improvement: List[str]
market_fit: str
recommendations: List[str]
technical_skills: List[Dict[str, int]]
soft_skills: List[Dict[str, int]]
skill_gaps: List[str]
key_achievements: List[str]
impact_metrics: List[Dict[str, str]]
areas_of_expertise: List[str]
async def analyze_cv_async(cv_content):
cv_analyzer = Agent(
role="CV Analysis Expert",
goal="Provide comprehensive CV evaluation and recommendations",
backstory="""You are an expert CV analyst with deep knowledge of industry requirements,
market trends, and talent assessment. You excel at evaluating skills, experience,
and providing actionable recommendations.""",
verbose=True
)
cv_analysis_task = Task(
description="""Perform a detailed CV analysis with scoring metrics and provide output in the following format:
1. Overall Score (0-100):
- Calculate based on weighted average of all metrics
2. Individual Skill Metrics:
- For each major skill found in CV:
* Name of skill
* Score (0-100)
* Specific feedback/comments
3. Core Metrics (0-100 each):
- Experience Score: Based on relevance and years
- Education Score: Based on relevance and level
- Presentation Score: CV format, clarity, structure
- Achievement Impact Score: Measurable results
- Market Readiness: Current market fit
4. Technical Skills:
- List each skill with proficiency score (0-100)
5. Soft Skills:
- List each skill with proficiency score (0-100)
6. Detailed Analysis sections
7. Recommendations:
- 3-5 actionable steps for improvement""",
expected_output="Structured CV analysis report with detailed metrics and scoring justification",
agent=cv_analyzer,
output_pydantic=CVAnalysisReport
)
agents = PraisonAIAgents(
agents=[cv_analyzer],
tasks=[cv_analysis_task]
)
try:
result = await agents.astart(cv_content)
return result['task_results'][0].pydantic
except Exception as e:
print(f"Error during analysis: {str(e)}")
raise e
def analyze_cv(cv_content):
async def run_analysis():
return await analyze_cv_async(cv_content)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
result = loop.run_until_complete(run_analysis())
return result
except Exception as e:
raise e
finally:
loop.close()
def main():
st.set_page_config(page_title="CV Analyzer", layout="wide")
st.title("Professional CV Analyzer")
# File upload
cv_file = st.file_uploader("Upload your CV (TXT format)", type=['txt'])
if cv_file:
cv_content = cv_file.read().decode()
with st.spinner('Analyzing your CV...'):
try:
analysis = analyze_cv(cv_content)
# Display Overall Score
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Overall Score", f"{analysis.overall_score}/100")
with col2:
st.metric("Market Readiness", f"{analysis.market_readiness}/100")
with col3:
st.metric("Experience Score", f"{analysis.experience_score}/100")
# Core Metrics
st.subheader("Core Metrics")
metrics_cols = st.columns(4)
with metrics_cols[0]:
st.metric("Education", f"{analysis.education_score}/100")
with metrics_cols[1]:
st.metric("Presentation", f"{analysis.presentation_score}/100")
with metrics_cols[2]:
st.metric("Achievement Impact", f"{analysis.achievement_impact_score}/100")
with metrics_cols[3]:
st.metric("Market Readiness", f"{analysis.market_readiness}/100")
# Skill Metrics
st.subheader("Detailed Skill Analysis")
for skill in analysis.skill_metrics:
with st.expander(f"{skill.name} - Score: {skill.score}/100"):
st.write(skill.comments)
# Technical and Soft Skills
col1, col2 = st.columns(2)
with col1:
st.subheader("Technical Skills")
for skill in analysis.technical_skills:
for skill_name, score in skill.items():
st.progress(score/100)
st.caption(f"{skill_name}: {score}/100")
with col2:
st.subheader("Soft Skills")
for skill in analysis.soft_skills:
for skill_name, score in skill.items():
st.progress(score/100)
st.caption(f"{skill_name}: {score}/100")
# Key Information
st.subheader("Key Analysis")
col1, col2 = st.columns(2)
with col1:
st.write("🎯 Key Strengths")
for strength in analysis.key_strengths:
st.write(f"• {strength}")
st.write("🎮 Areas of Expertise")
for expertise in analysis.areas_of_expertise:
st.write(f"• {expertise}")
with col2:
st.write("🔄 Areas for Improvement")
for area in analysis.areas_for_improvement:
st.write(f"• {area}")
st.write("⚠️ Skill Gaps")
for gap in analysis.skill_gaps:
st.write(f"• {gap}")
# Impact Metrics
st.subheader("Impact Metrics")
for metric in analysis.impact_metrics:
for key, value in metric.items():
st.metric(key, value)
# Recommendations
st.subheader("📋 Recommendations")
for rec in analysis.recommendations:
st.write(f"• {rec}")
except Exception as e:
st.error(f"An error occurred during analysis: {str(e)}")
else:
st.info("Please upload your CV in TXT format to begin the analysis")
if __name__ == "__main__":
main()
+61
View File
@@ -0,0 +1,61 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from pydantic import BaseModel
from typing import List, Dict
class CVAnalysisReport(BaseModel):
overall_score: int
key_strengths: List[str]
areas_for_improvement: List[str]
market_fit: str
recommendations: List[str]
technical_skills: List[str]
soft_skills: List[str]
skill_gaps: List[str]
key_achievements: List[str]
impact_metrics: List[Dict[str, str]]
areas_of_expertise: List[str]
# Create single CV analysis agent
cv_analyzer = Agent(
role="CV Analysis Expert",
goal="Provide comprehensive CV evaluation and recommendations",
backstory="""You are an expert CV analyst with deep knowledge of industry requirements,
market trends, and talent assessment. You excel at evaluating skills, experience,
and providing actionable recommendations.""",
verbose=True
)
# Read CV content first
with open('cv.txt', 'r') as file:
cv_content = file.read()
cv_key_strengths = Task(
description="List the key strengths to look",
expected_output="List of key strengths",
agent=cv_analyzer,
)
# Single comprehensive analysis task with CV content in context
cv_analysis_task = Task(
description="""Analyze the CV comprehensively including:
1. Technical and soft skills assessment
2. Experience and achievements evaluation
3. Overall CV evaluation with recommendations
Provide detailed insights and actionable recommendations.""",
expected_output="Complete CV analysis report",
agent=cv_analyzer,
output_pydantic=CVAnalysisReport,
context=[cv_content, cv_key_strengths] # Pass CV content directly in task context
)
# Create and run the agent
agents = PraisonAIAgents(
agents=[cv_analyzer],
tasks=[cv_key_strengths,cv_analysis_task],
process="sequential"
)
# Start the analysis - no need to pass content again since it's in task context
result = agents.start()
print(result)
+27
View File
@@ -0,0 +1,27 @@
# example tools.py
from duckduckgo_search import DDGS
from praisonai_tools import BaseTool
class InternetSearchTool(BaseTool):
name: str = "InternetSearchTool"
description: str = "Search Internet for relevant information based on a query or latest news"
def _run(self, query: str):
ddgs = DDGS()
results = ddgs.text(keywords=query, region='wt-wt', safesearch='moderate', max_results=5)
return results
# Example agent
from praisonaiagents import Agent
from praisonaiagents import PraisonAIAgents
agent = Agent(
name="Internet Search Agent",
tools=[InternetSearchTool],
instructions="""
You are an agent that can search the internet for relevant information based on a query or latest news.
"""
)
# Run the agent
result = PraisonAIAgents(agents=[agent], verbose=10).start()
print(result)
+57
View File
@@ -0,0 +1,57 @@
Name: Alex Johnson
Location: London, UK
Professional Summary
Dynamic and results-oriented leader with a proven track record of driving innovation, scaling operations, and achieving transformational growth. Over 15 years of experience in engineering, strategic management, and executive leadership in the automotive and renewable energy sectors. Passionate about sustainable technologies and visionary leadership.
Professional Experience
Chief Operating Officer (COO)
NextGen Mobility Ltd. | Palo Alto, CA | 2020 – Present
Spearheaded global operations for an electric vehicle company, scaling production by 200% in three years.
Introduced automation-driven supply chain strategies, reducing costs by 15%.
Collaborated with R&D to launch three new EV models, driving 25% revenue growth annually.
Vice President of Engineering
ElectroDrive Systems | San Francisco, CA | 2015 – 2020
Led a team of 500+ engineers in developing next-generation EV batteries, achieving a 30% increase in energy efficiency.
Secured $50M in government funding for sustainable energy innovation.
Drove cross-functional collaboration, aligning product development with market needs.
Director of Renewable Energy Initiatives
GreenTech Innovations | London, UK | 2011 – 2015
Oversaw £100M projects in solar and wind energy deployment across Europe.
Established partnerships with policymakers to influence renewable energy legislation.
Pioneered a smart energy grid system, enhancing energy distribution efficiency by 40%.
Senior Project Manager
Innovate Auto Ltd. | Munich, Germany | 2008 – 2011
Managed R&D projects for autonomous driving technologies.
Delivered projects 20% under budget through strategic resource allocation.
Filed 10+ patents in automotive AI and renewable integration.
Mechanical Engineer
AutoFusion Technologies | Berlin, Germany | 2005 – 2008
Designed core systems for EV powertrains, optimising energy consumption by 15%.
Collaborated with suppliers to improve material efficiency, cutting costs by £5M annually.
Education
MBA in Strategic Management
Harvard Business School | Cambridge, MA | 2015
MSc in Renewable Energy Engineering
Imperial College London | London, UK | 2007
BSc in Mechanical Engineering
Technical University of Munich | Munich, Germany | 2005
Skills
Strategic Leadership & Visionary Thinking
Electric Vehicle Engineering
Renewable Energy Systems
Global Operations Management
Policy Advocacy & Government Relations
Supply Chain Optimisation
Achievements
Featured in Forbes 30 Under 40 for innovations in EV technology.
Author of The Future of Sustainable Mobility.
Keynote speaker at World Energy Summit, 2023.
+10
View File
@@ -0,0 +1,10 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import read_csv, read_excel, write_csv, write_excel, filter_data, get_summary, group_by, pivot_table
import os
agent = Agent(instructions="You are a Data Analyst Agent", tools=[read_csv, read_excel, write_csv, write_excel, filter_data, get_summary, group_by, pivot_table])
agent.start(f"""
Read the data from the csv file {os.path.join(os.path.dirname(__file__), "tesla-stock-price.csv")}
Analyse the data and give me the insights
read_csv to read the file
""")
+3
View File
@@ -0,0 +1,3 @@
I am Mervin Praison, a software engineer and entrepreneur.
I am the founder of Praison AI, a company that is building AI agents to help people with their daily tasks.
+70
View File
@@ -0,0 +1,70 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create generator and evaluator agents
generator = Agent(
name="Generator",
role="Solution generator",
goal="Generate initial solutions and incorporate feedback",
instructions=(
"1. Look at the context from previous tasks.\n"
"2. If you see that you have already produced 2 points, then add another 2 new points "
" so that the total becomes 10.\n"
"3. Otherwise, just produce the first 2 points.\n"
"4. Return only the final list of points, with no extra explanation."
)
)
evaluator = Agent(
name="Evaluator",
role="Solution evaluator",
goal="Evaluate solutions and provide improvement feedback",
instructions=(
"1. Count how many lines in the response start with a number and a period (like '1. ' or '2. ').\n"
"2. If there are 10 or more, respond with 'done'.\n"
"3. Otherwise, respond with 'more'.\n"
"4. Return only the single word: 'done' or 'more'."
)
)
# Create tasks for the feedback loop
generate_task = Task(
name="generate",
description="Write 2 points about AI incuding if anything exiting from previous points",
expected_output="2 points",
agent=generator,
is_start=True,
task_type="decision",
next_tasks=["evaluate"]
)
evaluate_task = Task(
name="evaluate",
description="Check if there are 10 points about AI",
expected_output="more or done",
agent=evaluator,
next_tasks=["generate"],
context=[generate_task],
task_type="decision",
condition={
"more": ["generate"], # Continue to generate
"done": [""] # Exit when optimization complete
}
)
# Create workflow manager
workflow = PraisonAIAgents(
agents=[generator, evaluator],
tasks=[generate_task, evaluate_task],
process="workflow",
verbose=True
)
# Run optimization workflow
results = workflow.start()
# Print results
print("\nEvaluator-Optimizer Results:")
for task_id, result in results["task_results"].items():
if result:
print(f"Task {task_id}: {result.raw}")
+151
View File
@@ -0,0 +1,151 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from typing import List, Dict, Union
from duckduckgo_search import DDGS
from langchain_community.tools import YouTubeSearchTool
from langchain_community.utilities import WikipediaAPIWrapper
# 1. Tool
def internet_search_tool(query: str) -> List[Dict]:
"""
Perform a search using DuckDuckGo.
Args:
query (str): The search query.
Returns:
list: A list of search result titles, URLs, and snippets.
"""
try:
results = []
ddgs = DDGS()
for result in ddgs.text(keywords=query, max_results=10):
results.append({
"title": result.get("title", ""),
"url": result.get("href", ""),
"snippet": result.get("body", "")
})
return results
except Exception as e:
print(f"Error during DuckDuckGo search: {e}")
return []
def youtube_search_tool(query: str, inspect: bool = False, max_results: int = 2):
"""
Provide a custom wrapper around the YouTubeSearchTool.
Args:
query (str): The search query for YouTube.
inspect (bool): If True, returns tool inspection info instead of search results.
max_results (int): Maximum number of results to return (default: 2).
Returns:
Union[List[str], dict]: List of YouTube video URLs or tool inspection info.
"""
yt = YouTubeSearchTool()
if inspect:
inspection_info = {
"type": type(yt),
"attributes": [attr for attr in dir(yt) if not attr.startswith('_')],
"methods": {
"run": getattr(yt, 'run', None),
"arun": getattr(yt, 'arun', None)
},
"properties": {
"name": getattr(yt, 'name', 'youtube_search'),
"description": getattr(yt, 'description', 'Search YouTube videos'),
"return_direct": getattr(yt, 'return_direct', False)
}
}
return inspection_info
# Format query with max_results
formatted_query = f"{query}, {max_results}"
return yt.run(formatted_query)
def wikipedia_search_tool(query: str, inspect: bool = False, max_chars: int = 4000, top_k: int = 3):
"""
Provide a custom wrapper around langchain_community's WikipediaAPIWrapper.
Args:
query (str): A search query for Wikipedia.
inspect (bool): If True, returns tool inspection info instead of search results.
max_chars (int): Maximum characters to return (default: 4000).
top_k (int): Number of top results to consider (default: 3).
Returns:
Union[str, dict]: Summary from Wikipedia or tool inspection info if inspect=True.
"""
w = WikipediaAPIWrapper(
top_k_results=top_k,
doc_content_chars_max=max_chars,
lang='en'
)
if inspect:
inspection_info = {
"type": type(w),
"attributes": [attr for attr in dir(w) if not attr.startswith('_')],
"methods": {
"run": getattr(w, 'run', None),
"arun": getattr(w, 'arun', None)
},
"properties": {
"name": "wikipedia",
"description": "Search and get summaries from Wikipedia",
"top_k": w.top_k_results,
"lang": w.lang,
"max_chars": w.doc_content_chars_max
}
}
return inspection_info
try:
result = w.run(query)
return result
except Exception as e:
return f"Error searching Wikipedia: {str(e)}"
# 2. Agent
data_agent = Agent(
name="DataCollector",
role="Search Specialist",
goal="Perform internet searches to collect relevant information.",
backstory="Expert in finding and organizing internet data from multiple sources.",
tools=[internet_search_tool, youtube_search_tool, wikipedia_search_tool],
self_reflect=False
)
# 3. Tasks
collect_task = Task(
description="Perform an internet search using the query: 'AI job trends in 2024'. Return results as a list of title, URL, and snippet.",
expected_output="List of search results with titles, URLs, and snippets.",
agent=data_agent,
name="collect_data",
is_start=True,
next_tasks=["validate_data"]
)
validate_task = Task(
description="""Validate the collected data. Check if:
1. At least 5 results are returned.
2. Each result contains a title and a URL.
Return validation_result as 'valid' or 'invalid' only no other text.""",
expected_output="Validation result indicating if data is valid or invalid.",
agent=data_agent,
name="validate_data",
task_type="decision",
condition={
"valid": [], # End the workflow on valid data
"invalid": ["collect_data"] # Retry data collection on invalid data
},
)
# 4. Workflow
agents = PraisonAIAgents(
agents=[data_agent],
tasks=[collect_task, validate_task],
verbose=1,
process="workflow"
)
agents.start()
File renamed without changes.
+5
View File
@@ -0,0 +1,5 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import get_stock_price, get_stock_info, get_historical_data
agent = Agent(instructions="You are a Research Agent", tools=[get_stock_price, get_stock_info, get_historical_data])
agent.start("Understand current stock price and historical data of Apple and Google. Tell me if I can invest in them")
+34
View File
@@ -0,0 +1,34 @@
import gradio as gr
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import duckduckgo
def research(query):
agent = Agent(instructions="You are a Research Agent", tools=[duckduckgo])
result = agent.start(query)
# Format the result with enhanced markdown
formatted_result = f"""
{result}
----
*Generated by PraisonAI Research Assistant*
"""
return formatted_result
# Create a simple Gradio interface
demo = gr.Interface(
fn=research,
inputs=gr.Textbox(
label="Research Query",
placeholder="Enter your research topic...",
lines=2
),
outputs=gr.Markdown(
show_copy_button=True,
height=500,
container=True
),
title="AI Research Assistant",
description="Enter your research query below to get started!",
)
if __name__ == "__main__":
demo.launch()
View File
Whitespace-only changes.
+159
View File
@@ -0,0 +1,159 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from pydantic import BaseModel
from typing import List, Dict
import os
import json
class ProjectEvaluation(BaseModel):
innovation_score: int # 0-100
technical_complexity: int # 0-100
presentation_quality: int # 0-100
user_experience: int # 0-100
completeness: int # 0-100
overall_score: int # 0-100
key_strengths: List[str]
areas_for_improvement: List[str]
notable_features: List[str]
technical_highlights: List[str]
recommendations: List[str]
market_potential: str
scalability_assessment: str
# Create Vision Analysis Agent
hackathon_judge = Agent(
name="HackathonJudge",
role="Technical Project Evaluator",
goal="Evaluate hackathon projects through video demonstrations",
backstory="""You are an experienced hackathon judge and technical expert.
You excel at evaluating innovation, technical implementation, and presentation quality.
You provide constructive feedback and identify both strengths and areas for improvement.""",
llm="gpt-4o-mini", # Using vision-capable model
self_reflect=False,
knowledge=""
)
def evaluate_project(video_path: str) -> ProjectEvaluation:
"""
Evaluate a hackathon project based on its video demonstration
"""
evaluation_task = Task(
name="project_evaluation",
description="""Analyze this hackathon project video demonstration and provide a comprehensive evaluation:
1. Score the following aspects (0-100):
- Innovation and Creativity
- Technical Complexity
- Presentation Quality
- User Experience
- Project Completeness
2. Identify:
- Key strengths and standout features
- Areas that could be improved
- Notable technical implementations
- Market potential and scalability
3. Provide:
- Specific recommendations for improvement
- Technical suggestions
- Potential future enhancements""",
expected_output="Detailed project evaluation with scores and feedback",
agent=hackathon_judge,
output_pydantic=ProjectEvaluation,
images=[video_path] # Video input for multimodal analysis
)
# Initialize and run evaluation
agents = PraisonAIAgents(
agents=[hackathon_judge],
tasks=[evaluation_task],
process="sequential",
verbose=True
)
response = agents.start()
try:
# If response contains task_results, extract the Pydantic model directly
if isinstance(response, dict) and 'task_results' in response:
task_output = response['task_results'][0]
if hasattr(task_output, 'pydantic'):
return task_output.pydantic
elif hasattr(task_output, 'raw'):
# Extract JSON from raw string if it's wrapped in ```json
raw_text = task_output.raw
if raw_text.startswith('```json'):
raw_text = raw_text.split('\n', 1)[1].rsplit('\n', 1)[0]
evaluation_data = json.loads(raw_text)
else:
evaluation_data = json.loads(task_output) if isinstance(task_output, str) else task_output
# If response is a string, try to parse it as JSON
elif isinstance(response, str):
evaluation_data = json.loads(response)
# If response is a dict with task_status
elif isinstance(response, dict) and 'task_status' in response:
content = response['task_status']
if isinstance(content, dict):
evaluation_data = content
else:
evaluation_data = json.loads(content) if isinstance(content, str) else content
else:
evaluation_data = response
print(f"Debug - Parsed evaluation_data: {evaluation_data}")
# Create and return ProjectEvaluation instance
return ProjectEvaluation(
innovation_score=int(evaluation_data.get('innovation_score', 0)),
technical_complexity=int(evaluation_data.get('technical_complexity', 0)),
presentation_quality=int(evaluation_data.get('presentation_quality', 0)),
user_experience=int(evaluation_data.get('user_experience', 0)),
completeness=int(evaluation_data.get('completeness', 0)),
overall_score=int(evaluation_data.get('overall_score', 0)),
key_strengths=evaluation_data.get('key_strengths', []),
areas_for_improvement=evaluation_data.get('areas_for_improvement', []),
notable_features=evaluation_data.get('notable_features', []),
technical_highlights=evaluation_data.get('technical_highlights', []),
recommendations=evaluation_data.get('recommendations', []),
market_potential=str(evaluation_data.get('market_potential', '')),
scalability_assessment=str(evaluation_data.get('scalability_assessment', ''))
)
except Exception as e:
print(f"Debug - Raw response: {response}")
print(f"Error processing response: {e}")
raise
if __name__ == "__main__":
# Example usage
current_dir = os.path.dirname(os.path.abspath(__file__))
video_path = os.path.join(current_dir, "presentation.mp4")
result = evaluate_project(video_path) # Now returns ProjectEvaluation directly
print("\nHackathon Project Evaluation")
print("===========================")
print(f"\nOverall Score: {result.overall_score}/100")
print("\nDetailed Scores:")
print(f"Innovation: {result.innovation_score}/100")
print(f"Technical Complexity: {result.technical_complexity}/100")
print(f"Presentation: {result.presentation_quality}/100")
print(f"User Experience: {result.user_experience}/100")
print(f"Completeness: {result.completeness}/100")
print("\nKey Strengths:")
for strength in result.key_strengths:
print(f"- {strength}")
print("\nAreas for Improvement:")
for area in result.areas_for_improvement:
print(f"- {area}")
print("\nTechnical Highlights:")
for highlight in result.technical_highlights:
print(f"- {highlight}")
print("\nRecommendations:")
for rec in result.recommendations:
print(f"- {rec}")
print(f"\nMarket Potential: {result.market_potential}")
print(f"\nScalability Assessment: {result.scalability_assessment}")
+159
View File
@@ -0,0 +1,159 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from pydantic import BaseModel
from typing import List, Dict
import os
import json
class ProjectEvaluation(BaseModel):
innovation_score: int # 0-100
technical_complexity: int # 0-100
presentation_quality: int # 0-100
user_experience: int # 0-100
completeness: int # 0-100
overall_score: int # 0-100
key_strengths: List[str]
areas_for_improvement: List[str]
notable_features: List[str]
technical_highlights: List[str]
recommendations: List[str]
market_potential: str
scalability_assessment: str
# Create Vision Analysis Agent
hackathon_judge = Agent(
name="HackathonJudge",
role="Technical Project Evaluator",
goal="Evaluate hackathon projects through video demonstrations",
backstory="""You are an experienced hackathon judge and technical expert.
You excel at evaluating innovation, technical implementation, and presentation quality.
You provide constructive feedback and identify both strengths and areas for improvement.""",
llm="gpt-4o-mini", # Using vision-capable model
self_reflect=False,
knowledge=""
)
def evaluate_project(video_path: str) -> ProjectEvaluation:
"""
Evaluate a hackathon project based on its video demonstration
"""
evaluation_task = Task(
name="project_evaluation",
description="""Analyze this hackathon project video demonstration and provide a comprehensive evaluation:
1. Score the following aspects (0-100):
- Innovation and Creativity
- Technical Complexity
- Presentation Quality
- User Experience
- Project Completeness
2. Identify:
- Key strengths and standout features
- Areas that could be improved
- Notable technical implementations
- Market potential and scalability
3. Provide:
- Specific recommendations for improvement
- Technical suggestions
- Potential future enhancements""",
expected_output="Detailed project evaluation with scores and feedback",
agent=hackathon_judge,
output_pydantic=ProjectEvaluation,
images=[video_path] # Video input for multimodal analysis
)
# Initialize and run evaluation
agents = PraisonAIAgents(
agents=[hackathon_judge],
tasks=[evaluation_task],
process="sequential",
verbose=True
)
response = agents.start()
try:
# If response contains task_results, extract the Pydantic model directly
if isinstance(response, dict) and 'task_results' in response:
task_output = response['task_results'][0]
if hasattr(task_output, 'pydantic'):
return task_output.pydantic
elif hasattr(task_output, 'raw'):
# Extract JSON from raw string if it's wrapped in ```json
raw_text = task_output.raw
if raw_text.startswith('```json'):
raw_text = raw_text.split('\n', 1)[1].rsplit('\n', 1)[0]
evaluation_data = json.loads(raw_text)
else:
evaluation_data = json.loads(task_output) if isinstance(task_output, str) else task_output
# If response is a string, try to parse it as JSON
elif isinstance(response, str):
evaluation_data = json.loads(response)
# If response is a dict with task_status
elif isinstance(response, dict) and 'task_status' in response:
content = response['task_status']
if isinstance(content, dict):
evaluation_data = content
else:
evaluation_data = json.loads(content) if isinstance(content, str) else content
else:
evaluation_data = response
print(f"Debug - Parsed evaluation_data: {evaluation_data}")
# Create and return ProjectEvaluation instance
return ProjectEvaluation(
innovation_score=int(evaluation_data.get('innovation_score', 0)),
technical_complexity=int(evaluation_data.get('technical_complexity', 0)),
presentation_quality=int(evaluation_data.get('presentation_quality', 0)),
user_experience=int(evaluation_data.get('user_experience', 0)),
completeness=int(evaluation_data.get('completeness', 0)),
overall_score=int(evaluation_data.get('overall_score', 0)),
key_strengths=evaluation_data.get('key_strengths', []),
areas_for_improvement=evaluation_data.get('areas_for_improvement', []),
notable_features=evaluation_data.get('notable_features', []),
technical_highlights=evaluation_data.get('technical_highlights', []),
recommendations=evaluation_data.get('recommendations', []),
market_potential=str(evaluation_data.get('market_potential', '')),
scalability_assessment=str(evaluation_data.get('scalability_assessment', ''))
)
except Exception as e:
print(f"Debug - Raw response: {response}")
print(f"Error processing response: {e}")
raise
if __name__ == "__main__":
# Example usage
current_dir = os.path.dirname(os.path.abspath(__file__))
video_path = os.path.join(current_dir, "presentation.mp4")
result = evaluate_project(video_path) # Now returns ProjectEvaluation directly
print("\nHackathon Project Evaluation")
print("===========================")
print(f"\nOverall Score: {result.overall_score}/100")
print("\nDetailed Scores:")
print(f"Innovation: {result.innovation_score}/100")
print(f"Technical Complexity: {result.technical_complexity}/100")
print(f"Presentation: {result.presentation_quality}/100")
print(f"User Experience: {result.user_experience}/100")
print(f"Completeness: {result.completeness}/100")
print("\nKey Strengths:")
for strength in result.key_strengths:
print(f"- {strength}")
print("\nAreas for Improvement:")
for area in result.areas_for_improvement:
print(f"- {area}")
print("\nTechnical Highlights:")
for highlight in result.technical_highlights:
print(f"- {highlight}")
print("\nRecommendations:")
for rec in result.recommendations:
print(f"- {rec}")
print(f"\nMarket Potential: {result.market_potential}")
print(f"\nScalability Assessment: {result.scalability_assessment}")
+210
View File
@@ -0,0 +1,210 @@
import streamlit as st
import os
from praisonaiagents import Agent, Task, PraisonAIAgents
from pydantic import BaseModel
from typing import List, Dict
import tempfile
import json
class ProjectEvaluation(BaseModel):
innovation_score: int # 0-100
technical_complexity: int # 0-100
presentation_quality: int # 0-100
user_experience: int # 0-100
completeness: int # 0-100
overall_score: int # 0-100
key_strengths: List[str]
areas_for_improvement: List[str]
notable_features: List[str]
technical_highlights: List[str]
recommendations: List[str]
market_potential: str
scalability_assessment: str
# Set page config
st.set_page_config(
page_title="Hackathon Project Evaluator",
page_icon="🏆",
layout="wide"
)
# Create Vision Analysis Agent
@st.cache_resource
def get_hackathon_judge():
return Agent(
name="HackathonJudge",
role="Technical Project Evaluator",
goal="Evaluate hackathon projects through video demonstrations",
backstory="""You are an experienced hackathon judge and technical expert.
You excel at evaluating innovation, technical implementation, and presentation quality.
You provide constructive feedback and identify both strengths and areas for improvement.""",
llm="gpt-4o-mini", # Using vision-capable model
self_reflect=False,
knowledge=""
)
def evaluate_project(video_path: str) -> ProjectEvaluation:
"""
Evaluate a hackathon project based on its video demonstration
"""
hackathon_judge = get_hackathon_judge()
evaluation_task = Task(
name="project_evaluation",
description="""Analyze this hackathon project video demonstration and provide a comprehensive evaluation:
1. Score the following aspects (0-100):
- Innovation and Creativity
- Technical Complexity
- Presentation Quality
- User Experience
- Project Completeness
2. Identify:
- Key strengths and standout features
- Areas that could be improved
- Notable technical implementations
- Market potential and scalability
3. Provide:
- Specific recommendations for improvement
- Technical suggestions
- Potential future enhancements""",
expected_output="Detailed project evaluation with scores and feedback",
agent=hackathon_judge,
output_pydantic=ProjectEvaluation,
images=[video_path] # Video input for multimodal analysis
)
# Initialize and run evaluation
agents = PraisonAIAgents(
agents=[hackathon_judge],
tasks=[evaluation_task],
process="sequential",
verbose=True
)
response = agents.start()
try:
# If response contains task_results, extract the Pydantic model directly
if isinstance(response, dict) and 'task_results' in response:
task_output = response['task_results'][0]
if hasattr(task_output, 'pydantic'):
return task_output.pydantic
elif hasattr(task_output, 'raw'):
# Extract JSON from raw string if it's wrapped in ```json
raw_text = task_output.raw
if raw_text.startswith('```json'):
raw_text = raw_text.split('\n', 1)[1].rsplit('\n', 1)[0]
evaluation_data = json.loads(raw_text)
else:
evaluation_data = json.loads(task_output) if isinstance(task_output, str) else task_output
elif isinstance(response, str):
evaluation_data = json.loads(response)
elif isinstance(response, dict) and 'task_status' in response:
content = response['task_status']
if isinstance(content, dict):
evaluation_data = content
else:
evaluation_data = json.loads(content) if isinstance(content, str) else content
else:
evaluation_data = response
# Create and return ProjectEvaluation instance
return ProjectEvaluation(
innovation_score=int(evaluation_data.get('innovation_score', 0)),
technical_complexity=int(evaluation_data.get('technical_complexity', 0)),
presentation_quality=int(evaluation_data.get('presentation_quality', 0)),
user_experience=int(evaluation_data.get('user_experience', 0)),
completeness=int(evaluation_data.get('completeness', 0)),
overall_score=int(evaluation_data.get('overall_score', 0)),
key_strengths=evaluation_data.get('key_strengths', []),
areas_for_improvement=evaluation_data.get('areas_for_improvement', []),
notable_features=evaluation_data.get('notable_features', []),
technical_highlights=evaluation_data.get('technical_highlights', []),
recommendations=evaluation_data.get('recommendations', []),
market_potential=str(evaluation_data.get('market_potential', '')),
scalability_assessment=str(evaluation_data.get('scalability_assessment', ''))
)
except Exception as e:
print(f"Debug - Raw response: {response}")
print(f"Error processing response: {e}")
raise
# Title and description
st.title("🏆 Hackathon Judge Agent")
st.markdown("""
Upload your hackathon project demonstration video for an AI-powered evaluation.
Get comprehensive feedback on various aspects of your project.
""")
# File uploader
uploaded_file = st.file_uploader("Choose a video file", type=['mp4', 'avi', 'mov', 'mkv'])
if uploaded_file:
# Create a temporary file to store the video
with tempfile.NamedTemporaryFile(delete=False, suffix='.'+uploaded_file.name.split('.')[-1]) as tmp_file:
tmp_file.write(uploaded_file.getvalue())
video_path = tmp_file.name
with st.spinner("🤖 AI is evaluating your project..."):
try:
# Evaluate the project
result = evaluate_project(video_path)
# Display results
st.header("Overall Score")
st.metric("Overall Score", f"{result.overall_score}/100")
# Display detailed scores
st.header("Detailed Scores")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Innovation", f"{result.innovation_score}/100")
st.metric("Technical Complexity", f"{result.technical_complexity}/100")
with col2:
st.metric("Presentation", f"{result.presentation_quality}/100")
st.metric("User Experience", f"{result.user_experience}/100")
with col3:
st.metric("Completeness", f"{result.completeness}/100")
# Display qualitative feedback
st.header("Key Strengths")
for strength in result.key_strengths:
st.write(f"• {strength}")
st.header("Areas for Improvement")
for area in result.areas_for_improvement:
st.write(f"• {area}")
st.header("Technical Highlights")
for highlight in result.technical_highlights:
st.write(f"• {highlight}")
st.header("Notable Features")
for feature in result.notable_features:
st.write(f"• {feature}")
st.header("Recommendations")
for rec in result.recommendations:
st.write(f"• {rec}")
# Market Analysis
st.header("Market Analysis")
col1, col2 = st.columns(2)
with col1:
st.subheader("Market Potential")
st.write(result.market_potential)
with col2:
st.subheader("Scalability Assessment")
st.write(result.scalability_assessment)
except Exception as e:
st.error(f"Error evaluating the project: {str(e)}")
finally:
# Clean up the temporary file
os.unlink(video_path)
else:
# Display placeholder content
st.info("👆 Upload a video file to get started!")
+47
View File
@@ -0,0 +1,47 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import duckduckgo
agent = Agent(instructions="You are a Image Analysis Agent", tools=[duckduckgo])
agent.start("I want to go London next week, find me a good hotel and flight")
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create Image Analysis Agent
image_agent = Agent(
name="ImageAnalyst",
role="Image Analysis Specialist",
goal="Analyze images and videos to extract meaningful information",
backstory="""You are an expert in computer vision and image analysis.
You excel at describing images, detecting objects, and understanding visual content.""",
llm="gpt-4o-mini",
self_reflect=False
)
# 1. Task with Image URL
task1 = Task(
name="analyze_landmark",
description="Describe this famous landmark and its architectural features.",
expected_output="Detailed description of the landmark's architecture and significance",
agent=image_agent,
images=["https://upload.wikimedia.org/wikipedia/commons/b/bf/Krakow_-_Kosciol_Mariacki.jpg"]
)
# 2. Task with Local Image File
task2 = Task(
name="analyze_local_image",
description="What objects can you see in this image? Describe their arrangement.",
expected_output="Detailed description of objects and their spatial relationships",
agent=image_agent,
images=["image.jpg"]
)
# Create PraisonAIAgents instance
agents = PraisonAIAgents(
agents=[image_agent],
tasks=[task1, task2],
process="sequential",
verbose=1
)
# Run all tasks
agents.start()
+47
View File
@@ -0,0 +1,47 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import duckduckgo
agent = Agent(instructions="You are a Image Analysis Agent", tools=[duckduckgo])
agent.start("I want to go London next week, find me a good hotel and flight")
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create Image Analysis Agent
image_agent = Agent(
name="ImageAnalyst",
role="Image Analysis Specialist",
goal="Analyze images and videos to extract meaningful information",
backstory="""You are an expert in computer vision and image analysis.
You excel at describing images, detecting objects, and understanding visual content.""",
llm="gpt-4o-mini",
self_reflect=False
)
# 1. Task with Image URL
task1 = Task(
name="analyze_landmark",
description="Describe this famous landmark and its architectural features.",
expected_output="Detailed description of the landmark's architecture and significance",
agent=image_agent,
images=["https://upload.wikimedia.org/wikipedia/commons/b/bf/Krakow_-_Kosciol_Mariacki.jpg"]
)
# 2. Task with Local Image File
task2 = Task(
name="analyze_local_image",
description="What objects can you see in this image? Describe their arrangement.",
expected_output="Detailed description of objects and their spatial relationships",
agent=image_agent,
images=["image.jpg"]
)
# Create PraisonAIAgents instance
agents = PraisonAIAgents(
agents=[image_agent],
tasks=[task1, task2],
process="sequential",
verbose=1
)
# Run all tasks
agents.start()
+48
View File
@@ -0,0 +1,48 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
import logging
import os
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Define the configuration for the Knowledge instance
config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "knowledge_test",
"path": ".praison",
}
}
}
# Create an agent with knowledge capabilities
knowledge_agent = Agent(
name="KnowledgeAgent",
role="Information Specialist",
goal="Store and retrieve knowledge efficiently",
backstory="Expert in managing and utilizing stored knowledge",
knowledge=["sample.pdf"],
knowledge_config=config,
verbose=True
)
# Define a task for the agent
knowledge_task = Task(
name="knowledge_task",
description="Who is Mervin Praison?",
expected_output="Answer to the question",
agent=knowledge_agent
)
# Create and start the agents
agents = PraisonAIAgents(
agents=[knowledge_agent],
tasks=[knowledge_task],
process="sequential",
user_id="user1"
)
# Start execution
result = agents.start()
+20
View File
@@ -0,0 +1,20 @@
from praisonaiagents import Agent
config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "custom_knowledge",
"path": ".praison",
}
}
}
agent = Agent(
name="Knowledge Agent",
instructions="You answer questions based on the provided knowledge.",
knowledge=["large.pdf"],
knowledge_config=config
)
agent.start("What is KAG in one line?")
+171
View File
@@ -0,0 +1,171 @@
import logging
from praisonaiagents.knowledge import Knowledge
import os
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "test",
"path": ".praison",
}
}
}
knowledge = Knowledge(config)
# Store memories
logger.info("Storing memories...")
cricket_memory = knowledge.store("Likes to play cricket on weekends", user_id="alice", metadata={"category": "hobbies"})
logger.info(f"Stored cricket memory: {cricket_memory}")
tennis_memory = knowledge.store("Enjoys playing tennis with friends", user_id="alice", metadata={"category": "hobbies"})
logger.info(f"Stored tennis memory: {tennis_memory}")
work_memory = knowledge.store("Works as a software engineer", user_id="alice", metadata={"category": "work"})
logger.info(f"Stored work memory: {work_memory}")
print("Stored memories:")
print(cricket_memory)
print(tennis_memory)
print(work_memory)
# Retrieve all memories for a user
logger.info("Retrieving all memories for Alice...")
alice_memories = knowledge.get_all(user_id="alice")
logger.info(f"Retrieved memories for Alice: {alice_memories}")
print("\nAll memories for Alice:")
for memory in alice_memories:
print(memory)
# Retrieve a specific memory by ID
logger.info(f"Retrieving memory with ID: {cricket_memory['results'][0]['id']}")
retrieved_memory = knowledge.get(cricket_memory['results'][0]["id"])
logger.info(f"Retrieved memory: {retrieved_memory}")
print("\nRetrieved memory:")
print(retrieved_memory)
# Search for memories based on a query
query = "What are Alice's hobbies?"
logger.info(f"Searching for memories with query: {query}")
search_results = knowledge.search(query, user_id="alice")
logger.info(f"Search results: {search_results}")
print(f"\nSearch results for '{query}':")
for memory in search_results:
print(memory)
# Update a memory
logger.info(f"Updating memory with ID: {tennis_memory['results'][0]['id']}")
updated_memory = knowledge.update(memory_id=tennis_memory['results'][0]["id"], data="Loves playing tennis on weekends")
logger.info(f"Updated memory: {updated_memory}")
print("\nUpdated memory:")
print(updated_memory)
# Get memory history
logger.info(f"Retrieving memory history for ID: {tennis_memory['results'][0]['id']}")
memory_history = knowledge.history(memory_id=tennis_memory['results'][0]["id"])
logger.info(f"Memory history: {memory_history}")
print("\nMemory history:")
for entry in memory_history:
print(entry)
# Delete a memory
logger.info(f"Deleting memory with ID: {work_memory['results'][0]['id']}")
knowledge.delete(memory_id=work_memory['results'][0]["id"])
logger.info("Memory deleted")
print("\nDeleted memory")
# Retrieve memories after deletion
logger.info("Retrieving memories after deletion...")
alice_memories = knowledge.get_all(user_id="alice")
logger.info(f"Retrieved memories after deletion: {alice_memories}")
print("\nMemories after deletion:")
for memory in alice_memories:
print(memory)
# Delete all memories for a user
logger.info("Deleting all memories for Alice...")
knowledge.delete_all(user_id="alice")
logger.info("All memories for Alice deleted")
print("\nAll memories for Alice deleted")
# Retrieve memories after deleting all
logger.info("Retrieving memories after deleting all...")
alice_memories = knowledge.get_all(user_id="alice")
logger.info(f"Retrieved memories after deleting all: {alice_memories}")
print("\nMemories after deleting all:")
print(alice_memories)
# Reset all memories
logger.info("Resetting all memories...")
knowledge.reset()
logger.info("All memories reset")
print("\nAll memories reset")
# Test file handling
logger.info("Testing file handling...")
try:
# Get absolute path to test files
current_dir = os.path.dirname(os.path.abspath(__file__))
pdf_file = os.path.join(current_dir, "small.pdf")
txt_file = os.path.join(current_dir, "sample.txt")
# Test text file
logger.info(f"Testing text file: {txt_file}")
# Read and display content
with open(txt_file, 'r') as file:
txt_content = file.read()
print("\nText file content:")
print(txt_content)
# Store the text file using add
txt_memory = knowledge.add(txt_file, user_id="bob", metadata={"type": "text"})
logger.info(f"Stored text file: {txt_memory}")
print("\nStored text content:")
if txt_memory and 'results' in txt_memory and txt_memory['results']:
for mem in txt_memory['results']:
print(mem)
else:
print("No valid memories were stored from the text file")
# Verify text memories are stored
if txt_memory and 'results' in txt_memory and txt_memory['results']:
logger.info("Verifying text memories...")
for mem in txt_memory['results']:
if isinstance(mem, dict) and 'id' in mem:
stored = knowledge.get(mem['id'])
logger.info(f"Verified text memory: {stored}")
else:
logger.warning(f"Unexpected memory format: {mem}")
else:
logger.warning("No valid memories were stored from the text file")
# Test pdf file
logger.info(f"Testing pdf file: {pdf_file}")
pdf_memory = knowledge.add(pdf_file, user_id="bob", metadata={"type": "pdf"})
logger.info(f"Stored pdf file: {pdf_memory}")
print("\nStored pdf content:")
if pdf_memory and 'results' in pdf_memory and pdf_memory['results']:
for mem in pdf_memory['results']:
print(mem)
else:
print("No valid memories were stored from the PDF file")
# Verify stored content
if pdf_memory and 'results' in pdf_memory and pdf_memory['results']:
for memory in pdf_memory['results']:
if isinstance(memory, dict) and 'id' in memory:
stored_memory = knowledge.get(memory['id'])
logger.info(f"Verified stored memory: {stored_memory}")
print("\nVerified content:")
print(stored_memory)
else:
logger.warning(f"Unexpected memory format: {memory}")
else:
logger.warning("No valid memories were stored from the PDF file")
except Exception as e:
logger.error(f"Error during file handling test: {str(e)}")
raise
+40
View File
@@ -0,0 +1,40 @@
import logging
from praisonaiagents.knowledge import Knowledge
import os
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
knowledge = Knowledge()
try:
# Get absolute path to test files
current_dir = os.path.dirname(os.path.abspath(__file__))
pdf_file = os.path.join(current_dir, "large.pdf")
# Test pdf file
logger.info(f"Testing pdf file: {pdf_file}")
pdf_memory = knowledge.add(pdf_file, user_id="bob", metadata={"type": "pdf"})
logger.info(f"Stored pdf file: {pdf_memory}")
print("\nStored pdf content:")
if pdf_memory and 'results' in pdf_memory and pdf_memory['results']:
for mem in pdf_memory['results']:
print(mem)
else:
print("No valid memories were stored from the PDF file")
# Verify stored content
if pdf_memory and 'results' in pdf_memory and pdf_memory['results']:
for memory in pdf_memory['results']:
if isinstance(memory, dict) and 'id' in memory:
stored_memory = knowledge.get(memory['id'])
logger.info(f"Verified stored memory: {stored_memory}")
print("\nVerified content:")
print(stored_memory)
else:
logger.warning(f"Unexpected memory format: {memory}")
else:
logger.warning("No valid memories were stored from the PDF file")
except Exception as e:
logger.error(f"Error during file handling test: {str(e)}")
raise
+30
View File
@@ -0,0 +1,30 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from langchain_community.tools import YouTubeSearchTool
from langchain_community.utilities import WikipediaAPIWrapper
# Create an agent with both tools
agent = Agent(
name="SearchAgent",
role="Research Assistant",
goal="Search for information from multiple sources",
backstory="I am an AI assistant that can search YouTube and Wikipedia.",
tools=[YouTubeSearchTool, WikipediaAPIWrapper],
self_reflect=False
)
# Create tasks to demonstrate both tools
task = Task(
name="search_task",
description="Search for information about 'AI advancements' on both YouTube and Wikipedia",
expected_output="Combined information from YouTube videos and Wikipedia articles",
agent=agent
)
# Create and start the workflow
agents = PraisonAIAgents(
agents=[agent],
tasks=[task],
verbose=True
)
agents.start()
+73
View File
@@ -0,0 +1,73 @@
from langchain_community.tools import YouTubeSearchTool
from langchain_community.utilities import WikipediaAPIWrapper
def test_tools():
"""Test both YouTube and Wikipedia tools"""
print("\n=== Testing YouTube Search ===")
# Test YouTube
yt = YouTubeSearchTool()
# Test basic search
print("\nBasic YouTube Search:")
query = "AI advancements 2024"
print(f"Query: {query}")
result = yt.run(query)
print(f"Results: {result}")
# Test with max results
print("\nYouTube Search with max results:")
query = "AI advancements 2024, 3" # Format: query, max_results
print(f"Query: {query}")
result = yt.run(query)
print(f"Results: {result}")
print("\n=== Testing Wikipedia Search ===")
# Test Wikipedia
wiki = WikipediaAPIWrapper(
top_k_results=2,
doc_content_chars_max=500
)
# Test basic search
print("\nBasic Wikipedia Search:")
query = "Artificial Intelligence"
print(f"Query: {query}")
result = wiki.run(query)
print(f"Results: {result[:200]}..." if result else "No result")
# Test with different parameters
print("\nWikipedia Search with different language:")
wiki_fr = WikipediaAPIWrapper(
lang='fr',
top_k_results=1,
doc_content_chars_max=300
)
result = wiki_fr.run("Intelligence Artificielle")
print(f"French Results: {result[:200]}..." if result else "No result")
def print_tool_info():
"""Print detailed information about the tools"""
print("\n=== Tool Information ===")
# YouTube Tool Info
print("\nYouTube Tool:")
yt = YouTubeSearchTool()
print("Type:", type(yt))
print("Name:", getattr(yt, 'name', None))
print("Description:", getattr(yt, 'description', None))
print("Arguments:", getattr(yt, 'args', None))
# Wikipedia Tool Info
print("\nWikipedia Tool:")
wiki = WikipediaAPIWrapper()
print("Type:", type(wiki))
print("Available Settings:")
print("- Language:", wiki.lang)
print("- Top K Results:", wiki.top_k_results)
print("- Max Characters:", wiki.doc_content_chars_max)
if __name__ == "__main__":
print_tool_info()
test_tools()
BIN
View File
Binary file not shown.
View File
Whitespace-only changes.
+4
View File
@@ -0,0 +1,4 @@
from praisonaiagents import Agent
agent = Agent(instructions="You are a Markdown Agent, output in markdown format")
agent.start("Write a blog post about AI")
+5
View File
@@ -0,0 +1,5 @@
from markitdown import MarkItDown
md = MarkItDown()
result = md.convert("sample.txt")
print(result.text_content)
+183
View File
@@ -0,0 +1,183 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
import logging
import os
def main():
# Initialize memory config
memory_config = {
"provider": "rag",
"use_embedding": True,
"storage": {
"type": "sqlite",
"path": "./.praison/memory.db"
},
"rag_db_path": "./.praison/chroma_db"
}
# Test facts
fact1 = "The capital city of Jujuha is Hahanu and its population is 102300"
fact2 = "Three main ingredients in a classic proloder are eggs, sugar, and flour"
fact3 = "The year the first Josinga was released is 2007"
# # Check if database exists
# if os.path.exists("./memory.db"):
# logger.info("Found existing memory database")
# else:
# logger.info("Creating new memory database")
# Create task config (without memory config since it's moved to PraisonAIAgents)
task_config = {}
# Create agents with different roles
researcher = Agent(
role="Research Analyst",
goal="Research and document key information about topics",
backstory="Expert at analyzing and storing information in memory",
llm="gpt-4o-mini"
)
retriever = Agent(
role="Information Retriever",
goal="Retrieve and verify stored information from memory",
backstory="Specialist in searching and validating information from memory",
llm="gpt-4o-mini"
)
# Task 1: Process the facts
store_task = Task(
description=f"""
Process and analyze this information:
1. {fact1}
2. {fact2}
3. {fact3}
Provide a clear summary of each fact.
""",
expected_output="""
Clear statements summarizing each fact.
Example format:
1. [Summary of fact 1]
2. [Summary of fact 2]
3. [Summary of fact 3]
""",
agent=researcher
)
# Task 2: Write essay about AI
verify_task = Task(
description="""
write few points about AI
""",
expected_output="Points about AI",
agent=retriever
)
# Task 3: Query memory
query_task = Task(
description="""
Using ONLY information found in memory:
1. What is stored in memory about Hahanu?
2. What ingredients for proloder are recorded in memory?
3. What year is stored in memory for the Josinga release?
For each answer, cite the memory record you found.
""",
expected_output="Answers based solely on memory records with citations",
agent=retriever
)
# Task 4: Query both short-term and long-term memory
query_both_task = Task(
description="""
Using ONLY information found in memory:
1. What is stored in both short-term and long-term memory about Jujuha?
2. What ingredients for proloder are recorded in both short-term and long-term memory?
3. What year is stored in both short-term and long-term memory for the Josinga release?
For each answer, cite the memory record you found.
""",
expected_output="Answers based solely on memory records with citations",
agent=retriever
)
# Initialize PraisonAIAgents with memory configuration
agents = PraisonAIAgents(
agents=[researcher, retriever],
tasks=[store_task, verify_task, query_task, query_both_task],
verbose=True, # Use same verbose level as memory
memory=True,
embedder={
"provider": "openai",
"config": {
"model": "text-embedding-3-small"
}
}
)
# agents = PraisonAIAgents(
# agents=[researcher, retriever],
# tasks=[store_task, verify_task, query_task, query_both_task],
# verbose=True, # Use same verbose level as memory
# memory=True
# )
# Execute tasks
print("\nExecuting Memory Test Tasks...")
print("-" * 50)
agents.start()
# Use shared memory for final checks
memory = agents.shared_memory
# Test memory retrieval with different quality thresholds
if memory:
print("\nFinal Memory Check:")
print("-" * 50)
queries = ["Jujuha", "proloder", "Josinga"]
for query in queries:
print(f"\nSearching memory for: {query}")
# Search in both short-term and long-term memory
print("\nShort-term memory results:")
stm_results = memory.search_short_term(query)
if stm_results:
for item in stm_results:
print(f"Content: {item.get('content', '')[:200]}")
if 'meta' in item:
print(f"Metadata: {item['meta']}")
print("-" * 20)
else:
print("No results found in short-term memory")
print("\nLong-term memory results:")
ltm_results = memory.search_long_term(query)
if ltm_results:
for item in ltm_results:
print(f"Content: {item.get('text', '')[:200]}")
if 'metadata' in item:
print(f"Metadata: {item['metadata']}")
print("-" * 20)
else:
print("No results found in long-term memory")
# Also check ChromaDB if using RAG
if memory.use_rag and hasattr(memory, "chroma_col"):
print("\nChromaDB results:")
try:
all_items = memory.chroma_col.get()
print(f"Found {len(all_items['ids'])} items in ChromaDB")
for i in range(len(all_items['ids'])):
print(f"ID: {all_items['ids'][i]}")
print(f"Content: {all_items['documents'][i][:200]}")
print(f"Metadata: {all_items['metadatas'][i]}")
print("-" * 20)
except Exception as e:
print(f"Error querying ChromaDB: {e}")
print("-" * 30)
else:
print("\nNo memory available for final checks")
if __name__ == "__main__":
main()
+63
View File
@@ -0,0 +1,63 @@
from praisonaiagents.agents.agents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import duckduckgo
# Test facts
fact1 = "The capital city of Jujuha is Hahanu and its population is 102300"
fact2 = "Three main ingredients in a classic proloder are eggs, sugar, and flour"
fact3 = "The year the first Josinga was released is 2007"
fact_agent = Agent(
name="Fact Agent",
instructions="You are a fact agent, you store and retrieve facts in memory",
llm="gpt-4o-mini"
)
research_agent = Agent(
name="Research Agent",
instructions="You are a research analyst, you research and document key points about topics",
llm="gpt-4o-mini"
)
blog_agent = Agent(
name="Blog Agent",
instructions="You are a blog writer, you write a blog post about the research",
llm="gpt-4o-mini"
)
fact_task = Task(
description="Store the following facts in memory: " + fact1 + ", " + fact2 + ", " + fact3,
agent=fact_agent
)
research_task = Task(
description="Research and document 2 key points about AI",
agent=research_agent
)
research_task2 = Task(
description="Research and document 2 key points about AI in healthcare",
agent=research_agent
)
research_task3 = Task(
description="Research and document 2 key points about AI in education",
agent=research_agent
)
research_task4 = Task(
description="Research and document 2 key points about AI in finance",
agent=research_agent
)
blog_task = Task(
description="Write a blog post about Jujuha",
agent=blog_agent
)
agents = PraisonAIAgents(
agents=[fact_agent, research_agent, blog_agent],
tasks=[fact_task, research_task, research_task2, research_task3, research_task4, blog_task],
memory=True
)
agents.start()
+1 -6
View File
@@ -51,9 +51,4 @@ agents = PraisonAIAgents(
)
# Run all tasks
result = agents.start()
# Print results
for task_id, task_result in result["task_results"].items():
print(f"\nTask {task_id} Result:")
print(task_result.raw)
agents.start()
+110
View File
@@ -0,0 +1,110 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
import time
def get_time_check():
current_time = int(time.time())
if current_time % 3 == 0:
result = 1
elif current_time % 3 == 1:
result = 2
else:
result = 3
print(f"Time: {current_time}, Result: {result}")
return result
# Create orchestrator and worker agents
router = Agent(
name="Router",
role="Task router",
goal="Distribute tasks to based on response from get_time_check",
tools=[get_time_check]
)
worker1 = Agent(
name="Worker 1",
role="Specialized worker",
goal="Handle specific subtask type 1",
)
worker2 = Agent(
name="Worker 2",
role="Specialized worker",
goal="Handle specific subtask type 2",
)
worker3 = Agent(
name="Worker 3",
role="Specialized worker",
goal="Handle specific subtask type 3",
)
synthesizer = Agent(
name="Synthesizer",
role="Result synthesizer",
goal="Combine and synthesize worker outputs",
)
# Create orchestrated workflow tasks
router_task = Task(
name="route_task",
description="Analyze input from get_time_check and route to appropriate workers",
expected_output="Task routing decision, 1 , 2 or 3",
agent=router,
is_start=True,
task_type="decision",
next_tasks=["worker1_task", "worker2_task", "worker3_task"],
condition={
"1": ["worker1_task"],
"2": ["worker2_task"],
"3": ["worker3_task"]
}
)
worker1_task = Task(
name="worker1_task",
description="Process type 1 operation",
expected_output="Worker 1 result",
agent=worker1,
next_tasks=["synthesize_task"]
)
worker2_task = Task(
name="worker2_task",
description="Process type 2 operation",
expected_output="Worker 2 result",
agent=worker2,
next_tasks=["synthesize_task"]
)
worker3_task = Task(
name="worker3_task",
description="Process type 3 operation",
expected_output="Worker 3 result",
agent=worker3,
next_tasks=["synthesize_task"]
)
synthesize_task = Task(
name="synthesize_task",
description="Synthesize worker results into final output",
expected_output="Final synthesized result",
agent=synthesizer,
context=[worker1_task, worker2_task, worker3_task]
)
# Create workflow manager
workflow = PraisonAIAgents(
agents=[router, worker1, worker2, worker3, synthesizer],
tasks=[router_task, worker1_task, worker2_task, worker3_task, synthesize_task],
process="workflow",
verbose=True
)
# Run orchestrated workflow
results = workflow.start()
# Print results
print("\nOrchestrator-Workers Results:")
for task_id, result in results["task_results"].items():
if result:
print(f"Task {task_id}: {result.raw}")
+95
View File
@@ -0,0 +1,95 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from datetime import datetime
import asyncio
def process_time():
"""Simulate processing"""
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"Processing at: {current_time}")
return f"Processed at {current_time}"
# Create parallel processing agents
agent1 = Agent(
name="Processor 1",
role="Time collector",
goal="Get the time and return it",
tools=[process_time]
)
agent2 = Agent(
name="Processor 2",
role="Time collector",
goal="Get the time and return it",
tools=[process_time]
)
agent3 = Agent(
name="Processor 3",
role="Time collector",
goal="Get the time and return it",
tools=[process_time]
)
aggregator = Agent(
name="Aggregator",
role="Result aggregator",
goal="Collect all the processed time from all tasks"
)
# Create parallel tasks with memory disabled
task1 = Task(
name="process_1",
description="Use process_time tool to get the time",
expected_output="processed time",
agent=agent1,
is_start=True,
async_execution=True
)
task2 = Task(
name="process_2",
description="Use process_time tool to get the time",
expected_output="processed time",
agent=agent2,
is_start=True,
async_execution=True
)
task3 = Task(
name="process_3",
description="Use process_time tool to get the time",
expected_output="processed time",
agent=agent3,
is_start=True,
async_execution=True
)
aggregate_task = Task(
name="aggregate",
description="Collect all the processed time from all tasks",
expected_output="Output all the processed time from all tasks and just the time",
agent=aggregator,
context=[task1, task2, task3]
)
async def main():
# Create workflow manager
workflow = PraisonAIAgents(
agents=[agent1, agent2, agent3, aggregator],
tasks=[task1, task2, task3, aggregate_task],
process="workflow"
)
# Run parallel workflow
results = await workflow.astart()
# Print results
print("\nParallel Processing Results:")
for task_id, result in results["task_results"].items():
if result:
print(f"Task {task_id}: {result.raw}")
# Run the async main function
if __name__ == "__main__":
asyncio.run(main())
+41
View File
@@ -0,0 +1,41 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create PDF Analysis Agent
pdf_agent = Agent(
name="PDFAnalyst",
role="PDF Document Specialist",
goal="Analyze PDF documents to extract meaningful information",
backstory="""You are an expert in PDF document analysis and text extraction.
You excel at understanding document structure, extracting content, and analyzing textual information.""",
llm="gpt-4o-mini",
self_reflect=False
)
# 1. Task with PDF URL
task1 = Task(
name="analyze_pdf_url",
description="Extract and analyze content from this PDF document.",
expected_output="Detailed analysis of the PDF content and structure",
agent=pdf_agent,
input=["https://example.com/document.pdf"]
)
# 2. Task with Local PDF File
task2 = Task(
name="analyze_local_pdf",
description="What information can you extract from this PDF? Analyze its content.",
expected_output="Detailed analysis of the PDF content and structure",
agent=pdf_agent,
input=["document.pdf"]
)
# Create PraisonAIAgents instance
agents = PraisonAIAgents(
agents=[pdf_agent],
tasks=[task1, task2],
process="sequential",
verbose=1
)
# Run all tasks
agents.start()
+5
View File
@@ -0,0 +1,5 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import duckduckgo
agent = Agent(instructions="You are a Planning Agent", tools=[duckduckgo])
agent.start("I want to go London next week, find me a good hotel and flight")
+8
View File
@@ -6,6 +6,10 @@ from .agent.agent import Agent
from .agents.agents import PraisonAIAgents
from .task.task import Task
from .tools.tools import Tools
from .agents.autoagents import AutoAgents
from .knowledge.knowledge import Knowledge
from .knowledge.chunking import Chunking
from .memory.memory import Memory
from .main import (
TaskOutput,
ReflectionOutput,
@@ -33,6 +37,8 @@ __all__ = [
'Task',
'TaskOutput',
'ReflectionOutput',
'AutoAgents',
'Memory',
'display_interaction',
'display_self_reflection',
'display_instruction',
@@ -44,4 +50,6 @@ __all__ = [
'register_display_callback',
'sync_display_callbacks',
'async_display_callbacks',
'Knowledge',
'Chunking'
]
+165 -56
View File
@@ -16,8 +16,11 @@ from ..main import (
display_self_reflection,
ReflectionOutput,
client,
error_logs
error_logs,
adisplay_instruction
)
import inspect
import uuid
if TYPE_CHECKING:
from ..task.task import Task
@@ -67,9 +70,29 @@ class Agent:
return None
import inspect
# Langchain tools
if inspect.isclass(func) and hasattr(func, 'run') and not hasattr(func, '_run'):
original_func = func
func = func.run
function_name = original_func.__name__
# CrewAI tools
elif inspect.isclass(func) and hasattr(func, '_run'):
original_func = func
func = func._run
function_name = original_func.__name__
sig = inspect.signature(func)
logging.debug(f"Function signature: {sig}")
# Skip self, *args, **kwargs, so they don't get passed in arguments
parameters_list = []
for name, param in sig.parameters.items():
if name == "self":
continue
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
continue
parameters_list.append((name, param))
parameters = {
"type": "object",
"properties": {},
@@ -95,7 +118,7 @@ class Agent:
logging.debug(f"Parameter descriptions: {param_descriptions}")
for name, param in sig.parameters.items():
for name, param in parameters_list:
param_type = "string" # Default type
if param.annotation != inspect.Parameter.empty:
if param.annotation == int:
@@ -146,7 +169,7 @@ class Agent:
max_iter: int = 20,
max_rpm: Optional[int] = None,
max_execution_time: Optional[int] = None,
memory: bool = True,
memory: Optional[Any] = None,
verbose: bool = True,
allow_delegation: bool = False,
step_callback: Optional[Any] = None,
@@ -159,13 +182,15 @@ class Agent:
respect_context_window: bool = True,
code_execution_mode: Literal["safe", "unsafe"] = "safe",
embedder_config: Optional[Dict[str, Any]] = None,
knowledge_sources: Optional[List[Any]] = None,
knowledge: Optional[List[str]] = None,
knowledge_config: Optional[Dict[str, Any]] = None,
use_system_prompt: Optional[bool] = True,
markdown: bool = True,
self_reflect: Optional[bool] = None,
self_reflect: bool = False,
max_reflect: int = 3,
min_reflect: int = 1,
reflect_llm: Optional[str] = None
reflect_llm: Optional[str] = None,
user_id: Optional[str] = None
):
# Handle backward compatibility for required fields
if all(x is None for x in [name, role, goal, backstory, instructions]):
@@ -209,7 +234,7 @@ class Agent:
self.respect_context_window = respect_context_window
self.code_execution_mode = code_execution_mode
self.embedder_config = embedder_config
self.knowledge_sources = knowledge_sources
self.knowledge = knowledge
self.use_system_prompt = use_system_prompt
self.chat_history = []
self.markdown = markdown
@@ -225,6 +250,40 @@ Your Role: {self.role}\n
Your Goal: {self.goal}
"""
# Generate unique IDs
self.agent_id = str(uuid.uuid4())
# Store user_id
self.user_id = user_id
# Check if knowledge parameter has any values
if not knowledge:
self.knowledge = None
else:
# Initialize Knowledge with provided or default config
from praisonaiagents.knowledge import Knowledge
self.knowledge = Knowledge(knowledge_config or None)
# Handle knowledge
if knowledge:
for source in knowledge:
self._process_knowledge(source)
def _process_knowledge(self, knowledge_item):
"""Process and store knowledge from a file path, URL, or string."""
try:
if os.path.exists(knowledge_item):
# It's a file path
self.knowledge.add(knowledge_item, user_id=self.user_id, agent_id=self.agent_id)
elif knowledge_item.startswith("http://") or knowledge_item.startswith("https://"):
# It's a URL
pass
else:
# It's a string content
self.knowledge.store(knowledge_item, user_id=self.user_id, agent_id=self.agent_id)
except Exception as e:
logging.error(f"Error processing knowledge item: {knowledge_item}, error: {e}")
def generate_task(self) -> 'Task':
"""Generate a Task object from the agent's instructions"""
from ..task.task import Task
@@ -249,25 +308,39 @@ Your Goal: {self.goal}
# Try to find the function in the agent's tools list first
func = None
for tool in self.tools:
if callable(tool) and getattr(tool, '__name__', '') == function_name:
if (callable(tool) and getattr(tool, '__name__', '') == function_name) or \
(inspect.isclass(tool) and tool.__name__ == function_name):
func = tool
break
logging.debug(f"Looking for {function_name} in agent tools: {func is not None}")
# If not found in tools, try globals and main
if not func:
if func is None:
# If not found in tools, try globals and main
func = globals().get(function_name)
logging.debug(f"Looking for {function_name} in globals: {func is not None}")
if not func:
import __main__
func = getattr(__main__, function_name, None)
logging.debug(f"Looking for {function_name} in __main__: {func is not None}")
if func and callable(func):
if func:
try:
return func(**arguments)
# Langchain: If it's a class with run but not _run, instantiate and call run
if inspect.isclass(func) and hasattr(func, 'run') and not hasattr(func, '_run'):
instance = func()
run_params = {k: v for k, v in arguments.items()
if k in inspect.signature(instance.run).parameters
and k != 'self'}
return instance.run(**run_params)
# CrewAI: If it's a class with an _run method, instantiate and call _run
elif inspect.isclass(func) and hasattr(func, '_run'):
instance = func()
run_params = {k: v for k, v in arguments.items()
if k in inspect.signature(instance._run).parameters
and k != 'self'}
return instance._run(**run_params)
# Otherwise treat as regular function
elif callable(func):
return func(**arguments)
except Exception as e:
error_msg = str(e)
logging.error(f"Error executing tool {function_name}: {error_msg}")
@@ -359,7 +432,7 @@ Your Goal: {self.goal}
display_generating("", start_time),
console=self.console,
refresh_per_second=4,
transient=False, # Changed to False to preserve output
transient=True, # Changed to False to preserve output
vertical_overflow="ellipsis",
auto_refresh=True
) as live:
@@ -394,14 +467,31 @@ Your Goal: {self.goal}
display_error(f"Error in chat completion: {e}")
return None
def chat(self, prompt, temperature=0.2, tools=None, output_json=None):
def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None):
# Search for existing knowledge if any knowledge is provided
if self.knowledge:
search_results = self.knowledge.search(prompt, agent_id=self.agent_id)
if search_results:
# Check if search_results is a list of dictionaries or strings
if isinstance(search_results, dict) and 'results' in search_results:
# Extract memory content from the results
knowledge_content = "\n".join([result['memory'] for result in search_results['results']])
else:
# If search_results is a list of strings, join them directly
knowledge_content = "\n".join(search_results)
# Append found knowledge to the prompt
prompt = f"{prompt}\n\nKnowledge: {knowledge_content}"
if self.use_system_prompt:
system_prompt = f"""{self.backstory}\n
Your Role: {self.role}\n
Your Goal: {self.goal}
"""
if output_json:
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {output_json.schema_json()}"
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())}"
else:
system_prompt = None
@@ -410,9 +500,9 @@ Your Goal: {self.goal}
messages.append({"role": "system", "content": system_prompt})
messages.extend(self.chat_history)
# Modify prompt if output_json is specified
# Modify prompt if output_json or output_pydantic is specified
original_prompt = prompt
if output_json:
if output_json or output_pydantic:
if isinstance(prompt, str):
prompt += "\nReturn ONLY a valid JSON object. No other text or explanation."
elif isinstance(prompt, list):
@@ -442,7 +532,15 @@ Your Goal: {self.goal}
display_text = next((item["text"] for item in prompt if item["type"] == "text"), "")
if display_text and str(display_text).strip():
display_instruction(f"Agent {self.name} is processing prompt: {display_text}", console=self.console)
# Pass agent information to display_instruction
agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
display_instruction(
f"Agent {self.name} is processing prompt: {display_text}",
console=self.console,
agent_name=self.name,
agent_role=self.role,
agent_tools=agent_tools
)
response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None)
if not response:
@@ -487,29 +585,21 @@ Your Goal: {self.goal}
return None
response_text = response.choices[0].message.content.strip()
# Handle output_json if specified
if output_json:
try:
# Clean the response text to get only JSON
cleaned_json = self.clean_json_output(response_text)
# Parse into Pydantic model
parsed_model = output_json.model_validate_json(cleaned_json)
# Add to chat history and return
self.chat_history.append({"role": "user", "content": original_prompt})
self.chat_history.append({"role": "assistant", "content": response_text})
if self.verbose:
display_interaction(original_prompt, response_text, markdown=self.markdown,
generation_time=time.time() - start_time, console=self.console)
return parsed_model
except Exception as e:
display_error(f"Failed to parse response as {output_json.__name__}: {e}")
return None
# Handle output_json or output_pydantic if specified
if output_json or output_pydantic:
# Add to chat history and return raw response
self.chat_history.append({"role": "user", "content": original_prompt})
self.chat_history.append({"role": "assistant", "content": response_text})
if self.verbose:
display_interaction(original_prompt, response_text, markdown=self.markdown,
generation_time=time.time() - start_time, console=self.console)
return response_text
if not self.self_reflect:
self.chat_history.append({"role": "user", "content": original_prompt})
self.chat_history.append({"role": "assistant", "content": response_text})
if self.verbose:
logging.info(f"Agent {self.name} final response: {response_text}")
logging.debug(f"Agent {self.name} final response: {response_text}")
display_interaction(original_prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
return response_text
@@ -585,19 +675,21 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
cleaned = cleaned[:-3].strip()
return cleaned
async def achat(self, prompt, temperature=0.2, tools=None, output_json=None):
async def achat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None):
"""Async version of chat method"""
try:
# Build system prompt
system_prompt = self.system_prompt
if output_json:
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {output_json.schema_json()}"
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())}"
# Build messages
if isinstance(prompt, str):
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt + ("\nReturn ONLY a valid JSON object. No other text or explanation." if output_json else "")}
{"role": "user", "content": prompt + ("\nReturn ONLY a valid JSON object. No other text or explanation." if (output_json or output_pydantic) else "")}
]
else:
# For multimodal prompts
@@ -605,13 +697,29 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
if output_json:
if output_json or output_pydantic:
# Add JSON instruction to text content
for item in messages[-1]["content"]:
if item["type"] == "text":
item["text"] += "\nReturn ONLY a valid JSON object. No other text or explanation."
break
# Display instruction with agent info if verbose
if self.verbose:
display_text = prompt
if isinstance(prompt, list):
display_text = next((item["text"] for item in prompt if item["type"] == "text"), "")
if display_text and str(display_text).strip():
agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
await adisplay_instruction(
f"Agent {self.name} is processing prompt: {display_text}",
console=self.console,
agent_name=self.name,
agent_role=self.role,
agent_tools=agent_tools
)
# Format tools if provided
formatted_tools = []
if tools:
@@ -639,22 +747,15 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
tools=formatted_tools
)
return await self._achat_completion(response, tools)
elif output_json:
elif output_json or output_pydantic:
response = await async_client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
response_format={"type": "json_object"}
)
result = response.choices[0].message.content
# Clean and parse the JSON response
cleaned_json = self.clean_json_output(result)
try:
parsed = json.loads(cleaned_json)
return output_json(**parsed)
except Exception as e:
display_error(f"Error parsing JSON response: {e}")
return None
# Return the raw response
return response.choices[0].message.content
else:
response = await async_client.chat.completions.create(
model=self.llm,
@@ -722,4 +823,12 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
return None
except Exception as e:
display_error(f"Error in _achat_completion: {e}")
return None
return None
def run(self):
"""Alias for start() method"""
return self.start()
def start(self, prompt: str, **kwargs):
"""Start the agent with a prompt. This is a convenience method that wraps chat()."""
return self.chat(prompt, **kwargs)
+2 -1
View File
@@ -1,4 +1,5 @@
"""Agents module for managing multiple AI agents"""
from .agents import PraisonAIAgents
from .autoagents import AutoAgents
__all__ = ['PraisonAIAgents']
__all__ = ['PraisonAIAgents', 'AutoAgents']
+299 -65
View File
@@ -12,6 +12,10 @@ from ..agent.agent import Agent
from ..task.task import Task
from ..process.process import Process, LoopItems
import asyncio
import uuid
# Set up logger
logger = logging.getLogger(__name__)
def encode_file_to_base64(file_path: str) -> str:
"""Base64-encode a file."""
@@ -41,10 +45,18 @@ def process_video(video_path: str, seconds_per_frame=2):
return base64_frames
class PraisonAIAgents:
def __init__(self, agents, tasks=None, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None):
def __init__(self, agents, tasks=None, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None, memory=False, memory_config=None, embedder=None, user_id=None, max_iter=10):
if not agents:
raise ValueError("At least one agent must be provided")
self.run_id = str(uuid.uuid4()) # Auto-generate run_id
self.user_id = user_id # Optional user_id
self.max_iter = max_iter # Add max_iter parameter
# Pass user_id to each agent
for agent in agents:
agent.user_id = self.user_id
self.agents = agents
self.tasks = {}
if max_retries < 3:
@@ -57,8 +69,21 @@ class PraisonAIAgents:
# Check for manager_llm in environment variable if not provided
self.manager_llm = manager_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
# Set logger level based on verbose
if verbose >= 5:
logger.setLevel(logging.INFO)
else:
logger.setLevel(logging.WARNING)
# Also set third-party loggers to WARNING
logging.getLogger('chromadb').setLevel(logging.WARNING)
logging.getLogger('openai').setLevel(logging.WARNING)
logging.getLogger('httpx').setLevel(logging.WARNING)
logging.getLogger('httpcore').setLevel(logging.WARNING)
if self.verbose:
logging.info(f"Using model {self.manager_llm} for manager")
logger.info(f"Using model {self.manager_llm} for manager")
# If no tasks provided, generate them from agents
if tasks is None:
@@ -66,12 +91,12 @@ class PraisonAIAgents:
for agent in self.agents:
task = agent.generate_task()
tasks.append(task)
logging.info(f"Auto-generated {len(tasks)} tasks from agents")
logger.info(f"Auto-generated {len(tasks)} tasks from agents")
else:
# Validate tasks for backward compatibility
if not tasks:
raise ValueError("If tasks are provided, at least one task must be present")
logging.info(f"Using {len(tasks)} provided tasks")
logger.info(f"Using {len(tasks)} provided tasks")
# Add tasks and set their status
for task in tasks:
@@ -87,9 +112,65 @@ class PraisonAIAgents:
if tasks[i + 1].context is None:
tasks[i + 1].context = []
tasks[i + 1].context.append(tasks[i])
logging.info("Set up sequential flow with automatic context passing")
logger.info("Set up sequential flow with automatic context passing")
self._state = {} # Add state storage at PraisonAIAgents level
# Initialize memory system
self.shared_memory = None
if memory:
try:
from ..memory.memory import Memory
# Get memory config from parameter or first task
mem_cfg = memory_config
if not mem_cfg:
mem_cfg = next((t.config.get('memory_config') for t in tasks if hasattr(t, 'config') and t.config), None)
# Set default memory config if none provided
if not mem_cfg:
mem_cfg = {
"provider": "rag",
"use_embedding": True,
"storage": {
"type": "sqlite",
"path": "./.praison/memory.db"
},
"rag_db_path": "./.praison/chroma_db"
}
# Add embedder config if provided
if embedder:
if isinstance(embedder, dict):
mem_cfg = mem_cfg or {}
mem_cfg["embedder"] = embedder
else:
# Handle direct embedder function
mem_cfg = mem_cfg or {}
mem_cfg["embedder_function"] = embedder
if mem_cfg:
# Pass verbose level to Memory
self.shared_memory = Memory(config=mem_cfg, verbose=verbose)
if verbose >= 5:
logger.info("Initialized shared memory for PraisonAIAgents")
# Distribute memory to tasks
for task in tasks:
if not task.memory:
task.memory = self.shared_memory
if verbose >= 5:
logger.info(f"Assigned shared memory to task {task.id}")
except Exception as e:
logger.error(f"Failed to initialize shared memory: {e}")
# Update tasks with shared memory
if self.shared_memory:
for task in tasks:
if not task.memory:
task.memory = self.shared_memory
logger.info(f"Assigned shared memory to task {task.id}")
def add_task(self, task):
task_id = self.task_id_counter
@@ -139,26 +220,44 @@ class PraisonAIAgents:
executor_agent = task.agent
# Ensure tools are available from both task and agent
tools = task.tools or []
if executor_agent and executor_agent.tools:
tools.extend(executor_agent.tools)
task_prompt = f"""
You need to do the following task: {task.description}.
Expected Output: {task.expected_output}.
"""
"""
if task.context:
context_results = ""
for context_task in task.context:
if context_task.result:
context_results += f"Result of previous task {context_task.name if context_task.name else context_task.description}: {context_task.result.raw}\n"
else:
context_results += f"Previous task {context_task.name if context_task.name else context_task.description} had no result.\n"
context_results = [] # Use list to avoid duplicates
for context_item in task.context:
if isinstance(context_item, str):
context_results.append(f"Input Content:\n{context_item}")
elif isinstance(context_item, list):
context_results.append(f"Input Content: {' '.join(str(x) for x in context_item)}")
elif hasattr(context_item, 'result'): # Task object
if context_item.result:
context_results.append(
f"Result of previous task {context_item.name if context_item.name else context_item.description}:\n{context_item.result.raw}"
)
else:
context_results.append(
f"Previous task {context_item.name if context_item.name else context_item.description} has no result yet."
)
# Join unique context results
unique_contexts = list(dict.fromkeys(context_results)) # Remove duplicates
task_prompt += f"""
Here are the results of previous tasks that might be useful:\n
{context_results}
Context:
{' '.join(unique_contexts)}
"""
task_prompt += "Please provide only the final result of your work. Do not add any conversation or extra explanation."
if self.verbose >= 2:
logging.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
logging.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
logger.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
logger.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
if task.images:
def _get_multimodal_message(text_prompt, images):
@@ -195,10 +294,17 @@ Here are the results of previous tasks that might be useful:\n
agent_output = await executor_agent.achat(
_get_multimodal_message(task_prompt, task.images),
tools=task.tools
tools=tools,
output_json=task.output_json,
output_pydantic=task.output_pydantic
)
else:
agent_output = await executor_agent.achat(task_prompt, tools=task.tools)
agent_output = await executor_agent.achat(
task_prompt,
tools=tools,
output_json=task.output_json,
output_pydantic=task.output_pydantic
)
if agent_output:
task_output = TaskOutput(
@@ -216,8 +322,8 @@ Here are the results of previous tasks that might be useful:\n
task_output.json_dict = parsed
task_output.output_format = "JSON"
except:
logging.warning(f"Warning: Could not parse output of task {task_id} as JSON")
logging.debug(f"Output that failed JSON parsing: {agent_output}")
logger.warning(f"Warning: Could not parse output of task {task_id} as JSON")
logger.debug(f"Output that failed JSON parsing: {agent_output}")
if task.output_pydantic:
cleaned = self.clean_json_output(agent_output)
@@ -227,8 +333,8 @@ Here are the results of previous tasks that might be useful:\n
task_output.pydantic = pyd_obj
task_output.output_format = "Pydantic"
except:
logging.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
logging.debug(f"Output that failed Pydantic parsing: {agent_output}")
logger.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
logger.debug(f"Output that failed Pydantic parsing: {agent_output}")
task.result = task_output
return task_output
@@ -243,37 +349,53 @@ Here are the results of previous tasks that might be useful:\n
return
task = self.tasks[task_id]
if task.status == "completed":
logging.info(f"Task with ID {task_id} is already completed")
logger.info(f"Task with ID {task_id} is already completed")
return
retries = 0
while task.status != "completed" and retries < self.max_retries:
logging.debug(f"Attempt {retries+1} for task {task_id}")
logger.debug(f"Attempt {retries+1} for task {task_id}")
if task.status in ["not started", "in progress"]:
task_output = await self.aexecute_task(task_id)
if task_output and self.completion_checker(task, task_output.raw):
task.status = "completed"
if task.callback:
# Run execute_callback for memory operations
try:
await task.execute_callback(task_output)
except Exception as e:
logger.error(f"Error executing memory callback for task {task_id}: {e}")
logger.exception(e)
# Run task callback if exists
if task.callback:
try:
if asyncio.iscoroutinefunction(task.callback):
await task.callback(task_output)
else:
task.callback(task_output)
except Exception as e:
logger.error(f"Error executing task callback for task {task_id}: {e}")
logger.exception(e)
self.save_output_to_file(task, task_output)
if self.verbose >= 1:
logging.info(f"Task {task_id} completed successfully.")
logger.info(f"Task {task_id} completed successfully.")
else:
task.status = "in progress"
if self.verbose >= 1:
logging.info(f"Task {task_id} not completed, retrying")
logger.info(f"Task {task_id} not completed, retrying")
await asyncio.sleep(1)
retries += 1
else:
if task.status == "failed":
logging.info("Task is failed, resetting to in-progress for another try...")
logger.info("Task is failed, resetting to in-progress for another try...")
task.status = "in progress"
else:
logging.info("Invalid Task status")
logger.info("Invalid Task status")
break
if retries == self.max_retries and task.status != "completed":
logging.info(f"Task {task_id} failed after {self.max_retries} retries.")
logger.info(f"Task {task_id} failed after {self.max_retries} retries.")
async def arun_all_tasks(self):
"""Async version of run_all_tasks method"""
@@ -281,15 +403,30 @@ Here are the results of previous tasks that might be useful:\n
tasks=self.tasks,
agents=self.agents,
manager_llm=self.manager_llm,
verbose=self.verbose
verbose=self.verbose,
max_iter=self.max_iter
)
if self.process == "workflow":
# Collect all tasks that should run in parallel
parallel_tasks = []
async for task_id in process.aworkflow():
if self.tasks[task_id].async_execution:
await self.arun_task(task_id)
else:
self.run_task(task_id)
if self.tasks[task_id].async_execution and self.tasks[task_id].is_start:
parallel_tasks.append(task_id)
elif parallel_tasks:
# Execute collected parallel tasks
await asyncio.gather(*[self.arun_task(t) for t in parallel_tasks])
parallel_tasks = []
# Run the current non-parallel task
if self.tasks[task_id].async_execution:
await self.arun_task(task_id)
else:
self.run_task(task_id)
# Execute any remaining parallel tasks
if parallel_tasks:
await asyncio.gather(*[self.arun_task(t) for t in parallel_tasks])
elif self.process == "sequential":
async for task_id in process.asequential():
if self.tasks[task_id].async_execution:
@@ -305,8 +442,16 @@ Here are the results of previous tasks that might be useful:\n
else:
self.run_task(task_id)
async def astart(self):
async def astart(self, content=None, **kwargs):
"""Async version of start method"""
if content:
# Add content to context of all tasks
for task in self.tasks.values():
if isinstance(content, (str, list)):
if not task.context:
task.context = []
task.context.append(content)
await self.arun_all_tasks()
return {
"task_status": self.get_all_tasks_status(),
@@ -321,7 +466,7 @@ Here are the results of previous tasks that might be useful:\n
with open(task.output_file, "w") as f:
f.write(str(task_output))
if self.verbose >= 1:
logging.info(f"Task output saved to {task.output_file}")
logger.info(f"Task output saved to {task.output_file}")
except Exception as e:
display_error(f"Error saving task output to file: {e}")
@@ -332,6 +477,15 @@ Here are the results of previous tasks that might be useful:\n
return
task = self.tasks[task_id]
logger.info(f"Starting execution of task {task_id}")
logger.info(f"Task config: {task.config}")
# Initialize memory before task execution
if not task.memory:
task.memory = task.initialize_memory()
logger.info(f"Task memory status: {'Initialized' if task.memory else 'Not initialized'}")
# Only import multimodal dependencies if task has images
if task.images and task.status == "not started":
try:
@@ -354,21 +508,44 @@ You need to do the following task: {task.description}.
Expected Output: {task.expected_output}.
"""
if task.context:
context_results = ""
for context_task in task.context:
if context_task.result:
context_results += f"Result of previous task {context_task.name if context_task.name else context_task.description}: {context_task.result.raw}\n"
else:
context_results += f"Previous task {context_task.name if context_task.name else context_task.description} had no result.\n"
context_results = [] # Use list to avoid duplicates
for context_item in task.context:
if isinstance(context_item, str):
context_results.append(f"Input Content:\n{context_item}")
elif isinstance(context_item, list):
context_results.append(f"Input Content: {' '.join(str(x) for x in context_item)}")
elif hasattr(context_item, 'result'): # Task object
if context_item.result:
context_results.append(
f"Result of previous task {context_item.name if context_item.name else context_item.description}:\n{context_item.result.raw}"
)
else:
context_results.append(
f"Previous task {context_item.name if context_item.name else context_item.description} has no result yet."
)
# Join unique context results
unique_contexts = list(dict.fromkeys(context_results)) # Remove duplicates
task_prompt += f"""
Here are the results of previous tasks that might be useful:\n
{context_results}
Context:
{' '.join(unique_contexts)}
"""
# Add memory context if available
if task.memory:
try:
memory_context = task.memory.build_context_for_task(task.description)
if memory_context:
task_prompt += f"\n\nRelevant memory context:\n{memory_context}"
except Exception as e:
logger.error(f"Error getting memory context: {e}")
task_prompt += "Please provide only the final result of your work. Do not add any conversation or extra explanation."
if self.verbose >= 2:
logging.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
logging.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
logger.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
logger.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
if task.images:
def _get_multimodal_message(text_prompt, images):
@@ -405,12 +582,30 @@ Here are the results of previous tasks that might be useful:\n
agent_output = executor_agent.chat(
_get_multimodal_message(task_prompt, task.images),
tools=task.tools
tools=task.tools,
output_json=task.output_json,
output_pydantic=task.output_pydantic
)
else:
agent_output = executor_agent.chat(task_prompt, tools=task.tools)
agent_output = executor_agent.chat(
task_prompt,
tools=task.tools,
output_json=task.output_json,
output_pydantic=task.output_pydantic
)
if agent_output:
# Store the response in memory
if task.memory:
try:
task.store_in_memory(
content=agent_output,
agent_name=executor_agent.name,
task_id=task_id
)
except Exception as e:
logger.error(f"Failed to store agent output in memory: {e}")
task_output = TaskOutput(
description=task.description,
summary=task.description[:10],
@@ -426,8 +621,8 @@ Here are the results of previous tasks that might be useful:\n
task_output.json_dict = parsed
task_output.output_format = "JSON"
except:
logging.warning(f"Warning: Could not parse output of task {task_id} as JSON")
logging.debug(f"Output that failed JSON parsing: {agent_output}")
logger.warning(f"Warning: Could not parse output of task {task_id} as JSON")
logger.debug(f"Output that failed JSON parsing: {agent_output}")
if task.output_pydantic:
cleaned = self.clean_json_output(agent_output)
@@ -437,8 +632,8 @@ Here are the results of previous tasks that might be useful:\n
task_output.pydantic = pyd_obj
task_output.output_format = "Pydantic"
except:
logging.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
logging.debug(f"Output that failed Pydantic parsing: {agent_output}")
logger.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
logger.debug(f"Output that failed Pydantic parsing: {agent_output}")
task.result = task_output
return task_output
@@ -453,37 +648,63 @@ Here are the results of previous tasks that might be useful:\n
return
task = self.tasks[task_id]
if task.status == "completed":
logging.info(f"Task with ID {task_id} is already completed")
logger.info(f"Task with ID {task_id} is already completed")
return
retries = 0
while task.status != "completed" and retries < self.max_retries:
logging.debug(f"Attempt {retries+1} for task {task_id}")
logger.debug(f"Attempt {retries+1} for task {task_id}")
if task.status in ["not started", "in progress"]:
task_output = self.execute_task(task_id)
if task_output and self.completion_checker(task, task_output.raw):
task.status = "completed"
# Run execute_callback for memory operations
try:
if asyncio.get_event_loop().is_running():
asyncio.create_task(task.execute_callback(task_output))
else:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(task.execute_callback(task_output))
except Exception as e:
logger.error(f"Error executing memory callback for task {task_id}: {e}")
logger.exception(e)
# Run task callback if exists
if task.callback:
task.callback(task_output)
try:
if asyncio.iscoroutinefunction(task.callback):
if asyncio.get_event_loop().is_running():
asyncio.create_task(task.callback(task_output))
else:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(task.callback(task_output))
else:
task.callback(task_output)
except Exception as e:
logger.error(f"Error executing task callback for task {task_id}: {e}")
logger.exception(e)
self.save_output_to_file(task, task_output)
if self.verbose >= 1:
logging.info(f"Task {task_id} completed successfully.")
logger.info(f"Task {task_id} completed successfully.")
else:
task.status = "in progress"
if self.verbose >= 1:
logging.info(f"Task {task_id} not completed, retrying")
logger.info(f"Task {task_id} not completed, retrying")
time.sleep(1)
retries += 1
else:
if task.status == "failed":
logging.info("Task is failed, resetting to in-progress for another try...")
logger.info("Task is failed, resetting to in-progress for another try...")
task.status = "in progress"
else:
logging.info("Invalid Task status")
logger.info("Invalid Task status")
break
if retries == self.max_retries and task.status != "completed":
logging.info(f"Task {task_id} failed after {self.max_retries} retries.")
logger.info(f"Task {task_id} failed after {self.max_retries} retries.")
def run_all_tasks(self):
"""Synchronous version of run_all_tasks method"""
@@ -491,7 +712,8 @@ Here are the results of previous tasks that might be useful:\n
tasks=self.tasks,
agents=self.agents,
manager_llm=self.manager_llm,
verbose=self.verbose
verbose=self.verbose,
max_iter=self.max_iter
)
if self.process == "workflow":
@@ -530,12 +752,24 @@ Here are the results of previous tasks that might be useful:\n
return str(agent[0])
return None
def start(self):
def start(self, content=None, **kwargs):
"""Start agent execution with optional content and config"""
if content:
# Add content to context of all tasks
for task in self.tasks.values():
if isinstance(content, (str, list)):
# If context is empty, initialize it
if not task.context:
task.context = []
# Add content to context
task.context.append(content)
# Run tasks as before
self.run_all_tasks()
return {
"task_status": self.get_all_tasks_status(),
"task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
}
}
def set_state(self, key: str, value: Any) -> None:
"""Set a state value"""
+335
View File
@@ -0,0 +1,335 @@
"""
AutoAgents - A class for automatically creating and managing AI agents and tasks.
This class provides a simplified interface for creating and running AI agents with tasks.
It automatically handles agent creation, task setup, and execution flow.
"""
from .agents import PraisonAIAgents
from ..agent.agent import Agent
from ..task.task import Task
from typing import List, Any, Optional, Dict, Union
import logging
import os
from pydantic import BaseModel, Field
from ..main import display_instruction, display_tool_call, display_interaction, client
# Define Pydantic models for structured output
class TaskConfig(BaseModel):
name: str
description: str
expected_output: str
tools: List[str]
class AgentConfig(BaseModel):
name: str
role: str
goal: str
backstory: str
tools: List[str]
tasks: List[TaskConfig]
class AutoAgentsConfig(BaseModel):
main_instruction: str
process_type: str
agents: List[AgentConfig]
class AutoAgents(PraisonAIAgents):
def __init__(
self,
instructions: str,
tools: Optional[List[Any]] = None,
verbose: bool = False,
process: str = "sequential",
manager_llm: Optional[str] = None,
max_retries: int = 5,
completion_checker: Optional[Any] = None,
allow_code_execution: bool = False,
memory: bool = True,
markdown: bool = True,
self_reflect: bool = False,
max_reflect: int = 3,
min_reflect: int = 1,
llm: Optional[str] = None,
function_calling_llm: Optional[str] = None,
respect_context_window: bool = True,
code_execution_mode: str = "safe",
embedder_config: Optional[Dict[str, Any]] = None,
knowledge_sources: Optional[List[Any]] = None,
use_system_prompt: bool = True,
cache: bool = True,
allow_delegation: bool = False,
step_callback: Optional[Any] = None,
system_template: Optional[str] = None,
prompt_template: Optional[str] = None,
response_template: Optional[str] = None,
max_rpm: Optional[int] = None,
max_execution_time: Optional[int] = None,
max_iter: int = 20,
reflect_llm: Optional[str] = None,
base_url: Optional[str] = None,
api_key: Optional[str] = None,
max_agents: int = 3 # New parameter for maximum number of agents
):
"""Initialize AutoAgents with configuration for automatic agent and task creation."""
if max_agents < 1:
raise ValueError("max_agents must be at least 1")
if max_agents > 10:
raise ValueError("max_agents cannot exceed 10")
# Store all configuration parameters first
self.instructions = instructions
self.tools = tools or []
self.verbose = verbose # Store verbose parameter
self.max_agents = max_agents # Store max_agents parameter
self.allow_code_execution = allow_code_execution
self.memory = memory
self.markdown = markdown
self.self_reflect = self_reflect
self.max_reflect = max_reflect
self.min_reflect = min_reflect
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
self.function_calling_llm = function_calling_llm
self.respect_context_window = respect_context_window
self.code_execution_mode = code_execution_mode
self.embedder_config = embedder_config
self.knowledge_sources = knowledge_sources
self.use_system_prompt = use_system_prompt
self.cache = cache
self.allow_delegation = allow_delegation
self.step_callback = step_callback
self.system_template = system_template
self.prompt_template = prompt_template
self.response_template = response_template
self.max_rpm = max_rpm
self.max_execution_time = max_execution_time
self.max_iter = max_iter
self.reflect_llm = reflect_llm
# Display initial instruction
if self.verbose:
display_instruction(f"🎯 Main Task: {self.instructions}")
display_instruction(f"📊 Maximum Agents: {self.max_agents}")
if self.tools:
tool_names = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
display_tool_call(f"🛠️ Available Tools: {', '.join(tool_names)}")
# Generate agents and tasks configuration
config = self._generate_config()
agents, tasks = self._create_agents_and_tasks(config)
# Display agents and their tasks
if self.verbose:
self._display_agents_and_tasks(agents, tasks)
# Initialize parent class with generated agents and tasks
super().__init__(
agents=agents,
tasks=tasks,
verbose=verbose,
completion_checker=completion_checker,
max_retries=max_retries,
process=process,
manager_llm=manager_llm
)
def _display_agents_and_tasks(self, agents: List[Agent], tasks: List[Task]):
"""Display the created agents and their assigned tasks"""
display_instruction("\n🤖 Generated Agents and Tasks:")
# Create a mapping of agents to their tasks
agent_tasks = {}
for task in tasks:
if task.agent not in agent_tasks:
agent_tasks[task.agent] = []
agent_tasks[task.agent].append(task)
# Display each agent and their tasks
for agent in agents:
agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in agent.tools]
display_interaction(
f"\n👤 Agent: {agent.name}",
f"""Role: {agent.role}
Goal: {agent.goal}
Tools: {', '.join(agent_tools)}"""
)
# Display tasks for this agent
if agent in agent_tasks:
for i, task in enumerate(agent_tasks[agent], 1):
task_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in task.tools]
display_instruction(
f""" 📋 Task {i}: {task.name}
Description: {task.description}
Expected Output: {task.expected_output}
Tools: {', '.join(task_tools)}"""
)
def _get_available_tools(self) -> List[str]:
"""Get list of available tools"""
if not self.tools:
return []
return [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
def _get_tool_by_name(self, tool_name: str) -> Optional[Any]:
"""Get tool object by its name"""
for tool in self.tools:
if (hasattr(tool, '__name__') and tool.__name__ == tool_name) or str(tool) == tool_name:
return tool
return None
def _assign_tools_to_agent(self, agent_config: AgentConfig) -> List[Any]:
"""
Assign appropriate tools to an agent based on its role and tasks.
Args:
agent_config: The agent configuration containing role and required tools
Returns:
List of tool objects assigned to this agent
"""
assigned_tools = []
tool_names = set(agent_config.tools)
# Also look at task requirements
for task in agent_config.tasks:
tool_names.update(task.tools)
# Assign tools that match the requirements
for tool_name in tool_names:
tool = self._get_tool_by_name(tool_name)
if tool:
assigned_tools.append(tool)
# If no specific tools matched but we have tools available,
# assign all tools to ensure functionality
if not assigned_tools and self.tools:
assigned_tools = self.tools
return assigned_tools
def _generate_config(self) -> AutoAgentsConfig:
"""Generate the configuration for agents and tasks"""
prompt = f"""
Generate a configuration for AI agents to accomplish this task: "{self.instructions}"
The configuration should include:
1. A main instruction that clearly states the overall goal
2. A process type (sequential, workflow, or hierarchical)
3. A list of maximum {self.max_agents} agents (no more, no less), each with:
- Name, role, goal, and backstory
- List of required tools from: {self._get_available_tools()}
- Only add tools that are needed for the agent to perform its task
- Only one task per agent. Add more than one task if absolutely necessary.
- List of specific tasks they need to perform
- Whether they should self-reflect or allow delegation
Requirements:
1. Each agent should have clear, focused responsibilities
2. Tasks should be broken down into manageable steps
3. Tool selection should be appropriate for each task
4. The process type should match the task requirements
5. Generate maximum {self.max_agents} agents to handle this task efficiently
Return the configuration in a structured JSON format matching the AutoAgentsConfig schema.
"""
try:
response = client.beta.chat.completions.parse(
model=self.llm,
response_format=AutoAgentsConfig,
messages=[
{"role": "system", "content": "You are a helpful assistant designed to generate AI agent configurations."},
{"role": "user", "content": prompt}
]
)
# Ensure we have exactly max_agents number of agents
if len(response.choices[0].message.parsed.agents) > self.max_agents:
response.choices[0].message.parsed.agents = response.choices[0].message.parsed.agents[:self.max_agents]
elif len(response.choices[0].message.parsed.agents) < self.max_agents:
logging.warning(f"Generated {len(response.choices[0].message.parsed.agents)} agents, expected {self.max_agents}")
return response.choices[0].message.parsed
except Exception as e:
logging.error(f"Error generating configuration: {e}")
raise
def _create_agents_and_tasks(self, config: AutoAgentsConfig) -> tuple[List[Agent], List[Task]]:
"""Create agents and tasks from configuration"""
agents = []
tasks = []
for agent_config in config.agents:
# Get appropriate tools for this agent
agent_tools = self._assign_tools_to_agent(agent_config)
# Create the agent with all parameters
agent = Agent(
name=agent_config.name,
role=agent_config.role,
goal=agent_config.goal,
backstory=agent_config.backstory,
tools=agent_tools, # Use assigned tools
verbose=self.verbose >= 1,
allow_code_execution=self.allow_code_execution,
memory=self.memory,
markdown=self.markdown,
self_reflect=self.self_reflect,
max_reflect=self.max_reflect,
min_reflect=self.min_reflect,
llm=self.llm,
function_calling_llm=self.function_calling_llm,
respect_context_window=self.respect_context_window,
code_execution_mode=self.code_execution_mode,
embedder_config=self.embedder_config,
knowledge_sources=self.knowledge_sources,
use_system_prompt=self.use_system_prompt,
cache=self.cache,
allow_delegation=self.allow_delegation,
step_callback=self.step_callback,
system_template=self.system_template,
prompt_template=self.prompt_template,
response_template=self.response_template,
max_rpm=self.max_rpm,
max_execution_time=self.max_execution_time,
max_iter=self.max_iter,
reflect_llm=self.reflect_llm
)
agents.append(agent)
# Create tasks for this agent
for task_config in agent_config.tasks:
# Get task-specific tools
task_tools = [self._get_tool_by_name(t) for t in task_config.tools]
task_tools = [t for t in task_tools if t] # Remove None values
# If no specific tools matched, use agent's tools
if not task_tools:
task_tools = agent_tools
task = Task(
name=task_config.name,
description=task_config.description,
expected_output=task_config.expected_output,
agent=agent,
tools=task_tools # Use task-specific tools
)
tasks.append(task)
return agents, tasks
async def astart(self):
"""
Async version of start() method.
Creates tasks based on the instructions, then starts execution.
Returns the task status and results dictionary.
"""
return await super().astart()
def start(self):
"""
Creates tasks based on the instructions, then starts execution.
Returns the task status and results dictionary.
"""
return super().start()
@@ -0,0 +1,8 @@
"""
PraisonAI Knowledge - Advanced knowledge management system with configurable features
"""
from praisonaiagents.knowledge.knowledge import Knowledge
from praisonaiagents.knowledge.chunking import Chunking
__all__ = ["Knowledge", "Chunking"]
@@ -0,0 +1,182 @@
from typing import List, Union, Optional, Dict, Any
from functools import cached_property
import importlib
class Chunking:
"""A unified class for text chunking with various chunking strategies."""
CHUNKER_PARAMS = {
'token': ['chunk_size', 'chunk_overlap', 'tokenizer'],
'word': ['chunk_size', 'chunk_overlap', 'tokenizer'],
'sentence': ['chunk_size', 'chunk_overlap', 'tokenizer'],
'semantic': ['chunk_size', 'embedding_model', 'tokenizer'],
'sdpm': ['chunk_size', 'embedding_model', 'tokenizer'],
'late': ['chunk_size', 'embedding_model', 'tokenizer'],
'recursive': ['chunk_size', 'tokenizer']
}
@cached_property
def SUPPORTED_CHUNKERS(self) -> Dict[str, Any]:
"""Lazy load chunker classes."""
try:
from chonkie.chunker import (
TokenChunker,
WordChunker,
SentenceChunker,
SemanticChunker,
SDPMChunker,
LateChunker,
RecursiveChunker
)
except ImportError:
raise ImportError(
"chonkie package not found. Please install it using: pip install 'praisonaiagents[knowledge]'"
)
return {
'token': TokenChunker,
'word': WordChunker,
'sentence': SentenceChunker,
'semantic': SemanticChunker,
'sdpm': SDPMChunker,
'late': LateChunker,
'recursive': RecursiveChunker
}
def __init__(
self,
chunker_type: str = 'token',
chunk_size: int = 512,
chunk_overlap: int = 128,
tokenizer: str = "gpt2",
embedding_model: Optional[Union[str, Any]] = None,
**kwargs
):
"""Initialize the Chunking class."""
if chunker_type not in self.CHUNKER_PARAMS:
raise ValueError(
f"Unsupported chunker type: {chunker_type}. "
f"Must be one of: {list(self.CHUNKER_PARAMS.keys())}"
)
self.chunker_type = chunker_type
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
self.tokenizer = tokenizer
self._embedding_model = embedding_model
self.kwargs = kwargs
# Initialize these as None for lazy loading
self._chunker = None
self._embeddings = None
@cached_property
def embedding_model(self):
"""Lazy load the embedding model."""
if self._embedding_model is None and self.chunker_type in ['semantic', 'sdpm', 'late']:
from chonkie.embeddings import AutoEmbeddings
return AutoEmbeddings.get_embeddings("all-MiniLM-L6-v2")
elif isinstance(self._embedding_model, str):
from chonkie.embeddings import AutoEmbeddings
return AutoEmbeddings.get_embeddings(self._embedding_model)
return self._embedding_model
def _get_chunker_params(self) -> Dict[str, Any]:
"""Get the appropriate parameters for the current chunker type."""
allowed_params = self.CHUNKER_PARAMS[self.chunker_type]
params = {'chunk_size': self.chunk_size}
if 'chunk_overlap' in allowed_params:
params['chunk_overlap'] = self.chunk_overlap
if 'tokenizer' in allowed_params:
if self.chunker_type in ['semantic', 'sdpm', 'late']:
params['tokenizer'] = self.embedding_model.get_tokenizer_or_token_counter()
else:
params['tokenizer'] = self.tokenizer
if 'embedding_model' in allowed_params:
params['embedding_model'] = self.embedding_model
# Add any additional kwargs that are in allowed_params
for key, value in self.kwargs.items():
if key in allowed_params:
params[key] = value
return params
@cached_property
def chunker(self):
"""Lazy load the chunker instance."""
if self._chunker is None:
chunker_cls = self.SUPPORTED_CHUNKERS[self.chunker_type]
common_params = self._get_chunker_params()
self._chunker = chunker_cls(**common_params)
return self._chunker
def _get_overlap_refinery(self, context_size: Optional[int] = None, **kwargs):
"""Lazy load the overlap refinery."""
try:
from chonkie.refinery import OverlapRefinery
except ImportError:
raise ImportError("Failed to import OverlapRefinery from chonkie.refinery")
if context_size is None:
context_size = self.chunk_overlap
return OverlapRefinery(
context_size=context_size,
tokenizer=self.chunker.tokenizer,
**kwargs
)
def add_overlap_context(
self,
chunks: List[Any],
context_size: int = None,
mode: str = "suffix",
merge_context: bool = True
) -> List[Any]:
"""Add overlap context to chunks using OverlapRefinery."""
refinery = self._get_overlap_refinery(
context_size=context_size,
mode=mode,
merge_context=merge_context
)
return refinery.refine(chunks)
def chunk(
self,
text: Union[str, List[str]],
add_context: bool = False,
context_params: Optional[Dict[str, Any]] = None
) -> Union[List[Any], List[List[Any]]]:
"""Chunk text using the configured chunking strategy."""
chunks = self.chunker(text)
if add_context:
context_params = context_params or {}
if isinstance(text, str):
chunks = self.add_overlap_context(chunks, **context_params)
else:
chunks = [self.add_overlap_context(c, **context_params) for c in chunks]
return chunks
def __call__(
self,
text: Union[str, List[str]],
add_context: bool = False,
context_params: Optional[Dict[str, Any]] = None
) -> Union[List[Any], List[List[Any]]]:
"""Make the Chunking instance callable."""
return self.chunk(text, add_context, context_params)
def __repr__(self) -> str:
"""String representation of the Chunking instance."""
return (
f"Chunking(chunker_type='{self.chunker_type}', "
f"chunk_size={self.chunk_size}, "
f"chunk_overlap={self.chunk_overlap})"
)
@@ -0,0 +1,303 @@
import os
import logging
import uuid
import time
from .chunking import Chunking
from functools import cached_property
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
logger = logging.getLogger(__name__)
class CustomMemory:
@classmethod
def from_config(cls, config):
from mem0 import Memory
return type('CustomMemory', (Memory,), {
'_add_to_vector_store': cls._add_to_vector_store
}).from_config(config)
@staticmethod
def _add_to_vector_store(self, messages, metadata, filters):
# Custom implementation that doesn't use LLM
parsed_messages = "\n".join([msg["content"] for msg in messages])
# Create a simple fact without using LLM
new_retrieved_facts = [parsed_messages]
# Process embeddings and continue with vector store operations
new_message_embeddings = {}
for new_mem in new_retrieved_facts:
messages_embeddings = self.embedding_model.embed(new_mem)
new_message_embeddings[new_mem] = messages_embeddings
# Create the memory
memory_id = self._create_memory(
data=parsed_messages,
existing_embeddings=new_message_embeddings,
metadata=metadata
)
return [{
"id": memory_id,
"memory": parsed_messages,
"event": "ADD"
}]
class Knowledge:
def __init__(self, config=None, verbose=None):
self._config = config
self._verbose = verbose or 0
os.environ['ANONYMIZED_TELEMETRY'] = 'False' # Chromadb
# Configure logging levels based on verbose setting
if not self._verbose:
# Suppress logs from all relevant dependencies
for logger_name in [
'mem0',
'chromadb',
'local_persistent_hnsw',
'_client',
'main'
]:
logging.getLogger(logger_name).setLevel(logging.WARNING)
# Disable OpenAI API request logging
logging.getLogger('openai').setLevel(logging.WARNING)
# Set root logger to warning to catch any uncategorized logs
logging.getLogger().setLevel(logging.WARNING)
@cached_property
def _deps(self):
try:
from markitdown import MarkItDown
import chromadb
return {
'chromadb': chromadb,
'markdown': MarkItDown()
}
except ImportError:
raise ImportError(
"Required packages not installed. Please install using: "
'pip install "praisonaiagents[knowledge]"'
)
@cached_property
def config(self):
# Generate unique collection name for each instance
collection_name = f"test_{int(time.time())}_{str(uuid.uuid4())[:8]}"
persist_dir = ".praison"
# Create persistent client config
base_config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": collection_name,
"path": persist_dir,
"client": self._deps['chromadb'].PersistentClient(path=persist_dir)
}
},
"version": "v1.1",
"custom_prompt": "Return {{\"facts\": [text]}} where text is the exact input provided and json response"
}
# If config is provided, merge it with base config
if self._config:
if "vector_store" in self._config and "config" in self._config["vector_store"]:
config_copy = self._config["vector_store"]["config"].copy()
for key in ["collection_name", "client"]:
if key in config_copy:
del config_copy[key]
base_config["vector_store"]["config"].update(config_copy)
return base_config
@cached_property
def memory(self):
try:
return CustomMemory.from_config(self.config)
except (NotImplementedError, ValueError) as e:
if "list_collections" in str(e) or "Extra fields not allowed" in str(e):
# Keep only allowed fields
vector_store_config = {
"collection_name": self.config["vector_store"]["config"]["collection_name"],
"path": self.config["vector_store"]["config"]["path"]
}
self.config["vector_store"]["config"] = vector_store_config
from mem0 import Memory
return Memory.from_config(self.config)
raise
@cached_property
def markdown(self):
return self._deps['markdown']
@cached_property
def chunker(self):
return Chunking(
chunker_type='recursive',
chunk_size=512,
chunk_overlap=50
)
def _log(self, message, level=2):
"""Internal logging helper"""
if self._verbose and self._verbose >= level:
logger.info(message)
def store(self, content, user_id=None, agent_id=None, run_id=None, metadata=None):
"""Store a memory."""
try:
if isinstance(content, str):
if any(content.lower().endswith(ext) for ext in ['.pdf', '.doc', '.docx', '.txt']):
self._log(f"Content appears to be a file path, processing file: {content}")
return self.add(content, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
content = content.strip()
if not content:
return []
result = self.memory.add(content, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
self._log(f"Store operation result: {result}")
return result
except Exception as e:
logger.error(f"Error storing content: {str(e)}")
return []
def get_all(self, user_id=None, agent_id=None, run_id=None):
"""Retrieve all memories."""
return self.memory.get_all(user_id=user_id, agent_id=agent_id, run_id=run_id)
def get(self, memory_id):
"""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 update(self, memory_id, data):
"""Update a memory."""
return self.memory.update(memory_id, data)
def history(self, memory_id):
"""Get the history of changes for a memory."""
return self.memory.history(memory_id)
def delete(self, memory_id):
"""Delete a memory."""
self.memory.delete(memory_id)
def delete_all(self, user_id=None, agent_id=None, run_id=None):
"""Delete all memories."""
self.memory.delete_all(user_id=user_id, agent_id=agent_id, run_id=run_id)
def reset(self):
"""Reset all memories."""
self.memory.reset()
def normalize_content(self, content):
"""Normalize content for consistent storage."""
# Example normalization: strip whitespace, convert to lowercase
return content.strip().lower()
def add(self, file_path, user_id=None, agent_id=None, run_id=None, metadata=None):
"""Read file content and store it in memory.
Args:
file_path: Can be:
- A string path to local file
- A URL string
- A list containing file paths and/or URLs
"""
if isinstance(file_path, (list, tuple)):
results = []
for path in file_path:
result = self._process_single_input(path, user_id, agent_id, run_id, metadata)
results.extend(result.get('results', []))
return {'results': results, 'relations': []}
return self._process_single_input(file_path, user_id, agent_id, run_id, metadata)
def _process_single_input(self, input_path, user_id=None, agent_id=None, run_id=None, metadata=None):
"""Process a single input which can be a file path or URL."""
try:
# Define supported file extensions
DOCUMENT_EXTENSIONS = {
'document': ('.pdf', '.ppt', '.pptx', '.doc', '.docx', '.xls', '.xlsx'),
'media': ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.mp3', '.wav', '.ogg', '.m4a'),
'text': ('.txt', '.csv', '.json', '.xml', '.md', '.html', '.htm'),
'archive': '.zip'
}
# Check if input is URL
if isinstance(input_path, str) and (input_path.startswith('http://') or input_path.startswith('https://')):
self._log(f"Processing URL: {input_path}")
raise NotImplementedError("URL processing not yet implemented")
# Check if input ends with any supported extension
is_supported_file = any(input_path.lower().endswith(ext)
for exts in DOCUMENT_EXTENSIONS.values()
for ext in (exts if isinstance(exts, tuple) else (exts,)))
if is_supported_file:
self._log(f"Processing as file path: {input_path}")
if not os.path.exists(input_path):
logger.error(f"File not found: {input_path}")
raise FileNotFoundError(f"File not found: {input_path}")
file_ext = '.' + input_path.lower().split('.')[-1] # Get extension reliably
# Process file based on type
if file_ext in DOCUMENT_EXTENSIONS['text']:
with open(input_path, 'r', encoding='utf-8') as file:
content = file.read().strip()
if not content:
raise ValueError("Empty text file")
memories = [self.normalize_content(content)]
else:
# Use MarkItDown for documents and media
result = self.markdown.convert(input_path)
content = result.text_content
if not content:
raise ValueError("No content could be extracted from file")
chunks = self.chunker.chunk(content)
memories = [chunk.text.strip() if hasattr(chunk, 'text') else str(chunk).strip()
for chunk in chunks if chunk]
# Set metadata for file
if not metadata:
metadata = {}
metadata['file_type'] = file_ext.lstrip('.')
metadata['filename'] = os.path.basename(input_path)
else:
# Treat as raw text content only if no file extension
memories = [self.normalize_content(input_path)]
# Create progress display
progress = Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
transient=True
)
# Store memories with progress bar
all_results = []
with progress:
store_task = progress.add_task(f"Adding to Knowledge from {os.path.basename(input_path)}", total=len(memories))
for memory in memories:
if memory:
memory_result = self.store(memory, user_id=user_id, agent_id=agent_id,
run_id=run_id, metadata=metadata)
if memory_result:
all_results.extend(memory_result.get('results', []))
progress.advance(store_task)
return {'results': all_results, 'relations': []}
except Exception as e:
logger.error(f"Error processing input {input_path}: {str(e)}", exc_info=True)
raise
+36 -8
View File
@@ -23,6 +23,10 @@ logging.basicConfig(
handlers=[RichHandler(rich_tracebacks=True)]
)
# Add these lines to suppress markdown parser debug logs
logging.getLogger('markdown_it').setLevel(logging.WARNING)
logging.getLogger('rich.markdown').setLevel(logging.WARNING)
# Global list to store error logs
error_logs = []
@@ -116,10 +120,10 @@ def display_interaction(message, response, markdown=True, generation_time=None,
console.print(Text(f"Response generated in {generation_time:.1f}s", style="dim"))
if markdown:
console.print(Panel.fit(Markdown(message), title="Message", border_style="cyan"))
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
console.print(Panel.fit(Markdown(response), title="Response", border_style="cyan"))
else:
console.print(Panel.fit(Text(message, style="bold green"), title="Message", border_style="cyan"))
console.print(Panel.fit(Text(message, style="bold green"), title="Task", border_style="cyan"))
console.print(Panel.fit(Text(response, style="bold blue"), title="Response", border_style="cyan"))
def display_self_reflection(message: str, console=None):
@@ -135,7 +139,7 @@ def display_self_reflection(message: str, console=None):
console.print(Panel.fit(Text(message, style="bold yellow"), title="Self Reflection", border_style="magenta"))
def display_instruction(message: str, console=None):
def display_instruction(message: str, console=None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None):
if not message or not message.strip():
return
if console is None:
@@ -146,7 +150,19 @@ def display_instruction(message: str, console=None):
if 'instruction' in sync_display_callbacks:
sync_display_callbacks['instruction'](message=message)
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
# Display agent info if available
if agent_name:
agent_info = f"[bold #FF9B9B]👤 Agent:[/] [#FFE5E5]{agent_name}[/]"
if agent_role:
agent_info += f"\n[bold #B4B4B3]Role:[/] [#FFE5E5]{agent_role}[/]"
if agent_tools:
tools_str = ", ".join(f"[italic #B4D4FF]{tool}[/]" for tool in agent_tools)
agent_info += f"\n[bold #86A789]Tools:[/] {tools_str}"
console.print(Panel(agent_info, border_style="#D2E3C8", title="[bold]Agent Info[/]", title_align="left", padding=(1, 2)))
# Only print if log level is DEBUG
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
def display_tool_call(message: str, console=None):
if not message or not message.strip():
@@ -222,10 +238,10 @@ async def adisplay_interaction(message, response, markdown=True, generation_time
console.print(Text(f"Response generated in {generation_time:.1f}s", style="dim"))
if markdown:
console.print(Panel.fit(Markdown(message), title="Message", border_style="cyan"))
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
console.print(Panel.fit(Markdown(response), title="Response", border_style="cyan"))
else:
console.print(Panel.fit(Text(message, style="bold green"), title="Message", border_style="cyan"))
console.print(Panel.fit(Text(message, style="bold green"), title="Task", border_style="cyan"))
console.print(Panel.fit(Text(response, style="bold blue"), title="Response", border_style="cyan"))
async def adisplay_self_reflection(message: str, console=None):
@@ -241,7 +257,7 @@ async def adisplay_self_reflection(message: str, console=None):
console.print(Panel.fit(Text(message, style="bold yellow"), title="Self Reflection", border_style="magenta"))
async def adisplay_instruction(message: str, console=None):
async def adisplay_instruction(message: str, console=None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None):
"""Async version of display_instruction."""
if not message or not message.strip():
return
@@ -252,7 +268,19 @@ async def adisplay_instruction(message: str, console=None):
if 'instruction' in async_display_callbacks:
await async_display_callbacks['instruction'](message=message)
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
# Display agent info if available
if agent_name:
agent_info = f"[bold #FF9B9B]👤 Agent:[/] [#FFE5E5]{agent_name}[/]"
if agent_role:
agent_info += f"\n[bold #B4B4B3]Role:[/] [#FFE5E5]{agent_role}[/]"
if agent_tools:
tools_str = ", ".join(f"[italic #B4D4FF]{tool}[/]" for tool in agent_tools)
agent_info += f"\n[bold #86A789]Tools:[/] {tools_str}"
console.print(Panel(agent_info, border_style="#D2E3C8", title="[bold]Agent Info[/]", title_align="left", padding=(1, 2)))
# Only print if log level is DEBUG
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
async def adisplay_tool_call(message: str, console=None):
"""Async version of display_tool_call."""
+931
View File
@@ -0,0 +1,931 @@
import os
import sqlite3
import json
import time
import shutil
from typing import Any, Dict, List, Optional, Union, Literal
import logging
# Set up logger
logger = logging.getLogger(__name__)
try:
import chromadb
from chromadb.config import Settings as ChromaSettings
CHROMADB_AVAILABLE = True
except ImportError:
CHROMADB_AVAILABLE = False
logger.warning("To use memory features, please run: pip install \"praisonaiagents[memory]\"")
try:
import mem0
MEM0_AVAILABLE = True
except ImportError:
MEM0_AVAILABLE = False
try:
import openai
OPENAI_AVAILABLE = True
except ImportError:
OPENAI_AVAILABLE = False
class Memory:
"""
A single-file memory manager covering:
- Short-term memory (STM) for ephemeral context
- Long-term memory (LTM) for persistent knowledge
- Entity memory (structured data about named entities)
- User memory (preferences/history for each user)
- Quality score logic for deciding which data to store in LTM
- Context building from multiple memory sources
Config example:
{
"provider": "rag" or "mem0" or "none",
"use_embedding": True,
"short_db": "short_term.db",
"long_db": "long_term.db",
"rag_db_path": "rag_db", # optional path for local embedding store
"config": {
"api_key": "...", # if mem0 usage
"org_id": "...",
"project_id": "...",
...
}
}
"""
def __init__(self, config: Dict[str, Any], verbose: int = 0):
self.cfg = config or {}
self.verbose = verbose
# Set logger level based on verbose
if verbose >= 5:
logger.setLevel(logging.INFO)
else:
logger.setLevel(logging.WARNING)
# Also set ChromaDB and OpenAI client loggers to WARNING
logging.getLogger('chromadb').setLevel(logging.WARNING)
logging.getLogger('openai').setLevel(logging.WARNING)
logging.getLogger('httpx').setLevel(logging.WARNING)
logging.getLogger('httpcore').setLevel(logging.WARNING)
logging.getLogger('chromadb.segment.impl.vector.local_persistent_hnsw').setLevel(logging.ERROR)
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)
# Create .praison directory if it doesn't exist
os.makedirs(".praison", exist_ok=True)
# Short-term DB
self.short_db = self.cfg.get("short_db", ".praison/short_term.db")
self._init_stm()
# Long-term DB
self.long_db = self.cfg.get("long_db", ".praison/long_term.db")
self._init_ltm()
# Conditionally init Mem0 or local RAG
if self.use_mem0:
self._init_mem0()
elif self.use_rag:
self._init_chroma()
def _log_verbose(self, msg: str, level: int = logging.INFO):
"""Only log if verbose >= 5"""
if self.verbose >= 5:
logger.log(level, msg)
# -------------------------------------------------------------------------
# Initialization
# -------------------------------------------------------------------------
def _init_stm(self):
"""Creates or verifies short-term memory table."""
os.makedirs(os.path.dirname(self.short_db) or ".", exist_ok=True)
conn = sqlite3.connect(self.short_db)
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS short_mem (
id TEXT PRIMARY KEY,
content TEXT,
meta TEXT,
created_at REAL
)
""")
conn.commit()
conn.close()
def _init_ltm(self):
"""Creates or verifies long-term memory table."""
os.makedirs(os.path.dirname(self.long_db) or ".", exist_ok=True)
conn = sqlite3.connect(self.long_db)
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS long_mem (
id TEXT PRIMARY KEY,
content TEXT,
meta TEXT,
created_at REAL
)
""")
conn.commit()
conn.close()
def _init_mem0(self):
"""Initialize Mem0 client for agent or user memory."""
from mem0 import MemoryClient
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)
else:
self.mem0_client = MemoryClient(api_key=api_key)
def _init_chroma(self):
"""Initialize a local Chroma client for embedding-based search."""
try:
# Create directory if it doesn't exist
rag_path = self.cfg.get("rag_db_path", "chroma_db")
os.makedirs(rag_path, exist_ok=True)
# Initialize ChromaDB with persistent storage
self.chroma_client = chromadb.PersistentClient(
path=rag_path,
settings=ChromaSettings(
anonymized_telemetry=False,
allow_reset=True
)
)
collection_name = "memory_store"
try:
self.chroma_col = self.chroma_client.get_collection(name=collection_name)
self._log_verbose("Using existing ChromaDB collection")
except Exception as e:
self._log_verbose(f"Collection '{collection_name}' not found. Creating new collection. Error: {e}")
self.chroma_col = self.chroma_client.create_collection(
name=collection_name,
metadata={"hnsw:space": "cosine"}
)
self._log_verbose("Created new ChromaDB collection")
except Exception as e:
self._log_verbose(f"Failed to initialize ChromaDB: {e}", logging.ERROR)
self.use_rag = False
# -------------------------------------------------------------------------
# Basic Quality Score Computation
# -------------------------------------------------------------------------
def compute_quality_score(
self,
completeness: float,
relevance: float,
clarity: float,
accuracy: float,
weights: Dict[str, float] = None
) -> float:
"""
Combine multiple sub-metrics into one final score, as an example.
Args:
completeness (float): 0-1
relevance (float): 0-1
clarity (float): 0-1
accuracy (float): 0-1
weights (Dict[str, float]): optional weighting like {"completeness": 0.25, "relevance": 0.3, ...}
Returns:
float: Weighted average 0-1
"""
if not weights:
weights = {
"completeness": 0.25,
"relevance": 0.25,
"clarity": 0.25,
"accuracy": 0.25
}
total = (completeness * weights["completeness"]
+ relevance * weights["relevance"]
+ clarity * weights["clarity"]
+ accuracy * weights["accuracy"]
)
return round(total, 3) # e.g. round to 3 decimal places
# -------------------------------------------------------------------------
# Short-Term Methods
# -------------------------------------------------------------------------
def store_short_term(
self,
text: str,
metadata: Dict[str, Any] = None,
completeness: float = None,
relevance: float = None,
clarity: float = None,
accuracy: float = None,
weights: Dict[str, float] = None,
evaluator_quality: float = None
):
"""Store in short-term memory with optional quality metrics"""
logger.info(f"Storing in short-term memory: {text[:100]}...")
logger.info(f"Metadata: {metadata}")
metadata = self._process_quality_metrics(
metadata, completeness, relevance, clarity,
accuracy, weights, evaluator_quality
)
logger.info(f"Processed metadata: {metadata}")
# Existing store logic
try:
conn = sqlite3.connect(self.short_db)
ident = str(time.time_ns())
conn.execute(
"INSERT INTO short_mem (id, content, meta, created_at) VALUES (?,?,?,?)",
(ident, text, json.dumps(metadata), time.time())
)
conn.commit()
conn.close()
logger.info(f"Successfully stored in short-term memory with ID: {ident}")
except Exception as e:
logger.error(f"Failed to store in short-term memory: {e}")
raise
def search_short_term(
self,
query: str,
limit: int = 5,
min_quality: float = 0.0,
relevance_cutoff: float = 0.0
) -> 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)
filtered = [r for r in results if r.get("score", 1.0) >= relevance_cutoff]
return filtered
elif self.use_rag and hasattr(self, "chroma_col"):
try:
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
input=query,
model="text-embedding-3-small"
)
query_embedding = response.data[0].embedding
resp = self.chroma_col.query(
query_embeddings=[query_embedding],
n_results=limit
)
results = []
if resp["ids"]:
for i in range(len(resp["ids"][0])):
metadata = resp["metadatas"][0][i] if "metadatas" in resp else {}
quality = metadata.get("quality", 0.0)
score = 1.0 - (resp["distances"][0][i] if "distances" in resp else 0.0)
if quality >= min_quality and score >= relevance_cutoff:
results.append({
"id": resp["ids"][0][i],
"text": resp["documents"][0][i],
"metadata": metadata,
"score": score
})
return results
except Exception as e:
self._log_verbose(f"Error searching ChromaDB: {e}", logging.ERROR)
return []
else:
# Local fallback
conn = sqlite3.connect(self.short_db)
c = conn.cursor()
rows = c.execute(
"SELECT id, content, meta FROM short_mem WHERE content LIKE ? LIMIT ?",
(f"%{query}%", limit)
).fetchall()
conn.close()
results = []
for row in rows:
meta = json.loads(row[2] or "{}")
quality = meta.get("quality", 0.0)
if quality >= min_quality:
results.append({
"id": row[0],
"text": row[1],
"metadata": meta
})
return results
def reset_short_term(self):
"""Completely clears short-term memory."""
conn = sqlite3.connect(self.short_db)
conn.execute("DELETE FROM short_mem")
conn.commit()
conn.close()
# -------------------------------------------------------------------------
# Long-Term Methods
# -------------------------------------------------------------------------
def _sanitize_metadata(self, metadata: Dict) -> Dict:
"""Sanitize metadata for ChromaDB - convert to acceptable types"""
sanitized = {}
for k, v in metadata.items():
if v is None:
continue
if isinstance(v, (str, int, float, bool)):
sanitized[k] = v
elif isinstance(v, dict):
# Convert dict to string representation
sanitized[k] = str(v)
else:
# Convert other types to string
sanitized[k] = str(v)
return sanitized
def store_long_term(
self,
text: str,
metadata: Dict[str, Any] = None,
completeness: float = None,
relevance: float = None,
clarity: float = None,
accuracy: float = None,
weights: Dict[str, float] = None,
evaluator_quality: float = None
):
"""Store in long-term memory with optional quality metrics"""
logger.info(f"Storing in long-term memory: {text[:100]}...")
logger.info(f"Initial metadata: {metadata}")
# Process metadata
metadata = metadata or {}
metadata = self._process_quality_metrics(
metadata, completeness, relevance, clarity,
accuracy, weights, evaluator_quality
)
logger.info(f"Processed metadata: {metadata}")
# Generate unique ID
ident = str(time.time_ns())
created = time.time()
# Store in SQLite
try:
conn = sqlite3.connect(self.long_db)
conn.execute(
"INSERT INTO long_mem (id, content, meta, created_at) VALUES (?,?,?,?)",
(ident, text, json.dumps(metadata), created)
)
conn.commit()
conn.close()
logger.info(f"Successfully stored in SQLite with ID: {ident}")
except Exception as e:
logger.error(f"Error storing in SQLite: {e}")
return
# Store in vector database if enabled
if self.use_rag and hasattr(self, "chroma_col"):
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # Ensure API key is correctly set
logger.info("Getting embeddings from OpenAI...")
logger.debug(f"Embedding input text: {text}") # Log the input text
response = client.embeddings.create(
input=text,
model="text-embedding-3-small"
)
embedding = response.data[0].embedding
logger.info("Successfully got embeddings")
logger.debug(f"Received embedding of length: {len(embedding)}") # Log embedding details
# Sanitize metadata for ChromaDB
sanitized_metadata = self._sanitize_metadata(metadata)
# Store in ChromaDB with embedding
self.chroma_col.add(
documents=[text],
metadatas=[sanitized_metadata],
ids=[ident],
embeddings=[embedding]
)
logger.info(f"Successfully stored in ChromaDB with ID: {ident}")
except Exception as e:
logger.error(f"Error storing in ChromaDB: {e}")
elif self.use_mem0 and hasattr(self, "mem0_client"):
try:
self.mem0_client.add(text, metadata=metadata)
logger.info("Successfully stored in Mem0")
except Exception as e:
logger.error(f"Error storing in Mem0: {e}")
def search_long_term(
self,
query: str,
limit: int = 5,
relevance_cutoff: float = 0.0,
min_quality: float = 0.0
) -> List[Dict[str, Any]]:
"""Search long-term memory with optional quality filter"""
self._log_verbose(f"Searching long memory for: {query}")
self._log_verbose(f"Min quality: {min_quality}")
found = []
if self.use_mem0 and hasattr(self, "mem0_client"):
results = self.mem0_client.search(query=query, limit=limit)
# 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")
return filtered
elif self.use_rag and hasattr(self, "chroma_col"):
try:
from openai import OpenAI
client = OpenAI()
# Get query embedding
response = client.embeddings.create(
input=query,
model="text-embedding-3-small" # Using consistent model
)
query_embedding = response.data[0].embedding
# Search ChromaDB with embedding
resp = self.chroma_col.query(
query_embeddings=[query_embedding],
n_results=limit,
include=["documents", "metadatas", "distances"]
)
results = []
if resp["ids"]:
for i in range(len(resp["ids"][0])):
metadata = resp["metadatas"][0][i] if "metadatas" in resp else {}
text = resp["documents"][0][i]
# Add memory record citation
text = f"{text} (Memory record: {text})"
found.append({
"id": resp["ids"][0][i],
"text": text,
"metadata": metadata,
"score": 1.0 - (resp["distances"][0][i] if "distances" in resp else 0.0)
})
logger.info(f"Found {len(found)} results in ChromaDB")
except Exception as e:
self._log_verbose(f"Error searching ChromaDB: {e}", logging.ERROR)
# Always try SQLite as fallback or additional source
conn = sqlite3.connect(self.long_db)
c = conn.cursor()
rows = c.execute(
"SELECT id, content, meta, created_at FROM long_mem WHERE content LIKE ? LIMIT ?",
(f"%{query}%", limit)
).fetchall()
conn.close()
for row in rows:
meta = json.loads(row[2] or "{}")
text = row[1]
# Add memory record citation if not already present
if "(Memory record:" not in text:
text = f"{text} (Memory record: {text})"
# Only add if not already found by ChromaDB/Mem0
if not any(f["id"] == row[0] for f in found):
found.append({
"id": row[0],
"text": text,
"metadata": meta,
"created_at": row[3]
})
logger.info(f"Found {len(found)} total results after SQLite")
results = found
# Filter by quality if needed
if min_quality > 0:
self._log_verbose(f"Found {len(results)} initial results")
results = [
r for r in results
if r.get("metadata", {}).get("quality", 0.0) >= min_quality
]
self._log_verbose(f"After quality filter: {len(results)} results")
# Apply relevance cutoff if specified
if relevance_cutoff > 0:
results = [r for r in results if r.get("score", 1.0) >= relevance_cutoff]
logger.info(f"After relevance filter: {len(results)} results")
return results[:limit]
def reset_long_term(self):
"""Clear local LTM DB, plus Chroma or mem0 if in use."""
conn = sqlite3.connect(self.long_db)
conn.execute("DELETE FROM long_mem")
conn.commit()
conn.close()
if self.use_mem0 and hasattr(self, "mem0_client"):
# Mem0 has no universal reset API. Could implement partial or no-op.
pass
if self.use_rag and hasattr(self, "chroma_client"):
self.chroma_client.reset() # entire DB
self._init_chroma() # re-init fresh
# -------------------------------------------------------------------------
# Entity Memory Methods
# -------------------------------------------------------------------------
def store_entity(self, name: str, type_: str, desc: str, relations: str):
"""
Save entity info in LTM (or mem0/rag).
We'll label the metadata type = entity for easy filtering.
"""
data = f"Entity {name}({type_}): {desc} | relationships: {relations}"
self.store_long_term(data, metadata={"category": "entity"})
def search_entity(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
"""
Filter to items that have metadata 'category=entity'.
"""
all_hits = self.search_long_term(query, limit=20) # gather more
ents = []
for h in all_hits:
meta = h.get("metadata") or {}
if meta.get("category") == "entity":
ents.append(h)
return ents[:limit]
def reset_entity_only(self):
"""
If you only want to drop entity items from LTM, you'd do a custom
delete from local DB where meta LIKE '%category=entity%'.
For brevity, we do a full LTM reset here.
"""
self.reset_long_term()
# -------------------------------------------------------------------------
# User Memory Methods
# -------------------------------------------------------------------------
def store_user_memory(self, user_id: str, text: str, extra: Dict[str, Any] = None):
"""
If mem0 is used, do user-based addition. Otherwise store in LTM with user in metadata.
"""
meta = {"user_id": user_id}
if extra:
meta.update(extra)
if self.use_mem0 and hasattr(self, "mem0_client"):
self.mem0_client.add(text, user_id=user_id, metadata=meta)
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]]:
"""
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)
else:
hits = self.search_long_term(query, limit=20)
filtered = []
for h in hits:
meta = h.get("metadata", {})
if meta.get("user_id") == user_id:
filtered.append(h)
return filtered[:limit]
def reset_user_memory(self):
"""
Clear all user-based info. For simplicity, we do a full LTM reset.
Real usage might filter only metadata "user_id".
"""
self.reset_long_term()
# -------------------------------------------------------------------------
# Putting it all Together: Task Finalization
# -------------------------------------------------------------------------
def finalize_task_output(
self,
content: str,
agent_name: str,
quality_score: float,
threshold: float = 0.7,
metrics: Dict[str, Any] = None,
task_id: str = None
):
"""Store task output in memory with appropriate metadata"""
logger.info(f"Finalizing task output: {content[:100]}...")
logger.info(f"Agent: {agent_name}, Quality: {quality_score}, Threshold: {threshold}")
metadata = {
"task_id": task_id,
"agent": agent_name,
"quality": quality_score,
"metrics": metrics,
"task_type": "output",
"stored_at": time.time()
}
logger.info(f"Prepared metadata: {metadata}")
# Always store in short-term memory
try:
logger.info("Storing in short-term memory...")
self.store_short_term(
text=content,
metadata=metadata
)
logger.info("Successfully stored in short-term memory")
except Exception as e:
logger.error(f"Failed to store in short-term memory: {e}")
# Store in long-term memory if quality meets threshold
if quality_score >= threshold:
try:
logger.info(f"Quality score {quality_score} >= {threshold}, storing in long-term memory...")
self.store_long_term(
text=content,
metadata=metadata
)
logger.info("Successfully stored in long-term memory")
except Exception as e:
logger.error(f"Failed to store in long-term memory: {e}")
else:
logger.info(f"Quality score {quality_score} < {threshold}, skipping long-term storage")
# -------------------------------------------------------------------------
# Building Context (Short, Long, Entities, User)
# -------------------------------------------------------------------------
def build_context_for_task(
self,
task_descr: str,
user_id: Optional[str] = None,
additional: str = "",
max_items: int = 3
) -> str:
"""
Merges relevant short-term, long-term, entity, user memories
into a single text block with deduplication and clean formatting.
"""
q = (task_descr + " " + additional).strip()
lines = []
seen_contents = set() # Track unique contents
def normalize_content(content: str) -> str:
"""Normalize content for deduplication"""
# Extract just the main content without citations for comparison
normalized = content.split("(Memory record:")[0].strip()
# Keep more characters to reduce false duplicates
normalized = ''.join(c.lower() for c in normalized if not c.isspace())
return normalized
def format_content(content: str, max_len: int = 150) -> str:
"""Format content with clean truncation at word boundaries"""
if not content:
return ""
# Clean up content by removing extra whitespace and newlines
content = ' '.join(content.split())
# If content contains a memory citation, preserve it
if "(Memory record:" in content:
return content # Keep original citation format
# Regular content truncation
if len(content) <= max_len:
return content
truncate_at = content.rfind(' ', 0, max_len - 3)
if truncate_at == -1:
truncate_at = max_len - 3
return content[:truncate_at] + "..."
def add_section(title: str, hits: List[Any]) -> None:
"""Add a section of memory hits with deduplication"""
if not hits:
return
formatted_hits = []
for h in hits:
content = h.get('text', '') if isinstance(h, dict) else str(h)
if not content:
continue
# Keep original format if it has a citation
if "(Memory record:" in content:
formatted = content
else:
formatted = format_content(content)
# Only add if we haven't seen this normalized content before
normalized = normalize_content(formatted)
if normalized not in seen_contents:
seen_contents.add(normalized)
formatted_hits.append(formatted)
if formatted_hits:
# Add section header
if lines:
lines.append("") # Space before new section
lines.append(title)
lines.append("=" * len(title)) # Underline the title
lines.append("") # Space after title
# Add formatted content with bullet points
for content in formatted_hits:
lines.append(f" • {content}")
# Add each section
# First get all results
short_term = self.search_short_term(q, limit=max_items)
long_term = self.search_long_term(q, limit=max_items)
entities = self.search_entity(q, limit=max_items)
user_mem = self.search_user_memory(user_id, q, limit=max_items) if user_id else []
# Add sections in order of priority
add_section("Short-term Memory Context", short_term)
add_section("Long-term Memory Context", long_term)
add_section("Entity Context", entities)
if user_id:
add_section("User Context", user_mem)
return "\n".join(lines) if lines else ""
# -------------------------------------------------------------------------
# Master Reset (Everything)
# -------------------------------------------------------------------------
def reset_all(self):
"""
Fully wipes short-term, long-term, and any memory in mem0 or rag.
"""
self.reset_short_term()
self.reset_long_term()
# Entities & user memory are stored in LTM or mem0, so no separate step needed.
def _process_quality_metrics(
self,
metadata: Dict[str, Any],
completeness: float = None,
relevance: float = None,
clarity: float = None,
accuracy: float = None,
weights: Dict[str, float] = None,
evaluator_quality: float = None
) -> Dict[str, Any]:
"""Process and store quality metrics in metadata"""
metadata = metadata or {}
# Handle sub-metrics if provided
if None not in [completeness, relevance, clarity, accuracy]:
metadata.update({
"completeness": completeness,
"relevance": relevance,
"clarity": clarity,
"accuracy": accuracy,
"quality": self.compute_quality_score(
completeness, relevance, clarity, accuracy, weights
)
})
# Handle external evaluator quality if provided
elif evaluator_quality is not None:
metadata["quality"] = evaluator_quality
return metadata
def calculate_quality_metrics(
self,
output: str,
expected_output: str,
llm: Optional[str] = None,
custom_prompt: Optional[str] = None
) -> Dict[str, float]:
"""Calculate quality metrics using LLM"""
logger.info("Calculating quality metrics for output")
logger.info(f"Output: {output[:100]}...")
logger.info(f"Expected: {expected_output[:100]}...")
# Default evaluation prompt
default_prompt = f"""
Evaluate the following output against expected output.
Score each metric from 0.0 to 1.0:
- Completeness: Does it address all requirements?
- Relevance: Does it match expected output?
- Clarity: Is it clear and well-structured?
- Accuracy: Is it factually correct?
Expected: {expected_output}
Actual: {output}
Return ONLY a JSON with these keys: completeness, relevance, clarity, accuracy
Example: {{"completeness": 0.95, "relevance": 0.8, "clarity": 0.9, "accuracy": 0.85}}
"""
try:
# Use OpenAI client from main.py
from ..main import client
response = client.chat.completions.create(
model=llm or "gpt-4o",
messages=[{
"role": "user",
"content": custom_prompt or default_prompt
}],
response_format={"type": "json_object"},
temperature=0.3
)
metrics = json.loads(response.choices[0].message.content)
# Validate metrics
required = ["completeness", "relevance", "clarity", "accuracy"]
if not all(k in metrics for k in required):
raise ValueError("Missing required metrics in LLM response")
logger.info(f"Calculated metrics: {metrics}")
return metrics
except Exception as e:
logger.error(f"Error calculating metrics: {e}")
return {
"completeness": 0.0,
"relevance": 0.0,
"clarity": 0.0,
"accuracy": 0.0
}
def store_quality(
self,
text: str,
quality_score: float,
task_id: Optional[str] = None,
iteration: Optional[int] = None,
metrics: Optional[Dict[str, float]] = None,
memory_type: Literal["short", "long"] = "long"
) -> None:
"""Store quality metrics in memory"""
logger.info(f"Attempting to store in {memory_type} memory: {text[:100]}...")
metadata = {
"quality": quality_score,
"task_id": task_id,
"iteration": iteration
}
if metrics:
metadata.update({
k: v for k, v in metrics.items() # Remove metric_ prefix
})
logger.info(f"With metadata: {metadata}")
try:
if memory_type == "short":
self.store_short_term(text, metadata=metadata)
logger.info("Successfully stored in short-term memory")
else:
self.store_long_term(text, metadata=metadata)
logger.info("Successfully stored in long-term memory")
except Exception as e:
logger.error(f"Failed to store in memory: {e}")
def search_with_quality(
self,
query: str,
min_quality: float = 0.0,
memory_type: Literal["short", "long"] = "long",
limit: int = 5
) -> List[Dict[str, Any]]:
"""Search with quality filter"""
logger.info(f"Searching {memory_type} memory for: {query}")
logger.info(f"Min quality: {min_quality}")
search_func = (
self.search_short_term if memory_type == "short"
else self.search_long_term
)
results = search_func(query, limit=limit)
logger.info(f"Found {len(results)} initial results")
filtered = [
r for r in results
if r.get("metadata", {}).get("quality", 0.0) >= min_quality
]
logger.info(f"After quality filter: {len(filtered)} results")
return filtered
+202 -86
View File
@@ -5,19 +5,23 @@ from pydantic import BaseModel
from ..agent.agent import Agent
from ..task.task import Task
from ..main import display_error, client
import csv
import os
class LoopItems(BaseModel):
items: List[Any]
class Process:
def __init__(self, tasks: Dict[str, Task], agents: List[Agent], manager_llm: Optional[str] = None, verbose: bool = False):
def __init__(self, tasks: Dict[str, Task], agents: List[Agent], manager_llm: Optional[str] = None, verbose: bool = False, max_iter: int = 10):
self.tasks = tasks
self.agents = agents
self.manager_llm = manager_llm
self.verbose = verbose
self.max_iter = max_iter
async def aworkflow(self) -> AsyncGenerator[str, None]:
"""Async version of workflow method"""
current_iter = 0 # Track how many times we've looped
# Build workflow relationships first
for task in self.tasks.values():
if task.next_tasks:
@@ -41,7 +45,12 @@ class Process:
visited_tasks = set()
loop_data = {} # Store loop-specific data
while current_task and current_task.id not in visited_tasks:
while current_task:
current_iter += 1
if current_iter > self.max_iter:
logging.info(f"Max iteration limit {self.max_iter} reached, ending workflow.")
break
task_id = current_task.id
logging.info(f"Executing workflow task: {current_task.name if current_task.name else task_id}")
@@ -55,46 +64,47 @@ class Process:
if prev_task and prev_task.result:
# Handle loop data
if current_task.task_type == "loop":
# create a loop manager Agent
loop_manager = Agent(
name="Loop Manager",
role="Loop data processor",
goal="Process loop data and convert it to list format",
backstory="Expert at handling loop data and converting it to proper format",
llm=self.manager_llm,
verbose=self.verbose,
markdown=True
)
# # create a loop manager Agent
# loop_manager = Agent(
# name="Loop Manager",
# role="Loop data processor",
# goal="Process loop data and convert it to list format",
# backstory="Expert at handling loop data and converting it to proper format",
# llm=self.manager_llm,
# verbose=self.verbose,
# markdown=True
# )
# get the loop data convert it to list using calling Agent class chat
loop_prompt = f"""
Process this data into a list format:
{prev_task.result.raw}
# # get the loop data convert it to list using calling Agent class chat
# loop_prompt = f"""
# Process this data into a list format:
# {prev_task.result.raw}
Return a JSON object with an 'items' array containing the items to process.
"""
if current_task.async_execution:
loop_data_str = await loop_manager.achat(
prompt=loop_prompt,
output_json=LoopItems
)
else:
loop_data_str = loop_manager.chat(
prompt=loop_prompt,
output_json=LoopItems
)
# Return a JSON object with an 'items' array containing the items to process.
# """
# if current_task.async_execution:
# loop_data_str = await loop_manager.achat(
# prompt=loop_prompt,
# output_json=LoopItems
# )
# else:
# loop_data_str = loop_manager.chat(
# prompt=loop_prompt,
# output_json=LoopItems
# )
try:
# The response will already be parsed into LoopItems model
loop_data[f"loop_{current_task.name}"] = {
"items": loop_data_str.items,
"index": 0,
"remaining": len(loop_data_str.items)
}
context += f"\nCurrent loop item: {loop_data_str.items[0]}"
except Exception as e:
display_error(f"Failed to process loop data: {e}")
context += f"\n{prev_name}: {prev_task.result.raw}"
# try:
# # The response will already be parsed into LoopItems model
# loop_data[f"loop_{current_task.name}"] = {
# "items": loop_data_str.items,
# "index": 0,
# "remaining": len(loop_data_str.items)
# }
# context += f"\nCurrent loop item: {loop_data_str.items[0]}"
# except Exception as e:
# display_error(f"Failed to process loop data: {e}")
# context += f"\n{prev_name}: {prev_task.result.raw}"
context += f"\n{prev_name}: {prev_task.result.raw}"
else:
context += f"\n{prev_name}: {prev_task.result.raw}"
@@ -111,6 +121,11 @@ Return a JSON object with an 'items' array containing the items to process.
yield task_id
visited_tasks.add(task_id)
# Reset completed task to "not started" so it can run again
if self.tasks[task_id].status == "completed":
logging.debug(f"Task {task_id} was completed, resetting to 'not started' for next iteration.")
self.tasks[task_id].status = "not started"
# Handle loop progression
if current_task.task_type == "loop":
loop_key = f"loop_{current_task.name}"
@@ -130,22 +145,32 @@ Return a JSON object with an 'items' array containing the items to process.
# Determine next task based on result
next_task = None
if current_task.result:
if current_task and current_task.result:
if current_task.task_type in ["decision", "loop"]:
result = current_task.result.raw.lower()
# MINIMAL CHANGE: use pydantic decision if present
decision_str = current_task.result.raw.lower()
if current_task.result.pydantic and hasattr(current_task.result.pydantic, "decision"):
decision_str = current_task.result.pydantic.decision.lower()
# Check conditions
for condition, tasks in current_task.condition.items():
if condition.lower() in result and tasks:
next_task_name = tasks[0]
if condition.lower() == decision_str:
# Handle both list and direct string values
task_value = tasks[0] if isinstance(tasks, list) else tasks
if not task_value or task_value == "exit":
logging.info("Workflow exit condition met, ending workflow")
current_task = None
break
next_task_name = task_value
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
# For loops, allow revisiting the same task
if next_task and next_task.id == current_task.id:
visited_tasks.discard(current_task.id)
break
if not next_task and current_task.next_tasks:
next_task_name = current_task.next_tasks[0]
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
if not next_task and current_task and current_task.next_tasks:
next_task_name = current_task.next_tasks[0]
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
current_task = next_task
if not current_task:
@@ -285,6 +310,7 @@ Provide a JSON with the structure:
def workflow(self):
"""Synchronous version of workflow method"""
current_iter = 0 # Track how many times we've looped
# Build workflow relationships first
for task in self.tasks.values():
if task.next_tasks:
@@ -303,12 +329,86 @@ Provide a JSON with the structure:
if not start_task:
start_task = list(self.tasks.values())[0]
logging.info("No start task marked, using first task")
# If loop type and no input_file, default to tasks.csv
if start_task and start_task.task_type == "loop" and not start_task.input_file:
start_task.input_file = "tasks.csv"
# --- If loop + input_file, read file & create tasks
if start_task and start_task.task_type == "loop" and getattr(start_task, "input_file", None):
try:
file_ext = os.path.splitext(start_task.input_file)[1].lower()
new_tasks = []
if file_ext == ".csv":
# existing CSV reading logic
with open(start_task.input_file, "r", encoding="utf-8") as f:
# Try as simple CSV first
reader = csv.reader(f)
previous_task = None
for i, row in enumerate(reader):
if row: # Skip empty rows
task_desc = row[0] # Take first column
row_task = Task(
description=task_desc, # Keep full row as description
agent=start_task.agent,
name=task_desc, # Use first column as name
is_start=(i == 0),
task_type="task",
condition={
"complete": ["next"],
"retry": ["current"]
}
)
self.tasks[row_task.id] = row_task
new_tasks.append(row_task)
if previous_task:
previous_task.next_tasks = [row_task.name]
previous_task.condition["complete"] = [row_task.name]
previous_task = row_task
else:
# If not CSV, read lines
with open(start_task.input_file, "r", encoding="utf-8") as f:
lines = f.read().splitlines()
previous_task = None
for i, line in enumerate(lines):
row_task = Task(
description=line.strip(),
agent=start_task.agent,
name=line.strip(),
is_start=(i == 0),
task_type="task",
condition={
"complete": ["next"],
"retry": ["current"]
}
)
self.tasks[row_task.id] = row_task
new_tasks.append(row_task)
if previous_task:
previous_task.next_tasks = [row_task.name]
previous_task.condition["complete"] = [row_task.name]
previous_task = row_task
if new_tasks:
start_task = new_tasks[0]
logging.info(f"Created {len(new_tasks)} tasks from: {start_task.input_file}")
except Exception as e:
logging.error(f"Failed to read file tasks: {e}")
# end of the new block
current_task = start_task
visited_tasks = set()
loop_data = {} # Store loop-specific data
while current_task and current_task.id not in visited_tasks:
while current_task:
current_iter += 1
if current_iter > self.max_iter:
logging.info(f"Max iteration limit {self.max_iter} reached, ending workflow.")
break
task_id = current_task.id
logging.info(f"Executing workflow task: {current_task.name if current_task.name else task_id}")
@@ -322,40 +422,41 @@ Provide a JSON with the structure:
if prev_task and prev_task.result:
# Handle loop data
if current_task.task_type == "loop":
# create a loop manager Agent
loop_manager = Agent(
name="Loop Manager",
role="Loop data processor",
goal="Process loop data and convert it to list format",
backstory="Expert at handling loop data and converting it to proper format",
llm=self.manager_llm,
verbose=self.verbose,
markdown=True
)
# # create a loop manager Agent
# loop_manager = Agent(
# name="Loop Manager",
# role="Loop data processor",
# goal="Process loop data and convert it to list format",
# backstory="Expert at handling loop data and converting it to proper format",
# llm=self.manager_llm,
# verbose=self.verbose,
# markdown=True
# )
# get the loop data convert it to list using calling Agent class chat
loop_prompt = f"""
Process this data into a list format:
{prev_task.result.raw}
# # get the loop data convert it to list using calling Agent class chat
# loop_prompt = f"""
# Process this data into a list format:
# {prev_task.result.raw}
Return a JSON object with an 'items' array containing the items to process.
"""
loop_data_str = loop_manager.chat(
prompt=loop_prompt,
output_json=LoopItems
)
# Return a JSON object with an 'items' array containing the items to process.
# """
# loop_data_str = loop_manager.chat(
# prompt=loop_prompt,
# output_json=LoopItems
# )
try:
# The response will already be parsed into LoopItems model
loop_data[f"loop_{current_task.name}"] = {
"items": loop_data_str.items,
"index": 0,
"remaining": len(loop_data_str.items)
}
context += f"\nCurrent loop item: {loop_data_str.items[0]}"
except Exception as e:
display_error(f"Failed to process loop data: {e}")
context += f"\n{prev_name}: {prev_task.result.raw}"
# try:
# # The response will already be parsed into LoopItems model
# loop_data[f"loop_{current_task.name}"] = {
# "items": loop_data_str.items,
# "index": 0,
# "remaining": len(loop_data_str.items)
# }
# context += f"\nCurrent loop item: {loop_data_str.items[0]}"
# except Exception as e:
# display_error(f"Failed to process loop data: {e}")
# context += f"\n{prev_name}: {prev_task.result.raw}"
context += f"\n{prev_name}: {prev_task.result.raw}"
else:
context += f"\n{prev_name}: {prev_task.result.raw}"
@@ -372,6 +473,11 @@ Return a JSON object with an 'items' array containing the items to process.
yield task_id
visited_tasks.add(task_id)
# Reset completed task to "not started" so it can run again: Only for workflow because some tasks may be revisited
if self.tasks[task_id].status == "completed":
logging.debug(f"Task {task_id} was completed, resetting to 'not started' for next iteration.")
self.tasks[task_id].status = "not started"
# Handle loop progression
if current_task.task_type == "loop":
loop_key = f"loop_{current_task.name}"
@@ -391,22 +497,32 @@ Return a JSON object with an 'items' array containing the items to process.
# Determine next task based on result
next_task = None
if current_task.result:
if current_task and current_task.result:
if current_task.task_type in ["decision", "loop"]:
result = current_task.result.raw.lower()
# MINIMAL CHANGE: use pydantic decision if present
decision_str = current_task.result.raw.lower()
if current_task.result.pydantic and hasattr(current_task.result.pydantic, "decision"):
decision_str = current_task.result.pydantic.decision.lower()
# Check conditions
for condition, tasks in current_task.condition.items():
if condition.lower() in result and tasks:
next_task_name = tasks[0]
if condition.lower() == decision_str:
# Handle both list and direct string values
task_value = tasks[0] if isinstance(tasks, list) else tasks
if not task_value or task_value == "exit":
logging.info("Workflow exit condition met, ending workflow")
current_task = None
break
next_task_name = task_value
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
# For loops, allow revisiting the same task
if next_task and next_task.id == current_task.id:
visited_tasks.discard(current_task.id)
break
if not next_task and current_task.next_tasks:
next_task_name = current_task.next_tasks[0]
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
if not next_task and current_task and current_task.next_tasks:
next_task_name = current_task.next_tasks[0]
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
current_task = next_task
if not current_task:
+231 -11
View File
@@ -1,20 +1,25 @@
import logging
import asyncio
from typing import List, Optional, Dict, Any, Type, Callable, Union, Coroutine
from typing import List, Optional, Dict, Any, Type, Callable, Union, Coroutine, Literal
from pydantic import BaseModel
from ..main import TaskOutput
from ..agent.agent import Agent
import uuid
import os
import time
# Set up logger
logger = logging.getLogger(__name__)
class Task:
def __init__(
self,
description: str,
expected_output: str,
expected_output: Optional[str] = None,
agent: Optional[Agent] = None,
name: Optional[str] = None,
tools: Optional[List[Any]] = None,
context: Optional[List["Task"]] = None,
context: Optional[List[Union[str, List, 'Task']]] = None,
async_execution: Optional[bool] = False,
config: Optional[Dict[str, Any]] = None,
output_file: Optional[str] = None,
@@ -30,12 +35,16 @@ class Task:
task_type: str = "task",
condition: Optional[Dict[str, List[str]]] = None,
is_start: bool = False,
loop_state: Optional[Dict[str, Union[str, int]]] = None
loop_state: Optional[Dict[str, Union[str, int]]] = None,
memory=None,
quality_check=True,
input_file: Optional[str] = None
):
self.input_file = input_file
self.id = str(uuid.uuid4()) if id is None else str(id)
self.name = name
self.description = description
self.expected_output = expected_output
self.expected_output = expected_output if expected_output is not None else "Complete the task successfully"
self.agent = agent
self.tools = tools if tools else []
self.context = context if context else []
@@ -54,6 +63,21 @@ class Task:
self.condition = condition if condition else {}
self.is_start = is_start
self.loop_state = loop_state if loop_state else {}
self.memory = memory
self.quality_check = quality_check
# Set logger level based on config verbose level
verbose = self.config.get("verbose", 0)
if verbose >= 5:
logger.setLevel(logging.INFO)
else:
logger.setLevel(logging.WARNING)
# Also set third-party loggers to WARNING
logging.getLogger('chromadb').setLevel(logging.WARNING)
logging.getLogger('openai').setLevel(logging.WARNING)
logging.getLogger('httpx').setLevel(logging.WARNING)
logging.getLogger('httpcore').setLevel(logging.WARNING)
if self.output_json and self.output_pydantic:
raise ValueError("Only one output type can be defined")
@@ -61,14 +85,210 @@ class Task:
# Track previous tasks based on next_tasks relationships
self.previous_tasks = []
# If task_type="decision" and output_pydantic is not set
if self.task_type == "decision" and not self.output_pydantic:
from pydantic import BaseModel
from typing import Literal
# Gather condition keys for the "decision" field
condition_keys = list(self.condition.keys())
if not condition_keys:
# Fall back to placeholders if nothing is specified
condition_keys = ["next_task", "exit"]
# Create a dynamic literal type from condition keys
DecisionLiteral = Literal.__getitem__(tuple(condition_keys))
class DecisionModel(BaseModel):
response: str
decision: DecisionLiteral
self.output_pydantic = DecisionModel
# If task_type="loop" and output_pydantic is not set
if self.task_type == "loop" and not self.output_pydantic:
from pydantic import BaseModel
from typing import Literal
# Gather condition keys for the "decision" field
condition_keys = list(self.condition.keys())
if not condition_keys:
# Fall back to placeholders if nothing is specified
condition_keys = ["next_item", "exit"]
# Create a dynamic literal type
LoopLiteral = Literal.__getitem__(tuple(condition_keys))
class LoopModel(BaseModel):
response: str
decision: LoopLiteral
loop_id: str # Additional field for loop
self.output_pydantic = LoopModel
def __str__(self):
return f"Task(name='{self.name if self.name else 'None'}', description='{self.description}', agent='{self.agent.name if self.agent else 'None'}', status='{self.status}')"
def initialize_memory(self):
"""Initialize memory if config exists but memory doesn't"""
if not self.memory and self.config.get('memory_config'):
try:
from ..memory.memory import Memory
logger.info(f"Task {self.id}: Initializing memory from config: {self.config['memory_config']}")
self.memory = Memory(config=self.config['memory_config'])
logger.info(f"Task {self.id}: Memory initialized successfully")
# Verify database was created
if os.path.exists(self.config['memory_config']['storage']['path']):
logger.info(f"Task {self.id}: Memory database exists after initialization")
else:
logger.error(f"Task {self.id}: Failed to create memory database!")
return self.memory
except Exception as e:
logger.error(f"Task {self.id}: Failed to initialize memory: {e}")
logger.exception(e)
return None
def store_in_memory(self, content: str, agent_name: str = None, task_id: str = None):
"""Store content in memory with metadata"""
if self.memory:
try:
logger.info(f"Task {self.id}: Storing content in memory...")
self.memory.store_long_term(
text=content,
metadata={
"agent_name": agent_name or "Agent",
"task_id": task_id or self.id,
"timestamp": time.time()
}
)
logger.info(f"Task {self.id}: Content stored in memory")
except Exception as e:
logger.error(f"Task {self.id}: Failed to store content in memory: {e}")
logger.exception(e)
async def execute_callback(self, task_output: TaskOutput) -> None:
"""Execute the callback function, handling both sync and async callbacks"""
"""Execute callback and store quality metrics if enabled"""
logger.info(f"Task {self.id}: execute_callback called")
logger.info(f"Quality check enabled: {self.quality_check}")
# Initialize memory if not already initialized
if not self.memory:
self.memory = self.initialize_memory()
logger.info(f"Memory object exists: {self.memory is not None}")
if self.memory:
logger.info(f"Memory config: {self.memory.cfg}")
# Store task output in memory
try:
logger.info(f"Task {self.id}: Storing task output in memory...")
self.store_in_memory(
content=task_output.raw,
agent_name=self.agent.name if self.agent else "Agent",
task_id=self.id
)
logger.info(f"Task {self.id}: Task output stored in memory")
except Exception as e:
logger.error(f"Task {self.id}: Failed to store task output in memory: {e}")
logger.exception(e)
logger.info(f"Task output: {task_output.raw[:100]}...")
if self.quality_check and self.memory:
try:
logger.info(f"Task {self.id}: Starting memory operations")
logger.info(f"Task {self.id}: Calculating quality metrics for output: {task_output.raw[:100]}...")
# Get quality metrics from LLM
metrics = self.memory.calculate_quality_metrics(
task_output.raw,
self.expected_output
)
logger.info(f"Task {self.id}: Quality metrics calculated: {metrics}")
quality_score = metrics.get("accuracy", 0.0)
logger.info(f"Task {self.id}: Quality score: {quality_score}")
# Store in both short and long-term memory with higher threshold
logger.info(f"Task {self.id}: Finalizing task output in memory...")
self.memory.finalize_task_output(
content=task_output.raw,
agent_name=self.agent.name if self.agent else "Agent",
quality_score=quality_score,
threshold=0.7, # Only high quality outputs in long-term memory
metrics=metrics,
task_id=self.id
)
logger.info(f"Task {self.id}: Finalized task output in memory")
# Store quality metrics separately
logger.info(f"Task {self.id}: Storing quality metrics...")
self.memory.store_quality(
text=task_output.raw,
quality_score=quality_score,
task_id=self.id,
metrics=metrics
)
# Store in both short and long-term memory with higher threshold
self.memory.finalize_task_output(
content=task_output.raw,
agent_name=self.agent.name if self.agent else "Agent",
quality_score=quality_score,
threshold=0.7 # Only high quality outputs in long-term memory
)
# Build context for next tasks
if self.next_tasks:
logger.info(f"Task {self.id}: Building context for next tasks...")
context = self.memory.build_context_for_task(
task_descr=task_output.raw,
max_items=5
)
logger.info(f"Task {self.id}: Built context for next tasks: {len(context)} items")
logger.info(f"Task {self.id}: Memory operations complete")
except Exception as e:
logger.error(f"Task {self.id}: Failed to process memory operations: {e}")
logger.exception(e) # Print full stack trace
# Continue execution even if memory operations fail
# Execute original callback
if self.callback:
if asyncio.iscoroutinefunction(self.callback):
await self.callback(task_output)
else:
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, self.callback, task_output)
try:
if asyncio.iscoroutinefunction(self.callback):
await self.callback(task_output)
else:
self.callback(task_output)
except Exception as e:
logger.error(f"Task {self.id}: Failed to execute callback: {e}")
logger.exception(e)
task_prompt = f"""
You need to do the following task: {self.description}.
Expected Output: {self.expected_output}.
"""
if self.context:
context_results = [] # Use list to avoid duplicates
for context_item in self.context:
if isinstance(context_item, str):
context_results.append(f"Input Content:\n{context_item}")
elif isinstance(context_item, list):
context_results.append(f"Input Content: {' '.join(str(x) for x in context_item)}")
elif hasattr(context_item, 'result'): # Task object
if context_item.result:
context_results.append(
f"Result of previous task {context_item.name if context_item.name else context_item.description}:\n{context_item.result.raw}"
)
else:
context_results.append(
f"Previous task {context_item.name if context_item.name else context_item.description} has no result yet."
)
# Join unique context results
unique_contexts = list(dict.fromkeys(context_results)) # Remove duplicates
task_prompt += f"""
Context:
{' '.join(unique_contexts)}
"""
+149
View File
@@ -0,0 +1,149 @@
# PraisonAI Tools Guide
Welcome to the PraisonAI Tools directory! This guide will help you understand how our tools work and how to create new ones, whether you're a beginner or an experienced programmer.
## What is a Tool?
A tool is a piece of code that helps our AI agents perform specific tasks. Think of tools as special abilities that we give to our agents. For example:
- An internet search tool lets agents search the web
- A stock market tool lets agents check stock prices
- A weather tool lets agents check the weather
## Creating New Tools: The Two Approaches
### 1. Function-Based Approach (Simple Tools)
Best for simple tools that do one specific thing. Like a calculator that just adds numbers.
**When to use:**
- Tool does one simple task
- Doesn't need to remember information between uses
- Doesn't need to share information with other tools
- Quick, one-time operations
**Example:**
```python
def internet_search(query: str):
# Search the internet and return results
return search_results
```
**Usage:**
```python
from praisonaiagents.tools import internet_search
results = internet_search("AI news")
```
### 2. Class-Based Approach (Complex Tools)
Best for tools that do multiple related things or need to remember information. Like a smart calculator that remembers your previous calculations and can do many different math operations.
**When to use:**
- Tool has multiple related functions
- Needs to remember or share information
- Needs to manage resources efficiently
- Has complex setup requirements
**Example:**
```python
class StockTools:
def get_stock_price(self, symbol):
# Get current stock price
return price
def get_stock_info(self, symbol):
# Get detailed stock information
return info
```
**Usage:**
```python
from praisonaiagents.tools import get_stock_price, get_stock_info
price = get_stock_price("AAPL")
info = get_stock_info("AAPL")
```
## How to Choose Your Approach
Ask yourself these questions:
1. **Is your tool doing one simple thing?**
- Yes → Use Function-Based Approach
- No → Consider Class-Based Approach
2. **Does your tool need to remember information?**
- Yes → Use Class-Based Approach
- No → Use Function-Based Approach
3. **Are your tool's operations related to each other?**
- Yes → Use Class-Based Approach
- No → Use Function-Based Approach
4. **Does your tool need to manage resources efficiently?**
- Yes → Use Class-Based Approach
- No → Use Function-Based Approach
## Real-World Examples
### Internet Search Tool (Function-Based)
- Does one thing: searches the internet
- Doesn't need to remember previous searches
- Each search is independent
- Simple input/output operation
### Stock Market Tool (Class-Based)
- Does multiple things: check prices, get company info, get historical data
- Remembers stock information to avoid repeated downloads
- Operations are related (all about stocks)
- Manages connections efficiently
## Getting Started
1. **Choose Your Approach** based on the guidelines above
2. **Create Your Tool File**:
- Name it descriptively (e.g., `weather_tools.py`)
- Place it in the `praisonaiagents/tools` directory
3. **Write Your Tool**:
- Add clear documentation
- Include type hints for better understanding
- Handle errors gracefully
4. **Test Your Tool**:
- Make sure it works as expected
- Test error cases
- Check performance
## Best Practices
1. **Documentation**:
- Explain what your tool does
- Provide examples
- List any requirements
2. **Error Handling**:
- Always handle possible errors
- Return helpful error messages
- Don't let your tool crash
3. **Performance**:
- Keep it efficient
- Don't waste resources
- Cache when helpful
4. **User-Friendly**:
- Make it easy to use
- Use clear function/method names
- Keep it simple
## Need Help?
- Check existing tools for examples
- Ask in our community
- Read the documentation
- Don't hesitate to ask questions!
Remember: The goal is to make tools that are easy to use and maintain. Choose the approach that makes the most sense for your specific tool's needs.
+176 -2
View File
@@ -1,4 +1,178 @@
"""Tools package for PraisonAI Agents"""
from .tools import Tools
from importlib import import_module
from typing import Any
__all__ = ['Tools']
# Map of function names to their module and class (if any)
TOOL_MAPPINGS = {
# Direct functions
'internet_search': ('.duckduckgo_tools', None),
'duckduckgo': ('.duckduckgo_tools', None),
# arXiv Tools
'search_arxiv': ('.arxiv_tools', None),
'get_arxiv_paper': ('.arxiv_tools', None),
'get_papers_by_author': ('.arxiv_tools', None),
'get_papers_by_category': ('.arxiv_tools', None),
'arxiv_tools': ('.arxiv_tools', None),
# Wikipedia Tools
'wiki_search': ('.wikipedia_tools', None),
'wiki_summary': ('.wikipedia_tools', None),
'wiki_page': ('.wikipedia_tools', None),
'wiki_random': ('.wikipedia_tools', None),
'wiki_language': ('.wikipedia_tools', None),
'wikipedia_tools': ('.wikipedia_tools', None),
# Newspaper Tools
'get_article': ('.newspaper_tools', None),
'get_news_sources': ('.newspaper_tools', None),
'get_articles_from_source': ('.newspaper_tools', None),
'get_trending_topics': ('.newspaper_tools', None),
'newspaper_tools': ('.newspaper_tools', None),
# Spider Tools
'scrape_page': ('.spider_tools', None),
'extract_links': ('.spider_tools', None),
'crawl': ('.spider_tools', None),
'extract_text': ('.spider_tools', None),
'spider_tools': ('.spider_tools', None),
# DuckDB Tools
'query': ('.duckdb_tools', None),
'create_table': ('.duckdb_tools', None),
'load_data': ('.duckdb_tools', None),
'export_data': ('.duckdb_tools', None),
'get_table_info': ('.duckdb_tools', None),
'analyze_data': ('.duckdb_tools', None),
'duckdb_tools': ('.duckdb_tools', None),
# Shell Tools
'execute_command': ('.shell_tools', None),
'list_processes': ('.shell_tools', None),
'kill_process': ('.shell_tools', None),
'get_system_info': ('.shell_tools', None),
'shell_tools': ('.shell_tools', None),
# Calculator Tools
'evaluate': ('.calculator_tools', None),
'solve_equation': ('.calculator_tools', None),
'convert_units': ('.calculator_tools', None),
'calculate_statistics': ('.calculator_tools', None),
'calculate_financial': ('.calculator_tools', None),
'calculator_tools': ('.calculator_tools', None),
# Class methods from YFinance
'get_stock_price': ('.yfinance_tools', 'YFinanceTools'),
'get_stock_info': ('.yfinance_tools', 'YFinanceTools'),
'get_historical_data': ('.yfinance_tools', 'YFinanceTools'),
'yfinance': ('.yfinance_tools', 'YFinanceTools'),
# File Tools
'read_file': ('.file_tools', 'FileTools'),
'write_file': ('.file_tools', 'FileTools'),
'list_files': ('.file_tools', 'FileTools'),
'get_file_info': ('.file_tools', 'FileTools'),
'copy_file': ('.file_tools', 'FileTools'),
'move_file': ('.file_tools', 'FileTools'),
'delete_file': ('.file_tools', 'FileTools'),
'file_tools': ('.file_tools', 'FileTools'),
# CSV Tools
'read_csv': ('.csv_tools', 'CSVTools'),
'write_csv': ('.csv_tools', 'CSVTools'),
'merge_csv': ('.csv_tools', 'CSVTools'),
'analyze_csv': ('.csv_tools', 'CSVTools'),
'split_csv': ('.csv_tools', 'CSVTools'),
'csv_tools': ('.csv_tools', 'CSVTools'),
# JSON Tools
'read_json': ('.json_tools', 'JSONTools'),
'write_json': ('.json_tools', 'JSONTools'),
'merge_json': ('.json_tools', 'JSONTools'),
'validate_json': ('.json_tools', 'JSONTools'),
'analyze_json': ('.json_tools', 'JSONTools'),
'transform_json': ('.json_tools', 'JSONTools'),
'json_tools': ('.json_tools', 'JSONTools'),
# Excel Tools
'read_excel': ('.excel_tools', 'ExcelTools'),
'write_excel': ('.excel_tools', 'ExcelTools'),
'merge_excel': ('.excel_tools', 'ExcelTools'),
'create_chart': ('.excel_tools', 'ExcelTools'),
'add_chart_to_sheet': ('.excel_tools', 'ExcelTools'),
'excel_tools': ('.excel_tools', 'ExcelTools'),
# XML Tools
'read_xml': ('.xml_tools', 'XMLTools'),
'write_xml': ('.xml_tools', 'XMLTools'),
'transform_xml': ('.xml_tools', 'XMLTools'),
'validate_xml': ('.xml_tools', 'XMLTools'),
'xml_to_dict': ('.xml_tools', 'XMLTools'),
'dict_to_xml': ('.xml_tools', 'XMLTools'),
'xpath_query': ('.xml_tools', 'XMLTools'),
'xml_tools': ('.xml_tools', 'XMLTools'),
# YAML Tools
'read_yaml': ('.yaml_tools', 'YAMLTools'),
'write_yaml': ('.yaml_tools', 'YAMLTools'),
'merge_yaml': ('.yaml_tools', 'YAMLTools'),
'validate_yaml': ('.yaml_tools', 'YAMLTools'),
'analyze_yaml': ('.yaml_tools', 'YAMLTools'),
'transform_yaml': ('.yaml_tools', 'YAMLTools'),
'yaml_tools': ('.yaml_tools', 'YAMLTools'),
# Python Tools
'execute_code': ('.python_tools', 'PythonTools'),
'analyze_code': ('.python_tools', 'PythonTools'),
'format_code': ('.python_tools', 'PythonTools'),
'lint_code': ('.python_tools', 'PythonTools'),
'disassemble_code': ('.python_tools', 'PythonTools'),
'python_tools': ('.python_tools', 'PythonTools'),
# Pandas Tools
'filter_data': ('.pandas_tools', 'PandasTools'),
'get_summary': ('.pandas_tools', 'PandasTools'),
'group_by': ('.pandas_tools', 'PandasTools'),
'pivot_table': ('.pandas_tools', 'PandasTools'),
'pandas_tools': ('.pandas_tools', 'PandasTools'),
}
_instances = {} # Cache for class instances
def __getattr__(name: str) -> Any:
"""Smart lazy loading of tools with class method support."""
if name not in TOOL_MAPPINGS:
raise AttributeError(f"module '{__package__}' has no attribute '{name}'")
module_path, class_name = TOOL_MAPPINGS[name]
if class_name is None:
# Direct function import
module = import_module(module_path, __package__)
if name in [
'duckduckgo', 'internet_search',
'search_arxiv', 'get_arxiv_paper', 'get_papers_by_author', 'get_papers_by_category',
'wiki_search', 'wiki_summary', 'wiki_page', 'wiki_random', 'wiki_language',
'get_article', 'get_news_sources', 'get_articles_from_source', 'get_trending_topics',
'scrape_page', 'extract_links', 'crawl', 'extract_text',
'query', 'create_table', 'load_data', 'export_data', 'get_table_info', 'analyze_data',
'execute_command', 'list_processes', 'kill_process', 'get_system_info',
'evaluate', 'solve_equation', 'convert_units', 'calculate_statistics', 'calculate_financial'
]:
return getattr(module, name)
if name in ['file_tools', 'pandas_tools', 'wikipedia_tools',
'newspaper_tools', 'arxiv_tools', 'spider_tools', 'duckdb_tools', 'csv_tools', 'json_tools', 'excel_tools', 'xml_tools', 'yaml_tools', 'calculator_tools', 'python_tools', 'shell_tools']:
return module # Returns the callable module
return getattr(module, name)
else:
# Class method import
if class_name not in _instances:
module = import_module(module_path, __package__)
class_ = getattr(module, class_name)
_instances[class_name] = class_()
# Get the method and bind it to the instance
method = getattr(_instances[class_name], name)
return method
__all__ = list(TOOL_MAPPINGS.keys())
+292
View File
@@ -0,0 +1,292 @@
"""Tools for searching and retrieving papers from arXiv.
Usage:
from praisonaiagents.tools import arxiv_tools
papers = arxiv_tools.search("quantum computing")
paper = arxiv_tools.get_paper("2401.00123")
or
from praisonaiagents.tools import search_arxiv, get_arxiv_paper
papers = search_arxiv("quantum computing")
"""
import logging
from typing import List, Dict, Union, Optional, Any
from importlib import util
import json
# Map sort criteria to arxiv.SortCriterion
SORT_CRITERIA = {
"relevance": "Relevance",
"lastupdateddate": "LastUpdatedDate",
"submitteddate": "SubmittedDate"
}
# Map sort order to arxiv.SortOrder
SORT_ORDER = {
"ascending": "Ascending",
"descending": "Descending"
}
class ArxivTools:
"""Tools for searching and retrieving papers from arXiv."""
def __init__(self):
"""Initialize ArxivTools and check for arxiv package."""
self._check_arxiv()
def _check_arxiv(self):
"""Check if arxiv package is installed."""
if util.find_spec("arxiv") is None:
raise ImportError("arxiv package is not available. Please install it using: pip install arxiv")
global arxiv
import arxiv
def search(
self,
query: str,
max_results: int = 10,
sort_by: str = "relevance",
sort_order: str = "descending",
include_fields: Optional[List[str]] = None
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
"""
Search arXiv for papers matching the query.
Args:
query: Search query (e.g., "quantum computing", "author:Einstein")
max_results: Maximum number of results to return
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
sort_order: Sort order ("ascending" or "descending")
include_fields: List of fields to include in results. If None, includes all.
Available fields: ["title", "authors", "summary", "comment", "journal_ref",
"doi", "primary_category", "categories", "links"]
Returns:
List[Dict] or Dict: List of papers or error dict
"""
try:
import arxiv
# Configure search client
client = arxiv.Client()
# Map sort criteria
sort_by_enum = getattr(arxiv.SortCriterion, SORT_CRITERIA[sort_by.lower()])
sort_order_enum = getattr(arxiv.SortOrder, SORT_ORDER[sort_order.lower()])
# Build search query
search = arxiv.Search(
query=query,
max_results=max_results,
sort_by=sort_by_enum,
sort_order=sort_order_enum
)
# Execute search
results = []
for result in client.results(search):
# Convert to dict with selected fields
paper = self._result_to_dict(result, include_fields)
results.append(paper)
return results
except Exception as e:
error_msg = f"Error searching arXiv: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_paper(
self,
paper_id: str,
include_fields: Optional[List[str]] = None
) -> Union[Dict[str, Any], Dict[str, str]]:
"""
Get details of a specific paper by its arXiv ID.
Args:
paper_id: arXiv paper ID (e.g., "2401.00123")
include_fields: List of fields to include in results. If None, includes all.
Available fields: ["title", "authors", "summary", "comment", "journal_ref",
"doi", "primary_category", "categories", "links"]
Returns:
Dict: Paper details or error dict
"""
try:
import arxiv
# Configure client
client = arxiv.Client()
# Get paper by ID
search = arxiv.Search(id_list=[paper_id])
results = list(client.results(search))
if not results:
return {"error": f"Paper with ID {paper_id} not found"}
# Convert to dict with selected fields
paper = self._result_to_dict(results[0], include_fields)
return paper
except Exception as e:
error_msg = f"Error getting paper {paper_id}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_papers_by_author(
self,
author: str,
max_results: int = 10,
sort_by: str = "submittedDate",
sort_order: str = "descending",
include_fields: Optional[List[str]] = None
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
"""
Get papers by a specific author.
Args:
author: Author name (e.g., "Einstein")
max_results: Maximum number of results to return
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
sort_order: Sort order ("ascending" or "descending")
include_fields: List of fields to include in results
Returns:
List[Dict] or Dict: List of papers or error dict
"""
query = f'au:"{author}"'
return self.search(query, max_results, sort_by, sort_order, include_fields)
def get_papers_by_category(
self,
category: str,
max_results: int = 10,
sort_by: str = "submittedDate",
sort_order: str = "descending",
include_fields: Optional[List[str]] = None
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
"""
Get papers from a specific category.
Args:
category: arXiv category (e.g., "cs.AI", "physics.gen-ph")
max_results: Maximum number of results to return
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
sort_order: Sort order ("ascending" or "descending")
include_fields: List of fields to include in results
Returns:
List[Dict] or Dict: List of papers or error dict
"""
query = f'cat:{category}'
return self.search(query, max_results, sort_by, sort_order, include_fields)
def _result_to_dict(
self,
result: Any,
include_fields: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Convert arxiv.Result to dictionary with selected fields."""
# Default fields to include
if include_fields is None:
include_fields = [
"title", "authors", "summary", "comment", "journal_ref",
"doi", "primary_category", "categories", "links"
]
# Build paper dict with selected fields
paper = {}
# Always include these basic fields
paper["arxiv_id"] = result.entry_id.split("/")[-1]
paper["updated"] = result.updated.isoformat() if result.updated else None
paper["published"] = result.published.isoformat() if result.published else None
# Add requested fields
if "title" in include_fields:
paper["title"] = result.title
if "authors" in include_fields:
paper["authors"] = [str(author) for author in result.authors]
if "summary" in include_fields:
paper["summary"] = result.summary
if "comment" in include_fields:
paper["comment"] = result.comment
if "journal_ref" in include_fields:
paper["journal_ref"] = result.journal_ref
if "doi" in include_fields:
paper["doi"] = result.doi
if "primary_category" in include_fields:
paper["primary_category"] = result.primary_category
if "categories" in include_fields:
paper["categories"] = result.categories
if "links" in include_fields:
paper["pdf_url"] = result.pdf_url
paper["abstract_url"] = f"https://arxiv.org/abs/{paper['arxiv_id']}"
return paper
# Create instance for direct function access
_arxiv_tools = ArxivTools()
search_arxiv = _arxiv_tools.search
get_arxiv_paper = _arxiv_tools.get_paper
get_papers_by_author = _arxiv_tools.get_papers_by_author
get_papers_by_category = _arxiv_tools.get_papers_by_category
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("ArxivTools Demonstration")
print("==================================================\n")
# 1. Search for papers
print("1. Searching for Papers")
print("------------------------------")
query = "quantum computing"
papers = search_arxiv(query, max_results=3)
print(f"Papers about {query}:")
if isinstance(papers, list):
print(json.dumps(papers, indent=2))
else:
print(papers) # Show error
print()
# 2. Get specific paper
print("2. Getting Specific Paper")
print("------------------------------")
if isinstance(papers, list) and papers:
paper_id = papers[0]["arxiv_id"]
paper = get_arxiv_paper(paper_id)
print(f"Paper {paper_id}:")
if "error" not in paper:
print(json.dumps(paper, indent=2))
else:
print(paper) # Show error
print()
# 3. Get papers by author
print("3. Getting Papers by Author")
print("------------------------------")
author = "Yoshua Bengio"
author_papers = get_papers_by_author(author, max_results=3)
print(f"Papers by {author}:")
if isinstance(author_papers, list):
print(json.dumps(author_papers, indent=2))
else:
print(author_papers) # Show error
print()
# 4. Get papers by category
print("4. Getting Papers by Category")
print("------------------------------")
category = "cs.AI"
category_papers = get_papers_by_category(category, max_results=3)
print(f"Papers in category {category}:")
if isinstance(category_papers, list):
print(json.dumps(category_papers, indent=2))
else:
print(category_papers) # Show error
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
@@ -0,0 +1,278 @@
"""Tools for performing calculations.
Usage:
from praisonaiagents.tools import calculator_tools
result = calculator_tools.evaluate("2 + 2 * 3")
or
from praisonaiagents.tools import evaluate, solve_equation, convert_units
result = evaluate("2 + 2 * 3")
"""
import logging
from typing import List, Dict, Optional, Any
from importlib import util
import math
class CalculatorTools:
"""Tools for performing calculations."""
def __init__(self):
"""Initialize CalculatorTools."""
self._check_dependencies()
def _check_dependencies(self):
"""Check if required packages are installed."""
missing = []
for package in ['sympy', 'pint']:
if util.find_spec(package) is None:
missing.append(package)
if missing:
raise ImportError(
f"Required packages not available. Please install: {', '.join(missing)}\n"
f"Run: pip install {' '.join(missing)}"
)
def evaluate(
self,
expression: str,
variables: Optional[Dict[str, float]] = None,
precision: int = 10
) -> Dict[str, Any]:
"""Evaluate a mathematical expression."""
try:
if util.find_spec('sympy') is None:
error_msg = "sympy package is not available. Please install it using: pip install sympy"
logging.error(error_msg)
return {"error": error_msg}
# Import sympy only when needed
import sympy
# Replace common mathematical functions
expression = expression.lower()
replacements = {
'sin(': 'math.sin(',
'cos(': 'math.cos(',
'tan(': 'math.tan(',
'sqrt(': 'math.sqrt(',
'log(': 'math.log(',
'exp(': 'math.exp(',
'pi': str(math.pi),
'e': str(math.e)
}
for old, new in replacements.items():
expression = expression.replace(old, new)
# Create safe namespace
safe_dict = {
'math': math,
'__builtins__': None
}
if variables:
safe_dict.update(variables)
# Evaluate expression
result = eval(expression, safe_dict)
# Round to specified precision
if isinstance(result, (int, float)):
return {"result": round(float(result), precision)}
return {"error": "Invalid expression"}
except Exception as e:
error_msg = f"Error evaluating expression: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def solve_equation(
self,
equation: str,
variable: str = 'x'
) -> Dict[str, Any]:
"""Solve a mathematical equation."""
try:
if util.find_spec('sympy') is None:
error_msg = "sympy package is not available. Please install it using: pip install sympy"
logging.error(error_msg)
return {"error": error_msg}
# Import sympy only when needed
import sympy
# Parse equation
if '=' in equation:
left, right = equation.split('=')
equation = f"({left}) - ({right})"
# Convert to SymPy expression
x = sympy.Symbol(variable)
expr = sympy.sympify(equation)
# Solve equation
solutions = sympy.solve(expr, x)
# Convert complex solutions to real if possible
real_solutions = []
for sol in solutions:
if sol.is_real:
real_solutions.append(float(sol))
return {"solutions": real_solutions} if real_solutions else {"error": "No real solutions found"}
except Exception as e:
error_msg = f"Error solving equation: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def convert_units(
self,
value: float,
from_unit: str,
to_unit: str
) -> Dict[str, Any]:
"""Convert between different units."""
try:
if util.find_spec('pint') is None:
error_msg = "pint package is not available. Please install it using: pip install pint"
logging.error(error_msg)
return {"error": error_msg}
# Import pint only when needed
import pint
# Create quantity with source unit
ureg = pint.UnitRegistry()
quantity = value * ureg(from_unit)
# Convert to target unit
result = quantity.to(to_unit)
return {"result": float(result.magnitude)}
except Exception as e:
error_msg = f"Error converting units: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def calculate_statistics(
self,
numbers: List[float]
) -> Dict[str, Any]:
"""Calculate basic statistics for a list of numbers."""
try:
if not numbers:
return {"error": "No numbers provided"}
n = len(numbers)
mean = sum(numbers) / n
# Calculate variance and std dev
squared_diff_sum = sum((x - mean) ** 2 for x in numbers)
variance = squared_diff_sum / (n - 1) if n > 1 else 0
std_dev = math.sqrt(variance)
# Calculate median
sorted_nums = sorted(numbers)
if n % 2 == 0:
median = (sorted_nums[n//2 - 1] + sorted_nums[n//2]) / 2
else:
median = sorted_nums[n//2]
return {
"mean": mean,
"median": median,
"std_dev": std_dev,
"variance": variance,
"min": min(numbers),
"max": max(numbers),
"range": max(numbers) - min(numbers),
"count": n
}
except Exception as e:
error_msg = f"Error calculating statistics: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def calculate_financial(
self,
principal: float,
rate: float,
time: float,
frequency: str = 'yearly'
) -> Dict[str, Any]:
"""Calculate financial metrics like compound interest."""
try:
# Convert rate to decimal
rate = rate / 100
# Get compounding frequency
freq_map = {
'yearly': 1,
'semi-annual': 2,
'quarterly': 4,
'monthly': 12,
'daily': 365
}
n = freq_map.get(frequency.lower(), 1)
# Calculate compound interest
amount = principal * (1 + rate/n)**(n*time)
interest = amount - principal
# Calculate simple interest
simple_interest = principal * rate * time
return {
"final_amount": amount,
"compound_interest": interest,
"simple_interest": simple_interest,
"difference": interest - simple_interest
}
except Exception as e:
error_msg = f"Error calculating financial metrics: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
# Create instance for direct function access
_calculator_tools = CalculatorTools()
evaluate = _calculator_tools.evaluate
solve_equation = _calculator_tools.solve_equation
convert_units = _calculator_tools.convert_units
calculate_statistics = _calculator_tools.calculate_statistics
calculate_financial = _calculator_tools.calculate_financial
if __name__ == "__main__":
print("\n==================================================")
print("CalculatorTools Demonstration")
print("==================================================\n")
print("1. Basic Arithmetic")
print("------------------------------")
print("2 + 2 =", evaluate("2 + 2")["result"])
print("5 * 6 =", evaluate("5 * 6")["result"])
print("10 / 3 =", evaluate("10 / 3")["result"])
print()
print("2. Complex Expressions")
print("------------------------------")
print("(2 + 3) * 4 =", evaluate("(2 + 3) * 4")["result"])
print("2^3 + 4 =", evaluate("2^3 + 4")["result"])
print("sqrt(16) + 2 =", evaluate("sqrt(16) + 2")["result"])
print()
print("3. Solving Equations")
print("------------------------------")
result = solve_equation("x^2 - 5*x + 6 = 0")
print("x^2 - 5*x + 6 = 0")
print("Solutions:", result["solutions"])
print()
print("4. Unit Conversions")
print("------------------------------")
print("100 meters to kilometers:", convert_units(100, "meters", "kilometers")["result"])
print("1 hour to minutes:", convert_units(1, "hours", "minutes")["result"])
print("32 fahrenheit to celsius:", convert_units(32, "fahrenheit", "celsius")["result"])
print()
print("==================================================")
print("Demonstration Complete")
print("==================================================")
+266
View File
@@ -0,0 +1,266 @@
"""Tools for working with CSV files.
Usage:
from praisonaiagents.tools import csv_tools
df = csv_tools.read_csv("data.csv")
or
from praisonaiagents.tools import read_csv, write_csv, merge_csv
df = read_csv("data.csv")
"""
import logging
from typing import List, Dict, Union, Optional, Any, TYPE_CHECKING
from importlib import util
import json
import csv
from pathlib import Path
if TYPE_CHECKING:
import pandas as pd
class CSVTools:
"""Tools for working with CSV files."""
def __init__(self):
"""Initialize CSVTools."""
pass
def _get_pandas(self) -> Optional['pd']:
"""Get pandas module, installing if needed"""
if util.find_spec('pandas') is None:
error_msg = "pandas package is not available. Please install it using: pip install pandas"
logging.error(error_msg)
return None
import pandas as pd
return pd
def read_csv(
self,
filepath: str,
encoding: str = 'utf-8',
delimiter: str = ',',
header: Union[int, List[int], None] = 0,
usecols: Optional[List[str]] = None,
dtype: Optional[Dict[str, str]] = None,
parse_dates: Optional[List[str]] = None,
na_values: Optional[List[str]] = None,
nrows: Optional[int] = None
) -> List[Dict[str, Any]]:
"""Read a CSV file with advanced options.
Args:
filepath: Path to CSV file
encoding: File encoding
delimiter: Column delimiter
header: Row number(s) to use as column names
usecols: Columns to read
dtype: Dict of column dtypes
parse_dates: List of columns to parse as dates
na_values: Additional strings to recognize as NA/NaN
nrows: Number of rows to read
Returns:
List of row dicts
"""
try:
pd = self._get_pandas()
if pd is None:
return {"error": "pandas package not available"}
df = pd.read_csv(
filepath,
encoding=encoding,
delimiter=delimiter,
header=header,
usecols=usecols,
dtype=dtype,
parse_dates=parse_dates,
na_values=na_values,
nrows=nrows
)
return df.to_dict('records')
except Exception as e:
error_msg = f"Error reading CSV file {filepath}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def write_csv(
self,
filepath: str,
data: List[Dict[str, Any]],
encoding: str = 'utf-8',
delimiter: str = ',',
index: bool = False,
header: bool = True,
float_format: Optional[str] = None,
date_format: Optional[str] = None,
mode: str = 'w'
) -> bool:
"""Write data to a CSV file.
Args:
filepath: Path to CSV file
data: List of row dicts to write
encoding: File encoding
delimiter: Column delimiter
index: Whether to write row indices
header: Whether to write column headers
float_format: Format string for float values
date_format: Format string for date values
mode: Write mode ('w' for write, 'a' for append)
Returns:
bool: Success status
"""
try:
pd = self._get_pandas()
if pd is None:
return False
df = pd.DataFrame(data)
df.to_csv(
filepath,
encoding=encoding,
sep=delimiter,
index=index,
header=header,
float_format=float_format,
date_format=date_format,
mode=mode
)
return True
except Exception as e:
error_msg = f"Error writing CSV file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def merge_csv(
self,
files: List[str],
output_file: str,
how: str = 'inner',
on: Optional[Union[str, List[str]]] = None,
suffixes: Optional[tuple] = None
) -> bool:
"""Merge multiple CSV files.
Args:
files: List of CSV files to merge
output_file: Output file path
how: Merge method ('inner', 'outer', 'left', 'right')
on: Column(s) to merge on
suffixes: Suffixes for overlapping columns
Returns:
bool: Success status
"""
try:
if len(files) < 2:
error_msg = "At least two files are required for merging"
logging.error(error_msg)
return False
pd = self._get_pandas()
if pd is None:
return False
# Read first file
result = pd.read_csv(files[0])
# Merge with remaining files
for file in files[1:]:
df = pd.read_csv(file)
result = pd.merge(
result,
df,
how=how,
on=on,
suffixes=suffixes or ('_1', '_2')
)
# Write merged result
result.to_csv(output_file, index=False)
return True
except Exception as e:
error_msg = f"Error merging CSV files: {str(e)}"
logging.error(error_msg)
return False
# Create instance for direct function access
_csv_tools = CSVTools()
read_csv = _csv_tools.read_csv
write_csv = _csv_tools.write_csv
merge_csv = _csv_tools.merge_csv
if __name__ == "__main__":
print("\n==================================================")
print("CSVTools Demonstration")
print("==================================================\n")
# Create a temporary file for testing
import tempfile
import os
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp:
temp_file = temp.name
print("1. Writing CSV File")
print("------------------------------")
data = [
{"name": "Alice", "age": 25, "city": "New York"},
{"name": "Bob", "age": 30, "city": "San Francisco"},
{"name": "Charlie", "age": 35, "city": "Chicago"}
]
result = write_csv(temp_file, data)
print(f"Data written successfully: {result}")
print()
print("2. Reading CSV File")
print("------------------------------")
read_data = read_csv(temp_file)
print("Contents of CSV file:")
for row in read_data:
print(row)
print()
print("3. Merging CSV Files")
print("------------------------------")
# Create a second file for merging
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp2:
temp_file2 = temp2.name
data2 = [
{"name": "Alice", "salary": 75000},
{"name": "Bob", "salary": 85000},
{"name": "Charlie", "salary": 95000}
]
write_csv(temp_file2, data2)
# Merge files
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp3:
temp_file3 = temp3.name
result = merge_csv(
[temp_file, temp_file2],
temp_file3,
how='inner',
on='name'
)
print(f"Files merged successfully: {result}")
if result:
merged_data = read_csv(temp_file3)
print("\nMerged contents:")
for row in merged_data:
print(row)
print()
# Clean up temporary files
os.unlink(temp_file)
os.unlink(temp_file2)
os.unlink(temp_file3)
print("==================================================")
print("Demonstration Complete")
print("==================================================\n")
@@ -0,0 +1,268 @@
"""Tools for working with DuckDB databases.
Usage:
from praisonaiagents.tools import duckdb_tools
df = duckdb_tools.query("SELECT * FROM my_table")
or
from praisonaiagents.tools import query_db, create_table, load_data
df = query_db("SELECT * FROM my_table")
"""
import logging
from typing import List, Dict, Any, Optional, Union, TYPE_CHECKING
from importlib import util
import json
if TYPE_CHECKING:
import duckdb
import pandas as pd
class DuckDBTools:
"""Tools for working with DuckDB databases."""
def __init__(self, database: str = ':memory:'):
"""Initialize DuckDBTools.
Args:
database: Path to database file or ':memory:' for in-memory database
"""
self.database = database
self._conn = None
def _get_duckdb(self) -> Optional['duckdb']:
"""Get duckdb module, installing if needed"""
if util.find_spec('duckdb') is None:
error_msg = "duckdb package is not available. Please install it using: pip install duckdb"
logging.error(error_msg)
return None
import duckdb
return duckdb
def _get_pandas(self) -> Optional['pd']:
"""Get pandas module, installing if needed"""
if util.find_spec('pandas') is None:
error_msg = "pandas package is not available. Please install it using: pip install pandas"
logging.error(error_msg)
return None
import pandas as pd
return pd
def _get_connection(self) -> Optional['duckdb.DuckDBPyConnection']:
"""Get or create database connection"""
if self._conn is None:
duckdb = self._get_duckdb()
if duckdb is None:
return None
try:
self._conn = duckdb.connect(self.database)
except Exception as e:
error_msg = f"Error connecting to database {self.database}: {str(e)}"
logging.error(error_msg)
return None
return self._conn
def execute_query(
self,
query: str,
params: Optional[Union[tuple, dict]] = None,
return_df: bool = True
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
"""Execute a SQL query.
Args:
query: SQL query to execute
params: Query parameters
return_df: If True, return results as DataFrame records
Returns:
Query results as list of dicts, or error dict
"""
try:
conn = self._get_connection()
if conn is None:
return {"error": "Could not connect to database"}
if params:
result = conn.execute(query, params)
else:
result = conn.execute(query)
if return_df:
pd = self._get_pandas()
if pd is None:
return {"error": "pandas package not available"}
df = result.df()
return df.to_dict('records')
else:
return [dict(row) for row in result.fetchall()]
except Exception as e:
error_msg = f"Error executing query: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def load_csv(
self,
table_name: str,
filepath: str,
schema: Optional[Dict[str, str]] = None,
if_exists: str = 'replace'
) -> bool:
"""Load a CSV file into a table.
Args:
table_name: Name of table to create
filepath: Path to CSV file
schema: Optional column definitions
if_exists: What to do if table exists ('fail', 'replace', 'append')
Returns:
bool: Success status
"""
try:
conn = self._get_connection()
if conn is None:
return False
# Check if table exists
exists = conn.execute(f"""
SELECT name FROM sqlite_master
WHERE type='table' AND name='{table_name}'
""").fetchone() is not None
if exists:
if if_exists == 'fail':
raise ValueError(f"Table {table_name} already exists")
elif if_exists == 'replace':
conn.execute(f"DROP TABLE IF EXISTS {table_name}")
elif if_exists != 'append':
raise ValueError("if_exists must be 'fail', 'replace', or 'append'")
# Create table if needed
if not exists or if_exists == 'replace':
if schema:
# Create table with schema
columns = ', '.join(f"{k} {v}" for k, v in schema.items())
conn.execute(f"CREATE TABLE {table_name} ({columns})")
else:
# Infer schema from CSV
conn.execute(f"""
CREATE TABLE {table_name} AS
SELECT * FROM read_csv_auto('{filepath}')
WHERE 1=0
""")
# Load data
conn.execute(f"""
INSERT INTO {table_name}
SELECT * FROM read_csv_auto('{filepath}')
""")
return True
except Exception as e:
error_msg = f"Error loading CSV file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def export_csv(
self,
query: str,
filepath: str,
params: Optional[Union[tuple, dict]] = None
) -> bool:
"""Export query results to CSV.
Args:
query: SQL query to execute
filepath: Output file path
params: Optional query parameters
Returns:
bool: Success status
"""
try:
# Execute query and get results as DataFrame
results = self.execute_query(query, params)
if isinstance(results, dict) and 'error' in results:
return False
pd = self._get_pandas()
if pd is None:
return False
# Convert to DataFrame and save
df = pd.DataFrame(results)
df.to_csv(filepath, index=False)
return True
except Exception as e:
error_msg = f"Error exporting to CSV file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def close(self):
"""Close database connection."""
if self._conn:
self._conn.close()
self._conn = None
# Create instance for direct function access
_duckdb_tools = DuckDBTools()
execute_query = _duckdb_tools.execute_query
load_csv = _duckdb_tools.load_csv
export_csv = _duckdb_tools.export_csv
if __name__ == "__main__":
print("\n==================================================")
print("DuckDBTools Demonstration")
print("==================================================\n")
# Create a temporary file for testing
import tempfile
import os
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp:
temp_file = temp.name
# Create sample data
with open(temp_file, 'w') as f:
f.write("name,age,city\n")
f.write("Alice,25,New York\n")
f.write("Bob,30,San Francisco\n")
f.write("Charlie,35,Chicago\n")
print("1. Loading CSV File")
print("------------------------------")
result = load_csv('users', temp_file)
print(f"CSV loaded successfully: {result}")
print()
print("2. Executing Query")
print("------------------------------")
query = "SELECT * FROM users WHERE age > 25"
results = execute_query(query)
print("Query results:")
for row in results:
print(row)
print()
print("3. Exporting Query Results")
print("------------------------------")
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp2:
temp_file2 = temp2.name
result = export_csv(query, temp_file2)
print(f"Results exported successfully: {result}")
if result:
print("\nExported file contents:")
with open(temp_file2) as f:
print(f.read())
# Clean up temporary files
os.unlink(temp_file)
os.unlink(temp_file2)
print("==================================================")
print("Demonstration Complete")
print("==================================================\n")
@@ -0,0 +1,52 @@
"""DuckDuckGo search functionality.
Usage:
from praisonaiagents.tools import internet_search
results = internet_search("AI news")
or
from praisonaiagents.tools import duckduckgo
results = duckduckgo("AI news")
"""
from typing import List, Dict
import logging
from importlib import util
def internet_search(query: str) -> List[Dict]:
"""Perform an internet search using DuckDuckGo."""
# Check if duckduckgo_search is installed
if util.find_spec("duckduckgo_search") is None:
error_msg = "DuckDuckGo search is not available. Please install duckduckgo_search package using: pip install duckduckgo_search"
logging.error(error_msg)
return [{"error": error_msg}]
try:
# Import only when needed
from duckduckgo_search import DDGS
results = []
ddgs = DDGS()
for result in ddgs.text(keywords=query, max_results=5):
results.append({
"title": result.get("title", ""),
"url": result.get("href", ""),
"snippet": result.get("body", "")
})
return results
except Exception as e:
error_msg = f"Error during DuckDuckGo search: {e}"
logging.error(error_msg)
return [{"error": error_msg}]
def duckduckgo(query: str) -> List[Dict]:
"""Alias for internet_search function."""
return internet_search(query)
if __name__ == "__main__":
# Example usage
results = internet_search("Python programming")
for result in results:
print(f"\nTitle: {result.get('title')}")
print(f"URL: {result.get('url')}")
print(f"Snippet: {result.get('snippet')}")
+310
View File
@@ -0,0 +1,310 @@
"""Tools for working with Excel files.
Usage:
from praisonaiagents.tools import excel_tools
df = excel_tools.read_excel("data.xlsx")
or
from praisonaiagents.tools import read_excel, write_excel, merge_excel
df = read_excel("data.xlsx")
"""
import logging
from typing import List, Dict, Union, Optional, Any, TYPE_CHECKING, Tuple
from importlib import util
import json
from pathlib import Path
import tempfile
import os
if TYPE_CHECKING:
import pandas as pd
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.chart import BarChart, LineChart, PieChart, Reference
from openpyxl.chart.label import DataLabelList
class ExcelTools:
"""Tools for working with Excel files."""
def __init__(self):
"""Initialize ExcelTools."""
pass
def _get_pandas(self) -> Optional['pd']:
"""Get pandas module, installing if needed"""
if util.find_spec('pandas') is None:
error_msg = "pandas package is not available. Please install it using: pip install pandas"
logging.error(error_msg)
return None
if util.find_spec('openpyxl') is None:
error_msg = "openpyxl package is not available. Please install it using: pip install openpyxl"
logging.error(error_msg)
return None
import pandas as pd
return pd
def read_excel(
self,
filepath: str,
sheet_name: Optional[Union[str, int, List[Union[str, int]]]] = 0,
header: Optional[int] = 0,
usecols: Optional[List[str]] = None,
skiprows: Optional[Union[int, List[int]]] = None,
na_values: Optional[List[str]] = None,
dtype: Optional[Dict[str, str]] = None
) -> Union[Dict[str, List[Dict[str, Any]]], List[Dict[str, Any]]]:
"""Read an Excel file with advanced options.
Args:
filepath: Path to Excel file
sheet_name: Sheet name(s) or index(es)
header: Row number(s) to use as column names
usecols: Columns to read
skiprows: Line numbers to skip
na_values: Additional strings to recognize as NA/NaN
dtype: Dict of column dtypes
Returns:
Dict of sheet names to data if multiple sheets, else list of row dicts
"""
try:
pd = self._get_pandas()
if pd is None:
return {"error": "Required packages not available"}
# Read Excel file
df = pd.read_excel(
filepath,
sheet_name=sheet_name,
header=header,
usecols=usecols,
skiprows=skiprows,
na_values=na_values,
dtype=dtype,
engine='openpyxl'
)
# Convert to dict format
if isinstance(df, dict):
return {
name: df[name].to_dict('records')
for name in df.keys()
}
else:
return df.to_dict('records')
except Exception as e:
error_msg = f"Error reading Excel file {filepath}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def write_excel(
self,
filepath: str,
data: Union[Dict[str, List[Dict[str, Any]]], List[Dict[str, Any]]],
sheet_name: Optional[str] = None,
index: bool = False,
header: bool = True,
mode: str = 'w'
) -> bool:
"""Write data to an Excel file.
Args:
filepath: Path to Excel file
data: Data to write (dict of sheet names to data or list of row dicts)
sheet_name: Sheet name if data is a list
index: Whether to write row indices
header: Whether to write column headers
mode: Write mode ('w' for write, 'a' for append)
Returns:
bool: Success status
"""
try:
pd = self._get_pandas()
if pd is None:
return False
# Convert data to DataFrame(s)
if isinstance(data, dict):
if mode == 'a' and os.path.exists(filepath):
book = pd.ExcelFile(filepath)
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
# Copy existing sheets
for sheet in book.sheet_names:
pd.read_excel(filepath, sheet_name=sheet).to_excel(
writer, sheet_name=sheet, index=index, header=header
)
# Add new sheets
for name, sheet_data in data.items():
df = pd.DataFrame(sheet_data)
df.to_excel(
writer, sheet_name=name, index=index, header=header
)
else:
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
for name, sheet_data in data.items():
df = pd.DataFrame(sheet_data)
df.to_excel(
writer, sheet_name=name, index=index, header=header
)
else:
if mode == 'a' and os.path.exists(filepath):
book = pd.ExcelFile(filepath)
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
# Copy existing sheets
for sheet in book.sheet_names:
pd.read_excel(filepath, sheet_name=sheet).to_excel(
writer, sheet_name=sheet, index=index, header=header
)
# Add new sheet
df = pd.DataFrame(data)
df.to_excel(
writer,
sheet_name=sheet_name or 'Sheet1',
index=index,
header=header
)
else:
df = pd.DataFrame(data)
df.to_excel(
filepath,
sheet_name=sheet_name or 'Sheet1',
index=index,
header=header,
engine='openpyxl'
)
return True
except Exception as e:
error_msg = f"Error writing Excel file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def merge_excel(
self,
files: List[str],
output_file: str,
how: str = 'inner',
on: Optional[Union[str, List[str]]] = None,
suffixes: Optional[Tuple[str, str]] = None
) -> bool:
"""Merge multiple Excel files.
Args:
files: List of Excel files to merge
output_file: Output file path
how: Merge method ('inner', 'outer', 'left', 'right')
on: Column(s) to merge on
suffixes: Suffixes for overlapping columns
Returns:
bool: Success status
"""
try:
if len(files) < 2:
error_msg = "At least two files are required for merging"
logging.error(error_msg)
return False
# Read first file
result = self.read_excel(files[0])
# Merge with remaining files
for file in files[1:]:
df = self.read_excel(file)
if isinstance(df, dict):
df = next(iter(df.values()))
pd = self._get_pandas()
if pd is None:
return False
result = pd.merge(
pd.DataFrame(result),
pd.DataFrame(df),
how=how,
on=on,
suffixes=suffixes or ('_1', '_2')
).to_dict('records')
# Write merged result
return self.write_excel(output_file, result)
except Exception as e:
error_msg = f"Error merging Excel files: {str(e)}"
logging.error(error_msg)
return False
# Create instance for direct function access
_excel_tools = ExcelTools()
read_excel = _excel_tools.read_excel
write_excel = _excel_tools.write_excel
merge_excel = _excel_tools.merge_excel
if __name__ == "__main__":
print("\n==================================================")
print("ExcelTools Demonstration")
print("==================================================\n")
# Create a temporary file for testing
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp:
temp_file = temp.name
print("1. Writing Excel File")
print("------------------------------")
data = [
{"name": "Alice", "age": 25, "city": "New York"},
{"name": "Bob", "age": 30, "city": "San Francisco"},
{"name": "Charlie", "age": 35, "city": "Chicago"}
]
result = write_excel(temp_file, data, "People")
print(f"Data written successfully: {result}")
print()
print("2. Reading Excel File")
print("------------------------------")
read_data = read_excel(temp_file)
print("Contents of Excel file:")
for row in read_data:
print(row)
print()
print("3. Merging Excel Files")
print("------------------------------")
# Create a second file for merging
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp2:
temp_file2 = temp2.name
data2 = [
{"name": "Alice", "salary": 75000},
{"name": "Bob", "salary": 85000},
{"name": "Charlie", "salary": 95000}
]
write_excel(temp_file2, data2, "Salaries")
# Merge files
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp3:
temp_file3 = temp3.name
result = merge_excel(
[temp_file, temp_file2],
temp_file3,
how='inner',
on='name'
)
print(f"Files merged successfully: {result}")
if result:
merged_data = read_excel(temp_file3)
print("\nMerged contents:")
for row in merged_data:
print(row)
print()
# Clean up temporary files
os.unlink(temp_file)
os.unlink(temp_file2)
os.unlink(temp_file3)
print("==================================================")
print("Demonstration Complete")
print("==================================================\n")
+274
View File
@@ -0,0 +1,274 @@
"""File handling tools for basic file operations.
Usage:
from praisonaiagents.tools import file_tools
content = file_tools.read_file("example.txt")
file_tools.write_file("output.txt", "Hello World")
or
from praisonaiagents.tools import read_file, write_file, list_files
content = read_file("example.txt")
"""
import os
import json
from typing import List, Dict, Union, Optional
from pathlib import Path
import shutil
import logging
class FileTools:
"""Tools for file operations including read, write, list, and information."""
@staticmethod
def read_file(filepath: str, encoding: str = 'utf-8') -> str:
"""
Read content from a file.
Args:
filepath: Path to the file
encoding: File encoding (default: utf-8)
Returns:
str: Content of the file
"""
try:
with open(filepath, 'r', encoding=encoding) as f:
return f.read()
except Exception as e:
error_msg = f"Error reading file {filepath}: {str(e)}"
logging.error(error_msg)
return error_msg
@staticmethod
def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> bool:
"""
Write content to a file.
Args:
filepath: Path to the file
content: Content to write
encoding: File encoding (default: utf-8)
Returns:
bool: True if successful, False otherwise
"""
try:
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, 'w', encoding=encoding) as f:
f.write(content)
return True
except Exception as e:
error_msg = f"Error writing to file {filepath}: {str(e)}"
logging.error(error_msg)
return False
@staticmethod
def list_files(directory: str, pattern: Optional[str] = None) -> List[Dict[str, Union[str, int]]]:
"""
List files in a directory with optional pattern matching.
Args:
directory: Directory path
pattern: Optional glob pattern (e.g., "*.txt")
Returns:
List[Dict]: List of file information dictionaries
"""
try:
path = Path(directory)
if pattern:
files = path.glob(pattern)
else:
files = path.iterdir()
result = []
for file in files:
if file.is_file():
stat = file.stat()
result.append({
'name': file.name,
'path': str(file),
'size': stat.st_size,
'modified': stat.st_mtime,
'created': stat.st_ctime
})
return result
except Exception as e:
error_msg = f"Error listing files in {directory}: {str(e)}"
logging.error(error_msg)
return [{'error': error_msg}]
@staticmethod
def get_file_info(filepath: str) -> Dict[str, Union[str, int]]:
"""
Get detailed information about a file.
Args:
filepath: Path to the file
Returns:
Dict: File information including size, dates, etc.
"""
try:
path = Path(filepath)
if not path.exists():
return {'error': f'File not found: {filepath}'}
stat = path.stat()
return {
'name': path.name,
'path': str(path),
'size': stat.st_size,
'modified': stat.st_mtime,
'created': stat.st_ctime,
'is_file': path.is_file(),
'is_dir': path.is_dir(),
'extension': path.suffix,
'parent': str(path.parent)
}
except Exception as e:
error_msg = f"Error getting file info for {filepath}: {str(e)}"
logging.error(error_msg)
return {'error': error_msg}
@staticmethod
def copy_file(src: str, dst: str) -> bool:
"""
Copy a file from source to destination.
Args:
src: Source file path
dst: Destination file path
Returns:
bool: True if successful, False otherwise
"""
try:
# Create destination directory if it doesn't exist
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(src, dst)
return True
except Exception as e:
error_msg = f"Error copying file from {src} to {dst}: {str(e)}"
logging.error(error_msg)
return False
@staticmethod
def move_file(src: str, dst: str) -> bool:
"""
Move a file from source to destination.
Args:
src: Source file path
dst: Destination file path
Returns:
bool: True if successful, False otherwise
"""
try:
# Create destination directory if it doesn't exist
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.move(src, dst)
return True
except Exception as e:
error_msg = f"Error moving file from {src} to {dst}: {str(e)}"
logging.error(error_msg)
return False
@staticmethod
def delete_file(filepath: str) -> bool:
"""
Delete a file.
Args:
filepath: Path to the file
Returns:
bool: True if successful, False otherwise
"""
try:
os.remove(filepath)
return True
except Exception as e:
error_msg = f"Error deleting file {filepath}: {str(e)}"
logging.error(error_msg)
return False
# Create instance for direct function access
_file_tools = FileTools()
read_file = _file_tools.read_file
write_file = _file_tools.write_file
list_files = _file_tools.list_files
get_file_info = _file_tools.get_file_info
copy_file = _file_tools.copy_file
move_file = _file_tools.move_file
delete_file = _file_tools.delete_file
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("FileTools Demonstration")
print("==================================================\n")
# Create a test directory
test_dir = os.path.join(os.getcwd(), "test_files")
os.makedirs(test_dir, exist_ok=True)
# Create test files
test_file = os.path.join(test_dir, "test_file.txt")
test_content = "Hello, this is a test file!"
print("1. Writing to file")
print("------------------------------")
success = write_file(test_file, test_content)
print(f"Write successful: {success}\n")
print("2. Reading from file")
print("------------------------------")
content = read_file(test_file)
print(f"Content: {content}\n")
print("3. File Information")
print("------------------------------")
info = get_file_info(test_file)
print(json.dumps(info, indent=2))
print()
print("4. Listing Files")
print("------------------------------")
files = list_files(test_dir, "*.txt")
for file in files:
print(f"Found: {file['name']} ({file['size']} bytes)")
print()
print("5. Copying File")
print("------------------------------")
copy_file_path = os.path.join(test_dir, "test_file_copy.txt")
copy_success = copy_file(test_file, copy_file_path)
print(f"Copy successful: {copy_success}\n")
print("6. Moving File")
print("------------------------------")
move_file_path = os.path.join(test_dir, "test_file_moved.txt")
move_success = move_file(copy_file_path, move_file_path)
print(f"Move successful: {move_success}\n")
print("7. Deleting Files")
print("------------------------------")
delete_success = delete_file(test_file)
print(f"Delete original successful: {delete_success}")
delete_success = delete_file(move_file_path)
print(f"Delete moved file successful: {delete_success}\n")
# Clean up test directory
try:
shutil.rmtree(test_dir)
print("Test directory cleaned up successfully")
except Exception as e:
print(f"Error cleaning up test directory: {str(e)}")
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
+515
View File
@@ -0,0 +1,515 @@
"""Tools for working with JSON files.
Usage:
from praisonaiagents.tools import json_tools
data = json_tools.read_json("data.json")
or
from praisonaiagents.tools import read_json, write_json, merge_json
data = read_json("data.json")
"""
import logging
from typing import List, Dict, Union, Optional, Any, Tuple
from importlib import util
import json
from datetime import datetime
class JSONTools:
"""Tools for working with JSON files."""
def __init__(self):
"""Initialize JSONTools."""
pass
def read_json(
self,
filepath: str,
encoding: str = 'utf-8',
validate_schema: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Read a JSON file with optional schema validation.
Args:
filepath: Path to JSON file
encoding: File encoding
validate_schema: Optional JSON schema for validation
Returns:
Dict with JSON data
"""
try:
# Read JSON file
with open(filepath, 'r', encoding=encoding) as f:
data = json.load(f)
# Validate against schema if provided
if validate_schema:
if util.find_spec('jsonschema') is None:
error_msg = "jsonschema package is not available. Please install it using: pip install jsonschema"
logging.error(error_msg)
return {"error": error_msg}
import jsonschema
try:
jsonschema.validate(instance=data, schema=validate_schema)
except jsonschema.exceptions.ValidationError as e:
error_msg = f"JSON validation failed: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
return data
except Exception as e:
error_msg = f"Error reading JSON file {filepath}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def write_json(
self,
data: Union[Dict[str, Any], List[Any]],
filepath: str,
encoding: str = 'utf-8',
indent: int = 2,
sort_keys: bool = False,
ensure_ascii: bool = False
) -> bool:
"""Write data to a JSON file.
Args:
data: Data to write
filepath: Output file path
encoding: File encoding
indent: Number of spaces for indentation
sort_keys: Sort dictionary keys
ensure_ascii: Escape non-ASCII characters
Returns:
True if successful, False otherwise
"""
try:
with open(filepath, 'w', encoding=encoding) as f:
json.dump(
data,
f,
indent=indent,
sort_keys=sort_keys,
ensure_ascii=ensure_ascii
)
return True
except Exception as e:
error_msg = f"Error writing JSON file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def merge_json(
self,
files: List[str],
output_file: str,
merge_arrays: bool = True,
overwrite_duplicates: bool = True
) -> bool:
"""Merge multiple JSON files.
Args:
files: List of JSON files to merge
output_file: Output file path
merge_arrays: Merge arrays instead of overwriting
overwrite_duplicates: Overwrite duplicate keys
Returns:
True if successful, False otherwise
"""
try:
if len(files) < 2:
raise ValueError("At least two files are required for merging")
# Read first file
result = self.read_json(files[0])
# Merge with remaining files
for file in files[1:]:
data = self.read_json(file)
result = self._deep_merge(
result,
data,
merge_arrays=merge_arrays,
overwrite_duplicates=overwrite_duplicates
)
# Write merged result
return self.write_json(result, output_file)
except Exception as e:
error_msg = f"Error merging JSON files: {str(e)}"
logging.error(error_msg)
return False
def _deep_merge(
self,
dict1: Dict[str, Any],
dict2: Dict[str, Any],
merge_arrays: bool = True,
overwrite_duplicates: bool = True
) -> Dict[str, Any]:
"""Deep merge two dictionaries.
Args:
dict1: First dictionary
dict2: Second dictionary
merge_arrays: Merge arrays instead of overwriting
overwrite_duplicates: Overwrite duplicate keys
Returns:
Merged dictionary
"""
result = dict1.copy()
for key, value in dict2.items():
if key in result:
if isinstance(result[key], dict) and isinstance(value, dict):
result[key] = self._deep_merge(
result[key],
value,
merge_arrays=merge_arrays,
overwrite_duplicates=overwrite_duplicates
)
elif isinstance(result[key], list) and isinstance(value, list):
if merge_arrays:
result[key].extend(value)
elif overwrite_duplicates:
result[key] = value
elif overwrite_duplicates:
result[key] = value
else:
result[key] = value
return result
def validate_json(
self,
data: Union[Dict[str, Any], str],
schema: Dict[str, Any]
) -> Tuple[bool, Optional[str]]:
"""Validate JSON data against a schema.
Args:
data: JSON data or filepath
schema: JSON schema for validation
Returns:
Tuple of (is_valid, error_message)
"""
try:
if util.find_spec('jsonschema') is None:
error_msg = "jsonschema package is not available. Please install it using: pip install jsonschema"
logging.error(error_msg)
return False, error_msg
import jsonschema
# Load data if filepath provided
if isinstance(data, str):
with open(data, 'r') as f:
data = json.load(f)
jsonschema.validate(instance=data, schema=schema)
return True, None
except jsonschema.exceptions.ValidationError as e:
error_msg = f"JSON validation failed: {str(e)}"
logging.error(error_msg)
return False, error_msg
except Exception as e:
error_msg = f"Error validating JSON: {str(e)}"
logging.error(error_msg)
return False, error_msg
def analyze_json(
self,
data: Union[Dict[str, Any], str],
max_depth: int = 10
) -> Dict[str, Any]:
"""Analyze JSON data structure.
Args:
data: JSON data or filepath
max_depth: Maximum depth to analyze
Returns:
Dict with analysis results
"""
try:
# Load data if filepath provided
if isinstance(data, str):
data = self.read_json(data)
def analyze_value(value: Any, depth: int = 0) -> Dict[str, Any]:
if depth >= max_depth:
return {'type': 'max_depth_reached'}
result = {'type': type(value).__name__}
if isinstance(value, dict):
result['size'] = len(value)
result['keys'] = list(value.keys())
if depth < max_depth - 1:
result['children'] = {
k: analyze_value(v, depth + 1)
for k, v in value.items()
}
elif isinstance(value, list):
result['length'] = len(value)
if value:
result['element_types'] = list(set(
type(x).__name__ for x in value
))
if depth < max_depth - 1:
result['sample_elements'] = [
analyze_value(x, depth + 1)
for x in value[:5]
]
elif isinstance(value, (int, float)):
result.update({
'value': value,
'is_integer': isinstance(value, int)
})
elif isinstance(value, str):
result.update({
'length': len(value),
'sample': value[:100] if len(value) > 100 else value
})
return result
return {
'analysis_time': datetime.now().isoformat(),
'structure': analyze_value(data)
}
except Exception as e:
error_msg = f"Error analyzing JSON: {str(e)}"
logging.error(error_msg)
return {}
def transform_json(
self,
data: Union[Dict[str, Any], str],
transformations: List[Dict[str, Any]]
) -> Dict[str, Any]:
"""Transform JSON data using a list of operations.
Args:
data: JSON data or filepath
transformations: List of transformation operations
Returns:
Transformed JSON data
"""
try:
# Load data if filepath provided
if isinstance(data, str):
data = self.read_json(data)
result = data.copy()
for transform in transformations:
op = transform.get('operation')
path = transform.get('path', '').split('.')
value = transform.get('value')
if op == 'set':
self._set_value(result, path, value)
elif op == 'delete':
self._delete_value(result, path)
elif op == 'rename':
old_path = path
new_path = value.split('.')
self._rename_key(result, old_path, new_path)
elif op == 'move':
old_path = path
new_path = value.split('.')
self._move_value(result, old_path, new_path)
return result
except Exception as e:
error_msg = f"Error transforming JSON: {str(e)}"
logging.error(error_msg)
return data
def _set_value(self, data: Dict[str, Any], path: List[str], value: Any):
"""Set a value at the specified path."""
current = data
for key in path[:-1]:
if key not in current:
current[key] = {}
current = current[key]
current[path[-1]] = value
def _delete_value(self, data: Dict[str, Any], path: List[str]):
"""Delete a value at the specified path."""
current = data
for key in path[:-1]:
if key not in current:
return
current = current[key]
if path[-1] in current:
del current[path[-1]]
def _rename_key(
self,
data: Dict[str, Any],
old_path: List[str],
new_path: List[str]
):
"""Rename a key at the specified path."""
value = self._get_value(data, old_path)
if value is not None:
self._delete_value(data, old_path)
self._set_value(data, new_path, value)
def _move_value(
self,
data: Dict[str, Any],
old_path: List[str],
new_path: List[str]
):
"""Move a value from one path to another."""
self._rename_key(data, old_path, new_path)
def _get_value(
self,
data: Dict[str, Any],
path: List[str]
) -> Optional[Any]:
"""Get a value at the specified path."""
current = data
for key in path:
if key not in current:
return None
current = current[key]
return current
# Create instance for direct function access
_json_tools = JSONTools()
read_json = _json_tools.read_json
write_json = _json_tools.write_json
merge_json = _json_tools.merge_json
validate_json = _json_tools.validate_json
analyze_json = _json_tools.analyze_json
transform_json = _json_tools.transform_json
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("JSONTools Demonstration")
print("==================================================\n")
# Sample data
data1 = {
'id': 1,
'name': 'Alice',
'scores': [95, 87, 92],
'details': {
'age': 25,
'city': 'New York'
}
}
data2 = {
'id': 2,
'name': 'Bob',
'scores': [88, 90, 85],
'details': {
'age': 30,
'country': 'USA'
}
}
# 1. Write JSON files
print("1. Writing JSON Files")
print("------------------------------")
success = write_json(data1, 'test1.json')
print(f"First file written: {success}")
success = write_json(data2, 'test2.json')
print(f"Second file written: {success}")
print()
# 2. Read JSON file
print("2. Reading JSON File")
print("------------------------------")
data = read_json('test1.json')
print("Contents of test1.json:")
print(json.dumps(data, indent=2))
print()
# 3. Merge JSON files
print("3. Merging JSON Files")
print("------------------------------")
success = merge_json(['test1.json', 'test2.json'], 'merged.json')
print(f"Files merged: {success}")
if success:
print("Merged contents:")
print(json.dumps(read_json('merged.json'), indent=2))
print()
# 4. Validate JSON
print("4. Validating JSON")
print("------------------------------")
schema = {
'type': 'object',
'properties': {
'id': {'type': 'integer'},
'name': {'type': 'string'},
'scores': {
'type': 'array',
'items': {'type': 'number'}
},
'details': {
'type': 'object',
'properties': {
'age': {'type': 'integer'},
'city': {'type': 'string'}
}
}
},
'required': ['id', 'name']
}
is_valid, error = validate_json(data1, schema)
print(f"Validation result: {is_valid}")
if error:
print(f"Validation error: {error}")
print()
# 5. Analyze JSON
print("5. Analyzing JSON")
print("------------------------------")
analysis = analyze_json(data1)
print("Analysis results:")
print(json.dumps(analysis, indent=2))
print()
# 6. Transform JSON
print("6. Transforming JSON")
print("------------------------------")
transformations = [
{
'operation': 'set',
'path': 'details.status',
'value': 'active'
},
{
'operation': 'rename',
'path': 'details.city',
'value': 'details.location'
}
]
transformed = transform_json(data1, transformations)
print("Transformed data:")
print(json.dumps(transformed, indent=2))
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
@@ -0,0 +1,354 @@
"""Newspaper tools for scraping and parsing news articles.
Usage:
from praisonaiagents.tools import newspaper_tools
article = newspaper_tools.get_article("https://example.com/article")
sources = newspaper_tools.get_news_sources("technology")
articles = newspaper_tools.get_articles_from_source("https://techcrunch.com")
or
from praisonaiagents.tools import get_article, get_news_sources
article = get_article("https://example.com/article")
"""
import logging
from typing import List, Dict, Union, Optional, Any
from importlib import util
import json
from urllib.parse import urlparse
# Predefined list of popular news sources
POPULAR_NEWS_SOURCES = {
'technology': [
'https://techcrunch.com',
'https://www.theverge.com',
'https://www.wired.com',
'https://www.engadget.com',
'https://arstechnica.com'
],
'business': [
'https://www.bloomberg.com',
'https://www.reuters.com',
'https://www.wsj.com',
'https://www.ft.com',
'https://www.cnbc.com'
],
'general': [
'https://www.nytimes.com',
'https://www.theguardian.com',
'https://www.washingtonpost.com',
'https://www.bbc.com',
'https://www.cnn.com'
],
'sports': [
'https://www.espn.com',
'https://sports.yahoo.com',
'https://www.cbssports.com',
'https://www.skysports.com',
'https://www.bleacherreport.com'
],
'entertainment': [
'https://variety.com',
'https://www.hollywoodreporter.com',
'https://www.ew.com',
'https://www.deadline.com',
'https://www.imdb.com/news'
],
'science': [
'https://www.scientificamerican.com',
'https://www.sciencedaily.com',
'https://www.newscientist.com',
'https://www.sciencemag.org',
'https://www.nature.com/news'
]
}
class NewspaperTools:
"""Tools for scraping and parsing news articles."""
def __init__(self):
"""Initialize NewspaperTools and check for newspaper package."""
self._check_newspaper()
def _check_newspaper(self):
"""Check if newspaper package is installed."""
if util.find_spec("newspaper") is None:
raise ImportError("newspaper3k package is not available. Please install it using: pip install newspaper3k")
global newspaper
import newspaper
def get_article(
self,
url: str,
language: str = 'en'
) -> Dict[str, Any]:
"""
Extract and parse a news article from a URL.
Args:
url: URL of the article
language: Language code (e.g., 'en' for English)
Returns:
Dict: Article information including title, text, authors, etc.
"""
try:
from newspaper import Article, Config
# Configure article download
config = Config()
config.browser_user_agent = 'Mozilla/5.0'
config.language = language
# Download and parse article
article = Article(url, config=config)
article.download()
article.parse()
# Try to extract additional information
try:
article.nlp()
except Exception as e:
logging.warning(f"NLP processing failed: {str(e)}")
# Build response
response = {
"url": url,
"title": article.title,
"text": article.text,
"authors": article.authors,
"publish_date": article.publish_date.isoformat() if article.publish_date else None,
"top_image": article.top_image,
"images": list(article.images),
"movies": list(article.movies),
"source_domain": urlparse(url).netloc,
}
# Add NLP results if available
if hasattr(article, 'keywords') and article.keywords:
response["keywords"] = article.keywords
if hasattr(article, 'summary') and article.summary:
response["summary"] = article.summary
return response
except Exception as e:
error_msg = f"Error extracting article from {url}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_news_sources(
self,
category: Optional[str] = None,
language: str = 'en',
country: Optional[str] = None
) -> Union[List[Dict[str, str]], Dict[str, str]]:
"""
Get a list of news sources, optionally filtered by category.
Args:
category: Category to filter by (e.g., 'technology', 'sports')
language: Language code
country: Country code
Returns:
List[Dict] or Dict: List of news sources or error dict
"""
try:
sources = []
# Get sources for the specified category or all categories
if category:
category = category.lower()
if category in POPULAR_NEWS_SOURCES:
urls = POPULAR_NEWS_SOURCES[category]
else:
urls = []
for cat_urls in POPULAR_NEWS_SOURCES.values():
urls.extend(cat_urls)
else:
urls = []
for cat_urls in POPULAR_NEWS_SOURCES.values():
urls.extend(cat_urls)
# Create source objects
for url in urls:
domain = urlparse(url).netloc
source = {
"url": url,
"domain": domain,
"name": domain.replace("www.", "").split(".")[0].title(),
"category": category if category else "general"
}
sources.append(source)
return sources
except Exception as e:
error_msg = f"Error getting news sources: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_articles_from_source(
self,
source_url: str,
limit: int = 10,
language: str = 'en'
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
"""
Get recent articles from a news source.
Args:
source_url: URL of the news source
limit: Maximum number of articles to return
language: Language code
Returns:
List[Dict] or Dict: List of articles or error dict
"""
try:
from newspaper import Source, Config
# Configure source scraping
config = Config()
config.browser_user_agent = 'Mozilla/5.0'
config.language = language
config.fetch_images = False # Speed up processing
# Build news source
source = Source(source_url, config=config)
source.build()
# Get articles
articles = []
for article_url in source.article_urls()[:limit]:
try:
article = self.get_article(article_url, language)
if "error" not in article:
articles.append(article)
except Exception as e:
logging.warning(f"Error processing article {article_url}: {str(e)}")
continue
if len(articles) >= limit:
break
return articles
except Exception as e:
error_msg = f"Error getting articles from {source_url}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_trending_topics(
self,
sources: Optional[List[str]] = None,
limit: int = 10,
language: str = 'en'
) -> Union[List[str], Dict[str, str]]:
"""
Get trending topics across news sources.
Args:
sources: List of source URLs to analyze
limit: Maximum number of trending topics to return
language: Language code
Returns:
List[str] or Dict: List of trending topics or error dict
"""
try:
from collections import Counter
# Use default sources if none provided
if not sources:
sources_data = self.get_news_sources(language=language)
if isinstance(sources_data, dict) and "error" in sources_data:
return sources_data
sources = [s["url"] for s in sources_data[:5]] # Use top 5 sources
# Collect keywords from articles
all_keywords = []
for source_url in sources:
try:
articles = self.get_articles_from_source(source_url, limit=5, language=language)
if isinstance(articles, list):
for article in articles:
if "keywords" in article:
all_keywords.extend(article["keywords"])
except Exception as e:
logging.warning(f"Error processing source {source_url}: {str(e)}")
continue
# Get most common keywords
trending = Counter(all_keywords).most_common(limit)
return [topic for topic, count in trending]
except Exception as e:
error_msg = f"Error getting trending topics: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
# Create instance for direct function access
_newspaper_tools = NewspaperTools()
get_article = _newspaper_tools.get_article
get_news_sources = _newspaper_tools.get_news_sources
get_articles_from_source = _newspaper_tools.get_articles_from_source
get_trending_topics = _newspaper_tools.get_trending_topics
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("NewspaperTools Demonstration")
print("==================================================\n")
# 1. Get news sources
print("1. Getting News Sources")
print("------------------------------")
tech_sources = get_news_sources("technology")
print("Technology news sources:")
if isinstance(tech_sources, list):
print(json.dumps(tech_sources[:3], indent=2)) # Show first 3 sources
else:
print(tech_sources) # Show error
print()
if isinstance(tech_sources, list) and tech_sources:
source_url = tech_sources[0]["url"]
# 2. Get articles from a source
print("2. Getting Articles from Source")
print("------------------------------")
articles = get_articles_from_source(source_url, limit=2)
print(f"Articles from {source_url}:")
if isinstance(articles, list):
for article in articles:
print(f"- {article['title']}")
if "summary" in article:
print(f" Summary: {article['summary'][:200]}...")
else:
print(articles) # Show error
print()
# 3. Get a single article
print("3. Getting Single Article")
print("------------------------------")
if isinstance(articles, list) and articles:
article_url = articles[0]["url"]
article = get_article(article_url)
if "error" not in article:
print(f"Article: {article['title']}")
if "summary" in article:
print(f"Summary: {article['summary'][:200]}...")
print(f"Authors: {', '.join(article['authors'])}")
print(f"Date: {article['publish_date']}")
else:
print(article) # Show error
print()
# 4. Get trending topics
print("4. Getting Trending Topics")
print("------------------------------")
topics = get_trending_topics([source_url], limit=5)
print("Trending topics:")
print(json.dumps(topics, indent=2))
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
@@ -0,0 +1,329 @@
"""Pandas tools for data manipulation and analysis.
Usage:
from praisonaiagents.tools import pandas_tools
df = pandas_tools.read_csv("data.csv")
df = pandas_tools.filter_data(df, "column > 5")
summary = pandas_tools.get_summary(df)
or
from praisonaiagents.tools import read_csv, filter_data, get_summary
df = read_csv("data.csv")
"""
import logging
from typing import List, Dict, Union, Optional, Any
from importlib import util
import json
import os
# Import pandas for type hints, but don't use it until we check it's installed
if util.find_spec("pandas") is not None:
import pandas as pd
import numpy as np
else:
# Create a placeholder for type hints
class pd:
DataFrame = None
def _convert_to_serializable(obj: Any) -> Any:
"""Convert numpy/pandas types to JSON serializable Python types."""
if isinstance(obj, (np.integer, np.floating)):
return obj.item()
elif isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, pd.Series):
return obj.to_list()
elif isinstance(obj, pd.DataFrame):
return obj.to_dict(orient='records')
return obj
class PandasTools:
"""Tools for data manipulation and analysis using pandas."""
def __init__(self):
"""Initialize PandasTools and check for pandas installation."""
self._check_pandas()
def _check_pandas(self):
"""Check if pandas is installed."""
if util.find_spec("pandas") is None:
raise ImportError("pandas is not available. Please install it using: pip install pandas")
global pd, np
import pandas as pd
import numpy as np
def read_csv(self, filepath: str, **kwargs) -> Union[pd.DataFrame, Dict[str, str]]:
"""
Read a CSV file into a pandas DataFrame.
Args:
filepath: Path to the CSV file
**kwargs: Additional arguments to pass to pd.read_csv()
Returns:
pd.DataFrame or Dict: DataFrame if successful, error dict if failed
"""
try:
return pd.read_csv(filepath, **kwargs)
except Exception as e:
error_msg = f"Error reading CSV file {filepath}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def read_excel(self, filepath: str, **kwargs) -> Union[pd.DataFrame, Dict[str, str]]:
"""
Read an Excel file into a pandas DataFrame.
Args:
filepath: Path to the Excel file
**kwargs: Additional arguments to pass to pd.read_excel()
Returns:
pd.DataFrame or Dict: DataFrame if successful, error dict if failed
"""
try:
return pd.read_excel(filepath, **kwargs)
except Exception as e:
error_msg = f"Error reading Excel file {filepath}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def write_csv(self, df: pd.DataFrame, filepath: str, **kwargs) -> bool:
"""
Write DataFrame to a CSV file.
Args:
df: DataFrame to write
filepath: Output file path
**kwargs: Additional arguments to pass to df.to_csv()
Returns:
bool: True if successful, False otherwise
"""
try:
os.makedirs(os.path.dirname(filepath), exist_ok=True)
df.to_csv(filepath, **kwargs)
return True
except Exception as e:
error_msg = f"Error writing CSV file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def write_excel(self, df: pd.DataFrame, filepath: str, **kwargs) -> bool:
"""
Write DataFrame to an Excel file.
Args:
df: DataFrame to write
filepath: Output file path
**kwargs: Additional arguments to pass to df.to_excel()
Returns:
bool: True if successful, False otherwise
"""
try:
os.makedirs(os.path.dirname(filepath), exist_ok=True)
df.to_excel(filepath, **kwargs)
return True
except Exception as e:
error_msg = f"Error writing Excel file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def filter_data(self, df: pd.DataFrame, query: str) -> Union[pd.DataFrame, Dict[str, str]]:
"""
Filter DataFrame using a query string.
Args:
df: Input DataFrame
query: Query string (e.g., "column > 5 and other_column == 'value'")
Returns:
pd.DataFrame or Dict: Filtered DataFrame if successful, error dict if failed
"""
try:
return df.query(query)
except Exception as e:
error_msg = f"Error filtering data with query '{query}': {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_summary(self, df: pd.DataFrame) -> Dict[str, Any]:
"""
Get a summary of the DataFrame including basic statistics and info.
Args:
df: Input DataFrame
Returns:
Dict: Summary statistics and information
"""
try:
if not isinstance(df, pd.DataFrame):
raise TypeError(f"Expected pandas DataFrame, got {type(df).__name__}")
numeric_summary = df.describe().to_dict()
# Convert numpy types to native Python types
for col in numeric_summary:
numeric_summary[col] = {k: _convert_to_serializable(v)
for k, v in numeric_summary[col].items()}
summary = {
"shape": list(df.shape),
"columns": list(df.columns),
"dtypes": df.dtypes.astype(str).to_dict(),
"null_counts": df.isnull().sum().to_dict(),
"numeric_summary": numeric_summary,
"memory_usage": int(df.memory_usage(deep=True).sum()),
}
return summary
except Exception as e:
error_msg = f"Error getting data summary: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def group_by(
self,
df: pd.DataFrame,
columns: Union[str, List[str]],
agg_dict: Dict[str, Union[str, List[str]]]
) -> Union[pd.DataFrame, Dict[str, str]]:
"""
Group DataFrame by columns and apply aggregation functions.
Args:
df: Input DataFrame
columns: Column(s) to group by
agg_dict: Dictionary of column:function pairs for aggregation
Returns:
pd.DataFrame or Dict: Grouped DataFrame if successful, error dict if failed
"""
try:
return df.groupby(columns).agg(agg_dict).reset_index()
except Exception as e:
error_msg = f"Error grouping data: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def pivot_table(
self,
df: pd.DataFrame,
index: Union[str, List[str]],
columns: Optional[Union[str, List[str]]] = None,
values: Optional[Union[str, List[str]]] = None,
aggfunc: str = "mean"
) -> Union[pd.DataFrame, Dict[str, str]]:
"""
Create a pivot table from DataFrame.
Args:
df: Input DataFrame
index: Column(s) to use as index
columns: Column(s) to use as columns
values: Column(s) to aggregate
aggfunc: Aggregation function to use
Returns:
pd.DataFrame or Dict: Pivot table if successful, error dict if failed
"""
try:
return pd.pivot_table(
df,
index=index,
columns=columns,
values=values,
aggfunc=aggfunc
).reset_index()
except Exception as e:
error_msg = f"Error creating pivot table: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
# Create instance for direct function access
_pandas_tools = PandasTools()
read_csv = _pandas_tools.read_csv
read_excel = _pandas_tools.read_excel
write_csv = _pandas_tools.write_csv
write_excel = _pandas_tools.write_excel
filter_data = _pandas_tools.filter_data
get_summary = _pandas_tools.get_summary
group_by = _pandas_tools.group_by
pivot_table = _pandas_tools.pivot_table
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("PandasTools Demonstration")
print("==================================================\n")
# Create a test directory
test_dir = os.path.join(os.getcwd(), "test_files")
os.makedirs(test_dir, exist_ok=True)
# Create a sample DataFrame
df = pd.DataFrame({
'name': ['John', 'Jane', 'Bob', 'Alice', 'Charlie'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 75000, 65000, 55000]
})
# 1. Write to CSV
print("1. Writing to CSV")
print("------------------------------")
csv_file = os.path.join(test_dir, "sample.csv")
success = write_csv(df, csv_file, index=False)
print(f"Write successful: {success}\n")
# 2. Read from CSV
print("2. Reading from CSV")
print("------------------------------")
df_read = read_csv(csv_file)
print("First few rows:")
print(df_read.head())
print()
# 3. Filter Data
print("3. Filtering Data")
print("------------------------------")
filtered_df = filter_data(df, "age > 30 and salary > 60000")
print("People over 30 with salary > 60000:")
print(filtered_df)
print()
# 4. Get Summary
print("4. Data Summary")
print("------------------------------")
summary = get_summary(df)
print(json.dumps(summary, indent=2))
print()
# 5. Group By
print("5. Group By")
print("------------------------------")
grouped = group_by(df, "city", {"salary": ["mean", "count"], "age": "mean"})
print("Statistics by city:")
print(grouped)
print()
# 6. Pivot Table
print("6. Pivot Table")
print("------------------------------")
pivoted = pivot_table(df, index="city", values=["salary", "age"])
print("Pivot table by city:")
print(pivoted)
print()
# Clean up test directory
try:
import shutil
shutil.rmtree(test_dir)
print("Test directory cleaned up successfully")
except Exception as e:
print(f"Error cleaning up test directory: {str(e)}")
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
@@ -0,0 +1,423 @@
"""Tools for Python code execution and analysis.
Usage:
from praisonaiagents.tools import python_tools
result = python_tools.execute_code("print('Hello, World!')")
or
from praisonaiagents.tools import execute_code, analyze_code, format_code
result = execute_code("print('Hello, World!')")
"""
import logging
from typing import Dict, List, Optional, Any
from importlib import util
import io
from contextlib import redirect_stdout, redirect_stderr
import traceback
class PythonTools:
"""Tools for Python code execution and analysis."""
def __init__(self):
"""Initialize PythonTools."""
self._check_dependencies()
def _check_dependencies(self):
"""Check if required packages are installed."""
missing = []
for package in ['black', 'pylint', 'autopep8']:
if util.find_spec(package) is None:
missing.append(package)
if missing:
raise ImportError(
f"Required packages not available. Please install: {', '.join(missing)}\n"
f"Run: pip install {' '.join(missing)}"
)
def execute_code(
self,
code: str,
globals_dict: Optional[Dict[str, Any]] = None,
locals_dict: Optional[Dict[str, Any]] = None,
timeout: int = 30,
max_output_size: int = 10000
) -> Dict[str, Any]:
"""Execute Python code safely."""
try:
# Set up execution environment
if globals_dict is None:
globals_dict = {'__builtins__': __builtins__}
if locals_dict is None:
locals_dict = {}
# Capture output
stdout_buffer = io.StringIO()
stderr_buffer = io.StringIO()
try:
# Compile code
compiled_code = compile(code, '<string>', 'exec')
# Execute with output capture
with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
exec(compiled_code, globals_dict, locals_dict)
# Get last expression value if any
import ast
tree = ast.parse(code)
if tree.body and isinstance(tree.body[-1], ast.Expr):
result = eval(
compile(ast.Expression(tree.body[-1].value), '<string>', 'eval'),
globals_dict,
locals_dict
)
else:
result = None
# Get output
stdout = stdout_buffer.getvalue()
stderr = stderr_buffer.getvalue()
# Truncate output if too large
if len(stdout) > max_output_size:
stdout = stdout[:max_output_size] + "...[truncated]"
if len(stderr) > max_output_size:
stderr = stderr[:max_output_size] + "...[truncated]"
return {
'result': result,
'stdout': stdout,
'stderr': stderr,
'success': True
}
except Exception as e:
error_msg = f"Error executing code: {str(e)}"
logging.error(error_msg)
return {
'result': None,
'stdout': stdout_buffer.getvalue(),
'stderr': error_msg,
'success': False
}
except Exception as e:
error_msg = f"Error executing code: {str(e)}"
logging.error(error_msg)
return {
'result': None,
'stdout': '',
'stderr': error_msg,
'success': False
}
def analyze_code(
self,
code: str
) -> Optional[Dict[str, Any]]:
"""Analyze Python code structure and quality.
Args:
code: Python code to analyze
Returns:
Dictionary with analysis results
"""
try:
# Import ast only when needed
import ast
# Parse code
tree = ast.parse(code)
# Analyze structure
analysis = {
'imports': [],
'functions': [],
'classes': [],
'variables': [],
'complexity': {
'lines': len(code.splitlines()),
'functions': 0,
'classes': 0,
'branches': 0
}
}
# Analyze nodes
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for name in node.names:
analysis['imports'].append(name.name)
elif isinstance(node, ast.ImportFrom):
module = node.module or ''
for name in node.names:
analysis['imports'].append(f"{module}.{name.name}")
elif isinstance(node, ast.FunctionDef):
analysis['functions'].append({
'name': node.name,
'args': [
arg.arg for arg in node.args.args
],
'decorators': [
ast.unparse(d) for d in node.decorator_list
]
})
analysis['complexity']['functions'] += 1
elif isinstance(node, ast.ClassDef):
analysis['classes'].append({
'name': node.name,
'bases': [
ast.unparse(base) for base in node.bases
],
'decorators': [
ast.unparse(d) for d in node.decorator_list
]
})
analysis['complexity']['classes'] += 1
elif isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
analysis['variables'].append(target.id)
elif isinstance(node, (ast.If, ast.While, ast.For)):
analysis['complexity']['branches'] += 1
return analysis
except Exception as e:
error_msg = f"Error analyzing code: {str(e)}"
logging.error(error_msg)
return None
def format_code(
self,
code: str,
style: str = 'black',
line_length: int = 88
) -> Optional[str]:
"""Format Python code according to style guide.
Args:
code: Python code to format
style: Formatting style ('black' or 'pep8')
line_length: Maximum line length
Returns:
Formatted code
"""
try:
if util.find_spec(style) is None:
error_msg = f"{style} package is not available. Please install it using: pip install {style}"
logging.error(error_msg)
return None
if style == 'black':
import black
return black.format_str(
code,
mode=black.FileMode(
line_length=line_length
)
)
else: # pep8
import autopep8
return autopep8.fix_code(
code,
options={
'max_line_length': line_length
}
)
except Exception as e:
error_msg = f"Error formatting code: {str(e)}"
logging.error(error_msg)
return None
def lint_code(
self,
code: str
) -> Optional[Dict[str, List[Dict[str, Any]]]]:
"""Lint Python code for potential issues.
Args:
code: Python code to lint
Returns:
Dictionary with linting results
"""
try:
if util.find_spec('pylint') is None:
error_msg = "pylint package is not available. Please install it using: pip install pylint"
logging.error(error_msg)
return None
# Import pylint only when needed
from pylint.reporters import JSONReporter
from pylint.lint.run import Run
# Create temporary file for pylint
import tempfile
with tempfile.NamedTemporaryFile(
mode='w',
suffix='.py',
delete=False
) as f:
f.write(code)
temp_path = f.name
# Run pylint
reporter = JSONReporter()
Run(
[temp_path],
reporter=reporter,
exit=False
)
# Process results
results = {
'errors': [],
'warnings': [],
'conventions': []
}
for msg in reporter.messages:
item = {
'type': msg.category,
'module': msg.module,
'obj': msg.obj,
'line': msg.line,
'column': msg.column,
'path': msg.path,
'symbol': msg.symbol,
'message': msg.msg,
'message-id': msg.msg_id
}
if msg.category in ['error', 'fatal']:
results['errors'].append(item)
elif msg.category == 'warning':
results['warnings'].append(item)
else:
results['conventions'].append(item)
# Clean up
import os
os.unlink(temp_path)
return results
except Exception as e:
error_msg = f"Error linting code: {str(e)}"
logging.error(error_msg)
return {
'errors': [],
'warnings': [],
'conventions': []
}
def disassemble_code(
self,
code: str
) -> Optional[str]:
"""Disassemble Python code to bytecode.
Args:
code: Python code to disassemble
Returns:
Disassembled bytecode as string
"""
try:
# Import dis only when needed
import dis
# Compile code
compiled_code = compile(code, '<string>', 'exec')
# Capture disassembly
output = io.StringIO()
with redirect_stdout(output):
dis.dis(compiled_code)
return output.getvalue()
except Exception as e:
error_msg = f"Error disassembling code: {str(e)}"
logging.error(error_msg)
return None
# Create instance for direct function access
_python_tools = PythonTools()
execute_code = _python_tools.execute_code
analyze_code = _python_tools.analyze_code
format_code = _python_tools.format_code
lint_code = _python_tools.lint_code
disassemble_code = _python_tools.disassemble_code
if __name__ == "__main__":
print("\n==================================================")
print("PythonTools Demonstration")
print("==================================================\n")
print("1. Execute Python Code")
print("------------------------------")
code = """
def greet(name):
return f"Hello, {name}!"
result = greet("World")
print(result)
"""
print("Code to execute:")
print(code)
print("\nOutput:")
result = execute_code(code)
print(result["stdout"])
print()
print("2. Format Python Code")
print("------------------------------")
code = """
def messy_function(x,y, z):
if x>0:
return y+z
else:
return y-z
"""
print("Before formatting:")
print(code)
print("\nAfter formatting:")
result = format_code(code)
print(result)
print()
print("3. Lint Python Code")
print("------------------------------")
code = """
def bad_function():
unused_var = 42
return 'result'
"""
print("Code to lint:")
print(code)
print("\nLinting results:")
result = lint_code(code)
print(result)
print()
print("4. Execute Code with Variables")
print("------------------------------")
code = """
x = 10
y = 20
result = x + y
print(f"The sum of {x} and {y} is {result}")
"""
print("Code to execute:")
print(code)
print("\nOutput:")
result = execute_code(code)
print(result["stdout"])
print()
print("==================================================")
print("Demonstration Complete")
print("==================================================")
+278
View File
@@ -0,0 +1,278 @@
"""Tools for executing shell commands safely.
This module provides a safe interface for executing shell commands with:
- Timeout control
- Output capture
- Error handling
- Resource limits
"""
import subprocess
import shlex
import logging
import os
import time
from typing import Dict, List, Optional, Union
class ShellTools:
"""Tools for executing shell commands safely."""
def __init__(self):
"""Initialize ShellTools."""
self._check_dependencies()
def _check_dependencies(self):
"""Check if required packages are installed."""
try:
import psutil
except ImportError:
raise ImportError(
"Required package not available. Please install: psutil\n"
"Run: pip install psutil"
)
def execute_command(
self,
command: str,
cwd: Optional[str] = None,
timeout: int = 30,
shell: bool = False,
env: Optional[Dict[str, str]] = None,
max_output_size: int = 10000
) -> Dict[str, Union[str, int, bool]]:
"""Execute a shell command safely.
Args:
command: Command to execute
cwd: Working directory
timeout: Maximum execution time in seconds
shell: Whether to run command in shell
env: Environment variables
max_output_size: Maximum output size in bytes
Returns:
Dictionary with execution results
"""
try:
# Split command if not using shell
if not shell:
command = shlex.split(command)
# Set up process environment
process_env = os.environ.copy()
if env:
process_env.update(env)
# Start process
start_time = time.time()
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd,
shell=shell,
env=process_env,
text=True
)
try:
# Wait for process with timeout
stdout, stderr = process.communicate(timeout=timeout)
# Truncate output if too large
if len(stdout) > max_output_size:
stdout = stdout[:max_output_size] + "...[truncated]"
if len(stderr) > max_output_size:
stderr = stderr[:max_output_size] + "...[truncated]"
return {
'stdout': stdout,
'stderr': stderr,
'exit_code': process.returncode,
'success': process.returncode == 0,
'execution_time': time.time() - start_time
}
except subprocess.TimeoutExpired:
# Kill process on timeout
parent = psutil.Process(process.pid)
children = parent.children(recursive=True)
for child in children:
child.kill()
parent.kill()
return {
'stdout': '',
'stderr': f'Command timed out after {timeout} seconds',
'exit_code': -1,
'success': False,
'execution_time': timeout
}
except Exception as e:
error_msg = f"Error executing command: {str(e)}"
logging.error(error_msg)
return {
'stdout': '',
'stderr': error_msg,
'exit_code': -1,
'success': False,
'execution_time': 0
}
def list_processes(self) -> List[Dict[str, Union[int, str, float]]]:
"""List running processes with their details.
Returns:
List of process information dictionaries
"""
try:
processes = []
for proc in psutil.process_iter(['pid', 'name', 'username', 'memory_percent', 'cpu_percent']):
try:
pinfo = proc.info
processes.append({
'pid': pinfo['pid'],
'name': pinfo['name'],
'username': pinfo['username'],
'memory_percent': round(pinfo['memory_percent'], 2),
'cpu_percent': round(pinfo['cpu_percent'], 2)
})
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return processes
except Exception as e:
error_msg = f"Error listing processes: {str(e)}"
logging.error(error_msg)
return []
def kill_process(
self,
pid: int,
force: bool = False
) -> Dict[str, Union[bool, str]]:
"""Kill a process by its PID.
Args:
pid: Process ID to kill
force: Whether to force kill (-9)
Returns:
Dictionary with operation results
"""
try:
process = psutil.Process(pid)
if force:
process.kill() # SIGKILL
else:
process.terminate() # SIGTERM
return {
'success': True,
'message': f'Process {pid} killed successfully'
}
except psutil.NoSuchProcess:
return {
'success': False,
'message': f'No process found with PID {pid}'
}
except psutil.AccessDenied:
return {
'success': False,
'message': f'Access denied to kill process {pid}'
}
except Exception as e:
error_msg = f"Error killing process: {str(e)}"
logging.error(error_msg)
return {
'success': False,
'message': error_msg
}
def get_system_info(self) -> Dict[str, Union[float, int, str, Dict]]:
"""Get system information.
Returns:
Dictionary with system information
"""
try:
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
return {
'cpu': {
'percent': cpu_percent,
'cores': psutil.cpu_count(),
'physical_cores': psutil.cpu_count(logical=False)
},
'memory': {
'total': memory.total,
'available': memory.available,
'percent': memory.percent,
'used': memory.used,
'free': memory.free
},
'disk': {
'total': disk.total,
'used': disk.used,
'free': disk.free,
'percent': disk.percent
},
'boot_time': psutil.boot_time(),
'platform': os.uname().sysname
}
except Exception as e:
error_msg = f"Error getting system info: {str(e)}"
logging.error(error_msg)
return {}
# Create instance for direct function access
_shell_tools = ShellTools()
execute_command = _shell_tools.execute_command
list_processes = _shell_tools.list_processes
kill_process = _shell_tools.kill_process
get_system_info = _shell_tools.get_system_info
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("ShellTools Demonstration")
print("==================================================\n")
# 1. Execute command
print("1. Command Execution")
print("------------------------------")
result = execute_command("ls -la")
print(f"Success: {result['success']}")
print(f"Output:\n{result['stdout']}")
if result['stderr']:
print(f"Errors:\n{result['stderr']}")
print(f"Execution time: {result['execution_time']:.2f}s")
print()
# 2. System Information
print("2. System Information")
print("------------------------------")
info = get_system_info()
print(f"CPU Usage: {info['cpu']['percent']}%")
print(f"Memory Usage: {info['memory']['percent']}%")
print(f"Disk Usage: {info['disk']['percent']}%")
print(f"Platform: {info['platform']}")
print()
# 3. Process List
print("3. Process List (top 5 by CPU)")
print("------------------------------")
processes = sorted(
list_processes(),
key=lambda x: x['cpu_percent'],
reverse=True
)[:5]
for proc in processes:
print(f"PID: {proc['pid']}, Name: {proc['name']}, CPU: {proc['cpu_percent']}%")
print()
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
@@ -0,0 +1,431 @@
"""Tools for web scraping and crawling.
Usage:
from praisonaiagents.tools import spider_tools
content = spider_tools.scrape_page("https://example.com")
links = spider_tools.extract_links("https://example.com")
or
from praisonaiagents.tools import scrape_page, extract_links
content = scrape_page("https://example.com")
"""
import logging
from typing import List, Dict, Union, Optional, Any
from importlib import util
import json
from urllib.parse import urljoin, urlparse
import re
import os
import hashlib
import time
class SpiderTools:
"""Tools for web scraping and crawling."""
def __init__(self):
"""Initialize SpiderTools and check for required packages."""
self._session = None
def _get_session(self):
"""Get or create requests session with common headers."""
if util.find_spec('requests') is None:
error_msg = "requests package is not available. Please install it using: pip install requests"
logging.error(error_msg)
return None
if self._session is None:
import requests
self._session = requests.Session()
self._session.headers.update({
'User-Agent': 'Mozilla/5.0 (compatible; PraisonAI/1.0; +http://praisonai.com/bot)',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
})
return self._session
def scrape_page(
self,
url: str,
selector: Optional[str] = None,
extract_images: bool = False,
extract_links: bool = False,
timeout: int = 30,
verify_ssl: bool = True
) -> Union[Dict[str, Any], Dict[str, str]]:
"""
Scrape content from a webpage.
Args:
url: URL to scrape
selector: Optional CSS selector to extract specific content
extract_images: Whether to extract image URLs
extract_links: Whether to extract links
timeout: Request timeout in seconds
verify_ssl: Whether to verify SSL certificates
Returns:
Dict: Scraped content or error dict
"""
try:
session = self._get_session()
if session is None:
return {"error": "requests package not available"}
# Import BeautifulSoup only when needed
if util.find_spec('bs4') is None:
error_msg = "bs4 package is not available. Please install it using: pip install beautifulsoup4"
logging.error(error_msg)
return {"error": error_msg}
from bs4 import BeautifulSoup
# Make request
response = session.get(
url,
timeout=timeout,
verify=verify_ssl
)
response.raise_for_status()
# Parse HTML
soup = BeautifulSoup(response.text, 'lxml')
# Remove unwanted elements
for element in soup(['script', 'style']):
element.decompose()
# Initialize result
result = {
'url': url,
'status_code': response.status_code,
'encoding': response.encoding,
'headers': dict(response.headers),
}
# Extract content based on selector
if selector:
elements = soup.select(selector)
result['content'] = [elem.get_text(strip=True) for elem in elements]
result['html'] = [str(elem) for elem in elements]
else:
result['title'] = soup.title.string if soup.title else None
result['content'] = soup.get_text(separator=' ', strip=True)
result['html'] = str(soup)
# Extract metadata
meta_tags = {}
for meta in soup.find_all('meta'):
name = meta.get('name') or meta.get('property')
if name:
meta_tags[name] = meta.get('content')
result['meta_tags'] = meta_tags
# Extract images if requested
if extract_images:
images = []
for img in soup.find_all('img'):
src = img.get('src')
if src:
images.append({
'src': urljoin(url, src),
'alt': img.get('alt', ''),
'title': img.get('title', '')
})
result['images'] = images
# Extract links if requested
if extract_links:
links = []
for link in soup.find_all('a'):
href = link.get('href')
if href:
links.append({
'url': urljoin(url, href),
'text': link.get_text(strip=True),
'title': link.get('title', '')
})
result['links'] = links
return result
except Exception as e:
error_msg = f"Error scraping {url}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def extract_links(
self,
url: str,
same_domain: bool = True,
exclude_patterns: Optional[List[str]] = None,
timeout: int = 30,
verify_ssl: bool = True
) -> Union[List[Dict[str, str]], Dict[str, str]]:
"""
Extract all links from a webpage.
Args:
url: URL to extract links from
same_domain: Only return links from the same domain
exclude_patterns: List of regex patterns to exclude
timeout: Request timeout in seconds
verify_ssl: Whether to verify SSL certificates
Returns:
List[Dict] or Dict: List of links or error dict
"""
try:
# Compile exclude patterns
if exclude_patterns:
exclude_patterns = [re.compile(p) for p in exclude_patterns]
# Get base domain
base_domain = urlparse(url).netloc
# Scrape page
result = self.scrape_page(
url,
extract_links=True,
timeout=timeout,
verify_ssl=verify_ssl
)
if "error" in result:
return result
# Filter and clean links
links = []
seen_urls = set()
for link in result.get('links', []):
link_url = link['url']
# Skip if already seen
if link_url in seen_urls:
continue
# Parse URL
parsed = urlparse(link_url)
# Skip if not same domain and same_domain is True
if same_domain and parsed.netloc != base_domain:
continue
# Skip if matches exclude patterns
if exclude_patterns and any(p.search(link_url) for p in exclude_patterns):
continue
# Add to results
links.append(link)
seen_urls.add(link_url)
return links
except Exception as e:
error_msg = f"Error extracting links from {url}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def crawl(
self,
start_url: str,
max_pages: int = 10,
same_domain: bool = True,
exclude_patterns: Optional[List[str]] = None,
delay: float = 1.0,
timeout: int = 30,
verify_ssl: bool = True,
output_dir: Optional[str] = None
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
"""
Crawl multiple pages starting from a URL.
Args:
start_url: Starting URL
max_pages: Maximum number of pages to crawl
same_domain: Only crawl pages from the same domain
exclude_patterns: List of regex patterns to exclude
delay: Delay between requests in seconds
timeout: Request timeout in seconds
verify_ssl: Whether to verify SSL certificates
output_dir: Directory to save crawled pages
Returns:
List[Dict] or Dict: Crawled pages or error dict
"""
try:
# Create output directory if needed
if output_dir:
os.makedirs(output_dir, exist_ok=True)
# Initialize crawl state
to_visit = {start_url}
visited = set()
results = []
while to_visit and len(visited) < max_pages:
# Get next URL
url = to_visit.pop()
# Skip if already visited
if url in visited:
continue
# Add to visited
visited.add(url)
# Delay if not first request
if len(visited) > 1:
time.sleep(delay)
# Scrape page
result = self.scrape_page(
url,
extract_links=True,
timeout=timeout,
verify_ssl=verify_ssl
)
if "error" in result:
logging.warning(f"Error crawling {url}: {result['error']}")
continue
# Save result
results.append(result)
# Save to file if requested
if output_dir:
filename = hashlib.md5(url.encode()).hexdigest() + '.json'
filepath = os.path.join(output_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
# Add new links to visit
for link in result.get('links', []):
link_url = link['url']
parsed = urlparse(link_url)
# Skip if not same domain and same_domain is True
if same_domain and parsed.netloc != urlparse(start_url).netloc:
continue
# Skip if matches exclude patterns
if exclude_patterns and any(
re.compile(p).search(link_url) for p in exclude_patterns
):
continue
# Add to visit if not visited
if link_url not in visited:
to_visit.add(link_url)
return results
except Exception as e:
error_msg = f"Error crawling from {start_url}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def extract_text(
self,
url: str,
selector: Optional[str] = None,
timeout: int = 30,
verify_ssl: bool = True
) -> Union[str, Dict[str, str]]:
"""
Extract clean text content from a webpage.
Args:
url: URL to extract text from
selector: Optional CSS selector to extract specific content
timeout: Request timeout in seconds
verify_ssl: Whether to verify SSL certificates
Returns:
str or Dict: Extracted text or error dict
"""
try:
result = self.scrape_page(
url,
selector=selector,
timeout=timeout,
verify_ssl=verify_ssl
)
if "error" in result:
return result
if selector:
return '\n'.join(result['content'])
return result['content']
except Exception as e:
error_msg = f"Error extracting text from {url}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
# Create instance for direct function access
_spider_tools = SpiderTools()
scrape_page = _spider_tools.scrape_page
extract_links = _spider_tools.extract_links
crawl = _spider_tools.crawl
extract_text = _spider_tools.extract_text
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("SpiderTools Demonstration")
print("==================================================\n")
# 1. Scrape a webpage
print("1. Scraping Webpage")
print("------------------------------")
url = "https://example.com"
result = scrape_page(url, extract_images=True, extract_links=True)
print(f"Content from {url}:")
if "error" not in result:
print(f"Title: {result['title']}")
print(f"Content length: {len(result['content'])} characters")
print(f"Number of images: {len(result.get('images', []))}")
print(f"Number of links: {len(result.get('links', []))}")
else:
print(result) # Show error
print()
# 2. Extract links
print("2. Extracting Links")
print("------------------------------")
links = extract_links(url)
print(f"Links from {url}:")
if isinstance(links, list):
for link in links:
print(f"- {link['url']} ({link['text']})")
else:
print(links) # Show error
print()
# 3. Extract text
print("3. Extracting Text")
print("------------------------------")
text = extract_text(url)
print(f"Text from {url}:")
if isinstance(text, str):
print(text[:500] + "..." if len(text) > 500 else text)
else:
print(text) # Show error
print()
# 4. Crawl multiple pages
print("4. Crawling Multiple Pages")
print("------------------------------")
results = crawl(url, max_pages=2, delay=1.0)
print(f"Crawl results from {url}:")
if isinstance(results, list):
print(f"Crawled {len(results)} pages")
for result in results:
print(f"- {result['url']}: {result['title']}")
else:
print(results) # Show error
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Test runner for all tools."""
import os
import glob
import logging
import subprocess
# Set up logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def main():
"""Run all tool files."""
logger.info("Starting tests...")
# Get all *_tools.py files
tools_dir = os.path.dirname(os.path.abspath(__file__))
tool_files = glob.glob(os.path.join(tools_dir, "*_tools.py"))
# Run each tool file
for tool_file in sorted(tool_files):
module_name = os.path.basename(tool_file)
logger.info(f"\nRunning {module_name}...")
try:
# Run the tool file directly
result = subprocess.run(
["python3", tool_file],
capture_output=True,
text=True,
cwd=tools_dir
)
# Log output
if result.stdout:
logger.info(f"Output:\n{result.stdout}")
if result.stderr:
logger.error(f"Errors:\n{result.stderr}")
if result.returncode == 0:
logger.info(f" {module_name} completed successfully")
else:
logger.error(f" {module_name} failed with return code {result.returncode}")
except Exception as e:
logger.error(f"Error running {module_name}: {str(e)}")
continue
logger.info("\nAll tests completed!")
if __name__ == "__main__":
main()
+5 -36
View File
@@ -1,40 +1,9 @@
"""Tools module for PraisonAI Agents"""
from typing import List, Dict
import logging
import importlib
from .duckduckgo_tools import internet_search
class Tools:
@staticmethod
def internet_search(query: str) -> List[Dict]:
"""
Perform a search using DuckDuckGo.
"""Tools class for backward compatibility"""
internet_search = staticmethod(internet_search)
Args:
query (str): The search query.
Returns:
list: A list of search result titles, URLs, and snippets.
"""
# Check if duckduckgo_search is installed
if importlib.util.find_spec("duckduckgo_search") is None:
error_msg = "DuckDuckGo search is not available. Please install duckduckgo_search package using: pip install duckduckgo_search"
logging.error(error_msg)
return [{"error": error_msg}]
try:
# Import only when needed
from duckduckgo_search import DDGS
results = []
ddgs = DDGS()
for result in ddgs.text(keywords=query, max_results=5):
results.append({
"title": result.get("title", ""),
"url": result.get("href", ""),
"snippet": result.get("body", "")
})
return results
except Exception as e:
error_msg = f"Error during DuckDuckGo search: {e}"
logging.error(error_msg)
return [{"error": error_msg}]
# Re-export the function
__all__ = ['Tools', 'internet_search']
@@ -0,0 +1,272 @@
"""Wikipedia tools for accessing and searching Wikipedia content.
Usage:
from praisonaiagents.tools import wikipedia_tools
summary = wikipedia_tools.wiki_summary("Python programming language")
page = wikipedia_tools.wiki_page("Python programming language")
results = wikipedia_tools.wiki_search("Python programming")
or
from praisonaiagents.tools import wiki_search, wiki_summary, wiki_page
summary = wiki_summary("Python programming language")
"""
import logging
from typing import List, Dict, Union, Any
from importlib import util
import json
class WikipediaTools:
"""Tools for accessing and searching Wikipedia content."""
def __init__(self):
"""Initialize WikipediaTools and check for wikipedia package."""
self._check_wikipedia()
def _check_wikipedia(self):
"""Check if wikipedia package is installed."""
if util.find_spec("wikipedia") is None:
raise ImportError("wikipedia package is not available. Please install it using: pip install wikipedia")
global wikipedia
import wikipedia
# Set default language to English
wikipedia.set_lang("en")
def wiki_search(
self,
query: str,
results: int = 10,
suggestion: bool = True
) -> Union[List[str], Dict[str, str]]:
"""
Search Wikipedia for a query.
Args:
query: Search query
results: Maximum number of results to return
suggestion: Whether to suggest similar queries
Returns:
List[str] or Dict: List of search results or error dict
"""
try:
search_results = wikipedia.search(query, results=results, suggestion=suggestion)
if isinstance(search_results, tuple):
# If suggestion is True, returns (results, suggestion)
return {
"results": search_results[0],
"suggestion": search_results[1] if search_results[1] else None
}
return {"results": search_results, "suggestion": None}
except Exception as e:
error_msg = f"Error searching Wikipedia for '{query}': {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def wiki_summary(
self,
title: str,
sentences: int = 5,
auto_suggest: bool = True
) -> Union[str, Dict[str, str]]:
"""
Get a summary of a Wikipedia page.
Args:
title: Title of the Wikipedia page
sentences: Number of sentences to return
auto_suggest: Whether to auto-suggest similar titles
Returns:
str or Dict: Page summary if successful, error dict if failed
"""
try:
return wikipedia.summary(title, sentences=sentences, auto_suggest=auto_suggest)
except wikipedia.exceptions.DisambiguationError as e:
return {
"error": "Disambiguation page",
"options": e.options[:10] # Limit to first 10 options
}
except Exception as e:
error_msg = f"Error getting summary for '{title}': {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def wiki_page(
self,
title: str,
auto_suggest: bool = True
) -> Union[Dict[str, Any], Dict[str, str]]:
"""
Get detailed information about a Wikipedia page.
Args:
title: Title of the Wikipedia page
auto_suggest: Whether to auto-suggest similar titles
Returns:
Dict: Page information if successful, error dict if failed
"""
try:
page = wikipedia.page(title, auto_suggest=auto_suggest)
return {
"title": page.title,
"url": page.url,
"content": page.content,
"summary": page.summary,
"references": page.references,
"categories": page.categories,
"links": page.links,
"images": page.images,
"sections": self.get_sections(page)
}
except wikipedia.exceptions.DisambiguationError as e:
return {
"error": "Disambiguation page",
"options": e.options[:10] # Limit to first 10 options
}
except Exception as e:
error_msg = f"Error getting page for '{title}': {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_sections(self, page: Any) -> List[Dict[str, Any]]:
"""
Extract sections and their content from a Wikipedia page.
Args:
page: Wikipedia page object
Returns:
List[Dict]: List of sections with their titles and content
"""
sections = []
content = page.content
section_titles = [s.strip() for s in content.split("==")[1::2]]
section_contents = [s.strip() for s in content.split("==")[2::2]]
# Add introduction section
intro = content.split("==")[0].strip()
if intro:
sections.append({
"title": "Introduction",
"level": 0,
"content": intro
})
# Process remaining sections
for title, content in zip(section_titles, section_contents):
# Count leading '=' to determine section level
level = title.count("=") // 2
clean_title = title.strip("= ")
sections.append({
"title": clean_title,
"level": level,
"content": content.strip()
})
return sections
def wiki_random(self, pages: int = 1) -> Union[List[str], Dict[str, str]]:
"""
Get random Wikipedia page titles.
Args:
pages: Number of random pages to return
Returns:
List[str] or Dict: List of random page titles or error dict
"""
try:
return wikipedia.random(pages)
except Exception as e:
error_msg = f"Error getting random pages: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def wiki_language(self, language: str) -> bool:
"""
Set the language for Wikipedia searches.
Args:
language: Language code (e.g., 'en' for English, 'es' for Spanish)
Returns:
bool: True if successful, False if failed
"""
try:
wikipedia.set_lang(language)
return True
except Exception as e:
error_msg = f"Error setting language to '{language}': {str(e)}"
logging.error(error_msg)
return False
# Create instance for direct function access
_wikipedia_tools = WikipediaTools()
wiki_search = _wikipedia_tools.wiki_search
wiki_summary = _wikipedia_tools.wiki_summary
wiki_page = _wikipedia_tools.wiki_page
wiki_random = _wikipedia_tools.wiki_random
wiki_language = _wikipedia_tools.wiki_language
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("WikipediaTools Demonstration")
print("==================================================\n")
# 1. Search for a topic
print("1. Searching Wikipedia")
print("------------------------------")
search_results = wiki_search("Python programming language")
print("Search results:")
print(json.dumps(search_results, indent=2))
print()
# 2. Get a page summary
print("2. Getting Page Summary")
print("------------------------------")
summary = wiki_summary("Python programming language", sentences=3)
print("Summary:")
print(summary)
print()
# 3. Get detailed page information
print("3. Getting Full Page Information")
print("------------------------------")
page = wiki_page("Python programming language")
if isinstance(page, dict) and "error" not in page:
print("Page sections:")
for section in page["sections"]:
print(f"- {section['title']} (Level {section['level']})")
print(f"\nNumber of references: {len(page['references'])}")
print(f"Number of categories: {len(page['categories'])}")
print(f"Number of links: {len(page['links'])}")
print(f"Number of images: {len(page['images'])}")
else:
print("Error getting page:", page.get("error"))
print()
# 4. Get random pages
print("4. Getting Random Pages")
print("------------------------------")
random_pages = wiki_random(3)
print("Random page titles:")
print(json.dumps(random_pages, indent=2))
print()
# 5. Try different language
print("5. Changing Language")
print("------------------------------")
success = wiki_language("es")
if success:
summary = wiki_summary("Python (lenguaje de programación)", sentences=1)
print("Spanish summary:")
print(summary)
print()
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
+498
View File
@@ -0,0 +1,498 @@
"""Tools for working with XML files.
Usage:
from praisonaiagents.tools import xml_tools
tree = xml_tools.read_xml("data.xml")
or
from praisonaiagents.tools import read_xml, write_xml, transform_xml
tree = read_xml("data.xml")
"""
import logging
from typing import List, Dict, Union, Optional, Any, Tuple
from importlib import util
import xml.etree.ElementTree as ET
import xml.dom.minidom as minidom
from io import StringIO
import json
class XMLTools:
"""Tools for working with XML files."""
def __init__(self):
"""Initialize XMLTools."""
pass
def read_xml(
self,
filepath: str,
encoding: str = 'utf-8',
validate_schema: Optional[str] = None,
parser: str = 'lxml'
) -> ET.Element:
"""Read an XML file with optional schema validation.
Args:
filepath: Path to XML file
encoding: File encoding
validate_schema: Optional path to XSD schema file
parser: XML parser to use ('lxml' or 'etree')
Returns:
ElementTree root element
"""
try:
if parser == 'lxml':
if util.find_spec('lxml') is None:
error_msg = "lxml package is not available. Please install it using: pip install lxml"
logging.error(error_msg)
return None
import lxml.etree as lxml_etree
tree = lxml_etree.parse(filepath)
root = tree.getroot()
else:
tree = ET.parse(filepath)
root = tree.getroot()
if validate_schema:
if util.find_spec('xmlschema') is None:
error_msg = "xmlschema package is not available. Please install it using: pip install xmlschema"
logging.error(error_msg)
return None
import xmlschema
schema = xmlschema.XMLSchema(validate_schema)
if not schema.is_valid(filepath):
error_msg = f"XML file does not validate against schema: {schema.validate(filepath)}"
logging.error(error_msg)
return None
return root
except Exception as e:
error_msg = f"Error reading XML file {filepath}: {str(e)}"
logging.error(error_msg)
return None
def write_xml(
self,
root: ET.Element,
filepath: str,
encoding: str = 'utf-8',
pretty: bool = True,
xml_declaration: bool = True
) -> bool:
"""Write XML Element tree to file.
Args:
root: XML Element tree root
filepath: Output file path
encoding: File encoding
pretty: Format output with proper indentation
xml_declaration: Include XML declaration
Returns:
True if successful, False otherwise
"""
try:
# Convert to string
if pretty:
xml_str = minidom.parseString(
ET.tostring(root, encoding='unicode')
).toprettyxml(indent=' ')
else:
xml_str = ET.tostring(
root,
encoding='unicode'
)
# Add declaration if requested
if xml_declaration:
if not xml_str.startswith('<?xml'):
xml_str = (
f'<?xml version="1.0" encoding="{encoding}"?>\n'
+ xml_str
)
# Write to file
with open(filepath, 'w', encoding=encoding) as f:
f.write(xml_str)
return True
except Exception as e:
error_msg = f"Error writing XML file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def transform_xml(
self,
xml_file: str,
xslt_file: str,
output_file: str,
params: Optional[Dict[str, str]] = None
) -> bool:
"""Transform XML using XSLT stylesheet.
Args:
xml_file: Input XML file
xslt_file: XSLT stylesheet file
output_file: Output file path
params: Optional parameters for transformation
Returns:
True if successful, False otherwise
"""
try:
# Parse XML and XSLT
if util.find_spec('lxml') is None:
error_msg = "lxml package is not available. Please install it using: pip install lxml"
logging.error(error_msg)
return False
import lxml.etree as lxml_etree
xml_doc = lxml_etree.parse(xml_file)
xslt_doc = lxml_etree.parse(xslt_file)
transform = lxml_etree.XSLT(xslt_doc)
# Apply transformation
if params:
result = transform(xml_doc, **params)
else:
result = transform(xml_doc)
# Write result
result.write(
output_file,
pretty_print=True,
xml_declaration=True,
encoding='utf-8'
)
return True
except Exception as e:
error_msg = f"Error transforming XML: {str(e)}"
logging.error(error_msg)
return False
def validate_xml(
self,
xml_file: str,
schema_file: str
) -> Tuple[bool, Optional[str]]:
"""Validate XML against XSD schema.
Args:
xml_file: XML file to validate
schema_file: XSD schema file
Returns:
Tuple of (is_valid, error_message)
"""
try:
if util.find_spec('xmlschema') is None:
error_msg = "xmlschema package is not available. Please install it using: pip install xmlschema"
logging.error(error_msg)
return False, error_msg
import xmlschema
schema = xmlschema.XMLSchema(schema_file)
schema.validate(xml_file)
return True, None
except xmlschema.validators.exceptions.XMLSchemaValidationError as e:
return False, str(e)
except Exception as e:
error_msg = f"Error validating XML: {str(e)}"
logging.error(error_msg)
return False, error_msg
def xml_to_dict(
self,
root: Union[str, ET.Element],
preserve_attrs: bool = True
) -> Dict[str, Any]:
"""Convert XML to dictionary.
Args:
root: XML string or Element tree root
preserve_attrs: Keep XML attributes in result
Returns:
Dict representation of XML
"""
try:
# Parse XML if string
if isinstance(root, str):
if root.startswith('<'):
root = ET.fromstring(root)
else:
root = ET.parse(root).getroot()
result = {}
# Add attributes if present and requested
if preserve_attrs and root.attrib:
result['@attributes'] = dict(root.attrib)
# Add children
children = list(root)
if not children:
text = root.text
if text is not None and text.strip():
result = text.strip()
else:
for child in children:
child_data = self.xml_to_dict(child, preserve_attrs)
if child.tag in result:
if not isinstance(result[child.tag], list):
result[child.tag] = [result[child.tag]]
result[child.tag].append(child_data)
else:
result[child.tag] = child_data
return result
except Exception as e:
error_msg = f"Error converting XML to dict: {str(e)}"
logging.error(error_msg)
return {}
def dict_to_xml(
self,
data: Dict[str, Any],
root_tag: str = 'root'
) -> Optional[ET.Element]:
"""Convert dictionary to XML.
Args:
data: Dictionary to convert
root_tag: Tag for root element
Returns:
XML Element tree root
"""
try:
def _create_element(
parent: ET.Element,
key: str,
value: Any
):
"""Create XML element from key-value pair."""
if key == '@attributes':
for attr_key, attr_val in value.items():
parent.set(attr_key, str(attr_val))
elif isinstance(value, dict):
child = ET.SubElement(parent, key)
for k, v in value.items():
_create_element(child, k, v)
elif isinstance(value, list):
for item in value:
child = ET.SubElement(parent, key)
if isinstance(item, dict):
for k, v in item.items():
_create_element(child, k, v)
else:
child.text = str(item)
else:
child = ET.SubElement(parent, key)
child.text = str(value)
root = ET.Element(root_tag)
for key, value in data.items():
_create_element(root, key, value)
return root
except Exception as e:
error_msg = f"Error converting dict to XML: {str(e)}"
logging.error(error_msg)
return None
def xpath_query(
self,
root: Union[str, ET.Element],
query: str,
namespaces: Optional[Dict[str, str]] = None
) -> List[ET.Element]:
"""Execute XPath query on XML.
Args:
root: XML string or Element tree root
query: XPath query string
namespaces: Optional namespace mappings
Returns:
List of matching elements
"""
try:
# Parse XML if string
if isinstance(root, str):
if root.startswith('<'):
if util.find_spec('lxml') is None:
error_msg = "lxml package is not available. Please install it using: pip install lxml"
logging.error(error_msg)
return []
import lxml.etree as lxml_etree
tree = lxml_etree.fromstring(root)
else:
tree = ET.parse(root)
else:
if util.find_spec('lxml') is None:
error_msg = "lxml package is not available. Please install it using: pip install lxml"
logging.error(error_msg)
return []
import lxml.etree as lxml_etree
tree = lxml_etree.fromstring(
ET.tostring(root, encoding='unicode')
)
# Execute query
results = tree.xpath(
query,
namespaces=namespaces or {}
)
# Convert results to standard ElementTree elements
return [
ET.fromstring(lxml_etree.tostring(elem, encoding='unicode'))
for elem in results
]
except Exception as e:
error_msg = f"Error executing XPath query: {str(e)}"
logging.error(error_msg)
return []
# Create instance for direct function access
_xml_tools = XMLTools()
read_xml = _xml_tools.read_xml
write_xml = _xml_tools.write_xml
transform_xml = _xml_tools.transform_xml
validate_xml = _xml_tools.validate_xml
xml_to_dict = _xml_tools.xml_to_dict
dict_to_xml = _xml_tools.dict_to_xml
xpath_query = _xml_tools.xpath_query
if __name__ == "__main__":
print("\n==================================================")
print("XMLTools Demonstration")
print("==================================================\n")
# Create temporary files
import tempfile
import os
temp_file = tempfile.mktemp(suffix='.xml')
try:
print("1. Creating XML Document")
print("------------------------------")
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="fiction">
<title>The Great Gatsby</title>
<author>F. Scott Fitzgerald</author>
<year>1925</year>
<price>10.99</price>
</book>
<book category="non-fiction">
<title>A Brief History of Time</title>
<author>Stephen Hawking</author>
<year>1988</year>
<price>15.99</price>
</book>
</bookstore>"""
with open(temp_file, 'w') as f:
f.write(xml_content)
print("Sample XML file created")
print()
print("2. Parsing XML")
print("------------------------------")
result = read_xml(temp_file)
print("XML structure:")
print(minidom.parseString(ET.tostring(result, encoding='unicode')).toprettyxml(indent=' '))
print()
print("3. Querying XML")
print("------------------------------")
xpath = "//book[@category='fiction']/title/text()"
result = xpath_query(result, xpath)
print(f"Fiction book titles (XPath: {xpath}):")
for title in result:
print(title.text)
print()
print("4. Validating XML")
print("------------------------------")
schema_file = tempfile.mktemp(suffix='.xsd')
schema_content = """<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="bookstore">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="author" type="xs:string"/>
<xs:element name="year" type="xs:integer"/>
<xs:element name="price" type="xs:decimal"/>
</xs:sequence>
<xs:attribute name="category" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>"""
with open(schema_file, 'w') as f:
f.write(schema_content)
result, error = validate_xml(temp_file, schema_file)
print(f"XML validation result: {result}")
if error:
print(f"Error: {error}")
print()
print("5. Transforming XML")
print("------------------------------")
xslt_content = """<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Bookstore Inventory</h2>
<table>
<tr>
<th>Title</th>
<th>Author</th>
<th>Price</th>
</tr>
<xsl:for-each select="bookstore/book">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="author"/></td>
<td><xsl:value-of select="price"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>"""
xslt_file = tempfile.mktemp(suffix='.xslt')
with open(xslt_file, 'w') as f:
f.write(xslt_content)
output_file = tempfile.mktemp(suffix='.html')
result = transform_xml(temp_file, xslt_file, output_file)
print(f"XML transformation result: {result}")
if result and os.path.exists(output_file):
print("\nTransformed HTML content:")
with open(output_file, 'r') as f:
print(f.read())
finally:
# Clean up temporary files
for file in [temp_file, schema_file, xslt_file, output_file]:
if os.path.exists(file):
os.unlink(file)
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
+417
View File
@@ -0,0 +1,417 @@
"""Tools for working with YAML files.
Usage:
from praisonaiagents.tools import yaml_tools
data = yaml_tools.read_yaml("config.yaml")
or
from praisonaiagents.tools import read_yaml, write_yaml, merge_yaml
data = read_yaml("config.yaml")
"""
import logging
from typing import List, Dict, Union, Optional, Any
from importlib import util
import os
from copy import deepcopy
class YAMLTools:
"""Tools for working with YAML files."""
def __init__(self):
"""Initialize YAMLTools."""
self._check_dependencies()
def _check_dependencies(self):
"""Check if required packages are installed."""
missing = []
for package, module in [('pyyaml', 'yaml'), ('jsonschema', 'jsonschema')]:
if util.find_spec(module) is None:
missing.append(package)
if missing:
raise ImportError(
f"Required packages not available. Please install: {', '.join(missing)}\n"
f"Run: pip install {' '.join(missing)}"
)
def read_yaml(
self,
filepath: str,
safe_load: bool = True,
encoding: str = 'utf-8'
) -> Optional[Union[Dict[str, Any], List[Any]]]:
"""Read a YAML file."""
try:
if util.find_spec('yaml') is None:
error_msg = "pyyaml package is not available. Please install it using: pip install pyyaml"
logging.error(error_msg)
return None
# Import yaml only when needed
import yaml
with open(filepath, 'r', encoding=encoding) as f:
if safe_load:
return yaml.safe_load(f)
return yaml.load(f, Loader=yaml.FullLoader)
except Exception as e:
error_msg = f"Error reading YAML file {filepath}: {str(e)}"
logging.error(error_msg)
return None
def write_yaml(
self,
data: Union[Dict[str, Any], List[Any]],
filepath: str,
default_flow_style: bool = False,
encoding: str = 'utf-8',
sort_keys: bool = False,
allow_unicode: bool = True
) -> bool:
"""Write data to YAML file."""
try:
if util.find_spec('yaml') is None:
error_msg = "pyyaml package is not available. Please install it using: pip install pyyaml"
logging.error(error_msg)
return False
# Import yaml only when needed
import yaml
with open(filepath, 'w', encoding=encoding) as f:
yaml.dump(
data,
f,
default_flow_style=default_flow_style,
sort_keys=sort_keys,
allow_unicode=allow_unicode
)
return True
except Exception as e:
error_msg = f"Error writing YAML file {filepath}: {str(e)}"
logging.error(error_msg)
return False
def merge_yaml(
self,
files: List[str],
output_file: Optional[str] = None,
strategy: str = 'deep'
) -> Union[Dict[str, Any], List[Any], None]:
"""Merge multiple YAML files."""
try:
if util.find_spec('yaml') is None:
error_msg = "pyyaml package is not available. Please install it using: pip install pyyaml"
logging.error(error_msg)
return None
def deep_merge(source: dict, destination: dict) -> dict:
"""Deep merge two dictionaries."""
for key, value in source.items():
if key in destination:
if isinstance(value, dict) and isinstance(destination[key], dict):
destination[key] = deep_merge(value, destination[key])
elif isinstance(value, list) and isinstance(destination[key], list):
destination[key].extend(value)
else:
destination[key] = value
else:
destination[key] = value
return destination
result = {}
for file in files:
data = self.read_yaml(file)
if data is None:
continue
if strategy == 'deep':
if isinstance(data, dict):
result = deep_merge(data, result)
elif isinstance(data, list):
if not result:
result = []
result.extend(data)
else: # shallow
if isinstance(data, dict):
result.update(data)
elif isinstance(data, list):
if not result:
result = []
result.extend(data)
if output_file:
self.write_yaml(result, output_file)
return result
except Exception as e:
error_msg = f"Error merging YAML files: {str(e)}"
logging.error(error_msg)
return None
def validate_yaml(
self,
data: Union[Dict[str, Any], List[Any], str],
schema: Dict[str, Any]
) -> bool:
"""Validate YAML data against a schema."""
try:
if util.find_spec('jsonschema') is None:
error_msg = "jsonschema package is not available. Please install it using: pip install jsonschema"
logging.error(error_msg)
return False
# Import jsonschema only when needed
from jsonschema import validate, ValidationError
# Load data if file path
if isinstance(data, str):
data = self.read_yaml(data)
if data is None:
return False
try:
validate(instance=data, schema=schema)
return True
except ValidationError as e:
logging.error(f"YAML validation error: {str(e)}")
return False
except Exception as e:
error_msg = f"Error validating YAML: {str(e)}"
logging.error(error_msg)
return False
def analyze_yaml(
self,
data: Union[Dict[str, Any], List[Any], str]
) -> Optional[Dict[str, Any]]:
"""Analyze YAML data structure."""
try:
# Load data if file path
if isinstance(data, str):
data = self.read_yaml(data)
if data is None:
return None
def analyze_value(value: Any) -> Dict[str, Any]:
"""Analyze a single value."""
result = {
'type': type(value).__name__,
'size': len(value) if hasattr(value, '__len__') else 1
}
if isinstance(value, dict):
result['keys'] = list(value.keys())
result['nested_types'] = {
k: type(v).__name__
for k, v in value.items()
}
elif isinstance(value, list):
result['element_types'] = list(set(
type(x).__name__ for x in value
))
return result
result = {
'structure': analyze_value(data),
'stats': {
'total_keys': sum(1 for _ in self._walk_dict(data))
if isinstance(data, dict) else len(data)
}
}
return result
except Exception as e:
error_msg = f"Error analyzing YAML: {str(e)}"
logging.error(error_msg)
return None
def _walk_dict(self, d: Dict[str, Any]) -> Any:
"""Walk through nested dictionary."""
for k, v in d.items():
yield k
if isinstance(v, dict):
yield from self._walk_dict(v)
def transform_yaml(
self,
data: Union[Dict[str, Any], List[Any], str],
operations: List[Dict[str, Any]]
) -> Optional[Union[Dict[str, Any], List[Any]]]:
"""Transform YAML data using specified operations."""
try:
# Load data if file path
if isinstance(data, str):
data = self.read_yaml(data)
if data is None:
return None
result = deepcopy(data)
for op in operations:
op_type = op.get('type')
path = op.get('path', '')
value = op.get('value')
if not op_type:
continue
# Split path into parts
parts = [p for p in path.split('/') if p]
# Get reference to target location
target = result
parent = None
last_key = None
for part in parts[:-1]:
if isinstance(target, dict):
if part not in target:
target[part] = {}
parent = target
target = target[part]
last_key = part
elif isinstance(target, list):
idx = int(part)
while len(target) <= idx:
target.append({})
parent = target
target = target[idx]
last_key = idx
if parts:
last_part = parts[-1]
if isinstance(target, dict):
if op_type == 'set':
target[last_part] = value
elif op_type == 'delete':
target.pop(last_part, None)
elif op_type == 'append' and isinstance(target.get(last_part), list):
target[last_part].append(value)
elif isinstance(target, list):
idx = int(last_part)
if op_type == 'set':
while len(target) <= idx:
target.append(None)
target[idx] = value
elif op_type == 'delete' and idx < len(target):
del target[idx]
elif op_type == 'append':
target.append(value)
else:
if op_type == 'set':
result = value
elif op_type == 'append' and isinstance(result, list):
result.append(value)
return result
except Exception as e:
error_msg = f"Error transforming YAML: {str(e)}"
logging.error(error_msg)
return None
# Create instance for direct function access
_yaml_tools = YAMLTools()
read_yaml = _yaml_tools.read_yaml
write_yaml = _yaml_tools.write_yaml
merge_yaml = _yaml_tools.merge_yaml
validate_yaml = _yaml_tools.validate_yaml
analyze_yaml = _yaml_tools.analyze_yaml
transform_yaml = _yaml_tools.transform_yaml
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("YAMLTools Demonstration")
print("==================================================\n")
# Sample data
data1 = {
'server': {
'host': 'localhost',
'port': 8080,
'debug': True
},
'database': {
'url': 'postgresql://localhost:5432/db',
'pool_size': 5
}
}
data2 = {
'server': {
'workers': 4,
'timeout': 30
},
'logging': {
'level': 'INFO',
'file': 'app.log'
}
}
# 1. Write YAML files
print("1. Writing YAML Files")
print("------------------------------")
success1 = write_yaml(data1, 'config1.yaml')
success2 = write_yaml(data2, 'config2.yaml')
print(f"First file written: {success1}")
print(f"Second file written: {success2}")
print()
# 2. Read YAML file
print("2. Reading YAML File")
print("------------------------------")
config = read_yaml('config1.yaml')
print("YAML content:")
print(config)
print()
# 3. Merge YAML files
print("3. Merging YAML Files")
print("------------------------------")
merged = merge_yaml(['config1.yaml', 'config2.yaml'])
print("Merged content:")
print(merged)
print()
# 4. Analyze YAML
print("4. Analyzing YAML")
print("------------------------------")
analysis = analyze_yaml(merged)
print("Analysis results:")
print(analysis)
print()
# 5. Transform YAML
print("5. Transforming YAML")
print("------------------------------")
operations = [
{
'type': 'set',
'path': 'server/host',
'value': '0.0.0.0'
},
{
'type': 'delete',
'path': 'server/debug'
},
{
'type': 'set',
'path': 'logging/handlers',
'value': ['console', 'file']
}
]
transformed = transform_yaml(merged, operations)
print("Transformed content:")
print(transformed)
print("\n==================================================")
print("Demonstration Complete")
print("==================================================")
# Cleanup
for file in ['config1.yaml', 'config2.yaml']:
if os.path.exists(file):
os.remove(file)
@@ -0,0 +1,221 @@
"""YFinance tools for stock market data.
Usage:
from praisonaiagents.tools import get_stock_price, get_stock_info
price = get_stock_price("AAPL")
info = get_stock_info("AAPL")
or
from praisonaiagents.tools import yfinance
price = yfinance.get_stock_price("AAPL")
info = yfinance.get_stock_info("AAPL")
"""
from typing import List, Dict, Optional, Any
import logging
from importlib import util
from datetime import datetime
class YFinanceTools:
"""A comprehensive tool for financial data analysis using yfinance"""
def __init__(self):
"""Initialize YFinanceTools"""
self._tickers = {}
def _get_yfinance(self):
"""Get yfinance module, installing if needed"""
if util.find_spec('yfinance') is None:
error_msg = "yfinance package is not available. Please install it using: pip install yfinance"
logging.error(error_msg)
return None
import yfinance as yf
return yf
def _get_ticker(self, symbol: str):
"""Get or create ticker instance"""
if symbol not in self._tickers:
yf = self._get_yfinance()
if yf is None:
return None
self._tickers[symbol] = yf.Ticker(symbol)
return self._tickers[symbol]
def get_stock_price(self, symbol: str) -> Dict[str, float]:
"""
Get current stock price and related price metrics
Args:
symbol (str): Stock ticker symbol
Returns:
Dict[str, float]: Current price information
"""
try:
ticker = self._get_ticker(symbol)
if ticker is None:
return {"error": "yfinance package not available"}
info = ticker.info
return {
'symbol': symbol,
'price': info.get('regularMarketPrice', 0.0),
'open': info.get('regularMarketOpen', 0.0),
'high': info.get('regularMarketDayHigh', 0.0),
'low': info.get('regularMarketDayLow', 0.0),
'volume': info.get('regularMarketVolume', 0),
'previous_close': info.get('regularMarketPreviousClose', 0.0),
'change': info.get('regularMarketChange', 0.0),
'change_percent': info.get('regularMarketChangePercent', 0.0)
}
except Exception as e:
error_msg = f"Error getting stock price for {symbol}: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_stock_info(self, symbol: str) -> Dict:
"""
Get detailed information about a stock
Args:
symbol (str): Stock ticker symbol
Returns:
Dict: Stock information including company details and key metrics
"""
try:
ticker = self._get_ticker(symbol)
if ticker is None:
return {"error": "yfinance package not available"}
info = ticker.info
relevant_info = {
"longName": info.get("longName"),
"symbol": info.get("symbol"),
"sector": info.get("sector"),
"industry": info.get("industry"),
"country": info.get("country"),
"marketCap": info.get("marketCap"),
"currentPrice": info.get("currentPrice"),
"currency": info.get("currency"),
"exchange": info.get("exchange"),
"fiftyTwoWeekHigh": info.get("fiftyTwoWeekHigh"),
"fiftyTwoWeekLow": info.get("fiftyTwoWeekLow"),
"trailingPE": info.get("trailingPE"),
"forwardPE": info.get("forwardPE"),
"dividendYield": info.get("dividendYield"),
"beta": info.get("beta"),
"volume": info.get("volume"),
"averageVolume": info.get("averageVolume"),
"sharesOutstanding": info.get("sharesOutstanding"),
"website": info.get("website"),
"longBusinessSummary": info.get("longBusinessSummary")
}
return {k: v for k, v in relevant_info.items() if v is not None}
except Exception as e:
error_msg = f"Error fetching stock info: {str(e)}"
logging.error(error_msg)
return {"error": error_msg}
def get_historical_data(
self,
symbol: str,
period: str = "1y",
interval: str = "1d",
start: Optional[datetime] = None,
end: Optional[datetime] = None
) -> List[Dict[str, Any]]:
"""
Get historical price data for a stock
Args:
symbol (str): Stock ticker symbol
period (str): Data period e.g. 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max
interval (str): Data interval e.g. 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo
start (datetime, optional): Start date for data
end (datetime, optional): End date for data
Returns:
List[Dict[str, Any]]: List of historical price data points
"""
try:
ticker = self._get_ticker(symbol)
if ticker is None:
return [{"error": "yfinance package not available"}]
hist = ticker.history(period=period, interval=interval, start=start, end=end)
data = hist.reset_index().to_dict('records')
# Convert timestamps to ISO format strings
for record in data:
if 'Date' in record and hasattr(record['Date'], 'isoformat'):
record['Date'] = record['Date'].isoformat()
if 'Datetime' in record and hasattr(record['Datetime'], 'isoformat'):
record['Datetime'] = record['Datetime'].isoformat()
return data
except Exception as e:
error_msg = f"Error fetching historical data: {str(e)}"
logging.error(error_msg)
return [{"error": error_msg}]
if __name__ == "__main__":
# Example usage
print("\n==================================================")
print("YFinanceTools Demonstration")
print("==================================================\n")
try:
yf_tools = YFinanceTools()
# 1. Basic Stock Information
print("1. Basic Stock Information for AAPL")
print("------------------------------")
info = yf_tools.get_stock_info("AAPL")
if "error" not in info:
print(f"Company: {info.get('longName')}")
print(f"Sector: {info.get('sector')}")
print(f"Industry: {info.get('industry')}")
print(f"Country: {info.get('country')}\n")
else:
print(f"Error: {info['error']}\n")
# 2. Current Price Data
print("2. Current Price Data")
print("------------------------------")
price_data = yf_tools.get_stock_price("AAPL")
if "error" not in price_data:
print(f"Current Price: ${price_data.get('price', 'N/A')}")
print(f"Previous Close: ${price_data.get('previous_close', 'N/A')}")
print(f"Day Range: ${price_data.get('low', 'N/A')} - ${price_data.get('high', 'N/A')}\n")
else:
print(f"Error: {price_data['error']}\n")
# 3. Historical Data
print("3. Historical Data (Last 5 Days)")
print("------------------------------")
hist_data = yf_tools.get_historical_data("AAPL", period="5d")
if not any("error" in d for d in hist_data):
for day in hist_data:
print(f"Date: {day.get('Date')}, Close: ${day.get('Close', 'N/A'):.2f}")
print()
else:
print(f"{hist_data[0]['error']}\n")
# 4. Multi-Stock Example
print("5. Multi-Stock Current Prices")
print("------------------------------")
for symbol in ["AAPL", "MSFT", "GOOGL"]:
price_data = yf_tools.get_stock_price(symbol)
if "error" not in price_data:
print(f"{symbol}: ${price_data.get('price', 'N/A')}")
else:
print(f"{symbol}: Error fetching price")
print()
except Exception as e:
print(f"Error: {str(e)}")
print("==================================================")
print("Analysis Complete")
print("==================================================")
+19
View File
@@ -0,0 +1,19 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import execute_code, analyze_code, format_code, lint_code, disassemble_code # Code Tools
from praisonaiagents.tools import execute_command, list_processes, kill_process, get_system_info # Shell Tools
from praisonaiagents.tools import duckduckgo # Web Search Tool
agent = Agent(
instructions="You are a Programming Agent", self_reflect=True, min_reflect=5, max_reflect=10,
tools=[execute_code, analyze_code, format_code, lint_code, disassemble_code, execute_command, list_processes, kill_process, get_system_info, duckduckgo]
)
agent.start(
"Write a python script using yfinance to find the stock price of Tesla"
"First check if required packages are installed"
"Run it using execute_code"
"execute_command if you want to run any terminal command"
"search internet using duckduckgo if you want to know update python package information"
"Analyse the output using analyze_code and fix error if required"
"if no package is installed, install it"
"then run the code"
)
+91
View File
@@ -0,0 +1,91 @@
from praisonaiagents.agent import Agent
from praisonaiagents.task import Task
from praisonaiagents.agents import PraisonAIAgents
from typing import List, Dict
import time
def get_time_check():
current_time = int(time.time())
result = "even" if current_time % 2 == 0 else "odd"
print(f"Time check: {current_time} is {result}")
return result
def create_prompt_chain():
# Create agents for each step in the chain
agent1 = Agent(
name="Time Checker",
role="Time checker",
goal="Check if the time is even or odd",
instructions="Check if the time is even or odd",
tools=[get_time_check]
)
agent2 = Agent(
name="Advanced Analyzer",
role="Advanced data analyzer",
goal="Perform in-depth analysis of processed data",
instructions="Analyze the processed data in detail"
)
agent3 = Agent(
name="Final Processor",
role="Final data processor",
goal="Generate final output based on analysis",
instructions="Create final output based on analyzed data"
)
# Create tasks for each step
initial_task = Task(
name="time_check",
description="Getting time check and checking if it is even or odd",
expected_output="Getting time check and checking if it is even or odd",
agent=agent1,
is_start=True, # Mark as the starting task
task_type="decision", # This task will make a decision
next_tasks=["advanced_analysis"], # Next task if condition passes
condition={
"even": ["advanced_analysis"], # If passes, go to advanced analysis
"odd": ["final_processing"] # If fails, exit the chain
}
)
analysis_task = Task(
name="advanced_analysis",
description="Perform advanced analysis on the processed data",
expected_output="Analyzed data ready for final processing",
agent=agent2,
next_tasks=["final_processing"]
)
final_task = Task(
name="final_processing",
description="Generate final output",
expected_output="Final processed result",
agent=agent3
)
# Create the workflow manager
workflow = PraisonAIAgents(
agents=[agent1, agent2, agent3],
tasks=[initial_task, analysis_task, final_task],
process="workflow", # Use workflow process type
verbose=True
)
return workflow
def main():
# Create and run the prompt chain
workflow = create_prompt_chain()
# Run the workflow
results = workflow.start()
# Print results
print("\nWorkflow Results:")
for task_id, result in results["task_results"].items():
if result:
print(f"Task {task_id}: {result.raw}")
if __name__ == "__main__":
main()
+78
View File
@@ -0,0 +1,78 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
import time
def get_time_check():
current_time = int(time.time())
result = "even" if current_time % 2 == 0 else "odd"
print(f"Time check: {current_time} is {result}")
return result
# Create agents for each step in the chain
agent1 = Agent(
name="Time Checker",
role="Time checker",
goal="Check if the time is even or odd",
instructions="Check if the time is even or odd",
tools=[get_time_check]
)
agent2 = Agent(
name="Advanced Analyzer",
role="Advanced data analyzer",
goal="Perform in-depth analysis of processed data",
instructions="Analyze the processed data in detail"
)
agent3 = Agent(
name="Final Processor",
role="Final data processor",
goal="Generate final output based on analysis",
instructions="Create final output based on analyzed data"
)
# Create tasks for each step
initial_task = Task(
name="time_check",
description="Getting time check and checking if it is even or odd",
expected_output="Getting time check and checking if it is even or odd",
agent=agent1,
is_start=True, # Mark as the starting task
task_type="decision", # This task will make a decision
next_tasks=["advanced_analysis"], # Next task if condition passes
condition={
"even": ["advanced_analysis"], # If passes, go to advanced analysis
"odd": "exit" # If fails, exit the chain
}
)
analysis_task = Task(
name="advanced_analysis",
description="Perform advanced analysis on the processed data",
expected_output="Analyzed data ready for final processing",
agent=agent2,
next_tasks=["final_processing"]
)
final_task = Task(
name="final_processing",
description="Generate final output",
expected_output="Final processed result",
agent=agent3
)
# Create the workflow manager
workflow = PraisonAIAgents(
agents=[agent1, agent2, agent3],
tasks=[initial_task, analysis_task, final_task],
process="workflow", # Use workflow process type
verbose=True
)
# Run the workflow
results = workflow.start()
# Print results
print("\nWorkflow Results:")
for task_id, result in results["task_results"].items():
if result:
print(f"Task {task_id}: {result.raw}")
+20 -2
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.22"
version = "0.0.44"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
authors = [
{ name="Mervin Praison" }
@@ -13,4 +13,22 @@ dependencies = [
"pydantic",
"rich",
"openai"
]
]
[project.optional-dependencies]
memory = [
"chromadb>=0.5.23"
]
knowledge = [
"mem0ai>=0.1.0",
"chromadb==0.5.23",
"markitdown",
"chonkie"
]
# Combined features
all = [
"praisonaiagents[memory]",
"praisonaiagents[knowledge]"
]
+5
View File
@@ -0,0 +1,5 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import duckduckgo
agent = Agent(instructions="You are a Recommendation Agent", tools=[duckduckgo])
agent.start("Recommend me a good movie to watch in 2025")
+22
View File
@@ -0,0 +1,22 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
agent = Agent(
instructions="You are a loop agent that creating a loop of tasks.",
llm="gpt-4o-mini"
)
task = Task(
description="Create the list of tasks to be looped through.",
agent=agent,
task_type="loop",
input_file="tasks.csv"
)
agents = PraisonAIAgents(
agents=[agent],
tasks=[task],
process="workflow",
max_iter=30
)
agents.start()
+139
View File
@@ -0,0 +1,139 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import read_csv, write_csv
import pandas as pd
def read_csv_smart(csv_path: str) -> pd.DataFrame:
"""Read CSV and use first column as task, handling both header/no-header cases"""
# Try reading with headers first
df = pd.read_csv(csv_path)
# Check if first row looks like data
if not any(col.lower() in ['task', 'description', 'status', 'state'] for col in df.columns):
# If no header-like names found, read without headers
df = pd.read_csv(csv_path, header=None)
# Rename columns consistently
df.columns = [*['task'], *[f'col_{i}' for i in range(1, len(df.columns))]]
if 'status' not in df.columns:
df['status'] = 'pending'
if 'loop_id' not in df.columns:
# Start loop_id from 1 instead of 0
df['loop_id'] = (df.index + 1).astype(str)
return df[['task', 'status', 'loop_id']]
def has_pending_tasks(df: pd.DataFrame) -> bool:
"""Check if there are any pending tasks in the DataFrame"""
return not df[df['status'] == 'pending'].empty
def check_task_status(csv_path: str) -> dict:
"""Read task status from CSV file"""
try:
# Always read directly from CSV file
df = pd.read_csv(csv_path, keep_default_na=False)
# Get only pending tasks from original status
pending_tasks = df[df['status'].str.lower() == 'pending']
if not pending_tasks.empty:
task_data = pending_tasks.iloc[0].to_dict()
return {
'has_pending': True,
'next_task': task_data
}
return {'has_pending': False, 'next_task': None}
except Exception as e:
print(f"Error reading CSV: {e}")
return {'has_pending': False, 'next_task': None, 'error': str(e)}
def get_next_task() -> dict:
"""Get the next pending task from CSV"""
status = check_task_status("tasks.csv")
if status['has_pending'] and status['next_task']:
task = status['next_task']
if task['status'].lower() == 'pending':
return {
'loop_data': {
'task': task['task'],
'loop_id': task['loop_id'],
'status': task['status']
}
}
return None
def update_task_status(loop_id: str, new_status: str) -> bool:
"""Update task status in CSV file using loop_id as identifier"""
try:
# Read directly from CSV file
df = pd.read_csv("tasks.csv", keep_default_na=False)
df['loop_id'] = df['loop_id'].astype(str)
mask = df['loop_id'] == str(loop_id)
if mask.any():
df.loc[mask, 'status'] = new_status
df.to_csv("tasks.csv", index=False)
return True
print(f"No task found with loop_id: {loop_id}")
return False
except Exception as e:
print(f"Error updating status: {e}")
return False
identify_agent = Agent(
name="Identify Agent",
role="Pending Task Identifier",
goal="Identify the next pending task",
tools=[get_next_task],
llm="gpt-4o-mini"
)
# Create the repetitive agent
repetitive_agent = Agent(
name="TaskProcessor",
role="Task Processing Specialist",
goal="Complete the task response",
llm="gpt-4o-mini"
)
update_status_agent = Agent(
name="UpdateStatusAgent",
role="Status Updater",
goal="Update the status of the task in CSV",
tools=[update_task_status],
llm="gpt-4o-mini"
)
# Define the repetitive task
identify_task = Task(
name="identify_task",
description="Identify the next pending task. If there are no pending tasks decision is completed.",
agent=identify_agent,
task_type="loop",
condition={
"pending": ["process_task"],
"completed": "exit"
}
)
process_task = Task(
name="process_task",
description="Complete the task mentioned in the response",
agent=repetitive_agent,
context=[identify_task],
task_type="loop",
next_tasks=["update_task"]
)
update_task = Task(
name="update_task",
description="Update the task status",
agent=update_status_agent,
next_tasks= ["identify_task"]
)
# Create and run agents manager
agents = PraisonAIAgents(
agents=[identify_agent, repetitive_agent],
tasks=[identify_task, process_task, update_task],
process="workflow"
)
agents.start()
+15
View File
@@ -0,0 +1,15 @@
# Core dependencies
pydantic
rich
openai>=1.30.0
# Memory dependencies
chromadb>=0.5.23
# Knowledge dependencies
mem0ai>=0.1.0
markitdown
chonkie
# Development dependencies
python-dotenv>=0.19.0
+5
View File
@@ -0,0 +1,5 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import duckduckgo
agent = Agent(instructions="You are a Research Agent", tools=[duckduckgo])
agent.start("Research about AI 2024")
+82
View File
@@ -0,0 +1,82 @@
from praisonaiagents.agent import Agent
from praisonaiagents.task import Task
from praisonaiagents.agents import PraisonAIAgents
import time
current_time = int(time.time())
result = "even" if current_time % 2 == 0 else "odd"
print(f"Time check: {current_time} is {result}")
# Create specialized agents
router = Agent(
name="Router",
role="Input Router",
goal="Evaluate input and determine routing path",
instructions="Analyze input and decide whether to proceed or exit",
tools=[get_time_check]
)
processor1 = Agent(
name="Processor 1",
role="Secondary Processor",
goal="Process valid inputs that passed initial check",
instructions="Process data that passed the routing check"
)
processor2 = Agent(
name="Processor 2",
role="Final Processor",
goal="Perform final processing on validated data",
instructions="Generate final output for processed data"
)
# Create tasks with routing logic
routing_task = Task(
name="initial_routing",
description="check the time and return according to what is returned",
expected_output="pass or fail based on what is returned",
agent=router,
is_start=True,
task_type="decision",
condition={
"pass": ["process_valid"],
"fail": "exit"
}
)
processing_task = Task(
name="process_valid",
description="Process validated input",
expected_output="Processed data ready for final step",
agent=processor1,
next_tasks=["final_process"]
)
final_task = Task(
name="final_process",
description="Generate final output",
expected_output="Final processed result",
agent=processor2
)
# Create and run workflow
workflow = PraisonAIAgents(
agents=[router, processor1, processor2],
tasks=[routing_task, processing_task, final_task],
process="workflow",
verbose=True
)
print("\nStarting Routing Workflow...")
print("=" * 50)
results = workflow.start()
print("\nWorkflow Results:")
print("=" * 50)
for task_id, result in results["task_results"].items():
if result:
task_name = result.description
print(f"\nTask: {task_name}")
print(f"Result: {result.raw}")
print("-" * 50)
+83
View File
@@ -0,0 +1,83 @@
from praisonaiagents.agent import Agent
from praisonaiagents.task import Task
from praisonaiagents.agents import PraisonAIAgents
import time
def get_time_check():
current_time = int(time.time())
result = "even" if current_time % 2 == 0 else "odd"
print(f"Time check: {current_time} is {result}")
return result
# Create specialized agents
router = Agent(
name="Router",
role="Input Router",
goal="Evaluate input and determine routing path",
instructions="Analyze input and decide whether to proceed or exit",
tools=[get_time_check]
)
processor1 = Agent(
name="Processor 1",
role="Secondary Processor",
goal="Process valid inputs that passed initial check",
instructions="Process data that passed the routing check"
)
processor2 = Agent(
name="Processor 2",
role="Final Processor",
goal="Perform final processing on validated data",
instructions="Generate final output for processed data"
)
# Create tasks with routing logic
routing_task = Task(
name="initial_routing",
description="check the time and return according to what is returned",
expected_output="pass or fail based on what is returned",
agent=router,
is_start=True,
task_type="decision",
condition={
"pass": ["process_valid"],
"fail": ["process_invalid"]
}
)
processing_task = Task(
name="process_valid",
description="Process validated input",
expected_output="Processed data ready for final step",
agent=processor1,
)
final_task = Task(
name="process_invalid",
description="Generate final output",
expected_output="Final processed result",
agent=processor2
)
# Create and run workflow
workflow = PraisonAIAgents(
agents=[router, processor1, processor2],
tasks=[routing_task, processing_task, final_task],
process="workflow",
verbose=True
)
print("\nStarting Routing Workflow...")
print("=" * 50)
results = workflow.start()
print("\nWorkflow Results:")
print("=" * 50)
for task_id, result in results["task_results"].items():
if result:
task_name = result.description
print(f"\nTask: {task_name}")
print(f"Result: {result.raw}")
print("-" * 50)
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
# Sample Markdown
## Section 1
This is a test markdown file.
## Section 2
It contains some formatted text and sections.
+3
View File
@@ -0,0 +1,3 @@
This is a sample text file.
It contains multiple lines.
This is for testing purposes.
+5
View File
@@ -0,0 +1,5 @@
from praisonaiagents import Agent, Tools
from praisonaiagents.tools import duckduckgo
agent = Agent(instructions="You are a Shopping Agent", tools=[duckduckgo])
agent.start("I want to buy iPhone 16 Pro Max, check 5 stores and give me price in table")
Loaded 100 of 303 files, more files were not shown because too many files have changed in this diff. Show more