flock-core 0.2.1__py3-none-any.whl → 0.2.2__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.

@@ -1,31 +1,31 @@
1
- """A context object for storing state, history, and agent definitions."""
2
-
3
1
  from dataclasses import asdict, dataclass, field
4
2
  from datetime import datetime
5
3
  from typing import Any, Literal
6
4
 
5
+ from opentelemetry import trace
6
+
7
7
  from flock.core.context.context_vars import FLOCK_LAST_AGENT, FLOCK_LAST_RESULT
8
+ from flock.core.logging.logging import get_logger
8
9
  from flock.core.util.serializable import Serializable
9
10
 
11
+ logger = get_logger("context")
12
+ tracer = trace.get_tracer(__name__)
13
+
10
14
 
11
15
  @dataclass
12
16
  class AgentRunRecord:
13
- """A record of an agent run, including the agent name, data, and timestamp."""
14
-
15
- agent: str = field(default="") # Agent name
17
+ agent: str = field(default="")
16
18
  data: dict[str, Any] = field(default_factory=dict)
17
19
  timestamp: str = field(default="")
18
- hand_off: dict = field(default=dict) # Next agent name
19
- called_from: str = field(default="") # Previous agent name
20
+ hand_off: dict = field(default_factory=dict)
21
+ called_from: str = field(default="")
20
22
 
21
23
 
22
24
  @dataclass
23
25
  class AgentDefinition:
24
- """A serializable definition for an agent, including the agent type, name, and data."""
25
-
26
26
  agent_type: str = field(default="")
27
27
  agent_name: str = field(default="")
28
- agent_data: dict = field(default=dict)
28
+ agent_data: dict = field(default_factory=dict)
29
29
  serializer: Literal["json", "cloudpickle", "msgpack"] = field(
30
30
  default="cloudpickle"
31
31
  )
@@ -33,8 +33,6 @@ class AgentDefinition:
33
33
 
34
34
  @dataclass
35
35
  class FlockContext(Serializable):
36
- """A context object for storing state, history, and agent definitions."""
37
-
38
36
  state: dict[str, Any] = field(default_factory=dict)
39
37
  history: list[AgentRunRecord] = field(default_factory=list)
40
38
  agent_definitions: dict[str, AgentDefinition] = field(default_factory=dict)
@@ -50,162 +48,135 @@ class FlockContext(Serializable):
50
48
  hand_off: str,
51
49
  called_from: str,
52
50
  ) -> None:
53
- """Record an agent run and update the state with the agent's output."""
54
- try:
55
- record = AgentRunRecord(
56
- agent=agent_name,
57
- data=data.copy(),
58
- timestamp=timestamp,
59
- hand_off=hand_off,
60
- called_from=called_from,
51
+ record = AgentRunRecord(
52
+ agent=agent_name,
53
+ data=data.copy(),
54
+ timestamp=timestamp,
55
+ hand_off=hand_off,
56
+ called_from=called_from,
57
+ )
58
+ self.history.append(record)
59
+ for key, value in data.items():
60
+ self.set_variable(f"{agent_name}.{key}", value)
61
+ self.set_variable(FLOCK_LAST_RESULT, data)
62
+ self.set_variable(FLOCK_LAST_AGENT, agent_name)
63
+ logger.info(
64
+ "Agent run recorded",
65
+ agent=agent_name,
66
+ timestamp=timestamp,
67
+ data=data,
68
+ )
69
+ current_span = trace.get_current_span()
70
+ if current_span.get_span_context().is_valid:
71
+ current_span.add_event(
72
+ "record",
73
+ attributes={"agent": agent_name, "timestamp": timestamp},
61
74
  )
62
- self.history.append(record)
63
-
64
- for key, value in data.items():
65
- self.set_variable(f"{agent_name}.{key}", value)
66
- self.set_variable(FLOCK_LAST_RESULT, data)
67
- self.set_variable(FLOCK_LAST_AGENT, agent_name)
68
- except Exception:
69
- raise
70
75
 
71
76
  def get_variable(self, key: str) -> Any:
72
- """Get the current value of a state variable."""
73
- try:
74
- return self.state.get(key)
75
- except Exception:
76
- raise
77
+ return self.state.get(key)
77
78
 
78
79
  def set_variable(self, key: str, value: Any) -> None:
79
- """Set the value of a state variable."""
80
- try:
81
- self.state[key] = value
82
- except Exception:
83
- raise
80
+ old_value = self.state.get(key)
81
+ self.state[key] = value
82
+ if old_value != value:
83
+ logger.info(
84
+ "Context variable updated",
85
+ variable=key,
86
+ old=old_value,
87
+ new=value,
88
+ )
89
+ current_span = trace.get_current_span()
90
+ if current_span.get_span_context().is_valid:
91
+ current_span.add_event(
92
+ "set_variable",
93
+ attributes={
94
+ "key": key,
95
+ "old": str(old_value),
96
+ "new": str(value),
97
+ },
98
+ )
84
99
 
85
100
  def deepcopy(self) -> "FlockContext":
86
- """Create a deep copy of the context."""
87
- try:
88
- return FlockContext.from_dict(self.to_dict())
89
- except Exception:
90
- raise
101
+ return FlockContext.from_dict(self.to_dict())
91
102
 
92
103
  def get_agent_history(self, agent_name: str) -> list[AgentRunRecord]:
93
- """Return all agent run records for a given agent name."""
94
- try:
95
- return [
96
- record for record in self.history if record.agent == agent_name
97
- ]
98
- except Exception:
99
- raise
104
+ return [record for record in self.history if record.agent == agent_name]
100
105
 
101
106
  def next_input_for(self, agent) -> Any:
102
- """By default, the next input for an agent is taken from the context state.
103
-
104
- If the agent.input is a comma-separated list (e.g., "input1, input2"),
105
- this method will return a dictionary with keys for each of the input names,
106
- fetching the latest values from the state.
107
-
108
- If only a single input is specified, the raw value is returned.
109
- """
110
107
  try:
111
108
  if hasattr(agent, "input") and isinstance(agent.input, str):
112
109
  keys = [k.strip() for k in agent.input.split(",") if k.strip()]
113
-
114
110
  if len(keys) == 1:
115
111
  return self.get_variable(keys[0])
116
112
  else:
117
113
  return {key: self.get_variable(key) for key in keys}
118
114
  else:
119
- # Fallback to "init_input"
120
115
  return self.get_variable("init_input")
121
- except Exception:
116
+ except Exception as e:
117
+ logger.error(
118
+ "Error getting next input for agent",
119
+ agent=agent.name,
120
+ error=str(e),
121
+ )
122
122
  raise
123
123
 
124
124
  def get_most_recent_value(self, variable_name: str) -> Any:
125
- """Get the definition for a specific agent."""
126
- try:
127
- for history_record in reversed(self.history):
128
- if variable_name in history_record.data:
129
- return history_record.data[variable_name]
130
- except Exception:
131
- raise
125
+ for history_record in reversed(self.history):
126
+ if variable_name in history_record.data:
127
+ return history_record.data[variable_name]
132
128
 
133
129
  def get_agent_definition(self, agent_name: str) -> AgentDefinition | None:
134
- """Get the definition for a specific agent."""
135
- try:
136
- for definition in self.agent_definitions:
137
- if definition.name == agent_name:
138
- return definition
139
- return None
140
- except Exception:
141
- raise
130
+ return self.agent_definitions.get(agent_name)
142
131
 
143
132
  def add_agent_definition(
144
133
  self, agent_type: type, agent_name: str, agent_data: Any
145
134
  ) -> None:
146
- """Add a new agent definition to the context."""
147
- try:
148
- definition = AgentDefinition(
149
- agent_type=agent_type.__name__,
150
- agent_name=agent_name,
151
- agent_data=agent_data,
152
- )
153
- self.agent_definitions[agent_name] = definition
154
- except Exception:
155
- raise
156
-
157
- # Allow dict-like access for convenience.
135
+ definition = AgentDefinition(
136
+ agent_type=agent_type.__name__,
137
+ agent_name=agent_name,
138
+ agent_data=agent_data,
139
+ )
140
+ self.agent_definitions[agent_name] = definition
141
+
142
+ # Use the reactive setter for dict-like access.
158
143
  def __getitem__(self, key: str) -> Any:
159
- """Get the current value of a state variable."""
160
- value = self.state[key]
161
- return value
144
+ return self.get_variable(key)
162
145
 
163
146
  def __setitem__(self, key: str, value: Any) -> None:
164
- """Set the value of a state variable."""
165
- self.state[key] = value
147
+ self.set_variable(key, value)
166
148
 
167
149
  def to_dict(self) -> dict[str, Any]:
168
- """Convert the context to a dictionary for serialization."""
169
- try:
170
-
171
- def convert(obj):
172
- if isinstance(obj, datetime):
173
- return obj.isoformat()
174
- if hasattr(obj, "__dataclass_fields__"): # Is a dataclass
175
- return asdict(
176
- obj,
177
- dict_factory=lambda x: {k: convert(v) for k, v in x},
178
- )
179
- return obj
150
+ def convert(obj):
151
+ if isinstance(obj, datetime):
152
+ return obj.isoformat()
153
+ if hasattr(obj, "__dataclass_fields__"):
154
+ return asdict(
155
+ obj, dict_factory=lambda x: {k: convert(v) for k, v in x}
156
+ )
157
+ return obj
180
158
 
181
- return convert(asdict(self))
182
- except Exception:
183
- raise
159
+ return convert(asdict(self))
184
160
 
185
161
  @classmethod
186
162
  def from_dict(cls, data: dict[str, Any]) -> "FlockContext":
187
- """Create a context instance from a dictionary."""
188
- try:
189
-
190
- def convert(obj):
191
- if isinstance(obj, dict):
192
- if "timestamp" in obj: # AgentRunRecord
193
- return AgentRunRecord(
194
- **{
195
- **obj,
196
- "timestamp": datetime.fromisoformat(
197
- obj["timestamp"]
198
- ),
199
- }
200
- )
201
- if "agent_type" in obj: # AgentDefinition
202
- return AgentDefinition(**obj)
203
- return {k: convert(v) for k, v in obj.items()}
204
- if isinstance(obj, list):
205
- return [convert(v) for v in obj]
206
- return obj
207
-
208
- converted = convert(data)
209
- return cls(**converted)
210
- except Exception:
211
- raise
163
+ def convert(obj):
164
+ if isinstance(obj, dict):
165
+ if "timestamp" in obj:
166
+ return AgentRunRecord(
167
+ **{
168
+ **obj,
169
+ "timestamp": datetime.fromisoformat(
170
+ obj["timestamp"]
171
+ ),
172
+ }
173
+ )
174
+ if "agent_type" in obj:
175
+ return AgentDefinition(**obj)
176
+ return {k: convert(v) for k, v in obj.items()}
177
+ if isinstance(obj, list):
178
+ return [convert(v) for v in obj]
179
+ return obj
180
+
181
+ converted = convert(data)
182
+ return cls(**converted)
@@ -1,14 +1,24 @@
1
1
  # src/your_package/core/execution/temporal_executor.py
2
+ from devtools import pprint
3
+
2
4
  from flock.core.context.context import FlockContext
5
+ from flock.core.context.context_vars import FLOCK_RUN_ID
6
+ from flock.core.logging.formatters.formatter_factory import FormatterFactory
3
7
  from flock.core.logging.logging import get_logger
4
- from flock.workflow.activities import run_agent # Activity function used in Temporal
8
+ from flock.workflow.activities import (
9
+ run_agent, # Activity function used in Temporal
10
+ )
5
11
  from flock.workflow.temporal_setup import create_temporal_client, setup_worker
6
12
  from flock.workflow.workflow import FlockWorkflow # Your workflow class
7
13
 
8
14
  logger = get_logger("flock")
9
15
 
10
16
 
11
- async def run_temporal_workflow(context: FlockContext, box_result: bool = True) -> dict:
17
+ async def run_temporal_workflow(
18
+ context: FlockContext,
19
+ output_formatter,
20
+ box_result: bool = True,
21
+ ) -> dict:
12
22
  """Execute the agent workflow via Temporal for robust, distributed processing.
13
23
 
14
24
  Args:
@@ -22,16 +32,22 @@ async def run_temporal_workflow(context: FlockContext, box_result: bool = True)
22
32
  await setup_worker(workflow=FlockWorkflow, activity=run_agent)
23
33
  logger.debug("Creating Temporal client")
24
34
  flock_client = await create_temporal_client()
25
- workflow_id = context.get_variable("FLOCK_RUN_ID")
35
+ workflow_id = context.get_variable(FLOCK_RUN_ID)
26
36
  logger.info("Executing Temporal workflow", workflow_id=workflow_id)
27
37
  result = await flock_client.execute_workflow(
28
- FlockWorkflow.run, context.to_dict(), id=workflow_id, task_queue="flock-queue"
38
+ FlockWorkflow.run,
39
+ context.to_dict(),
40
+ id=workflow_id,
41
+ task_queue="flock-queue",
29
42
  )
30
- from flock.core.logging.formatters.themed_formatter import ThemedAgentResultFormatter
31
43
 
32
44
  agent_name = context.get_variable("FLOCK_CURRENT_AGENT")
33
45
  logger.debug("Formatting Temporal result", agent=agent_name)
34
- ThemedAgentResultFormatter.print_result(result, agent_name)
46
+ if output_formatter:
47
+ formatter = FormatterFactory.create_formatter(output_formatter)
48
+ formatter.display(result, agent_name, output_formatter.wait_for_input)
49
+ else:
50
+ pprint(result)
35
51
  if box_result:
36
52
  from box import Box
37
53
 
flock/core/flock.py CHANGED
@@ -4,6 +4,7 @@ import os
4
4
  import uuid
5
5
  from typing import TypeVar
6
6
 
7
+ from opentelemetry import trace
7
8
  from rich.prompt import Prompt
8
9
 
9
10
  from flock.core.context.context import FlockContext
@@ -14,12 +15,15 @@ from flock.core.flock_agent import FlockAgent
14
15
  from flock.core.logging.formatters.base_formatter import FormatterOptions
15
16
  from flock.core.logging.formatters.pprint_formatter import PrettyPrintFormatter
16
17
  from flock.core.logging.logging import get_logger
18
+ from flock.core.logging.telemetry import setup_tracing
17
19
  from flock.core.registry.agent_registry import Registry
18
20
  from flock.core.util.cli_helper import display_banner
19
21
  from flock.core.util.input_resolver import top_level_to_keys
20
22
 
21
23
  T = TypeVar("T", bound=FlockAgent)
22
24
  logger = get_logger("flock")
25
+ tracer = setup_tracing()
26
+ tracer = trace.get_tracer(__name__)
23
27
 
24
28
 
25
29
  class Flock:
@@ -46,29 +50,36 @@ class Flock:
46
50
  enable_logging (bool): If True, enable verbose logging. Defaults to False.
47
51
  output_formatter (FormatterOptions): Options for formatting output results.
48
52
  """
49
- logger.info(
50
- "Initializing Flock",
51
- model=model,
52
- local_debug=local_debug,
53
- enable_logging=enable_logging,
54
- )
55
- logger.enable_logging = enable_logging
53
+ with tracer.start_as_current_span("flock_init") as span:
54
+ span.set_attribute("model", model)
55
+ span.set_attribute("local_debug", local_debug)
56
+ span.set_attribute("enable_logging", enable_logging)
57
+ span.set_attribute(
58
+ "output_formatter", output_formatter.formatter.__name__
59
+ )
60
+ logger.info(
61
+ "Initializing Flock",
62
+ model=model,
63
+ local_debug=local_debug,
64
+ enable_logging=enable_logging,
65
+ )
66
+ logger.enable_logging = enable_logging
56
67
 
57
- display_banner()
68
+ display_banner()
58
69
 
59
- self.agents: dict[str, FlockAgent] = {}
60
- self.registry = Registry()
61
- self.context = FlockContext()
62
- self.model = model
63
- self.local_debug = local_debug
64
- self.output_formatter = output_formatter
70
+ self.agents: dict[str, FlockAgent] = {}
71
+ self.registry = Registry()
72
+ self.context = FlockContext()
73
+ self.model = model
74
+ self.local_debug = local_debug
75
+ self.output_formatter = output_formatter
65
76
 
66
- if local_debug:
67
- os.environ["LOCAL_DEBUG"] = "1"
68
- logger.debug("Set LOCAL_DEBUG environment variable")
69
- elif "LOCAL_DEBUG" in os.environ:
70
- del os.environ["LOCAL_DEBUG"]
71
- logger.debug("Removed LOCAL_DEBUG environment variable")
77
+ if local_debug:
78
+ os.environ["LOCAL_DEBUG"] = "1"
79
+ logger.debug("Set LOCAL_DEBUG environment variable")
80
+ elif "LOCAL_DEBUG" in os.environ:
81
+ del os.environ["LOCAL_DEBUG"]
82
+ logger.debug("Removed LOCAL_DEBUG environment variable")
72
83
 
73
84
  def add_agent(self, agent: T) -> T:
74
85
  """Add a new agent to the Flock system.
@@ -151,49 +162,71 @@ class Flock:
151
162
  ValueError: If the specified agent is not found in the registry.
152
163
  Exception: For any other errors encountered during execution.
153
164
  """
154
- try:
155
- if isinstance(start_agent, str):
156
- logger.debug("Looking up agent by name", agent_name=start_agent)
157
- start_agent = self.registry.get_agent(start_agent)
158
- if not start_agent:
159
- logger.error("Agent not found", agent_name=start_agent)
160
- raise ValueError(
161
- f"Agent '{start_agent}' not found in registry"
162
- )
163
- if context:
164
- logger.debug("Using provided context")
165
- self.context = context
166
- if not run_id:
167
- run_id = f"{start_agent.name}_{uuid.uuid4().hex[:4]}"
168
- logger.debug("Generated run ID", run_id=run_id)
169
-
170
- # TODO - Add a check for required input keys
171
- input_keys = top_level_to_keys(start_agent.input)
172
- for key in input_keys:
173
- if key.startswith("flock."):
174
- key = key[6:] # Remove the "flock." prefix
175
- if key not in input:
176
- input[key] = Prompt.ask(
177
- f"Please enter {key} for {start_agent.name}"
178
- )
179
-
180
- # Initialize the context with standardized variables
181
- initialize_context(
182
- self.context, start_agent.name, input, run_id, self.local_debug
165
+ with tracer.start_as_current_span("run_async") as span:
166
+ span.set_attribute(
167
+ "start_agent",
168
+ start_agent.name
169
+ if hasattr(start_agent, "name")
170
+ else start_agent,
183
171
  )
184
172
 
185
- logger.info(
186
- "Starting agent execution",
187
- agent=start_agent.name,
188
- local_debug=self.local_debug,
189
- )
173
+ span.set_attribute("input", str(input))
174
+ span.set_attribute("context", str(context))
175
+ span.set_attribute("run_id", run_id)
176
+ span.set_attribute("box_result", box_result)
177
+
178
+ try:
179
+ if isinstance(start_agent, str):
180
+ logger.debug(
181
+ "Looking up agent by name", agent_name=start_agent
182
+ )
183
+ start_agent = self.registry.get_agent(start_agent)
184
+ if not start_agent:
185
+ logger.error("Agent not found", agent_name=start_agent)
186
+ raise ValueError(
187
+ f"Agent '{start_agent}' not found in registry"
188
+ )
189
+ start_agent.resolve_callables(context=self.context)
190
+ if context:
191
+ logger.debug("Using provided context")
192
+ self.context = context
193
+ if not run_id:
194
+ run_id = f"{start_agent.name}_{uuid.uuid4().hex[:4]}"
195
+ logger.debug("Generated run ID", run_id=run_id)
196
+
197
+ # TODO - Add a check for required input keys
198
+ input_keys = top_level_to_keys(start_agent.input)
199
+ for key in input_keys:
200
+ if key.startswith("flock."):
201
+ key = key[6:] # Remove the "flock." prefix
202
+ if key not in input:
203
+ input[key] = Prompt.ask(
204
+ f"Please enter {key} for {start_agent.name}"
205
+ )
206
+
207
+ # Initialize the context with standardized variables
208
+ initialize_context(
209
+ self.context,
210
+ start_agent.name,
211
+ input,
212
+ run_id,
213
+ self.local_debug,
214
+ )
190
215
 
191
- if self.local_debug:
192
- return await run_local_workflow(
193
- self.context, self.output_formatter, box_result
216
+ logger.info(
217
+ "Starting agent execution",
218
+ agent=start_agent.name,
219
+ local_debug=self.local_debug,
194
220
  )
195
- else:
196
- return await run_temporal_workflow(self.context, box_result)
197
- except Exception as e:
198
- logger.exception("Execution failed", error=str(e))
199
- raise
221
+
222
+ if self.local_debug:
223
+ return await run_local_workflow(
224
+ self.context, self.output_formatter, box_result
225
+ )
226
+ else:
227
+ return await run_temporal_workflow(
228
+ self.context, self.output_formatter, box_result
229
+ )
230
+ except Exception as e:
231
+ logger.exception("Execution failed", error=str(e))
232
+ raise