Skip to main content

kedi-typesafe

Framework-native integrations for using TypeSafe AI's Jev models with Kedi's supported agent frameworks. Core Jev schema translation and the Pydantic AI and LangChain integrations ship as one kedi-typesafe package.

Pydantic AI

Install the package and provide TYPESAFE_API_KEY in your environment:

pip install kedi-typesafe
export TYPESAFE_API_KEY="your-key"
from typing import Literal

from pydantic import BaseModel, Field
from pydantic_ai import Agent

from kedi_typesafe.integrations.pydantic import TypeSafeModel


class Triage(BaseModel):
    urgent: bool = Field(description="The request needs action today")
    route: Literal["billing", "support", "other"] = Field(
        description="Which team should handle the request?"
    )


model = TypeSafeModel("jev-latest")
agent = Agent(model, output_type=Triage)
result = agent.run_sync("I was charged twice. Please fix this today.")
print(result.output)

The surface supports booleans, finite string Literal/Enum values, constrained text extraction, and nested Pydantic models composed of supported fields. Pydantic's native EmailStr and pydantic-extra-types PhoneNumber schemas are recognized automatically. A JSON Schema pattern is treated as a regex candidate extractor; other string fields can provide an explicit field extractor:

Install kedi-typesafe[email], kedi-typesafe[phone], or kedi-typesafe[all] when those optional Pydantic field types are used.

from kedi_typesafe import RegexExtractor
from kedi_typesafe.integrations.pydantic import TypeSafeModel

model = TypeSafeModel(
    text_extractors={"ticket_id": RegexExtractor(pattern=r"CASE-\d+")},
)

Candidate extraction is deterministic and Jev only selects among values found in state, following TypeSafe's pre-parsed extraction pattern. Jev is not a free-form text-generation model, so unconstrained strings, arbitrary numeric extraction, recursive schemas, media, and omitted/defaulted fields are rejected before a provider request is made. Nullable choices and extraction are supported: no-match is an explicit option, not a confidence cutoff. Missing candidates resolve locally to None only for nullable extraction. Provider failures remain exceptions.

Boolean answers use Jev's Noul probability and a configurable threshold. The default decision is strictly probability > 0.85; equality is false. Pass threshold=... to either model constructor, or typesafe_threshold per request to override it without mutating the model. This is a decision policy, not a guarantee of 85% empirical accuracy. Version 0.2 changes the legacy default of 0.5.

agent = Agent(TypeSafeModel(threshold=0.9), output_type=Triage)
result = agent.run_sync("Please refund the duplicate charge", model_settings={"typesafe_threshold": 0.95})

Typed Questions

from typing import Annotated, Literal
from pydantic import BaseModel, Field
from kedi_typesafe import BooleanCriteria, ChoiceCriteria, Rubric

class Review(BaseModel):
    probability: Annotated[
        float,
        Field(ge=0, le=1, description="Is a refund requested?"),
    ]
    quality: Annotated[
        float,
        Rubric(["Incorrect", "Partially correct", "Correct"]),
    ]
    urgent: Annotated[
        bool,
        BooleanCriteria(
            true="Immediate harm",
            false="No established immediate harm",
        ),
    ]
    route: Annotated[
        Literal["billing", "support"],
        ChoiceCriteria({"billing": "Payments", "support": "Product help"}),
    ]
    labels: list[Literal["refund", "bug"]]

All independent fields share one provider request. Raw bounded probabilities are not thresholded. Rubric floats retain fractional positions; integer rubric fields round to the nearest level, with ties rounded upward. The original score remains in metadata. Each multi-label option gets an independent Noul question and is included only above the configured threshold, in declaration order. Questions that depend on another answer belong in subsequent calls.

Rubric requires 2-10 described levels. ChoiceCriteria keys must match the declared options. Criteria may contain JSON objects or arrays; they are not flattened into strings. Unsupported or conflicting metadata is rejected. List/numeric constraints are validated, never silently repaired.

For both a probability and its boolean decision, request the probability once and compare it in Python. A second model call is unnecessary.

Evidence and Lifecycle

Pydantic responses expose provider_details["typesafe"] and metadata["typesafe"]. LangChain messages expose response_metadata["typesafe"]; use include_raw=True with with_structured_output to retain them. Evidence includes raw probabilities, Choice/Score distributions and confidence, rubric legends, thresholds, and reported usage. Noul has no separate provider confidence. The versioned payload also includes each answer's output path and request, state, question, and decision-configuration fingerprints. The combined request fingerprint changes when the effective model or boolean threshold changes. Unknown usage remains None in evidence even when framework counters need zero.

When this package is used through Kedi, kedi.decision_info(name) attaches that final-response evidence to the corresponding output binding. The helper belongs to Kedi, not this standalone package; kedi-typesafe remains usable without importing Kedi or storing a global last response.

Reuse the model for warm connections. async with TypeSafeModel() and async with TypeSafeChatModel() close owned connections; borrowed SDK clients remain caller-owned. Async clients are event-loop-local. Pydantic AI streaming emits the complete answer rather than pretending Jev generates incremental tokens. No evidence is stored in a global last-result slot.

LangChain

Install kedi-typesafe[langchain] and use the native structured-output surface:

from typing import Literal

from langchain.agents import create_agent
from pydantic import BaseModel, Field

from kedi_typesafe.integrations.langchain import TypeSafeChatModel


class Triage(BaseModel):
    urgent: bool = Field(description="The request needs action today")
    route: Literal["billing", "support", "other"] = Field(
        description="Which team should handle the request?"
    )


agent = create_agent(TypeSafeChatModel("jev-latest"), response_format=Triage)
result = agent.invoke(
    {"messages": [{"role": "user", "content": "I was charged twice. Fix this today."}]}
)
print(result["structured_response"])

TypeSafeChatModel.with_structured_output(...) is also supported for direct LangChain model use. The same Jev schema restrictions described above apply. Free-form invocation, media, stop sequences, and unsupported model settings fail before a provider request. Tool and retry history are retained. Set typesafe_threshold in .bind(...) or a request-local model copy.

Explicit Tool Routing

Attach tools only when the application intends routing. Jev can select a zero-argument tool; the framework executes it through its normal hooks and approval policy. Jev does not fabricate arguments. An argument-requiring tool raises ToolCallProposed, with its name and probability. Use an explicit downstream handler or Pydantic AI FallbackModel if desired; this package does not silently call another model. LangChain exports its proposal from kedi_typesafe.integrations.langchain.

typesafe_tool_call_threshold controls routing independently (default 0.6, upstream semantics). Provide a meaningful output model docstring or agent instructions for Pydantic routing. The routing decision is another question in the same batch, not another provider request. Routing is not authorization: never treat a positive Jev assessment as permission to bypass deterministic tool approval or workspace policy.

Integration Boundaries

The Pydantic model extends the official implementation in Pydantic AI 2.45.0. The LangChain chat model delegates provider transport to langchain-typesafe==0.0.1a2; a narrow pinned private transport bridge avoids nesting a second LLM trace under the chat-model trace. Shared schema lowering and decoding provide the additional thresholds, criteria, and extraction. The beta dependency and private upstream bridges are covered by contract tests.

Sampling settings do not apply. Configure connection timeout in the constructor. Client-level retry behavior belongs to the chosen transport; the wrappers do not add a second retry loop. Install kedi-typesafe[all] and run examples/capabilities.py for both standalone integrations. The seven programs under examples/kedi/ require Kedi; examples/validate_kedi.py runs them on both adapters. Their semantic outputs are model judgments, not guaranteed expected answers.

Email and Phone Extraction

Install the field validators:

pip install 'kedi-typesafe[email,phone]'
from pydantic import BaseModel, EmailStr, Field
from pydantic_ai import Agent
from pydantic_extra_types.phone_numbers import PhoneNumber

from kedi_typesafe.integrations.pydantic import TypeSafeModel


class Contact(BaseModel):
    email: EmailStr = Field(description="The current support email, not the archived address")
    phone: PhoneNumber = Field(description="The current support phone number")


agent = Agent(TypeSafeModel(), output_type=Contact)
result = agent.run_sync(
    "Archived contact: old@example.com. Current support: help@example.com, +1 202-555-0100."
)
print(result.output)

Email and phone candidates are extracted from the supplied text automatically. Jev selects the candidate matching the field description; Pydantic then validates the result. Phone numbers use Pydantic's normalized format, for example tel:+1-202-555-0100. Provide international numbers with a country code. Missing candidates or a no-match answer raise an extraction error rather than inventing a value, unless the declared type is nullable. Fields must be required; a required nullable field may contain None but may not be omitted.

For LangChain, use the same Contact schema with create_agent(TypeSafeChatModel(), response_format=Contact) and install kedi-typesafe[langchain,email,phone].

Runnable Examples

After cloning this repository, run uv sync --all-extras and set TYPESAFE_API_KEY.

Example Command
Pydantic classification uv run examples/pydantic_agent.py
LangChain classification uv run examples/langchain_agent.py
Pydantic email and phone uv run examples/pydantic_contacts.py
LangChain email and phone uv run examples/langchain_contacts.py

These examples call the real TypeSafe API. An async context manager closes owned connections after use; it is optional for constructing the model. Reusing a model within the same event loop allows its HTTP connection to be reused.

The Kedi reply-review example combines generative drafting with Jev decisions and an optional revision step. It includes the actual live output and decision controls, and requires a Kedi checkout with the decision-metadata API plus an OpenRouter API key.

Probability and Rubric Bounds

Probability is a reusable finite float annotation in [0, 1]:

from typing import Annotated
from pydantic import BaseModel
from kedi_typesafe import Probability, Rubric

class Assessment(BaseModel):
    supported_probability: Probability
    quality: Annotated[
        float,
        Rubric(["Incorrect", "Partly correct", "Correct"]),
    ]

The rubric contributes and validates [0, N-1] bounds automatically. Fractional float scores are retained. You do not need another Field(ge=..., le=...) for those same bounds. General constraints remain Pydantic-compatible. When using Kedi, from kedi import Constraints offers a validation-only helper and from kedi.typesafe import Probability, Rubric exposes these Jev primitives. The standalone package does not depend on Kedi.

See release validation for reproducible synthetic batching checks and their limitations. Version 0.2 changes the default boolean threshold from 0.5 to 0.85 while retaining the strict > comparator; explicitly set the old threshold if an application requires that policy.

Development

uv sync --all-extras --group dev
make prod

Normal tests use deterministic typed fakes and never call TypeSafe. Live API validation is kept separate so CI and local quality gates do not consume credentials or incur cost.

Release files for kedi-typesafe 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kedi-typesafe 0.2.1
File Size Uploaded
kedi_typesafe-0.2.1.tar.gz 211.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kedi-typesafe 0.2.1
File Interpreter ABI Platform
kedi_typesafe-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 243.6 kB

Release files / kedi_typesafe-0.2.1.tar.gz

Download URL kedi_typesafe-0.2.1.tar.gz
Size 211.8 kB
Tags Source
SHA-256 checksum
How to use checksums
213e05688a2f6e0a77949fa2f06acd2a39574f2f4890403520a12d412b0ebdcb
BLAKE2b-256 checksum
How to use checksums
8b8e14aa0e8fd4c071f5d67bbbb06e8cc6bffa72d4a932dce1b0c2700ba73bd8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / kedi_typesafe-0.2.1-py3-none-any.whl

Download URL kedi_typesafe-0.2.1-py3-none-any.whl
Size 31.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4ba738565c9e99b04b594e4df06d4a6a27be6f930a83dd876e465d51ecc914a6
BLAKE2b-256 checksum
How to use checksums
1f5c0ba6ab064c86bea29431f2049c43ed29386848f60cec7da591e1ff592eb1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page