Copy disabled (too large)
Download .txt
Showing preview only (10,049K chars total). Download the full file to get everything.
Repository: ShoggothAI/motleycrew
Branch: main
Commit: 527bbb8ccbd8
Files: 186
Total size: 9.5 MB
Directory structure:
gitextract_mhfszbdc/
├── .coderabbit.yaml
├── .flake8
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── .mcp.json
├── .readthedocs.yaml
├── CLAUDE.md
├── LICENSE
├── Makefile
├── README.md
├── docs/
│ ├── Makefile
│ ├── make.bat
│ └── source/
│ ├── _static/
│ │ └── .gitkeep
│ ├── _templates/
│ │ ├── .gitkeep
│ │ └── autosummary/
│ │ ├── base.rst
│ │ └── module.rst
│ ├── agents.rst
│ ├── api.rst
│ ├── autogen.rst
│ ├── caching_observability.nblink
│ ├── choosing_llms.rst
│ ├── conf.py
│ ├── examples/
│ │ ├── advanced_output_handling.nblink
│ │ ├── blog_with_images.nblink
│ │ ├── customer_support.rst
│ │ ├── event_driven.nblink
│ │ ├── integrating_autogen.nblink
│ │ ├── math_single_agent.nblink
│ │ ├── research_agent.nblink
│ │ ├── streaming_agent_output.nblink
│ │ └── validating_agent_output.nblink
│ ├── examples.rst
│ ├── index.rst
│ ├── install_pandoc.py
│ ├── installation.rst
│ ├── key_concepts.rst
│ ├── key_value_store.rst
│ ├── knowledge_graph.nblink
│ ├── quickstart.rst
│ └── usage.rst
├── examples/
│ ├── Advanced output handling.ipynb
│ ├── Blog with Images.ipynb
│ ├── Caching and observability.ipynb
│ ├── Event-driven orchestration for AI systems.ipynb
│ ├── Interaction with the knowledge graph.ipynb
│ ├── Math via python code with a single agent.ipynb
│ ├── Multi-step research agent.ipynb
│ ├── Streaming agent output.ipynb
│ ├── Using AutoGen with motleycrew.ipynb
│ ├── Validating agent output.ipynb
│ ├── __init__.py
│ ├── aider_code_generation.py
│ ├── blog_post/
│ │ ├── blog_post.py
│ │ └── blog_post_input.py
│ ├── data/
│ │ ├── groupchat/
│ │ │ ├── fetch_arxiv_gpt4.py
│ │ │ └── fetch_latest_gpt4_paper.py
│ │ └── research_agent_storage/
│ │ ├── default__vector_store.json
│ │ ├── docstore.json
│ │ ├── graph_store.json
│ │ ├── image__vector_store.json
│ │ └── index_store.json
│ ├── hacking dependencies.ipynb
│ ├── image_from_gslides_example.py
│ ├── key_value_store.py
│ ├── llama_index_output_handler.py
│ ├── recognize_charts.py
│ ├── submit_image.py
│ └── tool_calling_with_memory.py
├── motleycrew/
│ ├── __init__.py
│ ├── agents/
│ │ ├── __init__.py
│ │ ├── abstract_parent.py
│ │ ├── langchain/
│ │ │ ├── __init__.py
│ │ │ ├── langchain.py
│ │ │ ├── legacy_react.py
│ │ │ ├── tool_calling_react.py
│ │ │ └── tool_calling_react_prompts.py
│ │ ├── llama_index/
│ │ │ ├── __init__.py
│ │ │ ├── llama_index.py
│ │ │ └── llama_index_react.py
│ │ ├── mixins.py
│ │ └── parent.py
│ ├── applications/
│ │ ├── __init__.py
│ │ ├── customer_support/
│ │ │ ├── README.md
│ │ │ ├── communication.py
│ │ │ ├── example_issues.csv
│ │ │ ├── issue_tree.py
│ │ │ ├── ray_serve_app.py
│ │ │ ├── requirements.txt
│ │ │ ├── static/
│ │ │ │ └── index.html
│ │ │ └── support_agent.py
│ │ ├── expenses/
│ │ │ ├── expenses.py
│ │ │ ├── schema_delta.py
│ │ │ └── sql_tools.py
│ │ ├── faust_workflow/
│ │ │ ├── __init__.py
│ │ │ ├── faust_workflow.py
│ │ │ └── visualize.py
│ │ └── research_agent/
│ │ ├── __init__.py
│ │ ├── answer_task.py
│ │ ├── question.py
│ │ ├── question_answerer.py
│ │ ├── question_generator.py
│ │ ├── question_prioritizer.py
│ │ └── question_task.py
│ ├── common/
│ │ ├── __init__.py
│ │ ├── aux_prompts.py
│ │ ├── defaults.py
│ │ ├── enums.py
│ │ ├── exceptions.py
│ │ ├── llms.py
│ │ ├── logging.py
│ │ ├── types.py
│ │ └── utils.py
│ ├── crew/
│ │ ├── __init__.py
│ │ ├── crew.py
│ │ └── crew_threads.py
│ ├── storage/
│ │ ├── __init__.py
│ │ ├── graph_node.py
│ │ ├── graph_store.py
│ │ ├── graph_store_utils.py
│ │ ├── kuzu_graph_store.py
│ │ └── kv_store_domain.py
│ ├── tasks/
│ │ ├── __init__.py
│ │ ├── simple.py
│ │ ├── task.py
│ │ └── task_unit.py
│ ├── tools/
│ │ ├── __init__.py
│ │ ├── agentic_validation_loop.py
│ │ ├── autogen_chat_tool.py
│ │ ├── code/
│ │ │ ├── __init__.py
│ │ │ ├── aider_tool.py
│ │ │ ├── postgresql_linter.py
│ │ │ ├── python_linter.py
│ │ │ └── python_repl.py
│ │ ├── html_render_tool.py
│ │ ├── image/
│ │ │ ├── __init__.py
│ │ │ ├── dall_e.py
│ │ │ ├── download_image.py
│ │ │ └── replicate_tool.py
│ │ ├── llm_tool.py
│ │ ├── mermaid_evaluator_tool.py
│ │ ├── simple_retriever_tool.py
│ │ ├── sql_validation.py
│ │ ├── structured_passthrough.py
│ │ └── tool.py
│ ├── tracking/
│ │ ├── __init__.py
│ │ ├── callbacks.py
│ │ └── utils.py
│ └── utils/
│ ├── __init__.py
│ ├── chart_to_df.py
│ ├── image_utils.py
│ └── structured_output_with_retries.py
├── pyproject.toml
├── pytest.ini
├── requirements-extra.txt
└── tests/
├── __init__.py
├── conftest.py
├── run_integration_tests.py
├── test_agents/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_agent_chain.py
│ ├── test_agents.py
│ ├── test_kv_store_tools.py
│ ├── test_langchain_output_handler.py
│ └── test_llms.py
├── test_crew/
│ ├── __init__.py
│ ├── test_crew.py
│ └── test_crew_threads.py
├── test_image_utils.py
├── test_storage/
│ ├── __init__.py
│ ├── test_graph_store.py
│ └── test_kuzu_graph_store.py
├── test_tasks/
│ ├── __init__.py
│ ├── test_simple_task.py
│ ├── test_task.py
│ └── test_task_unit.py
└── test_tools/
├── __init__.py
├── test_agentic_validation_loop.py
├── test_html_render_tool.py
├── test_image_utils.py
├── test_linter_tools.py
├── test_repl_tool.py
├── test_sql_validation.py
├── test_structured_passthrough_tool.py
├── test_tool.py
└── test_tool_chain.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .coderabbit.yaml
================================================
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
reviews:
auto_review:
enabled: true
drafts: true # Enable reviews on draft PRs
base_branches: ['.*'] # Enable reviews on all branches
================================================
FILE: .flake8
================================================
[flake8]
max-line-length=88
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
schedule:
- cron: "0 2 * * *" # integration tests at 2am UTC nightly
# If another push to the same PR or branch happens while this workflow is still running,
# cancel the earlier run in favor of the next run.
#
# There's no point in testing an outdated version of the code. GitHub only allows
# a limited number of job runners to be active at the same time, so it's better to cancel
# pointless jobs early so that more useful jobs can run sooner.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
id: setup-python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
run: pip install -U poetry
- name: Configure poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry config installer.parallel true
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install extra dependencies
run: poetry run pip install -r requirements-extra.txt
- name: Install dependencies
run: poetry install --no-interaction --all-extras --with dev
- name: Run tests
run: poetry run pytest --cov=motleycrew tests/
integration-test:
needs: test
if: |
github.event_name == 'push' && github.ref == 'refs/heads/main' ||
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch'
environment: integration_test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
id: setup-python
uses: actions/setup-python@v4
with:
python-version: 3.12
- name: Install poetry
run: pip install -U poetry
- name: Configure poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry config installer.parallel true
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Load test cache
uses: actions/cache@v4
with:
path: tests/itest_cache
enableCrossOsArchive: true
key: integration-tests-cache
restore-keys: |
integration-tests-cache
- name: Install extra dependencies
run: poetry run pip install -r requirements-extra.txt
- name: Install dependencies
run: poetry install --no-interaction --all-extras
- name: Run integration tests
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
TIKTOKEN_CACHE_DIR: tests/itest_cache/tiktoken_cache
run: poetry run python tests/run_integration_tests.py
build:
needs: integration-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
id: setup-python
uses: actions/setup-python@v4
with:
python-version: 3.12
- name: Install poetry
run: pip install -U poetry
- name: Configure poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry config installer.parallel true
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install project
run: poetry install --no-interaction --with dev --all-extras
- name: Run build
run: poetry build
- name: Install pandoc
working-directory: ./docs/source
run: poetry run python install_pandoc.py
- name: Run docs build
env:
TZ: UTC
working-directory: ./docs
run: poetry run make html
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on:
release:
types: [published]
permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-latest
permissions:
# This permission is used for trusted publishing:
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
#
# Trusted publishing has to also be configured on PyPI for each package:
# https://docs.pypi.org/trusted-publishers/adding-a-publisher/
id-token: write
steps:
- uses: actions/checkout@v4
- name: Set up Python
id: setup-python
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Install poetry
run: pip install -U poetry
- name: Configure poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry config installer.parallel true
poetry config pypi-token.pypi "${{ secrets.PYPI_API_KEY }}"
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
run: poetry install --no-interaction
- name: Build using poetry
run: poetry build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
================================================
FILE: .gitignore
================================================
# Editors
.vscode/
.idea/
# Vagrant
.vagrant/
# Mac/OSX
.DS_Store
# Windows
Thumbs.db
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# IPython
profile_default/
ipython_config.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.python-version
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
*.pyc
scripts/
tests/itest_cache/
# docs-related stuff
docs/source/_autosummary
================================================
FILE: .mcp.json
================================================
{
"mcpServers": {
"mcp-omnisearch": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"mcp-omnisearch"
],
"env": {
"PERPLEXITY_API_KEY": "${PERPLEXITY_API_KEY}"
}
},
"sequential-thinking": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-sequential-thinking"
]
},
"context7": {
"command": "npx",
"args": [
"-y",
"@upstash/context7-mcp@latest"
]
},
"linear": {
"type": "sse",
"url": "https://mcp.linear.app/sse"
},
"notion": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@notionhq/notion-mcp-server"
],
"env": {
"NOTION_TOKEN": "${NOTION_API_KEY}"
}
},
"notion-markdown": {
"command": "npx",
"args": ["gwanli-mcp"],
"env": {
"NOTION_TOKEN": "${NOTION_API_KEY}"
}
}
}
}
================================================
FILE: .readthedocs.yaml
================================================
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "19"
# rust: "1.64"
# golang: "1.19"
apt_packages:
- pandoc
jobs:
post_create_environment:
# Install poetry
# https://python-poetry.org/docs/#installing-manually
- python -m pip install poetry
post_install:
# Install dependencies with 'docs' dependency group
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
#python:
# install:
# - requirements: docs/requirements.txt
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
MotleyCrew is a Python framework for building multi-agent AI systems. It orchestrates AI agents and tools from various frameworks (Langchain, LlamaIndex, CrewAI, Autogen) using a knowledge graph backend and provides workflow management for complex AI tasks.
## Development Commands
### Setup and Installation
```bash
pip install poetry # If not already installed
poetry install # Install dependencies
```
### Testing
```bash
poetry run pytest # Run tests (excludes "fat" tests by default)
poetry run pytest --cov --cov-report=term-missing # Run tests with coverage
poetry run pytest -m fat # Run resource-intensive tests
make test # Alternative test command
make cov # Test with coverage using Makefile
```
### Code Quality and Linting
```bash
poetry run black . # Format code
poetry run flake8 . # Check style
poetry run mypy . # Type checking
poetry run isort . # Sort imports
```
### Build and Release
```bash
poetry build # Build package
make build # Alternative build command
make clean # Clean build artifacts
make all # Clean, test with coverage, and build
```
## Architecture
### Core Components
**MotleyCrew**: Main orchestration class that executes tasks and manages agents using a graph store backend.
**Tasks**: Abstract base classes for describing work units:
- `Task`: Generic task interface with knowledge graph integration
- `SimpleTask`: Basic task implementation with agent and description
- Tasks can be chained using `>>` operator for sequential execution
**Agents**: Wrapper classes for different AI agent frameworks:
- `motleycrew.agents.langchain/`: Langchain agent implementations
- `motleycrew.agents.llama_index/`: LlamaIndex agent implementations
- Support for ReAct, tool-calling, and other agent types
**Knowledge Graph**: Kuzu-based graph store for task orchestration and data persistence:
- `motleycrew.storage.kuzu_graph_store`: Primary graph store implementation
- `motleycrew.storage.graph_store_utils`: Utilities for graph operations
- Used for task dependencies, data flow, and system state
**Tools**: Integration layer for external capabilities:
- `motleycrew.tools.code/`: Code-related tools (Python REPL, linters)
- `motleycrew.tools.image/`: Image generation tools
- Tools implement Langchain's Runnable API for compatibility
### Key Patterns
- All components implement Langchain's Runnable interface for LCEL compatibility
- Tasks are stored and managed in a knowledge graph for complex workflow orchestration
- Async execution support with configurable backends (asyncio, threading)
- Built-in caching (motleycache) and observability (Lunary) integrations
### Test Structure
- Unit tests in `tests/` organized by component
- Integration tests marked with `fat` marker for resource-intensive scenarios
- `conftest.py` files provide test fixtures and configuration
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 motleycrew
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
.PHONY: all
all: clean cov build
.PHONY: clean
clean:
rm -rf dist/
.PHONY: test
test:
poetry run pytest
.PHONY: cov
cov:
poetry run pytest --cov --cov-report=term-missing
.PHONY: build
build:
poetry build
================================================
FILE: README.md
================================================
# motleycrew
[](https://pypi.org/project/motleycrew/)
[](https://github.com/ShoggothAI/motleycrew/actions/workflows/ci.yml)
[Website](https://motleycrew.ai) •︎ [Documentation](https://motleycrew.readthedocs.io)
Welcome to motleycrew, your ultimate framework for building multi-agent AI systems. With motleycrew, you can seamlessly mix and match AI agents and tools from popular frameworks, design advanced workflows, and leverage dynamic knowledge graphs — all with simplicity and elegance.
Think of motleycrew as a conductor that orchestrates a symphony of AI agents and tools. It provides building blocks for creating AI systems, enabling you to focus on the high-level design while motleycrew takes care of the rest.
## Features
- **Integration**: Combine AI agents and tools from Langchain, LlamaIndex, CrewAI, and Autogen. Use tools from Langchain and LlamaIndex, with more integrations coming soon.
- **Flexibility**: Provide your agents with any tools or even other agents. All components implement Langchain's Runnable API, making them compatible with LCEL.
- **Advanced Flow Design**: Design systems of any complexity by just coding a brief set of rules. Simply chain tasks together or utilize knowledge graphs for sophisticated flow design.
- **Caching and Observability**: Built-in open-source observability with [Lunary](https://github.com/lunary-ai/lunary) and caching of HTTP requests, including LLM API calls, with [motleycache](https://github.com/ShoggothAI/motleycache).
See our [quickstart](https://motleycrew.readthedocs.io/en/latest/quickstart.html) page for an overview of the framework and its capabilities.
## Getting started
### Installation
```
pip install motleycrew
```
### First steps
To get you started, here's a simple example of how to create a crew with two agents: a writer and an illustrator. The writer will write a short article, and the illustrator will illustrate it.
```python
from motleycrew import MotleyCrew
from motleycrew.agents.langchain import ReActToolCallingMotleyAgent
from motleycrew.tasks import SimpleTask
from motleycrew.tools.image.dall_e import DallEImageGeneratorTool
from langchain_community.tools import DuckDuckGoSearchRun
crew = MotleyCrew()
writer = ReActToolCallingMotleyAgent(name="writer", tools=[DuckDuckGoSearchRun()])
illustrator = ReActToolCallingMotleyAgent(name="illustrator", tools=[DallEImageGeneratorTool()])
write_task = SimpleTask(
crew=crew, agent=writer, description="Write a short article about latest AI advancements"
)
illustrate_task = SimpleTask(
crew=crew, agent=illustrator, description="Illustrate the given article"
)
write_task >> illustrate_task
crew.run()
print(write_task.output)
print(illustrate_task.output)
```
Here, we have a chain of two consecutive tasks. A SimpleTask basically just contains a prompt and an agent that will execute it. The `>>` operator is used to chain tasks together.
If you want to learn more about creating flows in such fashion, see our [blog with images](https://motleycrew.readthedocs.io/en/latest/examples/blog_with_images.html) example.
### Knowledge graph and custom tasks
Under the hood, the tasks are stored in a knowledge graph, as well as all the data needed for their execution. You can create custom tasks that utilize the knowledge graph in any way you want. The graph can be used to control the flow of your system, or simply as a universal data store.
Please read our docs on [key concepts and API](https://motleycrew.readthedocs.io/en/latest/key_concepts.html) to learn more about creating custom tasks and using the knowledge graph. Also, see how it all comes alive in the [research agent](https://motleycrew.readthedocs.io/en/latest/examples/research_agent.html) example.
### Caching and observability
We provide a universal HTTP caching tool, [motleycache](https://github.com/ShoggothAI/motleycache), also available as a separate package. It can cache all HTTP requests made by your agents, including LLM and tool calls, out of the box. This is especially useful for debugging and testing.
Motleycrew also comes with support for [Lunary](https://github.com/lunary-ai/lunary), an open-source observability platform. You can use it to monitor your agents' performance, visualize the flow of your system, and more.
To learn more about these features, see our [caching and observability](https://motleycrew.readthedocs.io/en/latest/caching_observability.html) docs.
### Examples
We have a small but growing collection of examples in our [documentation](https://motleycrew.readthedocs.io/en/latest/examples.html).
- For a working example of agents, tools, crew, and SimpleTask, check out the [blog with images](https://motleycrew.readthedocs.io/en/latest/examples/blog_with_images.html).
- For a working example of custom tasks that fully utilize the knowledge graph backend, check out the [research agent](https://motleycrew.readthedocs.io/en/latest/examples/research_agent.html).
## Support and contributions
We have a community [Discord server](https://discord.gg/P4Pxqf9MEs) where you can ask questions, share your ideas, and get help with your projects.
If you find a bug or have a feature request, feel free to [open an issue](https://github.com/ShoggothAI/motleycrew/issues/new) in this repository.
Contributions of any kind are also welcome!
================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
================================================
FILE: docs/make.bat
================================================
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
================================================
FILE: docs/source/_static/.gitkeep
================================================
================================================
FILE: docs/source/_templates/.gitkeep
================================================
================================================
FILE: docs/source/_templates/autosummary/base.rst
================================================
{{ fullname | escape | underline }}
.. currentmodule:: {{ module }}
.. auto{{ objtype }}:: {{ objname }}
================================================
FILE: docs/source/_templates/autosummary/module.rst
================================================
{{ fullname | escape | underline}}
.. automodule:: {{ fullname }}
{% block attributes %}
{% if attributes %}
.. rubric:: {{ _('Module Attributes') }}
.. autosummary::
{% for item in attributes %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block functions %}
{% if functions %}
.. rubric:: {{ _('Functions') }}
.. autosummary::
{% for item in functions %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block classes %}
{% if classes %}
.. rubric:: {{ _('Classes') }}
.. autosummary::
{% for item in classes %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block exceptions %}
{% if exceptions %}
.. rubric:: {{ _('Exceptions') }}
.. autosummary::
{% for item in exceptions %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block modules %}
{% if modules %}
.. rubric:: Modules
.. autosummary::
:toctree:
:recursive:
{% for item in modules %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
================================================
FILE: docs/source/agents.rst
================================================
Agents
======
MotleyCrew is first and foremost a multi-agent framework, so the concept of an agent is central to it.
An agent is essentially an actor that can perform tasks. Usually, it contains some kind of loop
that interacts with an LLM and performs actions based on the data it receives.
ReAct tool calling agent
------------------------
MotleyCrew provides a robust general-purpose agent that implements
`ReAct prompting <https://react-lm.github.io/>`_: :class:`motleycrew.agents.langchain.ReActToolCallingMotleyAgent`.
This agent is probably a good starting point for most tasks.
.. code-block:: python
from motleycrew.agents.langchain import ReActToolCallingMotleyAgent
from langchain_community.tools import DuckDuckGoSearchRun
agent = ReActToolCallingMotleyAgent(tools=[DuckDuckGoSearchRun()])
agent.invoke({"prompt": "Which country currently has more population, China or India?"})
``ReActToolCallingMotleyAgent`` was tested with the newer OpenAI and Anthropic models, and it should work
with any model that supports function calling. If you want a similar agent for models without
function calling support, look at :class:`motleycrew.agents.langchain.LegacyReActMotleyAgent`
or :class:`motleycrew.agents.llama_index.ReActLlamaIndexMotleyAgent`.
Using agents from other frameworks
----------------------------------
For many tasks, it's reasonable to use a pre-built agent from some framework,
like Langchain, LlamaIndex, CrewAI etc. MotleyCrew provides adapters for these frameworks,
which allow you to mix and match different agents together and easily provide them with any tools.
* :class:`motleycrew.agents.langchain.LangchainMotleyAgent`
* :class:`motleycrew.agents.llama_index.LlamaIndexMotleyAgent`
2025 update on CrewAI and LlamaIndex agents
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Lately, CrewAI has been increasingly complex and hard to integrate, as it's driving towards a more
opinionated and closed environment.
For this reason, we're deprecating the :class:`motleycrew.agents.crewai.CrewAIMotleyAgent`.
Please use :class:`motleycrew.agents.langchain.ReActToolCallingMotleyAgent` instead.
LlamaIndex has deprecated their AgentRunner in favor of Workflow-based agents,
so we're deprecating :class:`motleycrew.agents.llama_index.LlamaIndexMotleyAgent` as well.
We're still supporting tools from CrewAI and LlamaIndex, but we'll focus on delivering
practical agents that are reliable and easy to use for most tasks.
If you still want to integrate agents from CrewAI or LlamaIndex, or from any other framework,
you can do so either by wrapping them in a `tool <quickstart.html#motleytool>`_,
or by specifying them as `workers <key_concepts.html#tasks-task-units-and-workers>`_ in a task.
Creating your own agent
-----------------------
The simplest way to create your own agent is to subclass :class:`motleycrew.agents.parent.MotleyAgentParent`.
Note that in a `crew <key_concepts.html#crew-and-knowledge-graph>`_,
not only an agent can be a `worker <key_concepts.html#tasks-task-units-and-workers>`_.
A worker is basically any `Runnable <https://python.langchain.com/v0.1/docs/expression_language/interface/>`_,
and all agents and tools implement the Runnable interface in motleycrew.
================================================
FILE: docs/source/api.rst
================================================
API
===
.. autosummary::
:toctree: _autosummary
:recursive:
motleycrew
================================================
FILE: docs/source/autogen.rst
================================================
AutoGen-related Examples
========================
Here are some examples that firstly, show how some AutoGen patterns translate into motleycrew (in particular,
how cases where UserProxy is only used as an AgentExecutor don't need multiple agents in other frameworks),
and secondly, how to use motleycrew together with autogen, both by wrapping a collection of autogen agents as
a motleycrew tool, and by giving motleycrew tools and agents as tools to autogen.
.. toctree::
:maxdepth: 2
examples/math_single_agent
examples/integrating_autogen
================================================
FILE: docs/source/caching_observability.nblink
================================================
{
"path": "../../examples/Caching and observability.ipynb"
}
================================================
FILE: docs/source/choosing_llms.rst
================================================
Choosing LLMs
====================
Generally, the interaction with an LLM is up to the agent implementation.
However, as motleycrew integrates with several agent frameworks, there is some common ground for how to choose LLMs.
Providing an LLM to an agent
----------------------------
In general, you can pass a specific LLM to the agent you're using.
.. code-block:: python
from motleycrew.agents.langchain import ReActToolCallingMotleyAgent
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = ReActToolCallingMotleyAgent(llm=llm, tools=[...])
The LLM class depends on the agent framework you're using.
That's why we have an ``init_llm`` function to help you set up the LLM.
.. code-block:: python
from motleycrew.common.llms import init_llm
from motleycrew.common import LLMFramework, LLMProvider
llm = init_llm(
llm_framework=LLMFramework.LANGCHAIN,
llm_provider=LLMProvider.ANTHROPIC,
llm_name="claude-3-5-sonnet-latest",
temperature=0
)
agent = ReActToolCallingMotleyAgent(llm=llm, tools=[...])
The currently supported frameworks (:py:class:`motleycrew.common.enums.LLMFramework`) are:
- :py:class:`Langchain <motleycrew.common.enums.LLMFramework.LANGCHAIN>` for Langchain-based agents from Langchain, CrewAI, motelycrew etc.
- :py:class:`LlamaIndex <motleycrew.common.enums.LLMFramework.LLAMA_INDEX>` for LlamaIndex-based agents.
The currently supported LLM providers (:py:class:`motleycrew.common.enums.LLMProvider`) are:
- :py:class:`OpenAI <motleycrew.common.enums.LLMProvider.OPENAI>`
- :py:class:`Anthropic <motleycrew.common.enums.LLMProvider.ANTHROPIC>`
- :py:class:`Groq <motleycrew.common.enums.LLMProvider.GROQ>`
- :py:class:`Together <motleycrew.common.enums.LLMProvider.TOGETHER>`
- :py:class:`Replicate <motleycrew.common.enums.LLMProvider.REPLICATE>`
- :py:class:`Ollama <motleycrew.common.enums.LLMProvider.OLLAMA>`
- :py:class:`Azure OpenAI <motleycrew.common.enums.LLMProvider.AZURE_OPENAI>`
Please raise an issue if you need to add support for another LLM provider.
Default LLM
-----------
At present, we default to OpenAI's latest ``gpt-4o`` model for our agents,
and rely on the user to set the `OPENAI_API_KEY` environment variable.
You can control the default LLM as follows:
.. code-block:: python
from motleycrew.common import Defaults
Defaults.DEFAULT_LLM_PROVIDE = "the_new_default_LLM_provider"
Defaults.DEFAULT_LLM_NAME = "name_of_the_new_default_model_from_the_provider"
Using custom LLMs
-----------------
To use a custom LLM provider to use as the default or via the ``init_llm`` function,
you need to make sure that for all the frameworks you're using (currently at most Langchain, LlamaIndex),
the `LLM_MAP` has an entry for the LLM provider, for example as follows:
.. code-block:: python
from motleycrew.common import LLMProvider
from motleycrew.common.llms import LLM_MAP
LLM_MAP[(LLMFramework.LANGCHAIN, "MyLLMProvider")] = my_langchain_llm_factory
LLM_MAP[(LLMFramework.LLAMA_INDEX, "MyLLMProvider")] = my_llamaindex_llm_factory
Here each llm factory is a function with a signature
``def llm_factory(llm_name: str, **kwargs)`` that returns the model object for the relevant framework.
For example, this is the built-in OpenAI model factory for Langchain:
.. code-block:: python
def langchain_openai_llm(
llm_name: str = Defaults.DEFAULT_LLM_NAME,
**kwargs,
):
from langchain_openai import ChatOpenAI
return ChatOpenAI(model=llm_name, **kwargs)
Temperature and other model parameters can be passed via ``**kwargs``, for example:
.. code-block:: python
llm = init_llm(
llm_framework=LLMFramework.LANGCHAIN,
llm_provider=LLMProvider.OPENAI,
temperature=0.7,
max_tokens=1000
)
You can also overwrite the `LLM_MAP` values for e.g. the OpenAI models if, for example,
you want to use an in-house wrapper for Langchain or Llamaindex model adapters
(for example, to use an internal gateway instead of directly hitting the OpenAI endpoints).
Note that at present, if you use Autogen with motleycrew, you will need to separately control
the models that Autogen uses, using the Autogen-specific APIs.
================================================
FILE: docs/source/conf.py
================================================
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
import os
import sys
sys.path.append(os.path.abspath("../.."))
project = "motleycrew"
copyright = "2024, motleycrew"
author = "motleycrew"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.coverage",
"sphinx.ext.napoleon",
"sphinx_rtd_theme",
"nbsphinx",
"nbsphinx_link",
]
templates_path = ["_templates", "_templates/autosummary"]
exclude_patterns = []
# autodoc_default_options = {
# "member-order": "bysource",
# "special-members": "__init__",
# }
autodoc_default_options = {
"members": True,
"member-order": "bysource",
"special-members": "__init__",
"show-inheritance": True,
"inherited-members": False,
"undoc-members": True,
}
napoleon_google_docstring = True
napoleon_numpy_docstring = True
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
nbsphinx_allow_errors = True
nbsphinx_execute = "never"
# Additional configuration for better auto-generated documentation
autosummary_generate = True # Turn on autosummary
# Create separate .rst files for each module
autosummary_generate_overwrite = False
# Make sure that the generated files are included in the toctree
autosummary_generate_include_files = True
================================================
FILE: docs/source/examples/advanced_output_handling.nblink
================================================
{
"path": "../../../examples/Advanced output handling.ipynb"
}
================================================
FILE: docs/source/examples/blog_with_images.nblink
================================================
{
"path": "../../../examples/Blog with Images.ipynb",
"extra-media": [
"../../../examples/images"
]
}
================================================
FILE: docs/source/examples/customer_support.rst
================================================
Customer support chatbot with Ray Serve
=======================================
This example demonstrates how to build a customer support chatbot using MotleyCrew and Ray Serve.
The chatbot is designed to answer customer queries based on a database of past issues and their resolutions.
The code for this example can be found `here <https://github.com/ShoggothAI/motleycrew/tree/main/motleycrew/applications/customer_support>`_.
Also, see the `blog post <https://blog.motleycrew.ai/blog/building-a-customer-support-chatbot-using-motleycrew-and-ray>`_ about this app.
Key Components
--------------
1. Issue Database
- Stores information about past issues and their solutions in a tree structure
- Intermediate nodes represent issue categories
- Leaf nodes represent individual issues
- Uses Kuzu to store and query the issue tree through our OGM (see :doc:`../knowledge_graph` for more details)
2. AI Support Agent
- Attempts to resolve customer issues based on past solutions
- Navigates the issue tree to find relevant information
- Can ask clarifying questions to the customer
- Proposes solutions or escalates to a human agent if necessary
3. Agent Tools
- IssueTreeViewTool: Allows the agent to navigate the issue tree
- CustomerChatTool: Enables the agent to ask additional questions to the customer
- ResolveIssueTool: Used to submit a solution or escalate to a human agent
4. Ray Serve Deployment
- Exposes the chatbot as an API
- Allows multiple customers to connect simultaneously
- Uses WebSockets over FastAPI for communication
Implementation Details
----------------------
The support agent is implemented using the "every response is a tool call" design.
The agent loop can only end with a ResolveIssueTool call or when a constraint (e.g., number of iterations) is reached.
This is achieved by making the ResolveIssueTool an output handler.
The Ray Serve deployment is configured using a simple decorator:
.. code-block:: python
@serve.deployment(num_replicas=3, ray_actor_options={"num_cpus": 1, "num_gpus": 0})
class SupportAgentDeployment:
...
This setup allows for easy scaling and supports multiple simultaneous sessions balanced between replicas.
Running the Example
-------------------
The project includes sample issue data that can be used to populate the issue tree.
To run this example:
.. code-block:: bash
git clone https://github.com/ShoggothAI/motleycrew.git
cd motleycrew
pip install -r requirements.txt
python -m motleycrew.applications.customer_support.issue_tree # populate the issue tree
ray start --head
python -m motleycrew.applications.customer_support.ray_serve_app
This example showcases the flexibility of MotleyCrew for building agent-based applications, allowing you to choose your preferred agent framework, orchestration model, and deployment solution.
================================================
FILE: docs/source/examples/event_driven.nblink
================================================
{
"path": "../../../examples/Event-driven orchestration for AI systems.ipynb"
}
================================================
FILE: docs/source/examples/integrating_autogen.nblink
================================================
{
"path": "../../../examples/Using AutoGen with motleycrew.ipynb"
}
================================================
FILE: docs/source/examples/math_single_agent.nblink
================================================
{
"path": "../../../examples/Math via python code with a single agent.ipynb"
}
================================================
FILE: docs/source/examples/research_agent.nblink
================================================
{
"path": "../../../examples/Multi-step research agent.ipynb"
}
================================================
FILE: docs/source/examples/streaming_agent_output.nblink
================================================
{
"path": "../../../examples/Streaming agent output.ipynb"
}
================================================
FILE: docs/source/examples/validating_agent_output.nblink
================================================
{
"path": "../../../examples/Validating agent output.ipynb"
}
================================================
FILE: docs/source/examples.rst
================================================
Examples
========
.. toctree::
:maxdepth: 2
examples/blog_with_images
examples/research_agent
examples/validating_agent_output
examples/advanced_output_handling
examples/customer_support
examples/streaming_agent_output
examples/event_driven
autogen
================================================
FILE: docs/source/index.rst
================================================
.. motleycrew documentation master file, created by
sphinx-quickstart on Fri May 3 12:05:08 2024.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to motleycrew's documentation!
======================================
.. toctree::
Home <self>
installation
usage
examples
api
:maxdepth: 3
:caption: Contents:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
================================================
FILE: docs/source/install_pandoc.py
================================================
"""
Script for installing Pandoc in GitHub Actions CI environments.
"""
import os
import shutil
from pypandoc.pandoc_download import download_pandoc
pandoc_location = os.path.abspath("../../.venv/_pandoc")
with open(os.environ["GITHUB_PATH"], "a") as path:
path.write(str(pandoc_location) + "\n")
if not shutil.which("pandoc"):
download_pandoc(targetfolder=pandoc_location)
================================================
FILE: docs/source/installation.rst
================================================
Installation
============
Installation using pip
---------------------
.. code-block:: console
pip install motleycrew
Installation from source
------------------------
| Motleycrew uses Poetry to manage its dependencies. We suggest you use it if you want to install motleycrew from source.
| For installation instructions, see https://python-poetry.org/docs/#installation.
Clone the repository_ and install the dependencies:
.. code-block:: console
git clone https://github.com/ShoggothAI/motleycrew.git
cd motleycrew
poetry install
This will create a virtual environment and install the required dependencies.
If you want to install extra dependencies for development, you can use the following command:
.. code-block:: console
poetry install --all-extras --with dev
.. _repository: https://github.com/ShoggothAI/motleycrew
================================================
FILE: docs/source/key_concepts.rst
================================================
Key Concepts and API
====================
This is an overview of motleycrew's key concepts.
If you want to see them in action, see our `research agent example <examples/research_agent.html>`_.
For a basic introduction, you can check out the `quickstart <quickstart.html>`_.
Crew and knowledge graph
------------------------
The crew (:class:`motleycrew.crew.crew.MotleyCrew`) is a central concept in motleycrew. It is the orchestrator that knows what tasks sould be done in which order,
and manages the execution of those tasks.
The crew has an underlying knowledge graph, in which it stores all information relevant to the execution of the tasks.
Besides storing the tasks themselves, the knowledge graph can act as a universal storage for any kind of context
that is relevant to the tasks. You can find more info on how to use the knowledge graph in the `tutorial <knowledge_graph.html>`_.
We currently use `Kùzu <https://kuzudb.com/>`_ as a knowledge graph backend because it's embeddable,
available under an MIT license, and is one of the LlamaIndex-supported KG backends -
please raise an issue on GitHub if you'd like us to support others.
The relationships between tasks are automatically stored in the KG backend; but the agents that are working
on the tasks can also read and write any other context they want to share.
.. code-block:: python
from motleycrew import MotleyCrew
crew = MotleyCrew()
crew.graph_store
# MotleyKuzuGraphStore(path=/path/to/kuzu_db)
If you want to persist the data or otherwise customize the graph store, you can pass a graph store instance to the crew.
.. code-block:: python
import kuzu
from motleycrew.storage import MotleyKuzuGraphStore
database = kuzu.Database(database_path="kuzu_db")
graph_store = MotleyKuzuGraphStore(database=database)
crew = MotleyCrew(graph_store=graph_store)
Tasks, task units, and workers
------------------------------
In motleycrew, a **task** is a body of work that is carried out according to certain rules. The task provides the crew
with a description of what needs to be done in the form of **task units**, and who must do it - that's called a
**worker**. A worker can be an agent, a tool, or for that matter any Runnable (in the Langchain sense).
The worker receives a task unit as an input, processes it, and returns a result.
In a simple case, a task will have a single task unit, and becomes completed as soon as the unit is done.
For such cases, motleycrew provides a `SimpleTask` class, which basically contains an agent and a prompt.
Refer to the `blog with images <examples/blog_with_images.html>`_ example for a more elaborate illustration.
.. code-block:: python
from motleycrew.tasks import SimpleTask
crew = MotleyCrew()
agent = ...
task = SimpleTask(crew=crew, agent=agent, name="example task", description="Do something")
crew.run()
print(task.output)
This task is basically a prompt ("Do something") that is fed to the provided agent. The task will be completed as
soon as the agent finishes processing the only task unit.
For describing more complex tasks, you should subclass the `Task` class. It has two abstract
methods that you should implement: ``get_next_unit`` and ``get_worker``, as well as some optional methods
that you can override to customize the task's behavior.
#. ``get_next_unit()`` should return the next task unit to be processed. If there are no units to do at the moment, it should return `None`.
#. ``get_worker()`` should return the worker (typically an agent) that will process the task's units.
#. `optional` ``on_unit_dispatch(unit)`` is called by the crew when a task unit is dispatched.
#. `optional` ``on_unit_completion(unit)`` is called by the crew when a task unit is completed.
Task hierarchy
--------------
Tasks can be set to depend on other tasks, forming a directed acyclic graph. This is done by either calling a
task's ``set_upstream`` method or by using the ``>>`` operator. The crew will then make sure that the upstream
tasks are completed before starting the dependent task, and pass the former's output to the latter.
.. code-block:: python
task1 = SimpleTask(crew=crew, agent=agent, name="first task", description="Do something")
task2 = SimpleTask(crew=crew, agent=agent, name="second task", description="Do something else")
task1 >> task2
crew.run()
How the crew handles tasks
--------------------------
The crew queries the tasks for task units and dispatches them in a loop. The crew will keep running until either all
tasks are completed or available tasks stop providing task units.
A task is considered completed when it has ``done`` attribute set to ``True``. For example, in the case of `SimpleTask`,
this happens when its only task unit is completed and the crew calls the task's ``on_unit_completion`` method.
In case of a custom task, this behavior is up to the task's implementation.
Available tasks are defined as tasks that have not been completed and have no incomplete
upstream tasks. On each iteration, available tasks are queried for task units one by one,
and the crew will dispatch the task unit to the worker that the task provides.
When a task unit is dispatched, the crew adds it to the knowledge graph and calls the task's ``on_unit_dispatch``
method. When the worker finishes processing the task unit, the crew calls the task's ``on_unit_completion`` method.
.. image:: images/crew_diagram.png
:alt: Crew main loop
:align: center
Now that you know the basics, we suggest you check out the `research agent example <examples/research_agent.html>`_
to see how it all works together.
Asynchronous execution
----------------------
Motleycrew supports asynchronous execution of task units. In this mode, the crew does not wait for the
completion of a task unit before searching for others. Instead, it searches and queues the task units
in an infinite loop till the queue is empty and no tasks are returning units.
Note that the dispatching process is always synchronous, so a task's ``get_next_unit`` method can be called only
after the previous unit is marked as pending and added to the knowledge graph and the execution queue.
So a task can generate new units based on what units have already been dispatched.
Motleycrew implements this behavior with 2 backends: ``asyncio`` and ``threading``. Feel free to make a feature request
if your needs call for some other backend.
.. code-block:: python
from motleycrew.common import AsyncBackend
crew = MotleyCrew(async_backend=AsyncBackend.ASYNCIO) # or AsyncBackend.THREADING
crew.run()
Also, you should specify if a task's units can be executed in parallel to each other, by providing
``allow_async_units=True`` to the ``__init__`` method of the ``Task`` class. Otherwise, this task's
units will be only run in parallel to other tasks' units.
================================================
FILE: docs/source/key_value_store.rst
================================================
Key-value store for manipulating objects
========================================
In many real-world tasks, the agent needs to deal with different types of data.
Even assuming that the LLM can reliably manipulate text, you often need to work
with other objects, such as structured data in the form of dataframes, JSONs,
or just any Python objects.
MotleyCrew provides a simple way to store and retrieve objects using a key-value store,
allowing the agent to use them in its operations, like passing them to tools
(e.g. calling a query tool to get some statistics about a particular dataset).
The key-value store is a dictionary that can be accessed at ``agent.kv_store``.
.. code-block:: python
class ObjectWriterTool(MotleyTool):
def run(self, key: str, value: Any):
"""Write an object to the key-value store."""
self.agent.kv_store[key] = value
class ObjectReaderTool(MotleyTool):
def run(self, key: str) -> Any:
"""Read an object from the key-value store."""
return self.agent.kv_store[key]
A simple example of using the key-value store can be found
`here <https://github.com/ShoggothAI/motleycrew/blob/main/examples/key_value_store.py>`_.
================================================
FILE: docs/source/knowledge_graph.nblink
================================================
{
"path": "../../examples/Interaction with the knowledge graph.ipynb"
}
================================================
FILE: docs/source/quickstart.rst
================================================
Quickstart
==========
This is a brief introduction to motleycrew.
For a working example of agents, tools, crew, and SimpleTask, check out the :doc:`blog with images <examples/blog_with_images>`.
For a working example of custom tasks that fully utilize the knowledge graph backend, check out the :doc:`research agent <examples/research_agent>`.
Agents and tools
----------------
Motleycrew provides thin wrappers for several common agent frameworks: Langchain, LlamaIndex, CrewAI, and Autogen (please let us know if you want any others added!).
It also provides thin wrappers for Langchain and LlamaIndex tools, allowing you to use any of these tools with any of these agents.
MotleyCrew also supports **delegation**: you can simply give any agent as a tool to any other agent.
All the wrappers for tools and agents implement the Runnable interface, so you can use them as-is in LCEL and Langgraph code.
Output handlers (aka return_direct)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An **output handler** is a tool that the agent uses for submitting the final output instead of returning it raw. Besides defining a schema for the output, the output handler enables you to implement any validation logic inside it, including agent-based. If your agent returns invalid output, you can raise an exception that will be returned to the agent so that it can retry.
Essentially, an output handler is a tool that returns its output directly to the user, thus finishing the agent execution. This behavior is enabled by setting the ``return_direct=True`` argument for the tool. Unlike other frameworks, MotleyCrew allows to have multiple output handlers for one agent, from which the agent can choose one.
MotleyCrew also supports **forced output handlers**. This means that the agent will only be able to return output via an output handler, and not directly. This is useful if you want to ensure that the agent only returns output in a specific format.
See our usage examples with a :doc:`simple validator <examples/validating_agent_output>` and an :doc:`advanced output handler with multiple fields <examples/advanced_output_handling>`.
MotleyTool
^^^^^^^^^^
A tool in motleycrew, like in other frameworks, is basically a function that takes an input and returns an output.
It is called a tool in the sense that it is usually used by an agent to perform a specific action.
Besides the function itself, a tool also contains an input schema which describes the input format to the LLM.
``MotleyTool`` is the base class for all tools in motleycrew. It is a subclass of ``Runnable`` that adds some additional features to the tool, along with necessary adapters and converters.
If you pass a tool from a supported framework (currently Langchain, LlamaIndex, and CrewAI) to a motleycrew agent, it will be automatically converted. If you want to have control over this, e.g. to customize tool params, you can do it manually.
.. code-block:: python
motley_tool = MotleyTool.from_supported_tool(my_tool)
It is also possible to define a custom tool using the ``MotleyTool`` base class, overriding the ``run`` method. This is especially useful if you want to access context such as the caller agent or its last input, which can be useful for validation. Also, you can use the agent's :doc:`key-value store <key_value_store>` to store and retrieve objects.
.. code-block:: python
class MyTool(MotleyTool):
def run(self, some_input: str) -> str:
self.agent.kv_store["some_key"] = some_input # for use in other tools
return f"Received {some_input} from agent {self.agent} with last input {self.agent_input}"
Tools can be executed asynchronously, either directly of via by an asynchronous agent. By default, the async version will just run the sync version in a separate thread.
MotleyTool can return exceptions that are raised inside it back to the agent, which can then retry the tool call. You can pass a list of exception classes to the ``handle_exceptions`` argument in the constructor (or even pass ``True`` to handle everything).
Crew and tasks
--------------
The other two key concepts in motleycrew are crew and tasks. The crew is the orchestrator for tasks, and must be passed to all tasks at creation; tasks can be connected into a DAG using the ``>>`` operator, for example ``TaskA >> TaskB``. This means that ``TaskB`` will not be started before ``TaskA`` is complete, and will be given ``TaskA``'s output.
Once all tasks and their relationships have been set up, it all can be run via ``crew.run()``, which returns a list of the executed ``TaskUnits`` (see below for details).
SimpleTask
^^^^^^^^^^
``SimpleTask`` is a basic implementation of the ``Task`` API. It only requires a crew, a description, and an agent. When it's executed, the description is combined with the output of any upstream tasks and passed on to the agent, and the agent's output is the tasks's output.
For a working illustration of all the concepts so far, see the :doc:`blog with images <examples/blog_with_images>` example.
Knowledge graph backend and custom tasks
----------------------------------------
The functionality so far is convenient, allowing us to mix all the popular agents and tools, but otherwise fairly vanilla, little different from, for example, the CrewAI semantics. Fortunately, the above introduction just scratched the surface of the motleycrew ``Task`` API.
In motleycrew, a task is basically a set of rules describing how to perform actions. It provides a **worker** (e.g. an agent) and sets of input data called **task units**. This allows defining workflows of any complexity concisely using crew semantics. For a deeper dive, check out the page on :doc:`key concepts <key_concepts>`.
The crew queries and dispatches available task units in a loop, managing task states using an embedded :doc:`knowledge graph <knowledge_graph>`.
This dispatch method easily supports different execution backends, from synchronous to asyncio, threaded, etc.
Example: Recursive question-answering in the research agent
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Motleycrew architecture described above easily allows to generate task units on the fly, if needed. An example of the power of this approach is the :doc:`research agent <examples/research_agent>` that dynamically generates new questions based on retrieved context for previous questions.
This example also shows how workers can collaborate via the shared knowledge graph, storing all necessary data in a way that is natural to the task.
================================================
FILE: docs/source/usage.rst
================================================
Usage
=====
.. toctree::
:maxdepth: 2
quickstart
key_concepts
agents
knowledge_graph
choosing_llms
caching_observability
key_value_store
================================================
FILE: examples/Advanced output handling.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Advanced output handling\n",
"\n",
"\n",
"For grasping the basic idea of output handlers, check out this introduction: [validating agent output](validating_agent_output.html).\n",
"\n",
"The purpose of this example is to demonstrate two concepts:\n",
"\n",
"- Obtaining structured output from the agent\n",
"- Validating the output against the prompt that was given to the agent\n",
"\n",
"This should give a good understanding of output handler semantics and prepare you to build your own ones."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Install lunary package if you need logging."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from dotenv import load_dotenv\n",
"load_dotenv()\n",
"\n",
"from motleycrew import MotleyCrew\n",
"from motleycrew.agents.langchain import ReActToolCallingMotleyAgent\n",
"from motleycrew.tasks import SimpleTask\n",
"from motleycrew.common.exceptions import InvalidOutput\n",
"\n",
"from motleycrew.tools.code import PythonREPLTool\n",
"from motleycrew.tools.llm_tool import LLMTool\n",
"from motleycrew.tools import MotleyTool"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the [validating agent output](validating_agent_output.html) example, we used a simple tool to receive and check the output.\n",
"\n",
"Here we want to have access to the agent input, so we subclass the `MotleyTool` class and implement its `run` method. Inside it, we'll call an LLM critic that will assess the agent's output.\n",
"\n",
"`MotleyTool` has the `handle_exceptions` argument, which allows us to return exceptions from the tool back to the agent. This is useful for providing feedback on why the tool failed and how to fix it. By default, it handles `InvalidOutput` exceptions, but you can pass other exceptions to it as well, or pass `True` for handling any exception."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class CoderOutputHandler(MotleyTool):\n",
" def __init__(self):\n",
" super().__init__(\n",
" name=\"coder_output_handler\",\n",
" description=\"Output handler. ONLY RETURN THE FINAL RESULT USING THIS TOOL!\",\n",
" return_direct=True,\n",
" handle_exceptions=[ValueError],\n",
" ) # args_schema is inferred automatically from the run method signature, but you can specify it explicitly\n",
"\n",
" def run(self, code: str, comment: str):\n",
" agent_prompt = self.agent_input[\"prompt\"]\n",
" critic = LLMTool(\n",
" name=\"critic\",\n",
" description=\"improve the agent output\",\n",
" prompt=(\n",
" \"An AI agent was given this prompt: \\n{agent_prompt}\\n\\n\"\n",
" \"Here's the code it generated: \\n```\\n{code}\\n```\\n\\n\"\n",
" \"And the comment it gave: \\n{comment}\\n\\n\"\n",
" \"The code must be as efficient as possible, \"\n",
" \"and the comment must be sensible and easily understandable. \"\n",
" \"Give constructive advice on how to improve them unless they are already perfect. \"\n",
" \"In this case, just return a single \\\"OK\\\" without quotes.\"\n",
" )\n",
" )\n",
"\n",
" critic_message = critic.invoke({\"agent_prompt\": agent_prompt, \"code\": code, \"comment\": comment}).content\n",
" print(\"Critic response: \", critic_message)\n",
"\n",
" if critic_message.strip().lower().startswith(\"ok\"):\n",
" return code, comment\n",
" raise InvalidOutput(critic_message) # This will be returned to the agent to make it improve its output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In case the critic is not satisfied, the `InvalidOutput` exception will be returned to the agent, as if a regular tool was called. If everything is OK, we just return the agent's output.\n",
"\n",
"The ReActToolCallingMotleyAgent constructor has a `max_iterations` argument, as well as most other agents. It's the maximum number of times the agent will be allowed to call tools. If the agent doesn't provide a valid output after `max_iterations`, an exception will be raised. To avoid this, you can add an iteration counter to the critic's state.\n",
"\n",
"The output schema is inferred automatically from the `run` method signature. You can also specify it directly using the `args_schema` argument."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"crew = MotleyCrew()\n",
"\n",
"coder = ReActToolCallingMotleyAgent(\n",
" name=\"coder\",\n",
" tools=[PythonREPLTool(), CoderOutputHandler()],\n",
" force_output_handler=True,\n",
" verbose=True,\n",
" max_iterations=15,\n",
")\n",
"\n",
"task = SimpleTask(\n",
" crew=crew,\n",
" name=\"Explain and implement the quicksort algorithm\",\n",
" description=\"Write a Python program that implements the quicksort algorithm \"\n",
" \"and test your implementation using the REPL tool. \\n\"\n",
" \"Return the code and a comment explaining how it works.\",\n",
" agent=coder,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `python_repl` with `{'command': 'def quicksort(arr):\\n if len(arr) <= 1:\\n return arr\\n else:\\n pivot = arr[0]\\n less_than_pivot = [x for x in arr[1:] if x <= pivot]\\n greater_than_pivot = [x for x in arr[1:] if x > pivot]\\n return quicksort(less_than_pivot) + [pivot] + quicksort(greater_than_pivot)\\n\\n# Test the quicksort function\\nsample_array = [3, 6, 8, 10, 1, 2, 1]\\nsorted_array = quicksort(sample_array)\\nprint(sorted_array)'}`\n",
"responded: Thought: To implement the quicksort algorithm in Python, I will write a function that follows the quicksort logic. This involves selecting a pivot element, partitioning the array into elements less than and greater than the pivot, and recursively sorting the sub-arrays. After implementing the function, I will test it using the Python REPL tool to ensure it works correctly. Finally, I will return the code along with a comment explaining how it works.\n",
"\n",
"Let's start by implementing the quicksort algorithm and testing it.\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3m[1, 1, 2, 3, 6, 8, 10]\n",
"\u001b[0m\u001b[32;1m\u001b[1;3m\n",
"Invoking: `coder_output_handler` with `{'code': 'def quicksort(arr):\\n if len(arr) <= 1:\\n return arr\\n else:\\n pivot = arr[0]\\n less_than_pivot = [x for x in arr[1:] if x <= pivot]\\n greater_than_pivot = [x for x in arr[1:] if x > pivot]\\n return quicksort(less_than_pivot) + [pivot] + quicksort(greater_than_pivot)\\n\\n# Test the quicksort function\\nsample_array = [3, 6, 8, 10, 1, 2, 1]\\nsorted_array = quicksort(sample_array)\\nprint(sorted_array)', 'comment': \"The quicksort function is a recursive sorting algorithm. It works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays: those less than the pivot and those greater than the pivot. The function then recursively sorts the sub-arrays. The base case for the recursion is an array of zero or one element, which is already sorted. The sorted array is constructed by concatenating the sorted 'less than pivot' sub-array, the pivot, and the sorted 'greater than pivot' sub-array. The test demonstrates the function by sorting a sample array.\"}`\n",
"responded: Thought: The quicksort implementation has been successfully tested and sorted the sample array correctly. Now, I will return the code along with a comment explaining how the quicksort algorithm works.\n",
"\n",
"Let's proceed to return the final result.\n",
"\n",
"\u001b[0mCritic response: The code and comment are generally correct, but there are a few improvements that can be made for efficiency and clarity:\n",
"\n",
"1. **Pivot Selection**: The current implementation always selects the first element as the pivot. This can lead to poor performance (O(n^2) time complexity) on already sorted arrays or arrays with many duplicate elements. A better approach is to select a random pivot or use the median-of-three method (choosing the median of the first, middle, and last elements).\n",
"\n",
"2. **In-place Sorting**: The current implementation creates new lists for elements less than and greater than the pivot, which can be inefficient in terms of space. An in-place partitioning method can be used to improve space efficiency.\n",
"\n",
"3. **Comment Clarity**: The comment is mostly clear, but it could mention the potential inefficiency of the pivot selection method and suggest alternatives.\n",
"\n",
"Here's an improved version of the code with in-place partitioning and a random pivot selection:\n",
"\n",
"```python\n",
"import random\n",
"\n",
"def quicksort(arr, low=0, high=None):\n",
" if high is None:\n",
" high = len(arr) - 1\n",
"\n",
" if low < high:\n",
" pivot_index = partition(arr, low, high)\n",
" quicksort(arr, low, pivot_index - 1)\n",
" quicksort(arr, pivot_index + 1, high)\n",
"\n",
"def partition(arr, low, high):\n",
" pivot_index = random.randint(low, high)\n",
" pivot = arr[pivot_index]\n",
" arr[pivot_index], arr[high] = arr[high], arr[pivot_index] # Move pivot to end\n",
" i = low\n",
" for j in range(low, high):\n",
" if arr[j] < pivot:\n",
" arr[i], arr[j] = arr[j], arr[i]\n",
" i += 1\n",
" arr[i], arr[high] = arr[high], arr[i] # Move pivot to its final place\n",
" return i\n",
"\n",
"# Test the quicksort function\n",
"sample_array = [3, 6, 8, 10, 1, 2, 1]\n",
"quicksort(sample_array)\n",
"print(sample_array)\n",
"```\n",
"\n",
"Comment:\n",
"The quicksort function is a recursive sorting algorithm that sorts an array in-place. It works by selecting a 'pivot' element and partitioning the array into elements less than the pivot and elements greater than the pivot. The function then recursively sorts the partitions. A random pivot is chosen to improve performance on average, reducing the risk of worst-case time complexity. The partitioning is done in-place to save space. The base case for the recursion is when the sub-array has one or zero elements, which are already sorted. The test demonstrates the function by sorting a sample array.\n",
"\u001b[33;1m\u001b[1;3mInvalidOutput: The code and comment are generally correct, but there are a few improvements that can be made for efficiency and clarity:\n",
"\n",
"1. **Pivot Selection**: The current implementation always selects the first element as the pivot. This can lead to poor performance (O(n^2) time complexity) on already sorted arrays or arrays with many duplicate elements. A better approach is to select a random pivot or use the median-of-three method (choosing the median of the first, middle, and last elements).\n",
"\n",
"2. **In-place Sorting**: The current implementation creates new lists for elements less than and greater than the pivot, which can be inefficient in terms of space. An in-place partitioning method can be used to improve space efficiency.\n",
"\n",
"3. **Comment Clarity**: The comment is mostly clear, but it could mention the potential inefficiency of the pivot selection method and suggest alternatives.\n",
"\n",
"Here's an improved version of the code with in-place partitioning and a random pivot selection:\n",
"\n",
"```python\n",
"import random\n",
"\n",
"def quicksort(arr, low=0, high=None):\n",
" if high is None:\n",
" high = len(arr) - 1\n",
"\n",
" if low < high:\n",
" pivot_index = partition(arr, low, high)\n",
" quicksort(arr, low, pivot_index - 1)\n",
" quicksort(arr, pivot_index + 1, high)\n",
"\n",
"def partition(arr, low, high):\n",
" pivot_index = random.randint(low, high)\n",
" pivot = arr[pivot_index]\n",
" arr[pivot_index], arr[high] = arr[high], arr[pivot_index] # Move pivot to end\n",
" i = low\n",
" for j in range(low, high):\n",
" if arr[j] < pivot:\n",
" arr[i], arr[j] = arr[j], arr[i]\n",
" i += 1\n",
" arr[i], arr[high] = arr[high], arr[i] # Move pivot to its final place\n",
" return i\n",
"\n",
"# Test the quicksort function\n",
"sample_array = [3, 6, 8, 10, 1, 2, 1]\n",
"quicksort(sample_array)\n",
"print(sample_array)\n",
"```\n",
"\n",
"Comment:\n",
"The quicksort function is a recursive sorting algorithm that sorts an array in-place. It works by selecting a 'pivot' element and partitioning the array into elements less than the pivot and elements greater than the pivot. The function then recursively sorts the partitions. A random pivot is chosen to improve performance on average, reducing the risk of worst-case time complexity. The partitioning is done in-place to save space. The base case for the recursion is when the sub-array has one or zero elements, which are already sorted. The test demonstrates the function by sorting a sample array.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
"Invoking: `coder_output_handler` with `{'code': 'import random\\n\\ndef quicksort(arr, low=0, high=None):\\n if high is None:\\n high = len(arr) - 1\\n\\n if low < high:\\n pivot_index = partition(arr, low, high)\\n quicksort(arr, low, pivot_index - 1)\\n quicksort(arr, pivot_index + 1, high)\\n\\ndef partition(arr, low, high):\\n pivot_index = random.randint(low, high)\\n pivot = arr[pivot_index]\\n arr[pivot_index], arr[high] = arr[high], arr[pivot_index] # Move pivot to end\\n i = low\\n for j in range(low, high):\\n if arr[j] < pivot:\\n arr[i], arr[j] = arr[j], arr[i]\\n i += 1\\n arr[i], arr[high] = arr[high], arr[i] # Move pivot to its final place\\n return i\\n\\n# Test the quicksort function\\nsample_array = [3, 6, 8, 10, 1, 2, 1]\\nquicksort(sample_array)\\nprint(sample_array)', 'comment': \"The quicksort function is a recursive sorting algorithm that sorts an array in-place. It works by selecting a 'pivot' element and partitioning the array into elements less than the pivot and elements greater than the pivot. The function then recursively sorts the partitions. A random pivot is chosen to improve performance on average, reducing the risk of worst-case time complexity. The partitioning is done in-place to save space. The base case for the recursion is when the sub-array has one or zero elements, which are already sorted. The test demonstrates the function by sorting a sample array.\"}`\n",
"responded: Thought: I will return the improved version of the quicksort algorithm, which includes in-place partitioning and random pivot selection for better performance. This version addresses the inefficiencies of the initial implementation and provides a more robust solution. I will also include a comment explaining how the improved quicksort algorithm works.\n",
"\n",
"Let's proceed to return the final result.\n",
"\n",
"\u001b[0mCritic response: OK\n",
"\u001b[32;1m\u001b[1;3m('import random\\n\\ndef quicksort(arr, low=0, high=None):\\n if high is None:\\n high = len(arr) - 1\\n\\n if low < high:\\n pivot_index = partition(arr, low, high)\\n quicksort(arr, low, pivot_index - 1)\\n quicksort(arr, pivot_index + 1, high)\\n\\ndef partition(arr, low, high):\\n pivot_index = random.randint(low, high)\\n pivot = arr[pivot_index]\\n arr[pivot_index], arr[high] = arr[high], arr[pivot_index] # Move pivot to end\\n i = low\\n for j in range(low, high):\\n if arr[j] < pivot:\\n arr[i], arr[j] = arr[j], arr[i]\\n i += 1\\n arr[i], arr[high] = arr[high], arr[i] # Move pivot to its final place\\n return i\\n\\n# Test the quicksort function\\nsample_array = [3, 6, 8, 10, 1, 2, 1]\\nquicksort(sample_array)\\nprint(sample_array)', \"The quicksort function is a recursive sorting algorithm that sorts an array in-place. It works by selecting a 'pivot' element and partitioning the array into elements less than the pivot and elements greater than the pivot. The function then recursively sorts the partitions. A random pivot is chosen to improve performance on average, reducing the risk of worst-case time complexity. The partitioning is done in-place to save space. The base case for the recursion is when the sub-array has one or zero elements, which are already sorted. The test demonstrates the function by sorting a sample array.\")\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
}
],
"source": [
"crew.run()\n",
"code, comment = task.output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"See, the initial code was optimized thanks to the critic!"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<style>pre { line-height: 125%; }\n",
"td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
"span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
"td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
"span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
".output_html .hll { background-color: #ffffcc }\n",
".output_html { background: #f8f8f8; }\n",
".output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
".output_html .err { border: 1px solid #F00 } /* Error */\n",
".output_html .k { color: #008000; font-weight: bold } /* Keyword */\n",
".output_html .o { color: #666 } /* Operator */\n",
".output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
".output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
".output_html .cp { color: #9C6500 } /* Comment.Preproc */\n",
".output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
".output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
".output_html .gd { color: #A00000 } /* Generic.Deleted */\n",
".output_html .ge { font-style: italic } /* Generic.Emph */\n",
".output_html .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */\n",
".output_html .gr { color: #E40000 } /* Generic.Error */\n",
".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
".output_html .gi { color: #008400 } /* Generic.Inserted */\n",
".output_html .go { color: #717171 } /* Generic.Output */\n",
".output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
".output_html .gs { font-weight: bold } /* Generic.Strong */\n",
".output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
".output_html .gt { color: #04D } /* Generic.Traceback */\n",
".output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
".output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
".output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
".output_html .kp { color: #008000 } /* Keyword.Pseudo */\n",
".output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
".output_html .kt { color: #B00040 } /* Keyword.Type */\n",
".output_html .m { color: #666 } /* Literal.Number */\n",
".output_html .s { color: #BA2121 } /* Literal.String */\n",
".output_html .na { color: #687822 } /* Name.Attribute */\n",
".output_html .nb { color: #008000 } /* Name.Builtin */\n",
".output_html .nc { color: #00F; font-weight: bold } /* Name.Class */\n",
".output_html .no { color: #800 } /* Name.Constant */\n",
".output_html .nd { color: #A2F } /* Name.Decorator */\n",
".output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
".output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
".output_html .nf { color: #00F } /* Name.Function */\n",
".output_html .nl { color: #767600 } /* Name.Label */\n",
".output_html .nn { color: #00F; font-weight: bold } /* Name.Namespace */\n",
".output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
".output_html .nv { color: #19177C } /* Name.Variable */\n",
".output_html .ow { color: #A2F; font-weight: bold } /* Operator.Word */\n",
".output_html .w { color: #BBB } /* Text.Whitespace */\n",
".output_html .mb { color: #666 } /* Literal.Number.Bin */\n",
".output_html .mf { color: #666 } /* Literal.Number.Float */\n",
".output_html .mh { color: #666 } /* Literal.Number.Hex */\n",
".output_html .mi { color: #666 } /* Literal.Number.Integer */\n",
".output_html .mo { color: #666 } /* Literal.Number.Oct */\n",
".output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n",
".output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
".output_html .sc { color: #BA2121 } /* Literal.String.Char */\n",
".output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
".output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
".output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n",
".output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
".output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
".output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
".output_html .sx { color: #008000 } /* Literal.String.Other */\n",
".output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n",
".output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n",
".output_html .ss { color: #19177C } /* Literal.String.Symbol */\n",
".output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
".output_html .fm { color: #00F } /* Name.Function.Magic */\n",
".output_html .vc { color: #19177C } /* Name.Variable.Class */\n",
".output_html .vg { color: #19177C } /* Name.Variable.Global */\n",
".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n",
".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n",
".output_html .il { color: #666 } /* Literal.Number.Integer.Long */</style><div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">random</span>\n",
"\n",
"<span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">quicksort</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"n\">low</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">high</span><span class=\"o\">=</span><span class=\"kc\">None</span><span class=\"p\">):</span>\n",
" <span class=\"k\">if</span> <span class=\"n\">high</span> <span class=\"ow\">is</span> <span class=\"kc\">None</span><span class=\"p\">:</span>\n",
" <span class=\"n\">high</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">)</span> <span class=\"o\">-</span> <span class=\"mi\">1</span>\n",
"\n",
" <span class=\"k\">if</span> <span class=\"n\">low</span> <span class=\"o\"><</span> <span class=\"n\">high</span><span class=\"p\">:</span>\n",
" <span class=\"n\">pivot_index</span> <span class=\"o\">=</span> <span class=\"n\">partition</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"n\">low</span><span class=\"p\">,</span> <span class=\"n\">high</span><span class=\"p\">)</span>\n",
" <span class=\"n\">quicksort</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"n\">low</span><span class=\"p\">,</span> <span class=\"n\">pivot_index</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">)</span>\n",
" <span class=\"n\">quicksort</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"n\">pivot_index</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">high</span><span class=\"p\">)</span>\n",
"\n",
"<span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">partition</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"n\">low</span><span class=\"p\">,</span> <span class=\"n\">high</span><span class=\"p\">):</span>\n",
" <span class=\"n\">pivot_index</span> <span class=\"o\">=</span> <span class=\"n\">random</span><span class=\"o\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span><span class=\"n\">low</span><span class=\"p\">,</span> <span class=\"n\">high</span><span class=\"p\">)</span>\n",
" <span class=\"n\">pivot</span> <span class=\"o\">=</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">pivot_index</span><span class=\"p\">]</span>\n",
" <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">pivot_index</span><span class=\"p\">],</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">high</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">high</span><span class=\"p\">],</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">pivot_index</span><span class=\"p\">]</span> <span class=\"c1\"># Move pivot to end</span>\n",
" <span class=\"n\">i</span> <span class=\"o\">=</span> <span class=\"n\">low</span>\n",
" <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">low</span><span class=\"p\">,</span> <span class=\"n\">high</span><span class=\"p\">):</span>\n",
" <span class=\"k\">if</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\"><</span> <span class=\"n\">pivot</span><span class=\"p\">:</span>\n",
" <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">],</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">j</span><span class=\"p\">],</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span>\n",
" <span class=\"n\">i</span> <span class=\"o\">+=</span> <span class=\"mi\">1</span>\n",
" <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">],</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">high</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">high</span><span class=\"p\">],</span> <span class=\"n\">arr</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"c1\"># Move pivot to its final place</span>\n",
" <span class=\"k\">return</span> <span class=\"n\">i</span>\n",
"\n",
"<span class=\"c1\"># Test the quicksort function</span>\n",
"<span class=\"n\">sample_array</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">6</span><span class=\"p\">,</span> <span class=\"mi\">8</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]</span>\n",
"<span class=\"n\">quicksort</span><span class=\"p\">(</span><span class=\"n\">sample_array</span><span class=\"p\">)</span>\n",
"<span class=\"nb\">print</span><span class=\"p\">(</span><span class=\"n\">sample_array</span><span class=\"p\">)</span>\n",
"</pre></div>\n"
],
"text/latex": [
"\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n",
"\\PY{k+kn}{import}\\PY{+w}{ }\\PY{n+nn}{random}\n",
"\n",
"\\PY{k}{def}\\PY{+w}{ }\\PY{n+nf}{quicksort}\\PY{p}{(}\\PY{n}{arr}\\PY{p}{,} \\PY{n}{low}\\PY{o}{=}\\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{high}\\PY{o}{=}\\PY{k+kc}{None}\\PY{p}{)}\\PY{p}{:}\n",
" \\PY{k}{if} \\PY{n}{high} \\PY{o+ow}{is} \\PY{k+kc}{None}\\PY{p}{:}\n",
" \\PY{n}{high} \\PY{o}{=} \\PY{n+nb}{len}\\PY{p}{(}\\PY{n}{arr}\\PY{p}{)} \\PY{o}{\\PYZhy{}} \\PY{l+m+mi}{1}\n",
"\n",
" \\PY{k}{if} \\PY{n}{low} \\PY{o}{\\PYZlt{}} \\PY{n}{high}\\PY{p}{:}\n",
" \\PY{n}{pivot\\PYZus{}index} \\PY{o}{=} \\PY{n}{partition}\\PY{p}{(}\\PY{n}{arr}\\PY{p}{,} \\PY{n}{low}\\PY{p}{,} \\PY{n}{high}\\PY{p}{)}\n",
" \\PY{n}{quicksort}\\PY{p}{(}\\PY{n}{arr}\\PY{p}{,} \\PY{n}{low}\\PY{p}{,} \\PY{n}{pivot\\PYZus{}index} \\PY{o}{\\PYZhy{}} \\PY{l+m+mi}{1}\\PY{p}{)}\n",
" \\PY{n}{quicksort}\\PY{p}{(}\\PY{n}{arr}\\PY{p}{,} \\PY{n}{pivot\\PYZus{}index} \\PY{o}{+} \\PY{l+m+mi}{1}\\PY{p}{,} \\PY{n}{high}\\PY{p}{)}\n",
"\n",
"\\PY{k}{def}\\PY{+w}{ }\\PY{n+nf}{partition}\\PY{p}{(}\\PY{n}{arr}\\PY{p}{,} \\PY{n}{low}\\PY{p}{,} \\PY{n}{high}\\PY{p}{)}\\PY{p}{:}\n",
" \\PY{n}{pivot\\PYZus{}index} \\PY{o}{=} \\PY{n}{random}\\PY{o}{.}\\PY{n}{randint}\\PY{p}{(}\\PY{n}{low}\\PY{p}{,} \\PY{n}{high}\\PY{p}{)}\n",
" \\PY{n}{pivot} \\PY{o}{=} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{pivot\\PYZus{}index}\\PY{p}{]}\n",
" \\PY{n}{arr}\\PY{p}{[}\\PY{n}{pivot\\PYZus{}index}\\PY{p}{]}\\PY{p}{,} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{high}\\PY{p}{]} \\PY{o}{=} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{high}\\PY{p}{]}\\PY{p}{,} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{pivot\\PYZus{}index}\\PY{p}{]} \\PY{c+c1}{\\PYZsh{} Move pivot to end}\n",
" \\PY{n}{i} \\PY{o}{=} \\PY{n}{low}\n",
" \\PY{k}{for} \\PY{n}{j} \\PY{o+ow}{in} \\PY{n+nb}{range}\\PY{p}{(}\\PY{n}{low}\\PY{p}{,} \\PY{n}{high}\\PY{p}{)}\\PY{p}{:}\n",
" \\PY{k}{if} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{j}\\PY{p}{]} \\PY{o}{\\PYZlt{}} \\PY{n}{pivot}\\PY{p}{:}\n",
" \\PY{n}{arr}\\PY{p}{[}\\PY{n}{i}\\PY{p}{]}\\PY{p}{,} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{j}\\PY{p}{]} \\PY{o}{=} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{j}\\PY{p}{]}\\PY{p}{,} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{i}\\PY{p}{]}\n",
" \\PY{n}{i} \\PY{o}{+}\\PY{o}{=} \\PY{l+m+mi}{1}\n",
" \\PY{n}{arr}\\PY{p}{[}\\PY{n}{i}\\PY{p}{]}\\PY{p}{,} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{high}\\PY{p}{]} \\PY{o}{=} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{high}\\PY{p}{]}\\PY{p}{,} \\PY{n}{arr}\\PY{p}{[}\\PY{n}{i}\\PY{p}{]} \\PY{c+c1}{\\PYZsh{} Move pivot to its final place}\n",
" \\PY{k}{return} \\PY{n}{i}\n",
"\n",
"\\PY{c+c1}{\\PYZsh{} Test the quicksort function}\n",
"\\PY{n}{sample\\PYZus{}array} \\PY{o}{=} \\PY{p}{[}\\PY{l+m+mi}{3}\\PY{p}{,} \\PY{l+m+mi}{6}\\PY{p}{,} \\PY{l+m+mi}{8}\\PY{p}{,} \\PY{l+m+mi}{10}\\PY{p}{,} \\PY{l+m+mi}{1}\\PY{p}{,} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{l+m+mi}{1}\\PY{p}{]}\n",
"\\PY{n}{quicksort}\\PY{p}{(}\\PY{n}{sample\\PYZus{}array}\\PY{p}{)}\n",
"\\PY{n+nb}{print}\\PY{p}{(}\\PY{n}{sample\\PYZus{}array}\\PY{p}{)}\n",
"\\end{Verbatim}\n"
],
"text/plain": [
"import random\n",
"\n",
"def quicksort(arr, low=0, high=None):\n",
" if high is None:\n",
" high = len(arr) - 1\n",
"\n",
" if low < high:\n",
" pivot_index = partition(arr, low, high)\n",
" quicksort(arr, low, pivot_index - 1)\n",
" quicksort(arr, pivot_index + 1, high)\n",
"\n",
"def partition(arr, low, high):\n",
" pivot_index = random.randint(low, high)\n",
" pivot = arr[pivot_index]\n",
" arr[pivot_index], arr[high] = arr[high], arr[pivot_index] # Move pivot to end\n",
" i = low\n",
" for j in range(low, high):\n",
" if arr[j] < pivot:\n",
" arr[i], arr[j] = arr[j], arr[i]\n",
" i += 1\n",
" arr[i], arr[high] = arr[high], arr[i] # Move pivot to its final place\n",
" return i\n",
"\n",
"# Test the quicksort function\n",
"sample_array = [3, 6, 8, 10, 1, 2, 1]\n",
"quicksort(sample_array)\n",
"print(sample_array)"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from IPython.display import display, Markdown, Code\n",
"\n",
"display(Code(code, language=\"python\"))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"The quicksort function is a recursive sorting algorithm that sorts an array in-place. It works by selecting a 'pivot' element and partitioning the array into elements less than the pivot and elements greater than the pivot. The function then recursively sorts the partitions. A random pivot is chosen to improve performance on average, reducing the risk of worst-case time complexity. The partitioning is done in-place to save space. The base case for the recursion is when the sub-array has one or zero elements, which are already sorted. The test demonstrates the function by sorting a sample array."
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display(Markdown(comment))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.13"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
================================================
FILE: examples/Blog with Images.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "87b73640",
"metadata": {},
"source": [
"# Blog with images"
]
},
{
"cell_type": "markdown",
"id": "bf3534fb-ed51-4a82-ad66-71259b2e282f",
"metadata": {},
"source": [
"We use this example of creating a web search-based blog with illustrations to show how to \n",
"\n",
"* give agents as tools to other agents\n",
"* create tasks and link them using the >> operator"
]
},
{
"cell_type": "markdown",
"id": "6ab41b51-2064-4623-866b-f4310d062425",
"metadata": {},
"source": [
"Let's install external dependencies for the example."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e8d44edb-4841-4f3c-822c-136276606e09",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: duckduckgo-search in /Users/whimo/motleycrew/.venv/lib/python3.11/site-packages (8.1.1)\n",
"Requirement already satisfied: click>=8.1.8 in /Users/whimo/motleycrew/.venv/lib/python3.11/site-packages (from duckduckgo-search) (8.2.1)\n",
"Requirement already satisfied: primp>=0.15.0 in /Users/whimo/motleycrew/.venv/lib/python3.11/site-packages (from duckduckgo-search) (0.15.0)\n",
"Requirement already satisfied: lxml>=5.3.0 in /Users/whimo/motleycrew/.venv/lib/python3.11/site-packages (from duckduckgo-search) (6.0.0)\n",
"\n",
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n",
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
"Note: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install duckduckgo-search\n",
"\n",
"# install lunary if you need logging\n",
"# %pip install lunary==1.1.5"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2596164c",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/whimo/motleycrew/.venv/lib/python3.11/site-packages/lunary/__init__.py:3: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.\n",
" from pkg_resources import parse_version\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from pathlib import Path\n",
"import os\n",
"import sys\n",
"\n",
"from dotenv import load_dotenv\n",
"from langchain_community.tools import DuckDuckGoSearchRun\n",
"\n",
"WORKING_DIR = Path(os.path.realpath(\".\"))\n",
"\n",
"try:\n",
" from motleycrew import MotleyCrew\n",
"except ImportError:\n",
" # if we are running this from source\n",
" motleycrew_location = os.path.realpath(WORKING_DIR / \"..\")\n",
" sys.path.append(motleycrew_location)\n",
"\n",
"from motleycrew import MotleyCrew\n",
"from motleycrew.agents.langchain import ReActToolCallingMotleyAgent\n",
"from motleycrew.tools import MotleyTool, RetryConfig\n",
"from motleycrew.tools.image.dall_e import DallEImageGeneratorTool\n",
"from motleycrew.common import configure_logging\n",
"from motleycrew.tasks import SimpleTask\n",
"\n",
"configure_logging()\n",
"load_dotenv()"
]
},
{
"cell_type": "markdown",
"id": "0c099c23-9c49-4385-a91d-ecc062155bf3",
"metadata": {},
"source": [
"First, we create a fairly conventional agent that has a search tool. "
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c90e7903-e7fc-44fb-b10a-0007e56f10a5",
"metadata": {},
"outputs": [],
"source": [
"search_tool = MotleyTool.from_supported_tool(\n",
" DuckDuckGoSearchRun(), retry_config=RetryConfig(max_retries=7) # for retrying rate limit errors\n",
")\n",
"\n",
"# The name and description are helpers, they are not included in the prompt\n",
"researcher = ReActToolCallingMotleyAgent(\n",
" name=\"Senior Research Analyst\",\n",
" description=\"Agent that conducts research on AI and data science\",\n",
" prompt=\"You work at a leading tech think tank. \"\n",
" \"Your expertise lies in identifying emerging trends. \"\n",
" \"You have a knack for dissecting complex data and presenting actionable insights. \"\n",
" \"You uncover cutting-edge developments in AI and data science, doing web search if necessary. \"\n",
" \"\\n\\n{prompt}\",\n",
" verbose=True,\n",
" tools=[search_tool],\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d675f0df-e47b-40db-ba7a-69fa6f38484b",
"metadata": {},
"source": [
"Now the fun starts happening: we pass that agent to another agent as a tool. That's how delegation works in motleycrew: you just pass agents as tools to other agents; the API is automatically converted under the hood."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "9328ccb5-2bc4-4cdb-acca-c95af642eff9",
"metadata": {},
"outputs": [],
"source": [
"# You can give agents as tools to other agents\n",
"writer = ReActToolCallingMotleyAgent(\n",
" name=\"AI writer agent\",\n",
" prompt=\"You are an expert writer for a tech blog. \"\n",
" \"You like to write about complex topics in a way that is easy to understand, \"\n",
" \"with the help of the researcher agent.\"\n",
" \"\\n\\n{prompt}\",\n",
" tools=[researcher],\n",
" verbose=True,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "4ff89e64-543a-4404-9868-abd0cdd28e11",
"metadata": {},
"source": [
"\n",
"The `{prompt}` placeholders are where the task-specific prompts will go.\n",
"\n",
"Let's create a first task, writing a blog post, and give it to the writer agent. We'll need to instantiate a crew first as a crew orchestrates the tasks."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "6a9de88c-a1be-482a-a3d2-9954f94cfe52",
"metadata": {},
"outputs": [],
"source": [
"# You always need a crew to orchestrate the agents\n",
"crew = MotleyCrew()\n",
"\n",
"blog_post_task = SimpleTask(\n",
" crew=crew,\n",
" name=\"produce blog post on AI advancements\",\n",
" description=\"Develop an engaging blog post that highlights the latest AI advancements in 2025. \"\n",
" \"Your post should be informative yet accessible, catering to a tech-savvy audience. \"\n",
" \"Make it sound cool, avoid complex words so it doesn't sound like AI. \"\n",
" \"Create a blog post of at least 4 paragraphs, in markdown format.\",\n",
" agent=writer,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "eda8ffdf-0b8b-4cf8-8e05-c66c7a574080",
"metadata": {},
"source": [
"Now let's create a second task, to generate and insert into the blog some illustrations."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "e414e877-566c-49d5-933a-8e7bc088ff33",
"metadata": {},
"outputs": [],
"source": [
"# Illustrator\n",
"image_generator_tool = DallEImageGeneratorTool(os.path.realpath(\"./images\"),\n",
" model=\"dall-e-3\",\n",
" refine_prompt_with_llm=True,\n",
" size=\"1024x1024\",\n",
" style=\"vivid\")\n",
"\n",
"illustrator = ReActToolCallingMotleyAgent(\n",
" name=\"Illustrator\",\n",
" description=\"Create beautiful and insightful illustrations for a blog post\",\n",
" tools=[image_generator_tool],\n",
")\n",
"\n",
"illustration_task = SimpleTask(\n",
" crew=crew,\n",
" name=\"create an illustration for the blog post\",\n",
" description=\"Create beautiful and insightful illustrations to accompany the blog post on AI advancements. \"\n",
" \"The blog post will be provided to you in markdown format. \"\n",
" \"Make sure to use the illustration tool provided to you, once per illustration, \"\n",
" \"and embed the URL provided by the tool into the blog post. \"\n",
" \"Create between 2 and 3 illustrations, neither more nor less.\\n\"\n",
" \"Only return the full text of the blog post with embedded URLs, no backticks, no other text. \"\n",
" \"Only use slashes as path separators, never backslashes\",\n",
" agent=illustrator,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "14f77cc4-0750-4be8-923a-991bfc9ccef7",
"metadata": {},
"source": [
"Now let's connect the two tasks, and run them!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "54c7b7fb-e544-4d23-bd56-dedd9c8a15b1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `senior_research_analyst` with `{'input': 'latest AI advancements in 2025'}`\n",
"responded: Thought: To write an engaging blog post about the latest AI advancements in 2025, I need to gather information on the most recent developments and breakthroughs in AI technology. This will help ensure that the content is both current and relevant. I will use the senior_research_analyst tool to research the latest AI advancements in 2025.\n",
"\n",
"Let's proceed with gathering this information.\n",
"\n",
"\u001b[0m\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `duckduckgo_search` with `{'query': 'latest AI advancements 2025'}`\n",
"responded: Thought: To provide the latest AI advancements in 2025, I need to perform a web search to gather current information on this topic. I will use the DuckDuckGo search tool to find relevant and up-to-date information about AI developments in 2025.\n",
"\n",
"\n",
"\n",
"\u001b[0m"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/whimo/motleycrew/.venv/lib/python3.11/site-packages/langchain_community/utilities/duckduckgo_search.py:63: RuntimeWarning: This package (`duckduckgo_search`) has been renamed to `ddgs`! Use `pip install ddgs` instead.\n",
" with DDGS() as ddgs:\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[36;1m\u001b[1;3mJul 24, 2025 · PwC’s mid-year AI check-in: get the latest on 2025 AI predictions, discover which trends are accelerating or fading, and gain action-ready insights to stay ahead. May 21, 2025 · Many of the most important AI trends in the first half of 2025 reflect changes in how the industry is applying existing ideas—some pragmatic and productive, others less so. … Jan 3, 2025 · Explore the key trends shaping AI in 2025, from multimodal models and AI agents to security challenges and evolving regulatory landscapes. Jun 23, 2025 · In 2025, there’s a new AI tool for everything – text, images, coding, video, you name it, and professionals are eager to know “what’s the best tool for making their work easy?” This … Jun 13, 2025 · The breakthroughs span across healthcare, finance, transportation, robotics, communication, and more. As leading industry pioneers, we explore the most impactful AI …\u001b[0m\u001b[32;1m\u001b[1;3mFinal Answer: In 2025, AI advancements are characterized by several key trends and developments:\n",
"\n",
"1. **Multimodal Models and AI Agents**: There is a significant focus on developing AI systems that can process and integrate multiple types of data, such as text, images, and audio, to perform complex tasks. AI agents are becoming more sophisticated, capable of handling a variety of tasks autonomously.\n",
"\n",
"2. **Industry-Specific Applications**: AI is being increasingly tailored to specific industries, with notable breakthroughs in healthcare, finance, transportation, and robotics. These advancements are aimed at improving efficiency, accuracy, and decision-making processes within these sectors.\n",
"\n",
"3. **Security Challenges**: As AI systems become more integrated into critical infrastructure, addressing security challenges is a major focus. This includes developing robust systems to protect against cyber threats and ensuring data privacy.\n",
"\n",
"4. **Regulatory Landscapes**: The evolving regulatory environment is shaping how AI technologies are developed and deployed. There is a growing emphasis on ethical AI, transparency, and accountability in AI systems.\n",
"\n",
"5. **Proliferation of AI Tools**: There is a wide array of AI tools available for various applications, from text and image processing to coding and video creation. These tools are designed to make professional tasks easier and more efficient.\n",
"\n",
"These trends reflect a combination of technological innovation and practical application, driving the AI industry forward in 2025.\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"\u001b[36;1m\u001b[1;3mFinal Answer: In 2025, AI advancements are characterized by several key trends and developments:\n",
"\n",
"1. **Multimodal Models and AI Agents**: There is a significant focus on developing AI systems that can process and integrate multiple types of data, such as text, images, and audio, to perform complex tasks. AI agents are becoming more sophisticated, capable of handling a variety of tasks autonomously.\n",
"\n",
"2. **Industry-Specific Applications**: AI is being increasingly tailored to specific industries, with notable breakthroughs in healthcare, finance, transportation, and robotics. These advancements are aimed at improving efficiency, accuracy, and decision-making processes within these sectors.\n",
"\n",
"3. **Security Challenges**: As AI systems become more integrated into critical infrastructure, addressing security challenges is a major focus. This includes developing robust systems to protect against cyber threats and ensuring data privacy.\n",
"\n",
"4. **Regulatory Landscapes**: The evolving regulatory environment is shaping how AI technologies are developed and deployed. There is a growing emphasis on ethical AI, transparency, and accountability in AI systems.\n",
"\n",
"5. **Proliferation of AI Tools**: There is a wide array of AI tools available for various applications, from text and image processing to coding and video creation. These tools are designed to make professional tasks easier and more efficient.\n",
"\n",
"These trends reflect a combination of technological innovation and practical application, driving the AI industry forward in 2025.\u001b[0m\u001b[32;1m\u001b[1;3mFinal Answer: \n",
"\n",
"# The Coolest AI Advancements of 2025: A Peek into the Future\n",
"\n",
"Welcome to 2025, where AI is not just a buzzword but a transformative force reshaping our world. This year, AI has taken some giant leaps, making it an exciting time for tech enthusiasts and professionals alike. Let's dive into the coolest advancements that are making waves in the AI landscape.\n",
"\n",
"## Multimodal Models and AI Agents\n",
"\n",
"Imagine an AI that can understand and process text, images, and audio all at once. That's the power of multimodal models, a major trend in 2025. These AI systems are like supercharged assistants, capable of performing complex tasks by integrating different types of data. Whether it's helping doctors diagnose diseases by analyzing medical images and patient records or creating immersive virtual experiences, these AI agents are setting new standards for what machines can do.\n",
"\n",
"## Tailored AI for Every Industry\n",
"\n",
"AI is no longer a one-size-fits-all solution. In 2025, we're seeing AI tailored to specific industries, bringing about breakthroughs in healthcare, finance, transportation, and robotics. In healthcare, AI is improving diagnostic accuracy and personalizing treatment plans. In finance, it's enhancing fraud detection and automating trading. The transportation sector is witnessing smarter, safer autonomous vehicles, while robotics is seeing more efficient and adaptable machines. These industry-specific applications are not just improving efficiency but also revolutionizing how we approach problems.\n",
"\n",
"## Tackling Security and Ethical Challenges\n",
"\n",
"With great power comes great responsibility, and AI is no exception. As AI systems become more integrated into our daily lives, security challenges are at the forefront. In 2025, there's a strong focus on developing robust systems to protect against cyber threats and ensure data privacy. Moreover, the regulatory landscape is evolving, emphasizing ethical AI, transparency, and accountability. These efforts are crucial in building trust and ensuring that AI technologies are used responsibly.\n",
"\n",
"## A Toolbox of AI Wonders\n",
"\n",
"The proliferation of AI tools in 2025 is nothing short of amazing. From text and image processing to coding and video creation, there's an AI tool for almost every professional task. These tools are designed to make our lives easier, allowing us to focus on creativity and innovation. Whether you're a developer, a designer, or a content creator, AI is here to enhance your workflow and open up new possibilities.\n",
"\n",
"In conclusion, 2025 is a landmark year for AI, marked by technological innovation and practical application. As we continue to explore the potential of AI, one thing is clear: the future is bright, and it's powered by artificial intelligence.\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
}
],
"source": [
"# Make sure the illustration task runs only once the blog post task is complete, and gets its input\n",
"blog_post_task >> illustration_task\n",
"\n",
"# Get your crew to work!\n",
"result = crew.run()"
]
},
{
"cell_type": "markdown",
"id": "2ae3c916-c920-473e-b7da-ee60bf0f6893",
"metadata": {},
"source": [
"Let's display the result (swapping absolute for relative paths, as Jupyter seems to struggle with the former)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "54ed121a-1fd5-428d-8de9-40cd3e3cc5fc",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Final Answer: \n",
"\n",
"# The Coolest AI Advancements of 2025: A Peek into the Future\n",
"\n",
"Welcome to 2025, where AI is not just a buzzword but a transformative force reshaping our world. This year, AI has taken some giant leaps, making it an exciting time for tech enthusiasts and professionals alike. Let's dive into the coolest advancements that are making waves in the AI landscape.\n",
"\n",
"## Multimodal Models and AI Agents\n",
"\n",
"Imagine an AI that can understand and process text, images, and audio all at once. That's the power of multimodal models, a major trend in 2025. These AI systems are like supercharged assistants, capable of performing complex tasks by integrating different types of data. Whether it's helping doctors diagnose diseases by analyzing medical images and patient records or creating immersive virtual experiences, these AI agents are setting new standards for what machines can do.\n",
"\n",
"\n",
"\n",
"## Tailored AI for Every Industry\n",
"\n",
"AI is no longer a one-size-fits-all solution. In 2025, we're seeing AI tailored to specific industries, bringing about breakthroughs in healthcare, finance, transportation, and robotics. In healthcare, AI is improving diagnostic accuracy and personalizing treatment plans. In finance, it's enhancing fraud detection and automating trading. The transportation sector is witnessing smarter, safer autonomous vehicles, while robotics is seeing more efficient and adaptable machines. These industry-specific applications are not just improving efficiency but also revolutionizing how we approach problems.\n",
"\n",
"\n",
"\n",
"## Tackling Security and Ethical Challenges\n",
"\n",
"With great power comes great responsibility, and AI is no exception. As AI systems become more integrated into our daily lives, security challenges are at the forefront. In 2025, there's a strong focus on developing robust systems to protect against cyber threats and ensure data privacy. Moreover, the regulatory landscape is evolving, emphasizing ethical AI, transparency, and accountability. These efforts are crucial in building trust and ensuring that AI technologies are used responsibly.\n",
"\n",
"\n",
"\n",
"## A Toolbox of AI Wonders\n",
"\n",
"The proliferation of AI tools in 2025 is nothing short of amazing. From text and image processing to coding and video creation, there's an AI tool for almost every professional task. These tools are designed to make our lives easier, allowing us to focus on creativity and innovation. Whether you're a developer, a designer, or a content creator, AI is here to enhance your workflow and open up new possibilities.\n",
"\n",
"In conclusion, 2025 is a landmark year for AI, marked by technological innovation and practical application. As we continue to explore the potential of AI, one thing is clear: the future is bright, and it's powered by artificial intelligence."
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from IPython.display import Markdown, display\n",
"here = os.path.realpath(\".\").replace(os.sep,\"/\")\n",
"display(Markdown(illustration_task.output.replace(here,\".\")))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
================================================
FILE: examples/Caching and observability.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "a17c962c-a1e7-44d4-8dd9-5a2d37f7c3af",
"metadata": {},
"source": [
"# Caching and observability"
]
},
{
"cell_type": "markdown",
"id": "88482bb2-4cfc-49e6-98bb-7540d70abebc",
"metadata": {},
"source": [
"Motleycrew provides a universal caching engine that caches LLM and tool calls, as well as other web requests out of the box. \n",
"It works with all requests made using most popular Python HTTP clients: `requests`, `HTTPX`, and `Curl CFFI`.\n",
"\n",
"We also provide integration with [Lunary](https://lunary.ai), an open-source observability platform. \n",
"To enable tracking via Lunary, you need to set the `LUNARY_PUBLIC_KEY` environment variable.\n",
"\n",
"We will demonstrate the tracking and caching capabilities on a simple task with one agent and one tool."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14a64d7d-de4e-44a1-b7f4-da8ff6c92724",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"os.environ[\"OPENAI_API_KEY\"] = \"<Your OpenAI API key here>\"\n",
"os.environ[\"LUNARY_PUBLIC_KEY\"] = \"<Your Lunary public key here (get it in Lunary settings)>\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8d3c223c-9d9e-4566-96a4-ad61c00a295f",
"metadata": {},
"outputs": [],
"source": [
"# Alternatively, load everything from .env\n",
"from dotenv import load_dotenv\n",
"load_dotenv()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "f111d0f5-4bf8-45ae-8d0a-5cdef85bc5fc",
"metadata": {},
"outputs": [],
"source": [
"from motleycrew import MotleyCrew\n",
"from motleycrew.agents.langchain.react import ReActMotleyAgent\n",
"from motleycrew.tasks import SimpleTask\n",
"from langchain_community.tools import DuckDuckGoSearchRun"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4a0e4da9-c425-4a1a-966b-0395b4faccc2",
"metadata": {},
"outputs": [],
"source": [
"from motleycache import enable_cache\n",
"enable_cache() # Caching is on!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e113c847-3e3a-4f29-859b-a34fb7649b6e",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"WARNING:root:Multithreading is not implemented yet, will run in single thread\n",
"WARNING:root:No known Cypher type matching annotation typing.Optional[typing.Any], will use JSON string\n",
"WARNING:root:No known Cypher type matching annotation typing.List[str], will use JSON string\n",
"WARNING:root:No known Cypher type matching annotation typing.List[str], will use JSON string\n",
"WARNING:root:No known Cypher type matching annotation typing.Optional[typing.Any], will use JSON string\n"
]
},
{
"data": {
"text/plain": [
"[TaskUnit(status=done)]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"crew = MotleyCrew()\n",
"\n",
"duckduckgo_search = DuckDuckGoSearchRun()\n",
"\n",
"writer = ReActMotleyAgent(\n",
" name=\"writer\",\n",
" prompt=\"Using the results of a web search, write an article on the latest advancements in AI in 2024.\\n\\n{prompt}\",\n",
" tools=[duckduckgo_search],\n",
")\n",
"\n",
"task = SimpleTask(\n",
" crew=crew,\n",
" agent=writer,\n",
" name=\"write an article on the latest advancements in AI\",\n",
" description=\"Using the results of a web search, write an article on the latest advancements in AI in 2024. \"\n",
" \"Write in a way that speaks to the general audience.\"\n",
")\n",
"crew.run()"
]
},
{
"attachments": {
"04465011-7222-4e8c-8dd9-bfff9e1aeba9.png": {
"image/png": "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e6/Exsa6t9m8ebM888wz8r///c8974UXXpDExES7zS233OKef6SJ1NRUef75523/9u3bZ/t/2mmn2eOFh4fbTdPT0+2x9IEOJ33WWWd53aX2Ozk5WQYNGiQ333yze52MjAx58cUXZenSpXYY6k6dOsmIESPkoYcekqSkJPd6OvHXv/5VDhw4INddd5307t1bXnrpJVmyZIlccsklcuedd7rXLSkpkZdfflnmzp0rGzZskJ49e8qwYcPkvvvuk3bt2rnXmz59unz33XfW7oknnnDPdybeeecdWbNmjXV7+OGHndnue+23Pg9q4jyn7oVMIOAhEN8iTn5yzWUec2qf1OuQL65FtR+BJQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIINL5AULlpvujGyvWb7W6GDezti9153cczz79i52s48eoJF1VZR6sn7tyzz877zQN3V1k2+Z0P7TDPkRHh8uA9P6+y7HgepKWlSdu2be2mf/zjH+Xxxx+XsrIy966CgoLk3XfftaG5888/XzIzM93LdGLIkCE2rOcEAJ2Fr776qvzqV7+SvLw8Z5b7vkePHqJhOg3KaZs1a5bovr21yy67TD799FO76KOPPpJrrrlGEhIS5NAhV7jT2Wb27Nly00032UCgM8+517Df1KlT7TnoPA0lfv/99zJ8+HBZvny5s5r7PiUlRbp06SJFRUV2u6uvvtoumzNnjg0reqvyGBcXJx9//LENbTo70nDj+vXrbVBRPTSIqW3SpEk25KjT27dvtwHGZcuW6cMqTZ+Xt99+Wy680BVS/eKLL2yoUlfSffXq1cu9voYc27dvL/p86nO2Y8cOew7OCvo8tG7dWgoKCuTDDz+0js4y7hHwZ4H6uh47+x3cr4c/n77P+lZWVi75BYWSb65rRUUl9jrvea332YHYEQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgg0K4Hg4GDRW3h4qESZInJRkRHmcVCTNPC7IZ5rU95VET7U5V061RwWtXfP7u5NPdfVmWeYYaMjzRM5cljlcM/ulU9w4v/+7//kd7/7nWilxDfeeENOPvlk0cznz372Mxu804qKWuVPq/hpGDAkJMSuq1ULPZtW+rv77rttOPGiiy6Sr776ylYGfO+992xIcNu2bTZop0FAbVox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1HdNOJHiiam6C+k3AggggAACfVKgOZnVJzc/sRutoUANzWnT6oQdhRN1unPOOScSoNPqi73VosOHY8aMOWCxGobsafva175m9V2rD86fP99anIYT8/JMybY2WnZ2duSedevWRS539sJZZ51lTVpfXy933XVXpFpl9PyLFi2SBx98UG699Va55557rLs++OADmTlzphU+nDt3bvTkoj465LW2mpqaFvdxBYHWBGrXBGXLQ3759BuNsup7Ptk1xy/JDid6ckRKTvTI+B9kymF/z5YxN2RIv+mEE1vbf9yGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC6SeQO95UHvxialceHHW9RzKKSSem39HJFiGAAAIIIJDeAgQUE7h/n3nmmcjazj333Mjl9i6UlpbKsccea02yadMm+eijj9qbvNP3TZgwITKthvX27t1rXQ8GgzJnzpxIcC8yUTcujB49Wk455ZTInDp0tFYibK/pENY6VLO2999/X+69917rfNu2be3NFrnvzDPPlKOPPtq6/sorr8i1115rDdNcVVUl6vfYY4/JjTfeKLqdWVlZ8qUvfcmaVoeG3rp1q3X57rvvlnfeeceqWqmVK1988UVZv369dd+0adOsc/5DIFagdl1Qtj7sl6UmlLjyf0wo8TkTStyT3IqJbpP3LTneI+O+nyGHmlDi2JszTOVEE0pM/4K1sbuH6wgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICADL7ILdkjUxOi9FwzMtYRhBNTc+/RawQQQAABBPq2AAN7Jmj/b9++XRYuXGit7eCDD5axY8d2es0XXHCB2FX9tIri4Ycf3ul525rw1FNPlYcfflg2bNhgLVsrD44cOVJ27twpDQ0NMmDAANFhmrWKoYb0utu0AqQOi6ztjDPOkIEDB3a4KB1W+f7777fW+9BDD4mebrnlFrnkkks6nFcn+NGPfmSFEDVUqJUR9RTbdFjnO++809pGvW/cuHFy9dVXW+vVIOPNN98sublmHFzTdLhrbTo89be+9S3rMv8hoAJ1G4JSNjc8fHPjzuSGEe094s4S6XeUxwQT3dLvSI/odRoCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIAZGdkUcRh9vVdW35paQz3njHbJ0MuoPcQxjAACCCCAAAKpKcCrmATtt+eeey4y3PB5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
gitextract_mhfszbdc/
├── .coderabbit.yaml
├── .flake8
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── .mcp.json
├── .readthedocs.yaml
├── CLAUDE.md
├── LICENSE
├── Makefile
├── README.md
├── docs/
│ ├── Makefile
│ ├── make.bat
│ └── source/
│ ├── _static/
│ │ └── .gitkeep
│ ├── _templates/
│ │ ├── .gitkeep
│ │ └── autosummary/
│ │ ├── base.rst
│ │ └── module.rst
│ ├── agents.rst
│ ├── api.rst
│ ├── autogen.rst
│ ├── caching_observability.nblink
│ ├── choosing_llms.rst
│ ├── conf.py
│ ├── examples/
│ │ ├── advanced_output_handling.nblink
│ │ ├── blog_with_images.nblink
│ │ ├── customer_support.rst
│ │ ├── event_driven.nblink
│ │ ├── integrating_autogen.nblink
│ │ ├── math_single_agent.nblink
│ │ ├── research_agent.nblink
│ │ ├── streaming_agent_output.nblink
│ │ └── validating_agent_output.nblink
│ ├── examples.rst
│ ├── index.rst
│ ├── install_pandoc.py
│ ├── installation.rst
│ ├── key_concepts.rst
│ ├── key_value_store.rst
│ ├── knowledge_graph.nblink
│ ├── quickstart.rst
│ └── usage.rst
├── examples/
│ ├── Advanced output handling.ipynb
│ ├── Blog with Images.ipynb
│ ├── Caching and observability.ipynb
│ ├── Event-driven orchestration for AI systems.ipynb
│ ├── Interaction with the knowledge graph.ipynb
│ ├── Math via python code with a single agent.ipynb
│ ├── Multi-step research agent.ipynb
│ ├── Streaming agent output.ipynb
│ ├── Using AutoGen with motleycrew.ipynb
│ ├── Validating agent output.ipynb
│ ├── __init__.py
│ ├── aider_code_generation.py
│ ├── blog_post/
│ │ ├── blog_post.py
│ │ └── blog_post_input.py
│ ├── data/
│ │ ├── groupchat/
│ │ │ ├── fetch_arxiv_gpt4.py
│ │ │ └── fetch_latest_gpt4_paper.py
│ │ └── research_agent_storage/
│ │ ├── default__vector_store.json
│ │ ├── docstore.json
│ │ ├── graph_store.json
│ │ ├── image__vector_store.json
│ │ └── index_store.json
│ ├── hacking dependencies.ipynb
│ ├── image_from_gslides_example.py
│ ├── key_value_store.py
│ ├── llama_index_output_handler.py
│ ├── recognize_charts.py
│ ├── submit_image.py
│ └── tool_calling_with_memory.py
├── motleycrew/
│ ├── __init__.py
│ ├── agents/
│ │ ├── __init__.py
│ │ ├── abstract_parent.py
│ │ ├── langchain/
│ │ │ ├── __init__.py
│ │ │ ├── langchain.py
│ │ │ ├── legacy_react.py
│ │ │ ├── tool_calling_react.py
│ │ │ └── tool_calling_react_prompts.py
│ │ ├── llama_index/
│ │ │ ├── __init__.py
│ │ │ ├── llama_index.py
│ │ │ └── llama_index_react.py
│ │ ├── mixins.py
│ │ └── parent.py
│ ├── applications/
│ │ ├── __init__.py
│ │ ├── customer_support/
│ │ │ ├── README.md
│ │ │ ├── communication.py
│ │ │ ├── example_issues.csv
│ │ │ ├── issue_tree.py
│ │ │ ├── ray_serve_app.py
│ │ │ ├── requirements.txt
│ │ │ ├── static/
│ │ │ │ └── index.html
│ │ │ └── support_agent.py
│ │ ├── expenses/
│ │ │ ├── expenses.py
│ │ │ ├── schema_delta.py
│ │ │ └── sql_tools.py
│ │ ├── faust_workflow/
│ │ │ ├── __init__.py
│ │ │ ├── faust_workflow.py
│ │ │ └── visualize.py
│ │ └── research_agent/
│ │ ├── __init__.py
│ │ ├── answer_task.py
│ │ ├── question.py
│ │ ├── question_answerer.py
│ │ ├── question_generator.py
│ │ ├── question_prioritizer.py
│ │ └── question_task.py
│ ├── common/
│ │ ├── __init__.py
│ │ ├── aux_prompts.py
│ │ ├── defaults.py
│ │ ├── enums.py
│ │ ├── exceptions.py
│ │ ├── llms.py
│ │ ├── logging.py
│ │ ├── types.py
│ │ └── utils.py
│ ├── crew/
│ │ ├── __init__.py
│ │ ├── crew.py
│ │ └── crew_threads.py
│ ├── storage/
│ │ ├── __init__.py
│ │ ├── graph_node.py
│ │ ├── graph_store.py
│ │ ├── graph_store_utils.py
│ │ ├── kuzu_graph_store.py
│ │ └── kv_store_domain.py
│ ├── tasks/
│ │ ├── __init__.py
│ │ ├── simple.py
│ │ ├── task.py
│ │ └── task_unit.py
│ ├── tools/
│ │ ├── __init__.py
│ │ ├── agentic_validation_loop.py
│ │ ├── autogen_chat_tool.py
│ │ ├── code/
│ │ │ ├── __init__.py
│ │ │ ├── aider_tool.py
│ │ │ ├── postgresql_linter.py
│ │ │ ├── python_linter.py
│ │ │ └── python_repl.py
│ │ ├── html_render_tool.py
│ │ ├── image/
│ │ │ ├── __init__.py
│ │ │ ├── dall_e.py
│ │ │ ├── download_image.py
│ │ │ └── replicate_tool.py
│ │ ├── llm_tool.py
│ │ ├── mermaid_evaluator_tool.py
│ │ ├── simple_retriever_tool.py
│ │ ├── sql_validation.py
│ │ ├── structured_passthrough.py
│ │ └── tool.py
│ ├── tracking/
│ │ ├── __init__.py
│ │ ├── callbacks.py
│ │ └── utils.py
│ └── utils/
│ ├── __init__.py
│ ├── chart_to_df.py
│ ├── image_utils.py
│ └── structured_output_with_retries.py
├── pyproject.toml
├── pytest.ini
├── requirements-extra.txt
└── tests/
├── __init__.py
├── conftest.py
├── run_integration_tests.py
├── test_agents/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_agent_chain.py
│ ├── test_agents.py
│ ├── test_kv_store_tools.py
│ ├── test_langchain_output_handler.py
│ └── test_llms.py
├── test_crew/
│ ├── __init__.py
│ ├── test_crew.py
│ └── test_crew_threads.py
├── test_image_utils.py
├── test_storage/
│ ├── __init__.py
│ ├── test_graph_store.py
│ └── test_kuzu_graph_store.py
├── test_tasks/
│ ├── __init__.py
│ ├── test_simple_task.py
│ ├── test_task.py
│ └── test_task_unit.py
└── test_tools/
├── __init__.py
├── test_agentic_validation_loop.py
├── test_html_render_tool.py
├── test_image_utils.py
├── test_linter_tools.py
├── test_repl_tool.py
├── test_sql_validation.py
├── test_structured_passthrough_tool.py
├── test_tool.py
└── test_tool_chain.py
SYMBOL INDEX (690 symbols across 96 files)
FILE: examples/aider_code_generation.py
function main (line 32) | def main():
FILE: examples/data/groupchat/fetch_arxiv_gpt4.py
function search_arxiv (line 6) | def search_arxiv(query):
function parse_response (line 21) | def parse_response(response):
function main (line 31) | def main():
FILE: examples/data/groupchat/fetch_latest_gpt4_paper.py
function fetch_latest_paper (line 5) | def fetch_latest_paper():
FILE: examples/image_from_gslides_example.py
function demo_complete_workflow (line 23) | def demo_complete_workflow():
function demo_backward_compatibility (line 79) | def demo_backward_compatibility():
function demo_error_handling (line 104) | def demo_error_handling():
FILE: examples/key_value_store.py
class ObjectInsertionTool (line 10) | class ObjectInsertionTool(MotleyTool):
method run (line 11) | def run(self, query: str) -> List[str]:
class ObjectFetcherTool (line 29) | class ObjectFetcherTool(MotleyTool):
method run (line 30) | def run(self, object_id: str) -> str:
FILE: examples/llama_index_output_handler.py
function main (line 12) | def main():
FILE: examples/submit_image.py
function create_multimodal_messages (line 9) | def create_multimodal_messages(context: dict) -> list[HumanMessage]:
FILE: examples/tool_calling_with_memory.py
function main (line 10) | def main():
FILE: motleycrew/agents/abstract_parent.py
class MotleyAgentAbstractParent (line 10) | class MotleyAgentAbstractParent(Runnable, ABC):
method kv_store (line 18) | def kv_store(self) -> dict:
method invoke (line 22) | def invoke(
method as_tool (line 31) | def as_tool(self, **kwargs) -> Any:
FILE: motleycrew/agents/langchain/langchain.py
class LangchainMotleyAgent (line 23) | class LangchainMotleyAgent(MotleyAgentParent, LangchainOutputHandlingAge...
method __init__ (line 26) | def __init__(
method materialize (line 108) | def materialize(self):
method _prepare_config (line 177) | def _prepare_config(self, config: RunnableConfig) -> RunnableConfig:
method invoke (line 187) | def invoke(
method ainvoke (line 208) | async def ainvoke(
method from_agent (line 234) | def from_agent(
FILE: motleycrew/agents/langchain/legacy_react.py
class LegacyReActMotleyAgent (line 41) | class LegacyReActMotleyAgent(LangchainMotleyAgent):
method __init__ (line 48) | def __init__(
FILE: motleycrew/agents/langchain/tool_calling_react.py
function check_variables (line 33) | def check_variables(prompt: ChatPromptTemplate):
function render_text_description (line 43) | def render_text_description(tools: list[BaseTool]) -> str:
function get_relevant_internal_prompt (line 50) | def get_relevant_internal_prompt(
function create_tool_calling_react_agent (line 64) | def create_tool_calling_react_agent(
class ReActToolCallingMotleyAgent (line 99) | class ReActToolCallingMotleyAgent(LangchainMotleyAgent):
method __init__ (line 107) | def __init__(
FILE: motleycrew/agents/langchain/tool_calling_react_prompts.py
class ToolCallingReActPrompts (line 14) | class ToolCallingReActPrompts:
method __init__ (line 22) | def __init__(self):
class ToolCallingReActPromptsForOpenAI (line 47) | class ToolCallingReActPromptsForOpenAI(ToolCallingReActPrompts):
class ToolCallingReActPromptsForAnthropic (line 135) | class ToolCallingReActPromptsForAnthropic(ToolCallingReActPrompts):
FILE: motleycrew/agents/llama_index/llama_index.py
class LlamaIndexMotleyAgent (line 28) | class LlamaIndexMotleyAgent(MotleyAgentParent):
method __init__ (line 31) | def __init__(
method _propagate_error_step (line 91) | def _propagate_error_step(self, task_id: str, message: str):
method _run_step_decorator (line 102) | def _run_step_decorator(self):
method materialize (line 152) | def materialize(self):
method invoke (line 156) | def invoke(
method ainvoke (line 171) | async def ainvoke(
method from_agent (line 189) | def from_agent(
FILE: motleycrew/agents/llama_index/llama_index_react.py
class ReActLlamaIndexMotleyAgent (line 20) | class ReActLlamaIndexMotleyAgent(LlamaIndexMotleyAgent):
method __init__ (line 23) | def __init__(
FILE: motleycrew/agents/mixins.py
class LangchainOutputHandlingAgentMixin (line 17) | class LangchainOutputHandlingAgentMixin:
method _create_agent_error_tool (line 25) | def _create_agent_error_tool(self) -> BaseTool:
method _create_error_action (line 39) | def _create_error_action(self, message: str, error_message: str) -> Ag...
method _is_error_action (line 49) | def _is_error_action(self, action: AgentAction) -> bool:
method agent_plan_decorator (line 53) | def agent_plan_decorator(self, func: Callable):
method agent_aplan_decorator (line 122) | def agent_aplan_decorator(self, func: Callable):
method take_next_step_decorator (line 184) | def take_next_step_decorator(self, func: Callable):
method _run_tool_direct_decorator (line 232) | def _run_tool_direct_decorator(self, func: Callable):
method run_tool_direct_decorator (line 251) | def run_tool_direct_decorator(self, func: Callable):
FILE: motleycrew/agents/parent.py
class MotleyAgentParent (line 24) | class MotleyAgentParent(MotleyAgentAbstractParent, ABC):
method __init__ (line 36) | def __init__(
method kv_store (line 97) | def kv_store(self) -> dict:
method __repr__ (line 100) | def __repr__(self):
method __str__ (line 103) | def __str__(self):
method compose_prompt (line 106) | def compose_prompt(
method agent (line 182) | def agent(self):
method is_materialized (line 192) | def is_materialized(self):
method get_output_handlers (line 196) | def get_output_handlers(self):
method _check_force_output_handler (line 200) | def _check_force_output_handler(self):
method materialize (line 207) | def materialize(self):
method _prepare_for_invocation (line 219) | def _prepare_for_invocation(
method add_tools (line 246) | def add_tools(self, tools: Sequence[MotleySupportedTool]):
method as_tool (line 267) | def as_tool(self, **kwargs) -> MotleyTool:
method invoke (line 302) | def invoke(
FILE: motleycrew/applications/customer_support/communication.py
class CommunicationInterface (line 5) | class CommunicationInterface(ABC):
method send_message_to_customer (line 7) | async def send_message_to_customer(self, message: str) -> str:
method escalate_to_human_agent (line 20) | def escalate_to_human_agent(self) -> None:
method resolve_issue (line 27) | def resolve_issue(self, resolution: str) -> str:
class DummyCommunicationInterface (line 40) | class DummyCommunicationInterface(CommunicationInterface):
method send_message_to_customer (line 41) | async def send_message_to_customer(self, message: str) -> str:
method escalate_to_human_agent (line 45) | def escalate_to_human_agent(self) -> None:
method resolve_issue (line 48) | def resolve_issue(self, resolution: str) -> str:
class RealCommunicationInterface (line 58) | class RealCommunicationInterface(CommunicationInterface):
method send_message_to_customer (line 59) | async def send_message_to_customer(self, message: str) -> str:
method escalate_to_human_agent (line 64) | def escalate_to_human_agent(self) -> None:
FILE: motleycrew/applications/customer_support/issue_tree.py
class IssueNode (line 15) | class IssueNode(MotleyGraphNode):
class Issue (line 20) | class Issue(BaseModel):
class IssueData (line 26) | class IssueData(MotleyGraphNode):
function insert_issue_data (line 31) | def insert_issue_data(graph_store: MotleyKuzuGraphStore, issue: Issue, c...
class IssueCategorizer (line 77) | class IssueCategorizer:
method __init__ (line 78) | def __init__(self, graph_store: MotleyKuzuGraphStore, max_depth: int =...
method _get_or_create_root (line 83) | def _get_or_create_root(self) -> IssueNode:
method _get_children (line 99) | def _get_children(self, parent: IssueNode) -> List[IssueNode]:
method _create_child (line 112) | def _create_child(self, parent: IssueNode, name: str, description: str...
method _categorize (line 118) | def _categorize(self, issue: Issue, categories: List[IssueNode]) -> Op...
method categorize_issue (line 156) | def categorize_issue(self, issue: Issue, insert_issue: bool = False):
function main (line 199) | def main():
FILE: motleycrew/applications/customer_support/ray_serve_app.py
class WebSocketCommunicationInterface (line 23) | class WebSocketCommunicationInterface:
method __init__ (line 24) | def __init__(self, websocket: WebSocket):
method send_message_to_customer (line 27) | async def send_message_to_customer(self, message: str) -> str:
method escalate_to_human_agent (line 32) | async def escalate_to_human_agent(self) -> None:
method resolve_issue (line 37) | async def resolve_issue(self, resolution: str) -> str:
class SupportAgentDeployment (line 43) | class SupportAgentDeployment:
method __init__ (line 44) | def __init__(self):
method root (line 55) | async def root(self):
method websocket_endpoint (line 59) | async def websocket_endpoint(self, websocket: WebSocket):
function main (line 91) | def main():
FILE: motleycrew/applications/customer_support/support_agent.py
class SupportAgentContext (line 32) | class SupportAgentContext:
class IssueTreeViewInput (line 38) | class IssueTreeViewInput(BaseModel):
class IssueTreeViewTool (line 44) | class IssueTreeViewTool(MotleyTool):
method __init__ (line 45) | def __init__(self, context: SupportAgentContext):
method run (line 53) | def run(self, categories_path: List[str]):
method _get_root_node (line 71) | def _get_root_node(self) -> Optional[IssueNode]:
method _get_child_node (line 76) | def _get_child_node(self, parent: IssueNode, category: str) -> Optiona...
method _format_node_view (line 89) | def _format_node_view(self, node: IssueNode) -> str:
method _get_subcategories (line 110) | def _get_subcategories(self, node: IssueNode) -> List[IssueNode]:
method _get_issues (line 120) | def _get_issues(self, node: IssueNode) -> List[IssueData]:
class CustomerChatInput (line 131) | class CustomerChatInput(BaseModel):
class CustomerChatTool (line 135) | class CustomerChatTool(MotleyTool):
method __init__ (line 136) | def __init__(self, context: SupportAgentContext):
method arun (line 145) | async def arun(self, message: str) -> str:
class ResolveIssueInput (line 156) | class ResolveIssueInput(BaseModel):
class ResolveIssueTool (line 161) | class ResolveIssueTool(MotleyTool):
method __init__ (line 162) | def __init__(self, context: SupportAgentContext):
method run (line 171) | def run(self, resolution: Optional[str] = None, escalate: bool = False...
function format_chat_history (line 239) | def format_chat_history(chat_history: list) -> str:
function create_support_agent (line 249) | def create_support_agent(
class SupportAgent (line 275) | class SupportAgent(LangchainMotleyAgent):
method __init__ (line 276) | def __init__(
function main (line 308) | async def main():
FILE: motleycrew/applications/expenses/expenses.py
class SchemaDesignerOutput (line 24) | class SchemaDesignerOutput(BaseModel):
class SchemaDesignerExtendedOutput (line 35) | class SchemaDesignerExtendedOutput(SchemaDesignerOutput):
class SchemaDesignerInput (line 42) | class SchemaDesignerInput(BaseModel):
class VerifyPolicyRepresentation (line 50) | class VerifyPolicyRepresentation(MotleyTool):
method invoke (line 51) | def invoke(self, latest_schema: str, query: str):
FILE: motleycrew/applications/expenses/schema_delta.py
class DeltaGeneratorInput (line 4) | class DeltaGeneratorInput(BaseModel):
FILE: motleycrew/applications/expenses/sql_tools.py
class SQLExpression (line 1) | class SQLExpression(BaseModel):
FILE: motleycrew/applications/faust_workflow/faust_workflow.py
class ExecutionContext (line 8) | class ExecutionContext(Record):
class Event (line 14) | class Event(Record, abstract=True):
function step (line 18) | def step(func):
class FaustWorkflow (line 26) | class FaustWorkflow:
method __init__ (line 33) | def __init__(self, app: App, verbose: bool = False, timeout: int = 60):
method _initialize_steps (line 52) | def _initialize_steps(self):
method _create_agent (line 73) | def _create_agent(
method _create_final_agent (line 108) | def _create_final_agent(self):
method send_event (line 123) | async def send_event(self, ev: Event):
method run (line 148) | async def run(self, ev: Event) -> Event:
FILE: motleycrew/applications/faust_workflow/visualize.py
function draw_faust_workflow (line 8) | def draw_faust_workflow(
FILE: motleycrew/applications/research_agent/answer_task.py
class QuestionAnsweringTaskUnit (line 14) | class QuestionAnsweringTaskUnit(TaskUnit):
class AnswerTask (line 18) | class AnswerTask(Task):
method __init__ (line 21) | def __init__(
method get_next_unit (line 38) | def get_next_unit(self) -> QuestionAnsweringTaskUnit | None:
method get_worker (line 61) | def get_worker(self, tools: Optional[List[MotleyTool]]) -> Runnable:
FILE: motleycrew/applications/research_agent/question.py
class Question (line 8) | class Question(MotleyGraphNode):
method __repr__ (line 15) | def __repr__(self):
FILE: motleycrew/applications/research_agent/question_answerer.py
class AnswerSubQuestionTool (line 32) | class AnswerSubQuestionTool(MotleyTool):
method __init__ (line 35) | def __init__(
class QuestionAnswererInput (line 52) | class QuestionAnswererInput(BaseModel, arbitrary_types_allowed=True):
function get_subquestions (line 58) | def get_subquestions(graph: MotleyGraphStore, question: Question) -> lis...
function create_answer_question_langchain_tool (line 71) | def create_answer_question_langchain_tool(
FILE: motleycrew/applications/research_agent/question_generator.py
class QuestionGeneratorTool (line 36) | class QuestionGeneratorTool(MotleyTool):
method __init__ (line 47) | def __init__(
class QuestionGeneratorToolInput (line 66) | class QuestionGeneratorToolInput(BaseModel, arbitrary_types_allowed=True):
function create_question_generator_langchain_tool (line 72) | def create_question_generator_langchain_tool(
FILE: motleycrew/applications/research_agent/question_prioritizer.py
class QuestionPrioritizerTool (line 16) | class QuestionPrioritizerTool(MotleyTool):
method __init__ (line 19) | def __init__(
class QuestionPrioritizerInput (line 43) | class QuestionPrioritizerInput(BaseModel, arbitrary_types_allowed=True):
function create_question_prioritizer_langchain_tool (line 52) | def create_question_prioritizer_langchain_tool(
FILE: motleycrew/applications/research_agent/question_task.py
class QuestionGenerationTaskUnit (line 16) | class QuestionGenerationTaskUnit(TaskUnit):
class QuestionTask (line 20) | class QuestionTask(Task):
method __init__ (line 23) | def __init__(
method get_next_unit (line 49) | def get_next_unit(self) -> QuestionGenerationTaskUnit | None:
method on_unit_dispatch (line 76) | def on_unit_dispatch(self, unit: TaskUnitType) -> None:
method on_unit_completion (line 82) | def on_unit_completion(self, unit: TaskUnitType) -> None:
method get_worker (line 90) | def get_worker(self, tools: Optional[List[MotleyTool]]) -> Runnable:
method get_unanswered_questions (line 95) | def get_unanswered_questions(self, only_without_children: bool = False...
FILE: motleycrew/common/aux_prompts.py
class AuxPrompts (line 7) | class AuxPrompts:
method get_direct_output_error_message (line 27) | def get_direct_output_error_message(output_handlers: List["MotleyTool"...
method get_ambiguous_output_handler_call_error_message (line 40) | def get_ambiguous_output_handler_call_error_message(
FILE: motleycrew/common/defaults.py
class Defaults (line 4) | class Defaults:
FILE: motleycrew/common/enums.py
class LLMProvider (line 4) | class LLMProvider:
class LLMFramework (line 16) | class LLMFramework:
class GraphStoreType (line 23) | class GraphStoreType:
class TaskUnitStatus (line 29) | class TaskUnitStatus:
class LunaryRunType (line 37) | class LunaryRunType:
class LunaryEventName (line 47) | class LunaryEventName:
class AsyncBackend (line 56) | class AsyncBackend:
FILE: motleycrew/common/exceptions.py
class LLMProviderNotSupported (line 8) | class LLMProviderNotSupported(Exception):
method __init__ (line 11) | def __init__(self, llm_framework: str, llm_provider: str):
method __str__ (line 15) | def __str__(self) -> str:
class LLMFrameworkNotSupported (line 19) | class LLMFrameworkNotSupported(Exception):
method __init__ (line 22) | def __init__(self, llm_framework: str):
method __str__ (line 25) | def __str__(self) -> str:
class AgentNotMaterialized (line 29) | class AgentNotMaterialized(Exception):
method __init__ (line 32) | def __init__(self, agent_name: str):
method __str__ (line 35) | def __str__(self) -> str:
class CannotModifyMaterializedAgent (line 39) | class CannotModifyMaterializedAgent(Exception):
method __init__ (line 42) | def __init__(self, agent_name: str | None):
method __str__ (line 45) | def __str__(self) -> str:
class TaskDependencyCycleError (line 51) | class TaskDependencyCycleError(Exception):
class IntegrationTestException (line 55) | class IntegrationTestException(Exception):
method __init__ (line 58) | def __init__(self, test_names: list[str]):
method __str__ (line 65) | def __str__(self):
class IpynbIntegrationTestResultNotFound (line 69) | class IpynbIntegrationTestResultNotFound(Exception):
method __init__ (line 72) | def __init__(self, ipynb_path: str, result_path: str):
method __str__ (line 76) | def __str__(self):
class ModuleNotInstalled (line 82) | class ModuleNotInstalled(Exception):
method __init__ (line 86) | def __init__(self, module_name: str, install_command: str = None):
method __str__ (line 97) | def __str__(self):
class InvalidToolInput (line 106) | class InvalidToolInput(Exception):
method __init__ (line 109) | def __init__(self, tool: Any, input: Any, message: Optional[str] = None):
method __str__ (line 114) | def __str__(self):
class InvalidOutput (line 121) | class InvalidOutput(Exception):
FILE: motleycrew/common/llms.py
function langchain_openai_llm (line 10) | def langchain_openai_llm(
function llama_index_openai_llm (line 24) | def llama_index_openai_llm(
function langchain_anthropic_llm (line 40) | def langchain_anthropic_llm(
function llama_index_anthropic_llm (line 55) | def llama_index_anthropic_llm(
function langchain_replicate_llm (line 70) | def langchain_replicate_llm(
function llama_index_replicate_llm (line 85) | def llama_index_replicate_llm(
function langchain_together_llm (line 100) | def langchain_together_llm(
function llama_index_together_llm (line 114) | def llama_index_together_llm(
function langchain_groq_llm (line 129) | def langchain_groq_llm(
function llama_index_groq_llm (line 143) | def llama_index_groq_llm(
function langchain_ollama_llm (line 158) | def langchain_ollama_llm(
function llama_index_ollama_llm (line 172) | def llama_index_ollama_llm(
function langchain_azure_openai_llm (line 187) | def langchain_azure_openai_llm(
function llama_index_azure_openai_llm (line 201) | def llama_index_azure_openai_llm(
function init_llm (line 242) | def init_llm(
FILE: motleycrew/common/logging.py
function configure_logging (line 18) | def configure_logging(verbose: bool = False, debug: bool = False):
FILE: motleycrew/common/types.py
class MotleyAgentFactory (line 36) | class MotleyAgentFactory(Protocol[AgentType]):
method __call__ (line 47) | def __call__(
FILE: motleycrew/common/utils.py
function to_str (line 13) | def to_str(value: str | BaseMessage | Sequence[str] | Sequence[BaseMessa...
function is_http_url (line 27) | def is_http_url(url):
function generate_hex_hash (line 37) | def generate_hex_hash(data: str, length: Optional[int] = None):
function print_passthrough (line 49) | def print_passthrough(x):
function ensure_module_is_installed (line 57) | def ensure_module_is_installed(module_name: str, install_command: str = ...
FILE: motleycrew/crew/crew.py
class MotleyCrew (line 15) | class MotleyCrew:
method __init__ (line 20) | def __init__(
method run (line 48) | def run(self) -> list[TaskUnit]:
method add_dependency (line 71) | def add_dependency(self, upstream: Task, downstream: Task):
method register_tasks (line 84) | def register_tasks(self, tasks: Collection[Task]):
method _prepare_next_unit_for_dispatch (line 104) | def _prepare_next_unit_for_dispatch(
method _handle_task_unit_completion (line 145) | def _handle_task_unit_completion(
method _run_sync (line 172) | def _run_sync(self) -> list[TaskUnit]:
method _run_threaded (line 202) | def _run_threaded(self) -> list[TaskUnit]:
method _async_invoke_agent (line 237) | async def _async_invoke_agent(agent: MotleyAgentParent, unit: TaskUnit...
method _run_async (line 240) | async def _run_async(self) -> list[TaskUnit]:
method get_available_tasks (line 274) | def get_available_tasks(self) -> list[Task]:
method add_task_unit_to_graph (line 295) | def add_task_unit_to_graph(self, task: Task, unit: TaskUnitType):
method get_extra_tools (line 311) | def get_extra_tools(self, task: Task) -> list[MotleyTool]:
method check_cyclical_dependencies (line 323) | def check_cyclical_dependencies(self):
FILE: motleycrew/crew/crew_threads.py
class TaskUnitThreadState (line 17) | class TaskUnitThreadState(Enum):
class TaskUnitThread (line 26) | class TaskUnitThread(threading.Thread):
method __init__ (line 29) | def __init__(self, input_queue: Queue, output_queue: Queue, *args, **k...
method state (line 45) | def state(self):
method run (line 49) | def run(self) -> None:
class TaskUnitThreadPool (line 76) | class TaskUnitThreadPool:
method __init__ (line 79) | def __init__(self, num_threads: int = Defaults.DEFAULT_NUM_THREADS):
method add_task_unit (line 97) | def add_task_unit(self, agent: Runnable, task: "Task", unit: "TaskUnit...
method get_completed_task_units (line 108) | def get_completed_task_units(self) -> List[Tuple["Task", "TaskUnit", A...
method wait_and_close (line 125) | def wait_and_close(self):
method is_completed (line 136) | def is_completed(self) -> bool:
FILE: motleycrew/storage/graph_node.py
class MotleyGraphNode (line 9) | class MotleyGraphNode(BaseModel, ABC):
method id (line 23) | def id(self) -> Optional[Any]:
method is_inserted (line 32) | def is_inserted(self) -> bool:
method get_label (line 37) | def get_label(cls) -> str:
method __setattr__ (line 55) | def __setattr__(self, name, value):
method __eq__ (line 73) | def __eq__(self, other):
FILE: motleycrew/storage/graph_store.py
class MotleyGraphStore (line 6) | class MotleyGraphStore(ABC):
method check_node_exists_by_class_and_id (line 10) | def check_node_exists_by_class_and_id(
method check_node_exists (line 22) | def check_node_exists(self, node: MotleyGraphNode) -> bool:
method check_relation_exists (line 35) | def check_relation_exists(
method get_node_by_class_and_id (line 49) | def get_node_by_class_and_id(
method insert_node (line 65) | def insert_node(self, node: MotleyGraphNode):
method create_relation (line 78) | def create_relation(
method upsert_triplet (line 95) | def upsert_triplet(self, from_node: MotleyGraphNode, to_node: MotleyGr...
method delete_node (line 111) | def delete_node(self, node: MotleyGraphNode) -> None:
method update_property (line 123) | def update_property(self, node: MotleyGraphNode, property_name: str):
method ensure_node_table (line 135) | def ensure_node_table(self, node_class: Type[MotleyGraphNode]) -> str:...
method ensure_relation_table (line 146) | def ensure_relation_table(
method run_cypher_query (line 162) | def run_cypher_query(
FILE: motleycrew/storage/graph_store_utils.py
function init_graph_store (line 11) | def init_graph_store(
FILE: motleycrew/storage/kuzu_graph_store.py
class MotleyKuzuGraphStore (line 19) | class MotleyKuzuGraphStore(MotleyGraphStore):
method __init__ (line 37) | def __init__(self, database: Any) -> None:
method __repr__ (line 46) | def __repr__(self):
method __str__ (line 49) | def __str__(self):
method database_path (line 53) | def database_path(self) -> str:
method _execute_query (line 56) | def _execute_query(
method _check_node_table_exists (line 75) | def _check_node_table_exists(self, label: str) -> bool:
method _check_rel_table_exists (line 86) | def _check_rel_table_exists(
method _get_node_property_names (line 112) | def _get_node_property_names(self, label: str) -> Collection[str]:
method ensure_node_table (line 123) | def ensure_node_table(self, node_class: Type[MotleyGraphNode]) -> str:
method ensure_relation_table (line 160) | def ensure_relation_table(
method check_node_exists_by_class_and_id (line 187) | def check_node_exists_by_class_and_id(
method check_node_exists (line 208) | def check_node_exists(self, node: MotleyGraphNode) -> bool:
method check_relation_exists (line 222) | def check_relation_exists(
method get_node_by_class_and_id (line 264) | def get_node_by_class_and_id(
method insert_node (line 292) | def insert_node(self, node: MotleyGraphNodeType) -> MotleyGraphNodeType:
method create_relation (line 328) | def create_relation(
method upsert_triplet (line 374) | def upsert_triplet(
method delete_node (line 399) | def delete_node(self, node: MotleyGraphNode) -> None:
method update_property (line 436) | def update_property(self, node: MotleyGraphNode, property_name: str) -...
method run_cypher_query (line 490) | def run_cypher_query(
method _deserialize_node (line 519) | def _deserialize_node(
method _set_node_id (line 552) | def _set_node_id(node: MotleyGraphNode, node_id: Optional[int]) -> None:
method _node_to_cypher_mapping_with_parameters (line 562) | def _node_to_cypher_mapping_with_parameters(node: MotleyGraphNode) -> ...
method _get_cypher_type_and_is_json_by_python_type_annotation (line 595) | def _get_cypher_type_and_is_json_by_python_type_annotation(
method from_persist_dir (line 617) | def from_persist_dir(
method from_dict (line 639) | def from_dict(cls, config_dict: Dict[str, Any]) -> "MotleyKuzuGraphSto...
FILE: motleycrew/storage/kv_store_domain.py
class RetrievableObjectParent (line 6) | class RetrievableObjectParent(ABC):
method __init__ (line 7) | def __init__(self, id: str, name: str, description: Optional[str] = No...
method summary (line 17) | def summary(self) -> str:
class SimpleRetrievableObject (line 21) | class SimpleRetrievableObject(RetrievableObjectParent):
method __init__ (line 22) | def __init__(
method summary (line 29) | def summary(self) -> str:
FILE: motleycrew/tasks/simple.py
function compose_simple_task_prompt_with_dependencies (line 27) | def compose_simple_task_prompt_with_dependencies(
class SimpleTaskUnit (line 68) | class SimpleTaskUnit(TaskUnit):
class SimpleTask (line 82) | class SimpleTask(Task):
method __init__ (line 92) | def __init__(
method on_unit_completion (line 125) | def on_unit_completion(self, unit: SimpleTaskUnit) -> None:
method get_next_unit (line 139) | def get_next_unit(self) -> SimpleTaskUnit | None:
method get_worker (line 169) | def get_worker(self, tools: Optional[List[MotleyTool]]) -> MotleyAgent...
FILE: motleycrew/tasks/task.py
class TaskNode (line 17) | class TaskNode(MotleyGraphNode):
method __eq__ (line 30) | def __eq__(self, other):
class Task (line 37) | class Task(ABC, Generic[TaskUnitType]):
method __init__ (line 50) | def __init__(
method prepare_graph_store (line 81) | def prepare_graph_store(self):
method graph_store (line 93) | def graph_store(self) -> MotleyGraphStore:
method __repr__ (line 102) | def __repr__(self) -> str:
method __str__ (line 105) | def __str__(self) -> str:
method set_upstream (line 108) | def set_upstream(self, task: Task) -> Task:
method __rshift__ (line 127) | def __rshift__(self, other: Task | Sequence[Task]) -> Task:
method __rrshift__ (line 143) | def __rrshift__(self, other: Sequence[Task]) -> Sequence[Task]:
method get_units (line 153) | def get_units(self, status: Optional[str] = None) -> List[TaskUnitType]:
method get_upstream_tasks (line 185) | def get_upstream_tasks(self) -> List[Task]:
method get_downstream_tasks (line 208) | def get_downstream_tasks(self) -> List[Task]:
method set_done (line 231) | def set_done(self, value: bool = True):
method on_unit_dispatch (line 240) | def on_unit_dispatch(self, unit: TaskUnitType) -> None:
method on_unit_completion (line 250) | def on_unit_completion(self, unit: TaskUnitType) -> None:
method get_next_unit (line 261) | def get_next_unit(self) -> TaskUnitType | None:
method get_worker (line 282) | def get_worker(self, tools: Optional[List[MotleyTool]]) -> Runnable:
FILE: motleycrew/tasks/task_unit.py
class TaskUnit (line 10) | class TaskUnit(MotleyGraphNode, ABC):
method __repr__ (line 25) | def __repr__(self) -> str:
method __str__ (line 28) | def __str__(self) -> str:
method __eq__ (line 31) | def __eq__(self, other: TaskUnit):
method pending (line 35) | def pending(self):
method running (line 40) | def running(self):
method done (line 45) | def done(self):
method set_pending (line 49) | def set_pending(self):
method set_running (line 53) | def set_running(self):
method set_done (line 57) | def set_done(self):
method as_dict (line 61) | def as_dict(self):
FILE: motleycrew/tools/agentic_validation_loop.py
class AgenticValidationLoop (line 14) | class AgenticValidationLoop(MotleyTool):
method __init__ (line 16) | def __init__(
method run (line 49) | def run(self, **kwargs) -> Any:
FILE: motleycrew/tools/autogen_chat_tool.py
function get_last_message (line 18) | def get_last_message(chat_result: ChatResult) -> str:
class AutoGenChatTool (line 24) | class AutoGenChatTool(MotleyTool):
method __init__ (line 27) | def __init__(
function create_autogen_chat_tool (line 70) | def create_autogen_chat_tool(
FILE: motleycrew/tools/code/aider_tool.py
class AiderTool (line 19) | class AiderTool(MotleyTool):
method __init__ (line 22) | def __init__(
class AiderToolInput (line 43) | class AiderToolInput(BaseModel):
function create_aider_tool (line 49) | def create_aider_tool(coder: Coder):
FILE: motleycrew/tools/code/postgresql_linter.py
class PostgreSQLLinterTool (line 18) | class PostgreSQLLinterTool(MotleyTool):
method __init__ (line 21) | def __init__(
class PostgreSQLLinterInput (line 38) | class PostgreSQLLinterInput(BaseModel):
function create_pgsql_linter_tool (line 44) | def create_pgsql_linter_tool() -> Tool:
FILE: motleycrew/tools/code/python_linter.py
class PythonLinterTool (line 16) | class PythonLinterTool(MotleyTool):
method __init__ (line 19) | def __init__(
class PythonLinterInput (line 34) | class PythonLinterInput(BaseModel):
function create_python_linter_tool (line 41) | def create_python_linter_tool() -> StructuredTool:
FILE: motleycrew/tools/code/python_repl.py
function warn_once (line 16) | def warn_once() -> None:
class MissingPrintStatementError (line 25) | class MissingPrintStatementError(Exception):
method __init__ (line 28) | def __init__(self, command: str):
class PythonREPLTool (line 35) | class PythonREPLTool(MotleyTool):
method __init__ (line 42) | def __init__(
method sanitize_input (line 65) | def sanitize_input(query: str) -> str:
method run (line 81) | def run(self, command: str) -> str:
method validate_input (line 100) | def validate_input(self, command: str):
class REPLToolInput (line 105) | class REPLToolInput(BaseModel):
FILE: motleycrew/tools/html_render_tool.py
class HTMLRenderer (line 21) | class HTMLRenderer:
method __init__ (line 24) | def __init__(
method render_image (line 47) | def render_image(self, html: str, file_name: str | None = None):
method build_file_paths (line 84) | def build_file_paths(self, file_name: str | None = None) -> Tuple[str,...
class HTMLRenderTool (line 99) | class HTMLRenderTool(MotleyTool):
method __init__ (line 102) | def __init__(
class HTMLRenderToolInput (line 130) | class HTMLRenderToolInput(BaseModel):
function create_render_tool (line 136) | def create_render_tool(renderer: HTMLRenderer):
FILE: motleycrew/tools/image/dall_e.py
class DallEImageGeneratorTool (line 24) | class DallEImageGeneratorTool(MotleyTool):
method __init__ (line 31) | def __init__(
class DallEToolInput (line 70) | class DallEToolInput(BaseModel):
function run_dalle_and_save_images (line 76) | def run_dalle_and_save_images(
function create_dalle_image_generator_langchain_tool (line 120) | def create_dalle_image_generator_langchain_tool(
FILE: motleycrew/tools/image/download_image.py
function download_image (line 10) | def download_image(url: str, file_path: str) -> Optional[str]:
function download_url_to_directory (line 35) | def download_url_to_directory(url: str, images_directory: str, file_name...
FILE: motleycrew/tools/image/replicate_tool.py
function run_model_in_replicate (line 29) | def run_model_in_replicate(model_name: str, prompt: str, **kwargs) -> st...
function run_model_in_replicate_and_save_images (line 36) | def run_model_in_replicate_and_save_images(
class ImageToolInput (line 52) | class ImageToolInput(BaseModel):
class ReplicateImageGeneratorTool (line 58) | class ReplicateImageGeneratorTool(MotleyTool):
method __init__ (line 59) | def __init__(
function create_replicate_image_generator_langchain_tool (line 88) | def create_replicate_image_generator_langchain_tool(
FILE: motleycrew/tools/llm_tool.py
class LLMTool (line 14) | class LLMTool(MotleyTool):
method __init__ (line 17) | def __init__(
function create_llm_langchain_tool (line 54) | def create_llm_langchain_tool(
FILE: motleycrew/tools/mermaid_evaluator_tool.py
class MermaidEvaluatorTool (line 15) | class MermaidEvaluatorTool(MotleyTool):
method __init__ (line 16) | def __init__(
function eval_mermaid (line 41) | def eval_mermaid(mermaid_code: str, format: Optional[str] = "svg") -> io...
FILE: motleycrew/tools/simple_retriever_tool.py
class SimpleRetrieverTool (line 20) | class SimpleRetrieverTool(MotleyTool):
method __init__ (line 23) | def __init__(
class RetrieverToolInput (line 46) | class RetrieverToolInput(BaseModel, arbitrary_types_allowed=True):
function make_retriever_langchain_tool (line 54) | def make_retriever_langchain_tool(
FILE: motleycrew/tools/sql_validation.py
class ValidationLevel (line 35) | class ValidationLevel(Enum):
class ValidationError (line 43) | class ValidationError:
method __str__ (line 51) | def __str__(self) -> str:
class SchemaValidator (line 60) | class SchemaValidator:
method __init__ (line 69) | def __init__(self, dialect: str = "postgres"):
method load_schema_from_sql (line 79) | def load_schema_from_sql(self, schema_sql: str) -> Dict[str, Dict[str,...
method load_schema_from_dict (line 288) | def load_schema_from_dict(self, schema_dict: Dict[str, Dict[str, str]]...
method validate_query (line 297) | def validate_query(
method get_detailed_validation_errors (line 376) | def get_detailed_validation_errors(self, query: str) -> List[Validatio...
class SQLValidator (line 476) | class SQLValidator:
method validate_syntax (line 484) | def validate_syntax(query: str, dialect: str = "postgres") -> Tuple[bo...
method validate_query_against_schema (line 504) | def validate_query_against_schema(
method validate_query_against_schema_dict (line 530) | def validate_query_against_schema_dict(
method format_query (line 556) | def format_query(query: str, dialect: str = "postgres", pretty: bool =...
method transpile_query (line 575) | def transpile_query(
FILE: motleycrew/tools/structured_passthrough.py
class StructuredPassthroughTool (line 10) | class StructuredPassthroughTool(MotleyTool):
method __init__ (line 15) | def __init__(
method run (line 35) | def run(self, **kwargs) -> T:
FILE: motleycrew/tools/tool.py
class DirectOutput (line 35) | class DirectOutput(BaseException):
method __init__ (line 42) | def __init__(self, output: Any):
class RetryConfig (line 47) | class RetryConfig:
class MotleyTool (line 63) | class MotleyTool(Runnable):
method __init__ (line 69) | def __init__(
method __repr__ (line 136) | def __repr__(self):
method __str__ (line 139) | def __str__(self):
method name (line 143) | def name(self):
method description (line 148) | def description(self):
method args_schema (line 153) | def args_schema(self):
method is_async (line 158) | def is_async(self):
method _patch_tool_run (line 162) | def _patch_tool_run(self):
method _patch_tool_arun (line 203) | def _patch_tool_arun(self):
method _format_error (line 244) | def _format_error(e: Exception) -> str:
method ainvoke (line 261) | async def ainvoke(
method invoke (line 269) | def invoke(
method run (line 277) | def run(self, *args, **kwargs):
method arun (line 280) | async def arun(self, *args, **kwargs):
method _tool_from_run_method (line 283) | def _tool_from_run_method(self, name: str, description: str, args_sche...
method from_langchain_tool (line 307) | def from_langchain_tool(
method from_llama_index_tool (line 321) | def from_llama_index_tool(
method from_crewai_tool (line 356) | def from_crewai_tool(
method from_motley_agent (line 388) | def from_motley_agent(
method from_supported_tool (line 413) | def from_supported_tool(
method to_langchain_tool (line 466) | def to_langchain_tool(self) -> BaseTool:
method to_llama_index_tool (line 474) | def to_llama_index_tool(self) -> LlamaIndex__BaseTool:
method to_autogen_tool (line 495) | def to_autogen_tool(self) -> Callable:
method to_crewai_tool (line 517) | def to_crewai_tool(self) -> CrewAI__BaseTool:
FILE: motleycrew/tracking/callbacks.py
function event_delegate_decorator (line 29) | def event_delegate_decorator(f):
function _message_to_dict (line 78) | def _message_to_dict(message: ChatMessage) -> Dict[str, Any]:
class LlamaIndexLunaryCallbackHandler (line 98) | class LlamaIndexLunaryCallbackHandler(BaseCallbackHandler):
method __init__ (line 102) | def __init__(
method _get_initial_track_event_params (line 134) | def _get_initial_track_event_params(
method _on_llm_start (line 156) | def _on_llm_start(
method _on_llm_end (line 190) | def _on_llm_end(
method _on_function_call_start (line 214) | def _on_function_call_start(
method _on_function_call_end (line 249) | def _on_function_call_end(
method _on_agent_step_start (line 271) | def _on_agent_step_start(
method _on_agent_step_end (line 300) | def _on_agent_step_end(
method _on_exception_start (line 329) | def _on_exception_start(
method start_trace (line 360) | def start_trace(self, trace_id: Optional[str] = None) -> None:
method end_trace (line 364) | def end_trace(
method on_event_start (line 373) | def on_event_start(
method on_event_end (line 396) | def on_event_end(
method check_parent_id (line 414) | def check_parent_id(parent_id: str) -> Union[str, None]:
FILE: motleycrew/tracking/utils.py
function get_lunary_public_key (line 19) | def get_lunary_public_key():
function create_lunary_callback (line 35) | def create_lunary_callback() -> LunaryCallbackHandler:
function get_llamaindex_default_callbacks (line 47) | def get_llamaindex_default_callbacks():
function get_langchain_default_callbacks (line 64) | def get_langchain_default_callbacks():
function get_default_callbacks_list (line 86) | def get_default_callbacks_list(
function add_callback_handlers_to_config (line 109) | def add_callback_handlers_to_config(
function add_default_callbacks_to_langchain_config (line 149) | def add_default_callbacks_to_langchain_config(
FILE: motleycrew/utils/chart_to_df.py
class SeriesData (line 14) | class SeriesData(BaseModel):
class ChartDataResult (line 19) | class ChartDataResult(BaseModel):
method validate_series_data_length (line 26) | def validate_series_data_length(self):
method to_df (line 40) | def to_df(self, x_axis_name: str = "x_axis") -> pd.DataFrame:
function extract_chart_data_from_file (line 48) | def extract_chart_data_from_file(
function extract_chart_data (line 55) | def extract_chart_data(
FILE: motleycrew/utils/image_utils.py
function _convert_with_libreoffice (line 25) | def _convert_with_libreoffice(image_bytes: bytes, source_mime_type: str)...
function _convert_with_wand (line 91) | def _convert_with_wand(image_bytes: bytes, source_mime_type: str) -> Tup...
function convert_image_to_png (line 120) | def convert_image_to_png(image_bytes: bytes, source_mime_type: str) -> T...
function human_message_from_image_bytes (line 145) | def human_message_from_image_bytes(image_bytes: bytes, mime_type: str) -...
function image_file_to_bytes_and_mime_type (line 153) | def image_file_to_bytes_and_mime_type(image_path: str) -> Tuple[bytes, s...
function is_this_a_chart (line 175) | def is_this_a_chart(image_bytes: bytes, mime_type: str, llm: BaseLanguag...
FILE: motleycrew/utils/structured_output_with_retries.py
function structured_output_with_retries (line 13) | def structured_output_with_retries(
FILE: tests/run_integration_tests.py
function get_args_parser (line 61) | def get_args_parser():
function run_ipynb (line 90) | def run_ipynb(ipynb_path: str, strong_cache: bool = False, cache_sub_dir...
function build_ipynb_integration_tests (line 122) | def build_ipynb_integration_tests(is_minimal: bool = False) -> dict:
function run_integration_tests (line 136) | def run_integration_tests(
function main (line 196) | def main():
FILE: tests/test_agents/__init__.py
class MockToolInput (line 7) | class MockToolInput(BaseModel):
class MockTool (line 13) | class MockTool(BaseTool):
method _run (line 21) | def _run(self, tool_input: str, *args, **kwargs):
FILE: tests/test_agents/test_agent_chain.py
class AgentMock (line 6) | class AgentMock:
method invoke (line 7) | def invoke(self, input_dict: dict, *args, **kwargs):
class MotleyAgentMock (line 11) | class MotleyAgentMock(MotleyAgentParent):
method invoke (line 13) | def invoke(self, *args, **kwargs):
function agent_factory (line 18) | def agent_factory(*args, **kwargs):
function motley_agents (line 23) | def motley_agents():
function test_agent_chain (line 29) | def test_agent_chain(motley_agents):
FILE: tests/test_agents/test_agents.py
class TestAgents (line 19) | class TestAgents:
method agent (line 21) | def agent(self):
method test_add_tools (line 31) | def test_add_tools(self, agent):
method test_materialized (line 37) | def test_materialized(self, agent):
method test_compose_prompt (line 48) | def test_compose_prompt(self, agent):
method test_streaming_configuration_preserved (line 54) | def test_streaming_configuration_preserved(self):
method test_streaming_configuration_preserved_true (line 80) | def test_streaming_configuration_preserved_true(self):
method test_default_llm_no_streaming_configuration (line 105) | def test_default_llm_no_streaming_configuration(self):
method test_compose_prompt_with_messages_and_variables_error (line 119) | def test_compose_prompt_with_messages_and_variables_error(self):
method test_compose_prompt_with_messages_no_variables (line 132) | def test_compose_prompt_with_messages_no_variables(self):
method test_compose_prompt_with_messages_no_variables_as_string (line 149) | def test_compose_prompt_with_messages_no_variables_as_string(self):
FILE: tests/test_agents/test_kv_store_tools.py
class TestObjectInsertionTool (line 10) | class TestObjectInsertionTool(MotleyTool):
method run (line 11) | def run(self, query: str) -> List[str]:
class TestObjectFetcherTool (line 19) | class TestObjectFetcherTool(MotleyTool):
method run (line 20) | def run(self, object_id: str) -> str:
function kv_store_agent (line 26) | def kv_store_agent():
function test_kv_store_insertion (line 43) | def test_kv_store_insertion(kv_store_agent):
function test_kv_store_retrieval (line 55) | def test_kv_store_retrieval(kv_store_agent):
function test_kv_store_nonexistent_key (line 67) | def test_kv_store_nonexistent_key(kv_store_agent):
FILE: tests/test_agents/test_langchain_output_handler.py
class ReportOutputHandler (line 13) | class ReportOutputHandler(MotleyTool):
method __init__ (line 14) | def __init__(self):
method run (line 21) | def run(self, output: str):
function fake_agent_plan (line 28) | def fake_agent_plan(intermediate_steps, step, **kwargs):
function fake_agent_take_next_step (line 32) | def fake_agent_take_next_step(
function agent (line 46) | def agent():
function run_kwargs (line 67) | def run_kwargs(agent):
function test_agent_plan (line 79) | def test_agent_plan(agent):
function test_agent_take_next_step (line 97) | def test_agent_take_next_step(agent, run_kwargs):
function test_anthropic_output_handler_error_recovery (line 116) | def test_anthropic_output_handler_error_recovery():
FILE: tests/test_agents/test_llms.py
function test_init_llm (line 18) | def test_init_llm(llm_provider, llm_framework, expected_class):
function test_raise_init_llm (line 23) | def test_raise_init_llm():
function test_init_llm_with_temperature_via_kwargs (line 28) | def test_init_llm_with_temperature_via_kwargs():
function test_init_llm_without_temperature (line 42) | def test_init_llm_without_temperature():
FILE: tests/test_crew/__init__.py
class AgentMock (line 7) | class AgentMock:
method invoke (line 8) | def invoke(self, input_dict) -> str:
method ainvoke (line 12) | async def ainvoke(self, input_dict) -> str:
method clear_input_dict (line 16) | def clear_input_dict(input_dict: dict) -> dict:
class CrewFixtures (line 25) | class CrewFixtures:
method crew (line 29) | def crew(self):
method agent (line 34) | def agent(self):
method tasks (line 38) | def tasks(self, request, crew, agent):
FILE: tests/test_crew/test_crew.py
class TestCrew (line 7) | class TestCrew(CrewFixtures):
method test_create_simple_task (line 9) | def test_create_simple_task(self, crew, agent):
method test_add_dependency (line 17) | def test_add_dependency(self, crew, tasks):
method test_register_added_task (line 23) | def test_register_added_task(self, tasks, crew):
method test_get_available_task (line 29) | def test_get_available_task(self, crew):
method test_get_extra_tools (line 33) | def test_get_extra_tools(self, crew):
method test_run (line 37) | def test_run(self, crew, agent):
FILE: tests/test_crew/test_crew_threads.py
class TestInvokeThreadPool (line 8) | class TestInvokeThreadPool(CrewFixtures):
method thread_pool (line 11) | def thread_pool(self):
method thread_pool_with_tasks (line 18) | def thread_pool_with_tasks(self, tasks, thread_pool, agent):
method test_init_thread_pool (line 25) | def test_init_thread_pool(self, thread_pool):
method test_put (line 33) | def test_put(self, thread_pool, agent, tasks):
method test_get_completed_tasks (line 42) | def test_get_completed_tasks(self, thread_pool, agent, tasks):
method test_get_completed_task_exception (line 56) | def test_get_completed_task_exception(self, thread_pool, agent, tasks):
method test_close (line 66) | def test_close(self, thread_pool):
FILE: tests/test_image_utils.py
function test_human_message_from_image_bytes (line 14) | def test_human_message_from_image_bytes():
function test_image_file_to_bytes_and_mime_type (line 28) | def test_image_file_to_bytes_and_mime_type():
function test_image_file_to_bytes_and_mime_type_unknown_type (line 43) | def test_image_file_to_bytes_and_mime_type_unknown_type():
function test_is_this_a_chart (line 58) | def test_is_this_a_chart():
function test_is_this_a_chart_not_chart (line 85) | def test_is_this_a_chart_not_chart():
FILE: tests/test_storage/__init__.py
class GraphStoreFixtures (line 7) | class GraphStoreFixtures:
method kuzu_graph_store (line 9) | def kuzu_graph_store(self, tmpdir):
method graph_store (line 17) | def graph_store(self, request, kuzu_graph_store):
FILE: tests/test_storage/test_graph_store.py
class Entity (line 11) | class Entity(MotleyGraphNode):
class TestMotleyGraphStore (line 17) | class TestMotleyGraphStore(GraphStoreFixtures):
method test_insert_new_node (line 19) | def test_insert_new_node(self, graph_store):
method test_insert_node_and_retrieve (line 26) | def test_insert_node_and_retrieve(self, graph_store):
method test_check_node_exists_true (line 37) | def test_check_node_exists_true(self, graph_store):
method test_check_node_exists_false (line 44) | def test_check_node_exists_false(self, graph_store):
method test_create_relation (line 55) | def test_create_relation(self, graph_store):
method test_upsert_triplet (line 69) | def test_upsert_triplet(self, graph_store):
method test_nodes_do_not_exist (line 83) | def test_nodes_do_not_exist(self, graph_store):
method test_relation_does_not_exist (line 94) | def test_relation_does_not_exist(self, graph_store):
method test_delete_node (line 108) | def test_delete_node(self, graph_store):
method test_delete_entity_with_relations (line 119) | def test_delete_entity_with_relations(self, graph_store):
method test_set_property (line 134) | def test_set_property(self, graph_store):
method test_run_cypher_query (line 150) | def test_run_cypher_query(self, graph_store):
method test_run_cypher_query_with_container (line 172) | def test_run_cypher_query_with_container(self, graph_store):
FILE: tests/test_storage/test_kuzu_graph_store.py
class Entity (line 12) | class Entity(MotleyGraphNode):
function database (line 19) | def database(tmpdir):
class TestMotleyKuzuGraphStore (line 25) | class TestMotleyKuzuGraphStore(GraphStoreFixtures):
method test_set_get_node_id (line 26) | def test_set_get_node_id(self):
method test_insert_node_with_id_already_set (line 32) | def test_insert_node_with_id_already_set(self, graph_store):
FILE: tests/test_tasks/test_simple_task.py
function graph_store (line 22) | def graph_store():
function crew (line 28) | def crew(graph_store):
function agent (line 33) | def agent():
function tasks (line 50) | def tasks(crew, agent):
class TestSimpleTask (line 57) | class TestSimpleTask:
method test_register_completed_unit (line 58) | def test_register_completed_unit(self, tasks, crew):
method test_get_next_unit (line 73) | def test_get_next_unit(self, tasks, crew):
method test_get_worker (line 89) | def test_get_worker(self, tasks, agent):
FILE: tests/test_tasks/test_task.py
class TaskMock (line 14) | class TaskMock(Task):
method get_next_unit (line 15) | def get_next_unit(self) -> List[TaskUnitType]:
method get_worker (line 18) | def get_worker(self, tools: Optional[List[MotleyTool]]) -> Runnable:
function create_dummy_task (line 22) | def create_dummy_task(crew: MotleyCrew, name: str):
function graph_store (line 31) | def graph_store():
function crew (line 37) | def crew(graph_store):
function task_1 (line 42) | def task_1(crew):
function task_2 (line 47) | def task_2(crew):
function task_3 (line 52) | def task_3(crew):
class TestSetUpstream (line 56) | class TestSetUpstream:
method test_set_upstream_returns_self (line 57) | def test_set_upstream_returns_self(self, task_1, task_2):
method test_set_upstream_sets_upstream (line 62) | def test_set_upstream_sets_upstream(self, task_1, task_2):
method test_set_upstream_sets_downstreams (line 68) | def test_set_upstream_sets_downstreams(self, task_1, task_2):
method test_rshift_returns_left (line 74) | def test_rshift_returns_left(self, task_1, task_2):
method test_rshift_sets_downstream (line 79) | def test_rshift_sets_downstream(self, task_1, task_2):
method test_rshift_sets_upstream (line 85) | def test_rshift_sets_upstream(self, task_1, task_2):
method test_rshift_set_multiple_downstream (line 91) | def test_rshift_set_multiple_downstream(self, task_1, task_2, task_3):
method test_rshift_set_multiple_upstream (line 98) | def test_rshift_set_multiple_upstream(self, task_1, task_2, task_3):
method test_sequence_on_left_returns_sequence (line 105) | def test_sequence_on_left_returns_sequence(self, task_1, task_2, task_3):
method test_sequence_on_left_sets_downstream (line 110) | def test_sequence_on_left_sets_downstream(self, task_1, task_2, task_3):
method test_sequence_on_left_sets_upstream (line 117) | def test_sequence_on_left_sets_upstream(self, task_1, task_2, task_3):
method test_deduplicates (line 124) | def test_deduplicates(self, task_1, task_2):
method test_error_on_direct_dependency_cycle (line 129) | def test_error_on_direct_dependency_cycle(self, task_1):
class TestTask (line 134) | class TestTask:
method test_set_done (line 135) | def test_set_done(self, task_1):
FILE: tests/test_tasks/test_task_unit.py
class TestTaskUnit (line 6) | class TestTaskUnit:
method unit (line 9) | def unit(self):
method test_set_pending (line 12) | def test_set_pending(self, unit):
method test_set_running (line 16) | def test_set_running(self, unit):
method test_set_done (line 20) | def test_set_done(self, unit):
method test_as_dict (line 24) | def test_as_dict(self, unit):
FILE: tests/test_tools/test_agentic_validation_loop.py
class TestInputSchema (line 9) | class TestInputSchema(BaseModel):
function mock_llm (line 14) | def mock_llm():
function mock_agent (line 21) | def mock_agent():
class TestAgenticValidationLoop (line 27) | class TestAgenticValidationLoop:
method test_init_with_string_prompt (line 29) | def test_init_with_string_prompt(self, mock_llm):
method test_init_with_prompt_template (line 46) | def test_init_with_prompt_template(self, mock_llm):
method test_init_with_custom_schema (line 59) | def test_init_with_custom_schema(self, mock_llm):
method test_run_method (line 73) | def test_run_method(self, mock_structured_tool, mock_agent_class, mock...
method test_post_processing (line 103) | def test_post_processing(self, mock_llm):
FILE: tests/test_tools/test_html_render_tool.py
function test_render_tool (line 16) | def test_render_tool(tmpdir, html_code):
FILE: tests/test_tools/test_image_utils.py
function image_path_girl (line 17) | def image_path_girl():
function image_path_chart (line 24) | def image_path_chart():
function test_human_message_from_image_bytes (line 30) | def test_human_message_from_image_bytes(image_path_girl):
function test_image_file_to_bytes_and_mime_type_jpg (line 41) | def test_image_file_to_bytes_and_mime_type_jpg():
function test_is_this_a_chart_with_chart_image (line 71) | def test_is_this_a_chart_with_chart_image(image_path_chart):
function test_is_this_a_chart_with_non_chart_image (line 90) | def test_is_this_a_chart_with_non_chart_image(image_path_girl):
function test_convert_image_to_png_supported_format_unchanged (line 109) | def test_convert_image_to_png_supported_format_unchanged(image_path_girl):
function test_convert_image_to_png_all_supported_types (line 122) | def test_convert_image_to_png_all_supported_types():
function test_libreoffice_formats_constant (line 134) | def test_libreoffice_formats_constant():
function test_supported_mime_types_constant (line 142) | def test_supported_mime_types_constant():
FILE: tests/test_tools/test_linter_tools.py
function pgsql_linter_tool (line 8) | def pgsql_linter_tool():
function python_linter_tool (line 14) | def python_linter_tool():
function test_python_tool (line 45) | def test_python_tool(python_linter_tool, code, file_name, valid_code, ra...
FILE: tests/test_tools/test_repl_tool.py
class TestREPLTool (line 4) | class TestREPLTool:
method test_repl_tool (line 5) | def test_repl_tool(self):
method test_repl_tool_multiple_calls (line 12) | def test_repl_tool_multiple_calls(self):
method test_repl_tool_multiline_input_with_import (line 17) | def test_repl_tool_multiline_input_with_import(self):
FILE: tests/test_tools/test_structured_passthrough_tool.py
class SampleSchema (line 8) | class SampleSchema(BaseModel):
class B (line 13) | class B(BaseModel):
class C (line 19) | class C(BaseModel):
class A (line 25) | class A(BaseModel):
function sample_schema (line 30) | def sample_schema():
function structured_passthrough_tool (line 35) | def structured_passthrough_tool(sample_schema):
function test_structured_passthrough_tool_initialization (line 39) | def test_structured_passthrough_tool_initialization(structured_passthrou...
function test_structured_passthrough_tool_run_valid_input (line 45) | def test_structured_passthrough_tool_run_valid_input(structured_passthro...
function test_structured_passthrough_tool_run_invalid_input (line 52) | def test_structured_passthrough_tool_run_invalid_input(structured_passth...
function test_structured_passthrough_tool_post_process (line 58) | def test_structured_passthrough_tool_post_process(structured_passthrough...
function test_structured_passthrough_tool_post_process_noop (line 73) | def test_structured_passthrough_tool_post_process_noop(structured_passth...
function test_structured_passthrough_tool_with_nested_model_instances (line 87) | def test_structured_passthrough_tool_with_nested_model_instances():
FILE: tests/test_tools/test_tool.py
function mock_tool_args_schema (line 14) | def mock_tool_args_schema():
function mock_tool_function (line 21) | def mock_tool_function(mock_input: str):
function mock_input (line 29) | def mock_input():
function langchain_tool (line 34) | def langchain_tool(mock_tool_args_schema):
function llama_index_tool (line 46) | def llama_index_tool(mock_tool_args_schema):
function crewai_tool (line 58) | def crewai_tool(mock_tool_args_schema):
function motley_agent (line 70) | def motley_agent(langchain_tool):
class TestMotleyTool (line 80) | class TestMotleyTool:
method test_tool_return_direct (line 81) | def test_tool_return_direct(self, langchain_tool, mock_input):
method test_tool_handle_exception (line 89) | def test_tool_handle_exception(self, langchain_tool, mock_input):
method test_langchain_tool_conversion (line 94) | def test_langchain_tool_conversion(self, langchain_tool, mock_input):
method test_llama_index_tool_conversion (line 108) | def test_llama_index_tool_conversion(self, llama_index_tool, mock_input):
method test_motley_tool_self_conversion (line 123) | def test_motley_tool_self_conversion(self, langchain_tool):
method test_motley_agent_conversion (line 129) | def test_motley_agent_conversion(self, motley_agent, mock_input):
method test_autogen_tool_conversion (line 136) | def test_autogen_tool_conversion(self, langchain_tool, mock_input):
method test_crewai_tool_conversion (line 145) | def test_crewai_tool_conversion(self, crewai_tool, mock_input):
FILE: tests/test_tools/test_tool_chain.py
class ToolMock (line 6) | class ToolMock:
method invoke (line 7) | def invoke(self, input: dict, *args, **kwargs):
method _run (line 10) | def _run(self, input: dict, *args, **kwargs):
function tools (line 15) | def tools():
function test_tool_chain (line 21) | def test_tool_chain(tools):
Copy disabled (too large)
Download .json
Condensed preview — 186 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (10,130K chars).
[
{
"path": ".coderabbit.yaml",
"chars": 232,
"preview": "# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json\n\nreviews:\n auto_review:\n enabled: "
},
{
"path": ".flake8",
"chars": 28,
"preview": "[flake8]\nmax-line-length=88\n"
},
{
"path": ".github/workflows/ci.yml",
"chars": 4454,
"preview": "name: CI\n\non:\n pull_request:\n push:\n branches:\n - main\n workflow_dispatch:\n schedule:\n - cron: \"0 2 * * *"
},
{
"path": ".github/workflows/publish.yml",
"chars": 1413,
"preview": "name: Publish\n\non:\n release:\n types: [published]\n\npermissions:\n contents: write\n\njobs:\n publish:\n runs-on: ubun"
},
{
"path": ".gitignore",
"chars": 1019,
"preview": "# Editors\n.vscode/\n.idea/\n\n# Vagrant\n.vagrant/\n\n# Mac/OSX\n.DS_Store\n\n# Windows\nThumbs.db\n\n# Byte-compiled / optimized / "
},
{
"path": ".mcp.json",
"chars": 979,
"preview": "{\n \"mcpServers\": {\n \"mcp-omnisearch\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\n \"-y\","
},
{
"path": ".readthedocs.yaml",
"chars": 1132,
"preview": "# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Requir"
},
{
"path": "CLAUDE.md",
"chars": 3136,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "LICENSE",
"chars": 1067,
"preview": "MIT License\n\nCopyright (c) 2024 motleycrew\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
},
{
"path": "Makefile",
"chars": 213,
"preview": ".PHONY: all\nall: clean cov build\n\n.PHONY: clean\nclean:\n\trm -rf dist/\n\n.PHONY: test\ntest:\n\tpoetry run pytest\n\n.PHONY: cov"
},
{
"path": "README.md",
"chars": 5459,
"preview": "# motleycrew\n\n[](https://pypi.org/project/motleycrew/)\n[![CI]"
},
{
"path": "docs/Makefile",
"chars": 638,
"preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the "
},
{
"path": "docs/make.bat",
"chars": 769,
"preview": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-bu"
},
{
"path": "docs/source/_static/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "docs/source/_templates/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "docs/source/_templates/autosummary/base.rst",
"chars": 108,
"preview": "{{ fullname | escape | underline }}\n\n.. currentmodule:: {{ module }}\n\n\n.. auto{{ objtype }}:: {{ objname }}\n"
},
{
"path": "docs/source/_templates/autosummary/module.rst",
"chars": 1011,
"preview": "{{ fullname | escape | underline}}\n\n\n.. automodule:: {{ fullname }}\n\n {% block attributes %}\n {% if attributes %}\n .. ru"
},
{
"path": "docs/source/agents.rst",
"chars": 3246,
"preview": "Agents\n======\n\nMotleyCrew is first and foremost a multi-agent framework, so the concept of an agent is central to it.\n\nA"
},
{
"path": "docs/source/api.rst",
"chars": 82,
"preview": "API\n===\n\n.. autosummary::\n :toctree: _autosummary\n :recursive:\n\n motleycrew\n"
},
{
"path": "docs/source/autogen.rst",
"chars": 554,
"preview": "AutoGen-related Examples\n========================\n\nHere are some examples that firstly, show how some AutoGen patterns t"
},
{
"path": "docs/source/caching_observability.nblink",
"chars": 64,
"preview": "{\n \"path\": \"../../examples/Caching and observability.ipynb\"\n}"
},
{
"path": "docs/source/choosing_llms.rst",
"chars": 4293,
"preview": "Choosing LLMs\n====================\n\nGenerally, the interaction with an LLM is up to the agent implementation.\nHowever, a"
},
{
"path": "docs/source/conf.py",
"chars": 1927,
"preview": "# Configuration file for the Sphinx documentation builder.\n#\n# For the full list of built-in configuration values, see t"
},
{
"path": "docs/source/examples/advanced_output_handling.nblink",
"chars": 67,
"preview": "{\n \"path\": \"../../../examples/Advanced output handling.ipynb\"\n}\n"
},
{
"path": "docs/source/examples/blog_with_images.nblink",
"chars": 123,
"preview": "{\n \"path\": \"../../../examples/Blog with Images.ipynb\",\n \"extra-media\": [\n \"../../../examples/images\"\n ]"
},
{
"path": "docs/source/examples/customer_support.rst",
"chars": 2903,
"preview": "Customer support chatbot with Ray Serve\n=======================================\n\nThis example demonstrates how to build "
},
{
"path": "docs/source/examples/event_driven.nblink",
"chars": 84,
"preview": "{\n \"path\": \"../../../examples/Event-driven orchestration for AI systems.ipynb\"\n}\n"
},
{
"path": "docs/source/examples/integrating_autogen.nblink",
"chars": 71,
"preview": "{\n \"path\": \"../../../examples/Using AutoGen with motleycrew.ipynb\"\n}"
},
{
"path": "docs/source/examples/math_single_agent.nblink",
"chars": 82,
"preview": "{\n \"path\": \"../../../examples/Math via python code with a single agent.ipynb\"\n}"
},
{
"path": "docs/source/examples/research_agent.nblink",
"chars": 68,
"preview": "{\n \"path\": \"../../../examples/Multi-step research agent.ipynb\"\n}\n"
},
{
"path": "docs/source/examples/streaming_agent_output.nblink",
"chars": 65,
"preview": "{\n \"path\": \"../../../examples/Streaming agent output.ipynb\"\n}\n"
},
{
"path": "docs/source/examples/validating_agent_output.nblink",
"chars": 66,
"preview": "{\n \"path\": \"../../../examples/Validating agent output.ipynb\"\n}\n"
},
{
"path": "docs/source/examples.rst",
"chars": 279,
"preview": "Examples\n========\n\n\n.. toctree::\n :maxdepth: 2\n\n examples/blog_with_images\n examples/research_agent\n examples/va"
},
{
"path": "docs/source/index.rst",
"chars": 504,
"preview": ".. motleycrew documentation master file, created by\n sphinx-quickstart on Fri May 3 12:05:08 2024.\n You can adapt t"
},
{
"path": "docs/source/install_pandoc.py",
"chars": 386,
"preview": "\"\"\"\nScript for installing Pandoc in GitHub Actions CI environments.\n\"\"\"\n\nimport os\nimport shutil\nfrom pypandoc.pandoc_do"
},
{
"path": "docs/source/installation.rst",
"chars": 857,
"preview": "Installation\n============\n\nInstallation using pip\n---------------------\n\n.. code-block:: console\n\n pip install motley"
},
{
"path": "docs/source/key_concepts.rst",
"chars": 6896,
"preview": "Key Concepts and API\n====================\n\nThis is an overview of motleycrew's key concepts.\n\nIf you want to see them in"
},
{
"path": "docs/source/key_value_store.rst",
"chars": 1224,
"preview": "Key-value store for manipulating objects\n========================================\n\nIn many real-world tasks, the agent n"
},
{
"path": "docs/source/knowledge_graph.nblink",
"chars": 75,
"preview": "{\n \"path\": \"../../examples/Interaction with the knowledge graph.ipynb\"\n}"
},
{
"path": "docs/source/quickstart.rst",
"chars": 6561,
"preview": "Quickstart\n==========\n\nThis is a brief introduction to motleycrew.\n\nFor a working example of agents, tools, crew, and Si"
},
{
"path": "docs/source/usage.rst",
"chars": 164,
"preview": "Usage\n=====\n\n\n.. toctree::\n :maxdepth: 2\n\n quickstart\n key_concepts\n agents\n knowledge_graph\n choosing_llms\n"
},
{
"path": "examples/Advanced output handling.ipynb",
"chars": 36414,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Advanced output handling\\n\",\n "
},
{
"path": "examples/Blog with Images.ipynb",
"chars": 23465,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"87b73640\",\n \"metadata\": {},\n \"source\": [\n \"# Blog with im"
},
{
"path": "examples/Caching and observability.ipynb",
"chars": 2760210,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"a17c962c-a1e7-44d4-8dd9-5a2d37f7c3af\",\n \"metadata\": {},\n \"so"
},
{
"path": "examples/Event-driven orchestration for AI systems.ipynb",
"chars": 163578,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Event-driven orchestration for AI"
},
{
"path": "examples/Interaction with the knowledge graph.ipynb",
"chars": 11632,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"332833da-f31a-4723-aa28-bc0788cd1f64\",\n \"metadata\": {},\n \"so"
},
{
"path": "examples/Math via python code with a single agent.ipynb",
"chars": 14830,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"957999a7-2c5d-439d-b536-2763a1b6e50e\",\n \"metadata\": {},\n \"so"
},
{
"path": "examples/Multi-step research agent.ipynb",
"chars": 668022,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"0af01a23-58b5-4679-9e9c-89c74708fdab\",\n \"metadata\": {},\n \"so"
},
{
"path": "examples/Streaming agent output.ipynb",
"chars": 9634,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Streaming agent output\\n\",\n \"\\"
},
{
"path": "examples/Using AutoGen with motleycrew.ipynb",
"chars": 86246,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"4a864294\",\n \"metadata\": {},\n \"source\": [\n \"# Using AutoGe"
},
{
"path": "examples/Validating agent output.ipynb",
"chars": 38220,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Using an output handler for valid"
},
{
"path": "examples/__init__.py",
"chars": 79,
"preview": "# Has to be here to make the examples package importable for integration tests\n"
},
{
"path": "examples/aider_code_generation.py",
"chars": 2393,
"preview": "import logging\nimport os\nimport sys\nfrom pathlib import Path\n\nfrom dotenv import load_dotenv\nfrom langchain_community.to"
},
{
"path": "examples/blog_post/blog_post.py",
"chars": 5118,
"preview": "from dotenv import load_dotenv\nfrom langchain.schema import HumanMessage, SystemMessage\nfrom langchain_core.prompts.chat"
},
{
"path": "examples/blog_post/blog_post_input.py",
"chars": 4670,
"preview": "text = \"\"\"\nWise Pizza: A library for automated figuring out most unusual segments\nWisePizza is a library to find and vis"
},
{
"path": "examples/data/groupchat/fetch_arxiv_gpt4.py",
"chars": 1416,
"preview": "# filename: fetch_arxiv_gpt4.py\nimport urllib.request\nimport urllib.parse\nimport xml.etree.ElementTree as ET\n\ndef search"
},
{
"path": "examples/data/groupchat/fetch_latest_gpt4_paper.py",
"chars": 1609,
"preview": "# filename: fetch_latest_gpt4_paper.py\nimport requests\nfrom datetime import datetime\n\ndef fetch_latest_paper():\n # De"
},
{
"path": "examples/data/research_agent_storage/default__vector_store.json",
"chars": 5041699,
"preview": "{\"embedding_dict\": {\"394c9dac-edf6-460f-8d10-7fe768e2faa0\": [-0.008804631419479847, -0.0076374271884560585, -0.009128479"
},
{
"path": "examples/data/research_agent_storage/docstore.json",
"chars": 567583,
"preview": "{\"docstore/metadata\": {\"b8d37ed3-5853-4e84-abaf-383854262f57\": {\"doc_hash\": \"913b1a97cb5f7674d4140365fe02d225fa2af178b3c"
},
{
"path": "examples/data/research_agent_storage/graph_store.json",
"chars": 18,
"preview": "{\"graph_dict\": {}}"
},
{
"path": "examples/data/research_agent_storage/image__vector_store.json",
"chars": 72,
"preview": "{\"embedding_dict\": {}, \"text_id_to_ref_doc_id\": {}, \"metadata_dict\": {}}"
},
{
"path": "examples/data/research_agent_storage/index_store.json",
"chars": 12343,
"preview": "{\"index_store/data\": {\"79d69843-9180-402d-8ea0-170c1d89ecaf\": {\"__type__\": \"vector_store\", \"__data__\": \"{\\\"index_id\\\": \\"
},
{
"path": "examples/hacking dependencies.ipynb",
"chars": 40011,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"id\": \"61c30b80\",\n \"metadata\": {},\n \"source\": [\n \"# Simple inher"
},
{
"path": "examples/image_from_gslides_example.py",
"chars": 5810,
"preview": "#!/usr/bin/env python3\n\"\"\"\nExample usage of the new ImageElement.get_image_data() and enhanced image_utils functionality"
},
{
"path": "examples/key_value_store.py",
"chars": 2256,
"preview": "from typing import List\n\nfrom motleycrew import MotleyCrew\nfrom motleycrew.agents.langchain import ReActToolCallingMotle"
},
{
"path": "examples/llama_index_output_handler.py",
"chars": 1940,
"preview": "from dotenv import load_dotenv\nfrom langchain_community.tools import DuckDuckGoSearchRun\n\nfrom motleycrew import MotleyC"
},
{
"path": "examples/recognize_charts.py",
"chars": 427,
"preview": "from motleycrew.common import LLMFramework\nfrom motleycrew.common.llms import init_llm\nfrom motleycrew.utils.image_utils"
},
{
"path": "examples/submit_image.py",
"chars": 1402,
"preview": "from langchain_core.messages import HumanMessage\nfrom langchain_core.runnables import RunnableLambda\n\nfrom motleycrew.co"
},
{
"path": "examples/tool_calling_with_memory.py",
"chars": 1610,
"preview": "from dotenv import load_dotenv\nfrom langchain_community.tools import DuckDuckGoSearchRun\n\nfrom motleycrew import MotleyC"
},
{
"path": "motleycrew/__init__.py",
"chars": 229,
"preview": "\"\"\"MotleyCrew root package.\"\"\"\nfrom importlib import metadata\n\nfrom .crew import MotleyCrew\nfrom .tasks import Task\n\ntry"
},
{
"path": "motleycrew/agents/__init__.py",
"chars": 319,
"preview": "\"\"\"Everything agent-related: wrappers, pre-made agents, output handlers etc.\"\"\"\n\nfrom .abstract_parent import MotleyAgen"
},
{
"path": "motleycrew/agents/abstract_parent.py",
"chars": 977,
"preview": "from abc import ABC, abstractmethod, abstractproperty\nfrom typing import Optional, Any, TYPE_CHECKING\n\nfrom langchain_co"
},
{
"path": "motleycrew/agents/langchain/__init__.py",
"chars": 262,
"preview": "from .langchain import LangchainMotleyAgent\n\nfrom .legacy_react import LegacyReActMotleyAgent\nfrom .tool_calling_react i"
},
{
"path": "motleycrew/agents/langchain/langchain.py",
"chars": 11295,
"preview": "from __future__ import annotations\n\nimport asyncio\nfrom typing import Any, List, Optional, Sequence, Union\n\nfrom langcha"
},
{
"path": "motleycrew/agents/langchain/legacy_react.py",
"chars": 4967,
"preview": "from __future__ import annotations\n\nfrom typing import Optional, Sequence\n\nfrom langchain.agents import AgentExecutor, c"
},
{
"path": "motleycrew/agents/langchain/tool_calling_react.py",
"chars": 9869,
"preview": "from __future__ import annotations\n\nimport warnings\nfrom typing import Callable, Sequence\n\nfrom langchain.agents import "
},
{
"path": "motleycrew/agents/langchain/tool_calling_react_prompts.py",
"chars": 9180,
"preview": "from __future__ import annotations\n\nimport json\n\nfrom langchain_core.messages import BaseMessage, AIMessage, ToolMessage"
},
{
"path": "motleycrew/agents/llama_index/__init__.py",
"chars": 183,
"preview": "from .llama_index import LlamaIndexMotleyAgent\nfrom .llama_index_react import ReActLlamaIndexMotleyAgent\n\n__all__ = [\n "
},
{
"path": "motleycrew/agents/llama_index/llama_index.py",
"chars": 7907,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport uuid\nfrom typing import Any, Optional, Sequence\n\ntry:\n from"
},
{
"path": "motleycrew/agents/llama_index/llama_index_react.py",
"chars": 3236,
"preview": "from __future__ import annotations\n\nfrom typing import Sequence\n\ntry:\n from llama_index.core.agent import ReActAgent\n"
},
{
"path": "motleycrew/agents/mixins.py",
"chars": 10881,
"preview": "import asyncio\nfrom typing import Any, Callable, Dict, List, Optional, Tuple, Union\n\nfrom langchain_core.agents import A"
},
{
"path": "motleycrew/agents/parent.py",
"chars": 11719,
"preview": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\nfrom typing import TYPE_CHECKING, Any, List, Opt"
},
{
"path": "motleycrew/applications/__init__.py",
"chars": 45,
"preview": "\"\"\"Miscellaneous motleycrew applications.\"\"\"\n"
},
{
"path": "motleycrew/applications/customer_support/README.md",
"chars": 780,
"preview": "# Customer support agent demo\n\nThis is a demo of a customer support app built using motleycrew and Ray.\n\nIt includes sam"
},
{
"path": "motleycrew/applications/customer_support/communication.py",
"chars": 2105,
"preview": "from abc import ABC, abstractmethod\nimport asyncio\n\n\nclass CommunicationInterface(ABC):\n @abstractmethod\n async de"
},
{
"path": "motleycrew/applications/customer_support/example_issues.csv",
"chars": 23986,
"preview": "issue_id,category,issue_description,solution\n1,\"Smartphone Issues > Power and Battery > Device won't turn on\",\"My phone "
},
{
"path": "motleycrew/applications/customer_support/issue_tree.py",
"chars": 8429,
"preview": "from typing import List, Optional\nfrom dotenv import load_dotenv\nimport shutil\nfrom pathlib import Path\n\nimport pandas\nf"
},
{
"path": "motleycrew/applications/customer_support/ray_serve_app.py",
"chars": 3292,
"preview": "from pathlib import Path\n\nimport ray\nfrom dotenv import load_dotenv\nfrom fastapi import FastAPI, WebSocket\nfrom fastapi."
},
{
"path": "motleycrew/applications/customer_support/requirements.txt",
"chars": 29,
"preview": "motleycrew\nray[serve]\npandas\n"
},
{
"path": "motleycrew/applications/customer_support/static/index.html",
"chars": 4111,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width"
},
{
"path": "motleycrew/applications/customer_support/support_agent.py",
"chars": 13751,
"preview": "import asyncio\nfrom dataclasses import dataclass, field\nfrom pathlib import Path\nfrom typing import List, Optional, Sequ"
},
{
"path": "motleycrew/applications/expenses/expenses.py",
"chars": 3899,
"preview": "from motleycrew.agents.langchain import ReActToolCallingMotleyAgent\nfrom schema_delta import delta_generator\nfrom sql_to"
},
{
"path": "motleycrew/applications/expenses/schema_delta.py",
"chars": 1497,
"preview": "from sql_tools import SQLExpression\n\n\nclass DeltaGeneratorInput(BaseModel):\n original_schema: SQLExpression\n lates"
},
{
"path": "motleycrew/applications/expenses/sql_tools.py",
"chars": 198,
"preview": "class SQLExpression(BaseModel):\n # TODO: when creating it, validate that it's a valid pgsql expression!\n # TODO: _"
},
{
"path": "motleycrew/applications/faust_workflow/__init__.py",
"chars": 167,
"preview": "from .faust_workflow import Event, FaustWorkflow, step\nfrom .visualize import draw_faust_workflow\n\n__all__ = [\"Event\", \""
},
{
"path": "motleycrew/applications/faust_workflow/faust_workflow.py",
"chars": 6206,
"preview": "import asyncio\nimport contextvars\nfrom typing import Callable, Dict, List, Tuple, Type, get_type_hints\n\nfrom faust impor"
},
{
"path": "motleycrew/applications/faust_workflow/visualize.py",
"chars": 2422,
"preview": "from typing import get_args, get_origin\n\nfrom pyvis.network import Network\n\nfrom .faust_workflow import Event, FaustWork"
},
{
"path": "motleycrew/applications/research_agent/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "motleycrew/applications/research_agent/answer_task.py",
"chars": 2174,
"preview": "from typing import List, Optional\n\nfrom langchain_core.runnables import Runnable\nfrom langchain_core.language_models imp"
},
{
"path": "motleycrew/applications/research_agent/question.py",
"chars": 857,
"preview": "from typing import Optional\n\nfrom motleycrew.storage.graph_node import MotleyGraphNode\n\nREPR_CONTEXT_LENGTH_LIMIT = 30\n\n"
},
{
"path": "motleycrew/applications/research_agent/question_answerer.py",
"chars": 4759,
"preview": "from typing import Optional\n\nfrom langchain.prompts import PromptTemplate\nfrom langchain_core.language_models import Bas"
},
{
"path": "motleycrew/applications/research_agent/question_generator.py",
"chars": 4568,
"preview": "from typing import Optional\n\nfrom langchain_core.language_models import BaseLanguageModel\nfrom langchain_core.prompts im"
},
{
"path": "motleycrew/applications/research_agent/question_prioritizer.py",
"chars": 4127,
"preview": "from typing import Optional\n\nfrom langchain.prompts import PromptTemplate\nfrom langchain_core.language_models import Bas"
},
{
"path": "motleycrew/applications/research_agent/question_task.py",
"chars": 3921,
"preview": "from typing import List, Optional\n\nfrom langchain_core.language_models import BaseLanguageModel\nfrom langchain_core.runn"
},
{
"path": "motleycrew/common/__init__.py",
"chars": 774,
"preview": "\"\"\"Common utilities, types, enums, exceptions, loggers etc.\"\"\"\n\nfrom .aux_prompts import AuxPrompts\nfrom .defaults impor"
},
{
"path": "motleycrew/common/aux_prompts.py",
"chars": 1988,
"preview": "from typing import List, TYPE_CHECKING\n\nif TYPE_CHECKING:\n from motleycrew.common import MotleyTool\n\n\nclass AuxPrompt"
},
{
"path": "motleycrew/common/defaults.py",
"chars": 849,
"preview": "from motleycrew.common.enums import GraphStoreType, LLMProvider\n\n\nclass Defaults:\n \"\"\"Default values for various sett"
},
{
"path": "motleycrew/common/enums.py",
"chars": 1337,
"preview": "\"\"\"Various enums used in the project.\"\"\"\n\n\nclass LLMProvider:\n OPENAI = \"openai\"\n ANTHROPIC = \"anthropic\"\n REPL"
},
{
"path": "motleycrew/common/exceptions.py",
"chars": 3829,
"preview": "\"\"\"Exceptions for motleycrew\"\"\"\n\nfrom typing import Any, Dict, Optional\n\nfrom motleycrew.common import Defaults\n\n\nclass "
},
{
"path": "motleycrew/common/llms.py",
"chars": 7362,
"preview": "\"\"\"Helper functions to initialize Language Models (LLMs) from different frameworks.\"\"\"\n\nfrom typing import Optional\n\nfro"
},
{
"path": "motleycrew/common/logging.py",
"chars": 845,
"preview": "\"\"\"Project logger configuration module\n\nAttributes:\n logger (logging.Logger): project logger\n\"\"\"\nimport logging\n\n\n# i"
},
{
"path": "motleycrew/common/types.py",
"chars": 1572,
"preview": "\"\"\"Various types and type protocols used in motleycrew.\n\nAttributes:\n MotleySupportedTool: Type that represents a too"
},
{
"path": "motleycrew/common/utils.py",
"chars": 1766,
"preview": "\"\"\"Various helpers and utility functions used throughout the project.\"\"\"\n\nimport hashlib\nimport sys\nfrom typing import O"
},
{
"path": "motleycrew/crew/__init__.py",
"chars": 121,
"preview": "\"\"\"MotleyCrew class, orchestration and related functionality.\"\"\"\n\nfrom .crew import MotleyCrew\n\n__all__ = [\"MotleyCrew\"]"
},
{
"path": "motleycrew/crew/crew.py",
"chars": 11221,
"preview": "import asyncio\nimport threading\nimport time\nfrom typing import Any, Collection, Generator, Optional\n\nfrom motleycrew.age"
},
{
"path": "motleycrew/crew/crew_threads.py",
"chars": 4345,
"preview": "\"\"\"Thread pool module for running agents.\"\"\"\n\nimport threading\nfrom enum import Enum\nfrom queue import Queue\nfrom typing"
},
{
"path": "motleycrew/storage/__init__.py",
"chars": 201,
"preview": "\"\"\"Graph storage.\"\"\"\n\nfrom .graph_node import MotleyGraphNode\nfrom .graph_node import MotleyGraphNodeType\n\nfrom .graph_s"
},
{
"path": "motleycrew/storage/graph_node.py",
"chars": 2905,
"preview": "from typing import Optional, Any, TypeVar, TYPE_CHECKING\nfrom abc import ABC\nfrom pydantic import BaseModel\n\nif TYPE_CHE"
},
{
"path": "motleycrew/storage/graph_store.py",
"chars": 4972,
"preview": "from abc import ABC, abstractmethod\nfrom typing import Optional, Type\nfrom motleycrew.storage import MotleyGraphNode, Mo"
},
{
"path": "motleycrew/storage/graph_store_utils.py",
"chars": 1114,
"preview": "from typing import Optional\nimport tempfile\nimport os\n\nfrom motleycrew.common import Defaults\nfrom motleycrew.common imp"
},
{
"path": "motleycrew/storage/kuzu_graph_store.py",
"chars": 22426,
"preview": "\"\"\"\nCode derived from: https://github.com/run-llama/llama_index/blob/802064aee72b03ab38ead0cda780cfa3e37ce728/llama-inde"
},
{
"path": "motleycrew/storage/kv_store_domain.py",
"chars": 904,
"preview": "from typing import Any, Optional\nfrom abc import ABC, abstractmethod\nfrom pprint import pprint\n\n\nclass RetrievableObject"
},
{
"path": "motleycrew/tasks/__init__.py",
"chars": 276,
"preview": "\"\"\"Tasks and task units.\"\"\"\n\nfrom motleycrew.tasks.simple import SimpleTask\nfrom motleycrew.tasks.task import Task\nfrom "
},
{
"path": "motleycrew/tasks/simple.py",
"chars": 7123,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, List, Optional, Sequence\n\nfrom langchain_core"
},
{
"path": "motleycrew/tasks/task.py",
"chars": 10057,
"preview": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\nfrom typing import Optional, Sequence, List, Typ"
},
{
"path": "motleycrew/tasks/task_unit.py",
"chars": 1931,
"preview": "from __future__ import annotations\n\nfrom abc import ABC\nfrom typing import Optional, Any, TypeVar\n\nfrom motleycrew.commo"
},
{
"path": "motleycrew/tools/__init__.py",
"chars": 329,
"preview": "\"\"\"MotleyTool class and tools library.\"\"\"\n\nfrom motleycrew.tools.tool import DirectOutput, MotleyTool, RetryConfig\nfrom "
},
{
"path": "motleycrew/tools/agentic_validation_loop.py",
"chars": 2403,
"preview": "from typing import Any, Callable, Optional, Type\n\nfrom langchain_core.prompts import PromptTemplate\nfrom langchain_core."
},
{
"path": "motleycrew/tools/autogen_chat_tool.py",
"chars": 3598,
"preview": "from typing import Any, Callable, List, Optional, Type\n\nfrom langchain_core.prompts import PromptTemplate\nfrom langchain"
},
{
"path": "motleycrew/tools/code/__init__.py",
"chars": 257,
"preview": "from .aider_tool import AiderTool\nfrom .postgresql_linter import PostgreSQLLinterTool\nfrom .python_linter import PythonL"
},
{
"path": "motleycrew/tools/code/aider_tool.py",
"chars": 1586,
"preview": "from typing import List, Type\n\nfrom motleycrew.common.utils import ensure_module_is_installed\n\ntry:\n from aider.coder"
},
{
"path": "motleycrew/tools/code/postgresql_linter.py",
"chars": 1428,
"preview": "from typing import List, Type\n\nfrom langchain_core.tools import Tool\nfrom pydantic import BaseModel, Field\n\ntry:\n fro"
},
{
"path": "motleycrew/tools/code/python_linter.py",
"chars": 1858,
"preview": "import os\nfrom typing import List, Type, Union\n\nfrom langchain_core.tools import StructuredTool\nfrom pydantic import Bas"
},
{
"path": "motleycrew/tools/code/python_repl.py",
"chars": 3501,
"preview": "import functools\nimport logging\nimport re\nimport sys\nfrom io import StringIO\nfrom typing import Dict, List, Type\n\nfrom p"
},
{
"path": "motleycrew/tools/html_render_tool.py",
"chars": 4626,
"preview": "from datetime import datetime\nfrom pathlib import Path\nfrom typing import List, Optional, Tuple, Type\n\nfrom motleycrew.c"
},
{
"path": "motleycrew/tools/image/__init__.py",
"chars": 182,
"preview": "from .dall_e import DallEImageGeneratorTool\nfrom .replicate_tool import ReplicateImageGeneratorTool\n\n\n__all__ = [\n \"D"
},
{
"path": "motleycrew/tools/image/dall_e.py",
"chars": 5450,
"preview": "from typing import List, Optional, Type\n\nfrom langchain.agents import Tool\nfrom langchain.prompts import PromptTemplate\n"
},
{
"path": "motleycrew/tools/image/download_image.py",
"chars": 1436,
"preview": "import mimetypes\nimport os\nfrom typing import Optional\n\nimport requests\n\nfrom motleycrew.common import logger, utils as "
},
{
"path": "motleycrew/tools/image/replicate_tool.py",
"chars": 4115,
"preview": "from typing import List, Optional, Type\n\nfrom langchain.agents import Tool\nfrom pydantic import BaseModel, Field\n\nimport"
},
{
"path": "motleycrew/tools/llm_tool.py",
"chars": 2987,
"preview": "from typing import List, Optional, Type\n\nfrom langchain_core.language_models import BaseLanguageModel\nfrom langchain_cor"
},
{
"path": "motleycrew/tools/mermaid_evaluator_tool.py",
"chars": 3521,
"preview": "# https://nodejs.org/en/download\n# npm install -g @mermaid-js/mermaid-cli\nimport io\nimport os.path\nimport subprocess\nimp"
},
{
"path": "motleycrew/tools/simple_retriever_tool.py",
"chars": 3346,
"preview": "import os\nfrom typing import List, Optional, Type\n\nfrom langchain_core.tools import StructuredTool\nfrom llama_index.core"
},
{
"path": "motleycrew/tools/sql_validation.py",
"chars": 26966,
"preview": "\"\"\"\nSQL Validation Module\n\nThis module provides utilities for validating SQL queries against schema definitions\nwithout "
},
{
"path": "motleycrew/tools/structured_passthrough.py",
"chars": 1603,
"preview": "from typing import Callable, Optional, Type, TypeVar\n\nfrom pydantic import BaseModel\n\nfrom motleycrew.tools.tool import "
},
{
"path": "motleycrew/tools/tool.py",
"chars": 19457,
"preview": "import functools\nimport inspect\nfrom dataclasses import dataclass\nfrom time import sleep\nfrom typing import Any, Callabl"
},
{
"path": "motleycrew/tracking/__init__.py",
"chars": 133,
"preview": "\"\"\"Observability-related functionality.\"\"\"\n\nfrom .utils import add_default_callbacks_to_langchain_config, get_default_ca"
},
{
"path": "motleycrew/tracking/callbacks.py",
"chars": 12788,
"preview": "\"\"\"\nThe module contains callback handlers for sending data to the Lunary service\n\"\"\"\n\nimport traceback\nfrom typing impor"
},
{
"path": "motleycrew/tracking/utils.py",
"chars": 4826,
"preview": "\"\"\"The module contains additional utilities for working with callback handlers for tracking\"\"\"\n\nimport os\nfrom typing im"
},
{
"path": "motleycrew/utils/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "motleycrew/utils/chart_to_df.py",
"chars": 5381,
"preview": "from typing import List\n\nimport pandas as pd\nfrom langchain_core.language_models import BaseLanguageModel\nfrom pydantic "
},
{
"path": "motleycrew/utils/image_utils.py",
"chars": 7652,
"preview": "\"\"\"Utilities for working with images in LLM contexts.\"\"\"\n\nimport base64\nimport logging\nimport mimetypes\nimport os\nimport"
},
{
"path": "motleycrew/utils/structured_output_with_retries.py",
"chars": 2024,
"preview": "from typing import Callable, List, Optional, Type, TypeVar\n\nfrom langchain_core.language_models import BaseLanguageModel"
},
{
"path": "pyproject.toml",
"chars": 838,
"preview": "[tool.poetry]\nname = \"motleycrew\"\nversion = \"0.3.7\"\ndescription = \"A lightweight agent interaction framework.\"\nauthors ="
},
{
"path": "pytest.ini",
"chars": 91,
"preview": "[pytest]\nmarkers =\n fat: marks fat tests\naddopts = -m \"not fat\" --import-mode=importlib\n"
},
{
"path": "requirements-extra.txt",
"chars": 69,
"preview": "motleycache\nlunary\nllama-index\nduckduckgo-search\npglast\nray[default]\n"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/conftest.py",
"chars": 64,
"preview": "import os\n\nos.environ[\"OPENAI_API_KEY\"] = \"YOUR OPENAI API KEY\"\n"
},
{
"path": "tests/run_integration_tests.py",
"chars": 7136,
"preview": "import argparse\nimport os\nimport sys\nimport traceback\nfrom copy import copy\nfrom functools import partial\nfrom pathlib i"
},
{
"path": "tests/test_agents/__init__.py",
"chars": 506,
"preview": "from typing import Type\n\nfrom langchain_core.tools import BaseTool\nfrom pydantic import BaseModel, Field\n\n\nclass MockToo"
},
{
"path": "tests/test_agents/conftest.py",
"chars": 64,
"preview": "import os\n\nos.environ[\"OPENAI_API_KEY\"] = \"YOUR OPENAI API KEY\"\n"
},
{
"path": "tests/test_agents/test_agent_chain.py",
"chars": 858,
"preview": "import pytest\n\nfrom motleycrew.agents.parent import MotleyAgentParent\n\n\nclass AgentMock:\n def invoke(self, input_dict"
},
{
"path": "tests/test_agents/test_agents.py",
"chars": 6096,
"preview": "import os\n\nimport pytest\nfrom langchain_core.messages import HumanMessage\nfrom langchain_core.prompts.chat import ChatPr"
},
{
"path": "tests/test_agents/test_kv_store_tools.py",
"chars": 2239,
"preview": "from typing import List\n\nimport pytest\n\nfrom motleycrew.agents.langchain import ReActToolCallingMotleyAgent\nfrom motleyc"
},
{
"path": "tests/test_agents/test_langchain_output_handler.py",
"chars": 5861,
"preview": "import pytest\nfrom langchain_core.agents import AgentAction, AgentFinish\n\nfrom motleycrew.agents.langchain.tool_calling_"
},
{
"path": "tests/test_agents/test_llms.py",
"chars": 1711,
"preview": "import pytest\nfrom langchain_openai import ChatOpenAI\nfrom llama_index.llms.openai import OpenAI\nfrom unittest.mock impo"
},
{
"path": "tests/test_crew/__init__.py",
"chars": 1196,
"preview": "import pytest\n\nfrom motleycrew.crew import MotleyCrew\nfrom motleycrew.tasks import SimpleTask\n\n\nclass AgentMock:\n def"
},
{
"path": "tests/test_crew/test_crew.py",
"chars": 1767,
"preview": "import pytest\n\nfrom motleycrew.tasks.simple import SimpleTask, SimpleTaskUnit\nfrom tests.test_crew import CrewFixtures\n\n"
},
{
"path": "tests/test_crew/test_crew_threads.py",
"chars": 2529,
"preview": "import pytest\n\nfrom motleycrew.crew.crew_threads import TaskUnitThreadPool, TaskUnitThreadState\nfrom motleycrew.common i"
},
{
"path": "tests/test_image_utils.py",
"chars": 3481,
"preview": "import base64\nimport tempfile\nimport pytest\nfrom unittest.mock import MagicMock\n\nfrom motleycrew.utils.image_utils impor"
},
{
"path": "tests/test_storage/__init__.py",
"chars": 515,
"preview": "import kuzu\nimport pytest\n\nfrom motleycrew.storage import MotleyKuzuGraphStore\n\n\nclass GraphStoreFixtures:\n @pytest.f"
},
{
"path": "tests/test_storage/test_graph_store.py",
"chars": 8018,
"preview": "from typing import Optional\n\nimport pytest\n\nfrom motleycrew.common import GraphStoreType\nfrom motleycrew.storage import "
},
{
"path": "tests/test_storage/test_kuzu_graph_store.py",
"chars": 1089,
"preview": "from typing import Optional\n\nimport kuzu\nimport pytest\n\nfrom motleycrew.common import GraphStoreType\nfrom motleycrew.sto"
},
{
"path": "tests/test_tasks/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_tasks/test_simple_task.py",
"chars": 2780,
"preview": "import pytest\n\nfrom motleycrew.agents.langchain.tool_calling_react import ReActToolCallingMotleyAgent\n\ntry:\n from lan"
},
{
"path": "tests/test_tasks/test_task.py",
"chars": 3978,
"preview": "from typing import List, Optional\n\nimport pytest\n\nfrom langchain_core.runnables import Runnable\n\nfrom motleycrew import "
},
{
"path": "tests/test_tasks/test_task_unit.py",
"chars": 509,
"preview": "import pytest\n\nfrom motleycrew.tasks import TaskUnit\n\n\nclass TestTaskUnit:\n\n @pytest.fixture(scope=\"class\")\n def u"
},
{
"path": "tests/test_tools/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_tools/test_agentic_validation_loop.py",
"chars": 4708,
"preview": "import pytest\nfrom unittest.mock import MagicMock, patch\nfrom pydantic import BaseModel, Field\nfrom langchain_core.promp"
},
{
"path": "tests/test_tools/test_html_render_tool.py",
"chars": 745,
"preview": "import os\n\nimport pytest\n\nfrom motleycrew.tools.html_render_tool import HTMLRenderTool\n\n\n@pytest.mark.fat\n@pytest.mark.p"
},
{
"path": "tests/test_tools/test_image_utils.py",
"chars": 5580,
"preview": "import os\nimport pytest\nfrom langchain_core.messages import HumanMessage\nfrom unittest.mock import MagicMock\n\nfrom motle"
},
{
"path": "tests/test_tools/test_linter_tools.py",
"chars": 1774,
"preview": "import pytest\n\nfrom motleycrew.tools.code import PostgreSQLLinterTool, PythonLinterTool\nfrom motleycrew.common.exception"
},
{
"path": "tests/test_tools/test_repl_tool.py",
"chars": 823,
"preview": "from motleycrew.tools.code import PythonREPLTool\n\n\nclass TestREPLTool:\n def test_repl_tool(self):\n repl_tool ="
},
{
"path": "tests/test_tools/test_sql_validation.py",
"chars": 9920,
"preview": "# \"\"\"\n# Tests for the SQL validation module.\n# \"\"\"\n#\n# import pytest\n#\n# from motleycrew.tools.sql_validation import (\n#"
},
{
"path": "tests/test_tools/test_structured_passthrough_tool.py",
"chars": 3415,
"preview": "import pytest\nfrom typing import List\nfrom pydantic import BaseModel, ValidationError\n\nfrom motleycrew.tools.structured_"
},
{
"path": "tests/test_tools/test_tool.py",
"chars": 5491,
"preview": "import pytest\nfrom langchain.tools import BaseTool\nfrom pydantic import BaseModel, Field\n\ntry:\n from crewai_tools imp"
},
{
"path": "tests/test_tools/test_tool_chain.py",
"chars": 568,
"preview": "import pytest\n\nfrom motleycrew.tools import MotleyTool\n\n\nclass ToolMock:\n def invoke(self, input: dict, *args, **kwar"
}
]
About this extraction
This page contains the full source code of the ShoggothAI/motleycrew GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 186 files (9.5 MB), approximately 2.5M tokens, and a symbol index with 690 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.