add health endpoint for gateway and everos

This commit is contained in:
2026-06-11 10:52:13 +08:00
parent b74923e435
commit 7155704b73
4 changed files with 138 additions and 9 deletions

View File

@ -111,7 +111,55 @@ resource:{user_id}:{resource_id}
`POST /users` 外,所有业务 API 都需要携带 `user_id``user_key`。认证失败返回 `401`
### 1. 创建用户
### 1. 健康检查
```http
GET /health
```
该接口不需要 `user_id``user_key`,用于确认 Gateway API 是否可响应,以及上游 EverOS 是否可访问。
请求示例:
```bash
curl http://127.0.0.1:8010/health
```
EverOS 正常时响应示例:
```json
{
"status": "ok",
"api": {
"status": "ok"
},
"everos": {
"status": "ok",
"base_url": "http://127.0.0.1:8000",
"data": {
"status": "ok"
}
}
}
```
EverOS 不可访问时仍返回 HTTP 200`status` 会变成 `degraded`便于区分“Gateway API 活着”和“上游 EverOS 故障”:
```json
{
"status": "degraded",
"api": {
"status": "ok"
},
"everos": {
"status": "unavailable",
"base_url": "http://127.0.0.1:8000",
"error": "Connection refused"
}
}
```
### 2. 创建用户
```http
POST /users
@ -144,7 +192,7 @@ curl -X POST http://127.0.0.1:8010/users \
`user_key` 需要由调用方保存,后续上传、查询、搜索、修改和删除都要传入。如果同一个 `user_id` 已存在,接口会返回已有 `user_key`
### 2. 上传资源
### 3. 上传资源
```http
POST /resources
@ -212,7 +260,7 @@ curl -X POST http://127.0.0.1:8010/resources \
对外返回的 `uri` 永远是 `resource://{user_id}/{resource_id}`,不会泄露内部 `file://` 路径。
### 3. 查询资源列表
### 4. 查询资源列表
```http
GET /resources?user_id={user_id}&user_key={user_key}
@ -264,7 +312,7 @@ curl "http://127.0.0.1:8010/resources?user_id=u_123&user_key=uk_xxx"
}
```
### 4. 查询资源详情
### 5. 查询资源详情
```http
GET /resources/{resource_id}?user_id={user_id}&user_key={user_key}
@ -322,7 +370,7 @@ curl "http://127.0.0.1:8010/resources/r_xxx?user_id=u_123&user_key=uk_xxx"
这种设计避免通过资源 ID 探测其他用户的数据。`uri` 同样只返回公开 `resource://{user_id}/{resource_id}`,不会泄露内部 URI。
### 5. 删除资源
### 6. 删除资源
```http
DELETE /resources/{resource_id}?user_id={user_id}&user_key={user_key}
@ -352,7 +400,7 @@ curl -X DELETE "http://127.0.0.1:8010/resources/r_xxx?user_id=u_123&user_key=uk_
}
```
### 6. 搜索记忆
### 7. 搜索记忆
```http
POST /memories/search
@ -429,7 +477,7 @@ curl -X POST http://127.0.0.1:8010/memories/search \
}
```
### 7. 修改记忆
### 8. 修改记忆
```http
PATCH /memories/{memory_id}
@ -470,7 +518,7 @@ curl -X PATCH http://127.0.0.1:8010/memories/mem_abc \
}
```
### 8. 删除记忆
### 9. 删除记忆
```http
DELETE /memories/{memory_id}
@ -518,6 +566,7 @@ Gateway 内部通过 `core/everos_client.py` 调用 EverOS
- `add_memory(payload)` -> `POST /api/v1/memory/add`
- `flush_memory(session_id, app_id, project_id)` -> `POST /api/v1/memory/flush`
- `search_memory(payload)` -> `POST /api/v1/memory/search`
- `health_check()` -> `GET /health`
## 运行测试

View File

@ -66,6 +66,30 @@ def create_app(
if not service.authenticate_user(user_id, user_key):
raise HTTPException(status_code=401, detail="invalid user credentials")
@router.get("/health")
async def health() -> dict[str, Any]:
try:
everos_health = await client.health_check()
except Exception as exc:
return {
"status": "degraded",
"api": {"status": "ok"},
"everos": {
"status": "unavailable",
"base_url": cfg.everos_base_url,
"error": str(exc),
},
}
return {
"status": "ok",
"api": {"status": "ok"},
"everos": {
"status": "ok",
"base_url": cfg.everos_base_url,
"data": everos_health,
},
}
@router.post("/users")
async def create_user(request: UserCreateRequest) -> dict[str, Any]:
return service.create_user(request.user_id)

View File

@ -31,6 +31,15 @@ class EverOSClient:
async def search_memory(self, payload: dict[str, Any]) -> dict[str, Any]:
return await self._post("/api/v1/memory/search", payload)
async def health_check(self) -> dict[str, Any]:
async with httpx.AsyncClient(
base_url=self.base_url,
timeout=self.timeout,
) as client:
response = await client.get("/health")
response.raise_for_status()
return response.json()
async def _post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
async with httpx.AsyncClient(
base_url=self.base_url,

View File

@ -13,11 +13,16 @@ from core.repository import MemoryRepository
class FakeEverOSClient:
def __init__(self, search_results: list[dict[str, Any]] | None = None) -> None:
def __init__(
self,
search_results: list[dict[str, Any]] | None = None,
health_error: Exception | None = None,
) -> None:
self.add_calls: list[dict[str, Any]] = []
self.flush_calls: list[dict[str, str]] = []
self.search_calls: list[dict[str, Any]] = []
self.search_results = search_results or []
self.health_error = health_error
async def add_memory(self, payload: dict[str, Any]) -> dict[str, Any]:
self.add_calls.append(payload)
@ -38,6 +43,11 @@ class FakeEverOSClient:
self.search_calls.append(payload)
return {"request_id": "search", "data": {"episodes": self.search_results}}
async def health_check(self) -> dict[str, Any]:
if self.health_error is not None:
raise self.health_error
return {"status": "ok"}
@pytest.fixture
def config(tmp_path: Path) -> GatewayConfig:
@ -72,6 +82,43 @@ async def create_user(client: httpx.AsyncClient, user_id: str = "u_123") -> str:
return body["user_key"]
@pytest.mark.asyncio
async def test_health_reports_api_and_everos_ok(
config: GatewayConfig,
) -> None:
everos = FakeEverOSClient()
async with app_client(config, everos) as client:
response = await client.get("/health")
assert response.status_code == 200, response.text
assert response.json() == {
"status": "ok",
"api": {"status": "ok"},
"everos": {
"status": "ok",
"base_url": "http://everos.test",
"data": {"status": "ok"},
},
}
@pytest.mark.asyncio
async def test_health_reports_degraded_when_everos_fails(
config: GatewayConfig,
) -> None:
everos = FakeEverOSClient(health_error=RuntimeError("everos down"))
async with app_client(config, everos) as client:
response = await client.get("/health")
assert response.status_code == 200, response.text
body = response.json()
assert body["status"] == "degraded"
assert body["api"] == {"status": "ok"}
assert body["everos"]["status"] == "unavailable"
assert body["everos"]["base_url"] == "http://everos.test"
assert body["everos"]["error"] == "everos down"
@pytest.mark.asyncio
async def test_create_user_generates_and_persists_user_key(
config: GatewayConfig,