1、api_key可以为空

2、环境命令补充
This commit is contained in:
dark 2026-06-02 14:06:22 +08:00
parent 05ece1bffc
commit 0cd43c37a7
4 changed files with 72 additions and 12 deletions

View File

@ -68,7 +68,7 @@ packaging/
- 实现人工确认入口:`confirm --decision approve|reject` 只处理待确认回滚。 - 实现人工确认入口:`confirm --decision approve|reject` 只处理待确认回滚。
- 实现 checkpoint 自动保存和 `resume` 续跑:全局步骤、成功 IP、单 IP 已完成 action 会跳过。 - 实现 checkpoint 自动保存和 `resume` 续跑:全局步骤、成功 IP、单 IP 已完成 action 会跳过。
- 实现 LLM structured output 骨架:意图识别、参数抽取、部署计划生成。 - 实现 LLM structured output 骨架:意图识别、参数抽取、部署计划生成。
- 实现 OpenAI-compatible 真实 LLM client支持 `base_url` / `api_key` / `model` 配置。 - 实现 OpenAI-compatible 真实 LLM client支持 `base_url` / `model` 配置`api_key` 可为空
- 固化真实 LLM 提示词:意图识别、参数抽取、部署计划生成均要求 JSON structured output。 - 固化真实 LLM 提示词:意图识别、参数抽取、部署计划生成均要求 JSON structured output。
- 增加规则 fallback `RuleBasedLlmClient`,用于本地开发和测试。 - 增加规则 fallback `RuleBasedLlmClient`,用于本地开发和测试。
- 增加 LLM 输出 guardrails禁止计划中出现可执行脚本命令和非法 action。 - 增加 LLM 输出 guardrails禁止计划中出现可执行脚本命令和非法 action。
@ -90,11 +90,12 @@ packaging/
## LLM 配置 ## LLM 配置
默认不配置 LLM 时,`analyze` 使用本地规则 fallback。配置真实 LLM 后,会走 OpenAI-compatible `/chat/completions` 默认不配置 LLM 时,`analyze` 使用本地规则 fallback。配置真实 LLM 后,会走 OpenAI-compatible `/chat/completions`
`base_url``model` 必填;`api_key` 可为空。如果模型服务不需要鉴权,不配置 `PAM_LLM_API_KEY` 或不传 `--llm-api-key` 即可Agent 不会发送 `Authorization` 请求头。
```powershell ```powershell
$env:PAM_LLM_BASE_URL="https://your-llm.example.com/v1" $env:PAM_LLM_BASE_URL="https://your-llm.example.com/v1"
$env:PAM_LLM_API_KEY="your-api-key"
$env:PAM_LLM_MODEL="your-model-name" $env:PAM_LLM_MODEL="your-model-name"
python -m pam_deploy_graph.cli analyze --config doc_scripts/config.txt.example --text "请用 MCP 预演部署 HET PAM Node 版本 2.0.5,不要动环境" python -m pam_deploy_graph.cli analyze --config doc_scripts/config.txt.example --text "请用 MCP 预演部署 HET PAM Node 版本 2.0.5,不要动环境"
@ -107,12 +108,17 @@ python -m pam_deploy_graph.cli analyze \
--config doc_scripts/config.txt.example \ --config doc_scripts/config.txt.example \
--text "请用 MCP 预演部署 HET PAM Node 版本 2.0.5,不要动环境" \ --text "请用 MCP 预演部署 HET PAM Node 版本 2.0.5,不要动环境" \
--llm-base-url https://your-llm.example.com/v1 \ --llm-base-url https://your-llm.example.com/v1 \
--llm-api-key your-api-key \
--llm-model your-model-name --llm-model your-model-name
``` ```
真实 LLM 调用位置在 `pam_deploy_graph/llm/openai_compatible.py`,提示词在 `pam_deploy_graph/llm/prompts.py`。发送给 LLM 的 `base_params` 会脱敏,`CLIENT_SECRET` 不会进入 prompt本地生成计划后仍会执行 guardrails 校验。 真实 LLM 调用位置在 `pam_deploy_graph/llm/openai_compatible.py`,提示词在 `pam_deploy_graph/llm/prompts.py`。发送给 LLM 的 `base_params` 会脱敏,`CLIENT_SECRET` 不会进入 prompt本地生成计划后仍会执行 guardrails 校验。
如果服务需要鉴权,再补充:
```bash
export PAM_LLM_API_KEY="your-api-key"
```
## MCP Client 配置 ## MCP Client 配置
CLI/chat 已支持通过 `--mcp-config` 直接加载 MCP 配置。常用场景只需要配置 MCP `server_url` 和独立鉴权信息Agent 会连接 MCP server调用 `list_tools` 自动发现 server 暴露的 tools再按 action 名自动匹配。 CLI/chat 已支持通过 `--mcp-config` 直接加载 MCP 配置。常用场景只需要配置 MCP `server_url` 和独立鉴权信息Agent 会连接 MCP server调用 `list_tools` 自动发现 server 暴露的 tools再按 action 名自动匹配。
@ -183,6 +189,24 @@ agent = PamDeployAgent(mcp_runner=runner)
## 使用方式 ## 使用方式
开发项目迁移到新环境后,推荐先安装完整开发依赖:
```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip setuptools wheel
python -m pip install -e ".[mcp,chat,test]"
pytest -q
```
Windows PowerShell 激活虚拟环境使用:
```powershell
.\.venv\Scripts\Activate.ps1
```
基础要求Python 3.11+。如果要执行 Linux 脚本 action运行环境还需要 `bash``curl`;如果要构建 Linux 解压即用包,请在 Linux x86_64 构建机上执行打包脚本。
整体逻辑结构流程图: 整体逻辑结构流程图:
```text ```text

View File

@ -26,8 +26,6 @@ def build_llm_client(
missing = [] missing = []
if not actual_base_url: if not actual_base_url:
missing.append("base_url") missing.append("base_url")
if not actual_api_key:
missing.append("api_key")
if not actual_model: if not actual_model:
missing.append("model") missing.append("model")
if missing: if missing:

View File

@ -43,8 +43,6 @@ class OpenAICompatibleLlmClient:
"""保存连接参数、模型参数和可替换的 HTTP transport。""" """保存连接参数、模型参数和可替换的 HTTP transport。"""
if not base_url: if not base_url:
raise ValueError("必须配置 LLM base_url") raise ValueError("必须配置 LLM base_url")
if not api_key:
raise ValueError("必须配置 LLM api_key")
if not model: if not model:
raise ValueError("必须配置 LLM model") raise ValueError("必须配置 LLM model")
self.base_url = base_url.rstrip("/") self.base_url = base_url.rstrip("/")
@ -178,12 +176,12 @@ class OpenAICompatibleLlmClient:
}, },
], ],
} }
headers = {"Content-Type": "application/json"}
if self.api_key:
headers["Authorization"] = f"Bearer {self.api_key}"
response = self.transport( response = self.transport(
_chat_completions_url(self.base_url), _chat_completions_url(self.base_url),
{ headers,
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
request_payload, request_payload,
self.timeout_sec, self.timeout_sec,
) )

View File

@ -2,6 +2,7 @@ from dataclasses import asdict
from pam_deploy_graph.agent import PamDeployAgent from pam_deploy_graph.agent import PamDeployAgent
from pam_deploy_graph.checkpoint_store import redact_mapping from pam_deploy_graph.checkpoint_store import redact_mapping
from pam_deploy_graph.llm.factory import build_llm_client
from pam_deploy_graph.llm.openai_compatible import OpenAICompatibleLlmClient from pam_deploy_graph.llm.openai_compatible import OpenAICompatibleLlmClient
from pam_deploy_graph.llm.rule_based import RuleBasedLlmClient from pam_deploy_graph.llm.rule_based import RuleBasedLlmClient
from pam_deploy_graph.llm.validators import validate_deploy_plan from pam_deploy_graph.llm.validators import validate_deploy_plan
@ -110,6 +111,45 @@ def test_openai_compatible_client_uses_base_url_api_key_and_prompt():
assert "只输出一个 JSON 对象" in calls[0][2]["messages"][0]["content"] assert "只输出一个 JSON 对象" in calls[0][2]["messages"][0]["content"]
def test_openai_compatible_client_allows_empty_api_key():
calls = []
def transport(url, headers, payload, timeout_sec):
calls.append((url, headers, payload, timeout_sec))
return {
"choices": [
{
"message": {
"content": (
'{"intent":"deploy","mode_preference":"未指定",'
'"strategy_preference":"fake","confidence":0.8}'
)
}
}
]
}
client = OpenAICompatibleLlmClient(
base_url="https://llm.example/v1",
api_key="",
model="model-a",
transport=transport,
)
result = client.understand_request("部署")
assert result.intent == "deploy"
assert "Authorization" not in calls[0][1]
assert calls[0][1]["Content-Type"] == "application/json"
def test_llm_factory_allows_empty_api_key_with_base_url_and_model():
client = build_llm_client(base_url="https://llm.example/v1", model="model-a")
assert isinstance(client, OpenAICompatibleLlmClient)
assert client.api_key == ""
def test_openai_compatible_client_does_not_send_base_secret(): def test_openai_compatible_client_does_not_send_base_secret():
calls = [] calls = []