flock-core 0.4.519__py3-none-any.whl → 0.5.0b1__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/cli/manage_agents.py +3 -3
- flock/components/__init__.py +28 -0
- flock/components/evaluation/__init__.py +9 -0
- flock/components/evaluation/declarative_evaluation_component.py +198 -0
- flock/components/routing/__init__.py +15 -0
- flock/{routers/conditional/conditional_router.py → components/routing/conditional_routing_component.py} +60 -49
- flock/components/routing/default_routing_component.py +103 -0
- flock/components/routing/llm_routing_component.py +208 -0
- flock/components/utility/__init__.py +15 -0
- flock/{modules/enterprise_memory/enterprise_memory_module.py → components/utility/memory_utility_component.py} +195 -173
- flock/{modules/performance/metrics_module.py → components/utility/metrics_utility_component.py} +101 -86
- flock/{modules/output/output_module.py → components/utility/output_utility_component.py} +49 -49
- flock/core/__init__.py +2 -8
- flock/core/agent/__init__.py +16 -0
- flock/core/agent/flock_agent_components.py +104 -0
- flock/core/agent/flock_agent_execution.py +101 -0
- flock/core/agent/flock_agent_integration.py +147 -0
- flock/core/agent/flock_agent_lifecycle.py +177 -0
- flock/core/agent/flock_agent_serialization.py +378 -0
- flock/core/component/__init__.py +15 -0
- flock/core/{flock_module.py → component/agent_component_base.py} +136 -35
- flock/core/component/evaluation_component_base.py +56 -0
- flock/core/component/routing_component_base.py +75 -0
- flock/core/component/utility_component_base.py +69 -0
- flock/core/config/flock_agent_config.py +49 -2
- flock/core/evaluation/utils.py +1 -1
- flock/core/execution/evaluation_executor.py +1 -1
- flock/core/flock.py +137 -483
- flock/core/flock_agent.py +151 -1018
- flock/core/flock_factory.py +94 -73
- flock/core/{flock_registry.py → flock_registry.py.backup} +3 -17
- flock/core/logging/logging.py +1 -0
- flock/core/mcp/flock_mcp_server.py +42 -37
- flock/core/mixin/dspy_integration.py +5 -5
- flock/core/orchestration/__init__.py +18 -0
- flock/core/orchestration/flock_batch_processor.py +94 -0
- flock/core/orchestration/flock_evaluator.py +113 -0
- flock/core/orchestration/flock_execution.py +288 -0
- flock/core/orchestration/flock_initialization.py +125 -0
- flock/core/orchestration/flock_server_manager.py +65 -0
- flock/core/orchestration/flock_web_server.py +117 -0
- flock/core/registry/__init__.py +39 -0
- flock/core/registry/agent_registry.py +69 -0
- flock/core/registry/callable_registry.py +139 -0
- flock/core/registry/component_discovery.py +142 -0
- flock/core/registry/component_registry.py +64 -0
- flock/core/registry/config_mapping.py +64 -0
- flock/core/registry/decorators.py +137 -0
- flock/core/registry/registry_hub.py +202 -0
- flock/core/registry/server_registry.py +57 -0
- flock/core/registry/type_registry.py +86 -0
- flock/core/serialization/flock_serializer.py +33 -30
- flock/core/serialization/serialization_utils.py +28 -25
- flock/core/util/input_resolver.py +29 -2
- flock/platform/docker_tools.py +3 -3
- flock/tools/markdown_tools.py +1 -2
- flock/tools/text_tools.py +1 -2
- flock/webapp/app/main.py +9 -5
- flock/workflow/activities.py +59 -84
- flock/workflow/activities_unified.py +230 -0
- flock/workflow/agent_execution_activity.py +6 -6
- flock/workflow/flock_workflow.py +1 -1
- {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/METADATA +4 -4
- {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/RECORD +67 -68
- flock/core/flock_evaluator.py +0 -60
- flock/core/flock_router.py +0 -83
- flock/evaluators/__init__.py +0 -1
- flock/evaluators/declarative/__init__.py +0 -1
- flock/evaluators/declarative/declarative_evaluator.py +0 -194
- flock/evaluators/memory/memory_evaluator.py +0 -90
- flock/evaluators/test/test_case_evaluator.py +0 -38
- flock/evaluators/zep/zep_evaluator.py +0 -59
- flock/modules/__init__.py +0 -1
- flock/modules/assertion/__init__.py +0 -1
- flock/modules/assertion/assertion_module.py +0 -286
- flock/modules/callback/__init__.py +0 -1
- flock/modules/callback/callback_module.py +0 -91
- flock/modules/enterprise_memory/README.md +0 -99
- flock/modules/mem0/__init__.py +0 -1
- flock/modules/mem0/mem0_module.py +0 -126
- flock/modules/mem0_async/__init__.py +0 -1
- flock/modules/mem0_async/async_mem0_module.py +0 -126
- flock/modules/memory/__init__.py +0 -1
- flock/modules/memory/memory_module.py +0 -429
- flock/modules/memory/memory_parser.py +0 -125
- flock/modules/memory/memory_storage.py +0 -736
- flock/modules/output/__init__.py +0 -1
- flock/modules/performance/__init__.py +0 -1
- flock/modules/zep/__init__.py +0 -1
- flock/modules/zep/zep_module.py +0 -192
- flock/routers/__init__.py +0 -1
- flock/routers/agent/__init__.py +0 -1
- flock/routers/agent/agent_router.py +0 -236
- flock/routers/agent/handoff_agent.py +0 -58
- flock/routers/default/__init__.py +0 -1
- flock/routers/default/default_router.py +0 -80
- flock/routers/feedback/feedback_router.py +0 -114
- flock/routers/list_generator/list_generator_router.py +0 -166
- flock/routers/llm/__init__.py +0 -1
- flock/routers/llm/llm_router.py +0 -365
- {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/WHEEL +0 -0
- {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -24,13 +24,13 @@ from pydantic import BaseModel
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
|
-
from flock.core.
|
|
27
|
+
from flock.core.registry import get_registry
|
|
28
28
|
from flock.core.logging.logging import get_logger
|
|
29
29
|
|
|
30
30
|
logger = get_logger("serialization.utils")
|
|
31
31
|
|
|
32
32
|
# Remove this line to avoid circular import at module level
|
|
33
|
-
#
|
|
33
|
+
# registry = get_registry() # Get singleton instance
|
|
34
34
|
|
|
35
35
|
# --- Serialization Helper ---
|
|
36
36
|
|
|
@@ -188,16 +188,16 @@ def collect_pydantic_models(
|
|
|
188
188
|
|
|
189
189
|
def serialize_item(item: Any) -> Any:
|
|
190
190
|
"""Recursively prepares an item for serialization (e.g., to dict for YAML/JSON).
|
|
191
|
-
Converts known callables to their path strings using
|
|
191
|
+
Converts known callables to their path strings using registry.
|
|
192
192
|
Converts Pydantic models using model_dump.
|
|
193
193
|
"""
|
|
194
194
|
# Import the registry lazily when needed
|
|
195
|
-
from flock.core.
|
|
195
|
+
from flock.core.registry import get_registry
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
registry = get_registry()
|
|
198
198
|
|
|
199
199
|
if callable(item) and not isinstance(item, type):
|
|
200
|
-
path_str =
|
|
200
|
+
path_str = registry.get_callable_path_string(
|
|
201
201
|
item
|
|
202
202
|
) # Use registry helper
|
|
203
203
|
if path_str:
|
|
@@ -248,12 +248,12 @@ def serialize_item(item: Any) -> Any:
|
|
|
248
248
|
elif isinstance(
|
|
249
249
|
item, type
|
|
250
250
|
): # Handle type objects themselves (e.g. if stored directly)
|
|
251
|
-
type_name =
|
|
251
|
+
type_name = registry.get_component_type_name(
|
|
252
252
|
item
|
|
253
253
|
) # Check components first
|
|
254
254
|
if type_name:
|
|
255
255
|
return {"__component_ref__": type_name}
|
|
256
|
-
type_name =
|
|
256
|
+
type_name = registry._get_path_string(
|
|
257
257
|
item
|
|
258
258
|
) # Check regular types/classes by path
|
|
259
259
|
if type_name:
|
|
@@ -274,13 +274,13 @@ def serialize_item(item: Any) -> Any:
|
|
|
274
274
|
|
|
275
275
|
def deserialize_item(item: Any) -> Any:
|
|
276
276
|
"""Recursively processes a deserialized item (e.g., from YAML/JSON dict).
|
|
277
|
-
Converts reference dicts back to actual callables or types using
|
|
277
|
+
Converts reference dicts back to actual callables or types using registry.
|
|
278
278
|
Handles nested lists and dicts.
|
|
279
279
|
"""
|
|
280
280
|
# Import the registry lazily when needed
|
|
281
|
-
from flock.core.
|
|
281
|
+
from flock.core.registry import get_registry
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
registry = get_registry()
|
|
284
284
|
|
|
285
285
|
if isinstance(item, Mapping):
|
|
286
286
|
if "__callable_ref__" in item and len(item) == 1:
|
|
@@ -288,7 +288,7 @@ def deserialize_item(item: Any) -> Any:
|
|
|
288
288
|
try:
|
|
289
289
|
# The registry's get_callable needs to handle lookup by simple name OR full path
|
|
290
290
|
# Or we assume get_callable handles finding the right function from the simple name
|
|
291
|
-
resolved_callable =
|
|
291
|
+
resolved_callable = registry.get_callable(ref_name)
|
|
292
292
|
logger.debug(
|
|
293
293
|
f"Deserialized callable reference '{ref_name}' to {resolved_callable}"
|
|
294
294
|
)
|
|
@@ -307,7 +307,7 @@ def deserialize_item(item: Any) -> Any:
|
|
|
307
307
|
elif "__component_ref__" in item and len(item) == 1:
|
|
308
308
|
type_name = item["__component_ref__"]
|
|
309
309
|
try:
|
|
310
|
-
return
|
|
310
|
+
return registry.get_component(type_name)
|
|
311
311
|
except KeyError:
|
|
312
312
|
logger.error(
|
|
313
313
|
f"Component reference '{type_name}' not found during deserialization."
|
|
@@ -318,7 +318,7 @@ def deserialize_item(item: Any) -> Any:
|
|
|
318
318
|
try:
|
|
319
319
|
# For general types, use get_type or fallback to dynamic import like get_callable
|
|
320
320
|
# Using get_type for now, assuming it needs registration
|
|
321
|
-
return
|
|
321
|
+
return registry.get_type(type_name)
|
|
322
322
|
except KeyError:
|
|
323
323
|
# Attempt dynamic import as fallback if get_type fails (similar to get_callable)
|
|
324
324
|
try:
|
|
@@ -329,7 +329,7 @@ def deserialize_item(item: Any) -> Any:
|
|
|
329
329
|
mod = importlib.import_module(module_name)
|
|
330
330
|
type_obj = getattr(mod, class_name)
|
|
331
331
|
if isinstance(type_obj, type):
|
|
332
|
-
|
|
332
|
+
registry.register_type(
|
|
333
333
|
type_obj, type_name
|
|
334
334
|
) # Cache it
|
|
335
335
|
return type_obj
|
|
@@ -356,12 +356,12 @@ def deserialize_component(
|
|
|
356
356
|
data: dict | None, expected_base_type: type
|
|
357
357
|
) -> Any | None:
|
|
358
358
|
"""Deserializes a component (Module, Evaluator, Router) from its dict representation.
|
|
359
|
-
Uses the 'type' field to find the correct class via
|
|
359
|
+
Uses the 'type' field to find the correct class via registry.
|
|
360
360
|
"""
|
|
361
361
|
# Import the registry and COMPONENT_BASE_TYPES lazily when needed
|
|
362
|
-
from flock.core.
|
|
362
|
+
from flock.core.registry import get_registry
|
|
363
363
|
|
|
364
|
-
|
|
364
|
+
registry = get_registry()
|
|
365
365
|
|
|
366
366
|
if data is None:
|
|
367
367
|
return None
|
|
@@ -379,14 +379,17 @@ def deserialize_component(
|
|
|
379
379
|
return None
|
|
380
380
|
|
|
381
381
|
try:
|
|
382
|
-
ComponentClass =
|
|
382
|
+
ComponentClass = registry.get_component(type_name) # Use registry
|
|
383
383
|
# Optional: Keep the base type check
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
384
|
+
try:
|
|
385
|
+
from flock.core.component.agent_component_base import AgentComponent
|
|
386
|
+
if not issubclass(ComponentClass, AgentComponent):
|
|
387
|
+
logger.warning(
|
|
388
|
+
f"Deserialized class '{type_name}' is not a subclass of AgentComponent."
|
|
389
|
+
)
|
|
390
|
+
except ImportError:
|
|
391
|
+
# AgentComponent not available during setup
|
|
392
|
+
pass
|
|
390
393
|
|
|
391
394
|
# Recursively deserialize the data *before* passing to Pydantic constructor
|
|
392
395
|
# This handles nested callables/types within the component's config/data
|
|
@@ -45,7 +45,12 @@ def top_level_to_keys(s: str) -> list[str]:
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def resolve_inputs(
|
|
48
|
-
input_spec: str,
|
|
48
|
+
input_spec: str,
|
|
49
|
+
context: FlockContext,
|
|
50
|
+
previous_agent_name: str,
|
|
51
|
+
previous_agent_output:str,
|
|
52
|
+
previous_agent_handoff_strategy:str,
|
|
53
|
+
previous_agent_handoff_map:dict[str, str]
|
|
49
54
|
) -> dict:
|
|
50
55
|
"""Build a dictionary of inputs based on the input specification string and the provided context.
|
|
51
56
|
|
|
@@ -63,6 +68,28 @@ def resolve_inputs(
|
|
|
63
68
|
eg. agent name: "idea_agent", variable: "ia_idea" (ia = idea agent)
|
|
64
69
|
- or set hand off mode to strict to avoid conflicts.
|
|
65
70
|
with strict mode, the agent will only accept inputs from the previous agent.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Strategy for passing data to the next agent.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
ReviewAgent.next_agent = SummaryAgent
|
|
77
|
+
ReviewAgent(output = "text:str, keywords:list[str], rating:int")
|
|
78
|
+
SummaryAgent(input = "text:str, title:str")
|
|
79
|
+
|
|
80
|
+
'append' means the difference in signature is appended to the next agent's input signature.
|
|
81
|
+
SummaryAgent(input = "text:str, title:str, keywords:list[str], rating:int")
|
|
82
|
+
|
|
83
|
+
'override' means the target agent's signature is getting overriden.
|
|
84
|
+
SummaryAgent(input = "text:str, keywords:list[str], rating:int")
|
|
85
|
+
|
|
86
|
+
'static' means the the target agent's signature is not changed at all.
|
|
87
|
+
If source agent has no output fields that match the target agent's input,
|
|
88
|
+
there will be no data passed to the next agent.
|
|
89
|
+
SummaryAgent(input = "text:str, title:str")
|
|
90
|
+
|
|
91
|
+
'map' means the source agent's output is mapped to the target agent's input
|
|
92
|
+
based on 'handoff_map' configuration.
|
|
66
93
|
|
|
67
94
|
Args:
|
|
68
95
|
input_spec: Comma-separated input keys (e.g., "query" or "agent_name.property").
|
|
@@ -120,7 +147,7 @@ def resolve_inputs(
|
|
|
120
147
|
inputs[key] = context.get_agent_definition(property_name)
|
|
121
148
|
continue
|
|
122
149
|
|
|
123
|
-
# Otherwise, attempt to look up a state variable with the key "
|
|
150
|
+
# Otherwise, attempt to look up a state variable with the key "entity_name.property_name"
|
|
124
151
|
inputs[key] = context.get_variable(f"{entity_name}.{property_name}")
|
|
125
152
|
continue
|
|
126
153
|
|
flock/platform/docker_tools.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import subprocess
|
|
2
|
-
import time
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def _check_docker_running():
|
|
@@ -16,7 +16,7 @@ def _check_docker_running():
|
|
|
16
16
|
return False
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def _start_docker():
|
|
19
|
+
async def _start_docker():
|
|
20
20
|
"""Attempt to start Docker.
|
|
21
21
|
This example first tries 'systemctl start docker' and then 'service docker start'.
|
|
22
22
|
Adjust as needed for your environment.
|
|
@@ -37,7 +37,7 @@ def _start_docker():
|
|
|
37
37
|
text=True,
|
|
38
38
|
)
|
|
39
39
|
# Give Docker a moment to start.
|
|
40
|
-
|
|
40
|
+
await asyncio.sleep(3)
|
|
41
41
|
if _check_docker_running():
|
|
42
42
|
print("Docker is now running.")
|
|
43
43
|
return True
|
flock/tools/markdown_tools.py
CHANGED
|
@@ -26,8 +26,7 @@ def markdown_split_by_headers(
|
|
|
26
26
|
chunks = []
|
|
27
27
|
|
|
28
28
|
# Process each section
|
|
29
|
-
for i in
|
|
30
|
-
current_header = headers[i]
|
|
29
|
+
for i, current_header in enumerate(headers):
|
|
31
30
|
header_text = current_header.group(2).strip()
|
|
32
31
|
header_level = len(current_header.group(1))
|
|
33
32
|
|
flock/tools/text_tools.py
CHANGED
|
@@ -206,8 +206,7 @@ def text_split_code_by_functions(code: str) -> list[dict[str, Any]]:
|
|
|
206
206
|
functions = []
|
|
207
207
|
|
|
208
208
|
# Process each function
|
|
209
|
-
for i in
|
|
210
|
-
current_match = matches[i]
|
|
209
|
+
for i, current_match in enumerate(matches):
|
|
211
210
|
function_name = current_match.group(2)
|
|
212
211
|
|
|
213
212
|
# Determine function content
|
flock/webapp/app/main.py
CHANGED
|
@@ -124,6 +124,10 @@ def mask_sensitive_value_web(value: str) -> str:
|
|
|
124
124
|
if len(value) <= 4: return "••••"
|
|
125
125
|
return value[:2] + "•" * (len(value) - 4) + value[-2:]
|
|
126
126
|
|
|
127
|
+
def create_hx_trigger_header(triggers: dict[str, Any]) -> str:
|
|
128
|
+
"""Helper function to create HX-Trigger header with JSON serialization."""
|
|
129
|
+
return json.dumps(triggers)
|
|
130
|
+
|
|
127
131
|
def get_show_secrets_setting_web(env_vars: dict[str, str]) -> bool:
|
|
128
132
|
return env_vars.get(SHOW_SECRETS_KEY, "false").lower() == "true"
|
|
129
133
|
|
|
@@ -843,12 +847,12 @@ async def ui_load_flock_by_name_action(request: Request, selected_flock_filename
|
|
|
843
847
|
if loaded_flock:
|
|
844
848
|
success_message_text = f"Flock '{loaded_flock.name}' loaded from '{selected_flock_filename}'."
|
|
845
849
|
response_headers["HX-Push-Url"] = "/ui/editor/execute?ui_mode=" + ui_mode_query
|
|
846
|
-
response_headers["HX-Trigger"] =
|
|
850
|
+
response_headers["HX-Trigger"] = create_hx_trigger_header({"flockLoaded": None, "notify": {"type": "success", "message": success_message_text}})
|
|
847
851
|
context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
|
|
848
852
|
return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
|
|
849
853
|
else:
|
|
850
854
|
error_message_text = f"Failed to load flock file '{selected_flock_filename}'."
|
|
851
|
-
response_headers["HX-Trigger"] =
|
|
855
|
+
response_headers["HX-Trigger"] = create_hx_trigger_header({"notify": {"type": "error", "message": error_message_text}})
|
|
852
856
|
context = get_base_context_web(request, error=error_message_text, ui_mode=ui_mode_query)
|
|
853
857
|
context["error_message_inline"] = error_message_text # For direct display in partial
|
|
854
858
|
return templates.TemplateResponse("partials/_load_manager_view.html", context, headers=response_headers)
|
|
@@ -874,13 +878,13 @@ async def ui_load_flock_by_upload_action(request: Request, flock_file_upload: Up
|
|
|
874
878
|
if loaded_flock:
|
|
875
879
|
success_message_text = f"Flock '{loaded_flock.name}' loaded from '{filename_to_load}'."
|
|
876
880
|
response_headers["HX-Push-Url"] = f"/ui/editor/execute?ui_mode={ui_mode_query}"
|
|
877
|
-
response_headers["HX-Trigger"] =
|
|
881
|
+
response_headers["HX-Trigger"] = create_hx_trigger_header({"flockLoaded": None, "flockFileListChanged": None, "notify": {"type": "success", "message": success_message_text}})
|
|
878
882
|
context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
|
|
879
883
|
return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
|
|
880
884
|
else: error_message_text = f"Failed to process uploaded '{filename_to_load}'."
|
|
881
885
|
|
|
882
886
|
final_error_msg = error_message_text or "Upload failed."
|
|
883
|
-
response_headers["HX-Trigger"] =
|
|
887
|
+
response_headers["HX-Trigger"] = create_hx_trigger_header({"notify": {"type": "error", "message": final_error_msg}})
|
|
884
888
|
context = get_base_context_web(request, error=final_error_msg, ui_mode=ui_mode_query)
|
|
885
889
|
return templates.TemplateResponse("partials/_create_flock_form.html", context, headers=response_headers)
|
|
886
890
|
|
|
@@ -893,7 +897,7 @@ async def ui_create_flock_action(request: Request, flock_name: str = Form(...),
|
|
|
893
897
|
|
|
894
898
|
new_flock = create_new_flock_service(flock_name, default_model, description, request.app.state)
|
|
895
899
|
success_msg_text = f"New flock '{new_flock.name}' created. Navigating to Execute page. Configure properties and agents as needed."
|
|
896
|
-
response_headers = {"HX-Push-Url": f"/ui/editor/execute?ui_mode={ui_mode_query}", "HX-Trigger":
|
|
900
|
+
response_headers = {"HX-Push-Url": f"/ui/editor/execute?ui_mode={ui_mode_query}", "HX-Trigger": create_hx_trigger_header({"flockLoaded": None, "notify": {"type": "success", "message": success_msg_text}})}
|
|
897
901
|
context = get_base_context_web(request, success=success_msg_text, ui_mode=ui_mode_query)
|
|
898
902
|
return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
|
|
899
903
|
|
flock/workflow/activities.py
CHANGED
|
@@ -7,15 +7,28 @@ from temporalio import activity
|
|
|
7
7
|
|
|
8
8
|
from flock.core.context.context import FlockContext
|
|
9
9
|
from flock.core.context.context_vars import FLOCK_CURRENT_AGENT, FLOCK_MODEL
|
|
10
|
-
from flock.core.
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
from flock.core.registry import get_registry
|
|
11
|
+
|
|
12
|
+
# HandOffRequest removed - using agent.next_agent directly
|
|
13
13
|
from flock.core.logging.logging import get_logger
|
|
14
14
|
from flock.core.util.input_resolver import resolve_inputs
|
|
15
15
|
|
|
16
16
|
logger = get_logger("activities")
|
|
17
17
|
tracer = trace.get_tracer(__name__)
|
|
18
18
|
|
|
19
|
+
def apply_handoff_strategy(previous_agent_output:str, next_agent_input:str, previous_agent_handoff_strategy:str, previous_agent_handoff_map:dict[str, str]) -> str:
|
|
20
|
+
if previous_agent_handoff_strategy == "append":
|
|
21
|
+
return next_agent_input + previous_agent_output
|
|
22
|
+
elif previous_agent_handoff_strategy == "override":
|
|
23
|
+
return previous_agent_output
|
|
24
|
+
elif previous_agent_handoff_strategy == "static":
|
|
25
|
+
return next_agent_input
|
|
26
|
+
elif previous_agent_handoff_strategy == "map":
|
|
27
|
+
for key, value in previous_agent_handoff_map.items():
|
|
28
|
+
next_agent_input = next_agent_input.replace(key, value)
|
|
29
|
+
return next_agent_input
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
19
32
|
|
|
20
33
|
@activity.defn
|
|
21
34
|
async def run_agent(context: FlockContext) -> dict:
|
|
@@ -29,6 +42,9 @@ async def run_agent(context: FlockContext) -> dict:
|
|
|
29
42
|
registry = get_registry()
|
|
30
43
|
|
|
31
44
|
previous_agent_name = ""
|
|
45
|
+
previous_agent_output = ""
|
|
46
|
+
previous_agent_handoff_strategy = ""
|
|
47
|
+
previous_agent_handoff_map = {}
|
|
32
48
|
if isinstance(context, dict):
|
|
33
49
|
context = FlockContext.from_dict(context)
|
|
34
50
|
current_agent_name = context.get_variable(FLOCK_CURRENT_AGENT)
|
|
@@ -53,8 +69,14 @@ async def run_agent(context: FlockContext) -> dict:
|
|
|
53
69
|
iter_span.set_attribute("agent.name", agent.name)
|
|
54
70
|
agent.context = context
|
|
55
71
|
# Resolve inputs for the agent.
|
|
72
|
+
# Gets values from context, previous agent output, and handoff strategy.
|
|
56
73
|
agent_inputs = resolve_inputs(
|
|
57
|
-
agent.input,
|
|
74
|
+
agent.input,
|
|
75
|
+
context,
|
|
76
|
+
previous_agent_name,
|
|
77
|
+
previous_agent_output,
|
|
78
|
+
previous_agent_handoff_strategy,
|
|
79
|
+
previous_agent_handoff_map
|
|
58
80
|
)
|
|
59
81
|
iter_span.add_event(
|
|
60
82
|
"resolved inputs", attributes={"inputs": str(agent_inputs)}
|
|
@@ -78,72 +100,44 @@ async def run_agent(context: FlockContext) -> dict:
|
|
|
78
100
|
exec_span.record_exception(e)
|
|
79
101
|
raise
|
|
80
102
|
|
|
81
|
-
# Determine the next agent using
|
|
82
|
-
|
|
103
|
+
# Determine the next agent using routing component if available
|
|
104
|
+
next_agent_name = None
|
|
83
105
|
|
|
84
|
-
if agent.
|
|
106
|
+
if agent.router:
|
|
85
107
|
logger.info(
|
|
86
|
-
f"Using
|
|
108
|
+
f"Using router: {agent.router.__class__.__name__}",
|
|
87
109
|
agent=agent.name,
|
|
88
110
|
)
|
|
89
111
|
try:
|
|
90
|
-
# Route to the next agent
|
|
91
|
-
|
|
112
|
+
# Route to the next agent using new routing component
|
|
113
|
+
next_agent_name = await agent.router.determine_next_step(
|
|
92
114
|
agent, result, context
|
|
93
115
|
)
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"Executing handoff function", agent=agent.name
|
|
98
|
-
)
|
|
99
|
-
try:
|
|
100
|
-
handoff_data = handoff_data(context, result)
|
|
101
|
-
if isinstance(
|
|
102
|
-
handoff_data.next_agent, FlockAgent
|
|
103
|
-
):
|
|
104
|
-
handoff_data.next_agent = (
|
|
105
|
-
handoff_data.next_agent.name
|
|
106
|
-
)
|
|
107
|
-
except Exception as e:
|
|
108
|
-
logger.error(
|
|
109
|
-
"Handoff function error {} {}",
|
|
110
|
-
agent=agent.name,
|
|
111
|
-
error=str(e),
|
|
112
|
-
)
|
|
113
|
-
iter_span.record_exception(e)
|
|
114
|
-
return {"error": f"Handoff function error: {e}"}
|
|
115
|
-
elif isinstance(handoff_data.next_agent, FlockAgent):
|
|
116
|
-
handoff_data.next_agent = (
|
|
117
|
-
handoff_data.next_agent.name
|
|
118
|
-
)
|
|
117
|
+
# Set next_agent on the agent instance
|
|
118
|
+
agent.next_agent = next_agent_name
|
|
119
119
|
|
|
120
|
-
if not handoff_data.next_agent:
|
|
121
|
-
logger.info(
|
|
122
|
-
"Router found no suitable next agent",
|
|
123
|
-
agent=agent.name,
|
|
124
|
-
)
|
|
125
|
-
context.record(
|
|
126
|
-
agent.name,
|
|
127
|
-
result,
|
|
128
|
-
timestamp=datetime.now().isoformat(),
|
|
129
|
-
hand_off=None,
|
|
130
|
-
called_from=previous_agent_name,
|
|
131
|
-
)
|
|
132
|
-
logger.info("Completing chain", agent=agent.name)
|
|
133
|
-
iter_span.add_event("chain completed")
|
|
134
|
-
return result
|
|
135
120
|
except Exception as e:
|
|
136
121
|
logger.error(
|
|
137
|
-
"Router error {}
|
|
138
|
-
agent.name,
|
|
139
|
-
str(e),
|
|
122
|
+
f"Router error: {e}",
|
|
123
|
+
agent=agent.name,
|
|
124
|
+
error=str(e),
|
|
140
125
|
)
|
|
141
126
|
iter_span.record_exception(e)
|
|
142
127
|
return {"error": f"Router error: {e}"}
|
|
143
128
|
else:
|
|
144
|
-
#
|
|
129
|
+
# Check if next_agent was set directly by user
|
|
130
|
+
next_agent_name = agent.next_agent
|
|
131
|
+
if callable(next_agent_name):
|
|
132
|
+
try:
|
|
133
|
+
next_agent_name = next_agent_name(context, result)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"next_agent callable error: {e}")
|
|
136
|
+
return {"error": f"next_agent callable error: {e}"}
|
|
137
|
+
|
|
138
|
+
if not next_agent_name:
|
|
145
139
|
logger.info(
|
|
146
|
-
"No
|
|
140
|
+
"No next agent found, completing chain",
|
|
147
141
|
agent=agent.name,
|
|
148
142
|
)
|
|
149
143
|
context.record(
|
|
@@ -161,53 +155,34 @@ async def run_agent(context: FlockContext) -> dict:
|
|
|
161
155
|
agent.name,
|
|
162
156
|
result,
|
|
163
157
|
timestamp=datetime.now().isoformat(),
|
|
164
|
-
hand_off=
|
|
158
|
+
hand_off={"next_agent": next_agent_name} if next_agent_name else None,
|
|
165
159
|
called_from=previous_agent_name,
|
|
166
160
|
)
|
|
161
|
+
# Remember the current agent's details for the next iteration.
|
|
167
162
|
previous_agent_name = agent.name
|
|
168
163
|
previous_agent_output = agent.output
|
|
169
|
-
|
|
170
|
-
|
|
164
|
+
previous_agent_handoff_strategy = agent.config.handoff_strategy
|
|
165
|
+
previous_agent_handoff_map = agent.config.handoff_map
|
|
171
166
|
|
|
172
|
-
#
|
|
167
|
+
# Activate the next agent.
|
|
173
168
|
try:
|
|
174
|
-
agent = registry.get_agent(
|
|
175
|
-
if handoff_data.output_to_input_merge_strategy == "add":
|
|
176
|
-
agent.input = previous_agent_output + ", " + agent.input
|
|
177
|
-
|
|
178
|
-
if handoff_data.add_input_fields:
|
|
179
|
-
for field in handoff_data.add_input_fields:
|
|
180
|
-
agent.input = field + ", " + agent.input
|
|
181
|
-
|
|
182
|
-
if handoff_data.add_output_fields:
|
|
183
|
-
for field in handoff_data.add_output_fields:
|
|
184
|
-
agent.output = field + ", " + agent.output
|
|
185
|
-
|
|
186
|
-
if handoff_data.add_description:
|
|
187
|
-
if agent.description:
|
|
188
|
-
agent.description = (
|
|
189
|
-
agent.description
|
|
190
|
-
+ "\n"
|
|
191
|
-
+ handoff_data.add_description
|
|
192
|
-
)
|
|
193
|
-
else:
|
|
194
|
-
agent.description = handoff_data.add_description
|
|
195
|
-
|
|
196
|
-
agent.resolve_callables(context=context)
|
|
169
|
+
agent = registry.get_agent(next_agent_name)
|
|
197
170
|
if not agent:
|
|
198
171
|
logger.error(
|
|
199
172
|
"Next agent not found",
|
|
200
|
-
agent=
|
|
173
|
+
agent=next_agent_name,
|
|
201
174
|
)
|
|
202
175
|
iter_span.record_exception(
|
|
203
176
|
Exception(
|
|
204
|
-
f"Next agent '{
|
|
177
|
+
f"Next agent '{next_agent_name}' not found"
|
|
205
178
|
)
|
|
206
179
|
)
|
|
207
180
|
return {
|
|
208
|
-
"error": f"Next agent '{
|
|
181
|
+
"error": f"Next agent '{next_agent_name}' not found."
|
|
209
182
|
}
|
|
210
183
|
|
|
184
|
+
agent.resolve_callables(context=context)
|
|
185
|
+
|
|
211
186
|
context.set_variable(FLOCK_CURRENT_AGENT, agent.name)
|
|
212
187
|
|
|
213
188
|
logger.info("Handing off to next agent", next=agent.name)
|