# AI 网关服务

> 版本：1.0（P5 完整实现）
> 端口：HTTP 3008 + gRPC 50058
> 定位：D6 智能洞察领域 · 生成子域（Python/FastAPI，严格无状态）

## 职责

统一封装 LLM 调用（多模型路由 + 故障切换 + 熔断 + 限流 + 成本控制），提供：
- **聊天**：非流式 + SSE 流式
- **题目生成**：非流式 + 流式 + 评估三道防线（RuleValidator + LLMJudge + QualityGate）
- **表达优化**：文字清晰度/简洁度/语气优化
- **备课工作流**：4 步编排（分析学情 → 推荐知识点 → 生成题目 → 教师审核入库）

通过 gRPC 查询 content 题库与 data-ana 学情数据。通过 Kafka 发布 AIUsageEvent 供 data-ana 统计。

## 技术栈

- Python 3.12 + FastAPI 0.115
- Pydantic 2 + pydantic-settings（12-factor 配置）
- grpc.aio（gRPC server 8 RPC + client 拦截器）
- aiokafka（用量事件发布，事务性 + 幂等，派生数据豁免 Outbox）
- Jinja2 + YAML（Prompt 模板渲染）
- redis（限流令牌桶 + 用量记录 + 工作流状态存储）
- OpenTelemetry（HTTP + gRPC 链路追踪）
- prometheus-client + structlog（指标 + 结构化日志）

## 架构分层

```
HTTP 端点（/v1/ai 前缀，ActionState 信封）
    ↓
PermissionGuard（权限校验）→ RateLimiter（三维度令牌桶）
    ↓
Service 层（ChatService / QuestionService / ExpressionService / LessonPlanWorkflowService）
    ↓
LLM ProviderFailoverChain（4 适配器 + CircuitBreaker）
    ↓
LLM Provider（OpenAI / Anthropic / Baichuan / LocalOllama）

gRPC server（端口 50058，8 RPC，interceptor 链）
    ↓
AiServicer（proto ↔ domain 模型转换）
    ↓
Service 层（同 HTTP）

备课工作流：FastAPI BackgroundTasks + Redis 状态存储（24h TTL）
安全层：PIIRedactor + InputSanitizer + OutputModerator
用量：UsageRecorder（Redis）→ KafkaProducer → edu.ai.usage topic
```

## 开发

```bash
# 安装依赖
uv sync

# 启动开发服务（dev_mode=true 跳过 OTel exporter）
DEV_MODE=true uv run uvicorn src.ai.main:app --reload --port 3008

# Lint
uv run ruff check src/

# 测试
uv run pytest
```

## HTTP API

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /healthz | — | liveness |
| GET | /readyz | — | readiness（含 LLM/gRPC/Provider 状态） |
| GET | /metrics | — | Prometheus 指标 |
| POST | /v1/ai/chat | ai:chat | 非流式聊天 |
| POST | /v1/ai/chat/stream | ai:chat | 流式聊天（SSE） |
| POST | /v1/ai/generate/question | ai:question:generate | 生成题目 |
| POST | /v1/ai/generate/question/stream | ai:question:generate | 流式生成题目（SSE） |
| POST | /v1/ai/optimize/expression | ai:expression:optimize | 优化表达 |
| POST | /v1/ai/lesson-plan/generate | ai:lesson:generate | 启动备课工作流 |
| GET | /v1/ai/lesson-plan/status/{workflow_id} | — | 查询工作流状态 |
| POST | /v1/ai/lesson-plan/confirm/{workflow_id} | ai:lesson:confirm | 教师确认入库 |

所有业务响应统一使用 ActionState 信封：`{success, data, error:{code, message, details?, traceId?}}`

降级采用方案 B（总裁裁决 §2.6）：`success=true + error=null + data 内 degraded=true + degraded_reason`

## gRPC API（端口 50058，8 RPC）

| RPC | 请求 | 响应 |
|-----|------|------|
| Chat | ChatRequest | ChatResponse |
| StreamChat | ChatRequest | stream ChatChunk |
| GenerateQuestion | GenerateQuestionRequest | GeneratedQuestion |
| StreamGenerateQuestion | GenerateQuestionRequest | stream GeneratedQuestionChunk |
| OptimizeExpression | OptimizeExpressionRequest | OptimizedExpression |
| GenerateLessonPlan | GenerateLessonPlanRequest | LessonPlanResponse |
| GetLessonPlanStatus | GetLessonPlanStatusRequest | LessonPlanStatus |
| ConfirmLessonPlan | ConfirmLessonPlanRequest | ConfirmResult |

## 环境变量

| 变量 | 默认值 | 说明 |
|------|--------|------|
| http_port | 3008 | HTTP 端口 |
| grpc_port | 50058 | gRPC 端口 |
| dev_mode | false | 开发模式（跳过 OTel + 权限校验） |
| openai_api_key | — | OpenAI API 密钥 |
| anthropic_api_key | — | Anthropic API 密钥 |
| baichuan_api_key | — | 百川 API 密钥 |
| ollama_base_url | — | 本地 Ollama 地址 |
| llm_provider_priority | openai,anthropic,baichuan,local_ollama | Provider 故障切换顺序 |
| redis_url | redis://localhost:6379/0 | Redis 连接 |
| kafka_bootstrap_servers | localhost:9092 | Kafka 连接 |
| kafka_ai_usage_topic | edu.ai.usage | 用量事件 topic |
| otel_endpoint | http://localhost:4318 | OpenTelemetry OTLP 端点 |

## 限流（三维度）

| 维度 | 限制 | 算法 |
|------|------|------|
| user | 10 req/min | Redis Lua 令牌桶 |
| IP | 30 req/min | Redis Lua 令牌桶 |
| school | 100 req/min | Redis Lua 令牌桶 |

Redis 不可用时降级放行（记录警告）。

## 错误码

前缀 `AI_*`，完整 21 个错误码见 [02-architecture-design.md §6.2](docs/02-architecture-design.md)。
