Files
2026-06-14 14:30:48 -04:00

334 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
sidebar_position: 15
title: "Microsoft Foundry"
description: "将 Hermes Agent 与 Microsoft Foundry 配合使用——OpenAI 风格与 Anthropic 风格端点、传输协议与已部署模型的自动检测"
---
# Microsoft Foundry
Hermes Agent 的 `azure-foundry` provider 支持 Microsoft Foundry(原 Azure AI Foundry)和 Azure OpenAI。单个 Foundry 资源可以托管两种不同传输格式的模型:
- **OpenAI 风格** — 在 `https://<resource>.openai.azure.com/openai/v1` 等端点上执行 `POST /v1/chat/completions`。用于 GPT-4.x、GPT-5.x、Llama、Mistral 及大多数开放权重模型。
- **Anthropic 风格** — 在 `https://<resource>.services.ai.azure.com/anthropic` 等端点上执行 `POST /v1/messages`。当 Microsoft Foundry 通过 Anthropic Messages API 格式提供 Claude 模型时使用。
设置向导会探测你的端点并自动检测所使用的传输协议、可用的部署以及每个模型的上下文长度。
## 前提条件
- 一个至少包含一个部署的 Microsoft Foundry 或 Azure OpenAI 资源
- 该部署的端点 URL
- **以下之一**API 密钥(从 Azure Portal 的"Keys and Endpoint"获取),**或者**在 Foundry 资源上拥有 **Azure AI User** RBAC 角色(如果你计划使用 Microsoft Entra ID——即 Microsoft 推荐的无密钥方式)。某些租户在 Microsoft 重命名推出期间可能将该角色显示为 **Foundry User**
## 快速开始
```bash
hermes model
# → 选择 "Azure Foundry"
# → 输入你的端点 URL
# → 选择认证方式:
# 1. API key
# 2. Microsoft Entra ID(托管标识 / 工作负载标识 / az login
# → EntraHermes 探测 DefaultAzureCredential;成功后不再询问密钥
# → API key)输入你的 API 密钥
# Hermes 探测端点并自动检测传输协议 + 模型
# → 从列表中选择模型(或手动输入部署名称)
```
向导将执行以下操作:
1. **嗅探 URL 路径** — 以 `/anthropic` 结尾的 URL 被识别为 Microsoft Foundry Claude 路由。
2. **探测 `GET <base>/models`** — 如果端点返回 OpenAI 格式的模型列表,Hermes 切换到 `chat_completions` 并用返回的部署 ID 预填选择器。
3. **探测 Anthropic Messages 格式** — 针对不暴露 `/models` 但接受 Anthropic Messages 格式的端点的回退方案。
4. **回退到手动输入** — 拒绝所有探测的私有/受限端点仍然可用;你手动选择 API 模式并输入部署名称。
所选模型的上下文长度通过 Hermes 的标准元数据链(`models.dev`、provider 元数据及硬编码的系列回退)解析,并存储在 `config.yaml` 中,以便模型正确确定自身的上下文窗口大小。
## Microsoft Entra ID(无密钥,RBAC)——推荐
Microsoft 推荐在生产 Foundry 工作负载中使用 [Microsoft Entra ID 无密钥认证](https://learn.microsoft.com/azure/ai-foundry/foundry-models/how-to/configure-entra-id)。Hermes 对**两种** API 接口均支持 Entra ID
- **OpenAI 风格**`api_mode: chat_completions` / `codex_responses`)— GPT-4/5、Llama、Mistral、DeepSeek 等。
- **Anthropic 风格**`api_mode: anthropic_messages`)— Microsoft Foundry 上的 Claude 模型。
Foundry 的 RBAC 是按资源级别的(`Azure AI User` 授予两种接口的访问权限;某些租户可能显示为 `Foundry User`),Microsoft 文档对两者使用相同的推理 scope(`https://ai.azure.com/.default`)。底层实现:
- OpenAI 风格使用 OpenAI Python SDK 原生的可调用 `api_key=` 契约——SDK 每次请求自动生成新的 JWT。
- Anthropic 风格使用带有请求事件 hook 的 `httpx.Client`,该 hook 由 `agent.azure_identity_adapter.build_bearer_http_client` 安装,因为 Anthropic SDK 原生不接受可调用的 `auth_token`。该 hook 在每次出站请求时重写 `Authorization: Bearer <fresh-jwt>`。RBAC 和 Foundry scope 相同——唯一的区别在于 SDK 契约。
### 为什么使用 Entra ID
- 无需轮换或吊销长期有效的 API 密钥。
- RBAC 驱动的访问控制——在 Foundry 资源上授予或移除 `Azure AI User`,无需重写配置。
- 访问和审计日志按被分配者分段,而非所有调用者共享一个静态密钥。
- 通过托管标识,为 Azure VM、AKS Pod、App Service、Functions、Container Apps 和 Foundry Agent Service 提供统一的认证接口。
- 支持 CI/CD 流水线的工作负载标识和服务主体流程。
### 一次性设置(Azure 侧)
1. 在 Azure Portal 中,打开你的 Foundry 资源 → **访问控制 (IAM)****添加 → 添加角色分配**
2. 选择 **Azure AI User** 角色(如果你的租户已重命名,则选择 **Foundry User**)。
3. 将其分配给:
- **你的用户账户**,用于通过 `az login` 进行本地开发。
- **托管标识或工作负载标识**,用于 Azure 托管计算(生产环境推荐)。
- **Foundry Agent Service 托管 Agent 的 Agent 标识**,当 Hermes 在托管 Agent 内运行时。
- **服务主体**,用于工作负载标识不可用时的 CI/CD 流水线。
4. 等待约 5 分钟以使角色生效。
Azure CLI 等效命令:
```bash
az role assignment create \
--assignee <principal-or-agent-identity-client-id> \
--role "Azure AI User" \
--scope <foundry-resource-id>
```
### 一次性设置(Hermes 侧)
```bash
hermes model
# → 选择 "Azure Foundry"
# → 输入你的端点 URL
# → 认证方式:2Microsoft Entra ID
# → (可选)用户分配的托管标识客户端 ID
# → (可选)Azure 租户 ID
# → Hermes 探测 DefaultAzureCredential() 并报告哪个内部凭据成功
# (例如 AzureCliCredential、ManagedIdentityCredential
```
向导运行一个有时间限制的预检探测(10 秒超时)。失败时提供"仍然保存,稍后验证"选项——适用于在当前机器上尚无凭据但运行时会有凭据的场景(例如为托管标识部署准备配置)。
`azure-identity` 在首次使用时通过 Hermes 的懒加载安装路径自动安装。如需预先安装:
```bash
pip install azure-identity
```
### 写入 `config.yaml` 的配置
```yaml
model:
provider: azure-foundry
base_url: https://my-resource.openai.azure.com/openai/v1
api_mode: chat_completions
auth_mode: entra_id
default: gpt-4o
context_length: 128000
entra:
scope: https://ai.azure.com/.default # 仅在覆盖默认值时使用
```
Hermes 在 `config.yaml` 中只管理一个 Entra 专属配置项:
- **`scope`** — OAuth 资源 scope。默认为 Microsoft 文档中的推理 scope`https://ai.azure.com/.default`)。仅在你的资源针对非标准 audience 进行了预配时才需要覆盖。
其他所有内容(租户、服务主体密钥、联合令牌文件、主权云 authority、broker 偏好)均由 `azure-identity` 直接从标准 `AZURE_*` 环境变量读取——参见下方的[凭据解析顺序](#credential-resolution-order)。在 `~/.hermes/.env` 或你的部署环境中设置这些变量,与 Microsoft SDK 参考文档的描述完全一致。
Entra 模式下不会将任何密钥写入 `~/.hermes/.env`——`azure-identity` 在进程内缓存令牌(在可用时也会使用操作系统密钥链 / `~/.IdentityService`)。
### 凭据解析顺序
`azure-identity``DefaultAzureCredential` 在每次令牌请求时按以下链路逐一尝试,在第一个返回令牌的凭据处停止:
1. **环境凭据**`AZURE_TENANT_ID` + `AZURE_CLIENT_ID` + `AZURE_CLIENT_SECRET`(或 `AZURE_CLIENT_CERTIFICATE_PATH` / `AZURE_FEDERATED_TOKEN_FILE`)。
2. **工作负载标识**`AZURE_FEDERATED_TOKEN_FILE`AKS 联合令牌 / OIDC)。
3. **托管标识** — 虚拟机使用 IMDS 端点(`169.254.169.254`);App Service / Functions / Container Apps 使用 `IDENTITY_ENDPOINT`。Foundry Agent Service 托管 Agent 使用托管 Agent 的 Agent 标识。
4. **Visual Studio Code** — Azure 账户扩展。
5. **Azure CLI**`az login` 会话。
6. **Azure Developer CLI**`azd auth login`
7. **Azure PowerShell**`Connect-AzAccount`
8. **Broker**(仅限 Windows / WSL)— Web Account Manager。
交互式浏览器凭据在无人值守的 Hermes 运行中默认被排除;请改用 Azure CLI、Azure Developer CLI、托管标识、工作负载标识或服务主体凭据。
### 部署模式
**本地开发:**
```bash
az login
hermes model # 选择 Azure Foundry → Entra ID
hermes # 使用你的 az login 令牌
```
**Azure VM / Functions / App Service / Container Apps(系统分配的托管标识):**
1. 在计算资源上启用系统分配的标识。
2. 在 Foundry 资源上为该标识授予 `Azure AI User`(或 `Foundry User`)角色。
3. 在 config.yaml 中设置 `model.auth_mode: entra_id`——无需环境变量。
**Azure VM / Functions / App Service / Container Apps(用户分配的托管标识):**
-`AZURE_CLIENT_ID` 设置为用户分配标识的客户端 ID,以便 `DefaultAzureCredential` 选择正确的标识。
**Foundry Agent Service 托管 Agent**
- 创建托管 Agent 并在 Foundry 资源上为该 Agent 的标识授予 `Azure AI User`(或 `Foundry User`)角色。Hermes 在托管 Agent 内部使用 `ManagedIdentityCredential`;角色分配应针对 Agent 标识,而非仅针对父项目或你的用户。
**AKS 工作负载标识(替代 AAD Pod Identity):**
- 使用工作负载标识客户端 ID 注解 Pod 的服务账户。
- Pod 的联合令牌文件通过 `AZURE_FEDERATED_TOKEN_FILE` 自动检测。
- `model.auth_mode: entra_id` 无需进一步修改配置即可使用。
**CI 中的服务主体:**
- 在 runner 环境中设置 `AZURE_TENANT_ID``AZURE_CLIENT_ID``AZURE_CLIENT_SECRET`
#### 主权云(政府云、中国云)
导出 `AZURE_AUTHORITY_HOST`(例如 Azure Government 使用 `https://login.microsoftonline.us`Azure China 使用 `https://login.partner.microsoftonline.cn`)。`azure-identity` 会直接读取该变量。
### 健康检查
`model.auth_mode: entra_id` 时,`hermes doctor` 会对 `DefaultAzureCredential` 运行 10 秒探测,报告哪个内部凭据成功(环境变量是否存在、托管标识端点是否可达等)。
`hermes auth` 显示结构化状态块:
```
azure-foundry (Microsoft Entra ID):
Endpoint: https://my-resource.openai.azure.com/openai/v1
Scope: https://ai.azure.com/.default
Status: configured; live token probe is skipped here
```
### 限制
- **Anthropic 风格端点使用 httpx 事件 hook。** Anthropic Python SDK(≤ 0.86.0)原生不接受可调用的 `auth_token`。Hermes 在自定义 `httpx.Client` 上安装请求事件 hook,每次出站请求时生成新的 JWT 并重写 `Authorization: Bearer <jwt>`。这在功能上等同于 OpenAI SDK 原生的 `Callable[[], str]` 契约,但多了一层间接调用。如果 Anthropic SDK 在未来版本中添加对可调用认证的原生支持,Hermes 将透明地切换到该方式。
- **批处理任务与 `multiprocessing.Pool`。** Entra 令牌 provider 是一个闭包,无法跨进程边界序列化。`batch_runner.py` 会自动从 worker 配置中移除该可调用对象,让每个 worker 进程从 `config.yaml` 重建自己的 provider——无需用户操作,但每个 worker 在启动时需要执行一次凭据链遍历。
- **不在 `auth.json` 中持久化 Bearer JWT。** Hermes 不复制 `azure-identity` 的内部令牌缓存;冷启动时会在首次推理时遍历凭据链。
## 配置(写入 `config.yaml`
运行向导后,你将看到类似如下的内容:
```yaml
model:
provider: azure-foundry
base_url: https://my-resource.openai.azure.com/openai/v1
api_mode: chat_completions # 或 "anthropic_messages"
default: gpt-5.4-mini # 你的部署 / 模型名称
context_length: 400000 # 自动检测
```
以及在 `~/.hermes/.env` 中:
```
AZURE_FOUNDRY_API_KEY=<your-azure-key>
```
## OpenAI 风格端点(GPT、Llama 等)
Azure OpenAI 的 v1 GA 端点接受标准 `openai` Python 客户端,改动极少:
```yaml
model:
provider: azure-foundry
base_url: https://my-resource.openai.azure.com/openai/v1
api_mode: chat_completions
default: gpt-5.4
```
重要行为:
- **GPT-5.x、codex 和 o 系列自动路由到 Responses API。** Microsoft Foundry 将 GPT-5 / codex / o1 / o3 / o4 模型部署为仅支持 Responses API——对其调用 `/chat/completions` 会返回 `400 "The requested operation is unsupported."`。Hermes 通过名称检测这些模型系列,并透明地将 `api_mode` 升级为 `codex_responses`,即使 `config.yaml` 中仍写着 `api_mode: chat_completions`。GPT-4、GPT-4o、Llama、Mistral 及其他部署保持使用 `/chat/completions`
- **自动使用 `max_completion_tokens`。** Azure OpenAI(与直接使用 OpenAI 一样)对 gpt-4o、o 系列和 gpt-5.x 模型要求使用 `max_completion_tokens`。Hermes 根据端点发送正确的参数。
- **需要 `api-version` 的旧版端点。** 如果你有类似 `https://<resource>.openai.azure.com/openai?api-version=2025-04-01-preview` 的旧版 base URL,Hermes 会提取查询字符串并通过每次请求的 `default_query` 转发(否则 OpenAI SDK 在拼接路径时会丢弃它)。
## Anthropic 风格端点(通过 Microsoft Foundry 使用 Claude
对于 Claude 部署,使用 Anthropic 风格路由:
```yaml
model:
provider: azure-foundry
base_url: https://my-resource.services.ai.azure.com/anthropic
api_mode: anthropic_messages
default: claude-sonnet-4-6
```
重要行为:
- **从 base URL 中去除 `/v1`。** Anthropic SDK 在每次请求 URL 后追加 `/v1/messages`——Hermes 在将 URL 传递给 SDK 之前移除末尾的 `/v1`,以避免出现双重 `/v1` 路径。
- **`api-version` 通过 `default_query` 传递,而非追加到 URL。** Azure Anthropic 要求 `api-version` 查询字符串。将其嵌入 base URL 会产生类似 `/anthropic?api-version=.../v1/messages` 的畸形路径并返回 404。Hermes 通过 Anthropic SDK 的 `default_query` 传递 `api-version=2025-04-15`
- **使用 Bearer 认证而非 `x-api-key`。** Azure 的 Anthropic 兼容路由要求 `Authorization: Bearer <key>`,而非 Anthropic 原生的 `x-api-key` 头。Hermes 检测到 base URL 中包含 `azure.com` 时,通过 SDK 的 `auth_token` 字段路由 API 密钥,确保正确的头部到达上游。
- **保留 1M 上下文窗口 beta 头。** Azure 仍通过 `anthropic-beta: context-1m-2025-08-07` 头控制 1M token Claude 上下文(Opus 4.6/4.7、Sonnet 4.6)的访问。Hermes 在 Azure 路径上保留该 beta 头(在原生 Anthropic OAuth 请求中会被去除,因为某些订阅会拒绝它,但 Azure 要求它)。
- **禁用 OAuth 令牌刷新。** Azure 部署使用静态 API 密钥。适用于 Anthropic Console 的 `~/.claude/.credentials.json` OAuth 令牌刷新循环对 Azure 端点明确跳过,以防止 Claude Code OAuth 令牌在会话中途覆盖你的 Azure 密钥。
## 替代方案:`provider: anthropic` + Azure base URL
如果你已配置 `provider: anthropic` 并只想将其指向 Microsoft Foundry 以使用 Claude,可以完全跳过 `azure-foundry` provider
```yaml
model:
provider: anthropic
base_url: https://my-resource.services.ai.azure.com/anthropic
key_env: AZURE_ANTHROPIC_KEY
default: claude-sonnet-4-6
```
`~/.hermes/.env` 中设置 `AZURE_ANTHROPIC_KEY`。Hermes 检测到 base URL 中包含 `azure.com` 时,会绕过 Claude Code OAuth 令牌链,直接使用 Azure 密钥进行 `x-api-key` 认证。
`key_env` 是规范的 snake_case 字段名;`api_key_env`(以及驼峰式 `keyEnv` / `apiKeyEnv`)作为别名被接受。如果同时设置了 `key_env``AZURE_ANTHROPIC_KEY`/`ANTHROPIC_API_KEY``key_env` 指定的环境变量优先。
## 模型发现
Azure **不**暴露纯 API 密钥端点来列出你的*已部署*模型部署。部署枚举需要 Azure Resource Manager 认证(`az cognitiveservices account deployment list`)和 Azure AD 主体,而非推理 API 密钥。
Hermes 能做的:
- Azure OpenAI v1 端点(`<resource>.openai.azure.com/openai/v1`)通过 `GET /models` 暴露资源的**可用**模型目录。Hermes 使用此列表预填模型选择器。
- Microsoft Foundry `/anthropic` 路由:通过 URL 路径检测,模型名称手动输入。
- 私有 / 防火墙后的端点:手动输入,并显示友好的"无法探测"提示。
你始终可以直接输入部署名称——Hermes 不会对返回的列表进行验证。
## 环境变量
| 变量 | 用途 |
|----------|---------|
| `AZURE_FOUNDRY_API_KEY` | Microsoft Foundry / Azure OpenAI 的主 API 密钥(api_key 模式) |
| `AZURE_FOUNDRY_BASE_URL` | 端点 URL(通过 `hermes model` 设置;环境变量作为回退) |
| `AZURE_ANTHROPIC_KEY` | 由 `provider: anthropic` + Azure base URL 使用(`ANTHROPIC_API_KEY` 的替代) |
| `AZURE_TENANT_ID` | 服务主体流程的 Entra ID 租户 |
| `AZURE_CLIENT_ID` | Entra ID 客户端 ID(服务主体、工作负载标识或用户分配的托管标识) |
| `AZURE_CLIENT_SECRET` | 服务主体密钥 |
| `AZURE_CLIENT_CERTIFICATE_PATH` | 服务主体证书(密钥的替代方案) |
| `AZURE_FEDERATED_TOKEN_FILE` | 工作负载标识联合令牌路径(AKS) |
| `AZURE_AUTHORITY_HOST` | 主权云 authority 主机覆盖 |
| `IDENTITY_ENDPOINT` / `MSI_ENDPOINT` | App Service、Functions 和 Container Apps 的托管标识端点;VM 通常改用 IMDS |
Azure SDK 直接读取 `AZURE_*` 环境变量。Hermes 除在 `hermes doctor` 输出中报告哪些来源存在外,不会检查这些变量。
## 故障排查
**gpt-5.x 部署返回 401 Unauthorized。**
Azure 在 `/chat/completions` 上提供 gpt-5.x,而非 `/responses`。当 URL 包含 `openai.azure.com` 时,Hermes 会自动处理此问题,但如果你看到带有 `Invalid API key` 正文的 401,请检查 `config.yaml` 中的 `api_mode` 是否为 `chat_completions`
**`/v1/messages?api-version=.../v1/messages` 返回 404。**
这是修复前 Azure Anthropic 设置中的畸形 URL 问题。升级 Hermes——`api-version` 参数现在通过 `default_query` 传递,而非嵌入 base URL,因此 SDK 在 URL 拼接时不会破坏它。
**向导提示"自动检测不完整"。**
端点拒绝了 `/models` 探测和 Anthropic Messages 探测。这对于防火墙后或设有 IP 白名单的私有端点是正常现象。回退到手动选择 API 模式并输入部署名称——一切仍然正常工作,Hermes 只是无法预填选择器。
**选择了错误的传输协议。**
再次运行 `hermes model`,向导将重新探测。如果探测仍然选择了错误的模式,可以直接编辑 `config.yaml`
```yaml
model:
provider: azure-foundry
api_mode: anthropic_messages # 或 chat_completions
```
**Entra ID"credential chain exhausted" 或切换到 `auth_mode: entra_id` 后返回 401 Unauthorized。**
- 运行 `az login` 刷新你的开发者会话(缓存的令牌可能已过期)。
- 验证 `Azure AI User`(或 `Foundry User`)角色分配是否已生效:`az role assignment list --assignee <user-or-identity-id>` 应在你的 Foundry 资源上列出该角色。角色传播最多需要 5 分钟。
- 对于用户分配的托管标识,请仔细检查 `AZURE_CLIENT_ID` 是否与附加到计算资源的标识匹配。
- 运行 `hermes doctor`——Azure Entra 探测会报告令牌获取是否成功,并提供修复提示。
**Entra ID:向导预检挂起或超时。**
10 秒预检是软性检查。选择"仍然保存,稍后验证",部署到目标环境后运行 `hermes doctor`。常见原因包括令牌服务不可达或本地登录状态过期——在 CI 中优先使用工作负载标识,使用服务主体时设置 `AZURE_TENANT_ID`+`AZURE_CLIENT_ID`+`AZURE_CLIENT_SECRET`,或在本地开发时运行 `az login`
**Anthropic 风格端点使用 Entra ID 时返回 401。**
验证同一 `Azure AI User`(或 `Foundry User`)角色是否已在 Foundry 资源上分配(它同时覆盖 `/openai/v1``/anthropic` 路径)。如果向导期间 OpenAI 风格探测成功,但运行时 `claude-*` 请求失败,最常见的原因是早期向导运行遗留的过时 `model.entra.scope`——从 `config.yaml` 中删除 `entra.scope` 行,使运行时回退到默认的 `https://ai.azure.com/.default` scope。
## 相关链接
- [环境变量](/reference/environment-variables)
- [配置](/user-guide/configuration)
- [AWS Bedrock](/guides/aws-bedrock) — 另一个主要的云 provider 集成
- [Microsoft:为 Foundry 配置 Entra ID](https://learn.microsoft.com/azure/ai-foundry/foundry-models/how-to/configure-entra-id) — 无密钥路径的上游文档