agentic-blocks 0.1.22__tar.gz → 0.1.23__tar.gz
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.
- {agentic_blocks-0.1.22/src/agentic_blocks.egg-info → agentic_blocks-0.1.23}/PKG-INFO +1 -1
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/pyproject.toml +1 -1
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23/src/agentic_blocks.egg-info}/PKG-INFO +1 -1
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks.egg-info/SOURCES.txt +0 -4
- agentic_blocks-0.1.22/src/agentic_blocks/tracing/__init__.py +0 -13
- agentic_blocks-0.1.22/src/agentic_blocks/tracing/config.py +0 -111
- agentic_blocks-0.1.22/src/agentic_blocks/tracing/core.py +0 -287
- agentic_blocks-0.1.22/src/agentic_blocks/tracing/decorator.py +0 -316
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/LICENSE +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/README.md +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/setup.cfg +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks/__init__.py +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks/agent.py +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks/llm.py +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks/mcp_client.py +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks/messages.py +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks/utils/tools_utils.py +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks/visualization/visualize.py +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks.egg-info/dependency_links.txt +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks.egg-info/requires.txt +0 -0
- {agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks.egg-info/top_level.txt +0 -0
@@ -14,7 +14,7 @@ agentic_blocks = []
|
|
14
14
|
|
15
15
|
[project]
|
16
16
|
name = "agentic-blocks"
|
17
|
-
version = "0.1.
|
17
|
+
version = "0.1.23"
|
18
18
|
description = "Simple building blocks for agentic AI systems with MCP client and conversation management"
|
19
19
|
readme = "README.md"
|
20
20
|
requires-python = ">=3.11"
|
@@ -11,9 +11,5 @@ src/agentic_blocks.egg-info/SOURCES.txt
|
|
11
11
|
src/agentic_blocks.egg-info/dependency_links.txt
|
12
12
|
src/agentic_blocks.egg-info/requires.txt
|
13
13
|
src/agentic_blocks.egg-info/top_level.txt
|
14
|
-
src/agentic_blocks/tracing/__init__.py
|
15
|
-
src/agentic_blocks/tracing/config.py
|
16
|
-
src/agentic_blocks/tracing/core.py
|
17
|
-
src/agentic_blocks/tracing/decorator.py
|
18
14
|
src/agentic_blocks/utils/tools_utils.py
|
19
15
|
src/agentic_blocks/visualization/visualize.py
|
@@ -1,13 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
PocketFlow Tracing Module
|
3
|
-
|
4
|
-
This module provides observability and tracing capabilities for PocketFlow workflows
|
5
|
-
using Langfuse as the backend. It includes decorators and utilities to automatically
|
6
|
-
trace node execution, inputs, and outputs.
|
7
|
-
"""
|
8
|
-
|
9
|
-
from .config import TracingConfig
|
10
|
-
from .core import LangfuseTracer
|
11
|
-
from .decorator import trace_flow
|
12
|
-
|
13
|
-
__all__ = ["trace_flow", "TracingConfig", "LangfuseTracer"]
|
@@ -1,111 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Configuration module for PocketFlow tracing with Langfuse.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import os
|
6
|
-
from dataclasses import dataclass
|
7
|
-
from typing import Optional
|
8
|
-
from dotenv import load_dotenv
|
9
|
-
|
10
|
-
|
11
|
-
@dataclass
|
12
|
-
class TracingConfig:
|
13
|
-
"""Configuration class for PocketFlow tracing with Langfuse."""
|
14
|
-
|
15
|
-
# Langfuse configuration
|
16
|
-
langfuse_secret_key: Optional[str] = None
|
17
|
-
langfuse_public_key: Optional[str] = None
|
18
|
-
langfuse_host: Optional[str] = None
|
19
|
-
|
20
|
-
# PocketFlow tracing configuration
|
21
|
-
debug: bool = False
|
22
|
-
trace_inputs: bool = True
|
23
|
-
trace_outputs: bool = True
|
24
|
-
trace_prep: bool = True
|
25
|
-
trace_exec: bool = True
|
26
|
-
trace_post: bool = True
|
27
|
-
trace_errors: bool = True
|
28
|
-
|
29
|
-
# Session configuration
|
30
|
-
session_id: Optional[str] = None
|
31
|
-
user_id: Optional[str] = None
|
32
|
-
|
33
|
-
@classmethod
|
34
|
-
def from_env(cls, env_file: Optional[str] = None) -> "TracingConfig":
|
35
|
-
"""
|
36
|
-
Create TracingConfig from environment variables.
|
37
|
-
|
38
|
-
Args:
|
39
|
-
env_file: Optional path to .env file. If None, looks for .env in current directory.
|
40
|
-
|
41
|
-
Returns:
|
42
|
-
TracingConfig instance with values from environment variables.
|
43
|
-
"""
|
44
|
-
# Load environment variables from .env file if it exists
|
45
|
-
if env_file:
|
46
|
-
load_dotenv(env_file)
|
47
|
-
else:
|
48
|
-
# Try to find .env file in current directory or parent directories
|
49
|
-
load_dotenv()
|
50
|
-
|
51
|
-
return cls(
|
52
|
-
langfuse_secret_key=os.getenv("LANGFUSE_SECRET_KEY"),
|
53
|
-
langfuse_public_key=os.getenv("LANGFUSE_PUBLIC_KEY"),
|
54
|
-
langfuse_host=os.getenv("LANGFUSE_HOST"),
|
55
|
-
debug=os.getenv("POCKETFLOW_TRACING_DEBUG", "false").lower() == "true",
|
56
|
-
trace_inputs=os.getenv("POCKETFLOW_TRACE_INPUTS", "true").lower() == "true",
|
57
|
-
trace_outputs=os.getenv("POCKETFLOW_TRACE_OUTPUTS", "true").lower() == "true",
|
58
|
-
trace_prep=os.getenv("POCKETFLOW_TRACE_PREP", "true").lower() == "true",
|
59
|
-
trace_exec=os.getenv("POCKETFLOW_TRACE_EXEC", "true").lower() == "true",
|
60
|
-
trace_post=os.getenv("POCKETFLOW_TRACE_POST", "true").lower() == "true",
|
61
|
-
trace_errors=os.getenv("POCKETFLOW_TRACE_ERRORS", "true").lower() == "true",
|
62
|
-
session_id=os.getenv("POCKETFLOW_SESSION_ID"),
|
63
|
-
user_id=os.getenv("POCKETFLOW_USER_ID"),
|
64
|
-
)
|
65
|
-
|
66
|
-
def validate(self) -> bool:
|
67
|
-
"""
|
68
|
-
Validate that required configuration is present.
|
69
|
-
|
70
|
-
Returns:
|
71
|
-
True if configuration is valid, False otherwise.
|
72
|
-
"""
|
73
|
-
if not self.langfuse_secret_key:
|
74
|
-
if self.debug:
|
75
|
-
print("Warning: LANGFUSE_SECRET_KEY not set")
|
76
|
-
return False
|
77
|
-
|
78
|
-
if not self.langfuse_public_key:
|
79
|
-
if self.debug:
|
80
|
-
print("Warning: LANGFUSE_PUBLIC_KEY not set")
|
81
|
-
return False
|
82
|
-
|
83
|
-
if not self.langfuse_host:
|
84
|
-
if self.debug:
|
85
|
-
print("Warning: LANGFUSE_HOST not set")
|
86
|
-
return False
|
87
|
-
|
88
|
-
return True
|
89
|
-
|
90
|
-
def to_langfuse_kwargs(self) -> dict:
|
91
|
-
"""
|
92
|
-
Convert configuration to kwargs for Langfuse client initialization.
|
93
|
-
|
94
|
-
Returns:
|
95
|
-
Dictionary of kwargs for Langfuse client.
|
96
|
-
"""
|
97
|
-
kwargs = {}
|
98
|
-
|
99
|
-
if self.langfuse_secret_key:
|
100
|
-
kwargs["secret_key"] = self.langfuse_secret_key
|
101
|
-
|
102
|
-
if self.langfuse_public_key:
|
103
|
-
kwargs["public_key"] = self.langfuse_public_key
|
104
|
-
|
105
|
-
if self.langfuse_host:
|
106
|
-
kwargs["host"] = self.langfuse_host
|
107
|
-
|
108
|
-
if self.debug:
|
109
|
-
kwargs["debug"] = True
|
110
|
-
|
111
|
-
return kwargs
|
@@ -1,287 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Core tracing functionality for PocketFlow with Langfuse integration.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import json
|
6
|
-
import time
|
7
|
-
import uuid
|
8
|
-
from typing import Any, Dict, Optional, Union
|
9
|
-
from datetime import datetime
|
10
|
-
|
11
|
-
try:
|
12
|
-
from langfuse import Langfuse
|
13
|
-
|
14
|
-
LANGFUSE_AVAILABLE = True
|
15
|
-
except ImportError:
|
16
|
-
LANGFUSE_AVAILABLE = False
|
17
|
-
print("Warning: langfuse package not installed. Install with: pip install langfuse")
|
18
|
-
|
19
|
-
from .config import TracingConfig
|
20
|
-
|
21
|
-
|
22
|
-
class LangfuseTracer:
|
23
|
-
"""
|
24
|
-
Core tracer class that handles Langfuse integration for PocketFlow.
|
25
|
-
"""
|
26
|
-
|
27
|
-
def __init__(self, config: TracingConfig):
|
28
|
-
"""
|
29
|
-
Initialize the LangfuseTracer.
|
30
|
-
|
31
|
-
Args:
|
32
|
-
config: TracingConfig instance with Langfuse settings.
|
33
|
-
"""
|
34
|
-
self.config = config
|
35
|
-
self.client = None
|
36
|
-
self.current_trace = None
|
37
|
-
self.spans = {} # Store spans by node ID
|
38
|
-
|
39
|
-
if LANGFUSE_AVAILABLE and config.validate():
|
40
|
-
try:
|
41
|
-
# Initialize Langfuse client with proper parameters
|
42
|
-
kwargs = {}
|
43
|
-
if config.langfuse_secret_key:
|
44
|
-
kwargs["secret_key"] = config.langfuse_secret_key
|
45
|
-
if config.langfuse_public_key:
|
46
|
-
kwargs["public_key"] = config.langfuse_public_key
|
47
|
-
if config.langfuse_host:
|
48
|
-
kwargs["host"] = config.langfuse_host
|
49
|
-
if config.debug:
|
50
|
-
kwargs["debug"] = True
|
51
|
-
|
52
|
-
self.client = Langfuse(**kwargs)
|
53
|
-
if config.debug:
|
54
|
-
print(
|
55
|
-
f"✓ Langfuse client initialized with host: {config.langfuse_host}"
|
56
|
-
)
|
57
|
-
except Exception as e:
|
58
|
-
if config.debug:
|
59
|
-
print(f"✗ Failed to initialize Langfuse client: {e}")
|
60
|
-
self.client = None
|
61
|
-
else:
|
62
|
-
if config.debug:
|
63
|
-
print("✗ Langfuse not available or configuration invalid")
|
64
|
-
|
65
|
-
def start_trace(self, flow_name: str, input_data: Dict[str, Any]) -> Optional[str]:
|
66
|
-
"""
|
67
|
-
Start a new trace for a flow execution.
|
68
|
-
|
69
|
-
Args:
|
70
|
-
flow_name: Name of the flow being traced.
|
71
|
-
input_data: Input data for the flow.
|
72
|
-
|
73
|
-
Returns:
|
74
|
-
Trace ID if successful, None otherwise.
|
75
|
-
"""
|
76
|
-
if not self.client:
|
77
|
-
return None
|
78
|
-
|
79
|
-
try:
|
80
|
-
# Serialize input data safely
|
81
|
-
serialized_input = self._serialize_data(input_data)
|
82
|
-
|
83
|
-
# Use Langfuse v2 API to create a trace
|
84
|
-
self.current_trace = self.client.trace(
|
85
|
-
name=flow_name,
|
86
|
-
input=serialized_input,
|
87
|
-
metadata={
|
88
|
-
"framework": "PocketFlow",
|
89
|
-
"trace_type": "flow_execution",
|
90
|
-
"timestamp": datetime.now().isoformat(),
|
91
|
-
},
|
92
|
-
session_id=self.config.session_id,
|
93
|
-
user_id=self.config.user_id,
|
94
|
-
)
|
95
|
-
|
96
|
-
# Get the trace ID
|
97
|
-
trace_id = self.current_trace.id
|
98
|
-
|
99
|
-
if self.config.debug:
|
100
|
-
print(f"✓ Started trace: {trace_id} for flow: {flow_name}")
|
101
|
-
|
102
|
-
return trace_id
|
103
|
-
|
104
|
-
except Exception as e:
|
105
|
-
if self.config.debug:
|
106
|
-
print(f"✗ Failed to start trace: {e}")
|
107
|
-
return None
|
108
|
-
|
109
|
-
def end_trace(self, output_data: Dict[str, Any], status: str = "success") -> None:
|
110
|
-
"""
|
111
|
-
End the current trace.
|
112
|
-
|
113
|
-
Args:
|
114
|
-
output_data: Output data from the flow.
|
115
|
-
status: Status of the trace execution.
|
116
|
-
"""
|
117
|
-
if not self.current_trace:
|
118
|
-
return
|
119
|
-
|
120
|
-
try:
|
121
|
-
# Serialize output data safely
|
122
|
-
serialized_output = self._serialize_data(output_data)
|
123
|
-
|
124
|
-
# Update the trace with output data using v2 API
|
125
|
-
self.current_trace.update(
|
126
|
-
output=serialized_output,
|
127
|
-
metadata={
|
128
|
-
"status": status,
|
129
|
-
"end_timestamp": datetime.now().isoformat(),
|
130
|
-
},
|
131
|
-
)
|
132
|
-
|
133
|
-
if self.config.debug:
|
134
|
-
print(f"✓ Ended trace with status: {status}")
|
135
|
-
|
136
|
-
except Exception as e:
|
137
|
-
if self.config.debug:
|
138
|
-
print(f"✗ Failed to end trace: {e}")
|
139
|
-
finally:
|
140
|
-
self.current_trace = None
|
141
|
-
self.spans.clear()
|
142
|
-
|
143
|
-
def start_node_span(
|
144
|
-
self, node_name: str, node_id: str, phase: str
|
145
|
-
) -> Optional[str]:
|
146
|
-
"""
|
147
|
-
Start a span for a node execution phase.
|
148
|
-
|
149
|
-
Args:
|
150
|
-
node_name: Name/type of the node.
|
151
|
-
node_id: Unique identifier for the node instance.
|
152
|
-
phase: Execution phase (prep, exec, post).
|
153
|
-
|
154
|
-
Returns:
|
155
|
-
Span ID if successful, None otherwise.
|
156
|
-
"""
|
157
|
-
if not self.current_trace:
|
158
|
-
return None
|
159
|
-
|
160
|
-
try:
|
161
|
-
span_id = f"{node_id}_{phase}"
|
162
|
-
|
163
|
-
# Create a child span using v2 API
|
164
|
-
span = self.current_trace.span(
|
165
|
-
name=f"{node_name}.{phase}",
|
166
|
-
metadata={
|
167
|
-
"node_type": node_name,
|
168
|
-
"node_id": node_id,
|
169
|
-
"phase": phase,
|
170
|
-
"start_timestamp": datetime.now().isoformat(),
|
171
|
-
},
|
172
|
-
)
|
173
|
-
|
174
|
-
self.spans[span_id] = span
|
175
|
-
|
176
|
-
if self.config.debug:
|
177
|
-
print(f"✓ Started span: {span_id}")
|
178
|
-
|
179
|
-
return span_id
|
180
|
-
|
181
|
-
except Exception as e:
|
182
|
-
if self.config.debug:
|
183
|
-
print(f"✗ Failed to start span: {e}")
|
184
|
-
return None
|
185
|
-
|
186
|
-
def end_node_span(
|
187
|
-
self,
|
188
|
-
span_id: str,
|
189
|
-
input_data: Any = None,
|
190
|
-
output_data: Any = None,
|
191
|
-
error: Exception = None,
|
192
|
-
) -> None:
|
193
|
-
"""
|
194
|
-
End a node execution span.
|
195
|
-
|
196
|
-
Args:
|
197
|
-
span_id: ID of the span to end.
|
198
|
-
input_data: Input data for the phase.
|
199
|
-
output_data: Output data from the phase.
|
200
|
-
error: Exception if the phase failed.
|
201
|
-
"""
|
202
|
-
if span_id not in self.spans:
|
203
|
-
return
|
204
|
-
|
205
|
-
try:
|
206
|
-
span = self.spans[span_id]
|
207
|
-
|
208
|
-
# Prepare update data
|
209
|
-
update_data = {}
|
210
|
-
|
211
|
-
if input_data is not None and self.config.trace_inputs:
|
212
|
-
update_data["input"] = self._serialize_data(input_data)
|
213
|
-
if output_data is not None and self.config.trace_outputs:
|
214
|
-
update_data["output"] = self._serialize_data(output_data)
|
215
|
-
|
216
|
-
if error and self.config.trace_errors:
|
217
|
-
update_data.update(
|
218
|
-
{
|
219
|
-
"level": "ERROR",
|
220
|
-
"status_message": str(error),
|
221
|
-
"metadata": {
|
222
|
-
"error_type": type(error).__name__,
|
223
|
-
"error_message": str(error),
|
224
|
-
"end_timestamp": datetime.now().isoformat(),
|
225
|
-
},
|
226
|
-
}
|
227
|
-
)
|
228
|
-
else:
|
229
|
-
update_data.update(
|
230
|
-
{
|
231
|
-
"level": "DEFAULT",
|
232
|
-
"metadata": {"end_timestamp": datetime.now().isoformat()},
|
233
|
-
}
|
234
|
-
)
|
235
|
-
|
236
|
-
# Update the span with all data at once
|
237
|
-
span.update(**update_data)
|
238
|
-
|
239
|
-
# End the span
|
240
|
-
span.end()
|
241
|
-
|
242
|
-
if self.config.debug:
|
243
|
-
status = "ERROR" if error else "SUCCESS"
|
244
|
-
print(f"✓ Ended span: {span_id} with status: {status}")
|
245
|
-
|
246
|
-
except Exception as e:
|
247
|
-
if self.config.debug:
|
248
|
-
print(f"✗ Failed to end span: {e}")
|
249
|
-
finally:
|
250
|
-
if span_id in self.spans:
|
251
|
-
del self.spans[span_id]
|
252
|
-
|
253
|
-
def _serialize_data(self, data: Any) -> Any:
|
254
|
-
"""
|
255
|
-
Safely serialize data for Langfuse.
|
256
|
-
|
257
|
-
Args:
|
258
|
-
data: Data to serialize.
|
259
|
-
|
260
|
-
Returns:
|
261
|
-
Serialized data that can be sent to Langfuse.
|
262
|
-
"""
|
263
|
-
try:
|
264
|
-
# Handle common PocketFlow data types
|
265
|
-
if hasattr(data, "__dict__"):
|
266
|
-
# Convert objects to dict representation
|
267
|
-
return {"_type": type(data).__name__, "_data": str(data)}
|
268
|
-
elif isinstance(data, (dict, list, str, int, float, bool, type(None))):
|
269
|
-
# JSON-serializable types
|
270
|
-
return data
|
271
|
-
else:
|
272
|
-
# Fallback to string representation
|
273
|
-
return {"_type": type(data).__name__, "_data": str(data)}
|
274
|
-
except Exception:
|
275
|
-
# Ultimate fallback
|
276
|
-
return {"_type": "unknown", "_data": "<serialization_failed>"}
|
277
|
-
|
278
|
-
def flush(self) -> None:
|
279
|
-
"""Flush any pending traces to Langfuse."""
|
280
|
-
if self.client:
|
281
|
-
try:
|
282
|
-
self.client.flush()
|
283
|
-
if self.config.debug:
|
284
|
-
print("✓ Flushed traces to Langfuse")
|
285
|
-
except Exception as e:
|
286
|
-
if self.config.debug:
|
287
|
-
print(f"✗ Failed to flush traces: {e}")
|
@@ -1,316 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Decorator for tracing PocketFlow workflows with Langfuse.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import functools
|
6
|
-
import inspect
|
7
|
-
import uuid
|
8
|
-
from typing import Any, Callable, Dict, Optional, Union
|
9
|
-
|
10
|
-
from .config import TracingConfig
|
11
|
-
from .core import LangfuseTracer
|
12
|
-
|
13
|
-
|
14
|
-
def trace_flow(
|
15
|
-
config: Optional[TracingConfig] = None,
|
16
|
-
flow_name: Optional[str] = None,
|
17
|
-
session_id: Optional[str] = None,
|
18
|
-
user_id: Optional[str] = None,
|
19
|
-
):
|
20
|
-
"""
|
21
|
-
Decorator to add Langfuse tracing to PocketFlow flows.
|
22
|
-
|
23
|
-
This decorator automatically traces:
|
24
|
-
- Flow execution start/end
|
25
|
-
- Each node's prep, exec, and post phases
|
26
|
-
- Input and output data for each phase
|
27
|
-
- Errors and exceptions
|
28
|
-
|
29
|
-
Args:
|
30
|
-
config: TracingConfig instance. If None, loads from environment.
|
31
|
-
flow_name: Custom name for the flow. If None, uses the flow class name.
|
32
|
-
session_id: Session ID for grouping related traces.
|
33
|
-
user_id: User ID for the trace.
|
34
|
-
|
35
|
-
Returns:
|
36
|
-
Decorated flow class or function.
|
37
|
-
|
38
|
-
Example:
|
39
|
-
```python
|
40
|
-
from tracing import trace_flow
|
41
|
-
|
42
|
-
@trace_flow()
|
43
|
-
class MyFlow(Flow):
|
44
|
-
def __init__(self):
|
45
|
-
super().__init__(start=MyNode())
|
46
|
-
|
47
|
-
# Or with custom configuration
|
48
|
-
config = TracingConfig.from_env()
|
49
|
-
|
50
|
-
@trace_flow(config=config, flow_name="CustomFlow")
|
51
|
-
class MyFlow(Flow):
|
52
|
-
pass
|
53
|
-
```
|
54
|
-
"""
|
55
|
-
|
56
|
-
def decorator(flow_class_or_func):
|
57
|
-
# Handle both class and function decoration
|
58
|
-
if inspect.isclass(flow_class_or_func):
|
59
|
-
return _trace_flow_class(
|
60
|
-
flow_class_or_func, config, flow_name, session_id, user_id
|
61
|
-
)
|
62
|
-
else:
|
63
|
-
return _trace_flow_function(
|
64
|
-
flow_class_or_func, config, flow_name, session_id, user_id
|
65
|
-
)
|
66
|
-
|
67
|
-
return decorator
|
68
|
-
|
69
|
-
|
70
|
-
def _trace_flow_class(flow_class, config, flow_name, session_id, user_id):
|
71
|
-
"""Trace a Flow class by wrapping its methods."""
|
72
|
-
|
73
|
-
# Get or create config
|
74
|
-
if config is None:
|
75
|
-
config = TracingConfig.from_env()
|
76
|
-
|
77
|
-
# Override session/user if provided
|
78
|
-
if session_id:
|
79
|
-
config.session_id = session_id
|
80
|
-
if user_id:
|
81
|
-
config.user_id = user_id
|
82
|
-
|
83
|
-
# Get flow name
|
84
|
-
if flow_name is None:
|
85
|
-
flow_name = flow_class.__name__
|
86
|
-
|
87
|
-
# Store original methods
|
88
|
-
original_init = flow_class.__init__
|
89
|
-
original_run = getattr(flow_class, "run", None)
|
90
|
-
original_run_async = getattr(flow_class, "run_async", None)
|
91
|
-
|
92
|
-
def traced_init(self, *args, **kwargs):
|
93
|
-
"""Initialize the flow with tracing capabilities."""
|
94
|
-
# Call original init
|
95
|
-
original_init(self, *args, **kwargs)
|
96
|
-
|
97
|
-
# Add tracing attributes
|
98
|
-
self._tracer = LangfuseTracer(config)
|
99
|
-
self._flow_name = flow_name
|
100
|
-
self._trace_id = None
|
101
|
-
|
102
|
-
# Patch all nodes in the flow
|
103
|
-
self._patch_nodes()
|
104
|
-
|
105
|
-
def traced_run(self, shared):
|
106
|
-
"""Traced version of the run method."""
|
107
|
-
if not hasattr(self, "_tracer"):
|
108
|
-
# Fallback if not properly initialized
|
109
|
-
return original_run(self, shared) if original_run else None
|
110
|
-
|
111
|
-
# Start trace
|
112
|
-
self._trace_id = self._tracer.start_trace(self._flow_name, shared)
|
113
|
-
|
114
|
-
try:
|
115
|
-
# Run the original flow
|
116
|
-
result = original_run(self, shared) if original_run else None
|
117
|
-
|
118
|
-
# End trace successfully
|
119
|
-
self._tracer.end_trace(shared, "success")
|
120
|
-
|
121
|
-
return result
|
122
|
-
|
123
|
-
except Exception as e:
|
124
|
-
# End trace with error
|
125
|
-
self._tracer.end_trace(shared, "error")
|
126
|
-
raise
|
127
|
-
finally:
|
128
|
-
# Ensure cleanup
|
129
|
-
self._tracer.flush()
|
130
|
-
|
131
|
-
async def traced_run_async(self, shared):
|
132
|
-
"""Traced version of the async run method."""
|
133
|
-
if not hasattr(self, "_tracer"):
|
134
|
-
# Fallback if not properly initialized
|
135
|
-
return (
|
136
|
-
await original_run_async(self, shared) if original_run_async else None
|
137
|
-
)
|
138
|
-
|
139
|
-
# Start trace
|
140
|
-
self._trace_id = self._tracer.start_trace(self._flow_name, shared)
|
141
|
-
|
142
|
-
try:
|
143
|
-
# Run the original flow
|
144
|
-
result = (
|
145
|
-
await original_run_async(self, shared) if original_run_async else None
|
146
|
-
)
|
147
|
-
|
148
|
-
# End trace successfully
|
149
|
-
self._tracer.end_trace(shared, "success")
|
150
|
-
|
151
|
-
return result
|
152
|
-
|
153
|
-
except Exception as e:
|
154
|
-
# End trace with error
|
155
|
-
self._tracer.end_trace(shared, "error")
|
156
|
-
raise
|
157
|
-
finally:
|
158
|
-
# Ensure cleanup
|
159
|
-
self._tracer.flush()
|
160
|
-
|
161
|
-
def patch_nodes(self):
|
162
|
-
"""Patch all nodes in the flow to add tracing."""
|
163
|
-
if not hasattr(self, "start_node") or not self.start_node:
|
164
|
-
return
|
165
|
-
|
166
|
-
visited = set()
|
167
|
-
nodes_to_patch = [self.start_node]
|
168
|
-
|
169
|
-
while nodes_to_patch:
|
170
|
-
node = nodes_to_patch.pop(0)
|
171
|
-
if id(node) in visited:
|
172
|
-
continue
|
173
|
-
|
174
|
-
visited.add(id(node))
|
175
|
-
|
176
|
-
# Patch this node
|
177
|
-
self._patch_node(node)
|
178
|
-
|
179
|
-
# Add successors to patch list
|
180
|
-
if hasattr(node, "successors"):
|
181
|
-
for successor in node.successors.values():
|
182
|
-
if successor and id(successor) not in visited:
|
183
|
-
nodes_to_patch.append(successor)
|
184
|
-
|
185
|
-
def patch_node(self, node):
|
186
|
-
"""Patch a single node to add tracing."""
|
187
|
-
if hasattr(node, "_pocketflow_traced"):
|
188
|
-
return # Already patched
|
189
|
-
|
190
|
-
node_id = str(uuid.uuid4())
|
191
|
-
node_name = type(node).__name__
|
192
|
-
|
193
|
-
# Store original methods
|
194
|
-
original_prep = getattr(node, "prep", None)
|
195
|
-
original_exec = getattr(node, "exec", None)
|
196
|
-
original_post = getattr(node, "post", None)
|
197
|
-
original_prep_async = getattr(node, "prep_async", None)
|
198
|
-
original_exec_async = getattr(node, "exec_async", None)
|
199
|
-
original_post_async = getattr(node, "post_async", None)
|
200
|
-
|
201
|
-
# Create traced versions
|
202
|
-
if original_prep:
|
203
|
-
node.prep = self._create_traced_method(
|
204
|
-
original_prep, node_id, node_name, "prep"
|
205
|
-
)
|
206
|
-
if original_exec:
|
207
|
-
node.exec = self._create_traced_method(
|
208
|
-
original_exec, node_id, node_name, "exec"
|
209
|
-
)
|
210
|
-
if original_post:
|
211
|
-
node.post = self._create_traced_method(
|
212
|
-
original_post, node_id, node_name, "post"
|
213
|
-
)
|
214
|
-
if original_prep_async:
|
215
|
-
node.prep_async = self._create_traced_async_method(
|
216
|
-
original_prep_async, node_id, node_name, "prep"
|
217
|
-
)
|
218
|
-
if original_exec_async:
|
219
|
-
node.exec_async = self._create_traced_async_method(
|
220
|
-
original_exec_async, node_id, node_name, "exec"
|
221
|
-
)
|
222
|
-
if original_post_async:
|
223
|
-
node.post_async = self._create_traced_async_method(
|
224
|
-
original_post_async, node_id, node_name, "post"
|
225
|
-
)
|
226
|
-
|
227
|
-
# Mark as traced
|
228
|
-
node._pocketflow_traced = True
|
229
|
-
|
230
|
-
def create_traced_method(self, original_method, node_id, node_name, phase):
|
231
|
-
"""Create a traced version of a synchronous method."""
|
232
|
-
|
233
|
-
@functools.wraps(original_method)
|
234
|
-
def traced_method(*args, **kwargs):
|
235
|
-
span_id = self._tracer.start_node_span(node_name, node_id, phase)
|
236
|
-
|
237
|
-
try:
|
238
|
-
result = original_method(*args, **kwargs)
|
239
|
-
self._tracer.end_node_span(span_id, input_data=args, output_data=result)
|
240
|
-
return result
|
241
|
-
except Exception as e:
|
242
|
-
self._tracer.end_node_span(span_id, input_data=args, error=e)
|
243
|
-
raise
|
244
|
-
|
245
|
-
return traced_method
|
246
|
-
|
247
|
-
def create_traced_async_method(self, original_method, node_id, node_name, phase):
|
248
|
-
"""Create a traced version of an asynchronous method."""
|
249
|
-
|
250
|
-
@functools.wraps(original_method)
|
251
|
-
async def traced_async_method(*args, **kwargs):
|
252
|
-
span_id = self._tracer.start_node_span(node_name, node_id, phase)
|
253
|
-
|
254
|
-
try:
|
255
|
-
result = await original_method(*args, **kwargs)
|
256
|
-
self._tracer.end_node_span(span_id, input_data=args, output_data=result)
|
257
|
-
return result
|
258
|
-
except Exception as e:
|
259
|
-
self._tracer.end_node_span(span_id, input_data=args, error=e)
|
260
|
-
raise
|
261
|
-
|
262
|
-
return traced_async_method
|
263
|
-
|
264
|
-
# Replace methods on the class
|
265
|
-
flow_class.__init__ = traced_init
|
266
|
-
flow_class._patch_nodes = patch_nodes
|
267
|
-
flow_class._patch_node = patch_node
|
268
|
-
flow_class._create_traced_method = create_traced_method
|
269
|
-
flow_class._create_traced_async_method = create_traced_async_method
|
270
|
-
|
271
|
-
if original_run:
|
272
|
-
flow_class.run = traced_run
|
273
|
-
if original_run_async:
|
274
|
-
flow_class.run_async = traced_run_async
|
275
|
-
|
276
|
-
return flow_class
|
277
|
-
|
278
|
-
|
279
|
-
def _trace_flow_function(flow_func, config, flow_name, session_id, user_id):
|
280
|
-
"""Trace a flow function (for functional-style flows)."""
|
281
|
-
|
282
|
-
# Get or create config
|
283
|
-
if config is None:
|
284
|
-
config = TracingConfig.from_env()
|
285
|
-
|
286
|
-
# Override session/user if provided
|
287
|
-
if session_id:
|
288
|
-
config.session_id = session_id
|
289
|
-
if user_id:
|
290
|
-
config.user_id = user_id
|
291
|
-
|
292
|
-
# Get flow name
|
293
|
-
if flow_name is None:
|
294
|
-
flow_name = flow_func.__name__
|
295
|
-
|
296
|
-
tracer = LangfuseTracer(config)
|
297
|
-
|
298
|
-
@functools.wraps(flow_func)
|
299
|
-
def traced_flow_func(*args, **kwargs):
|
300
|
-
# Assume first argument is shared data
|
301
|
-
shared = args[0] if args else {}
|
302
|
-
|
303
|
-
# Start trace
|
304
|
-
trace_id = tracer.start_trace(flow_name, shared)
|
305
|
-
|
306
|
-
try:
|
307
|
-
result = flow_func(*args, **kwargs)
|
308
|
-
tracer.end_trace(shared, "success")
|
309
|
-
return result
|
310
|
-
except Exception as e:
|
311
|
-
tracer.end_trace(shared, "error")
|
312
|
-
raise
|
313
|
-
finally:
|
314
|
-
tracer.flush()
|
315
|
-
|
316
|
-
return traced_flow_func
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks/visualization/visualize.py
RENAMED
File without changes
|
{agentic_blocks-0.1.22 → agentic_blocks-0.1.23}/src/agentic_blocks.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|