agentpool 2.1.9__py3-none-any.whl → 2.5.0__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.
- acp/__init__.py +13 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +20 -50
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/stdio.py +39 -9
- acp/task/supervisor.py +2 -2
- acp/transports.py +362 -2
- acp/utils.py +17 -4
- agentpool/__init__.py +6 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +407 -277
- agentpool/agents/acp_agent/acp_converters.py +196 -38
- agentpool/agents/acp_agent/client_handler.py +191 -26
- agentpool/agents/acp_agent/session_state.py +17 -6
- agentpool/agents/agent.py +607 -572
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +176 -110
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +632 -17
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
- agentpool/agents/claude_code_agent/converters.py +74 -143
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +24 -0
- agentpool/agents/events/builtin_handlers.py +67 -1
- agentpool/agents/events/event_emitter.py +32 -2
- agentpool/agents/events/events.py +104 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +67 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +56 -21
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +10 -6
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +136 -289
- agentpool/delegation/team.py +58 -57
- agentpool/delegation/teamrun.py +51 -55
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +76 -32
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +34 -54
- agentpool/mcp_server/registries/official_registry_client.py +35 -1
- agentpool/mcp_server/tool_bridge.py +186 -139
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +99 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +54 -35
- agentpool/messaging/processing.py +12 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -24
- agentpool/models/acp_agents/mcp_capable.py +126 -157
- agentpool/models/acp_agents/non_mcp.py +129 -95
- agentpool/models/agents.py +98 -76
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +144 -19
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +113 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +11 -1
- agentpool/resource_providers/aggregating.py +56 -5
- agentpool/resource_providers/base.py +70 -4
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +89 -12
- agentpool/resource_providers/plan_provider.py +228 -46
- agentpool/resource_providers/pool.py +7 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +4 -2
- agentpool/sessions/__init__.py +4 -1
- agentpool/sessions/manager.py +33 -5
- agentpool/sessions/models.py +59 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +572 -49
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +538 -20
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +616 -2
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
- agentpool-2.5.0.dist-info/RECORD +579 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +24 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +100 -21
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +42 -5
- agentpool_commands/agents.py +75 -2
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +80 -30
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +132 -6
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +82 -38
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -22
- agentpool_config/toolsets.py +109 -233
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +234 -181
- agentpool_server/acp_server/commands/acp_commands.py +151 -156
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +24 -90
- agentpool_server/acp_server/session.py +173 -331
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +401 -0
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +19 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +975 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +421 -0
- agentpool_server/opencode_server/models/__init__.py +250 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +72 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +821 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +44 -0
- agentpool_server/opencode_server/models/message.py +179 -0
- agentpool_server/opencode_server/models/parts.py +323 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +111 -0
- agentpool_server/opencode_server/routes/__init__.py +29 -0
- agentpool_server/opencode_server/routes/agent_routes.py +473 -0
- agentpool_server/opencode_server/routes/app_routes.py +202 -0
- agentpool_server/opencode_server/routes/config_routes.py +302 -0
- agentpool_server/opencode_server/routes/file_routes.py +571 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +761 -0
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +300 -0
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +1276 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +475 -0
- agentpool_server/opencode_server/state.py +151 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +12 -0
- agentpool_storage/base.py +184 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/claude_provider/provider.py +1089 -0
- agentpool_storage/file_provider.py +278 -15
- agentpool_storage/memory_provider.py +193 -12
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +26 -6
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +269 -3
- agentpool_storage/sql_provider/utils.py +12 -13
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -12
- agentpool_toolsets/builtin/code.py +96 -57
- agentpool_toolsets/builtin/debug.py +118 -48
- agentpool_toolsets/builtin/execution_environment.py +115 -230
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +9 -4
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +99 -7
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +511 -0
- agentpool_toolsets/mcp_run_toolset.py +87 -12
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool-2.1.9.dist-info/RECORD +0 -474
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/text_log_provider.py +0 -275
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
agentpool/agents/agent.py
CHANGED
|
@@ -3,83 +3,86 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
from collections.abc import Awaitable
|
|
7
|
-
from contextlib import AsyncExitStack, asynccontextmanager
|
|
8
|
-
from dataclasses import
|
|
6
|
+
from collections.abc import Awaitable
|
|
7
|
+
from contextlib import AsyncExitStack, asynccontextmanager
|
|
8
|
+
from dataclasses import replace
|
|
9
9
|
import time
|
|
10
10
|
from typing import TYPE_CHECKING, Any, Self, TypedDict, TypeVar, overload
|
|
11
11
|
from uuid import uuid4
|
|
12
12
|
|
|
13
13
|
from anyenv import method_spawner
|
|
14
|
-
import anyio
|
|
15
|
-
from llmling_models import function_to_model, infer_model
|
|
16
14
|
import logfire
|
|
17
|
-
from psygnal import Signal
|
|
18
15
|
from pydantic import ValidationError
|
|
19
16
|
from pydantic._internal import _typing_extra
|
|
20
17
|
from pydantic_ai import (
|
|
21
18
|
Agent as PydanticAgent,
|
|
22
|
-
AgentRunResultEvent,
|
|
23
19
|
BaseToolCallPart,
|
|
20
|
+
CallToolsNode,
|
|
24
21
|
FunctionToolCallEvent,
|
|
25
22
|
FunctionToolResultEvent,
|
|
26
|
-
|
|
23
|
+
ModelRequestNode,
|
|
27
24
|
PartStartEvent,
|
|
28
25
|
RunContext,
|
|
29
|
-
TextPart,
|
|
30
|
-
TextPartDelta,
|
|
31
26
|
ToolReturnPart,
|
|
32
27
|
)
|
|
28
|
+
from pydantic_ai.models import Model
|
|
33
29
|
|
|
34
30
|
from agentpool.agents.base_agent import BaseAgent
|
|
35
|
-
from agentpool.agents.events import
|
|
31
|
+
from agentpool.agents.events import (
|
|
32
|
+
RunStartedEvent,
|
|
33
|
+
StreamCompleteEvent,
|
|
34
|
+
ToolCallCompleteEvent,
|
|
35
|
+
)
|
|
36
|
+
from agentpool.agents.events.processors import FileTracker
|
|
37
|
+
from agentpool.agents.modes import ModeInfo
|
|
36
38
|
from agentpool.log import get_logger
|
|
37
|
-
from agentpool.messaging import ChatMessage, MessageHistory
|
|
38
|
-
from agentpool.messaging.processing import prepare_prompts
|
|
39
|
+
from agentpool.messaging import ChatMessage, MessageHistory
|
|
39
40
|
from agentpool.prompts.convert import convert_prompts
|
|
40
41
|
from agentpool.storage import StorageManager
|
|
41
|
-
from agentpool.talk.stats import MessageStats
|
|
42
42
|
from agentpool.tools import Tool, ToolManager
|
|
43
43
|
from agentpool.tools.exceptions import ToolError
|
|
44
|
-
from agentpool.utils.inspection import
|
|
45
|
-
from agentpool.utils.
|
|
44
|
+
from agentpool.utils.inspection import get_argument_key
|
|
45
|
+
from agentpool.utils.pydantic_ai_helpers import safe_args_as_dict
|
|
46
46
|
from agentpool.utils.result_utils import to_type
|
|
47
47
|
from agentpool.utils.streams import merge_queue_into_iterator
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
TResult = TypeVar("TResult")
|
|
51
|
-
|
|
52
|
-
|
|
53
50
|
if TYPE_CHECKING:
|
|
54
|
-
from collections.abc import AsyncIterator, Coroutine, Sequence
|
|
55
|
-
from datetime import datetime
|
|
51
|
+
from collections.abc import AsyncIterator, Callable, Coroutine, Sequence
|
|
56
52
|
from types import TracebackType
|
|
57
53
|
|
|
58
54
|
from exxec import ExecutionEnvironment
|
|
59
|
-
from
|
|
55
|
+
from llmling_models_config import AnyModelConfig
|
|
56
|
+
from pydantic_ai import UsageLimits, UserContent
|
|
57
|
+
from pydantic_ai.builtin_tools import AbstractBuiltinTool
|
|
60
58
|
from pydantic_ai.output import OutputSpec
|
|
61
59
|
from pydantic_ai.settings import ModelSettings
|
|
60
|
+
from slashed import BaseCommand
|
|
61
|
+
from tokonomics.model_discovery import ProviderType
|
|
62
|
+
from tokonomics.model_discovery.model_info import ModelInfo
|
|
62
63
|
from toprompt import AnyPromptType
|
|
63
64
|
from upathtools import JoinablePathLike
|
|
64
65
|
|
|
65
66
|
from agentpool.agents import AgentContext
|
|
66
67
|
from agentpool.agents.events import RichAgentStreamEvent
|
|
68
|
+
from agentpool.agents.modes import ModeCategory
|
|
67
69
|
from agentpool.common_types import (
|
|
68
|
-
AgentName,
|
|
69
70
|
BuiltinEventHandlerType,
|
|
70
71
|
EndStrategy,
|
|
71
72
|
IndividualEventHandler,
|
|
72
73
|
ModelType,
|
|
73
74
|
ProcessorCallback,
|
|
74
|
-
PromptCompatible,
|
|
75
75
|
SessionIdType,
|
|
76
76
|
ToolType,
|
|
77
77
|
)
|
|
78
|
-
from agentpool.delegation import AgentPool
|
|
78
|
+
from agentpool.delegation import AgentPool
|
|
79
79
|
from agentpool.hooks import AgentHooks
|
|
80
|
-
from agentpool.
|
|
80
|
+
from agentpool.messaging import MessageNode
|
|
81
|
+
from agentpool.models.agents import NativeAgentConfig, ToolMode
|
|
82
|
+
from agentpool.models.manifest import AgentsManifest
|
|
81
83
|
from agentpool.prompts.prompts import PromptType
|
|
82
84
|
from agentpool.resource_providers import ResourceProvider
|
|
85
|
+
from agentpool.tools.base import FunctionTool
|
|
83
86
|
from agentpool.ui.base import InputProvider
|
|
84
87
|
from agentpool_config.knowledge import Knowledge
|
|
85
88
|
from agentpool_config.mcp_server import MCPServerConfig
|
|
@@ -92,6 +95,36 @@ logger = get_logger(__name__)
|
|
|
92
95
|
# OutputDataT = TypeVar('OutputDataT', default=str, covariant=True)
|
|
93
96
|
NoneType = type(None)
|
|
94
97
|
|
|
98
|
+
TResult = TypeVar("TResult")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _extract_text_from_messages(
|
|
102
|
+
messages: list[Any], include_interruption_note: bool = False
|
|
103
|
+
) -> str:
|
|
104
|
+
"""Extract text content from pydantic-ai messages.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
messages: List of ModelRequest/ModelResponse messages
|
|
108
|
+
include_interruption_note: Whether to append interruption notice
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Concatenated text content from all ModelResponse TextParts
|
|
112
|
+
"""
|
|
113
|
+
from pydantic_ai.messages import ModelResponse, TextPart as PydanticTextPart
|
|
114
|
+
|
|
115
|
+
content = "".join(
|
|
116
|
+
part.content
|
|
117
|
+
for msg in messages
|
|
118
|
+
if isinstance(msg, ModelResponse)
|
|
119
|
+
for part in msg.parts
|
|
120
|
+
if isinstance(part, PydanticTextPart)
|
|
121
|
+
)
|
|
122
|
+
if include_interruption_note:
|
|
123
|
+
if content:
|
|
124
|
+
content += "\n\n"
|
|
125
|
+
content += "[Request interrupted by user]"
|
|
126
|
+
return content
|
|
127
|
+
|
|
95
128
|
|
|
96
129
|
class AgentKwargs(TypedDict, total=False):
|
|
97
130
|
"""Keyword arguments for configuring an Agent instance."""
|
|
@@ -111,9 +144,11 @@ class AgentKwargs(TypedDict, total=False):
|
|
|
111
144
|
input_provider: InputProvider | None
|
|
112
145
|
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None
|
|
113
146
|
env: ExecutionEnvironment | None
|
|
114
|
-
|
|
147
|
+
|
|
115
148
|
hooks: AgentHooks | None
|
|
116
149
|
model_settings: ModelSettings | None
|
|
150
|
+
usage_limits: UsageLimits | None
|
|
151
|
+
providers: Sequence[ProviderType] | None
|
|
117
152
|
|
|
118
153
|
|
|
119
154
|
class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
@@ -122,25 +157,13 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
122
157
|
Generically typed with: Agent[Type of Dependencies, Type of Result]
|
|
123
158
|
"""
|
|
124
159
|
|
|
125
|
-
|
|
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__(
|
|
160
|
+
def __init__( # noqa: PLR0915
|
|
138
161
|
# we dont use AgentKwargs here so that we can work with explicit ones in the ctor
|
|
139
162
|
self,
|
|
140
163
|
name: str = "agentpool",
|
|
141
164
|
*,
|
|
142
165
|
deps_type: type[TDeps] | None = None,
|
|
143
|
-
model: ModelType
|
|
166
|
+
model: ModelType,
|
|
144
167
|
output_type: OutputSpec[OutputDataT] = str, # type: ignore[assignment]
|
|
145
168
|
# context: AgentContext[TDeps] | None = None,
|
|
146
169
|
session: SessionIdType | SessionQuery | MemoryConfig | bool | int = None,
|
|
@@ -164,9 +187,12 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
164
187
|
knowledge: Knowledge | None = None,
|
|
165
188
|
agent_config: NativeAgentConfig | None = None,
|
|
166
189
|
env: ExecutionEnvironment | None = None,
|
|
167
|
-
auto_cache: AutoCache = "off",
|
|
168
190
|
hooks: AgentHooks | None = None,
|
|
169
191
|
tool_confirmation_mode: ToolConfirmationMode = "per_tool",
|
|
192
|
+
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
|
|
193
|
+
usage_limits: UsageLimits | None = None,
|
|
194
|
+
providers: Sequence[ProviderType] | None = None,
|
|
195
|
+
commands: Sequence[BaseCommand] | None = None,
|
|
170
196
|
) -> None:
|
|
171
197
|
"""Initialize agent.
|
|
172
198
|
|
|
@@ -206,17 +232,24 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
206
232
|
knowledge: Knowledge sources for this agent
|
|
207
233
|
agent_config: Agent configuration
|
|
208
234
|
env: Execution environment for code/command execution and filesystem access
|
|
209
|
-
auto_cache: Automatic caching configuration ("off", "5m", or "1h")
|
|
210
235
|
hooks: AgentHooks instance for intercepting agent behavior at run and tool events
|
|
211
236
|
tool_confirmation_mode: Tool confirmation mode
|
|
237
|
+
builtin_tools: PydanticAI builtin tools (WebSearchTool, CodeExecutionTool, etc.)
|
|
238
|
+
usage_limits: Per-request usage limits (applied to each run() call independently,
|
|
239
|
+
not cumulative across the session)
|
|
240
|
+
providers: Model providers for model discovery (e.g., ["openai", "anthropic"]).
|
|
241
|
+
Defaults to ["models.dev"] if not specified.
|
|
242
|
+
commands: Slash commands
|
|
212
243
|
"""
|
|
244
|
+
from llmling_models_config import StringModelConfig
|
|
245
|
+
|
|
213
246
|
from agentpool.agents.interactions import Interactions
|
|
214
247
|
from agentpool.agents.sys_prompts import SystemPrompts
|
|
215
248
|
from agentpool.models.agents import NativeAgentConfig
|
|
216
249
|
from agentpool.prompts.conversion_manager import ConversionManager
|
|
250
|
+
from agentpool_commands.pool import CompactCommand
|
|
217
251
|
from agentpool_config.session import MemoryConfig
|
|
218
252
|
|
|
219
|
-
self._infinite = False
|
|
220
253
|
self.deps_type = deps_type
|
|
221
254
|
self.model_settings = model_settings
|
|
222
255
|
memory_cfg = (
|
|
@@ -226,7 +259,10 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
226
259
|
all_mcp_servers = list(mcp_servers) if mcp_servers else []
|
|
227
260
|
if agent_config and agent_config.mcp_servers:
|
|
228
261
|
all_mcp_servers.extend(agent_config.get_mcp_servers())
|
|
229
|
-
|
|
262
|
+
# Add CompactCommand - only makes sense for Native Agent (has own history)
|
|
263
|
+
# Other agents (ClaudeCode, ACP, AGUI) don't control their history directly
|
|
264
|
+
all_commands = list(commands) if commands else []
|
|
265
|
+
all_commands.append(CompactCommand())
|
|
230
266
|
# Call base class with shared parameters
|
|
231
267
|
super().__init__(
|
|
232
268
|
name=name,
|
|
@@ -241,11 +277,28 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
241
277
|
output_type=to_type(output_type), # type: ignore[arg-type]
|
|
242
278
|
tool_confirmation_mode=tool_confirmation_mode,
|
|
243
279
|
event_handlers=event_handlers,
|
|
280
|
+
commands=all_commands,
|
|
244
281
|
)
|
|
245
282
|
|
|
246
283
|
# Store config for context creation
|
|
247
|
-
|
|
284
|
+
# Convert model to proper config type for NativeAgentConfig
|
|
248
285
|
|
|
286
|
+
config_model: AnyModelConfig
|
|
287
|
+
if isinstance(model, Model):
|
|
288
|
+
config_model = StringModelConfig(
|
|
289
|
+
identifier=model.model_name,
|
|
290
|
+
**({"model_settings": model._settings} if model._settings else {}),
|
|
291
|
+
)
|
|
292
|
+
elif isinstance(model, str):
|
|
293
|
+
config_model = StringModelConfig(
|
|
294
|
+
identifier=model,
|
|
295
|
+
**({"model_settings": model_settings} if model_settings else {}),
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
config_model = model
|
|
299
|
+
self._agent_config = agent_config or NativeAgentConfig(name=name, model=config_model)
|
|
300
|
+
# Store builtin tools for pydantic-ai
|
|
301
|
+
self._builtin_tools = list(builtin_tools) if builtin_tools else []
|
|
249
302
|
# Override tools with Agent-specific ToolManager (with tools and tool_mode)
|
|
250
303
|
all_tools = list(tools or [])
|
|
251
304
|
self.tools = ToolManager(all_tools, tool_mode=tool_mode)
|
|
@@ -253,7 +306,6 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
253
306
|
self.tools.add_provider(toolset_provider)
|
|
254
307
|
aggregating_provider = self.mcp.get_aggregating_provider()
|
|
255
308
|
self.tools.add_provider(aggregating_provider)
|
|
256
|
-
|
|
257
309
|
# Override conversation with Agent-specific MessageHistory (with storage, etc.)
|
|
258
310
|
resources = list(resources)
|
|
259
311
|
if knowledge:
|
|
@@ -265,14 +317,17 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
265
317
|
session_config=memory_cfg,
|
|
266
318
|
resources=resources,
|
|
267
319
|
)
|
|
268
|
-
|
|
320
|
+
if isinstance(model, str):
|
|
321
|
+
self._model, settings = self._resolve_model_string(model)
|
|
322
|
+
if settings:
|
|
323
|
+
self.model_settings = settings
|
|
324
|
+
else:
|
|
325
|
+
self._model = model
|
|
269
326
|
self._retries = retries
|
|
270
327
|
self._end_strategy: EndStrategy = end_strategy
|
|
271
328
|
self._output_retries = output_retries
|
|
272
329
|
self.parallel_init = parallel_init
|
|
273
|
-
self._background_task: asyncio.Task[ChatMessage[Any]] | None = None
|
|
274
330
|
self.talk = Interactions(self)
|
|
275
|
-
|
|
276
331
|
# Set up system prompts
|
|
277
332
|
all_prompts: list[AnyPromptType] = []
|
|
278
333
|
if isinstance(system_prompt, (list, tuple)):
|
|
@@ -280,12 +335,12 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
280
335
|
elif system_prompt:
|
|
281
336
|
all_prompts.append(system_prompt)
|
|
282
337
|
self.sys_prompts = SystemPrompts(all_prompts, prompt_manager=self._manifest.prompt_manager)
|
|
283
|
-
|
|
284
338
|
# Store hooks
|
|
285
339
|
self.hooks = hooks
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
340
|
+
# Store default usage limits
|
|
341
|
+
self._default_usage_limits = usage_limits
|
|
342
|
+
# Store providers for model discovery
|
|
343
|
+
self._providers = list(providers) if providers else None
|
|
289
344
|
|
|
290
345
|
def __repr__(self) -> str:
|
|
291
346
|
desc = f", {self.description!r}" if self.description else ""
|
|
@@ -300,6 +355,143 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
300
355
|
parts.extend([await self.tools.__prompt__(), self.conversation.__prompt__()])
|
|
301
356
|
return "\n".join(parts)
|
|
302
357
|
|
|
358
|
+
@classmethod
|
|
359
|
+
def from_config( # noqa: PLR0915
|
|
360
|
+
cls,
|
|
361
|
+
config: NativeAgentConfig,
|
|
362
|
+
*,
|
|
363
|
+
name: str | None = None,
|
|
364
|
+
manifest: AgentsManifest | None = None,
|
|
365
|
+
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
366
|
+
input_provider: InputProvider | None = None,
|
|
367
|
+
agent_pool: AgentPool[Any] | None = None,
|
|
368
|
+
deps_type: type[TDeps] | None = None,
|
|
369
|
+
) -> Self:
|
|
370
|
+
"""Create a native Agent from a config object.
|
|
371
|
+
|
|
372
|
+
This is the preferred way to instantiate an Agent from configuration.
|
|
373
|
+
Handles system prompt resolution, model resolution, toolsets setup, etc.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
config: Native agent configuration
|
|
377
|
+
name: Optional name override (used for manifest lookups, defaults to config.name)
|
|
378
|
+
manifest: Optional manifest for resolving prompts, models, output types.
|
|
379
|
+
If not provided, uses agent_pool.manifest or creates empty one.
|
|
380
|
+
event_handlers: Optional event handlers (merged with config handlers)
|
|
381
|
+
input_provider: Optional input provider for user interactions
|
|
382
|
+
agent_pool: Optional agent pool for coordination
|
|
383
|
+
deps_type: Optional dependency type
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Configured Agent instance
|
|
387
|
+
"""
|
|
388
|
+
from pathlib import Path
|
|
389
|
+
|
|
390
|
+
from agentpool.models.manifest import AgentsManifest
|
|
391
|
+
from agentpool.utils.result_utils import to_type
|
|
392
|
+
from agentpool_config.system_prompts import (
|
|
393
|
+
FilePromptConfig,
|
|
394
|
+
FunctionPromptConfig,
|
|
395
|
+
LibraryPromptConfig,
|
|
396
|
+
StaticPromptConfig,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Get manifest from pool or create empty one
|
|
400
|
+
if manifest is None:
|
|
401
|
+
manifest = agent_pool.manifest if agent_pool else AgentsManifest()
|
|
402
|
+
|
|
403
|
+
# Use provided name, fall back to config.name, then default
|
|
404
|
+
name = name or config.name or "agent"
|
|
405
|
+
|
|
406
|
+
# Normalize system_prompt to a list for iteration
|
|
407
|
+
sys_prompts: list[str] = []
|
|
408
|
+
prompt_source = config.system_prompt
|
|
409
|
+
if prompt_source is not None:
|
|
410
|
+
prompts_to_process = (
|
|
411
|
+
[prompt_source] if isinstance(prompt_source, str) else prompt_source
|
|
412
|
+
)
|
|
413
|
+
for prompt in prompts_to_process:
|
|
414
|
+
match prompt:
|
|
415
|
+
case (str() as sys_prompt) | StaticPromptConfig(content=sys_prompt):
|
|
416
|
+
sys_prompts.append(sys_prompt)
|
|
417
|
+
case FilePromptConfig(path=path, variables=variables):
|
|
418
|
+
template_path = Path(path)
|
|
419
|
+
if not template_path.is_absolute() and config.config_file_path:
|
|
420
|
+
template_path = Path(config.config_file_path).parent / path
|
|
421
|
+
template_content = template_path.read_text("utf-8")
|
|
422
|
+
if variables:
|
|
423
|
+
from jinja2 import Template
|
|
424
|
+
|
|
425
|
+
template = Template(template_content)
|
|
426
|
+
content = template.render(**variables)
|
|
427
|
+
else:
|
|
428
|
+
content = template_content
|
|
429
|
+
sys_prompts.append(content)
|
|
430
|
+
case LibraryPromptConfig(reference=reference):
|
|
431
|
+
try:
|
|
432
|
+
content = manifest.prompt_manager.get.sync(reference)
|
|
433
|
+
sys_prompts.append(content)
|
|
434
|
+
except Exception as e:
|
|
435
|
+
msg = f"Failed to load library prompt {reference!r} for agent {name}"
|
|
436
|
+
logger.exception(msg)
|
|
437
|
+
raise ValueError(msg) from e
|
|
438
|
+
case FunctionPromptConfig(function=function, arguments=arguments):
|
|
439
|
+
content = function(**arguments)
|
|
440
|
+
sys_prompts.append(content)
|
|
441
|
+
|
|
442
|
+
# Prepare toolsets list
|
|
443
|
+
toolsets_list = config.get_toolsets()
|
|
444
|
+
if config_tool_provider := config.get_tool_provider():
|
|
445
|
+
toolsets_list.append(config_tool_provider)
|
|
446
|
+
# Convert workers config to a toolset (backwards compatibility)
|
|
447
|
+
if config.workers:
|
|
448
|
+
from agentpool_toolsets.builtin.workers import WorkersTools
|
|
449
|
+
|
|
450
|
+
workers_provider = WorkersTools(workers=list(config.workers), name="workers")
|
|
451
|
+
toolsets_list.append(workers_provider)
|
|
452
|
+
# Resolve output type
|
|
453
|
+
agent_output_type = manifest.get_output_type(name) or str
|
|
454
|
+
resolved_output_type = to_type(agent_output_type, manifest.responses)
|
|
455
|
+
# Merge event handlers
|
|
456
|
+
config_handlers = config.get_event_handlers()
|
|
457
|
+
merged_handlers: list[IndividualEventHandler | BuiltinEventHandlerType] = [
|
|
458
|
+
*config_handlers,
|
|
459
|
+
*(event_handlers or []),
|
|
460
|
+
]
|
|
461
|
+
# Resolve model
|
|
462
|
+
resolved_model = manifest.resolve_model(config.model)
|
|
463
|
+
model = resolved_model.get_model()
|
|
464
|
+
model_settings = resolved_model.get_model_settings()
|
|
465
|
+
# Extract builtin tools
|
|
466
|
+
builtin_tools = config.get_builtin_tools()
|
|
467
|
+
return cls(
|
|
468
|
+
model=model,
|
|
469
|
+
model_settings=model_settings,
|
|
470
|
+
system_prompt=sys_prompts,
|
|
471
|
+
name=name,
|
|
472
|
+
display_name=config.display_name,
|
|
473
|
+
deps_type=deps_type,
|
|
474
|
+
env=config.environment.get_provider() if config.environment else None,
|
|
475
|
+
description=config.description,
|
|
476
|
+
retries=config.retries,
|
|
477
|
+
session=config.get_session_config(),
|
|
478
|
+
output_retries=config.output_retries,
|
|
479
|
+
end_strategy=config.end_strategy,
|
|
480
|
+
agent_config=config,
|
|
481
|
+
input_provider=input_provider,
|
|
482
|
+
output_type=resolved_output_type, # type: ignore[arg-type]
|
|
483
|
+
event_handlers=merged_handlers or None,
|
|
484
|
+
agent_pool=agent_pool,
|
|
485
|
+
tool_mode=config.tool_mode,
|
|
486
|
+
knowledge=config.knowledge,
|
|
487
|
+
toolsets=toolsets_list,
|
|
488
|
+
hooks=config.hooks.get_agent_hooks() if config.hooks else None,
|
|
489
|
+
tool_confirmation_mode=config.requires_tool_confirmation,
|
|
490
|
+
builtin_tools=builtin_tools or None,
|
|
491
|
+
usage_limits=config.usage_limits,
|
|
492
|
+
providers=config.model_providers,
|
|
493
|
+
)
|
|
494
|
+
|
|
303
495
|
async def __aenter__(self) -> Self:
|
|
304
496
|
"""Enter async context and set up MCP servers."""
|
|
305
497
|
try:
|
|
@@ -327,57 +519,6 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
327
519
|
"""Exit async context."""
|
|
328
520
|
await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
329
521
|
|
|
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
522
|
@overload
|
|
382
523
|
@classmethod
|
|
383
524
|
def from_callback(
|
|
@@ -416,16 +557,18 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
416
557
|
name: Optional name for the agent
|
|
417
558
|
kwargs: Additional arguments for agent
|
|
418
559
|
"""
|
|
560
|
+
from llmling_models import function_to_model
|
|
561
|
+
|
|
419
562
|
name = name or callback.__name__ or "processor"
|
|
420
563
|
model = function_to_model(callback)
|
|
421
|
-
|
|
564
|
+
output_type = _typing_extra.get_function_type_hints(callback).get("return")
|
|
422
565
|
if ( # If async, unwrap from Awaitable
|
|
423
|
-
|
|
424
|
-
and hasattr(
|
|
425
|
-
and
|
|
566
|
+
output_type
|
|
567
|
+
and hasattr(output_type, "__origin__")
|
|
568
|
+
and output_type.__origin__ is Awaitable
|
|
426
569
|
):
|
|
427
|
-
|
|
428
|
-
return Agent(model=model, name=name, output_type=
|
|
570
|
+
output_type = output_type.__args__[0]
|
|
571
|
+
return Agent(model=model, name=name, output_type=output_type or str, **kwargs)
|
|
429
572
|
|
|
430
573
|
@property
|
|
431
574
|
def name(self) -> str:
|
|
@@ -457,15 +600,34 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
457
600
|
data=data,
|
|
458
601
|
)
|
|
459
602
|
|
|
603
|
+
def _resolve_model_string(self, model: str) -> tuple[Model, ModelSettings | None]:
|
|
604
|
+
"""Resolve a model string, checking variants first.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
model: Model identifier or variant name
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
Tuple of (Model instance, ModelSettings or None)
|
|
611
|
+
Settings are only returned for variants.
|
|
612
|
+
"""
|
|
613
|
+
from llmling_models import infer_model
|
|
614
|
+
|
|
615
|
+
# Check if it's a variant
|
|
616
|
+
if self.agent_pool and model in self.agent_pool.manifest.model_variants:
|
|
617
|
+
config = self.agent_pool.manifest.model_variants[model]
|
|
618
|
+
return config.get_model(), config.get_model_settings()
|
|
619
|
+
# Regular model string - no settings
|
|
620
|
+
return infer_model(model), None
|
|
621
|
+
|
|
460
622
|
def to_structured[NewOutputDataT](
|
|
461
623
|
self,
|
|
462
624
|
output_type: type[NewOutputDataT],
|
|
463
|
-
*,
|
|
464
|
-
tool_name: str | None = None,
|
|
465
|
-
tool_description: str | None = None,
|
|
466
625
|
) -> Agent[TDeps, NewOutputDataT]:
|
|
467
626
|
"""Convert this agent to a structured agent.
|
|
468
627
|
|
|
628
|
+
Warning: This method mutates the agent in place and breaks caching.
|
|
629
|
+
Changing output type modifies tool definitions sent to the API.
|
|
630
|
+
|
|
469
631
|
Args:
|
|
470
632
|
output_type: Type for structured responses. Can be:
|
|
471
633
|
- A Python type (Pydantic model)
|
|
@@ -473,23 +635,17 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
473
635
|
tool_description: Optional override for result tool description
|
|
474
636
|
|
|
475
637
|
Returns:
|
|
476
|
-
|
|
638
|
+
Self (same instance, not a copy)
|
|
477
639
|
"""
|
|
478
640
|
self.log.debug("Setting result type", output_type=output_type)
|
|
479
641
|
self._output_type = to_type(output_type) # type: ignore[assignment]
|
|
480
642
|
return self # type: ignore
|
|
481
643
|
|
|
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
644
|
@property
|
|
487
645
|
def model_name(self) -> str | None:
|
|
488
646
|
"""Get the model name in a consistent format (provider:model_name)."""
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
return f"{self._model.system}:{self._model.model_name}"
|
|
492
|
-
return None
|
|
647
|
+
# Construct full model ID with provider prefix (e.g., "anthropic:claude-haiku-4-5")
|
|
648
|
+
return f"{self._model.system}:{self._model.model_name}" if self._model else None
|
|
493
649
|
|
|
494
650
|
def to_tool(
|
|
495
651
|
self,
|
|
@@ -500,7 +656,7 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
500
656
|
pass_message_history: bool = False,
|
|
501
657
|
parent: Agent[Any, Any] | None = None,
|
|
502
658
|
**_kwargs: Any,
|
|
503
|
-
) ->
|
|
659
|
+
) -> FunctionTool[OutputDataT]:
|
|
504
660
|
"""Create a tool from this agent.
|
|
505
661
|
|
|
506
662
|
Args:
|
|
@@ -517,7 +673,7 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
517
673
|
raise ToolError(msg)
|
|
518
674
|
|
|
519
675
|
if reset_history_on_run:
|
|
520
|
-
self.conversation.clear()
|
|
676
|
+
await self.conversation.clear()
|
|
521
677
|
|
|
522
678
|
history = None
|
|
523
679
|
if pass_message_history and parent:
|
|
@@ -550,8 +706,7 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
550
706
|
|
|
551
707
|
async def get_agentlet[AgentOutputType](
|
|
552
708
|
self,
|
|
553
|
-
|
|
554
|
-
model: ModelType,
|
|
709
|
+
model: ModelType | None,
|
|
555
710
|
output_type: type[AgentOutputType] | None,
|
|
556
711
|
input_provider: InputProvider | None = None,
|
|
557
712
|
) -> PydanticAgent[TDeps, AgentOutputType]:
|
|
@@ -560,10 +715,13 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
560
715
|
|
|
561
716
|
from agentpool.agents.tool_wrapping import wrap_tool
|
|
562
717
|
|
|
563
|
-
tools = await self.tools.get_tools(state="enabled"
|
|
718
|
+
tools = await self.tools.get_tools(state="enabled")
|
|
564
719
|
final_type = to_type(output_type) if output_type not in [None, str] else self._output_type
|
|
565
720
|
actual_model = model or self._model
|
|
566
|
-
|
|
721
|
+
if isinstance(actual_model, str):
|
|
722
|
+
model_, _settings = self._resolve_model_string(actual_model)
|
|
723
|
+
else:
|
|
724
|
+
model_ = actual_model
|
|
567
725
|
agent = PydanticAgent(
|
|
568
726
|
name=self.name,
|
|
569
727
|
model=model_,
|
|
@@ -574,6 +732,7 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
574
732
|
output_retries=self._output_retries,
|
|
575
733
|
deps_type=self.deps_type or NoneType,
|
|
576
734
|
output_type=final_type,
|
|
735
|
+
builtin_tools=self._builtin_tools,
|
|
577
736
|
)
|
|
578
737
|
|
|
579
738
|
base_context = self.get_context()
|
|
@@ -594,168 +753,46 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
594
753
|
|
|
595
754
|
return agent # type: ignore[return-value]
|
|
596
755
|
|
|
597
|
-
|
|
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
|
|
756
|
+
async def _stream_events( # noqa: PLR0915
|
|
704
757
|
self,
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
758
|
+
prompts: list[UserContent],
|
|
759
|
+
*,
|
|
760
|
+
user_msg: ChatMessage[Any],
|
|
761
|
+
effective_parent_id: str | None,
|
|
709
762
|
store_history: bool = True,
|
|
710
|
-
usage_limits: UsageLimits | None = None,
|
|
711
763
|
message_id: str | None = None,
|
|
712
764
|
conversation_id: str | None = None,
|
|
765
|
+
parent_id: str | None = None,
|
|
713
766
|
message_history: MessageHistory | None = None,
|
|
714
767
|
input_provider: InputProvider | None = None,
|
|
715
768
|
wait_for_connections: bool | None = None,
|
|
716
769
|
deps: TDeps | None = None,
|
|
717
|
-
|
|
770
|
+
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
718
771
|
) -> AsyncIterator[RichAgentStreamEvent[OutputDataT]]:
|
|
719
|
-
|
|
772
|
+
from anyenv import MultiEventHandler
|
|
773
|
+
from pydantic_graph import End
|
|
720
774
|
|
|
721
|
-
|
|
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.
|
|
775
|
+
from agentpool.agents.events import resolve_event_handlers
|
|
739
776
|
|
|
740
|
-
Raises:
|
|
741
|
-
UnexpectedModelBehavior: If the model fails or behaves unexpectedly
|
|
742
|
-
"""
|
|
743
777
|
conversation = message_history if message_history is not None else self.conversation
|
|
778
|
+
# Use provided event handlers or fall back to agent's handlers
|
|
779
|
+
if event_handlers is not None:
|
|
780
|
+
handler: MultiEventHandler[IndividualEventHandler] = MultiEventHandler(
|
|
781
|
+
resolve_event_handlers(event_handlers)
|
|
782
|
+
)
|
|
783
|
+
else:
|
|
784
|
+
handler = self.event_handler
|
|
744
785
|
message_id = message_id or str(uuid4())
|
|
745
786
|
run_id = str(uuid4())
|
|
746
|
-
|
|
747
|
-
self.
|
|
787
|
+
# Reset cancellation state
|
|
788
|
+
self._cancelled = False
|
|
789
|
+
# Initialize conversation_id on first run and log to storage
|
|
790
|
+
# Conversation ID initialization handled by BaseAgent
|
|
791
|
+
processed_prompts = prompts
|
|
792
|
+
await self.message_received.emit(user_msg)
|
|
748
793
|
start_time = time.perf_counter()
|
|
749
794
|
history_list = conversation.get_history()
|
|
750
795
|
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
796
|
# Execute pre-run hooks
|
|
760
797
|
if self.hooks:
|
|
761
798
|
pre_run_result = await self.hooks.run_pre_run_hooks(
|
|
@@ -763,188 +800,167 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
763
800
|
prompt=user_msg.content
|
|
764
801
|
if isinstance(user_msg.content, str)
|
|
765
802
|
else str(user_msg.content),
|
|
766
|
-
conversation_id=conversation_id,
|
|
803
|
+
conversation_id=self.conversation_id,
|
|
767
804
|
)
|
|
768
805
|
if pre_run_result.get("decision") == "deny":
|
|
769
806
|
reason = pre_run_result.get("reason", "Blocked by pre-run hook")
|
|
770
807
|
msg = f"Run blocked: {reason}"
|
|
771
808
|
raise RuntimeError(msg)
|
|
772
809
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
810
|
+
assert self.conversation_id is not None # Initialized by BaseAgent.run_stream()
|
|
811
|
+
run_started = RunStartedEvent(
|
|
812
|
+
thread_id=self.conversation_id, run_id=run_id, agent_name=self.name
|
|
813
|
+
)
|
|
814
|
+
await handler(None, run_started)
|
|
815
|
+
yield run_started
|
|
816
|
+
|
|
817
|
+
agentlet = await self.get_agentlet(None, self._output_type, input_provider)
|
|
818
|
+
content = await convert_prompts(processed_prompts)
|
|
819
|
+
response_msg: ChatMessage[Any] | None = None
|
|
820
|
+
# Prepend pending context parts (content is already pydantic-ai format)
|
|
821
|
+
converted = [*pending_parts, *content]
|
|
822
|
+
history = [m for run in history_list for m in run.to_pydantic_ai()]
|
|
823
|
+
# Track tool call starts to combine with results later
|
|
824
|
+
pending_tcs: dict[str, BaseToolCallPart] = {}
|
|
825
|
+
file_tracker = FileTracker()
|
|
826
|
+
async with agentlet.iter(
|
|
827
|
+
converted,
|
|
828
|
+
deps=deps, # type: ignore[arg-type]
|
|
829
|
+
message_history=history,
|
|
830
|
+
usage_limits=self._default_usage_limits,
|
|
831
|
+
) as agent_run:
|
|
832
|
+
try:
|
|
833
|
+
async for node in agent_run:
|
|
834
|
+
if self._cancelled:
|
|
835
|
+
self.log.info("Stream cancelled by user")
|
|
836
|
+
break
|
|
837
|
+
if isinstance(node, End):
|
|
838
|
+
break
|
|
794
839
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
|
840
|
+
# Stream events from model request node
|
|
841
|
+
if isinstance(node, ModelRequestNode):
|
|
842
|
+
async with (
|
|
843
|
+
node.stream(agent_run.ctx) as agent_stream,
|
|
844
|
+
merge_queue_into_iterator(
|
|
845
|
+
agent_stream, # type: ignore[arg-type]
|
|
846
|
+
self._event_queue,
|
|
847
|
+
) as merged,
|
|
848
|
+
):
|
|
849
|
+
async for event in file_tracker(merged):
|
|
850
|
+
if self._cancelled:
|
|
851
|
+
break
|
|
852
|
+
await handler(None, event)
|
|
853
|
+
yield event
|
|
854
|
+
combined = self._process_tool_event(event, pending_tcs, message_id)
|
|
855
|
+
if combined:
|
|
856
|
+
await handler(None, combined)
|
|
857
|
+
yield combined
|
|
858
|
+
|
|
859
|
+
# Stream events from tool call node
|
|
860
|
+
elif isinstance(node, CallToolsNode):
|
|
861
|
+
async with (
|
|
862
|
+
node.stream(agent_run.ctx) as tool_stream,
|
|
863
|
+
merge_queue_into_iterator(tool_stream, self._event_queue) as merged,
|
|
864
|
+
):
|
|
865
|
+
async for event in file_tracker(merged):
|
|
866
|
+
if self._cancelled:
|
|
867
|
+
break
|
|
868
|
+
await handler(None, event)
|
|
869
|
+
yield event
|
|
870
|
+
combined = self._process_tool_event(event, pending_tcs, message_id)
|
|
871
|
+
if combined:
|
|
872
|
+
await handler(None, combined)
|
|
873
|
+
yield combined
|
|
874
|
+
except asyncio.CancelledError:
|
|
875
|
+
self.log.info("Stream cancelled via task cancellation")
|
|
876
|
+
self._cancelled = True
|
|
877
|
+
|
|
878
|
+
# Build response message
|
|
879
|
+
response_time = time.perf_counter() - start_time
|
|
856
880
|
if self._cancelled:
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
partial_content += "\n\n"
|
|
861
|
-
partial_content += "[Request interrupted by user]"
|
|
881
|
+
partial_content = _extract_text_from_messages(
|
|
882
|
+
agent_run.all_messages(), include_interruption_note=True
|
|
883
|
+
)
|
|
862
884
|
response_msg = ChatMessage(
|
|
863
885
|
content=partial_content,
|
|
864
886
|
role="assistant",
|
|
865
887
|
name=self.name,
|
|
866
888
|
message_id=message_id,
|
|
867
|
-
conversation_id=
|
|
889
|
+
conversation_id=self.conversation_id,
|
|
890
|
+
parent_id=user_msg.message_id,
|
|
868
891
|
response_time=response_time,
|
|
869
892
|
finish_reason="stop",
|
|
870
893
|
)
|
|
871
|
-
|
|
872
|
-
|
|
894
|
+
complete_event = StreamCompleteEvent(message=response_msg)
|
|
895
|
+
await handler(None, complete_event)
|
|
896
|
+
yield complete_event
|
|
873
897
|
return
|
|
874
898
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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(
|
|
899
|
+
if agent_run.result:
|
|
900
|
+
response_msg = await ChatMessage.from_run_result(
|
|
901
|
+
agent_run.result,
|
|
886
902
|
agent_name=self.name,
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
903
|
+
message_id=message_id,
|
|
904
|
+
conversation_id=self.conversation_id,
|
|
905
|
+
parent_id=user_msg.message_id,
|
|
906
|
+
response_time=response_time,
|
|
907
|
+
metadata=file_tracker.get_metadata(),
|
|
890
908
|
)
|
|
909
|
+
else:
|
|
910
|
+
msg = "Stream completed without producing a result"
|
|
911
|
+
raise RuntimeError(msg)
|
|
891
912
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
self.
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
913
|
+
# Execute post-run hooks
|
|
914
|
+
if self.hooks:
|
|
915
|
+
prompt_str = (
|
|
916
|
+
user_msg.content if isinstance(user_msg.content, str) else str(user_msg.content)
|
|
917
|
+
)
|
|
918
|
+
await self.hooks.run_post_run_hooks(
|
|
919
|
+
agent_name=self.name,
|
|
920
|
+
prompt=prompt_str,
|
|
921
|
+
result=response_msg.content,
|
|
922
|
+
conversation_id=self.conversation_id,
|
|
923
|
+
)
|
|
902
924
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
finally:
|
|
908
|
-
self._current_stream_task = None
|
|
925
|
+
# Send additional enriched completion event
|
|
926
|
+
complete_event = StreamCompleteEvent(message=response_msg)
|
|
927
|
+
await handler(None, complete_event)
|
|
928
|
+
yield complete_event
|
|
909
929
|
|
|
910
|
-
|
|
930
|
+
def _process_tool_event(
|
|
911
931
|
self,
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
) -> AsyncIterator[ChatMessage[OutputDataT]]:
|
|
918
|
-
"""Run agent sequentially on multiple prompt groups.
|
|
932
|
+
event: RichAgentStreamEvent[Any],
|
|
933
|
+
pending_tool_calls: dict[str, BaseToolCallPart],
|
|
934
|
+
message_id: str,
|
|
935
|
+
) -> ToolCallCompleteEvent | None:
|
|
936
|
+
"""Process tool-related events and return combined event when complete.
|
|
919
937
|
|
|
920
938
|
Args:
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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)
|
|
939
|
+
event: The streaming event to process
|
|
940
|
+
pending_tool_calls: Dict tracking in-progress tool calls by ID
|
|
941
|
+
message_id: Message ID for the combined event
|
|
942
|
+
|
|
943
|
+
Returns:
|
|
944
|
+
ToolCallCompleteEvent if a tool call completed, None otherwise
|
|
938
945
|
"""
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
946
|
+
match event:
|
|
947
|
+
case PartStartEvent(part=BaseToolCallPart() as tool_part):
|
|
948
|
+
pending_tool_calls[tool_part.tool_call_id] = tool_part
|
|
949
|
+
case FunctionToolCallEvent(part=tool_part):
|
|
950
|
+
pending_tool_calls[tool_part.tool_call_id] = tool_part
|
|
951
|
+
case FunctionToolResultEvent(tool_call_id=call_id) as result_event:
|
|
952
|
+
if call_info := pending_tool_calls.pop(call_id, None):
|
|
953
|
+
return ToolCallCompleteEvent(
|
|
954
|
+
tool_name=call_info.tool_name,
|
|
955
|
+
tool_call_id=call_id,
|
|
956
|
+
tool_input=safe_args_as_dict(call_info),
|
|
957
|
+
tool_result=result_event.result.content
|
|
958
|
+
if isinstance(result_event.result, ToolReturnPart)
|
|
959
|
+
else result_event.result,
|
|
960
|
+
agent_name=self.name,
|
|
961
|
+
message_id=message_id,
|
|
962
|
+
)
|
|
963
|
+
return None
|
|
948
964
|
|
|
949
965
|
@method_spawner
|
|
950
966
|
async def run_job(
|
|
@@ -998,122 +1014,6 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
998
1014
|
msg = f"Task execution failed: {e}"
|
|
999
1015
|
raise JobError(msg) from e
|
|
1000
1016
|
|
|
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
1017
|
def register_worker(
|
|
1118
1018
|
self,
|
|
1119
1019
|
worker: MessageNode[Any, Any],
|
|
@@ -1131,14 +1031,19 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
1131
1031
|
parent=self if pass_message_history else None,
|
|
1132
1032
|
)
|
|
1133
1033
|
|
|
1134
|
-
def set_model(self, model:
|
|
1034
|
+
async def set_model(self, model: Model | str) -> None:
|
|
1135
1035
|
"""Set the model for this agent.
|
|
1136
1036
|
|
|
1137
1037
|
Args:
|
|
1138
1038
|
model: New model to use (name or instance)
|
|
1139
1039
|
|
|
1140
1040
|
"""
|
|
1141
|
-
|
|
1041
|
+
if isinstance(model, str):
|
|
1042
|
+
self._model, settings = self._resolve_model_string(model)
|
|
1043
|
+
if settings:
|
|
1044
|
+
self.model_settings = settings
|
|
1045
|
+
else:
|
|
1046
|
+
self._model = model
|
|
1142
1047
|
|
|
1143
1048
|
async def set_tool_confirmation_mode(self, mode: ToolConfirmationMode) -> None:
|
|
1144
1049
|
"""Set the tool confirmation mode for this agent.
|
|
@@ -1152,25 +1057,6 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
1152
1057
|
self.tool_confirmation_mode = mode
|
|
1153
1058
|
self.log.info("Tool confirmation mode changed", mode=mode)
|
|
1154
1059
|
|
|
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
1060
|
@asynccontextmanager
|
|
1175
1061
|
async def temporary_state[T](
|
|
1176
1062
|
self,
|
|
@@ -1199,6 +1085,7 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
1199
1085
|
model: Temporary model override
|
|
1200
1086
|
"""
|
|
1201
1087
|
old_model = self._model
|
|
1088
|
+
old_settings = self.model_settings
|
|
1202
1089
|
if output_type:
|
|
1203
1090
|
old_type = self._output_type
|
|
1204
1091
|
self.to_structured(output_type)
|
|
@@ -1221,14 +1108,21 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
1221
1108
|
if pause_routing: # Routing
|
|
1222
1109
|
await stack.enter_async_context(self.connections.paused_routing())
|
|
1223
1110
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1111
|
+
if model is not None: # Model
|
|
1112
|
+
if isinstance(model, str):
|
|
1113
|
+
self._model, settings = self._resolve_model_string(model)
|
|
1114
|
+
if settings:
|
|
1115
|
+
self.model_settings = settings
|
|
1116
|
+
else:
|
|
1117
|
+
self._model = model
|
|
1226
1118
|
|
|
1227
1119
|
try:
|
|
1228
1120
|
yield self
|
|
1229
|
-
finally: # Restore model
|
|
1230
|
-
if model is not None
|
|
1231
|
-
|
|
1121
|
+
finally: # Restore model and settings
|
|
1122
|
+
if model is not None:
|
|
1123
|
+
if old_model:
|
|
1124
|
+
self._model = old_model
|
|
1125
|
+
self.model_settings = old_settings
|
|
1232
1126
|
if output_type:
|
|
1233
1127
|
self.to_structured(old_type)
|
|
1234
1128
|
|
|
@@ -1247,6 +1141,147 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
1247
1141
|
else:
|
|
1248
1142
|
return True
|
|
1249
1143
|
|
|
1144
|
+
async def get_available_models(self) -> list[ModelInfo] | None:
|
|
1145
|
+
"""Get available models for this agent.
|
|
1146
|
+
|
|
1147
|
+
Uses tokonomics model discovery to fetch models from configured providers.
|
|
1148
|
+
Defaults to openai, anthropic, and gemini if no providers specified.
|
|
1149
|
+
|
|
1150
|
+
Returns:
|
|
1151
|
+
List of tokonomics ModelInfo, or None if discovery fails
|
|
1152
|
+
"""
|
|
1153
|
+
from datetime import timedelta
|
|
1154
|
+
|
|
1155
|
+
from tokonomics.model_discovery import get_all_models
|
|
1156
|
+
|
|
1157
|
+
try:
|
|
1158
|
+
max_age = timedelta(days=200)
|
|
1159
|
+
return await get_all_models(
|
|
1160
|
+
providers=self._providers or ["models.dev"], max_age=max_age
|
|
1161
|
+
)
|
|
1162
|
+
except Exception:
|
|
1163
|
+
self.log.exception("Failed to discover models")
|
|
1164
|
+
return None
|
|
1165
|
+
|
|
1166
|
+
async def get_modes(self) -> list[ModeCategory]:
|
|
1167
|
+
"""Get available mode categories for this agent.
|
|
1168
|
+
|
|
1169
|
+
Native agents expose permission modes and model selection.
|
|
1170
|
+
|
|
1171
|
+
Returns:
|
|
1172
|
+
List of ModeCategory for permissions and models
|
|
1173
|
+
"""
|
|
1174
|
+
from agentpool.agents.modes import ModeCategory, ModeInfo
|
|
1175
|
+
|
|
1176
|
+
categories: list[ModeCategory] = []
|
|
1177
|
+
|
|
1178
|
+
# Permission modes
|
|
1179
|
+
mode_id_map = {
|
|
1180
|
+
"per_tool": "default",
|
|
1181
|
+
"always": "default",
|
|
1182
|
+
"never": "acceptEdits",
|
|
1183
|
+
}
|
|
1184
|
+
current_id = mode_id_map.get(self.tool_confirmation_mode, "default")
|
|
1185
|
+
|
|
1186
|
+
categories.append(
|
|
1187
|
+
ModeCategory(
|
|
1188
|
+
id="permissions",
|
|
1189
|
+
name="Permissions",
|
|
1190
|
+
available_modes=[
|
|
1191
|
+
ModeInfo(
|
|
1192
|
+
id="default",
|
|
1193
|
+
name="Default",
|
|
1194
|
+
description="Require confirmation for tools marked as needing it",
|
|
1195
|
+
category_id="permissions",
|
|
1196
|
+
),
|
|
1197
|
+
ModeInfo(
|
|
1198
|
+
id="acceptEdits",
|
|
1199
|
+
name="Accept Edits",
|
|
1200
|
+
description="Auto-approve all tool calls without confirmation",
|
|
1201
|
+
category_id="permissions",
|
|
1202
|
+
),
|
|
1203
|
+
],
|
|
1204
|
+
current_mode_id=current_id,
|
|
1205
|
+
category="mode",
|
|
1206
|
+
)
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
# Model selection
|
|
1210
|
+
models = await self.get_available_models()
|
|
1211
|
+
if models:
|
|
1212
|
+
current_model = self.model_name or (models[0].id if models else "")
|
|
1213
|
+
categories.append(
|
|
1214
|
+
ModeCategory(
|
|
1215
|
+
id="model",
|
|
1216
|
+
name="Model",
|
|
1217
|
+
available_modes=[
|
|
1218
|
+
ModeInfo(
|
|
1219
|
+
id=m.id,
|
|
1220
|
+
name=m.name or m.id,
|
|
1221
|
+
description=m.description or "",
|
|
1222
|
+
category_id="model",
|
|
1223
|
+
)
|
|
1224
|
+
for m in models
|
|
1225
|
+
],
|
|
1226
|
+
current_mode_id=current_model,
|
|
1227
|
+
category="model",
|
|
1228
|
+
)
|
|
1229
|
+
)
|
|
1230
|
+
|
|
1231
|
+
return categories
|
|
1232
|
+
|
|
1233
|
+
async def set_mode(self, mode: ModeInfo | str, category_id: str | None = None) -> None:
|
|
1234
|
+
"""Set a mode for this agent.
|
|
1235
|
+
|
|
1236
|
+
Native agents support:
|
|
1237
|
+
- "permissions" category: default, acceptEdits
|
|
1238
|
+
- "model" category: any available model ID
|
|
1239
|
+
|
|
1240
|
+
Args:
|
|
1241
|
+
mode: Mode to activate - ModeInfo object or mode ID string
|
|
1242
|
+
category_id: Category ID ("permissions" or "model")
|
|
1243
|
+
|
|
1244
|
+
Raises:
|
|
1245
|
+
ValueError: If mode_id or category_id is invalid
|
|
1246
|
+
"""
|
|
1247
|
+
# Extract mode_id and category from ModeInfo if provided
|
|
1248
|
+
if isinstance(mode, ModeInfo):
|
|
1249
|
+
mode_id = mode.id
|
|
1250
|
+
category_id = category_id or mode.category_id
|
|
1251
|
+
else:
|
|
1252
|
+
mode_id = mode
|
|
1253
|
+
|
|
1254
|
+
# Default to permissions if no category specified
|
|
1255
|
+
if category_id is None:
|
|
1256
|
+
category_id = "permissions"
|
|
1257
|
+
|
|
1258
|
+
if category_id == "permissions":
|
|
1259
|
+
# Map mode_id to confirmation mode
|
|
1260
|
+
mode_map: dict[str, ToolConfirmationMode] = {
|
|
1261
|
+
"default": "per_tool",
|
|
1262
|
+
"acceptEdits": "never",
|
|
1263
|
+
}
|
|
1264
|
+
if mode_id not in mode_map:
|
|
1265
|
+
msg = f"Unknown permission mode: {mode_id}. Available: {list(mode_map.keys())}"
|
|
1266
|
+
raise ValueError(msg)
|
|
1267
|
+
await self.set_tool_confirmation_mode(mode_map[mode_id])
|
|
1268
|
+
|
|
1269
|
+
elif category_id == "model":
|
|
1270
|
+
# Validate model exists
|
|
1271
|
+
models = await self.get_available_models()
|
|
1272
|
+
if models:
|
|
1273
|
+
valid_ids = {m.id for m in models}
|
|
1274
|
+
if mode_id not in valid_ids:
|
|
1275
|
+
msg = f"Unknown model: {mode_id}. Available: {valid_ids}"
|
|
1276
|
+
raise ValueError(msg)
|
|
1277
|
+
# Set the model using set_model method
|
|
1278
|
+
await self.set_model(mode_id)
|
|
1279
|
+
self.log.info("Model changed", model=mode_id)
|
|
1280
|
+
|
|
1281
|
+
else:
|
|
1282
|
+
msg = f"Unknown category: {category_id}. Available: permissions, model"
|
|
1283
|
+
raise ValueError(msg)
|
|
1284
|
+
|
|
1250
1285
|
|
|
1251
1286
|
if __name__ == "__main__":
|
|
1252
1287
|
import logging
|
|
@@ -1261,4 +1296,4 @@ if __name__ == "__main__":
|
|
|
1261
1296
|
print(f"[EVENT] {type(event).__name__}: {event}")
|
|
1262
1297
|
|
|
1263
1298
|
agent = Agent(model=_model, tools=["webbrowser.open"], event_handlers=[handle_events])
|
|
1264
|
-
result = agent.run.sync(sys_prompt)
|
|
1299
|
+
result = agent.run.sync(sys_prompt)
|