Pythonã§ã¹ããªãŒãã³ã°å¿çãå®è£ ããïŒOpenAI SDKïŒ
LLM ã®å¿çã¯ãçæãçµãããŸã§åŸ
ã€ãšæ°ç§ãåæ°ç§ãããããšããããŸãããŠãŒã¶ãŒããèŠããšãåºãŸã£ãŠãããããã«æããããé¢è±ã®åå ã«ãªããŸããããã§äœ¿ãã®ãã¹ããªãŒãã³ã°ã§ããçæããããã°ããããŒã¯ã³ãç»é¢ã«æµãã°ãæåã®æåãåºããŸã§ã®åŸ
ã¡æéïŒTTFTïŒãçããªããäœæé床ã倧ããäžãããŸããFastMetal 㯠OpenAI äºæãªã®ã§ãstream=True ãä»ããã ãã§å®è£
ã§ããŸãã
ã¹ããªãŒãã³ã°ãšã¯
éåžžã®ãªã¯ãšã¹ãã¯ããµãŒããŒãå¿çããã¹ãŠçæãçµããŠãããŸãšããŠè¿ããŸããäžæ¹ã¹ããªãŒãã³ã°ã§ã¯ããµãŒããŒãçæããæ¬¡ç¬¬ããŒã¯ã³ã鿬¡éãè¿ããããã¯ã©ã€ã¢ã³ãã¯å·®åãåãåããªãã衚瀺ã§ããŸããå
éšçã«ã¯ Server-Sent EventsïŒSSEïŒãšããä»çµã¿ã§ãdata: è¡ãé£ç¶ããŠéãããæåŸã« data: [DONE] ã§çµäºãç¥ãããŸããOpenAI SDK ã¯ãã® SSE ã®ããŒã¹ãè©ä»£ããããŠãããã®ã§ãç§ãã¡ã¯å·®åãé çªã«åãåãã ãã§æžã¿ãŸãã
Python ã®å®è£ äŸ
stream=True ãæž¡ããšãæ»ãå€ã¯ã¬ã¹ãã³ã¹ãªããžã§ã¯ãã§ã¯ãªãã€ãã¬ãŒã¿ã«ãªããŸããfor ã§åããå chunk ã® choices[0].delta.content ãåãåºããŸãã
from openai import OpenAI
client = OpenAI(
base_url="https://api.fastmetal.ai/v1",
api_key="sk-...", # FastMetal ã® API ããŒ
)
stream = client.chat.completions.create(
model="anthropic-claude-haiku-4-5",
messages=[{"role": "user", "content": "çãç©èªãæžããŠ"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
print()
ãã€ã³ãã¯æ¬¡ã®ãšããã§ãã
- å
chunkã®choices[0].delta.contentã«å·®åããã¹ããå ¥ããŸãã - å
é ïŒrole ã ãã®ãã£ã³ã¯ïŒãçµç«¯ã§ã¯
contentãNoneã«ãªããããif delta:ã§ã¬ãŒãããŸãã print(..., flush=True)ãä»ãããšããããã¡ã«æºããå³åº§ã«è¡šç€ºãããŸãã
å®éçšã§æ°ãã€ããããš
ãµã³ãã«ãåããŠããæ¬çªã§ã¯éä¿¡ãéäžã§åããããå¿çãå®äºããåã«æ¢ãŸã£ããããŸããæäœéãæ¬¡ã®ç¹ãæŒãããŠãããŸãããã
try:
stream = client.chat.completions.create(
model="anthropic-claude-haiku-4-5",
messages=[{"role": "user", "content": "çãç©èªãæžããŠ"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
except KeyboardInterrupt:
stream.close() # ãŠãŒã¶ãŒãäžæãããæ¥ç¶ãéãã
except Exception as e:
print(f"\n[ã¹ããªãŒã äžæ] {e}")
- äžæã»åæ: ãããã¯ãŒã¯æãã¿ã€ã ã¢ãŠãã§
forã®éäžã§äŸå€ãé£ã¶ããšããããŸãããããŸã§åãåã£ãåãä¿æãã€ã€ããšã©ãŒãæ¡ãã€ã¶ãããã°ã«æ®ããŸãã - éäžçµäº: ãŠãŒã¶ãŒãçæãæ¢ãããå Žåã¯
stream.close()ã§æ¥ç¶ãéããã°ã以éã®ããŒã¯ã³çæãæã¡åããŸãã - ããŒã¯ã³èª²éã¯éåžžã©ãã: ã¹ããªãŒãã³ã°ã§ããå®éã«çæãããããŒã¯ã³åã¯éåžžãªã¯ãšã¹ããšåãã課éãããŸããéäžã§éããå Žåã¯ããã®æç¹ãŸã§ã«çæãããåã察象ã§ãããã¹ããªãŒã ã ããå®ãïŒç¡æããšããããšã¯ãããŸããã
Web ã¢ããªã§ãã£ãã UI ã«ãã
ãã©ãŠã¶ã®ãã£ãã UI ãäœããšãã¯ããµãŒããŒåŽã§ FastMetal ã®å¿çãåãåããããã SSE ãšããŠããã³ããžäžç¶ããŸããAPI ããŒããã©ãŠã¶ã«æããã«æžãã®ãå©ç¹ã§ããFastAPI ãªã StreamingResponse ã䜿ããŸãã
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import OpenAI
app = FastAPI()
client = OpenAI(base_url="https://api.fastmetal.ai/v1", api_key="sk-...")
@app.get("/chat")
def chat(q: str):
def event_stream():
stream = client.chat.completions.create(
model="anthropic-claude-haiku-4-5",
messages=[{"role": "user", "content": q}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
yield f"data: {delta}\n\n" # SSE 圢åŒã§1ãã£ã³ã¯ãã€éã
yield "data: [DONE]\n\n"
return StreamingResponse(event_stream(), media_type="text/event-stream")
ããã³ãåŽã¯ EventSource ã§åãåããonmessage ã§å±ããå·®åãèŠçŽ ã«è¿œèšããŠããã ãã§ãïŒ[DONE] ãåãããéããŸãïŒãå®éã«ã¯ããã¹ãã«æ¹è¡ãå«ãŸãããš SSE ãããããããæ¬çªã§ã¯å·®åã JSON ã«ãšã³ã³ãŒãã㊠data: {"t": "..."} ã®åœ¢ã§éãã®ãå®å
šã§ãã
ãããã質å
Q. ã¹ããªãŒãã³ã°ãšéåžžãªã¯ãšã¹ãã§æéã¯å€ãããŸããïŒ A. å€ãããŸãããã©ã¡ããå®éã«çæãããããŒã¯ã³éã§èª²éãããŸããã¹ããªãŒãã³ã°ã¯ã衚瀺ã®åºãæ¹ããéãã ãã§ãã
Q. éäžã§ stream.close() ããå Žåãæ®ãã®ããŒã¯ã³ã課éãããŸããïŒ
A. 課é察象ã¯ãã®æç¹ãŸã§ã«çæãããåã§ããéããåŸã«çæãããªãããŒã¯ã³ã¯èª²éãããŸããã
Q. ã©ã®ã¢ãã«ã§ãã¹ããªãŒãã³ã°ã§ããŸããïŒ
A. FastMetal ã® OpenAI äºæãšã³ããã€ã³ã㯠stream=True ã«å¯Ÿå¿ããŠããŸããå©çšã§ããã¢ãã« ID ã¯ã¢ãã«ã«ã¿ãã°ã§ç¢ºèªã§ããŸãã
次ã®ã¹ããã
ãŸãã¯æå
ã®ã¹ã¯ãªããã§ stream=True ã詊ããæ¬¡ã«ãµãŒããŒçµç±ã® SSE äžç¶ãžé²ãã®ãããããã§ãã察å¿ã¢ãã«ã¯ã¢ãã«ã«ã¿ãã°ããšã³ããã€ã³ãã®è©³çްããã©ã¡ãŒã¿ã¯ããã¥ã¡ã³ããã芧ãã ããã