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.

Files changed (103) hide show
  1. flock/cli/manage_agents.py +3 -3
  2. flock/components/__init__.py +28 -0
  3. flock/components/evaluation/__init__.py +9 -0
  4. flock/components/evaluation/declarative_evaluation_component.py +198 -0
  5. flock/components/routing/__init__.py +15 -0
  6. flock/{routers/conditional/conditional_router.py → components/routing/conditional_routing_component.py} +60 -49
  7. flock/components/routing/default_routing_component.py +103 -0
  8. flock/components/routing/llm_routing_component.py +208 -0
  9. flock/components/utility/__init__.py +15 -0
  10. flock/{modules/enterprise_memory/enterprise_memory_module.py → components/utility/memory_utility_component.py} +195 -173
  11. flock/{modules/performance/metrics_module.py → components/utility/metrics_utility_component.py} +101 -86
  12. flock/{modules/output/output_module.py → components/utility/output_utility_component.py} +49 -49
  13. flock/core/__init__.py +2 -8
  14. flock/core/agent/__init__.py +16 -0
  15. flock/core/agent/flock_agent_components.py +104 -0
  16. flock/core/agent/flock_agent_execution.py +101 -0
  17. flock/core/agent/flock_agent_integration.py +147 -0
  18. flock/core/agent/flock_agent_lifecycle.py +177 -0
  19. flock/core/agent/flock_agent_serialization.py +378 -0
  20. flock/core/component/__init__.py +15 -0
  21. flock/core/{flock_module.py → component/agent_component_base.py} +136 -35
  22. flock/core/component/evaluation_component_base.py +56 -0
  23. flock/core/component/routing_component_base.py +75 -0
  24. flock/core/component/utility_component_base.py +69 -0
  25. flock/core/config/flock_agent_config.py +49 -2
  26. flock/core/evaluation/utils.py +1 -1
  27. flock/core/execution/evaluation_executor.py +1 -1
  28. flock/core/flock.py +137 -483
  29. flock/core/flock_agent.py +151 -1018
  30. flock/core/flock_factory.py +94 -73
  31. flock/core/{flock_registry.py → flock_registry.py.backup} +3 -17
  32. flock/core/logging/logging.py +1 -0
  33. flock/core/mcp/flock_mcp_server.py +42 -37
  34. flock/core/mixin/dspy_integration.py +5 -5
  35. flock/core/orchestration/__init__.py +18 -0
  36. flock/core/orchestration/flock_batch_processor.py +94 -0
  37. flock/core/orchestration/flock_evaluator.py +113 -0
  38. flock/core/orchestration/flock_execution.py +288 -0
  39. flock/core/orchestration/flock_initialization.py +125 -0
  40. flock/core/orchestration/flock_server_manager.py +65 -0
  41. flock/core/orchestration/flock_web_server.py +117 -0
  42. flock/core/registry/__init__.py +39 -0
  43. flock/core/registry/agent_registry.py +69 -0
  44. flock/core/registry/callable_registry.py +139 -0
  45. flock/core/registry/component_discovery.py +142 -0
  46. flock/core/registry/component_registry.py +64 -0
  47. flock/core/registry/config_mapping.py +64 -0
  48. flock/core/registry/decorators.py +137 -0
  49. flock/core/registry/registry_hub.py +202 -0
  50. flock/core/registry/server_registry.py +57 -0
  51. flock/core/registry/type_registry.py +86 -0
  52. flock/core/serialization/flock_serializer.py +33 -30
  53. flock/core/serialization/serialization_utils.py +28 -25
  54. flock/core/util/input_resolver.py +29 -2
  55. flock/platform/docker_tools.py +3 -3
  56. flock/tools/markdown_tools.py +1 -2
  57. flock/tools/text_tools.py +1 -2
  58. flock/webapp/app/main.py +9 -5
  59. flock/workflow/activities.py +59 -84
  60. flock/workflow/activities_unified.py +230 -0
  61. flock/workflow/agent_execution_activity.py +6 -6
  62. flock/workflow/flock_workflow.py +1 -1
  63. {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/METADATA +4 -4
  64. {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/RECORD +67 -68
  65. flock/core/flock_evaluator.py +0 -60
  66. flock/core/flock_router.py +0 -83
  67. flock/evaluators/__init__.py +0 -1
  68. flock/evaluators/declarative/__init__.py +0 -1
  69. flock/evaluators/declarative/declarative_evaluator.py +0 -194
  70. flock/evaluators/memory/memory_evaluator.py +0 -90
  71. flock/evaluators/test/test_case_evaluator.py +0 -38
  72. flock/evaluators/zep/zep_evaluator.py +0 -59
  73. flock/modules/__init__.py +0 -1
  74. flock/modules/assertion/__init__.py +0 -1
  75. flock/modules/assertion/assertion_module.py +0 -286
  76. flock/modules/callback/__init__.py +0 -1
  77. flock/modules/callback/callback_module.py +0 -91
  78. flock/modules/enterprise_memory/README.md +0 -99
  79. flock/modules/mem0/__init__.py +0 -1
  80. flock/modules/mem0/mem0_module.py +0 -126
  81. flock/modules/mem0_async/__init__.py +0 -1
  82. flock/modules/mem0_async/async_mem0_module.py +0 -126
  83. flock/modules/memory/__init__.py +0 -1
  84. flock/modules/memory/memory_module.py +0 -429
  85. flock/modules/memory/memory_parser.py +0 -125
  86. flock/modules/memory/memory_storage.py +0 -736
  87. flock/modules/output/__init__.py +0 -1
  88. flock/modules/performance/__init__.py +0 -1
  89. flock/modules/zep/__init__.py +0 -1
  90. flock/modules/zep/zep_module.py +0 -192
  91. flock/routers/__init__.py +0 -1
  92. flock/routers/agent/__init__.py +0 -1
  93. flock/routers/agent/agent_router.py +0 -236
  94. flock/routers/agent/handoff_agent.py +0 -58
  95. flock/routers/default/__init__.py +0 -1
  96. flock/routers/default/default_router.py +0 -80
  97. flock/routers/feedback/feedback_router.py +0 -114
  98. flock/routers/list_generator/list_generator_router.py +0 -166
  99. flock/routers/llm/__init__.py +0 -1
  100. flock/routers/llm/llm_router.py +0 -365
  101. {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/WHEEL +0 -0
  102. {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/entry_points.txt +0 -0
  103. {flock_core-0.4.519.dist-info → flock_core-0.5.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -1,114 +0,0 @@
1
- # src/flock/routers/correction/correction_router.py (New File)
2
-
3
- from typing import Any
4
-
5
- from pydantic import Field
6
-
7
- from flock.core.context.context import FlockContext
8
- from flock.core.flock_agent import FlockAgent
9
- from flock.core.flock_registry import flock_component
10
- from flock.core.flock_router import (
11
- FlockRouter,
12
- FlockRouterConfig,
13
- HandOffRequest,
14
- )
15
- from flock.core.logging.logging import get_logger
16
-
17
- logger = get_logger("router.correction")
18
-
19
-
20
- class FeedbackRetryRouterConfig(FlockRouterConfig):
21
- max_retries: int = Field(
22
- default=1,
23
- description="Maximum number of times to retry the same agent on failure.",
24
- )
25
- feedback_context_key: str = Field(
26
- default="flock.assertion_feedback",
27
- description="Context key containing feedback from AssertionCheckerModule.",
28
- )
29
- retry_count_context_key_prefix: str = Field(
30
- default="flock.retry_count_",
31
- description="Prefix for context key storing retry attempts per agent.",
32
- )
33
- fallback_agent: str | None = Field(
34
- None, description="Agent to route to if max_retries is exceeded."
35
- )
36
-
37
-
38
- @flock_component(config_class=FeedbackRetryRouterConfig)
39
- class FeedbackRetryRouter(FlockRouter):
40
- """Routes based on assertion feedback in the context.
41
-
42
- If feedback exists for the current agent and retries are not exhausted,
43
- it routes back to the same agent, adding the feedback to its input.
44
- Otherwise, it can route to a fallback agent or stop the chain.
45
- """
46
-
47
- name: str = "feedback_retry_router"
48
- config: FeedbackRetryRouterConfig = Field(
49
- default_factory=FeedbackRetryRouterConfig
50
- )
51
-
52
- async def route(
53
- self,
54
- current_agent: FlockAgent,
55
- result: dict[str, Any],
56
- context: FlockContext,
57
- ) -> HandOffRequest:
58
- feedback = context.get_variable(self.config.feedback_context_key)
59
-
60
- if feedback:
61
- logger.warning(
62
- f"Assertion feedback detected for agent '{current_agent.name}'. Attempting retry."
63
- )
64
-
65
- retry_key = f"{self.config.retry_count_context_key_prefix}{current_agent.name}"
66
- retry_count = context.get_variable(retry_key, 0)
67
- logger.warning(f"Feedback: {feedback} - Retry Count {retry_count}")
68
-
69
- if retry_count < self.config.max_retries:
70
- logger.info(
71
- f"Routing back to agent '{current_agent.name}' for retry #{retry_count + 1}"
72
- )
73
- context.set_variable(retry_key, retry_count + 1)
74
- context.set_variable(
75
- f"{current_agent.name}_prev_result", result
76
- )
77
- # Add feedback to the *next* agent's input (which is the same agent)
78
- # Requires the agent's signature to potentially accept a 'feedback' input field.
79
- return HandOffRequest(
80
- next_agent=current_agent.name,
81
- output_to_input_merge_strategy="match", # Add feedback to existing context/previous results
82
- add_input_fields=[
83
- f"{self.config.feedback_context_key} | Feedback for prev result",
84
- f"{current_agent.name}_prev_result | Previous Result",
85
- ],
86
- add_description=f"Try to fix the previous result based on the feedback.",
87
- override_context=None, # Context already updated with feedback and retry count
88
- )
89
- else:
90
- logger.error(
91
- f"Max retries ({self.config.max_retries}) exceeded for agent '{current_agent.name}'."
92
- )
93
- # Max retries exceeded, route to fallback or stop
94
- if self.config.fallback_agent:
95
- logger.info(
96
- f"Routing to fallback agent '{self.config.fallback_agent}'"
97
- )
98
- # Clear feedback before going to fallback? Optional.
99
- if self.config.feedback_context_key in context.state:
100
- del context.state[self.config.feedback_context_key]
101
- return HandOffRequest(next_agent=self.config.fallback_agent)
102
- else:
103
- logger.info("No fallback agent defined. Stopping workflow.")
104
- return HandOffRequest(next_agent="") # Stop the chain
105
-
106
- else:
107
- # No feedback, assertions passed or module not configured for feedback
108
- logger.debug(
109
- f"No assertion feedback for agent '{current_agent.name}'. Proceeding normally."
110
- )
111
- # Default behavior: Stop the chain if no other routing is defined
112
- # In a real system, you might chain this with another router (e.g., LLMRouter)
113
- # to decide the *next different* agent if assertions passed.
114
- return HandOffRequest(next_agent="") # Stop or pass to next router
@@ -1,166 +0,0 @@
1
- # src/flock/routers/list_generator/iterative_list_router.py (New File)
2
-
3
- from typing import Any
4
-
5
- from pydantic import Field
6
-
7
- from flock.core.context.context import FlockContext
8
- from flock.core.flock_agent import FlockAgent
9
- from flock.core.flock_registry import flock_component
10
- from flock.core.flock_router import (
11
- FlockRouter,
12
- FlockRouterConfig,
13
- HandOffRequest,
14
- )
15
- from flock.core.logging.logging import get_logger
16
-
17
- # Need signature utils
18
-
19
- logger = get_logger("router.list_generator")
20
-
21
-
22
- class IterativeListGeneratorRouterConfig(FlockRouterConfig):
23
- target_list_field: str = Field(
24
- ...,
25
- description="Name of the final list output field (e.g., 'chapters').",
26
- )
27
- item_output_field: str = Field(
28
- ...,
29
- description="Name of the single item output field for each iteration (e.g., 'chapter').",
30
- )
31
- context_input_field: str = Field(
32
- default="previous_items",
33
- description="Input field name for passing back generated items (e.g., 'existing_chapters').",
34
- )
35
- max_iterations: int = Field(
36
- default=10, description="Maximum number of items to generate."
37
- )
38
- # More advanced: termination_condition: Optional[Callable] = None
39
- # Store iteration state in context under this prefix
40
- context_state_prefix: str = Field(
41
- default="flock.iterator_state_",
42
- description="Prefix for context keys storing iteration state.",
43
- )
44
-
45
- # Field to extract item type from target_list_field signature
46
- # This might require parsing the original agent's output signature
47
- # item_type_str: Optional[str] = None # e.g., 'dict[str, str]' or 'MyChapterType'
48
-
49
-
50
- @flock_component(config_class=IterativeListGeneratorRouterConfig)
51
- class IterativeListGeneratorRouter(FlockRouter):
52
- name: str = "iterative_list_generator"
53
- config: IterativeListGeneratorRouterConfig = Field(
54
- default_factory=IterativeListGeneratorRouterConfig
55
- )
56
-
57
- # Helper to get state keys
58
- def _get_state_keys(self, agent_name: str) -> tuple[str, str]:
59
- prefix = self.config.context_state_prefix
60
- list_key = f"{prefix}{agent_name}_{self.config.target_list_field}"
61
- count_key = f"{prefix}{agent_name}_iteration_count"
62
- return list_key, count_key
63
-
64
- async def route(
65
- self,
66
- current_agent: FlockAgent,
67
- result: dict[str, Any],
68
- context: FlockContext,
69
- ) -> HandOffRequest:
70
- list_key, count_key = self._get_state_keys(current_agent.name)
71
-
72
- # --- State Initialization (First Run) ---
73
- if count_key not in context.state:
74
- logger.debug(
75
- f"Initializing iterative list generation for '{self.config.target_list_field}' in agent '{current_agent.name}'."
76
- )
77
- context.set_variable(count_key, 0)
78
- context.set_variable(list_key, [])
79
- # Modify agent signature for the *first* iteration (remove context_input_field, use item_output_field)
80
- # This requires modifying the agent's internal state or creating a temporary one.
81
- # Let's try modifying the context passed to the *next* run instead.
82
- context.set_variable(
83
- f"{current_agent.name}.next_run_output_field",
84
- self.config.item_output_field,
85
- )
86
- context.set_variable(
87
- f"{current_agent.name}.next_run_input_fields_to_exclude",
88
- {self.config.context_input_field},
89
- )
90
-
91
- # --- Process Result of Previous Iteration ---
92
- iteration_count = context.get_variable(count_key, 0)
93
- generated_items = context.get_variable(list_key, [])
94
-
95
- # Get the single item generated in the *last* run
96
- # The result dict should contain the 'item_output_field' if it wasn't the very first run
97
- new_item = result.get(self.config.item_output_field)
98
-
99
- if (
100
- new_item is not None and iteration_count > 0
101
- ): # Add item from previous run (not the init run)
102
- generated_items.append(new_item)
103
- context.set_variable(list_key, generated_items) # Update context
104
- logger.info(
105
- f"Added item #{iteration_count} to list '{self.config.target_list_field}' for agent '{current_agent.name}'."
106
- )
107
- elif iteration_count > 0:
108
- logger.warning(
109
- f"Iteration {iteration_count} for agent '{current_agent.name}' did not produce expected output field '{self.config.item_output_field}'."
110
- )
111
- # Decide how to handle: stop, retry, continue? Let's continue for now.
112
-
113
- # Increment iteration count *after* processing the result of the previous one
114
- current_iteration = iteration_count + 1
115
- context.set_variable(count_key, current_iteration)
116
-
117
- # --- Termination Check ---
118
- if current_iteration > self.config.max_iterations:
119
- logger.info(
120
- f"Max iterations ({self.config.max_iterations}) reached for '{self.config.target_list_field}' in agent '{current_agent.name}'. Finalizing."
121
- )
122
- # Clean up state
123
- del context.state[count_key]
124
- # Final result should be the list itself under the target_list_field key
125
- final_result = {self.config.target_list_field: generated_items}
126
- # Handoff with empty next_agent to stop, but potentially override the *result*
127
- # This is tricky. Routers usually decide the *next agent*, not the *final output*.
128
- # Maybe the router should just signal termination, and the Flock run loop handles assembling the final output?
129
- # Let's assume the router signals termination by returning next_agent=""
130
- # The final list is already in the context under list_key.
131
- # A final "AssemblerAgent" could read this context variable.
132
- # OR we modify the HandOffRequest:
133
- return HandOffRequest(
134
- next_agent="", final_output_override=final_result
135
- ) # Needs HandOffRequest modification
136
-
137
- # --- Prepare for Next Iteration ---
138
- logger.info(
139
- f"Routing back to agent '{current_agent.name}' for item #{current_iteration} of '{self.config.target_list_field}'."
140
- )
141
-
142
- # The agent needs the context (previously generated items) and the original inputs again.
143
- # We will pass the generated items via the context_input_field.
144
- # The original inputs (like story_outline) should still be in the context.
145
- next_input_override = {
146
- self.config.context_input_field: generated_items # Pass the list back
147
- }
148
-
149
- # Modify agent signature for the *next* iteration (add context_input_field, use item_output_field)
150
- # This is the trickiest part - how to modify the agent's perceived signature for the next run?
151
- # Option 1: Pass overrides via HandOffRequest (cleanest)
152
- next_signature_input = f"{current_agent.input}, {self.config.context_input_field}: list | Previously generated items" # Needs smarter joining
153
- next_signature_output = (
154
- self.config.item_output_field
155
- ) # Only ask for one item
156
-
157
- # This requires HandOffRequest and Flock execution loop to support signature overrides
158
- return HandOffRequest(
159
- next_agent=current_agent.name,
160
- output_to_input_merge_strategy="add", # Add the context_input_field to existing context
161
- input_override=next_input_override, # Provide the actual list data
162
- # --- Hypothetical Overrides ---
163
- next_run_input_signature_override=next_signature_input,
164
- next_run_output_signature_override=next_signature_output,
165
- # -----------------------------
166
- )
@@ -1 +0,0 @@
1
- """LLM-based router implementation for the Flock framework."""
@@ -1,365 +0,0 @@
1
- """LLM-based router implementation for the Flock framework."""
2
-
3
- import json
4
- from typing import Any
5
-
6
- import litellm
7
-
8
- from flock.core.context.context import FlockContext
9
- from flock.core.flock_agent import FlockAgent
10
- from flock.core.flock_registry import flock_component
11
- from flock.core.flock_router import (
12
- FlockRouter,
13
- FlockRouterConfig,
14
- HandOffRequest,
15
- )
16
- from flock.core.logging.logging import get_logger
17
-
18
- logger = get_logger("llm_router")
19
-
20
-
21
- class LLMRouterConfig(FlockRouterConfig):
22
- """Configuration for the LLM router.
23
-
24
- This class extends FlockRouterConfig with parameters specific to the LLM router.
25
- """
26
-
27
- temperature: float = 0.2
28
- max_tokens: int = 500
29
- confidence_threshold: float = 0.5
30
- prompt: str = ""
31
-
32
-
33
- @flock_component(config_class=LLMRouterConfig)
34
- class LLMRouter(FlockRouter):
35
- """Router that uses an LLM to determine the next agent in a workflow.
36
-
37
- This class is responsible for:
38
- 1. Analyzing available agents in the registry
39
- 2. Using an LLM to score each agent's suitability as the next step
40
- 3. Selecting the highest-scoring agent
41
- 4. Creating a HandOff object with the selected agent
42
- """
43
-
44
- def __init__(
45
- self,
46
- name: str = "llm_router",
47
- config: LLMRouterConfig | None = None,
48
- ):
49
- """Initialize the LLMRouter.
50
-
51
- Args:
52
- registry: The agent registry containing all available agents
53
- name: The name of the router
54
- config: The router configuration
55
- """
56
- logger.info(f"Initializing LLM Router '{name}'")
57
- super().__init__(name=name, config=config or LLMRouterConfig(name=name))
58
- logger.debug(
59
- "LLM Router configuration",
60
- temperature=self.config.temperature,
61
- max_tokens=self.config.max_tokens,
62
- )
63
-
64
- async def route(
65
- self,
66
- current_agent: FlockAgent,
67
- result: dict[str, Any],
68
- context: FlockContext,
69
- ) -> HandOffRequest:
70
- """Determine the next agent to hand off to based on the current agent's output.
71
-
72
- Args:
73
- current_agent: The agent that just completed execution
74
- result: The output from the current agent
75
- context: The global execution context
76
-
77
- Returns:
78
- A HandOff object containing the next agent and input data
79
- """
80
- logger.info(
81
- f"Routing from agent '{current_agent.name}'",
82
- current_agent=current_agent.name,
83
- )
84
- logger.debug("Current agent result", result=result)
85
-
86
- agent_definitions = context.agent_definitions
87
- # Get all available agents from the registry
88
- available_agents = self._get_available_agents(
89
- agent_definitions, current_agent.name
90
- )
91
- logger.debug(
92
- "Available agents for routing",
93
- count=len(available_agents),
94
- agents=[a.agent_data["name"] for a in available_agents],
95
- )
96
-
97
- if not available_agents:
98
- logger.warning(
99
- "No available agents for routing",
100
- current_agent=current_agent.name,
101
- )
102
- return HandOffRequest(
103
- next_agent="", override_next_agent={}, override_context=None
104
- )
105
-
106
- # Use LLM to determine the best next agent
107
- next_agent_name, score = await self._select_next_agent(
108
- current_agent, result, available_agents
109
- )
110
- logger.info(
111
- "Agent selection result",
112
- next_agent=next_agent_name,
113
- score=score,
114
- )
115
-
116
- if not next_agent_name or score < self.config.confidence_threshold:
117
- logger.warning(
118
- "No suitable next agent found",
119
- best_score=score,
120
- )
121
- return HandOffRequest(
122
- next_agent="", override_next_agent={}, override_context=None
123
- )
124
-
125
- # Get the next agent from the registry
126
- next_agent = agent_definitions.get(next_agent_name)
127
- if not next_agent:
128
- logger.error(
129
- "Selected agent not found in registry",
130
- agent_name=next_agent_name,
131
- )
132
- return HandOffRequest(
133
- next_agent="", override_next_agent={}, override_context=None
134
- )
135
-
136
- # Create input for the next agent
137
-
138
- logger.success(
139
- f"Successfully routed to agent '{next_agent_name}'",
140
- score=score,
141
- from_agent=current_agent.name,
142
- )
143
- return HandOffRequest(
144
- next_agent=next_agent_name,
145
- output_to_input_merge_strategy="add",
146
- override_next_agent=None,
147
- override_context=None,
148
- )
149
-
150
- def _get_available_agents(
151
- self, agent_definitions: dict[str, Any], current_agent_name: str
152
- ) -> list[FlockAgent]:
153
- """Get all available agents except the current one.
154
-
155
- Args:
156
- current_agent_name: Name of the current agent to exclude
157
-
158
- Returns:
159
- List of available agents
160
- """
161
- logger.debug(
162
- "Getting available agents",
163
- total_agents=len(agent_definitions),
164
- current_agent=current_agent_name,
165
- )
166
- agents = []
167
- for agent in agent_definitions:
168
- if agent != current_agent_name:
169
- agents.append(agent_definitions.get(agent))
170
- return agents
171
-
172
- async def _select_next_agent(
173
- self,
174
- current_agent: FlockAgent,
175
- result: dict[str, Any],
176
- available_agents: list[FlockAgent],
177
- ) -> tuple[str, float]:
178
- """Use an LLM to select the best next agent.
179
-
180
- Args:
181
- current_agent: The agent that just completed execution
182
- result: The output from the current agent
183
- available_agents: List of available agents to choose from
184
-
185
- Returns:
186
- Tuple of (selected_agent_name, confidence_score)
187
- """
188
- logger.debug(
189
- "Selecting next agent",
190
- current_agent=current_agent.name,
191
- available_count=len(available_agents),
192
- )
193
-
194
- # Prepare the prompt for the LLM
195
- prompt = self._create_selection_prompt(
196
- current_agent, result, available_agents
197
- )
198
- logger.debug("Generated selection prompt", prompt_length=len(prompt))
199
-
200
- try:
201
- logger.info(
202
- "Calling LLM for agent selection",
203
- model=current_agent.model,
204
- temperature=self.config.temperature,
205
- )
206
- # Call the LLM to get the next agent
207
- response = await litellm.acompletion(
208
- model=current_agent.model,
209
- messages=[{"role": "user", "content": prompt}],
210
- temperature=self.config.temperature
211
- if isinstance(self.config, LLMRouterConfig)
212
- else 0.2,
213
- max_tokens=self.config.max_tokens
214
- if isinstance(self.config, LLMRouterConfig)
215
- else 500,
216
- )
217
-
218
- content = response.choices[0].message.content
219
- # Parse the response to get the agent name and score
220
- try:
221
- # extract the json object from the response
222
- content = content.split("```json")[1].split("```")[0]
223
- data = json.loads(content)
224
- next_agent = data.get("next_agent", "")
225
- score = float(data.get("score", 0))
226
- reasoning = data.get("reasoning", "")
227
- logger.info(
228
- "Successfully parsed LLM response",
229
- next_agent=next_agent,
230
- score=score,
231
- reasoning=reasoning,
232
- )
233
- return next_agent, score
234
- except (json.JSONDecodeError, ValueError) as e:
235
- logger.error(
236
- "Failed to parse LLM response",
237
- error=str(e),
238
- raw_response=content,
239
- )
240
- logger.debug("Attempting fallback parsing")
241
-
242
- # Fallback: try to extract the agent name from the text
243
- for agent in available_agents:
244
- if agent.agent_data["name"] in content:
245
- logger.info(
246
- "Found agent name in response using fallback",
247
- agent=agent.agent_data["name"],
248
- )
249
- return agent.agent_data[
250
- "name"
251
- ], 0.6 # Default score for fallback
252
-
253
- return "", 0.0
254
-
255
- except Exception as e:
256
- logger.error(
257
- "Error calling LLM for agent selection",
258
- error=str(e),
259
- current_agent=current_agent.name,
260
- )
261
- return "", 0.0
262
-
263
- def _create_selection_prompt(
264
- self,
265
- current_agent: FlockAgent,
266
- result: dict[str, Any],
267
- available_agents: list[FlockAgent],
268
- ) -> str:
269
- """Create a prompt for the LLM to select the next agent.
270
-
271
- Args:
272
- current_agent: The agent that just completed execution
273
- result: The output from the current agent
274
- available_agents: List of available agents to choose from
275
-
276
- Returns:
277
- Prompt string for the LLM
278
- """
279
- # Format the current agent's output
280
- result_str = json.dumps(result, indent=2)
281
-
282
- # Format the available agents' information
283
- agents_info = []
284
- for agent in available_agents:
285
- agent_info = {
286
- "name": agent.agent_data["name"],
287
- "description": agent.agent_data["description"]
288
- if agent.agent_data["description"]
289
- else "",
290
- "input": agent.agent_data["input"],
291
- "output": agent.agent_data["output"],
292
- }
293
- agents_info.append(agent_info)
294
-
295
- agents_str = json.dumps(agents_info, indent=2)
296
-
297
- # Create the prompt
298
- if self.config.prompt:
299
- prompt = self.config.prompt
300
- else:
301
- prompt = f"""
302
- You are a workflow router that determines the next agent to execute in a multi-agent system.
303
-
304
- CURRENT AGENT:
305
- Name: {current_agent.name}
306
- Description: {current_agent.description}
307
- Input: {current_agent.input}
308
- Output: {current_agent.output}
309
-
310
- CURRENT AGENT'S OUTPUT:
311
- {result_str}
312
-
313
- AVAILABLE AGENTS:
314
- {agents_str}
315
-
316
- Based on the current agent's output and the available agents, determine which agent should be executed next.
317
- Consider the following:
318
- 1. Which agent's input requirements best match the current agent's output?
319
- 2. Which agent's purpose and description make it the most logical next step?
320
- 3. Which agent would provide the most value in continuing the workflow?
321
-
322
- Respond with a JSON object containing:
323
- 1. "next_agent": The name of the selected agent
324
- 2. "score": A confidence score between 0 and 1 indicating how suitable this agent is
325
- 3. "reasoning": A brief explanation of why this agent was selected
326
-
327
- If no agent is suitable, set "next_agent" to an empty string and "score" to 0.
328
-
329
- JSON Response:
330
- """
331
- return prompt
332
-
333
- def _create_next_input(
334
- self,
335
- current_agent: FlockAgent,
336
- result: dict[str, Any],
337
- next_agent: FlockAgent,
338
- ) -> dict[str, Any]:
339
- """Create the input for the next agent, including the previous agent's output.
340
-
341
- Args:
342
- current_agent: The agent that just completed execution
343
- result: The output from the current agent
344
- next_agent: The next agent to execute
345
-
346
- Returns:
347
- Input dictionary for the next agent
348
- """
349
- # Start with an empty input
350
- next_input = {}
351
-
352
- # Add a special field for the previous agent's output
353
- next_input["previous_agent_output"] = {
354
- "agent_name": current_agent.name,
355
- "result": result,
356
- }
357
-
358
- # Try to map the current agent's output to the next agent's input
359
- # This is a simple implementation that could be enhanced with more sophisticated mapping
360
- for key in result:
361
- # If the next agent expects this key, add it directly
362
- if key in next_agent.input:
363
- next_input[key] = result[key]
364
-
365
- return next_input