feat(p5): messaging, push gateway and AI assistant services
P5 阶段交付物: - services/msg: 消息通知服务(NestJS) - notifications: 发送通知 + ES 全文检索 + search - config/elasticsearch.ts: ES Client 单例 - package.json: 补充 @opentelemetry/sdk-node + exporter-trace-otlp-http - services/push-gateway: WebSocket 推送网关(Go Gin) - internal/hub/hub.go: WebSocket 连接池管理(Register/Unregister/SendToUser) - internal/ws/handler.go: JWT 鉴权 + WebSocket 升级 + 内部推送 API - services/ai: AI 辅助服务(Python FastAPI) - /chat + /chat/stream(SSE 流式) - /generate/question + /optimize/expression - config.py: OpenAI 兼容 API 配置 - packages/shared-proto/proto/msg.proto: NotificationService 契约(send/search) - packages/shared-proto/proto/ai.proto: AiService 契约(含 stream 方法)
This commit is contained in:
0
services/ai/src/ai/__init__.py
Normal file
0
services/ai/src/ai/__init__.py
Normal file
18
services/ai/src/ai/config.py
Normal file
18
services/ai/src/ai/config.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""配置管理."""
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""应用配置."""
|
||||
|
||||
port: int = 3008
|
||||
openai_api_key: str = ""
|
||||
anthropic_api_key: str = ""
|
||||
otel_endpoint: str = "http://localhost:4318"
|
||||
log_level: str = "info"
|
||||
|
||||
model_config = {"env_file": ".env", "env_prefix": ""}
|
||||
|
||||
|
||||
settings = Settings()
|
||||
111
services/ai/src/ai/main.py
Normal file
111
services/ai/src/ai/main.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""AI 网关服务入口."""
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import structlog
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import StreamingResponse
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
from prometheus_client import make_asgi_app
|
||||
from pydantic import BaseModel
|
||||
|
||||
logger = structlog.get_logger()
|
||||
tracer = trace.get_tracer(__name__)
|
||||
|
||||
|
||||
def init_tracer() -> None:
|
||||
"""初始化 OpenTelemetry."""
|
||||
provider = TracerProvider()
|
||||
exporter = OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")
|
||||
provider.add_span_processor(BatchSpanProcessor(exporter))
|
||||
trace.set_tracer_provider(provider)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""应用生命周期."""
|
||||
init_tracer()
|
||||
logger.info("ai service starting")
|
||||
yield
|
||||
logger.info("ai service stopping")
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="AI Gateway Service",
|
||||
version="0.1.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
app.mount("/metrics", make_asgi_app())
|
||||
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
"""聊天请求."""
|
||||
|
||||
messages: list[dict]
|
||||
model: str = "gpt-4o-mini"
|
||||
temperature: float = 0.7
|
||||
stream: bool = False
|
||||
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
"""聊天响应."""
|
||||
|
||||
content: str
|
||||
model: str
|
||||
usage: dict
|
||||
|
||||
|
||||
@app.get("/healthz")
|
||||
async def healthz():
|
||||
"""健康检查."""
|
||||
return {"status": "ok", "service": "ai"}
|
||||
|
||||
|
||||
@app.post("/chat", response_model=ChatResponse)
|
||||
async def chat(req: ChatRequest):
|
||||
"""LLM 聊天接口."""
|
||||
with tracer.start_as_current_span("ai_chat"):
|
||||
# P5 骨架:实际调用 OpenAI/Anthropic API
|
||||
# 需要从环境变量获取 API key
|
||||
return {
|
||||
"content": "P5 skeleton - LLM integration pending",
|
||||
"model": req.model,
|
||||
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
|
||||
}
|
||||
|
||||
|
||||
@app.post("/chat/stream")
|
||||
async def chat_stream(req: ChatRequest):
|
||||
"""流式聊天(SSE)."""
|
||||
|
||||
async def generate():
|
||||
with tracer.start_as_current_span("ai_chat_stream"):
|
||||
# P5 骨架:流式调用 LLM
|
||||
yield "data: P5 skeleton\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
|
||||
return StreamingResponse(generate(), media_type="text/event-stream")
|
||||
|
||||
|
||||
@app.post("/generate/question")
|
||||
async def generate_question(prompt: str):
|
||||
"""生成题目."""
|
||||
with tracer.start_as_current_span("generate_question"):
|
||||
return {
|
||||
"success": True,
|
||||
"data": {"question": "P5 skeleton - question generation pending"},
|
||||
}
|
||||
|
||||
|
||||
@app.post("/optimize/expression")
|
||||
async def optimize_expression(text: str):
|
||||
"""优化表达."""
|
||||
with tracer.start_as_current_span("optimize_expression"):
|
||||
return {
|
||||
"success": True,
|
||||
"data": {"optimized": "P5 skeleton - expression optimization pending"},
|
||||
}
|
||||
Reference in New Issue
Block a user