agentpool 2.1.9__py3-none-any.whl
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.
Potentially problematic release.
This version of agentpool might be problematic. Click here for more details.
- acp/README.md +64 -0
- acp/__init__.py +172 -0
- acp/__main__.py +10 -0
- acp/acp_requests.py +285 -0
- acp/agent/__init__.py +6 -0
- acp/agent/connection.py +256 -0
- acp/agent/implementations/__init__.py +6 -0
- acp/agent/implementations/debug_server/__init__.py +1 -0
- acp/agent/implementations/debug_server/cli.py +79 -0
- acp/agent/implementations/debug_server/debug.html +234 -0
- acp/agent/implementations/debug_server/debug_server.py +496 -0
- acp/agent/implementations/testing.py +91 -0
- acp/agent/protocol.py +65 -0
- acp/bridge/README.md +162 -0
- acp/bridge/__init__.py +6 -0
- acp/bridge/__main__.py +91 -0
- acp/bridge/bridge.py +246 -0
- acp/bridge/py.typed +0 -0
- acp/bridge/settings.py +15 -0
- acp/client/__init__.py +7 -0
- acp/client/connection.py +251 -0
- acp/client/implementations/__init__.py +7 -0
- acp/client/implementations/default_client.py +185 -0
- acp/client/implementations/headless_client.py +266 -0
- acp/client/implementations/noop_client.py +110 -0
- acp/client/protocol.py +61 -0
- acp/connection.py +280 -0
- acp/exceptions.py +46 -0
- acp/filesystem.py +524 -0
- acp/notifications.py +832 -0
- acp/py.typed +0 -0
- acp/schema/__init__.py +265 -0
- acp/schema/agent_plan.py +30 -0
- acp/schema/agent_requests.py +126 -0
- acp/schema/agent_responses.py +256 -0
- acp/schema/base.py +39 -0
- acp/schema/capabilities.py +230 -0
- acp/schema/client_requests.py +247 -0
- acp/schema/client_responses.py +96 -0
- acp/schema/common.py +81 -0
- acp/schema/content_blocks.py +188 -0
- acp/schema/mcp.py +82 -0
- acp/schema/messages.py +171 -0
- acp/schema/notifications.py +82 -0
- acp/schema/protocol_stuff.md +3 -0
- acp/schema/session_state.py +160 -0
- acp/schema/session_updates.py +419 -0
- acp/schema/slash_commands.py +51 -0
- acp/schema/terminal.py +15 -0
- acp/schema/tool_call.py +347 -0
- acp/stdio.py +250 -0
- acp/task/__init__.py +53 -0
- acp/task/debug.py +197 -0
- acp/task/dispatcher.py +93 -0
- acp/task/queue.py +69 -0
- acp/task/sender.py +82 -0
- acp/task/state.py +87 -0
- acp/task/supervisor.py +93 -0
- acp/terminal_handle.py +30 -0
- acp/tool_call_reporter.py +199 -0
- acp/tool_call_state.py +178 -0
- acp/transports.py +104 -0
- acp/utils.py +240 -0
- agentpool/__init__.py +63 -0
- agentpool/__main__.py +7 -0
- agentpool/agents/__init__.py +30 -0
- agentpool/agents/acp_agent/__init__.py +5 -0
- agentpool/agents/acp_agent/acp_agent.py +837 -0
- agentpool/agents/acp_agent/acp_converters.py +294 -0
- agentpool/agents/acp_agent/client_handler.py +317 -0
- agentpool/agents/acp_agent/session_state.py +44 -0
- agentpool/agents/agent.py +1264 -0
- agentpool/agents/agui_agent/__init__.py +19 -0
- agentpool/agents/agui_agent/agui_agent.py +677 -0
- agentpool/agents/agui_agent/agui_converters.py +423 -0
- agentpool/agents/agui_agent/chunk_transformer.py +204 -0
- agentpool/agents/agui_agent/event_types.py +83 -0
- agentpool/agents/agui_agent/helpers.py +192 -0
- agentpool/agents/architect.py +71 -0
- agentpool/agents/base_agent.py +177 -0
- agentpool/agents/claude_code_agent/__init__.py +11 -0
- agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
- agentpool/agents/claude_code_agent/converters.py +243 -0
- agentpool/agents/context.py +105 -0
- agentpool/agents/events/__init__.py +61 -0
- agentpool/agents/events/builtin_handlers.py +129 -0
- agentpool/agents/events/event_emitter.py +320 -0
- agentpool/agents/events/events.py +561 -0
- agentpool/agents/events/tts_handlers.py +186 -0
- agentpool/agents/interactions.py +419 -0
- agentpool/agents/slashed_agent.py +244 -0
- agentpool/agents/sys_prompts.py +178 -0
- agentpool/agents/tool_wrapping.py +184 -0
- agentpool/base_provider.py +28 -0
- agentpool/common_types.py +226 -0
- agentpool/config_resources/__init__.py +16 -0
- agentpool/config_resources/acp_assistant.yml +24 -0
- agentpool/config_resources/agents.yml +109 -0
- agentpool/config_resources/agents_template.yml +18 -0
- agentpool/config_resources/agui_test.yml +18 -0
- agentpool/config_resources/claude_code_agent.yml +16 -0
- agentpool/config_resources/claude_style_subagent.md +30 -0
- agentpool/config_resources/external_acp_agents.yml +77 -0
- agentpool/config_resources/opencode_style_subagent.md +19 -0
- agentpool/config_resources/tts_test_agents.yml +78 -0
- agentpool/delegation/__init__.py +8 -0
- agentpool/delegation/base_team.py +504 -0
- agentpool/delegation/message_flow_tracker.py +39 -0
- agentpool/delegation/pool.py +1129 -0
- agentpool/delegation/team.py +325 -0
- agentpool/delegation/teamrun.py +343 -0
- agentpool/docs/__init__.py +5 -0
- agentpool/docs/gen_examples.py +42 -0
- agentpool/docs/utils.py +370 -0
- agentpool/functional/__init__.py +20 -0
- agentpool/functional/py.typed +0 -0
- agentpool/functional/run.py +80 -0
- agentpool/functional/structure.py +136 -0
- agentpool/hooks/__init__.py +20 -0
- agentpool/hooks/agent_hooks.py +247 -0
- agentpool/hooks/base.py +119 -0
- agentpool/hooks/callable.py +140 -0
- agentpool/hooks/command.py +180 -0
- agentpool/hooks/prompt.py +122 -0
- agentpool/jinja_filters.py +132 -0
- agentpool/log.py +224 -0
- agentpool/mcp_server/__init__.py +17 -0
- agentpool/mcp_server/client.py +429 -0
- agentpool/mcp_server/constants.py +32 -0
- agentpool/mcp_server/conversions.py +172 -0
- agentpool/mcp_server/helpers.py +47 -0
- agentpool/mcp_server/manager.py +232 -0
- agentpool/mcp_server/message_handler.py +164 -0
- agentpool/mcp_server/registries/__init__.py +1 -0
- agentpool/mcp_server/registries/official_registry_client.py +345 -0
- agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
- agentpool/mcp_server/tool_bridge.py +548 -0
- agentpool/messaging/__init__.py +58 -0
- agentpool/messaging/compaction.py +928 -0
- agentpool/messaging/connection_manager.py +319 -0
- agentpool/messaging/context.py +66 -0
- agentpool/messaging/event_manager.py +426 -0
- agentpool/messaging/events.py +39 -0
- agentpool/messaging/message_container.py +209 -0
- agentpool/messaging/message_history.py +491 -0
- agentpool/messaging/messagenode.py +377 -0
- agentpool/messaging/messages.py +655 -0
- agentpool/messaging/processing.py +76 -0
- agentpool/mime_utils.py +95 -0
- agentpool/models/__init__.py +21 -0
- agentpool/models/acp_agents/__init__.py +22 -0
- agentpool/models/acp_agents/base.py +308 -0
- agentpool/models/acp_agents/mcp_capable.py +790 -0
- agentpool/models/acp_agents/non_mcp.py +842 -0
- agentpool/models/agents.py +450 -0
- agentpool/models/agui_agents.py +89 -0
- agentpool/models/claude_code_agents.py +238 -0
- agentpool/models/file_agents.py +116 -0
- agentpool/models/file_parsing.py +367 -0
- agentpool/models/manifest.py +658 -0
- agentpool/observability/__init__.py +9 -0
- agentpool/observability/observability_registry.py +97 -0
- agentpool/prompts/__init__.py +1 -0
- agentpool/prompts/base.py +27 -0
- agentpool/prompts/builtin_provider.py +75 -0
- agentpool/prompts/conversion_manager.py +95 -0
- agentpool/prompts/convert.py +96 -0
- agentpool/prompts/manager.py +204 -0
- agentpool/prompts/parts/zed.md +33 -0
- agentpool/prompts/prompts.py +581 -0
- agentpool/py.typed +0 -0
- agentpool/queries/tree-sitter-language-pack/README.md +7 -0
- agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
- agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/README.md +24 -0
- agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
- agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
- agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
- agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
- agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
- agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
- agentpool/repomap.py +1231 -0
- agentpool/resource_providers/__init__.py +17 -0
- agentpool/resource_providers/aggregating.py +54 -0
- agentpool/resource_providers/base.py +172 -0
- agentpool/resource_providers/codemode/__init__.py +9 -0
- agentpool/resource_providers/codemode/code_executor.py +215 -0
- agentpool/resource_providers/codemode/default_prompt.py +19 -0
- agentpool/resource_providers/codemode/helpers.py +83 -0
- agentpool/resource_providers/codemode/progress_executor.py +212 -0
- agentpool/resource_providers/codemode/provider.py +150 -0
- agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
- agentpool/resource_providers/codemode/remote_provider.py +171 -0
- agentpool/resource_providers/filtering.py +42 -0
- agentpool/resource_providers/mcp_provider.py +246 -0
- agentpool/resource_providers/plan_provider.py +196 -0
- agentpool/resource_providers/pool.py +69 -0
- agentpool/resource_providers/static.py +289 -0
- agentpool/running/__init__.py +20 -0
- agentpool/running/decorators.py +56 -0
- agentpool/running/discovery.py +101 -0
- agentpool/running/executor.py +284 -0
- agentpool/running/injection.py +111 -0
- agentpool/running/py.typed +0 -0
- agentpool/running/run_nodes.py +87 -0
- agentpool/server.py +122 -0
- agentpool/sessions/__init__.py +13 -0
- agentpool/sessions/manager.py +302 -0
- agentpool/sessions/models.py +71 -0
- agentpool/sessions/session.py +239 -0
- agentpool/sessions/store.py +163 -0
- agentpool/skills/__init__.py +5 -0
- agentpool/skills/manager.py +120 -0
- agentpool/skills/registry.py +210 -0
- agentpool/skills/skill.py +36 -0
- agentpool/storage/__init__.py +17 -0
- agentpool/storage/manager.py +419 -0
- agentpool/storage/serialization.py +136 -0
- agentpool/talk/__init__.py +13 -0
- agentpool/talk/registry.py +128 -0
- agentpool/talk/stats.py +159 -0
- agentpool/talk/talk.py +604 -0
- agentpool/tasks/__init__.py +20 -0
- agentpool/tasks/exceptions.py +25 -0
- agentpool/tasks/registry.py +33 -0
- agentpool/testing.py +129 -0
- agentpool/text_templates/__init__.py +39 -0
- agentpool/text_templates/system_prompt.jinja +30 -0
- agentpool/text_templates/tool_call_default.jinja +13 -0
- agentpool/text_templates/tool_call_markdown.jinja +25 -0
- agentpool/text_templates/tool_call_simple.jinja +5 -0
- agentpool/tools/__init__.py +16 -0
- agentpool/tools/base.py +269 -0
- agentpool/tools/exceptions.py +9 -0
- agentpool/tools/manager.py +255 -0
- agentpool/tools/tool_call_info.py +87 -0
- agentpool/ui/__init__.py +2 -0
- agentpool/ui/base.py +89 -0
- agentpool/ui/mock_provider.py +81 -0
- agentpool/ui/stdlib_provider.py +150 -0
- agentpool/utils/__init__.py +44 -0
- agentpool/utils/baseregistry.py +185 -0
- agentpool/utils/count_tokens.py +62 -0
- agentpool/utils/dag.py +184 -0
- agentpool/utils/importing.py +206 -0
- agentpool/utils/inspection.py +334 -0
- agentpool/utils/model_capabilities.py +25 -0
- agentpool/utils/network.py +28 -0
- agentpool/utils/now.py +22 -0
- agentpool/utils/parse_time.py +87 -0
- agentpool/utils/result_utils.py +35 -0
- agentpool/utils/signatures.py +305 -0
- agentpool/utils/streams.py +112 -0
- agentpool/utils/tasks.py +186 -0
- agentpool/vfs_registry.py +250 -0
- agentpool-2.1.9.dist-info/METADATA +336 -0
- agentpool-2.1.9.dist-info/RECORD +474 -0
- agentpool-2.1.9.dist-info/WHEEL +4 -0
- agentpool-2.1.9.dist-info/entry_points.txt +14 -0
- agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
- agentpool_cli/__init__.py +34 -0
- agentpool_cli/__main__.py +66 -0
- agentpool_cli/agent.py +175 -0
- agentpool_cli/cli_types.py +23 -0
- agentpool_cli/common.py +163 -0
- agentpool_cli/create.py +175 -0
- agentpool_cli/history.py +217 -0
- agentpool_cli/log.py +78 -0
- agentpool_cli/py.typed +0 -0
- agentpool_cli/run.py +84 -0
- agentpool_cli/serve_acp.py +177 -0
- agentpool_cli/serve_api.py +69 -0
- agentpool_cli/serve_mcp.py +74 -0
- agentpool_cli/serve_vercel.py +233 -0
- agentpool_cli/store.py +171 -0
- agentpool_cli/task.py +84 -0
- agentpool_cli/utils.py +104 -0
- agentpool_cli/watch.py +54 -0
- agentpool_commands/__init__.py +180 -0
- agentpool_commands/agents.py +199 -0
- agentpool_commands/base.py +45 -0
- agentpool_commands/commands.py +58 -0
- agentpool_commands/completers.py +110 -0
- agentpool_commands/connections.py +175 -0
- agentpool_commands/markdown_utils.py +31 -0
- agentpool_commands/models.py +62 -0
- agentpool_commands/prompts.py +78 -0
- agentpool_commands/py.typed +0 -0
- agentpool_commands/read.py +77 -0
- agentpool_commands/resources.py +210 -0
- agentpool_commands/session.py +48 -0
- agentpool_commands/tools.py +269 -0
- agentpool_commands/utils.py +189 -0
- agentpool_commands/workers.py +163 -0
- agentpool_config/__init__.py +53 -0
- agentpool_config/builtin_tools.py +265 -0
- agentpool_config/commands.py +237 -0
- agentpool_config/conditions.py +301 -0
- agentpool_config/converters.py +30 -0
- agentpool_config/durable.py +331 -0
- agentpool_config/event_handlers.py +600 -0
- agentpool_config/events.py +153 -0
- agentpool_config/forward_targets.py +251 -0
- agentpool_config/hook_conditions.py +331 -0
- agentpool_config/hooks.py +241 -0
- agentpool_config/jinja.py +206 -0
- agentpool_config/knowledge.py +41 -0
- agentpool_config/loaders.py +350 -0
- agentpool_config/mcp_server.py +243 -0
- agentpool_config/nodes.py +202 -0
- agentpool_config/observability.py +191 -0
- agentpool_config/output_types.py +55 -0
- agentpool_config/pool_server.py +267 -0
- agentpool_config/prompt_hubs.py +105 -0
- agentpool_config/prompts.py +185 -0
- agentpool_config/py.typed +0 -0
- agentpool_config/resources.py +33 -0
- agentpool_config/session.py +119 -0
- agentpool_config/skills.py +17 -0
- agentpool_config/storage.py +288 -0
- agentpool_config/system_prompts.py +190 -0
- agentpool_config/task.py +162 -0
- agentpool_config/teams.py +52 -0
- agentpool_config/tools.py +112 -0
- agentpool_config/toolsets.py +1033 -0
- agentpool_config/workers.py +86 -0
- agentpool_prompts/__init__.py +1 -0
- agentpool_prompts/braintrust_hub.py +235 -0
- agentpool_prompts/fabric.py +75 -0
- agentpool_prompts/langfuse_hub.py +79 -0
- agentpool_prompts/promptlayer_provider.py +59 -0
- agentpool_prompts/py.typed +0 -0
- agentpool_server/__init__.py +9 -0
- agentpool_server/a2a_server/__init__.py +5 -0
- agentpool_server/a2a_server/a2a_types.py +41 -0
- agentpool_server/a2a_server/server.py +190 -0
- agentpool_server/a2a_server/storage.py +81 -0
- agentpool_server/acp_server/__init__.py +22 -0
- agentpool_server/acp_server/acp_agent.py +786 -0
- agentpool_server/acp_server/acp_tools.py +43 -0
- agentpool_server/acp_server/commands/__init__.py +18 -0
- agentpool_server/acp_server/commands/acp_commands.py +594 -0
- agentpool_server/acp_server/commands/debug_commands.py +376 -0
- agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
- agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
- agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
- agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
- agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
- agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
- agentpool_server/acp_server/commands/spawn.py +210 -0
- agentpool_server/acp_server/converters.py +235 -0
- agentpool_server/acp_server/input_provider.py +338 -0
- agentpool_server/acp_server/server.py +288 -0
- agentpool_server/acp_server/session.py +969 -0
- agentpool_server/acp_server/session_manager.py +313 -0
- agentpool_server/acp_server/syntax_detection.py +250 -0
- agentpool_server/acp_server/zed_tools.md +90 -0
- agentpool_server/aggregating_server.py +309 -0
- agentpool_server/agui_server/__init__.py +11 -0
- agentpool_server/agui_server/server.py +128 -0
- agentpool_server/base.py +189 -0
- agentpool_server/http_server.py +164 -0
- agentpool_server/mcp_server/__init__.py +6 -0
- agentpool_server/mcp_server/server.py +314 -0
- agentpool_server/mcp_server/zed_wrapper.py +110 -0
- agentpool_server/openai_api_server/__init__.py +5 -0
- agentpool_server/openai_api_server/completions/__init__.py +1 -0
- agentpool_server/openai_api_server/completions/helpers.py +81 -0
- agentpool_server/openai_api_server/completions/models.py +98 -0
- agentpool_server/openai_api_server/responses/__init__.py +1 -0
- agentpool_server/openai_api_server/responses/helpers.py +74 -0
- agentpool_server/openai_api_server/responses/models.py +96 -0
- agentpool_server/openai_api_server/server.py +242 -0
- agentpool_server/py.typed +0 -0
- agentpool_storage/__init__.py +9 -0
- agentpool_storage/base.py +310 -0
- agentpool_storage/file_provider.py +378 -0
- agentpool_storage/formatters.py +129 -0
- agentpool_storage/memory_provider.py +396 -0
- agentpool_storage/models.py +108 -0
- agentpool_storage/py.typed +0 -0
- agentpool_storage/session_store.py +262 -0
- agentpool_storage/sql_provider/__init__.py +21 -0
- agentpool_storage/sql_provider/cli.py +146 -0
- agentpool_storage/sql_provider/models.py +249 -0
- agentpool_storage/sql_provider/queries.py +15 -0
- agentpool_storage/sql_provider/sql_provider.py +444 -0
- agentpool_storage/sql_provider/utils.py +234 -0
- agentpool_storage/text_log_provider.py +275 -0
- agentpool_toolsets/__init__.py +15 -0
- agentpool_toolsets/builtin/__init__.py +33 -0
- agentpool_toolsets/builtin/agent_management.py +239 -0
- agentpool_toolsets/builtin/chain.py +288 -0
- agentpool_toolsets/builtin/code.py +398 -0
- agentpool_toolsets/builtin/debug.py +291 -0
- agentpool_toolsets/builtin/execution_environment.py +381 -0
- agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
- agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
- agentpool_toolsets/builtin/history.py +36 -0
- agentpool_toolsets/builtin/integration.py +85 -0
- agentpool_toolsets/builtin/skills.py +77 -0
- agentpool_toolsets/builtin/subagent_tools.py +324 -0
- agentpool_toolsets/builtin/tool_management.py +90 -0
- agentpool_toolsets/builtin/user_interaction.py +52 -0
- agentpool_toolsets/builtin/workers.py +128 -0
- agentpool_toolsets/composio_toolset.py +96 -0
- agentpool_toolsets/config_creation.py +192 -0
- agentpool_toolsets/entry_points.py +47 -0
- agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
- agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
- agentpool_toolsets/fsspec_toolset/grep.py +450 -0
- agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
- agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
- agentpool_toolsets/mcp_run_toolset.py +61 -0
- agentpool_toolsets/notifications.py +146 -0
- agentpool_toolsets/openapi.py +118 -0
- agentpool_toolsets/py.typed +0 -0
- agentpool_toolsets/search_toolset.py +202 -0
- agentpool_toolsets/semantic_memory_toolset.py +536 -0
- agentpool_toolsets/streaming_tools.py +265 -0
- agentpool_toolsets/vfs_toolset.py +124 -0
|
@@ -0,0 +1,1264 @@
|
|
|
1
|
+
"""The main Agent. Can do all sort of crazy things."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import Awaitable, Callable
|
|
7
|
+
from contextlib import AsyncExitStack, asynccontextmanager, suppress
|
|
8
|
+
from dataclasses import dataclass, field, replace
|
|
9
|
+
import time
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Self, TypedDict, TypeVar, overload
|
|
11
|
+
from uuid import uuid4
|
|
12
|
+
|
|
13
|
+
from anyenv import method_spawner
|
|
14
|
+
import anyio
|
|
15
|
+
from llmling_models import function_to_model, infer_model
|
|
16
|
+
import logfire
|
|
17
|
+
from psygnal import Signal
|
|
18
|
+
from pydantic import ValidationError
|
|
19
|
+
from pydantic._internal import _typing_extra
|
|
20
|
+
from pydantic_ai import (
|
|
21
|
+
Agent as PydanticAgent,
|
|
22
|
+
AgentRunResultEvent,
|
|
23
|
+
BaseToolCallPart,
|
|
24
|
+
FunctionToolCallEvent,
|
|
25
|
+
FunctionToolResultEvent,
|
|
26
|
+
PartDeltaEvent,
|
|
27
|
+
PartStartEvent,
|
|
28
|
+
RunContext,
|
|
29
|
+
TextPart,
|
|
30
|
+
TextPartDelta,
|
|
31
|
+
ToolReturnPart,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from agentpool.agents.base_agent import BaseAgent
|
|
35
|
+
from agentpool.agents.events import RunStartedEvent, StreamCompleteEvent, ToolCallCompleteEvent
|
|
36
|
+
from agentpool.log import get_logger
|
|
37
|
+
from agentpool.messaging import ChatMessage, MessageHistory, MessageNode
|
|
38
|
+
from agentpool.messaging.processing import prepare_prompts
|
|
39
|
+
from agentpool.prompts.convert import convert_prompts
|
|
40
|
+
from agentpool.storage import StorageManager
|
|
41
|
+
from agentpool.talk.stats import MessageStats
|
|
42
|
+
from agentpool.tools import Tool, ToolManager
|
|
43
|
+
from agentpool.tools.exceptions import ToolError
|
|
44
|
+
from agentpool.utils.inspection import call_with_context, get_argument_key
|
|
45
|
+
from agentpool.utils.now import get_now
|
|
46
|
+
from agentpool.utils.result_utils import to_type
|
|
47
|
+
from agentpool.utils.streams import merge_queue_into_iterator
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
TResult = TypeVar("TResult")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from collections.abc import AsyncIterator, Coroutine, Sequence
|
|
55
|
+
from datetime import datetime
|
|
56
|
+
from types import TracebackType
|
|
57
|
+
|
|
58
|
+
from exxec import ExecutionEnvironment
|
|
59
|
+
from pydantic_ai import UsageLimits
|
|
60
|
+
from pydantic_ai.output import OutputSpec
|
|
61
|
+
from pydantic_ai.settings import ModelSettings
|
|
62
|
+
from toprompt import AnyPromptType
|
|
63
|
+
from upathtools import JoinablePathLike
|
|
64
|
+
|
|
65
|
+
from agentpool.agents import AgentContext
|
|
66
|
+
from agentpool.agents.events import RichAgentStreamEvent
|
|
67
|
+
from agentpool.common_types import (
|
|
68
|
+
AgentName,
|
|
69
|
+
BuiltinEventHandlerType,
|
|
70
|
+
EndStrategy,
|
|
71
|
+
IndividualEventHandler,
|
|
72
|
+
ModelType,
|
|
73
|
+
ProcessorCallback,
|
|
74
|
+
PromptCompatible,
|
|
75
|
+
SessionIdType,
|
|
76
|
+
ToolType,
|
|
77
|
+
)
|
|
78
|
+
from agentpool.delegation import AgentPool, Team, TeamRun
|
|
79
|
+
from agentpool.hooks import AgentHooks
|
|
80
|
+
from agentpool.models.agents import AutoCache, NativeAgentConfig, ToolMode
|
|
81
|
+
from agentpool.prompts.prompts import PromptType
|
|
82
|
+
from agentpool.resource_providers import ResourceProvider
|
|
83
|
+
from agentpool.ui.base import InputProvider
|
|
84
|
+
from agentpool_config.knowledge import Knowledge
|
|
85
|
+
from agentpool_config.mcp_server import MCPServerConfig
|
|
86
|
+
from agentpool_config.nodes import ToolConfirmationMode
|
|
87
|
+
from agentpool_config.session import MemoryConfig, SessionQuery
|
|
88
|
+
from agentpool_config.task import Job
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
logger = get_logger(__name__)
|
|
92
|
+
# OutputDataT = TypeVar('OutputDataT', default=str, covariant=True)
|
|
93
|
+
NoneType = type(None)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class AgentKwargs(TypedDict, total=False):
|
|
97
|
+
"""Keyword arguments for configuring an Agent instance."""
|
|
98
|
+
|
|
99
|
+
description: str | None
|
|
100
|
+
model: ModelType
|
|
101
|
+
system_prompt: str | Sequence[str]
|
|
102
|
+
tools: Sequence[ToolType] | None
|
|
103
|
+
toolsets: Sequence[ResourceProvider] | None
|
|
104
|
+
mcp_servers: Sequence[str | MCPServerConfig] | None
|
|
105
|
+
skills_paths: Sequence[JoinablePathLike] | None
|
|
106
|
+
retries: int
|
|
107
|
+
output_retries: int | None
|
|
108
|
+
end_strategy: EndStrategy
|
|
109
|
+
# context: AgentContext[Any] | None # x
|
|
110
|
+
session: SessionIdType | SessionQuery | MemoryConfig | bool | int
|
|
111
|
+
input_provider: InputProvider | None
|
|
112
|
+
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None
|
|
113
|
+
env: ExecutionEnvironment | None
|
|
114
|
+
auto_cache: AutoCache
|
|
115
|
+
hooks: AgentHooks | None
|
|
116
|
+
model_settings: ModelSettings | None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
120
|
+
"""The main agent class.
|
|
121
|
+
|
|
122
|
+
Generically typed with: Agent[Type of Dependencies, Type of Result]
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class AgentReset:
|
|
127
|
+
"""Emitted when agent is reset."""
|
|
128
|
+
|
|
129
|
+
agent_name: AgentName
|
|
130
|
+
previous_tools: dict[str, bool]
|
|
131
|
+
new_tools: dict[str, bool]
|
|
132
|
+
timestamp: datetime = field(default_factory=get_now)
|
|
133
|
+
|
|
134
|
+
run_failed = Signal(str, Exception)
|
|
135
|
+
agent_reset = Signal(AgentReset)
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
# we dont use AgentKwargs here so that we can work with explicit ones in the ctor
|
|
139
|
+
self,
|
|
140
|
+
name: str = "agentpool",
|
|
141
|
+
*,
|
|
142
|
+
deps_type: type[TDeps] | None = None,
|
|
143
|
+
model: ModelType = None,
|
|
144
|
+
output_type: OutputSpec[OutputDataT] = str, # type: ignore[assignment]
|
|
145
|
+
# context: AgentContext[TDeps] | None = None,
|
|
146
|
+
session: SessionIdType | SessionQuery | MemoryConfig | bool | int = None,
|
|
147
|
+
system_prompt: AnyPromptType | Sequence[AnyPromptType] = (),
|
|
148
|
+
description: str | None = None,
|
|
149
|
+
display_name: str | None = None,
|
|
150
|
+
tools: Sequence[ToolType] | None = None,
|
|
151
|
+
toolsets: Sequence[ResourceProvider] | None = None,
|
|
152
|
+
mcp_servers: Sequence[str | MCPServerConfig] | None = None,
|
|
153
|
+
resources: Sequence[PromptType | str] = (),
|
|
154
|
+
skills_paths: Sequence[JoinablePathLike] | None = None,
|
|
155
|
+
retries: int = 1,
|
|
156
|
+
output_retries: int | None = None,
|
|
157
|
+
end_strategy: EndStrategy = "early",
|
|
158
|
+
input_provider: InputProvider | None = None,
|
|
159
|
+
parallel_init: bool = True,
|
|
160
|
+
model_settings: ModelSettings | None = None,
|
|
161
|
+
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
162
|
+
agent_pool: AgentPool[Any] | None = None,
|
|
163
|
+
tool_mode: ToolMode | None = None,
|
|
164
|
+
knowledge: Knowledge | None = None,
|
|
165
|
+
agent_config: NativeAgentConfig | None = None,
|
|
166
|
+
env: ExecutionEnvironment | None = None,
|
|
167
|
+
auto_cache: AutoCache = "off",
|
|
168
|
+
hooks: AgentHooks | None = None,
|
|
169
|
+
tool_confirmation_mode: ToolConfirmationMode = "per_tool",
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Initialize agent.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
name: Identifier for the agent (used for logging and lookups)
|
|
175
|
+
deps_type: Type of dependencies to use
|
|
176
|
+
model: The default model to use (defaults to GPT-5)
|
|
177
|
+
output_type: The default output type to use (defaults to str)
|
|
178
|
+
context: Agent context with configuration
|
|
179
|
+
session: Memory configuration.
|
|
180
|
+
- None: Default memory config
|
|
181
|
+
- False: Disable message history (max_messages=0)
|
|
182
|
+
- int: Max tokens for memory
|
|
183
|
+
- str/UUID: Session identifier
|
|
184
|
+
- MemoryConfig: Full memory configuration
|
|
185
|
+
- MemoryProvider: Custom memory provider
|
|
186
|
+
- SessionQuery: Session query
|
|
187
|
+
|
|
188
|
+
system_prompt: System prompts for the agent
|
|
189
|
+
description: Description of the Agent ("what it can do")
|
|
190
|
+
display_name: Human-readable display name (falls back to name)
|
|
191
|
+
tools: List of tools to register with the agent
|
|
192
|
+
toolsets: List of toolset resource providers for the agent
|
|
193
|
+
mcp_servers: MCP servers to connect to
|
|
194
|
+
resources: Additional resources to load
|
|
195
|
+
skills_paths: Local directories to search for agent-specific skills
|
|
196
|
+
retries: Default number of retries for failed operations
|
|
197
|
+
output_retries: Max retries for result validation (defaults to retries)
|
|
198
|
+
end_strategy: Strategy for handling tool calls that are requested alongside
|
|
199
|
+
a final result
|
|
200
|
+
input_provider: Provider for human input (tool confirmation / HumanProviders)
|
|
201
|
+
parallel_init: Whether to initialize resources in parallel
|
|
202
|
+
model_settings: Settings for the AI model
|
|
203
|
+
event_handlers: Sequence of event handlers to register with the agent
|
|
204
|
+
agent_pool: AgentPool instance for managing agent resources
|
|
205
|
+
tool_mode: Tool execution mode (None or "codemode")
|
|
206
|
+
knowledge: Knowledge sources for this agent
|
|
207
|
+
agent_config: Agent configuration
|
|
208
|
+
env: Execution environment for code/command execution and filesystem access
|
|
209
|
+
auto_cache: Automatic caching configuration ("off", "5m", or "1h")
|
|
210
|
+
hooks: AgentHooks instance for intercepting agent behavior at run and tool events
|
|
211
|
+
tool_confirmation_mode: Tool confirmation mode
|
|
212
|
+
"""
|
|
213
|
+
from agentpool.agents.interactions import Interactions
|
|
214
|
+
from agentpool.agents.sys_prompts import SystemPrompts
|
|
215
|
+
from agentpool.models.agents import NativeAgentConfig
|
|
216
|
+
from agentpool.prompts.conversion_manager import ConversionManager
|
|
217
|
+
from agentpool_config.session import MemoryConfig
|
|
218
|
+
|
|
219
|
+
self._infinite = False
|
|
220
|
+
self.deps_type = deps_type
|
|
221
|
+
self.model_settings = model_settings
|
|
222
|
+
memory_cfg = (
|
|
223
|
+
session if isinstance(session, MemoryConfig) else MemoryConfig.from_value(session)
|
|
224
|
+
)
|
|
225
|
+
# Collect MCP servers from config
|
|
226
|
+
all_mcp_servers = list(mcp_servers) if mcp_servers else []
|
|
227
|
+
if agent_config and agent_config.mcp_servers:
|
|
228
|
+
all_mcp_servers.extend(agent_config.get_mcp_servers())
|
|
229
|
+
|
|
230
|
+
# Call base class with shared parameters
|
|
231
|
+
super().__init__(
|
|
232
|
+
name=name,
|
|
233
|
+
description=description,
|
|
234
|
+
display_name=display_name,
|
|
235
|
+
enable_logging=memory_cfg.enable,
|
|
236
|
+
mcp_servers=all_mcp_servers,
|
|
237
|
+
agent_pool=agent_pool,
|
|
238
|
+
event_configs=agent_config.triggers if agent_config else [],
|
|
239
|
+
env=env,
|
|
240
|
+
input_provider=input_provider,
|
|
241
|
+
output_type=to_type(output_type), # type: ignore[arg-type]
|
|
242
|
+
tool_confirmation_mode=tool_confirmation_mode,
|
|
243
|
+
event_handlers=event_handlers,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Store config for context creation
|
|
247
|
+
self._agent_config = agent_config or NativeAgentConfig(name=name)
|
|
248
|
+
|
|
249
|
+
# Override tools with Agent-specific ToolManager (with tools and tool_mode)
|
|
250
|
+
all_tools = list(tools or [])
|
|
251
|
+
self.tools = ToolManager(all_tools, tool_mode=tool_mode)
|
|
252
|
+
for toolset_provider in toolsets or []:
|
|
253
|
+
self.tools.add_provider(toolset_provider)
|
|
254
|
+
aggregating_provider = self.mcp.get_aggregating_provider()
|
|
255
|
+
self.tools.add_provider(aggregating_provider)
|
|
256
|
+
|
|
257
|
+
# Override conversation with Agent-specific MessageHistory (with storage, etc.)
|
|
258
|
+
resources = list(resources)
|
|
259
|
+
if knowledge:
|
|
260
|
+
resources.extend(knowledge.get_resources())
|
|
261
|
+
storage = agent_pool.storage if agent_pool else StorageManager(self._manifest.storage)
|
|
262
|
+
self.conversation = MessageHistory(
|
|
263
|
+
storage=storage,
|
|
264
|
+
converter=ConversionManager(config=self._manifest.conversion),
|
|
265
|
+
session_config=memory_cfg,
|
|
266
|
+
resources=resources,
|
|
267
|
+
)
|
|
268
|
+
self._model = infer_model(model) if isinstance(model, str) else model
|
|
269
|
+
self._retries = retries
|
|
270
|
+
self._end_strategy: EndStrategy = end_strategy
|
|
271
|
+
self._output_retries = output_retries
|
|
272
|
+
self.parallel_init = parallel_init
|
|
273
|
+
self._background_task: asyncio.Task[ChatMessage[Any]] | None = None
|
|
274
|
+
self.talk = Interactions(self)
|
|
275
|
+
|
|
276
|
+
# Set up system prompts
|
|
277
|
+
all_prompts: list[AnyPromptType] = []
|
|
278
|
+
if isinstance(system_prompt, (list, tuple)):
|
|
279
|
+
all_prompts.extend(system_prompt)
|
|
280
|
+
elif system_prompt:
|
|
281
|
+
all_prompts.append(system_prompt)
|
|
282
|
+
self.sys_prompts = SystemPrompts(all_prompts, prompt_manager=self._manifest.prompt_manager)
|
|
283
|
+
|
|
284
|
+
# Store hooks
|
|
285
|
+
self.hooks = hooks
|
|
286
|
+
|
|
287
|
+
# Store auto_cache setting
|
|
288
|
+
self._auto_cache: AutoCache = auto_cache
|
|
289
|
+
|
|
290
|
+
def __repr__(self) -> str:
|
|
291
|
+
desc = f", {self.description!r}" if self.description else ""
|
|
292
|
+
return f"Agent({self.name!r}, model={self._model!r}{desc})"
|
|
293
|
+
|
|
294
|
+
async def __prompt__(self) -> str:
|
|
295
|
+
typ = self.__class__.__name__
|
|
296
|
+
model = self.model_name or "default"
|
|
297
|
+
parts = [f"Agent: {self.name}", f"Type: {typ}", f"Model: {model}"]
|
|
298
|
+
if self.description:
|
|
299
|
+
parts.append(f"Description: {self.description}")
|
|
300
|
+
parts.extend([await self.tools.__prompt__(), self.conversation.__prompt__()])
|
|
301
|
+
return "\n".join(parts)
|
|
302
|
+
|
|
303
|
+
async def __aenter__(self) -> Self:
|
|
304
|
+
"""Enter async context and set up MCP servers."""
|
|
305
|
+
try:
|
|
306
|
+
# Collect all coroutines that need to be run
|
|
307
|
+
coros: list[Coroutine[Any, Any, Any]] = []
|
|
308
|
+
coros.append(super().__aenter__())
|
|
309
|
+
coros.extend(self.conversation.get_initialization_tasks())
|
|
310
|
+
if self.parallel_init and coros:
|
|
311
|
+
await asyncio.gather(*coros)
|
|
312
|
+
else:
|
|
313
|
+
for coro in coros:
|
|
314
|
+
await coro
|
|
315
|
+
except Exception as e:
|
|
316
|
+
msg = "Failed to initialize agent"
|
|
317
|
+
raise RuntimeError(msg) from e
|
|
318
|
+
else:
|
|
319
|
+
return self
|
|
320
|
+
|
|
321
|
+
async def __aexit__(
|
|
322
|
+
self,
|
|
323
|
+
exc_type: type[BaseException] | None,
|
|
324
|
+
exc_val: BaseException | None,
|
|
325
|
+
exc_tb: TracebackType | None,
|
|
326
|
+
) -> None:
|
|
327
|
+
"""Exit async context."""
|
|
328
|
+
await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
329
|
+
|
|
330
|
+
@overload
|
|
331
|
+
def __and__( # if other doesnt define deps, we take the agents one
|
|
332
|
+
self, other: ProcessorCallback[Any] | Team[TDeps] | Agent[TDeps, Any]
|
|
333
|
+
) -> Team[TDeps]: ...
|
|
334
|
+
|
|
335
|
+
@overload
|
|
336
|
+
def __and__( # otherwise, we dont know and deps is Any
|
|
337
|
+
self, other: ProcessorCallback[Any] | Team[Any] | Agent[Any, Any]
|
|
338
|
+
) -> Team[Any]: ...
|
|
339
|
+
|
|
340
|
+
def __and__(self, other: MessageNode[Any, Any] | ProcessorCallback[Any]) -> Team[Any]:
|
|
341
|
+
"""Create sequential team using & operator.
|
|
342
|
+
|
|
343
|
+
Example:
|
|
344
|
+
group = analyzer & planner & executor # Create group of 3
|
|
345
|
+
group = analyzer & existing_group # Add to existing group
|
|
346
|
+
"""
|
|
347
|
+
from agentpool.delegation.team import Team
|
|
348
|
+
|
|
349
|
+
match other:
|
|
350
|
+
case Team():
|
|
351
|
+
return Team([self, *other.nodes])
|
|
352
|
+
case Callable():
|
|
353
|
+
agent_2 = Agent.from_callback(other)
|
|
354
|
+
agent_2.agent_pool = self.agent_pool
|
|
355
|
+
return Team([self, agent_2])
|
|
356
|
+
case MessageNode():
|
|
357
|
+
return Team([self, other])
|
|
358
|
+
case _:
|
|
359
|
+
msg = f"Invalid agent type: {type(other)}"
|
|
360
|
+
raise ValueError(msg)
|
|
361
|
+
|
|
362
|
+
@overload
|
|
363
|
+
def __or__(self, other: MessageNode[TDeps, Any]) -> TeamRun[TDeps, Any]: ...
|
|
364
|
+
|
|
365
|
+
@overload
|
|
366
|
+
def __or__[TOtherDeps](self, other: MessageNode[TOtherDeps, Any]) -> TeamRun[Any, Any]: ...
|
|
367
|
+
|
|
368
|
+
@overload
|
|
369
|
+
def __or__(self, other: ProcessorCallback[Any]) -> TeamRun[Any, Any]: ...
|
|
370
|
+
|
|
371
|
+
def __or__(self, other: MessageNode[Any, Any] | ProcessorCallback[Any]) -> TeamRun[Any, Any]:
|
|
372
|
+
# Create new execution with sequential mode (for piping)
|
|
373
|
+
from agentpool import TeamRun
|
|
374
|
+
|
|
375
|
+
if callable(other):
|
|
376
|
+
other = Agent.from_callback(other)
|
|
377
|
+
other.agent_pool = self.agent_pool
|
|
378
|
+
|
|
379
|
+
return TeamRun([self, other])
|
|
380
|
+
|
|
381
|
+
@overload
|
|
382
|
+
@classmethod
|
|
383
|
+
def from_callback(
|
|
384
|
+
cls,
|
|
385
|
+
callback: Callable[..., Awaitable[TResult]],
|
|
386
|
+
*,
|
|
387
|
+
name: str | None = None,
|
|
388
|
+
**kwargs: Any,
|
|
389
|
+
) -> Agent[None, TResult]: ...
|
|
390
|
+
|
|
391
|
+
@overload
|
|
392
|
+
@classmethod
|
|
393
|
+
def from_callback(
|
|
394
|
+
cls,
|
|
395
|
+
callback: Callable[..., TResult],
|
|
396
|
+
*,
|
|
397
|
+
name: str | None = None,
|
|
398
|
+
**kwargs: Any,
|
|
399
|
+
) -> Agent[None, TResult]: ...
|
|
400
|
+
|
|
401
|
+
@classmethod
|
|
402
|
+
def from_callback(
|
|
403
|
+
cls,
|
|
404
|
+
callback: ProcessorCallback[Any],
|
|
405
|
+
*,
|
|
406
|
+
name: str | None = None,
|
|
407
|
+
**kwargs: Any,
|
|
408
|
+
) -> Agent[None, Any]:
|
|
409
|
+
"""Create an agent from a processing callback.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
callback: Function to process messages. Can be:
|
|
413
|
+
- sync or async
|
|
414
|
+
- with or without context
|
|
415
|
+
- must return str for pipeline compatibility
|
|
416
|
+
name: Optional name for the agent
|
|
417
|
+
kwargs: Additional arguments for agent
|
|
418
|
+
"""
|
|
419
|
+
name = name or callback.__name__ or "processor"
|
|
420
|
+
model = function_to_model(callback)
|
|
421
|
+
return_type = _typing_extra.get_function_type_hints(callback).get("return")
|
|
422
|
+
if ( # If async, unwrap from Awaitable
|
|
423
|
+
return_type
|
|
424
|
+
and hasattr(return_type, "__origin__")
|
|
425
|
+
and return_type.__origin__ is Awaitable
|
|
426
|
+
):
|
|
427
|
+
return_type = return_type.__args__[0]
|
|
428
|
+
return Agent(model=model, name=name, output_type=return_type or str, **kwargs)
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def name(self) -> str:
|
|
432
|
+
"""Get agent name."""
|
|
433
|
+
return self._name or "agentpool"
|
|
434
|
+
|
|
435
|
+
@name.setter
|
|
436
|
+
def name(self, value: str) -> None:
|
|
437
|
+
"""Set agent name."""
|
|
438
|
+
self._name = value
|
|
439
|
+
|
|
440
|
+
def get_context(self, data: TDeps | None = None) -> AgentContext[TDeps]: # type: ignore[override]
|
|
441
|
+
"""Create a new context for this agent.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
data: Optional custom data to attach to the context
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
A new AgentContext instance
|
|
448
|
+
"""
|
|
449
|
+
from agentpool.agents import AgentContext
|
|
450
|
+
|
|
451
|
+
return AgentContext(
|
|
452
|
+
node=self,
|
|
453
|
+
definition=self._manifest,
|
|
454
|
+
config=self._agent_config,
|
|
455
|
+
input_provider=self._input_provider,
|
|
456
|
+
pool=self.agent_pool,
|
|
457
|
+
data=data,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
def to_structured[NewOutputDataT](
|
|
461
|
+
self,
|
|
462
|
+
output_type: type[NewOutputDataT],
|
|
463
|
+
*,
|
|
464
|
+
tool_name: str | None = None,
|
|
465
|
+
tool_description: str | None = None,
|
|
466
|
+
) -> Agent[TDeps, NewOutputDataT]:
|
|
467
|
+
"""Convert this agent to a structured agent.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
output_type: Type for structured responses. Can be:
|
|
471
|
+
- A Python type (Pydantic model)
|
|
472
|
+
tool_name: Optional override for result tool name
|
|
473
|
+
tool_description: Optional override for result tool description
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
Typed Agent
|
|
477
|
+
"""
|
|
478
|
+
self.log.debug("Setting result type", output_type=output_type)
|
|
479
|
+
self._output_type = to_type(output_type) # type: ignore[assignment]
|
|
480
|
+
return self # type: ignore
|
|
481
|
+
|
|
482
|
+
def is_busy(self) -> bool:
|
|
483
|
+
"""Check if agent is currently processing tasks."""
|
|
484
|
+
return bool(self.task_manager._pending_tasks or self._background_task)
|
|
485
|
+
|
|
486
|
+
@property
|
|
487
|
+
def model_name(self) -> str | None:
|
|
488
|
+
"""Get the model name in a consistent format (provider:model_name)."""
|
|
489
|
+
if self._model:
|
|
490
|
+
# Construct full model ID with provider prefix (e.g., "anthropic:claude-haiku-4-5")
|
|
491
|
+
return f"{self._model.system}:{self._model.model_name}"
|
|
492
|
+
return None
|
|
493
|
+
|
|
494
|
+
def to_tool(
|
|
495
|
+
self,
|
|
496
|
+
*,
|
|
497
|
+
name: str | None = None,
|
|
498
|
+
description: str | None = None,
|
|
499
|
+
reset_history_on_run: bool = True,
|
|
500
|
+
pass_message_history: bool = False,
|
|
501
|
+
parent: Agent[Any, Any] | None = None,
|
|
502
|
+
**_kwargs: Any,
|
|
503
|
+
) -> Tool[OutputDataT]:
|
|
504
|
+
"""Create a tool from this agent.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
name: Optional tool name override
|
|
508
|
+
description: Optional tool description override
|
|
509
|
+
reset_history_on_run: Clear agent's history before each run
|
|
510
|
+
pass_message_history: Pass parent's message history to agent
|
|
511
|
+
parent: Optional parent agent for history/context sharing
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
async def wrapped_tool(prompt: str) -> Any:
|
|
515
|
+
if pass_message_history and not parent:
|
|
516
|
+
msg = "Parent agent required for message history sharing"
|
|
517
|
+
raise ToolError(msg)
|
|
518
|
+
|
|
519
|
+
if reset_history_on_run:
|
|
520
|
+
self.conversation.clear()
|
|
521
|
+
|
|
522
|
+
history = None
|
|
523
|
+
if pass_message_history and parent:
|
|
524
|
+
history = parent.conversation.get_history()
|
|
525
|
+
old = self.conversation.get_history()
|
|
526
|
+
self.conversation.set_history(history)
|
|
527
|
+
result = await self.run(prompt)
|
|
528
|
+
if history:
|
|
529
|
+
self.conversation.set_history(old)
|
|
530
|
+
return result.data
|
|
531
|
+
|
|
532
|
+
# Set the correct return annotation dynamically
|
|
533
|
+
wrapped_tool.__annotations__ = {"prompt": str, "return": self._output_type or Any}
|
|
534
|
+
|
|
535
|
+
normalized_name = self.name.replace("_", " ").title()
|
|
536
|
+
docstring = f"Get expert answer from specialized agent: {normalized_name}"
|
|
537
|
+
description = description or self.description
|
|
538
|
+
if description:
|
|
539
|
+
docstring = f"{docstring}\n\n{description}"
|
|
540
|
+
tool_name = name or f"ask_{self.name}"
|
|
541
|
+
wrapped_tool.__doc__ = docstring
|
|
542
|
+
wrapped_tool.__name__ = tool_name
|
|
543
|
+
|
|
544
|
+
return Tool.from_callable(
|
|
545
|
+
wrapped_tool,
|
|
546
|
+
name_override=tool_name,
|
|
547
|
+
description_override=docstring,
|
|
548
|
+
source="agent",
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
async def get_agentlet[AgentOutputType](
|
|
552
|
+
self,
|
|
553
|
+
tool_choice: str | list[str] | None,
|
|
554
|
+
model: ModelType,
|
|
555
|
+
output_type: type[AgentOutputType] | None,
|
|
556
|
+
input_provider: InputProvider | None = None,
|
|
557
|
+
) -> PydanticAgent[TDeps, AgentOutputType]:
|
|
558
|
+
"""Create pydantic-ai agent from current state."""
|
|
559
|
+
# Monkey patch pydantic-ai to recognize AgentContext
|
|
560
|
+
|
|
561
|
+
from agentpool.agents.tool_wrapping import wrap_tool
|
|
562
|
+
|
|
563
|
+
tools = await self.tools.get_tools(state="enabled", names=tool_choice)
|
|
564
|
+
final_type = to_type(output_type) if output_type not in [None, str] else self._output_type
|
|
565
|
+
actual_model = model or self._model
|
|
566
|
+
model_ = infer_model(actual_model) if isinstance(actual_model, str) else actual_model
|
|
567
|
+
agent = PydanticAgent(
|
|
568
|
+
name=self.name,
|
|
569
|
+
model=model_,
|
|
570
|
+
model_settings=self.model_settings,
|
|
571
|
+
instructions=await self.sys_prompts.format_system_prompt(self),
|
|
572
|
+
retries=self._retries,
|
|
573
|
+
end_strategy=self._end_strategy,
|
|
574
|
+
output_retries=self._output_retries,
|
|
575
|
+
deps_type=self.deps_type or NoneType,
|
|
576
|
+
output_type=final_type,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
base_context = self.get_context()
|
|
580
|
+
context_for_tools = (
|
|
581
|
+
base_context
|
|
582
|
+
if input_provider is None
|
|
583
|
+
else replace(base_context, input_provider=input_provider)
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
for tool in tools:
|
|
587
|
+
wrapped = wrap_tool(tool, context_for_tools, hooks=self.hooks)
|
|
588
|
+
if get_argument_key(wrapped, RunContext):
|
|
589
|
+
logger.info("Registering tool: with context", tool_name=tool.name)
|
|
590
|
+
agent.tool(wrapped)
|
|
591
|
+
else:
|
|
592
|
+
logger.info("Registering tool: no context", tool_name=tool.name)
|
|
593
|
+
agent.tool_plain(wrapped)
|
|
594
|
+
|
|
595
|
+
return agent # type: ignore[return-value]
|
|
596
|
+
|
|
597
|
+
@overload
|
|
598
|
+
async def run(
|
|
599
|
+
self,
|
|
600
|
+
*prompts: PromptCompatible | ChatMessage[Any],
|
|
601
|
+
output_type: None = None,
|
|
602
|
+
model: ModelType = None,
|
|
603
|
+
store_history: bool = True,
|
|
604
|
+
tool_choice: str | list[str] | None = None,
|
|
605
|
+
usage_limits: UsageLimits | None = None,
|
|
606
|
+
message_id: str | None = None,
|
|
607
|
+
conversation_id: str | None = None,
|
|
608
|
+
message_history: MessageHistory | None = None,
|
|
609
|
+
deps: TDeps | None = None,
|
|
610
|
+
input_provider: InputProvider | None = None,
|
|
611
|
+
wait_for_connections: bool | None = None,
|
|
612
|
+
instructions: str | None = None,
|
|
613
|
+
) -> ChatMessage[OutputDataT]: ...
|
|
614
|
+
|
|
615
|
+
@overload
|
|
616
|
+
async def run[OutputTypeT](
|
|
617
|
+
self,
|
|
618
|
+
*prompts: PromptCompatible | ChatMessage[Any],
|
|
619
|
+
output_type: type[OutputTypeT],
|
|
620
|
+
model: ModelType = None,
|
|
621
|
+
store_history: bool = True,
|
|
622
|
+
tool_choice: str | list[str] | None = None,
|
|
623
|
+
usage_limits: UsageLimits | None = None,
|
|
624
|
+
message_id: str | None = None,
|
|
625
|
+
conversation_id: str | None = None,
|
|
626
|
+
message_history: MessageHistory | None = None,
|
|
627
|
+
deps: TDeps | None = None,
|
|
628
|
+
input_provider: InputProvider | None = None,
|
|
629
|
+
wait_for_connections: bool | None = None,
|
|
630
|
+
instructions: str | None = None,
|
|
631
|
+
) -> ChatMessage[OutputTypeT]: ...
|
|
632
|
+
|
|
633
|
+
@method_spawner # type: ignore[misc]
|
|
634
|
+
async def run(
|
|
635
|
+
self,
|
|
636
|
+
*prompts: PromptCompatible | ChatMessage[Any],
|
|
637
|
+
output_type: type[Any] | None = None,
|
|
638
|
+
model: ModelType = None,
|
|
639
|
+
store_history: bool = True,
|
|
640
|
+
tool_choice: str | list[str] | None = None,
|
|
641
|
+
usage_limits: UsageLimits | None = None,
|
|
642
|
+
message_id: str | None = None,
|
|
643
|
+
conversation_id: str | None = None,
|
|
644
|
+
message_history: MessageHistory | None = None,
|
|
645
|
+
deps: TDeps | None = None,
|
|
646
|
+
input_provider: InputProvider | None = None,
|
|
647
|
+
wait_for_connections: bool | None = None,
|
|
648
|
+
instructions: str | None = None,
|
|
649
|
+
) -> ChatMessage[Any]:
|
|
650
|
+
"""Run agent with prompt and get response.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
prompts: User query or instruction
|
|
654
|
+
output_type: Optional type for structured responses
|
|
655
|
+
model: Optional model override
|
|
656
|
+
store_history: Whether the message exchange should be added to the
|
|
657
|
+
context window
|
|
658
|
+
tool_choice: Filter tool choice by name
|
|
659
|
+
usage_limits: Optional usage limits for the model
|
|
660
|
+
message_id: Optional message id for the returned message.
|
|
661
|
+
Automatically generated if not provided.
|
|
662
|
+
conversation_id: Optional conversation id for the returned message.
|
|
663
|
+
message_history: Optional MessageHistory object to
|
|
664
|
+
use instead of agent's own conversation
|
|
665
|
+
deps: Optional dependencies for the agent
|
|
666
|
+
input_provider: Optional input provider for the agent
|
|
667
|
+
wait_for_connections: Whether to wait for connected agents to complete
|
|
668
|
+
instructions: Optional instructions to override the agent's system prompt
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
Result containing response and run information
|
|
672
|
+
|
|
673
|
+
Raises:
|
|
674
|
+
UnexpectedModelBehavior: If the model fails or behaves unexpectedly
|
|
675
|
+
"""
|
|
676
|
+
# Collect all events through run_stream
|
|
677
|
+
final_message: ChatMessage[Any] | None = None
|
|
678
|
+
async for event in self.run_stream(
|
|
679
|
+
*prompts,
|
|
680
|
+
output_type=output_type,
|
|
681
|
+
model=model,
|
|
682
|
+
store_history=store_history,
|
|
683
|
+
tool_choice=tool_choice,
|
|
684
|
+
usage_limits=usage_limits,
|
|
685
|
+
message_id=message_id,
|
|
686
|
+
conversation_id=conversation_id,
|
|
687
|
+
message_history=message_history,
|
|
688
|
+
deps=deps,
|
|
689
|
+
input_provider=input_provider,
|
|
690
|
+
wait_for_connections=wait_for_connections,
|
|
691
|
+
instructions=instructions,
|
|
692
|
+
):
|
|
693
|
+
if isinstance(event, StreamCompleteEvent):
|
|
694
|
+
final_message = event.message
|
|
695
|
+
|
|
696
|
+
if final_message is None:
|
|
697
|
+
msg = "No final message received from stream"
|
|
698
|
+
raise RuntimeError(msg)
|
|
699
|
+
|
|
700
|
+
return final_message
|
|
701
|
+
|
|
702
|
+
@method_spawner
|
|
703
|
+
async def run_stream( # noqa: PLR0915
|
|
704
|
+
self,
|
|
705
|
+
*prompt: PromptCompatible,
|
|
706
|
+
output_type: type[OutputDataT] | None = None,
|
|
707
|
+
model: ModelType = None,
|
|
708
|
+
tool_choice: str | list[str] | None = None,
|
|
709
|
+
store_history: bool = True,
|
|
710
|
+
usage_limits: UsageLimits | None = None,
|
|
711
|
+
message_id: str | None = None,
|
|
712
|
+
conversation_id: str | None = None,
|
|
713
|
+
message_history: MessageHistory | None = None,
|
|
714
|
+
input_provider: InputProvider | None = None,
|
|
715
|
+
wait_for_connections: bool | None = None,
|
|
716
|
+
deps: TDeps | None = None,
|
|
717
|
+
instructions: str | None = None,
|
|
718
|
+
) -> AsyncIterator[RichAgentStreamEvent[OutputDataT]]:
|
|
719
|
+
"""Run agent with prompt and get a streaming response.
|
|
720
|
+
|
|
721
|
+
Args:
|
|
722
|
+
prompt: User query or instruction
|
|
723
|
+
output_type: Optional type for structured responses
|
|
724
|
+
model: Optional model override
|
|
725
|
+
tool_choice: Filter tool choice by name
|
|
726
|
+
store_history: Whether the message exchange should be added to the
|
|
727
|
+
context window
|
|
728
|
+
usage_limits: Optional usage limits for the model
|
|
729
|
+
message_id: Optional message id for the returned message.
|
|
730
|
+
Automatically generated if not provided.
|
|
731
|
+
conversation_id: Optional conversation id for the returned message.
|
|
732
|
+
message_history: Optional MessageHistory to use instead of agent's own
|
|
733
|
+
input_provider: Optional input provider for the agent
|
|
734
|
+
wait_for_connections: Whether to wait for connected agents to complete
|
|
735
|
+
deps: Optional dependencies for the agent
|
|
736
|
+
instructions: Optional instructions to override the agent's system prompt
|
|
737
|
+
Returns:
|
|
738
|
+
An async iterator yielding streaming events with final message embedded.
|
|
739
|
+
|
|
740
|
+
Raises:
|
|
741
|
+
UnexpectedModelBehavior: If the model fails or behaves unexpectedly
|
|
742
|
+
"""
|
|
743
|
+
conversation = message_history if message_history is not None else self.conversation
|
|
744
|
+
message_id = message_id or str(uuid4())
|
|
745
|
+
run_id = str(uuid4())
|
|
746
|
+
user_msg, prompts, original_message = await prepare_prompts(*prompt)
|
|
747
|
+
self.message_received.emit(user_msg)
|
|
748
|
+
start_time = time.perf_counter()
|
|
749
|
+
history_list = conversation.get_history()
|
|
750
|
+
pending_parts = conversation.get_pending_parts()
|
|
751
|
+
|
|
752
|
+
# Reset cancellation state and track current task
|
|
753
|
+
self._cancelled = False
|
|
754
|
+
self._current_stream_task = asyncio.current_task()
|
|
755
|
+
|
|
756
|
+
# Track accumulated content for partial message on cancellation
|
|
757
|
+
accumulated_text: list[str] = []
|
|
758
|
+
|
|
759
|
+
# Execute pre-run hooks
|
|
760
|
+
if self.hooks:
|
|
761
|
+
pre_run_result = await self.hooks.run_pre_run_hooks(
|
|
762
|
+
agent_name=self.name,
|
|
763
|
+
prompt=user_msg.content
|
|
764
|
+
if isinstance(user_msg.content, str)
|
|
765
|
+
else str(user_msg.content),
|
|
766
|
+
conversation_id=conversation_id,
|
|
767
|
+
)
|
|
768
|
+
if pre_run_result.get("decision") == "deny":
|
|
769
|
+
reason = pre_run_result.get("reason", "Blocked by pre-run hook")
|
|
770
|
+
msg = f"Run blocked: {reason}"
|
|
771
|
+
raise RuntimeError(msg)
|
|
772
|
+
|
|
773
|
+
yield RunStartedEvent(thread_id=self.conversation_id, run_id=run_id, agent_name=self.name)
|
|
774
|
+
try:
|
|
775
|
+
agentlet = await self.get_agentlet(tool_choice, model, output_type, input_provider)
|
|
776
|
+
content = await convert_prompts(prompts)
|
|
777
|
+
response_msg: ChatMessage[Any] | None = None
|
|
778
|
+
# Prepend pending context parts (content is already pydantic-ai format)
|
|
779
|
+
converted = [*pending_parts, *content]
|
|
780
|
+
|
|
781
|
+
# Add CachePoint if auto_cache is enabled
|
|
782
|
+
if self._auto_cache != "off":
|
|
783
|
+
from pydantic_ai.messages import CachePoint
|
|
784
|
+
|
|
785
|
+
cache_point = CachePoint(ttl=self._auto_cache)
|
|
786
|
+
converted.append(cache_point)
|
|
787
|
+
stream_events = agentlet.run_stream_events(
|
|
788
|
+
converted,
|
|
789
|
+
deps=deps, # type: ignore[arg-type]
|
|
790
|
+
message_history=[m for run in history_list for m in run.to_pydantic_ai()],
|
|
791
|
+
usage_limits=usage_limits,
|
|
792
|
+
instructions=instructions,
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
# Stream events through merge_queue for progress events
|
|
796
|
+
async with merge_queue_into_iterator(stream_events, self._event_queue) as events:
|
|
797
|
+
# Track tool call starts to combine with results later
|
|
798
|
+
pending_tcs: dict[str, BaseToolCallPart] = {}
|
|
799
|
+
try:
|
|
800
|
+
async for event in events:
|
|
801
|
+
# Check for cancellation
|
|
802
|
+
if self._cancelled:
|
|
803
|
+
self.log.info("Stream cancelled by user")
|
|
804
|
+
break
|
|
805
|
+
|
|
806
|
+
# Call event handlers for all events
|
|
807
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
808
|
+
await handler(None, event)
|
|
809
|
+
|
|
810
|
+
yield event # type: ignore[misc]
|
|
811
|
+
|
|
812
|
+
# Accumulate text content for partial message
|
|
813
|
+
match event:
|
|
814
|
+
case PartDeltaEvent(delta=TextPartDelta(content_delta=delta)):
|
|
815
|
+
accumulated_text.append(delta)
|
|
816
|
+
case PartStartEvent(part=TextPart(content=text)) if text:
|
|
817
|
+
accumulated_text.append(text)
|
|
818
|
+
|
|
819
|
+
match event:
|
|
820
|
+
case (
|
|
821
|
+
PartStartEvent(part=BaseToolCallPart() as tool_part)
|
|
822
|
+
| FunctionToolCallEvent(part=tool_part)
|
|
823
|
+
):
|
|
824
|
+
# Store tool call start info for later combination with result
|
|
825
|
+
pending_tcs[tool_part.tool_call_id] = tool_part
|
|
826
|
+
case FunctionToolResultEvent(tool_call_id=call_id) as result_event:
|
|
827
|
+
# Check if we have a pending tool call to combine with
|
|
828
|
+
if call_info := pending_tcs.pop(call_id, None):
|
|
829
|
+
# Create and yield combined event
|
|
830
|
+
combined_event = ToolCallCompleteEvent(
|
|
831
|
+
tool_name=call_info.tool_name,
|
|
832
|
+
tool_call_id=call_id,
|
|
833
|
+
tool_input=call_info.args_as_dict(),
|
|
834
|
+
tool_result=result_event.result.content
|
|
835
|
+
if isinstance(result_event.result, ToolReturnPart)
|
|
836
|
+
else result_event.result,
|
|
837
|
+
agent_name=self.name,
|
|
838
|
+
message_id=message_id,
|
|
839
|
+
)
|
|
840
|
+
yield combined_event
|
|
841
|
+
case AgentRunResultEvent():
|
|
842
|
+
# Capture final result data, Build final response message
|
|
843
|
+
response_time = time.perf_counter() - start_time
|
|
844
|
+
response_msg = await ChatMessage.from_run_result(
|
|
845
|
+
event.result,
|
|
846
|
+
agent_name=self.name,
|
|
847
|
+
message_id=message_id,
|
|
848
|
+
conversation_id=conversation_id or user_msg.conversation_id,
|
|
849
|
+
response_time=response_time,
|
|
850
|
+
)
|
|
851
|
+
except asyncio.CancelledError:
|
|
852
|
+
self.log.info("Stream cancelled via task cancellation")
|
|
853
|
+
self._cancelled = True
|
|
854
|
+
|
|
855
|
+
# Handle cancellation - emit partial message
|
|
856
|
+
if self._cancelled:
|
|
857
|
+
response_time = time.perf_counter() - start_time
|
|
858
|
+
partial_content = "".join(accumulated_text)
|
|
859
|
+
if partial_content:
|
|
860
|
+
partial_content += "\n\n"
|
|
861
|
+
partial_content += "[Request interrupted by user]"
|
|
862
|
+
response_msg = ChatMessage(
|
|
863
|
+
content=partial_content,
|
|
864
|
+
role="assistant",
|
|
865
|
+
name=self.name,
|
|
866
|
+
message_id=message_id,
|
|
867
|
+
conversation_id=conversation_id or user_msg.conversation_id,
|
|
868
|
+
response_time=response_time,
|
|
869
|
+
finish_reason="stop",
|
|
870
|
+
)
|
|
871
|
+
yield StreamCompleteEvent(message=response_msg)
|
|
872
|
+
self._current_stream_task = None
|
|
873
|
+
return
|
|
874
|
+
|
|
875
|
+
# Only finalize if we got a result (stream may exit early on error)
|
|
876
|
+
if response_msg is None:
|
|
877
|
+
msg = "Stream completed without producing a result"
|
|
878
|
+
raise RuntimeError(msg) # noqa: TRY301
|
|
879
|
+
|
|
880
|
+
# Execute post-run hooks
|
|
881
|
+
if self.hooks:
|
|
882
|
+
prompt_str = (
|
|
883
|
+
user_msg.content if isinstance(user_msg.content, str) else str(user_msg.content)
|
|
884
|
+
)
|
|
885
|
+
await self.hooks.run_post_run_hooks(
|
|
886
|
+
agent_name=self.name,
|
|
887
|
+
prompt=prompt_str,
|
|
888
|
+
result=response_msg.content,
|
|
889
|
+
conversation_id=conversation_id,
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
# Apply forwarding logic if needed
|
|
893
|
+
if original_message:
|
|
894
|
+
response_msg = response_msg.forwarded(original_message)
|
|
895
|
+
# Send additional enriched completion event
|
|
896
|
+
yield StreamCompleteEvent(message=response_msg)
|
|
897
|
+
self.message_sent.emit(response_msg)
|
|
898
|
+
await self.log_message(response_msg)
|
|
899
|
+
if store_history:
|
|
900
|
+
conversation.add_chat_messages([user_msg, response_msg])
|
|
901
|
+
await self.connections.route_message(response_msg, wait=wait_for_connections)
|
|
902
|
+
|
|
903
|
+
except Exception as e:
|
|
904
|
+
self.log.exception("Agent stream failed")
|
|
905
|
+
self.run_failed.emit("Agent stream failed", e)
|
|
906
|
+
raise
|
|
907
|
+
finally:
|
|
908
|
+
self._current_stream_task = None
|
|
909
|
+
|
|
910
|
+
async def run_iter(
|
|
911
|
+
self,
|
|
912
|
+
*prompt_groups: Sequence[PromptCompatible],
|
|
913
|
+
output_type: type[OutputDataT] | None = None,
|
|
914
|
+
model: ModelType = None,
|
|
915
|
+
store_history: bool = True,
|
|
916
|
+
wait_for_connections: bool | None = None,
|
|
917
|
+
) -> AsyncIterator[ChatMessage[OutputDataT]]:
|
|
918
|
+
"""Run agent sequentially on multiple prompt groups.
|
|
919
|
+
|
|
920
|
+
Args:
|
|
921
|
+
prompt_groups: Groups of prompts to process sequentially
|
|
922
|
+
output_type: Optional type for structured responses
|
|
923
|
+
model: Optional model override
|
|
924
|
+
store_history: Whether to store in conversation history
|
|
925
|
+
wait_for_connections: Whether to wait for connected agents
|
|
926
|
+
|
|
927
|
+
Yields:
|
|
928
|
+
Response messages in sequence
|
|
929
|
+
|
|
930
|
+
Example:
|
|
931
|
+
questions = [
|
|
932
|
+
["What is your name?"],
|
|
933
|
+
["How old are you?", image1],
|
|
934
|
+
["Describe this image", image2],
|
|
935
|
+
]
|
|
936
|
+
async for response in agent.run_iter(*questions):
|
|
937
|
+
print(response.content)
|
|
938
|
+
"""
|
|
939
|
+
for prompts in prompt_groups:
|
|
940
|
+
response = await self.run(
|
|
941
|
+
*prompts,
|
|
942
|
+
output_type=output_type,
|
|
943
|
+
model=model,
|
|
944
|
+
store_history=store_history,
|
|
945
|
+
wait_for_connections=wait_for_connections,
|
|
946
|
+
)
|
|
947
|
+
yield response # pyright: ignore
|
|
948
|
+
|
|
949
|
+
@method_spawner
|
|
950
|
+
async def run_job(
|
|
951
|
+
self,
|
|
952
|
+
job: Job[TDeps, str | None],
|
|
953
|
+
*,
|
|
954
|
+
store_history: bool = True,
|
|
955
|
+
include_agent_tools: bool = True,
|
|
956
|
+
) -> ChatMessage[OutputDataT]:
|
|
957
|
+
"""Execute a pre-defined task.
|
|
958
|
+
|
|
959
|
+
Args:
|
|
960
|
+
job: Job configuration to execute
|
|
961
|
+
store_history: Whether the message exchange should be added to the
|
|
962
|
+
context window
|
|
963
|
+
include_agent_tools: Whether to include agent tools
|
|
964
|
+
Returns:
|
|
965
|
+
Job execution result
|
|
966
|
+
|
|
967
|
+
Raises:
|
|
968
|
+
JobError: If task execution fails
|
|
969
|
+
ValueError: If task configuration is invalid
|
|
970
|
+
"""
|
|
971
|
+
from agentpool.tasks import JobError
|
|
972
|
+
|
|
973
|
+
if job.required_dependency is not None:
|
|
974
|
+
agent_ctx = self.get_context()
|
|
975
|
+
if not isinstance(agent_ctx.data, job.required_dependency):
|
|
976
|
+
msg = (
|
|
977
|
+
f"Agent dependencies ({type(agent_ctx.data)}) "
|
|
978
|
+
f"don't match job requirement ({job.required_dependency})"
|
|
979
|
+
)
|
|
980
|
+
raise JobError(msg)
|
|
981
|
+
|
|
982
|
+
# Load task knowledge
|
|
983
|
+
if job.knowledge:
|
|
984
|
+
# Add knowledge sources to context
|
|
985
|
+
for source in list(job.knowledge.paths):
|
|
986
|
+
await self.conversation.load_context_source(source)
|
|
987
|
+
for prompt in job.knowledge.prompts:
|
|
988
|
+
await self.conversation.load_context_source(prompt)
|
|
989
|
+
try:
|
|
990
|
+
# Register task tools temporarily
|
|
991
|
+
tools = job.get_tools()
|
|
992
|
+
async with self.tools.temporary_tools(tools, exclusive=not include_agent_tools):
|
|
993
|
+
# Execute job with job-specific tools
|
|
994
|
+
return await self.run(await job.get_prompt(), store_history=store_history)
|
|
995
|
+
|
|
996
|
+
except Exception as e:
|
|
997
|
+
self.log.exception("Task execution failed", error=str(e))
|
|
998
|
+
msg = f"Task execution failed: {e}"
|
|
999
|
+
raise JobError(msg) from e
|
|
1000
|
+
|
|
1001
|
+
async def run_in_background(
|
|
1002
|
+
self,
|
|
1003
|
+
*prompt: PromptCompatible,
|
|
1004
|
+
max_count: int | None = None,
|
|
1005
|
+
interval: float = 1.0,
|
|
1006
|
+
**kwargs: Any,
|
|
1007
|
+
) -> asyncio.Task[ChatMessage[OutputDataT] | None]:
|
|
1008
|
+
"""Run agent continuously in background with prompt or dynamic prompt function.
|
|
1009
|
+
|
|
1010
|
+
Args:
|
|
1011
|
+
prompt: Static prompt or function that generates prompts
|
|
1012
|
+
max_count: Maximum number of runs (None = infinite)
|
|
1013
|
+
interval: Seconds between runs
|
|
1014
|
+
**kwargs: Arguments passed to run()
|
|
1015
|
+
"""
|
|
1016
|
+
self._infinite = max_count is None
|
|
1017
|
+
|
|
1018
|
+
async def _continuous() -> ChatMessage[Any]:
|
|
1019
|
+
count = 0
|
|
1020
|
+
self.log.debug("Starting continuous run", max_count=max_count, interval=interval)
|
|
1021
|
+
latest = None
|
|
1022
|
+
while (max_count is None or count < max_count) and not self._cancelled:
|
|
1023
|
+
try:
|
|
1024
|
+
agent_ctx = self.get_context()
|
|
1025
|
+
current_prompts = [
|
|
1026
|
+
call_with_context(p, agent_ctx, **kwargs) if callable(p) else p
|
|
1027
|
+
for p in prompt
|
|
1028
|
+
]
|
|
1029
|
+
self.log.debug("Generated prompt", iteration=count)
|
|
1030
|
+
latest = await self.run(current_prompts, **kwargs)
|
|
1031
|
+
self.log.debug("Run continuous result", iteration=count)
|
|
1032
|
+
|
|
1033
|
+
count += 1
|
|
1034
|
+
await anyio.sleep(interval)
|
|
1035
|
+
except asyncio.CancelledError:
|
|
1036
|
+
self.log.debug("Continuous run cancelled")
|
|
1037
|
+
break
|
|
1038
|
+
except Exception:
|
|
1039
|
+
# Check if we were cancelled (may surface as other exceptions)
|
|
1040
|
+
if self._cancelled:
|
|
1041
|
+
self.log.debug("Continuous run cancelled via flag")
|
|
1042
|
+
break
|
|
1043
|
+
count += 1
|
|
1044
|
+
self.log.exception("Background run failed")
|
|
1045
|
+
await anyio.sleep(interval)
|
|
1046
|
+
self.log.debug("Continuous run completed", iterations=count)
|
|
1047
|
+
return latest # type: ignore[return-value]
|
|
1048
|
+
|
|
1049
|
+
await self.stop() # Cancel any existing background task
|
|
1050
|
+
self._cancelled = False # Reset cancellation flag for new run
|
|
1051
|
+
task = asyncio.create_task(_continuous(), name=f"background_{self.name}")
|
|
1052
|
+
self.log.debug("Started background task", task_name=task.get_name())
|
|
1053
|
+
self._background_task = task
|
|
1054
|
+
return task
|
|
1055
|
+
|
|
1056
|
+
async def stop(self) -> None:
|
|
1057
|
+
"""Stop continuous execution if running."""
|
|
1058
|
+
self._cancelled = True # Signal cancellation via flag
|
|
1059
|
+
if self._background_task and not self._background_task.done():
|
|
1060
|
+
self._background_task.cancel()
|
|
1061
|
+
with suppress(asyncio.CancelledError): # Expected when we cancel the task
|
|
1062
|
+
await self._background_task
|
|
1063
|
+
self._background_task = None
|
|
1064
|
+
|
|
1065
|
+
async def wait(self) -> ChatMessage[OutputDataT]:
|
|
1066
|
+
"""Wait for background execution to complete."""
|
|
1067
|
+
if not self._background_task:
|
|
1068
|
+
msg = "No background task running"
|
|
1069
|
+
raise RuntimeError(msg)
|
|
1070
|
+
if self._infinite:
|
|
1071
|
+
msg = "Cannot wait on infinite execution"
|
|
1072
|
+
raise RuntimeError(msg)
|
|
1073
|
+
try:
|
|
1074
|
+
return await self._background_task
|
|
1075
|
+
finally:
|
|
1076
|
+
self._background_task = None
|
|
1077
|
+
|
|
1078
|
+
async def share(
|
|
1079
|
+
self,
|
|
1080
|
+
target: Agent[TDeps, Any],
|
|
1081
|
+
*,
|
|
1082
|
+
tools: list[str] | None = None,
|
|
1083
|
+
history: bool | int | None = None, # bool or number of messages
|
|
1084
|
+
token_limit: int | None = None,
|
|
1085
|
+
) -> None:
|
|
1086
|
+
"""Share capabilities and knowledge with another agent.
|
|
1087
|
+
|
|
1088
|
+
Args:
|
|
1089
|
+
target: Agent to share with
|
|
1090
|
+
tools: List of tool names to share
|
|
1091
|
+
history: Share conversation history:
|
|
1092
|
+
- True: Share full history
|
|
1093
|
+
- int: Number of most recent messages to share
|
|
1094
|
+
- None: Don't share history
|
|
1095
|
+
token_limit: Optional max tokens for history
|
|
1096
|
+
|
|
1097
|
+
Raises:
|
|
1098
|
+
ValueError: If requested items don't exist
|
|
1099
|
+
RuntimeError: If runtime not available for resources
|
|
1100
|
+
"""
|
|
1101
|
+
# Share tools if requested
|
|
1102
|
+
for name in tools or []:
|
|
1103
|
+
tool = await self.tools.get_tool(name)
|
|
1104
|
+
meta = {"shared_from": self.name}
|
|
1105
|
+
target.tools.register_tool(tool.callable, metadata=meta)
|
|
1106
|
+
|
|
1107
|
+
# Share history if requested
|
|
1108
|
+
if history:
|
|
1109
|
+
history_text = await self.conversation.format_history(
|
|
1110
|
+
max_tokens=token_limit,
|
|
1111
|
+
num_messages=history if isinstance(history, int) else None,
|
|
1112
|
+
)
|
|
1113
|
+
target.conversation.add_context_message(
|
|
1114
|
+
history_text, source=self.name, metadata={"type": "shared_history"}
|
|
1115
|
+
)
|
|
1116
|
+
|
|
1117
|
+
def register_worker(
|
|
1118
|
+
self,
|
|
1119
|
+
worker: MessageNode[Any, Any],
|
|
1120
|
+
*,
|
|
1121
|
+
name: str | None = None,
|
|
1122
|
+
reset_history_on_run: bool = True,
|
|
1123
|
+
pass_message_history: bool = False,
|
|
1124
|
+
) -> Tool:
|
|
1125
|
+
"""Register another agent as a worker tool."""
|
|
1126
|
+
return self.tools.register_worker(
|
|
1127
|
+
worker,
|
|
1128
|
+
name=name,
|
|
1129
|
+
reset_history_on_run=reset_history_on_run,
|
|
1130
|
+
pass_message_history=pass_message_history,
|
|
1131
|
+
parent=self if pass_message_history else None,
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
def set_model(self, model: ModelType) -> None:
|
|
1135
|
+
"""Set the model for this agent.
|
|
1136
|
+
|
|
1137
|
+
Args:
|
|
1138
|
+
model: New model to use (name or instance)
|
|
1139
|
+
|
|
1140
|
+
"""
|
|
1141
|
+
self._model = infer_model(model) if isinstance(model, str) else model
|
|
1142
|
+
|
|
1143
|
+
async def set_tool_confirmation_mode(self, mode: ToolConfirmationMode) -> None:
|
|
1144
|
+
"""Set the tool confirmation mode for this agent.
|
|
1145
|
+
|
|
1146
|
+
Args:
|
|
1147
|
+
mode: Tool confirmation mode:
|
|
1148
|
+
- "always": Always require confirmation for all tools
|
|
1149
|
+
- "never": Never require confirmation
|
|
1150
|
+
- "per_tool": Use individual tool settings
|
|
1151
|
+
"""
|
|
1152
|
+
self.tool_confirmation_mode = mode
|
|
1153
|
+
self.log.info("Tool confirmation mode changed", mode=mode)
|
|
1154
|
+
|
|
1155
|
+
async def reset(self) -> None:
|
|
1156
|
+
"""Reset agent state (conversation history and tool states)."""
|
|
1157
|
+
old_tools = await self.tools.list_tools()
|
|
1158
|
+
self.conversation.clear()
|
|
1159
|
+
await self.tools.reset_states()
|
|
1160
|
+
new_tools = await self.tools.list_tools()
|
|
1161
|
+
|
|
1162
|
+
event = self.AgentReset(
|
|
1163
|
+
agent_name=self.name,
|
|
1164
|
+
previous_tools=old_tools,
|
|
1165
|
+
new_tools=new_tools,
|
|
1166
|
+
)
|
|
1167
|
+
self.agent_reset.emit(event)
|
|
1168
|
+
|
|
1169
|
+
async def get_stats(self) -> MessageStats:
|
|
1170
|
+
"""Get message statistics (async version)."""
|
|
1171
|
+
messages = await self.get_message_history()
|
|
1172
|
+
return MessageStats(messages=messages)
|
|
1173
|
+
|
|
1174
|
+
@asynccontextmanager
|
|
1175
|
+
async def temporary_state[T](
|
|
1176
|
+
self,
|
|
1177
|
+
*,
|
|
1178
|
+
system_prompts: list[AnyPromptType] | None = None,
|
|
1179
|
+
output_type: type[T] | None = None,
|
|
1180
|
+
replace_prompts: bool = False,
|
|
1181
|
+
tools: list[ToolType] | None = None,
|
|
1182
|
+
replace_tools: bool = False,
|
|
1183
|
+
history: list[AnyPromptType] | SessionQuery | None = None,
|
|
1184
|
+
replace_history: bool = False,
|
|
1185
|
+
pause_routing: bool = False,
|
|
1186
|
+
model: ModelType | None = None,
|
|
1187
|
+
) -> AsyncIterator[Self | Agent[T]]:
|
|
1188
|
+
"""Temporarily modify agent state.
|
|
1189
|
+
|
|
1190
|
+
Args:
|
|
1191
|
+
system_prompts: Temporary system prompts to use
|
|
1192
|
+
output_type: Temporary output type to use
|
|
1193
|
+
replace_prompts: Whether to replace existing prompts
|
|
1194
|
+
tools: Temporary tools to make available
|
|
1195
|
+
replace_tools: Whether to replace existing tools
|
|
1196
|
+
history: Conversation history (prompts or query)
|
|
1197
|
+
replace_history: Whether to replace existing history
|
|
1198
|
+
pause_routing: Whether to pause message routing
|
|
1199
|
+
model: Temporary model override
|
|
1200
|
+
"""
|
|
1201
|
+
old_model = self._model
|
|
1202
|
+
if output_type:
|
|
1203
|
+
old_type = self._output_type
|
|
1204
|
+
self.to_structured(output_type)
|
|
1205
|
+
async with AsyncExitStack() as stack:
|
|
1206
|
+
if system_prompts is not None: # System prompts
|
|
1207
|
+
await stack.enter_async_context(
|
|
1208
|
+
self.sys_prompts.temporary_prompt(system_prompts, exclusive=replace_prompts)
|
|
1209
|
+
)
|
|
1210
|
+
|
|
1211
|
+
if tools is not None: # Tools
|
|
1212
|
+
await stack.enter_async_context(
|
|
1213
|
+
self.tools.temporary_tools(tools, exclusive=replace_tools)
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
if history is not None: # History
|
|
1217
|
+
await stack.enter_async_context(
|
|
1218
|
+
self.conversation.temporary_state(history, replace_history=replace_history)
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
if pause_routing: # Routing
|
|
1222
|
+
await stack.enter_async_context(self.connections.paused_routing())
|
|
1223
|
+
|
|
1224
|
+
elif model is not None: # Model
|
|
1225
|
+
self._model = infer_model(model) if isinstance(model, str) else model
|
|
1226
|
+
|
|
1227
|
+
try:
|
|
1228
|
+
yield self
|
|
1229
|
+
finally: # Restore model
|
|
1230
|
+
if model is not None and old_model:
|
|
1231
|
+
self._model = old_model
|
|
1232
|
+
if output_type:
|
|
1233
|
+
self.to_structured(old_type)
|
|
1234
|
+
|
|
1235
|
+
async def validate_against(
|
|
1236
|
+
self,
|
|
1237
|
+
prompt: str,
|
|
1238
|
+
criteria: type[OutputDataT],
|
|
1239
|
+
**kwargs: Any,
|
|
1240
|
+
) -> bool:
|
|
1241
|
+
"""Check if agent's response satisfies stricter criteria."""
|
|
1242
|
+
result = await self.run(prompt, **kwargs)
|
|
1243
|
+
try:
|
|
1244
|
+
criteria.model_validate(result.content.model_dump()) # type: ignore
|
|
1245
|
+
except ValidationError:
|
|
1246
|
+
return False
|
|
1247
|
+
else:
|
|
1248
|
+
return True
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
if __name__ == "__main__":
|
|
1252
|
+
import logging
|
|
1253
|
+
|
|
1254
|
+
logfire.configure()
|
|
1255
|
+
logfire.instrument_pydantic_ai()
|
|
1256
|
+
logging.basicConfig(handlers=[logfire.LogfireLoggingHandler()])
|
|
1257
|
+
sys_prompt = "Open browser with google,"
|
|
1258
|
+
_model = "openai:gpt-5-nano"
|
|
1259
|
+
|
|
1260
|
+
async def handle_events(ctx: RunContext, event: Any) -> None:
|
|
1261
|
+
print(f"[EVENT] {type(event).__name__}: {event}")
|
|
1262
|
+
|
|
1263
|
+
agent = Agent(model=_model, tools=["webbrowser.open"], event_handlers=[handle_events])
|
|
1264
|
+
result = agent.run.sync(sys_prompt) # type: ignore[attr-defined]
|