Skip to content

API reference

The reference is generated from lattice-core's docstrings and type annotations. It documents each module's public API: the names in its __all__.

lattice_core.schema

JSON Schema helpers for Pydantic models.

A member of a discriminated union, such as a command, gives its tag (type or kind) a default, so Python code can leave it out. A client can't: the discriminator needs the tag to tell the members apart. Pydantic's schema calls a field with a default optional, so each member's config names tag_required, which says what the discriminator does.

tag_required

tag_required(schema: dict[str, JsonValue]) -> None

Mark a model's fields fixed to one value, its tags, required in its JSON Schema.

Name it as a model's json_schema_extra.

lattice_core.integrations

Integrations: what a workspace offers beyond the libraries' own, each switched on or off.

An integration is named by a key, such as tracker:plane or ui:forms. Each is declared once, in a registry, with whether it is on where no one has set it and a sentence that says what it is: KNOWN is lattice's, and an application that adds its own declares one more Integration and passes registry both.

Before an integration acts in a workspace, its package asks an Integrations. grantr's adapter answers from the deployment's installation, the tenant's setting and the workspace's together: it is on only where each of them leaves it on, and a level no one has set inherits the one above (lattice ADR-0027). Without a store, Defaults answers each key's default.

The port only answers yes or no, so an external provider, such as a feature-flag service, can only narrow it: an Integrations of the application's that asks grantr's first, and then the provider, is on only where both are. lattice builds none (lattice ADR-0027).

KEY module-attribute

KEY: Final = re.compile(
    "[a-z][a-z0-9-]*(:[a-z][a-z0-9-]*)?"
)

An integration key's form: a name, or a family, a colon and a name, such as files:wordcraft, in lowercase letters, digits and hyphens.

KNOWN module-attribute

KNOWN: Final = registry(
    Integration(
        "tracker:plane",
        default=True,
        description="Plane: rules file and change work items in a Plane project.",
    ),
    Integration(
        "tracker:vikunja",
        default=True,
        description="Vikunja: rules file and change tasks in a Vikunja project.",
    ),
    Integration(
        "files:wordcraft",
        default=True,
        description="WordCraft: Word documents kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:gridcraft",
        default=False,
        description="GridCraft: Excel workbooks kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:deckcraft",
        default=False,
        description="DeckCraft: PowerPoint presentations kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:pdfcraft",
        default=False,
        description="PdfCraft: PDF documents kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:cadcraft",
        default=False,
        description="CADCraft: DXF and DWG drawings kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:photocraft",
        default=False,
        description="PhotoCraft: Photoshop documents kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:vectorcraft",
        default=False,
        description="VectorCraft: SVG drawings kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:designcraft",
        default=False,
        description="DesignCraft: InDesign layouts, as IDML, kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:figcraft",
        default=False,
        description="FigCraft: interface designs kept as native files, edited by people and agents.",
    ),
    Integration(
        "files:filmcraft",
        default=False,
        description="FilmCraft: video projects rendered and exported, one file at a time.",
    ),
    Integration(
        "files:effectcraft",
        default=False,
        description="EffectCraft: motion graphics rendered and exported, one file at a time.",
    ),
    Integration(
        "files:soundcraft",
        default=False,
        description="SoundCraft: audio sessions mixed down and exported, one file at a time.",
    ),
    Integration(
        "files:lightcraft",
        default=False,
        description="LightCraft: photos developed and exported, one file at a time.",
    ),
    Integration(
        "ui:forms",
        default=True,
        description="Forms: the agent asks with a form, and people fill it in the chat.",
    ),
    Integration(
        "ui:dashboards",
        default=True,
        description="Dashboards: the agent shows figures and charts as a dashboard in the chat.",
    ),
    Integration(
        "ui:mini-apps",
        default=False,
        description="Mini apps: small apps the agent writes, run apart from the portal.",
    ),
    Integration(
        "excalidraw",
        default=True,
        description="Diagrams: Excalidraw drawings that people and agents draw on together.",
    ),
    Integration(
        "markdown:math",
        default=True,
        description="Math: formulas in messages and documents, typeset.",
    ),
    Integration(
        "markdown:mermaid",
        default=False,
        description="Mermaid: diagrams written as text in messages, drawn.",
    ),
)

lattice's integrations, by key: one Integration each.

Integration dataclass

Integration(key: str, default: bool, description: str)

One integration: its key, whether it is on where no one has set it, and what it is.

Raises:

Type Description
ValueError

If the key isn't of KEY's form, or the description is empty.

description instance-attribute

description: str

One sentence, for the admin who switches it.

Integrations

Bases: Protocol

Whether an integration is on in a tenant's workspace: the port its package asks first.

It is asked before each command, and each model request, of an integration's, so an implementation answers from what it reads cheaply. A key it doesn't know is off. Its arguments are positional, so an implementation names them as it likes.

is_enabled async

is_enabled(
    tenant_id: str, workspace_id: str, key: str
) -> bool

Return whether the integration key is on in the tenant's workspace.

Defaults

Defaults(known: Mapping[str, Integration] = KNOWN)

Each integration's default, everywhere: the answer without a store.

Parameters:

Name Type Description Default
known Mapping[str, Integration]

The integrations it knows; KNOWN if omitted. Any other key is off.

KNOWN

is_enabled async

is_enabled(
    _tenant_id: str, _workspace_id: str, key: str
) -> bool

Return the integration's default, or False for a key it doesn't know.

registry

registry(
    *integrations: Integration,
) -> Mapping[str, Integration]

Return the integrations by key, in the order given, read-only.

Raises:

Type Description
ValueError

If two declare one key.

lattice_core.tags

The tags that file a model request's spend under its tenant and its workspace.

LiteLLM adds up each tag's spend across every request that carries it, whichever tenant made it. Workspace ids are each tenant's own, so workspace:<id> alone mixes tenants whose workspaces share an id. workspace_tag names one tenant's workspace, and parse_workspace_tag reads it back (lattice ADR-0019). The gateways write the tags and the portal reads them, so this module is the format's one implementation.

WORKSPACE_TAG_PREFIX module-attribute

WORKSPACE_TAG_PREFIX: Final = 'tenant-workspace:'

What every workspace_tag starts with.

workspace_tag

workspace_tag(tenant_id: str, workspace_id: str) -> str

Return tenant-workspace:<tenant>/<workspace>, with each id percent-encoded.

Each id is encoded in UTF-8, every byte but those of ASCII letters, digits and -._~ (RFC 3986's unreserved characters) as %XX in uppercase hex, as urllib.parse.quote(id, safe="") does. So the one / in the tag is the separator, the tag holds no comma, which LiteLLM's tag queries split their lists on, and parse_workspace_tag reads both ids back exactly. An id that isn't valid UTF-8, such as one holding a lone surrogate, raises UnicodeEncodeError.

parse_workspace_tag

parse_workspace_tag(tag: str) -> tuple[str, str] | None

Return the tenant and workspace ids a workspace_tag names, or None for another tag.

It strips the prefix, splits at the first /, and percent-decodes each half as UTF-8. A tag with the prefix but no /, or whose ids aren't percent-encoded UTF-8, is no workspace tag.

request_tags

request_tags(
    tenant_id: str, workspace_id: str
) -> list[str]

Return the tags of a model request in a tenant's workspace, in order.

  • tenant:<tenant id>
  • workspace:<workspace id>
  • workspace_tag's tenant-workspace:<tenant>/<workspace>

lattice_core.text

Checks on text before it is stored.

PostgreSQL's text can't hold a NUL character, so a storage that may be PostgreSQL refuses one before anything is stored. holds_nul finds one, and the caller raises its own error.

holds_nul

holds_nul(*values: object) -> bool

Whether any string values hold, as models, mappings or collections, has a NUL character.

A model is read as its model_dump(), and a mapping's keys are read as well as its values. Bytes are not text, and are passed over.

lattice_core.sql

The sql extra.

SQLite engines that serialize transactions, for storage that locks rows to change them.

SQLite has no row locks and ignores FOR UPDATE. Instead, every transaction takes the database's write lock as it begins (BEGIN IMMEDIATE), so a transaction's reads cannot go stale before it writes, and concurrent writers wait for each other instead of failing.

Since transactions run one at a time anyway, an engine keeps a single connection. A process's transactions queue for it and take turns in the order they begin, as they do in memory, instead of polling for the write lock; only other processes contend for the lock.

create_sqlite_engine

create_sqlite_engine(
    url: str | URL, **kwargs: Any
) -> AsyncEngine

Create an async SQLite engine whose transactions take turns.

Every transaction on the engine, reads included, holds the database's write lock, so transactions run one at a time, on the engine's one connection. A transaction waits for the connection for up to the engine's pool_timeout (30 seconds), and for another process's lock for up to the timeout connect argument of Python's sqlite3 (5 seconds unless connect_args says otherwise). Use a database file (or a shared-cache memory database).

Parameters:

Name Type Description Default
url str | URL

A SQLite URL for an async driver, such as sqlite+aiosqlite:///app.db.

required
**kwargs Any

Passed to SQLAlchemy's create_async_engine. Give poolclass (with its own arguments) to pool connections differently.

{}

Returns:

Type Description
AsyncEngine

The engine. Dispose of it when you are done.

Awaiting a database call to its end, and locking a row to change it.

A caller can be cancelled at any await: by a timeout, a stopped worker or a closed connection. A database call cancelled halfway can leave a connection, a lock or a transaction behind, so storage awaits each call to its end, and raises the cancellation after it.

to_the_end async

to_the_end(call: Awaitable[T]) -> T

Await a database call to its end; a cancellation that came meanwhile is raised after.

awaited_to_the_end

awaited_to_the_end(
    method: Callable[P, Awaitable[T]],
) -> Callable[P, CoroutineType[Any, Any, T]]

Await every call of a method to its end, with to_the_end.

lock_row async

lock_row(
    session: AsyncSession,
    model: type[R],
    key: object,
    new: R,
) -> R

Lock a row (SELECT ... FOR UPDATE), inserting new first if it does not exist.

key is the row's primary key, as SQLAlchemy's AsyncSession.get takes it: a value, a tuple or a mapping.

Row-level security: each transaction tells PostgreSQL its tenant (lattice RFC-0001).

Storage that turns the option on opens its sessions as TenantSession, which sets TENANT_SETTING to its tenant, for the transaction only, as the transaction begins. Policies like those tenant_policy makes then show each transaction only its tenant's rows. A pooled connection never carries a tenant into its next transaction, and a session that names no tenant can't begin one.

TENANT_SETTING module-attribute

TENANT_SETTING: Final = 'lattice.tenant_id'

The PostgreSQL setting that holds a transaction's tenant.

TENANT_KEY module-attribute

TENANT_KEY: Final = 'tenant_id'

The key of a TenantSession's tenant in its info.

CURRENT_TENANT module-attribute

CURRENT_TENANT: Final = (
    f"current_setting('{TENANT_SETTING}', true)"
)

The transaction's tenant, in SQL: null, or empty, when it names none, which no row's is.

TenantSession

Bases: Session

A session that tells PostgreSQL its tenant as each transaction begins.

It sets TENANT_SETTING to the tenant in its info. SQLite has no row-level security, so there it sets nothing, but a session must still name its tenant.

tenant_policy

tenant_policy(
    table: str, column: str = "tenant_id"
) -> tuple[str, ...]

The DDL that keeps a table's rows to their tenant, on PostgreSQL.

table and column go into the DDL as they are, unquoted, unlike grant's role and tables: pass only trusted identifiers, such as a model's own table name. The policy is enabled, not forced, so the tables' owner isn't held by it. A transaction sees, changes and adds only rows whose column is its tenant; one that names none, or an empty one, sees nothing.

grant

grant(
    connection: Connection,
    role: str,
    tables: Collection[str],
) -> None

Let a role use some tables as storage does, and their sequences, on PostgreSQL.

It grants the role USAGE on the current schema, reading and writing each table, and USAGE on the sequences the tables own. Granting again changes nothing.

lattice_core.otel

OpenTelemetry for applications, in one call.

Two modules, each the one path to its names:

  • lattice_core.otel.contribution: the contract a library's contribution follows. It needs no extra, so a library's telemetry layer can name it (lattice ADR-0020).
  • lattice_core.otel.configure: configure_telemetry and its handle, the otel extra. Each library's otel module calls it with its own contribution, and re-exports the rest, so an application uses the library's.

Nothing here names a library.

lattice_core.otel.contribution

No extra.

The contract between a library and configure_telemetry: what a contribution holds.

It needs no extra (lattice ADR-0020). OpenTelemetry's SDK is named in the annotations alone, so a library's telemetry layer, which records through the API, can import the contract, and a library's contribution can be built wherever the SDK is installed.

Instrumented module-attribute

Instrumented = Literal[
    "fastapi", "sqlalchemy", "asyncpg", "httpx"
]

A library configure_telemetry can instrument. httpx covers httpx and httpx2.

MetricsDetail module-attribute

MetricsDetail = Literal['workspace', 'tenant', 'none']

How much tenancy detail the libraries' metrics keep: part of the contributions' contract.

TelemetryContribution

Bases: Protocol

What configure_telemetry reads from a library's contribution: the port.

It reads these four fields and nothing else, so any value that has them will do: a library's own, which no other library imports, or an application's Contribution for its metrics.

name property

name: str

The library's name, which is its instrumentation scope; one contribution per name.

metric_views property

metric_views: Callable[[MetricsDetail], Sequence[View]]

Return the views that apply the library's metric cardinality policy at a detail.

should_export_span property

should_export_span: Callable[[ReadableSpan], bool]

Return whether a span belongs in the library's traces: Langfuse's span filter.

instrument property

instrument: frozenset[str]

The instrumentations the library advises: those that trace its work.

Names configure_telemetry does not know (see Instrumented) are ignored.

Contribution dataclass

Contribution(
    name: str,
    metric_views: Callable[[MetricsDetail], Sequence[View]],
    should_export_span: Callable[[ReadableSpan], bool],
    instrument: frozenset[Instrumented],
)

A library's contribution to an application's telemetry.

Parameters:

Name Type Description Default
name str

The library's name, which is its instrumentation scope.

required
metric_views Callable[[MetricsDetail], Sequence[View]]

Returns the views that keep only the attributes the library's metrics may carry at a level of detail.

required
should_export_span Callable[[ReadableSpan], bool]

Whether a span belongs in the library's traces.

required
instrument frozenset[Instrumented]

The instrumentations the library advises.

required

lattice_core.otel.configure

The otel extra.

configure_telemetry: the OpenTelemetry SDK, exporters and instrumentations in one call.

Each library's own configure_telemetry calls this one with the application's contributions, and its own last; lattice_core.otel.contribution is the contract they follow. The instrumentations import the libraries they instrument, so each is imported only when it is used: an application that does not use FastAPI or SQLAlchemy does not need them installed.

INSTRUMENTED module-attribute

INSTRUMENTED: tuple[Instrumented, ...] = (
    "fastapi",
    "sqlalchemy",
    "asyncpg",
    "httpx",
)

Every library configure_telemetry can instrument.

LangfuseMode module-attribute

LangfuseMode = Literal['traces', 'scores']

What an application sends Langfuse itself.

  • "traces": the traces, filtered by the contributions' span filters, as well as each session, user and tags, and scores
  • "scores": the session, user and tags, set on the spans the Collector sends Langfuse, and scores, but no spans: for a Collector that sends Langfuse every trace already

BAGGAGE_KEYS module-attribute

BAGGAGE_KEYS: frozenset[str] = frozenset({'session.id'})

The baggage entries copied onto every span: the session, which a library places in baggage around the work it traces.

TENANT_SUFFIX module-attribute

TENANT_SUFFIX: Final = '.tenant.id'

A contribution's tenant attribute is its name and this, as in <name>.tenant.id. The log records of a span that names one, or of any span inside it, carry it too, so the Collector can filter every signal by tenant (lattice RFC-0002).

URL_ATTRIBUTES module-attribute

URL_ATTRIBUTES: Final = (
    "url.full",
    "url.query",
    "http.url",
    "http.target",
)

The span attributes that hold a request's URL, or its query, under the current semantic conventions and the older ones. FastAPI's server spans don't set url.full; it is here so a span that does is redacted too.

TelemetryHandle

TelemetryHandle(
    *,
    tracer_provider: TracerProvider,
    meter_provider: MeterProvider,
    logger_provider: LoggerProvider | None,
    instrumentation: InstrumentationSettings,
    instrument: Collection[Instrumented] = (),
    engines: Sequence[AsyncEngine | Engine] = (),
    log_handler: Handler | None = None,
    langfuse: Langfuse | None = None,
)

What configure_telemetry set up, and the way to shut it down.

Use it as a context manager, or call shutdown when the application stops.

Parameters:

Name Type Description Default
tracer_provider TracerProvider

The SDK's tracer provider.

required
meter_provider MeterProvider

The SDK's meter provider.

required
logger_provider LoggerProvider | None

The SDK's logger provider, if logs are exported.

required
instrumentation InstrumentationSettings

pydantic-ai's instrumentation settings.

required
instrument Collection[Instrumented]

The libraries to instrument now.

()
engines Sequence[AsyncEngine | Engine]

SQLAlchemy engines to instrument now.

()
log_handler Handler | None

A handler to add to the root logger until shutdown.

None
langfuse Langfuse | None

A Langfuse client to shut down with the rest.

None

tracer_provider instance-attribute

tracer_provider = tracer_provider

The SDK's tracer provider.

meter_provider instance-attribute

meter_provider = meter_provider

The SDK's meter provider.

logger_provider instance-attribute

logger_provider = logger_provider

The SDK's logger provider, when logs are exported.

instrumentation instance-attribute

instrumentation = instrumentation

pydantic-ai's instrumentation settings over these providers.

langfuse instance-attribute

langfuse = langfuse

The Langfuse client, when the application sends Langfuse its traces or its scores.

capability

capability() -> Instrumentation

Return pydantic-ai's Instrumentation capability, for an agent's capabilities.

instrument_app

instrument_app(app: FastAPI) -> None

Trace and measure a FastAPI application's requests.

FastAPI's instrumentation only reaches applications whose class it patched first; pass an application here to instrument it whenever it was created. An access_token in a request's URL, as a WebSocket carries a browser's token, is redacted from its span.

instrument_engine

instrument_engine(engine: AsyncEngine | Engine) -> None

Trace a SQLAlchemy engine's queries and measure its connection pool.

create_async_engine is imported before telemetry is configured in most applications, so its engines are not instrumented automatically: pass them here, or to configure_telemetry(engines=...).

shutdown

shutdown() -> None

Remove the instrumentation, and flush and shut down the providers. Idempotent.

installed

installed() -> set[Instrumented]

Return the instrumentable libraries that are installed.

httpx counts when either httpx or httpx2 is: one instruments the other's clients too.

configure_telemetry

configure_telemetry(
    *contributions: TelemetryContribution,
    service_name: str,
    service_version: str | None = None,
    environment: str | None = None,
    otlp_endpoint: str | None = None,
    otlp_headers: Mapping[str, str] | None = None,
    instrument: Collection[Instrumented] | None = None,
    engines: Sequence[AsyncEngine | Engine] = (),
    include_content: bool = False,
    metrics_detail: MetricsDetail = "workspace",
    logs: bool = True,
    span_exporter: SpanExporter | None = None,
    metric_reader: MetricReader | None = None,
    log_exporter: LogRecordExporter | None = None,
    span_processors: Sequence[SpanProcessor] = (),
    langfuse: LangfuseMode | None = None,
    langfuse_options: Mapping[str, Any] | None = None,
    set_global: bool = True,
) -> TelemetryHandle

Set up OpenTelemetry for an application: providers, OTLP export and instrumentation.

Call it once per process, with every contribution: a second call builds a second set of providers beside the first, patches the instrumented libraries again and adds a second handler to the root logger, so it warns while a handle it returned is not shut down. A library's own configure_telemetry passes the application's contributions first and its own last, so an application's contribution of the same name wins. It sets up:

  • tracer, meter and logger providers, with service.name, service.version and deployment.environment.name on their resource
  • OTLP over HTTP to otlp_endpoint, or to the endpoint the OTEL_EXPORTER_OTLP_* environment variables name (a local Collector by default)
  • every contribution's views, which apply each library's metric cardinality policy at metrics_detail
  • a baggage processor that copies the session in baggage onto every span in it
  • the open instrumentations the contributions advise (FastAPI, SQLAlchemy, httpx and httpx2), or those instrument names, with the stable HTTP semantic conventions (OTEL_SEMCONV_STABILITY_OPT_IN=http, unless it is set already)
  • pydantic-ai's instrumentation settings, which TelemetryHandle.capability wraps for an agent
  • with langfuse, a Langfuse client on the same tracer provider (the langfuse extra), which sends Langfuse whole traces, or only scores

Parameters:

Name Type Description Default
*contributions TelemetryContribution

The libraries' contributions (TelemetryContribution), and the application's own. One is kept per name, the first given.

()
service_name str

The service's name, as it appears in every backend.

required
service_version str | None

The service's version.

None
environment str | None

The deployment environment, such as production.

None
otlp_endpoint str | None

The OTLP/HTTP base URL, such as http://collector:4318. Defaults to the OTEL_EXPORTER_OTLP_ENDPOINT environment variable.

None
otlp_headers Mapping[str, str] | None

Headers for every OTLP request, such as a backend's credentials.

None
instrument Collection[Instrumented] | None

Which libraries to instrument. Defaults to those the contributions advise that are installed.

None
engines Sequence[AsyncEngine | Engine]

SQLAlchemy engines that already exist, to trace their queries.

()
include_content bool

Whether pydantic-ai records prompts, completions and tool arguments.

False
metrics_detail MetricsDetail

How much tenancy detail the libraries' metrics keep.

'workspace'
logs bool

Whether to export the logging module's records through OTLP.

True
span_exporter SpanExporter | None

Where spans go instead of OTLP, such as an in-memory exporter in tests.

None
metric_reader MetricReader | None

How metrics are read instead of a periodic OTLP export.

None
log_exporter LogRecordExporter | None

Where log records go instead of OTLP.

None
span_processors Sequence[SpanProcessor]

More span processors to add, such as a backend's own.

()
langfuse LangfuseMode | None

What the application sends Langfuse itself (LangfuseMode): "traces", with a span filter that keeps a span any contribution keeps, and Langfuse's own LLM spans; or "scores", when the Collector sends Langfuse the traces. Its keys come from the LANGFUSE_* environment variables or langfuse_options.

None
langfuse_options Mapping[str, Any] | None

Passed to Langfuse(...); a should_export_span here replaces the filter langfuse chooses.

None
set_global bool

Whether to make the providers the global ones, which the libraries, pydantic-ai and the instrumentations default to.

True

Returns:

Type Description
TelemetryHandle

A handle that shuts it all down.

Warns:

Type Description
RuntimeWarning

If a handle it returned before is not shut down yet.

redact_access_token

redact_access_token(text: str) -> str

Return a URL, a query or a path with each access_token parameter's value hidden.

access_token is OAuth's query parameter for a bearer token (RFC 6750, section 2.3). A parameter is one however its name is encoded, as servers decode it, and every other parameter is kept as it is, byte for byte.

lattice_core.langfuse

The langfuse extra.

Span filters for a Langfuse client: the langfuse extra.

span_filter

span_filter(
    *keeps: Callable[[ReadableSpan], bool],
) -> Callable[[ReadableSpan], bool]

Return a span filter that keeps Langfuse's LLM spans and every span one of keeps keeps.

configure_telemetry(langfuse="traces") builds its filter this way, from the libraries' contributions, so an application of several libraries keeps each one's spans.

no_spans

no_spans(span: ReadableSpan) -> bool

Keep no spans: the filter for a client that only sets trace attributes and sends scores.

configure_telemetry(langfuse="scores") uses it when a Collector sends Langfuse every trace already, so no span arrives twice. The client's span processor still sets each session, user and tags on the spans, which reach Langfuse through the Collector.

lattice_core.litellm

The litellm extra.

Models over a LiteLLM proxy: the litellm extra.

PROPAGATED_TAG_PREFIXES module-attribute

PROPAGATED_TAG_PREFIXES: Final = ('rule:', 'kind:')

The kinds of trace tag that reach LiteLLM: a rule's name and an artifact's kind, which are as many as the application defines. thread: and chain: tags stay in Langfuse, since each thread and each chain would add a row to LiteLLM's tag spend per day, without bound; a thread's cost is read from Langfuse's session. tenant: and workspace: are in a request already.

litellm_model

litellm_model(
    model: str,
    *,
    api_base: str | None = None,
    api_key: str | None = None,
    http_client: AsyncClient | None = None,
    settings: ModelSettings | None = None,
) -> OpenAIChatModel

Return a pydantic-ai model that calls a LiteLLM proxy's model group.

Routing, fallbacks, budgets and guardrails are the proxy's configuration; the model only names the group.

Parameters:

Name Type Description Default
model str

The proxy's model group, such as claude-sonnet.

required
api_base str | None

The proxy's URL, such as http://litellm:4000. Defaults to the provider's environment variables.

None
api_key str | None

The proxy key used when a request carries no tenant's key.

None
http_client AsyncClient | None

The HTTP client, for tests or a shared connection pool.

None
settings ModelSettings | None

The model's default settings, as every pydantic-ai model takes them, such as a temperature. An agent's or a run's model_settings override them key by key.

None

add_propagated_tags

add_propagated_tags(tags: Sequence[str]) -> list[str]

Return tags and the bounded trace tags Langfuse is propagating in the current context.

The trace tags whose prefix is in PROPAGATED_TAG_PREFIXES, such as rule:<name> or kind:<artifact kind>, reach LiteLLM's spend and logs this way, whichever package set them (lattice ADR-0022). A tag already in tags is not added again, so the request lists each once, in order: tags, then the propagated tags in the order Langfuse holds them. Without Langfuse, or outside a propagate_attributes block, it returns tags.

Parameters:

Name Type Description Default
tags Sequence[str]

The tags a request carries already.

required

lattice_core.logs

Logging left as the application configured it.

logging_left_alone

logging_left_alone() -> Generator[None]

Put the root logger's handlers and level back as they were when the block ends.

Some libraries configure logging as they start, such as the MCP SDK's MCPServer, which calls logging.basicConfig as it is built, with no option to skip it: if the root logger has no handlers yet, the whole process then logs at INFO through a rich handler. Logging is the application's to configure, so the handlers the block added are removed and closed, and the level restored.

lattice_core.refusals

The refusals a REST router documents.

Every router of lattice's packages answers a refused request with the same body, Refused, and documents the same responses, so a client reads a refusal one way (lattice ADR-0021). The types are the OpenAPI document's own: moving them here changes none of their names. It needs pydantic alone: a router passes refusals(...) to FastAPI's APIRouter(responses=...).

RejectionDetail pydantic-model

Bases: BaseModel

A rejection, as REST sends it: its type, its message and its typed details.

Config:

  • extra: allow

Fields:

FieldError pydantic-model

Bases: BaseModel

One problem FastAPI found with a request's parameters or body.

Config:

  • extra: allow

Fields:

Refused pydantic-model

Bases: BaseModel

The body of a refused request.

detail is the rejection, the message of a resolver's refusal, or the problems FastAPI found with the request.

Fields:

refusals

refusals(*statuses: int) -> dict[int | str, dict[str, Any]]

Return the refused responses to document, for APIRouter(responses=...).

Each status is described in words of its own, since FastAPI's default is Python's phrase for the status, which changes between versions. A router documents only the statuses it answers: artifactr's and reflexr's refuse with 400, 401, 403, 404, 409 and 422.

Parameters:

Name Type Description Default
*statuses int

The HTTP statuses a router's routes refuse with.

()