From bd8698f44700f42dfc0697d2cb428eba142ebb6e Mon Sep 17 00:00:00 2001 From: TetrisGGBOBot Date: Tue, 31 Mar 2026 13:48:51 +0000 Subject: [PATCH] refactor: split streaming endpoint from Azure Functions Disable /search/stream in Function runtime with a 501 guidance response, keep streaming on always-on FastAPI service, and update README with the new deployment and routing model. Made-with: Cursor --- README.md | 22 ++++++++++++++++++---- ai_search_agent/api.py | 20 ++++++++++++++++++-- function_app.py | 4 +++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 35a91d8..ab562ad 100644 --- a/README.md +++ b/README.md @@ -103,9 +103,10 @@ uvicorn serve:app --host 0.0.0.0 --port 8080 --reload |------|------|------| | GET | `/health` | 健康检查(本地 FastAPI) | | POST | `/search` | 同步搜索(阻塞式,本地 FastAPI) | -| POST | `/search/stream` | 流式搜索(SSE 事件,本地 FastAPI) | +| POST | `/search/stream` | 流式搜索(SSE 事件,仅常驻 FastAPI 服务) | -> Azure Functions 部署时由 `function_app.py` 挂载到 `/api` 前缀,对外路径分别为 `/api/health`、`/api/search`、`/api/search/stream`。 +> Azure Functions 部署时由 `function_app.py` 挂载到 `/api` 前缀,仅提供 `/api/health`、`/api/search`。 +> `/search/stream` 必须走常驻 `uvicorn` 服务(避免 Functions 网关缓冲导致假流式)。 ### 1. 健康检查 @@ -144,10 +145,10 @@ Jina 搜索结果固定上限: - `cache_hit` - 是否缓存命中 - `audit_id` - 审计追踪 ID -### 3. SSE 流式搜索 +### 3. SSE 流式搜索(常驻 FastAPI) ```bash -curl -N -X POST https://aisousuo.azurewebsites.net/api/search/stream \ +curl -N -X POST https://your-stream-service/search/stream \ -H "Content-Type: application/json" \ -d '{"query":"PydanticAI 适合哪些场景?","search_mode":"deep"}' ``` @@ -157,6 +158,8 @@ curl -N -X POST https://aisousuo.azurewebsites.net/api/search/stream \ > 当请求超时时,SSE 会发送 `timeout` 事件并结束流。 +> 如果你误调 Azure Functions 的 `/api/search/stream`,会返回 `501`,并提示使用独立流式服务地址。 + --- ## 环境变量配置 @@ -233,6 +236,17 @@ func azure functionapp publish aisousuo --python curl https://aisousuo.azurewebsites.net/api/health ``` +### 流式服务部署(推荐 App Service / Container) + +```bash +# 启动常驻服务(示例) +uvicorn serve:app --host 0.0.0.0 --port 8080 +``` + +- 将前端流式请求改为 `https:///search/stream` +- Azure Functions 继续承载同步接口:`/api/search` +- 可在 Functions 环境变量中配置 `STREAM_SERVICE_BASE_URL=https://`,用于错误提示回传目标流式地址 + --- ## 设计架构 diff --git a/ai_search_agent/api.py b/ai_search_agent/api.py index bd02472..e7629a4 100644 --- a/ai_search_agent/api.py +++ b/ai_search_agent/api.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio import json +import os from fastapi import FastAPI from fastapi.responses import JSONResponse @@ -13,7 +14,7 @@ from .config import Settings from .models import SearchRequest, SearchResponse -def create_app() -> FastAPI: +def create_app(enable_streaming: bool = True) -> FastAPI: settings = Settings.from_env() agent = AISearchAgent(settings) audit_logger = AuditLogger(settings.audit_log_path) @@ -44,7 +45,22 @@ def create_app() -> FastAPI: return JSONResponse(content=response.model_dump()) @app.post("/search/stream") - async def search_stream(request: SearchRequest) -> StreamingResponse: + async def search_stream(request: SearchRequest): + if not enable_streaming: + stream_base_url = os.getenv("STREAM_SERVICE_BASE_URL", "").rstrip("/") + return JSONResponse( + status_code=501, + content={ + "error": "STREAMING_NOT_AVAILABLE_IN_FUNCTIONS", + "message": "Use the always-on stream service for /search/stream.", + "stream_endpoint": ( + f"{stream_base_url}/search/stream" if stream_base_url else None + ), + "query": request.query, + "search_mode": agent.resolve_search_mode(request), + }, + ) + async def event_generator(): # Send an immediate event so clients/proxies flush the SSE channel early. yield "event: connected\ndata: {}\n\n" diff --git a/function_app.py b/function_app.py index a89f6eb..b348c4e 100644 --- a/function_app.py +++ b/function_app.py @@ -6,7 +6,9 @@ from starlette.routing import Mount from ai_search_agent.api import create_app -_fastapi = create_app() +# Azure Functions keeps only synchronous search endpoints. +# Stream endpoint should be served by always-on uvicorn service. +_fastapi = create_app(enable_streaming=False) # Azure Functions delivers the full path including /api/ prefix to the ASGI app. # Mount FastAPI under /api so that /api/health -> FastAPI /health, etc.