# Azure AI Search Agent
基于 **Pydantic AI** 的 Azure AI Search 资源库管理 Agent,作为 **OpenClaw 的外部资源库**。
采用 **混合搜索**(关键词 BM25 + 向量语义),支持项目文档的上传、下载、修改、搜索和删除。
调用方可以是 OpenClaw 也可以是人类用户。
## 搜索模式
| 模式 | 说明 | 适用场景 |
|------|------|----------|
| `hybrid`(默认) | 关键词 + 向量融合排序 | 通用场景,推荐 |
| `keyword` | 纯 BM25 关键词匹配 | 精确搜索术语/ID |
| `vector` | 纯语义向量搜索 | 自然语言提问,找语义相关文档 |
- 上传文档时自动通过 LiteLLM 生成 embedding 向量(`taiji/text-embedding-3-small`,1536 维)
- 更新 `title` 或 `content` 时自动重新生成向量
- 搜索时关键词和向量结果由 Azure AI Search 自动融合排序(RRF)
## 功能
| 工具 | 说明 |
|------|------|
| `create_index` | 创建混合搜索索引(含向量字段) |
| `list_indexes` | 列出所有索引 |
| `upload_documents` | 上传文档(自动生成向量,支持批量) |
| `search_documents` | 混合/关键词/向量搜索,支持筛选、排序 |
| `get_document` | 根据 ID 获取单个文档 |
| `update_document` | 更新文档部分字段(自动重新生成向量) |
| `delete_documents` | 删除文档 |
| `smart_query` | AI 智能问答(混合搜索 + LLM 总结) |
## 文档 Schema
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | String (Key) | 文档唯一 ID |
| `title` | String (Searchable) | 标题 |
| `content` | String (Searchable) | 正文内容 |
| `content_vector` | Collection(Single) | 内容向量(1536 维,自动生成) |
| `project` | String (Filterable) | 项目名称 |
| `category` | String (Facetable) | 分类 |
| `tags` | String (Filterable) | 标签,逗号分隔 |
| `source` | String (Filterable) | 来源(openclaw / human) |
| `author` | String (Filterable) | 作者 |
| `created_at` | DateTimeOffset | 创建时间 |
| `updated_at` | DateTimeOffset | 更新时间 |
| `metadata` | String (Searchable) | 额外元数据 JSON |
## 如何运行(标准 Azure 函数)
**重要**:`func start` 会使用当前 shell 的 Python 环境加载你的代码。必须先在**本项目目录**下创建并激活虚拟环境、安装依赖,再启动,否则会报 `ModuleNotFoundError: No module named 'fastapi'`。
在项目根目录 `azure_search_agent/` 下按顺序执行:
```bash
# 1. 进入项目目录
cd /path/to/azure_search_agent
# 2. 创建虚拟环境(若尚未创建)
python3 -m venv .venv
# 3. 激活虚拟环境(必做,否则 func 找不到依赖)
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
# 4. 安装依赖
pip install -r requirements.txt
# 5. 确认已有 local.settings.json,再启动
func start
```
一行命令(在项目目录下,且已存在 .venv):
```bash
source .venv/bin/activate && func start
```
启动成功后终端会显示:
```
Functions:
http_app: [GET,POST,DELETE,HEAD,PATCH,PUT,OPTIONS] http://localhost:7071/{*route}
```
在浏览器或 curl 访问:
- 根路径:
- 健康检查:
- 搜索 API:`curl -X POST http://localhost:7071/api/v1/search -H "Content-Type: application/json" -H "api-key: sk-xxx" -d '{"query":"test","top":5}'`
如需先安装 **Azure Functions Core Tools**(尚未安装时):
```bash
# Windows (npm)
npm i -g azure-functions-core-tools@4
# macOS
brew tap azure/functions && brew install azure-functions-core-tools@4
# Linux (Ubuntu/Debian)
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-$(lsb_release -cs)-prod $(lsb_release -cs) main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-get update && sudo apt-get install azure-functions-core-tools-4
```
---
## 快速开始
### 1. 配置环境变量
```bash
export AZURE_SEARCH_ENDPOINT="https://your-search-service.search.windows.net"
export AZURE_SEARCH_API_KEY="your-admin-key"
export AZURE_SEARCH_INDEX_NAME="openclaw-resources"
# LiteLLM(用于 embedding 和 smart_query)
export OPENAI_BASE_URL="https://litellm.example.com/v1"
export OPENAI_API_KEY="sk-your-key"
export EMBEDDING_MODEL="taiji/text-embedding-3-small"
```
### 2. 本地测试
```bash
pip install -r requirements.txt
python run_api_server.py
```
## 部署到 Azure Function App
本 Agent 支持直接部署到 **Azure 函数应用**(无需 AKS)。
### 1. 安装 Azure Functions Core Tools
```bash
# Windows (npm)
npm i -g azure-functions-core-tools@4
# macOS (Homebrew)
brew tap azure/functions && brew install azure-functions-core-tools@4
# Linux 见: https://learn.microsoft.com/azure/azure-functions/functions-run-local
```
### 2. 本地配置
```bash
cp local.settings.json.example local.settings.json
# 编辑 local.settings.json,填入 AZURE_SEARCH_*、OPENAI_* 等
```
**注意**:`local.settings.json` 含密钥,不要提交到 Git(已在 .gitignore 中忽略)。
### 3. 本地运行(模拟 Function App)
```bash
pip install -r requirements.txt
func start
```
默认地址:`http://localhost:7071`。例如健康检查:`http://localhost:7071/health`。
### 4. 部署到 Azure
**方式 A:Azure CLI**
```bash
# 登录
az login
# 创建资源组与存储(若尚未创建)
az group create --name rg-azure-search-agent --location eastasia
az storage account create --name styouragent --resource-group rg-azure-search-agent --sku Standard_LRS
# 创建 Function App(Linux + Python 3.11)
az functionapp create \
--resource-group rg-azure-search-agent \
--consumption-plan-location eastasia \
--runtime python \
--runtime-version 3.11 \
--functions-version 4 \
--name func-azure-search-agent \
--storage-account styouragent \
--os-type Linux
# 配置应用设置(与 local.settings.json 中的 Values 对应)
az functionapp config appsettings set --name func-azure-search-agent --resource-group rg-azure-search-agent --settings \
AZURE_SEARCH_ENDPOINT="https://aiagnet.search.windows.net" \
AZURE_SEARCH_API_KEY="<你的密钥>" \
AZURE_SEARCH_INDEX_NAME="openclaw-resources" \
OPENAI_BASE_URL="https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1" \
OPENAI_API_KEY="<你的密钥>" \
EMBEDDING_MODEL="taiji/text-embedding-3-small"
# 部署代码(在项目根目录执行)
func azure functionapp publish func-azure-search-agent
```
**方式 B:VS Code**
安装 [Azure Functions 扩展](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions),右键项目 → “Deploy to Function App” → 选择或新建 Function App。
**方式 C:使用发布配置文件 (agnetdoc.PublishSettings)**
若已从 Azure 门户下载了发布配置文件(如 `agnetdoc.PublishSettings`),可按以下任一方式发布到 Function App **agnetdoc**。
1. **命令行(先登录 Azure)**
在项目根目录执行(将 `agnetdoc` 换成你的 Function App 名称):
```bash
az login
func azure functionapp publish agnetdoc
```
如需把本地的 `local.settings.json` 里的应用设置同步到 Azure(会覆盖远程同名设置):
```bash
func azure functionapp publish agnetdoc --publish-local-settings
```
2. **VS Code 使用发布配置文件**
- 安装 Azure Functions 扩展。
- 在 Azure 视图中找到你的 Function App(如 agnetdoc),右键 → **Deploy to Function App using Publish Profile**。
- 选择已下载的 `agnetdoc.PublishSettings` 文件(例如在 `Downloads` 中)。
3. **用脚本 + 发布配置文件发布(无需 az login)**
项目内提供 `publish_with_profile.py`,读取 `.PublishSettings` 后通过 Kudu 做 zip 部署:
```bash
# Windows(发布配置文件在下载目录)
.venv\Scripts\python publish_with_profile.py "C:\Users\gjgon\Downloads\agnetdoc.PublishSettings"
# Linux/macOS
.venv/bin/python publish_with_profile.py /path/to/agnetdoc.PublishSettings
```
### 5. 部署后访问
- 根路径:`https://.azurewebsites.net/`
- 健康检查:`https://.azurewebsites.net/health`
- REST API:`https://.azurewebsites.net/api/v1/search` 等
业务接口仍通过请求头 `api-key` 或 `Authorization: Bearer ` 鉴权,与本地/uvicorn 行为一致。
### 6. 说明
- **host.json** 中 `routePrefix: ""` 必须保留,否则 FastAPI 路由会多一层前缀。
- **AzureWebJobsStorage 与本地 Unhealthy**:`local.settings.json` 里若为 `UseDevelopmentStorage=true`,需本机运行 [Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite) 才会通过存储检查;否则 func 会一直报 `azure.functions.webjobs.storage: Unhealthy`,但 HTTP 接口仍可用。若不想跑 Azurite,可把 `AzureWebJobsStorage` 改为真实的 Azure 存储连接串,Unhealthy 即会消失。
- 如需生产级鉴权,可在 Azure 门户将 HTTP 触发器改为 `FUNCTION` 级别,或在前端加 APIM/网关。
- 消费计划(Consumption)有冷启动与超时限制;长时间运行或高并发可考虑 **Premium** 或 **Dedicated** 计划。
## API 接口
### MCP 端点
- `POST /mcp` — MCP JSON-RPC
- `GET /mcp/sse` — MCP SSE 连接
- `POST /mcp/sse` — MCP SSE 请求
### REST API
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/indexes` | 创建索引 |
| GET | `/api/v1/indexes` | 列出索引 |
| POST | `/api/v1/upload` | 上传文档 |
| POST | `/api/v1/search` | 搜索文档 |
| GET | `/api/v1/documents/{id}` | 获取文档 |
| PATCH | `/api/v1/documents/{id}` | 更新文档 |
| DELETE | `/api/v1/documents/{id}` | 删除文档 |
| POST | `/api/v1/smart-query` | AI 智能问答 |
### 使用示例
**上传文档:**
```bash
curl -X POST http://localhost:8000/api/v1/upload \
-H "api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{
"id": "doc-001",
"title": "项目架构设计文档",
"content": "本项目采用微服务架构...",
"project": "my-project",
"category": "设计文档",
"source": "human",
"author": "张三"
}
]
}'
```
**混合搜索(默认):**
```bash
curl -X POST http://localhost:8000/api/v1/search \
-H "api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "系统用了什么技术栈", "top": 5}'
```
**纯关键词搜索:**
```bash
curl -X POST http://localhost:8000/api/v1/search \
-H "api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "微服务", "search_mode": "keyword"}'
```
**AI 智能问答:**
```bash
curl -X POST http://localhost:8000/api/v1/smart-query \
-H "api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "怎么开发新的 Agent?", "project": "openclaw"}'
```
## 环境变量
| 变量 | 必需 | 说明 |
|------|------|------|
| AZURE_SEARCH_ENDPOINT | 是 | Azure AI Search 服务端点 URL |
| AZURE_SEARCH_API_KEY | 是 | Azure AI Search 管理密钥 |
| AZURE_SEARCH_INDEX_NAME | 否 | 默认索引名称,默认 `openclaw-resources` |
| OPENAI_BASE_URL | 否 | LiteLLM Gateway URL(embedding + 智能问答) |
| OPENAI_API_KEY | 否 | LiteLLM API Key |
| EMBEDDING_MODEL | 否 | Embedding 模型名称,默认 `taiji/text-embedding-3-small` |
| EMBEDDING_DIMENSIONS | 否 | 向量维度,默认 `1536` |
| LITELLM_MODEL | 否 | LLM 模型名称,默认 `taiji/gpt-4o-mini` |
| API_PORT | 否 | 端口,默认 8000 |
## 项目结构
```
azure_search_agent/
├── README.md
├── requirements.txt
├── run_api_server.py # 本地 uvicorn 启动(非 Function App)
├── function_app.py # Azure Function App 入口(ASGI 挂载 FastAPI)
├── host.json # Function 主机配置(routePrefix 为空)
├── local.settings.json.example
├── local.settings.json # 本地配置(勿提交,复制 example 后填写)
└── src/
├── __init__.py
└── server/
├── __init__.py
├── api_server.py # FastAPI + MCP HTTP + REST API
└── mcp_server.py # MCP 工具定义(混合搜索 + embedding)
```