Adds the two Protocol-based client contracts the pipeline steps depend on, plus test-oriented fakes. Real engines (Surya, Ollama) land in Chunk 4. - ix.ocr.client.OCRClient — runtime_checkable Protocol with async ocr(). - ix.genai.client.GenAIClient — runtime_checkable Protocol with async invoke(); GenAIInvocationResult + GenAIUsage dataclasses carry the parsed model, token usage, and model name. - FakeOCRClient / FakeGenAIClient: return canned results; both expose a raise_on_call hook for error-path tests. 8 unit tests across tests/unit/test_ocr_fake.py + test_genai_fake.py confirm protocol conformance, canned-return behaviour, usage/model-name defaults, and raise_on_call propagation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
72 lines
2.1 KiB
Python
72 lines
2.1 KiB
Python
"""GenAIClient Protocol + invocation-result dataclasses (spec §6.3).
|
|
|
|
Structural typing: any object with an async
|
|
``invoke(request_kwargs, response_schema) -> GenAIInvocationResult``
|
|
method satisfies the Protocol. :class:`~ix.pipeline.genai_step.GenAIStep`
|
|
depends on the Protocol; swapping ``FakeGenAIClient`` in tests for
|
|
``OllamaClient`` in prod stays a wiring change.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, Protocol, runtime_checkable
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class GenAIUsage:
|
|
"""Token counters returned by the LLM backend.
|
|
|
|
Both fields default to 0 so fakes / degraded backends can omit them.
|
|
"""
|
|
|
|
prompt_tokens: int = 0
|
|
completion_tokens: int = 0
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class GenAIInvocationResult:
|
|
"""One LLM call's full output.
|
|
|
|
Attributes
|
|
----------
|
|
parsed:
|
|
The Pydantic instance produced from the model's structured output
|
|
(typed as ``Any`` because the concrete class is the response schema
|
|
passed into :meth:`GenAIClient.invoke`).
|
|
usage:
|
|
Token usage counters. Fakes may return a zero-filled
|
|
:class:`GenAIUsage`.
|
|
model_name:
|
|
Echo of the model that served the request. Written to
|
|
``ix_result.meta_data['model_name']`` by
|
|
:class:`~ix.pipeline.genai_step.GenAIStep`.
|
|
"""
|
|
|
|
parsed: Any
|
|
usage: GenAIUsage
|
|
model_name: str
|
|
|
|
|
|
@runtime_checkable
|
|
class GenAIClient(Protocol):
|
|
"""Async LLM backend with structured-output support.
|
|
|
|
Implementations accept an already-assembled ``request_kwargs`` dict
|
|
(messages, model, format, etc.) and a Pydantic class describing the
|
|
expected structured-output schema, and return a
|
|
:class:`GenAIInvocationResult`.
|
|
"""
|
|
|
|
async def invoke(
|
|
self,
|
|
request_kwargs: dict[str, Any],
|
|
response_schema: type[BaseModel],
|
|
) -> GenAIInvocationResult:
|
|
"""Run the LLM; parse the response into ``response_schema``; return it."""
|
|
...
|
|
|
|
|
|
__all__ = ["GenAIClient", "GenAIInvocationResult", "GenAIUsage"]
|