aid-runtime-python 0.1.39__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.
Files changed (52) hide show
  1. aid_runtime/__init__.py +500 -0
  2. aid_runtime/agents/__init__.py +94 -0
  3. aid_runtime/agents/chat_model.py +351 -0
  4. aid_runtime/agents/deep_agents.py +600 -0
  5. aid_runtime/agents/engine.py +102 -0
  6. aid_runtime/agents/guardrails.py +519 -0
  7. aid_runtime/agents/ir.py +622 -0
  8. aid_runtime/agents/loop.py +1308 -0
  9. aid_runtime/agents/memory.py +241 -0
  10. aid_runtime/agents/policy.py +163 -0
  11. aid_runtime/agents/state.py +182 -0
  12. aid_runtime/cache.py +185 -0
  13. aid_runtime/concurrency.py +70 -0
  14. aid_runtime/control.py +329 -0
  15. aid_runtime/cost.py +168 -0
  16. aid_runtime/errors.py +337 -0
  17. aid_runtime/evals/__init__.py +72 -0
  18. aid_runtime/evals/dataset.py +77 -0
  19. aid_runtime/evals/gate.py +430 -0
  20. aid_runtime/evals/metrics.py +335 -0
  21. aid_runtime/evals/predicate.py +211 -0
  22. aid_runtime/evals/runner.py +224 -0
  23. aid_runtime/gateway.py +473 -0
  24. aid_runtime/pipeline.py +168 -0
  25. aid_runtime/prompts.py +185 -0
  26. aid_runtime/providers/__init__.py +20 -0
  27. aid_runtime/providers/base.py +57 -0
  28. aid_runtime/providers/fake.py +256 -0
  29. aid_runtime/providers/http.py +344 -0
  30. aid_runtime/py.typed +0 -0
  31. aid_runtime/retrieval/__init__.py +178 -0
  32. aid_runtime/retrieval/chunking.py +222 -0
  33. aid_runtime/retrieval/citations.py +131 -0
  34. aid_runtime/retrieval/connectors.py +131 -0
  35. aid_runtime/retrieval/embedding.py +293 -0
  36. aid_runtime/retrieval/gate.py +120 -0
  37. aid_runtime/retrieval/hybrid.py +164 -0
  38. aid_runtime/retrieval/metrics.py +213 -0
  39. aid_runtime/retrieval/pgvector.py +258 -0
  40. aid_runtime/retrieval/rerank.py +133 -0
  41. aid_runtime/retrieval/retriever.py +335 -0
  42. aid_runtime/retrieval/stores.py +161 -0
  43. aid_runtime/retrieval/types.py +433 -0
  44. aid_runtime/steps.py +284 -0
  45. aid_runtime/structured.py +150 -0
  46. aid_runtime/tools.py +271 -0
  47. aid_runtime/tracing.py +241 -0
  48. aid_runtime/types.py +332 -0
  49. aid_runtime_python-0.1.39.dist-info/METADATA +268 -0
  50. aid_runtime_python-0.1.39.dist-info/RECORD +52 -0
  51. aid_runtime_python-0.1.39.dist-info/WHEEL +5 -0
  52. aid_runtime_python-0.1.39.dist-info/top_level.txt +1 -0
@@ -0,0 +1,433 @@
1
+ """Shapes the retrieval runtime shares across its modules.
2
+
3
+ Two families live here and the split is deliberate.
4
+
5
+ * The **IR mirror** models (``SourceSpec``, ``ChunkingSpec``, ``IndexSpec``,
6
+ ``RerankSpec``, ``RefreshSpec``, ``RetrieverSpec``, ``EmbeddingSpec``,
7
+ ``VectorStoreSpec``) reproduce the L1 IR's ``retrievers`` / ``embeddings`` /
8
+ ``stores`` entries field for field. Field names are Pythonic; the IR's
9
+ camelCase spelling is carried as a pydantic *alias*, so an IR fragment
10
+ validates against these models unchanged and the two vocabularies cannot drift
11
+ without a test going red. Enum *values* are the IR's own strings
12
+ (``recursive``, ``pgvector``, ``cosine``) and are never re-spelled.
13
+ * The **resolved** models (``PinnedEmbedding``, ``RetrieverBinding``) are what
14
+ generated code binds with literals. They carry the exact embedding model id
15
+ rather than an alias, so a run is reproducible from the generated source alone
16
+ and nothing is resolved at run time.
17
+ * The **store records** (``StoreRecord``, ``SearchHit``) are the payloads the store
18
+ adapters read and write. They live here rather than in ``stores`` so the pgvector
19
+ adapter can build them without importing ``stores``, which imports ``pgvector``
20
+ back lazily; the module graph then has no cycle.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import hashlib
26
+ from enum import StrEnum
27
+ from typing import Any, Literal
28
+
29
+ from pydantic import BaseModel, ConfigDict, Field
30
+
31
+ from aid_runtime.errors import ConfigurationError, FeatureDeferredError
32
+ from aid_runtime.types import CostProfile
33
+
34
+ __all__ = [
35
+ "Chunk",
36
+ "ChunkingSpec",
37
+ "ChunkingStrategy",
38
+ "DistanceMetric",
39
+ "EmbeddingCostProfile",
40
+ "EmbeddingSpec",
41
+ "IndexSpec",
42
+ "PinnedEmbedding",
43
+ "RefreshMode",
44
+ "RefreshSpec",
45
+ "RerankSpec",
46
+ "RetrievalMetricSpec",
47
+ "RetrieverBinding",
48
+ "RetrieverSpec",
49
+ "ScoredChunk",
50
+ "SearchHit",
51
+ "SourceKind",
52
+ "SourceSpec",
53
+ "StoreKind",
54
+ "StoreRecord",
55
+ "VectorStoreSpec",
56
+ "binding_from_spec",
57
+ "chunk_id_for",
58
+ "pinned_embedding",
59
+ ]
60
+
61
+ #: The IR spells several fields in camelCase. Accepting both spellings is what
62
+ #: lets one model validate an IR fragment *and* be built from generated Python.
63
+ _IR_RECORD = ConfigDict(frozen=True, extra="forbid", populate_by_name=True)
64
+
65
+ #: The runtime's own records are constructed by us, never parsed from IR JSON,
66
+ #: so they need no aliases and refuse anything unexpected.
67
+ _RUNTIME_RECORD = ConfigDict(frozen=True, extra="forbid")
68
+
69
+
70
+ class DistanceMetric(StrEnum):
71
+ """``retriever.index.metric``."""
72
+
73
+ COSINE = "cosine"
74
+ DOT = "dot"
75
+ L2 = "l2"
76
+
77
+
78
+ class SourceKind(StrEnum):
79
+ """``retriever.source.kind``.
80
+
81
+ ``FILE`` is the only kind the Phase 1 connector implements; the rest are part
82
+ of the IR contract and fail loudly rather than silently indexing nothing.
83
+ """
84
+
85
+ FILE = "file"
86
+ S3 = "s3"
87
+ HTTP = "http"
88
+ DB = "db"
89
+ MCP = "mcp"
90
+
91
+
92
+ class ChunkingStrategy(StrEnum):
93
+ """``retriever.chunking.strategy``."""
94
+
95
+ FIXED = "fixed"
96
+ RECURSIVE = "recursive"
97
+ SEMANTIC = "semantic"
98
+ CODE = "code"
99
+
100
+
101
+ class StoreKind(StrEnum):
102
+ """``vectorStore.kind``."""
103
+
104
+ MEMORY = "memory"
105
+ PGVECTOR = "pgvector"
106
+ QDRANT = "qdrant"
107
+ PINECONE = "pinecone"
108
+ WEAVIATE = "weaviate"
109
+ CUSTOM = "custom"
110
+
111
+
112
+ class RefreshMode(StrEnum):
113
+ """``retriever.refresh.mode``. Only ``FULL`` is implemented."""
114
+
115
+ FULL = "full"
116
+ INCREMENTAL = "incremental"
117
+
118
+
119
+ class SourceSpec(BaseModel):
120
+ """``retriever.source``."""
121
+
122
+ model_config = _IR_RECORD
123
+
124
+ kind: SourceKind
125
+ uri: str
126
+ glob: str | None = None
127
+
128
+
129
+ class ChunkingSpec(BaseModel):
130
+ """``retriever.chunking``."""
131
+
132
+ model_config = _IR_RECORD
133
+
134
+ strategy: ChunkingStrategy
135
+ size: int = Field(gt=0)
136
+ overlap: int = Field(ge=0)
137
+ keep_metadata: tuple[str, ...] = Field(alias="keepMetadata")
138
+
139
+
140
+ class IndexSpec(BaseModel):
141
+ """``retriever.index``."""
142
+
143
+ model_config = _IR_RECORD
144
+
145
+ metric: DistanceMetric
146
+ hybrid: bool
147
+ params: dict[str, Any] | None = None
148
+
149
+
150
+ class RerankSpec(BaseModel):
151
+ """``retriever.rerank``."""
152
+
153
+ model_config = _IR_RECORD
154
+
155
+ model: str
156
+ top_n: int = Field(gt=0, alias="topN")
157
+
158
+
159
+ class RefreshSpec(BaseModel):
160
+ """``retriever.refresh``."""
161
+
162
+ model_config = _IR_RECORD
163
+
164
+ mode: RefreshMode
165
+ schedule: str | None = None
166
+
167
+
168
+ class RetrieverSpec(BaseModel):
169
+ """One entry of the IR's ``retrievers`` map."""
170
+
171
+ model_config = _IR_RECORD
172
+
173
+ id: str = Field(min_length=1)
174
+ source: SourceSpec
175
+ chunking: ChunkingSpec
176
+ embed_model: str = Field(alias="embedModel")
177
+ store: str
178
+ index: IndexSpec
179
+ top_k: int = Field(gt=0, alias="topK")
180
+ rerank: RerankSpec | None = None
181
+ filters: tuple[str, ...] | None = None
182
+ refresh: RefreshSpec
183
+ traits: tuple[str, ...]
184
+
185
+
186
+ class EmbeddingCostProfile(BaseModel):
187
+ """``embedding.costProfile`` -- USD per **million** tokens.
188
+
189
+ Not interchangeable with :class:`~aid_runtime.types.CostProfile`, which prices
190
+ per *thousand* tokens because it predates the IR. :func:`pinned_embedding` is
191
+ the single place the two are converted, so the factor of 1000 is stated once
192
+ rather than assumed at every call site.
193
+ """
194
+
195
+ model_config = _IR_RECORD
196
+
197
+ input_per_mtok: float = Field(ge=0.0, alias="inputPerMTok")
198
+ output_per_mtok: float = Field(ge=0.0, alias="outputPerMTok")
199
+
200
+
201
+ class EmbeddingSpec(BaseModel):
202
+ """One entry of the IR's ``embeddings`` map.
203
+
204
+ ``id`` is the *alias* a retriever's ``embedModel`` names -- the resolution the
205
+ runtime must never repeat at run time.
206
+ """
207
+
208
+ model_config = _IR_RECORD
209
+
210
+ id: str = Field(min_length=1)
211
+ provider: str
212
+ model_id: str = Field(alias="modelId")
213
+ dimensions: int | None = Field(default=None, gt=0)
214
+ cost_profile: EmbeddingCostProfile | None = Field(default=None, alias="costProfile")
215
+ traits: tuple[str, ...]
216
+
217
+
218
+ class VectorStoreSpec(BaseModel):
219
+ """One entry of the IR's ``stores`` map."""
220
+
221
+ model_config = _IR_RECORD
222
+
223
+ id: str = Field(min_length=1)
224
+ kind: StoreKind
225
+ uri: str | None = None
226
+ dimensions: int | None = Field(default=None, gt=0)
227
+ traits: tuple[str, ...]
228
+
229
+
230
+ class RetrievalMetricSpec(BaseModel):
231
+ """The IR's ``retrieval`` metric variant: ``{kind, expectDocs, k}``."""
232
+
233
+ model_config = _IR_RECORD
234
+
235
+ kind: Literal["retrieval"] = "retrieval"
236
+ expect_docs: tuple[str, ...] = Field(alias="expectDocs")
237
+ k: int = Field(gt=0)
238
+
239
+
240
+ class PinnedEmbedding(BaseModel):
241
+ """An ``embedModel`` alias already resolved to an exact embedding model.
242
+
243
+ The same contract as :class:`~aid_runtime.types.PinnedModel`: a baseline
244
+ recorded against ``text-embedding-3-small`` means nothing if the alias floats
245
+ to another revision, so the resolved id, the dimensions and the price are all
246
+ *recorded inputs* rather than lookups a run performs.
247
+ """
248
+
249
+ model_config = _RUNTIME_RECORD
250
+
251
+ alias: str = Field(min_length=1)
252
+ provider: str = Field(min_length=1)
253
+ model_id: str = Field(min_length=1)
254
+ dimensions: int = Field(gt=0)
255
+ cost: CostProfile
256
+
257
+
258
+ class RetrieverBinding(BaseModel):
259
+ """A retriever as generated code binds it, with every alias resolved.
260
+
261
+ ``embed_model`` keeps the declared alias purely so cost can be attributed to
262
+ the name the spec used; ``embedding`` is the resolved model actually called.
263
+ ``store_kind`` is resolved from the store entry, because a run must not
264
+ discover at search time which backend it wrote to.
265
+
266
+ ``store_uri`` is deliberately kept *unresolved*: a hosted store's location is a
267
+ deployment secret, which is why the IR writes ``env:PGVECTOR_URL`` rather than a
268
+ DSN. The name is pinned in code; the value is read from the environment at run
269
+ time, and an environment variable is not an alias.
270
+ """
271
+
272
+ model_config = _RUNTIME_RECORD
273
+
274
+ retriever_id: str = Field(min_length=1)
275
+ source: SourceSpec
276
+ chunking: ChunkingSpec
277
+ embed_model: str = Field(min_length=1)
278
+ embedding: PinnedEmbedding
279
+ store: str = Field(min_length=1)
280
+ store_kind: StoreKind
281
+ store_uri: str | None = None
282
+ index: IndexSpec
283
+ top_k: int = Field(gt=0)
284
+ rerank: RerankSpec | None = None
285
+ refresh_mode: RefreshMode = RefreshMode.FULL
286
+ traits: tuple[str, ...] = ()
287
+
288
+
289
+ class Chunk(BaseModel):
290
+ """One indexed passage.
291
+
292
+ ``chunk_id`` hashes the document id, the chunk's position *and* its text.
293
+ That is what makes ingest idempotent: identical input re-chunks to identical
294
+ ids, so a second ingest replaces rather than duplicates. Position is part of
295
+ the hash on purpose -- two byte-identical paragraphs in one document must stay
296
+ two chunks instead of colliding into one.
297
+ """
298
+
299
+ model_config = _RUNTIME_RECORD
300
+
301
+ chunk_id: str = Field(min_length=1)
302
+ doc_id: str = Field(min_length=1)
303
+ uri: str = ""
304
+ index: int = Field(ge=0)
305
+ start: int = Field(ge=0)
306
+ end: int = Field(ge=0)
307
+ text: str
308
+ metadata: dict[str, str] = Field(default_factory=dict)
309
+
310
+
311
+ class StoreRecord(BaseModel):
312
+ """One chunk and its vector, exactly as it is written to a store."""
313
+
314
+ model_config = _RUNTIME_RECORD
315
+
316
+ chunk: Chunk
317
+ vector: tuple[float, ...]
318
+
319
+
320
+ class SearchHit(BaseModel):
321
+ """A record and its similarity to the query, higher being better."""
322
+
323
+ model_config = _RUNTIME_RECORD
324
+
325
+ record: StoreRecord
326
+ score: float
327
+
328
+
329
+ class ScoredChunk(BaseModel):
330
+ """A chunk with the score that ranked it."""
331
+
332
+ model_config = _RUNTIME_RECORD
333
+
334
+ chunk: Chunk
335
+ score: float
336
+
337
+
338
+ def chunk_id_for(doc_id: str, index: int, text: str) -> str:
339
+ """The content hash that identifies a chunk.
340
+
341
+ Shared by every chunker so the id scheme cannot differ between strategies; a
342
+ strategy that changed the id scheme would silently duplicate a corpus on
343
+ re-ingest instead of replacing it.
344
+ """
345
+ digest = hashlib.sha256()
346
+ for part in (doc_id, str(index), text):
347
+ digest.update(part.encode("utf-8"))
348
+ digest.update(b"\x00")
349
+ return f"sha256:{digest.hexdigest()}"
350
+
351
+
352
+ def pinned_embedding(
353
+ spec: EmbeddingSpec, *, alias: str, dimensions: int | None = None
354
+ ) -> PinnedEmbedding:
355
+ """Resolve an IR embedding entry into the pinned form generated code carries.
356
+
357
+ ``dimensions`` may be supplied when the IR entry omits it (the schema makes it
358
+ optional); it is an error to have neither, because an unpinned width would let
359
+ a store accept vectors a later run cannot compare against.
360
+ """
361
+ width = dimensions if dimensions is not None else spec.dimensions
362
+ if width is None:
363
+ raise ConfigurationError(
364
+ f"embedding {spec.id!r} pins no dimensions and none were supplied; the vector "
365
+ "width must be recorded with the pinned model so stores stay comparable"
366
+ )
367
+ profile = spec.cost_profile
368
+ # costProfile quotes USD per MTok; CostProfile quotes USD per 1k tokens.
369
+ scale = 1_000.0
370
+ return PinnedEmbedding(
371
+ alias=alias,
372
+ provider=spec.provider,
373
+ model_id=spec.model_id,
374
+ dimensions=width,
375
+ cost=CostProfile(
376
+ input_per_1k_usd=(profile.input_per_mtok / scale) if profile else 0.0,
377
+ output_per_1k_usd=(profile.output_per_mtok / scale) if profile else 0.0,
378
+ ),
379
+ )
380
+
381
+
382
+ def binding_from_spec(
383
+ spec: RetrieverSpec,
384
+ *,
385
+ embedding: EmbeddingSpec,
386
+ store: VectorStoreSpec,
387
+ dimensions: int | None = None,
388
+ ) -> RetrieverBinding:
389
+ """Bind one IR retriever entry to the embedding and store it names.
390
+
391
+ ``embedModel`` names an *embeddings entry id*, not a provider model, and
392
+ ``store`` names a *stores entry id*. Both are checked here rather than
393
+ trusted, because a mistyped reference in generated code would otherwise
394
+ surface as an empty or mis-scored corpus far from its cause.
395
+ """
396
+ if spec.embed_model != embedding.id:
397
+ raise ConfigurationError(
398
+ f"retriever {spec.id!r} names embedModel {spec.embed_model!r} but embedding "
399
+ f"{embedding.id!r} was supplied"
400
+ )
401
+ if spec.store != store.id:
402
+ raise ConfigurationError(
403
+ f"retriever {spec.id!r} names store {spec.store!r} but store {store.id!r} was supplied"
404
+ )
405
+ if spec.filters:
406
+ # The spec validator already refuses filters; the runtime checks too,
407
+ # because generated code is reachable without the validator having run.
408
+ raise FeatureDeferredError(
409
+ f"retriever {spec.id!r} declares filters, which spec v0.1 does not implement: a "
410
+ "filter would have to be pushed into every store adapter's query"
411
+ )
412
+ pinned = pinned_embedding(embedding, alias=spec.embed_model, dimensions=dimensions)
413
+ if store.dimensions is not None and store.dimensions != pinned.dimensions:
414
+ raise ConfigurationError(
415
+ f"store {store.id!r} is declared {store.dimensions}-dimensional but retriever "
416
+ f"{spec.id!r} pins {pinned.dimensions} dimensions via embedModel "
417
+ f"{spec.embed_model!r}; the two must agree before anything is indexed"
418
+ )
419
+ return RetrieverBinding(
420
+ retriever_id=spec.id,
421
+ source=spec.source,
422
+ chunking=spec.chunking,
423
+ embed_model=spec.embed_model,
424
+ embedding=pinned,
425
+ store=spec.store,
426
+ store_kind=store.kind,
427
+ store_uri=store.uri,
428
+ index=spec.index,
429
+ top_k=spec.top_k,
430
+ rerank=spec.rerank,
431
+ refresh_mode=spec.refresh.mode,
432
+ traits=spec.traits,
433
+ )