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.
- flock/core/context/context.py +104 -133
- flock/core/execution/temporal_executor.py +22 -6
- flock/core/flock.py +95 -62
- flock/core/flock_agent.py +150 -68
- flock/core/logging/logging.py +22 -4
- flock/core/logging/telemetry.py +21 -0
- flock/core/logging/trace_and_logged.py +55 -0
- flock/core/mixin/dspy_integration.py +40 -14
- flock/core/registry/agent_registry.py +89 -74
- flock/core/tools/basic_tools.py +27 -4
- flock/core/tools/dev_tools/github.py +37 -8
- flock/core/util/cli_helper.py +7 -3
- flock/workflow/activities.py +148 -90
- {flock_core-0.2.1.dist-info → flock_core-0.2.2.dist-info}/METADATA +35 -2
- {flock_core-0.2.1.dist-info → flock_core-0.2.2.dist-info}/RECORD +17 -20
- flock/agents/__init__.py +0 -0
- flock/agents/batch_agent.py +0 -140
- flock/agents/loop_agent.py +0 -117
- flock/agents/trigger_agent.py +0 -113
- flock/agents/user_agent.py +0 -145
- {flock_core-0.2.1.dist-info → flock_core-0.2.2.dist-info}/WHEEL +0 -0
- {flock_core-0.2.1.dist-info → flock_core-0.2.2.dist-info}/licenses/LICENSE +0 -0
flock/core/flock_agent.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""FlockAgent is the core, declarative base class for all agents in the Flock framework."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC
|
|
4
|
-
from collections.abc import Callable
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from typing import Any, Literal, Union
|
|
7
7
|
|
|
@@ -10,16 +10,28 @@ from pydantic import BaseModel, Field
|
|
|
10
10
|
|
|
11
11
|
from flock.core.context.context import FlockContext
|
|
12
12
|
from flock.core.logging.logging import get_logger
|
|
13
|
-
from flock.core.mixin.dspy_integration import DSPyIntegrationMixin
|
|
13
|
+
from flock.core.mixin.dspy_integration import AgentType, DSPyIntegrationMixin
|
|
14
14
|
from flock.core.mixin.prompt_parser import PromptParserMixin
|
|
15
15
|
|
|
16
16
|
logger = get_logger("flock")
|
|
17
|
+
from opentelemetry import trace
|
|
18
|
+
|
|
19
|
+
tracer = trace.get_tracer(__name__)
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
@dataclass
|
|
20
23
|
class FlockAgentConfig:
|
|
21
24
|
"""Configuration options for a FlockAgent."""
|
|
22
25
|
|
|
26
|
+
agent_type_override: AgentType = field(
|
|
27
|
+
default=None,
|
|
28
|
+
metadata={
|
|
29
|
+
"description": "Overrides the agent type. TOOL USE ONLY WORKS WITH REACT"
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
disable_output: bool = field(
|
|
33
|
+
default=False, metadata={"description": "Disables the agent's output."}
|
|
34
|
+
)
|
|
23
35
|
save_to_file: bool = field(
|
|
24
36
|
default=False,
|
|
25
37
|
metadata={
|
|
@@ -30,7 +42,7 @@ class FlockAgentConfig:
|
|
|
30
42
|
|
|
31
43
|
|
|
32
44
|
@dataclass
|
|
33
|
-
class
|
|
45
|
+
class HandOff:
|
|
34
46
|
"""Base class for handoff returns."""
|
|
35
47
|
|
|
36
48
|
next_agent: Union[str, "FlockAgent"] = field(
|
|
@@ -124,14 +136,14 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
124
136
|
"", description="A human-readable description of the agent."
|
|
125
137
|
)
|
|
126
138
|
|
|
127
|
-
input: str | None = Field(
|
|
139
|
+
input: str | Callable[..., str] | None = Field(
|
|
128
140
|
None,
|
|
129
141
|
description=(
|
|
130
142
|
"A comma-separated list of input keys. Optionally supports type hints (:) and descriptions (|). "
|
|
131
143
|
"For example: 'query: str | The search query, chapter_list: list[str] | The chapter list of the document'."
|
|
132
144
|
),
|
|
133
145
|
)
|
|
134
|
-
output: str | None = Field(
|
|
146
|
+
output: str | Callable[..., str] | None = Field(
|
|
135
147
|
None,
|
|
136
148
|
description=(
|
|
137
149
|
"A comma-separated list of output keys. Optionally supports type hints (:) and descriptions (|). "
|
|
@@ -157,7 +169,7 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
157
169
|
),
|
|
158
170
|
)
|
|
159
171
|
|
|
160
|
-
termination: str | None = Field(
|
|
172
|
+
termination: str | Callable[..., str] | None = Field(
|
|
161
173
|
None,
|
|
162
174
|
description="An optional termination condition or phrase used to indicate when the agent should stop processing.",
|
|
163
175
|
)
|
|
@@ -167,32 +179,58 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
167
179
|
description="Configuration options for the agent, such as serialization settings.",
|
|
168
180
|
)
|
|
169
181
|
|
|
182
|
+
# Lifecycle callback fields: if provided, these callbacks are used instead of overriding the methods.
|
|
183
|
+
initialize_callback: Callable[[dict[str, Any]], Awaitable[None]] | None = (
|
|
184
|
+
Field(
|
|
185
|
+
default=None,
|
|
186
|
+
description="Optional callback function for initialization. If provided, this async function is called with the inputs.",
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
terminate_callback: (
|
|
190
|
+
Callable[[dict[str, Any], dict[str, Any]], Awaitable[None]] | None
|
|
191
|
+
) = Field(
|
|
192
|
+
default=None,
|
|
193
|
+
description="Optional callback function for termination. If provided, this async function is called with the inputs and result.",
|
|
194
|
+
)
|
|
195
|
+
on_error_callback: (
|
|
196
|
+
Callable[[Exception, dict[str, Any]], Awaitable[None]] | None
|
|
197
|
+
) = Field(
|
|
198
|
+
default=None,
|
|
199
|
+
description="Optional callback function for error handling. If provided, this async function is called with the error and inputs.",
|
|
200
|
+
)
|
|
201
|
+
|
|
170
202
|
# Lifecycle hooks
|
|
171
203
|
async def initialize(self, inputs: dict[str, Any]) -> None:
|
|
172
|
-
"""
|
|
204
|
+
"""Called at the very start of the agent's execution.
|
|
173
205
|
|
|
174
|
-
Override this method to perform
|
|
175
|
-
such as loading resources or validating inputs.
|
|
206
|
+
Override this method or provide an `initialize_callback` to perform setup tasks such as input validation or resource loading.
|
|
176
207
|
"""
|
|
177
|
-
|
|
208
|
+
if self.initialize_callback is not None:
|
|
209
|
+
await self.initialize_callback(self, inputs)
|
|
210
|
+
else:
|
|
211
|
+
pass
|
|
178
212
|
|
|
179
213
|
async def terminate(
|
|
180
214
|
self, inputs: dict[str, Any], result: dict[str, Any]
|
|
181
215
|
) -> None:
|
|
182
|
-
"""
|
|
216
|
+
"""Called at the very end of the agent's execution.
|
|
183
217
|
|
|
184
|
-
Override this method to perform
|
|
185
|
-
such as releasing resources or logging results.
|
|
218
|
+
Override this method or provide a `terminate_callback` to perform cleanup tasks such as releasing resources or logging results.
|
|
186
219
|
"""
|
|
187
|
-
|
|
220
|
+
if self.terminate_callback is not None:
|
|
221
|
+
await self.terminate_callback(self, inputs, result)
|
|
222
|
+
else:
|
|
223
|
+
pass
|
|
188
224
|
|
|
189
225
|
async def on_error(self, error: Exception, inputs: dict[str, Any]) -> None:
|
|
190
226
|
"""Called if the agent encounters an error during execution.
|
|
191
227
|
|
|
192
|
-
Override this method to implement
|
|
193
|
-
custom error handling or recovery strategies.
|
|
228
|
+
Override this method or provide an `on_error_callback` to implement custom error handling or recovery strategies.
|
|
194
229
|
"""
|
|
195
|
-
|
|
230
|
+
if self.on_error_callback is not None:
|
|
231
|
+
await self.on_error_callback(self, error, inputs)
|
|
232
|
+
else:
|
|
233
|
+
pass
|
|
196
234
|
|
|
197
235
|
async def evaluate(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
198
236
|
"""Process the agent's task using the provided inputs and return the result.
|
|
@@ -253,24 +291,35 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
253
291
|
- Return a dictionary similar to:
|
|
254
292
|
{"idea": "A fun app idea based on ...", "query": "build an app", "context": {"previous_idea": "messaging app"}}
|
|
255
293
|
"""
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
294
|
+
with tracer.start_as_current_span("agent.evaluate") as span:
|
|
295
|
+
span.set_attribute("agent.name", self.name)
|
|
296
|
+
span.set_attribute("inputs", str(inputs))
|
|
297
|
+
try:
|
|
298
|
+
# Create and configure the signature and language model.
|
|
299
|
+
self.__dspy_signature = self.create_dspy_signature_class(
|
|
300
|
+
self.name,
|
|
301
|
+
self.description,
|
|
302
|
+
f"{self.input} -> {self.output}",
|
|
303
|
+
)
|
|
304
|
+
self._configure_language_model()
|
|
305
|
+
agent_task = self._select_task(
|
|
306
|
+
self.__dspy_signature,
|
|
307
|
+
agent_type_override=self.config.agent_type_override,
|
|
308
|
+
)
|
|
309
|
+
# Execute the task.
|
|
310
|
+
result = agent_task(**inputs)
|
|
311
|
+
result = self._process_result(result, inputs)
|
|
312
|
+
span.set_attribute("result", str(result))
|
|
313
|
+
logger.info("Evaluation successful", agent=self.name)
|
|
314
|
+
return result
|
|
315
|
+
except Exception as eval_error:
|
|
316
|
+
logger.error(
|
|
317
|
+
"Error during evaluation",
|
|
318
|
+
agent=self.name,
|
|
319
|
+
error=str(eval_error),
|
|
320
|
+
)
|
|
321
|
+
span.record_exception(eval_error)
|
|
322
|
+
raise
|
|
274
323
|
|
|
275
324
|
async def run(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
276
325
|
"""Run the agent with the given inputs and return its generated output.
|
|
@@ -318,15 +367,23 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
318
367
|
The method might return:
|
|
319
368
|
{"result": "A conversational chatbot that uses AI to...", "query": "build a chatbot", "context": {"user": "Alice"}}
|
|
320
369
|
"""
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
370
|
+
with tracer.start_as_current_span("agent.run") as span:
|
|
371
|
+
span.set_attribute("agent.name", self.name)
|
|
372
|
+
span.set_attribute("inputs", str(inputs))
|
|
373
|
+
try:
|
|
374
|
+
await self.initialize(inputs)
|
|
375
|
+
result = await self.evaluate(inputs)
|
|
376
|
+
await self.terminate(inputs, result)
|
|
377
|
+
span.set_attribute("result", str(result))
|
|
378
|
+
logger.info("Agent run completed", agent=self.name)
|
|
379
|
+
return result
|
|
380
|
+
except Exception as run_error:
|
|
381
|
+
logger.error(
|
|
382
|
+
"Error running agent", agent=self.name, error=str(run_error)
|
|
383
|
+
)
|
|
384
|
+
await self.on_error(run_error, inputs)
|
|
385
|
+
span.record_exception(run_error)
|
|
386
|
+
raise
|
|
330
387
|
|
|
331
388
|
async def run_temporal(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
332
389
|
"""Execute this agent via a Temporal workflow for enhanced fault tolerance and asynchronous processing.
|
|
@@ -369,29 +426,54 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
369
426
|
result = await agent.run_temporal({"query": "analyze data", "context": {"source": "sales"}})
|
|
370
427
|
will execute the agent on a Temporal worker and return the output in a structured dictionary format.
|
|
371
428
|
"""
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
client
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
429
|
+
with tracer.start_as_current_span("agent.run_temporal") as span:
|
|
430
|
+
span.set_attribute("agent.name", self.name)
|
|
431
|
+
span.set_attribute("inputs", str(inputs))
|
|
432
|
+
try:
|
|
433
|
+
from temporalio.client import Client
|
|
434
|
+
|
|
435
|
+
from flock.workflow.agent_activities import (
|
|
436
|
+
run_flock_agent_activity,
|
|
437
|
+
)
|
|
438
|
+
from flock.workflow.temporal_setup import run_activity
|
|
439
|
+
|
|
440
|
+
client = await Client.connect(
|
|
441
|
+
"localhost:7233", namespace="default"
|
|
442
|
+
)
|
|
443
|
+
agent_data = self.to_dict()
|
|
444
|
+
inputs_data = inputs
|
|
445
|
+
|
|
446
|
+
result = await run_activity(
|
|
447
|
+
client,
|
|
448
|
+
self.name,
|
|
449
|
+
run_flock_agent_activity,
|
|
450
|
+
{"agent_data": agent_data, "inputs": inputs_data},
|
|
451
|
+
)
|
|
452
|
+
span.set_attribute("result", str(result))
|
|
453
|
+
logger.info("Temporal run successful", agent=self.name)
|
|
454
|
+
return result
|
|
455
|
+
except Exception as temporal_error:
|
|
456
|
+
logger.error(
|
|
457
|
+
"Error in Temporal workflow",
|
|
458
|
+
agent=self.name,
|
|
459
|
+
error=str(temporal_error),
|
|
460
|
+
)
|
|
461
|
+
span.record_exception(temporal_error)
|
|
462
|
+
raise
|
|
463
|
+
|
|
464
|
+
def resolve_callables(self, context) -> None:
|
|
465
|
+
"""Resolve any callable fields in the agent instance using the provided context.
|
|
466
|
+
|
|
467
|
+
This method resolves any callable fields in the agent instance using the provided context. It iterates over
|
|
468
|
+
the agent's fields and replaces any callable objects (such as lifecycle hooks or tools) with their corresponding
|
|
469
|
+
resolved values from the context. This ensures that the agent is fully configured and ready
|
|
470
|
+
"""
|
|
471
|
+
if isinstance(self.input, Callable):
|
|
472
|
+
self.input = self.input(context)
|
|
473
|
+
if isinstance(self.output, Callable):
|
|
474
|
+
self.output = self.output(context)
|
|
475
|
+
if isinstance(self.description, Callable):
|
|
476
|
+
self.description = self.description(context)
|
|
395
477
|
|
|
396
478
|
def to_dict(self) -> dict[str, Any]:
|
|
397
479
|
"""Serialize the FlockAgent instance to a dictionary.
|
|
@@ -438,7 +520,7 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
438
520
|
return {k: convert_callable(v) for k, v in obj.items()}
|
|
439
521
|
return obj
|
|
440
522
|
|
|
441
|
-
data = self.
|
|
523
|
+
data = self.model_dump()
|
|
442
524
|
return convert_callable(data)
|
|
443
525
|
|
|
444
526
|
@classmethod
|
flock/core/logging/logging.py
CHANGED
|
@@ -13,6 +13,8 @@ Key points:
|
|
|
13
13
|
|
|
14
14
|
import sys
|
|
15
15
|
|
|
16
|
+
from opentelemetry import trace
|
|
17
|
+
|
|
16
18
|
# Always import Temporal workflow (since it's part of the project)
|
|
17
19
|
from temporalio import workflow
|
|
18
20
|
|
|
@@ -37,6 +39,16 @@ def in_workflow_context() -> bool:
|
|
|
37
39
|
return False
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
def get_current_trace_id() -> str:
|
|
43
|
+
"""Fetch the current trace ID from OpenTelemetry, if available."""
|
|
44
|
+
current_span = trace.get_current_span()
|
|
45
|
+
span_context = current_span.get_span_context()
|
|
46
|
+
# Format the trace_id as hex (if valid)
|
|
47
|
+
if span_context.is_valid:
|
|
48
|
+
return format(span_context.trace_id, "032x")
|
|
49
|
+
return "no-trace"
|
|
50
|
+
|
|
51
|
+
|
|
40
52
|
# Configure Loguru for non-workflow (local/worker) contexts.
|
|
41
53
|
# Note that in workflow code, we will use Temporal's workflow.logger instead.
|
|
42
54
|
loguru_logger.remove()
|
|
@@ -44,7 +56,10 @@ loguru_logger.add(
|
|
|
44
56
|
sys.stderr,
|
|
45
57
|
level="DEBUG",
|
|
46
58
|
colorize=True,
|
|
47
|
-
format=
|
|
59
|
+
format=(
|
|
60
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | "
|
|
61
|
+
"<cyan>[trace_id: {extra[trace_id]}]</cyan> | <magenta>[{extra[category]}]</magenta> | {message}"
|
|
62
|
+
),
|
|
48
63
|
)
|
|
49
64
|
# Optionally add a file handler, e.g.:
|
|
50
65
|
# loguru_logger.add("logs/flock.log", rotation="100 MB", retention="30 days", level="DEBUG")
|
|
@@ -92,9 +107,12 @@ class FlockLogger:
|
|
|
92
107
|
if in_workflow_context():
|
|
93
108
|
# Use Temporal's workflow.logger inside a workflow context.
|
|
94
109
|
return workflow.logger
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
# Bind our logger with category and trace_id
|
|
111
|
+
return loguru_logger.bind(
|
|
112
|
+
name=self.name,
|
|
113
|
+
category=self.name, # Customize this per module (e.g., "flock", "agent", "context")
|
|
114
|
+
trace_id=get_current_trace_id(),
|
|
115
|
+
)
|
|
98
116
|
|
|
99
117
|
def debug(self, message: str, *args, **kwargs):
|
|
100
118
|
self._get_logger().debug(message, *args, **kwargs)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from opentelemetry import trace
|
|
2
|
+
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
|
|
3
|
+
from opentelemetry.sdk.resources import Resource
|
|
4
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
5
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def setup_tracing():
|
|
9
|
+
resource = Resource(attributes={"service.name": "flock-agent-framework"})
|
|
10
|
+
provider = TracerProvider(resource=resource)
|
|
11
|
+
trace.set_tracer_provider(provider)
|
|
12
|
+
|
|
13
|
+
# Create a Jaeger exporter
|
|
14
|
+
jaeger_exporter = JaegerExporter(
|
|
15
|
+
agent_host_name="localhost", # or the hostname where Jaeger is running
|
|
16
|
+
agent_port=6831, # default Jaeger agent port
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
provider.add_span_processor(BatchSpanProcessor(jaeger_exporter))
|
|
20
|
+
tracer = trace.get_tracer(__name__)
|
|
21
|
+
return tracer
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
from opentelemetry import trace
|
|
5
|
+
|
|
6
|
+
from flock.core.logging.logging import get_logger
|
|
7
|
+
|
|
8
|
+
logger = get_logger("tools")
|
|
9
|
+
tracer = trace.get_tracer(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def traced_and_logged(func):
|
|
13
|
+
"""A decorator that wraps a function in an OpenTelemetry span and logs its inputs,
|
|
14
|
+
outputs, and exceptions. Supports both synchronous and asynchronous functions.
|
|
15
|
+
"""
|
|
16
|
+
if inspect.iscoroutinefunction(func):
|
|
17
|
+
|
|
18
|
+
@functools.wraps(func)
|
|
19
|
+
async def async_wrapper(*args, **kwargs):
|
|
20
|
+
with tracer.start_as_current_span(func.__name__) as span:
|
|
21
|
+
span.set_attribute("args", str(args))
|
|
22
|
+
span.set_attribute("kwargs", str(kwargs))
|
|
23
|
+
try:
|
|
24
|
+
result = await func(*args, **kwargs)
|
|
25
|
+
span.set_attribute("result", str(result))
|
|
26
|
+
logger.debug(
|
|
27
|
+
f"{func.__name__} executed successfully", result=result
|
|
28
|
+
)
|
|
29
|
+
return result
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.error(f"Error in {func.__name__}", error=str(e))
|
|
32
|
+
span.record_exception(e)
|
|
33
|
+
raise
|
|
34
|
+
|
|
35
|
+
return async_wrapper
|
|
36
|
+
else:
|
|
37
|
+
|
|
38
|
+
@functools.wraps(func)
|
|
39
|
+
def wrapper(*args, **kwargs):
|
|
40
|
+
with tracer.start_as_current_span(func.__name__) as span:
|
|
41
|
+
span.set_attribute("args", str(args))
|
|
42
|
+
span.set_attribute("kwargs", str(kwargs))
|
|
43
|
+
try:
|
|
44
|
+
result = func(*args, **kwargs)
|
|
45
|
+
span.set_attribute("result", str(result))
|
|
46
|
+
logger.debug(
|
|
47
|
+
f"{func.__name__} executed successfully", result=result
|
|
48
|
+
)
|
|
49
|
+
return result
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error(f"Error in {func.__name__}", error=str(e))
|
|
52
|
+
span.record_exception(e)
|
|
53
|
+
raise
|
|
54
|
+
|
|
55
|
+
return wrapper
|
|
@@ -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
|
-
) ->
|
|
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:
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
|