prela 0.1.0__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.
- prela/__init__.py +394 -0
- prela/_version.py +3 -0
- prela/contrib/CLI.md +431 -0
- prela/contrib/README.md +118 -0
- prela/contrib/__init__.py +5 -0
- prela/contrib/cli.py +1063 -0
- prela/contrib/explorer.py +571 -0
- prela/core/__init__.py +64 -0
- prela/core/clock.py +98 -0
- prela/core/context.py +228 -0
- prela/core/replay.py +403 -0
- prela/core/sampler.py +178 -0
- prela/core/span.py +295 -0
- prela/core/tracer.py +498 -0
- prela/evals/__init__.py +94 -0
- prela/evals/assertions/README.md +484 -0
- prela/evals/assertions/__init__.py +78 -0
- prela/evals/assertions/base.py +90 -0
- prela/evals/assertions/multi_agent.py +625 -0
- prela/evals/assertions/semantic.py +223 -0
- prela/evals/assertions/structural.py +443 -0
- prela/evals/assertions/tool.py +380 -0
- prela/evals/case.py +370 -0
- prela/evals/n8n/__init__.py +69 -0
- prela/evals/n8n/assertions.py +450 -0
- prela/evals/n8n/runner.py +497 -0
- prela/evals/reporters/README.md +184 -0
- prela/evals/reporters/__init__.py +32 -0
- prela/evals/reporters/console.py +251 -0
- prela/evals/reporters/json.py +176 -0
- prela/evals/reporters/junit.py +278 -0
- prela/evals/runner.py +525 -0
- prela/evals/suite.py +316 -0
- prela/exporters/__init__.py +27 -0
- prela/exporters/base.py +189 -0
- prela/exporters/console.py +443 -0
- prela/exporters/file.py +322 -0
- prela/exporters/http.py +394 -0
- prela/exporters/multi.py +154 -0
- prela/exporters/otlp.py +388 -0
- prela/instrumentation/ANTHROPIC.md +297 -0
- prela/instrumentation/LANGCHAIN.md +480 -0
- prela/instrumentation/OPENAI.md +59 -0
- prela/instrumentation/__init__.py +49 -0
- prela/instrumentation/anthropic.py +1436 -0
- prela/instrumentation/auto.py +129 -0
- prela/instrumentation/base.py +436 -0
- prela/instrumentation/langchain.py +959 -0
- prela/instrumentation/llamaindex.py +719 -0
- prela/instrumentation/multi_agent/__init__.py +48 -0
- prela/instrumentation/multi_agent/autogen.py +357 -0
- prela/instrumentation/multi_agent/crewai.py +404 -0
- prela/instrumentation/multi_agent/langgraph.py +299 -0
- prela/instrumentation/multi_agent/models.py +203 -0
- prela/instrumentation/multi_agent/swarm.py +231 -0
- prela/instrumentation/n8n/__init__.py +68 -0
- prela/instrumentation/n8n/code_node.py +534 -0
- prela/instrumentation/n8n/models.py +336 -0
- prela/instrumentation/n8n/webhook.py +489 -0
- prela/instrumentation/openai.py +1198 -0
- prela/license.py +245 -0
- prela/replay/__init__.py +31 -0
- prela/replay/comparison.py +390 -0
- prela/replay/engine.py +1227 -0
- prela/replay/loader.py +231 -0
- prela/replay/result.py +196 -0
- prela-0.1.0.dist-info/METADATA +399 -0
- prela-0.1.0.dist-info/RECORD +71 -0
- prela-0.1.0.dist-info/WHEEL +4 -0
- prela-0.1.0.dist-info/entry_points.txt +2 -0
- prela-0.1.0.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Shared data models for multi-agent instrumentation.
|
|
2
|
+
|
|
3
|
+
This module provides common data structures for tracing multi-agent systems
|
|
4
|
+
across different frameworks (CrewAI, AutoGen, LangGraph, Swarm).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Literal, Optional
|
|
13
|
+
import hashlib
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AgentRole(str, Enum):
|
|
17
|
+
"""Standard agent roles across frameworks."""
|
|
18
|
+
|
|
19
|
+
MANAGER = "manager"
|
|
20
|
+
WORKER = "worker"
|
|
21
|
+
SPECIALIST = "specialist"
|
|
22
|
+
CRITIC = "critic"
|
|
23
|
+
USER_PROXY = "user_proxy"
|
|
24
|
+
ASSISTANT = "assistant"
|
|
25
|
+
CUSTOM = "custom"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MessageType(str, Enum):
|
|
29
|
+
"""Types of inter-agent messages."""
|
|
30
|
+
|
|
31
|
+
TASK_ASSIGNMENT = "task_assignment"
|
|
32
|
+
TASK_RESULT = "task_result"
|
|
33
|
+
QUESTION = "question"
|
|
34
|
+
ANSWER = "answer"
|
|
35
|
+
FEEDBACK = "feedback"
|
|
36
|
+
DELEGATION = "delegation"
|
|
37
|
+
HANDOFF = "handoff"
|
|
38
|
+
SYSTEM = "system"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class AgentDefinition:
|
|
43
|
+
"""Represents an agent in a multi-agent system."""
|
|
44
|
+
|
|
45
|
+
agent_id: str
|
|
46
|
+
name: str
|
|
47
|
+
role: AgentRole
|
|
48
|
+
framework: str # "crewai", "autogen", "langgraph", "swarm"
|
|
49
|
+
|
|
50
|
+
# Agent configuration
|
|
51
|
+
model: Optional[str] = None
|
|
52
|
+
system_prompt: Optional[str] = None
|
|
53
|
+
tools: list[str] = field(default_factory=list)
|
|
54
|
+
|
|
55
|
+
# Framework-specific metadata
|
|
56
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
def to_span_attributes(self) -> dict[str, Any]:
|
|
59
|
+
"""Convert to span attributes for tracing."""
|
|
60
|
+
return {
|
|
61
|
+
"agent.id": self.agent_id,
|
|
62
|
+
"agent.name": self.name,
|
|
63
|
+
"agent.role": self.role.value,
|
|
64
|
+
"agent.framework": self.framework,
|
|
65
|
+
"agent.model": self.model,
|
|
66
|
+
"agent.tools": self.tools,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class AgentMessage:
|
|
72
|
+
"""A message between agents."""
|
|
73
|
+
|
|
74
|
+
message_id: str
|
|
75
|
+
sender_id: str
|
|
76
|
+
receiver_id: Optional[str] # None for broadcast
|
|
77
|
+
message_type: MessageType
|
|
78
|
+
content: str
|
|
79
|
+
timestamp: datetime
|
|
80
|
+
|
|
81
|
+
# Optional structured data
|
|
82
|
+
tool_calls: Optional[list[dict]] = None
|
|
83
|
+
tool_results: Optional[list[dict]] = None
|
|
84
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
85
|
+
|
|
86
|
+
def to_span_event(self) -> dict[str, Any]:
|
|
87
|
+
"""Convert to span event for tracing."""
|
|
88
|
+
return {
|
|
89
|
+
"name": f"agent.message.{self.message_type.value}",
|
|
90
|
+
"attributes": {
|
|
91
|
+
"message.id": self.message_id,
|
|
92
|
+
"message.sender": self.sender_id,
|
|
93
|
+
"message.receiver": self.receiver_id,
|
|
94
|
+
"message.type": self.message_type.value,
|
|
95
|
+
"message.content_length": len(self.content),
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class TaskAssignment:
|
|
102
|
+
"""A task assigned from one agent to another."""
|
|
103
|
+
|
|
104
|
+
task_id: str
|
|
105
|
+
assigner_id: str
|
|
106
|
+
assignee_id: str
|
|
107
|
+
description: str
|
|
108
|
+
expected_output: Optional[str] = None
|
|
109
|
+
|
|
110
|
+
# Execution tracking
|
|
111
|
+
status: Literal["pending", "in_progress", "completed", "failed"] = "pending"
|
|
112
|
+
started_at: Optional[datetime] = None
|
|
113
|
+
completed_at: Optional[datetime] = None
|
|
114
|
+
result: Optional[str] = None
|
|
115
|
+
error: Optional[str] = None
|
|
116
|
+
|
|
117
|
+
# Token/cost tracking
|
|
118
|
+
total_tokens: int = 0
|
|
119
|
+
total_cost_usd: float = 0.0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class CrewExecution:
|
|
124
|
+
"""Top-level execution of a multi-agent crew/team."""
|
|
125
|
+
|
|
126
|
+
execution_id: str
|
|
127
|
+
framework: str
|
|
128
|
+
|
|
129
|
+
# Crew configuration
|
|
130
|
+
agents: list[AgentDefinition] = field(default_factory=list)
|
|
131
|
+
tasks: list[TaskAssignment] = field(default_factory=list)
|
|
132
|
+
|
|
133
|
+
# Execution tracking
|
|
134
|
+
started_at: Optional[datetime] = None
|
|
135
|
+
completed_at: Optional[datetime] = None
|
|
136
|
+
status: Literal["running", "completed", "failed"] = "running"
|
|
137
|
+
|
|
138
|
+
# Aggregated metrics
|
|
139
|
+
total_llm_calls: int = 0
|
|
140
|
+
total_tokens: int = 0
|
|
141
|
+
total_cost_usd: float = 0.0
|
|
142
|
+
total_tool_calls: int = 0
|
|
143
|
+
total_agent_messages: int = 0
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class ConversationTurn:
|
|
148
|
+
"""A single turn in a multi-agent conversation (for AutoGen style)."""
|
|
149
|
+
|
|
150
|
+
turn_id: str
|
|
151
|
+
turn_number: int
|
|
152
|
+
speaker_id: str
|
|
153
|
+
content: str
|
|
154
|
+
timestamp: datetime
|
|
155
|
+
|
|
156
|
+
# If this turn triggered an LLM call
|
|
157
|
+
llm_call_id: Optional[str] = None
|
|
158
|
+
tokens_used: int = 0
|
|
159
|
+
|
|
160
|
+
# If this turn triggered tool use
|
|
161
|
+
tool_calls: list[dict] = field(default_factory=list)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# Helper functions
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def generate_agent_id(framework: str, name: str) -> str:
|
|
168
|
+
"""Generate a consistent agent ID.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
framework: The multi-agent framework (e.g., "crewai", "autogen")
|
|
172
|
+
name: The agent name
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
A deterministic 12-character hash based on framework and name
|
|
176
|
+
"""
|
|
177
|
+
return hashlib.sha256(f"{framework}:{name}".encode()).hexdigest()[:12]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def extract_agent_graph(
|
|
181
|
+
agents: list[AgentDefinition], messages: list[AgentMessage]
|
|
182
|
+
) -> dict:
|
|
183
|
+
"""Extract agent communication graph for visualization.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
agents: List of agent definitions
|
|
187
|
+
messages: List of messages exchanged between agents
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
A dictionary with "nodes" and "edges" for graph visualization
|
|
191
|
+
"""
|
|
192
|
+
nodes = {a.agent_id: {"name": a.name, "role": a.role.value} for a in agents}
|
|
193
|
+
edges = []
|
|
194
|
+
for msg in messages:
|
|
195
|
+
if msg.receiver_id:
|
|
196
|
+
edges.append(
|
|
197
|
+
{
|
|
198
|
+
"from": msg.sender_id,
|
|
199
|
+
"to": msg.receiver_id,
|
|
200
|
+
"type": msg.message_type.value,
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
return {"nodes": nodes, "edges": edges}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Swarm instrumentation for Prela.
|
|
2
|
+
|
|
3
|
+
This module provides automatic instrumentation for OpenAI Swarm multi-agent framework,
|
|
4
|
+
capturing agent orchestration, handoffs, and execution flow.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import functools
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from prela.core.span import SpanType
|
|
14
|
+
from prela.core.tracer import Tracer, get_tracer
|
|
15
|
+
from prela.instrumentation.base import Instrumentor
|
|
16
|
+
from prela.instrumentation.multi_agent.models import generate_agent_id
|
|
17
|
+
from prela.license import require_tier
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SwarmInstrumentor(Instrumentor):
|
|
21
|
+
"""Instrumentor for OpenAI Swarm multi-agent framework."""
|
|
22
|
+
|
|
23
|
+
FRAMEWORK = "swarm"
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def is_instrumented(self) -> bool:
|
|
27
|
+
"""Check if Swarm is currently instrumented."""
|
|
28
|
+
return self._is_instrumented
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self._active_swarms: dict[str, dict] = {}
|
|
33
|
+
self._is_instrumented = False
|
|
34
|
+
self._tracer: Optional[Tracer] = None
|
|
35
|
+
|
|
36
|
+
@require_tier("Swarm instrumentation", "lunch-money")
|
|
37
|
+
def instrument(self, tracer: Optional[Tracer] = None) -> None:
|
|
38
|
+
"""Patch Swarm classes for tracing.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
tracer: Optional tracer to use. If None, uses global tracer.
|
|
42
|
+
"""
|
|
43
|
+
if self.is_instrumented:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from swarm import Agent, Swarm
|
|
48
|
+
except ImportError:
|
|
49
|
+
return # Swarm not installed
|
|
50
|
+
|
|
51
|
+
self._tracer = tracer or get_tracer()
|
|
52
|
+
|
|
53
|
+
# Patch Swarm.run (main execution entry point)
|
|
54
|
+
if hasattr(Swarm, "run"):
|
|
55
|
+
self._patch_swarm_run(Swarm)
|
|
56
|
+
|
|
57
|
+
# Patch Agent.__init__ to track agents
|
|
58
|
+
if hasattr(Agent, "__init__"):
|
|
59
|
+
self._patch_agent_init(Agent)
|
|
60
|
+
|
|
61
|
+
self._is_instrumented = True
|
|
62
|
+
|
|
63
|
+
def uninstrument(self) -> None:
|
|
64
|
+
"""Restore original methods."""
|
|
65
|
+
if not self.is_instrumented:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
from swarm import Agent, Swarm
|
|
70
|
+
except ImportError:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
# Restore all patched methods
|
|
74
|
+
for cls, method_name in [
|
|
75
|
+
(Swarm, "run"),
|
|
76
|
+
(Agent, "__init__"),
|
|
77
|
+
]:
|
|
78
|
+
if hasattr(cls, f"_prela_original_{method_name}"):
|
|
79
|
+
original = getattr(cls, f"_prela_original_{method_name}")
|
|
80
|
+
setattr(cls, method_name, original)
|
|
81
|
+
delattr(cls, f"_prela_original_{method_name}")
|
|
82
|
+
|
|
83
|
+
self._is_instrumented = False
|
|
84
|
+
self._tracer = None
|
|
85
|
+
|
|
86
|
+
def _patch_swarm_run(self, swarm_cls) -> None:
|
|
87
|
+
"""Patch Swarm.run to create execution spans."""
|
|
88
|
+
if hasattr(swarm_cls, "_prela_original_run"):
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
original_run = swarm_cls.run
|
|
92
|
+
swarm_cls._prela_original_run = original_run
|
|
93
|
+
|
|
94
|
+
instrumentor = self
|
|
95
|
+
|
|
96
|
+
@functools.wraps(original_run)
|
|
97
|
+
def wrapped_run(swarm_self, agent, messages, *args, **kwargs):
|
|
98
|
+
tracer = instrumentor._tracer
|
|
99
|
+
if not tracer:
|
|
100
|
+
return original_run(swarm_self, agent, messages, *args, **kwargs)
|
|
101
|
+
|
|
102
|
+
execution_id = str(uuid.uuid4())
|
|
103
|
+
instrumentor._active_swarms[execution_id] = {
|
|
104
|
+
"agents_seen": set(),
|
|
105
|
+
"handoffs": [],
|
|
106
|
+
"total_tokens": 0,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Get initial agent name
|
|
110
|
+
initial_agent_name = agent.name if hasattr(agent, "name") else "unnamed"
|
|
111
|
+
|
|
112
|
+
# Extract context variables
|
|
113
|
+
context_vars = kwargs.get("context_variables", {})
|
|
114
|
+
context_keys = list(context_vars.keys()) if context_vars else []
|
|
115
|
+
|
|
116
|
+
swarm_attributes = {
|
|
117
|
+
"swarm.execution_id": execution_id,
|
|
118
|
+
"swarm.framework": instrumentor.FRAMEWORK,
|
|
119
|
+
"swarm.initial_agent": initial_agent_name,
|
|
120
|
+
"swarm.num_messages": len(messages) if messages else 0,
|
|
121
|
+
"swarm.context_variables": context_keys,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# NEW: Replay capture if enabled
|
|
125
|
+
replay_capture = None
|
|
126
|
+
if tracer.capture_for_replay:
|
|
127
|
+
from prela.core.replay import ReplayCapture
|
|
128
|
+
|
|
129
|
+
replay_capture = ReplayCapture()
|
|
130
|
+
# Capture agent context
|
|
131
|
+
replay_capture.set_agent_context(
|
|
132
|
+
system_prompt=getattr(agent, "instructions", None),
|
|
133
|
+
available_tools=[
|
|
134
|
+
{"name": func.__name__, "description": func.__doc__ or ""}
|
|
135
|
+
for func in getattr(agent, "functions", [])
|
|
136
|
+
],
|
|
137
|
+
config={
|
|
138
|
+
"framework": instrumentor.FRAMEWORK,
|
|
139
|
+
"execution_id": execution_id,
|
|
140
|
+
"agent_name": initial_agent_name,
|
|
141
|
+
},
|
|
142
|
+
memory={"context_variables": context_keys},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
with tracer.span(
|
|
146
|
+
name=f"swarm.run.{initial_agent_name}",
|
|
147
|
+
span_type=SpanType.AGENT,
|
|
148
|
+
attributes=swarm_attributes,
|
|
149
|
+
) as span:
|
|
150
|
+
try:
|
|
151
|
+
result = original_run(swarm_self, agent, messages, *args, **kwargs)
|
|
152
|
+
|
|
153
|
+
# Add execution statistics
|
|
154
|
+
swarm_state = instrumentor._active_swarms.get(execution_id, {})
|
|
155
|
+
agents_used = list(swarm_state.get("agents_seen", set()))
|
|
156
|
+
if agents_used:
|
|
157
|
+
span.set_attribute("swarm.agents_used", agents_used)
|
|
158
|
+
|
|
159
|
+
handoffs = swarm_state.get("handoffs", [])
|
|
160
|
+
span.set_attribute("swarm.num_handoffs", len(handoffs))
|
|
161
|
+
|
|
162
|
+
# Capture final agent
|
|
163
|
+
if hasattr(result, "agent"):
|
|
164
|
+
final_agent = (
|
|
165
|
+
result.agent.name
|
|
166
|
+
if hasattr(result.agent, "name")
|
|
167
|
+
else "unnamed"
|
|
168
|
+
)
|
|
169
|
+
span.set_attribute("swarm.final_agent", final_agent)
|
|
170
|
+
|
|
171
|
+
# Capture response messages
|
|
172
|
+
if hasattr(result, "messages"):
|
|
173
|
+
span.set_attribute(
|
|
174
|
+
"swarm.response_messages", len(result.messages)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Capture context updates
|
|
178
|
+
if hasattr(result, "context_variables"):
|
|
179
|
+
updated_keys = list(result.context_variables.keys())
|
|
180
|
+
span.set_attribute("swarm.updated_context", updated_keys)
|
|
181
|
+
|
|
182
|
+
# NEW: Attach replay snapshot
|
|
183
|
+
if replay_capture:
|
|
184
|
+
try:
|
|
185
|
+
object.__setattr__(span, "replay_snapshot", replay_capture.build())
|
|
186
|
+
except Exception as e:
|
|
187
|
+
import logging
|
|
188
|
+
logger = logging.getLogger(__name__)
|
|
189
|
+
logger.debug(f"Failed to capture replay data: {e}")
|
|
190
|
+
|
|
191
|
+
return result
|
|
192
|
+
except Exception as e:
|
|
193
|
+
span.add_event(
|
|
194
|
+
"exception",
|
|
195
|
+
attributes={
|
|
196
|
+
"exception.type": type(e).__name__,
|
|
197
|
+
"exception.message": str(e),
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
raise
|
|
201
|
+
finally:
|
|
202
|
+
if execution_id in instrumentor._active_swarms:
|
|
203
|
+
del instrumentor._active_swarms[execution_id]
|
|
204
|
+
|
|
205
|
+
swarm_cls.run = wrapped_run
|
|
206
|
+
|
|
207
|
+
def _patch_agent_init(self, agent_cls) -> None:
|
|
208
|
+
"""Patch Agent.__init__ to track agent creation."""
|
|
209
|
+
if hasattr(agent_cls, "_prela_original___init__"):
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
original_init = agent_cls.__init__
|
|
213
|
+
agent_cls._prela_original___init__ = original_init
|
|
214
|
+
|
|
215
|
+
instrumentor = self
|
|
216
|
+
|
|
217
|
+
@functools.wraps(original_init)
|
|
218
|
+
def wrapped_init(agent_self, *args, **kwargs):
|
|
219
|
+
result = original_init(agent_self, *args, **kwargs)
|
|
220
|
+
|
|
221
|
+
# Generate agent ID for tracking
|
|
222
|
+
agent_name = (
|
|
223
|
+
agent_self.name if hasattr(agent_self, "name") else str(id(agent_self))
|
|
224
|
+
)
|
|
225
|
+
agent_self._prela_agent_id = generate_agent_id(
|
|
226
|
+
instrumentor.FRAMEWORK, agent_name
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
agent_cls.__init__ = wrapped_init
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
n8n workflow automation platform instrumentation.
|
|
3
|
+
|
|
4
|
+
This module provides automatic tracing for n8n workflows, capturing:
|
|
5
|
+
- Workflow executions with status and timing
|
|
6
|
+
- Individual node executions (AI and non-AI)
|
|
7
|
+
- LLM calls with token usage and costs
|
|
8
|
+
- Tool invocations and retrievals
|
|
9
|
+
- Error tracking and retries
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
import prela
|
|
13
|
+
from prela.instrumentation.n8n import N8nInstrumentor
|
|
14
|
+
|
|
15
|
+
# Initialize Prela
|
|
16
|
+
tracer = prela.init(service_name="n8n-workflows")
|
|
17
|
+
|
|
18
|
+
# Instrument n8n
|
|
19
|
+
N8nInstrumentor().instrument(tracer)
|
|
20
|
+
|
|
21
|
+
# All n8n workflow executions are now automatically traced
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
# Check tier on module import
|
|
27
|
+
from prela.license import check_tier
|
|
28
|
+
|
|
29
|
+
if not check_tier("n8n instrumentation", "lunch-money", silent=False):
|
|
30
|
+
raise ImportError(
|
|
31
|
+
"n8n instrumentation requires 'lunch-money' subscription or higher. "
|
|
32
|
+
"Upgrade at https://prela.dev/pricing"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from prela.instrumentation.n8n.code_node import (
|
|
36
|
+
PrelaN8nContext,
|
|
37
|
+
prela_n8n_traced,
|
|
38
|
+
trace_n8n_code,
|
|
39
|
+
)
|
|
40
|
+
from prela.instrumentation.n8n.models import (
|
|
41
|
+
N8nAINodeExecution,
|
|
42
|
+
N8nNodeExecution,
|
|
43
|
+
N8nSpanType,
|
|
44
|
+
N8nWorkflowExecution,
|
|
45
|
+
)
|
|
46
|
+
from prela.instrumentation.n8n.webhook import (
|
|
47
|
+
N8N_AI_NODE_TYPES,
|
|
48
|
+
N8nWebhookHandler,
|
|
49
|
+
N8nWebhookPayload,
|
|
50
|
+
parse_n8n_webhook,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
# Models
|
|
55
|
+
"N8nWorkflowExecution",
|
|
56
|
+
"N8nNodeExecution",
|
|
57
|
+
"N8nAINodeExecution",
|
|
58
|
+
"N8nSpanType",
|
|
59
|
+
# Webhook
|
|
60
|
+
"N8nWebhookHandler",
|
|
61
|
+
"N8nWebhookPayload",
|
|
62
|
+
"parse_n8n_webhook",
|
|
63
|
+
"N8N_AI_NODE_TYPES",
|
|
64
|
+
# Code Node Helpers
|
|
65
|
+
"PrelaN8nContext",
|
|
66
|
+
"trace_n8n_code",
|
|
67
|
+
"prela_n8n_traced",
|
|
68
|
+
]
|