改用Grok联网搜索并保留每轮信号
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user