iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
自我挑戰組

Data Engineer 下班後偷學 AI系列 第 3

從 FastAPI + Codex App Server 到 CLIProxyAPI

  • 分享至 

  • xImage
  •  

可是我有訂閱欸

不是啊,我明明有訂閱啊,API 竟然還要另外收費會不會太可惡,萬惡的資本主義!
如果我自己把 codex CLI 和 claude CLI 做成 API 呢?

Codex App Server

Codex App Server 是一個長時間執行的 process,預設透過 JSONL over stdio 做雙向 RPC。

JSONL over stdio:透過 stdin / stdout 傳輸訊息,而且每個訊息用一行 JSON 表示。
雙向 RPC (Remote Function Call):指客戶端與伺服器都能隨時互相發送請求並接收回應的遠端程序呼叫機制。

啟動流程大致上是:

送 initialize
→ 等 response
→ 再送 initialized
→ 再送 thread/start
→ 根據回傳的 threadId 再送 turn/start

實際啟動演示一下:

codex app-server --stdio

接著我們就能用 JSONL 來跟 App Server 進行互動了

# 輸入
{"id":1,"method":"initialize","params":{"clientInfo":{"name":"cmd-test","version":"0.0.1"}}}
# 輸出
{"id":1,"result":{...}}
# 輸入
{"method":"initialized"}
{"id":2,"method":"thread/start","params":{}}
# 輸出
{"id":2,"result":{"thread":{"id":"01a0ab59..."}}}
# 輸入 (貼上 thread id)
{"id":3,"method":"turn/start","params":{"threadId":"01a0ab59...","input":[{"type":"text","text":"一句話介紹codex app server"}]}}
# 輸出
{"method":"turn/started", ...}
{"method":"turn/completed", "params":{"turn":{"items":[{"text":"Codex App Server 是讓 Codex 與外部應用程式透過標準介面互相溝通、呼叫工具與交換資料的服務。"}]}}, ...}

不過我們當然不需要那麼辛苦,OpenAI 現在也提供 openai-codex Python SDK,底層就是幫你啟動、管理這個 App Server。

from openai_codex import Codex, Sandbox

with Codex() as codex:
    thread = codex.thread_start(
        sandbox=Sandbox.read_only,
        ephemeral=True,
    )

    result = thread.run(
        "用三句話解釋目前這個資料夾是幹嘛的"
    )

print(result.final_response)
# 這是一個「30 天 AI 實作」學習專案,目前包含第 2~3 天的 Ollama、OpenAI、OpenRouter 與 Codex API 練習。
# ...

要注意 Codex 是可以在本機上做很多操作的,如果只是想把 Codex 當成一個文字 API 使用,最安全的做法是:給它一個空的 workspace,關掉寫入與權限升級,只讓它處理你明確傳進去的內容。

FastAPI + Codex App Server

如果沒有一定要走 HTTP 的話,其實直接跑 Codex App Server 的 thread 就行,這裡簡單練習一下用 FastAPI 來把 App Server 包成 API。

from contextlib import asynccontextmanager
from fastapi import FastAPI
from openai_codex import ApprovalMode, AsyncCodex, Sandbox
from pydantic import BaseModel

class AskRequest(BaseModel):
    model: str
    input: str

@asynccontextmanager
async def lifespan(_app: FastAPI):
    async with AsyncCodex() as _app.state.codex:
        yield

app = FastAPI(title="Simple Codex FastAPI Server", lifespan=lifespan)

@app.post("/ask")
async def ask(request: AskRequest) -> dict[str, str]:
    thread = await app.state.codex.thread_start(
        model=request.model,
        ephemeral=True,
        approval_mode=ApprovalMode.deny_all,
        sandbox=Sandbox.read_only,
    )
    result = await thread.run(request.input, sandbox=Sandbox.read_only)
    return {"output": result.final_response}

呼叫看看,現在我們也可以用很牛逼的模型啦。

import requests

response = requests.post(
    "http://127.0.0.1:8000/ask",
    json={
        "model": "gpt-6-astra",
        "input": "請用一句話介紹 FastAPI。",
    },
)

response.raise_for_status()
data = response.json()

print(data["output"])
# FastAPI 是以 Python 型別提示為基礎的高效能 Web 框架,能快速建立 API,並自動提供資料驗證與互動式 API 文件。

CLIProxyAPI

想當然爾我不會是第一個想把 CLI 架成 API 的人,GitHub 上一抓就是一大把現成架構,我就不造輪子啦!
CLIProxyAPI 是裡面星星數最多的,他還有桌面 GUI - EasyCLIProxyAPI,真的是滑鼠點一點就設置完成了,快速開始:

  1. 前往 GitHub Releases 下載對應作業系統的發行套件。
  2. 解壓縮 Windows 或 Linux 壓縮檔,macOS 使用者請開啟 DMG 檔案。
  3. 啟動 EasyCLIProxyAPI。
  4. 開啟「版本管理」頁面,安裝內建版本或最新版的 CLIProxyAPI 核心。
  5. 返回「首頁」啟動核心,接著複製所需的本機 API 位址,或設定 OAuth/API Provider。

https://ithelp.ithome.com.tw/upload/images/20260916/20138939wTTo7w1odJ.png

實在是成功得有點太簡單了。

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    base_url="http://127.0.0.1:8317/v1",
    api_key=os.getenv("CLIPROXY_API_KEY")
)

response = client.responses.create(
    model="gpt-6-astra",
    input="你有辦法執行任何tool嗎?你能讀取或編輯本地文件嗎?"
)

print(response.output_text)
# 可以,但僅限於這個對話環境提供的工具,**不是任意工具都能執行**。
#
# 目前我能使用的是:
# - **圖片生成與編輯工具**:依照文字描述生成圖片,或修改你上傳的圖片。
#
# 目前我**無法直接讀取或編輯你的本地文件**,也沒有終端機、檔案系統或執行程式碼的工具,因此不能直接存取你的電腦路徑、修改硬碟上的檔案。
#
# 如果你貼上文件內容,或透過介面上傳我能讀取的檔案,我可以協助分析、改寫,或提供修改後的內容,讓你自行儲存回本地。

response = client.responses.create(
    model="gpt-6-astra",
    input="生成一張夕陽下的台北城市插畫"
)

import base64
for item in response.output:
    if item.type == "image_generation_call":
        image_bytes = base64.b64decode(item.result)

        with open("output.png", "wb") as f:
            f.write(image_bytes)

https://ithelp.ithome.com.tw/upload/images/20260917/20138939CjGpINwC4X.png

每日一句

能用現成的,就不要感動自己。


上一篇
哪裡有免費的 LLM API 可以用
下一篇
可是老闆,那不是 Agent
系列文
Data Engineer 下班後偷學 AI8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言