改用Grok联网搜索并保留每轮信号

This commit is contained in:
Codex
2026-07-09 00:14:20 +08:00
parent 4f50de8def
commit e02d5db495
14 changed files with 465 additions and 110 deletions
+12 -5
View File
@@ -35,22 +35,29 @@ def test_home_page_returns_dashboard_shell(monkeypatch) -> None:
assert response.status_code == 200
assert "SignalScout" in response.text
assert "今日 AI 信号" in response.text
assert "手动搜索" in response.text
assert "renderMarkdownReport" in response.text
assert "自动记录" not in response.text
assert "采集今日情报" not in response.text
def test_manual_agent_run_endpoint_is_not_exposed(monkeypatch) -> None:
# Agent只通过后台调度器自动运行,HTTP API不提供手动启动入口
def test_manual_agent_run_endpoint_starts_background_job(monkeypatch) -> None:
# 手动搜索入口复用调度任务,方便验证每轮候选和入库数量
monkeypatch.setenv("SIGNALSCOUT_ENV", "test")
get_settings.cache_clear()
started = []
def fake_run_scheduled_job(settings):
started.append(settings.env)
monkeypatch.setattr("app.api.routes.run_scheduled_job", fake_run_scheduled_job)
with TestClient(create_app()) as client:
response = client.post("/agent/runs/daily")
stream_response = client.post("/agent/runs/daily/stream")
get_settings.cache_clear()
assert response.status_code == 404
assert stream_response.status_code == 404
assert response.status_code == 200
assert response.json()["status"] == "started"
assert started == ["test"]
def test_dashboard_returns_news_and_github_signals_separately(monkeypatch) -> None:
+37 -13
View File
@@ -2,26 +2,50 @@ from app.core.config import Settings
from app.integrations.news_search_client import NewsSearchClient
def test_news_search_client_maps_article_to_candidate() -> None:
# 新闻搜索结果会先转为候选信号,再交给模型筛选成最终情报
def test_news_search_client_maps_grok_article_to_candidate() -> None:
# Grok Web Search结果会先转为候选信号,再进入入库和日报总结流程
client = NewsSearchClient(Settings())
signal = client._story_to_signal(
signal = client._article_to_signal(
{
"title": "Show HN: AI agent platform launches",
"url": "https://example.com/ai-agent-platform",
"author": "builder",
"points": 128,
"num_comments": 24,
"created_at": "2026-07-08T12:00:00Z",
"objectID": "123",
"title": "国内大模型公司发布新一代Agent平台",
"summary": "中文AI新闻候选。",
"url": "https://example.cn/ai-agent-platform",
"source": "示例中文媒体",
"published_at": "2026-07-08T12:00:00Z",
"language": "zh",
"importance": 4,
"entities": ["Agent平台"],
}
)
assert signal is not None
assert signal.topic == "AI 新闻候选"
assert signal.topic == "中文 AI 新闻"
assert signal.source_type == "news"
assert signal.source_name == "Hacker News"
assert signal.source_name == "示例中文媒体"
assert signal.published_at is not None
assert signal.published_at.year == 2026
assert signal.published_at.hour == 20
assert "128 points" in signal.summary
assert signal.importance == 4
def test_news_search_client_parses_responses_output_text() -> None:
# Responses API会返回多段output,业务JSON只来自最终output_text。
client = NewsSearchClient(Settings())
payload = client._parse_response_json(
{
"output": [
{"type": "web_search_call", "status": "completed"},
{
"type": "message",
"content": [
{
"type": "output_text",
"text": '{"items":[{"title":"AI news","url":"https://example.com","source":"Example"}]}',
}
],
},
]
}
)
assert payload["items"][0]["title"] == "AI news"
+81
View File
@@ -0,0 +1,81 @@
from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from app.db.models import AgentRun, Base
from app.db.repository import AgentRepository
from app.schemas.agent import SignalItem
def test_save_signals_keeps_same_url_across_runs() -> None:
# 跨运行保留同一链接,保证每轮日报的实际信号快照不会被历史结果吞掉。
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
future=True,
)
Base.metadata.create_all(engine)
TestingSessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
with TestingSessionLocal() as session:
repo = AgentRepository(session)
first_run = AgentRun(status="running", started_at=datetime(2026, 7, 8), request_payload={})
second_run = AgentRun(status="running", started_at=datetime(2026, 7, 8), request_payload={})
session.add_all([first_run, second_run])
session.flush()
item = SignalItem(
source_type="news",
topic="AI 新闻",
title="Same URL",
summary="同一个来源链接。",
source_url="https://example.com/same",
source_name="Example",
published_at=datetime(2026, 7, 8),
importance=3,
entities=["Example"],
)
first_saved = repo.save_signals(first_run, [item])
second_saved = repo.save_signals(second_run, [item])
assert len(first_saved) == 1
assert len(second_saved) == 1
assert first_saved[0].run_id != second_saved[0].run_id
def test_save_signals_updates_duplicate_url_inside_same_run() -> None:
# 同一运行内相同链接只保留一条,避免模型重复输出造成页面重复。
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
future=True,
)
Base.metadata.create_all(engine)
TestingSessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
with TestingSessionLocal() as session:
repo = AgentRepository(session)
run = AgentRun(status="running", started_at=datetime(2026, 7, 8), request_payload={})
session.add(run)
session.flush()
first = SignalItem(
source_type="news",
topic="AI 新闻",
title="First title",
summary="第一条摘要。",
source_url="https://example.com/same",
source_name="Example",
published_at=datetime(2026, 7, 8),
importance=3,
entities=["Example"],
)
second = first.model_copy(update={"title": "Second title", "importance": 5})
saved = repo.save_signals(run, [first, second])
assert len(saved) == 2
assert saved[0].id == saved[1].id
assert saved[0].title == "Second title"
assert saved[0].importance == 5