""" Examples API - 提供 examples 项目列表和 prompt 读取接口 """ import os from typing import List, Optional from pathlib import Path from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter(prefix="/api/examples", tags=["examples"]) class ExampleProject(BaseModel): """Example 项目信息""" name: str path: str has_prompt: bool class ExampleListResponse(BaseModel): """Example 列表响应""" projects: List[ExampleProject] class PromptResponse(BaseModel): """Prompt 响应""" system_prompt: str user_prompt: str model: Optional[str] = None temperature: Optional[float] = None # 配置 examples 目录路径 EXAMPLES_DIR = Path("examples") @router.get("", response_model=ExampleListResponse) async def list_examples(): """ 列出所有 example 项目 扫描 examples 目录,返回所有子目录及其 prompt 文件状态 """ if not EXAMPLES_DIR.exists(): return ExampleListResponse(projects=[]) projects = [] for item in EXAMPLES_DIR.iterdir(): if item.is_dir(): # 检查是否有 prompt 文件 prompt_file = item / "production.prompt" has_prompt = prompt_file.exists() projects.append(ExampleProject( name=item.name, path=str(item), has_prompt=has_prompt )) # 按名称排序 projects.sort(key=lambda x: x.name) return ExampleListResponse(projects=projects) @router.get("/{project_name}/prompt", response_model=PromptResponse) async def get_example_prompt(project_name: str): """ 获取指定 example 项目的 prompt 读取 production.prompt 文件,解析 frontmatter 和内容 """ project_path = EXAMPLES_DIR / project_name if not project_path.exists() or not project_path.is_dir(): raise HTTPException(status_code=404, detail=f"Project not found: {project_name}") prompt_file = project_path / "production.prompt" if not prompt_file.exists(): raise HTTPException(status_code=404, detail=f"Prompt file not found for project: {project_name}") try: content = prompt_file.read_text(encoding="utf-8") # 解析 frontmatter 和内容 system_prompt = "" user_prompt = "" model = None temperature = None # 检查是否有 frontmatter if content.startswith("---"): parts = content.split("---", 2) if len(parts) >= 3: frontmatter = parts[1].strip() body = parts[2].strip() # 解析 frontmatter for line in frontmatter.split("\n"): if ":" in line: key, value = line.split(":", 1) key = key.strip() value = value.strip() if key == "model": model = value elif key == "temperature": try: temperature = float(value) except ValueError: pass else: body = content else: body = content # 解析 $system$ 和 $user$ 部分 if "$system$" in body: parts = body.split("$system$", 1) if len(parts) > 1: rest = parts[1] if "$user$" in rest: system_part, user_part = rest.split("$user$", 1) system_prompt = system_part.strip() user_prompt = user_part.strip() else: system_prompt = rest.strip() elif "$user$" in body: parts = body.split("$user$", 1) if len(parts) > 1: user_prompt = parts[1].strip() else: # 没有标记,全部作为 user_prompt user_prompt = body.strip() return PromptResponse( system_prompt=system_prompt, user_prompt=user_prompt, model=model, temperature=temperature ) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to read prompt file: {str(e)}")