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.tomldeclares what the project needs, including the TypeSafe SDK.uv.lockrecords the exact resolved dependency set so another machine can reproduce it..venvis the local isolated Python environment thatuvmanages.TYPESAFE_API_KEYis 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.
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:
- Initialize a project once.
- Add a runtime dependency with
uv add. - 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.environor 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:
| Layer | Code | Responsibility |
|---|---|---|
| Input evidence | state={"document": TICKET} | Supplies the ticket content that the judgment should consider |
| Decision contract | Choice(...) | Declares a bounded set of operational categories |
| Authentication and transport | TypeSafeClient() | Reads TYPESAFE_API_KEY from the environment and calls the service |
| Deterministic consumption | response.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:
uvfound the project environment.- The
typesafe-sdkdependency was installed and importable. TYPESAFE_API_KEYwas 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.
| Symptom | Likely cause | Direct action |
|---|---|---|
ModuleNotFoundError for typesafe_sdk | The dependency was not added to this project, or the script was run with a global Python | Run uv add typesafe-sdk, then use uv run main.py rather than python main.py. |
| An error says the API key is absent | The shell session does not contain TYPESAFE_API_KEY | Set the variable again in the same terminal, then rerun the non-secret presence check. |
| Authentication is rejected | The key was copied incorrectly, revoked, or belongs to a different account or environment | Create or copy a current key from the TypeSafe console. Do not paste the key into source code to test it. |
| Connection or timeout error | Network, proxy, VPN, or service availability issue | Confirm 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 match | Ensure 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-sdkplaced the SDK inpyproject.tomland updateduv.lock. -
TYPESAFE_API_KEYis set in the current terminal but does not appear inmain.py. -
uv run main.pycompletes 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-sdkto declare the SDK dependency, anduv run main.pyto execute in the managed environment. - Keep reproducibility artifacts such as
pyproject.tomlanduv.lockin version control; keep.venvand credentials out of it. - The Python SDK’s
TypeSafeClientreadsTYPESAFE_API_KEYfrom the environment, so authentication need not appear in code. - A synchronous
system_onecall takes a state payload and typed questions, then returns primitive-specific result collections such asresponse.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