#!/usr/bin/env python3 """真实 DeepSeek → SearXNG → DeepSeek 联网闭环验收。 需要服务器密钥与已启动的 SearXNG,不属于日常离线回归测试。 """ from __future__ import annotations import asyncio import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent.parent sys.path.insert(0, str(ROOT)) from app.config import AppConfig # noqa: E402 from app.llm import create_provider # noqa: E402 from app.search import ( # noqa: E402 WEB_SEARCH_PROMPT, WEB_SEARCH_TOOL, SearXNGSearchClient, ) async def main() -> int: cfg = AppConfig.load() if not cfg.search.enabled: raise RuntimeError("请先设置 SEARCH_ENABLED=true") llm = create_provider(cfg.llm) search = SearXNGSearchClient(cfg.search) messages = [ {"role": "system", "content": WEB_SEARCH_PROMPT}, {"role": "user", "content": "请搜索 DeepSeek 最近有什么消息,用一句话概括。"}, ] try: first = await llm.complete(messages, tools=[WEB_SEARCH_TOOL]) if not first.tool_calls: raise AssertionError(f"模型没有调用搜索工具,直接回答:{first.content!r}") call = first.tool_calls[0] query = call.parsed_arguments().get("query") if not isinstance(query, str) or not query.strip(): raise AssertionError(f"搜索参数无效:{call.arguments!r}") results = await search.search(query) if not results: raise AssertionError(f"SearXNG 对 {query!r} 没有返回结果") messages.extend([ { "role": "assistant", "content": first.content or None, "tool_calls": [{ "id": call.id, "type": "function", "function": {"name": call.name, "arguments": call.arguments}, }], }, { "role": "tool", "tool_call_id": call.id, "content": search.for_model(query, results), }, ]) final = await llm.complete(messages, max_tokens=250) if not final.content.strip(): raise AssertionError("搜索回灌后模型没有生成回答") print(f"工具调用: {call.name}({query!r})") print(f"搜索结果: {len(results)} 条;首条={results[0].title!r}") print(f"最终回答: {final.content.strip()}") print("真实联网闭环:PASS") return 0 finally: await llm.aclose() if __name__ == "__main__": raise SystemExit(asyncio.run(main()))