Create your own
Lesson illustration

Create a uv-Managed Project and Execute an Authenticated Jev SDK Request

Welcome back. You have already made a live Jev call from TypeScript and learned to distinguish the chosen option, its probability distribution, and confidence. Now you will reproduce that essential integration in Python, using the project workflow you prefer: uv manages the environment and dependencies, while the SDK reads an API key from the process environment.

By the end of this lesson, you will have a small, reproducible Python project that sends one authenticated system_one request and prints a typed Choice result. The model question is deliberately modest; the main goal is to establish a sound Python integration baseline without placing credentials in code or project files.


The project boundary: reproducible code, external secret

For this course, treat these as separate concerns:

  • pyproject.toml declares what the project needs, including the TypeSafe SDK.
  • uv.lock records the exact resolved dependency set so another machine can reproduce it.
  • .venv is the local isolated Python environment that uv manages.
  • TYPESAFE_API_KEY is a runtime secret. It belongs in the environment, never in source code, pyproject.toml, or version control.

That separation is practical rather than ceremonial. A repository can be shared safely; an API key identifies and authorizes its holder. The key should remain replaceable without changing or redeploying application code.

Read the relevant parts of Astral’s official uv guide before creating the project.

Working on projects | uv

Read Astral’s official uv guide to connect the commands you will use with the project files they maintain.

Start with “Creating a new project,” then continue through “Project structure,” “Managing dependencies,” and “Running commands.” Read the project model first. Then focus on how pyproject.toml, .venv, and uv.lock serve different roles, and on the descriptions of uv add and uv run. In “Project structure,” read the project layout explanation closely: uv creates the environment and lockfile when a project command first needs them.

The routine you will use throughout the Python portions of the course is:

  1. Initialize a project once.
  2. Add a runtime dependency with uv add.
  3. Execute a command with uv run.

There is no need to activate a virtual environment manually for this workflow. uv run selects the project environment and synchronizes it to the lockfile when necessary.


Create the Python project and install the SDK

In a terminal, first confirm that uv is available:

uv --version

Then create a sibling project to your earlier JavaScript work. Choose another location if you prefer, but keep the directory dedicated to this course project.

mkdir jev-python-demo
cd jev-python-demo

uv init .
uv add typesafe-sdk

After uv init ., you should have at least a pyproject.toml, a .python-version, a README, and a starter Python file or package structure. The exact starter layout may differ slightly by your uv version; that is not important for this lesson.

After uv add typesafe-sdk, inspect these files:

git status

You should see that pyproject.toml and uv.lock have changed or been created. The SDK is now a declared project dependency, and uv.lock captures the exact version resolution. Commit both files in a real repository. Do not commit .venv.

Avoid this less reproducible pattern:

pip install typesafe-sdk

It can work inside an activated environment, but it does not necessarily declare the dependency in your project metadata or update the lockfile. uv add typesafe-sdk makes the dependency an explicit part of the project contract.


Configure authentication without exposing the API key

The TypeSafe Python SDK reads TYPESAFE_API_KEY from the environment by default. Create an API key through the TypeSafe console, then set it in the current terminal session.

On macOS or Linux shells:

export TYPESAFE_API_KEY='paste-your-real-key-here'

In PowerShell:

$env:TYPESAFE_API_KEY = 'paste-your-real-key-here'

These commands set the key only for processes launched from the current terminal session. Open a new terminal later and you will need to set it again unless you configure your operating system’s persistent secret storage or deployment environment. That is useful while developing: it reduces the chance that a long-lived credential leaks into shell history, configuration repositories, or screenshots.

You can check whether the variable is present without printing its value:

uv run python -c "import os; print('TYPESAFE_API_KEY is set' if os.getenv('TYPESAFE_API_KEY') else 'TYPESAFE_API_KEY is missing')"

Expected output:

TYPESAFE_API_KEY is set

The TypeSafe SDK quickstart documents both the environment-variable convention and the synchronous client you will use.

TypeSafe Python SDK - TypeSafe AI

Read the official TypeSafe AI Python SDK quickstart to verify the package name, credential convention, and client call shape.

In “Quickstart,” begin with the installation step, then read the API-key step and the Sync example under “Call the System One API.” Use the credential and call sequence to locate the transition. Focus on the imports, the TypeSafeClient() context manager, the state dictionary, questions, and the primitive-specific result collections such as response.choices.

A few secret-handling rules are worth making habitual now:

  • Never place the literal key in main.py.
  • Never log os.environ or the key itself while debugging.
  • Never add a real key to a test fixture, issue, commit, or screenshot.
  • Treat a key that appears in any of those places as compromised: revoke or rotate it in the provider console.

Make a synchronous authenticated Jev request

Create main.py at the project root. Replace any starter content with this code:

from typesafe_sdk import Choice, TypeSafeClient


TICKET = """
I was charged twice for my subscription yesterday.
Please help me get the duplicate charge reversed.
""".strip()


def main() -> None:
    with TypeSafeClient() as client:
        response = client.system_one(
            state={"document": TICKET},
            questions={
                "category": Choice(
                    instructions=(
                        "Which support team should own this ticket? "
                        "Use billing for payments, charges, refunds, or subscriptions; "
                        "technical for bugs or integrations; "
                        "account for login or access issues; "
                        "other when none apply."
                    ),
                    criteria={
                        "billing": None,
                        "technical": None,
                        "account": None,
                        "other": None,
                    },
                )
            },
        )

    answer = response.choices["category"]

    print("Selected category:", answer.choice)
    print("Selected probability:", answer.probabilities[answer.choice])
    print("Confidence:", answer.confidence)
    print("All probabilities:", answer.probabilities)


if __name__ == "__main__":
    main()

Run it through the project environment:

uv run main.py

A successful output will have this general shape:

Selected category: billing
Selected probability: 0.9...
Confidence: 0....
All probabilities: {'billing': ..., 'technical': ..., 'account': ..., 'other': ...}

The exact numeric values are not fixed. They can vary across requests, and the purpose of this lesson is not to hard-code an expected model result. What matters is that the request completes and produces a typed Choice response.

What happens in the request

The code has four distinct layers:

LayerCodeResponsibility
Input evidencestate={"document": TICKET}Supplies the ticket content that the judgment should consider
Decision contractChoice(...)Declares a bounded set of operational categories
Authentication and transportTypeSafeClient()Reads TYPESAFE_API_KEY from the environment and calls the service
Deterministic consumptionresponse.choices["category"]Reads the answer under the question ID and prints known fields

The synchronous TypeSafeClient is appropriate for this command-line script because the request is made in a simple linear flow. The with statement ensures that the client is closed when the request finishes, including when an exception interrupts the call.

Notice one Python-SDK difference from the earlier TypeScript example: results are grouped by primitive type. This Choice result is accessed through:

response.choices["category"]

The "category" key is not magic; it is the question ID you chose in the questions dictionary. Keeping question IDs stable will become important when a workflow has several judgments and application code needs to map every answer predictably.

The script sends only a deliberately non-sensitive sample ticket. In a real application, review the state payload before a request: do not include credentials, raw payment details, session identifiers, or unrelated customer fields simply because they are available.


Verify success and diagnose the common failures

A live response demonstrates several things at once:

  • uv found the project environment.
  • The typesafe-sdk dependency was installed and importable.
  • TYPESAFE_API_KEY was visible to the Python process.
  • The API accepted the credential and returned a response.
  • Your code correctly located the Choice result by its question ID.

If the first run fails, use the error category rather than blindly retrying.

SymptomLikely causeDirect action
ModuleNotFoundError for typesafe_sdkThe dependency was not added to this project, or the script was run with a global PythonRun uv add typesafe-sdk, then use uv run main.py rather than python main.py.
An error says the API key is absentThe shell session does not contain TYPESAFE_API_KEYSet the variable again in the same terminal, then rerun the non-secret presence check.
Authentication is rejectedThe key was copied incorrectly, revoked, or belongs to a different account or environmentCreate or copy a current key from the TypeSafe console. Do not paste the key into source code to test it.
Connection or timeout errorNetwork, proxy, VPN, or service availability issueConfirm ordinary network access, then retry once. Production timeout and fallback policies come later in the course.
KeyError: 'category'The question key and result lookup do not matchEnsure that both places use exactly "category".

For now, let exceptions surface rather than wrapping the entire script in a broad except Exception. A vague “request failed” message hides whether the failure was in local dependency setup, authentication, connectivity, or the decision code itself. Later, when this becomes an application service, you will define explicit timeout, unavailable-service, and low-confidence fallback behavior.


Completion checklist

Before moving on, confirm that all of the following are true:

  • uv add typesafe-sdk placed the SDK in pyproject.toml and updated uv.lock.
  • TYPESAFE_API_KEY is set in the current terminal but does not appear in main.py.
  • uv run main.py completes an authenticated live request.
  • Your script reads a Choice answer from response.choices["category"].
  • You can point to the separate roles of state, question definition, SDK client, and deterministic result handling.

Key takeaways

A uv-managed Jev integration has a small but important structure:

  • Use uv init . to create the project, uv add typesafe-sdk to declare the SDK dependency, and uv run main.py to execute in the managed environment.
  • Keep reproducibility artifacts such as pyproject.toml and uv.lock in version control; keep .venv and credentials out of it.
  • The Python SDK’s TypeSafeClient reads TYPESAFE_API_KEY from the environment, so authentication need not appear in code.
  • A synchronous system_one call takes a state payload and typed questions, then returns primitive-specific result collections such as response.choices.
  • A real response confirms the full integration path, but its selected value and numeric fields should not be treated as fixed test expectations.

Next, you will make this live integration testable without a network call: you will represent a Jev response as a deterministic fixture while preserving the same response shape your application logic consumes.

Can't find a good explanation? Sign up and we'll make it for you

Sign up