import os import json from typing import Literal, Dict, Any import httpx from fastapi import FastAPI, Query, HTTPException from fastapi.responses import JSONResponse app = FastAPI() CEREBRAS_API_KEY = os.getenv("CEREBRAS_API_KEY") CEREBRAS_API_URL = "https://api.cerebras.ai/v1/chat/completions" def build_prompt(difficulty: str, language: str) -> str: return f""" あなたはクイズ作成者です。 難易度: {difficulty} 言語: {language} 次のフォーマットの JSON だけを返してください(前後に一切の文章を付けないこと): **出力には{language}を使ってください。** {{ "questions": [ {{ "question": "問題文({language})", "choices": ["選択肢1", "選択肢2", "選択肢3", "選択肢4"], "correctIndex": 0 }}, {{ "question": "問題文({language})", "choices": ["選択肢1", "選択肢2", "選択肢3", "選択肢4"], "correctIndex": 0 }}, {{ "question": "問題文({language})", "choices": ["選択肢1", "選択肢2", "選択肢3", "選択肢4"], "correctIndex": 0 }} ] }} 制約: - 質問は必ず3問 - 各問題は4択 - テーマは一般教養レベル(雑学など)で OK - 難易度は easy, medium, hard に応じて調整 - 最終的なアウトプットは {language}に合わせて出力 """ async def call_cerebras_quiz_api(difficulty: str, language: str) -> Dict[str, Any]: if not CEREBRAS_API_KEY: raise RuntimeError("CEREBRAS_API_KEY is not set") prompt = build_prompt(difficulty, language) headers = { "Content-Type": "application/json", "Authorization": f"Bearer {CEREBRAS_API_KEY}", } payload = { "model": "gpt-oss-120b", "messages": [ {"role": "system", "content": "You are a helpful quiz generator."}, {"role": "user", "content": prompt}, ], "temperature": 0.8, "max_tokens": 1024, } async with httpx.AsyncClient(timeout=30.0) as client: resp = await client.post(CEREBRAS_API_URL, headers=headers, json=payload) if resp.status_code != 200: raise RuntimeError(f"Cerebras API error: {resp.status_code}, {resp.text}") data = resp.json() content = data.get("choices", [{}])[0].get("message", {}).get("content", "") # たまにモデルが ```json ... ``` で囲んでくる可能性があるので軽く掃除 cleaned = content.strip() if cleaned.startswith("```"): # ```json or ``` で始まるケースに対応 cleaned = cleaned.strip("`") # 先頭行に "json" とか残っていたら落とす if cleaned.startswith("json"): cleaned = cleaned[len("json") :].lstrip() try: quiz = json.loads(cleaned) except json.JSONDecodeError as e: # デバッグ用に content を見たい場合は log に出す print("Failed to parse JSON from model:", content) raise RuntimeError(f"JSON parse error: {e}") return quiz @app.get("/quiz") async def get_quiz( difficulty: Literal["easy", "medium", "hard"] = Query("medium"), language: str = Query("en") ): """ クイズを3問返すAPI。 レスポンス形式: { "questions": [ { "question": "....", "choices": ["..", "..", "..", ".."], "correctIndex": 1 }, ... ] } """ try: quiz = await call_cerebras_quiz_api(difficulty, language) except Exception as e: # 拡張側が扱いやすいように 500 + メッセージだけ返す raise HTTPException(status_code=500, detail=str(e)) # 最低限のバリデーション(壊れたJSONでないかチェック) if not isinstance(quiz, dict) or "questions" not in quiz: raise HTTPException(status_code=500, detail="Invalid quiz format") return JSONResponse(content=quiz)