OpenAI Codex

OpenAI Codex

이 문서에서는 pydantic-ai에서 openai-codex 프로바이더를 사용하는 방법을 알려드려요. 토큰당 과금 API 키 대신 ChatGPT/Codex 구독을 사용해요. 공식 Codex CLI와 같은 OAuth 흐름으로 로그인하며, API 키는 openai 프로바이더를 사용하세요.

출처: 문서

본문

토큰당 과금 API 키 대신 Pydantic AI와 함께 ChatGPT/Codex 구독을 사용하세요. openai-codex 프로바이더는 공식 Codex CLI와 같은 OAuth 흐름으로 로그인해요. API 키에는 openai 프로바이더를 대신 사용하세요. Codex 백엔드 사용은 OpenAI와의 귀하의 계약에 따르며, 구독에 적용되는 사용 정책을 확인하세요.

Install

Codex 프로바이더를 사용하려면 pydantic-ai를 설치하거나, openai 옵션 그룹과 함께 pydantic-ai-slim을 설치해야 해요:

Terminal

pip install "pydantic-ai-slim[openai]"

Terminal

uv add "pydantic-ai-slim[openai]"

Usage

Codex CLIcodex login을 한 번 실행하고, 그 다음 openai-codex: 접두사를 사용하세요:

from pydantic_ai import Agent

agent = Agent('openai-codex:gpt-5.6-luna')
...

이것은 OpenAICodexProvider로 뒷받침되는 OpenAICodexModel로 해석되며, 프로바이더는 CLI의 자격 증명을 ~/.codex/auth.json(또는 $CODEX_HOME/auth.json)에서 읽어요. 파일은 절대 쓰이지 않으며, 갱신된 토큰은 프로세스가 끝날 때까지 메모리에 살아요.

Logging in without the Codex CLI

Codex CLI에 의존하고 싶지 않다면, OpenAICodexOAuthFlow가 같은 브라우저 로그인을 실행해요. Codex 클라이언트는 리다이렉트 URI를 http://localhost:1455/auth/callback으로 고정하므로, exchange_code_from_callback()은 브라우저가 거기로 리다이렉트할 때까지 그 포트에서 듣고, 그 다음 코드를 자격 증명으로 교환해요:

codex_login.py

import webbrowser

from pydantic_ai import Agent
from pydantic_ai.models.openai_codex import OpenAICodexModel
from pydantic_ai.providers.openai_codex import OpenAICodexOAuthFlow, OpenAICodexProvider


async def main():
    flow = OpenAICodexOAuthFlow()
    webbrowser.open(flow.authorization_url())
    credentials = await flow.exchange_code_from_callback()

    provider = OpenAICodexProvider(credentials=credentials)
    agent = Agent(OpenAICodexModel('gpt-5.6-luna', provider=provider))
    result = await agent.run('Where does "hello world" come from?')
    print(result.output)

credentials를 전달하면 그것을 메모리에만 두므로, 다음 프로세스가 다시 로그인해야 해요. 한 번 로그인하려면 아래 설명대로 영속하세요.

Persisting credentials

프로바이더는 만료된 토큰을 자동으로 갱신하며, refresh 토큰은 일회용이므로 저장된 복사본이 따라잡아야 해요. 프로바이더에 OpenAICodexCredentialSource를 주면 첫 사용 시 load()를 호출하고 매 갱신 후 save()를 호출해요. 로그인 흐름은 저장소가 비어 있을 때만 실행하세요:

codex_persist.py

import json
import webbrowser
from dataclasses import asdict
from pathlib import Path

from pydantic_ai import Agent
from pydantic_ai.models.openai_codex import OpenAICodexModel
from pydantic_ai.providers.openai_codex import (
    OpenAICodexCredentials,
    OpenAICodexCredentialSource,
    OpenAICodexOAuthFlow,
    OpenAICodexProvider,
)


class FileCredentialSource(OpenAICodexCredentialSource):
    def __init__(self, path: Path):
        self.path = path

    async def load(self) -> OpenAICodexCredentials:
        return OpenAICodexCredentials(**json.loads(self.path.read_text()))

    async def save(self, credentials: OpenAICodexCredentials) -> None:
        self.path.write_text(json.dumps(asdict(credentials)))


async def main():
    source = FileCredentialSource(Path('codex-credentials.json'))
    if not source.path.exists():
        flow = OpenAICodexOAuthFlow()
        webbrowser.open(flow.authorization_url())
        await source.save(await flow.exchange_code_from_callback())

    provider = OpenAICodexProvider(credential_source=source)
    agent = Agent(OpenAICodexModel('gpt-5.6-luna', provider=provider))
    result = await agent.run('Where does "hello world" come from?')
    print(result.output)

자격 증명 파일을 보호하세요

파일은 구독에 대한 액세스를 부여하는 refresh 토큰을 담으므로, 다른 어떤 비밀처럼 취급하세요. 앱이 비밀을 두는 곳 어디든 두고(~/.codex에는 두지 마세요. Codex CLI 것이니까요), 현재 사용자로 제한하세요. 예를 들어 쓰기 후 self.path.chmod(0o600)으로.

save()가 예외를 발생시키면 갱신된 자격 증명이 메모리에 계속 살아 있고 CredentialsPersistenceError가 발생해요. 그것과 CredentialsRefreshError 둘 다 ModelAPIError를 상속하므로, FallbackModel이 사용 불가 로그인을 다른 어떤 프로바이더 실패처럼 취급해요.

Tracing without exposing credentials

Logfire 계측은 OAuth 자격 증명을 포착하지 않고 에이전트 실행을 추적할 수 있어요. 필요하지 않으면 HTTP 본문 캡처를 비활성화로 두세요. logfire.instrument_httpx(capture_all=True)는 인가 코드와 토큰 응답을 포착하며, 추가 스크러빙 패턴이 필요해요.

전체 HTTP 캡처를 활성화하면 OAuth 흐름을 시작하기 전에 스크러빙을 구성하세요:

codex_tracing.py

import logfire

logfire.configure(
    scrubbing=logfire.ScrubbingOptions(
        extra_patterns=[
            'access_token',
            'refresh_token',
            'id_token',
            'code_verifier',
            '^code$',
            'chatgpt-account-id',
            '^account_id$',
            'safety_identifier',
        ]
    ),
)
logfire.instrument_pydantic_ai()
logfire.instrument_httpx(capture_all=True)

추가 패턴은 OAuth 자격 증명과 계정 식별자를 다듬고, Logfire의 기본 패턴은 이미 인가 헤더를 다듬어요.

Prompt caching

공식 Codex 클라이언트의 프롬프트 캐시 친화도를 반영하기 위해, OpenAICodexModelsession-id, thread-id, x-client-request-id 헤더와 prompt_cache_key 요청 필드를 전송해요. 네 개 모두 메시지 히스토리의 conversation_id에서 파생되므로, 같은 대화를 이어가는 실행은 안정적인 정체성을 재사용해요. 명시적 openai_prompt_cache_key 모델 설정이나 명시적으로 공급된 extra_headers는 항상 파생된 값을 이겨요. 이것이 캐시 히트를 보장하지는 않아요.

Limitations

  • Codex 백엔드는 스트리밍 전용이에요. 비스트리밍 실행에서는 라이브러리가 스트림을 투명하게 배수하므로, agent.run_sync() 등이 평소처럼 동작해요.
  • 지원되지 않는 일반 설정(max_tokens, temperature, top_p)은 전송 전에 버려져요. Codex 프로필은 명시적 openai_top_logprobs, openai_truncation, openai_user 설정을 표준 OpenAI 처리에 맡기므로, 백엔드 비호환성이 오류로 표면화돼요. 로그 확률에 대한 평소의 추론 관련 제한이 여전히 적용돼요.
  • 백엔드는 store=false를 요구하므로, 모든 요청이 그것과 함께 전송되고 명시적 openai_store=True는 조용히 덮어써져요. 응답은 서버 측에 절대 영속되지 않아요. 결과적으로, 일시 중단된 실행을 재개하면 이어갈 저장된 응답이 없으므로 UserError가 발생해요.
  • count_tokens()UserError를 발생시켜요. input-tokens 엔드포인트가 구독 인증 아래에서 서빙되지 않으니까요.
  • device flow가 없어요. 위의 브라우저 로그인이 Codex 클라이언트가 지원하는 유일한 로그인 흐름이에요.

더 알아보기 (Learn more)