flock-core 0.2.1__py3-none-any.whl → 0.2.3__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 CHANGED
@@ -1 +1,5 @@
1
1
  """Flock package initialization."""
2
+
3
+ from flock.config import TELEMETRY
4
+
5
+ tracer = TELEMETRY.setup_tracing()
flock/config.py ADDED
@@ -0,0 +1,29 @@
1
+ # flock/config.py
2
+ import os
3
+
4
+ from flock.core.logging.telemetry import TelemetryConfig
5
+
6
+ # -- Connection and External Service Configurations --
7
+ TEMPORAL_SERVER_URL = os.getenv("TEMPORAL_SERVER_URL", "localhost:7233")
8
+ DEFAULT_MODEL = os.getenv("DEFAULT_MODEL", "openai/gpt-4o")
9
+
10
+ # API Keys and related settings
11
+ TAVILY_API_KEY = os.getenv("TAVILY_API_KEY", "")
12
+ GITHUB_PAT = os.getenv("GITHUB_PAT", "")
13
+ GITHUB_REPO = os.getenv("GITHUB_REPO", "")
14
+ GITHUB_USERNAME = os.getenv("GITHUB_USERNAME", "")
15
+
16
+ # -- Debugging and Logging Configurations --
17
+ LOCAL_DEBUG = os.getenv("LOCAL_DEBUG", "0") == "1"
18
+ LOG_LEVEL = os.getenv("LOG_LEVEL", "DEBUG")
19
+
20
+ OTL_SERVICE_NAME = os.getenv("OTL_SERVICE_NAME", "otel-flock")
21
+ JAEGER_ENDPOINT = os.getenv(
22
+ "JAEGER_ENDPOINT", "http://localhost:14268/api/traces"
23
+ ) # Default gRPC endpoint for Jaeger
24
+ JAEGER_TRANSPORT = os.getenv(
25
+ "JAEGER_TRANSPORT", "http"
26
+ ).lower() # Options: "grpc" or "http"
27
+
28
+
29
+ TELEMETRY = TelemetryConfig(OTL_SERVICE_NAME, JAEGER_ENDPOINT, JAEGER_TRANSPORT)
flock/core/__init__.py CHANGED
@@ -1 +1,6 @@
1
+ """This module contains the core classes of the flock package."""
1
2
 
3
+ from flock.core.flock import Flock
4
+ from flock.core.flock_agent import FlockAgent
5
+
6
+ __all__ = ["Flock", "FlockAgent"]
@@ -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
@@ -21,6 +22,8 @@ from flock.core.util.input_resolver import top_level_to_keys
21
22
  T = TypeVar("T", bound=FlockAgent)
22
23
  logger = get_logger("flock")
23
24
 
25
+ tracer = trace.get_tracer(__name__)
26
+
24
27
 
25
28
  class Flock:
26
29
  """High-level orchestrator for creating and executing agents.
@@ -46,29 +49,36 @@ class Flock:
46
49
  enable_logging (bool): If True, enable verbose logging. Defaults to False.
47
50
  output_formatter (FormatterOptions): Options for formatting output results.
48
51
  """
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
52
+ with tracer.start_as_current_span("flock_init") as span:
53
+ span.set_attribute("model", model)
54
+ span.set_attribute("local_debug", local_debug)
55
+ span.set_attribute("enable_logging", enable_logging)
56
+ span.set_attribute(
57
+ "output_formatter", output_formatter.formatter.__name__
58
+ )
59
+ logger.info(
60
+ "Initializing Flock",
61
+ model=model,
62
+ local_debug=local_debug,
63
+ enable_logging=enable_logging,
64
+ )
65
+ logger.enable_logging = enable_logging
56
66
 
57
- display_banner()
67
+ display_banner()
58
68
 
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
69
+ self.agents: dict[str, FlockAgent] = {}
70
+ self.registry = Registry()
71
+ self.context = FlockContext()
72
+ self.model = model
73
+ self.local_debug = local_debug
74
+ self.output_formatter = output_formatter
65
75
 
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")
76
+ if local_debug:
77
+ os.environ["LOCAL_DEBUG"] = "1"
78
+ logger.debug("Set LOCAL_DEBUG environment variable")
79
+ elif "LOCAL_DEBUG" in os.environ:
80
+ del os.environ["LOCAL_DEBUG"]
81
+ logger.debug("Removed LOCAL_DEBUG environment variable")
72
82
 
73
83
  def add_agent(self, agent: T) -> T:
74
84
  """Add a new agent to the Flock system.
@@ -151,49 +161,71 @@ class Flock:
151
161
  ValueError: If the specified agent is not found in the registry.
152
162
  Exception: For any other errors encountered during execution.
153
163
  """
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
164
+ with tracer.start_as_current_span("run_async") as span:
165
+ span.set_attribute(
166
+ "start_agent",
167
+ start_agent.name
168
+ if hasattr(start_agent, "name")
169
+ else start_agent,
183
170
  )
184
171
 
185
- logger.info(
186
- "Starting agent execution",
187
- agent=start_agent.name,
188
- local_debug=self.local_debug,
189
- )
172
+ span.set_attribute("input", str(input))
173
+ span.set_attribute("context", str(context))
174
+ span.set_attribute("run_id", run_id)
175
+ span.set_attribute("box_result", box_result)
176
+
177
+ try:
178
+ if isinstance(start_agent, str):
179
+ logger.debug(
180
+ "Looking up agent by name", agent_name=start_agent
181
+ )
182
+ start_agent = self.registry.get_agent(start_agent)
183
+ if not start_agent:
184
+ logger.error("Agent not found", agent_name=start_agent)
185
+ raise ValueError(
186
+ f"Agent '{start_agent}' not found in registry"
187
+ )
188
+ start_agent.resolve_callables(context=self.context)
189
+ if context:
190
+ logger.debug("Using provided context")
191
+ self.context = context
192
+ if not run_id:
193
+ run_id = f"{start_agent.name}_{uuid.uuid4().hex[:4]}"
194
+ logger.debug("Generated run ID", run_id=run_id)
195
+
196
+ # TODO - Add a check for required input keys
197
+ input_keys = top_level_to_keys(start_agent.input)
198
+ for key in input_keys:
199
+ if key.startswith("flock."):
200
+ key = key[6:] # Remove the "flock." prefix
201
+ if key not in input:
202
+ input[key] = Prompt.ask(
203
+ f"Please enter {key} for {start_agent.name}"
204
+ )
205
+
206
+ # Initialize the context with standardized variables
207
+ initialize_context(
208
+ self.context,
209
+ start_agent.name,
210
+ input,
211
+ run_id,
212
+ self.local_debug,
213
+ )
190
214
 
191
- if self.local_debug:
192
- return await run_local_workflow(
193
- self.context, self.output_formatter, box_result
215
+ logger.info(
216
+ "Starting agent execution",
217
+ agent=start_agent.name,
218
+ local_debug=self.local_debug,
194
219
  )
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
220
+
221
+ if self.local_debug:
222
+ return await run_local_workflow(
223
+ self.context, self.output_formatter, box_result
224
+ )
225
+ else:
226
+ return await run_temporal_workflow(
227
+ self.context, self.output_formatter, box_result
228
+ )
229
+ except Exception as e:
230
+ logger.exception("Execution failed", error=str(e))
231
+ raise