├── .assets ├── architecture_highlevel_overview.png ├── atomic-cli-tool-menu.png ├── atomic-cli.png ├── docs.png ├── logo.png ├── video-thumbnail-1.png ├── video-thumbnail-2.png ├── video-thumbnail.png └── what_is_sent_in_prompt.png ├── .coveragerc ├── .flake8 ├── .github ├── funding.yml └── workflows │ ├── code-quality.yml │ └── docs.yml ├── .gitignore ├── LICENSE ├── README.md ├── atomic-agents ├── LICENSE ├── MANIFEST.in ├── atomic_agents │ ├── agents │ │ ├── __init__.py │ │ └── base_agent.py │ └── lib │ │ ├── __init__.py │ │ ├── base │ │ ├── base_io_schema.py │ │ └── base_tool.py │ │ ├── components │ │ ├── __init__.py │ │ ├── agent_memory.py │ │ └── system_prompt_generator.py │ │ ├── factories │ │ ├── mcp_tool_factory.py │ │ ├── schema_transformer.py │ │ └── tool_definition_service.py │ │ └── utils │ │ ├── __init__.py │ │ └── format_tool_message.py └── tests │ ├── agents │ └── test_base_agent.py │ └── lib │ ├── base │ └── test_base_tool.py │ ├── components │ ├── test_agent_memory.py │ └── test_system_prompt_generator.py │ ├── factories │ ├── test_mcp_tool_factory.py │ ├── test_schema_transformer.py │ └── test_tool_definition_service.py │ └── utils │ └── test_format_tool_message.py ├── atomic-assembler ├── README.md └── atomic_assembler │ ├── app.py │ ├── color_utils.py │ ├── constants.py │ ├── main.py │ ├── screens │ ├── atomic_tool_explorer.py │ ├── file_explorer.py │ ├── main_menu.py │ └── tool_info_screen.py │ ├── utils.py │ └── widgets │ ├── confirmation_modal.py │ ├── generic_list.py │ ├── gradient_title.py │ ├── icon_selection_list.py │ ├── menu.py │ ├── new_item_input.py │ └── tool_info_display.py ├── atomic-examples ├── basic-multimodal │ ├── README.md │ ├── basic_multimodal │ │ └── main.py │ ├── poetry.lock │ ├── pyproject.toml │ └── test_images │ │ ├── nutrition_label_1.png │ │ └── nutrition_label_2.jpg ├── deep-research │ ├── README.md │ ├── deep_research │ │ ├── agents │ │ │ ├── choice_agent.py │ │ │ ├── qa_agent.py │ │ │ └── query_agent.py │ │ ├── config.py │ │ ├── context_providers.py │ │ ├── main.py │ │ └── tools │ │ │ ├── searxng_search.py │ │ │ └── webpage_scraper.py │ ├── mermaid.md │ ├── poetry.lock │ └── pyproject.toml ├── mcp-agent │ ├── README.md │ ├── example-client │ │ ├── example_client │ │ │ ├── main.py │ │ │ ├── main_sse.py │ │ │ └── main_stdio.py │ │ ├── poetry.lock │ │ └── pyproject.toml │ └── example-mcp-server │ │ ├── README.md │ │ ├── demo_tools.py │ │ ├── example_mcp_server │ │ ├── __init__.py │ │ ├── interfaces │ │ │ ├── __init__.py │ │ │ ├── resource.py │ │ │ └── tool.py │ │ ├── resources │ │ │ └── __init__.py │ │ ├── server.py │ │ ├── server_sse.py │ │ ├── server_stdio.py │ │ ├── services │ │ │ ├── __init__.py │ │ │ ├── resource_service.py │ │ │ └── tool_service.py │ │ └── tools │ │ │ ├── __init__.py │ │ │ ├── add_numbers.py │ │ │ ├── divide_numbers.py │ │ │ ├── multiply_numbers.py │ │ │ └── subtract_numbers.py │ │ ├── poetry.lock │ │ └── pyproject.toml ├── orchestration-agent │ ├── README.md │ ├── orchestration_agent │ │ ├── orchestrator.py │ │ └── tools │ │ │ ├── calculator.py │ │ │ └── searxng_search.py │ ├── poetry.lock │ └── pyproject.toml ├── quickstart │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ └── quickstart │ │ ├── 1_1_basic_chatbot_streaming.py │ │ ├── 1_basic_chatbot.py │ │ ├── 2_basic_custom_chatbot.py │ │ ├── 3_1_basic_custom_chatbot_with_custom_schema_streaming.py │ │ ├── 3_basic_custom_chatbot_with_custom_schema.py │ │ ├── 4_basic_chatbot_different_providers.py │ │ └── 5_custom_system_role_for_reasoning_models.py ├── rag-chatbot │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ └── rag_chatbot │ │ ├── .gitignore │ │ ├── agents │ │ ├── qa_agent.py │ │ └── query_agent.py │ │ ├── config.py │ │ ├── context_providers.py │ │ ├── main.py │ │ └── services │ │ └── chroma_db.py ├── web-search-agent │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ └── web_search_agent │ │ ├── agents │ │ ├── query_agent.py │ │ └── question_answering_agent.py │ │ ├── main.py │ │ └── tools │ │ └── searxng_search.py ├── youtube-summarizer │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ └── youtube_summarizer │ │ ├── agent.py │ │ ├── main.py │ │ └── tools │ │ └── youtube_transcript_scraper.py └── youtube-to-recipe │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ └── youtube_to_recipe │ ├── agent.py │ ├── main.py │ └── tools │ └── youtube_transcript_scraper.py ├── atomic-forge ├── README.md ├── guides │ └── tool_structure.md └── tools │ ├── calculator │ ├── .coveragerc │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ ├── requirements.txt │ ├── tests │ │ └── test_calculator.py │ └── tool │ │ └── calculator.py │ ├── searxng_search │ ├── .coveragerc │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ ├── pytest.ini │ ├── requirements.txt │ ├── tests │ │ └── test_searxng_search.py │ └── tool │ │ └── searxng_search.py │ ├── tavily_search │ ├── .coveragerc │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ ├── requirements.txt │ ├── tests │ │ └── test_tavily_seach.py │ └── tool │ │ └── tavily_search.py │ ├── webpage_scraper │ ├── .coveragerc │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ ├── requirements.txt │ ├── tests │ │ └── test_webpage_scraper.py │ └── tool │ │ └── webpage_scraper.py │ └── youtube_transcript_scraper │ ├── .coveragerc │ ├── README.md │ ├── poetry.lock │ ├── pyproject.toml │ ├── requirements.txt │ ├── tests │ └── test_youtube_transcript_scraper.py │ └── tool │ └── youtube_transcript_scraper.py ├── docs ├── .nojekyll ├── Makefile ├── _static │ └── logo.png ├── api │ ├── agents.md │ ├── components.md │ ├── index.md │ └── utils.md ├── conf.py ├── contributing.md ├── examples │ ├── index.md │ └── mcp_agent.md ├── guides │ ├── index.md │ ├── quickstart.md │ └── tools.md ├── index.md └── make.bat ├── guides └── DEV_GUIDE.md ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── scripts └── generate_llms_txt.py └── setup.py /.assets/architecture_highlevel_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/architecture_highlevel_overview.png -------------------------------------------------------------------------------- /.assets/atomic-cli-tool-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/atomic-cli-tool-menu.png -------------------------------------------------------------------------------- /.assets/atomic-cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/atomic-cli.png -------------------------------------------------------------------------------- /.assets/docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/docs.png -------------------------------------------------------------------------------- /.assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/logo.png -------------------------------------------------------------------------------- /.assets/video-thumbnail-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/video-thumbnail-1.png -------------------------------------------------------------------------------- /.assets/video-thumbnail-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/video-thumbnail-2.png -------------------------------------------------------------------------------- /.assets/video-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/video-thumbnail.png -------------------------------------------------------------------------------- /.assets/what_is_sent_in_prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/.assets/what_is_sent_in_prompt.png -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | show_missing=true 3 | exclude_lines = 4 | @abstractmethod 5 | @abc.abstractmethod 6 | exclude_also = 7 | if __name__ == .__main__.: 8 | 9 | [run] 10 | omit = 11 | */__init__.py 12 | */tests/* 13 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 150 3 | max-complexity = 10 4 | count = true 5 | statistics = true 6 | ignore = E203 7 | exclude = .venv,venv,__pycache__,build,dist 8 | 9 | per-file-ignores = 10 | */__main__.py:C901 11 | */*:C901 12 | 13 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: KennyVaneetvelde 2 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality & Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | quality: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: '3.11' 23 | 24 | - name: Install Poetry 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install poetry 28 | 29 | - name: Configure Poetry 30 | run: | 31 | poetry config virtualenvs.create true 32 | poetry config virtualenvs.in-project true 33 | 34 | - name: Cache Poetry virtualenv 35 | uses: actions/cache@v4 36 | id: cache 37 | with: 38 | path: ./.venv 39 | key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} 40 | restore-keys: | 41 | venv-${{ runner.os }}- 42 | 43 | - name: Install Dependencies 44 | run: | 45 | poetry install --with dev 46 | poetry run pip list # Verify installation 47 | 48 | - name: Verify Black Installation 49 | run: | 50 | poetry run which black || echo "Black not found" 51 | poetry run black --version 52 | 53 | - name: Run Black Check 54 | run: | 55 | poetry run black --check atomic-agents atomic-assembler atomic-examples atomic-forge 56 | if: success() 57 | 58 | - name: Run Flake8 59 | run: poetry run flake8 --extend-exclude=.venv atomic-agents atomic-assembler atomic-examples atomic-forge 60 | if: success() 61 | 62 | - name: Run Tests 63 | run: poetry run pytest --cov=atomic_agents atomic-agents 64 | if: success() 65 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # or your default branch 7 | workflow_dispatch: # Allows manual triggering 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow only one concurrent deployment 16 | concurrency: 17 | group: "pages" 18 | cancel-in-progress: false 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Set up Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: '3.10' # or your preferred version 31 | cache: 'pip' 32 | 33 | - name: Install dependencies 34 | run: | 35 | python -m pip install --upgrade pip 36 | pip install poetry 37 | poetry install 38 | 39 | - name: Build documentation 40 | run: | 41 | cd docs 42 | poetry run make singlehtml 43 | poetry run make html 44 | 45 | - name: Generate llms.txt 46 | run: | 47 | poetry run python scripts/generate_llms_txt.py 48 | 49 | - name: Upload artifact 50 | uses: actions/upload-pages-artifact@v3 51 | with: 52 | path: docs/_build/html 53 | 54 | deploy: 55 | environment: 56 | name: github-pages 57 | url: ${{ steps.deployment.outputs.page_url }} 58 | needs: build 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Deploy to GitHub Pages 62 | id: deployment 63 | uses: actions/deploy-pages@v4 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | wheels/ 21 | share/python-wheels/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | MANIFEST 26 | site-packages/ 27 | 28 | # Virtual environments 29 | .env 30 | .env.* 31 | .venv 32 | .venv.* 33 | env/ 34 | env.bak/ 35 | venv/ 36 | venv.bak/ 37 | ENV/ 38 | 39 | # PyInstaller 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | cover/ 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff 67 | *.log 68 | local_settings.py 69 | db.sqlite3 70 | db.sqlite3-journal 71 | 72 | # Flask stuff 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # IPython 89 | profile_default/ 90 | ipython_config.py 91 | 92 | # pyenv 93 | .python-version 94 | 95 | # celery beat schedule file 96 | celerybeat-schedule 97 | 98 | # SageMath parsed files 99 | *.sage.py 100 | 101 | # Spyder project settings 102 | .spyderproject 103 | .spyproject 104 | 105 | # Rope project settings 106 | .ropeproject 107 | 108 | # mkdocs documentation 109 | /site 110 | 111 | # mypy 112 | .mypy_cache/ 113 | .dmypy.json 114 | dmypy.json 115 | 116 | # Pyre type checker 117 | .pyre/ 118 | 119 | # pytype static type analyzer 120 | .pytype/ 121 | 122 | # Cython debug symbols 123 | cython_debug/ 124 | 125 | # Chroma related 126 | chromadb_persist/ 127 | chroma_data/ 128 | chromadb_tmp/ 129 | 130 | # IDEs and editors 131 | .vscode/ 132 | .idea/ 133 | *.swp 134 | *.swo 135 | *~ 136 | 137 | # Operating System Files 138 | .DS_Store 139 | Thumbs.db 140 | 141 | # Project specific 142 | personal_experiments/ 143 | personal_scripts/ 144 | *.psd 145 | .output/ 146 | 147 | # Logs 148 | debug.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kenny Vaneetvelde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /atomic-agents/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kenny Vaneetvelde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /atomic-agents/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | recursive-include atomic_agents *.py 4 | -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/atomic-agents/atomic_agents/agents/__init__.py -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/atomic-agents/atomic_agents/lib/__init__.py -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/lib/base/base_io_schema.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from pydantic import BaseModel 3 | from rich.json import JSON 4 | 5 | 6 | class BaseIOSchema(BaseModel): 7 | """Base schema for input/output in the Atomic Agents framework.""" 8 | 9 | def __str__(self): 10 | return self.model_dump_json() 11 | 12 | def __rich__(self): 13 | json_str = self.model_dump_json() 14 | return JSON(json_str) 15 | 16 | @classmethod 17 | def __pydantic_init_subclass__(cls, **kwargs): 18 | super().__pydantic_init_subclass__(**kwargs) 19 | cls._validate_description() 20 | 21 | @classmethod 22 | def _validate_description(cls): 23 | description = cls.__doc__ 24 | 25 | if not description or not description.strip(): 26 | if cls.__module__ != "instructor.function_calls" and not hasattr(cls, "from_streaming_response"): 27 | raise ValueError(f"{cls.__name__} must have a non-empty docstring to serve as its description") 28 | 29 | @classmethod 30 | def model_json_schema(cls, *args, **kwargs): 31 | schema = super().model_json_schema(*args, **kwargs) 32 | if "description" not in schema and cls.__doc__: 33 | schema["description"] = inspect.cleandoc(cls.__doc__) 34 | if "title" not in schema: 35 | schema["title"] = cls.__name__ 36 | return schema 37 | -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/lib/base/base_tool.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Type 2 | from pydantic import BaseModel 3 | 4 | from atomic_agents.lib.base.base_io_schema import BaseIOSchema 5 | 6 | 7 | class BaseToolConfig(BaseModel): 8 | """ 9 | Configuration for a tool. 10 | 11 | Attributes: 12 | title (Optional[str]): Overrides the default title of the tool. 13 | description (Optional[str]): Overrides the default description of the tool. 14 | """ 15 | 16 | title: Optional[str] = None 17 | description: Optional[str] = None 18 | 19 | 20 | class BaseTool: 21 | """ 22 | Base class for tools within the Atomic Agents framework. 23 | 24 | Attributes: 25 | input_schema (Type[BaseIOSchema]): Schema defining the input data. 26 | output_schema (Type[BaseIOSchema]): Schema defining the output data. 27 | tool_name (str): The name of the tool, derived from the input schema's description or overridden by the user. 28 | tool_description (str): Description of the tool, derived from the input schema's description or overridden by the user. 29 | """ 30 | 31 | input_schema: Type[BaseIOSchema] 32 | output_schema: Type[BaseIOSchema] 33 | 34 | def __init__(self, config: BaseToolConfig = BaseToolConfig()): 35 | """ 36 | Initializes the BaseTool with an optional configuration override. 37 | 38 | Args: 39 | config (BaseToolConfig, optional): Configuration for the tool, including optional title and description overrides. 40 | """ 41 | self.tool_name = config.title or self.input_schema.model_json_schema()["title"] 42 | self.tool_description = config.description or self.input_schema.model_json_schema()["description"] 43 | 44 | def run(self, params: Type[BaseIOSchema]) -> BaseIOSchema: 45 | """ 46 | Executes the tool with the provided parameters. 47 | 48 | Args: 49 | params (BaseIOSchema): Input parameters adhering to the input schema. 50 | 51 | Returns: 52 | BaseIOSchema: Output resulting from executing the tool, adhering to the output schema. 53 | 54 | Raises: 55 | NotImplementedError: If the method is not implemented by a subclass. 56 | """ 57 | raise NotImplementedError("Subclasses should implement this method") 58 | -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/lib/components/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/atomic-agents/atomic_agents/lib/components/__init__.py -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/lib/components/system_prompt_generator.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Dict, List, Optional 3 | 4 | 5 | class SystemPromptContextProviderBase(ABC): 6 | def __init__(self, title: str): 7 | self.title = title 8 | 9 | @abstractmethod 10 | def get_info(self) -> str: 11 | pass 12 | 13 | def __repr__(self) -> str: 14 | return self.get_info() 15 | 16 | 17 | class SystemPromptGenerator: 18 | def __init__( 19 | self, 20 | background: Optional[List[str]] = None, 21 | steps: Optional[List[str]] = None, 22 | output_instructions: Optional[List[str]] = None, 23 | context_providers: Optional[Dict[str, SystemPromptContextProviderBase]] = None, 24 | ): 25 | self.background = background or ["This is a conversation with a helpful and friendly AI assistant."] 26 | self.steps = steps or [] 27 | self.output_instructions = output_instructions or [] 28 | self.context_providers = context_providers or {} 29 | 30 | self.output_instructions.extend( 31 | [ 32 | "Always respond using the proper JSON schema.", 33 | "Always use the available additional information and context to enhance the response.", 34 | ] 35 | ) 36 | 37 | def generate_prompt(self) -> str: 38 | sections = [ 39 | ("IDENTITY and PURPOSE", self.background), 40 | ("INTERNAL ASSISTANT STEPS", self.steps), 41 | ("OUTPUT INSTRUCTIONS", self.output_instructions), 42 | ] 43 | 44 | prompt_parts = [] 45 | 46 | for title, content in sections: 47 | if content: 48 | prompt_parts.append(f"# {title}") 49 | prompt_parts.extend(f"- {item}" for item in content) 50 | prompt_parts.append("") 51 | 52 | if self.context_providers: 53 | prompt_parts.append("# EXTRA INFORMATION AND CONTEXT") 54 | for provider in self.context_providers.values(): 55 | info = provider.get_info() 56 | if info: 57 | prompt_parts.append(f"## {provider.title}") 58 | prompt_parts.append(info) 59 | prompt_parts.append("") 60 | 61 | return "\n".join(prompt_parts).strip() 62 | -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/lib/factories/schema_transformer.py: -------------------------------------------------------------------------------- 1 | """Module for transforming JSON schemas to Pydantic models.""" 2 | 3 | import logging 4 | from typing import Any, Dict, List, Optional, Type, Tuple, Literal, cast 5 | 6 | from pydantic import Field, create_model 7 | 8 | from atomic_agents.lib.base.base_io_schema import BaseIOSchema 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | # JSON type mapping 13 | JSON_TYPE_MAP = { 14 | "string": str, 15 | "number": float, 16 | "integer": int, 17 | "boolean": bool, 18 | "array": list, 19 | "object": dict, 20 | } 21 | 22 | 23 | class SchemaTransformer: 24 | """Class for transforming JSON schemas to Pydantic models.""" 25 | 26 | @staticmethod 27 | def json_to_pydantic_field(prop_schema: Dict[str, Any], required: bool) -> Tuple[Type, Field]: 28 | """ 29 | Convert a JSON schema property to a Pydantic field. 30 | 31 | Args: 32 | prop_schema: JSON schema for the property 33 | required: Whether the field is required 34 | 35 | Returns: 36 | Tuple of (type, Field) 37 | """ 38 | json_type = prop_schema.get("type") 39 | description = prop_schema.get("description") 40 | default = prop_schema.get("default") 41 | python_type: Any = Any 42 | 43 | if json_type in JSON_TYPE_MAP: 44 | python_type = JSON_TYPE_MAP[json_type] 45 | if json_type == "array": 46 | items_schema = prop_schema.get("items", {}) 47 | item_type_str = items_schema.get("type") 48 | if item_type_str in JSON_TYPE_MAP: 49 | python_type = List[JSON_TYPE_MAP[item_type_str]] 50 | else: 51 | python_type = List[Any] 52 | elif json_type == "object": 53 | python_type = Dict[str, Any] 54 | 55 | field_kwargs = {"description": description} 56 | if required: 57 | field_kwargs["default"] = ... 58 | elif default is not None: 59 | field_kwargs["default"] = default 60 | else: 61 | python_type = Optional[python_type] 62 | field_kwargs["default"] = None 63 | 64 | return (python_type, Field(**field_kwargs)) 65 | 66 | @staticmethod 67 | def create_model_from_schema( 68 | schema: Dict[str, Any], 69 | model_name: str, 70 | tool_name_literal: str, 71 | docstring: Optional[str] = None, 72 | ) -> Type[BaseIOSchema]: 73 | """ 74 | Dynamically create a Pydantic model from a JSON schema. 75 | 76 | Args: 77 | schema: JSON schema 78 | model_name: Name for the model 79 | tool_name_literal: Tool name to use for the Literal type 80 | docstring: Optional docstring for the model 81 | 82 | Returns: 83 | Pydantic model class 84 | """ 85 | fields = {} 86 | required_fields = set(schema.get("required", [])) 87 | properties = schema.get("properties") 88 | 89 | if properties: 90 | for prop_name, prop_schema in properties.items(): 91 | is_required = prop_name in required_fields 92 | fields[prop_name] = SchemaTransformer.json_to_pydantic_field(prop_schema, is_required) 93 | elif schema.get("type") == "object" and not properties: 94 | pass 95 | elif schema: 96 | logger.warning( 97 | f"Schema for {model_name} is not a typical object with properties. Fields might be empty beyond tool_name." 98 | ) 99 | 100 | # Create a proper Literal type for tool_name 101 | tool_name_type = cast(Type[str], Literal[tool_name_literal]) # type: ignore 102 | fields["tool_name"] = ( 103 | tool_name_type, 104 | Field(..., description=f"Required identifier for the {tool_name_literal} tool."), 105 | ) 106 | 107 | # Create the model 108 | model = create_model( 109 | model_name, 110 | __base__=BaseIOSchema, 111 | __doc__=docstring or f"Dynamically generated Pydantic model for {model_name}", 112 | __config__={"title": tool_name_literal}, 113 | **fields, 114 | ) 115 | 116 | return model 117 | -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/lib/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/atomic-agents/atomic_agents/lib/utils/__init__.py -------------------------------------------------------------------------------- /atomic-agents/atomic_agents/lib/utils/format_tool_message.py: -------------------------------------------------------------------------------- 1 | import json 2 | import uuid 3 | from pydantic import BaseModel 4 | from typing import Dict, Optional, Type 5 | 6 | 7 | def format_tool_message(tool_call: Type[BaseModel], tool_id: Optional[str] = None) -> Dict: 8 | """ 9 | Formats a message for a tool call. 10 | 11 | Args: 12 | tool_call (Type[BaseModel]): The Pydantic model instance representing the tool call. 13 | tool_id (str, optional): The unique identifier for the tool call. If not provided, a random UUID will be generated. 14 | 15 | Returns: 16 | Dict: A formatted message dictionary for the tool call. 17 | """ 18 | if tool_id is None: 19 | tool_id = str(uuid.uuid4()) 20 | 21 | # Get the tool name from the Config.title if available, otherwise use the class name 22 | return { 23 | "id": tool_id, 24 | "type": "function", 25 | "function": { 26 | "name": tool_call.__class__.__name__, 27 | "arguments": json.dumps(tool_call.model_dump(), separators=(", ", ": ")), 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /atomic-agents/tests/lib/base/test_base_tool.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pydantic import BaseModel 3 | from atomic_agents.lib.base.base_tool import BaseToolConfig, BaseTool 4 | from atomic_agents.agents.base_agent import BaseIOSchema 5 | 6 | 7 | # Mock classes for testing 8 | class MockInputSchema(BaseIOSchema): 9 | """Mock input schema for testing""" 10 | 11 | query: str 12 | 13 | 14 | class MockOutputSchema(BaseIOSchema): 15 | """Mock output schema for testing""" 16 | 17 | result: str 18 | 19 | 20 | class MockTool(BaseTool): 21 | input_schema = MockInputSchema 22 | output_schema = MockOutputSchema 23 | 24 | def run(self, params: MockInputSchema) -> MockOutputSchema: 25 | return MockOutputSchema(result="Mock result") 26 | 27 | 28 | def test_base_tool_config_creation(): 29 | config = BaseToolConfig() 30 | assert config.title is None 31 | assert config.description is None 32 | 33 | 34 | def test_base_tool_config_with_values(): 35 | config = BaseToolConfig(title="Test Tool", description="Test description") 36 | assert config.title == "Test Tool" 37 | assert config.description == "Test description" 38 | 39 | 40 | def test_base_tool_initialization(): 41 | tool = MockTool() 42 | assert tool.tool_name == "MockInputSchema" 43 | assert tool.tool_description == "Mock input schema for testing" 44 | 45 | 46 | def test_base_tool_with_config(): 47 | config = BaseToolConfig(title="Custom Title", description="Custom description") 48 | tool = MockTool(config=config) 49 | assert tool.tool_name == "Custom Title" 50 | assert tool.tool_description == "Custom description" 51 | 52 | 53 | def test_base_tool_with_custom_title(): 54 | config = BaseToolConfig(title="Custom Tool Name") 55 | tool = MockTool(config=config) 56 | assert tool.tool_name == "Custom Tool Name" 57 | assert tool.tool_description == "Mock input schema for testing" 58 | 59 | 60 | def test_base_tool_run_not_implemented(): 61 | class UnimplementedTool(BaseTool): 62 | input_schema = MockInputSchema 63 | output_schema = MockOutputSchema 64 | 65 | tool = UnimplementedTool() 66 | with pytest.raises(NotImplementedError): 67 | tool.run(MockInputSchema(query="mock query")) 68 | 69 | 70 | def test_mock_tool_run(): 71 | tool = MockTool() 72 | result = tool.run(MockInputSchema(query="mock query")) 73 | assert isinstance(result, MockOutputSchema) 74 | assert result.result == "Mock result" 75 | 76 | 77 | def test_base_tool_input_schema(): 78 | tool = MockTool() 79 | assert tool.input_schema == MockInputSchema 80 | 81 | 82 | def test_base_tool_output_schema(): 83 | tool = MockTool() 84 | assert tool.output_schema == MockOutputSchema 85 | 86 | 87 | def test_base_tool_inheritance(): 88 | tool = MockTool() 89 | assert isinstance(tool, BaseTool) 90 | 91 | 92 | def test_base_tool_config_is_pydantic_model(): 93 | assert issubclass(BaseToolConfig, BaseModel) 94 | 95 | 96 | def test_base_tool_config_optional_fields(): 97 | config = BaseToolConfig() 98 | assert hasattr(config, "title") 99 | assert hasattr(config, "description") 100 | -------------------------------------------------------------------------------- /atomic-agents/tests/lib/utils/test_format_tool_message.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from pydantic import BaseModel 3 | import pytest 4 | from atomic_agents.agents.base_agent import BaseIOSchema 5 | from atomic_agents.lib.utils.format_tool_message import format_tool_message 6 | 7 | 8 | # Mock classes for testing 9 | class MockToolCall(BaseModel): 10 | """Mock class for testing""" 11 | 12 | param1: str 13 | param2: int 14 | 15 | 16 | def test_format_tool_message_with_provided_tool_id(): 17 | tool_call = MockToolCall(param1="test", param2=42) 18 | tool_id = "test-tool-id" 19 | 20 | result = format_tool_message(tool_call, tool_id) 21 | 22 | assert result == { 23 | "id": "test-tool-id", 24 | "type": "function", 25 | "function": {"name": "MockToolCall", "arguments": '{"param1": "test", "param2": 42}'}, 26 | } 27 | 28 | 29 | def test_format_tool_message_without_tool_id(): 30 | tool_call = MockToolCall(param1="test", param2=42) 31 | 32 | result = format_tool_message(tool_call) 33 | 34 | assert isinstance(result["id"], str) 35 | assert len(result["id"]) == 36 # UUID length 36 | assert result["type"] == "function" 37 | assert result["function"]["name"] == "MockToolCall" 38 | assert result["function"]["arguments"] == '{"param1": "test", "param2": 42}' 39 | 40 | 41 | def test_format_tool_message_with_different_tool(): 42 | class AnotherToolCall(BaseModel): 43 | """Another tool schema""" 44 | 45 | field1: bool 46 | field2: float 47 | 48 | tool_call = AnotherToolCall(field1=True, field2=3.14) 49 | 50 | result = format_tool_message(tool_call) 51 | 52 | assert result["type"] == "function" 53 | assert result["function"]["name"] == "AnotherToolCall" 54 | assert result["function"]["arguments"] == '{"field1": true, "field2": 3.14}' 55 | 56 | 57 | def test_format_tool_message_id_is_valid_uuid(): 58 | tool_call = MockToolCall(param1="test", param2=42) 59 | 60 | result = format_tool_message(tool_call) 61 | 62 | try: 63 | uuid.UUID(result["id"]) 64 | except ValueError: 65 | pytest.fail("The generated tool_id is not a valid UUID") 66 | 67 | 68 | def test_format_tool_message_consistent_output(): 69 | tool_call = MockToolCall(param1="test", param2=42) 70 | tool_id = "fixed-id" 71 | 72 | result1 = format_tool_message(tool_call, tool_id) 73 | result2 = format_tool_message(tool_call, tool_id) 74 | 75 | assert result1 == result2 76 | 77 | 78 | def test_format_tool_message_with_complex_model(): 79 | class ComplexToolCall(BaseIOSchema): 80 | """Mock complex tool call schema""" 81 | 82 | nested: dict 83 | list_field: list 84 | 85 | tool_call = ComplexToolCall(nested={"key": "value"}, list_field=[1, 2, 3]) 86 | 87 | result = format_tool_message(tool_call) 88 | 89 | assert result["function"]["name"] == "ComplexToolCall" 90 | assert result["function"]["arguments"] == '{"nested": {"key": "value"}, "list_field": [1, 2, 3]}' 91 | 92 | 93 | if __name__ == "__main__": 94 | pytest.main() 95 | -------------------------------------------------------------------------------- /atomic-assembler/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/atomic-assembler/README.md -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from textual import on 3 | from textual.app import App 4 | from pathlib import Path 5 | import webbrowser 6 | 7 | from atomic_assembler.screens.main_menu import MainMenuScreen 8 | from atomic_assembler.screens.file_explorer import FileExplorerScreen 9 | from atomic_assembler.screens.atomic_tool_explorer import AtomicToolExplorerScreen 10 | from atomic_assembler.constants import Mode 11 | 12 | 13 | class AtomicAssembler(App): 14 | """The main application class for Atomic Assembler.""" 15 | 16 | CSS = """ 17 | Screen { 18 | align: center middle; 19 | } 20 | """ 21 | 22 | SCREENS = { 23 | "main_menu": MainMenuScreen, 24 | "atomic_tool_explorer": AtomicToolExplorerScreen, 25 | "file_explorer": FileExplorerScreen, 26 | } 27 | 28 | def __init__(self, *args, **kwargs): 29 | super().__init__(*args, **kwargs) 30 | self.selected_path = None 31 | 32 | def on_mount(self) -> None: 33 | """Handler called when app is mounted.""" 34 | self.push_screen("main_menu") 35 | 36 | def handle_menu_action(self, action: str, **kwargs) -> None: 37 | """Handle all menu actions dynamically.""" 38 | action_map = { 39 | "browse_files": self.push_file_explorer, 40 | "browse_folders": self.push_folder_explorer, 41 | "download_tools": self.push_atomic_tool_explorer, 42 | "open_github": self.open_github, 43 | "exit": self.exit_app, 44 | } 45 | 46 | if action in action_map: 47 | action_map[action](**kwargs) 48 | else: 49 | logging.warning(f"Action '{action}' not implemented") 50 | 51 | def open_github(self) -> None: 52 | """Open the Atomic Agents GitHub page in a web browser.""" 53 | webbrowser.open("https://github.com/BrainBlend-AI/atomic-agents") 54 | 55 | def push_file_explorer(self, **kwargs): 56 | """Push the file explorer screen in file mode.""" 57 | self.push_screen( 58 | FileExplorerScreen( 59 | mode=Mode.FILE_MODE, 60 | callback=self.handle_selection, 61 | ) 62 | ) 63 | 64 | def push_folder_explorer(self, **kwargs): 65 | """Push the file explorer screen in directory mode.""" 66 | self.push_screen( 67 | FileExplorerScreen( 68 | mode=Mode.DIRECTORY_MODE, 69 | callback=self.handle_selection, 70 | ) 71 | ) 72 | 73 | def push_atomic_tool_explorer(self, **kwargs) -> None: 74 | """Push the Atomic Tool Explorer screen.""" 75 | self.push_screen(AtomicToolExplorerScreen()) 76 | 77 | def exit_app(self, **kwargs): 78 | """Exit the application.""" 79 | self.exit() 80 | 81 | def handle_selection(self, selected_path: Path) -> None: 82 | """Handle the selection of a file or folder.""" 83 | logging.debug(f"File or folder selected in main app: {selected_path}") 84 | self.selected_path = selected_path 85 | 86 | @on(FileExplorerScreen.FileSelected) 87 | def handle_file_selected(self, message: FileExplorerScreen.FileSelected) -> None: 88 | """Handle the file selected event.""" 89 | logging.debug(f"File selected in main app: {message.path}") 90 | self.selected_path = message.path 91 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/color_utils.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | 4 | def hex_to_rgb(hex_color: str) -> Tuple[int, int, int]: 5 | """ 6 | Convert a hex color string to an RGB tuple. 7 | 8 | Args: 9 | hex_color (str): The hex color string (e.g., "#FF0000"). 10 | 11 | Returns: 12 | Tuple[int, int, int]: The RGB values as a tuple. 13 | """ 14 | return tuple(int(hex_color[i : i + 2], 16) for i in (1, 3, 5)) 15 | 16 | 17 | def interpolate_color(start_color: str, end_color: str, mix_ratio: float) -> str: 18 | """ 19 | Interpolate between two colors based on a mix ratio. 20 | 21 | Args: 22 | start_color (str): The starting color in hex format (e.g., "#FF0000"). 23 | end_color (str): The ending color in hex format. 24 | mix_ratio (float): A value between 0 and 1 representing the mix ratio. 25 | 26 | Returns: 27 | str: The interpolated color in hex format. 28 | """ 29 | r1, g1, b1 = hex_to_rgb(start_color) 30 | r2, g2, b2 = hex_to_rgb(end_color) 31 | 32 | r = int(r1 * (1 - mix_ratio) + r2 * mix_ratio) 33 | g = int(g1 * (1 - mix_ratio) + g2 * mix_ratio) 34 | b = int(b1 * (1 - mix_ratio) + b2 * mix_ratio) 35 | 36 | return f"#{r:02x}{g:02x}{b:02x}" 37 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/constants.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, Any, Optional 2 | from dataclasses import dataclass 3 | from enum import Enum 4 | 5 | # Color configuration 6 | PRIMARY_COLOR: str = "#AAAA00" 7 | SECONDARY_COLOR: str = "#AA00AA" 8 | BORDER_STYLE: str = f"bold {SECONDARY_COLOR}" 9 | 10 | # Title configuration 11 | TITLE_FONT: str = "big" 12 | 13 | # Repository configuration 14 | TOOLS_SUBFOLDER: str = "atomic-forge/tools" 15 | 16 | # Base URL for GitHub repository 17 | GITHUB_BASE_URL: str = "https://github.com/BrainBlend-AI/atomic-agents.git" 18 | GITHUB_BRANCH: str = "main" 19 | 20 | 21 | @dataclass 22 | class MenuOption: 23 | """Dataclass representing a menu option.""" 24 | 25 | label: str 26 | action: str 27 | params: Optional[Dict[str, Any]] = None 28 | 29 | 30 | # Define menu options as a list of MenuOption instances 31 | MENU_OPTIONS: List[MenuOption] = [ 32 | MenuOption("Download Tools", "download_tools"), 33 | MenuOption("Open Atomic Agents on GitHub", "open_github"), 34 | MenuOption("Quit", "exit"), 35 | ] 36 | 37 | 38 | class Mode(Enum): 39 | FILE_MODE = "file_mode" 40 | DIRECTORY_MODE = "directory_mode" 41 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import argparse 3 | from atomic_assembler.app import AtomicAssembler 4 | 5 | 6 | def setup_logging(enable_logging: bool): 7 | if enable_logging: 8 | logging.basicConfig( 9 | level=logging.DEBUG, 10 | format="%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s", 11 | handlers=[ 12 | logging.FileHandler("atomic_assembler.log"), 13 | ], 14 | ) 15 | else: 16 | logging.basicConfig(level=logging.CRITICAL) 17 | 18 | 19 | logger = logging.getLogger(__name__) 20 | 21 | 22 | def main(): 23 | parser = argparse.ArgumentParser(description="Atomic Assembler") 24 | parser.add_argument("--enable-logging", action="store_true", help="Enable logging") 25 | args = parser.parse_args() 26 | 27 | setup_logging(args.enable_logging) 28 | 29 | app = AtomicAssembler() 30 | app.run() 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/screens/main_menu.py: -------------------------------------------------------------------------------- 1 | # In main_menu.py 2 | 3 | from textual.app import ComposeResult 4 | from textual.screen import Screen 5 | from textual.containers import Container, Vertical 6 | from textual import on 7 | 8 | from atomic_assembler.widgets.gradient_title import GradientTitle 9 | from atomic_assembler.widgets.menu import MenuWidget 10 | from atomic_assembler.constants import ( 11 | PRIMARY_COLOR, 12 | SECONDARY_COLOR, 13 | MENU_OPTIONS, 14 | ) 15 | 16 | 17 | class MainMenuScreen(Screen): 18 | """The main menu screen for the application.""" 19 | 20 | CSS = """ 21 | Vertical { 22 | width: 100%; 23 | height: auto; 24 | max-height: 20; 25 | align: center middle; 26 | } 27 | 28 | #title_container { 29 | width: 100%; 30 | height: auto; 31 | content-align: center top; 32 | } 33 | 34 | #menu_container { 35 | width: 100%; 36 | height: 1fr; 37 | align: center bottom; 38 | padding-bottom: 1; 39 | } 40 | 41 | MenuWidget { 42 | width: 100%; 43 | height: auto; 44 | content-align: center middle; 45 | } 46 | """ 47 | 48 | def __init__(self): 49 | """Initialize the MainMenuScreen with a menu widget.""" 50 | super().__init__() 51 | self.menu_widget = MenuWidget(MENU_OPTIONS) 52 | 53 | def compose(self) -> ComposeResult: 54 | """Compose the main layout of the screen.""" 55 | yield Vertical( 56 | Container( 57 | GradientTitle( 58 | "Atomic Assembler", 59 | start_color=PRIMARY_COLOR, 60 | end_color=SECONDARY_COLOR, 61 | ), 62 | id="title_container", 63 | ), 64 | Container( 65 | self.menu_widget, 66 | id="menu_container", 67 | ), 68 | ) 69 | 70 | @on(MenuWidget.ItemSelected) 71 | def handle_item_selected(self, event: MenuWidget.ItemSelected) -> None: 72 | """Handle the selection of a menu item.""" 73 | selected_option = MENU_OPTIONS[event.index] 74 | 75 | self.app.handle_menu_action(selected_option.action, **(selected_option.params or {})) 76 | 77 | def action_quit(self) -> None: 78 | """Quit the application.""" 79 | self.app.exit() 80 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/screens/tool_info_screen.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.containers import VerticalScroll 3 | from textual.widgets import Footer, Markdown 4 | from textual.binding import Binding 5 | from textual.screen import Screen 6 | 7 | 8 | class ToolInfoScreen(Screen): 9 | """Screen for displaying tool information.""" 10 | 11 | BINDINGS = [Binding("escape", "app.pop_screen", "Back")] 12 | 13 | def __init__(self, tool_name: str, readme_content: str): 14 | """Initialize the ToolInfoScreen with tool information.""" 15 | super().__init__() 16 | self.tool_name = tool_name 17 | self.readme_content = readme_content 18 | 19 | def compose(self) -> ComposeResult: 20 | """Compose the layout of the tool info screen.""" 21 | with VerticalScroll(): 22 | yield Markdown(self.readme_content) 23 | yield Footer() 24 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/widgets/confirmation_modal.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from textual.screen import ModalScreen 3 | from textual.widgets import Static 4 | from textual.containers import Vertical 5 | from textual.app import ComposeResult 6 | from atomic_assembler.constants import PRIMARY_COLOR 7 | from typing import Callable 8 | 9 | 10 | class ConfirmationModal(ModalScreen): 11 | """A modal widget for confirming file selection.""" 12 | 13 | def __init__(self, message: str, callback: Callable[[bool], None], mode: str = "yes_no"): 14 | super().__init__() 15 | self.message = message 16 | self.callback = callback 17 | self.mode = mode 18 | logging.info(f"ConfirmationModal initialized with message: {message} and mode: {mode}") 19 | 20 | BINDINGS = [ 21 | ("y", "confirm", "Yes"), 22 | ("n", "dismiss", "No"), 23 | ] 24 | 25 | def compose(self) -> ComposeResult: 26 | logging.debug("Composing ConfirmationModal") 27 | if self.mode == "yes_no": 28 | yield Vertical( 29 | Static(self.message, id="modal-content"), 30 | Static("[Y]es / [N]o", id="options"), 31 | id="dialog", 32 | ) 33 | elif self.mode == "continue": 34 | yield Vertical( 35 | Static(self.message, id="modal-content"), 36 | Static("Press any key to continue", id="options"), 37 | id="dialog", 38 | ) 39 | 40 | def action_confirm(self) -> None: 41 | logging.info("Confirmation action triggered") 42 | self.app.pop_screen() 43 | self.callback(True) 44 | 45 | def action_dismiss(self) -> None: 46 | logging.info("Dismissal action triggered") 47 | self.app.pop_screen() 48 | self.callback(False) 49 | 50 | def on_mount(self): 51 | logging.debug("ConfirmationModal mounted") 52 | 53 | def on_key(self, event) -> None: 54 | if self.mode == "continue": 55 | logging.info(f"Key '{event.key}' pressed in continue mode") 56 | self.app.pop_screen() 57 | self.callback(True) 58 | # Removed the call to super().on_key(event) 59 | 60 | CSS = f""" 61 | ModalScreen {{ 62 | align: center middle; 63 | }} 64 | 65 | #dialog {{ 66 | width: 40%; 67 | height: auto; 68 | border: solid {PRIMARY_COLOR}; 69 | background: $surface; 70 | }} 71 | 72 | Vertical {{ 73 | align: center middle; 74 | background: $surface; 75 | padding: 1 2; 76 | }} 77 | 78 | #modal-content {{ 79 | content-align: center middle; 80 | width: 100%; 81 | margin-bottom: 1; 82 | text-align: center; 83 | color: {PRIMARY_COLOR}; 84 | text-style: bold; 85 | }} 86 | 87 | #options {{ 88 | text-align: center; 89 | color: $text; 90 | }} 91 | 92 | Static {{ 93 | width: 100%; 94 | }} 95 | """ 96 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/widgets/generic_list.py: -------------------------------------------------------------------------------- 1 | from textual.widgets import ListView, ListItem 2 | from textual.binding import Binding 3 | from textual.message import Message 4 | from rich.text import Text 5 | from typing import Any, Callable, Optional 6 | 7 | 8 | class GenericList(ListView): 9 | """A generic ListView for displaying a list of items.""" 10 | 11 | class ItemSelected(Message): 12 | """Message emitted when an item is selected.""" 13 | 14 | def __init__(self, selected_item: Any) -> None: # Improved parameter name 15 | self.item = selected_item # Updated to match parameter name 16 | super().__init__() 17 | 18 | DEFAULT_CSS = """ 19 | GenericList { 20 | height: 1fr; 21 | border: solid #AAAA00; 22 | } 23 | 24 | GenericList > ListItem { 25 | background: transparent; 26 | } 27 | 28 | GenericList > ListItem.--highlight { 29 | color: #000000; 30 | text-style: bold; 31 | background: #AAAA00 !important; 32 | } 33 | """ 34 | 35 | BINDINGS = [ 36 | Binding("enter", "select", "Select Item", priority=True), 37 | ] 38 | 39 | def __init__(self, item_renderer: Callable[[Any], str]): 40 | """Initialize the GenericList with a custom item renderer. 41 | 42 | Args: 43 | item_renderer (Callable[[Any], str]): A function that takes an item and returns its string representation. 44 | """ 45 | super().__init__() 46 | self.item_list = [] # Renamed for clarity 47 | self.item_renderer = item_renderer 48 | self.highlighted_index = 0 49 | 50 | def update_list(self, new_items: list, highlighted_item: Optional[Any] = None): # Improved parameter name 51 | """Update the list with new items and optionally highlight one. 52 | 53 | Args: 54 | new_items (list): The list of items to display. 55 | highlighted_item (Optional[Any]): An item to highlight, if any. 56 | """ 57 | self.item_list = new_items # Renamed for clarity 58 | self.clear() 59 | for item in new_items: # Updated to match parameter name 60 | self.append(self._create_item(item)) 61 | 62 | def _create_item(self, item: Any) -> ListItem: 63 | """Create a ListItem representing a given item. 64 | 65 | Args: 66 | item (Any): The item to represent in the list. 67 | 68 | Returns: 69 | ListItem: The ListItem created for the item. 70 | """ 71 | list_item = ListItem() 72 | list_item.item_data = item 73 | 74 | def render() -> Text: 75 | """Render the item using the provided item renderer.""" 76 | return Text(self.item_renderer(item)) 77 | 78 | list_item.render = render 79 | return list_item 80 | 81 | def action_select(self): 82 | """Handle the selection action for the highlighted item.""" 83 | selected_item = self.highlighted_child # Renamed for clarity 84 | if selected_item: 85 | self.post_message(self.ItemSelected(selected_item.item_data)) 86 | 87 | def on_focus(self) -> None: 88 | self.index = self.highlighted_index 89 | 90 | def set_highlighted_index(self, index: int) -> None: 91 | self.highlighted_index = index 92 | self.blur() 93 | self.focus() 94 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/widgets/gradient_title.py: -------------------------------------------------------------------------------- 1 | import math 2 | from typing import List 3 | 4 | from pyfiglet import Figlet 5 | from rich.align import Align 6 | from rich.console import RenderResult 7 | from rich.style import Style 8 | from rich.text import Text 9 | from rich.console import Group 10 | from textual.widgets import Static 11 | 12 | from atomic_assembler.color_utils import interpolate_color 13 | 14 | 15 | class GradientTitle(Static): 16 | """A widget that displays a static gradient title.""" 17 | 18 | def __init__( 19 | self, 20 | title_text: str, 21 | font: str = "big", 22 | start_color: str = "#CCCC00", 23 | end_color: str = "#CC00CC", 24 | ): 25 | """ 26 | Initialize the GradientTitle widget. 27 | 28 | Args: 29 | title_text (str): The text to display as the title. 30 | font (str, optional): The font to use for the ASCII art. Defaults to "big". 31 | start_color (str, optional): The starting color of the gradient. Defaults to "#CCCC00". 32 | end_color (str, optional): The ending color of the gradient. Defaults to "#CC00CC". 33 | """ 34 | super().__init__() 35 | self.title_text = title_text 36 | self.font = font 37 | self.start_color = start_color 38 | self.end_color = end_color 39 | self.gradient_offset = 2 # Renamed from animation_offset 40 | 41 | self.ascii_art = Figlet(font=self.font).renderText(self.title_text) 42 | self.max_width = max(len(line) for line in self.ascii_art.splitlines()) 43 | 44 | def create_gradient_text_lines(self) -> List[Text]: 45 | """ 46 | Create text lines with a gradient effect and bold styling. 47 | 48 | Returns: 49 | List[Text]: A list of rich.text.Text objects with gradient coloring and bold styling. 50 | """ 51 | lines = self.ascii_art.splitlines() 52 | gradient_lines = [] 53 | 54 | for line_index, line in enumerate(lines): 55 | if not line.strip() and line_index not in (0, len(lines) - 1): 56 | continue 57 | 58 | mix_ratio = (math.sin(self.gradient_offset + line_index * 0.33) + 1) / 2 59 | interpolated_color = interpolate_color(self.start_color, self.end_color, mix_ratio) 60 | 61 | styled_line = Text(line, Style(color=interpolated_color, bold=True)) 62 | gradient_lines.append(styled_line) 63 | 64 | return gradient_lines 65 | 66 | def render(self) -> RenderResult: 67 | """ 68 | Render the gradient title. 69 | 70 | Returns: 71 | RenderResult: The rendered gradient title. 72 | """ 73 | gradient_lines = self.create_gradient_text_lines() 74 | 75 | centered_lines = [Align.center(line, width=self.max_width) for line in gradient_lines] 76 | 77 | return Align.center(Group(*centered_lines), vertical="middle") 78 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/widgets/icon_selection_list.py: -------------------------------------------------------------------------------- 1 | from textual.widgets import SelectionList 2 | from textual.widgets.selection_list import Selection 3 | from textual.binding import Binding 4 | from textual.message import Message 5 | from rich.text import Text 6 | 7 | 8 | class IconSelectionList(SelectionList): 9 | """A custom SelectionList that supports icons.""" 10 | 11 | class ItemSelected(Message): 12 | """Message emitted when an item is selected.""" 13 | 14 | def __init__(self, item_info: dict) -> None: 15 | self.item_info = item_info 16 | super().__init__() 17 | 18 | DEFAULT_CSS = """ 19 | IconSelectionList { 20 | height: 1fr; 21 | border: solid $accent; 22 | } 23 | 24 | IconSelectionList > .selection-list--option { 25 | background: transparent; 26 | } 27 | 28 | IconSelectionList > .selection-list--option.-highlight { 29 | color: $text; 30 | background: $accent; 31 | } 32 | """ 33 | 34 | BINDINGS = [ 35 | Binding("enter", "select", "Select", priority=True), 36 | ] 37 | 38 | def __init__(self): 39 | super().__init__() 40 | self.items = [] 41 | 42 | def update_list(self, items: list): 43 | """Update the selection list.""" 44 | self.items = items 45 | self.clear_options() 46 | for index, item in enumerate(items): 47 | self.add_option(self._create_item(item, index)) 48 | 49 | def _create_item(self, item: dict, index: int) -> Selection: 50 | """Create a Selection representing an item.""" 51 | icon = item.get("icon", "📄") 52 | label = Text(f"{icon} {item['name']}") 53 | return Selection(label, str(index)) # Use index as a string for the value 54 | 55 | def action_select(self): 56 | """Handle the selection action.""" 57 | highlighted = self.highlighted 58 | if highlighted is not None: 59 | index = int(self.get_option_at_index(highlighted).value) 60 | self.post_message(self.ItemSelected(self.items[index])) 61 | 62 | def get_selected_item(self) -> dict: 63 | """Get the currently selected item.""" 64 | highlighted = self.highlighted 65 | if highlighted is not None: 66 | index = int(self.get_option_at_index(highlighted).value) 67 | return self.items[index] 68 | return None 69 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/widgets/menu.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from textual.reactive import reactive 3 | from textual.widget import Widget 4 | from textual.message import Message 5 | from textual.binding import Binding 6 | from atomic_assembler.constants import PRIMARY_COLOR, MenuOption 7 | 8 | 9 | class MenuWidget(Widget): 10 | """A widget that displays a selectable menu.""" 11 | 12 | class ItemSelected(Message): 13 | """Emitted when an item is selected.""" 14 | 15 | def __init__(self, index: int): 16 | self.index = index 17 | super().__init__() 18 | 19 | _selected_index = reactive(0) 20 | 21 | BINDINGS = [ 22 | Binding("enter", "select", "Select item", priority=True), 23 | Binding("up", "move_up", "Move up"), 24 | Binding("down", "move_down", "Move down"), 25 | ] 26 | 27 | def __init__(self, menu_items: List[MenuOption]): 28 | """ 29 | Initialize the MenuWidget. 30 | 31 | Args: 32 | menu_items (List[MenuOption]): A list of MenuOption instances representing menu options. 33 | """ 34 | super().__init__() 35 | self._menu_items = menu_items 36 | self.can_focus = True 37 | 38 | def on_mount(self) -> None: 39 | """Set focus to this widget when mounted.""" 40 | self.focus() 41 | 42 | def render(self) -> str: 43 | """ 44 | Render the menu items with the current selection highlighted. 45 | 46 | Returns: 47 | str: The rendered menu items as a string. 48 | """ 49 | rendered_menu_items = [] 50 | for index, item in enumerate(self._menu_items): 51 | is_selected = index == self._selected_index 52 | menu_text = ( 53 | f"[{PRIMARY_COLOR} bold][ {item.label} ][/{PRIMARY_COLOR} bold]" if is_selected else f" {item.label} " 54 | ) 55 | rendered_menu_items.append(f"[center]{menu_text}[/center]") 56 | 57 | return "\n".join(rendered_menu_items) 58 | 59 | def action_move_up(self) -> None: 60 | """Move the selection up.""" 61 | self._move_selection(-1) 62 | 63 | def action_move_down(self) -> None: 64 | """Move the selection down.""" 65 | self._move_selection(1) 66 | 67 | def action_select(self) -> None: 68 | """Handle the selection of a menu item.""" 69 | self.post_message(self.ItemSelected(self._selected_index)) 70 | 71 | def _move_selection(self, direction: int) -> None: 72 | """Move the selection up or down, wrapping around if necessary.""" 73 | self._selected_index = (self._selected_index + direction) % len(self._menu_items) 74 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/widgets/new_item_input.py: -------------------------------------------------------------------------------- 1 | from textual.widgets import Input 2 | from textual.binding import Binding 3 | from textual.message import Message 4 | 5 | 6 | class NewItemInput(Input): 7 | """Configurable input field for creating new files or folders.""" 8 | 9 | class Cancelled(Message): 10 | """Emitted when the user cancels the item creation.""" 11 | 12 | class Submitted(Message): 13 | """Emitted when the user submits the item creation.""" 14 | 15 | def __init__(self, value: str): 16 | self.value = value 17 | super().__init__() 18 | 19 | DEFAULT_CSS = """ 20 | NewItemInput { 21 | dock: bottom; 22 | margin-bottom: 1; 23 | color: $text; 24 | display: none; 25 | border: solid #AAAA00 !important; 26 | } 27 | """ 28 | 29 | BINDINGS = [ 30 | Binding("enter", "submit", "Submit", show=True, priority=True), 31 | Binding("escape", "cancel", "Cancel", show=True, priority=True), 32 | ] 33 | 34 | async def action_submit(self) -> None: 35 | """Handle a submit action.""" 36 | 37 | self.post_message(self.Submitted(self.value)) 38 | 39 | async def action_cancel(self) -> None: 40 | """Handle a cancel action.""" 41 | self.post_message(self.Cancelled()) 42 | -------------------------------------------------------------------------------- /atomic-assembler/atomic_assembler/widgets/tool_info_display.py: -------------------------------------------------------------------------------- 1 | from textual.widget import Widget 2 | from textual.reactive import reactive 3 | from textual.containers import ScrollableContainer 4 | from textual.widgets import Static 5 | from textual.app import ComposeResult 6 | from atomic_assembler.constants import ( 7 | PRIMARY_COLOR, 8 | ) 9 | 10 | 11 | class ToolInfoDisplay(Widget): 12 | """Widget for displaying tool information.""" 13 | 14 | tool_info = reactive({}) 15 | 16 | DEFAULT_CSS = f""" 17 | ToolInfoDisplay {{ 18 | width: 100%; 19 | height: 100%; 20 | layout: grid; 21 | grid-size: 1; 22 | grid-rows: auto 1fr; 23 | }} 24 | 25 | #title {{ 26 | width: 100%; 27 | padding: 1 2; 28 | color: $text; 29 | text-align: center; 30 | text-style: bold; 31 | border: solid {PRIMARY_COLOR}; 32 | }} 33 | 34 | #content {{ 35 | width: 100%; 36 | height: 100%; 37 | }} 38 | 39 | .tool-details, .env-vars {{ 40 | border: solid {PRIMARY_COLOR}; 41 | padding: 1 2; 42 | margin: 1 0; 43 | }} 44 | 45 | #tool-header, .env-vars-header {{ 46 | text-align: left; 47 | text-style: bold; 48 | margin-bottom: 1; 49 | }} 50 | 51 | .env-var-name {{ 52 | color: {PRIMARY_COLOR}; 53 | text-style: bold; 54 | }} 55 | 56 | .env-var-description, .env-var-default {{ 57 | padding-left: 1; 58 | }} 59 | """ 60 | 61 | def __init__(self, tool_info: dict): 62 | super().__init__() 63 | self.tool_info = tool_info 64 | 65 | def compose(self) -> ComposeResult: 66 | """Compose the layout of the tool info display.""" 67 | yield Static("Tool Information", id="title") 68 | 69 | with ScrollableContainer(id="content"): 70 | # Tool Details Section 71 | with Static(classes="tool-details"): 72 | yield Static("🔧 Tool Details", id="tool-header") 73 | yield Static(f"Name: {self.tool_info.get('tool_name', 'N/A')}", id="tool-name") 74 | yield Static( 75 | f"Description: {self.tool_info.get('tool_description', 'No description available.')}", 76 | id="tool-description", 77 | ) 78 | 79 | # Environment Variables Section 80 | env_vars = self.tool_info.get("env_vars", {}) 81 | if env_vars: 82 | with Static(classes="env-vars"): 83 | yield Static("📌 Environment Variables", classes="env-vars-header") 84 | for var, var_info in env_vars.items(): 85 | with Static(classes="env-var"): 86 | yield Static(f"{var}", classes="env-var-name") 87 | yield Static( 88 | f"Description: {var_info.get('description', 'No description available.')}", 89 | classes="env-var-description", 90 | ) 91 | yield Static( 92 | f"Default Value: {var_info.get('default', 'No default value')}", 93 | classes="env-var-default", 94 | ) 95 | else: 96 | yield Static("No environment variables available.", classes="env-var-description") 97 | 98 | def watch_tool_info(self, new_info: dict) -> None: 99 | """React to changes in the tool_info.""" 100 | self.refresh() 101 | -------------------------------------------------------------------------------- /atomic-examples/basic-multimodal/README.md: -------------------------------------------------------------------------------- 1 | # Basic Multimodal Example 2 | 3 | This example demonstrates how to use the Atomic Agents framework to analyze images with text, specifically focusing on extracting structured information from nutrition labels using GPT-4 Vision capabilities. 4 | 5 | ## Features 6 | 7 | 1. Image Analysis: Process nutrition label images using GPT-4 Vision 8 | 2. Structured Data Extraction: Convert visual information into structured Pydantic models 9 | 3. Multi-Image Processing: Analyze multiple nutrition labels simultaneously 10 | 4. Comprehensive Nutritional Data: Extract detailed nutritional information including: 11 | - Basic nutritional facts (calories, fats, proteins, etc.) 12 | - Serving size information 13 | - Vitamin and mineral content 14 | - Product details 15 | 16 | ## Getting Started 17 | 18 | 1. Clone the main Atomic Agents repository: 19 | ```bash 20 | git clone https://github.com/BrainBlend-AI/atomic-agents 21 | ``` 22 | 23 | 2. Navigate to the basic-multimodal directory: 24 | ```bash 25 | cd atomic-agents/atomic-examples/basic-multimodal 26 | ``` 27 | 28 | 3. Install dependencies using Poetry: 29 | ```bash 30 | poetry install 31 | ``` 32 | 33 | 4. Set up environment variables: 34 | Create a `.env` file in the `basic-multimodal` directory with the following content: 35 | ```env 36 | OPENAI_API_KEY=your_openai_api_key 37 | ``` 38 | Replace `your_openai_api_key` with your actual OpenAI API key. 39 | 40 | 5. Run the example: 41 | ```bash 42 | poetry run python basic_multimodal/main.py 43 | ``` 44 | 45 | ## Components 46 | 47 | ### 1. Nutrition Label Schema (`NutritionLabel`) 48 | Defines the structure for storing nutrition information, including: 49 | - Macronutrients (fats, proteins, carbohydrates) 50 | - Micronutrients (vitamins and minerals) 51 | - Serving information 52 | - Product details 53 | 54 | ### 2. Input/Output Schemas 55 | - `NutritionAnalysisInput`: Handles input images and analysis instructions 56 | - `NutritionAnalysisOutput`: Structures the extracted nutrition information 57 | 58 | ### 3. Nutrition Analyzer Agent 59 | A specialized agent configured with: 60 | - GPT-4 Vision capabilities 61 | - Custom system prompts for nutrition label analysis 62 | - Structured data validation 63 | 64 | ## Example Usage 65 | 66 | The example includes test images in the `test_images` directory: 67 | - `nutrition_label_1.png`: Example nutrition label image 68 | - `nutrition_label_2.jpg`: Another example nutrition label image 69 | 70 | Running the example will: 71 | 1. Load the test images 72 | 2. Process them through the nutrition analyzer 73 | 3. Display structured nutritional information for each label 74 | 75 | ## Customization 76 | 77 | You can modify the example by: 78 | 1. Adding your own nutrition label images to the `test_images` directory 79 | 2. Adjusting the `NutritionLabel` schema to capture additional information 80 | 3. Modifying the system prompt to focus on specific aspects of nutrition labels 81 | 82 | ## Contributing 83 | 84 | Contributions are welcome! Please fork the repository and submit a pull request with your enhancements or bug fixes. 85 | 86 | ## License 87 | 88 | This project is licensed under the MIT License. See the [LICENSE](../../LICENSE) file for details. 89 | -------------------------------------------------------------------------------- /atomic-examples/basic-multimodal/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "basic-multimodal" 3 | version = "1.0.0" 4 | description = "Basic Multimodal Quickstart example for Atomic Agents" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | atomic-agents = {path = "../..", develop = true} 11 | instructor = ">=1.3.4,<2.0.0" 12 | openai = ">=1.35.12,<2.0.0" 13 | groq = ">=0.11.0,<1.0.0" 14 | mistralai = ">=1.1.0,<2.0.0" 15 | anthropic = ">=0.39.0,<1.0.0" 16 | 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /atomic-examples/basic-multimodal/test_images/nutrition_label_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/atomic-examples/basic-multimodal/test_images/nutrition_label_1.png -------------------------------------------------------------------------------- /atomic-examples/basic-multimodal/test_images/nutrition_label_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/atomic-examples/basic-multimodal/test_images/nutrition_label_2.jpg -------------------------------------------------------------------------------- /atomic-examples/deep-research/README.md: -------------------------------------------------------------------------------- 1 | # Deep Research Agent 2 | 3 | This directory contains the Deep Research Agent example for the Atomic Agents project. This example demonstrates how to create an intelligent research assistant that performs web searches and provides detailed answers with relevant follow-up questions. 4 | 5 | ## Getting Started 6 | 7 | 1. **Clone the main Atomic Agents repository:** 8 | ```bash 9 | git clone https://github.com/BrainBlend-AI/atomic-agents 10 | ``` 11 | 12 | 2. **Navigate to the Deep Research directory:** 13 | ```bash 14 | cd atomic-agents/atomic-examples/deep-research 15 | ``` 16 | 17 | 3. **Install dependencies using Poetry:** 18 | ```bash 19 | poetry install 20 | ``` 21 | 22 | 4. **Set up environment variables:** 23 | Create a `.env` file in the `deep-research` directory with: 24 | ```env 25 | OPENAI_API_KEY=your_openai_api_key 26 | ``` 27 | 28 | 5. **Set up SearxNG:** 29 | - Install SearxNG from the [official repository](https://github.com/searxng/searxng) 30 | - Default configuration expects SearxNG at `http://localhost:8080` 31 | 32 | 6. **Run the Deep Research Agent:** 33 | ```bash 34 | poetry run python deep_research/main.py 35 | ``` 36 | 37 | ## File Explanation 38 | 39 | ### 1. Agents (`agents/`) 40 | 41 | - **ChoiceAgent** (`choice_agent.py`): Determines when new searches are needed 42 | - **QueryAgent** (`query_agent.py`): Generates optimized search queries 43 | - **QuestionAnsweringAgent** (`qa_agent.py`): Processes information and generates responses 44 | 45 | ### 2. Tools (`tools/`) 46 | 47 | - **SearxNG Search** (`searxng_search.py`): Performs web searches across multiple engines 48 | - **Webpage Scraper** (`webpage_scraper.py`): Extracts and processes web content 49 | 50 | ### 3. Main (`main.py`) 51 | 52 | The entry point for the Deep Research Agent application. It orchestrates the research process: 53 | - Deciding when to perform new searches 54 | - Generating and executing search queries 55 | - Processing information and providing answers 56 | - Suggesting relevant follow-up questions 57 | 58 | ## Example Usage 59 | 60 | The agent can handle various research queries and provides: 61 | - Detailed answers based on current web information 62 | - Relevant citations and sources 63 | - Specific follow-up questions for deeper exploration 64 | - Context-aware responses that build on previous interactions 65 | 66 | ## Contributing 67 | 68 | Contributions are welcome! Please fork the repository and submit a pull request with your improvements. 69 | 70 | ## License 71 | 72 | This project is licensed under the MIT License. See the [LICENSE](../../LICENSE) file for details. 73 | -------------------------------------------------------------------------------- /atomic-examples/deep-research/deep_research/agents/choice_agent.py: -------------------------------------------------------------------------------- 1 | import instructor 2 | import openai 3 | from pydantic import Field 4 | from atomic_agents.agents.base_agent import BaseIOSchema, BaseAgent, BaseAgentConfig 5 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 6 | 7 | from deep_research.config import ChatConfig 8 | 9 | 10 | class ChoiceAgentInputSchema(BaseIOSchema): 11 | """Input schema for the ChoiceAgent.""" 12 | 13 | user_message: str = Field(..., description="The user's latest message or question") 14 | decision_type: str = Field(..., description="Explanation of the type of decision to make") 15 | 16 | 17 | class ChoiceAgentOutputSchema(BaseIOSchema): 18 | """Output schema for the ChoiceAgent.""" 19 | 20 | reasoning: str = Field(..., description="Detailed explanation of the decision-making process") 21 | decision: bool = Field(..., description="The final decision based on the analysis") 22 | 23 | 24 | choice_agent = BaseAgent( 25 | BaseAgentConfig( 26 | client=instructor.from_openai(openai.OpenAI(api_key=ChatConfig.api_key)), 27 | model=ChatConfig.model, 28 | system_prompt_generator=SystemPromptGenerator( 29 | background=[ 30 | "You are a decision-making agent that determines whether a new web search is needed to answer the user's question.", 31 | "Your primary role is to analyze whether the existing context contains sufficient, up-to-date information to answer the question.", 32 | "You must output a clear TRUE/FALSE decision - TRUE if a new search is needed, FALSE if existing context is sufficient.", 33 | ], 34 | steps=[ 35 | "1. Analyze the user's question to determine whether or not an answer warrants a new search", 36 | "2. Review the available web search results", 37 | "3. Determine if existing information is sufficient and relevant", 38 | "4. Make a binary decision: TRUE for new search, FALSE for using existing context", 39 | ], 40 | output_instructions=[ 41 | "Your reasoning must clearly state WHY you need or don't need new information", 42 | "If the web search context is empty or irrelevant, always decide TRUE for new search", 43 | "If the question is time-sensitive, check the current date to ensure context is recent", 44 | "For ambiguous cases, prefer to gather fresh information", 45 | "Your decision must match your reasoning - don't contradict yourself", 46 | ], 47 | ), 48 | input_schema=ChoiceAgentInputSchema, 49 | output_schema=ChoiceAgentOutputSchema, 50 | ) 51 | ) 52 | 53 | 54 | if __name__ == "__main__": 55 | # Example usage for search decision 56 | search_example = choice_agent.run( 57 | ChoiceAgentInputSchema(user_message="Who won the nobel prize in physics in 2024?", decision_type="needs_search") 58 | ) 59 | print(search_example) 60 | -------------------------------------------------------------------------------- /atomic-examples/deep-research/deep_research/agents/qa_agent.py: -------------------------------------------------------------------------------- 1 | import instructor 2 | import openai 3 | from pydantic import Field 4 | from atomic_agents.agents.base_agent import BaseIOSchema, BaseAgent, BaseAgentConfig 5 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 6 | 7 | from deep_research.config import ChatConfig 8 | 9 | 10 | class QuestionAnsweringAgentInputSchema(BaseIOSchema): 11 | """This is the input schema for the QuestionAnsweringAgent.""" 12 | 13 | question: str = Field(..., description="The question to answer.") 14 | 15 | 16 | class QuestionAnsweringAgentOutputSchema(BaseIOSchema): 17 | """This is the output schema for the QuestionAnsweringAgent.""" 18 | 19 | answer: str = Field(..., description="The answer to the question.") 20 | follow_up_questions: list[str] = Field( 21 | ..., 22 | description=( 23 | "Specific questions about the topic that would help the user learn more details about the subject matter. " 24 | "For example, if discussing a Nobel Prize winner, suggest questions about their research, impact, or " 25 | "related scientific concepts." 26 | ), 27 | ) 28 | 29 | 30 | question_answering_agent = BaseAgent( 31 | BaseAgentConfig( 32 | client=instructor.from_openai(openai.OpenAI(api_key=ChatConfig.api_key)), 33 | model=ChatConfig.model, 34 | system_prompt_generator=SystemPromptGenerator( 35 | background=[ 36 | "You are an expert question answering agent focused on providing factual information and encouraging deeper topic exploration.", 37 | "For general greetings or non-research questions, provide relevant questions about the system's capabilities and research functions.", 38 | ], 39 | steps=[ 40 | "Analyze the question and identify the core topic", 41 | "Answer the question using available information", 42 | "For topic-specific questions, generate follow-up questions that explore deeper aspects of the same topic", 43 | "For general queries about the system, suggest questions about research capabilities and functionality", 44 | ], 45 | output_instructions=[ 46 | "Answer in a direct, informative manner", 47 | "NEVER generate generic conversational follow-ups like 'How are you?' or 'What would you like to know?'", 48 | "For topic questions, follow-up questions MUST be about specific aspects of that topic", 49 | "For system queries, follow-up questions should be about specific research capabilities", 50 | "Example good follow-ups for a Nobel Prize question:", 51 | "- What specific discoveries led to their Nobel Prize?", 52 | "- How has their research influenced their field?", 53 | "- What other scientists collaborated on this research?", 54 | "Example good follow-ups for system queries:", 55 | "- What types of sources do you use for research?", 56 | "- How do you verify information accuracy?", 57 | "- What are the limitations of your search capabilities?", 58 | ], 59 | ), 60 | input_schema=QuestionAnsweringAgentInputSchema, 61 | output_schema=QuestionAnsweringAgentOutputSchema, 62 | ) 63 | ) 64 | -------------------------------------------------------------------------------- /atomic-examples/deep-research/deep_research/agents/query_agent.py: -------------------------------------------------------------------------------- 1 | from deep_research.config import ChatConfig 2 | import instructor 3 | import openai 4 | from pydantic import Field 5 | from atomic_agents.agents.base_agent import BaseIOSchema, BaseAgent, BaseAgentConfig 6 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 7 | 8 | from deep_research.tools.searxng_search import SearxNGSearchTool 9 | 10 | 11 | class QueryAgentInputSchema(BaseIOSchema): 12 | """This is the input schema for the QueryAgent.""" 13 | 14 | instruction: str = Field(..., description="A detailed instruction or request to generate search engine queries for.") 15 | num_queries: int = Field(..., description="The number of search queries to generate.") 16 | 17 | 18 | query_agent = BaseAgent( 19 | BaseAgentConfig( 20 | client=instructor.from_openai(openai.OpenAI(api_key=ChatConfig.api_key)), 21 | model=ChatConfig.model, 22 | system_prompt_generator=SystemPromptGenerator( 23 | background=[ 24 | ( 25 | "You are an expert search engine query generator with a deep understanding of which" 26 | "queries will maximize the number of relevant results." 27 | ) 28 | ], 29 | steps=[ 30 | "Analyze the given instruction to identify key concepts and aspects that need to be researched", 31 | "For each aspect, craft a search query using appropriate search operators and syntax", 32 | "Ensure queries cover different angles of the topic (technical, practical, comparative, etc.)", 33 | ], 34 | output_instructions=[ 35 | "Return exactly the requested number of queries", 36 | "Format each query like a search engine query, not a natural language question", 37 | "Each query should be a concise string of keywords and operators", 38 | ], 39 | ), 40 | input_schema=QueryAgentInputSchema, 41 | output_schema=SearxNGSearchTool.input_schema, 42 | ) 43 | ) 44 | -------------------------------------------------------------------------------- /atomic-examples/deep-research/deep_research/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dataclasses import dataclass 3 | from typing import Set 4 | 5 | 6 | def get_api_key() -> str: 7 | """Retrieve API key from environment or raise error""" 8 | api_key = os.getenv("OPENAI_API_KEY") 9 | if not api_key: 10 | raise ValueError("API key not found. Please set the OPENAI_API_KEY environment variable.") 11 | return api_key 12 | 13 | 14 | @dataclass 15 | class ChatConfig: 16 | """Configuration for the chat application""" 17 | 18 | api_key: str = get_api_key() # This becomes a class variable 19 | model: str = "gpt-4o-mini" 20 | exit_commands: Set[str] = frozenset({"/exit", "/quit"}) 21 | 22 | def __init__(self): 23 | # Prevent instantiation 24 | raise TypeError("ChatConfig is not meant to be instantiated") 25 | -------------------------------------------------------------------------------- /atomic-examples/deep-research/deep_research/context_providers.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import List 4 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptContextProviderBase 5 | 6 | 7 | @dataclass 8 | class ContentItem: 9 | content: str 10 | url: str 11 | 12 | 13 | class ScrapedContentContextProvider(SystemPromptContextProviderBase): 14 | def __init__(self, title: str): 15 | super().__init__(title=title) 16 | self.content_items: List[ContentItem] = [] 17 | 18 | def get_info(self) -> str: 19 | return "\n\n".join( 20 | [ 21 | f"Source {idx}:\nURL: {item.url}\nContent:\n{item.content}\n{'-' * 80}" 22 | for idx, item in enumerate(self.content_items, 1) 23 | ] 24 | ) 25 | 26 | 27 | class CurrentDateContextProvider(SystemPromptContextProviderBase): 28 | def __init__(self, title: str, date_format: str = "%A %B %d, %Y"): 29 | super().__init__(title=title) 30 | self.date_format = date_format 31 | 32 | def get_info(self) -> str: 33 | return f"The current date in the format {self.date_format} is {datetime.now().strftime(self.date_format)}." 34 | -------------------------------------------------------------------------------- /atomic-examples/deep-research/mermaid.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | flowchart TD 3 | %% Decision Flow Diagram 4 | subgraph DecisionFlow["Research Decision Flow"] 5 | Start([User Question]) --> B{Need Search?} 6 | B -->|Yes| C[Generate Search Queries] 7 | C --> D[Perform Web Search] 8 | D --> E[Scrape Webpages] 9 | E --> F[Update Context] 10 | F --> G[Generate Answer] 11 | B -->|No| G 12 | G --> H[Show Answer & Follow-ups] 13 | H --> End([End]) 14 | end 15 | 16 | classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px; 17 | classDef decision fill:#ff9800,stroke:#f57c00,color:#fff; 18 | classDef process fill:#4caf50,stroke:#388e3c,color:#fff; 19 | classDef terminator fill:#9c27b0,stroke:#7b1fa2,color:#fff; 20 | 21 | class B decision; 22 | class C,D,E,F,G process; 23 | class Start,End terminator; 24 | 25 | ``` 26 | 27 | ```mermaid 28 | graph TD 29 | %% System Architecture Diagram 30 | subgraph Agents["AI Agents"] 31 | CA[ChoiceAgent] 32 | QA[QueryAgent] 33 | AA[AnswerAgent] 34 | end 35 | 36 | subgraph Tools["External Tools"] 37 | ST[SearxNG Search] 38 | WS[Webpage Scraper] 39 | end 40 | 41 | subgraph Context["Context Providers"] 42 | SC[Scraped Content] 43 | CD[Current Date] 44 | end 45 | 46 | %% Connections 47 | User -->|Question| CA 48 | CA -->|Search Request| QA 49 | QA -->|Queries| ST 50 | ST -->|URLs| WS 51 | WS -->|Content| SC 52 | SC -.->|Context| CA & QA & AA 53 | CD -.->|Date Info| CA & QA & AA 54 | CA -->|Direct Answer| AA 55 | AA -->|Response| User 56 | 57 | %% Styling 58 | classDef agent fill:#4CAF50,stroke:#2E7D32,color:#fff; 59 | classDef tool fill:#FF9800,stroke:#EF6C00,color:#fff; 60 | classDef context fill:#F44336,stroke:#C62828,color:#fff; 61 | classDef user fill:#9C27B0,stroke:#6A1B9A,color:#fff; 62 | 63 | class CA,QA,AA agent; 64 | class ST,WS tool; 65 | class SC,CD context; 66 | class User user; 67 | 68 | ``` 69 | -------------------------------------------------------------------------------- /atomic-examples/deep-research/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "deep-research" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = ">=3.11,<4.0" 10 | atomic-agents = {path = "../..", develop = true} 11 | requests = "^2.32.3" 12 | beautifulsoup4 = "^4.12.3" 13 | markdownify = "^0.13.1" 14 | readability-lxml = "^0.8.1" 15 | lxml-html-clean = "^0.4.0" 16 | lxml = "^5.3.0" 17 | python-dotenv = ">=1.0.1,<2.0.0" 18 | openai = ">=1.35.12,<2.0.0" 19 | 20 | 21 | [build-system] 22 | requires = ["poetry-core"] 23 | build-backend = "poetry.core.masonry.api" 24 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-client/example_client/main.py: -------------------------------------------------------------------------------- 1 | # pyright: reportInvalidTypeForm=false 2 | """ 3 | Main entry point for the MCP Agent example client. 4 | This file serves as a launcher that can run either the STDIO or SSE transport version of the MCP agent. 5 | """ 6 | 7 | import argparse 8 | import sys 9 | 10 | 11 | def main(): 12 | """Parse command line arguments and launch the appropriate implementation.""" 13 | parser = argparse.ArgumentParser(description="MCP Agent example client") 14 | parser.add_argument( 15 | "--transport", 16 | choices=["stdio", "sse"], 17 | default="stdio", 18 | help="Transport method to use for MCP communication (stdio or sse)", 19 | ) 20 | args = parser.parse_args() 21 | 22 | if args.transport == "stdio": 23 | # Import and run STDIO implementation 24 | try: 25 | from example_client.main_stdio import main as stdio_main 26 | 27 | stdio_main() 28 | except ImportError as e: 29 | print(f"Failed to import STDIO implementation: {e}") 30 | sys.exit(1) 31 | else: 32 | # Import and run SSE implementation 33 | try: 34 | from example_client.main_sse import main as sse_main 35 | 36 | sse_main() 37 | except ImportError as e: 38 | print(f"Failed to import SSE implementation: {e}") 39 | sys.exit(1) 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-client/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "example-client" 3 | version = "0.1.0" 4 | description = "Example: Choosing the right MCP tool for a user query using the MCP Tool Factory." 5 | authors = ["Your Name "] 6 | 7 | [tool.poetry.dependencies] 8 | atomic-agents = { path = "../../../", develop = true } 9 | example-mcp-server = { path = "../example-mcp-server", develop = true } 10 | python = ">=3.10,<3.13" 11 | pydantic = ">=2.10.3,<3.0.0" 12 | rich = ">=13.0.0" 13 | openai = ">=1.0.0" 14 | mcp = {extras = ["cli"], version = "^1.6.0"} 15 | 16 | [build-system] 17 | requires = ["poetry-core>=1.0.0"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/__init__.py: -------------------------------------------------------------------------------- 1 | """example-mcp-server package.""" 2 | 3 | __version__ = "0.1.0" 4 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | """Interface definitions for the application.""" 2 | 3 | from .tool import Tool, BaseToolInput, ToolResponse, ToolContent 4 | from .resource import Resource 5 | 6 | __all__ = ["Tool", "BaseToolInput", "ToolResponse", "ToolContent", "Resource"] 7 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/interfaces/resource.py: -------------------------------------------------------------------------------- 1 | """Interfaces for resource abstractions.""" 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import List, Optional, ClassVar 5 | from pydantic import BaseModel, Field 6 | 7 | 8 | class ResourceContent(BaseModel): 9 | """Model for content in resource responses.""" 10 | 11 | type: str = Field(default="text") 12 | text: str 13 | uri: str 14 | mime_type: Optional[str] = None 15 | 16 | 17 | class ResourceResponse(BaseModel): 18 | """Model for resource responses.""" 19 | 20 | contents: List[ResourceContent] 21 | 22 | 23 | class Resource(ABC): 24 | """Abstract base class for all resources.""" 25 | 26 | name: ClassVar[str] 27 | description: ClassVar[str] 28 | uri: ClassVar[str] 29 | mime_type: ClassVar[Optional[str]] = None 30 | 31 | @abstractmethod 32 | async def read(self) -> ResourceResponse: 33 | """Read the resource content.""" 34 | pass 35 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/interfaces/tool.py: -------------------------------------------------------------------------------- 1 | """Interfaces for tool abstractions.""" 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import Any, Dict, List, Optional, ClassVar, Type, TypeVar 5 | from pydantic import BaseModel, Field 6 | 7 | # Define a type variable for generic model support 8 | T = TypeVar("T", bound=BaseModel) 9 | 10 | 11 | class BaseToolInput(BaseModel): 12 | """Base class for tool input models.""" 13 | 14 | model_config = {"extra": "forbid"} # Equivalent to additionalProperties: false 15 | 16 | 17 | class ToolContent(BaseModel): 18 | """Model for content in tool responses.""" 19 | 20 | type: str = Field(default="text", description="Content type identifier") 21 | 22 | # Common fields for all content types 23 | content_id: Optional[str] = Field(None, description="Optional content identifier") 24 | 25 | # Type-specific fields (using discriminated unions pattern) 26 | # Text content 27 | text: Optional[str] = Field(None, description="Text content when type='text'") 28 | 29 | # JSON content (for structured data) 30 | json_data: Optional[Dict[str, Any]] = Field(None, description="JSON data when type='json'") 31 | 32 | # Model content (will be converted to json_data during serialization) 33 | model: Optional[Any] = Field(None, exclude=True, description="Pydantic model instance") 34 | 35 | # Add more content types as needed (e.g., binary, image, etc.) 36 | 37 | def model_post_init(self, __context: Any) -> None: 38 | """Post-initialization hook to handle model conversion.""" 39 | if self.model and not self.json_data: 40 | # Convert model to json_data 41 | if isinstance(self.model, BaseModel): 42 | self.json_data = self.model.model_dump() 43 | if not self.type or self.type == "text": 44 | self.type = "json" 45 | 46 | 47 | class ToolResponse(BaseModel): 48 | """Model for tool responses.""" 49 | 50 | content: List[ToolContent] 51 | 52 | @classmethod 53 | def from_model(cls, model: BaseModel) -> "ToolResponse": 54 | """Create a ToolResponse from a Pydantic model. 55 | 56 | This makes it easier to return structured data directly. 57 | 58 | Args: 59 | model: A Pydantic model instance to convert 60 | 61 | Returns: 62 | A ToolResponse with the model data in JSON format 63 | """ 64 | return cls(content=[ToolContent(type="json", json_data=model.model_dump(), model=model)]) 65 | 66 | @classmethod 67 | def from_text(cls, text: str) -> "ToolResponse": 68 | """Create a ToolResponse from plain text. 69 | 70 | Args: 71 | text: The text content 72 | 73 | Returns: 74 | A ToolResponse with text content 75 | """ 76 | return cls(content=[ToolContent(type="text", text=text)]) 77 | 78 | 79 | class Tool(ABC): 80 | """Abstract base class for all tools.""" 81 | 82 | name: ClassVar[str] 83 | description: ClassVar[str] 84 | input_model: ClassVar[Type[BaseToolInput]] 85 | output_model: ClassVar[Optional[Type[BaseModel]]] = None 86 | 87 | @abstractmethod 88 | async def execute(self, input_data: BaseToolInput) -> ToolResponse: 89 | """Execute the tool with given arguments.""" 90 | pass 91 | 92 | def get_schema(self) -> Dict[str, Any]: 93 | """Get JSON schema for the tool.""" 94 | schema = { 95 | "name": self.name, 96 | "description": self.description, 97 | "input": self.input_model.model_json_schema(), 98 | } 99 | 100 | if self.output_model: 101 | schema["output"] = self.output_model.model_json_schema() 102 | 103 | return schema 104 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/resources/__init__.py: -------------------------------------------------------------------------------- 1 | """Resource exports.""" 2 | 3 | __all__ = [] 4 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/server.py: -------------------------------------------------------------------------------- 1 | """example-mcp-server MCP Server unified entry point.""" 2 | 3 | import argparse 4 | import sys 5 | 6 | 7 | def main(): 8 | """Entry point for the server.""" 9 | parser = argparse.ArgumentParser(description="example-mcp-server MCP Server") 10 | parser.add_argument( 11 | "--mode", 12 | type=str, 13 | required=True, 14 | choices=["stdio", "sse"], 15 | help="Server mode: stdio for standard I/O or sse for HTTP Server-Sent Events", 16 | ) 17 | 18 | # SSE specific arguments 19 | parser.add_argument("--host", default="0.0.0.0", help="Host to bind to (SSE mode only)") 20 | parser.add_argument("--port", type=int, default=6969, help="Port to listen on (SSE mode only)") 21 | parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development (SSE mode only)") 22 | 23 | args = parser.parse_args() 24 | 25 | if args.mode == "stdio": 26 | # Import and run the stdio server 27 | from example_mcp_server.server_stdio import main as stdio_main 28 | 29 | stdio_main() 30 | elif args.mode == "sse": 31 | # Import and run the SSE server with appropriate arguments 32 | from example_mcp_server.server_sse import main as sse_main 33 | 34 | sys.argv = [sys.argv[0], "--host", args.host, "--port", str(args.port)] 35 | if args.reload: 36 | sys.argv.append("--reload") 37 | sse_main() 38 | else: 39 | parser.print_help() 40 | sys.exit(1) 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/server_sse.py: -------------------------------------------------------------------------------- 1 | """example-mcp-server MCP Server implementation with SSE transport.""" 2 | 3 | from mcp.server.fastmcp import FastMCP 4 | from starlette.applications import Starlette 5 | from mcp.server.sse import SseServerTransport 6 | from starlette.requests import Request 7 | from starlette.routing import Mount, Route 8 | from mcp.server import Server 9 | import uvicorn 10 | from typing import List 11 | from starlette.middleware import Middleware 12 | from starlette.middleware.cors import CORSMiddleware 13 | 14 | from example_mcp_server.services.tool_service import ToolService 15 | from example_mcp_server.services.resource_service import ResourceService 16 | from example_mcp_server.interfaces.tool import Tool 17 | from example_mcp_server.interfaces.resource import Resource 18 | from example_mcp_server.tools import AddNumbersTool, SubtractNumbersTool, MultiplyNumbersTool, DivideNumbersTool 19 | 20 | 21 | def get_available_tools() -> List[Tool]: 22 | """Get list of all available tools.""" 23 | return [ 24 | AddNumbersTool(), 25 | SubtractNumbersTool(), 26 | MultiplyNumbersTool(), 27 | DivideNumbersTool(), 28 | ] 29 | 30 | 31 | def get_available_resources() -> List[Resource]: 32 | """Get list of all available resources.""" 33 | return [] 34 | 35 | 36 | def create_starlette_app(mcp_server: Server) -> Starlette: 37 | """Create a Starlette application that can serve the provided mcp server with SSE.""" 38 | sse = SseServerTransport("/messages/") 39 | 40 | async def handle_sse(request: Request) -> None: 41 | async with sse.connect_sse( 42 | request.scope, 43 | request.receive, 44 | request._send, # noqa: SLF001 45 | ) as (read_stream, write_stream): 46 | await mcp_server.run( 47 | read_stream, 48 | write_stream, 49 | mcp_server.create_initialization_options(), 50 | ) 51 | 52 | middleware = [ 53 | Middleware( 54 | CORSMiddleware, 55 | allow_origins=["*"], 56 | allow_methods=["*"], 57 | allow_headers=["*"], 58 | allow_credentials=True, 59 | ) 60 | ] 61 | 62 | return Starlette( 63 | routes=[ 64 | Route("/sse", endpoint=handle_sse), 65 | Mount("/messages/", app=sse.handle_post_message), 66 | ], 67 | middleware=middleware, 68 | ) 69 | 70 | 71 | # Initialize FastMCP server with SSE 72 | mcp = FastMCP("example-mcp-server") 73 | tool_service = ToolService() 74 | resource_service = ResourceService() 75 | 76 | # Register all tools and their MCP handlers 77 | tool_service.register_tools(get_available_tools()) 78 | tool_service.register_mcp_handlers(mcp) 79 | 80 | # Register all resources and their MCP handlers 81 | resource_service.register_resources(get_available_resources()) 82 | resource_service.register_mcp_handlers(mcp) 83 | 84 | # Get the MCP server 85 | mcp_server = mcp._mcp_server # noqa: WPS437 86 | 87 | # Create the Starlette app 88 | app = create_starlette_app(mcp_server) 89 | 90 | # Export the app 91 | __all__ = ["app"] 92 | 93 | 94 | def main(): 95 | """Entry point for the server.""" 96 | import argparse 97 | 98 | parser = argparse.ArgumentParser(description="Run MCP SSE-based server") 99 | parser.add_argument("--host", default="0.0.0.0", help="Host to bind to") 100 | parser.add_argument("--port", type=int, default=6969, help="Port to listen on") 101 | parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development") 102 | args = parser.parse_args() 103 | 104 | # Run the server with auto-reload if enabled 105 | uvicorn.run( 106 | "example_mcp_server.server_sse:app", # Use the app from server_sse.py directly 107 | host=args.host, 108 | port=args.port, 109 | reload=args.reload, 110 | reload_dirs=["example_mcp_server"], # Watch this directory for changes 111 | timeout_graceful_shutdown=5, # Add timeout 112 | ) 113 | 114 | 115 | if __name__ == "__main__": 116 | main() 117 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/server_stdio.py: -------------------------------------------------------------------------------- 1 | """example-mcp-server MCP Server implementation.""" 2 | 3 | from mcp.server.fastmcp import FastMCP 4 | from typing import List 5 | 6 | from example_mcp_server.services.tool_service import ToolService 7 | from example_mcp_server.services.resource_service import ResourceService 8 | from example_mcp_server.interfaces.tool import Tool 9 | from example_mcp_server.interfaces.resource import Resource 10 | 11 | # from example_mcp_server.tools import HelloWorldTool # Removed 12 | from example_mcp_server.tools import ( # Added imports for new tools 13 | AddNumbersTool, 14 | SubtractNumbersTool, 15 | MultiplyNumbersTool, 16 | DivideNumbersTool, 17 | ) 18 | 19 | 20 | def get_available_tools() -> List[Tool]: 21 | """Get list of all available tools.""" 22 | return [ 23 | # HelloWorldTool(), # Removed 24 | AddNumbersTool(), 25 | SubtractNumbersTool(), 26 | MultiplyNumbersTool(), 27 | DivideNumbersTool(), 28 | # Add more tools here as you create them 29 | ] 30 | 31 | 32 | def get_available_resources() -> List[Resource]: 33 | """Get list of all available resources.""" 34 | return [ 35 | # Add more resources here as you create them 36 | ] 37 | 38 | 39 | def main(): 40 | """Entry point for the server.""" 41 | mcp = FastMCP("example-mcp-server") 42 | tool_service = ToolService() 43 | resource_service = ResourceService() 44 | 45 | # Register all tools and their MCP handlers 46 | tool_service.register_tools(get_available_tools()) 47 | tool_service.register_mcp_handlers(mcp) 48 | 49 | # Register all resources and their MCP handlers 50 | resource_service.register_resources(get_available_resources()) 51 | resource_service.register_mcp_handlers(mcp) 52 | 53 | mcp.run() 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Service layer for the application.""" 2 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/__init__.py: -------------------------------------------------------------------------------- 1 | """Tool exports.""" 2 | 3 | from .add_numbers import AddNumbersTool 4 | from .subtract_numbers import SubtractNumbersTool 5 | from .multiply_numbers import MultiplyNumbersTool 6 | from .divide_numbers import DivideNumbersTool 7 | 8 | # Remove unused tools like DateDifferenceTool, ReverseStringTool, CurrentTimeTool, RandomNumberTool if they are not defined 9 | 10 | __all__ = [ 11 | "AddNumbersTool", 12 | "SubtractNumbersTool", 13 | "MultiplyNumbersTool", 14 | "DivideNumbersTool", 15 | # Add additional tools to the __all__ list as you create them 16 | ] 17 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/add_numbers.py: -------------------------------------------------------------------------------- 1 | """Tool for adding two numbers.""" 2 | 3 | from typing import Dict, Any, Union 4 | 5 | from pydantic import Field, BaseModel, ConfigDict 6 | 7 | from ..interfaces.tool import Tool, BaseToolInput, ToolResponse 8 | 9 | 10 | class AddNumbersInput(BaseToolInput): 11 | """Input schema for the AddNumbers tool.""" 12 | 13 | model_config = ConfigDict( 14 | json_schema_extra={"examples": [{"number1": 5, "number2": 3}, {"number1": -2.5, "number2": 1.5}]} 15 | ) 16 | 17 | number1: float = Field(description="The first number to add", examples=[5, -2.5]) 18 | number2: float = Field(description="The second number to add", examples=[3, 1.5]) 19 | 20 | 21 | class AddNumbersOutput(BaseModel): 22 | """Output schema for the AddNumbers tool.""" 23 | 24 | model_config = ConfigDict(json_schema_extra={"examples": [{"sum": 8, "error": None}, {"sum": -1.0, "error": None}]}) 25 | 26 | sum: float = Field(description="The sum of the two numbers") 27 | error: Union[str, None] = Field(default=None, description="An error message if the operation failed.") 28 | 29 | 30 | class AddNumbersTool(Tool): 31 | """Tool that adds two numbers together.""" 32 | 33 | name = "AddNumbers" 34 | description = "Adds two numbers (number1 + number2) and returns the sum" 35 | input_model = AddNumbersInput 36 | output_model = AddNumbersOutput 37 | 38 | def get_schema(self) -> Dict[str, Any]: 39 | """Get the JSON schema for this tool.""" 40 | return { 41 | "name": self.name, 42 | "description": self.description, 43 | "input": self.input_model.model_json_schema(), 44 | "output": self.output_model.model_json_schema(), 45 | } 46 | 47 | async def execute(self, input_data: AddNumbersInput) -> ToolResponse: 48 | """Execute the add numbers tool. 49 | 50 | Args: 51 | input_data: The validated input for the tool 52 | 53 | Returns: 54 | A response containing the sum 55 | """ 56 | result = input_data.number1 + input_data.number2 57 | output = AddNumbersOutput(sum=result, error=None) 58 | return ToolResponse.from_model(output) 59 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/divide_numbers.py: -------------------------------------------------------------------------------- 1 | """Tool for dividing two numbers.""" 2 | 3 | from typing import Dict, Any, Union 4 | 5 | from pydantic import Field, BaseModel, ConfigDict 6 | 7 | from ..interfaces.tool import Tool, BaseToolInput, ToolResponse 8 | 9 | 10 | class DivideNumbersInput(BaseToolInput): 11 | """Input schema for the DivideNumbers tool.""" 12 | 13 | model_config = ConfigDict( 14 | json_schema_extra={ 15 | "examples": [{"dividend": 10, "divisor": 2}, {"dividend": 5, "divisor": 0}, {"dividend": 7.5, "divisor": 2.5}] 16 | } 17 | ) 18 | 19 | dividend: float = Field(description="The number to be divided", examples=[10, 5, 7.5]) 20 | divisor: float = Field(description="The number to divide by", examples=[2, 0, 2.5]) 21 | 22 | 23 | class DivideNumbersOutput(BaseModel): 24 | """Output schema for the DivideNumbers tool.""" 25 | 26 | model_config = ConfigDict( 27 | json_schema_extra={"examples": [{"quotient": 5.0}, {"error": "Division by zero is not allowed."}, {"quotient": 3.0}]} 28 | ) 29 | 30 | quotient: Union[float, None] = Field( 31 | default=None, description="The result of the division (dividend / divisor). None if division by zero occurred." 32 | ) 33 | error: Union[str, None] = Field( 34 | default=None, description="An error message if the operation failed (e.g., division by zero)." 35 | ) 36 | 37 | 38 | class DivideNumbersTool(Tool): 39 | """Tool that divides one number by another.""" 40 | 41 | name = "DivideNumbers" 42 | description = "Divides the first number (dividend) by the second number (divisor) and returns the quotient. Handles division by zero." 43 | input_model = DivideNumbersInput 44 | output_model = DivideNumbersOutput 45 | 46 | def get_schema(self) -> Dict[str, Any]: 47 | """Get the JSON schema for this tool.""" 48 | return { 49 | "name": self.name, 50 | "description": self.description, 51 | "input": self.input_model.model_json_schema(), 52 | "output": self.output_model.model_json_schema(), 53 | } 54 | 55 | async def execute(self, input_data: DivideNumbersInput) -> ToolResponse: 56 | """Execute the divide numbers tool. 57 | 58 | Args: 59 | input_data: The validated input for the tool 60 | 61 | Returns: 62 | A response containing the quotient or an error message 63 | """ 64 | if input_data.divisor == 0: 65 | output = DivideNumbersOutput(error="Division by zero is not allowed.") 66 | # Optionally set a specific status code if your ToolResponse supports it 67 | # return ToolResponse(status_code=400, content=ToolContent.from_model(output)) 68 | return ToolResponse.from_model(output) 69 | else: 70 | result = input_data.dividend / input_data.divisor 71 | output = DivideNumbersOutput(quotient=result) 72 | return ToolResponse.from_model(output) 73 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/multiply_numbers.py: -------------------------------------------------------------------------------- 1 | """Tool for multiplying two numbers.""" 2 | 3 | from typing import Dict, Any, Union 4 | 5 | from pydantic import Field, BaseModel, ConfigDict 6 | 7 | from ..interfaces.tool import Tool, BaseToolInput, ToolResponse 8 | 9 | 10 | class MultiplyNumbersInput(BaseToolInput): 11 | """Input schema for the MultiplyNumbers tool.""" 12 | 13 | model_config = ConfigDict(json_schema_extra={"examples": [{"number1": 5, "number2": 3}, {"number1": -2.5, "number2": 4}]}) 14 | 15 | number1: float = Field(description="The first number to multiply", examples=[5, -2.5]) 16 | number2: float = Field(description="The second number to multiply", examples=[3, 4]) 17 | 18 | 19 | class MultiplyNumbersOutput(BaseModel): 20 | """Output schema for the MultiplyNumbers tool.""" 21 | 22 | model_config = ConfigDict( 23 | json_schema_extra={"examples": [{"product": 15, "error": None}, {"product": -10.0, "error": None}]} 24 | ) 25 | 26 | product: float = Field(description="The product of the two numbers (number1 * number2)") 27 | error: Union[str, None] = Field(default=None, description="An error message if the operation failed.") 28 | 29 | 30 | class MultiplyNumbersTool(Tool): 31 | """Tool that multiplies two numbers together.""" 32 | 33 | name = "MultiplyNumbers" 34 | description = "Multiplies two numbers (number1 * number2) and returns the product" 35 | input_model = MultiplyNumbersInput 36 | output_model = MultiplyNumbersOutput 37 | 38 | def get_schema(self) -> Dict[str, Any]: 39 | """Get the JSON schema for this tool.""" 40 | return { 41 | "name": self.name, 42 | "description": self.description, 43 | "input": self.input_model.model_json_schema(), 44 | "output": self.output_model.model_json_schema(), 45 | } 46 | 47 | async def execute(self, input_data: MultiplyNumbersInput) -> ToolResponse: 48 | """Execute the multiply numbers tool. 49 | 50 | Args: 51 | input_data: The validated input for the tool 52 | 53 | Returns: 54 | A response containing the product 55 | """ 56 | result = input_data.number1 * input_data.number2 57 | output = MultiplyNumbersOutput(product=result, error=None) 58 | return ToolResponse.from_model(output) 59 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/subtract_numbers.py: -------------------------------------------------------------------------------- 1 | """Tool for subtracting two numbers.""" 2 | 3 | from typing import Dict, Any, Union 4 | 5 | from pydantic import Field, BaseModel, ConfigDict 6 | 7 | from ..interfaces.tool import Tool, BaseToolInput, ToolResponse 8 | 9 | 10 | class SubtractNumbersInput(BaseToolInput): 11 | """Input schema for the SubtractNumbers tool.""" 12 | 13 | model_config = ConfigDict(json_schema_extra={"examples": [{"number1": 5, "number2": 3}, {"number1": 1.5, "number2": 2.5}]}) 14 | 15 | number1: float = Field(description="The number to subtract from", examples=[5, 1.5]) 16 | number2: float = Field(description="The number to subtract", examples=[3, 2.5]) 17 | 18 | 19 | class SubtractNumbersOutput(BaseModel): 20 | """Output schema for the SubtractNumbers tool.""" 21 | 22 | model_config = ConfigDict( 23 | json_schema_extra={"examples": [{"difference": 2, "error": None}, {"difference": -1.0, "error": None}]} 24 | ) 25 | 26 | difference: float = Field(description="The difference between the two numbers (number1 - number2)") 27 | error: Union[str, None] = Field(default=None, description="An error message if the operation failed.") 28 | 29 | 30 | class SubtractNumbersTool(Tool): 31 | """Tool that subtracts one number from another.""" 32 | 33 | name = "SubtractNumbers" 34 | description = "Subtracts the second number from the first number (number1 - number2) and returns the difference" 35 | input_model = SubtractNumbersInput 36 | output_model = SubtractNumbersOutput 37 | 38 | def get_schema(self) -> Dict[str, Any]: 39 | """Get the JSON schema for this tool.""" 40 | return { 41 | "name": self.name, 42 | "description": self.description, 43 | "input": self.input_model.model_json_schema(), 44 | "output": self.output_model.model_json_schema(), 45 | } 46 | 47 | async def execute(self, input_data: SubtractNumbersInput) -> ToolResponse: 48 | """Execute the subtract numbers tool. 49 | 50 | Args: 51 | input_data: The validated input for the tool 52 | 53 | Returns: 54 | A response containing the difference 55 | """ 56 | result = input_data.number1 - input_data.number2 57 | output = SubtractNumbersOutput(difference=result, error=None) 58 | return ToolResponse.from_model(output) 59 | -------------------------------------------------------------------------------- /atomic-examples/mcp-agent/example-mcp-server/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "example-mcp-server" 3 | version = "0.1.0" 4 | description = "example-mcp-server MCP server" 5 | authors = [] 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "mcp[cli]", 9 | "rich>=13.0.0", 10 | "pydantic>=2.0.0", 11 | "uvicorn>=0.15.0", 12 | ] 13 | 14 | [build-system] 15 | requires = ["hatchling"] 16 | build-backend = "hatchling.build" 17 | 18 | [project.scripts] 19 | example-mcp-server = "example_mcp_server.server:main" -------------------------------------------------------------------------------- /atomic-examples/orchestration-agent/README.md: -------------------------------------------------------------------------------- 1 | # Orchestration Agent Example 2 | 3 | This example demonstrates how to create an Orchestrator Agent that intelligently decides between using a search tool or a calculator tool based on user input. 4 | 5 | ## Features 6 | - Intelligent tool selection between search and calculator tools 7 | - Dynamic input/output schema handling 8 | - Real-time date context provider 9 | - Rich console output formatting 10 | - Final answer generation based on tool outputs 11 | 12 | ## Getting Started 13 | 14 | 1. Clone the Atomic Agents repository: 15 | ```bash 16 | git clone https://github.com/BrainBlend-AI/atomic-agents 17 | ``` 18 | 19 | 2. Navigate to the orchestration-agent directory: 20 | ```bash 21 | cd atomic-agents/atomic-examples/orchestration-agent 22 | ``` 23 | 24 | 3. Install dependencies using Poetry: 25 | ```bash 26 | poetry install 27 | ``` 28 | 29 | 4. Set up environment variables: 30 | Create a `.env` file in the `orchestration-agent` directory with: 31 | ```env 32 | OPENAI_API_KEY=your_openai_api_key 33 | ``` 34 | 35 | 5. Install SearxNG (See: https://github.com/searxng/searxng) 36 | 37 | 6. Run the example: 38 | ```bash 39 | poetry run python orchestration_agent/orchestrator.py 40 | ``` 41 | 42 | ## Components 43 | 44 | ### Input/Output Schemas 45 | 46 | - **OrchestratorInputSchema**: Handles user input messages 47 | - **OrchestratorOutputSchema**: Specifies tool selection and parameters 48 | - **FinalAnswerSchema**: Formats the final response 49 | 50 | ### Tools 51 | These tools were installed using the Atomic Assembler CLI (See the main README [here](../../README.md) for more info) 52 | The agent orchestrates between two tools: 53 | - **SearxNG Search Tool**: For queries requiring factual information 54 | - **Calculator Tool**: For mathematical calculations 55 | 56 | ### Context Providers 57 | 58 | - **CurrentDateProvider**: Provides the current date in YYYY-MM-DD format 59 | -------------------------------------------------------------------------------- /atomic-examples/orchestration-agent/orchestration_agent/tools/calculator.py: -------------------------------------------------------------------------------- 1 | from pydantic import Field 2 | from sympy import sympify 3 | 4 | from atomic_agents.agents.base_agent import BaseIOSchema 5 | from atomic_agents.lib.base.base_tool import BaseTool, BaseToolConfig 6 | 7 | 8 | ################ 9 | # INPUT SCHEMA # 10 | ################ 11 | class CalculatorToolInputSchema(BaseIOSchema): 12 | """ 13 | Tool for performing calculations. Supports basic arithmetic operations 14 | like addition, subtraction, multiplication, and division, as well as more 15 | complex operations like exponentiation and trigonometric functions. 16 | Use this tool to evaluate mathematical expressions. 17 | """ 18 | 19 | expression: str = Field(..., description="Mathematical expression to evaluate. For example, '2 + 2'.") 20 | 21 | 22 | ################# 23 | # OUTPUT SCHEMA # 24 | ################# 25 | class CalculatorToolOutputSchema(BaseIOSchema): 26 | """ 27 | Schema for the output of the CalculatorTool. 28 | """ 29 | 30 | result: str = Field(..., description="Result of the calculation.") 31 | 32 | 33 | ################# 34 | # CONFIGURATION # 35 | ################# 36 | class CalculatorToolConfig(BaseToolConfig): 37 | """ 38 | Configuration for the CalculatorTool. 39 | """ 40 | 41 | pass 42 | 43 | 44 | ##################### 45 | # MAIN TOOL & LOGIC # 46 | ##################### 47 | class CalculatorTool(BaseTool): 48 | """ 49 | Tool for performing calculations based on the provided mathematical expression. 50 | 51 | Attributes: 52 | input_schema (CalculatorToolInputSchema): The schema for the input data. 53 | output_schema (CalculatorToolOutputSchema): The schema for the output data. 54 | """ 55 | 56 | input_schema = CalculatorToolInputSchema 57 | output_schema = CalculatorToolOutputSchema 58 | 59 | def __init__(self, config: CalculatorToolConfig = CalculatorToolConfig()): 60 | """ 61 | Initializes the CalculatorTool. 62 | 63 | Args: 64 | config (CalculatorToolConfig): Configuration for the tool. 65 | """ 66 | super().__init__(config) 67 | 68 | def run(self, params: CalculatorToolInputSchema) -> CalculatorToolOutputSchema: 69 | """ 70 | Executes the CalculatorTool with the given parameters. 71 | 72 | Args: 73 | params (CalculatorToolInputSchema): The input parameters for the tool. 74 | 75 | Returns: 76 | CalculatorToolOutputSchema: The result of the calculation. 77 | """ 78 | # Convert the expression string to a symbolic expression 79 | parsed_expr = sympify(str(params.expression)) 80 | 81 | # Evaluate the expression numerically 82 | result = parsed_expr.evalf() 83 | return CalculatorToolOutputSchema(result=str(result)) 84 | 85 | 86 | ################# 87 | # EXAMPLE USAGE # 88 | ################# 89 | if __name__ == "__main__": 90 | calculator = CalculatorTool() 91 | result = calculator.run(CalculatorToolInputSchema(expression="sin(pi/2) + cos(pi/4)")) 92 | print(result) # Expected output: {"result":"1.70710678118655"} 93 | -------------------------------------------------------------------------------- /atomic-examples/orchestration-agent/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "orchestration-agent" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["KennyVaneetvelde "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | atomic-agents = {path = "../..", develop = true} 11 | instructor = "^1.6.1" 12 | pydantic = ">=2.10.3,<3.0.0" 13 | sympy = "^1.13.3" 14 | python-dotenv = ">=1.0.1,<2.0.0" 15 | openai = ">=1.35.12,<2.0.0" 16 | 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /atomic-examples/quickstart/README.md: -------------------------------------------------------------------------------- 1 | # Atomic Agents Quickstart Examples 2 | 3 | This directory contains quickstart examples for the Atomic Agents project. These examples demonstrate various features and capabilities of the Atomic Agents framework. 4 | 5 | ## Getting Started 6 | 7 | To run these examples: 8 | 9 | 1. Clone the main Atomic Agents repository: 10 | ``` 11 | git clone https://github.com/BrainBlend-AI/atomic-agents 12 | ``` 13 | 14 | 2. Navigate to the quickstart directory: 15 | ``` 16 | cd atomic-agents/atomic-examples/quickstart 17 | ``` 18 | 19 | 3. Install the dependencies using Poetry: 20 | ``` 21 | poetry install 22 | ``` 23 | 24 | 4. Run the examples using Poetry: 25 | ``` 26 | poetry run python quickstart/1_basic_chatbot.py 27 | ``` 28 | 29 | ## Example Files 30 | 31 | ### 1. Basic Chatbot (1_basic_chatbot.py) 32 | 33 | This example demonstrates a simple chatbot using the Atomic Agents framework. It includes: 34 | - Setting up the OpenAI API client 35 | - Initializing a basic agent with default configurations 36 | - Running a chat loop where the user can interact with the agent 37 | 38 | ### 2. Custom Chatbot (2_basic_custom_chatbot.py) 39 | 40 | This example shows how to create a custom chatbot with: 41 | - A custom system prompt 42 | - Customized agent configuration 43 | - A chat loop with rhyming responses 44 | 45 | ### 3. Custom Chatbot with Custom Schema (3_basic_custom_chatbot_with_custom_schema.py) 46 | 47 | This example demonstrates: 48 | - Creating a custom output schema for the agent 49 | - Implementing suggested follow-up questions in the agent's responses 50 | - Using a custom system prompt and agent configuration 51 | 52 | ### 4. Chatbot with Different Providers (4_basic_chatbot_different_providers.py) 53 | 54 | This example showcases: 55 | - How to use different AI providers (OpenAI, Groq, Ollama) 56 | - Dynamically selecting a provider at runtime 57 | - Adapting the agent configuration based on the chosen provider 58 | 59 | ## Running the Examples 60 | 61 | To run any of the examples, use the following command: 62 | 63 | ``` 64 | poetry run python quickstart/.py 65 | ``` 66 | 67 | Replace `` with the name of the example you want to run (e.g., `1_basic_chatbot.py`). 68 | 69 | These examples provide a great starting point for understanding and working with the Atomic Agents framework. Feel free to modify and experiment with them to learn more about the capabilities of Atomic Agents. 70 | -------------------------------------------------------------------------------- /atomic-examples/quickstart/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "quickstart" 3 | version = "1.0.0" 4 | description = "Quickstart example for Atomic Agents" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | atomic-agents = {path = "../..", develop = true} 11 | instructor = ">=1.3.4,<2.0.0" 12 | openai = ">=1.35.12,<2.0.0" 13 | groq = ">=0.11.0,<1.0.0" 14 | mistralai = ">=1.1.0,<2.0.0" 15 | anthropic = ">=0.39.0,<1.0.0" 16 | python-dotenv = ">=1.0.1,<2.0.0" 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /atomic-examples/quickstart/quickstart/1_1_basic_chatbot_streaming.py: -------------------------------------------------------------------------------- 1 | import os 2 | import instructor 3 | import openai 4 | from rich.console import Console 5 | from rich.panel import Panel 6 | from rich.text import Text 7 | from rich.live import Live 8 | from atomic_agents.lib.components.agent_memory import AgentMemory 9 | from atomic_agents.agents.base_agent import BaseAgent, BaseAgentConfig, BaseAgentInputSchema, BaseAgentOutputSchema 10 | 11 | # API Key setup 12 | API_KEY = "" 13 | if not API_KEY: 14 | API_KEY = os.getenv("OPENAI_API_KEY") 15 | 16 | if not API_KEY: 17 | raise ValueError( 18 | "API key is not set. Please set the API key as a static variable or in the environment variable OPENAI_API_KEY." 19 | ) 20 | 21 | # Initialize a Rich Console for pretty console outputs 22 | console = Console() 23 | 24 | # Memory setup 25 | memory = AgentMemory() 26 | 27 | # Initialize memory with an initial message from the assistant 28 | initial_message = BaseAgentOutputSchema(chat_message="Hello! How can I assist you today?") 29 | memory.add_message("assistant", initial_message) 30 | 31 | # OpenAI client setup using the Instructor library for async operations 32 | client = instructor.from_openai(openai.AsyncOpenAI(api_key=API_KEY)) 33 | 34 | # Agent setup with specified configuration 35 | agent = BaseAgent( 36 | config=BaseAgentConfig( 37 | client=client, 38 | model="gpt-4o-mini", 39 | memory=memory, 40 | ) 41 | ) 42 | 43 | # Generate the default system prompt for the agent 44 | default_system_prompt = agent.system_prompt_generator.generate_prompt() 45 | # Display the system prompt in a styled panel 46 | console.print(Panel(default_system_prompt, width=console.width, style="bold cyan"), style="bold cyan") 47 | 48 | # Display the initial message from the assistant 49 | console.print(Text("Agent:", style="bold green"), end=" ") 50 | console.print(Text(initial_message.chat_message, style="green")) 51 | 52 | 53 | async def main(): 54 | # Start an infinite loop to handle user inputs and agent responses 55 | while True: 56 | # Prompt the user for input with a styled prompt 57 | user_input = console.input("\n[bold blue]You:[/bold blue] ") 58 | # Check if the user wants to exit the chat 59 | if user_input.lower() in ["/exit", "/quit"]: 60 | console.print("Exiting chat...") 61 | break 62 | 63 | # Process the user's input through the agent and get the streaming response 64 | input_schema = BaseAgentInputSchema(chat_message=user_input) 65 | console.print() # Add newline before response 66 | 67 | # Use Live display to show streaming response 68 | with Live("", refresh_per_second=10, auto_refresh=True) as live: 69 | current_response = "" 70 | async for partial_response in agent.run_async(input_schema): 71 | if hasattr(partial_response, "chat_message") and partial_response.chat_message: 72 | # Only update if we have new content 73 | if partial_response.chat_message != current_response: 74 | current_response = partial_response.chat_message 75 | # Combine the label and response in the live display 76 | display_text = Text.assemble(("Agent: ", "bold green"), (current_response, "green")) 77 | live.update(display_text) 78 | 79 | 80 | if __name__ == "__main__": 81 | import asyncio 82 | 83 | asyncio.run(main()) 84 | -------------------------------------------------------------------------------- /atomic-examples/quickstart/quickstart/1_basic_chatbot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import instructor 3 | import openai 4 | from rich.console import Console 5 | from rich.panel import Panel 6 | from rich.text import Text 7 | from atomic_agents.lib.components.agent_memory import AgentMemory 8 | from atomic_agents.agents.base_agent import BaseAgent, BaseAgentConfig, BaseAgentInputSchema, BaseAgentOutputSchema 9 | 10 | # API Key setup 11 | API_KEY = "" 12 | if not API_KEY: 13 | API_KEY = os.getenv("OPENAI_API_KEY") 14 | 15 | if not API_KEY: 16 | raise ValueError( 17 | "API key is not set. Please set the API key as a static variable or in the environment variable OPENAI_API_KEY." 18 | ) 19 | 20 | # Initialize a Rich Console for pretty console outputs 21 | console = Console() 22 | 23 | # Memory setup 24 | memory = AgentMemory() 25 | 26 | # Initialize memory with an initial message from the assistant 27 | initial_message = BaseAgentOutputSchema(chat_message="Hello! How can I assist you today?") 28 | memory.add_message("assistant", initial_message) 29 | 30 | # OpenAI client setup using the Instructor library 31 | client = instructor.from_openai(openai.OpenAI(api_key=API_KEY)) 32 | 33 | # Agent setup with specified configuration 34 | agent = BaseAgent( 35 | config=BaseAgentConfig( 36 | client=client, 37 | model="gpt-4o-mini", 38 | memory=memory, 39 | ) 40 | ) 41 | 42 | # Generate the default system prompt for the agent 43 | default_system_prompt = agent.system_prompt_generator.generate_prompt() 44 | # Display the system prompt in a styled panel 45 | console.print(Panel(default_system_prompt, width=console.width, style="bold cyan"), style="bold cyan") 46 | 47 | # Display the initial message from the assistant 48 | console.print(Text("Agent:", style="bold green"), end=" ") 49 | console.print(Text(initial_message.chat_message, style="bold green")) 50 | 51 | # Start an infinite loop to handle user inputs and agent responses 52 | while True: 53 | # Prompt the user for input with a styled prompt 54 | user_input = console.input("[bold blue]You:[/bold blue] ") 55 | # Check if the user wants to exit the chat 56 | if user_input.lower() in ["/exit", "/quit"]: 57 | console.print("Exiting chat...") 58 | break 59 | 60 | # Process the user's input through the agent and get the response 61 | input_schema = BaseAgentInputSchema(chat_message=user_input) 62 | response = agent.run(input_schema) 63 | 64 | agent_message = Text(response.chat_message, style="bold green") 65 | console.print(Text("Agent:", style="bold green"), end=" ") 66 | console.print(agent_message) 67 | -------------------------------------------------------------------------------- /atomic-examples/quickstart/quickstart/2_basic_custom_chatbot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import instructor 3 | import openai 4 | from rich.console import Console 5 | from rich.panel import Panel 6 | from rich.text import Text 7 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 8 | from atomic_agents.lib.components.agent_memory import AgentMemory 9 | from atomic_agents.agents.base_agent import BaseAgent, BaseAgentConfig, BaseAgentOutputSchema 10 | 11 | # API Key setup 12 | API_KEY = "" 13 | if not API_KEY: 14 | API_KEY = os.getenv("OPENAI_API_KEY") 15 | 16 | if not API_KEY: 17 | raise ValueError( 18 | "API key is not set. Please set the API key as a static variable or in the environment variable OPENAI_API_KEY." 19 | ) 20 | 21 | # Initialize a Rich Console for pretty console outputs 22 | console = Console() 23 | 24 | # Memory setup 25 | memory = AgentMemory() 26 | 27 | # Initialize memory with an initial message from the assistant 28 | initial_message = BaseAgentOutputSchema( 29 | chat_message="How do you do? What can I do for you? Tell me, pray, what is your need today?" 30 | ) 31 | memory.add_message("assistant", initial_message) 32 | 33 | # OpenAI client setup using the Instructor library 34 | # Note, you can also set up a client using any other LLM provider, such as Anthropic, Cohere, etc. 35 | # See the Instructor library for more information: https://github.com/instructor-ai/instructor 36 | client = instructor.from_openai(openai.OpenAI(api_key=API_KEY)) 37 | 38 | # Instead of the default system prompt, we can set a custom system prompt 39 | system_prompt_generator = SystemPromptGenerator( 40 | background=[ 41 | "This assistant is a general-purpose AI designed to be helpful and friendly.", 42 | ], 43 | steps=["Understand the user's input and provide a relevant response.", "Respond to the user."], 44 | output_instructions=[ 45 | "Provide helpful and relevant information to assist the user.", 46 | "Be friendly and respectful in all interactions.", 47 | "Always answer in rhyming verse.", 48 | ], 49 | ) 50 | console.print(Panel(system_prompt_generator.generate_prompt(), width=console.width, style="bold cyan"), style="bold cyan") 51 | 52 | # Agent setup with specified configuration 53 | agent = BaseAgent( 54 | config=BaseAgentConfig( 55 | client=client, 56 | model="gpt-4o-mini", 57 | system_prompt_generator=system_prompt_generator, 58 | memory=memory, 59 | ) 60 | ) 61 | 62 | # Display the initial message from the assistant 63 | console.print(Text("Agent:", style="bold green"), end=" ") 64 | console.print(Text(initial_message.chat_message, style="bold green")) 65 | 66 | # Start an infinite loop to handle user inputs and agent responses 67 | while True: 68 | # Prompt the user for input with a styled prompt 69 | user_input = console.input("[bold blue]You:[/bold blue] ") 70 | # Check if the user wants to exit the chat 71 | if user_input.lower() in ["/exit", "/quit"]: 72 | console.print("Exiting chat...") 73 | break 74 | 75 | # Process the user's input through the agent and get the response and display it 76 | response = agent.run(agent.input_schema(chat_message=user_input)) 77 | agent_message = Text(response.chat_message, style="bold green") 78 | console.print(Text("Agent:", style="bold green"), end=" ") 79 | console.print(agent_message) 80 | -------------------------------------------------------------------------------- /atomic-examples/quickstart/quickstart/5_custom_system_role_for_reasoning_models.py: -------------------------------------------------------------------------------- 1 | import os 2 | import instructor 3 | import openai 4 | from rich.console import Console 5 | from rich.text import Text 6 | from atomic_agents.agents.base_agent import BaseAgent, BaseAgentConfig, BaseAgentInputSchema 7 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 8 | 9 | # API Key setup 10 | API_KEY = "" 11 | if not API_KEY: 12 | API_KEY = os.getenv("OPENAI_API_KEY") 13 | 14 | if not API_KEY: 15 | raise ValueError( 16 | "API key is not set. Please set the API key as a static variable or in the environment variable OPENAI_API_KEY." 17 | ) 18 | 19 | # Initialize a Rich Console for pretty console outputs 20 | console = Console() 21 | 22 | # OpenAI client setup using the Instructor library 23 | client = instructor.from_openai(openai.OpenAI(api_key=API_KEY)) 24 | 25 | # System prompt generator setup 26 | sysyem_prompt_generator = SystemPromptGenerator( 27 | background=["You are a math genious."], 28 | steps=["Think logically step by step and solve a math problem."], 29 | output_instructions=["Answer in plain English plus formulas."], 30 | ) 31 | # Agent setup with specified configuration 32 | agent = BaseAgent( 33 | config=BaseAgentConfig( 34 | client=client, 35 | model="o3-mini", 36 | sysyem_prompt_generator=sysyem_prompt_generator, 37 | # It is a convention to use "developer" as the system role for reasoning models from OpenAI such as o1, o3-mini. 38 | # Also these models are often used without a system prompt, which you can do by setting system_role=None 39 | system_role="developer", 40 | ) 41 | ) 42 | 43 | # Prompt the user for input with a styled prompt 44 | user_input = "Decompose this number to prime factors: 1234567890" 45 | console.print(Text("User:", style="bold green"), end=" ") 46 | console.print(user_input) 47 | 48 | # Process the user's input through the agent and get the response 49 | input_schema = BaseAgentInputSchema(chat_message=user_input) 50 | response = agent.run(input_schema) 51 | 52 | agent_message = Text(response.chat_message, style="bold green") 53 | console.print(Text("Agent:", style="bold green"), end=" ") 54 | console.print(agent_message) 55 | -------------------------------------------------------------------------------- /atomic-examples/rag-chatbot/README.md: -------------------------------------------------------------------------------- 1 | # RAG Chatbot 2 | 3 | This directory contains the RAG (Retrieval-Augmented Generation) Chatbot example for the Atomic Agents project. This example demonstrates how to build an intelligent chatbot that uses document retrieval to provide context-aware responses using the Atomic Agents framework. 4 | 5 | ## Features 6 | 7 | 1. Document Chunking: Automatically splits documents into manageable chunks with configurable overlap 8 | 2. Vector Storage: Uses ChromaDB for efficient storage and retrieval of document chunks 9 | 3. Semantic Search: Generates and executes semantic search queries to find relevant context 10 | 4. Context-Aware Responses: Provides detailed answers based on retrieved document chunks 11 | 5. Interactive UI: Rich console interface with progress indicators and formatted output 12 | 13 | ## Getting Started 14 | 15 | To get started with the RAG Chatbot: 16 | 17 | 1. **Clone the main Atomic Agents repository:** 18 | ```bash 19 | git clone https://github.com/BrainBlend-AI/atomic-agents 20 | ``` 21 | 22 | 2. **Navigate to the RAG Chatbot directory:** 23 | ```bash 24 | cd atomic-agents/atomic-examples/rag-chatbot 25 | ``` 26 | 27 | 3. **Install the dependencies using Poetry:** 28 | ```bash 29 | poetry install 30 | ``` 31 | 32 | 4. **Set up environment variables:** 33 | Create a `.env` file in the `rag-chatbot` directory with the following content: 34 | ```env 35 | OPENAI_API_KEY=your_openai_api_key 36 | ``` 37 | Replace `your_openai_api_key` with your actual OpenAI API key. 38 | 39 | 5. **Run the RAG Chatbot:** 40 | ```bash 41 | poetry run python rag_chatbot/main.py 42 | ``` 43 | 44 | ## Components 45 | 46 | ### 1. Query Agent (`agents/query_agent.py`) 47 | Generates semantic search queries based on user questions to find relevant document chunks. 48 | 49 | ### 2. QA Agent (`agents/qa_agent.py`) 50 | Analyzes retrieved chunks and generates comprehensive answers to user questions. 51 | 52 | ### 3. ChromaDB Service (`services/chroma_db.py`) 53 | Manages the vector database for storing and retrieving document chunks. 54 | 55 | ### 4. Context Provider (`context_providers.py`) 56 | Provides retrieved document chunks as context to the agents. 57 | 58 | ### 5. Main Script (`main.py`) 59 | Orchestrates the entire process, from document processing to user interaction. 60 | 61 | ## How It Works 62 | 63 | 1. The system initializes by: 64 | - Downloading a sample document (State of the Union address) 65 | - Splitting it into chunks with configurable overlap 66 | - Storing chunks in ChromaDB with vector embeddings 67 | 68 | 2. For each user question: 69 | - The Query Agent generates an optimized semantic search query 70 | - Relevant chunks are retrieved from ChromaDB 71 | - The QA Agent analyzes the chunks and generates a detailed answer 72 | - The system displays the thought process and final answer 73 | 74 | ## Customization 75 | 76 | You can customize the RAG Chatbot by: 77 | - Modifying chunk size and overlap in `config.py` 78 | - Adjusting the number of chunks to retrieve for each query 79 | - Using different documents as the knowledge base 80 | - Customizing the system prompts for both agents 81 | 82 | ## Example Usage 83 | 84 | The chatbot can answer questions about the loaded document, such as: 85 | - "What were the main points about the economy?" 86 | - "What did the president say about healthcare?" 87 | - "How did he address foreign policy?" 88 | 89 | ## Contributing 90 | 91 | Contributions are welcome! Please fork the repository and submit a pull request with your enhancements or bug fixes. 92 | 93 | ## License 94 | 95 | This project is licensed under the MIT License. See the [LICENSE](../../LICENSE) file for details. 96 | -------------------------------------------------------------------------------- /atomic-examples/rag-chatbot/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "rag-chatbot" 3 | version = "0.1.0" 4 | description = "A RAG chatbot example using Atomic Agents and ChromaDB" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = ">=3.11,<4.0" 10 | atomic-agents = {path = "../..", develop = true} 11 | chromadb = "^0.4.24" 12 | python-dotenv = ">=1.0.1,<2.0.0" 13 | openai = ">=1.35.12,<2.0.0" 14 | rich = ">=13.7.0,<14.0.0" 15 | wget = ">=3.2,<4.0" 16 | 17 | [build-system] 18 | requires = ["poetry-core"] 19 | build-backend = "poetry.core.masonry.api" 20 | -------------------------------------------------------------------------------- /atomic-examples/rag-chatbot/rag_chatbot/.gitignore: -------------------------------------------------------------------------------- 1 | chroma_db/ 2 | -------------------------------------------------------------------------------- /atomic-examples/rag-chatbot/rag_chatbot/agents/qa_agent.py: -------------------------------------------------------------------------------- 1 | import instructor 2 | import openai 3 | from pydantic import Field 4 | from atomic_agents.agents.base_agent import BaseIOSchema, BaseAgent, BaseAgentConfig 5 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 6 | 7 | from rag_chatbot.config import ChatConfig 8 | 9 | 10 | class RAGQuestionAnsweringAgentInputSchema(BaseIOSchema): 11 | """Input schema for the RAG QA agent.""" 12 | 13 | question: str = Field(..., description="The user's question to answer") 14 | 15 | 16 | class RAGQuestionAnsweringAgentOutputSchema(BaseIOSchema): 17 | """Output schema for the RAG QA agent.""" 18 | 19 | reasoning: str = Field(..., description="The reasoning process leading up to the final answer") 20 | answer: str = Field(..., description="The answer to the user's question based on the retrieved context") 21 | 22 | 23 | qa_agent = BaseAgent( 24 | BaseAgentConfig( 25 | client=instructor.from_openai(openai.OpenAI(api_key=ChatConfig.api_key)), 26 | model=ChatConfig.model, 27 | system_prompt_generator=SystemPromptGenerator( 28 | background=[ 29 | "You are an expert at answering questions using retrieved context chunks from a RAG system.", 30 | "Your role is to synthesize information from the chunks to provide accurate, well-supported answers.", 31 | "You must explain your reasoning process before providing the answer.", 32 | ], 33 | steps=[ 34 | "1. Analyze the question and available context chunks", 35 | "2. Identify the most relevant information in the chunks", 36 | "3. Explain how you'll use this information to answer the question", 37 | "4. Synthesize information into a coherent answer", 38 | ], 39 | output_instructions=[ 40 | "First explain your reasoning process clearly", 41 | "Then provide a clear, direct answer based on the context", 42 | "If context is insufficient, state this in your reasoning", 43 | "Never make up information not present in the chunks", 44 | "Focus on being accurate and concise", 45 | ], 46 | ), 47 | input_schema=RAGQuestionAnsweringAgentInputSchema, 48 | output_schema=RAGQuestionAnsweringAgentOutputSchema, 49 | ) 50 | ) 51 | -------------------------------------------------------------------------------- /atomic-examples/rag-chatbot/rag_chatbot/agents/query_agent.py: -------------------------------------------------------------------------------- 1 | import instructor 2 | import openai 3 | from pydantic import Field 4 | from atomic_agents.agents.base_agent import BaseIOSchema, BaseAgent, BaseAgentConfig 5 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 6 | 7 | from rag_chatbot.config import ChatConfig 8 | 9 | 10 | class RAGQueryAgentInputSchema(BaseIOSchema): 11 | """Input schema for the RAG query agent.""" 12 | 13 | user_message: str = Field(..., description="The user's question or message to generate a semantic search query for") 14 | 15 | 16 | class RAGQueryAgentOutputSchema(BaseIOSchema): 17 | """Output schema for the RAG query agent.""" 18 | 19 | reasoning: str = Field(..., description="The reasoning process leading up to the final query") 20 | query: str = Field(..., description="The semantic search query to use for retrieving relevant chunks") 21 | 22 | 23 | query_agent = BaseAgent( 24 | BaseAgentConfig( 25 | client=instructor.from_openai(openai.OpenAI(api_key=ChatConfig.api_key)), 26 | model=ChatConfig.model, 27 | system_prompt_generator=SystemPromptGenerator( 28 | background=[ 29 | "You are an expert at formulating semantic search queries for RAG systems.", 30 | "Your role is to convert user questions into effective semantic search queries that will retrieve the most relevant text chunks.", 31 | ], 32 | steps=[ 33 | "1. Analyze the user's question to identify key concepts and information needs", 34 | "2. Reformulate the question into a semantic search query that will match relevant content", 35 | "3. Ensure the query captures the core meaning while being general enough to match similar content", 36 | ], 37 | output_instructions=[ 38 | "Generate a clear, concise semantic search query", 39 | "Focus on key concepts and entities from the user's question", 40 | "Avoid overly specific details that might miss relevant matches", 41 | "Include synonyms or related terms when appropriate", 42 | "Explain your reasoning for the query formulation", 43 | ], 44 | ), 45 | input_schema=RAGQueryAgentInputSchema, 46 | output_schema=RAGQueryAgentOutputSchema, 47 | ) 48 | ) 49 | -------------------------------------------------------------------------------- /atomic-examples/rag-chatbot/rag_chatbot/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dataclasses import dataclass 3 | 4 | 5 | def get_api_key() -> str: 6 | """Retrieve API key from environment or raise error""" 7 | api_key = os.getenv("OPENAI_API_KEY") 8 | if not api_key: 9 | raise ValueError("API key not found. Please set the OPENAI_API_KEY environment variable.") 10 | return api_key 11 | 12 | 13 | @dataclass 14 | class ChatConfig: 15 | """Configuration for the chat application""" 16 | 17 | api_key: str = get_api_key() # This becomes a class variable 18 | model: str = "gpt-4o-mini" 19 | exit_commands: set[str] = frozenset({"/exit", "exit", "quit", "/quit"}) 20 | 21 | def __init__(self): 22 | # Prevent instantiation 23 | raise TypeError("ChatConfig is not meant to be instantiated") 24 | 25 | 26 | # Model Configuration 27 | EMBEDDING_MODEL = "text-embedding-3-small" # OpenAI's latest embedding model 28 | CHUNK_SIZE = 1000 29 | CHUNK_OVERLAP = 200 30 | 31 | # Vector Search Configuration 32 | NUM_CHUNKS_TO_RETRIEVE = 3 33 | SIMILARITY_METRIC = "cosine" 34 | 35 | # ChromaDB Configuration 36 | CHROMA_PERSIST_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "chroma_db") 37 | 38 | # Memory Configuration 39 | MEMORY_SIZE = 10 # Number of messages to keep in conversation memory 40 | MAX_CONTEXT_LENGTH = 4000 # Maximum length of combined context to send to the model 41 | -------------------------------------------------------------------------------- /atomic-examples/rag-chatbot/rag_chatbot/context_providers.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptContextProviderBase 4 | 5 | 6 | @dataclass 7 | class ChunkItem: 8 | content: str 9 | metadata: dict 10 | 11 | 12 | class RAGContextProvider(SystemPromptContextProviderBase): 13 | def __init__(self, title: str): 14 | super().__init__(title=title) 15 | self.chunks: List[ChunkItem] = [] 16 | 17 | def get_info(self) -> str: 18 | return "\n\n".join( 19 | [ 20 | f"Chunk {idx}:\nMetadata: {item.metadata}\nContent:\n{item.content}\n{'-' * 80}" 21 | for idx, item in enumerate(self.chunks, 1) 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /atomic-examples/web-search-agent/README.md: -------------------------------------------------------------------------------- 1 | # Web Search Agent 2 | 3 | This project demonstrates an intelligent web search agent built using the Atomic Agents framework. The agent can perform web searches, generate relevant queries, and provide detailed answers to user questions based on the search results. 4 | 5 | ## Features 6 | 7 | 1. Query Generation: Automatically generates relevant search queries based on user input. 8 | 2. Web Search: Utilizes SearxNG to perform web searches across multiple search engines. 9 | 3. Question Answering: Provides detailed answers to user questions based on search results. 10 | 4. Follow-up Questions: Suggests related questions to encourage further exploration of the topic. 11 | 12 | ## Components 13 | 14 | The Web Search Agent consists of several key components: 15 | 16 | 1. Query Agent (`query_agent.py`): Generates diverse and relevant search queries based on user input. 17 | 2. SearxNG Search Tool (`searxng_search.py`): Performs web searches using the SearxNG meta-search engine. 18 | 3. Question Answering Agent (`question_answering_agent.py`): Analyzes search results and provides detailed answers to user questions. 19 | 4. Main Script (`main.py`): Orchestrates the entire process, from query generation to final answer presentation. 20 | 21 | ## Getting Started 22 | 23 | To run the Web Search Agent: 24 | 25 | 1. Clone the Atomic Agents repository: 26 | ``` 27 | git clone https://github.com/BrainBlend-AI/atomic-agents 28 | ``` 29 | 30 | 2. Navigate to the web-search-agent directory: 31 | ``` 32 | cd atomic-agents/atomic-examples/web-search-agent 33 | ``` 34 | 35 | 3. Install dependencies using Poetry: 36 | ``` 37 | poetry install 38 | ``` 39 | 40 | 4. Set up environment variables: 41 | Create a `.env` file in the `web-search-agent` directory with the following content: 42 | ``` 43 | OPENAI_API_KEY=your_openai_api_key 44 | SEARXNG_BASE_URL=your_searxng_instance_url 45 | ``` 46 | Replace `your_openai_api_key` with your actual OpenAI API key and `your_searxng_instance_url` with the URL of your SearxNG instance. 47 | If you do not have a SearxNG instance, see the instructions below to set up one locally with docker. 48 | 49 | 50 | 5. Run the Web Search Agent: 51 | ``` 52 | poetry run python web_search_agent/main.py 53 | ``` 54 | 55 | ## How It Works 56 | 57 | 1. The user provides an initial question or topic for research. 58 | 2. The Query Agent generates multiple relevant search queries based on the user's input. 59 | 3. The SearxNG Search Tool performs web searches using the generated queries. 60 | 4. The Question Answering Agent analyzes the search results and formulates a detailed answer. 61 | 5. The main script presents the answer, along with references and follow-up questions. 62 | 63 | ## SearxNG Setup with docker 64 | 65 | From the [official instructions](https://docs.searxng.org/admin/installation-docker.html): 66 | 67 | ```shell 68 | mkdir my-instance 69 | cd my-instance 70 | export PORT=8080 71 | docker pull searxng/searxng 72 | docker run --rm \ 73 | -d -p ${PORT}:8080 \ 74 | -v "${PWD}/searxng:/etc/searxng" \ 75 | -e "BASE_URL=http://localhost:$PORT/" \ 76 | -e "INSTANCE_NAME=my-instance" \ 77 | searxng/searxng 78 | ``` 79 | 80 | Set the `SEARXNG_BASE_URL` environment variable to `http://localhost:8080/` in your `.env` file. 81 | 82 | 83 | Note: for the agent to communicate with SearxNG, the instance must enable the JSON engine, which is disabled by default. 84 | Edit `/etc/searxng/settings.yml` and add `- json` in the `search.formats` section, then restart the container. 85 | 86 | 87 | ## Customization 88 | 89 | You can customize the Web Search Agent by modifying the following: 90 | 91 | - Adjust the number of generated queries in `main.py`. 92 | - Modify the search categories or parameters in `searxng_search.py`. 93 | - Customize the system prompts for the Query Agent and Question Answering Agent in their respective files. 94 | 95 | ## Contributing 96 | 97 | Contributions to the Web Search Agent project are welcome! Please fork the repository and submit a pull request with your enhancements or bug fixes. 98 | 99 | ## License 100 | 101 | This project is licensed under the MIT License. See the [LICENSE](../../LICENSE) file for details. 102 | 103 | -------------------------------------------------------------------------------- /atomic-examples/web-search-agent/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "web-search-agent" 3 | version = "1.0.0" 4 | description = "Web search agent example for Atomic Agents" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | atomic-agents = {path = "../..", develop = true} 11 | openai = ">=1.35.12,<2.0.0" 12 | pydantic = ">=2.9.2,<3.0.0" 13 | instructor = ">=1.5.2,<2.0.0" 14 | python-dotenv = ">=1.0.1,<2.0.0" 15 | 16 | 17 | [build-system] 18 | requires = ["poetry-core"] 19 | build-backend = "poetry.core.masonry.api" 20 | -------------------------------------------------------------------------------- /atomic-examples/web-search-agent/web_search_agent/agents/query_agent.py: -------------------------------------------------------------------------------- 1 | import instructor 2 | import openai 3 | from pydantic import Field 4 | from atomic_agents.agents.base_agent import BaseIOSchema, BaseAgent, BaseAgentConfig 5 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 6 | 7 | from web_search_agent.tools.searxng_search import SearxNGSearchTool 8 | import dotenv 9 | 10 | dotenv.load_dotenv() 11 | 12 | 13 | class QueryAgentInputSchema(BaseIOSchema): 14 | """This is the input schema for the QueryAgent.""" 15 | 16 | instruction: str = Field(..., description="A detailed instruction or request to generate deep research queries for.") 17 | num_queries: int = Field(..., description="The number of queries to generate.") 18 | 19 | 20 | query_agent = BaseAgent( 21 | BaseAgentConfig( 22 | client=instructor.from_openai(openai.OpenAI()), 23 | model="gpt-4o-mini", 24 | system_prompt_generator=SystemPromptGenerator( 25 | background=[ 26 | "You are an intelligent query generation expert.", 27 | "Your task is to generate a specified number of diverse and highly relevant queries based on a given instruction or request.", 28 | "The queries should cover different aspects of the instruction to ensure comprehensive exploration.", 29 | ], 30 | steps=[ 31 | "You will receive a detailed instruction or request and the number of queries to generate.", 32 | "Generate the requested number of queries in a JSON format.", 33 | ], 34 | output_instructions=[ 35 | "Ensure clarity and conciseness in each query.", 36 | "Ensure each query is unique and as diverse as possible while remaining relevant to the instruction.", 37 | ], 38 | ), 39 | input_schema=QueryAgentInputSchema, 40 | output_schema=SearxNGSearchTool.input_schema, 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /atomic-examples/web-search-agent/web_search_agent/agents/question_answering_agent.py: -------------------------------------------------------------------------------- 1 | import instructor 2 | import openai 3 | from pydantic import Field, HttpUrl 4 | from typing import List 5 | from atomic_agents.agents.base_agent import BaseIOSchema, BaseAgent, BaseAgentConfig 6 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator 7 | 8 | 9 | class QuestionAnsweringAgentInputSchema(BaseIOSchema): 10 | """This schema defines the input schema for the QuestionAnsweringAgent.""" 11 | 12 | question: str = Field(..., description="A question that needs to be answered based on the provided context.") 13 | 14 | 15 | class QuestionAnsweringAgentOutputSchema(BaseIOSchema): 16 | """This schema defines the output schema for the QuestionAnsweringAgent.""" 17 | 18 | markdown_output: str = Field(..., description="The answer to the question in markdown format.") 19 | references: List[HttpUrl] = Field( 20 | ..., max_items=3, description="A list of up to 3 HTTP URLs used as references for the answer." 21 | ) 22 | followup_questions: List[str] = Field( 23 | ..., max_items=3, description="A list of up to 3 follow-up questions related to the answer." 24 | ) 25 | 26 | 27 | # Create the question answering agent 28 | question_answering_agent = BaseAgent( 29 | BaseAgentConfig( 30 | client=instructor.from_openai(openai.OpenAI()), 31 | model="gpt-4o-mini", 32 | system_prompt_generator=SystemPromptGenerator( 33 | background=[ 34 | "You are an intelligent question answering expert.", 35 | "Your task is to provide accurate and detailed answers to user questions based on the given context.", 36 | ], 37 | steps=[ 38 | "You will receive a question and the context information.", 39 | "Generate a detailed and accurate answer based on the context.", 40 | "Provide up to 3 relevant references (HTTP URLs) used in formulating the answer.", 41 | "Generate up to 3 follow-up questions related to the answer.", 42 | ], 43 | output_instructions=[ 44 | "Ensure clarity and conciseness in each answer.", 45 | "Ensure the answer is directly relevant to the question and context provided.", 46 | "Include up to 3 relevant HTTP URLs as references.", 47 | "Provide up to 3 follow-up questions to encourage further exploration of the topic.", 48 | ], 49 | ), 50 | input_schema=QuestionAnsweringAgentInputSchema, 51 | output_schema=QuestionAnsweringAgentOutputSchema, 52 | ) 53 | ) 54 | -------------------------------------------------------------------------------- /atomic-examples/web-search-agent/web_search_agent/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from rich.console import Console 4 | from rich.markdown import Markdown 5 | 6 | from atomic_agents.lib.components.agent_memory import AgentMemory 7 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptContextProviderBase 8 | 9 | from web_search_agent.tools.searxng_search import ( 10 | SearxNGSearchTool, 11 | SearxNGSearchToolConfig, 12 | SearxNGSearchToolInputSchema, 13 | ) 14 | 15 | from web_search_agent.agents.query_agent import QueryAgentInputSchema, query_agent 16 | from web_search_agent.agents.question_answering_agent import question_answering_agent, QuestionAnsweringAgentInputSchema 17 | 18 | 19 | load_dotenv() 20 | 21 | # Initialize a Rich Console for pretty console outputs 22 | console = Console() 23 | 24 | # Memory setup 25 | memory = AgentMemory() 26 | 27 | # Initialize the SearxNGSearchTool 28 | search_tool = SearxNGSearchTool(config=SearxNGSearchToolConfig(base_url=os.getenv("SEARXNG_BASE_URL"), max_results=5)) 29 | 30 | # Example usage 31 | instruction = "Tell me about the Atomic Agents AI agent framework." 32 | num_queries = 3 33 | 34 | # Generate queries using the query agent 35 | query_input = QueryAgentInputSchema(instruction=instruction, num_queries=num_queries) 36 | generated_queries = query_agent.run(query_input) 37 | 38 | console.print("[bold blue]Generated Queries:[/bold blue]") 39 | for query in generated_queries.queries: 40 | console.print(f"- {query}") 41 | 42 | # Perform searches using the generated queries 43 | search_input = SearxNGSearchToolInputSchema(queries=generated_queries.queries, category="general") 44 | 45 | 46 | class SearchResultsProvider(SystemPromptContextProviderBase): 47 | def __init__(self, title: str): 48 | super().__init__(title=title) 49 | self.search_results = [] 50 | 51 | def get_info(self) -> str: 52 | return f"SEARCH RESULTS: {self.search_results}" 53 | 54 | 55 | try: 56 | search_results = search_tool.run(search_input) 57 | search_results_provider = SearchResultsProvider(title="Search Results") 58 | search_results_provider.search_results = search_results 59 | 60 | question_answering_agent.register_context_provider("search results", search_results_provider) 61 | 62 | answer = question_answering_agent.run(QuestionAnsweringAgentInputSchema(question=instruction)) 63 | 64 | # Create a Rich Console instance 65 | console = Console() 66 | 67 | # Print the answer using Rich's Markdown rendering 68 | console.print("\n[bold blue]Answer:[/bold blue]") 69 | console.print(Markdown(answer.markdown_output)) 70 | 71 | # Print references 72 | console.print("\n[bold blue]References:[/bold blue]") 73 | for ref in answer.references: 74 | console.print(f"- {ref}") 75 | 76 | # Print follow-up questions 77 | console.print("\n[bold blue]Follow-up Questions:[/bold blue]") 78 | for question in answer.followup_questions: 79 | console.print(f"- {question}") 80 | 81 | except Exception as e: 82 | console.print(f"[bold red]Error:[/bold red] {str(e)}") 83 | -------------------------------------------------------------------------------- /atomic-examples/youtube-summarizer/README.md: -------------------------------------------------------------------------------- 1 | # YouTube Summarizer 2 | 3 | This directory contains the YouTube Summarizer example for the Atomic Agents project. This example demonstrates how to extract and summarize knowledge from YouTube videos using the Atomic Agents framework. 4 | 5 | ## Getting Started 6 | 7 | To get started with the YouTube Summarizer: 8 | 9 | 1. **Clone the main Atomic Agents repository:** 10 | ```bash 11 | git clone https://github.com/BrainBlend-AI/atomic-agents 12 | ``` 13 | 14 | 2. **Navigate to the YouTube Summarizer directory:** 15 | ```bash 16 | cd atomic-agents/atomic-examples/youtube-summarizer 17 | ``` 18 | 19 | 3. **Install the dependencies using Poetry:** 20 | ```bash 21 | poetry install 22 | ``` 23 | 24 | 4. **Set up environment variables:** 25 | 26 | Create a `.env` file in the `youtube-summarizer` directory with the following content: 27 | ```env 28 | OPENAI_API_KEY=your_openai_api_key 29 | YOUTUBE_API_KEY=your_youtube_api_key 30 | ``` 31 | 32 | To get your YouTube API key, follow the instructions in the [YouTube Scraper README](/atomic-forge/tools/youtube_transcript_scraper/README.md). 33 | 34 | Replace `your_openai_api_key` and `your_youtube_api_key` with your actual API keys. 35 | 36 | 5. **Run the YouTube Summarizer:** 37 | ```bash 38 | poetry run python youtube_summarizer/main.py 39 | ``` 40 | 41 | ## File Explanation 42 | 43 | ### 1. Agent (`agent.py`) 44 | 45 | This module defines the `YouTubeKnowledgeExtractionAgent`, responsible for extracting summaries, insights, quotes, and more from YouTube video transcripts. 46 | 47 | 48 | ### 2. YouTube Transcript Scraper (`tools/youtube_transcript_scraper.py`) 49 | 50 | This tool comes from the [Atomic Forge](/atomic-forge/README.md) and handles fetching transcripts and metadata from YouTube videos. 51 | 52 | ### 3. Main (`main.py`) 53 | 54 | The entry point for the YouTube Summarizer application. It orchestrates fetching transcripts, processing them through the agent, and displaying the results. 55 | 56 | ## Customization 57 | 58 | You can modify the `video_url` variable in `main.py` to analyze different YouTube videos. Additionally, you can adjust the agent's configuration in `agent.py` to tailor the summaries and insights according to your requirements. 59 | 60 | ## Contributing 61 | 62 | Contributions are welcome! Please fork the repository and submit a pull request with your enhancements or bug fixes. 63 | 64 | ## License 65 | 66 | This project is licensed under the MIT License. See the [LICENSE](../../LICENSE) file for details. 67 | -------------------------------------------------------------------------------- /atomic-examples/youtube-summarizer/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "youtube-summarizer" 3 | version = "1.0.0" 4 | description = "Youtube Summarizer example for Atomic Agents" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | atomic-agents = {path = "../..", develop = true} 11 | openai = ">=1.35.12,<2.0.0" 12 | pydantic = ">=2.10.3,<3.0.0" 13 | google-api-python-client = ">=2.101.0,<3.0.0" 14 | youtube-transcript-api = ">=0.1.9,<1.0.0" 15 | instructor = ">=1.5.2,<2.0.0" 16 | python-dotenv = ">=1.0.1,<2.0.0" 17 | 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | -------------------------------------------------------------------------------- /atomic-examples/youtube-summarizer/youtube_summarizer/agent.py: -------------------------------------------------------------------------------- 1 | import instructor 2 | import openai 3 | from pydantic import Field 4 | from typing import List, Optional 5 | 6 | from atomic_agents.agents.base_agent import BaseAgent, BaseAgentConfig, BaseIOSchema 7 | from atomic_agents.lib.components.system_prompt_generator import SystemPromptContextProviderBase, SystemPromptGenerator 8 | 9 | 10 | class YtTranscriptProvider(SystemPromptContextProviderBase): 11 | def __init__(self, title): 12 | super().__init__(title) 13 | self.transcript = None 14 | self.duration = None 15 | self.metadata = None 16 | 17 | def get_info(self) -> str: 18 | return f'VIDEO TRANSCRIPT: "{self.transcript}"\n\nDURATION: {self.duration}\n\nMETADATA: {self.metadata}' 19 | 20 | 21 | class YouTubeKnowledgeExtractionInputSchema(BaseIOSchema): 22 | """This schema defines the input schema for the YouTubeKnowledgeExtractionAgent.""" 23 | 24 | video_url: str = Field(..., description="The URL of the YouTube video to analyze") 25 | 26 | 27 | class YouTubeKnowledgeExtractionOutputSchema(BaseIOSchema): 28 | """This schema defines an elaborate set of insights about the contentof the video.""" 29 | 30 | summary: str = Field( 31 | ..., description="A short summary of the content, including who is presenting and the content being discussed." 32 | ) 33 | insights: List[str] = Field( 34 | ..., min_items=5, max_items=5, description="exactly 5 of the best insights and ideas from the input." 35 | ) 36 | quotes: List[str] = Field( 37 | ..., 38 | min_items=5, 39 | max_items=5, 40 | description="exactly 5 of the most surprising, insightful, and/or interesting quotes from the input.", 41 | ) 42 | habits: Optional[List[str]] = Field( 43 | None, 44 | min_items=5, 45 | max_items=5, 46 | description="exactly 5 of the most practical and useful personal habits mentioned by the speakers.", 47 | ) 48 | facts: List[str] = Field( 49 | ..., 50 | min_items=5, 51 | max_items=5, 52 | description="exactly 5 of the most surprising, insightful, and/or interesting valid facts about the greater world mentioned in the content.", 53 | ) 54 | recommendations: List[str] = Field( 55 | ..., 56 | min_items=5, 57 | max_items=5, 58 | description="exactly 5 of the most surprising, insightful, and/or interesting recommendations from the content.", 59 | ) 60 | references: List[str] = Field( 61 | ..., 62 | description="All mentions of writing, art, tools, projects, and other sources of inspiration mentioned by the speakers.", 63 | ) 64 | one_sentence_takeaway: str = Field( 65 | ..., description="The most potent takeaways and recommendations condensed into a single 20-word sentence." 66 | ) 67 | 68 | 69 | transcript_provider = YtTranscriptProvider(title="YouTube Transcript") 70 | 71 | youtube_knowledge_extraction_agent = BaseAgent( 72 | config=BaseAgentConfig( 73 | client=instructor.from_openai(openai.OpenAI()), 74 | model="gpt-4o-mini", 75 | system_prompt_generator=SystemPromptGenerator( 76 | background=[ 77 | "This Assistant is an expert at extracting knowledge and other insightful and interesting information from YouTube transcripts." 78 | ], 79 | steps=[ 80 | "Analyse the YouTube transcript thoroughly to extract the most valuable insights, facts, and recommendations.", 81 | "Adhere strictly to the provided schema when extracting information from the input content.", 82 | "Ensure that the output matches the field descriptions, types and constraints exactly.", 83 | ], 84 | output_instructions=[ 85 | "Only output Markdown-compatible strings.", 86 | "Ensure you follow ALL these instructions when creating your output.", 87 | ], 88 | context_providers={"yt_transcript": transcript_provider}, 89 | ), 90 | input_schema=YouTubeKnowledgeExtractionInputSchema, 91 | output_schema=YouTubeKnowledgeExtractionOutputSchema, 92 | ) 93 | ) 94 | -------------------------------------------------------------------------------- /atomic-examples/youtube-summarizer/youtube_summarizer/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from rich.console import Console 4 | 5 | from youtube_summarizer.tools.youtube_transcript_scraper import ( 6 | YouTubeTranscriptTool, 7 | YouTubeTranscriptToolConfig, 8 | YouTubeTranscriptToolInputSchema, 9 | ) 10 | 11 | from youtube_summarizer.agent import ( 12 | YouTubeKnowledgeExtractionInputSchema, 13 | youtube_knowledge_extraction_agent, 14 | transcript_provider, 15 | ) 16 | 17 | load_dotenv() 18 | 19 | # Initialize a Rich Console for pretty console outputs 20 | console = Console() 21 | 22 | # Initialize the YouTubeTranscriptTool 23 | transcript_tool = YouTubeTranscriptTool(config=YouTubeTranscriptToolConfig(api_key=os.getenv("YOUTUBE_API_KEY"))) 24 | 25 | # Remove the infinite loop and perform a one-time transcript extraction 26 | video_url = "https://www.youtube.com/watch?v=Sp30YsjGUW0" 27 | 28 | transcript_input = YouTubeTranscriptToolInputSchema(video_url=video_url, language="en") 29 | try: 30 | transcript_output = transcript_tool.run(transcript_input) 31 | console.print(f"[bold green]Transcript:[/bold green] {transcript_output.transcript}") 32 | console.print(f"[bold green]Duration:[/bold green] {transcript_output.duration} seconds") 33 | 34 | # Update transcript_provider with the scraped transcript data 35 | transcript_provider.transcript = transcript_output.transcript 36 | transcript_provider.duration = transcript_output.duration 37 | transcript_provider.metadata = transcript_output.metadata # Assuming metadata is available in transcript_output 38 | 39 | # Run the transcript through the agent 40 | transcript_input_schema = YouTubeKnowledgeExtractionInputSchema(video_url=video_url) 41 | agent_response = youtube_knowledge_extraction_agent.run(transcript_input_schema) 42 | 43 | # Print the output schema in a formatted way 44 | console.print("[bold blue]Agent Output Schema:[/bold blue]") 45 | console.print(agent_response) 46 | except Exception as e: 47 | console.print(f"[bold red]Error:[/bold red] {str(e)}") 48 | -------------------------------------------------------------------------------- /atomic-examples/youtube-to-recipe/README.md: -------------------------------------------------------------------------------- 1 | # YouTube Recipe Extractor 2 | 3 | This directory contains the YouTube Recipe Extractor example for the Atomic Agents project. This example demonstrates how to extract structured recipe information from cooking videos using the Atomic Agents framework. 4 | 5 | ## Getting Started 6 | 7 | To get started with the YouTube Recipe Extractor: 8 | 9 | 1. **Clone the main Atomic Agents repository:** 10 | ```bash 11 | git clone https://github.com/BrainBlend-AI/atomic-agents 12 | ``` 13 | 14 | 2. **Navigate to the YouTube Recipe Extractor directory:** 15 | ```bash 16 | cd atomic-agents/atomic-examples/youtube-to-recipe 17 | ``` 18 | 19 | 3. **Install the dependencies using Poetry:** 20 | ```bash 21 | poetry install 22 | ``` 23 | 24 | 4. **Set up environment variables:** 25 | 26 | Create a `.env` file in the `youtube-to-recipe` directory with the following content: 27 | ```env 28 | OPENAI_API_KEY=your_openai_api_key 29 | YOUTUBE_API_KEY=your_youtube_api_key 30 | ``` 31 | 32 | To get your YouTube API key, follow the instructions in the [YouTube Scraper README](/atomic-forge/tools/youtube_transcript_scraper/README.md). 33 | 34 | Replace `your_openai_api_key` and `your_youtube_api_key` with your actual API keys. 35 | 36 | 5. **Run the YouTube Recipe Extractor:** 37 | ```bash 38 | poetry run python youtube_to_recipe/main.py 39 | ``` 40 | 41 | ## File Explanation 42 | 43 | ### 1. Agent (`agent.py`) 44 | 45 | This module defines the `YouTubeRecipeExtractionAgent`, responsible for extracting structured recipe information from cooking video transcripts. It extracts: 46 | - Recipe name and description 47 | - Ingredients with quantities and units 48 | - Step-by-step cooking instructions 49 | - Required equipment 50 | - Cooking times and temperatures 51 | - Tips and dietary information 52 | 53 | ### 2. YouTube Transcript Scraper (`tools/youtube_transcript_scraper.py`) 54 | 55 | This tool comes from the [Atomic Forge](/atomic-forge/README.md) and handles fetching transcripts and metadata from YouTube cooking videos. 56 | 57 | ### 3. Main (`main.py`) 58 | 59 | The entry point for the YouTube Recipe Extractor application. It orchestrates fetching transcripts, processing them through the agent, and outputting structured recipe information. 60 | 61 | ## Example Output 62 | 63 | The agent extracts recipe information in a structured format including: 64 | - Detailed ingredient lists with measurements 65 | - Step-by-step cooking instructions with timing and temperature 66 | - Required kitchen equipment 67 | - Cooking tips and tricks 68 | - Dietary information and cuisine type 69 | - Preparation and cooking times 70 | 71 | ## Customization 72 | 73 | You can modify the `video_url` variable in `main.py` to extract recipes from different cooking videos. Additionally, you can adjust the agent's configuration in `agent.py` to customize the recipe extraction format or add additional fields to capture more recipe details. 74 | 75 | ## Contributing 76 | 77 | Contributions are welcome! Please fork the repository and submit a pull request with your enhancements or bug fixes. 78 | 79 | ## License 80 | 81 | This project is licensed under the MIT License. See the [LICENSE](../../LICENSE) file for details. 82 | -------------------------------------------------------------------------------- /atomic-examples/youtube-to-recipe/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "youtube-to-recipe" 3 | version = "1.0.0" 4 | description = "Youtube Recipe Extractor example for Atomic Agents" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | atomic-agents = {path = "../..", develop = true} 11 | openai = ">=1.35.12,<2.0.0" 12 | pydantic = ">=2.10.3,<3.0.0" 13 | google-api-python-client = ">=2.101.0,<3.0.0" 14 | youtube-transcript-api = ">=0.1.9,<1.0.0" 15 | instructor = ">=1.5.2,<2.0.0" 16 | python-dotenv = ">=1.0.1,<2.0.0" 17 | 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | -------------------------------------------------------------------------------- /atomic-examples/youtube-to-recipe/youtube_to_recipe/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from rich.console import Console 4 | 5 | from youtube_to_recipe.tools.youtube_transcript_scraper import ( 6 | YouTubeTranscriptTool, 7 | YouTubeTranscriptToolConfig, 8 | YouTubeTranscriptToolInputSchema, 9 | ) 10 | 11 | from youtube_to_recipe.agent import YouTubeRecipeExtractionInputSchema, youtube_recipe_extraction_agent, transcript_provider 12 | 13 | load_dotenv() 14 | 15 | # Initialize a Rich Console for pretty console outputs 16 | console = Console() 17 | 18 | # Initialize the YouTubeTranscriptTool 19 | transcript_tool = YouTubeTranscriptTool(config=YouTubeTranscriptToolConfig(api_key=os.getenv("YOUTUBE_API_KEY"))) 20 | 21 | # Remove the infinite loop and perform a one-time transcript extraction 22 | video_url = "https://www.youtube.com/watch?v=kUymAc9Oldk" 23 | 24 | transcript_input = YouTubeTranscriptToolInputSchema(video_url=video_url, language="en") 25 | try: 26 | transcript_output = transcript_tool.run(transcript_input) 27 | console.print(f"[bold green]Transcript:[/bold green] {transcript_output.transcript}") 28 | console.print(f"[bold green]Duration:[/bold green] {transcript_output.duration} seconds") 29 | 30 | # Update transcript_provider with the scraped transcript data 31 | transcript_provider.transcript = transcript_output.transcript 32 | transcript_provider.duration = transcript_output.duration 33 | transcript_provider.metadata = transcript_output.metadata # Assuming metadata is available in transcript_output 34 | 35 | # Run the transcript through the agent 36 | transcript_input_schema = YouTubeRecipeExtractionInputSchema(video_url=video_url) 37 | agent_response = youtube_recipe_extraction_agent.run(transcript_input_schema) 38 | 39 | # Print the output schema in a formatted way 40 | console.print("[bold blue]Agent Output Schema:[/bold blue]") 41 | console.print(agent_response) 42 | except Exception as e: 43 | console.print(f"[bold red]Error:[/bold red] {str(e)}") 44 | -------------------------------------------------------------------------------- /atomic-forge/README.md: -------------------------------------------------------------------------------- 1 | # Atomic Forge 2 | Atomic Forge is a collection of tools that can be used with Atomic Agents to extend its functionality and integrate with other services. 3 | 4 | **Note:** Atomic Forge is NOT a package, but a folder of downloadable tools. This may seem strange at first, but it improves the developer experience in several ways: 5 | 6 | 1. **Full Control:** You have full ownership and control over each tool that you download. Do you like the Search tool, but wish it would sort results in a specific way? You can change it without impacting other users! Though if your change is useful for others, feel free to submit a pull request to the Atomic Forge repository. 7 | 2. **Dependency Management:** Because the tool resides in your own codebase once downloaded, you have better control over dependencies. 8 | 3. **Lightweight:** Because each tool is a standalone component, you can download only the tools that you need, rather than bloating your project with many unnecessary dependencies. After all, you don't need dependencies such as Sympy if you are not using the Calculator tool! 9 | 10 | ## Using the Atomic Assembler CLI 11 | Please use the [Atomic Assembler CLI](../README.md) as mentioned in the main [README.md](/README.md) for managing and downloading Tools. 12 | 13 | ## Tools 14 | The Atomic Forge project includes the following tools: 15 | 16 | - [Calculator](/atomic-forge/tools/calculator/README.md) 17 | - [SearxNG Search](/atomic-forge/tools/searxng_search/README.md) 18 | - [Tavily Search](/atomic-forge/tools/tavily_search/README.md) 19 | - [YouTube Transcript Scraper](/atomic-forge/tools/youtube_transcript_scraper/README.md) 20 | - [Webpage Scraper](/atomic-forge/tools/webpage_scraper/README.md) 21 | 22 | ## Creating Custom Tools 23 | Creating your own tools is easy! See the [Creating Tools](/atomic-forge/guides/tool_structure.md) guide for more information. 24 | -------------------------------------------------------------------------------- /atomic-forge/tools/calculator/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = tool 3 | omit = */tests/* 4 | 5 | [report] 6 | exclude_lines = 7 | if __name__ == "__main__": 8 | show_missing = True 9 | -------------------------------------------------------------------------------- /atomic-forge/tools/calculator/README.md: -------------------------------------------------------------------------------- 1 | # Calculator Tool 2 | 3 | ## Overview 4 | The Calculator Tool is a utility within the Atomic Agents ecosystem designed for performing a variety of mathematical calculations. It's essentially a wrapper around the sympy library to allow for expression-based calculations. 5 | 6 | ## Prerequisites and Dependencies 7 | - Python 3.9 or later 8 | - atomic-agents (See [here](/README.md) for installation instructions) 9 | - pydantic 10 | - sympy 11 | 12 | ## Installation 13 | You can install the tool using any of the following options: 14 | 15 | 1. Using the CLI tool that comes with Atomic Agents. Simply run `atomic` and select the tool from the list of available tools. After doing so you will be asked for a target directory to download the tool into. 16 | 2. Good old fashioned copy/paste: Just like any other tool inside the Atomic Forge, you can copy the code from this repo directly into your own project, provided you already have atomic-agents installed according to the instructions in the main [README](/README.md). 17 | 18 | ## Input & Output Structure 19 | 20 | ### Input Schema 21 | - `expression` (str): Mathematical expression to evaluate. For example, '2 + 2'. 22 | 23 | ### Output Schema 24 | - `result` (str): Result of the calculation. 25 | 26 | ## Usage 27 | 28 | Here's an example of how to use the Calculator Tool: 29 | 30 | ```python 31 | from tool.calculator import CalculatorTool, CalculatorToolConfig 32 | 33 | # Initialize the tool 34 | calculator = CalculatorTool(config=CalculatorToolConfig()) 35 | 36 | # Define input data 37 | input_data = CalculatorTool.input_schema( 38 | expression="sin(pi/2) + cos(pi/4)" 39 | ) 40 | 41 | # Perform the calculation 42 | result = calculator.run(input_data) 43 | print(result) # Expected output: {"result":"1.70710678118655"} 44 | ``` 45 | 46 | ## Contributing 47 | 48 | Contributions are welcome! To contribute: 49 | 50 | 1. Fork the repository. 51 | 2. Create a new feature branch. 52 | 3. Commit your changes with clear messages. 53 | 4. Open a pull request detailing your changes. 54 | 55 | Please ensure you follow the project's coding standards and include tests for any new features or bug fixes. 56 | 57 | ## License 58 | 59 | This project is licensed under the same license as the main Atomic Agents project. See the [LICENSE](LICENSE) file in the repository root for more details. 60 | -------------------------------------------------------------------------------- /atomic-forge/tools/calculator/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "calculator" 3 | version = "1.0" 4 | description = "" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = ">=3.12,<4" 11 | atomic-agents = {path = "../../../", develop = true} 12 | pydantic = ">=2.10.3,<3.0.0" 13 | sympy = ">=1.12,<2" 14 | 15 | [tool.poetry.group.dev.dependencies] 16 | coverage = ">=7.6.1,<8.0.0" 17 | pytest-cov = ">=5.0.0,<6.0.0" 18 | pytest = ">=8.3.3,<9.0.0" 19 | 20 | [build-system] 21 | requires = ["poetry-core"] 22 | build-backend = "poetry.core.masonry.api" 23 | -------------------------------------------------------------------------------- /atomic-forge/tools/calculator/requirements.txt: -------------------------------------------------------------------------------- 1 | atomic-agents>=1.0.0,<2.0.0 2 | pydantic>=2.8.2,<3.0.0 3 | sympy>=1.12,<2.0.0 4 | -------------------------------------------------------------------------------- /atomic-forge/tools/calculator/tests/test_calculator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) 5 | 6 | from tool.calculator import ( # noqa: E402 7 | CalculatorTool, 8 | CalculatorToolInputSchema, 9 | CalculatorToolOutputSchema, 10 | ) 11 | 12 | 13 | def test_calculator_tool(): 14 | calculator_tool = CalculatorTool() 15 | input_schema = CalculatorToolInputSchema(expression="2 + 2") 16 | result = calculator_tool.run(input_schema) 17 | assert result == CalculatorToolOutputSchema(result="4.00000000000000") 18 | 19 | 20 | if __name__ == "__main__": 21 | test_calculator_tool() 22 | -------------------------------------------------------------------------------- /atomic-forge/tools/calculator/tool/calculator.py: -------------------------------------------------------------------------------- 1 | from pydantic import Field 2 | from sympy import sympify 3 | 4 | from atomic_agents.agents.base_agent import BaseIOSchema 5 | from atomic_agents.lib.base.base_tool import BaseTool, BaseToolConfig 6 | 7 | 8 | ################ 9 | # INPUT SCHEMA # 10 | ################ 11 | class CalculatorToolInputSchema(BaseIOSchema): 12 | """ 13 | Tool for performing calculations. Supports basic arithmetic operations 14 | like addition, subtraction, multiplication, and division, as well as more 15 | complex operations like exponentiation and trigonometric functions. 16 | Use this tool to evaluate mathematical expressions. 17 | """ 18 | 19 | expression: str = Field(..., description="Mathematical expression to evaluate. For example, '2 + 2'.") 20 | 21 | 22 | ################# 23 | # OUTPUT SCHEMA # 24 | ################# 25 | class CalculatorToolOutputSchema(BaseIOSchema): 26 | """ 27 | Schema for the output of the CalculatorTool. 28 | """ 29 | 30 | result: str = Field(..., description="Result of the calculation.") 31 | 32 | 33 | ################# 34 | # CONFIGURATION # 35 | ################# 36 | class CalculatorToolConfig(BaseToolConfig): 37 | """ 38 | Configuration for the CalculatorTool. 39 | """ 40 | 41 | pass 42 | 43 | 44 | ##################### 45 | # MAIN TOOL & LOGIC # 46 | ##################### 47 | class CalculatorTool(BaseTool): 48 | """ 49 | Tool for performing calculations based on the provided mathematical expression. 50 | 51 | Attributes: 52 | input_schema (CalculatorToolInputSchema): The schema for the input data. 53 | output_schema (CalculatorToolOutputSchema): The schema for the output data. 54 | """ 55 | 56 | input_schema = CalculatorToolInputSchema 57 | output_schema = CalculatorToolOutputSchema 58 | 59 | def __init__(self, config: CalculatorToolConfig = CalculatorToolConfig()): 60 | """ 61 | Initializes the CalculatorTool. 62 | 63 | Args: 64 | config (CalculatorToolConfig): Configuration for the tool. 65 | """ 66 | super().__init__(config) 67 | 68 | def run(self, params: CalculatorToolInputSchema) -> CalculatorToolOutputSchema: 69 | """ 70 | Executes the CalculatorTool with the given parameters. 71 | 72 | Args: 73 | params (CalculatorToolInputSchema): The input parameters for the tool. 74 | 75 | Returns: 76 | CalculatorToolOutputSchema: The result of the calculation. 77 | """ 78 | # Convert the expression string to a symbolic expression 79 | parsed_expr = sympify(str(params.expression)) 80 | 81 | # Evaluate the expression numerically 82 | result = parsed_expr.evalf() 83 | return CalculatorToolOutputSchema(result=str(result)) 84 | 85 | 86 | ################# 87 | # EXAMPLE USAGE # 88 | ################# 89 | if __name__ == "__main__": 90 | calculator = CalculatorTool() 91 | result = calculator.run(CalculatorToolInputSchema(expression="sin(pi/2) + cos(pi/4)")) 92 | print(result) # Expected output: {"result":"1.70710678118655"} 93 | -------------------------------------------------------------------------------- /atomic-forge/tools/searxng_search/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = tool 3 | omit = */tests/* 4 | 5 | [report] 6 | exclude_lines = 7 | if __name__ == "__main__": 8 | show_missing = True 9 | -------------------------------------------------------------------------------- /atomic-forge/tools/searxng_search/README.md: -------------------------------------------------------------------------------- 1 | # SearxNG Search Tool 2 | 3 | ## Overview 4 | The SearxNG Search Tool is a powerful utility within the Atomic Agents ecosystem that allows you to perform searches using SearxNG, a privacy-respecting metasearch engine. This tool enables you to fetch search results from multiple sources while maintaining user privacy. 5 | 6 | ## Prerequisites and Dependencies 7 | - Python 3.9 or later 8 | - atomic-agents (See [here](/README.md) for installation instructions) 9 | - pydantic 10 | - requests 11 | 12 | ## Installation 13 | You can install the tool using any of the following options: 14 | 15 | 1. Using the CLI tool that comes with Atomic Agents. Simply run `atomic` and select the tool from the list of available tools. After doing so you will be asked for a target directory to download the tool into. 16 | 2. Good old fashioned copy/paste: Just like any other tool inside the Atomic Forge, you can copy the code from this repo directly into your own project, provided you already have atomic-agents installed according to the instructions in the main [README](/README.md). 17 | 18 | ## Configuration 19 | 20 | ### Parameters 21 | 22 | - `base_url` (str): The base URL of the SearxNG instance. This should include the protocol (e.g., `https://`) and the domain or IP address where SearxNG is hosted. 23 | - `max_results` (int, optional): The maximum number of search results to return. Defaults to `10`. 24 | 25 | ### Example 26 | 27 | ```python 28 | config = SearxNGSearchToolConfig( 29 | base_url="https://searxng.example.com", 30 | max_results=5 31 | ) 32 | ``` 33 | 34 | ## Input & Output Structure 35 | 36 | ### Input Schema 37 | - `queries` (List[str]): List of search queries. 38 | - `category` (Optional[Literal["general", "news", "social_media"]]): Category of the search queries. Defaults to "general". 39 | 40 | ### Output Schema 41 | - `results` (List[SearxNGSearchResultItemSchema]): List of search result items. 42 | - `category` (Optional[str]): The category of the search results. 43 | 44 | Each `SearxNGSearchResultItemSchema` contains: 45 | - `url` (str): The URL of the search result. 46 | - `title` (str): The title of the search result. 47 | - `content` (Optional[str]): The content snippet of the search result. 48 | - `query` (str): The query used to obtain this search result. 49 | 50 | ## Usage 51 | 52 | Here's an example of how to use the SearxNG Search Tool: 53 | 54 | 55 | ```python 56 | import os 57 | from tool.searxng_search import SearxNGTool, SearxNGSearchToolConfig 58 | 59 | # Initialize the tool with your SearxNG instance URL 60 | config = SearxNGSearchToolConfig(base_url=os.getenv("SEARXNG_BASE_URL"), max_results=5) 61 | search_tool = SearxNGTool(config=config) 62 | 63 | # Define input data 64 | input_data = SearxNGTool.input_schema( 65 | queries=["Python programming", "Machine learning", "Artificial intelligence"], 66 | category="news" 67 | ) 68 | 69 | # Perform the search 70 | result = search_tool.run(input_data) 71 | print(result) 72 | ``` 73 | 74 | ## Contributing 75 | 76 | Contributions are welcome! To contribute: 77 | 78 | 1. Fork the repository. 79 | 2. Create a new feature branch. 80 | 3. Commit your changes with clear messages. 81 | 4. Open a pull request detailing your changes. 82 | 83 | Please ensure you follow the project's coding standards and include tests for any new features or bug fixes. 84 | 85 | ## License 86 | 87 | This project is licensed under the same license as the main Atomic Agents project. See the [LICENSE](/LICENSE) file in the repository root for more details. 88 | -------------------------------------------------------------------------------- /atomic-forge/tools/searxng_search/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "searxng-search" 3 | version = "1.0" 4 | description = "A tool for performing searches using SearxNG" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = ">=3.12,<4" 11 | atomic-agents = {path = "../../../", develop = true} 12 | pydantic = ">=2.10.3,<3.0.0" 13 | aiohttp = ">=3.9.0,<4" 14 | 15 | [tool.poetry.group.dev.dependencies] 16 | coverage = ">=7.6.1,<8.0.0" 17 | pytest = ">=8.3.3,<9.0.0" 18 | pytest-asyncio = ">=0.23.5,<1.0.0" 19 | pytest-cov = ">=5.0.0,<6.0.0" 20 | python-dotenv = ">=1.0.0,<2.0.0" 21 | rich = ">=13.7.0,<14.0.0" 22 | 23 | [build-system] 24 | requires = ["poetry-core"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /atomic-forge/tools/searxng_search/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | asyncio_default_fixture_loop_scope = "function" 3 | -------------------------------------------------------------------------------- /atomic-forge/tools/searxng_search/requirements.txt: -------------------------------------------------------------------------------- 1 | atomic-agents>=1.0.0,<2.0.0 2 | pydantic>=2.8.2,<3.0.0 3 | sympy>=1.12,<2.0.0 4 | -------------------------------------------------------------------------------- /atomic-forge/tools/tavily_search/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = tool 3 | omit = */tests/* 4 | 5 | [report] 6 | exclude_lines = 7 | if __name__ == "__main__": 8 | show_missing = True 9 | -------------------------------------------------------------------------------- /atomic-forge/tools/tavily_search/README.md: -------------------------------------------------------------------------------- 1 | # Tavily Search Tool 2 | 3 | ## Overview 4 | The Tavily Search Tool is a powerful utility within the Atomic Agents ecosystem that allows you to perform searches using Tavily, a search engine built for AI Agents. This tool enables you to fetch search results from Tavily. 5 | 6 | ## Prerequisites and Dependencies 7 | - Python 3.9 or later 8 | - atomic-agents (See [here](/README.md) for installation instructions) 9 | - pydantic 10 | - requests 11 | 12 | ## Installation 13 | You can install the tool using any of the following options: 14 | 15 | 1. Using the CLI tool that comes with Atomic Agents. Simply run `atomic` and select the tool from the list of available tools. After doing so you will be asked for a target directory to download the tool into. 16 | 2. Good old fashioned copy/paste: Just like any other tool inside the Atomic Forge, you can copy the code from this repo directly into your own project, provided you already have atomic-agents installed according to the instructions in the main [README](/README.md). 17 | 18 | ## Configuration 19 | 20 | ### Parameters 21 | 22 | - `api_url` (str): The api key of the Tavily user. 23 | - `max_results` (int, optional): The maximum number of search results to return. Defaults to `10`. 24 | 25 | ### Example 26 | 27 | ```python 28 | config = TavilySearchToolConfig( 29 | api_key="my-api-key", 30 | max_results=5 31 | ) 32 | ``` 33 | 34 | ## Input & Output Structure 35 | 36 | ### Input Schema 37 | - `queries` (List[str]): List of search queries. 38 | 39 | ### Output Schema 40 | - `results` (List[TavilySearchResultItemSchema]): List of search result items. 41 | 42 | Each `TavilySearchResultItemSchema` contains: 43 | - `title` (str): The title of the search result. 44 | - `url` (str): The URL of the search result. 45 | - `content` (Optional[str]): The content snippet of the search result. 46 | - `score` (float): The score of the search result. 47 | - `raw_content` (Optional[str]): The raw content of the search result. 48 | - `query` (str): The query used to obtain this search result. 49 | - `answer` (Optional[str]): The answer to the query provided by Tavily. 50 | 51 | ## Usage 52 | 53 | Here's an example of how to use the Tavily Search Tool: 54 | 55 | 56 | ```python 57 | import os 58 | from tool.tavily_search import TavilyTool, TavilySearchToolConfig 59 | 60 | # Initialize the tool with your Tavily instance URL 61 | config = TavilySearchToolConfig(api_key=os.getenv("TAVILY_API_KEY"), max_results=5) 62 | search_tool = TavilyTool(config=config) 63 | 64 | # Define input data 65 | input_data = TavilyTool.input_schema( 66 | queries=["Python programming", "Machine learning"], 67 | ) 68 | 69 | # Perform the search 70 | result = search_tool.run(input_data) 71 | print(result) 72 | ``` 73 | 74 | ## Contributing 75 | 76 | Contributions are welcome! To contribute: 77 | 78 | 1. Fork the repository. 79 | 2. Create a new feature branch. 80 | 3. Commit your changes with clear messages. 81 | 4. Open a pull request detailing your changes. 82 | 83 | Please ensure you follow the project's coding standards and include tests for any new features or bug fixes. 84 | 85 | ## License 86 | 87 | This project is licensed under the same license as the main Atomic Agents project. See the [LICENSE](/LICENSE) file in the repository root for more details. 88 | -------------------------------------------------------------------------------- /atomic-forge/tools/tavily_search/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "tavily-search" 3 | version = "1.0" 4 | description = "A tool for performing searches using Tavily" 5 | authors = ["Kyle Webb "] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = ">=3.12,<4" 11 | atomic-agents = {path = "../../../", develop = true} 12 | pydantic = ">=2.10.3,<3.0.0" 13 | aiohttp = ">=3.9.0,<4" 14 | 15 | [tool.poetry.group.dev.dependencies] 16 | coverage = ">=7.6.1,<8.0.0" 17 | pytest = ">=8.3.3,<9.0.0" 18 | pytest-asyncio = ">=0.23.5,<1.0.0" 19 | pytest-cov = ">=5.0.0,<6.0.0" 20 | python-dotenv = ">=1.0.0,<2.0.0" 21 | rich = ">=13.7.0,<14.0.0" 22 | 23 | [build-system] 24 | requires = ["poetry-core"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /atomic-forge/tools/tavily_search/requirements.txt: -------------------------------------------------------------------------------- 1 | atomic-agents>=1.0.0,<2.0.0 2 | pydantic>=2.8.2,<3.0.0 3 | sympy>=1.12,<2.0.0 4 | aiohttp>=3.9.0,<4.0.0 -------------------------------------------------------------------------------- /atomic-forge/tools/webpage_scraper/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = tool 3 | omit = */tests/* 4 | 5 | [report] 6 | exclude_lines = 7 | if __name__ == "__main__": 8 | show_missing = True 9 | -------------------------------------------------------------------------------- /atomic-forge/tools/webpage_scraper/README.md: -------------------------------------------------------------------------------- 1 | # Webpage Scraper Tool 2 | 3 | ## Overview 4 | The Webpage Scraper Tool is a utility within the Atomic Agents ecosystem designed for scraping web content and converting it to markdown format. It includes features for extracting metadata and cleaning up the content for better readability. 5 | 6 | ## Prerequisites and Dependencies 7 | - Python 3.9 or later 8 | - atomic-agents (See [here](/README.md) for installation instructions) 9 | - pydantic 10 | - requests 11 | - beautifulsoup4 12 | - markdownify 13 | - readability-lxml 14 | 15 | ## Installation 16 | You can install the tool using any of the following options: 17 | 18 | 1. Using the CLI tool that comes with Atomic Agents. Simply run `atomic` and select the tool from the list of available tools. After doing so you will be asked for a target directory to download the tool into. 19 | 2. Good old fashioned copy/paste: Just like any other tool inside the Atomic Forge, you can copy the code from this repo directly into your own project, provided you already have atomic-agents installed according to the instructions in the main [README](/README.md). 20 | 21 | ## Configuration 22 | 23 | ### Parameters 24 | 25 | - `user_agent` (str): User agent string to use for requests. Defaults to Chrome/Windows user agent. 26 | - `timeout` (int): Timeout in seconds for HTTP requests. Defaults to 30. 27 | - `max_content_length` (int): Maximum content length in bytes to process. Defaults to 1,000,000. 28 | 29 | ### Example 30 | 31 | ```python 32 | config = WebpageScraperToolConfig( 33 | user_agent="Custom User Agent String", 34 | timeout=60, 35 | max_content_length=2_000_000 36 | ) 37 | ``` 38 | 39 | ## Input & Output Structure 40 | 41 | ### Input Schema 42 | - `url` (HttpUrl): URL of the webpage to scrape. 43 | - `include_links` (bool): Whether to preserve hyperlinks in the markdown output. Defaults to True. 44 | 45 | ### Output Schema 46 | - `content` (str): The scraped content in markdown format. 47 | - `metadata` (WebpageMetadata): Metadata about the scraped webpage, including: 48 | - `title` (str): The title of the webpage 49 | - `author` (Optional[str]): The author of the webpage content 50 | - `description` (Optional[str]): Meta description of the webpage 51 | - `site_name` (Optional[str]): Name of the website 52 | - `domain` (str): Domain name of the website 53 | 54 | ## Usage 55 | 56 | Here's an example of how to use the Webpage Scraper Tool: 57 | 58 | ```python 59 | from tool.webpage_scraper import WebpageScraperTool, WebpageScraperToolConfig 60 | 61 | # Initialize the tool 62 | scraper = WebpageScraperTool(config=WebpageScraperToolConfig()) 63 | 64 | # Define input data 65 | input_data = WebpageScraperTool.input_schema( 66 | url="https://example.com/article", 67 | include_links=True 68 | ) 69 | 70 | # Perform the scraping 71 | result = scraper.run(input_data) 72 | print(f"Title: {result.metadata.title}") 73 | print(f"Content: {result.content[:200]}...") # Preview first 200 chars 74 | ``` 75 | 76 | ## Contributing 77 | 78 | Contributions are welcome! To contribute: 79 | 80 | 1. Fork the repository. 81 | 2. Create a new feature branch. 82 | 3. Commit your changes with clear messages. 83 | 4. Open a pull request detailing your changes. 84 | 85 | Please ensure you follow the project's coding standards and include tests for any new features or bug fixes. 86 | 87 | ## License 88 | 89 | This project is licensed under the same license as the main Atomic Agents project. See the [LICENSE](LICENSE) file in the repository root for more details. 90 | -------------------------------------------------------------------------------- /atomic-forge/tools/webpage_scraper/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "webpage_scraper" 3 | version = "1.0" 4 | description = "Tool for scraping webpage content and converting it to markdown format" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = ">=3.12,<4" 11 | atomic-agents = {path = "../../../", develop = true} 12 | pydantic = ">=2.10.3,<3.0.0" 13 | beautifulsoup4 = ">=4.12.0,<5.0.0" 14 | markdownify = ">=0.11.0,<1.0.0" 15 | readability-lxml = ">=0.8.1,<1.0.0" 16 | requests = ">=2.31.0,<3.0.0" 17 | lxml = ">=5.1.0,<6.0.0" 18 | lxml-html-clean = ">=0.3.1,<1.0.0" 19 | 20 | [tool.poetry.group.dev.dependencies] 21 | coverage = ">=7.6.1,<8.0.0" 22 | pytest-cov = ">=5.0.0,<6.0.0" 23 | pytest = ">=8.3.3,<9.0.0" 24 | 25 | [build-system] 26 | requires = ["poetry-core"] 27 | build-backend = "poetry.core.masonry.api" 28 | -------------------------------------------------------------------------------- /atomic-forge/tools/webpage_scraper/requirements.txt: -------------------------------------------------------------------------------- 1 | atomic-agents>=1.0.0,<2.0.0 2 | pydantic>=2.8.2,<3.0.0 3 | beautifulsoup4>=4.12.0,<5.0.0 4 | markdownify>=0.11.0,<1.0.0 5 | readability-lxml>=0.8.1,<1.0.0 6 | requests>=2.31.0,<3.0.0 7 | lxml>=5.1.0,<6.0.0 8 | lxml-html-clean>=0.3.1,<1.0.0 9 | -------------------------------------------------------------------------------- /atomic-forge/tools/youtube_transcript_scraper/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = tool 3 | omit = */tests/* 4 | 5 | [report] 6 | exclude_lines = 7 | if __name__ == "__main__": 8 | show_missing = True 9 | -------------------------------------------------------------------------------- /atomic-forge/tools/youtube_transcript_scraper/README.md: -------------------------------------------------------------------------------- 1 | # YouTube Transcript Scraper Tool 2 | 3 | ## Overview 4 | The **YouTube Transcript Scraper Tool** is a tool within the Atomic Agents ecosystem that allows you to fetch the transcript of a YouTube video. 5 | 6 | ## Prerequisites and Dependencies 7 | - Python 3.9 or later 8 | - atomic-agents (See [here](/README.md) for installation instructions) 9 | - pydantic 10 | - google-api-python-client 11 | - youtube-transcript-api 12 | 13 | ## Installation 14 | You can install the tool using any of the following options: 15 | 16 | 1. Using the CLI tool that comes with Atomic Agents. Simply run `atomic` and select the tool from the list of available tools. After doing so you will be asked for a target directory to download the tool into. 17 | 2. Good old fashioned copy/paste: Just like any other tool inside the Atomic Forge, you can copy the code from this repo directly into your own project, provided you already have atomic-agents installed according to the instructions in the main [README](/README.md). 18 | 19 | ## Configuration 20 | 21 | ### Parameters 22 | 23 | - `api_key` (str): Your YouTube API key. Obtain this key by following the steps outlined in the [Obtaining a YouTube API Key](#obtaining-a-youtube-api-key) section. 24 | 25 | ### Example 26 | 27 | ```python 28 | config = YouTubeTranscriptToolConfig( 29 | api_key="your_youtube_api_key" 30 | ) 31 | ``` 32 | 33 | ### Obtaining a YouTube API Key 34 | 35 | To use this tool, you'll need a YouTube API key. Follow these steps to obtain one: 36 | 37 | 1. **Access the Google Developers Console** 38 | - Visit the [Google Developers Console](https://console.developers.google.com/). 39 | - Sign in with your Google account. If you don't have one, you'll need to create it. 40 | 41 | 2. **Create a New Project** 42 | - Click on the project dropdown in the top-left corner and select "New Project." 43 | - Enter a project name and click "Create." 44 | 45 | 3. **Enable the YouTube Data API v3** 46 | - In the dashboard, click on "Enable APIs and Services." 47 | - Search for "YouTube Data API v3" and select it. 48 | - Click the "Enable" button. 49 | 50 | 4. **Generate Your API Key** 51 | - Navigate to "Credentials" in the left sidebar. 52 | - Click on "Create Credentials" and select "API Key." 53 | - Copy the generated API key and use it in your configuration as shown above. 54 | 55 | ## Input & Output Structure 56 | 57 | ### Input Schema 58 | - `video_url` (str): URL of the YouTube video to fetch the transcript for. 59 | - `language` (Optional[str]): Language code for the transcript (e.g., `'en'` for English). 60 | 61 | ### Output Schema 62 | - `transcript` (str): Transcript of the YouTube video. 63 | - `duration` (float): Duration of the YouTube video. 64 | - `comments` (List[str]): Comments on the YouTube video. 65 | - `metadata` (dict): Metadata of the YouTube video. 66 | 67 | ## Usage 68 | 69 | Here's an example of how to use the YouTube Transcript Scraper Tool: 70 | 71 | ```python 72 | from tool.youtube_transcript_scraper import YouTubeTranscriptTool, YouTubeTranscriptToolConfig 73 | 74 | # Initialize the tool with your API key 75 | config = YouTubeTranscriptToolConfig(api_key="your_youtube_api_key") 76 | transcript_tool = YouTubeTranscriptTool(config=config) 77 | 78 | # Define input data 79 | input_data = YouTubeTranscriptTool.input_schema( 80 | video_url="https://www.youtube.com/watch?v=t1e8gqXLbsU", 81 | language="en" 82 | ) 83 | 84 | # Fetch the transcript 85 | result = transcript_tool.run(input_data) 86 | print(result) 87 | ``` 88 | 89 | ## Contributing 90 | 91 | Contributions are welcome! To contribute: 92 | 93 | 1. Fork the repository. 94 | 2. Create a new feature branch. 95 | 3. Commit your changes with clear messages. 96 | 4. Open a pull request detailing your changes. 97 | 98 | Please ensure you follow the project's coding standards and include tests for any new features or bug fixes. 99 | 100 | ## License 101 | 102 | This project is licensed under the same license as the main Atomic Agents project. See the [LICENSE](LICENSE) file in the repository root for more details. 103 | -------------------------------------------------------------------------------- /atomic-forge/tools/youtube_transcript_scraper/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "youtube-transcript-scraper" 3 | version = "1.0" 4 | description = "A tool for fetching and processing YouTube video transcripts" 5 | authors = ["Kenny Vaneetvelde "] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = ">=3.12,<4" 11 | atomic-agents = {path = "../../../", develop = true} 12 | pydantic = ">=2.10.3,<3.0.0" 13 | google-api-python-client = ">=2.118.0,<3" 14 | youtube-transcript-api = ">=0.6.2,<1" 15 | 16 | [tool.poetry.group.dev.dependencies] 17 | coverage = ">=7.6.1,<8.0.0" 18 | pytest = ">=8.3.3,<9.0.0" 19 | pytest-cov = ">=5.0.0,<6.0.0" 20 | python-dotenv = ">=1.0.0,<2.0.0" 21 | rich = ">=13.7.0,<14.0.0" 22 | 23 | [build-system] 24 | requires = ["poetry-core"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /atomic-forge/tools/youtube_transcript_scraper/requirements.txt: -------------------------------------------------------------------------------- 1 | atomic-agents>=1.0.0,<2.0.0 2 | google-api-python-client>=2.118.0,<3.0.0 3 | pydantic>=2.8.2,<3.0.0 4 | youtube-transcript-api>=0.6.2,<1.0.0 5 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/docs/.nojekyll -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | 3 | # You can set these variables from the command line, and also 4 | # from the environment for the first two. 5 | SPHINXOPTS ?= 6 | SPHINXBUILD ?= sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrainBlend-AI/atomic-agents/212bf9d44d3c80fcc0108192141c362b296f2f87/docs/_static/logo.png -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | This section contains the API reference for all public modules and classes in Atomic Agents. 4 | 5 | ```{toctree} 6 | :maxdepth: 2 7 | :caption: API Reference 8 | 9 | agents 10 | components 11 | utils 12 | ``` 13 | 14 | ## Core Components 15 | 16 | The Atomic Agents framework is built around several core components that work together to provide a flexible and powerful system for building AI agents. 17 | 18 | ### Agents 19 | 20 | The agents module provides the base classes for creating AI agents: 21 | 22 | - `BaseAgent`: The foundational agent class that handles interactions with LLMs 23 | - `BaseAgentConfig`: Configuration class for customizing agent behavior 24 | - `BaseAgentInputSchema`: Standard input schema for agent interactions 25 | - `BaseAgentOutputSchema`: Standard output schema for agent responses 26 | 27 | [Learn more about agents](agents.md) 28 | 29 | ### Components 30 | 31 | The components module contains essential building blocks: 32 | 33 | - `AgentMemory`: Manages conversation history and state with support for: 34 | - Message history with role-based messages 35 | - Turn-based conversation tracking 36 | - Multimodal content 37 | - Serialization and persistence 38 | - Memory size management 39 | 40 | - `SystemPromptGenerator`: Creates structured system prompts with: 41 | - Background information 42 | - Processing steps 43 | - Output instructions 44 | - Dynamic context through context providers 45 | 46 | - `SystemPromptContextProviderBase`: Base class for creating custom context providers that can inject dynamic information into system prompts 47 | 48 | [Learn more about components](components.md) 49 | 50 | ### Utils 51 | 52 | The utils module provides helper functions and utilities: 53 | 54 | - Message formatting 55 | - Tool response handling 56 | - Schema validation 57 | - Error handling 58 | 59 | [Learn more about utilities](utils.md) 60 | 61 | ## Getting Started 62 | 63 | For practical examples and guides on using these components, see: 64 | 65 | - [Quickstart Guide](../guides/quickstart.md) 66 | - [Tools Guide](../guides/tools.md) 67 | -------------------------------------------------------------------------------- /docs/api/utils.md: -------------------------------------------------------------------------------- 1 | # Utilities 2 | 3 | ## Tool Message Formatting 4 | 5 | ```{eval-rst} 6 | .. automodule:: atomic_agents.lib.utils.format_tool_message 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.abspath("..")) 5 | 6 | project = "Atomic Agents" 7 | copyright = "2024, Kenny Vaneetvelde" 8 | author = "Kenny Vaneetvelde" 9 | version = "1.1.0" 10 | release = "1.1.0" 11 | 12 | extensions = [ 13 | "sphinx.ext.autodoc", 14 | "sphinx.ext.napoleon", 15 | "sphinx.ext.viewcode", 16 | "sphinx.ext.intersphinx", 17 | "myst_parser", 18 | "sphinx_copybutton", 19 | "sphinx_design", 20 | ] 21 | 22 | templates_path = ["_templates"] 23 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 24 | 25 | html_theme = "sphinx_rtd_theme" 26 | html_static_path = ["_static"] 27 | 28 | # Intersphinx configuration 29 | intersphinx_mapping = { 30 | "python": ("https://docs.python.org/3", None), 31 | "pydantic": ("https://docs.pydantic.dev/latest/", None), 32 | } 33 | 34 | # MyST configuration 35 | myst_enable_extensions = [ 36 | "colon_fence", 37 | "deflist", 38 | ] 39 | 40 | # Autodoc configuration 41 | autodoc_default_options = { 42 | "members": True, 43 | "member-order": "bysource", 44 | "special-members": "__init__", 45 | "undoc-members": True, 46 | "exclude-members": "__weakref__", 47 | } 48 | -------------------------------------------------------------------------------- /docs/examples/index.md: -------------------------------------------------------------------------------- 1 | # Example Projects 2 | 3 | This section contains detailed examples of using Atomic Agents in various scenarios. 4 | 5 | ## Quickstart Examples 6 | 7 | Simple examples to get started with the framework: 8 | 9 | - Basic chatbot with memory 10 | - Custom chatbot with personality 11 | - Streaming responses 12 | - Custom input/output schemas 13 | - Multiple provider support 14 | 15 | [View Quickstart Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/quickstart) 16 | 17 | ## Basic Multimodal 18 | 19 | Examples of working with images and text: 20 | 21 | - Image analysis with text descriptions 22 | - Image-based question answering 23 | - Visual content generation 24 | - Multi-image comparisons 25 | 26 | [View Basic Multimodal Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/basic-multimodal) 27 | 28 | ## RAG Chatbot 29 | 30 | Build context-aware chatbots with retrieval-augmented generation: 31 | 32 | - Document indexing and embedding 33 | - Semantic search integration 34 | - Context-aware responses 35 | - Source attribution 36 | - Follow-up suggestions 37 | 38 | [View RAG Chatbot Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/rag-chatbot) 39 | 40 | ## Web Search Agent 41 | 42 | Create agents that can search and analyze web content: 43 | 44 | - Web search integration 45 | - Content extraction 46 | - Result synthesis 47 | - Multi-source research 48 | - Citation tracking 49 | 50 | [View Web Search Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/web-search-agent) 51 | 52 | ## Deep Research 53 | 54 | Perform comprehensive research tasks: 55 | 56 | - Multi-step research workflows 57 | - Information synthesis 58 | - Source validation 59 | - Structured output generation 60 | - Citation management 61 | 62 | [View Deep Research Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/deep-research) 63 | 64 | ## YouTube Summarizer 65 | 66 | Extract and analyze information from videos: 67 | 68 | - Transcript extraction 69 | - Content summarization 70 | - Key point identification 71 | - Timestamp linking 72 | - Chapter generation 73 | 74 | [View YouTube Summarizer Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/youtube-summarizer) 75 | 76 | ## YouTube to Recipe 77 | 78 | Convert cooking videos into structured recipes: 79 | 80 | - Video analysis 81 | - Recipe extraction 82 | - Ingredient parsing 83 | - Step-by-step instructions 84 | - Time and temperature conversion 85 | 86 | [View YouTube Recipe Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/youtube-to-recipe) 87 | 88 | ## Orchestration Agent 89 | 90 | Coordinate multiple agents for complex tasks: 91 | 92 | - Agent coordination 93 | - Task decomposition 94 | - Progress tracking 95 | - Error handling 96 | - Result aggregation 97 | 98 | [View Orchestration Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/orchestration-agent) 99 | 100 | ## MCP Agent 101 | 102 | Build intelligent agents using the Model Context Protocol: 103 | 104 | - Server implementation with multiple transport methods 105 | - Dynamic tool discovery and registration 106 | - Natural language query processing 107 | - Stateful conversation handling 108 | - Extensible tool architecture 109 | 110 | [View MCP Agent Documentation](mcp_agent.md) 111 | [View MCP Agent Examples](https://github.com/BrainBlend-AI/atomic-agents/tree/main/atomic-examples/mcp-agent) 112 | -------------------------------------------------------------------------------- /docs/guides/index.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | This section contains detailed guides for working with Atomic Agents. 4 | 5 | ```{toctree} 6 | :maxdepth: 2 7 | :caption: Guides 8 | 9 | quickstart 10 | basic_concepts 11 | tools 12 | advanced_usage 13 | ``` 14 | 15 | ## Implementation Patterns 16 | 17 | The framework supports various implementation patterns and use cases: 18 | 19 | ### Chatbots and Assistants 20 | 21 | - Basic chat interfaces with any LLM provider 22 | - Streaming responses 23 | - Custom response schemas 24 | - Suggested follow-up questions 25 | - Memory management and context retention 26 | - Multi-turn conversations 27 | 28 | ### RAG Systems 29 | 30 | - Query generation and optimization 31 | - Context-aware responses 32 | - Document Q&A with source tracking 33 | - Information synthesis and summarization 34 | - Custom embedding and retrieval strategies 35 | - Hybrid search approaches 36 | 37 | ### Specialized Agents 38 | 39 | - YouTube video summarization and analysis 40 | - Web search and deep research 41 | - Recipe generation from various sources 42 | - Multimodal interactions (text, images, etc.) 43 | - Custom tool integration 44 | - Task orchestration 45 | 46 | ## Provider Integration Guide 47 | 48 | Atomic Agents is designed to be provider-agnostic. Here's how to work with different providers: 49 | 50 | ### Provider Selection 51 | 52 | - Choose any provider supported by Instructor 53 | - Configure provider-specific settings 54 | - Handle rate limits and quotas 55 | - Implement fallback strategies 56 | 57 | ### Local Development 58 | 59 | - Use Ollama for local testing 60 | - Mock responses for development 61 | - Debug provider interactions 62 | - Test provider switching 63 | 64 | ### Production Deployment 65 | 66 | - Load balancing between providers 67 | - Failover configurations 68 | - Cost optimization strategies 69 | - Performance monitoring 70 | 71 | ### Custom Provider Integration 72 | 73 | - Extend Instructor for new providers 74 | - Implement custom client wrappers 75 | - Add provider-specific features 76 | - Handle unique response formats 77 | 78 | ## Best Practices 79 | 80 | ### Error Handling 81 | 82 | - Implement proper exception handling 83 | - Add retry mechanisms 84 | - Log provider errors 85 | - Handle rate limits gracefully 86 | 87 | ### Performance Optimization 88 | 89 | - Use streaming for long responses 90 | - Implement caching strategies 91 | - Optimize prompt lengths 92 | - Batch operations when possible 93 | 94 | ### Security 95 | 96 | - Secure API key management 97 | - Input validation and sanitization 98 | - Output filtering 99 | - Rate limiting and quotas 100 | 101 | ## Getting Help 102 | 103 | If you need help, you can: 104 | 105 | 1. Check our [GitHub Issues](https://github.com/BrainBlend-AI/atomic-agents/issues) 106 | 2. Join our [Reddit community](https://www.reddit.com/r/AtomicAgents/) 107 | 3. Read through our examples in the repository 108 | 4. Review the example projects in `atomic-examples/` 109 | 110 | **See also**: 111 | - [API Reference](/api/index) - Browse the API reference 112 | - [Main Documentation](/index) - Return to main documentation 113 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /guides/DEV_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Atomic Agents Development Guide 2 | 3 | This guide provides instructions for developers who want to contribute to the Atomic Agents project. It covers the project structure, setup, development workflow, and best practices. 4 | 5 | ## Project Structure 6 | 7 | Atomic Agents uses a monorepo structure, which means multiple related projects are managed in a single repository. The main components are: 8 | 9 | 1. `atomic-agents/`: The core Atomic Agents library 10 | 2. `atomic-assembler/`: The CLI tool for managing Atomic Agents components 11 | 3. `atomic-examples/`: Example projects showcasing Atomic Agents usage 12 | 4. `atomic-forge/`: A collection of tools that can be used with Atomic Agents 13 | 14 | ## Getting Started 15 | 16 | ### Prerequisites 17 | 18 | - Python 3.11 or higher 19 | - Poetry (for dependency management) 20 | - Git 21 | 22 | ### Setting Up the Development Environment 23 | 24 | 1. Fork the repository on GitHub. 25 | 2. Clone your fork locally: 26 | ``` 27 | git clone https://github.com/BrainBlend-AI/atomic-agents.git 28 | cd atomic-agents 29 | ``` 30 | 3. Install dependencies using Poetry: 31 | ``` 32 | poetry install 33 | ``` 34 | 4. Activate the virtual environment: 35 | ``` 36 | # For Poetry < 2.0 37 | poetry shell 38 | 39 | # For Poetry >= 2.0 40 | poetry env activate 41 | ``` 42 | 43 | ## Development Workflow 44 | 45 | 1. Create a new branch for your feature or bugfix: 46 | ``` 47 | git checkout -b feature-branch 48 | ``` 49 | 50 | 2. Make your changes in the appropriate project directory. 51 | 52 | 3. Format your code using Black: 53 | ``` 54 | poetry run black atomic-agents atomic-assembler atomic-examples atomic-forge 55 | ``` 56 | 57 | 4. Lint your code using Flake8: 58 | ``` 59 | poetry run flake8 --extend-exclude=.venv atomic-agents atomic-assembler atomic-examples atomic-forge 60 | ``` 61 | 62 | 5. Run the tests: 63 | ``` 64 | poetry run pytest --cov=atomic_agents atomic-agents 65 | ``` 66 | 67 | 6. If you've added new functionality, make sure to add appropriate tests. 68 | 69 | 7. Commit your changes: 70 | ``` 71 | git commit -m 'Add some feature' 72 | ``` 73 | 74 | 8. Push to your fork: 75 | ``` 76 | git push origin feature-branch 77 | ``` 78 | 79 | 9. Open a pull request on GitHub. 80 | 81 | ## Code Style and Best Practices 82 | 83 | - Follow PEP 8 guidelines for Python code style. 84 | - Use type hints wherever possible. 85 | - Write clear, concise docstrings for all public modules, functions, classes, and methods. 86 | - Keep functions and methods small and focused on a single responsibility. 87 | - Use meaningful variable and function names. 88 | 89 | ## Testing 90 | 91 | - Write unit tests for all new functionality. 92 | - Make sure to get 100% test coverage for all new functionality. 93 | - Run the test suite before submitting a pull request: 94 | ``` 95 | pytest --cov atomic_agents 96 | ``` 97 | - To view a detailed coverage report: 98 | ``` 99 | coverage html 100 | ``` 101 | This will generate an HTML report in the `htmlcov/` directory. 102 | 103 | ## Documentation 104 | 105 | - Update the README.md file if you've added new features or changed existing functionality. 106 | - If you've added new modules or significant features, consider updating the API documentation. 107 | 108 | ## Submitting Changes 109 | 110 | - Create a pull request with a clear title and description. 111 | - Link any relevant issues in the pull request description. 112 | - Make sure all tests pass and there are no linting errors. 113 | - Be responsive to code review feedback and make necessary changes. 114 | 115 | ## Questions and Support 116 | 117 | If you have any questions or need support while developing, please open an issue on GitHub or reach out to the maintainers. 118 | 119 | Thank you for contributing to Atomic Agents! 120 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.0.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "atomic-agents" 7 | version = "1.1.3" 8 | description = "A versatile framework for creating and managing intelligent agents." 9 | authors = ["Kenny Vaneetvelde "] 10 | readme = "README.md" 11 | license = "MIT" 12 | packages = [ 13 | { include = "atomic_agents", from = "atomic-agents" }, 14 | { include = "atomic_assembler", from = "atomic-assembler" } 15 | ] 16 | 17 | [tool.poetry.dependencies] 18 | python = ">=3.10,<4.0" 19 | instructor = ">=1.3.4,<2.0.0" 20 | pydantic = ">=2.11.0,<3.0.0" 21 | rich = ">=13.7.1,<14.0.0" 22 | gitpython = ">=3.1.43,<4.0.0" 23 | pyfiglet = ">=1.0.2,<2.0.0" 24 | textual = ">=0.82.0,<1.0.0" 25 | pyyaml = ">=6.0.2,<7.0.0" 26 | requests = ">=2.32.3,<3.0.0" 27 | mcp = {extras = ["cli"], version = "^1.6.0"} 28 | 29 | [tool.poetry.group.dev.dependencies] 30 | black = ">=24.8.0,<25.0.0" 31 | flake8 = ">=7.1.1,<8.0.0" 32 | pdoc3 = ">=0.11.1,<1.0.0" 33 | pytest = ">=8.3.3,<9.0.0" 34 | pytest-cov = ">=5.0.0,<6.0.0" 35 | pytest-asyncio = ">=0.24.0,<1.0.0" 36 | openai = ">=1.35.12,<2.0.0" 37 | sphinx = ">=7.2.6,<8.0.0" 38 | sphinx-rtd-theme = ">=2.0.0,<3.0.0" 39 | myst-parser = ">=2.0.0,<3.0.0" 40 | sphinx-copybutton = ">=0.5.2,<1.0.0" 41 | sphinx-design = ">=0.5.0,<1.0.0" 42 | sphinx-autobuild = ">=2024.2.4,<2025.0.0" 43 | beautifulsoup4 = ">=4.12.3,<5.0.0" 44 | markdownify = ">=0.9.0,<1.0.0" 45 | 46 | [tool.poetry.scripts] 47 | atomic = "atomic_assembler.main:main" 48 | 49 | [tool.poetry.urls] 50 | Homepage = "https://github.com/BrainBlend-AI/atomic-agents" 51 | Repository = "https://github.com/BrainBlend-AI/atomic-agents" 52 | 53 | [tool.black] 54 | line-length = 127 55 | 56 | 57 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | instructor>=1.3.4,<2.0.0 2 | pydantic>=2.10.3,<3.0.0 3 | rich>=13.7.1,<14.0.0 4 | gitpython>=3.1.43,<4.0.0 5 | pyfiglet>=1.0.2,<2.0.0 6 | textual>=0.82.0,<1.0.0 7 | pyyaml>=6.0.2,<7.0.0 8 | requests>=2.32.3,<3.0.0 9 | atomic-agents>=1.0.0,<2.0.0 10 | -------------------------------------------------------------------------------- /scripts/generate_llms_txt.py: -------------------------------------------------------------------------------- 1 | import os 2 | from bs4 import BeautifulSoup 3 | from markdownify import markdownify as md 4 | 5 | # Paths 6 | docs_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'docs')) 7 | single_dir = os.path.join(docs_dir, '_build', 'singlehtml') 8 | llms_txt_path = os.path.join(docs_dir, 'llms.txt') 9 | 10 | # Read single HTML index 11 | html_file = os.path.join(single_dir, 'index.html') 12 | if not os.path.exists(html_file): 13 | print("Error: singlehtml output not found. Build with sphinx-build -b singlehtml.") 14 | exit(1) 15 | 16 | with open(html_file, encoding='utf-8') as f: 17 | html = f.read() 18 | 19 | soup = BeautifulSoup(html, 'html.parser') 20 | # Extract main documentation content 21 | doc_main = soup.find('div', {'role': 'main'}) or soup.find('div', class_='document') 22 | content_html = str(doc_main) if doc_main else html 23 | 24 | # Convert to Markdown 25 | md_text = md(content_html) 26 | 27 | # Write full documentation to llms.txt 28 | with open(llms_txt_path, 'w', encoding='utf-8') as out_f: 29 | out_f.write(md_text) 30 | 31 | print(f"Generated full llms.txt at {llms_txt_path}") 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | # Read the version from pyproject.toml 4 | with open("pyproject.toml", "r") as f: 5 | for line in f: 6 | if line.startswith("version = "): 7 | version = line.split("=")[1].strip().strip('"') 8 | break 9 | 10 | setup( 11 | name="atomic-agents", 12 | version=version, 13 | packages=find_packages(where="atomic-agents") + find_packages(where="atomic-assembler"), 14 | package_dir={"atomic_agents": "atomic-agents/atomic_agents", "atomic_assembler": "atomic-assembler/atomic_assembler"}, 15 | include_package_data=True, 16 | ) 17 | --------------------------------------------------------------------------------