flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__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 flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
"""Manages a pool of connections for a particular server."""
|
|
2
|
-
|
|
3
|
-
import copy
|
|
4
|
-
from abc import ABC, abstractmethod
|
|
5
|
-
from asyncio import Lock
|
|
6
|
-
from typing import Any, Generic, TypeVar
|
|
7
|
-
|
|
8
|
-
from opentelemetry import trace
|
|
9
|
-
from pydantic import (
|
|
10
|
-
BaseModel,
|
|
11
|
-
ConfigDict,
|
|
12
|
-
Field,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
from flock.core.logging.logging import get_logger
|
|
16
|
-
from flock.core.mcp.flock_mcp_tool import FlockMCPTool
|
|
17
|
-
from flock.core.mcp.mcp_client import (
|
|
18
|
-
FlockMCPClient,
|
|
19
|
-
)
|
|
20
|
-
from flock.core.mcp.mcp_config import FlockMCPConfiguration
|
|
21
|
-
|
|
22
|
-
logger = get_logger("mcp.client_manager")
|
|
23
|
-
tracer = trace.get_tracer(__name__)
|
|
24
|
-
|
|
25
|
-
TClient = TypeVar("TClient", bound="FlockMCPClient")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class FlockMCPClientManager(BaseModel, ABC, Generic[TClient]):
|
|
29
|
-
"""Handles a Pool of MCPClients of type TClient."""
|
|
30
|
-
|
|
31
|
-
client_config: FlockMCPConfiguration = Field(
|
|
32
|
-
..., description="Configuration for clients."
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
lock: Lock = Field(
|
|
36
|
-
default_factory=Lock,
|
|
37
|
-
description="Lock for mutex access.",
|
|
38
|
-
exclude=True,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
clients: dict[str, dict[str, FlockMCPClient]] = Field(
|
|
42
|
-
default_factory=dict,
|
|
43
|
-
exclude=True,
|
|
44
|
-
description="Internal Store for the clients.",
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# --- Pydantic v2 Configuratioin ---
|
|
48
|
-
model_config = ConfigDict(
|
|
49
|
-
arbitrary_types_allowed=True,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
@abstractmethod
|
|
53
|
-
async def make_client(
|
|
54
|
-
self,
|
|
55
|
-
additional_params: dict[str, Any] | None = None,
|
|
56
|
-
) -> type[TClient]:
|
|
57
|
-
"""Instantiate-but don't connect yet-a fresh client of the concrete subtype."""
|
|
58
|
-
# default implementation
|
|
59
|
-
pass
|
|
60
|
-
|
|
61
|
-
async def get_client(
|
|
62
|
-
self,
|
|
63
|
-
agent_id: str,
|
|
64
|
-
run_id: str,
|
|
65
|
-
additional_params: dict[str, Any] | None = None,
|
|
66
|
-
) -> type[TClient]:
|
|
67
|
-
"""Provides a client from the pool."""
|
|
68
|
-
# Attempt to get a client from the client store.
|
|
69
|
-
# clients are stored like this: agent_id -> run_id -> client
|
|
70
|
-
with tracer.start_as_current_span("client_manager.get_client") as span:
|
|
71
|
-
span.set_attribute("agent_id", agent_id)
|
|
72
|
-
span.set_attribute("run_id", run_id)
|
|
73
|
-
async with self.lock:
|
|
74
|
-
try:
|
|
75
|
-
logger.debug(
|
|
76
|
-
f"Attempting to get client for server '{self.client_config.name}'"
|
|
77
|
-
)
|
|
78
|
-
refresh = False
|
|
79
|
-
if additional_params:
|
|
80
|
-
refresh = bool(
|
|
81
|
-
additional_params.get("refresh_client", False)
|
|
82
|
-
)
|
|
83
|
-
client = None
|
|
84
|
-
run_clients = self.clients.get(agent_id, None)
|
|
85
|
-
if run_clients is None or refresh:
|
|
86
|
-
# This means, that across all runs, no agent has ever needed a client.
|
|
87
|
-
# This also means that we need to create a client.
|
|
88
|
-
client = await self.make_client(
|
|
89
|
-
additional_params=copy.deepcopy(additional_params)
|
|
90
|
-
)
|
|
91
|
-
# Insert the freshly created client
|
|
92
|
-
self.clients[agent_id] = {}
|
|
93
|
-
self.clients[agent_id][run_id] = client
|
|
94
|
-
|
|
95
|
-
else:
|
|
96
|
-
# This means there is at least one entry for the agent_id available
|
|
97
|
-
# Now, all we need to do is check if the run_id matches the entrie's run_id
|
|
98
|
-
client = run_clients.get(run_id, None)
|
|
99
|
-
if client is None or refresh:
|
|
100
|
-
# Means no client here with the respective run_id
|
|
101
|
-
client = await self.make_client(
|
|
102
|
-
additional_params=copy.deepcopy(
|
|
103
|
-
additional_params
|
|
104
|
-
)
|
|
105
|
-
)
|
|
106
|
-
# Insert the freshly created client.
|
|
107
|
-
self.clients[agent_id][run_id] = client
|
|
108
|
-
|
|
109
|
-
return client
|
|
110
|
-
except Exception as e:
|
|
111
|
-
# Log the exception and raise it so it becomes visible downstream
|
|
112
|
-
logger.error(
|
|
113
|
-
f"Unexpected Exception ocurred while trying to get client for server '{self.client_config.name}' with agent_id: {agent_id} and run_id: {run_id}: {e}"
|
|
114
|
-
)
|
|
115
|
-
span.record_exception(e)
|
|
116
|
-
raise e
|
|
117
|
-
|
|
118
|
-
async def call_tool(
|
|
119
|
-
self,
|
|
120
|
-
agent_id: str,
|
|
121
|
-
run_id: str,
|
|
122
|
-
name: str,
|
|
123
|
-
arguments: dict[str, Any],
|
|
124
|
-
additional_params: dict[str, Any] | None = None,
|
|
125
|
-
) -> Any:
|
|
126
|
-
"""Call a tool."""
|
|
127
|
-
with tracer.start_as_current_span("client_manager.call_tool") as span:
|
|
128
|
-
span.set_attribute("agent_id", agent_id)
|
|
129
|
-
span.set_attribute("run_id", run_id)
|
|
130
|
-
span.set_attribute("tool_name", name)
|
|
131
|
-
span.set_attribute("arguments", str(arguments))
|
|
132
|
-
try:
|
|
133
|
-
client = await self.get_client(
|
|
134
|
-
agent_id=agent_id,
|
|
135
|
-
run_id=run_id,
|
|
136
|
-
additional_params=additional_params,
|
|
137
|
-
)
|
|
138
|
-
result = await client.call_tool(
|
|
139
|
-
agent_id=agent_id,
|
|
140
|
-
run_id=run_id,
|
|
141
|
-
name=name,
|
|
142
|
-
arguments=arguments,
|
|
143
|
-
)
|
|
144
|
-
return result
|
|
145
|
-
except Exception as e:
|
|
146
|
-
logger.error(
|
|
147
|
-
f"Exception occurred while trying to call tool {name} on server '{self.client_config.name}': {e}"
|
|
148
|
-
)
|
|
149
|
-
span.record_exception(e)
|
|
150
|
-
return None
|
|
151
|
-
|
|
152
|
-
async def get_tools(
|
|
153
|
-
self,
|
|
154
|
-
agent_id: str,
|
|
155
|
-
run_id: str,
|
|
156
|
-
additional_params: dict[str, Any] | None = None,
|
|
157
|
-
) -> list[FlockMCPTool]:
|
|
158
|
-
"""Retrieves a list of tools for the agents to act on."""
|
|
159
|
-
with tracer.start_as_current_span("client_manager.get_tools") as span:
|
|
160
|
-
span.set_attribute("agent_id", agent_id)
|
|
161
|
-
span.set_attribute("run_id", run_id)
|
|
162
|
-
try:
|
|
163
|
-
client = await self.get_client(
|
|
164
|
-
agent_id=agent_id,
|
|
165
|
-
run_id=run_id,
|
|
166
|
-
additional_params=additional_params,
|
|
167
|
-
)
|
|
168
|
-
tools: list[FlockMCPTool] = await client.get_tools(
|
|
169
|
-
agent_id=agent_id, run_id=run_id
|
|
170
|
-
)
|
|
171
|
-
return tools
|
|
172
|
-
except Exception as e:
|
|
173
|
-
logger.error(
|
|
174
|
-
f"Exception occurred while trying to retrieve Tools for server '{self.client_config.name}' with agent_id: {agent_id} and run_id: {run_id}: {e}"
|
|
175
|
-
)
|
|
176
|
-
span.record_exception(e)
|
|
177
|
-
return []
|
|
178
|
-
|
|
179
|
-
async def close_all(self) -> None:
|
|
180
|
-
"""Closes all connections in the pool and cancels background tasks."""
|
|
181
|
-
with tracer.start_as_current_span("client_manager.close_all") as span:
|
|
182
|
-
async with self.lock:
|
|
183
|
-
for agent_id, run_dict in self.clients.items():
|
|
184
|
-
logger.debug(
|
|
185
|
-
f"Shutting down all clients for agent_id: {agent_id}"
|
|
186
|
-
)
|
|
187
|
-
for run_id, client in run_dict.items():
|
|
188
|
-
logger.debug(
|
|
189
|
-
f"Shutting down client for agent_id {agent_id} and run_id {run_id}"
|
|
190
|
-
)
|
|
191
|
-
try:
|
|
192
|
-
await client.disconnect()
|
|
193
|
-
except Exception as e:
|
|
194
|
-
logger.error(
|
|
195
|
-
f"Error when trying to disconnect client for server '{self.client_config.name}': {e}"
|
|
196
|
-
)
|
|
197
|
-
span.record_exception(e)
|
|
198
|
-
self.clients = {} # Let the GC take care of the rest.
|
|
199
|
-
logger.info(
|
|
200
|
-
f"All clients disconnected for server '{self.client_config.name}'"
|
|
201
|
-
)
|
flock/core/mcp/types/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""MCP Types package."""
|
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
# src/flock/core/mixin/dspy_integration.py
|
|
2
|
-
"""Mixin class for integrating with the dspy library.
|
|
3
|
-
|
|
4
|
-
This mixin centralizes Flock ↔ DSPy interop. It intentionally
|
|
5
|
-
delegates more to DSPy’s native builders (Signature, settings.context,
|
|
6
|
-
modules) to reduce custom glue and stay aligned with DSPy updates.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import ast
|
|
10
|
-
import re # Import re for parsing
|
|
11
|
-
import typing
|
|
12
|
-
from typing import Any, Literal
|
|
13
|
-
|
|
14
|
-
from dspy import Tool
|
|
15
|
-
|
|
16
|
-
from flock.core.logging.logging import get_logger
|
|
17
|
-
from flock.core.util.splitter import split_top_level
|
|
18
|
-
|
|
19
|
-
# Import split_top_level (assuming it's moved or copied appropriately)
|
|
20
|
-
# Option 1: If moved to a shared util
|
|
21
|
-
# from flock.core.util.parsing_utils import split_top_level
|
|
22
|
-
# Option 2: If kept within this file (as in previous example)
|
|
23
|
-
# Define split_top_level here or ensure it's imported
|
|
24
|
-
|
|
25
|
-
logger = get_logger("mixin.dspy")
|
|
26
|
-
|
|
27
|
-
# Type definition for agent type override
|
|
28
|
-
AgentType = Literal["ReAct", "Completion", "ChainOfThought"] | None
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# Helper function to resolve type strings (can be static or module-level)
|
|
32
|
-
def _resolve_type_string(type_str: str) -> type:
|
|
33
|
-
"""Resolves a type string into a Python type object.
|
|
34
|
-
Handles built-ins, registered types, and common typing generics like
|
|
35
|
-
List, Dict, Optional, Union, Literal.
|
|
36
|
-
"""
|
|
37
|
-
# Import registry here to avoid circular imports
|
|
38
|
-
from flock.core.registry import get_registry
|
|
39
|
-
|
|
40
|
-
registry = get_registry()
|
|
41
|
-
|
|
42
|
-
type_str = type_str.strip()
|
|
43
|
-
logger.debug(f"Attempting to resolve type string: '{type_str}'")
|
|
44
|
-
|
|
45
|
-
# 1. Check built-ins and registered types directly
|
|
46
|
-
try:
|
|
47
|
-
# This covers str, int, bool, Any, and types registered by name
|
|
48
|
-
resolved_type = registry.get_type(type_str)
|
|
49
|
-
logger.debug(f"Resolved '{type_str}' via registry to: {resolved_type}")
|
|
50
|
-
return resolved_type
|
|
51
|
-
except KeyError:
|
|
52
|
-
logger.debug(
|
|
53
|
-
f"'{type_str}' not found directly in registry, attempting generic parsing."
|
|
54
|
-
)
|
|
55
|
-
pass # Not found, continue parsing generics
|
|
56
|
-
|
|
57
|
-
# 2. Handle typing generics (List, Dict, Optional, Union, Literal)
|
|
58
|
-
# Use regex to match pattern like Generic[InnerType1, InnerType2, ...]
|
|
59
|
-
generic_match = re.fullmatch(r"(\w+)\s*\[(.*)\]", type_str)
|
|
60
|
-
if generic_match:
|
|
61
|
-
base_name = generic_match.group(1).strip()
|
|
62
|
-
args_str = generic_match.group(2).strip()
|
|
63
|
-
logger.debug(
|
|
64
|
-
f"Detected generic pattern: Base='{base_name}', Args='{args_str}'"
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
# Get the base generic type (e.g., list, dict, Optional) from registry/builtins
|
|
69
|
-
BaseType = registry.get_type(
|
|
70
|
-
base_name
|
|
71
|
-
) # Expects List, Dict etc. to be registered
|
|
72
|
-
logger.debug(
|
|
73
|
-
f"Resolved base generic type '{base_name}' to: {BaseType}"
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
# Special handling for Literal
|
|
77
|
-
if BaseType is typing.Literal:
|
|
78
|
-
# Split literal values, remove quotes, strip whitespace
|
|
79
|
-
def parse_literal_args(args_str: str) -> tuple[str, ...]:
|
|
80
|
-
try:
|
|
81
|
-
return tuple(ast.literal_eval(f"[{args_str}]"))
|
|
82
|
-
except (SyntaxError, ValueError) as exc:
|
|
83
|
-
raise ValueError(
|
|
84
|
-
f"Cannot parse {args_str!r} as literals"
|
|
85
|
-
) from exc
|
|
86
|
-
|
|
87
|
-
literal_args = parse_literal_args(args_str)
|
|
88
|
-
logger.debug(
|
|
89
|
-
f"Parsing Literal arguments: {args_str} -> {literal_args}"
|
|
90
|
-
)
|
|
91
|
-
resolved_type = typing.Literal[literal_args] # type: ignore
|
|
92
|
-
logger.debug(f"Constructed Literal type: {resolved_type}")
|
|
93
|
-
return resolved_type
|
|
94
|
-
|
|
95
|
-
# Recursively resolve arguments for other generics
|
|
96
|
-
logger.debug(f"Splitting generic arguments: '{args_str}'")
|
|
97
|
-
arg_strs = split_top_level(args_str)
|
|
98
|
-
logger.debug(f"Split arguments: {arg_strs}")
|
|
99
|
-
if not arg_strs:
|
|
100
|
-
raise ValueError("Generic type has no arguments.")
|
|
101
|
-
|
|
102
|
-
resolved_arg_types = tuple(
|
|
103
|
-
_resolve_type_string(arg) for arg in arg_strs
|
|
104
|
-
)
|
|
105
|
-
logger.debug(f"Resolved generic arguments: {resolved_arg_types}")
|
|
106
|
-
|
|
107
|
-
# Construct the generic type hint
|
|
108
|
-
if BaseType is typing.Optional:
|
|
109
|
-
if len(resolved_arg_types) != 1:
|
|
110
|
-
raise ValueError("Optional requires exactly one argument.")
|
|
111
|
-
# type: ignore
|
|
112
|
-
resolved_type = typing.Union[resolved_arg_types[0], type(None)]
|
|
113
|
-
logger.debug(
|
|
114
|
-
f"Constructed Optional type as Union: {resolved_type}"
|
|
115
|
-
)
|
|
116
|
-
return resolved_type
|
|
117
|
-
elif BaseType is typing.Union:
|
|
118
|
-
if not resolved_arg_types:
|
|
119
|
-
raise ValueError("Union requires at least one argument.")
|
|
120
|
-
# type: ignore
|
|
121
|
-
resolved_type = typing.Union[resolved_arg_types]
|
|
122
|
-
logger.debug(f"Constructed Union type: {resolved_type}")
|
|
123
|
-
return resolved_type
|
|
124
|
-
elif hasattr(
|
|
125
|
-
BaseType, "__getitem__"
|
|
126
|
-
): # Check if subscriptable (like list, dict, List, Dict)
|
|
127
|
-
resolved_type = BaseType[resolved_arg_types] # type: ignore
|
|
128
|
-
logger.debug(
|
|
129
|
-
f"Constructed subscripted generic type: {resolved_type}"
|
|
130
|
-
)
|
|
131
|
-
return resolved_type
|
|
132
|
-
else:
|
|
133
|
-
# Base type found but cannot be subscripted
|
|
134
|
-
logger.warning(
|
|
135
|
-
f"Base type '{base_name}' found but is not a standard subscriptable generic. Returning base type."
|
|
136
|
-
)
|
|
137
|
-
return BaseType
|
|
138
|
-
|
|
139
|
-
except (KeyError, ValueError, IndexError, TypeError) as e:
|
|
140
|
-
logger.warning(
|
|
141
|
-
f"Failed to parse generic type '{type_str}': {e}. Falling back."
|
|
142
|
-
)
|
|
143
|
-
# Fall through to raise KeyError below if base type itself wasn't found or parsing failed
|
|
144
|
-
|
|
145
|
-
# 3. If not resolved by now, raise error
|
|
146
|
-
logger.error(f"Type string '{type_str}' could not be resolved.")
|
|
147
|
-
raise KeyError(f"Type '{type_str}' could not be resolved.")
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
class DSPyIntegrationMixin:
|
|
151
|
-
"""Mixin class for integrating with the dspy library."""
|
|
152
|
-
|
|
153
|
-
def create_dspy_signature_class(self, agent_name: str, description_spec: str, fields_spec: str) -> Any:
|
|
154
|
-
"""Create a DSPy Signature using DSPy's native builder.
|
|
155
|
-
|
|
156
|
-
We support the Flock spec format: "field: type | description, ... -> ...".
|
|
157
|
-
This converts to the dict-based make_signature format with
|
|
158
|
-
InputField/OutputField and resolved Python types.
|
|
159
|
-
"""
|
|
160
|
-
try:
|
|
161
|
-
import dspy
|
|
162
|
-
except ImportError as exc:
|
|
163
|
-
logger.error("DSPy is not installed. Install with: pip install dspy-ai")
|
|
164
|
-
raise
|
|
165
|
-
|
|
166
|
-
# Split input/output part
|
|
167
|
-
if "->" in fields_spec:
|
|
168
|
-
inputs_spec, outputs_spec = fields_spec.split("->", 1)
|
|
169
|
-
else:
|
|
170
|
-
inputs_spec, outputs_spec = fields_spec, ""
|
|
171
|
-
|
|
172
|
-
def parse_field(field_str: str) -> tuple[str, type, str | None] | None:
|
|
173
|
-
field_str = field_str.strip()
|
|
174
|
-
if not field_str:
|
|
175
|
-
return None
|
|
176
|
-
parts = field_str.split("|", 1)
|
|
177
|
-
main_part = parts[0].strip()
|
|
178
|
-
desc = parts[1].strip() if len(parts) > 1 else None
|
|
179
|
-
if ":" in main_part:
|
|
180
|
-
name, type_str = [s.strip() for s in main_part.split(":", 1)]
|
|
181
|
-
else:
|
|
182
|
-
name, type_str = main_part, "str"
|
|
183
|
-
try:
|
|
184
|
-
py_type = _resolve_type_string(type_str)
|
|
185
|
-
except Exception as e:
|
|
186
|
-
logger.warning(
|
|
187
|
-
f"Type resolution failed for '{type_str}' in field '{name}': {e}. Falling back to str."
|
|
188
|
-
)
|
|
189
|
-
py_type = str
|
|
190
|
-
return name, py_type, desc
|
|
191
|
-
|
|
192
|
-
def to_field_tuples(spec: str, kind: str) -> dict[str, tuple[type, Any]]:
|
|
193
|
-
mapping: dict[str, tuple[type, Any]] = {}
|
|
194
|
-
if not spec.strip():
|
|
195
|
-
return mapping
|
|
196
|
-
for raw in split_top_level(spec):
|
|
197
|
-
parsed = parse_field(raw)
|
|
198
|
-
if not parsed:
|
|
199
|
-
continue
|
|
200
|
-
fname, ftype, fdesc = parsed
|
|
201
|
-
FieldClass = dspy.InputField if kind == "input" else dspy.OutputField
|
|
202
|
-
finfo = FieldClass(desc=fdesc) if fdesc is not None else FieldClass()
|
|
203
|
-
mapping[fname] = (ftype, finfo)
|
|
204
|
-
return mapping
|
|
205
|
-
|
|
206
|
-
try:
|
|
207
|
-
fields: dict[str, tuple[type, Any]] = {
|
|
208
|
-
**to_field_tuples(inputs_spec, "input"),
|
|
209
|
-
**to_field_tuples(outputs_spec, "output"),
|
|
210
|
-
}
|
|
211
|
-
sig = dspy.Signature(fields, description_spec or None, signature_name=f"dspy_{agent_name}")
|
|
212
|
-
logger.info("Created DSPy Signature %s", sig.__name__)
|
|
213
|
-
return sig
|
|
214
|
-
except Exception as e: # pragma: no cover - defensive
|
|
215
|
-
logger.error("Failed to create DSPy Signature for %s: %s", agent_name, e, exc_info=True)
|
|
216
|
-
raise
|
|
217
|
-
|
|
218
|
-
def _configure_language_model(
|
|
219
|
-
self,
|
|
220
|
-
model: str | None,
|
|
221
|
-
use_cache: bool,
|
|
222
|
-
temperature: float,
|
|
223
|
-
max_tokens: int,
|
|
224
|
-
) -> None:
|
|
225
|
-
"""Initialize and configure the language model using dspy."""
|
|
226
|
-
if model is None:
|
|
227
|
-
logger.warning(
|
|
228
|
-
"No model specified for DSPy configuration. Using DSPy default."
|
|
229
|
-
)
|
|
230
|
-
# Rely on DSPy's global default or raise error if none configured
|
|
231
|
-
# import dspy
|
|
232
|
-
# if dspy.settings.lm is None:
|
|
233
|
-
# raise ValueError("No model specified for agent and no global DSPy LM configured.")
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
try:
|
|
237
|
-
import dspy
|
|
238
|
-
except ImportError:
|
|
239
|
-
logger.error("DSPy is not installed; cannot configure LM.")
|
|
240
|
-
return
|
|
241
|
-
|
|
242
|
-
# Build an LM instance for per-call usage; prefer settings.context over global configure.
|
|
243
|
-
try:
|
|
244
|
-
lm_instance = dspy.LM(
|
|
245
|
-
model=model,
|
|
246
|
-
temperature=temperature,
|
|
247
|
-
max_tokens=max_tokens,
|
|
248
|
-
cache=use_cache,
|
|
249
|
-
)
|
|
250
|
-
# Do not call settings.configure() here to avoid cross-task/thread conflicts.
|
|
251
|
-
# Callers should pass this LM via dspy.settings.context(lm=...) or program.acall(lm=...)
|
|
252
|
-
dspy.settings # touch to ensure settings is importable
|
|
253
|
-
logger.info(
|
|
254
|
-
"Prepared DSPy LM (defer install to settings.context): model=%s temp=%s max_tokens=%s",
|
|
255
|
-
model,
|
|
256
|
-
temperature,
|
|
257
|
-
max_tokens,
|
|
258
|
-
)
|
|
259
|
-
except Exception as e:
|
|
260
|
-
logger.error("Failed to prepare DSPy LM '%s': %s", model, e, exc_info=True)
|
|
261
|
-
raise
|
|
262
|
-
|
|
263
|
-
def _select_task(
|
|
264
|
-
self,
|
|
265
|
-
signature: Any,
|
|
266
|
-
override_evaluator_type: AgentType,
|
|
267
|
-
max_tool_calls: int = 10,
|
|
268
|
-
tools: list[Any] | None = None,
|
|
269
|
-
mcp_tools: list[Any] | None = None,
|
|
270
|
-
kwargs: dict[str, Any] = {},
|
|
271
|
-
) -> Any:
|
|
272
|
-
"""Select and instantiate the appropriate DSPy Program/Module."""
|
|
273
|
-
try:
|
|
274
|
-
import dspy
|
|
275
|
-
except ImportError:
|
|
276
|
-
logger.error(
|
|
277
|
-
"DSPy library is not installed. Cannot select DSPy task."
|
|
278
|
-
)
|
|
279
|
-
raise ImportError("DSPy is required for this functionality.")
|
|
280
|
-
|
|
281
|
-
processed_tools = []
|
|
282
|
-
if tools:
|
|
283
|
-
for tool in tools:
|
|
284
|
-
if callable(tool): # Basic check
|
|
285
|
-
processed_tools.append(tool)
|
|
286
|
-
# Could add more sophisticated tool wrapping/validation here if needed
|
|
287
|
-
else:
|
|
288
|
-
logger.warning(
|
|
289
|
-
f"Item '{tool}' in tools list is not callable, skipping."
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
processed_mcp_tools = []
|
|
293
|
-
if mcp_tools:
|
|
294
|
-
for mcp_tool in mcp_tools:
|
|
295
|
-
if isinstance(mcp_tool, Tool): # Basic check
|
|
296
|
-
processed_mcp_tools.append(mcp_tool)
|
|
297
|
-
else:
|
|
298
|
-
logger.warning(
|
|
299
|
-
f"Item '{mcp_tool}' is not a dspy.primitives.Tool, skipping."
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
dspy_program = None
|
|
303
|
-
selected_type = override_evaluator_type
|
|
304
|
-
|
|
305
|
-
# Determine type if not overridden
|
|
306
|
-
if not selected_type:
|
|
307
|
-
selected_type = "ReAct" if processed_tools or processed_mcp_tools else "Predict"
|
|
308
|
-
|
|
309
|
-
# Normalize common aliases/casing
|
|
310
|
-
sel = selected_type.lower() if isinstance(selected_type, str) else selected_type
|
|
311
|
-
if isinstance(sel, str):
|
|
312
|
-
if sel in {"completion", "predict"}:
|
|
313
|
-
sel = "predict"
|
|
314
|
-
elif sel in {"react"}:
|
|
315
|
-
sel = "react"
|
|
316
|
-
elif sel in {"chainofthought", "cot", "chain_of_thought"}:
|
|
317
|
-
sel = "chain_of_thought"
|
|
318
|
-
|
|
319
|
-
logger.debug(
|
|
320
|
-
f"Selecting DSPy program type: {selected_type} (Tools provided: {bool(processed_tools)}) (MCP Tools: {bool(processed_mcp_tools)}"
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
# Merge list of native tools and processed tools.
|
|
324
|
-
# This makes mcp tools appear as native code functions to the llm of the agent.
|
|
325
|
-
merged_tools = []
|
|
326
|
-
|
|
327
|
-
if processed_tools:
|
|
328
|
-
merged_tools = merged_tools + processed_tools
|
|
329
|
-
|
|
330
|
-
if processed_mcp_tools:
|
|
331
|
-
merged_tools = merged_tools + processed_mcp_tools
|
|
332
|
-
|
|
333
|
-
try:
|
|
334
|
-
if sel == "chain_of_thought":
|
|
335
|
-
dspy_program = dspy.ChainOfThought(signature, **kwargs)
|
|
336
|
-
elif sel == "react":
|
|
337
|
-
if not kwargs:
|
|
338
|
-
kwargs = {"max_iters": max_tool_calls}
|
|
339
|
-
dspy_program = dspy.ReAct(
|
|
340
|
-
signature, tools=merged_tools or [], **kwargs
|
|
341
|
-
)
|
|
342
|
-
elif sel == "predict":
|
|
343
|
-
dspy_program = dspy.Predict(signature)
|
|
344
|
-
else: # Fallback or handle unknown type
|
|
345
|
-
logger.warning(
|
|
346
|
-
f"Unknown or unsupported agent_type_override '{selected_type}'. Defaulting to dspy.Predict."
|
|
347
|
-
)
|
|
348
|
-
dspy_program = dspy.Predict(signature)
|
|
349
|
-
|
|
350
|
-
logger.info(
|
|
351
|
-
f"Instantiated DSPy program: {type(dspy_program).__name__}"
|
|
352
|
-
)
|
|
353
|
-
return dspy_program
|
|
354
|
-
except Exception as e:
|
|
355
|
-
logger.error(
|
|
356
|
-
f"Failed to instantiate DSPy program of type '{selected_type}': {e}",
|
|
357
|
-
exc_info=True,
|
|
358
|
-
)
|
|
359
|
-
raise RuntimeError(f"Could not create DSPy program: {e}") from e
|
|
360
|
-
|
|
361
|
-
def _process_result(self, result: Any, inputs: dict[str, Any]) -> tuple[dict[str, Any], float, list]:
|
|
362
|
-
"""Convert a DSPy Prediction or mapping to a plain dict and attach LM history.
|
|
363
|
-
|
|
364
|
-
Returns (result_dict, cost_placeholder, lm_history). The cost is set to 0.0;
|
|
365
|
-
use token usage trackers elsewhere for accurate accounting.
|
|
366
|
-
"""
|
|
367
|
-
try:
|
|
368
|
-
import dspy
|
|
369
|
-
except ImportError:
|
|
370
|
-
dspy = None
|
|
371
|
-
|
|
372
|
-
if result is None:
|
|
373
|
-
logger.warning("DSPy program returned None result.")
|
|
374
|
-
return {}, 0.0, []
|
|
375
|
-
|
|
376
|
-
try:
|
|
377
|
-
# Best-effort extraction from DSPy Prediction
|
|
378
|
-
if dspy and isinstance(result, dspy.Prediction):
|
|
379
|
-
output_dict = dict(result.items(include_dspy=False))
|
|
380
|
-
elif isinstance(result, dict):
|
|
381
|
-
output_dict = result
|
|
382
|
-
elif hasattr(result, "items") and callable(result.items):
|
|
383
|
-
try:
|
|
384
|
-
output_dict = dict(result.items())
|
|
385
|
-
except Exception:
|
|
386
|
-
output_dict = {"raw_result": str(result)}
|
|
387
|
-
else:
|
|
388
|
-
output_dict = {"raw_result": str(result)}
|
|
389
|
-
|
|
390
|
-
final_result = {**inputs, **output_dict}
|
|
391
|
-
|
|
392
|
-
lm_history = []
|
|
393
|
-
try:
|
|
394
|
-
if dspy and dspy.settings.lm is not None and hasattr(dspy.settings.lm, "history"):
|
|
395
|
-
lm_history = dspy.settings.lm.history
|
|
396
|
-
except Exception:
|
|
397
|
-
lm_history = []
|
|
398
|
-
|
|
399
|
-
return final_result, 0.0, lm_history
|
|
400
|
-
|
|
401
|
-
except Exception as conv_error: # pragma: no cover - defensive
|
|
402
|
-
logger.error("Failed to process DSPy result into dictionary: %s", conv_error, exc_info=True)
|
|
403
|
-
return {"error": "Failed to process result", "raw_result": str(result)}, 0.0, []
|