flock-core 0.5.9__py3-none-any.whl → 0.5.11__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/agent.py +149 -62
- flock/api/themes.py +6 -2
- flock/api_models.py +285 -0
- flock/artifact_collector.py +6 -3
- flock/batch_accumulator.py +3 -1
- flock/cli.py +3 -1
- flock/components.py +45 -56
- flock/context_provider.py +531 -0
- flock/correlation_engine.py +8 -4
- flock/dashboard/collector.py +48 -29
- flock/dashboard/events.py +10 -4
- flock/dashboard/launcher.py +3 -1
- flock/dashboard/models/graph.py +9 -3
- flock/dashboard/service.py +187 -93
- flock/dashboard/websocket.py +17 -4
- flock/engines/dspy_engine.py +174 -98
- flock/engines/examples/simple_batch_engine.py +9 -3
- flock/examples.py +6 -2
- flock/frontend/src/services/indexeddb.test.ts +4 -4
- flock/frontend/src/services/indexeddb.ts +1 -1
- flock/helper/cli_helper.py +14 -1
- flock/logging/auto_trace.py +6 -1
- flock/logging/formatters/enum_builder.py +3 -1
- flock/logging/formatters/theme_builder.py +32 -17
- flock/logging/formatters/themed_formatter.py +38 -22
- flock/logging/logging.py +21 -7
- flock/logging/telemetry.py +9 -3
- flock/logging/telemetry_exporter/duckdb_exporter.py +27 -25
- flock/logging/trace_and_logged.py +14 -5
- flock/mcp/__init__.py +3 -6
- flock/mcp/client.py +49 -19
- flock/mcp/config.py +12 -6
- flock/mcp/manager.py +6 -2
- flock/mcp/servers/sse/flock_sse_server.py +9 -3
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +6 -2
- flock/mcp/tool.py +18 -6
- flock/mcp/types/handlers.py +3 -1
- flock/mcp/types/types.py +9 -3
- flock/orchestrator.py +449 -58
- flock/orchestrator_component.py +15 -5
- flock/patches/dspy_streaming_patch.py +12 -4
- flock/registry.py +9 -3
- flock/runtime.py +69 -18
- flock/service.py +135 -64
- flock/store.py +29 -10
- flock/subscription.py +6 -4
- flock/system_artifacts.py +33 -0
- flock/utilities.py +41 -13
- flock/utility/output_utility_component.py +31 -11
- {flock_core-0.5.9.dist-info → flock_core-0.5.11.dist-info}/METADATA +150 -26
- {flock_core-0.5.9.dist-info → flock_core-0.5.11.dist-info}/RECORD +54 -51
- {flock_core-0.5.9.dist-info → flock_core-0.5.11.dist-info}/WHEEL +0 -0
- {flock_core-0.5.9.dist-info → flock_core-0.5.11.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.9.dist-info → flock_core-0.5.11.dist-info}/licenses/LICENSE +0 -0
flock/api_models.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Pydantic response models for Flock REST API.
|
|
2
|
+
|
|
3
|
+
Provides proper OpenAPI schemas for all public API endpoints.
|
|
4
|
+
This improves API documentation and enables SDK generation.
|
|
5
|
+
|
|
6
|
+
All models maintain 100% backwards compatibility with existing wire format.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
from uuid import UUID
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# Agent Models
|
|
18
|
+
# ============================================================================
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentSubscription(BaseModel):
|
|
22
|
+
"""Subscription configuration for an agent."""
|
|
23
|
+
|
|
24
|
+
types: list[str] = Field(description="Artifact types this subscription consumes")
|
|
25
|
+
mode: str = Field(description="Subscription mode (e.g., 'all', 'any')")
|
|
26
|
+
delivery: str = Field(description="Delivery mode (e.g., 'immediate', 'batch')")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Agent(BaseModel):
|
|
30
|
+
"""Single agent representation."""
|
|
31
|
+
|
|
32
|
+
name: str = Field(description="Unique name of the agent")
|
|
33
|
+
description: str = Field(default="", description="Human-readable description")
|
|
34
|
+
subscriptions: list[AgentSubscription] = Field(
|
|
35
|
+
description="List of subscriptions this agent listens to"
|
|
36
|
+
)
|
|
37
|
+
outputs: list[str] = Field(description="Artifact types this agent can produce")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AgentListResponse(BaseModel):
|
|
41
|
+
"""Response for GET /api/v1/agents."""
|
|
42
|
+
|
|
43
|
+
agents: list[Agent] = Field(description="List of all registered agents")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ============================================================================
|
|
47
|
+
# Artifact Models
|
|
48
|
+
# ============================================================================
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class VisibilityInfo(BaseModel):
|
|
52
|
+
"""Artifact visibility configuration."""
|
|
53
|
+
|
|
54
|
+
kind: str = Field(description="Visibility kind (e.g., 'Public', 'Private')")
|
|
55
|
+
# Additional visibility fields added dynamically
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ArtifactBase(BaseModel):
|
|
59
|
+
"""Base artifact representation with common fields."""
|
|
60
|
+
|
|
61
|
+
id: str = Field(description="Unique artifact identifier (UUID)")
|
|
62
|
+
type: str = Field(description="Artifact type name")
|
|
63
|
+
payload: dict[str, Any] = Field(description="Artifact payload data")
|
|
64
|
+
produced_by: str = Field(description="Name of agent/source that produced this")
|
|
65
|
+
visibility: dict[str, Any] = Field(description="Visibility configuration")
|
|
66
|
+
visibility_kind: str = Field(description="Visibility kind (Public/Private/etc)")
|
|
67
|
+
created_at: str = Field(
|
|
68
|
+
description="Timestamp when artifact was created (ISO 8601)"
|
|
69
|
+
)
|
|
70
|
+
correlation_id: str | None = Field(
|
|
71
|
+
None, description="Optional correlation ID for workflow tracking"
|
|
72
|
+
)
|
|
73
|
+
partition_key: str | None = Field(None, description="Optional partition key")
|
|
74
|
+
tags: list[str] = Field(default_factory=list, description="List of tags")
|
|
75
|
+
version: int = Field(description="Artifact version number")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ConsumptionRecord(BaseModel):
|
|
79
|
+
"""Record of an artifact being consumed by an agent."""
|
|
80
|
+
|
|
81
|
+
artifact_id: str = Field(description="ID of the artifact that was consumed")
|
|
82
|
+
consumer: str = Field(description="Name of the agent that consumed it")
|
|
83
|
+
run_id: str = Field(description="Run ID of the consumption")
|
|
84
|
+
correlation_id: str = Field(description="Correlation ID of the consumption")
|
|
85
|
+
consumed_at: str = Field(description="Timestamp of consumption (ISO 8601)")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ArtifactWithConsumptions(ArtifactBase):
|
|
89
|
+
"""Artifact with consumption metadata included."""
|
|
90
|
+
|
|
91
|
+
consumptions: list[ConsumptionRecord] = Field(
|
|
92
|
+
default_factory=list, description="List of consumption records"
|
|
93
|
+
)
|
|
94
|
+
consumed_by: list[str] = Field(
|
|
95
|
+
default_factory=list,
|
|
96
|
+
description="List of unique agent names that consumed this artifact",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class PaginationInfo(BaseModel):
|
|
101
|
+
"""Pagination metadata."""
|
|
102
|
+
|
|
103
|
+
limit: int = Field(description="Number of items per page")
|
|
104
|
+
offset: int = Field(description="Offset into the result set")
|
|
105
|
+
total: int = Field(description="Total number of items matching the query")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ArtifactListResponse(BaseModel):
|
|
109
|
+
"""Response for GET /api/v1/artifacts."""
|
|
110
|
+
|
|
111
|
+
items: list[ArtifactBase | ArtifactWithConsumptions] = Field(
|
|
112
|
+
description="List of artifacts (may include consumption data if embed_meta=true)"
|
|
113
|
+
)
|
|
114
|
+
pagination: PaginationInfo = Field(description="Pagination information")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ArtifactPublishRequest(BaseModel):
|
|
118
|
+
"""Request body for POST /api/v1/artifacts."""
|
|
119
|
+
|
|
120
|
+
type: str = Field(description="Artifact type name")
|
|
121
|
+
payload: dict[str, Any] = Field(
|
|
122
|
+
default_factory=dict, description="Artifact payload data"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ArtifactPublishResponse(BaseModel):
|
|
127
|
+
"""Response for POST /api/v1/artifacts."""
|
|
128
|
+
|
|
129
|
+
status: Literal["accepted"] = Field(description="Publication status")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ArtifactSummary(BaseModel):
|
|
133
|
+
"""Summary statistics for artifacts."""
|
|
134
|
+
|
|
135
|
+
# Define based on actual summary structure from store
|
|
136
|
+
# This is a placeholder - update based on actual implementation
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ArtifactSummaryResponse(BaseModel):
|
|
140
|
+
"""Response for GET /api/v1/artifacts/summary."""
|
|
141
|
+
|
|
142
|
+
summary: dict[str, Any] = Field(description="Summary statistics")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ============================================================================
|
|
146
|
+
# Agent Run Models
|
|
147
|
+
# ============================================================================
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class AgentRunInput(BaseModel):
|
|
151
|
+
"""Input artifact for agent run."""
|
|
152
|
+
|
|
153
|
+
type: str = Field(description="Artifact type name")
|
|
154
|
+
payload: dict[str, Any] = Field(
|
|
155
|
+
default_factory=dict, description="Artifact payload data"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class AgentRunRequest(BaseModel):
|
|
160
|
+
"""Request body for POST /api/v1/agents/{name}/run."""
|
|
161
|
+
|
|
162
|
+
inputs: list[AgentRunInput] = Field(
|
|
163
|
+
default_factory=list, description="List of input artifacts"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ProducedArtifact(BaseModel):
|
|
168
|
+
"""Artifact produced by agent run."""
|
|
169
|
+
|
|
170
|
+
id: str = Field(description="Artifact ID (UUID)")
|
|
171
|
+
type: str = Field(description="Artifact type name")
|
|
172
|
+
payload: dict[str, Any] = Field(description="Artifact payload data")
|
|
173
|
+
produced_by: str = Field(description="Name of agent that produced this")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class AgentRunResponse(BaseModel):
|
|
177
|
+
"""Response for POST /api/v1/agents/{name}/run."""
|
|
178
|
+
|
|
179
|
+
artifacts: list[ProducedArtifact] = Field(
|
|
180
|
+
description="Artifacts produced by the agent run"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ============================================================================
|
|
185
|
+
# Schema Discovery Models
|
|
186
|
+
# ============================================================================
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class ArtifactTypeSchema(BaseModel):
|
|
190
|
+
"""Schema information for an artifact type."""
|
|
191
|
+
|
|
192
|
+
model_config = {"populate_by_name": True} # Allow using 'schema' as field name
|
|
193
|
+
|
|
194
|
+
name: str = Field(description="Type name")
|
|
195
|
+
schema_: dict[str, Any] = Field(
|
|
196
|
+
alias="schema", description="JSON Schema for this type"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ArtifactTypesResponse(BaseModel):
|
|
201
|
+
"""Response for GET /api/artifact-types."""
|
|
202
|
+
|
|
203
|
+
artifact_types: list[ArtifactTypeSchema] = Field(
|
|
204
|
+
description="List of all registered artifact types with their schemas"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ============================================================================
|
|
209
|
+
# Agent History Models
|
|
210
|
+
# ============================================================================
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class AgentHistorySummary(BaseModel):
|
|
214
|
+
"""Summary of agent execution history."""
|
|
215
|
+
|
|
216
|
+
agent_id: str = Field(description="Agent identifier")
|
|
217
|
+
summary: dict[str, Any] = Field(description="History summary statistics")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ============================================================================
|
|
221
|
+
# Correlation Status Models
|
|
222
|
+
# ============================================================================
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class CorrelationStatusResponse(BaseModel):
|
|
226
|
+
"""Response for GET /api/v1/correlations/{correlation_id}/status."""
|
|
227
|
+
|
|
228
|
+
correlation_id: str = Field(description="The correlation ID")
|
|
229
|
+
state: Literal["active", "completed", "failed", "not_found"] = Field(
|
|
230
|
+
description="Workflow state: active (work pending), completed (success), failed (only errors), not_found (no artifacts)"
|
|
231
|
+
)
|
|
232
|
+
has_pending_work: bool = Field(
|
|
233
|
+
description="Whether the orchestrator has pending work for this correlation"
|
|
234
|
+
)
|
|
235
|
+
artifact_count: int = Field(
|
|
236
|
+
description="Total number of artifacts with this correlation_id"
|
|
237
|
+
)
|
|
238
|
+
error_count: int = Field(description="Number of WorkflowError artifacts")
|
|
239
|
+
started_at: str | None = Field(
|
|
240
|
+
None, description="Timestamp of first artifact (ISO 8601)"
|
|
241
|
+
)
|
|
242
|
+
last_activity_at: str | None = Field(
|
|
243
|
+
None, description="Timestamp of most recent artifact (ISO 8601)"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ============================================================================
|
|
248
|
+
# Health & Metrics Models
|
|
249
|
+
# ============================================================================
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class HealthResponse(BaseModel):
|
|
253
|
+
"""Response for GET /health."""
|
|
254
|
+
|
|
255
|
+
status: Literal["ok"] = Field(description="Health status")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
__all__ = [
|
|
259
|
+
# Agent models
|
|
260
|
+
"Agent",
|
|
261
|
+
"AgentSubscription",
|
|
262
|
+
"AgentListResponse",
|
|
263
|
+
# Artifact models
|
|
264
|
+
"ArtifactBase",
|
|
265
|
+
"ArtifactWithConsumptions",
|
|
266
|
+
"ArtifactListResponse",
|
|
267
|
+
"ArtifactPublishRequest",
|
|
268
|
+
"ArtifactPublishResponse",
|
|
269
|
+
"ArtifactSummaryResponse",
|
|
270
|
+
"PaginationInfo",
|
|
271
|
+
"ConsumptionRecord",
|
|
272
|
+
# Agent run models
|
|
273
|
+
"AgentRunRequest",
|
|
274
|
+
"AgentRunResponse",
|
|
275
|
+
"ProducedArtifact",
|
|
276
|
+
# Schema discovery
|
|
277
|
+
"ArtifactTypesResponse",
|
|
278
|
+
"ArtifactTypeSchema",
|
|
279
|
+
# History
|
|
280
|
+
"AgentHistorySummary",
|
|
281
|
+
# Correlation status
|
|
282
|
+
"CorrelationStatusResponse",
|
|
283
|
+
# Health
|
|
284
|
+
"HealthResponse",
|
|
285
|
+
]
|
flock/artifact_collector.py
CHANGED
|
@@ -43,8 +43,8 @@ class ArtifactCollector:
|
|
|
43
43
|
# Structure: {(agent_name, subscription_index): {type_name: [artifact1, artifact2, ...]}}
|
|
44
44
|
# Example: {("diagnostician", 0): {"XRay": [artifact1], "LabResult": [artifact2]}}
|
|
45
45
|
# For count-based AND gates: {"TypeA": [artifact1, artifact2, artifact3]} (3 As collected)
|
|
46
|
-
self._waiting_pools: dict[tuple[str, int], dict[str, list[Artifact]]] =
|
|
47
|
-
lambda: defaultdict(list)
|
|
46
|
+
self._waiting_pools: dict[tuple[str, int], dict[str, list[Artifact]]] = (
|
|
47
|
+
defaultdict(lambda: defaultdict(list))
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
def add_artifact(
|
|
@@ -72,7 +72,10 @@ class ArtifactCollector:
|
|
|
72
72
|
- After returning complete=True, the pool is automatically cleared
|
|
73
73
|
"""
|
|
74
74
|
# Single-type subscription with count=1: No waiting needed (immediate trigger)
|
|
75
|
-
if
|
|
75
|
+
if (
|
|
76
|
+
len(subscription.type_names) == 1
|
|
77
|
+
and subscription.type_counts[artifact.type] == 1
|
|
78
|
+
):
|
|
76
79
|
return (True, [artifact])
|
|
77
80
|
|
|
78
81
|
# Multi-type or count-based subscription: Use waiting pool (AND gate logic)
|
flock/batch_accumulator.py
CHANGED
|
@@ -194,7 +194,9 @@ class BatchEngine:
|
|
|
194
194
|
|
|
195
195
|
return False # Not ready to flush yet
|
|
196
196
|
|
|
197
|
-
def flush_batch(
|
|
197
|
+
def flush_batch(
|
|
198
|
+
self, agent_name: str, subscription_index: int
|
|
199
|
+
) -> list[Artifact] | None:
|
|
198
200
|
"""
|
|
199
201
|
Flush a batch and return its artifacts.
|
|
200
202
|
|
flock/cli.py
CHANGED
|
@@ -123,7 +123,9 @@ def sqlite_maintenance(
|
|
|
123
123
|
try:
|
|
124
124
|
before_dt = datetime.fromisoformat(delete_before)
|
|
125
125
|
except ValueError as exc: # pragma: no cover - Typer handles but defensive
|
|
126
|
-
raise typer.BadParameter(
|
|
126
|
+
raise typer.BadParameter(
|
|
127
|
+
f"Invalid ISO timestamp: {delete_before}"
|
|
128
|
+
) from exc
|
|
127
129
|
deleted = await store.delete_before(before_dt)
|
|
128
130
|
if vacuum:
|
|
129
131
|
await store.vacuum()
|
flock/components.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING,
|
|
5
|
+
from typing import TYPE_CHECKING, Self
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, Field, create_model
|
|
8
8
|
from pydantic._internal._model_construction import ModelMetaclass
|
|
@@ -12,8 +12,6 @@ from flock.logging.auto_trace import AutoTracedMeta
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
15
|
-
from uuid import UUID
|
|
16
|
-
|
|
17
15
|
from flock.agent import Agent, OutputGroup
|
|
18
16
|
from flock.artifacts import Artifact
|
|
19
17
|
from flock.runtime import Context, EvalInputs, EvalResult
|
|
@@ -71,7 +69,9 @@ class AgentComponent(BaseModel, metaclass=TracedModelMeta):
|
|
|
71
69
|
) -> list[Artifact]:
|
|
72
70
|
return inputs
|
|
73
71
|
|
|
74
|
-
async def on_pre_evaluate(
|
|
72
|
+
async def on_pre_evaluate(
|
|
73
|
+
self, agent: Agent, ctx: Context, inputs: EvalInputs
|
|
74
|
+
) -> EvalInputs:
|
|
75
75
|
return inputs
|
|
76
76
|
|
|
77
77
|
async def on_post_evaluate(
|
|
@@ -89,7 +89,9 @@ class AgentComponent(BaseModel, metaclass=TracedModelMeta):
|
|
|
89
89
|
) -> None: # pragma: no cover - default
|
|
90
90
|
return None
|
|
91
91
|
|
|
92
|
-
async def on_terminate(
|
|
92
|
+
async def on_terminate(
|
|
93
|
+
self, agent: Agent, ctx: Context
|
|
94
|
+
) -> None: # pragma: no cover - default
|
|
93
95
|
return None
|
|
94
96
|
|
|
95
97
|
|
|
@@ -153,67 +155,54 @@ class EngineComponent(AgentComponent):
|
|
|
153
155
|
"""
|
|
154
156
|
raise NotImplementedError
|
|
155
157
|
|
|
156
|
-
|
|
158
|
+
def get_conversation_context(
|
|
157
159
|
self,
|
|
158
160
|
ctx: Context,
|
|
159
|
-
correlation_id: UUID | None = None,
|
|
160
161
|
max_artifacts: int | None = None,
|
|
161
|
-
) -> list[
|
|
162
|
-
"""
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
) -> list[Artifact]:
|
|
163
|
+
"""Get conversation context from Context (read-only helper).
|
|
164
|
+
|
|
165
|
+
Phase 8 Security Fix: This method now simply reads pre-filtered artifacts from
|
|
166
|
+
Context. The orchestrator evaluates context BEFORE creating Context, so engines
|
|
167
|
+
can no longer query arbitrary data.
|
|
168
|
+
|
|
169
|
+
REMOVED METHODS (Security Fix):
|
|
170
|
+
- fetch_conversation_context() - REMOVED (engines can't query anymore)
|
|
171
|
+
- get_latest_artifact_of_type() - REMOVED (engines can't query anymore)
|
|
172
|
+
|
|
173
|
+
Migration Guide:
|
|
174
|
+
Old (vulnerable): context = await self.fetch_conversation_context(ctx, agent, exclude_ids)
|
|
175
|
+
New (secure): context = ctx.artifacts # Pre-filtered by orchestrator!
|
|
165
176
|
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
Args:
|
|
178
|
+
ctx: Execution context with pre-filtered artifacts
|
|
179
|
+
max_artifacts: Optional limit (applies to already-filtered list)
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
List of Artifact objects (pre-filtered by orchestrator via context provider)
|
|
183
|
+
with full metadata (type, payload, produced_by, created_at, tags, etc.)
|
|
184
|
+
"""
|
|
185
|
+
if not self.enable_context or not ctx:
|
|
168
186
|
return []
|
|
169
187
|
|
|
170
|
-
|
|
171
|
-
all_artifacts = await ctx.board.list()
|
|
188
|
+
context_items = list(ctx.artifacts) # Copy to avoid mutation
|
|
172
189
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
)
|
|
190
|
+
# Apply engine-level filtering (type exclusions)
|
|
191
|
+
if self.context_exclude_types:
|
|
192
|
+
context_items = [
|
|
193
|
+
item
|
|
194
|
+
for item in context_items
|
|
195
|
+
if item.type not in self.context_exclude_types
|
|
180
196
|
]
|
|
181
197
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
context = []
|
|
189
|
-
i = 0
|
|
190
|
-
for artifact in context_artifacts:
|
|
191
|
-
context.append(
|
|
192
|
-
{
|
|
193
|
-
"type": artifact.type,
|
|
194
|
-
"payload": artifact.payload,
|
|
195
|
-
"produced_by": artifact.produced_by,
|
|
196
|
-
"event_number": i,
|
|
197
|
-
# "created_at": artifact.created_at.isoformat(),
|
|
198
|
-
}
|
|
199
|
-
)
|
|
200
|
-
i += 1
|
|
201
|
-
|
|
202
|
-
return context
|
|
203
|
-
|
|
204
|
-
except Exception:
|
|
205
|
-
return []
|
|
198
|
+
# Apply max artifacts limit
|
|
199
|
+
max_limit = (
|
|
200
|
+
max_artifacts if max_artifacts is not None else self.context_max_artifacts
|
|
201
|
+
)
|
|
202
|
+
if max_limit is not None and max_limit > 0:
|
|
203
|
+
context_items = context_items[-max_limit:]
|
|
206
204
|
|
|
207
|
-
|
|
208
|
-
self,
|
|
209
|
-
ctx: Context,
|
|
210
|
-
artifact_type: str,
|
|
211
|
-
correlation_id: UUID | None = None,
|
|
212
|
-
) -> dict[str, Any] | None:
|
|
213
|
-
"""Get the most recent artifact of a specific type in the conversation."""
|
|
214
|
-
context = await self.fetch_conversation_context(ctx, correlation_id)
|
|
215
|
-
matching = [a for a in context if a["type"].endswith(artifact_type)]
|
|
216
|
-
return matching[-1] if matching else None
|
|
205
|
+
return context_items
|
|
217
206
|
|
|
218
207
|
def should_use_context(self, inputs: EvalInputs) -> bool:
|
|
219
208
|
"""Determine if context should be included based on the current inputs."""
|