contextforge-eval 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.
- context_forge/__init__.py +95 -0
- context_forge/core/__init__.py +55 -0
- context_forge/core/trace.py +369 -0
- context_forge/core/types.py +121 -0
- context_forge/evaluation.py +267 -0
- context_forge/exceptions.py +56 -0
- context_forge/graders/__init__.py +44 -0
- context_forge/graders/base.py +264 -0
- context_forge/graders/deterministic/__init__.py +11 -0
- context_forge/graders/deterministic/memory_corruption.py +130 -0
- context_forge/graders/hybrid.py +190 -0
- context_forge/graders/judges/__init__.py +11 -0
- context_forge/graders/judges/backends/__init__.py +9 -0
- context_forge/graders/judges/backends/ollama.py +173 -0
- context_forge/graders/judges/base.py +158 -0
- context_forge/graders/judges/memory_hygiene_judge.py +332 -0
- context_forge/graders/judges/models.py +113 -0
- context_forge/harness/__init__.py +43 -0
- context_forge/harness/user_simulator/__init__.py +70 -0
- context_forge/harness/user_simulator/adapters/__init__.py +13 -0
- context_forge/harness/user_simulator/adapters/base.py +67 -0
- context_forge/harness/user_simulator/adapters/crewai.py +100 -0
- context_forge/harness/user_simulator/adapters/langgraph.py +157 -0
- context_forge/harness/user_simulator/adapters/pydanticai.py +105 -0
- context_forge/harness/user_simulator/llm/__init__.py +5 -0
- context_forge/harness/user_simulator/llm/ollama.py +119 -0
- context_forge/harness/user_simulator/models.py +103 -0
- context_forge/harness/user_simulator/persona.py +154 -0
- context_forge/harness/user_simulator/runner.py +342 -0
- context_forge/harness/user_simulator/scenario.py +95 -0
- context_forge/harness/user_simulator/simulator.py +307 -0
- context_forge/instrumentation/__init__.py +23 -0
- context_forge/instrumentation/base.py +307 -0
- context_forge/instrumentation/instrumentors/__init__.py +17 -0
- context_forge/instrumentation/instrumentors/langchain.py +671 -0
- context_forge/instrumentation/instrumentors/langgraph.py +534 -0
- context_forge/instrumentation/tracer.py +588 -0
- context_forge/py.typed +0 -0
- contextforge_eval-0.1.0.dist-info/METADATA +420 -0
- contextforge_eval-0.1.0.dist-info/RECORD +43 -0
- contextforge_eval-0.1.0.dist-info/WHEEL +5 -0
- contextforge_eval-0.1.0.dist-info/licenses/LICENSE +201 -0
- contextforge_eval-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""ContextForge: Evaluation framework for context-aware, agentic AI systems."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
# Simple evaluation API (Level 2)
|
|
6
|
+
from context_forge.evaluation import (
|
|
7
|
+
EvaluationResult,
|
|
8
|
+
evaluate_agent,
|
|
9
|
+
evaluate_trace,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Core trace types
|
|
13
|
+
from context_forge.core import (
|
|
14
|
+
AgentInfo,
|
|
15
|
+
BaseStep,
|
|
16
|
+
FinalOutputStep,
|
|
17
|
+
InterruptStep,
|
|
18
|
+
LLMCallStep,
|
|
19
|
+
MemoryReadStep,
|
|
20
|
+
MemoryWriteStep,
|
|
21
|
+
ResourceImpact,
|
|
22
|
+
RetrievalResult,
|
|
23
|
+
RetrievalStep,
|
|
24
|
+
StateChangeStep,
|
|
25
|
+
StepType,
|
|
26
|
+
TaskInfo,
|
|
27
|
+
ToolCallStep,
|
|
28
|
+
TraceRun,
|
|
29
|
+
TraceStep,
|
|
30
|
+
UserInputStep,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Instrumentation
|
|
34
|
+
from context_forge.instrumentation import (
|
|
35
|
+
BaseInstrumentor,
|
|
36
|
+
LangChainInstrumentor,
|
|
37
|
+
LangGraphInstrumentor,
|
|
38
|
+
RedactionConfig,
|
|
39
|
+
)
|
|
40
|
+
from context_forge.instrumentation.tracer import Tracer
|
|
41
|
+
|
|
42
|
+
# Simulation (user simulator)
|
|
43
|
+
from context_forge.harness.user_simulator import (
|
|
44
|
+
GenerativeScenario,
|
|
45
|
+
Goal,
|
|
46
|
+
LangGraphAdapter,
|
|
47
|
+
LLMUserSimulator,
|
|
48
|
+
Persona,
|
|
49
|
+
ScriptedScenario,
|
|
50
|
+
SimulationResult,
|
|
51
|
+
SimulationRunner,
|
|
52
|
+
SimulationState,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"__version__",
|
|
57
|
+
# Simple evaluation API (Level 2)
|
|
58
|
+
"evaluate_agent",
|
|
59
|
+
"evaluate_trace",
|
|
60
|
+
"EvaluationResult",
|
|
61
|
+
# Core trace types
|
|
62
|
+
"StepType",
|
|
63
|
+
"AgentInfo",
|
|
64
|
+
"TaskInfo",
|
|
65
|
+
"ResourceImpact",
|
|
66
|
+
"RetrievalResult",
|
|
67
|
+
"BaseStep",
|
|
68
|
+
"LLMCallStep",
|
|
69
|
+
"ToolCallStep",
|
|
70
|
+
"RetrievalStep",
|
|
71
|
+
"MemoryReadStep",
|
|
72
|
+
"MemoryWriteStep",
|
|
73
|
+
"InterruptStep",
|
|
74
|
+
"StateChangeStep",
|
|
75
|
+
"UserInputStep",
|
|
76
|
+
"FinalOutputStep",
|
|
77
|
+
"TraceStep",
|
|
78
|
+
"TraceRun",
|
|
79
|
+
# Instrumentation
|
|
80
|
+
"Tracer",
|
|
81
|
+
"BaseInstrumentor",
|
|
82
|
+
"LangChainInstrumentor",
|
|
83
|
+
"LangGraphInstrumentor",
|
|
84
|
+
"RedactionConfig",
|
|
85
|
+
# Simulation
|
|
86
|
+
"SimulationRunner",
|
|
87
|
+
"SimulationState",
|
|
88
|
+
"SimulationResult",
|
|
89
|
+
"Persona",
|
|
90
|
+
"Goal",
|
|
91
|
+
"ScriptedScenario",
|
|
92
|
+
"GenerativeScenario",
|
|
93
|
+
"LLMUserSimulator",
|
|
94
|
+
"LangGraphAdapter",
|
|
95
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Core types and trace models for ContextForge.
|
|
2
|
+
|
|
3
|
+
This module contains the canonical trace specification including:
|
|
4
|
+
- StepType enum for step discrimination
|
|
5
|
+
- AgentInfo, TaskInfo metadata models
|
|
6
|
+
- All step type models (LLMCallStep, ToolCallStep, etc.)
|
|
7
|
+
- TraceStep discriminated union
|
|
8
|
+
- TraceRun complete trace model
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from context_forge.core.types import (
|
|
12
|
+
AgentInfo,
|
|
13
|
+
ResourceImpact,
|
|
14
|
+
RetrievalResult,
|
|
15
|
+
StepType,
|
|
16
|
+
TaskInfo,
|
|
17
|
+
)
|
|
18
|
+
from context_forge.core.trace import (
|
|
19
|
+
BaseStep,
|
|
20
|
+
FinalOutputStep,
|
|
21
|
+
InterruptStep,
|
|
22
|
+
LLMCallStep,
|
|
23
|
+
MemoryReadStep,
|
|
24
|
+
MemoryWriteStep,
|
|
25
|
+
RetrievalStep,
|
|
26
|
+
StateChangeStep,
|
|
27
|
+
ToolCallStep,
|
|
28
|
+
TraceRun,
|
|
29
|
+
TraceStep,
|
|
30
|
+
UserInputStep,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# Enums
|
|
35
|
+
"StepType",
|
|
36
|
+
# Metadata models
|
|
37
|
+
"AgentInfo",
|
|
38
|
+
"TaskInfo",
|
|
39
|
+
"ResourceImpact",
|
|
40
|
+
"RetrievalResult",
|
|
41
|
+
# Step models
|
|
42
|
+
"BaseStep",
|
|
43
|
+
"LLMCallStep",
|
|
44
|
+
"ToolCallStep",
|
|
45
|
+
"RetrievalStep",
|
|
46
|
+
"MemoryReadStep",
|
|
47
|
+
"MemoryWriteStep",
|
|
48
|
+
"InterruptStep",
|
|
49
|
+
"StateChangeStep",
|
|
50
|
+
"UserInputStep",
|
|
51
|
+
"FinalOutputStep",
|
|
52
|
+
# Union and container
|
|
53
|
+
"TraceStep",
|
|
54
|
+
"TraceRun",
|
|
55
|
+
]
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"""Trace models for ContextForge.
|
|
2
|
+
|
|
3
|
+
This module implements the canonical trace schema:
|
|
4
|
+
- T016: BaseStep model
|
|
5
|
+
- T017-T022: All step type models
|
|
6
|
+
- T023: TraceStep discriminated union
|
|
7
|
+
- T024: TraceRun model
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Annotated, Any, Literal, Optional, Union
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
14
|
+
|
|
15
|
+
from context_forge.core.types import (
|
|
16
|
+
AgentInfo,
|
|
17
|
+
FieldChange,
|
|
18
|
+
ResourceImpact,
|
|
19
|
+
RetrievalResult,
|
|
20
|
+
StepType,
|
|
21
|
+
TaskInfo,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseStep(BaseModel):
|
|
26
|
+
"""Base fields shared by all step types.
|
|
27
|
+
|
|
28
|
+
All trace steps inherit these common fields for identification,
|
|
29
|
+
timing, and hierarchical organization.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
step_id: Unique identifier for this step within the trace
|
|
33
|
+
timestamp: When this step occurred (ISO8601 with ms precision)
|
|
34
|
+
parent_step_id: Optional reference to parent step for nested calls
|
|
35
|
+
metadata: Optional additional metadata for this step
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
model_config = ConfigDict(
|
|
39
|
+
validate_by_alias=True,
|
|
40
|
+
extra="ignore",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
step_id: str
|
|
44
|
+
timestamp: datetime
|
|
45
|
+
parent_step_id: Optional[str] = None
|
|
46
|
+
metadata: Optional[dict[str, Any]] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LLMCallStep(BaseStep):
|
|
50
|
+
"""An LLM invocation step.
|
|
51
|
+
|
|
52
|
+
Records a call to a language model including the prompt,
|
|
53
|
+
response, and resource usage.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
step_type: Discriminator (always 'llm_call')
|
|
57
|
+
model: Model identifier (e.g., 'gpt-4', 'claude-3-opus')
|
|
58
|
+
input: Prompt text or list of messages
|
|
59
|
+
output: Model response text or structured output
|
|
60
|
+
tokens_in: Number of input/prompt tokens
|
|
61
|
+
tokens_out: Number of output/completion tokens
|
|
62
|
+
tokens_total: Total tokens (input + output)
|
|
63
|
+
latency_ms: Response latency in milliseconds
|
|
64
|
+
cost_estimate: Estimated cost in USD
|
|
65
|
+
provider: LLM provider name (openai, anthropic, etc.)
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
step_type: Literal[StepType.LLM_CALL] = StepType.LLM_CALL
|
|
69
|
+
model: str
|
|
70
|
+
input: str | list[dict[str, Any]]
|
|
71
|
+
output: str | dict[str, Any]
|
|
72
|
+
tokens_in: Optional[int] = None
|
|
73
|
+
tokens_out: Optional[int] = None
|
|
74
|
+
tokens_total: Optional[int] = None
|
|
75
|
+
latency_ms: Optional[int] = None
|
|
76
|
+
cost_estimate: Optional[float] = None
|
|
77
|
+
provider: Optional[str] = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ToolCallStep(BaseStep):
|
|
81
|
+
"""A tool/function invocation step.
|
|
82
|
+
|
|
83
|
+
Records execution of a tool with its arguments and result.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
step_type: Discriminator (always 'tool_call')
|
|
87
|
+
tool_name: Tool identifier
|
|
88
|
+
arguments: Arguments passed to the tool
|
|
89
|
+
result: Tool execution result
|
|
90
|
+
latency_ms: Execution time in milliseconds
|
|
91
|
+
success: Whether the tool call succeeded
|
|
92
|
+
error: Error message if the call failed
|
|
93
|
+
resource_impact: Optional cost/credit impact
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
step_type: Literal[StepType.TOOL_CALL] = StepType.TOOL_CALL
|
|
97
|
+
tool_name: str
|
|
98
|
+
arguments: dict[str, Any]
|
|
99
|
+
result: Optional[Any] = None
|
|
100
|
+
latency_ms: Optional[int] = None
|
|
101
|
+
success: Optional[bool] = None
|
|
102
|
+
error: Optional[str] = None
|
|
103
|
+
resource_impact: Optional[ResourceImpact] = None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class RetrievalStep(BaseStep):
|
|
107
|
+
"""A retrieval/search step.
|
|
108
|
+
|
|
109
|
+
Records a query to a retrieval system (vector DB, search, etc.)
|
|
110
|
+
and its results.
|
|
111
|
+
|
|
112
|
+
Attributes:
|
|
113
|
+
step_type: Discriminator (always 'retrieval')
|
|
114
|
+
query: The search/retrieval query
|
|
115
|
+
results: List of retrieved documents/items
|
|
116
|
+
match_count: Number of matches returned
|
|
117
|
+
latency_ms: Query execution time in milliseconds
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
step_type: Literal[StepType.RETRIEVAL] = StepType.RETRIEVAL
|
|
121
|
+
query: str
|
|
122
|
+
results: list[RetrievalResult]
|
|
123
|
+
match_count: int
|
|
124
|
+
latency_ms: Optional[int] = None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class MemoryReadStep(BaseStep):
|
|
128
|
+
"""A memory read operation step.
|
|
129
|
+
|
|
130
|
+
Records reading from agent memory (context, history, etc.).
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
step_type: Discriminator (always 'memory_read')
|
|
134
|
+
query: Memory query (string or structured)
|
|
135
|
+
results: Items retrieved from memory
|
|
136
|
+
match_count: Number of matches found
|
|
137
|
+
relevance_scores: Optional relevance scores for results
|
|
138
|
+
total_available: Total items available in memory
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
step_type: Literal[StepType.MEMORY_READ] = StepType.MEMORY_READ
|
|
142
|
+
query: str | dict[str, Any]
|
|
143
|
+
results: list[Any]
|
|
144
|
+
match_count: int
|
|
145
|
+
relevance_scores: Optional[list[float]] = None
|
|
146
|
+
total_available: Optional[int] = None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class MemoryWriteStep(BaseStep):
|
|
150
|
+
"""A memory write operation step.
|
|
151
|
+
|
|
152
|
+
Records writing to agent memory with field-level change tracking
|
|
153
|
+
for model-agnostic hygiene grading.
|
|
154
|
+
|
|
155
|
+
Attributes:
|
|
156
|
+
step_type: Discriminator (always 'memory_write')
|
|
157
|
+
namespace: Storage namespace as list (e.g., ["profiles", "user_123"])
|
|
158
|
+
key: Storage key within the namespace
|
|
159
|
+
operation: Operation type (put, delete)
|
|
160
|
+
data: The complete data being written (for reference)
|
|
161
|
+
changes: Field-level changes with JSON paths (model-agnostic)
|
|
162
|
+
triggered_by_step_id: Step ID that triggered this write (for trace linking)
|
|
163
|
+
entity_type: Legacy field for backward compatibility
|
|
164
|
+
entity_id: Optional identifier for the entity
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
step_type: Literal[StepType.MEMORY_WRITE] = StepType.MEMORY_WRITE
|
|
168
|
+
namespace: Optional[list[str]] = None
|
|
169
|
+
key: Optional[str] = None
|
|
170
|
+
operation: Literal["add", "update", "delete", "put"]
|
|
171
|
+
data: dict[str, Any]
|
|
172
|
+
changes: Optional[list[FieldChange]] = None
|
|
173
|
+
triggered_by_step_id: Optional[str] = None
|
|
174
|
+
entity_type: Optional[str] = None # Legacy, derived from namespace if not set
|
|
175
|
+
entity_id: Optional[str] = None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class InterruptStep(BaseStep):
|
|
179
|
+
"""A human-in-the-loop interrupt step.
|
|
180
|
+
|
|
181
|
+
Records when the agent pauses for human input.
|
|
182
|
+
|
|
183
|
+
Attributes:
|
|
184
|
+
step_type: Discriminator (always 'interrupt')
|
|
185
|
+
prompt: The prompt/question shown to the user
|
|
186
|
+
response: The user's response
|
|
187
|
+
wait_duration_ms: How long the agent waited for response
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
step_type: Literal[StepType.INTERRUPT] = StepType.INTERRUPT
|
|
191
|
+
prompt: str
|
|
192
|
+
response: str | dict[str, Any]
|
|
193
|
+
wait_duration_ms: int
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class StateChangeStep(BaseStep):
|
|
197
|
+
"""An agent state change step.
|
|
198
|
+
|
|
199
|
+
Records changes to internal agent state.
|
|
200
|
+
|
|
201
|
+
Attributes:
|
|
202
|
+
step_type: Discriminator (always 'state_change')
|
|
203
|
+
state_key: The state field that changed
|
|
204
|
+
old_value: Previous value (if available)
|
|
205
|
+
new_value: New value
|
|
206
|
+
reason: Optional reason for the change
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
step_type: Literal[StepType.STATE_CHANGE] = StepType.STATE_CHANGE
|
|
210
|
+
state_key: str
|
|
211
|
+
old_value: Optional[Any] = None
|
|
212
|
+
new_value: Any
|
|
213
|
+
reason: Optional[str] = None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class UserInputStep(BaseStep):
|
|
217
|
+
"""A user input step.
|
|
218
|
+
|
|
219
|
+
Records input provided by the user to the agent.
|
|
220
|
+
|
|
221
|
+
Attributes:
|
|
222
|
+
step_type: Discriminator (always 'user_input')
|
|
223
|
+
content: The user's input content
|
|
224
|
+
input_type: Type of input (text, file, voice, etc.)
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
step_type: Literal[StepType.USER_INPUT] = StepType.USER_INPUT
|
|
228
|
+
content: str
|
|
229
|
+
input_type: Optional[str] = None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class FinalOutputStep(BaseStep):
|
|
233
|
+
"""A final output step.
|
|
234
|
+
|
|
235
|
+
Records the agent's final response/output.
|
|
236
|
+
|
|
237
|
+
Attributes:
|
|
238
|
+
step_type: Discriminator (always 'final_output')
|
|
239
|
+
content: The final output content
|
|
240
|
+
format: Output format (text, json, markdown, etc.)
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
step_type: Literal[StepType.FINAL_OUTPUT] = StepType.FINAL_OUTPUT
|
|
244
|
+
content: Any
|
|
245
|
+
format: Optional[str] = None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# Discriminated union for all step types
|
|
249
|
+
TraceStep = Annotated[
|
|
250
|
+
Union[
|
|
251
|
+
LLMCallStep,
|
|
252
|
+
ToolCallStep,
|
|
253
|
+
RetrievalStep,
|
|
254
|
+
MemoryReadStep,
|
|
255
|
+
MemoryWriteStep,
|
|
256
|
+
InterruptStep,
|
|
257
|
+
StateChangeStep,
|
|
258
|
+
UserInputStep,
|
|
259
|
+
FinalOutputStep,
|
|
260
|
+
],
|
|
261
|
+
Field(discriminator="step_type"),
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class TraceRun(BaseModel):
|
|
266
|
+
"""Complete record of an agent execution run.
|
|
267
|
+
|
|
268
|
+
This is the top-level container for a trace, holding all steps
|
|
269
|
+
and metadata about the run.
|
|
270
|
+
|
|
271
|
+
Attributes:
|
|
272
|
+
run_id: Unique identifier for this run (UUID v4)
|
|
273
|
+
started_at: When the run started (ISO8601)
|
|
274
|
+
ended_at: When the run ended (None if still running)
|
|
275
|
+
agent_info: Metadata about the agent
|
|
276
|
+
task_info: Optional metadata about the task
|
|
277
|
+
steps: Ordered list of trace steps
|
|
278
|
+
metadata: Additional run-level metadata
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
model_config = ConfigDict(
|
|
282
|
+
validate_by_alias=True,
|
|
283
|
+
extra="ignore",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
run_id: str
|
|
287
|
+
started_at: datetime
|
|
288
|
+
ended_at: Optional[datetime] = None
|
|
289
|
+
agent_info: AgentInfo
|
|
290
|
+
task_info: Optional[TaskInfo] = None
|
|
291
|
+
steps: list[TraceStep] = Field(default_factory=list)
|
|
292
|
+
metadata: Optional[dict[str, Any]] = None
|
|
293
|
+
|
|
294
|
+
@model_validator(mode="after")
|
|
295
|
+
def validate_steps(self) -> "TraceRun":
|
|
296
|
+
"""Validate step constraints.
|
|
297
|
+
|
|
298
|
+
Checks:
|
|
299
|
+
- All step_ids are unique within the trace
|
|
300
|
+
- ended_at >= started_at if both are set
|
|
301
|
+
"""
|
|
302
|
+
# Check unique step_ids
|
|
303
|
+
step_ids = [step.step_id for step in self.steps]
|
|
304
|
+
duplicates = [sid for sid in step_ids if step_ids.count(sid) > 1]
|
|
305
|
+
if duplicates:
|
|
306
|
+
raise ValueError(
|
|
307
|
+
f"Duplicate step_ids found: {list(set(duplicates))}. "
|
|
308
|
+
"Each step must have a unique step_id within a trace."
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Check ended_at >= started_at
|
|
312
|
+
if self.ended_at is not None and self.ended_at < self.started_at:
|
|
313
|
+
raise ValueError(
|
|
314
|
+
f"ended_at ({self.ended_at}) cannot be before started_at ({self.started_at})"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
return self
|
|
318
|
+
|
|
319
|
+
def add_step(self, step: TraceStep) -> None:
|
|
320
|
+
"""Add a step to the trace.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
step: The step to add
|
|
324
|
+
"""
|
|
325
|
+
self.steps.append(step)
|
|
326
|
+
|
|
327
|
+
def get_steps_by_type(self, step_type: StepType) -> list[TraceStep]:
|
|
328
|
+
"""Get all steps of a specific type.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
step_type: The type of steps to retrieve
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
List of matching steps
|
|
335
|
+
"""
|
|
336
|
+
return [s for s in self.steps if s.step_type == step_type]
|
|
337
|
+
|
|
338
|
+
def get_llm_calls(self) -> list[LLMCallStep]:
|
|
339
|
+
"""Get all LLM call steps."""
|
|
340
|
+
return [s for s in self.steps if isinstance(s, LLMCallStep)]
|
|
341
|
+
|
|
342
|
+
def get_tool_calls(self) -> list[ToolCallStep]:
|
|
343
|
+
"""Get all tool call steps."""
|
|
344
|
+
return [s for s in self.steps if isinstance(s, ToolCallStep)]
|
|
345
|
+
|
|
346
|
+
def total_tokens(self) -> int:
|
|
347
|
+
"""Calculate total tokens used across all LLM calls."""
|
|
348
|
+
total = 0
|
|
349
|
+
for step in self.get_llm_calls():
|
|
350
|
+
if step.tokens_total is not None:
|
|
351
|
+
total += step.tokens_total
|
|
352
|
+
elif step.tokens_in is not None and step.tokens_out is not None:
|
|
353
|
+
total += step.tokens_in + step.tokens_out
|
|
354
|
+
return total
|
|
355
|
+
|
|
356
|
+
def total_tool_calls(self) -> int:
|
|
357
|
+
"""Count total tool calls in the trace."""
|
|
358
|
+
return len(self.get_tool_calls())
|
|
359
|
+
|
|
360
|
+
def to_json(self, **kwargs) -> str:
|
|
361
|
+
"""Serialize the trace to JSON.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
**kwargs: Additional arguments passed to model_dump_json
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
JSON string representation
|
|
368
|
+
"""
|
|
369
|
+
return self.model_dump_json(exclude_none=True, **kwargs)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Core types and metadata models for ContextForge traces.
|
|
2
|
+
|
|
3
|
+
This module implements:
|
|
4
|
+
- T013: StepType enum
|
|
5
|
+
- T014: AgentInfo and TaskInfo models
|
|
6
|
+
- T015: ResourceImpact and RetrievalResult models
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StepType(str, Enum):
|
|
16
|
+
"""Discriminator for trace step types.
|
|
17
|
+
|
|
18
|
+
Each value represents a distinct operation that can occur during
|
|
19
|
+
an agent execution run.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
USER_INPUT = "user_input"
|
|
23
|
+
LLM_CALL = "llm_call"
|
|
24
|
+
TOOL_CALL = "tool_call"
|
|
25
|
+
RETRIEVAL = "retrieval"
|
|
26
|
+
MEMORY_READ = "memory_read"
|
|
27
|
+
MEMORY_WRITE = "memory_write"
|
|
28
|
+
STATE_CHANGE = "state_change"
|
|
29
|
+
INTERRUPT = "interrupt"
|
|
30
|
+
FINAL_OUTPUT = "final_output"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AgentInfo(BaseModel):
|
|
34
|
+
"""Metadata about the agent being traced.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
name: Agent name/identifier (required)
|
|
38
|
+
version: Agent version string
|
|
39
|
+
framework: Framework used (langchain, crewai, langgraph, custom)
|
|
40
|
+
framework_version: Version of the framework
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(extra="ignore")
|
|
44
|
+
|
|
45
|
+
name: str
|
|
46
|
+
version: Optional[str] = None
|
|
47
|
+
framework: Optional[str] = None
|
|
48
|
+
framework_version: Optional[str] = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TaskInfo(BaseModel):
|
|
52
|
+
"""Metadata about the task being executed.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
description: Human-readable task description
|
|
56
|
+
goal: The objective or goal of the task
|
|
57
|
+
input: Task input data as a dictionary
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
model_config = ConfigDict(extra="ignore")
|
|
61
|
+
|
|
62
|
+
description: Optional[str] = None
|
|
63
|
+
goal: Optional[str] = None
|
|
64
|
+
input: Optional[dict[str, Any]] = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ResourceImpact(BaseModel):
|
|
68
|
+
"""Cost or credit impact of a tool call.
|
|
69
|
+
|
|
70
|
+
Used to track resource consumption for tools that have
|
|
71
|
+
associated costs (API credits, compute units, etc.).
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
amount: Numeric amount of resource consumed
|
|
75
|
+
unit: Unit of measurement (credits, USD, compute_units, etc.)
|
|
76
|
+
breakdown: Optional detailed breakdown by category
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
model_config = ConfigDict(extra="ignore")
|
|
80
|
+
|
|
81
|
+
amount: float
|
|
82
|
+
unit: str
|
|
83
|
+
breakdown: Optional[dict[str, Any]] = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class RetrievalResult(BaseModel):
|
|
87
|
+
"""A single retrieved document or item.
|
|
88
|
+
|
|
89
|
+
Represents one result from a retrieval operation (vector search,
|
|
90
|
+
keyword search, etc.).
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
content: The retrieved content (text, document chunk, etc.)
|
|
94
|
+
score: Relevance score from 0.0 to 1.0 (higher is more relevant)
|
|
95
|
+
metadata: Additional metadata about the retrieved item
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
model_config = ConfigDict(extra="ignore")
|
|
99
|
+
|
|
100
|
+
content: str
|
|
101
|
+
score: Optional[float] = None
|
|
102
|
+
metadata: Optional[dict[str, Any]] = None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class FieldChange(BaseModel):
|
|
106
|
+
"""A single field-level change in a memory write operation.
|
|
107
|
+
|
|
108
|
+
Model-agnostic representation of a field modification using JSON path
|
|
109
|
+
notation. Enables hygiene grading without domain knowledge.
|
|
110
|
+
|
|
111
|
+
Attributes:
|
|
112
|
+
path: JSON path to the changed field (e.g., "$.equipment.solar_capacity_kw")
|
|
113
|
+
old_value: Previous value (None if field didn't exist)
|
|
114
|
+
new_value: New value (None if field was deleted)
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
model_config = ConfigDict(extra="ignore")
|
|
118
|
+
|
|
119
|
+
path: str
|
|
120
|
+
old_value: Optional[Any] = None
|
|
121
|
+
new_value: Optional[Any] = None
|