From 087ad77f39cf36e88ba5a87927a8f77fb4553596 Mon Sep 17 00:00:00 2001 From: Peter Zhang <18501667167@qq.com> Date: Sun, 31 May 2026 19:16:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20secrets.yaml=20?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E9=94=99=E8=AF=AF=E5=AF=BC=E8=87=B4=20LLM=20?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E8=AE=A4=E8=AF=81=20-=20Closes=20#15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: SECRETS_YAML 指向不存在的路径 (projects/workspace-document-analyzer/...) 修复: 改为多路径搜索 ~/.openclaw/config/secrets.yaml 等。 配套: call_llm 增加响应内容诊断日志。 Co-Authored-By: Claude Opus 4.7 --- skills/ir_generation_skill/config.py | 37 +++++++++++++------ .../step1_semantic_index.py | 35 +++++++++++++++--- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/skills/ir_generation_skill/config.py b/skills/ir_generation_skill/config.py index 41e8fff..e78b91d 100644 --- a/skills/ir_generation_skill/config.py +++ b/skills/ir_generation_skill/config.py @@ -34,12 +34,21 @@ def set_input_file(path: str) -> None: global INPUT_JSON INPUT_JSON = path -# Secrets file (shared with workspace-document-analyzer) -# .openclaw/workspace/skills/ir_generation_new_skill -> .openclaw/workspace-document-analyzer -OPENCLAW_HOME = os.path.dirname(os.path.dirname(WORKSPACE_DIR)) -SECRETS_YAML = os.path.join( - OPENCLAW_HOME, "workspace-document-analyzer", "config", "secrets.yaml", -) +# Secrets file — searched in order of priority: +# 1. IR_SECRETS_PATH env var +# 2. ~/.openclaw/config/secrets.yaml +# 3. ~/.openclaw/workspace-document-analyzer/config/secrets.yaml +_SECRETS_CANDIDATES = [ + os.path.join(os.path.expanduser("~"), ".openclaw", "config", "secrets.yaml"), + os.path.join(os.path.expanduser("~"), ".openclaw", "workspace-document-analyzer", + "config", "secrets.yaml"), +] + +_SECRETS_PATH = os.environ.get("IR_SECRETS_PATH", "") +if _SECRETS_PATH: + _SECRETS_CANDIDATES.insert(0, _SECRETS_PATH) + +SECRETS_YAML = _SECRETS_CANDIDATES[0] # primary path (backward compat) # Intermediate outputs (all under PROJECT_OUTPUT/ir/) SEMANTIC_INDEX_R1_JSON = os.path.join(IR_OUTPUT, "semantic_index_r1.json") @@ -84,11 +93,15 @@ ENSEMBLE_TEMPERATURES = [ def _load_secrets() -> dict[str, dict[str, str]]: """Load provider credentials from secrets.yaml. + Tries paths in order: IR_SECRETS_PATH env var → ~/.openclaw/config/ → + ~/.openclaw/workspace-document-analyzer/config/. + Returns a dict like: {"deepseek": {"apiKey": "...", "baseUrl": "..."}, ...} """ - if os.path.isfile(SECRETS_YAML): - with open(SECRETS_YAML, "r", encoding="utf-8") as f: - return yaml.safe_load(f) or {} + for p in _SECRETS_CANDIDATES: + if os.path.isfile(p): + with open(p, "r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} return {} @@ -108,9 +121,11 @@ def _get_provider_config(provider: str) -> dict[str, str]: ) if not api_key: + tried_paths = "\n ".join(_SECRETS_CANDIDATES) raise RuntimeError( - f"No API key found for provider '{provider}'. " - f"Check {SECRETS_YAML} or set {env_prefix}_API_KEY." + f"No API key found for provider '{provider}'.\n" + f"Tried secrets.yaml paths:\n {tried_paths}\n" + f"Or set {env_prefix}_API_KEY environment variable." ) return {"apiKey": api_key, "baseUrl": base_url} diff --git a/skills/ir_generation_skill/step1_semantic_index.py b/skills/ir_generation_skill/step1_semantic_index.py index 1ccc323..9562260 100644 --- a/skills/ir_generation_skill/step1_semantic_index.py +++ b/skills/ir_generation_skill/step1_semantic_index.py @@ -548,11 +548,20 @@ def call_llm(prompt: str, max_retries: int = 2, Args: temperature: Override config.TEMPERATURE. If None, uses config default. """ - client = config.llm_client() + import sys as _sys + + try: + client = config.llm_client() + except Exception as e: + print(f" LLM 客户端初始化失败: {e}", file=_sys.stderr) + print(f" 请检查: IR_PROVIDER={config.LLM_PROVIDER}, secrets.yaml 或环境变量", file=_sys.stderr) + raise + temp = temperature if temperature is not None else config.TEMPERATURE for attempt in range(max_retries + 1): - print(f" LLM 调用 T={temp} (尝试 {attempt + 1}/{max_retries + 1})...", flush=True) + print(f" LLM 调用 model={config.MODEL_NAME} T={temp} " + f"(尝试 {attempt + 1}/{max_retries + 1})...", flush=True) try: resp = client.chat.completions.create( model=config.MODEL_NAME, @@ -568,17 +577,31 @@ def call_llm(prompt: str, max_retries: int = 2, ) content = resp.choices[0].message.content if content is None: - raise RuntimeError("LLM returned empty response") + raise RuntimeError( + "LLM 返回空响应 (content=None)。可能是 API 配额不足或模型不可用。" + ) + + # Log response length and first characters for diagnostics + print(f" 响应长度: {len(content)} 字符", flush=True) json_str = extract_json_from_response(content) - return json.loads(json_str) + result = json.loads(json_str) + n_units = len(result.get("function_units", [])) + n_concepts = len(result.get("concepts", [])) + print(f" 提取: {n_concepts} 概念, {n_units} 功能单元", flush=True) + return result except (json.JSONDecodeError, ValueError) as e: - print(f" JSON 解析失败: {e}") + print(f" JSON 解析失败: {e}", file=_sys.stderr) + # Show a snippet of what the LLM returned for diagnosis + print(f" LLM 返回内容前 500 字符: {content[:500] if content else '(None)'}", file=_sys.stderr) if attempt < max_retries: time.sleep(2) - raise RuntimeError("无法从 LLM 响应中解析 JSON") + raise RuntimeError( + f"无法从 LLM 响应中解析 JSON({max_retries + 1} 次尝试均失败)。" + f"最后返回内容前 500 字符: {content[:500] if content else '(None)'}" + ) # ---- Ensemble Orchestration ---- -- 2.52.0