本文由 AI Agent(Antigravity)代筆撰寫,文中的「我」指的是 AI Agent。Patrick 只有在文章最後做過潤飾調整。
在上一篇文章中,我們為本機 LLM 推理引擎加上了以 Axum 為核心的 HTTP 伺服器,並實現了相容於 OpenAI Chat Completions 規範的 API 端點,支援 Server-Sent Events (SSE) 令牌串流輸出。
標準化的最大好處就是互操作性(Interoperability)。因為 API 格式與 OpenAI 完全一致,你不需要為本機的 llm-local-studio 重新撰寫任何客製化的 HTTP 請求解析程式,直接使用官方提供的 SDK 或現成的開源工具,僅需修改**伺服器端點位址(Base URL)**與將金鑰設為任意值,即可無縫對接。
這篇文章整理了如何使用 Python、Node.js、Rust 及 curl 串接本機伺服器的實用程式碼範例,並介紹如何將它對接到 VS Code 等日常開發工具中。
本機 API 服務端點回顧
在開始寫程式之前,先確認你的本機伺服器已啟動並監聽 8080 連接埠:
cargo run --release -- serve tinyllama --port 8080此時,你的本機服務對外提供以下相容於 OpenAI 的端點:
- API 根路徑 (Base URL):
http://localhost:8080/v1 - 模型清單:
GET http://localhost:8080/v1/models - 對談補完:
POST http://localhost:8080/v1/chat/completions
[!TIP] 因為本機服務不需要驗證,你的
api_key可以傳入任意字串(例如"local-studio"或"noop")。許多 SDK 要求必須設定該值,否則會拋出未配置金鑰的錯誤。
深入協議:OpenAI API 與 SSE 串流運作原理
在我們開始看各語言的 SDK 程式碼之前,先來了解這個「OpenAI 相容協議」在 HTTP 底層到底是怎麼傳遞資料的。這能幫助我們理解為什麼各大 SDK 只需要換個 URL 就能直接對接本機服務。
1. 聊天補完請求 (Chat Completions Request)
當客戶端向本機服務發送請求時,發起的是一個標準的 HTTP POST 請求:
- URL:
http://localhost:8080/v1/chat/completions - Headers:
Content-Type: application/json - JSON 欄位:
model(String): 本機載入的模型識別代號(如tinyllama)。messages(Array): 對話歷史紀錄,每個物件包含:role(String): 角色,可為system(系統提示詞)、user(使用者提問)或assistant(AI 回答)。content(String): 對話的文字內容。
stream(Boolean): 是否開啟串流模式(逐字生成)。max_tokens(Integer, 選填): 限制生成的 Token 最大數量。
2. 非串流模式的響應 (Non-Streaming Response)
如果 stream: false,伺服器會阻塞等待推理完全結束後,一次性返回 HTTP 200 與完整的 JSON 響應:
{
"id": "chatcmpl-c513296e-7eb2-4f66-9af7-045fbae2fa36",
"object": "chat.completion",
"created": 1779608333,
"model": "tinyllama-1.1b-chat-v1.0.Q4_K_M",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! I am a local assistant."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 8,
"total_tokens": 20
}
}3. 串流模式與 Server-Sent Events (SSE) 協議
當 stream: true 時,底層協議會切換為 Server-Sent Events (SSE)。這是一種基於 HTTP 的單向持久化連線技術,非常適合 LLM 逐字輸出的場景:
- HTTP 響應標頭:
Content-Type: text/event-stream(告訴瀏覽器/客戶端這是一個事件流)Cache-Control: no-cache(停用快取)Connection: keep-alive(保持連線不中斷)
- 傳輸格式:
伺服器會保持連接,每生成一個 token 片段,就向 TCP 通道寫入一段以
data:開頭、\n\n結尾的文字資料,內容是 JSON 格式的增量區塊(Delta Chunk):
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]}- 結束信號:
當模型生成結束或達到最大 token 限制時,伺服器會先傳送最後一個帶有
finish_reason的 chunk(通常內容為空),緊接著發送一個特殊的結束標記:客戶端收到data: [DONE][DONE]後,便會主動關閉這個 HTTP 連線。
這套簡潔的文本串流規範就是 OpenAI API 的精髓。接下來我們來看看如何使用各語言的 SDK 包裝這套協議。
1. Python 串接範例
Python 是資料科學與 AI 開發的首選語言。我們可以使用官方的 openai 庫直接串接本機服務。
首先安裝 SDK:
pip install openai非串流模式 (Non-Streaming)
from openai import OpenAI
# 指向本機伺服器的 Base URL,api_key 填入任意值即可
client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="local-studio"
)
response = client.chat.completions.create(
model="tinyllama",
messages=[
{"role": "user", "content": "Explain ownership in Rust in one sentence."}
],
max_tokens=80
)
print(response.choices[0].message.content)串流模式 (Streaming)
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="local-studio"
)
# 啟用 stream=True 進行打字機效果輸出
stream = client.chat.completions.create(
model="tinyllama",
messages=[
{"role": "user", "content": "Explain ownership in Rust in one sentence."}
],
max_tokens=80,
stream=True
)
for chunk in stream:
# 讀取 delta 增量內容並即時印出
content = chunk.choices[0].delta.content
if content is not None:
print(content, end="", flush=True)
print()2. Node.js / JavaScript 串接範例
如果你正在開發 Web 應用或 Node.js 後端服務,可以使用官方的 @openai/api 軟體包。
首先安裝 SDK:
npm install openai非同步串流模式 (ESM / TypeScript)
使用現代 JavaScript 的 for await...of 語法,可以極度簡潔地處理本機傳回的 SSE 串流:
import OpenAI from "openai";
const openai = new OpenAI({
baseURL: "http://localhost:8080/v1",
apiKey: "local-studio",
});
async function main() {
const stream = await openai.chat.completions.create({
model: "tinyllama",
messages: [
{ role: "user", content: "Explain ownership in Rust in one sentence." }
],
max_tokens: 80,
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || "";
process.stdout.write(content);
}
process.stdout.write("\n");
}
main().catch(console.error);3. Rust 串接範例
對於 Rust 開發者,社群中廣受歡迎的 async-openai crate 是串接 OpenAI 的主力。
在你的 Cargo.toml 中加入依賴:
[dependencies]
async-openai = "0.26"
tokio = { version = "1", features = ["full"] }
futures = "0.3"串流模式範例
我們需要透過 ClientConfig 自訂底層呼叫的 API 根路徑:
use async_openai::{
config::OpenAIConfig,
types::{CreateChatCompletionRequestArgs, ChatCompletionRequestMessage},
Client,
};
use futures::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. 自訂配置,將 api_base 指向本機端點
let config = OpenAIConfig::default()
.with_api_base("http://localhost:8080/v1")
.with_api_key("local-studio");
let client = Client::with_config(config);
// 2. 建立 Request 參數
let request = CreateChatCompletionRequestArgs::default()
.model("tinyllama")
.max_tokens(80u32)
.messages(vec![
ChatCompletionRequestMessage::User(
async_openai::types::ChatCompletionRequestUserMessageArgs::default()
.content("Explain ownership in Rust in one sentence.")
.build()?,
)
])
.stream(true)
.build()?;
// 3. 獲取非同步 Stream 並依序讀取 token
let mut stream = client.chat().create_stream(request).await?;
while let Some(result) = stream.next().await {
match result {
Ok(response) => {
for choice in response.choices {
if let Some(content) = choice.delta.content {
print!("{content}");
std::io::Write::flush(&mut std::io::stdout())?;
}
}
}
Err(err) => eprintln!("Error: {err}"),
}
}
println!();
Ok(())
}4. 終端機 curl 快速測試
不需要寫任何程式碼,使用 curl 也是進行快速排錯與 API 驗證的好幫手:
非串流 (返回單一 JSON)
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "tinyllama",
"messages": [
{"role": "user", "content": "Explain ownership in Rust in one sentence."}
],
"max_tokens": 80
}'串流 (返回 SSE 封包)
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "tinyllama",
"messages": [
{"role": "user", "content": "Hello!"}
],
"stream": true,
"max_tokens": 30
}'實戰整合:VS Code 開發助手 Continue
除了自己編寫程式外,你還可以直接將 llm-local-studio-2 連接至現成的編輯器外掛中。
以熱門的開源 VS Code 程式編寫助理外掛 Continue 為例,你只需修改 Continue 的 config.json 設定檔,將其模型供應商設為 openai,並將 apiBase 指向你的本機服務:
{
"models": [
{
"title": "Local Studio - TinyLlama",
"provider": "openai",
"model": "tinyllama",
"apiBase": "http://localhost:8080/v1",
"apiKey": "local-studio"
}
],
"tabAutocompleteModel": {
"title": "Local Studio - TinyLlama Autocomplete",
"provider": "openai",
"model": "tinyllama",
"apiBase": "http://localhost:8080/v1",
"apiKey": "local-studio"
}
}存檔後,VS Code 側邊欄的 Continue 對話框與行內自動補完功能,就會直接利用本機運行的 llm-local-studio 進行推論!
總結
相容於成熟的公有雲 API 標準,為本地 AI 工具注入了無限的可能性。透過將 FFI 執行緒與 Axum Web 伺服器解耦,並對外曝露標準的 OpenAI 協定,llm-local-studio-2 現在可以輕鬆地成為你開發流程中任何一環的「智慧核心」。
不論你是使用 Python 進行指令碼開發、用 Node.js 建立網頁應用、還是用 Rust 編寫系統級程式,串接方式都非常直覺簡單。歡迎將這些程式碼片段複製到你的專案中試試看!