agentic-blocks 0.1.18__tar.gz → 0.1.20__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.18/src/agentic_blocks.egg-info → agentic_blocks-0.1.20}/PKG-INFO +3 -1
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/pyproject.toml +3 -1
- agentic_blocks-0.1.20/src/agentic_blocks/tracing/__init__.py +13 -0
- agentic_blocks-0.1.20/src/agentic_blocks/tracing/config.py +111 -0
- agentic_blocks-0.1.20/src/agentic_blocks/tracing/core.py +287 -0
- agentic_blocks-0.1.20/src/agentic_blocks/tracing/decorator.py +316 -0
- agentic_blocks-0.1.20/src/agentic_blocks/visualization/visualize.py +1014 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20/src/agentic_blocks.egg-info}/PKG-INFO +3 -1
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks.egg-info/SOURCES.txt +6 -1
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks.egg-info/requires.txt +2 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/LICENSE +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/README.md +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/setup.cfg +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks/__init__.py +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks/agent.py +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks/llm.py +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks/mcp_client.py +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks/messages.py +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks/utils/tools_utils.py +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks.egg-info/dependency_links.txt +0 -0
- {agentic_blocks-0.1.18 → agentic_blocks-0.1.20}/src/agentic_blocks.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: agentic-blocks
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.20
|
4
4
|
Summary: Simple building blocks for agentic AI systems with MCP client and conversation management
|
5
5
|
Author-email: Magnus Bjelkenhed <bjelkenhed@gmail.com>
|
6
6
|
License: MIT
|
@@ -24,6 +24,8 @@ Requires-Dist: requests
|
|
24
24
|
Requires-Dist: python-dotenv
|
25
25
|
Requires-Dist: openai
|
26
26
|
Requires-Dist: langchain-core
|
27
|
+
Requires-Dist: langfuse
|
28
|
+
Requires-Dist: pocketflow
|
27
29
|
Provides-Extra: test
|
28
30
|
Requires-Dist: pytest; extra == "test"
|
29
31
|
Provides-Extra: dev
|
@@ -14,7 +14,7 @@ agentic_blocks = []
|
|
14
14
|
|
15
15
|
[project]
|
16
16
|
name = "agentic-blocks"
|
17
|
-
version = "0.1.
|
17
|
+
version = "0.1.20"
|
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"
|
@@ -40,6 +40,8 @@ dependencies = [
|
|
40
40
|
"python-dotenv",
|
41
41
|
"openai",
|
42
42
|
"langchain-core",
|
43
|
+
"langfuse",
|
44
|
+
"pocketflow",
|
43
45
|
]
|
44
46
|
|
45
47
|
[project.urls]
|
@@ -0,0 +1,13 @@
|
|
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"]
|
@@ -0,0 +1,111 @@
|
|
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
|
@@ -0,0 +1,287 @@
|
|
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}")
|