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.

@@ -1,26 +1,30 @@
1
1
  """Mixin class for integrating with the dspy library."""
2
2
 
3
3
  import sys
4
- from typing import Any
5
-
6
- import dspy
4
+ from typing import Any, Literal
7
5
 
8
6
  from flock.core.logging.logging import get_logger
9
7
  from flock.core.util.input_resolver import split_top_level
10
8
 
11
9
  logger = get_logger("flock")
12
10
 
11
+ AgentType = (
12
+ Literal["ReAct"] | Literal["Completion"] | Literal["ChainOfThought"] | None
13
+ )
14
+
13
15
 
14
16
  class DSPyIntegrationMixin:
15
17
  """Mixin class for integrating with the dspy library."""
16
18
 
17
19
  def create_dspy_signature_class(
18
20
  self, agent_name, description_spec, fields_spec
19
- ) -> dspy.Signature:
21
+ ) -> Any:
20
22
  """Trying to create a dynamic class using dspy library."""
21
23
  # ---------------------------
22
24
  # 1. Parse the class specification.
23
25
  # ---------------------------
26
+ import dspy
27
+
24
28
  base_class = dspy.Signature
25
29
 
26
30
  # Start building the class dictionary with a docstring and annotations dict.
@@ -116,13 +120,16 @@ class DSPyIntegrationMixin:
116
120
  return type("dspy_" + agent_name, (base_class,), class_dict)
117
121
 
118
122
  def _configure_language_model(self) -> None:
123
+ import dspy
124
+
119
125
  """Initialize and configure the language model using dspy."""
120
126
  lm = dspy.LM(self.model, cache=self.use_cache)
121
127
  dspy.configure(lm=lm)
122
128
 
123
129
  def _select_task(
124
130
  self,
125
- signature: dspy.Signature,
131
+ signature: Any,
132
+ agent_type_override: AgentType,
126
133
  ) -> Any:
127
134
  """Select and instantiate the appropriate task based on tool availability.
128
135
 
@@ -134,17 +141,36 @@ class DSPyIntegrationMixin:
134
141
  Returns:
135
142
  An instance of a dspy task (either ReAct or Predict).
136
143
  """
144
+ import dspy
145
+
137
146
  dspy_solver = None
138
- if self.tools:
139
- dspy_solver = dspy.ReAct(
140
- signature,
141
- tools=self.tools,
142
- max_iters=10,
143
- )
147
+
148
+ if agent_type_override:
149
+ if agent_type_override == "ChainOfThought":
150
+ dspy_solver = dspy.ChainOfThought(
151
+ signature,
152
+ )
153
+ if agent_type_override == "ReAct":
154
+ dspy.ReAct(
155
+ signature,
156
+ tools=self.tools,
157
+ max_iters=10,
158
+ )
159
+ if agent_type_override == "Completion":
160
+ dspy_solver = dspy.Predict(
161
+ signature,
162
+ )
144
163
  else:
145
- dspy_solver = dspy.Predict(
146
- signature,
147
- )
164
+ if self.tools:
165
+ dspy_solver = dspy.ReAct(
166
+ signature,
167
+ tools=self.tools,
168
+ max_iters=10,
169
+ )
170
+ else:
171
+ dspy_solver = dspy.Predict(
172
+ signature,
173
+ )
148
174
 
149
175
  return dspy_solver
150
176
 
@@ -1,8 +1,14 @@
1
- """Registry for storing and managing agents and tools."""
1
+ """Registry for storing and managing agents and tools with logging and tracing integration."""
2
2
 
3
3
  from collections.abc import Callable
4
4
 
5
+ from opentelemetry import trace
6
+
5
7
  from flock.core.flock_agent import FlockAgent
8
+ from flock.core.logging.logging import get_logger
9
+
10
+ logger = get_logger("registry")
11
+ tracer = trace.get_tracer(__name__)
6
12
 
7
13
 
8
14
  class Registry:
@@ -16,88 +22,97 @@ class Registry:
16
22
  _instance = None
17
23
 
18
24
  def __new__(cls):
19
- """Singleton pattern implementation."""
20
- if cls._instance is None:
21
- cls._instance = super().__new__(cls)
22
- cls._instance._initialize()
23
- return cls._instance
25
+ with tracer.start_as_current_span("Registry.__new__") as span:
26
+ if cls._instance is None:
27
+ cls._instance = super().__new__(cls)
28
+ cls._instance._initialize()
29
+ logger.info("Registry instance created")
30
+ span.set_attribute("instance.created", True)
31
+ return cls._instance
24
32
 
25
33
  def _initialize(self):
26
- """Initialize the registry's storage."""
27
- self._agents: list[FlockAgent] = []
28
- self._tools: list[tuple[str, Callable]] = []
34
+ with tracer.start_as_current_span("Registry._initialize"):
35
+ self._agents: list[FlockAgent] = []
36
+ self._tools: list[tuple[str, Callable]] = []
37
+ logger.info("Registry initialized", agents_count=0, tools_count=0)
29
38
 
30
39
  def register_tool(self, tool_name: str, tool: Callable) -> None:
31
- """Register a tool with the registry.
32
-
33
- Args:
34
- tool_name: The name to register the tool under
35
- tool: The tool function to register
36
- """
37
- try:
38
- self._tools.append((tool_name, tool))
39
- except Exception:
40
- raise
40
+ with tracer.start_as_current_span("Registry.register_tool") as span:
41
+ span.set_attribute("tool_name", tool_name)
42
+ try:
43
+ self._tools.append((tool_name, tool))
44
+ logger.info("Tool registered", tool_name=tool_name)
45
+ except Exception as e:
46
+ logger.error(
47
+ "Error registering tool", tool_name=tool_name, error=str(e)
48
+ )
49
+ span.record_exception(e)
50
+ raise
41
51
 
42
52
  def register_agent(self, agent: FlockAgent) -> None:
43
- """Register an agent with the registry.
44
-
45
- Args:
46
- agent: The agent instance to register
47
- """
48
- try:
49
- self._agents.append(agent)
50
- except Exception:
51
- raise
53
+ with tracer.start_as_current_span("Registry.register_agent") as span:
54
+ span.set_attribute("agent_name", agent.name)
55
+ try:
56
+ self._agents.append(agent)
57
+ logger.info("Agent registered", agent=agent.name)
58
+ except Exception as e:
59
+ logger.error(
60
+ "Error registering agent", agent=agent.name, error=str(e)
61
+ )
62
+ span.record_exception(e)
63
+ raise
52
64
 
53
65
  def get_agent(self, name: str) -> FlockAgent | None:
54
- """Retrieve an agent by name.
55
-
56
- Args:
57
- name: The name of the agent to retrieve
58
-
59
- Returns:
60
- The agent instance if found, None otherwise
61
- """
62
- try:
63
- for agent in self._agents:
64
- if agent.name == name:
65
- return agent
66
- return None
67
- except Exception:
68
- raise
66
+ with tracer.start_as_current_span("Registry.get_agent") as span:
67
+ span.set_attribute("search_agent_name", name)
68
+ try:
69
+ for agent in self._agents:
70
+ if agent.name == name:
71
+ logger.info("Agent found", agent=name)
72
+ span.set_attribute("found", True)
73
+ return agent
74
+ logger.warning("Agent not found", agent=name)
75
+ span.set_attribute("found", False)
76
+ return None
77
+ except Exception as e:
78
+ logger.error("Error retrieving agent", agent=name, error=str(e))
79
+ span.record_exception(e)
80
+ raise
69
81
 
70
82
  def get_tool(self, name: str) -> Callable | None:
71
- """Retrieve a tool by name.
72
-
73
- Args:
74
- name: The name of the tool to retrieve
75
-
76
- Returns:
77
- The tool function if found, None otherwise
78
- """
79
- try:
80
- for tool_name, tool in self._tools:
81
- if tool_name == name:
82
- return tool
83
- return None
84
- except Exception:
85
- raise
83
+ with tracer.start_as_current_span("Registry.get_tool") as span:
84
+ span.set_attribute("search_tool_name", name)
85
+ try:
86
+ for tool_name, tool in self._tools:
87
+ if tool_name == name:
88
+ logger.info("Tool found", tool=name)
89
+ span.set_attribute("found", True)
90
+ return tool
91
+ logger.warning("Tool not found", tool=name)
92
+ span.set_attribute("found", False)
93
+ return None
94
+ except Exception as e:
95
+ logger.error("Error retrieving tool", tool=name, error=str(e))
96
+ span.record_exception(e)
97
+ raise
86
98
 
87
99
  def get_tools(self, names: list[str] | None) -> list[Callable]:
88
- """Retrieve multiple tools by name.
89
-
90
- Args:
91
- names: List of tool names to retrieve
92
-
93
- Returns:
94
- List of found tool functions (may be empty if none found)
95
- """
96
- try:
97
- if not names:
98
- return []
99
-
100
- tools = [self.get_tool(name) for name in names]
101
- return [tool for tool in tools if tool is not None]
102
- except Exception:
103
- raise
100
+ with tracer.start_as_current_span("Registry.get_tools") as span:
101
+ span.set_attribute("search_tool_names", str(names))
102
+ try:
103
+ if not names:
104
+ logger.info("No tool names provided")
105
+ return []
106
+ tools = [self.get_tool(name) for name in names]
107
+ found_tools = [tool for tool in tools if tool is not None]
108
+ logger.info(
109
+ "Tools retrieved", requested=names, found=len(found_tools)
110
+ )
111
+ span.set_attribute("found_tools_count", len(found_tools))
112
+ return found_tools
113
+ except Exception as e:
114
+ logger.error(
115
+ "Error retrieving tools", names=str(names), error=str(e)
116
+ )
117
+ span.record_exception(e)
118
+ raise
@@ -3,7 +3,10 @@
3
3
  import importlib
4
4
  import os
5
5
 
6
+ from flock.core.logging.trace_and_logged import traced_and_logged
6
7
 
8
+
9
+ @traced_and_logged
7
10
  def web_search_tavily(query: str):
8
11
  """Performs a web search using the Tavily client.
9
12
 
@@ -32,9 +35,12 @@ def web_search_tavily(query: str):
32
35
  except Exception:
33
36
  raise
34
37
  else:
35
- raise ImportError("Optional tool dependencies not installed. Install with 'pip install flock-core[tools]'.")
38
+ raise ImportError(
39
+ "Optional tool dependencies not installed. Install with 'pip install flock-core[tools]'."
40
+ )
36
41
 
37
42
 
43
+ @traced_and_logged
38
44
  def get_web_content_as_markdown(url: str):
39
45
  """Fetches web content from a URL and converts it to Markdown.
40
46
 
@@ -51,7 +57,10 @@ def get_web_content_as_markdown(url: str):
51
57
  ImportError: If either 'httpx' or 'markdownify' dependencies are not installed.
52
58
  Exception: Re-raises any exceptions encountered during the HTTP request or conversion.
53
59
  """
54
- if importlib.util.find_spec("httpx") is not None and importlib.util.find_spec("markdownify") is not None:
60
+ if (
61
+ importlib.util.find_spec("httpx") is not None
62
+ and importlib.util.find_spec("markdownify") is not None
63
+ ):
55
64
  import httpx
56
65
  from markdownify import markdownify as md
57
66
 
@@ -63,9 +72,12 @@ def get_web_content_as_markdown(url: str):
63
72
  except Exception:
64
73
  raise
65
74
  else:
66
- raise ImportError("Optional tool dependencies not installed. Install with 'pip install flock-core[tools]'.")
75
+ raise ImportError(
76
+ "Optional tool dependencies not installed. Install with 'pip install flock-core[tools]'."
77
+ )
67
78
 
68
79
 
80
+ @traced_and_logged
69
81
  def get_anything_as_markdown(url_or_file_path: str):
70
82
  """Converts the content of a URL or file into Markdown format.
71
83
 
@@ -94,9 +106,12 @@ def get_anything_as_markdown(url_or_file_path: str):
94
106
  except Exception:
95
107
  raise
96
108
  else:
97
- raise ImportError("Optional tool dependencies not installed. Install with 'pip install flock-core[all-tools]'.")
109
+ raise ImportError(
110
+ "Optional tool dependencies not installed. Install with 'pip install flock-core[all-tools]'."
111
+ )
98
112
 
99
113
 
114
+ @traced_and_logged
100
115
  def evaluate_math(expression: str) -> float:
101
116
  """Evaluates a mathematical expression using the dspy interpreter.
102
117
 
@@ -121,6 +136,7 @@ def evaluate_math(expression: str) -> float:
121
136
  raise
122
137
 
123
138
 
139
+ @traced_and_logged
124
140
  def code_eval(python_code: str) -> float:
125
141
  """Executes Python code using the dspy interpreter.
126
142
 
@@ -145,6 +161,7 @@ def code_eval(python_code: str) -> float:
145
161
  raise
146
162
 
147
163
 
164
+ @traced_and_logged
148
165
  def get_current_time() -> str:
149
166
  """Retrieves the current time in ISO 8601 format.
150
167
 
@@ -157,6 +174,7 @@ def get_current_time() -> str:
157
174
  return time
158
175
 
159
176
 
177
+ @traced_and_logged
160
178
  def count_words(text: str) -> int:
161
179
  """Counts the number of words in the provided text.
162
180
 
@@ -172,6 +190,7 @@ def count_words(text: str) -> int:
172
190
  return count
173
191
 
174
192
 
193
+ @traced_and_logged
175
194
  def extract_urls(text: str) -> list[str]:
176
195
  """Extracts all URLs from the given text.
177
196
 
@@ -191,6 +210,7 @@ def extract_urls(text: str) -> list[str]:
191
210
  return urls
192
211
 
193
212
 
213
+ @traced_and_logged
194
214
  def extract_numbers(text: str) -> list[float]:
195
215
  """Extracts all numerical values from the provided text.
196
216
 
@@ -209,6 +229,7 @@ def extract_numbers(text: str) -> list[float]:
209
229
  return numbers
210
230
 
211
231
 
232
+ @traced_and_logged
212
233
  def json_parse_safe(text: str) -> dict:
213
234
  """Safely parses a JSON string into a dictionary.
214
235
 
@@ -230,6 +251,7 @@ def json_parse_safe(text: str) -> dict:
230
251
  return {}
231
252
 
232
253
 
254
+ @traced_and_logged
233
255
  def save_to_file(content: str, filename: str):
234
256
  """Saves the given content to a file.
235
257
 
@@ -250,6 +272,7 @@ def save_to_file(content: str, filename: str):
250
272
  raise
251
273
 
252
274
 
275
+ @traced_and_logged
253
276
  def read_from_file(filename: str) -> str:
254
277
  """Reads and returns the content of a file.
255
278
 
@@ -5,7 +5,10 @@ import os
5
5
 
6
6
  import httpx
7
7
 
8
+ from flock.core.logging.trace_and_logged import traced_and_logged
8
9
 
10
+
11
+ @traced_and_logged
9
12
  def create_user_stories_as_github_issue(title: str, body: str) -> str:
10
13
  """Create a new GitHub issue representing a user story.
11
14
 
@@ -44,6 +47,7 @@ def create_user_stories_as_github_issue(title: str, body: str) -> str:
44
47
  return "Failed to create issue. Please try again later."
45
48
 
46
49
 
50
+ @traced_and_logged
47
51
  def upload_readme(content: str):
48
52
  """Upload or update the README.md file in a GitHub repository.
49
53
 
@@ -66,7 +70,9 @@ def upload_readme(content: str):
66
70
  GITHUB_TOKEN = os.getenv("GITHUB_PAT")
67
71
 
68
72
  if not GITHUB_USERNAME or not REPO_NAME or not GITHUB_TOKEN:
69
- raise ValueError("Missing environment variables: GITHUB_USERNAME, GITHUB_REPO, or GITHUB_PAT")
73
+ raise ValueError(
74
+ "Missing environment variables: GITHUB_USERNAME, GITHUB_REPO, or GITHUB_PAT"
75
+ )
70
76
 
71
77
  GITHUB_API_URL = f"https://api.github.com/repos/{GITHUB_USERNAME}/{REPO_NAME}/contents/README.md"
72
78
 
@@ -75,13 +81,20 @@ def upload_readme(content: str):
75
81
  with httpx.Client() as client:
76
82
  response = client.get(
77
83
  GITHUB_API_URL,
78
- headers={"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json"},
84
+ headers={
85
+ "Authorization": f"Bearer {GITHUB_TOKEN}",
86
+ "Accept": "application/vnd.github.v3+json",
87
+ },
79
88
  )
80
89
 
81
90
  data = response.json()
82
91
  sha = data.get("sha", None)
83
92
 
84
- payload = {"message": "Updating README.md", "content": encoded_content, "branch": "main"}
93
+ payload = {
94
+ "message": "Updating README.md",
95
+ "content": encoded_content,
96
+ "branch": "main",
97
+ }
85
98
 
86
99
  if sha:
87
100
  payload["sha"] = sha
@@ -89,7 +102,10 @@ def upload_readme(content: str):
89
102
  response = client.put(
90
103
  GITHUB_API_URL,
91
104
  json=payload,
92
- headers={"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json"},
105
+ headers={
106
+ "Authorization": f"Bearer {GITHUB_TOKEN}",
107
+ "Accept": "application/vnd.github.v3+json",
108
+ },
93
109
  )
94
110
 
95
111
  if response.status_code in [200, 201]:
@@ -98,6 +114,7 @@ def upload_readme(content: str):
98
114
  print("Failed to upload README.md:", response.json())
99
115
 
100
116
 
117
+ @traced_and_logged
101
118
  def create_files(file_paths) -> str:
102
119
  """Create multiple files in a GitHub repository with a predefined content.
103
120
 
@@ -122,7 +139,9 @@ def create_files(file_paths) -> str:
122
139
  GITHUB_TOKEN = os.getenv("GITHUB_PAT")
123
140
 
124
141
  if not GITHUB_USERNAME or not REPO_NAME or not GITHUB_TOKEN:
125
- raise ValueError("Missing environment variables: GITHUB_USERNAME, GITHUB_REPO, or GITHUB_PAT")
142
+ raise ValueError(
143
+ "Missing environment variables: GITHUB_USERNAME, GITHUB_REPO, or GITHUB_PAT"
144
+ )
126
145
 
127
146
  encoded_content = base64.b64encode(b"#created by flock").decode()
128
147
 
@@ -132,13 +151,20 @@ def create_files(file_paths) -> str:
132
151
 
133
152
  response = client.get(
134
153
  GITHUB_API_URL,
135
- headers={"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json"},
154
+ headers={
155
+ "Authorization": f"token {GITHUB_TOKEN}",
156
+ "Accept": "application/vnd.github.v3+json",
157
+ },
136
158
  )
137
159
 
138
160
  data = response.json()
139
161
  sha = data.get("sha", None)
140
162
 
141
- payload = {"message": f"Creating {file_path}", "content": encoded_content, "branch": "main"}
163
+ payload = {
164
+ "message": f"Creating {file_path}",
165
+ "content": encoded_content,
166
+ "branch": "main",
167
+ }
142
168
 
143
169
  if sha:
144
170
  print(f"Skipping {file_path}, file already exists.")
@@ -147,7 +173,10 @@ def create_files(file_paths) -> str:
147
173
  response = client.put(
148
174
  GITHUB_API_URL,
149
175
  json=payload,
150
- headers={"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json"},
176
+ headers={
177
+ "Authorization": f"token {GITHUB_TOKEN}",
178
+ "Accept": "application/vnd.github.v3+json",
179
+ },
151
180
  )
152
181
 
153
182
  if response.status_code in [200, 201]:
@@ -8,9 +8,13 @@ def display_banner():
8
8
  """Display the Flock banner."""
9
9
  banner_text = Text(
10
10
  """
11
- ▒█▀▀▀ █░░ █▀▀█ █▀▀ █░█
12
- ▒█▀▀▀ █░░ █░░█ █░░ █▀▄
13
- ▒█░░░ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀░▀
11
+ 🦆 🐓 🐤 🦆
12
+ ╭━━━━━━━━━━━━━━━━━━━━━━━━╮
13
+ 🐓 │ ▒█▀▀▀ █░░ █▀▀█ █▀▀ █░█ │ 🐧
14
+ │ ▒█▀▀▀ █░░ █░░█ █░░ █▀▄ │
15
+ 🐧 │ ▒█░░░ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀░▀ │ 🐓
16
+ ╰━━━━━━━━━━━━━━━━━━━━━━━━╯
17
+ 🦆 🐤 🐧 🐓
14
18
  """,
15
19
  justify="center",
16
20
  style="bold orange3",