prefactor-core 0.2.3__tar.gz → 0.2.5__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.
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/.gitignore +1 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/PKG-INFO +8 -4
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/README.md +6 -2
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/examples/agent_e2e.py +20 -6
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/pyproject.toml +1 -1
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/_version.py +1 -1
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/client.py +10 -3
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/managers/agent_instance.py +26 -5
- prefactor_core-0.2.5/tests/test_agent_instance_finish_status.py +104 -0
- prefactor_core-0.2.5/tests/test_agent_instance_register.py +123 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_sdk_header.py +2 -2
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/__init__.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/config.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/context_stack.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/exceptions.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/managers/__init__.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/managers/span.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/models.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/operations.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/queue/__init__.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/queue/base.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/queue/executor.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/queue/memory.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/schema_registry.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/span_context.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/utils.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_client.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_failure_handling.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_imports.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_queue.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_span_context.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_span_manager.py +0 -0
- {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_utils.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: prefactor-core
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Core Prefactor SDK with async queue-based operations
|
|
5
5
|
Author-email: Prefactor Pty Ltd <josh@prefactor.tech>
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: <4.0.0,>=3.11.0
|
|
8
|
-
Requires-Dist: prefactor-http>=0.1.
|
|
8
|
+
Requires-Dist: prefactor-http>=0.1.3
|
|
9
9
|
Requires-Dist: pydantic>=2.0.0
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
|
|
@@ -81,6 +81,10 @@ async def main():
|
|
|
81
81
|
asyncio.run(main())
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
`create_agent_instance()` supports two auth modes:
|
|
85
|
+
- Account-scoped token: pass `agent_id` and usually `environment_id`.
|
|
86
|
+
- Deployment-scoped token: omit `agent_id` and `environment_id`; the API derives both from the token.
|
|
87
|
+
|
|
84
88
|
## API Reference
|
|
85
89
|
|
|
86
90
|
### `PrefactorCoreClient`
|
|
@@ -98,9 +102,9 @@ await client.close()
|
|
|
98
102
|
|
|
99
103
|
```python
|
|
100
104
|
handle = await client.create_agent_instance(
|
|
101
|
-
agent_id="my-agent",
|
|
102
105
|
agent_version={"name": "My Agent", "external_identifier": "v1.0.0"},
|
|
103
106
|
agent_schema_version=None, # Optional: auto-generated if schema_registry is configured
|
|
107
|
+
agent_id="my-agent", # Optional for deployment-scoped tokens
|
|
104
108
|
external_schema_version_id=None, # Optional: reference an existing schema version
|
|
105
109
|
) -> AgentInstanceHandle
|
|
106
110
|
```
|
|
@@ -240,8 +244,8 @@ config = PrefactorCoreConfig(
|
|
|
240
244
|
async with PrefactorCoreClient(config) as client:
|
|
241
245
|
# agent_schema_version is generated automatically from the registry
|
|
242
246
|
instance = await client.create_agent_instance(
|
|
243
|
-
agent_id="my-agent",
|
|
244
247
|
agent_version={"name": "My Agent", "external_identifier": "v1.0.0"},
|
|
248
|
+
agent_id="my-agent", # Optional for deployment-scoped tokens
|
|
245
249
|
)
|
|
246
250
|
```
|
|
247
251
|
|
|
@@ -70,6 +70,10 @@ async def main():
|
|
|
70
70
|
asyncio.run(main())
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
`create_agent_instance()` supports two auth modes:
|
|
74
|
+
- Account-scoped token: pass `agent_id` and usually `environment_id`.
|
|
75
|
+
- Deployment-scoped token: omit `agent_id` and `environment_id`; the API derives both from the token.
|
|
76
|
+
|
|
73
77
|
## API Reference
|
|
74
78
|
|
|
75
79
|
### `PrefactorCoreClient`
|
|
@@ -87,9 +91,9 @@ await client.close()
|
|
|
87
91
|
|
|
88
92
|
```python
|
|
89
93
|
handle = await client.create_agent_instance(
|
|
90
|
-
agent_id="my-agent",
|
|
91
94
|
agent_version={"name": "My Agent", "external_identifier": "v1.0.0"},
|
|
92
95
|
agent_schema_version=None, # Optional: auto-generated if schema_registry is configured
|
|
96
|
+
agent_id="my-agent", # Optional for deployment-scoped tokens
|
|
93
97
|
external_schema_version_id=None, # Optional: reference an existing schema version
|
|
94
98
|
) -> AgentInstanceHandle
|
|
95
99
|
```
|
|
@@ -229,8 +233,8 @@ config = PrefactorCoreConfig(
|
|
|
229
233
|
async with PrefactorCoreClient(config) as client:
|
|
230
234
|
# agent_schema_version is generated automatically from the registry
|
|
231
235
|
instance = await client.create_agent_instance(
|
|
232
|
-
agent_id="my-agent",
|
|
233
236
|
agent_version={"name": "My Agent", "external_identifier": "v1.0.0"},
|
|
237
|
+
agent_id="my-agent", # Optional for deployment-scoped tokens
|
|
234
238
|
)
|
|
235
239
|
```
|
|
236
240
|
|
|
@@ -24,8 +24,11 @@ Run via mise (env vars set automatically):
|
|
|
24
24
|
Or manually:
|
|
25
25
|
PREFACTOR_API_URL=https://api.prefactor.ai \\
|
|
26
26
|
PREFACTOR_API_TOKEN=your-token \\
|
|
27
|
-
PREFACTOR_AGENT_ID=your-agent-id \\
|
|
28
27
|
python packages/core/examples/agent_e2e.py
|
|
28
|
+
|
|
29
|
+
Set `PREFACTOR_AGENT_ID` only when using an account-scoped token. With a
|
|
30
|
+
deployment-scoped token, leave it unset and the API derives the agent and
|
|
31
|
+
environment from the token.
|
|
29
32
|
"""
|
|
30
33
|
|
|
31
34
|
import asyncio
|
|
@@ -182,7 +185,7 @@ async def simulate_retrieval(query: str) -> list[str]:
|
|
|
182
185
|
|
|
183
186
|
|
|
184
187
|
async def main() -> None:
|
|
185
|
-
agent_id = os.environ
|
|
188
|
+
agent_id = os.environ.get("PREFACTOR_AGENT_ID")
|
|
186
189
|
|
|
187
190
|
config = PrefactorCoreConfig(
|
|
188
191
|
http_config=HttpClientConfig(
|
|
@@ -194,10 +197,21 @@ async def main() -> None:
|
|
|
194
197
|
|
|
195
198
|
async with PrefactorCoreClient(config) as client:
|
|
196
199
|
# Register the agent instance — schema version derived from registry.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
if agent_id is None:
|
|
201
|
+
instance = await client.create_agent_instance(
|
|
202
|
+
agent_version={
|
|
203
|
+
"name": "Example Agent",
|
|
204
|
+
"external_identifier": "v8.0.0",
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
instance = await client.create_agent_instance(
|
|
209
|
+
agent_id=agent_id,
|
|
210
|
+
agent_version={
|
|
211
|
+
"name": "Example Agent",
|
|
212
|
+
"external_identifier": "v8.0.0",
|
|
213
|
+
},
|
|
214
|
+
)
|
|
201
215
|
|
|
202
216
|
await instance.start()
|
|
203
217
|
|
|
@@ -274,6 +274,7 @@ class PrefactorCoreClient:
|
|
|
274
274
|
elif operation.type == OperationType.FINISH_AGENT_INSTANCE:
|
|
275
275
|
await self._http.agent_instances.finish(
|
|
276
276
|
agent_instance_id=operation.payload["instance_id"],
|
|
277
|
+
status=operation.payload.get("status", "complete"),
|
|
277
278
|
timestamp=operation.timestamp,
|
|
278
279
|
idempotency_key=operation.payload.get("idempotency_key"),
|
|
279
280
|
)
|
|
@@ -313,11 +314,12 @@ class PrefactorCoreClient:
|
|
|
313
314
|
|
|
314
315
|
async def create_agent_instance(
|
|
315
316
|
self,
|
|
316
|
-
agent_id: str,
|
|
317
317
|
agent_version: dict[str, Any],
|
|
318
318
|
agent_schema_version: dict[str, Any] | None = None,
|
|
319
|
+
agent_id: str | None = None,
|
|
319
320
|
instance_id: str | None = None,
|
|
320
321
|
external_schema_version_id: str | None = None,
|
|
322
|
+
environment_id: str | None = None,
|
|
321
323
|
) -> "AgentInstanceHandle":
|
|
322
324
|
"""Create a new agent instance.
|
|
323
325
|
|
|
@@ -328,13 +330,14 @@ class PrefactorCoreClient:
|
|
|
328
330
|
the registry's schemas will be used automatically.
|
|
329
331
|
|
|
330
332
|
Args:
|
|
331
|
-
agent_id: ID
|
|
333
|
+
agent_id: Agent ID. Omit when using a deployment-scoped token.
|
|
332
334
|
agent_version: Version information (name, etc.).
|
|
333
335
|
agent_schema_version: Schema version. Uses registry if not provided
|
|
334
336
|
and registry is configured.
|
|
335
337
|
instance_id: Optional custom ID for the instance.
|
|
336
338
|
external_schema_version_id: Optional external identifier for the
|
|
337
339
|
schema version. Defaults to "auto-generated" when using registry.
|
|
340
|
+
environment_id: Optional environment ID used to scope the agent instance.
|
|
338
341
|
|
|
339
342
|
Returns:
|
|
340
343
|
AgentInstanceHandle for the created instance.
|
|
@@ -355,7 +358,10 @@ class PrefactorCoreClient:
|
|
|
355
358
|
# Use registry to generate schema version
|
|
356
359
|
ext_id = external_schema_version_id
|
|
357
360
|
if ext_id is None:
|
|
358
|
-
|
|
361
|
+
if agent_id is None:
|
|
362
|
+
ext_id = f"auto-deployment-{time.time()}"
|
|
363
|
+
else:
|
|
364
|
+
ext_id = f"auto-{agent_id}-{time.time()}"
|
|
359
365
|
final_schema_version = self._config.schema_registry.to_agent_schema_version(
|
|
360
366
|
ext_id
|
|
361
367
|
)
|
|
@@ -372,6 +378,7 @@ class PrefactorCoreClient:
|
|
|
372
378
|
agent_version=agent_version,
|
|
373
379
|
agent_schema_version=final_schema_version,
|
|
374
380
|
instance_id=instance_id,
|
|
381
|
+
environment_id=environment_id,
|
|
375
382
|
)
|
|
376
383
|
|
|
377
384
|
return AgentInstanceHandle(
|
|
@@ -14,6 +14,7 @@ from ..utils import generate_idempotency_key
|
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from prefactor_http.client import PrefactorHttpClient
|
|
17
|
+
from prefactor_http.models.types import FinishStatus
|
|
17
18
|
|
|
18
19
|
from ..client import PrefactorCoreClient
|
|
19
20
|
|
|
@@ -58,10 +59,11 @@ class AgentInstanceManager:
|
|
|
58
59
|
|
|
59
60
|
async def register(
|
|
60
61
|
self,
|
|
61
|
-
agent_id: str,
|
|
62
62
|
agent_version: dict[str, Any],
|
|
63
63
|
agent_schema_version: dict[str, Any],
|
|
64
|
+
agent_id: str | None = None,
|
|
64
65
|
instance_id: str | None = None,
|
|
66
|
+
environment_id: str | None = None,
|
|
65
67
|
) -> str:
|
|
66
68
|
"""Register a new agent instance.
|
|
67
69
|
|
|
@@ -69,12 +71,14 @@ class AgentInstanceManager:
|
|
|
69
71
|
the API-generated ID.
|
|
70
72
|
|
|
71
73
|
Args:
|
|
72
|
-
agent_id: ID
|
|
74
|
+
agent_id: Agent ID. Omit when using a deployment-scoped token.
|
|
73
75
|
agent_version: Version information (name, external_identifier, etc.).
|
|
74
76
|
agent_schema_version: Schema version information.
|
|
75
77
|
instance_id: Optional ID to forward to the API as ``id``. When
|
|
76
78
|
provided, the API uses it as the instance ID; when omitted,
|
|
77
79
|
the API generates one.
|
|
80
|
+
environment_id: Optional environment ID. Required when using an
|
|
81
|
+
account-scoped token; omit when using a deployment-scoped token.
|
|
78
82
|
|
|
79
83
|
Returns:
|
|
80
84
|
The instance ID (API-generated).
|
|
@@ -83,6 +87,7 @@ class AgentInstanceManager:
|
|
|
83
87
|
agent_id=agent_id,
|
|
84
88
|
agent_version=agent_version,
|
|
85
89
|
agent_schema_version=agent_schema_version,
|
|
90
|
+
environment_id=environment_id,
|
|
86
91
|
id=instance_id,
|
|
87
92
|
idempotency_key=generate_idempotency_key(),
|
|
88
93
|
)
|
|
@@ -115,20 +120,30 @@ class AgentInstanceManager:
|
|
|
115
120
|
|
|
116
121
|
await self._enqueue(operation)
|
|
117
122
|
|
|
118
|
-
async def finish(
|
|
123
|
+
async def finish(
|
|
124
|
+
self,
|
|
125
|
+
instance_id: str,
|
|
126
|
+
status: "FinishStatus" = "complete",
|
|
127
|
+
) -> None:
|
|
119
128
|
"""Mark an instance as finished.
|
|
120
129
|
|
|
121
130
|
Queues a finish operation for the instance.
|
|
122
131
|
|
|
123
132
|
Args:
|
|
124
133
|
instance_id: The ID of the instance to finish.
|
|
134
|
+
status: Terminal status for the instance. Defaults to ``"complete"``.
|
|
125
135
|
"""
|
|
126
|
-
await self.finish_with_idempotency_key(
|
|
136
|
+
await self.finish_with_idempotency_key(
|
|
137
|
+
instance_id,
|
|
138
|
+
generate_idempotency_key(),
|
|
139
|
+
status=status,
|
|
140
|
+
)
|
|
127
141
|
|
|
128
142
|
async def finish_with_idempotency_key(
|
|
129
143
|
self,
|
|
130
144
|
instance_id: str,
|
|
131
145
|
idempotency_key: str,
|
|
146
|
+
status: "FinishStatus" = "complete",
|
|
132
147
|
) -> None:
|
|
133
148
|
"""Queue a finish operation using a stable idempotency key."""
|
|
134
149
|
operation = Operation(
|
|
@@ -136,6 +151,7 @@ class AgentInstanceManager:
|
|
|
136
151
|
payload={
|
|
137
152
|
"instance_id": instance_id,
|
|
138
153
|
"idempotency_key": idempotency_key,
|
|
154
|
+
"status": status,
|
|
139
155
|
},
|
|
140
156
|
timestamp=datetime.now(timezone.utc),
|
|
141
157
|
)
|
|
@@ -199,16 +215,21 @@ class AgentInstanceHandle:
|
|
|
199
215
|
self._start_idempotency_key,
|
|
200
216
|
)
|
|
201
217
|
|
|
202
|
-
async def finish(self) -> None:
|
|
218
|
+
async def finish(self, status: "FinishStatus" = "complete") -> None:
|
|
203
219
|
"""Mark the instance as finished.
|
|
204
220
|
|
|
205
221
|
This queues a finish operation for the instance.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
status: Terminal status for the instance — one of ``"complete"``,
|
|
225
|
+
``"failed"``, or ``"cancelled"``. Defaults to ``"complete"``.
|
|
206
226
|
"""
|
|
207
227
|
manager = self._client.instance_manager
|
|
208
228
|
assert manager is not None
|
|
209
229
|
await manager.finish_with_idempotency_key(
|
|
210
230
|
self._instance_id,
|
|
211
231
|
self._finish_idempotency_key,
|
|
232
|
+
status=status,
|
|
212
233
|
)
|
|
213
234
|
|
|
214
235
|
async def create_span(
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Tests for AgentInstanceHandle.finish() accepting an optional status.
|
|
2
|
+
|
|
3
|
+
See GitHub issue prefactordev/python-sdk#13.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from types import SimpleNamespace
|
|
9
|
+
from unittest.mock import patch
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
from prefactor_core import PrefactorCoreClient
|
|
13
|
+
from prefactor_core.config import PrefactorCoreConfig, QueueConfig
|
|
14
|
+
from prefactor_http.config import HttpClientConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _StubAgentInstances:
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
self.finish_calls: list[dict] = []
|
|
20
|
+
|
|
21
|
+
async def register(self, **kwargs):
|
|
22
|
+
return SimpleNamespace(id=kwargs.get("id") or "inst-1")
|
|
23
|
+
|
|
24
|
+
async def start(self, **kwargs):
|
|
25
|
+
return SimpleNamespace(id=kwargs["agent_instance_id"])
|
|
26
|
+
|
|
27
|
+
async def finish(self, **kwargs):
|
|
28
|
+
self.finish_calls.append(kwargs)
|
|
29
|
+
return SimpleNamespace(id=kwargs["agent_instance_id"])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _StubAgentSpans:
|
|
33
|
+
async def create(self, **kwargs):
|
|
34
|
+
return SimpleNamespace(id="span-1")
|
|
35
|
+
|
|
36
|
+
async def finish(self, **kwargs):
|
|
37
|
+
return SimpleNamespace(id=kwargs["agent_span_id"])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _StubHttpClient:
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
*_args,
|
|
44
|
+
agent_instances: _StubAgentInstances | None = None,
|
|
45
|
+
**_kwargs,
|
|
46
|
+
) -> None:
|
|
47
|
+
self.agent_instances = agent_instances or _StubAgentInstances()
|
|
48
|
+
self.agent_spans = _StubAgentSpans()
|
|
49
|
+
|
|
50
|
+
async def __aenter__(self):
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
async def __aexit__(self, exc_type, exc, tb):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _make_client_config() -> PrefactorCoreConfig:
|
|
58
|
+
return PrefactorCoreConfig(
|
|
59
|
+
http_config=HttpClientConfig(
|
|
60
|
+
api_url="https://api.test.com",
|
|
61
|
+
api_token="test-token",
|
|
62
|
+
max_retries=0,
|
|
63
|
+
initial_retry_delay=0.01,
|
|
64
|
+
max_retry_delay=0.02,
|
|
65
|
+
),
|
|
66
|
+
queue_config=QueueConfig(num_workers=1, max_retries=0),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_finish_without_status_defaults_to_complete():
|
|
72
|
+
"""Default call to finish() should forward status='complete' to HTTP."""
|
|
73
|
+
stub_http = _StubHttpClient()
|
|
74
|
+
|
|
75
|
+
with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
|
|
76
|
+
async with PrefactorCoreClient(_make_client_config()) as client:
|
|
77
|
+
instance = await client.create_agent_instance(
|
|
78
|
+
agent_id="agent-1",
|
|
79
|
+
agent_version={"name": "v1"},
|
|
80
|
+
agent_schema_version={"span_schemas": {}},
|
|
81
|
+
)
|
|
82
|
+
await instance.finish()
|
|
83
|
+
|
|
84
|
+
assert len(stub_http.agent_instances.finish_calls) == 1
|
|
85
|
+
assert stub_http.agent_instances.finish_calls[0].get("status") == "complete"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
@pytest.mark.parametrize("status", ["failed", "cancelled", "complete"])
|
|
90
|
+
async def test_finish_forwards_explicit_status(status):
|
|
91
|
+
"""Explicit status on handle.finish() should reach the HTTP layer."""
|
|
92
|
+
stub_http = _StubHttpClient()
|
|
93
|
+
|
|
94
|
+
with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
|
|
95
|
+
async with PrefactorCoreClient(_make_client_config()) as client:
|
|
96
|
+
instance = await client.create_agent_instance(
|
|
97
|
+
agent_id="agent-1",
|
|
98
|
+
agent_version={"name": "v1"},
|
|
99
|
+
agent_schema_version={"span_schemas": {}},
|
|
100
|
+
)
|
|
101
|
+
await instance.finish(status=status)
|
|
102
|
+
|
|
103
|
+
assert len(stub_http.agent_instances.finish_calls) == 1
|
|
104
|
+
assert stub_http.agent_instances.finish_calls[0]["status"] == status
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Tests for environment_id threading through the core register flow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from prefactor_core import PrefactorCoreClient
|
|
10
|
+
from prefactor_core.config import PrefactorCoreConfig, QueueConfig
|
|
11
|
+
from prefactor_http.config import HttpClientConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _make_config() -> PrefactorCoreConfig:
|
|
15
|
+
return PrefactorCoreConfig(
|
|
16
|
+
http_config=HttpClientConfig(
|
|
17
|
+
api_url="https://api.test.com",
|
|
18
|
+
api_token="test-token",
|
|
19
|
+
max_retries=0,
|
|
20
|
+
),
|
|
21
|
+
queue_config=QueueConfig(num_workers=1, max_retries=0),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _StubAgentInstances:
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self.register_calls: list[dict] = []
|
|
28
|
+
|
|
29
|
+
async def register(self, **kwargs):
|
|
30
|
+
self.register_calls.append(kwargs)
|
|
31
|
+
return SimpleNamespace(id="inst-1")
|
|
32
|
+
|
|
33
|
+
async def start(self, **kwargs):
|
|
34
|
+
return SimpleNamespace(id=kwargs["agent_instance_id"])
|
|
35
|
+
|
|
36
|
+
async def finish(self, **kwargs):
|
|
37
|
+
return SimpleNamespace(id=kwargs["agent_instance_id"])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _StubAgentSpans:
|
|
41
|
+
async def create(self, **kwargs):
|
|
42
|
+
return SimpleNamespace(id="span-1")
|
|
43
|
+
|
|
44
|
+
async def finish(self, **kwargs):
|
|
45
|
+
return SimpleNamespace(id=kwargs["agent_span_id"])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _StubHttpClient:
|
|
49
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
50
|
+
self.agent_instances = _StubAgentInstances()
|
|
51
|
+
self.agent_spans = _StubAgentSpans()
|
|
52
|
+
|
|
53
|
+
async def __aenter__(self):
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
async def __aexit__(self, exc_type, exc, tb):
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.asyncio
|
|
61
|
+
async def test_create_agent_instance_passes_environment_id_to_register():
|
|
62
|
+
"""environment_id passed to create_agent_instance() reaches the HTTP layer."""
|
|
63
|
+
stub_http = _StubHttpClient()
|
|
64
|
+
with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
|
|
65
|
+
async with PrefactorCoreClient(_make_config()) as client:
|
|
66
|
+
await client.create_agent_instance(
|
|
67
|
+
agent_id="agent-1",
|
|
68
|
+
agent_version={"name": "v1"},
|
|
69
|
+
agent_schema_version={"span_schemas": {}},
|
|
70
|
+
environment_id="env-1",
|
|
71
|
+
)
|
|
72
|
+
assert stub_http.agent_instances.register_calls[0]["environment_id"] == "env-1"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.asyncio
|
|
76
|
+
async def test_create_agent_instance_without_environment_id_omits_field():
|
|
77
|
+
"""Omitting environment_id does not send it to the HTTP register call."""
|
|
78
|
+
stub_http = _StubHttpClient()
|
|
79
|
+
with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
|
|
80
|
+
async with PrefactorCoreClient(_make_config()) as client:
|
|
81
|
+
await client.create_agent_instance(
|
|
82
|
+
agent_id="agent-1",
|
|
83
|
+
agent_version={"name": "v1"},
|
|
84
|
+
agent_schema_version={"span_schemas": {}},
|
|
85
|
+
)
|
|
86
|
+
call = stub_http.agent_instances.register_calls[0]
|
|
87
|
+
assert "environment_id" not in call or call["environment_id"] is None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.asyncio
|
|
91
|
+
async def test_create_agent_instance_without_agent_id_omits_field():
|
|
92
|
+
"""Omitting agent_id does not send it to the HTTP register call."""
|
|
93
|
+
stub_http = _StubHttpClient()
|
|
94
|
+
with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
|
|
95
|
+
async with PrefactorCoreClient(_make_config()) as client:
|
|
96
|
+
await client.create_agent_instance(
|
|
97
|
+
agent_version={"name": "v1"},
|
|
98
|
+
agent_schema_version={"span_schemas": {}},
|
|
99
|
+
)
|
|
100
|
+
call = stub_http.agent_instances.register_calls[0]
|
|
101
|
+
assert "agent_id" not in call or call["agent_id"] is None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.asyncio
|
|
105
|
+
async def test_create_agent_instance_without_agent_id_uses_deployment_schema_fallback():
|
|
106
|
+
"""Registry fallback should not require agent_id to generate an external ID."""
|
|
107
|
+
stub_http = _StubHttpClient()
|
|
108
|
+
config = _make_config()
|
|
109
|
+
config.schema_registry = SimpleNamespace(
|
|
110
|
+
to_agent_schema_version=lambda external_id: {"external_identifier": external_id}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
|
|
114
|
+
async with PrefactorCoreClient(config) as client:
|
|
115
|
+
await client.create_agent_instance(
|
|
116
|
+
agent_version={"name": "v1"},
|
|
117
|
+
agent_schema_version=None,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
call = stub_http.agent_instances.register_calls[0]
|
|
121
|
+
assert call["agent_schema_version"]["external_identifier"].startswith(
|
|
122
|
+
"auto-deployment-"
|
|
123
|
+
)
|
|
@@ -47,7 +47,7 @@ class TestPrefactorCoreSdkHeader:
|
|
|
47
47
|
"""Core prepends the adapter entry ahead of the core entry."""
|
|
48
48
|
client = PrefactorCoreClient(
|
|
49
49
|
create_config(),
|
|
50
|
-
sdk_header_entry="prefactor-langchain@0.2.
|
|
50
|
+
sdk_header_entry="prefactor-langchain@0.2.5",
|
|
51
51
|
)
|
|
52
52
|
|
|
53
53
|
with (
|
|
@@ -63,7 +63,7 @@ class TestPrefactorCoreSdkHeader:
|
|
|
63
63
|
await client.initialize()
|
|
64
64
|
assert client._http is not None
|
|
65
65
|
assert client._http._sdk_header == (
|
|
66
|
-
f"prefactor-langchain@0.2.
|
|
66
|
+
f"prefactor-langchain@0.2.5 {CORE_SDK_HEADER_ENTRY}"
|
|
67
67
|
)
|
|
68
68
|
await client.close()
|
|
69
69
|
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|