文件導航

用法

使用 TypeSafe Python SDK 的指南與模式。

呼叫 System One API

import asyncio

from typesafe_sdk import AsyncTypeSafeClient, Choice, Noul, Score

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        result = await client.system_one(
            "I was charged twice. Please help ASAP.",
            {
                "billing": Noul(instructions="Is this about billing?"),
                "tone": Choice(
                    instructions="What is the tone?",
                    criteria={"calm": None, "angry": None},
                ),
                "urgency": Score(
                    instructions="How urgent is this?",
                    criteria=["low", "medium", "high"],
                ),
            },
        )
        print(
            result.nouls["billing"].noul,
            result.choices["tone"].choice,
            result.scores["urgency"].score,
        )

asyncio.run(main())
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

client = TypeSafeClient()
state = "I was charged twice. Please help ASAP."
questions = {
    "billing": Noul(instructions="Is this about billing?"),
    "tone": Choice(
        instructions="What is the tone?", criteria={"calm": None, "angry": None}
    ),
    "urgency": Score(
        instructions="How urgent is this?", criteria=["low", "medium", "high"]
    ),
}
result = client.system_one(state, questions)
print(
    result.nouls["billing"].noul,
    result.choices["tone"].choice,
    result.scores["urgency"].score,
)

帶型別的 system_one 響應

可以給 system_one 傳入一個響應模型,讓響應用起來更 type-safe(型別安全):

import asyncio

from typesafe_sdk import AsyncTypeSafeClient, Noul, NoulAnswer, SystemOneResponse

class BillingResponse(SystemOneResponse):
    billing: NoulAnswer

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        result = await client.system_one(
            "I was charged twice.",
            {"billing": Noul(instructions="Is this about billing?")},
            response_model=BillingResponse,
        )
        assert 0 <= result.billing.noul <= 1
        assert result.billing == result.nouls["billing"]
        print(result.request_id)

asyncio.run(main())
from typesafe_sdk import Noul, NoulAnswer, SystemOneResponse, TypeSafeClient

class BillingResponse(SystemOneResponse):
    billing: NoulAnswer

with TypeSafeClient() as client:
    result = client.system_one(
        "I was charged twice.",
        {"billing": Noul(instructions="Is this about billing?")},
        response_model=BillingResponse,
    )
    assert 0 <= result.billing.noul <= 1
    assert result.billing == result.nouls["billing"]
    print(result.request_id)

自定義響應型別

也可以完全從頭定義一個全新的響應模型,不必繼承 SystemOneResponse:

import asyncio

from pydantic import BaseModel

from typesafe_sdk import AsyncTypeSafeClient, Noul, NoulAnswer

class BillingAnswers(BaseModel):
    billing: NoulAnswer

class BillingResponse(BaseModel):
    answers: BillingAnswers

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        result = await client.system_one(
            "I was charged twice.",
            {"billing": Noul(instructions="Is this about billing?")},
            response_model=BillingResponse,
        )
        assert 0 <= result.answers.billing.noul <= 1

asyncio.run(main())
from pydantic import BaseModel

from typesafe_sdk import Noul, NoulAnswer, TypeSafeClient

class BillingAnswers(BaseModel):
    billing: NoulAnswer

class BillingResponse(BaseModel):
    answers: BillingAnswers

result = TypeSafeClient().system_one(
    "I was charged twice.",
    {"billing": Noul(instructions="Is this about billing?")},
    response_model=BillingResponse,
)
assert 0 <= result.answers.billing.noul <= 1

選擇模型

檢視可用模型:

import asyncio

from typesafe_sdk import AsyncTypeSafeClient

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        print(await client.models.list())

asyncio.run(main())
from typesafe_sdk import TypeSafeClient

print(TypeSafeClient().models.list())

在構造客戶端時選擇模型:

client = AsyncTypeSafeClient(model="jev")
client = TypeSafeClient(model="jev")

詳見 Models 資源參考。

配置 base URL

要用 SDK 連到另一個 API 地址,可以在客戶端上設定 base_url,或設定 TYPESAFE_BASE_URL 環境變數。這要求替代的 API 遵循 TypeSafe OpenAPI 規範。

例如,通過某個 AI 閘道器連線,使用它自己的 API key 和模型 ID。

使用 OpenRouter 的 API key 和一個 OpenRouter 模型 ID:

import asyncio
import os

from typesafe_sdk import AsyncTypeSafeClient, Noul

async def main() -> None:
    async with AsyncTypeSafeClient(
        api_key=os.environ["OPENROUTER_API_KEY"],
        base_url="https://openrouter.ai/api",
        model="~typesafe/jev-latest",
    ) as client:
        result = await client.system_one(
            "I was charged twice.",
            {"billing": Noul(instructions="Is this about billing?")},
        )
        print(result.nouls["billing"].noul)

asyncio.run(main())
import os

from typesafe_sdk import Noul, TypeSafeClient

with TypeSafeClient(
    api_key=os.environ["OPENROUTER_API_KEY"],
    base_url="https://openrouter.ai/api",
    model="~typesafe/jev-latest",
) as client:
    result = client.system_one(
        "I was charged twice.",
        {"billing": Noul(instructions="Is this about billing?")},
    )
    print(result.nouls["billing"].noul)

Vercel 提供的相容 TypeSafe 的 API 可以與 SDK 一起使用:

import asyncio
import os

from typesafe_sdk import AsyncTypeSafeClient, Noul

async def main() -> None:
    async with AsyncTypeSafeClient(
        api_key=os.environ["AI_GATEWAY_API_KEY"],
        base_url="https://ai-gateway.vercel.sh/typesafe",
        model="typesafe-ai/jev",
    ) as client:
        result = await client.system_one(
            "I was charged twice.",
            {"billing": Noul(instructions="Is this about billing?")},
        )
        print(result.nouls["billing"].noul)

asyncio.run(main())
import os

from typesafe_sdk import Noul, TypeSafeClient

with TypeSafeClient(
    api_key=os.environ["AI_GATEWAY_API_KEY"],
    base_url="https://ai-gateway.vercel.sh/typesafe",
    model="typesafe-ai/jev",
) as client:
    result = client.system_one(
        "I was charged twice.",
        {"billing": Noul(instructions="Is this about billing?")},
    )
    print(result.nouls["billing"].noul)

使用 Pydantic AI Gateway 的 API key:

import asyncio
import os

from typesafe_sdk import AsyncTypeSafeClient, Noul

async def main() -> None:
    async with AsyncTypeSafeClient(
        api_key=os.environ["PYDANTIC_AI_GATEWAY_API_KEY"],
        base_url="https://gateway-us.pydantic.dev/proxy/typesafe",
        model="jev-latest",
    ) as client:
        result = await client.system_one(
            "I was charged twice.",
            {"billing": Noul(instructions="Is this about billing?")},
        )
        print(result.nouls["billing"].noul)

asyncio.run(main())
import os

from typesafe_sdk import Noul, TypeSafeClient

with TypeSafeClient(
    api_key=os.environ["PYDANTIC_AI_GATEWAY_API_KEY"],
    base_url="https://gateway-us.pydantic.dev/proxy/typesafe",
    model="jev-latest",
) as client:
    result = client.system_one(
        "I was charged twice.",
        {"billing": Noul(instructions="Is this about billing?")},
    )
    print(result.nouls["billing"].noul)

HTTP/2

import httpx2

from typesafe_sdk import AsyncTypeSafeClient

client = AsyncTypeSafeClient(http_client=httpx2.AsyncClient(http2=True))
import httpx2

from typesafe_sdk import TypeSafeClient

client = TypeSafeClient(http_client=httpx2.Client(http2=True))

重試

把一個自定義的 RetryPolicy 作為 retry 傳給客戶端,或按單次呼叫傳入。無效的 API key 會在建立客戶端時就丟擲 TypeSafeError,早於任何請求或重試。

在客戶端上:

from typesafe_sdk import AsyncTypeSafeClient, RetryPolicy

client = AsyncTypeSafeClient(
    retry=RetryPolicy(max_retries=3, backoff_max=0.2, timeout=1.0)
)
from typesafe_sdk import RetryPolicy, TypeSafeClient

client = TypeSafeClient(retry=RetryPolicy(max_retries=3, backoff_max=0.2, timeout=1.0))

按單次呼叫:

import asyncio

from typesafe_sdk import AsyncTypeSafeClient, RetryPolicy

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        await client.system_one(
            state,
            questions,
            retry=RetryPolicy(max_retries=3, backoff_max=0.2, timeout=1.0),
        )

asyncio.run(main())
from typesafe_sdk import RetryPolicy

client.system_one(
    state, questions, retry=RetryPolicy(max_retries=3, backoff_max=0.2, timeout=1.0)
)

錯誤處理

處理 SDK 丟擲的異常:

import asyncio

from typesafe_sdk import AsyncTypeSafeClient, TypeSafeAPIError

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        try:
            await client.system_one(state, questions)
        except TypeSafeAPIError as error:
            print(error.status, error.request_id)

asyncio.run(main())
from typesafe_sdk import TypeSafeAPIError

try:
    client.system_one(state, questions)
except TypeSafeAPIError as error:
    print(error.status, error.request_id)

日誌

SDK 會向 typesafe_sdk 這個 logger 記錄日誌。按標準 logging 指南來配置它:

import logging

logging.getLogger("typesafe_sdk").setLevel(logging.DEBUG)

或者在匯入 SDK 之前,把 TYPESAFE_LOG_LEVEL 設為 debug、info、warning、error 或 off 之一。

info 會為每次請求記錄一行摘要;debug 還會記錄請求和響應的頭與請求體。機密頭 —— 授權資訊、API key、cookie,以及任何名字裡含 token 或 secret 的頭 —— 都會在日誌輸出中被脫敏。請求體和響應體不會脫敏。

環境變數

SDK 會讀取並使用以下環境變數:

變數 配置項 預設值
TYPESAFE_API_KEY API key(必填) —
TYPESAFE_BASE_URL API 根 URL https://api.typesafe.ai
TYPESAFE_DEFAULT_MODEL 預設模型 jev-latest
TYPESAFE_LOG_LEVEL typesafe_sdk logger 的級別,在匯入時應用一次 未設定

SDK 的預設值見常量參考。

通過 api_key 或 TYPESAFE_API_KEY 提供的 API key 會去掉首尾空白,包括來自 key 檔案的換行。空 key、內部空白、控制字元和非 ASCII 字元會在傳送請求之前被拒絕。顯式傳入的空 key 不會回退到環境變數。

前向相容

隨著 TypeSafe API 演進,SDK 會持續可用,因此你可以在某次 SDK 釋出正式支援某個新 API 特性之前,就先把它用起來。

額外的請求欄位

用 extra_body 傳送額外的 API 請求欄位。下面的 beam_width 欄位只是示意;只發送 API 支援的欄位。

import asyncio

from typesafe_sdk import AsyncTypeSafeClient, Noul

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        await client.system_one(
            "I was charged twice.",
            {"billing": Noul(instructions="About billing?")},
            extra_body={"beam_width": 4},
        )

asyncio.run(main())
from typesafe_sdk import Noul, TypeSafeClient

with TypeSafeClient() as client:
    client.system_one(
        "I was charged twice.",
        {"billing": Noul(instructions="About billing?")},
        extra_body={"beam_width": 4},
    )

原始問題字典

import asyncio

from typesafe_sdk import AsyncTypeSafeClient

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        await client.system_one(
            "I was charged twice.",
            {
                "billing": {
                    "type": "noul",
                    "instructions": "About billing?",
                    "weight": 2,
                }
            },
        )

asyncio.run(main())
from typesafe_sdk import TypeSafeClient

with TypeSafeClient() as client:
    client.system_one(
        "I was charged twice.",
        {"billing": {"type": "noul", "instructions": "About billing?", "weight": 2}},
    )

未知的答案種類

對於無法識別的答案種類,SDK 會記錄一條警告並跳過。用 raw_http_response 檢查完整的 API 響應,其中包含那些答案:

import asyncio

from typesafe_sdk import AsyncTypeSafeClient, Noul

async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        result = await client.system_one(
            "I was charged twice.",
            {"billing": Noul(instructions="Is this about billing?")},
        )
        print(result.raw_http_response.json()["answers"])

asyncio.run(main())
from typesafe_sdk import Noul, TypeSafeClient

result = TypeSafeClient().system_one(
    "I was charged twice.",
    {"billing": Noul(instructions="Is this about billing?")},
)
raw_answers = result.raw_http_response.json()["answers"]

未知的響應欄位

已識別響應上的未知額外欄位會被忽略。