marvin 3.2.2__tar.gz → 3.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {marvin-3.2.2 → marvin-3.2.4}/.gitignore +1 -0
- {marvin-3.2.2 → marvin-3.2.4}/PKG-INFO +4 -4
- {marvin-3.2.2 → marvin-3.2.4}/README.md +2 -2
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-database.mdx +6 -5
- {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/agents.mdx +0 -15
- {marvin-3.2.2 → marvin-3.2.4}/docs/installation.mdx +0 -2
- {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/memory.mdx +5 -5
- marvin-3.2.4/examples/provider_specific/aimlapi/run_agent.py +49 -0
- marvin-3.2.4/examples/provider_specific/aimlapi/structured_output.py +54 -0
- marvin-3.2.4/examples/provider_specific/aimlapi/tools_agent.py +58 -0
- marvin-3.2.4/examples/slackbot/Dockerfile.slackbot +25 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/README.md +2 -6
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/pyproject.toml +1 -0
- marvin-3.2.4/examples/slackbot/src/slackbot/_internal/constants.py +1 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/api.py +1 -6
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/models.py +1 -1
- marvin-3.2.4/examples/slackbot/src/slackbot/research_agent.py +127 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/timeseries.py +19 -7
- {marvin-3.2.2 → marvin-3.2.4}/pyproject.toml +1 -1
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/database.py +21 -6
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/streaming.py +8 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_database.py +41 -0
- marvin-3.2.4/uv.lock +7263 -0
- marvin-3.2.2/.cursor/rules/docs.mdc +0 -14
- marvin-3.2.2/.cursor/rules/general.mdc +0 -12
- marvin-3.2.2/.cursor/rules/python.mdc +0 -35
- marvin-3.2.2/examples/slackbot/Dockerfile.slackbot +0 -18
- marvin-3.2.2/examples/slackbot/src/slackbot/_internal/constants.py +0 -4
- marvin-3.2.2/examples/slackbot/src/slackbot/research_agent.py +0 -151
- marvin-3.2.2/uv.lock +0 -6310
- {marvin-3.2.2 → marvin-3.2.4}/.github/labeler.yml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/release.yml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/deploy-cloudrun-revision-community.yaml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/deploy-cloudrun-revision-internal.yaml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/image-build-and-push-community.yaml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/image-build-and-push-internal.yaml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/labeler.yml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/publish.yml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/run-tests.yml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/static-analysis.yml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/.pre-commit-config.yaml +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/CLAUDE.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/LICENSE +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/alembic.ini +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/README.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents-actor.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents-agent.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents-names.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents-team.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-beta.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli-config.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli-dev.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli-main.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli-migrations.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-defaults.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-end_turn.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-events.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-llm.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-orchestrator.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-streaming.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-cast.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-classify.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-extract.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-fn.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-generate.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-plan.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-run.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-say.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-summarize.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-handlers-handlers.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-handlers-print_handler.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-handlers-queue_handler.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-handlers.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-instructions.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-memory.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers-chroma.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers-lance.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers-postgres.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers-qdrant.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-prompts.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-settings.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-tasks-task.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-tasks.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-thread.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-tools-thread.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-tools.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-asyncio.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-jinja.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-jsonschema.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-logging.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-models.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-testing.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-tools.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-types.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/assets/brand/marvin_mascot.jpeg +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/assets/code.css +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/ai_model.png +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/dont_panic.png +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/it_hates_me.png +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/life_the_universe_and_ai.png +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/pip_install_marvin_hero.png +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/sort_of_thing_you_enjoy.png +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/community.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/memory.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/tasks.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/teams.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/threads.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/tools-and-context.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/docs.json +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/cast.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/classify.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/extract.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/fn.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/generate.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/plan.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/run.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/say.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/functions/summarize.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/guides/building-a-chatbot.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/guides/cli.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/guides/configuration.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/guides/configure-llms.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/guides/database-migrations.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/guides/mcp.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/guides/migration-guide.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/guides/multi-step-workflow.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/instructions.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/interactivity.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/running-tasks.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/task-results.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/tools.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/quickstart.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/snippets/version-badge.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/style.css +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/docs/welcome.mdx +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/agent_mcp.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/custom_handler.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/custom_prompt.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/deepseek_chat.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/english_to_schema.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/fastmcp/hello.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_agent.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_cast.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_classify.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_extract.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_fn.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_generate.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_marvin.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_run.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_task.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/hello_team.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/pandas/data.csv +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/pandas/dataframe.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/provider_specific/azure/run_agent.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/provider_specific/openai/run_agent.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/.dockerignore +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/CLAUDE.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/__main__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/monkey_patch.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/templates.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/thread_status.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/tool_tracking.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/assets.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/core.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/client.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/discussions.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/exceptions.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/issues.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/prompts.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/py.typed +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/search.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/settings.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/slack.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/strings.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/types.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/wrap.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/talk_to_marvin.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/task_retries.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/you_have_been_goosed/README.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/examples/you_have_been_goosed/goose_em.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/justfile +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/migrations/README.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/migrations/env.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/migrations/script.py.mako +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/migrations/versions/20250301_141903_initial_schema.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/migrations/versions/20250302_123716_improve_message_tracking.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/migrations/versions/20250302_135101_add_llmcall_message_tracking.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/scripts/check_for_breakpoints.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/scripts/create_mintlify_api_ref.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/_internal/deprecation.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/_internal/integrations/README.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/_internal/integrations/fastmcp.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/_internal/integrations/mcp.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/CLAUDE.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/actor.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/agent.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/names.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/team.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/beta/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/config.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/dev.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/main.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/migrations.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/defaults.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/CLAUDE.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/end_turn.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/events.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/llm.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/orchestrator.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/cast.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/classify.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/extract.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/fn.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/generate.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/plan.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/run.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/say.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/summarize.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/handlers/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/handlers/handlers.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/handlers/print_handler.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/handlers/queue_handler.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/instructions.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/instructions.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/memory.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/chroma.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/lance.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/postgres.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/qdrant.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/prompts.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/py.typed +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/settings.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tasks/CLAUDE.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tasks/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tasks/task.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/agent.jinja +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/memory.jinja +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/system.jinja +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/task.jinja +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/team.jinja +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/thread.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tools/CLAUDE.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tools/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tools/interactive/cli.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tools/thread.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/asyncio.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/jinja.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/jsonschema.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/logging.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/models.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/testing.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/tools.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/types.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/agents/test_mcp_integration.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/README.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_cast.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_classify.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_customization.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_extract.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_fn.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_generate.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_run.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_say.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_summarize.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/memory/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/memory/test_memory.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/tasks/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/tasks/test_tasks.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/ai/test_mcp.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/README.md +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/actors/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/actors/test_agent.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/actors/test_team.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/conftest.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_events.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_orchestrator.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_streaming.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_threads.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/memory/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/memory/test_memory.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/tasks/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/tasks/test_tasks.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/test_instructions.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/test_run.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/utilities/__init__.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/utilities/test_jsonschema.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/utilities/test_tools.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/basic/utilities/test_types.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/conftest.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/data/youre-funny.wav +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/settings/test_settings_object.py +0 -0
- {marvin-3.2.2 → marvin-3.2.4}/tests/test_concurrent_execution.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: marvin
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.4
|
|
4
4
|
Summary: a simple and powerful tool to get things done with AI
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -209,7 +209,7 @@ Requires-Dist: aiosqlite>=0.20.0
|
|
|
209
209
|
Requires-Dist: alembic>=1.12.0
|
|
210
210
|
Requires-Dist: jinja2>=3.1.4
|
|
211
211
|
Requires-Dist: partial-json-parser>=0.2.1.1.post5
|
|
212
|
-
Requires-Dist: pydantic-ai>=
|
|
212
|
+
Requires-Dist: pydantic-ai>=1.9.0
|
|
213
213
|
Requires-Dist: pydantic-settings>=2.6.1
|
|
214
214
|
Requires-Dist: pydantic[email]>=2.10.6
|
|
215
215
|
Requires-Dist: rich>=13.9.4
|
|
@@ -238,10 +238,10 @@ Marvin provides an intuitive API for defining workflows and delegating work to L
|
|
|
238
238
|
|
|
239
239
|
## Installation
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
Marvin is available on [PyPI](https://pypi.org/project/marvin/):
|
|
242
242
|
|
|
243
243
|
```bash
|
|
244
|
-
uv
|
|
244
|
+
uv add marvin
|
|
245
245
|
```
|
|
246
246
|
|
|
247
247
|
Configure your LLM provider (Marvin uses OpenAI by default but natively supports [all Pydantic AI models](https://ai.pydantic.dev/models/)):
|
|
@@ -13,10 +13,10 @@ Marvin provides an intuitive API for defining workflows and delegating work to L
|
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Marvin is available on [PyPI](https://pypi.org/project/marvin/):
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
uv
|
|
19
|
+
uv add marvin
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Configure your LLM provider (Marvin uses OpenAI by default but natively supports [all Pydantic AI models](https://ai.pydantic.dev/models/)):
|
|
@@ -120,15 +120,16 @@ Custom type for Usage objects that stores them as JSON in the database.
|
|
|
120
120
|
|
|
121
121
|
### `create_db_and_tables`
|
|
122
122
|
```python
|
|
123
|
-
def create_db_and_tables(force: bool = False)
|
|
123
|
+
def create_db_and_tables(force: bool = False, dispose_engine: bool = False)
|
|
124
124
|
```
|
|
125
|
-
Create all database tables
|
|
126
|
-
|
|
127
|
-
This is a synchronous alternative to create_db_and_tables() that can be used
|
|
128
|
-
in contexts where asyncio.run() cannot be called.
|
|
125
|
+
Create all database tables.
|
|
129
126
|
|
|
130
127
|
Args:
|
|
131
128
|
force: If True, drops all existing tables before creating new ones.
|
|
129
|
+
dispose_engine: If True, dispose the engine after creating tables.
|
|
130
|
+
This is useful when called from asyncio.run() to ensure the
|
|
131
|
+
aiosqlite worker thread is cleaned up and doesn't prevent
|
|
132
|
+
Python from exiting.
|
|
132
133
|
|
|
133
134
|
### `ensure_db_tables_exist`
|
|
134
135
|
```python
|
|
@@ -109,21 +109,6 @@ agent = marvin.Agent(
|
|
|
109
109
|
)
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
### Delegates
|
|
113
|
-
|
|
114
|
-
Agents can be configured with a list of other agents that they can delegate work to. This is particularly useful when working with teams of agents:
|
|
115
|
-
|
|
116
|
-
```python
|
|
117
|
-
import marvin
|
|
118
|
-
|
|
119
|
-
researcher = marvin.Agent(name="Researcher")
|
|
120
|
-
writer = marvin.Agent(name="Writer")
|
|
121
|
-
editor = marvin.Agent(
|
|
122
|
-
name="Editor",
|
|
123
|
-
delegates=[researcher, writer]
|
|
124
|
-
)
|
|
125
|
-
```
|
|
126
|
-
|
|
127
112
|
## Assigning agents to tasks
|
|
128
113
|
|
|
129
114
|
Agents must be assigned to tasks in order to work on them. You can assign agents by passing them to the `agents` parameter when creating a task:
|
|
@@ -18,8 +18,6 @@ The easiest way to install Marvin is a package manager like `pip` or [`uv`](http
|
|
|
18
18
|
```bash uv
|
|
19
19
|
# add marvin to your project
|
|
20
20
|
uv add marvin
|
|
21
|
-
# add marvin to your virtual environment
|
|
22
|
-
uv pip install marvin
|
|
23
21
|
# add marvin to your script
|
|
24
22
|
uv add --script some_script.py marvin
|
|
25
23
|
# run a python process with marvin installed ephemerally
|
|
@@ -140,7 +140,7 @@ pip install "marvin[chroma]"
|
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
```bash uv
|
|
143
|
-
uv
|
|
143
|
+
uv add "marvin[chroma]"
|
|
144
144
|
```
|
|
145
145
|
</CodeGroup>
|
|
146
146
|
|
|
@@ -170,7 +170,7 @@ pip install "marvin[lance]"
|
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
```bash uv
|
|
173
|
-
uv
|
|
173
|
+
uv add "marvin[lance]"
|
|
174
174
|
```
|
|
175
175
|
|
|
176
176
|
</CodeGroup>
|
|
@@ -198,7 +198,7 @@ pip install "marvin[postgres]"
|
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
```bash uv
|
|
201
|
-
uv
|
|
201
|
+
uv add "marvin[postgres]"
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
</CodeGroup>
|
|
@@ -229,7 +229,7 @@ pip install "marvin" "qdrant-client[fastembed]"
|
|
|
229
229
|
```
|
|
230
230
|
|
|
231
231
|
```bash uv
|
|
232
|
-
uv
|
|
232
|
+
uv add marvin "qdrant-client[fastembed]"
|
|
233
233
|
```
|
|
234
234
|
|
|
235
235
|
</CodeGroup>
|
|
@@ -259,6 +259,6 @@ pip install "marvin[memory]" # Includes chroma, lance, postgres, qdrant
|
|
|
259
259
|
```
|
|
260
260
|
|
|
261
261
|
```bash uv
|
|
262
|
-
uv
|
|
262
|
+
uv add "marvin[memory]" # Includes chroma, lance, postgres and qdrant
|
|
263
263
|
```
|
|
264
264
|
</CodeGroup>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
» AIML_API_KEY=your-api-key \
|
|
3
|
+
uv run examples/provider_specific/aimlapi/run_agent.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from pydantic_ai.models.openai import OpenAIModel
|
|
12
|
+
from pydantic_ai.providers.openai import OpenAIProvider
|
|
13
|
+
|
|
14
|
+
import marvin
|
|
15
|
+
|
|
16
|
+
AIML_API_URL = "https://api.aimlapi.com/v1"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_provider() -> OpenAIProvider:
|
|
20
|
+
api_key = os.getenv("AIML_API_KEY")
|
|
21
|
+
if not api_key:
|
|
22
|
+
raise RuntimeError(
|
|
23
|
+
"Set AIML_API_KEY environment variable to your AI/ML API key."
|
|
24
|
+
)
|
|
25
|
+
return OpenAIProvider(api_key=api_key, base_url=AIML_API_URL)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def write_file(path: str, content: str) -> None:
|
|
29
|
+
"""Write content to a file."""
|
|
30
|
+
Path(path).write_text(content)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main() -> None:
|
|
34
|
+
writer = marvin.Agent(
|
|
35
|
+
model=OpenAIModel("gpt-4o", provider=get_provider()),
|
|
36
|
+
name="AI/ML Writer",
|
|
37
|
+
instructions="Write concise, engaging content for developers",
|
|
38
|
+
tools=[write_file],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
result = marvin.run(
|
|
42
|
+
"how to use pydantic? write haiku to docs.md",
|
|
43
|
+
agents=[writer],
|
|
44
|
+
)
|
|
45
|
+
print(result)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
» AIML_API_KEY=your-api-key \
|
|
3
|
+
uv run examples/provider_specific/aimlapi/structured_output.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from pydantic_ai.models.openai import OpenAIModel
|
|
11
|
+
from pydantic_ai.providers.openai import OpenAIProvider
|
|
12
|
+
from typing_extensions import TypedDict
|
|
13
|
+
|
|
14
|
+
import marvin
|
|
15
|
+
|
|
16
|
+
AIML_API_URL = "https://api.aimlapi.com/v1"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LearningResource(TypedDict):
|
|
20
|
+
title: str
|
|
21
|
+
url: str
|
|
22
|
+
summary: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_provider() -> OpenAIProvider:
|
|
26
|
+
api_key = os.getenv("AIML_API_KEY")
|
|
27
|
+
if not api_key:
|
|
28
|
+
raise RuntimeError(
|
|
29
|
+
"Set AIML_API_KEY environment variable to your AI/ML API key."
|
|
30
|
+
)
|
|
31
|
+
return OpenAIProvider(api_key=api_key, base_url=AIML_API_URL)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main() -> None:
|
|
35
|
+
researcher = marvin.Agent(
|
|
36
|
+
model=OpenAIModel("gpt-4o", provider=get_provider()),
|
|
37
|
+
name="Resource Researcher",
|
|
38
|
+
instructions=(
|
|
39
|
+
"Return structured JSON describing useful developer resources for the AI/ML API."
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
resources = marvin.run(
|
|
44
|
+
"share three quickstart resources for building with the AI/ML API",
|
|
45
|
+
result_type=list[LearningResource],
|
|
46
|
+
agents=[researcher],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
for resource in resources:
|
|
50
|
+
print(f"- {resource['title']}\n {resource['url']}\n {resource['summary']}\n")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
» AIML_API_KEY=your-api-key \
|
|
3
|
+
uv run examples/provider_specific/aimlapi/tools_agent.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from datetime import date, timedelta
|
|
10
|
+
|
|
11
|
+
from pydantic_ai.models.openai import OpenAIModel
|
|
12
|
+
from pydantic_ai.providers.openai import OpenAIProvider
|
|
13
|
+
|
|
14
|
+
import marvin
|
|
15
|
+
|
|
16
|
+
AIML_API_URL = "https://api.aimlapi.com/v1"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_provider() -> OpenAIProvider:
|
|
20
|
+
api_key = os.getenv("AIML_API_KEY")
|
|
21
|
+
if not api_key:
|
|
22
|
+
raise RuntimeError(
|
|
23
|
+
"Set AIML_API_KEY environment variable to your AI/ML API key."
|
|
24
|
+
)
|
|
25
|
+
return OpenAIProvider(api_key=api_key, base_url=AIML_API_URL)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_event_date(offset_days: int = 0) -> str:
|
|
29
|
+
"""Return an ISO formatted date offset from today."""
|
|
30
|
+
today = date.today()
|
|
31
|
+
return (today + timedelta(days=offset_days)).isoformat()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def mock_weather_lookup(city: str) -> str:
|
|
35
|
+
"""Pretend to look up weather for a city."""
|
|
36
|
+
return f"The forecast in {city} calls for mild temperatures and light winds."
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main() -> None:
|
|
40
|
+
planner = marvin.Agent(
|
|
41
|
+
model=OpenAIModel("gpt-4o", provider=get_provider()),
|
|
42
|
+
name="AI/ML Event Planner",
|
|
43
|
+
instructions=(
|
|
44
|
+
"Plan concise community events for AI/ML API enthusiasts."
|
|
45
|
+
" Use the available tools for dates and weather when helpful."
|
|
46
|
+
),
|
|
47
|
+
tools=[get_event_date, mock_weather_lookup],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
plan = marvin.run(
|
|
51
|
+
"Design a Saturday workshop introducing the AI/ML API in Berlin",
|
|
52
|
+
agents=[planner],
|
|
53
|
+
)
|
|
54
|
+
print(plan)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
FROM python:3.13-slim
|
|
2
|
+
|
|
3
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
|
4
|
+
COPY --from=oven/bun:latest /usr/local/bin/bun /usr/local/bin/bun
|
|
5
|
+
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
COPY . /app
|
|
9
|
+
|
|
10
|
+
RUN apt-get update && \
|
|
11
|
+
apt-get install -y git build-essential curl && \
|
|
12
|
+
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
|
13
|
+
apt-get install -y nodejs && \
|
|
14
|
+
apt-get clean && \
|
|
15
|
+
rm -rf /var/lib/apt/lists/*
|
|
16
|
+
|
|
17
|
+
RUN uv sync --extra slackbot --no-dev
|
|
18
|
+
|
|
19
|
+
RUN bun install -g @anthropic-ai/claude-code
|
|
20
|
+
|
|
21
|
+
ENV PATH="/root/.bun/bin:${PATH}"
|
|
22
|
+
|
|
23
|
+
EXPOSE 4200
|
|
24
|
+
|
|
25
|
+
CMD ["uv", "run", "--no-sync", "-m", "slackbot"]
|
|
@@ -18,12 +18,8 @@ The slackbot can be run locally with minimal setup.
|
|
|
18
18
|
### Local Development
|
|
19
19
|
|
|
20
20
|
```console
|
|
21
|
-
#
|
|
22
|
-
uv
|
|
23
|
-
source .venv/bin/activate
|
|
24
|
-
|
|
25
|
-
# Install dependencies
|
|
26
|
-
uv pip install -e ".[slackbot]" -U
|
|
21
|
+
# Install dependencies (from the repo root)
|
|
22
|
+
uv sync --extra slackbot
|
|
27
23
|
```
|
|
28
24
|
|
|
29
25
|
### Configuration
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
WORKSPACE_TO_CHANNEL_ID: dict[str, str] = {}
|
|
@@ -279,12 +279,7 @@ async def handle_message(payload: SlackPayload, db: Database):
|
|
|
279
279
|
channel_id=event.channel,
|
|
280
280
|
thread_ts=thread_ts,
|
|
281
281
|
)
|
|
282
|
-
|
|
283
|
-
return Completed(
|
|
284
|
-
message="Error during agent execution",
|
|
285
|
-
name="ERROR_HANDLED",
|
|
286
|
-
data=dict(error=str(e), user_context=user_context),
|
|
287
|
-
)
|
|
282
|
+
raise
|
|
288
283
|
finally:
|
|
289
284
|
try:
|
|
290
285
|
await mark_thread_completed(db, message_ts)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Research agent for improved information gathering.
|
|
2
|
+
|
|
3
|
+
Now uses Claude Agent SDK for direct code inspection instead of relying on
|
|
4
|
+
documentation search alone. This prevents hallucination by allowing the agent
|
|
5
|
+
to actually read Prefect source code.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
|
11
|
+
from claude_agent_sdk.types import AssistantMessage, TextBlock
|
|
12
|
+
from prefect import task
|
|
13
|
+
from prefect.cache_policies import INPUTS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def research_topic_with_code_access(question: str, version: str = "3.x") -> str:
|
|
17
|
+
"""
|
|
18
|
+
Research a Prefect topic using Claude Agent SDK with direct source code access.
|
|
19
|
+
|
|
20
|
+
The agent will clone the Prefect repo to .research_cache/prefect if it doesn't exist,
|
|
21
|
+
or update it if it does. This works consistently in both local and Docker environments.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
question: The research question
|
|
25
|
+
version: Prefect version ("2.x" or "3.x")
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Research findings as a formatted string
|
|
29
|
+
"""
|
|
30
|
+
# Use current working directory for cache
|
|
31
|
+
# Locally: <wherever you run from>/.research_cache/prefect
|
|
32
|
+
# Docker: /app/.research_cache/prefect (since WORKDIR is /app)
|
|
33
|
+
cache_dir = Path.cwd() / ".research_cache"
|
|
34
|
+
prefect_repo = cache_dir / "prefect"
|
|
35
|
+
|
|
36
|
+
version_context = "Prefect 3.x" if version.startswith("3") else "Prefect 2.x"
|
|
37
|
+
branch = "main" if version.startswith("3") else "2.x"
|
|
38
|
+
|
|
39
|
+
system_prompt = f"""You are a specialized research agent for {version_context}.
|
|
40
|
+
Your job is to thoroughly research topics by reading the actual Prefect source code, documentation, and community discussions.
|
|
41
|
+
|
|
42
|
+
IMPORTANT: Before researching, ensure you have the Prefect source code:
|
|
43
|
+
1. If {prefect_repo} doesn't exist: clone it with `git clone https://github.com/PrefectHQ/prefect.git {prefect_repo}`
|
|
44
|
+
2. If it already exists: update it with `cd {prefect_repo} && git checkout {branch} && git pull`
|
|
45
|
+
3. Then search/read both:
|
|
46
|
+
- Source code in {prefect_repo}/src/prefect/
|
|
47
|
+
- Documentation in {prefect_repo}/docs/
|
|
48
|
+
|
|
49
|
+
DOCUMENTATION:
|
|
50
|
+
- Read docs from {prefect_repo}/docs/v3/ (or {prefect_repo}/docs/ on 2.x branch)
|
|
51
|
+
- Use {prefect_repo}/docs/docs.json to map file paths to published URLs at https://docs.prefect.io/
|
|
52
|
+
- Verify all doc links with `curl -I <url>` before providing them - never hallucinate URLs
|
|
53
|
+
|
|
54
|
+
If cloning fails (e.g., disk space), fall back to searching the installed package in your current environment.
|
|
55
|
+
|
|
56
|
+
ADDITIONAL RESEARCH TOOLS:
|
|
57
|
+
- Use `gh` CLI to search GitHub discussions and issues for community-verified solutions
|
|
58
|
+
- Example: `gh search issues --repo PrefectHQ/prefect "custom state event"`
|
|
59
|
+
- This helps find real-world patterns and edge cases that users have discovered
|
|
60
|
+
|
|
61
|
+
Use your tools to search and read the actual implementation - do not make assumptions.
|
|
62
|
+
Be thorough - verify everything by reading the source.
|
|
63
|
+
|
|
64
|
+
CRITICAL VERSION-SPECIFIC RULES:
|
|
65
|
+
- **DEFAULT TO {version_context}**: Do NOT suggest deprecated patterns
|
|
66
|
+
- **NEVER** suggest `Deployment.build_from_flow()` for Prefect 3.x - it's COMPLETELY REMOVED
|
|
67
|
+
- **NEVER** suggest `prefect deployment build` CLI command for 3.x - use `prefect deploy` instead
|
|
68
|
+
- The correct deployment pattern in 3.x is: `flow.from_source(...).deploy(...)`
|
|
69
|
+
- Default to Prefect 3.x patterns unless user explicitly states they're using 2.x
|
|
70
|
+
- If user is on 2.x, suggest upgrading to 3.x or using workers instead of deprecated patterns
|
|
71
|
+
|
|
72
|
+
Remember: You are the research specialist. The main agent relies on you for accurate, comprehensive information.
|
|
73
|
+
Be thorough - use tools repeatedly until you have complete information.
|
|
74
|
+
Do not use any Prefect syntax you have not verified by reading the actual source code."""
|
|
75
|
+
|
|
76
|
+
options = ClaudeAgentOptions(
|
|
77
|
+
allowed_tools=["Read", "Grep", "Glob", "Bash"],
|
|
78
|
+
cwd=str(Path.cwd()),
|
|
79
|
+
model="claude-haiku-4-5-20251001",
|
|
80
|
+
system_prompt=system_prompt,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
research_output = []
|
|
84
|
+
|
|
85
|
+
async with ClaudeSDKClient(options=options) as client:
|
|
86
|
+
await client.query(question)
|
|
87
|
+
|
|
88
|
+
async for message in client.receive_response():
|
|
89
|
+
if isinstance(message, AssistantMessage):
|
|
90
|
+
for block in message.content:
|
|
91
|
+
if isinstance(block, TextBlock):
|
|
92
|
+
research_output.append(block.text)
|
|
93
|
+
|
|
94
|
+
return "\n".join(research_output)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def research_prefect_topic(question: str, topic: str, version: str = "3.x") -> str:
|
|
98
|
+
"""
|
|
99
|
+
Thoroughly research a Prefect topic using Claude Agent SDK with source code access.
|
|
100
|
+
|
|
101
|
+
This tool uses Claude Agent SDK to give the research agent direct access to:
|
|
102
|
+
- Actual Prefect source code via Read tool
|
|
103
|
+
- Code search via Grep/Bash (rg)
|
|
104
|
+
- File discovery via Glob
|
|
105
|
+
- Shell commands for exploration
|
|
106
|
+
|
|
107
|
+
This eliminates hallucination by allowing the agent to verify everything
|
|
108
|
+
against the actual implementation.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
question: The specific question or topic to research
|
|
112
|
+
topic: A short display name for the topic based on the question (for bookkeeping)
|
|
113
|
+
version: Prefect version ("2.x" or "3.x")
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
result = (
|
|
117
|
+
task(task_run_name=f"Researching {topic}", cache_policy=INPUTS)(
|
|
118
|
+
research_topic_with_code_access
|
|
119
|
+
)
|
|
120
|
+
.submit(question, version)
|
|
121
|
+
.result()
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return f"**Research Findings (Code-Verified)**\n\n{result}"
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
return f"Research failed: {str(e)}. The agent may not have access to the source code."
|
|
@@ -2,9 +2,9 @@ import json
|
|
|
2
2
|
import re
|
|
3
3
|
from typing import Any, TypedDict
|
|
4
4
|
|
|
5
|
-
from atproto import Client
|
|
5
|
+
from atproto import Client, models
|
|
6
6
|
from atproto.exceptions import BadRequestError
|
|
7
|
-
from
|
|
7
|
+
from pydantic import field_validator
|
|
8
8
|
from pydantic_ai import ImageUrl
|
|
9
9
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
10
10
|
|
|
@@ -21,6 +21,15 @@ class Settings(BaseSettings):
|
|
|
21
21
|
|
|
22
22
|
bsky_handle: str
|
|
23
23
|
bsky_password: str
|
|
24
|
+
atproto_pds_url: str = "https://bsky.social"
|
|
25
|
+
|
|
26
|
+
@field_validator("atproto_pds_url")
|
|
27
|
+
@classmethod
|
|
28
|
+
def ensure_url_protocol(cls, v: str) -> str:
|
|
29
|
+
"""Ensure the PDS URL has a protocol"""
|
|
30
|
+
if not v.startswith(("http://", "https://")):
|
|
31
|
+
return f"https://{v}"
|
|
32
|
+
return v
|
|
24
33
|
|
|
25
34
|
|
|
26
35
|
class Snapshot(TypedDict):
|
|
@@ -48,7 +57,7 @@ def extract_post_id(bluesky_url: str) -> tuple[str, str]:
|
|
|
48
57
|
return match.group(1), match.group(2)
|
|
49
58
|
|
|
50
59
|
|
|
51
|
-
def build_context(thread: ThreadViewPost) -> dict[str, Any]:
|
|
60
|
+
def build_context(thread: models.AppBskyFeedDefs.ThreadViewPost) -> dict[str, Any]:
|
|
52
61
|
context: dict[str, Any] = {}
|
|
53
62
|
if thread and thread.post:
|
|
54
63
|
context["bsky post"] = {
|
|
@@ -65,7 +74,7 @@ def build_context(thread: ThreadViewPost) -> dict[str, Any]:
|
|
|
65
74
|
image_description_result = visual_extraction_agent.run(
|
|
66
75
|
[
|
|
67
76
|
"summarize this image concisely, include direct quotes from the image",
|
|
68
|
-
ImageUrl(url=img.fullsize),
|
|
77
|
+
ImageUrl(url=img.fullsize, media_type="image/jpeg"),
|
|
69
78
|
]
|
|
70
79
|
)
|
|
71
80
|
context["bsky post"]["embed"] = image_description_result
|
|
@@ -80,7 +89,10 @@ def build_context(thread: ThreadViewPost) -> dict[str, Any]:
|
|
|
80
89
|
"embed": visual_extraction_agent.run(
|
|
81
90
|
"summarize this image concisely, include direct quotes from the image",
|
|
82
91
|
attachments=[
|
|
83
|
-
ImageUrl(
|
|
92
|
+
ImageUrl(
|
|
93
|
+
url=reply_post.embed.images[0].fullsize,
|
|
94
|
+
media_type="image/jpeg",
|
|
95
|
+
),
|
|
84
96
|
],
|
|
85
97
|
)
|
|
86
98
|
}
|
|
@@ -99,7 +111,7 @@ def build_context(thread: ThreadViewPost) -> dict[str, Any]:
|
|
|
99
111
|
def explain_bsky_post(
|
|
100
112
|
bsky_post_url: str, details: dict[str, Any] | None = None
|
|
101
113
|
) -> None:
|
|
102
|
-
client = Client()
|
|
114
|
+
client = Client(base_url=settings.atproto_pds_url)
|
|
103
115
|
client.login(settings.bsky_handle, settings.bsky_password)
|
|
104
116
|
|
|
105
117
|
try:
|
|
@@ -111,7 +123,7 @@ def explain_bsky_post(
|
|
|
111
123
|
logger.error(f"Error fetching thread: {e}")
|
|
112
124
|
return
|
|
113
125
|
|
|
114
|
-
assert isinstance(thread, ThreadViewPost)
|
|
126
|
+
assert isinstance(thread, models.AppBskyFeedDefs.ThreadViewPost)
|
|
115
127
|
context = build_context(thread)
|
|
116
128
|
|
|
117
129
|
if details:
|
|
@@ -416,14 +416,17 @@ def _run_migrations(alembic_log_level: str = "WARNING") -> bool:
|
|
|
416
416
|
return False
|
|
417
417
|
|
|
418
418
|
|
|
419
|
-
async def create_db_and_tables(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
in contexts where asyncio.run() cannot be called.
|
|
419
|
+
async def create_db_and_tables(
|
|
420
|
+
*, force: bool = False, dispose_engine: bool = False
|
|
421
|
+
) -> None:
|
|
422
|
+
"""Create all database tables.
|
|
424
423
|
|
|
425
424
|
Args:
|
|
426
425
|
force: If True, drops all existing tables before creating new ones.
|
|
426
|
+
dispose_engine: If True, dispose the engine after creating tables.
|
|
427
|
+
This is useful when called from asyncio.run() to ensure the
|
|
428
|
+
aiosqlite worker thread is cleaned up and doesn't prevent
|
|
429
|
+
Python from exiting.
|
|
427
430
|
"""
|
|
428
431
|
engine = get_async_engine()
|
|
429
432
|
|
|
@@ -435,6 +438,15 @@ async def create_db_and_tables(*, force: bool = False) -> None:
|
|
|
435
438
|
await conn.run_sync(Base.metadata.create_all)
|
|
436
439
|
logger.debug("Database tables created.")
|
|
437
440
|
|
|
441
|
+
if dispose_engine:
|
|
442
|
+
await engine.dispose()
|
|
443
|
+
# Remove the engine from the cache since it's disposed
|
|
444
|
+
try:
|
|
445
|
+
loop = asyncio.get_running_loop()
|
|
446
|
+
except RuntimeError:
|
|
447
|
+
loop = None
|
|
448
|
+
_async_engine_cache.pop(loop, None)
|
|
449
|
+
|
|
438
450
|
|
|
439
451
|
def init_database_if_necessary():
|
|
440
452
|
"""Initialize the database file if necessary.
|
|
@@ -543,7 +555,10 @@ def ensure_db_tables_exist():
|
|
|
543
555
|
# Create tables synchronously using asyncio.run() if we're not in an async context
|
|
544
556
|
try:
|
|
545
557
|
with _create_tables_lock:
|
|
546
|
-
|
|
558
|
+
# Use dispose_engine=True to clean up the aiosqlite worker thread
|
|
559
|
+
# after table creation. Without this, the cached engine's worker
|
|
560
|
+
# thread could prevent Python from exiting cleanly.
|
|
561
|
+
asyncio.run(create_db_and_tables(dispose_engine=True))
|
|
547
562
|
logger.debug("Database tables created successfully.")
|
|
548
563
|
except Exception as e:
|
|
549
564
|
logger.error(f"Failed to create database tables: {e}")
|
|
@@ -10,6 +10,7 @@ from pydantic_ai.messages import (
|
|
|
10
10
|
FunctionToolResultEvent,
|
|
11
11
|
ModelResponsePart,
|
|
12
12
|
PartDeltaEvent,
|
|
13
|
+
PartEndEvent,
|
|
13
14
|
PartStartEvent,
|
|
14
15
|
RetryPromptPart,
|
|
15
16
|
TextPartDelta,
|
|
@@ -236,6 +237,13 @@ def _process_pydantic_event(
|
|
|
236
237
|
tool_call_id=snapshot.tool_call_id if snapshot else None,
|
|
237
238
|
tool=tools_map.get(event.part.tool_name),
|
|
238
239
|
)
|
|
240
|
+
# Handle Part End Events
|
|
241
|
+
elif isinstance(event, PartEndEvent):
|
|
242
|
+
# Part end events signal that a part is complete. We don't need to
|
|
243
|
+
# emit a Marvin event for this since we handle completion via other
|
|
244
|
+
# events (FunctionToolCallEvent, FinalResultEvent, etc.)
|
|
245
|
+
return None
|
|
246
|
+
|
|
239
247
|
# Handle Part Delta Events
|
|
240
248
|
elif isinstance(event, PartDeltaEvent):
|
|
241
249
|
# Process a delta update to an existing part
|