diff --git a/.gitignore b/.gitignore index 01de6aab3..ac0ddd328 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ conf/.env __pycache__/ .cursorignore .cursor + +.claude/ \ No newline at end of file diff --git a/README.md b/README.md index 975a207f9..9f3f85224 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## 功能 - 🚀 多模型支持 - - 兼容 DeepSeek、ZhipuAI、OpenAI、通义千问 和 Ollama,想用哪个就用哪个。 + - 兼容 DeepSeek、ZhipuAI、OpenAI、通义千问、Ollama 和 Claude Code,想用哪个就用哪个。 - 📢 消息即时推送 - 审查结果一键直达 钉钉、企业微信 或 飞书,代码问题无处可藏! - 📅 自动化日报生成 @@ -56,12 +56,17 @@ cp conf/.env.dist conf/.env - 编辑 conf/.env 文件,配置以下关键参数: ```bash -#大模型供应商配置,支持 zhipuai , openai , deepseek 和 ollama +#大模型供应商配置,支持 zhipuai, openai, deepseek, ollama 和 claudecode LLM_PROVIDER=deepseek #DeepSeek DEEPSEEK_API_KEY={YOUR_DEEPSEEK_API_KEY} +#Claude Code (需要先安装 CLI: npm install -g @anthropic-ai/claude-code) +#CLAUDE_CODE_API_KEY={YOUR_ANTHROPIC_API_KEY} +#CLAUDE_CODE_API_BASE_URL=https://api.anthropic.com +#CLAUDE_CODE_API_MODEL=sonnet + #支持review的文件类型(未配置的文件类型不会被审查) SUPPORTED_EXTENSIONS=.java,.py,.php,.yml,.vue,.go,.c,.cpp,.h,.js,.css,.md,.sql diff --git a/biz/gitlab/webhook_handler.py b/biz/gitlab/webhook_handler.py index 5bb837582..9fbd7498d 100644 --- a/biz/gitlab/webhook_handler.py +++ b/biz/gitlab/webhook_handler.py @@ -17,11 +17,15 @@ def filter_changes(changes: list): filter_deleted_files_changes = [change for change in changes if not change.get("deleted_file")] - # 过滤 `new_path` 以支持的扩展名结尾的元素, 仅保留diff和new_path字段 + # 过滤 `new_path` 以支持的扩展名结尾的元素, 保留必要的字段信息 filtered_changes = [ { 'diff': item.get('diff', ''), 'new_path': item['new_path'], + 'old_path': item.get('old_path', ''), + 'new_file': item.get('new_file', False), + 'renamed_file': item.get('renamed_file', False), + 'deleted_file': item.get('deleted_file', False), 'additions': len(re.findall(r'^\+(?!\+\+)', item.get('diff', ''), re.MULTILINE)), 'deletions': len(re.findall(r'^-(?!--)', item.get('diff', ''), re.MULTILINE)) } diff --git a/biz/llm/client/claudecode.py b/biz/llm/client/claudecode.py new file mode 100644 index 000000000..0695aa84a --- /dev/null +++ b/biz/llm/client/claudecode.py @@ -0,0 +1,420 @@ +import ast +import os +import re +import shutil +import subprocess +from datetime import datetime +from typing import Dict, List, Optional + +from biz.llm.client.base import BaseClient +from biz.llm.types import NotGiven, NOT_GIVEN +from biz.utils.log import logger + + +class ClaudeCodeClient(BaseClient): + """Claude Code LLM 客户端,通过 subprocess 调用 Claude Code CLI""" + + def __init__(self, api_key: str = None): + """ + 初始化 Claude Code 客户端 + + Args: + api_key: Anthropic API 密钥,如果未提供则从环境变量读取 + + Raises: + ValueError: 当 API 密钥未配置时 + RuntimeError: 当 Claude Code CLI 未安装时 + """ + self.api_key = api_key or os.getenv("CLAUDE_CODE_API_KEY") + if not self.api_key: + raise ValueError("API key is required. Please provide it or set CLAUDE_CODE_API_KEY in the environment variables.") + + self.base_url = os.getenv("CLAUDE_CODE_API_BASE_URL", "https://api.anthropic.com") + self.default_model = os.getenv("CLAUDE_CODE_API_MODEL", "sonnet") + self.cli_path = os.getenv("CLAUDE_CODE_CLI_PATH", "claude") + + # 检查 Claude CLI 是否已安装 + if not self._check_cli_installed(): + raise RuntimeError( + "Claude Code CLI 未安装,请运行: npm install -g @anthropic-ai/claude-code" + ) + + logger.info(f"ClaudeCodeClient initialized with model: {self.default_model}, base_url: {self.base_url}") + + def _check_cli_installed(self) -> bool: + """ + 检查 Claude Code CLI 是否已安装 + + Returns: + 已安装返回 True,否则返回 False + """ + cli_path = shutil.which(self.cli_path) + if cli_path: + logger.debug(f"Claude CLI found at: {cli_path}") + return True + else: + logger.error(f"Claude CLI not found in PATH: {self.cli_path}") + return False + + def _format_dict_to_readable(self, data: dict) -> str: + """ + 将字典转换为可读的 key-value 字符串格式 + + Args: + data: 字典数据 + + Returns: + 格式化后的字符串 + """ + # 定义字段顺序(优先显示的字段) + priority_keys = ['diff', 'new_path', 'old_path', 'new_file', 'renamed_file', 'deleted_file'] + result_parts = [] + + # 按优先级处理字段 + for key in priority_keys: + if key in data: + value = data[key] + result_parts.append(f"{key}:\n{value}\n") + + # 处理其他未在优先级列表中的字段 + for key, value in data.items(): + if key not in priority_keys: + result_parts.append(f"{key}:\n{value}\n") + + return "\n".join(result_parts) + + def _format_list_in_string(self, content: str) -> str: + """ + 查找字符串中的 Python 列表表示并格式化 + + Args: + content: 可能包含列表字面量的字符串 + + Returns: + 格式化后的字符串 + """ + def find_list_literals(text: str) -> list: + """查找字符串中所有的列表字面量,返回 (start, end, list_obj) 列表""" + results = [] + i = 0 + + while i < len(text): + if text[i] == '[': + # 尝试从这个位置开始解析列表 + # 使用栈来找到匹配的结束括号 + brackets = [] # 跟踪 [] + braces = [] # 跟踪 {} + in_string = False + escape_next = False + j = i + + while j < len(text): + char = text[j] + + if escape_next: + escape_next = False + j += 1 + continue + + if char == '\\': + escape_next = True + j += 1 + continue + + if char == '"' or char == "'": + in_string = not in_string + elif not in_string: + if char == '[': + brackets.append('[') + elif char == ']': + if brackets: + brackets.pop() + if not brackets: + # 找到匹配的结束括号 + list_str = text[i:j+1] + try: + list_obj = ast.literal_eval(list_str) + # 只处理包含字典的列表 + if isinstance(list_obj, list) and list_obj and any(isinstance(item, dict) for item in list_obj): + results.append((i, j+1, list_obj)) + except (SyntaxError, ValueError): + pass + break + elif char == '{': + braces.append('{') + elif char == '}': + if braces: + braces.pop() + + j += 1 + i += 1 + + return results + + # 查找所有列表字面量 + list_literals = find_list_literals(content) + + if not list_literals: + return content + + # 从后往前替换,避免位置偏移问题 + result = content + for start, end, list_obj in reversed(list_literals): + # 格式化列表中的每个字典 + formatted_items = [] + for i, item in enumerate(list_obj, 1): + if isinstance(item, dict): + formatted = self._format_dict_to_readable(item) + # 添加分隔符 + if i > 1: + formatted_items.append("\n" + "=" * 80 + "\n") + formatted_items.append(formatted) + else: + # 如果不是字典,保持原样 + formatted_items.append(str(item)) + + formatted_str = "\n".join(formatted_items) + result = result[:start] + formatted_str + result[end:] + + return result + + def _format_messages(self, messages: List[Dict[str, str]]) -> tuple[str, str]: + """ + 将消息列表格式化为 Claude Code CLI 可接受的提示文本 + + Args: + messages: 消息列表 + + Returns: + 格式化后的提示文本元组 (user_content, system_content) + """ + # 分别收集 user 和 system 消息 + user_parts = [] + system_parts = [] + + for message in messages: + role = message.get("role", "user") + content = message.get("content", "") + + if role == "user": + logger.debug(f"context 的类型为: {type(content)}") + # 处理 content:支持字典和字符串两种类型 + if isinstance(content, dict): + # 字典类型:直接格式化 + content = self._format_dict_to_readable(content) + elif isinstance(content, str): + # 字符串类型:查找并格式化其中的列表 + content = self._format_list_in_string(content) + user_parts.append(content) + elif role == "system": + system_parts.append(content) + + # 合并所有消息内容 + user_content = "\n\n".join(user_parts) + system_content = "\n\n".join(system_parts) + + logger.debug(f"Formatted {len(messages)} messages: user_content length={len(user_content)}, system_content length={len(system_content)}") + + return user_content, system_content + + def _execute_claude_command(self, user_content: str, system_content: str, model: Optional[str] = None) -> str: + """ + 执行 Claude Code CLI 命令 + + Args: + user_content: 用户消息内容 + system_content: 系统提示内容 + model: 模型名称(sonnet/opus/haiku) + + Returns: + 命令的标准输出 + + Raises: + subprocess.CalledProcessError: 命令执行失败时 + """ + # 设置环境变量,传递 API 密钥和 Base URL + env = os.environ.copy() + env['ANTHROPIC_API_KEY'] = self.api_key + env['ANTHROPIC_BASE_URL'] = self.base_url + + # 设置额外的环境变量(使用 CLAUDE_CODE_ENV_ 前缀的配置) + env_mappings = { + 'CLAUDE_CODE_ENV_http_proxy': 'http_proxy', + 'CLAUDE_CODE_ENV_https_proxy': 'https_proxy', + 'CLAUDE_CODE_ENV_HTTP_PROXY': 'HTTP_PROXY', + 'CLAUDE_CODE_ENV_HTTPS_PROXY': 'HTTPS_PROXY', + 'CLAUDE_CODE_ENV_NODE_EXTRA_CA_CERTS': 'NODE_EXTRA_CA_CERTS', + } + + for config_key, env_key in env_mappings.items(): + env_value = os.getenv(config_key) + if env_value: + env[env_key] = env_value + logger.debug(f"Setting environment variable from {config_key}: {env_key}={env_value}") + + # 构建 prompt + combined_prompt = f"{system_content}\n\n{user_content}" + prompt_size_kb = len(combined_prompt.encode('utf-8')) / 1024 + + # 获取 log 目录的绝对路径(从 LOG_FILE 环境变量提取目录) + log_file = os.environ.get("LOG_FILE", "log/app.log") + log_dir = os.path.abspath(os.path.dirname(log_file)) + os.makedirs(log_dir, exist_ok=True) + + # 创建临时文件(使用可读的时间戳格式) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # 保留毫秒 + temp_filename = os.path.join(log_dir, f"claude_prompt_{timestamp}.txt") + + temp_file_created = False + should_delete_temp = True + try: + # 写入 prompt 到临时文件 + with open(temp_filename, 'w', encoding='utf-8') as f: + f.write(combined_prompt) + temp_file_created = True + + logger.debug(f"Created temp file: {temp_filename}") + + # 构建 shell 管道命令: cat temp_file | claude -p --model xxx + cmd = f"cat {temp_filename} | {self.cli_path} -p --permission-mode acceptEdits --allowedTools \"Bash(*) Read(*) Edit(*) Write(*)\" --debug" + if model: + cmd += f" --model {model}" + + logger.debug(f"Executing Claude CLI command (model: {model or 'default'}, prompt size: {prompt_size_kb:.2f} KB)") + logger.debug(f"Shell command: {cmd}") + + # 执行命令(使用 shell=True 支持管道) + result = subprocess.run( + cmd, + shell=True, + capture_output=True, + text=True, + timeout=600, # 10分钟超时 + env=env + ) + + # 记录 stderr(如果有,可能包含警告信息) + if result.stderr: + logger.warning(f"Claude CLI stderr: {result.stderr}") + + # 检查返回码 + if result.returncode != 0: + logger.error(f"Claude CLI failed with return code {result.returncode}") + logger.error(f"STDOUT: {result.stdout}") + logger.error(f"STDERR: {result.stderr}") + should_delete_temp = False # 出错时保留临时文件 + logger.error(f"Temp file preserved for debugging: {temp_filename}") + raise subprocess.CalledProcessError( + result.returncode, + cmd, + result.stdout, + result.stderr + ) + + # 记录成功信息 + logger.debug(f"Claude CLI execution successful, output length: {len(result.stdout)} chars") + + # 如果输出为空或只有空白字符,记录更详细的信息 + if not result.stdout or not result.stdout.strip(): + logger.error(f"Claude CLI returned empty output. Command: {cmd}") + logger.error(f"Return code: {result.returncode}") + logger.error(f"STDERR: {result.stderr}") + # 记录输出的十六进制表示,帮助诊断问题 + if result.stdout: + logger.error(f"STDOUT hex: {result.stdout.encode('utf-8').hex()}") + logger.error(f"STDOUT repr: {repr(result.stdout)}") + # 出错时保留临时文件 + should_delete_temp = False + logger.error(f"Temp file preserved for debugging: {temp_filename}") + + return result.stdout + + finally: + # 清理临时文件(仅在没有错误时删除) + if temp_file_created and should_delete_temp and os.path.exists(temp_filename): + try: + os.unlink(temp_filename) + logger.debug(f"Deleted temp file: {temp_filename}") + except Exception as e: + logger.warning(f"Failed to delete temp file {temp_filename}: {e}") + + def completions(self, + messages: List[Dict[str, str]], + model: Optional[str] | NotGiven = NOT_GIVEN) -> str: + """ + 调用 Claude Code CLI 进行代码审查 + + Args: + messages: 消息列表,格式为 [{"role": "user", "content": "..."}] + model: 模型名称,如果未提供则使用默认模型 + + Returns: + Claude Code CLI 返回的文本内容 + + Raises: + Exception: CLI 调用失败时 + """ + try: + # 使用默认模型或指定模型 + model = model or self.default_model + + # 格式化消息 + user_content, system_content = self._format_messages(messages) + + logger.debug(f"Calling Claude Code CLI. Model: {model}, User content length: {len(user_content)}, System content length: {len(system_content)}") + + # 执行 Claude CLI 命令 + result = self._execute_claude_command(user_content, system_content, model) + + # 处理空响应 + if not result or not result.strip(): + logger.error("Empty response from Claude Code CLI") + return "Claude Code 返回为空,请稍后重试" + + # 返回结果(去除多余空格) + return result.strip() + + except subprocess.TimeoutExpired: + logger.error("Claude Code CLI timeout") + return "Claude Code 执行超时,请稍后重试或检查代码大小" + + except subprocess.CalledProcessError as e: + logger.error(f"Claude Code CLI error: {e.stderr}", exc_info=True) + stderr = str(e.stderr).lower() if e.stderr else "" + + # 根据错误信息返回友好提示 + if "authentication" in stderr or "api key" in stderr: + return "Claude Code 认证失败,请检查 ANTHROPIC_API_KEY 是否正确配置" + elif "model" in stderr: + return "Claude Code 模型配置错误,请使用 sonnet/opus/haiku" + elif "rate limit" in stderr: + return "Claude Code 请求过于频繁,请稍后重试" + else: + return f"Claude Code 执行失败: {e.stderr}" + + except Exception as e: + logger.error(f"Claude Code error: {str(e)}", exc_info=True) + return f"调用 Claude Code 时出错: {str(e)}" + + def ping(self) -> bool: + """ + 测试与 Claude Code CLI 的连接 + + Returns: + 连接成功返回 True,否则返回 False + """ + try: + # 发送简单测试消息 + result = self.completions(messages=[{"role": "user", "content": '请仅返回 "ok"。'}]) + + # 检查返回结果 + if result and result.strip().lower() == "ok": + logger.info("Claude Code CLI connection test successful") + return True + else: + logger.warning(f"Claude Code CLI connection test returned unexpected result: {result}") + return False + + except Exception as e: + logger.error(f"Claude Code CLI connection test failed: {str(e)}") + return False diff --git a/biz/llm/factory.py b/biz/llm/factory.py index 453ac3a3d..5849db19d 100644 --- a/biz/llm/factory.py +++ b/biz/llm/factory.py @@ -1,6 +1,7 @@ import os from biz.llm.client.base import BaseClient +from biz.llm.client.claudecode import ClaudeCodeClient from biz.llm.client.deepseek import DeepSeekClient from biz.llm.client.ollama_client import OllamaClient from biz.llm.client.openai import OpenAIClient @@ -18,7 +19,8 @@ def getClient(provider: str = None) -> BaseClient: 'openai': lambda: OpenAIClient(), 'deepseek': lambda: DeepSeekClient(), 'qwen': lambda: QwenClient(), - 'ollama': lambda : OllamaClient() + 'ollama': lambda: OllamaClient(), + 'claudecode': lambda: ClaudeCodeClient() } provider_func = chat_model_providers.get(provider) diff --git a/biz/utils/config_checker.py b/biz/utils/config_checker.py index cfd028e2d..d66a4c331 100644 --- a/biz/utils/config_checker.py +++ b/biz/utils/config_checker.py @@ -15,7 +15,7 @@ ] # 允许的 LLM 供应商 -LLM_PROVIDERS = {"zhipuai", "openai", "deepseek", "ollama", "qwen"} +LLM_PROVIDERS = {"zhipuai", "openai", "deepseek", "ollama", "qwen", "claudecode"} # 每种供应商必须配置的键 LLM_REQUIRED_KEYS = { @@ -24,6 +24,7 @@ "deepseek": ["DEEPSEEK_API_KEY", "DEEPSEEK_API_MODEL"], "ollama": ["OLLAMA_API_BASE_URL", "OLLAMA_API_MODEL"], "qwen": ["QWEN_API_KEY", "QWEN_API_MODEL"], + "claudecode": ["CLAUDE_CODE_API_KEY", "CLAUDE_CODE_API_MODEL"], } diff --git a/conf/.env.dist b/conf/.env.dist index 2f04f52f3..926a04a34 100644 --- a/conf/.env.dist +++ b/conf/.env.dist @@ -4,7 +4,7 @@ SERVER_PORT=5001 #Timezone TZ=Asia/Shanghai -#大模型供应商配置,支持 deepseek, openai,zhipuai,qwen 和 ollama +#大模型供应商配置,支持 deepseek, openai, zhipuai, qwen, ollama 和 claudecode LLM_PROVIDER=deepseek #DeepSeek settings @@ -31,6 +31,20 @@ QWEN_API_MODEL=qwen-coder-plus OLLAMA_API_BASE_URL=http://host.docker.internal:11434 OLLAMA_API_MODEL=deepseek-r1:latest +#Claude Code settings (需要先安装 Claude Code CLI: npm install -g @anthropic-ai/claude-code) +#注意: Claude Code CLI 需要 Node.js 18 或更高版本 +#CLAUDE_CODE_API_KEY=sk-ant-xxxx +#CLAUDE_CODE_API_BASE_URL=https://api.anthropic.com # 可选,默认值 +#CLAUDE_CODE_API_MODEL=sonnet # 必需,可选值: sonnet, opus, haiku +#CLAUDE_CODE_CLI_PATH=claude # 可选,默认值 + +#Claude Code 环境变量配置(可选) +#CLAUDE_CODE_ENV_http_proxy=http://127.0.0.1:8899 # HTTP 代理 +#CLAUDE_CODE_ENV_https_proxy=http://127.0.0.1:8899 # HTTPS 代理 +#CLAUDE_CODE_ENV_HTTP_PROXY=http://127.0.0.1:8899 # HTTP 代理(大写) +#CLAUDE_CODE_ENV_HTTPS_PROXY=http://127.0.0.1:8899 # HTTPS 代理(大写) +#CLAUDE_CODE_ENV_NODE_EXTRA_CA_CERTS=/path/to/rootCA.cer # 自定义 CA 证书路径 + #支持review的文件类型 SUPPORTED_EXTENSIONS=.c,.cc,.cpp,.cs,.css,.cxx,.go,.h,.hh,.hpp,.hxx,.java,.js,.jsx,.md,.php,.py,.sql,.ts,.tsx,.vue,.yml #每次 Review 的最大 Token 限制(超出部分自动截断) diff --git a/conf/prompt_templates.yml b/conf/prompt_templates.yml index 66258255a..6743797cf 100644 --- a/conf/prompt_templates.yml +++ b/conf/prompt_templates.yml @@ -3,7 +3,7 @@ code_review_prompt: 你是一位资深的软件开发工程师,专注于代码的规范性、功能性、安全性和稳定性。本次任务是对员工的代码进行审查,具体要求如下: ### 代码审查目标: - 1. 功能实现的正确性与健壮性(40分): 确保代码逻辑正确,能够处理各种边界情况和异常输入。 + 1. 功能实现的正确性与健壮性(40分): 确保代码逻辑正确,能够处理各种边界情况和异常输入, 是否对无关代码意外删除或修改, 是否存在隐藏的全局状态变更。 2. 安全性与潜在风险(30分):检查代码是否存在安全漏洞(如SQL注入、XSS攻击等),并评估其潜在风险。 3. 是否符合最佳实践(20分):评估代码是否遵循行业最佳实践,包括代码结构、命名规范、注释清晰度等。 4. 性能与资源利用效率(5分):分析代码的性能表现,评估是否存在资源浪费或性能瓶颈。 @@ -13,7 +13,7 @@ code_review_prompt: 请以Markdown格式输出代码审查报告,并包含以下内容: 1. 问题描述和优化建议(如果有):列出代码中存在的问题,简要说明其影响,并给出优化建议。 2. 评分明细:为每个评分标准提供具体分数。 - 3. 总分:格式为“总分:XX分”(例如:总分:80分),确保可通过正则表达式 r"总分[::]\s*(\d+)分?") 解析出总分。 + 3. 总分:格式为“总分:XX分”(例如:总分:80分),使用纯文字, 不要使用markdown等任何格式, 以确保可通过正则表达式 r"总分[::]\s*(\d+)分?") 解析出总分。 ### 特别说明: 整个评论要保持{{ style }}风格 diff --git a/doc/faq.md b/doc/faq.md index 8826b18e7..e6b1660a2 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -160,3 +160,119 @@ WORKER_QUEUE=gitlab_test_cn GITHUB_ACCESS_TOKEN=your-access-token #替换为你的Access Token ``` +### 如何使用 Claude Code 进行代码审查? + +Claude Code 是 Anthropic 提供的专门针对代码理解和生成优化的 AI 模型。要使用 Claude Code 进行代码审查,需要完成以下配置: + +**1. 安装 Claude Code CLI** + +Claude Code 需要通过命令行工具调用。首先确保你的系统已安装 Node.js 18 或更高版本,然后全局安装 Claude Code CLI: + +```bash +npm install -g @anthropic-ai/claude-code +``` + +**2. 获取 Anthropic API 密钥** + +- 访问 [Anthropic Console](https://console.anthropic.com/settings/keys) +- 登录或注册账号 +- 创建新的 API 密钥 +- 复制 API 密钥(以 sk-ant- 开头) + +**3. 配置 .env 文件** + +在 .env 文件中添加以下配置: + +```bash +# 设置 LLM 提供商为 claudecode +LLM_PROVIDER=claudecode + +# Claude Code API 密钥 +CLAUDE_CODE_API_KEY=sk-ant-your-api-key-here + +# Claude API Base URL(可选,默认为 https://api.anthropic.com) +# 如果使用自定义 API 端点,可以修改此配置 +CLAUDE_CODE_API_BASE_URL=https://api.anthropic.com + +# 选择模型(可选,默认为 sonnet) +# 可选值: sonnet(推荐), opus(最高质量), haiku(最快速度) +CLAUDE_CODE_API_MODEL=sonnet +``` + +**4. 配置额外环境变量(可选)** + +如果你需要通过代理访问 Claude API 或使用自定义 CA 证书,可以在 .env 文件中使用 `CLAUDE_CODE_ENV_` 前缀配置: + +```bash +# Claude Code 环境变量配置 +# HTTP 代理配置 +CLAUDE_CODE_ENV_http_proxy=http://127.0.0.1:8899 +CLAUDE_CODE_ENV_https_proxy=http://127.0.0.1:8899 +CLAUDE_CODE_ENV_HTTP_PROXY=http://127.0.0.1:8899 +CLAUDE_CODE_ENV_HTTPS_PROXY=http://127.0.0.1:8899 + +# 自定义 CA 证书(用于企业内网环境) +CLAUDE_CODE_ENV_NODE_EXTRA_CA_CERTS=/path/to/rootCA.cer +``` + +**配置说明**: +- **代理配置**: 使用 `CLAUDE_CODE_ENV_http_proxy` 和 `CLAUDE_CODE_ENV_https_proxy` 配置代理地址,建议同时配置小写和大写形式以确保兼容性 +- **CA 证书**: 使用 `CLAUDE_CODE_ENV_NODE_EXTRA_CA_CERTS` 指定自定义根证书路径,常用于企业内网环境中需要自签名证书的场景 +- **配置隔离**: 使用 `CLAUDE_CODE_ENV_` 前缀保持配置的统一性和隔离性,这些配置只会影响 Claude Code CLI 的执行,不会影响系统其他部分 + +**5. 重启服务** + +```bash +docker-compose restart +# 或 +python api.py +``` + +### Claude Code 配置常见错误排查 + +**错误:Claude Code CLI 未安装** + +- 错误信息:`Claude Code CLI 未安装,请运行: npm install -g @anthropic-ai/claude-code` +- 解决方案: + - 确保已安装 Node.js 18 或更高版本:`node --version` + - 运行安装命令:`npm install -g @anthropic-ai/claude-code` + - 验证安装:`claude --version` + +**错误:Claude Code 认证失败** + +- 错误信息:`Claude Code 认证失败,请检查 ANTHROPIC_API_KEY 是否正确配置` +- 解决方案: + - 检查 .env 文件中的 CLAUDE_CODE_API_KEY 是否正确配置 + - 确认 API 密钥以 sk-ant- 开头 + - 验证 API 密钥是否过期或被撤销 + - 在 [Anthropic Console](https://console.anthropic.com/settings/keys) 重新生成密钥 + +**错误:Claude Code 模型配置错误** + +- 错误信息:`Claude Code 模型配置错误,请使用 sonnet/opus/haiku` +- 解决方案: + - 检查 CLAUDE_CODE_API_MODEL 配置值 + - 确保使用正确的模型名称:sonnet、opus 或 haiku + - 如果未配置,系统将使用默认模型 sonnet + +**错误:Claude Code 执行超时** + +- 错误信息:`Claude Code 执行超时,请稍后重试或检查代码大小` +- 解决方案: + - 检查代码文件是否过大(超过 10000 tokens) + - 尝试减小 REVIEW_MAX_TOKENS 配置值 + - 检查网络连接是否正常 + - 稍后重试 + +**Docker 部署注意事项** + +如果使用 Docker 部署,需要确保容器内也安装了 Node.js 和 Claude Code CLI。建议在 Dockerfile 中添加: + +```dockerfile +# 安装 Node.js +RUN apt-get update && apt-get install -y nodejs npm + +# 安装 Claude Code CLI +RUN npm install -g @anthropic-ai/claude-code +``` +