Spaces:
Sleeping
Sleeping
| 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 | |
| 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) | |