flock-core 0.1.1__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 (48) hide show
  1. flock/__init__.py +4 -0
  2. flock/agents/__init__.py +3 -0
  3. flock/agents/batch_agent.py +175 -0
  4. flock/agents/declarative_agent.py +166 -0
  5. flock/agents/loop_agent.py +178 -0
  6. flock/agents/trigger_agent.py +191 -0
  7. flock/agents/user_agent.py +230 -0
  8. flock/app/components/__init__.py +14 -0
  9. flock/app/components/charts/agent_workflow.py +14 -0
  10. flock/app/components/charts/core_architecture.py +14 -0
  11. flock/app/components/charts/tool_system.py +14 -0
  12. flock/app/components/history_grid.py +168 -0
  13. flock/app/components/history_grid_alt.py +189 -0
  14. flock/app/components/sidebar.py +19 -0
  15. flock/app/components/theme.py +9 -0
  16. flock/app/components/util.py +18 -0
  17. flock/app/hive_app.py +118 -0
  18. flock/app/html/d3.html +179 -0
  19. flock/app/modules/__init__.py +12 -0
  20. flock/app/modules/about.py +17 -0
  21. flock/app/modules/agent_detail.py +70 -0
  22. flock/app/modules/agent_list.py +59 -0
  23. flock/app/modules/playground.py +322 -0
  24. flock/app/modules/settings.py +96 -0
  25. flock/core/__init__.py +7 -0
  26. flock/core/agent.py +150 -0
  27. flock/core/agent_registry.py +162 -0
  28. flock/core/config/declarative_agent_config.py +0 -0
  29. flock/core/context.py +279 -0
  30. flock/core/context_vars.py +6 -0
  31. flock/core/flock.py +208 -0
  32. flock/core/handoff/handoff_base.py +12 -0
  33. flock/core/logging/__init__.py +18 -0
  34. flock/core/logging/error_handler.py +84 -0
  35. flock/core/logging/formatters.py +122 -0
  36. flock/core/logging/handlers.py +117 -0
  37. flock/core/logging/logger.py +107 -0
  38. flock/core/serializable.py +206 -0
  39. flock/core/tools/basic_tools.py +98 -0
  40. flock/workflow/activities.py +115 -0
  41. flock/workflow/agent_activities.py +26 -0
  42. flock/workflow/temporal_setup.py +37 -0
  43. flock/workflow/workflow.py +53 -0
  44. flock_core-0.1.1.dist-info/METADATA +449 -0
  45. flock_core-0.1.1.dist-info/RECORD +48 -0
  46. flock_core-0.1.1.dist-info/WHEEL +4 -0
  47. flock_core-0.1.1.dist-info/entry_points.txt +2 -0
  48. flock_core-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,206 @@
1
+ """Module for serializable objects in the system."""
2
+
3
+ import json
4
+ from abc import ABC, abstractmethod
5
+ from pathlib import Path
6
+ from typing import Any, TypeVar
7
+
8
+ import cloudpickle
9
+ import msgpack
10
+
11
+ from flock.core.logging import flock_logger, performance_handler
12
+
13
+ T = TypeVar("T", bound="Serializable")
14
+
15
+
16
+ class Serializable(ABC):
17
+ """Base class for all serializable objects in the system.
18
+
19
+ Provides methods for serializing/deserializing objects to various formats
20
+ with comprehensive logging and performance tracking.
21
+ """
22
+
23
+ @abstractmethod
24
+ def to_dict(self) -> dict[str, Any]:
25
+ """Convert instance to dictionary representation."""
26
+ pass
27
+
28
+ @classmethod
29
+ @abstractmethod
30
+ def from_dict(cls: type[T], data: dict[str, Any]) -> T:
31
+ """Create instance from dictionary representation."""
32
+ pass
33
+
34
+ def to_json(self) -> str:
35
+ """Serialize to JSON string."""
36
+ try:
37
+ with performance_handler.track_time("json_serialization"):
38
+ flock_logger.debug(
39
+ "Serializing to JSON",
40
+ class_name=self.__class__.__name__,
41
+ )
42
+ json_str = json.dumps(self.to_dict())
43
+ flock_logger.debug(
44
+ "JSON serialization complete",
45
+ size_bytes=len(json_str),
46
+ )
47
+ return json_str
48
+ except Exception as e:
49
+ flock_logger.error(
50
+ "JSON serialization failed",
51
+ class_name=self.__class__.__name__,
52
+ error=str(e),
53
+ )
54
+ raise
55
+
56
+ @classmethod
57
+ def from_json(cls: type[T], json_str: str) -> T:
58
+ """Create instance from JSON string."""
59
+ try:
60
+ with performance_handler.track_time("json_deserialization"):
61
+ flock_logger.debug(
62
+ "Deserializing from JSON",
63
+ class_name=cls.__name__,
64
+ size_bytes=len(json_str),
65
+ )
66
+ instance = cls.from_dict(json.loads(json_str))
67
+ flock_logger.debug("JSON deserialization complete")
68
+ return instance
69
+ except Exception as e:
70
+ flock_logger.error(
71
+ "JSON deserialization failed",
72
+ class_name=cls.__name__,
73
+ error=str(e),
74
+ )
75
+ raise
76
+
77
+ def to_msgpack(self, path: Path | None = None) -> bytes:
78
+ """Serialize to msgpack bytes."""
79
+ try:
80
+ with performance_handler.track_time("msgpack_serialization"):
81
+ flock_logger.debug(
82
+ "Serializing to msgpack",
83
+ class_name=self.__class__.__name__,
84
+ )
85
+ msgpack_bytes = msgpack.packb(self.to_dict())
86
+
87
+ if path:
88
+ flock_logger.debug(f"Writing msgpack to file: {path}")
89
+ path.write_bytes(msgpack_bytes)
90
+
91
+ flock_logger.debug(
92
+ "Msgpack serialization complete",
93
+ size_bytes=len(msgpack_bytes),
94
+ file_path=str(path) if path else None,
95
+ )
96
+ return msgpack_bytes
97
+ except Exception as e:
98
+ flock_logger.error(
99
+ "Msgpack serialization failed",
100
+ class_name=self.__class__.__name__,
101
+ file_path=str(path) if path else None,
102
+ error=str(e),
103
+ )
104
+ raise
105
+
106
+ @classmethod
107
+ def from_msgpack(cls: type[T], msgpack_bytes: bytes) -> T:
108
+ """Create instance from msgpack bytes."""
109
+ try:
110
+ with performance_handler.track_time("msgpack_deserialization"):
111
+ flock_logger.debug(
112
+ "Deserializing from msgpack",
113
+ class_name=cls.__name__,
114
+ size_bytes=len(msgpack_bytes),
115
+ )
116
+ instance = cls.from_dict(msgpack.unpackb(msgpack_bytes))
117
+ flock_logger.debug("Msgpack deserialization complete")
118
+ return instance
119
+ except Exception as e:
120
+ flock_logger.error(
121
+ "Msgpack deserialization failed",
122
+ class_name=cls.__name__,
123
+ error=str(e),
124
+ )
125
+ raise
126
+
127
+ @classmethod
128
+ def from_msgpack_file(cls: type[T], path: Path) -> T:
129
+ """Create instance from msgpack file."""
130
+ try:
131
+ with performance_handler.track_time("msgpack_file_read"):
132
+ flock_logger.debug(
133
+ f"Reading msgpack from file: {path}",
134
+ class_name=cls.__name__,
135
+ )
136
+ return cls.from_msgpack(path.read_bytes())
137
+ except Exception as e:
138
+ flock_logger.error(
139
+ "Msgpack file read failed",
140
+ class_name=cls.__name__,
141
+ file_path=str(path),
142
+ error=str(e),
143
+ )
144
+ raise
145
+
146
+ def to_pickle(self) -> bytes:
147
+ """Serialize to pickle bytes."""
148
+ try:
149
+ with performance_handler.track_time("pickle_serialization"):
150
+ flock_logger.debug(
151
+ "Serializing to pickle",
152
+ class_name=self.__class__.__name__,
153
+ )
154
+ pickle_bytes = cloudpickle.dumps(self)
155
+ flock_logger.debug(
156
+ "Pickle serialization complete",
157
+ size_bytes=len(pickle_bytes),
158
+ )
159
+ return pickle_bytes
160
+ except Exception as e:
161
+ flock_logger.error(
162
+ "Pickle serialization failed",
163
+ class_name=self.__class__.__name__,
164
+ error=str(e),
165
+ )
166
+ raise
167
+
168
+ @classmethod
169
+ def from_pickle(cls, pickle_bytes: bytes) -> T:
170
+ """Create instance from pickle bytes."""
171
+ try:
172
+ with performance_handler.track_time("pickle_deserialization"):
173
+ flock_logger.debug(
174
+ "Deserializing from pickle",
175
+ class_name=cls.__name__,
176
+ size_bytes=len(pickle_bytes),
177
+ )
178
+ instance = cloudpickle.loads(pickle_bytes)
179
+ flock_logger.debug("Pickle deserialization complete")
180
+ return instance
181
+ except Exception as e:
182
+ flock_logger.error(
183
+ "Pickle deserialization failed",
184
+ class_name=cls.__name__,
185
+ error=str(e),
186
+ )
187
+ raise
188
+
189
+ @classmethod
190
+ def from_pickle_file(cls: type[T], path: Path) -> T:
191
+ """Create instance from pickle file."""
192
+ try:
193
+ with performance_handler.track_time("pickle_file_read"):
194
+ flock_logger.debug(
195
+ f"Reading pickle from file: {path}",
196
+ class_name=cls.__name__,
197
+ )
198
+ return cls.from_pickle(path.read_bytes())
199
+ except Exception as e:
200
+ flock_logger.error(
201
+ "Pickle file read failed",
202
+ class_name=cls.__name__,
203
+ file_path=str(path),
204
+ error=str(e),
205
+ )
206
+ raise
@@ -0,0 +1,98 @@
1
+ import os
2
+
3
+ import httpx
4
+ from markdownify import markdownify as md
5
+ from tavily import TavilyClient
6
+
7
+
8
+ def web_search_tavily(query: str):
9
+ client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
10
+ try:
11
+ response = client.search(query, include_answer=True) # type: ignore
12
+ return response
13
+ except Exception:
14
+ raise
15
+
16
+
17
+ def get_web_content_as_markdown(url: str):
18
+ try:
19
+ response = httpx.get(url)
20
+ response.raise_for_status()
21
+ markdown = md(response.text)
22
+ return markdown
23
+ except Exception:
24
+ raise
25
+
26
+
27
+ def evaluate_math(expression: str) -> float:
28
+ import dspy
29
+
30
+ try:
31
+ result = dspy.PythonInterpreter({}).execute(expression)
32
+ return result
33
+ except Exception:
34
+ raise
35
+
36
+
37
+ def code_eval(python_code: str) -> float:
38
+ import dspy
39
+
40
+ try:
41
+ result = dspy.PythonInterpreter({}).execute(python_code)
42
+ return result
43
+ except Exception:
44
+ raise
45
+
46
+
47
+ def get_current_time() -> str:
48
+ import datetime
49
+
50
+ time = datetime.datetime.now().isoformat()
51
+ return time
52
+
53
+
54
+ def count_words(text: str) -> int:
55
+ count = len(text.split())
56
+ return count
57
+
58
+
59
+ def extract_urls(text: str) -> list[str]:
60
+ import re
61
+
62
+ url_pattern = r"https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+"
63
+ urls = re.findall(url_pattern, text)
64
+ return urls
65
+
66
+
67
+ def extract_numbers(text: str) -> list[float]:
68
+ import re
69
+
70
+ numbers = [float(x) for x in re.findall(r"-?\d*\.?\d+", text)]
71
+ return numbers
72
+
73
+
74
+ def json_parse_safe(text: str) -> dict:
75
+ import json
76
+
77
+ try:
78
+ result = json.loads(text)
79
+ return result
80
+ except Exception:
81
+ return {}
82
+
83
+
84
+ def save_to_file(content: str, filename: str):
85
+ try:
86
+ with open(filename, "w") as f:
87
+ f.write(content)
88
+ except Exception:
89
+ raise
90
+
91
+
92
+ def read_from_file(filename: str) -> str:
93
+ try:
94
+ with open(filename) as f:
95
+ content = f.read()
96
+ return content
97
+ except Exception:
98
+ raise
@@ -0,0 +1,115 @@
1
+ """Defines Temporal activities for running a chain of agents."""
2
+
3
+ from datetime import datetime
4
+
5
+ from temporalio import activity
6
+
7
+ from flock.core.agent import Agent
8
+ from flock.core.agent_registry import Registry
9
+ from flock.core.context import FlockContext
10
+ from flock.core.context_vars import FLOCK_CURRENT_AGENT, FLOCK_INITIAL_INPUT
11
+ from flock.core.handoff.handoff_base import HandoffBase
12
+ from flock.core.logging import flock_logger
13
+
14
+
15
+ @activity.defn
16
+ async def run_agent(context: FlockContext) -> dict:
17
+ """Runs a chain of agents using the provided context.
18
+
19
+ The context contains:
20
+ - A state (e.g., "init_input", "current_agent", etc.),
21
+ - A history of agent runs.
22
+
23
+ Each agent uses the current value of the variable specified in its `input` attribute.
24
+ After each run, its output is merged into the context state.
25
+ The default handoff behavior is to fetch the next agent's input automatically from the context.
26
+ """
27
+ registry = Registry()
28
+ current_agent_name = context.get_variable(FLOCK_CURRENT_AGENT)
29
+ flock_logger.info(f"Starting agent chain with initial agent", agent=current_agent_name)
30
+
31
+ agent = registry.get_agent(current_agent_name)
32
+ if not agent:
33
+ flock_logger.error(f"Agent not found", agent=current_agent_name)
34
+ return {"error": f"Agent '{current_agent_name}' not found."}
35
+
36
+ while agent:
37
+ # Determine the input for this agent.
38
+ # (Preferably, the agent's input key is used; otherwise "init_input" is assumed.)
39
+ agent_input = (
40
+ context.get_variable(agent.input)
41
+ if getattr(agent, "input", None)
42
+ else context.get_variable(FLOCK_INITIAL_INPUT)
43
+ )
44
+ flock_logger.info(
45
+ "Determined agent input", agent=agent.name, input_source=getattr(agent, "input", "FLOCK_INITIAL_INPUT")
46
+ )
47
+
48
+ # Prepare a deep copy of the state for the agent's run.
49
+ local_context = FlockContext.from_dict(context.to_dict())
50
+ local_context.set_variable(
51
+ f"{agent.name}.{agent.input}", agent_input
52
+ ) # ensure the agent's expected input is set
53
+
54
+ # Execute the agent.
55
+ flock_logger.info("Executing agent", agent=agent.name)
56
+ result = await agent.run(local_context)
57
+ flock_logger.info("Agent execution completed", agent=agent.name)
58
+
59
+ # If no handoff is defined, return the result.
60
+ if not agent.hand_off:
61
+ # Record the agent's execution.
62
+ context.record(agent.name, result, timestamp=datetime.now(), hand_off=None)
63
+ flock_logger.info("No handoff defined, completing chain", agent=agent.name)
64
+ return result
65
+
66
+ # Determine the next agent.
67
+ if callable(agent.hand_off):
68
+ # The handoff function may override the default behavior:
69
+ # it can explicitly return a dict with "next_agent" and optionally "input"
70
+ flock_logger.info("Executing handoff function", agent=agent.name)
71
+ handoff_data: HandoffBase = agent.hand_off(context, result)
72
+ if isinstance(handoff_data.next_agent, Agent):
73
+ next_agent_name = handoff_data.next_agent.name
74
+ else:
75
+ next_agent_name = handoff_data.next_agent
76
+
77
+ # Use the provided new input if present, otherwise let the context update be automatic.
78
+ if handoff_data.input:
79
+ context.state["init_input"] = handoff_data["input"]
80
+ flock_logger.debug("Using handoff-provided input", agent=agent.name)
81
+ if handoff_data.context_params:
82
+ context.state.update(handoff_data.context_params)
83
+ flock_logger.debug("Updated context with handoff params", agent=agent.name)
84
+ elif isinstance(agent.hand_off, str | Agent):
85
+ next_agent_name = agent.hand_off if isinstance(agent.hand_off, str) else agent.hand_off.name
86
+
87
+ else:
88
+ return {"error": "Unsupported hand_off type."}
89
+ context.record(agent.name, result, timestamp=datetime.now(), hand_off=next_agent_name)
90
+ # Update the current agent and prepare the next input automatically.
91
+ next_agent = registry.get_agent(next_agent_name)
92
+ if not next_agent:
93
+ flock_logger.error("Next agent not found", agent=next_agent_name)
94
+ return {"error": f"Next agent '{next_agent_name}' not found."}
95
+
96
+ context.state["current_agent"] = next_agent.name
97
+ # By default, the next input is determined from the current state using next_input_for().
98
+ context.state["init_input"] = context.next_input_for(next_agent)
99
+ flock_logger.info("Handing off to next agent", current=agent.name, next=next_agent.name)
100
+ agent = next_agent
101
+
102
+ # If the loop ever terminates without a final result, return the latest state value.
103
+ flock_logger.info("Chain completed, returning final state")
104
+ return context.get_variable("init_input")
105
+
106
+
107
+ @activity.defn
108
+ async def get_next_agent(name: str) -> Agent | None:
109
+ """Retrieves the agent with the given name from the registry."""
110
+ flock_logger.debug("Looking up next agent", agent=name)
111
+ registry = Registry()
112
+ agent = registry.get_agent(name)
113
+ if not agent:
114
+ flock_logger.warning("Next agent not found", agent=name)
115
+ return agent
@@ -0,0 +1,26 @@
1
+ from temporalio import activity
2
+
3
+ from flock.agents.declarative_agent import DeclarativeAgent
4
+ from flock.core.context import FlockContext
5
+
6
+
7
+ @activity.defn
8
+ async def run_declarative_agent_activity(params: dict) -> dict:
9
+ """Temporal activity to run a declarative (or batch) agent.
10
+
11
+ Expects a dictionary with:
12
+ - "agent_data": a dict representation of the agent (as produced by .dict()),
13
+ - "context_data": a dict containing the FlockContext state and optionally other fields.
14
+
15
+ The activity reconstructs the agent and a FlockContext, then calls the agent’s _evaluate() method.
16
+ """
17
+ agent_data = params.get("agent_data")
18
+ context_data = params.get("context_data", {})
19
+ # Reconstruct the agent from its serialized representation.
20
+ agent = DeclarativeAgent.parse_obj(agent_data)
21
+ # Reconstruct the FlockContext from the state.
22
+ state = context_data.get("state", {})
23
+ # (For simplicity, we ignore history and agent_definitions here. You can extend this if needed.)
24
+ context = FlockContext(state=state)
25
+ result = await agent._evaluate(context)
26
+ return result
@@ -0,0 +1,37 @@
1
+ import asyncio
2
+ import uuid
3
+
4
+ from temporalio.client import Client
5
+ from temporalio.worker import Worker
6
+
7
+
8
+ async def create_temporal_client() -> Client:
9
+ client = await Client.connect("localhost:7233")
10
+ return client
11
+
12
+ async def setup_worker(workflow,activity) -> Client:
13
+ worker_client = await create_temporal_client()
14
+ worker = Worker(worker_client, task_queue="flock-queue", workflows=[workflow], activities=[activity])
15
+ asyncio.create_task(worker.run())
16
+ await asyncio.sleep(1)
17
+
18
+
19
+ async def run_worker(client: Client, task_queue: str, workflows, activities):
20
+ worker = Worker(client, task_queue=task_queue, workflows=workflows, activities=activities)
21
+ await worker.run()
22
+
23
+
24
+ async def run_activity(client: Client, name: str, func, param):
25
+ run_id = f"{name}_{uuid.uuid4().hex[:4]}"
26
+
27
+ try:
28
+ result = await client.execute_activity(
29
+ func,
30
+ param,
31
+ id=run_id,
32
+ task_queue="flock-queue",
33
+ start_to_close_timeout=300, # e.g., 5 minutes
34
+ )
35
+ return result
36
+ except Exception:
37
+ raise
@@ -0,0 +1,53 @@
1
+ from datetime import timedelta
2
+
3
+ from temporalio import workflow
4
+
5
+ from flock.core.context import FlockContext
6
+ from flock.core.logging import flock_logger
7
+ from flock.workflow.activities import run_agent
8
+
9
+ # Import activity, passing it through the sandbox without reloading the module
10
+ with workflow.unsafe.imports_passed_through():
11
+ from flock.workflow.activities import run_agent
12
+
13
+
14
+ @workflow.defn
15
+ class FlockWorkflow:
16
+ def __init__(self) -> None:
17
+ self.context = None
18
+
19
+ @workflow.run
20
+ async def run(self, context_dict: dict) -> dict:
21
+ self.context = FlockContext.from_dict(context_dict)
22
+ self.context.workflow_id = workflow.info().workflow_id
23
+ self.context.workflow_timestamp = workflow.info().start_time.strftime("%Y-%m-%d %H:%M:%S")
24
+
25
+ try:
26
+ flock_logger.set_context(workflow_id=self.context.workflow_id)
27
+ flock_logger.workflow_event(f"Starting workflow execution at {self.context.workflow_timestamp}")
28
+
29
+ result = await workflow.execute_activity(
30
+ run_agent, self.context, start_to_close_timeout=timedelta(minutes=5)
31
+ )
32
+
33
+ self.context.set_variable(
34
+ "flock.result",
35
+ {
36
+ "result": result,
37
+ "success": True,
38
+ },
39
+ )
40
+
41
+ flock_logger.workflow_event("Workflow completed successfully")
42
+ return result
43
+
44
+ except Exception as e:
45
+ flock_logger.error(f"Workflow execution failed: {e}")
46
+ self.context.set_variable(
47
+ "flock.result",
48
+ {
49
+ "result": f"Failed: {e}",
50
+ "success": False,
51
+ },
52
+ )
53
+ return self.context