From dfb745a5e46b5a63c64db8525187ee462842a96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Tue, 30 Jun 2026 16:04:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agents/ai_writing/nodes/researcher.py | 5 ++- .../agents/ai_writing/search/intranet_stub.py | 37 +++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/researcher.py b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/researcher.py index 77b51ff5..b53b5955 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/researcher.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/researcher.py @@ -349,7 +349,10 @@ async def _search_notebook( results: list[Material] = [] mat_idx = 0 try: - async with httpx.AsyncClient(timeout=20) as client: + # trust_env=False:笔记后端是内网/本机地址,但模型走公网,部署常设 + # HTTP_PROXY/HTTPS_PROXY;httpx 默认会把这个内网请求也走代理 → 不可达。 + # 强制忽略代理 env、直连(与内网 ES 检索同款修法)。 + async with httpx.AsyncClient(timeout=20, trust_env=False) as client: for kw in keywords[:_NOTEBOOK_MAX_KEYWORDS]: logger.info("[notebook-search] POST kw=%r body=%s", kw, { "query": kw, diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/search/intranet_stub.py b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/search/intranet_stub.py index ce3e6f2b..3f5fcce1 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/search/intranet_stub.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/search/intranet_stub.py @@ -113,8 +113,19 @@ class IntranetSearchProvider(BaseSearchProvider): "ok": False, "count": 0, } + # 排查用:每次真实调用都打一行请求明细(端点 / 知识库 / 检索词 / 超时), + # 内网复现时直接看后端日志即可知道"到底请求了哪个地址、用什么参数"。 + logger.info( + "[intranet-search] POST endpoint=%s kb=%s timeout=%ss query=%r", + self.endpoint, self.knowledge_base_name, self.timeout, query, + ) try: - async with httpx.AsyncClient(timeout=self.timeout) as client: + # trust_env=False:**关键**。内网 ES 是内网地址,但本部署的模型走公网, + # 服务器通常设了 HTTP_PROXY/HTTPS_PROXY 出公网;httpx 默认 trust_env=True + # 会把这个内网请求也套用公网代理 → 代理到不了内网 IP → 连接被重置/空回复, + # 表现为「接口异常」。强制忽略代理 env、直连内网(与 multi_agent.py 的回环 + # 调用同款修法)。 + async with httpx.AsyncClient(timeout=self.timeout, trust_env=False) as client: resp = await client.post(self.endpoint, json=payload) call["http_status"] = getattr(resp, "status_code", None) resp.raise_for_status() @@ -122,23 +133,31 @@ class IntranetSearchProvider(BaseSearchProvider): data = resp.json() except httpx.HTTPStatusError as e: # 非 2xx:接口有响应但状态码异常,把响应体一并暴露给前端 - logger.warning("intranet search failed for query=%r: %s", query, e) + body_for_log = e.response.text if e.response is not None else "" + logger.warning( + "[intranet-search] HTTP error endpoint=%s query=%r status=%s body=%s", + self.endpoint, query, getattr(e.response, "status_code", None), _raw_snippet(body_for_log), + ) message = f"{type(e).__name__}: {e}" self.errors.append(message) - body = e.response.text if e.response is not None else "" call.update({ "status": "http_error", "error": message, "http_status": getattr(e.response, "status_code", None), }) - snippet = _raw_snippet(body) + snippet = _raw_snippet(body_for_log) if snippet: call["raw_response"] = snippet - self._record_raw(body) + self._record_raw(body_for_log) return [] except Exception as e: - # 连接失败 / 超时 / JSON 解析失败等;JSON 解析失败时 raw_text 已是接口响应体 - logger.warning("intranet search failed for query=%r: %s", query, e) + # 连接失败 / 超时 / JSON 解析失败等;JSON 解析失败时 raw_text 已是接口响应体。 + # 打出异常类型,便于区分 ConnectError / ConnectTimeout / ReadTimeout / + # RemoteProtocolError(空回复) / JSONDecodeError(返回非 JSON) 等具体根因。 + logger.warning( + "[intranet-search] request failed endpoint=%s query=%r error=%s: %s", + self.endpoint, query, type(e).__name__, e, + ) message = f"{type(e).__name__}: {e}" self.errors.append(message) call.update({"status": "error", "error": message}) @@ -187,6 +206,10 @@ class IntranetSearchProvider(BaseSearchProvider): self._record_raw(raw_text) return [] + logger.info( + "[intranet-search] OK endpoint=%s query=%r http_status=%s raw_items=%s", + self.endpoint, query, call.get("http_status"), len(items) if isinstance(items, list) else 0, + ) results: List[SearchResult] = [] for i, item in enumerate(items[:max_results]): if not isinstance(item, dict):