Files
planet/backend/tests/test_ai_observability.py
rayd1o 5bf5c73ca0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
release: bump version to 0.66.0
2026-05-26 03:41:47 +08:00

129 lines
4.8 KiB
Python

import pytest
from fastapi import HTTPException
from app.schemas.ai import SituationalAnalysisRequest
from app.services.ai_tools import web_search as web_search_module
from app.services.ai_tools.schemas import SearchEvidence, WebSearchConfig, WebSearchProviderConfig
from app.services.ai_tools.web_search import WebSearchClient
from app.services import ai_client as ai_client_module
from app.services.ai_client import AIProviderClient
@pytest.mark.asyncio
async def test_ai_client_analyze_logs_summary_without_prompt(monkeypatch):
events = []
async def fake_emit_business_log(_logger, **payload):
events.append(payload)
async def fake_request(self, method, path, json=None, request_id=None, operation="request", payload_summary=None):
return {
"provider": "test-provider",
"model": "test-model",
"content": "ok",
"content_blocks": [],
"text_blocks": ["ok"],
"thinking_blocks": [],
"raw_response": {},
}
monkeypatch.setattr(ai_client_module, "emit_business_log", fake_emit_business_log)
monkeypatch.setattr(AIProviderClient, "_request", fake_request)
client = AIProviderClient(
service_url="http://provider.test",
llm_config={"provider": "openai", "provider_api": "openai-completions", "model": "gpt-test", "api_key": "sk-secret"},
)
result = await client.analyze(
SituationalAnalysisRequest(
title="Sensitive title",
objective="Do not store this full prompt",
observations=["secret observation"],
constraints=["secret constraint"],
context={"source": "test", "private": "value"},
),
request_id="req-ai-test",
)
assert result.model == "test-model"
assert [event["event"] for event in events] == [
"ai.provider.analyze.start",
"ai.provider.analyze.success",
]
serialized = str(events)
assert "Do not store this full prompt" not in serialized
assert "secret observation" not in serialized
assert "sk-secret" not in serialized
start_context = events[0]["context"]
assert start_context["model"] == "gpt-test"
assert start_context["input_summary"]["objective_length"] == len("Do not store this full prompt")
assert start_context["input_summary"]["observation_count"] == 1
assert start_context["input_summary"]["context_keys"] == ["private", "source"]
@pytest.mark.asyncio
async def test_ai_client_analyze_logs_failure(monkeypatch):
events = []
async def fake_emit_business_log(_logger, **payload):
events.append(payload)
async def fake_request(self, method, path, json=None, request_id=None, operation="request", payload_summary=None):
raise HTTPException(status_code=502, detail="provider failed")
monkeypatch.setattr(ai_client_module, "emit_business_log", fake_emit_business_log)
monkeypatch.setattr(AIProviderClient, "_request", fake_request)
client = AIProviderClient(service_url="http://provider.test", llm_config={"provider": "openai", "model": "gpt-test"})
with pytest.raises(HTTPException):
await client.analyze(
SituationalAnalysisRequest(title="T", objective="O", observations=["one"]),
request_id="req-ai-fail",
)
assert events[-1]["event"] == "ai.provider.analyze.failed"
assert events[-1]["level"] == "error"
assert events[-1]["context"]["error_type"] == "HTTPException"
@pytest.mark.asyncio
async def test_web_search_logs_query_hash_without_query(monkeypatch):
events = []
async def fake_emit_business_log(_logger, **payload):
events.append(payload)
async def fake_search_tavily(self, config, query, max_results, domains, freshness_days):
return [
SearchEvidence(
title="Example",
url="https://example.test",
snippet="result",
source_provider="tavily",
)
]
monkeypatch.setattr(web_search_module, "emit_business_log", fake_emit_business_log)
monkeypatch.setattr(WebSearchClient, "_search_tavily", fake_search_tavily)
client = WebSearchClient(
WebSearchConfig(
enabled=True,
default_provider="tavily",
providers={"tavily": WebSearchProviderConfig(provider="tavily", api_key="secret-key")},
)
)
results = await client.search("secret query text", max_results=1)
assert len(results) == 1
assert [event["event"] for event in events] == [
"ai_tool.web_search.start",
"ai_tool.web_search.success",
]
serialized = str(events)
assert "secret query text" not in serialized
assert "secret-key" not in serialized
assert events[0]["context"]["query_length"] == len("secret query text")
assert events[1]["context"]["result_count"] == 1