new version

This commit is contained in:
zhanggangyong
2026-01-15 17:30:48 +00:00
parent f065b00d12
commit 6eaffba1aa
125 changed files with 13494 additions and 6529 deletions
-1640
View File
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
# 支持 ARM 架构的 Dockerfile
FROM --platform=linux/arm64 python:3.11-slim
WORKDIR /app
# 安装必要的系统依赖
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# 复制应用代码
COPY requirements.txt .
COPY app.py .
COPY k8s_manager.py .
COPY database.py .
# 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt
# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV NAMESPACE=ai-agents
ENV SERVICE_PORT=8000
ENV SERVICE_HOST=0.0.0.0
# 暴露端口
EXPOSE 8000
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:8000/')" || exit 1
# 运行应用
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
-158
View File
@@ -1,158 +0,0 @@
# 健康检查修复说明
## 问题描述
之前的健康检查实现存在一个严重问题:即使 agent 的容器已经崩溃(crashed),查询状态时仍然会显示为健康(healthy)。
### 根本原因
原实现只检查了 Pod 的 `phase`(如 Running、Pending 等),但没有检查容器的实际状态。即使容器崩溃或处于等待/终止状态,Pod 的 phase 可能仍然是 "Running"。
## 修复内容
### 1. 修改 `k8s_manager.py` 的 `get_pod_status` 方法
**主要改进:**
- ✅ 检查容器实际状态(running、waiting、terminated)
- ✅ 检查容器就绪状态(ready)
- ✅ 检查容器重启次数
- ✅ 新增 `health_status` 字段,返回真实健康状态
**健康状态分类:**
- `healthy`: 所有容器运行正常且就绪
- `unhealthy`: 容器崩溃、终止或未就绪
- `degraded`: 容器重启次数过多(>5次)
**新增字段:**
- `health_status`: 真实健康状态
- `containers`: 容器详细信息数组,包含:
- `name`: 容器名称
- `ready`: 是否就绪
- `restart_count`: 重启次数
- `state`: 当前状态(running/waiting/terminated)
- `reason`: 状态原因(如果有)
- `exit_code`: 退出码(如果已终止)
### 2. 修改 `k8s_manager_new.py` 的 `get_deployment_status` 方法
对于基于 Deployment 的实现,同样增加了对底层 Pod 容器的健康检查。
### 3. 更新 `app.py` 的 `PodStatusResponse` 模型
添加了新字段以支持响应中的健康状态信息。
## 使用方法
### 查询 Agent 状态
```bash
curl http://localhost:8000/agents/my-mysql-agenty/status
```
### 示例响应(健康状态)
```json
{
"name": "my-mysql-agenty",
"namespace": "ai-agents",
"status": "Running",
"health_status": "healthy",
"containers": [
{
"name": "mysql-agent",
"ready": true,
"restart_count": 0,
"state": "running",
"started_at": "2026-01-06T10:00:00Z"
}
],
...
}
```
### 示例响应(崩溃状态)
```json
{
"name": "my-mysql-agenty",
"namespace": "ai-agents",
"status": "Terminated",
"health_status": "unhealthy",
"containers": [
{
"name": "mysql-agent",
"ready": false,
"restart_count": 3,
"state": "terminated",
"reason": "Error",
"exit_code": 1,
"message": "Connection refused",
"finished_at": "2026-01-06T10:30:00Z"
}
],
...
}
```
### 示例响应(等待状态)
```json
{
"name": "my-mysql-agenty",
"namespace": "ai-agents",
"status": "Waiting",
"health_status": "unhealthy",
"containers": [
{
"name": "mysql-agent",
"ready": false,
"restart_count": 2,
"state": "waiting",
"reason": "CrashLoopBackOff",
"message": "Back-off restarting failed container"
}
],
...
}
```
## 测试
运行测试脚本验证修复:
```bash
# 设置环境变量
export API_URL="http://localhost:8000"
export AGENT_NAME="my-mysql-agenty"
# 运行测试
./test_health_check.sh
```
## 重启服务
修复后需要重启 agent-manager 服务以应用更改:
```bash
# 如果使用 systemd
sudo systemctl restart agent-manager
# 或者如果直接运行
pkill -f "uvicorn.*app:app"
uvicorn app:app --host 0.0.0.0 --port 8000 --reload
```
## 注意事项
1. **向后兼容性**:
- 原有的 `status` 字段保持不变
- 新增的 `health_status` 字段不会影响现有客户端
2. **建议**:
- 在监控和告警系统中使用 `health_status` 而非 `status`
- 检查 `containers` 数组获取详细的失败原因
3. **健康状态判断优先级**:
- 任何容器 unhealthy → 整体 unhealthy
- 任何容器 degraded(且无 unhealthy)→ 整体 degraded
- 所有容器 healthy → 整体 healthy
+166
View File
@@ -0,0 +1,166 @@
#!/bin/bash
##############################################################################
# Agent Manager Kubernetes 部署指南
##############################################################################
cat << 'EOF'
╔════════════════════════════════════════════════════════════════╗
║ Agent Manager - Kubernetes 部署指南 (ARM64) ║
╔════════════════════════════════════════════════════════════════╝
📋 部署前准备
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 确保已安装必要工具:
✓ Docker (支持 buildx)
✓ kubectl
✓ Azure CLI (az)
2. 配置 Azure 凭据:
export AZURE_TENANT_ID="your-tenant-id"
export AZURE_CLIENT_ID="your-client-id"
export AZURE_CLIENT_SECRET="your-client-secret"
export AZURE_SUBSCRIPTION_ID="your-subscription-id"
export AZURE_RESOURCE_GROUP="your-resource-group"
3. 配置 ACR 凭据 (如果使用私有镜像):
export ACR_USERNAME="your-acr-username"
export ACR_PASSWORD="your-acr-password"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚀 部署方式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方式 1: 完整部署(构建 + 部署)
./deploy-to-k8s-arm64.sh
方式 2: 快速部署(仅部署,使用已有镜像)
./quick-deploy-k8s.sh
方式 3: 跳过镜像构建
./deploy-to-k8s-arm64.sh --skip-build
方式 4: 仅构建镜像
./deploy-to-k8s-arm64.sh --skip-deploy
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📝 配置说明
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. ConfigMap (k8s/agent-manager-configmap.yaml):
- DATABASE_URL: PostgreSQL 连接字符串
- NAMESPACE: 默认命名空间
- AZURE_DNS_ZONE: DNS 域名
2. Secret (k8s/agent-manager-secret.yaml):
- AZURE_TENANT_ID: Azure 租户 ID
- AZURE_CLIENT_ID: Azure 客户端 ID
- AZURE_CLIENT_SECRET: Azure 客户端密钥
3. Deployment (k8s/agent-manager-deployment.yaml):
- replicas: 副本数量(默认 2)
- resources: 资源限制
- nodeSelector: ARM64 节点选择器
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔍 验证部署
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 查看 Pods 状态:
kubectl get pods -n agent-manager
2. 查看服务:
kubectl get svc -n agent-manager
3. 获取外网 IP:
kubectl get svc agent-manager -n agent-manager \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'
4. 查看日志:
kubectl logs -n agent-manager -l app=agent-manager --tail=100
5. 测试访问:
EXTERNAL_IP=$(kubectl get svc agent-manager -n agent-manager \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://$EXTERNAL_IP/
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔧 常用管理命令
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 扩容/缩容
kubectl scale deployment agent-manager -n agent-manager --replicas=3
# 重启 Pod
kubectl rollout restart deployment agent-manager -n agent-manager
# 查看部署状态
kubectl rollout status deployment agent-manager -n agent-manager
# 查看详细信息
kubectl describe deployment agent-manager -n agent-manager
# 进入容器
kubectl exec -it -n agent-manager \
$(kubectl get pod -n agent-manager -l app=agent-manager -o jsonpath='{.items[0].metadata.name}') \
-- /bin/bash
# 更新镜像
kubectl set image deployment/agent-manager \
agent-manager=agnettaiji.azurecr.io/agent-manager:new-tag \
-n agent-manager
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🗑️ 卸载
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
完全卸载 Agent Manager:
./undeploy-k8s.sh
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📂 目录结构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
agent-manager/
├── Dockerfile.arm64 # ARM64 架构 Dockerfile
├── deploy-to-k8s-arm64.sh # 完整部署脚本
├── quick-deploy-k8s.sh # 快速部署脚本
├── undeploy-k8s.sh # 卸载脚本
└── k8s/
├── agent-manager-namespace.yaml # 命名空间
├── agent-manager-configmap.yaml # 配置
├── agent-manager-secret.yaml # 密钥
├── agent-manager-deployment.yaml # 部署
├── agent-manager-service.yaml # 服务
└── agent-manager-rbac.yaml # 权限
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ 注意事项
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. ARM64 节点: 确保 K8s 集群有 ARM64 架构的节点
2. LoadBalancer: 需要云平台支持 LoadBalancer 类型的 Service
3. 数据库: PostgreSQL 需要可从 K8s 集群访问
4. 权限: Agent Manager 需要集群级别权限来管理其他 Pods
5. 镜像: 首次部署需要先构建并推送 ARM64 镜像到 ACR
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📖 更多信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- API 文档: API_DOCUMENTATION.md
- PostgreSQL 迁移: POSTGRESQL_MIGRATION.md
- 快速参考: QUICK_REFERENCE_PGSQL.md
╚════════════════════════════════════════════════════════════════╝
EOF
-194
View File
@@ -1,194 +0,0 @@
## 实时 Metrics 功能修复报告
### 📋 问题描述
**原问题**:`/agents/{agent_name}/metrics` 接口返回的 metrics 始终一致,只显示 Pod 的资源配额(requests/limits),而不是实时的资源使用情况。
### ✅ 修复内容
#### 1. 修改 `k8s_manager.py::get_pod_metrics()` 方法
**修改前**:
```python
def get_pod_metrics(self, pod_name: str) -> Dict:
pod = self.v1.read_namespaced_pod(name=pod_name, namespace=self.namespace)
container = pod.spec.containers[0]
resources = container.resources
return {
"name": pod_name,
"requests": {...}, # 静态配额
"limits": {...} # 静态配额
}
```
**修改后**:
```python
def get_pod_metrics(self, pod_name: str) -> Dict:
# 1. 获取静态配额
pod = self.v1.read_namespaced_pod(...)
resources = pod.spec.containers[0].resources
# 2. 获取实时使用情况(通过 metrics.k8s.io API)
from kubernetes.client import CustomObjectsApi
custom_api = CustomObjectsApi()
metrics = custom_api.get_namespaced_custom_object(
group="metrics.k8s.io",
version="v1beta1",
namespace=self.namespace,
plural="pods",
name=pod_name
)
# 3. 返回完整数据
return {
"name": pod_name,
"namespace": self.namespace,
"requests": {...},
"limits": {...},
"usage": { # 🆕 实时使用
"cpu": "14502n",
"memory": "8704Ki"
},
"timestamp": "..." # 🆕 更新时间
}
```
#### 2. 更新 `app.py::PodMetricsResponse` 模型
**修改前**:
```python
class PodMetricsResponse(BaseModel):
name: str
requests: Dict
limits: Dict
```
**修改后**:
```python
class PodMetricsResponse(BaseModel):
name: str
namespace: Optional[str] = None
requests: Dict
limits: Dict
usage: Optional[Dict] = None # 🆕 实时使用
timestamp: Optional[str] = None # 🆕 时间戳
metrics_available: Optional[bool] = None # 🆕 可用性标志
```
#### 3. 更新 API 文档
在 `API_DOCUMENTATION.md` 中添加了详细的字段说明和单位解释。
### 📊 测试结果
#### 测试 1: 单个 Agent 多次查询
```bash
# 查询 alice-echo 三次
测试 1: CPU=13812n, Memory=8704Ki, Time=2026-01-06T05:00:18Z
测试 2: CPU=14502n, Memory=8704Ki, Time=2026-01-06T05:01:04Z
测试 3: CPU=15234n, Memory=8704Ki, Time=2026-01-06T05:02:18Z
```
✅ **结果**:CPU 使用率实时变化,时间戳更新
#### 测试 2: 多个 Agent 对比
| Agent | CPU 使用 | 内存使用 | CPU 限制 | 内存限制 |
|-------|----------|----------|----------|----------|
| alice-echo | 14502n (0.014m) | 8704Ki (8.5Mi) | 500m | 512Mi |
| bob-chat | 10010n (0.010m) | 10840Ki (10.6Mi) | 500m | 512Mi |
| carol-code | 5330n (0.005m) | 10868Ki (10.6Mi) | 500m | 512Mi |
| jina-search | **897912n (0.897m)** | **41416Ki (40.4Mi)** | 500m | 512Mi |
| my-agent | 14780n (0.015m) | 8688Ki (8.5Mi) | 500m | 512Mi |
✅ **结果**:不同 Agent 显示不同的实时使用情况
### 🔍 技术细节
#### Metrics API 调用
```python
# Kubernetes Metrics API 端点
GET /apis/metrics.k8s.io/v1beta1/namespaces/{namespace}/pods/{pod_name}
# 响应格式
{
"kind": "PodMetrics",
"apiVersion": "metrics.k8s.io/v1beta1",
"metadata": {...},
"timestamp": "2026-01-06T05:01:04Z",
"containers": [
{
"name": "echo-agent",
"usage": {
"cpu": "14502n",
"memory": "8704Ki"
}
}
]
}
```
#### 单位说明
**CPU**:
- `n` (nanocores): 1 核 = 1,000,000,000 nanocores
- `m` (millicores): 1 核 = 1,000 millicores
- 转换: `14502n = 0.014502m ≈ 0.000014 核`
**内存**:
- `Ki` (Kibibytes): 1 KiB = 1024 bytes
- `Mi` (Mebibytes): 1 MiB = 1024 KiB
- 转换: `8704Ki = 8.5 MiB ≈ 8.9 MB`
### 🎯 功能特性
1. **实时监控**:通过 Kubernetes metrics-server 获取实时数据
2. **降级支持**:如果 metrics-server 不可用,仍返回配额信息
3. **时间戳**:显示 metrics 数据的更新时间
4. **完整信息**:同时显示配额(limits/requests)和使用(usage)
### 📝 使用示例
```bash
# 获取单个 Agent 的 metrics
curl http://localhost:8000/agents/alice-echo/metrics
# 监控 CPU 使用率
watch -n 5 'curl -s http://localhost:8000/agents/alice-echo/metrics | jq ".usage.cpu"'
# 对比多个 Agent
for agent in alice-echo bob-chat carol-code; do
echo "$agent:"
curl -s http://localhost:8000/agents/$agent/metrics | jq ".usage"
done
```
### ⚠️ 注意事项
1. **metrics-server 依赖**:需要集群安装 metrics-server
```bash
kubectl get deployment metrics-server -n kube-system
```
2. **更新频率**:metrics-server 通常每 15-60 秒更新一次数据
3. **网络延迟**:metrics API 调用可能增加约 50-200ms 响应时间
4. **权限要求**:需要 kubeconfig 有权限访问 metrics.k8s.io API
### ✅ 修复完成
- [x] 修改 `k8s_manager.py::get_pod_metrics()`
- [x] 更新 `app.py::PodMetricsResponse` 模型
- [x] 更新 API 文档
- [x] 创建测试脚本 `test_realtime_metrics.sh`
- [x] 验证多个 Agent 的实时数据
- [x] 确认 CPU/内存使用率实时变化
**问题状态**: ✅ 已解决
**修复时间**: 2026-01-06
**测试通过**: ✅ 5/5 Agents 显示实时数据
-353
View File
@@ -1,353 +0,0 @@
# Azure Blob Agent 多框架实现总结
## 📋 概述
本次更新为 Azure Blob Storage Agent 实现了三种框架支持:
1. **LangChain 版本** (已有) - 使用 LangChain + LiteLLM
2. **MCP 版本** (新增) - 使用 Model Context Protocol
3. **A2A 版本** (新增) - 使用 Agent-to-Agent 框架
## 🆕 新增文件
### Agent 实现
| 文件 | 说明 |
|------|------|
| `azure_blob_agent_mcp.py` | MCP 框架版本的 Agent 实现 |
| `azure_blob_agent_a2a.py` | A2A 框架版本的 Agent 实现 |
### Docker 相关
| 文件 | 说明 |
|------|------|
| `azure_blob_agent_mcp.Dockerfile` | MCP 版本的 Dockerfile |
| `azure_blob_agent_a2a.Dockerfile` | A2A 版本的 Dockerfile |
| `requirements_mcp.txt` | MCP 版本的依赖 |
| `requirements_a2a.txt` | A2A 版本的依赖 |
| `build_azure_blob_mcp.sh` | MCP 版本构建脚本 |
| `build_azure_blob_a2a.sh` | A2A 版本构建脚本 |
### 文档和测试
| 文件 | 说明 |
|------|------|
| `MULTI_FRAMEWORK_GUIDE.md` | 多框架使用指南 |
| `test_multi_framework.sh` | 多框架集成测试脚本 |
## 🔄 修改的文件
### 数据库层
**database.py** - 扩展了数据模型:
#### Template 模型新增字段:
- `agent_framework` - Agent 框架类型 (langchain/mcp/a2a)
- `tools_config` - 工具配置 JSON
- `default_model_provider` - 默认模型提供商
- `default_model_name` - 默认模型名称
#### Agent 模型新增字段:
- `agent_framework` - Agent 框架类型
- `tools_config` - 工具配置
- `tool_endpoint` - 工具端点 URL
- `tool_api_key` - 工具 API 密钥
- `model_provider` - 模型提供商
- `model_name` - 模型名称
- `model_endpoint` - 模型端点
- `model_api_key` - 模型 API 密钥
- `storage_connection_string` - 存储连接字符串
- `storage_account_name` - 存储账户名称
### API 层
**app.py** - 扩展了请求模型:
#### CreateTemplateRequest 新增字段:
```python
agent_framework: str = "langchain"
tools_config: Optional[Dict] = {}
default_model_provider: Optional[str] = None
default_model_name: Optional[str] = None
```
#### CreatePlatformAgentRequest 新增字段:
```python
namespace: Optional[str] = "ai-agents"
agent_framework: Optional[str] = None
tools_config: Optional[Dict] = {}
tool_endpoint: Optional[str] = None
tool_api_key: Optional[str] = None
model_provider: Optional[str] = None
model_name: Optional[str] = None
model_endpoint: Optional[str] = None
model_api_key: Optional[str] = None
storage_connection_string: Optional[str] = None
storage_account_name: Optional[str] = None
```
#### CreateCustomAgentRequest 同样新增了上述字段
### Kubernetes 层
**k8s_manager.py** - 扩展了部署逻辑:
#### _generate_pod_manifest 方法更新:
- 支持传递框架类型到容器环境变量
- 支持传递工具配置 (tools_config, tool_endpoint, tool_api_key)
- 支持传递模型配置 (model_provider, model_name, model_endpoint, model_api_key)
- 支持传递存储配置 (storage_connection_string, storage_account_name)
- 支持传递用户标识 (user_id, tenant_id)
- 支持自定义命名空间
#### 新增镜像映射:
```python
"azure_blob_agent_mcp": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-mcp:latest"
"azure_blob_agent_a2a": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-a2a:latest"
```
#### 新增端口映射:
```python
"azure_blob_agent_mcp": 8080
"azure_blob_agent_a2a": 8080
```
#### 新增环境变量说明(用于文档)
## 🏗️ 架构设计
### 参数传递流程
```
用户请求 (API)
↓
app.py (API 层)
├─ 验证参数
├─ 保存到数据库 (database.py)
└─ 调用 K8sManager
↓
k8s_manager.py (K8s 层)
├─ 构建环境变量
│ ├─ AGENT_FRAMEWORK
│ ├─ TOOLS_CONFIG
│ ├─ MODEL_*
│ ├─ STORAGE_*
│ └─ USER_ID, TENANT_ID, NAMESPACE
├─ 创建 Pod/Deployment
└─ 传递到容器
↓
Agent 容器 (azure_blob_agent_*.py)
├─ 读取环境变量
├─ 初始化框架
├─ 配置工具
├─ 连接存储
└─ 提供 API 服务
```
### 框架特性对比
| 特性 | LangChain | MCP | A2A |
|------|-----------|-----|-----|
| **实现文件** | azure_blob_agent.py | azure_blob_agent_mcp.py | azure_blob_agent_a2a.py |
| **工具定义** | LangChain Tools | MCP Tool Classes | A2A Action Handlers |
| **API 端点** | /query | /mcp/tools, /mcp/call | /a2a/capabilities, /a2a/message |
| **协作能力** | ❌ | ❌ | ✅ Agent 注册和通信 |
| **工具发现** | 内置 | GET /mcp/tools | GET /a2a/capabilities |
| **消息格式** | 自然语言 | MCP Protocol | A2A Message Protocol |
| **依赖** | langchain, litellm | fastapi, pydantic | fastapi, httpx |
## 📝 数据库迁移
提供了迁移脚本 `migrate_multi_framework.py`:
```bash
python migrate_multi_framework.py
```
支持:
- ✅ SQLite (开发环境)
- ✅ PostgreSQL (生产环境)
- ✅ 自动检测已存在字段
- ✅ 验证迁移结果
## 🚀 部署流程
### 1. 构建镜像
```bash
cd agent_templates
# 构建 MCP 版本
./build_azure_blob_mcp.sh
# 构建 A2A 版本
./build_azure_blob_a2a.sh
```
### 2. 运行数据库迁移
```bash
python migrate_multi_framework.py
```
### 3. 创建 Agent
```bash
# 创建 MCP Agent
curl -X POST http://agent-manager:8000/v2/agents/platform \
-H "Content-Type: application/json" \
-d @mcp_agent_config.json
# 创建 A2A Agent
curl -X POST http://agent-manager:8000/v2/agents/platform \
-H "Content-Type: application/json" \
-d @a2a_agent_config.json
```
### 4. 测试
```bash
./test_multi_framework.sh
```
## 🔧 环境变量配置示例
### MCP Agent
```bash
# 框架配置
AGENT_FRAMEWORK=mcp
TEMPLATE_TYPE=azure_blob_agent_mcp
# 工具配置
TOOLS_CONFIG='{"enabled_tools": ["list_containers", "list_blobs"]}'
# 模型配置
MODEL_PROVIDER=openai
MODEL_NAME=gpt-4
MODEL_API_KEY=sk-xxxx
MODEL_ENDPOINT=https://api.openai.com/v1
# 存储配置
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;...
STORAGE_ACCOUNT_NAME=myaccount
# 用户信息
USER_ID=user123
TENANT_ID=tenant456
NAMESPACE=ai-agents
```
### A2A Agent
```bash
# 框架配置
AGENT_FRAMEWORK=a2a
TEMPLATE_TYPE=azure_blob_agent_a2a
# Agent 身份
AGENT_ID=blob-agent-001
AGENT_ROLE=storage_manager
AGENT_CAPABILITIES='["blob_storage", "file_operations"]'
# 模型配置
MODEL_PROVIDER=azure-openai
MODEL_NAME=gpt-4
MODEL_API_KEY=xxxx
MODEL_ENDPOINT=https://myopenai.openai.azure.com
# 存储配置
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;...
# 用户信息
USER_ID=user123
TENANT_ID=tenant456
NAMESPACE=ai-agents
```
## 🎯 使用场景
### LangChain 版本
- ✅ 复杂的推理任务
- ✅ 多步骤文件处理
- ✅ 与现有 LangChain 应用集成
### MCP 版本
- ✅ 标准化工具调用
- ✅ 跨平台工具共享
- ✅ 轻量级集成
### A2A 版本
- ✅ 多 Agent 协作
- ✅ 分布式任务处理
- ✅ Agent 间通信
## 📚 API 端点对比
### LangChain
- `POST /query` - 自然语言查询
- `GET /health` - 健康检查
- `POST /connect` - 连接存储
### MCP
- `GET /mcp/tools` - 列出可用工具
- `POST /mcp/call` - 调用工具
- `POST /query` - 查询(简化版)
- `GET /health` - 健康检查
- `POST /connect` - 连接存储
### A2A
- `GET /a2a/capabilities` - 获取能力
- `POST /a2a/register` - 注册其他 Agent
- `GET /a2a/agents` - 列出已注册 Agent
- `POST /a2a/message` - 处理 A2A 消息
- `POST /a2a/collaborate` - 与其他 Agent 协作
- `POST /query` - 查询
- `GET /health` - 健康检查
- `POST /connect` - 连接存储
## ✅ 测试清单
- [ ] 数据库迁移成功
- [ ] MCP 镜像构建成功
- [ ] A2A 镜像构建成功
- [ ] MCP Agent 创建成功
- [ ] A2A Agent 创建成功
- [ ] MCP 工具调用正常
- [ ] A2A 消息处理正常
- [ ] 健康检查通过
- [ ] 存储连接正常
- [ ] 环境变量正确传递
## 🐛 已知问题
1. **LLM 集成**: MCP 和 A2A 版本目前使用简单规则匹配,需要集成实际 LLM 进行意图识别
2. **安全性**: API 密钥等敏感信息应加密存储
3. **日志**: 需要统一的日志收集和监控
## 🔮 未来改进
1. **安全增强**
- 密钥加密存储
- RBAC 权限控制
- API 密钥轮换
2. **功能扩展**
- 更多 Azure 服务集成
- 自定义工具注册
- 工具组合和编排
3. **监控和调试**
- 分布式追踪
- 性能监控
- 调试工具
4. **开发体验**
- Web UI 管理界面
- 可视化工具设计器
- Agent 模板市场
## 📖 相关文档
- [多框架使用指南](agent_templates/MULTI_FRAMEWORK_GUIDE.md)
- [API 文档](API_DOCUMENTATION.md)
- [Azure Blob Agent 原始文档](agent_templates/AZURE_BLOB_AGENT_USAGE.md)
+167
View File
@@ -0,0 +1,167 @@
#!/bin/bash
cat << 'EOF'
╔══════════════════════════════════════════════════════════════════╗
║ Agent Manager - Kubernetes 部署快速开始 ║
╚══════════════════════════════════════════════════════════════════╝
📦 已创建的文件列表
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
核心部署文件:
✓ Dockerfile.arm64 - ARM64 架构 Docker 镜像
✓ deploy-to-k8s-arm64.sh - 完整部署脚本(构建+部署)
✓ quick-deploy-k8s.sh - 快速部署脚本(仅部署)
✓ undeploy-k8s.sh - 卸载脚本
✓ K8S_DEPLOYMENT_GUIDE.sh - 部署指南
Kubernetes 配置文件 (k8s/):
✓ agent-manager-namespace.yaml - 命名空间定义
✓ agent-manager-configmap.yaml - 配置信息
✓ agent-manager-secret.yaml - 敏感凭据
✓ agent-manager-deployment.yaml - 部署定义(2副本,ARM64)
✓ agent-manager-service.yaml - LoadBalancer 服务
✓ agent-manager-rbac.yaml - 集群权限
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚀 三步快速部署
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 1: 配置 Azure 凭据(必需)
────────────────────────────────────────────────────────────
export AZURE_TENANT_ID="your-tenant-id"
export AZURE_CLIENT_ID="your-client-id"
export AZURE_CLIENT_SECRET="your-client-secret"
export AZURE_SUBSCRIPTION_ID="your-subscription-id"
export AZURE_RESOURCE_GROUP="your-resource-group"
步骤 2: 配置 ACR 凭据(如果使用私有镜像仓库)
────────────────────────────────────────────────────────────
# 方式 1: 手动设置
export ACR_USERNAME="your-acr-username"
export ACR_PASSWORD="your-acr-password"
# 方式 2: 从 Azure CLI 自动获取
export ACR_USERNAME=$(az acr credential show --name agnettaiji --query username -o tsv)
export ACR_PASSWORD=$(az acr credential show --name agnettaiji --query passwords[0].value -o tsv)
步骤 3: 执行部署
────────────────────────────────────────────────────────────
# 完整部署(构建 ARM64 镜像 + 部署到 K8s)
./deploy-to-k8s-arm64.sh
# 或者,如果镜像已存在,仅部署
./quick-deploy-k8s.sh
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 部署后验证
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 查看 Pods 状态:
kubectl get pods -n agent-manager -w
2. 获取外网访问地址:
EXTERNAL_IP=$(kubectl get svc agent-manager -n agent-manager \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Agent Manager URL: http://$EXTERNAL_IP"
3. 测试 API:
curl http://$EXTERNAL_IP/
curl http://$EXTERNAL_IP/agents
4. 查看日志:
kubectl logs -n agent-manager -l app=agent-manager --tail=100 -f
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔧 常见问题排查
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
问题: Pod 处于 Pending 状态
解决: 检查是否有 ARM64 节点
kubectl get nodes -o wide
kubectl describe pod -n agent-manager <pod-name>
问题: ImagePullBackOff
解决: 检查 ACR 凭据
kubectl get secret acr-secret -n agent-manager -o yaml
kubectl describe pod -n agent-manager <pod-name>
问题: CrashLoopBackOff
解决: 查看日志找出错误原因
kubectl logs -n agent-manager <pod-name>
kubectl describe pod -n agent-manager <pod-name>
问题: LoadBalancer IP 长时间未分配
解决: 检查云平台 LoadBalancer 支持
kubectl describe svc agent-manager -n agent-manager
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 架构说明
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
部署架构:
┌─────────────────────────────────────────────┐
│ LoadBalancer Service │
│ (外网 IP: xxx.xxx.xxx.xxx) │
└─────────────────┬───────────────────────────┘
│ Port 80
┌─────────────────┴───────────────────────────┐
│ Agent Manager Deployment │
│ (2 Replicas) │
├─────────────────────────────────────────────┤
│ Pod 1 (ARM64) │ Pod 2 (ARM64) │
│ - FastAPI │ - FastAPI │
│ - K8s Client │ - K8s Client │
│ - PostgreSQL │ - PostgreSQL │
│ - Azure SDK │ - Azure SDK │
└─────────────────────────────────────────────┘
│
┌─────────────────┴───────────────────────────┐
│ PostgreSQL (Azure Database) │
│ taijipda.postgres.database.azure.com │
└─────────────────────────────────────────────┘
关键特性:
✓ ARM64 架构支持(优化性能和成本)
✓ 双副本高可用部署
✓ LoadBalancer 自动外网访问
✓ 集群级别权限(管理其他 Agents)
✓ ConfigMap/Secret 配置管理
✓ 健康检查和自动重启
✓ 资源限制(CPU: 200m-500m, Memory: 256Mi-512Mi)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔗 相关文档
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
查看完整部署指南:
./K8S_DEPLOYMENT_GUIDE.sh
查看 API 文档:
cat API_DOCUMENTATION.md
查看 PostgreSQL 配置:
cat POSTGRESQL_MIGRATION.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 提示
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 首次部署建议使用完整部署脚本 ./deploy-to-k8s-arm64.sh
2. 确保 Kubernetes 集群有 ARM64 节点
3. 修改 k8s/agent-manager-configmap.yaml 设置数据库连接
4. 修改 k8s/agent-manager-secret.yaml 设置 Azure 凭据
5. 部署完成后记录外网 IP 地址
╚══════════════════════════════════════════════════════════════════╝
EOF
-860
View File
@@ -1,860 +0,0 @@
# Agent Manager 服务需求文档
## 1. 概述
### 1.1 服务定位
Agent Manager 是一个独立的服务,负责 AKS/K8s 上所有 Agent 的部署、管理和查询操作。它是 Agent 生命周期管理的核心服务,不涉及权限验证、计费等业务逻辑。
### 1.2 系统架构
```mermaid
flowchart TB
subgraph Frontend[前端]
UI[用户界面]
end
subgraph MCPServer[MCP Server]
Auth[权限验证]
Billing[计费管理]
Quota[配额管理]
AgentAPI[Agent API]
end
subgraph AgentManager[Agent Manager]
TemplateManager[模板管理]
PodManager[Pod 管理]
ResourceManager[资源管理]
HealthChecker[健康检查]
end
subgraph AKS[Azure Kubernetes Service]
subgraph AgentNS[Agent 命名空间 - 统一]
PlatformPods[平台 Agent Pods]
CustomPods[自定义 Agent Pods]
end
end
subgraph ACR[Azure Container Registry]
PlatformImages[平台 Agent 镜像仓库]
CustomImages[自定义 Agent 镜像仓库]
end
UI --> MCPServer
MCPServer --> AgentManager
AgentManager --> AKS
AgentManager --> ACR
```
### 1.3 调用链路
```
前端 → MCP Server(权限验证、计费、配额检查)→ Agent Manager(K8s 部署操作)→ AKS
```
### 1.4 核心设计原则
1. **按需创建**:Agent Pod 在用户实际使用时才创建,不预先启动
2. **配额分配**:分配的是 Pod 数量配额,不是实际运行的 Pod
3. **镜像共享**:同一模板的镜像配置(CPU/内存)是平台级别固定的
4. **实例隔离**:每个用户使用时创建自己的 Pod 实例
---
## 2. Agent 类型定义
### 2.1 平台端 Agent (Platform Agent)
| 属性 | 说明 |
|------|------|
| **来源** | 平台管理员打镜像到 ACR 平台镜像仓库 |
| **部署方式** | K8s 部署,使用平台预设的镜像,**按需创建 Pod** |
| **资源配置** | 管理员固定设置每个 Pod 的 CPU/内存(平台级别统一) |
| **分配方式** | 管理员设置总 Pod 上限 → 分配 Pod 数量给渠道 → 渠道分配给租户 |
| **使用方式** | 用户只需传查询参数即可使用 |
| **Pod 创建时机** | 用户实际使用时才创建 Pod,不预先启动 |
| **弹性伸缩** | 用户可在分配的配额内启动多个 Pod |
### 2.2 自定义 Agent (Custom Agent)
| 属性 | 说明 |
|------|------|
| **来源** | 平台提供模板镜像到 ACR 自定义镜像仓库,用户配置自己的密钥和终结点 |
| **部署方式** | K8s 部署,使用模板镜像 + 用户环境变量,**按需创建 Pod** |
| **资源配置** | 用户在分配的资源总量(CPU/内存)内自由配置每个 Pod 的大小 |
| **分配方式** | 管理员 → 渠道(分配 CPU/内存总量)→ 租户 |
| **使用方式** | 需要传终结点、密钥、查询参数等 |
| **Pod 创建时机** | 用户创建 Agent 并配置完成后启动 Pod |
| **弹性伸缩** | 可设置预留 Pod 数和弹性 Pod 数(如固定 2 个 + 弹性 2 个) |
### 2.3 两种 Agent 的核心区别
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Agent 类型对比 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ 平台端 Agent │ │ 自定义 Agent │ │
│ ├─────────────────────────────────┤ ├─────────────────────────────────┤ │
│ │ 镜像: 平台预设,完整可用 │ │ 镜像: 模板,需要用户配置 │ │
│ │ 配置: 无需用户配置 │ │ 配置: 需要终结点、密钥等 │ │
│ │ 资源: 固定大小,限制 Pod 数量 │ │ 资源: 限制总量,自由分配 │ │
│ │ 弹性: 在配额内启动多个 Pod │ │ 弹性: 预留N个 + 弹性M个 │ │
│ │ 归属: 每个Pod属于一个用户 │ │ 归属: 每个Pod属于一个用户 │ │
│ │ 创建: 用户使用时按需创建 │ │ 创建: 配置完成后启动 │ │
│ └─────────────────────────────────┘ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 2.4 资源分配流程
```mermaid
flowchart TB
subgraph Admin[管理员层]
A1[设置平台Agent模板]
A2[设置CPU/内存/最大Pod数]
A3[设置自定义Agent模板]
A4[设置自定义Agent资源池]
end
subgraph Channel[渠道层]
C1[获得平台Agent Pod配额]
C2[获得自定义Agent资源配额]
C3[分配给租户]
end
subgraph Tenant[租户层]
T1[获得平台Agent Pod配额]
T2[获得自定义Agent资源配额]
end
subgraph Usage[使用层]
U1[使用平台Agent - 按需创建Pod]
U2[创建自定义Agent - 配置后启动Pod]
end
A1 --> A2
A3 --> A4
A2 --> C1
A4 --> C2
C1 --> C3
C2 --> C3
C3 --> T1
C3 --> T2
T1 --> U1
T2 --> U2
```
### 2.5 ACR 镜像仓库规划
| 仓库 | 用途 | 示例路径 |
|------|------|----------|
| 平台 Agent 镜像仓库 | 存放平台预设的完整 Agent 镜像 | `your-acr.azurecr.io/platform-agents/` |
| 自定义 Agent 镜像仓库 | 存放需要用户配置的模板镜像 | `your-acr.azurecr.io/custom-agents/` |
### 2.6 K8s 命名空间规划
| 命名空间 | 用途 |
|----------|------|
| `ai-agents` | 统一的 Agent 命名空间,包含平台 Agent 和自定义 Agent 的所有 Pod |
---
## 3. 功能需求
### 3.1 模板管理
#### 3.1.1 平台 Agent 模板
| 功能 | 说明 |
|------|------|
| 注册模板 | 管理员注册新的平台 Agent 模板,包含镜像地址、默认资源配置等 |
| 更新模板 | 更新模板的镜像版本、资源配置等 |
| 删除模板 | 删除不再使用的模板 |
| 查询模板 | 获取模板列表和详情 |
**模板信息结构**:
```json
{
"name": "jina_search_agent",
"displayName": "Jina 搜索 Agent",
"description": "基于 Jina AI 的搜索 Agent",
"image": "your-acr.azurecr.io/platform-agents/jina-search:v1.0",
"category": "search",
"defaultConfig": {
"cpuRequest": "100m",
"cpuLimit": "500m",
"memoryRequest": "128Mi",
"memoryLimit": "512Mi",
"port": 8080
},
"healthCheck": {
"path": "/health",
"port": 8080,
"intervalSeconds": 30
},
"endpoints": {
"query": "/query",
"status": "/status"
}
}
```
#### 3.1.2 自定义 Agent 模板
| 功能 | 说明 |
|------|------|
| 注册模板 | 管理员注册自定义 Agent 模板,定义所需的环境变量 |
| 更新模板 | 更新模板配置 |
| 删除模板 | 删除模板 |
| 查询模板 | 获取模板列表和详情,包含所需环境变量定义 |
**模板信息结构**:
```json
{
"name": "openai_agent_template",
"displayName": "OpenAI Agent 模板",
"description": "需要配置 OpenAI API 密钥的 Agent 模板",
"image": "your-acr.azurecr.io/custom-agents/openai-template:v1.0",
"category": "llm",
"requiredEnvVars": [
{
"name": "OPENAI_API_KEY",
"displayName": "OpenAI API 密钥",
"description": "您的 OpenAI API 密钥",
"required": true,
"sensitive": true
},
{
"name": "OPENAI_API_BASE",
"displayName": "API 终结点",
"description": "OpenAI API 终结点地址",
"required": true,
"default": "https://api.openai.com/v1"
},
{
"name": "MODEL_NAME",
"displayName": "模型名称",
"description": "使用的模型名称",
"required": false,
"default": "gpt-4"
}
],
"defaultConfig": {
"cpuRequest": "100m",
"cpuLimit": "500m",
"memoryRequest": "128Mi",
"memoryLimit": "512Mi",
"port": 8080
}
}
```
### 3.2 平台 Agent 管理
#### 3.2.1 创建平台 Agent
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | Agent 名称,K8s 资源命名规范 |
| template | string | 是 | 模板名称 |
| namespace | string | 否 | K8s 命名空间,默认 platform-agents |
| replicas | int | 否 | 副本数,默认 1 |
| maxReplicas | int | 否 | 最大副本数,用于弹性伸缩 |
| resourceConfig | object | 否 | 资源配置,覆盖模板默认值 |
**响应**:
```json
{
"success": true,
"data": {
"name": "jina-search-agent-001",
"namespace": "platform-agents",
"template": "jina_search_agent",
"status": "Pending",
"replicas": 1,
"maxReplicas": 5,
"resourceConfig": {
"cpuRequest": "100m",
"cpuLimit": "500m",
"memoryRequest": "128Mi",
"memoryLimit": "512Mi"
},
"createdAt": "2026-01-04T12:00:00Z"
}
}
```
#### 3.2.2 扩缩容平台 Agent
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | Agent 名称 |
| replicas | int | 是 | 目标副本数 |
#### 3.2.3 删除平台 Agent
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | Agent 名称 |
#### 3.2.4 查询平台 Agent
- 获取单个 Agent 状态
- 获取 Agent 列表(支持分页、筛选)
- 获取 Agent 资源使用情况
- 获取 Agent 日志
### 3.3 自定义 Agent 管理
#### 3.3.1 创建自定义 Agent
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | Agent 名称 |
| template | string | 是 | 模板名称 |
| namespace | string | 否 | K8s 命名空间,默认 custom-agents |
| ownerId | string | 是 | 所属用户 ID |
| envVars | object | 是 | 环境变量配置(终结点、密钥等) |
| resourceConfig | object | 是 | 资源配置 |
| scalingConfig | object | 否 | 弹性伸缩配置 |
**资源配置结构**:
```json
{
"cpuRequest": "200m",
"cpuLimit": "1000m",
"memoryRequest": "256Mi",
"memoryLimit": "1Gi"
}
```
**弹性伸缩配置结构**:
```json
{
"minReplicas": 2,
"maxReplicas": 4,
"targetCPUUtilization": 80
}
```
**响应**:
```json
{
"success": true,
"data": {
"name": "my-openai-agent-001",
"namespace": "custom-agents",
"template": "openai_agent_template",
"ownerId": "user-uuid-123",
"status": "Pending",
"resourceConfig": {
"cpuRequest": "200m",
"cpuLimit": "1000m",
"memoryRequest": "256Mi",
"memoryLimit": "1Gi"
},
"scalingConfig": {
"minReplicas": 2,
"maxReplicas": 4
},
"createdAt": "2026-01-04T12:00:00Z"
}
}
```
#### 3.3.2 更新自定义 Agent 配置
**可更新内容**:
- 环境变量(终结点、密钥等)
- 资源配置(需要重启 Pod)
- 弹性伸缩配置
#### 3.3.3 扩缩容自定义 Agent
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | Agent 名称 |
| replicas | int | 是 | 目标副本数 |
#### 3.3.4 删除自定义 Agent
#### 3.3.5 查询自定义 Agent
- 获取单个 Agent 状态
- 获取 Agent 列表(支持按 ownerId 筛选)
- 获取 Agent 资源使用情况
- 获取 Agent 日志
### 3.4 资源统计
#### 3.4.1 平台 Agent 资源统计
```json
{
"totalPods": 15,
"runningPods": 12,
"pendingPods": 2,
"failedPods": 1,
"byTemplate": {
"jina_search_agent": {
"totalPods": 5,
"runningPods": 5
},
"mysql_agent": {
"totalPods": 10,
"runningPods": 7
}
}
}
```
#### 3.4.2 自定义 Agent 资源统计
```json
{
"totalPods": 20,
"totalCpuRequested": "4000m",
"totalMemoryRequested": "8Gi",
"byOwner": {
"user-uuid-123": {
"pods": 3,
"cpuRequested": "600m",
"memoryRequested": "1.5Gi"
}
}
}
```
### 3.5 健康检查
| 功能 | 说明 |
|------|------|
| Pod 健康检查 | 定期检查 Pod 的健康状态 |
| 服务健康检查 | 检查 Agent 服务的可用性 |
| 自动恢复 | 检测到不健康的 Pod 时触发重启 |
---
## 4. API 设计
### 4.1 模板管理 API
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /templates | 获取所有模板列表 |
| GET | /templates/platform | 获取平台 Agent 模板列表 |
| GET | /templates/custom | 获取自定义 Agent 模板列表 |
| GET | /templates/{name} | 获取模板详情 |
| POST | /templates | 注册新模板 |
| PUT | /templates/{name} | 更新模板 |
| DELETE | /templates/{name} | 删除模板 |
### 4.2 平台 Agent API
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /platform-agents | 获取平台 Agent 列表 |
| GET | /platform-agents/{name} | 获取平台 Agent 详情 |
| GET | /platform-agents/{name}/status | 获取 Agent 状态 |
| GET | /platform-agents/{name}/metrics | 获取资源使用情况 |
| GET | /platform-agents/{name}/logs | 获取 Agent 日志 |
| POST | /platform-agents | 创建平台 Agent |
| PUT | /platform-agents/{name}/scale | 扩缩容 |
| PUT | /platform-agents/{name}/config | 更新配置 |
| DELETE | /platform-agents/{name} | 删除 Agent |
| POST | /platform-agents/{name}/restart | 重启 Agent |
### 4.3 自定义 Agent API
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /custom-agents | 获取自定义 Agent 列表 |
| GET | /custom-agents/{name} | 获取自定义 Agent 详情 |
| GET | /custom-agents/{name}/status | 获取 Agent 状态 |
| GET | /custom-agents/{name}/metrics | 获取资源使用情况 |
| GET | /custom-agents/{name}/logs | 获取 Agent 日志 |
| POST | /custom-agents | 创建自定义 Agent |
| PUT | /custom-agents/{name}/scale | 扩缩容 |
| PUT | /custom-agents/{name}/config | 更新配置 |
| PUT | /custom-agents/{name}/env | 更新环境变量 |
| DELETE | /custom-agents/{name} | 删除 Agent |
| POST | /custom-agents/{name}/restart | 重启 Agent |
### 4.4 统计 API
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /stats/overview | 获取整体统计 |
| GET | /stats/platform-agents | 获取平台 Agent 统计 |
| GET | /stats/custom-agents | 获取自定义 Agent 统计 |
| GET | /stats/resources | 获取资源使用统计 |
### 4.5 健康检查 API
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /health | 服务健康检查 |
| GET | /ready | 服务就绪检查 |
---
## 5. 数据模型
### 5.1 Template 模板
```python
class Template:
name: str # 模板名称,唯一标识
display_name: str # 显示名称
description: str # 描述
type: str # 类型:platform / custom
image: str # 镜像地址
category: str # 分类:search, llm, database 等
default_config: dict # 默认资源配置
required_env_vars: list # 所需环境变量定义(自定义 Agent)
health_check: dict # 健康检查配置
endpoints: dict # 端点定义
created_at: datetime
updated_at: datetime
```
### 5.2 PlatformAgent 平台 Agent
```python
class PlatformAgent:
name: str # Agent 名称
namespace: str # K8s 命名空间
template: str # 使用的模板
status: str # 状态:Pending, Running, Failed 等
replicas: int # 当前副本数
max_replicas: int # 最大副本数
resource_config: dict # 资源配置
pod_ips: list # Pod IP 列表
service_name: str # Service 名称
service_port: int # Service 端口
access_url: str # 访问 URL
created_at: datetime
updated_at: datetime
```
### 5.3 CustomAgent 自定义 Agent
```python
class CustomAgent:
name: str # Agent 名称
namespace: str # K8s 命名空间
template: str # 使用的模板
owner_id: str # 所属用户 ID
status: str # 状态
env_vars: dict # 环境变量(加密存储)
resource_config: dict # 资源配置
scaling_config: dict # 弹性伸缩配置
min_replicas: int # 最小副本数(预留)
max_replicas: int # 最大副本数(弹性)
current_replicas: int # 当前副本数
pod_ips: list # Pod IP 列表
service_name: str # Service 名称
service_port: int # Service 端口
access_url: str # 访问 URL
created_at: datetime
updated_at: datetime
```
---
## 6. 资源限制逻辑
### 6.1 平台 Agent 资源限制
#### 6.1.1 分配流程
```mermaid
flowchart LR
A[管理员] -->|设置模板| B[平台Agent模板]
B -->|固定配置| C[CPU/内存/最大Pod数]
A -->|分配Pod配额| D[渠道]
D -->|分配Pod配额| E[租户]
E -->|使用时创建| F[Pod实例]
```
#### 6.1.2 配额检查流程
```mermaid
flowchart TD
A[用户请求使用平台Agent] --> B[MCP Server 权限验证]
B --> C{检查用户Pod配额}
C -->|配额充足| D[调用 Agent Manager]
C -->|配额不足| E[拒绝请求]
D --> F[创建Pod实例]
F --> G[更新已使用Pod数]
```
**限制规则**:
- 每个 Pod 的资源配置(CPU/内存)由管理员在模板级别固定
- 管理员设置该模板的最大 Pod 总数
- 分配给渠道时,分配的是 Pod 数量配额
- 渠道分配给租户时,分配的也是 Pod 数量配额
- 用户使用时才真正创建 Pod,按需启动
- 用户可在配额内启动多个 Pod 实例
**配额分配示例**:
```
平台 Agent: jina_search_agent
├── 模板配置: CPU=500m, Memory=512Mi, 最大Pod数=100
│
├── 渠道A 配额: 30 个 Pod
│ ├── 租户A1: 10 个 Pod 配额
│ ├── 租户A2: 15 个 Pod 配额
│ └── 租户A3: 5 个 Pod 配额
│
└── 渠道B 配额: 20 个 Pod
├── 租户B1: 12 个 Pod 配额
└── 租户B2: 8 个 Pod 配额
```
### 6.2 自定义 Agent 资源限制
#### 6.2.1 分配流程
```mermaid
flowchart LR
A[管理员] -->|设置模板| B[自定义Agent模板]
A -->|分配资源配额| C[渠道]
C -->|分配资源配额| D[租户]
D -->|在配额内创建| E[自定义Agent]
E -->|启动| F[Pod实例]
```
#### 6.2.2 配额检查流程
```mermaid
flowchart TD
A[用户创建自定义Agent] --> B[MCP Server 权限验证]
B --> C[计算请求资源总量]
C --> D{检查资源配额}
D -->|配额充足| E[调用 Agent Manager]
D -->|配额不足| F[拒绝请求]
E --> G[创建Pod实例]
G --> H[更新已使用资源]
```
**限制规则**:
- 分配给渠道/租户的是资源总量(CPU/内存)
- 用户在总量内自由配置每个 Pod 的资源大小
- 计算公式:`Σ(每个Pod的资源) ≤ 资源配额`
- 支持预留 Pod 数 + 弹性 Pod 数配置
**配额分配示例**:
```
自定义 Agent 资源池
│
├── 渠道A 配额: 8 CPU, 16GB 内存
│ ├── 租户A1: 4 CPU, 8GB 内存
│ │ └── 可创建: 4个(1CPU,2GB) 或 2个(2CPU,4GB) 或混合
│ └── 租户A2: 4 CPU, 8GB 内存
│
└── 渠道B 配额: 4 CPU, 8GB 内存
└── 租户B1: 4 CPU, 8GB 内存
└── 配置: 预留2个Pod + 弹性2个Pod
```
### 6.3 弹性伸缩配置
#### 6.3.1 平台 Agent 弹性配置
| 参数 | 说明 | 示例 |
|------|------|------|
| minReplicas | 最小 Pod 数(预留) | 1 |
| maxReplicas | 最大 Pod 数(配额上限) | 5 |
**说明**:用户在 `minReplicas` 到 `maxReplicas` 范围内按需创建 Pod
#### 6.3.2 自定义 Agent 弹性配置
| 参数 | 说明 | 示例 |
|------|------|------|
| minReplicas | 预留 Pod 数(始终运行) | 2 |
| maxReplicas | 最大 Pod 数(弹性上限) | 4 |
| targetCPUUtilization | CPU 使用率阈值 | 80% |
**说明**:
- `minReplicas` 个 Pod 始终运行(预留)
- 根据负载自动扩展到 `maxReplicas`
- 总资源消耗不能超过用户配额
---
## 7. 安全考虑
### 7.1 敏感信息处理
- 自定义 Agent 的环境变量(密钥、终结点等)需要加密存储
- 使用 K8s Secret 存储敏感信息
- API 响应中不返回敏感信息明文
- 日志中脱敏处理敏感字段
### 7.2 命名空间隔离
- 所有 Agent Pod 统一部署在 `ai-agents` 命名空间
- 通过 Label 区分平台 Agent 和自定义 Agent
- 通过 Label 标记 Pod 所属的用户/渠道
### 7.3 网络策略
- 配置 NetworkPolicy 限制 Pod 间通信
- 自定义 Agent 的 Pod 之间相互隔离
- 只允许 Agent Manager 和 MCP Server 访问 Agent Pod
---
## 8. 与 MCP Server 的集成
### 8.1 调用关系
```mermaid
sequenceDiagram
participant FE as 前端
participant MCP as MCP Server
participant AM as Agent Manager
participant K8s as Kubernetes
FE->>MCP: 创建 Agent 请求
MCP->>MCP: 权限验证
MCP->>MCP: 配额检查
MCP->>AM: 调用创建 API
AM->>K8s: 创建 Pod/Deployment
K8s-->>AM: 返回结果
AM-->>MCP: 返回创建结果
MCP->>MCP: 记录计费信息
MCP-->>FE: 返回结果
```
### 8.2 MCP Server 职责
| 职责 | 说明 |
|------|------|
| 权限验证 | 验证用户是否有权限操作 Agent |
| 配额检查 | 检查用户的资源配额是否足够 |
| 计费管理 | 记录 Agent 使用情况,计算费用 |
| 分配管理 | 管理 Agent 的分配关系(管理员→渠道→租户) |
### 8.3 Agent Manager 职责
| 职责 | 说明 |
|------|------|
| K8s 操作 | 创建、删除、更新 K8s 资源 |
| 状态查询 | 查询 Pod 状态、资源使用情况 |
| 健康检查 | 监控 Agent 健康状态 |
| 日志获取 | 获取 Pod 日志 |
---
## 9. 部署架构
### 9.1 服务部署
```yaml
# Agent Manager 部署配置示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-manager
namespace: taiji-system
spec:
replicas: 2
selector:
matchLabels:
app: agent-manager
template:
spec:
containers:
- name: agent-manager
image: your-acr.azurecr.io/agent-manager:latest
env:
- name: KUBERNETES_NAMESPACE
value: "ai-agents"
- name: ACR_PLATFORM_REGISTRY
value: "your-acr.azurecr.io/platform-agents"
- name: ACR_CUSTOM_REGISTRY
value: "your-acr.azurecr.io/custom-agents"
```
### 9.2 命名空间规划
| 命名空间 | 用途 |
|----------|------|
| taiji-system | 系统服务(MCP Server, Agent Manager 等) |
| ai-agents | 所有 Agent Pods(平台 Agent + 自定义 Agent) |
### 9.3 ACR 镜像仓库规划
| 仓库路径 | 用途 |
|----------|------|
| `your-acr.azurecr.io/platform-agents/` | 平台 Agent 镜像(完整可用) |
| `your-acr.azurecr.io/custom-agents/` | 自定义 Agent 模板镜像(需要用户配置) |
### 9.4 Pod Label 规划
```yaml
# 平台 Agent Pod Labels
labels:
app: agent
agent-type: platform
template: jina_search_agent
owner-id: user-uuid-123
channel-id: channel-uuid-456
# 自定义 Agent Pod Labels
labels:
app: agent
agent-type: custom
template: openai_agent_template
owner-id: user-uuid-123
channel-id: channel-uuid-456
```
---
## 10. 待确认事项
1. **镜像仓库**:是否使用 Azure Container Registry (ACR)?需要确认仓库地址和认证方式。
2. **弹性伸缩**:是否需要集成 Kubernetes HPA (Horizontal Pod Autoscaler)?
3. **日志收集**:是否需要集成日志收集系统(如 Azure Monitor, ELK 等)?
4. **监控告警**:是否需要集成 Prometheus/Grafana 进行监控?
5. **备份恢复**:Agent 配置是否需要备份?
6. **Pod 命名规则**:建议格式 `{template}-{owner-id-short}-{random}`,如 `jina-search-a1b2c3-xyz123`
---
## 11. 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| v1.0 | 2026-01-04 | 初始版本 |
| v1.1 | 2026-01-04 | 更新资源限制逻辑,明确按需创建和配额分配机制 |
-126
View File
@@ -1,126 +0,0 @@
# 僵尸进程和 CPU 100% 问题修复方案
## 问题诊断
**容器**: taiji-mcp-server (ID: 64d363729ff9)
**进程**: PID 3411601, CPU 100%
**根因**: Docker 健康检查导致的僵尸进程泄漏 (800+ defunct curl 进程)
## 立即修复步骤
### 方案 1: 重启容器(最快)
```bash
# 重启容器,清理僵尸进程
docker restart taiji-mcp-server
# 检查状态
docker ps | grep taiji-mcp-server
```
### 方案 2: 临时禁用健康检查
```bash
# 停止容器
docker stop taiji-mcp-server
# 使用 --no-healthcheck 重新启动
docker run -d --name taiji-mcp-server-temp \
--no-healthcheck \
-p 8002:8000 \
taiji-ai-pad-mcp-server
# 或修改 docker-compose.yml,注释掉 healthcheck
```
## 长期修复方案
### 方案 A: 使用 Python 内置健康检查(推荐)
不依赖外部 curl 命令,避免子进程问题:
**Dockerfile 修改**:
```dockerfile
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()" || exit 1
```
### 方案 B: 使用 tini 或 dumb-init(推荐)
正确处理子进程回收:
**Dockerfile 修改**:
```dockerfile
# 安装 tini
RUN apt-get update && apt-get install -y tini
# 使用 tini 作为 init 进程
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["python3", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# 健康检查保持不变
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
```
### 方案 C: 修改健康检查端点,减少数据库连接
**main.py 修改** (假设你有 `/health` 端点):
```python
@app.get("/health")
async def health_check():
"""轻量级健康检查,不连接数据库"""
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
@app.get("/health/deep")
async def deep_health_check():
"""深度健康检查,包含数据库连接测试"""
try:
# 测试数据库连接
db = next(get_db())
db.execute(text("SELECT 1"))
return {"status": "healthy", "database": "connected"}
except Exception as e:
raise HTTPException(status_code=503, detail=f"Unhealthy: {str(e)}")
```
### 方案 D: 调整健康检查频率
如果服务稳定,可以降低检查频率:
```dockerfile
HEALTHCHECK --interval=60s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
```
## 验证修复
```bash
# 1. 检查容器健康状态
docker ps | grep taiji-mcp-server
# 2. 检查僵尸进程数量
docker exec taiji-mcp-server ps aux | grep defunct | wc -l
# 3. 检查 CPU 占用
docker stats --no-stream taiji-mcp-server
# 4. 检查日志
docker logs --tail 100 taiji-mcp-server
```
## 监控建议
```bash
# 定期检查僵尸进程
watch -n 5 'docker exec taiji-mcp-server ps aux | grep defunct | wc -l'
# 监控资源使用
docker stats taiji-mcp-server
```
## 参考资料
- Docker 僵尸进程问题: https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/
- tini 项目: https://github.com/krallin/tini
- dumb-init: https://github.com/Yelp/dumb-init
Binary file not shown.
Binary file not shown.
+91
View File
@@ -0,0 +1,91 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ name }}
namespace: {{ namespace }}
labels:
app: {{ name }}
type: search-agent
managed-by: agent-manager
user-id: {{ user_id }}
spec:
replicas: {{ replicas | default(1) }}
selector:
matchLabels:
app: {{ name }}
template:
metadata:
labels:
app: {{ name }}
type: search-agent
managed-by: agent-manager
user-id: {{ user_id }}
spec:
imagePullSecrets:
- name: acr-secret
containers:
- name: search-agent
image: {{ image }}
ports:
- containerPort: 8080
name: http
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: TEMPLATE_TYPE
value: "search_agent"
- name: SERVICE_HOST
value: "0.0.0.0"
- name: SERVICE_PORT
value: "8080"
{% if env_vars %}
{% for key, value in env_vars.items() %}
- name: {{ key }}
value: "{{ value }}"
{% endfor %}
{% endif %}
resources:
requests:
cpu: {{ resources.cpu_request | default("500m") }}
memory: {{ resources.memory_request | default("512Mi") }}
limits:
cpu: {{ resources.cpu_limit | default("1000m") }}
memory: {{ resources.memory_limit | default("1Gi") }}
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: {{ name }}
namespace: {{ namespace }}
labels:
app: {{ name }}
type: search-agent
managed-by: agent-manager
user-id: {{ user_id }}
spec:
selector:
app: {{ name }}
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: http
type: ClusterIP
-364
View File
@@ -1,364 +0,0 @@
# Azure Blob Storage AI Agent 使用指南
## 概述
这是一个基于 LangChain + LiteLLM 的智能 Azure Blob Storage 管理代理,支持:
- 通过 API 动态接收 Azure Storage 连接字符串
- 使用自然语言查询和管理存储
- 通过环境变量配置 LLM 模型
## 架构说明
```
┌─────────────┐ HTTP API ┌──────────────────┐ Azure SDK ┌─────────────────┐
│ 客户端 │ ──────────────> │ FastAPI Server │ ──────────────> │ Azure Blob │
│ │ │ + LangChain │ │ Storage │
└─────────────┘ │ + LiteLLM │ └─────────────────┘
└──────────────────┘
│
▼
┌──────────────────┐
│ LiteLLM Server │
│ (4000端口) │
└──────────────────┘
```
## 快速开始
### 1. 构建镜像
```bash
cd /home/taiji/tools/agent-manager/agent_templates
# 构建镜像
./build_azure_blob_agent.sh latest
# 或者手动构建
docker build -f azure_blob_agent.Dockerfile -t azure-blob-agent:latest .
```
### 2. 启动 LiteLLM 服务(如果还没启动)
确保你的 LiteLLM 服务正在运行,例如:
```bash
# 检查 LiteLLM 是否运行
curl http://localhost:4000/health
# 如果没运行,启动它
docker run -d --name litellm \
-p 4000:4000 \
-e OPENAI_API_KEY=your_key \
ghcr.io/berriai/litellm:latest
```
### 3. 启动 Azure Blob Agent
```bash
docker run -d --name azure-blob-agent \
-p 8080:8080 \
-e LITELLM_API_BASE=http://host.docker.internal:4000 \
-e LITELLM_MODEL=gpt-3.5-turbo \
-e LITELLM_API_KEY=sk-1234 \
azure-blob-agent:latest
```
**环境变量说明:**
- `LITELLM_API_BASE`: LiteLLM 服务地址
- `LITELLM_MODEL`: 使用的模型名称
- `LITELLM_API_KEY`: LiteLLM API 密钥
- `SERVICE_HOST`: 服务监听地址(默认 0.0.0.0)
- `SERVICE_PORT`: 服务监听端口(默认 8080)
## API 使用
### 1. 健康检查
```bash
curl http://localhost:8080/health
```
**响应示例:**
```json
{
"status": "healthy",
"connected": true,
"connection_info": {
"account_kind": "StorageV2",
"sku_name": "Standard_LRS",
"connected_at": "2026-01-08T20:00:00"
}
}
```
### 2. 连接到 Azure Storage
```bash
curl -X POST http://localhost:8080/connect \
-H 'Content-Type: application/json' \
-d '{
"connection_string": "DefaultEndpointsProtocol=https;AccountName=yourname;AccountKey=yourkey;EndpointSuffix=core.windows.net"
}'
```
**响应示例:**
```json
{
"status": "connected",
"message": "成功连接到Azure Blob Storage",
"account_info": {
"account_kind": "StorageV2",
"sku_name": "Standard_LRS"
}
}
```
### 3. 自然语言查询
#### 列出所有容器
```bash
curl -X POST http://localhost:8080/query \
-H 'Content-Type: application/json' \
-d '{
"query": "列出所有容器"
}'
```
#### 查看容器中的文件
```bash
curl -X POST http://localhost:8080/query \
-H 'Content-Type: application/json' \
-d '{
"query": "显示 images 容器中的所有文件"
}'
```
#### 搜索文件
```bash
curl -X POST http://localhost:8080/query \
-H 'Content-Type: application/json' \
-d '{
"query": "在 documents 容器中搜索包含 report 的文件"
}'
```
#### 获取存储统计
```bash
curl -X POST http://localhost:8080/query \
-H 'Content-Type: application/json' \
-d '{
"query": "显示存储统计信息"
}'
```
#### 获取文件详细信息
```bash
curl -X POST http://localhost:8080/query \
-H 'Content-Type: application/json' \
-d '{
"query": "获取 images 容器中 logo.png 的详细信息"
}'
```
**响应示例:**
```json
{
"status": "success",
"query": "列出所有容器",
"answer": "当前有3个容器:\n1. images (最后修改: 2026-01-08)\n2. documents (最后修改: 2026-01-07)\n3. backups (最后修改: 2026-01-06)",
"intermediate_steps": "..."
}
```
## 在 Kubernetes 中部署
### 方法 1: 使用 agent-manager API
```bash
# 1. 首先确保模板已添加到 k8s_manager.py
# 2. 创建 agent
curl -X POST http://localhost:8000/agents \
-H 'Content-Type: application/json' \
-d '{
"name": "my-blob-agent",
"template": "azure_blob_agent",
"env": {
"LITELLM_API_BASE": "http://litellm-service:4000",
"LITELLM_MODEL": "gpt-3.5-turbo",
"LITELLM_API_KEY": "sk-1234"
}
}'
# 3. 连接到存储
curl -X POST http://my-blob-agent-ip:8080/connect \
-H 'Content-Type: application/json' \
-d '{
"connection_string": "YOUR_CONNECTION_STRING"
}'
```
### 方法 2: 直接部署 YAML
创建 `azure-blob-agent-deployment.yaml`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: azure-blob-agent
namespace: ai-agents
spec:
replicas: 1
selector:
matchLabels:
app: azure-blob-agent
template:
metadata:
labels:
app: azure-blob-agent
spec:
containers:
- name: azure-blob-agent
image: agnettaiji.azurecr.io/ai-agents/azure-blob-agent:latest
ports:
- containerPort: 8080
env:
- name: LITELLM_API_BASE
value: "http://litellm-service:4000"
- name: LITELLM_MODEL
value: "gpt-3.5-turbo"
- name: LITELLM_API_KEY
valueFrom:
secretKeyRef:
name: litellm-secret
key: api-key
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: azure-blob-agent-service
namespace: ai-agents
spec:
selector:
app: azure-blob-agent
ports:
- port: 8080
targetPort: 8080
type: ClusterIP
```
部署:
```bash
kubectl apply -f azure-blob-agent-deployment.yaml
```
## 支持的查询示例
| 自然语言查询 | 功能 |
|------------|------|
| "列出所有容器" | 显示所有容器列表 |
| "显示 images 容器中的文件" | 列出指定容器的文件 |
| "在 documents 中搜索 report" | 搜索包含关键字的文件 |
| "获取 data/test.csv 的信息" | 显示文件详细信息 |
| "显示存储统计" | 显示整体存储使用情况 |
| "images 容器有多少文件" | 统计容器文件数 |
| "查找所有 .pdf 文件" | 按扩展名搜索 |
## 故障排查
### 1. Agent 启动失败
```bash
# 检查日志
docker logs azure-blob-agent
# 常见问题:
# - LiteLLM 服务不可达:检查 LITELLM_API_BASE
# - 端口冲突:修改 SERVICE_PORT
```
### 2. 连接 Azure Storage 失败
```bash
# 检查连接字符串格式
# 正确格式:
DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey==;EndpointSuffix=core.windows.net
# 测试连接
curl -X POST http://localhost:8080/connect \
-H 'Content-Type: application/json' \
-d '{"connection_string": "YOUR_STRING"}'
```
### 3. 查询返回错误
```bash
# 检查是否已连接
curl http://localhost:8080/health
# 查看详细日志
docker logs -f azure-blob-agent
```
## 开发与扩展
### 添加新工具
在 `azure_blob_agent.py` 中添加新的工具函数:
```python
def download_blob(container_name: str, blob_name: str) -> str:
"""下载 blob 内容(示例)"""
# 实现下载逻辑
pass
# 在 create_blob_agent() 中添加工具
tools.append(
Tool(
name="download_blob",
func=lambda input_str: download_blob(*input_str.split(",")),
description="下载指定的文件。输入格式: '容器名,文件名'"
)
)
```
### 自定义模型
支持任何 LiteLLM 兼容的模型:
```bash
# 使用 Claude
-e LITELLM_MODEL=claude-3-sonnet-20240229
# 使用本地模型
-e LITELLM_MODEL=ollama/llama2
-e LITELLM_API_BASE=http://localhost:11434
# 使用 Azure OpenAI
-e LITELLM_MODEL=azure/gpt-4
```
## 性能优化
1. **连接池**: BlobServiceClient 会自动管理连接池
2. **缓存**: 可以添加 Redis 缓存常用查询结果
3. **并发**: 使用 `max_workers` 参数提高并发处理能力
## 安全建议
1. **连接字符串**: 不要在代码中硬编码,使用环境变量或 K8s Secrets
2. **访问控制**: 使用 SAS token 而非完整连接字符串
3. **网络隔离**: 在 K8s 中使用 NetworkPolicy 限制访问
4. **日志脱敏**: 避免记录敏感信息
## 更多资源
- [Azure Blob Storage Python SDK](https://learn.microsoft.com/azure/storage/blobs/storage-quickstart-blobs-python)
- [LangChain Documentation](https://python.langchain.com/docs/get_started/introduction)
- [LiteLLM Documentation](https://docs.litellm.ai/)
-366
View File
@@ -1,366 +0,0 @@
# Azure Blob Agent - 多框架支持使用指南
本文档介绍如何使用三种不同框架版本的 Azure Blob Storage AI Agent:
- **LangChain 版本**: 使用 LangChain + LiteLLM
- **MCP 版本**: 使用 Model Context Protocol
- **A2A 版本**: 使用 Agent-to-Agent 框架
## 📋 目录
1. [框架对比](#框架对比)
2. [部署配置](#部署配置)
3. [API 使用示例](#api-使用示例)
4. [创建 Agent 示例](#创建-agent-示例)
## 🔍 框架对比
| 特性 | LangChain | MCP | A2A |
|------|-----------|-----|-----|
| 工具调用 | LangChain Tools | MCP Protocol | A2A Messages |
| Agent 协作 | ❌ | ❌ | ✅ |
| 结构化输出 | ✅ | ✅ | ✅ |
| 复杂推理 | ✅ | ⚡ 轻量 | ⚡ 轻量 |
| 适用场景 | 复杂任务链 | 标准化工具 | 多Agent协作 |
## 🚀 部署配置
### 1. LangChain 版本
```json
{
"name": "my-blob-agent",
"template_name": "azure_blob_agent",
"owner_id": "user123",
"namespace": "ai-agents",
"agent_framework": "langchain",
"environment_vars": {
"LITELLM_API_BASE": "http://litellm-service:4000",
"LITELLM_MODEL": "gpt-3.5-turbo",
"LITELLM_API_KEY": "sk-xxxx",
"AZURE_STORAGE_CONNECTION_STRING": "DefaultEndpointsProtocol=https;..."
}
}
```
### 2. MCP 版本
```json
{
"name": "my-blob-agent-mcp",
"template_name": "azure_blob_agent_mcp",
"owner_id": "user123",
"namespace": "ai-agents",
"agent_framework": "mcp",
"model_provider": "openai",
"model_name": "gpt-4",
"model_api_key": "sk-xxxx",
"model_endpoint": "https://api.openai.com/v1",
"storage_connection_string": "DefaultEndpointsProtocol=https;...",
"tools_config": {
"enabled_tools": ["list_containers", "list_blobs", "search_blobs"]
}
}
```
### 3. A2A 版本
```json
{
"name": "my-blob-agent-a2a",
"template_name": "azure_blob_agent_a2a",
"owner_id": "user123",
"namespace": "ai-agents",
"agent_framework": "a2a",
"model_provider": "openai",
"model_name": "gpt-4",
"model_api_key": "sk-xxxx",
"storage_connection_string": "DefaultEndpointsProtocol=https;...",
"environment_vars": {
"AGENT_ID": "blob-agent-001",
"AGENT_ROLE": "storage_manager",
"AGENT_CAPABILITIES": "[\"blob_storage\", \"file_operations\"]"
}
}
```
## 📡 API 使用示例
### MCP 版本 API
#### 1. 列出所有可用工具
```bash
curl http://<agent-url>/mcp/tools
```
响应:
```json
{
"tools": [
{
"name": "list_containers",
"description": "列出 Azure Blob Storage 中的所有容器",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
},
{
"name": "list_blobs",
"description": "列出指定容器中的所有文件",
"inputSchema": {
"type": "object",
"properties": {
"container_name": {
"type": "string",
"description": "容器名称"
}
},
"required": ["container_name"]
}
}
]
}
```
#### 2. 调用 MCP 工具
```bash
curl -X POST http://<agent-url>/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool_name": "list_containers",
"parameters": {}
}'
```
```bash
curl -X POST http://<agent-url>/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool_name": "list_blobs",
"parameters": {
"container_name": "my-container"
}
}'
```
### A2A 版本 API
#### 1. 获取 Agent 能力
```bash
curl http://<agent-url>/a2a/capabilities
```
响应:
```json
{
"agent_id": "blob-agent-001",
"agent_role": "storage_manager",
"capabilities": ["blob_storage", "file_operations"],
"supported_actions": [
"list_containers",
"list_blobs",
"get_blob_info",
"search_blobs",
"get_stats"
]
}
```
#### 2. 注册其他 Agent
```bash
curl -X POST http://<agent-url>/a2a/register \
-H "Content-Type: application/json" \
-d '{
"agent_id": "analytics-agent",
"agent_role": "data_analyzer",
"capabilities": ["data_analysis", "visualization"],
"endpoint": "http://analytics-agent:8080"
}'
```
#### 3. 发送 A2A 消息
```bash
curl -X POST http://<agent-url>/a2a/message \
-H "Content-Type: application/json" \
-d '{
"message_id": "msg-001",
"from_agent": "external-agent",
"to_agent": "blob-agent-001",
"message_type": "request",
"action": "list_containers",
"parameters": {}
}'
```
#### 4. Agent 间协作
```bash
curl -X POST http://<agent-url>/a2a/collaborate \
-H "Content-Type: application/json" \
-d '{
"target_agent_id": "analytics-agent",
"action": "analyze_data",
"parameters": {
"data_source": "blob_storage"
}
}'
```
## 🛠️ 创建 Agent 示例
### 使用 Agent Manager API 创建
#### 1. 创建 MCP Agent
```bash
curl -X POST http://agent-manager:8000/v2/agents/platform \
-H "Content-Type: application/json" \
-d '{
"name": "blob-mcp-001",
"template_name": "azure_blob_agent_mcp",
"owner_id": "user123",
"namespace": "ai-agents",
"agent_framework": "mcp",
"model_provider": "openai",
"model_name": "gpt-4",
"model_api_key": "sk-xxxx",
"storage_connection_string": "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=xxx;EndpointSuffix=core.windows.net",
"tools_config": {
"max_iterations": 5,
"timeout": 30
}
}'
```
#### 2. 创建 A2A Agent
```bash
curl -X POST http://agent-manager:8000/v2/agents/platform \
-H "Content-Type: application/json" \
-d '{
"name": "blob-a2a-001",
"template_name": "azure_blob_agent_a2a",
"owner_id": "user123",
"namespace": "ai-agents",
"agent_framework": "a2a",
"model_provider": "azure-openai",
"model_name": "gpt-4",
"model_endpoint": "https://myopenai.openai.azure.com",
"model_api_key": "xxxx",
"storage_connection_string": "DefaultEndpointsProtocol=https;...",
"query_params": {
"agent_id": "blob-a2a-001",
"agent_role": "storage_manager",
"agent_capabilities": ["blob_storage", "file_operations"]
}
}'
```
## 🔧 参数说明
### 通用参数(所有框架)
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `name` | string | ✅ | Agent 名称(唯一) |
| `template_name` | string | ✅ | 模板名称 |
| `owner_id` | string | ✅ | 所有者ID |
| `namespace` | string | ❌ | K8s 命名空间,默认 `ai-agents` |
| `agent_framework` | string | ❌ | 框架类型: `langchain`, `mcp`, `a2a` |
| `storage_connection_string` | string | ❌ | Azure Storage 连接字符串 |
| `storage_account_name` | string | ❌ | 存储账户名称 |
### 模型配置参数(MCP/A2A)
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `model_provider` | string | ✅ | 模型提供商: `openai`, `azure-openai` |
| `model_name` | string | ✅ | 模型名称: `gpt-4`, `gpt-3.5-turbo` |
| `model_api_key` | string | ✅ | 模型 API 密钥 |
| `model_endpoint` | string | ❌ | 模型 API 端点 |
### 工具配置参数(MCP/A2A)
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `tools_config` | object | ❌ | 工具配置 JSON |
| `tool_endpoint` | string | ❌ | 外部工具端点 |
| `tool_api_key` | string | ❌ | 工具 API 密钥 |
### 资源配置参数
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `cpu_request` | string | ❌ | CPU 请求,如 `100m` |
| `cpu_limit` | string | ❌ | CPU 限制,如 `500m` |
| `memory_request` | string | ❌ | 内存请求,如 `128Mi` |
| `memory_limit` | string | ❌ | 内存限制,如 `512Mi` |
## 🎯 使用场景
### LangChain 版本适用于:
- 需要复杂推理链的任务
- 多步骤文件处理流程
- 集成现有 LangChain 生态系统
### MCP 版本适用于:
- 标准化工具调用
- 轻量级集成
- 跨平台工具共享
### A2A 版本适用于:
- 多 Agent 协作场景
- 分布式任务处理
- Agent 间通信需求
## 📝 数据库迁移
如果从旧版本升级,需要运行数据库迁移:
```sql
-- 添加新字段到 templates 表
ALTER TABLE templates ADD COLUMN agent_framework VARCHAR(50) DEFAULT 'langchain';
ALTER TABLE templates ADD COLUMN tools_config JSON;
ALTER TABLE templates ADD COLUMN default_model_provider VARCHAR(100);
ALTER TABLE templates ADD COLUMN default_model_name VARCHAR(200);
-- 添加新字段到 agents 表
ALTER TABLE agents ADD COLUMN agent_framework VARCHAR(50) DEFAULT 'langchain';
ALTER TABLE agents ADD COLUMN tools_config JSON;
ALTER TABLE agents ADD COLUMN tool_endpoint VARCHAR(500);
ALTER TABLE agents ADD COLUMN tool_api_key VARCHAR(500);
ALTER TABLE agents ADD COLUMN model_provider VARCHAR(100);
ALTER TABLE agents ADD COLUMN model_name VARCHAR(200);
ALTER TABLE agents ADD COLUMN model_endpoint VARCHAR(500);
ALTER TABLE agents ADD COLUMN model_api_key VARCHAR(500);
ALTER TABLE agents ADD COLUMN storage_connection_string VARCHAR(1000);
ALTER TABLE agents ADD COLUMN storage_account_name VARCHAR(200);
```
## 🐛 故障排查
### 问题: MCP 工具调用失败
**解决方案**:
1. 检查工具名称是否正确
2. 验证参数格式
3. 查看日志: `kubectl logs <pod-name> -n ai-agents`
### 问题: A2A Agent 无法注册
**解决方案**:
1. 确认目标 Agent 可访问
2. 检查网络策略
3. 验证 endpoint URL 格式
## 📚 更多资源
- [LangChain 文档](https://python.langchain.com/)
- [MCP 协议规范](https://modelcontextprotocol.io/)
- [Agent Manager API 文档](../API_DOCUMENTATION.md)
-157
View File
@@ -1,157 +0,0 @@
# 🚀 Azure Blob Storage Agent 快速启动
## 一键启动命令
### 1. 构建镜像
```bash
cd /home/taiji/tools/agent-manager/agent_templates
./build_azure_blob_agent.sh latest
```
### 2. 启动 Agent(本地测试)
```bash
# 方式 A: 启动时提供连接字符串(推荐)
docker run -d --name azure-blob-agent \
-p 8080:8080 \
-e LITELLM_API_BASE=http://20.2.70.108:4000 \
-e LITELLM_MODEL=gpt-3.5-turbo \
-e LITELLM_API_KEY=sk-1234 \
-e AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=https;AccountName=xxx;AccountKey=xxx;EndpointSuffix=core.windows.net" \
azure-blob-agent:latest
# 方式 B: 稍后通过 API 连接
docker run -d --name azure-blob-agent \
-p 8080:8080 \
-e LITELLM_API_BASE=http://20.2.70.108:4000 \
-e LITELLM_MODEL=gpt-3.5-turbo \
-e LITELLM_API_KEY=sk-1234 \
azure-blob-agent:latest
# 然后调用 /connect API 连接
# 方式 C: 如果 LiteLLM 在另一个容器中
docker run -d --name azure-blob-agent \
--network host \
-e LITELLM_API_BASE=http://localhost:4000 \
-e LITELLM_MODEL=gpt-3.5-turbo \
-e LITELLM_API_KEY=sk-1234 \
-e AZURE_STORAGE_CONNECTION_STRING="YOUR_CONNECTION_STRING" \
azure-blob-agent:latest
```
### 3. 测试 Agent
#### 方法 1: 使用 Bash 测试脚本
```bash
./test_azure_blob_agent.sh
```
#### 方法 2: 使用 Python 客户端
```bash
# 设置连接字符串(可选)
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=https;AccountName=xxx;..."
# 运行客户端
python3 test_client.py
```
#### 方法 3: 使用 curl 手动测试
```bash
# 健康检查
curl http://localhost:8080/health
# 连接到 Azure Storage
curl -X POST http://localhost:8080/connect \
-H 'Content-Type: application/json' \
-d '{
"connection_string": "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey;EndpointSuffix=core.windows.net"
}'
# 执行查询
curl -X POST http://localhost:8080/query \
-H 'Content-Type: application/json' \
-d '{"query": "列出所有容器"}'
```
## 推送到 ACR
```bash
# 登录 ACR
az acr login --name agnettaiji
# 推送镜像
docker tag azure-blob-agent:latest agnettaiji.azurecr.io/ai-agents/azure-blob-agent:latest
docker push agnettaiji.azurecr.io/ai-agents/azure-blob-agent:latest
```
## 在 K8s 中部署
### 使用 agent-manager
```bash
# 添加到 k8s_manager.py 的 image_map
"azure_blob_agent": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent:latest"
# 创建 agent
curl -X POST http://localhost:8000/agents \
-H 'Content-Type: application/json' \
-d '{
"name": "my-blob-agent",
"template": "azure_blob_agent",
"env": {
"LITELLM_API_BASE": "http://litellm-service:4000",
"LITELLM_MODEL": "gpt-3.5-turbo",
"LITELLM_API_KEY": "sk-1234"
}
}'
```
## 常见问题
### Q: 容器启动失败
```bash
# 查看日志
docker logs azure-blob-agent
# 检查 LiteLLM 是否可达
docker exec azure-blob-agent curl http://host.docker.internal:4000/health
```
### Q: 无法连接到 Azure Storage
```bash
# 验证连接字符串格式
# 正确格式包含: AccountName, AccountKey, EndpointSuffix
# 测试连接
curl -X POST http://localhost:8080/connect \
-H 'Content-Type: application/json' \
-d '{"connection_string": "YOUR_STRING"}' -v
```
### Q: 查询没有响应
```bash
# 检查是否已连接
curl http://localhost:8080/health | jq .
# 查看详细日志
docker logs -f azure-blob-agent
```
## 文件清单
```
agent_templates/
├── azure_blob_agent.py # 主程序
├── azure_blob_agent.Dockerfile # Docker 镜像
├── build_azure_blob_agent.sh # 构建脚本
├── test_azure_blob_agent.sh # Bash 测试脚本
├── test_client.py # Python 客户端
├── AZURE_BLOB_AGENT_USAGE.md # 详细使用文档
└── QUICKSTART.md # 本文件
```
## 下一步
- 阅读 [详细使用文档](AZURE_BLOB_AGENT_USAGE.md)
- 查看 [agent_templates README](../README.md)
- 集成到你的应用中
-199
View File
@@ -1,199 +0,0 @@
# Azure Blob Agent - 快速参考
## 🚀 快速开始
### 1. 选择框架
| 框架 | 使用场景 | 文件 |
|------|---------|------|
| **LangChain** | 复杂推理任务 | `azure_blob_agent.py` |
| **MCP** | 标准化工具调用 | `azure_blob_agent_mcp.py` |
| **A2A** | 多 Agent 协作 | `azure_blob_agent_a2a.py` |
### 2. 创建 Agent (curl)
#### MCP 版本
```bash
curl -X POST http://agent-manager:8000/v2/agents/platform \
-H "Content-Type: application/json" \
-d '{
"name": "my-blob-mcp",
"template_name": "azure_blob_agent_mcp",
"owner_id": "user123",
"agent_framework": "mcp",
"model_provider": "openai",
"model_name": "gpt-4",
"model_api_key": "sk-xxxx",
"storage_connection_string": "DefaultEndpointsProtocol=https;..."
}'
```
#### A2A 版本
```bash
curl -X POST http://agent-manager:8000/v2/agents/platform \
-H "Content-Type: application/json" \
-d '{
"name": "my-blob-a2a",
"template_name": "azure_blob_agent_a2a",
"owner_id": "user123",
"agent_framework": "a2a",
"model_provider": "openai",
"model_name": "gpt-4",
"model_api_key": "sk-xxxx",
"storage_connection_string": "DefaultEndpointsProtocol=https;...",
"query_params": {
"agent_id": "my-blob-a2a",
"agent_role": "storage_manager"
}
}'
```
### 3. 使用 Agent
#### MCP - 列出工具
```bash
curl http://<agent-url>/mcp/tools
```
#### MCP - 调用工具
```bash
curl -X POST http://<agent-url>/mcp/call \
-H "Content-Type: application/json" \
-d '{"tool_name": "list_containers", "parameters": {}}'
```
#### A2A - 获取能力
```bash
curl http://<agent-url>/a2a/capabilities
```
#### A2A - 发送消息
```bash
curl -X POST http://<agent-url>/a2a/message \
-H "Content-Type: application/json" \
-d '{
"message_id": "msg-001",
"from_agent": "caller",
"to_agent": "my-blob-a2a",
"message_type": "request",
"action": "list_containers",
"parameters": {}
}'
```
## 🔧 必需参数
### MCP Agent
- ✅ `model_provider` - 模型提供商
- ✅ `model_name` - 模型名称
- ✅ `model_api_key` - API 密钥
### A2A Agent
- ✅ `model_provider` - 模型提供商
- ✅ `model_name` - 模型名称
- ✅ `model_api_key` - API 密钥
- ✅ `query_params.agent_id` - Agent ID
- ✅ `query_params.agent_role` - Agent 角色
## 🛠️ 可选参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `namespace` | K8s 命名空间 | `"ai-agents"` |
| `tools_config` | 工具配置 | `{"max_iterations": 5}` |
| `tool_endpoint` | 外部工具端点 | `"http://tools-api:8080"` |
| `model_endpoint` | 模型端点 | `"https://api.openai.com/v1"` |
| `storage_account_name` | 存储账户名 | `"myaccount"` |
| `cpu_request` | CPU 请求 | `"100m"` |
| `memory_request` | 内存请求 | `"256Mi"` |
## 📊 环境变量 (容器内)
### 框架相关
- `AGENT_FRAMEWORK` - 框架类型
- `TEMPLATE_TYPE` - 模板类型
### 模型相关
- `MODEL_PROVIDER` - 模型提供商
- `MODEL_NAME` - 模型名称
- `MODEL_API_KEY` - API 密钥
- `MODEL_ENDPOINT` - 端点 URL
### 工具相关
- `TOOLS_CONFIG` - 工具配置 JSON
- `TOOL_ENDPOINT` - 工具端点
- `TOOL_API_KEY` - 工具密钥
### 存储相关
- `AZURE_STORAGE_CONNECTION_STRING` - 连接字符串
- `STORAGE_ACCOUNT_NAME` - 账户名
### 用户相关
- `USER_ID` - 用户标识
- `TENANT_ID` - 租户标识
- `NAMESPACE` - 命名空间
## 🔍 故障排查
### Agent 启动失败
```bash
# 查看日志
kubectl logs <pod-name> -n ai-agents
# 查看事件
kubectl describe pod <pod-name> -n ai-agents
```
### 工具调用失败
```bash
# 检查工具列表
curl http://<agent-url>/mcp/tools
# 测试健康检查
curl http://<agent-url>/health
```
### 存储连接失败
```bash
# 验证连接字符串
curl -X POST http://<agent-url>/connect \
-H "Content-Type: application/json" \
-d '{"connection_string": "DefaultEndpointsProtocol=https;..."}'
```
## 📝 工具列表
### 共同工具(所有版本)
1. `list_containers` - 列出所有容器
2. `list_blobs` - 列出容器中的文件
3. `get_blob_info` - 获取文件详情
4. `search_blobs` - 搜索文件
5. `get_storage_stats` - 获取统计信息
## 🏗️ 构建镜像
```bash
cd agent_templates
# MCP 版本
./build_azure_blob_mcp.sh
# A2A 版本
./build_azure_blob_a2a.sh
```
## 🧪 测试
```bash
# 设置环境变量
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=https;..."
export OPENAI_API_KEY="sk-xxxx"
# 运行测试
./test_multi_framework.sh
```
## 📚 更多文档
- 详细指南: [MULTI_FRAMEWORK_GUIDE.md](MULTI_FRAMEWORK_GUIDE.md)
- 实现总结: [MULTI_FRAMEWORK_SUMMARY.md](../MULTI_FRAMEWORK_SUMMARY.md)
@@ -0,0 +1,16 @@
"""
A2A LiteLLM Agent Package
"""
from .agent import LiteLLMAgent
from .config import get_config, LiteLLMConfig, AgentConfig, A2AConfig
from .a2a_server import A2AAgentServer, create_app
__all__ = [
"LiteLLMAgent",
"get_config",
"LiteLLMConfig",
"AgentConfig",
"A2AConfig",
"A2AAgentServer",
"create_app"
]
@@ -0,0 +1,30 @@
# A2A LiteLLM Agent Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY agents/a2a_litellm_agent/requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY agents/a2a_litellm_agent/ .
# 设置环境变量
ENV SERVICE_HOST=0.0.0.0
ENV SERVICE_PORT=8080
ENV POD_NAME=a2a-litellm-agent
ENV TEMPLATE_TYPE=a2a_litellm_agent
# 暴露端口
EXPOSE 8080
# 启动命令
CMD ["python", "main.py"]
@@ -0,0 +1,539 @@
"""
A2A协议兼容的Agent服务
实现Google Agent2Agent协议规范
支持从请求传入 API key,也支持从环境变量获取
"""
import asyncio
import json
import uuid
import os
from typing import Optional, Dict, Any, AsyncGenerator
from datetime import datetime
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import structlog
from agent import LiteLLMAgent
from config import get_config, AgentConfig, A2AConfig
# 配置日志
logger = structlog.get_logger()
# 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0")
SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))
POD_NAME = os.getenv("POD_NAME", "a2a-litellm-agent")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "a2a_litellm_agent")
# ============== A2A 协议数据模型 ==============
class A2APart(BaseModel):
"""A2A消息部分"""
kind: str = "text"
text: Optional[str] = None
data: Optional[Dict[str, Any]] = None
mime_type: Optional[str] = None
class A2AMessage(BaseModel):
"""A2A消息"""
role: str
parts: list[A2APart]
messageId: str = Field(default_factory=lambda: uuid.uuid4().hex)
class A2AMessageSendParams(BaseModel):
"""A2A发送消息参数"""
message: A2AMessage
configuration: Optional[Dict[str, Any]] = None
api_key: Optional[str] = Field(None, description="LiteLLM API密钥(可选,优先使用,否则从环境变量获取)")
model: Optional[str] = Field(None, description="模型名称(可选,优先使用,否则从环境变量获取)")
class A2ARequest(BaseModel):
"""A2A JSON-RPC请求"""
jsonrpc: str = "2.0"
id: str
method: str
params: Optional[Dict[str, Any]] = None
class A2AArtifact(BaseModel):
"""A2A响应工件"""
artifactId: str = Field(default_factory=lambda: uuid.uuid4().hex)
name: str = "response"
parts: list[A2APart]
class A2ATaskStatus(BaseModel):
"""A2A任务状态"""
state: str # submitted, working, input-required, completed, failed, canceled
timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat() + "Z")
message: Optional[str] = None
class A2ATask(BaseModel):
"""A2A任务"""
kind: str = "task"
id: str = Field(default_factory=lambda: uuid.uuid4().hex)
contextId: str = Field(default_factory=lambda: uuid.uuid4().hex)
status: A2ATaskStatus
artifacts: Optional[list[A2AArtifact]] = None
class A2AResponse(BaseModel):
"""A2A JSON-RPC响应"""
jsonrpc: str = "2.0"
id: str
result: Optional[A2ATask] = None
error: Optional[Dict[str, Any]] = None
class A2AStreamEvent(BaseModel):
"""A2A流式事件"""
kind: str
taskId: str
contextId: str
data: Optional[Dict[str, Any]] = None
# ============== Agent Card ==============
class AgentSkill(BaseModel):
"""Agent技能"""
id: str
name: str
description: str
inputSchema: Optional[Dict[str, Any]] = None
outputSchema: Optional[Dict[str, Any]] = None
class AgentCapabilities(BaseModel):
"""Agent能力"""
text: bool = True
streaming: bool = True
push_notifications: bool = False
forms: bool = False
files: bool = False
class AgentCard(BaseModel):
"""A2A Agent Card - 描述Agent能力"""
name: str
description: str
version: str
url: str
capabilities: AgentCapabilities
skills: list[AgentSkill]
authentication: Optional[Dict[str, Any]] = None
# ============== A2A Server ==============
class A2AAgentServer:
"""A2A协议Agent服务器"""
def __init__(
self,
api_key: Optional[str] = None,
model: Optional[str] = None
):
"""
初始化A2A Agent服务器
Args:
api_key: LiteLLM API密钥(可选,优先使用,否则从环境变量获取)
model: 模型名称(可选,优先使用,否则从环境变量获取)
"""
# 获取配置
self.llm_config, self.agent_config, self.a2a_config = get_config(api_key, model)
# 创建Agent(使用默认配置)
self.default_agent = LiteLLMAgent(
litellm_config=self.llm_config,
agent_config=self.agent_config
)
# 任务存储
self.tasks: Dict[str, A2ATask] = {}
# 创建FastAPI应用
self.app = self._create_app()
def _create_app(self) -> FastAPI:
"""创建FastAPI应用"""
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("A2A Agent服务启动", agent_name=self.agent_config.name)
yield
await self.default_agent.close()
logger.info("A2A Agent服务关闭")
app = FastAPI(
title=f"{self.agent_config.name} - A2A Agent",
description=self.agent_config.description,
version=self.agent_config.version,
lifespan=lifespan
)
# CORS中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 注册路由
self._register_routes(app)
return app
def _get_agent(self, api_key: Optional[str] = None, model: Optional[str] = None) -> LiteLLMAgent:
"""
获取Agent实例
如果提供了api_key或model,创建新的Agent实例
否则使用默认Agent
"""
if api_key or model:
# 创建新的配置和Agent
llm_config, agent_config, _ = get_config(api_key, model)
return LiteLLMAgent(litellm_config=llm_config, agent_config=agent_config)
return self.default_agent
def _register_routes(self, app: FastAPI):
"""注册A2A协议路由"""
@app.get("/")
async def root():
"""服务根路径"""
return {
"name": self.agent_config.name,
"version": self.agent_config.version,
"protocol": "A2A",
"status": "running",
"pod_name": POD_NAME,
"template_type": TEMPLATE_TYPE
}
@app.get("/health")
async def health_check():
"""健康检查"""
return {
"status": "healthy",
"pod_name": POD_NAME,
"template_type": TEMPLATE_TYPE,
"configured": self.llm_config.api_key is not None,
"timestamp": datetime.utcnow().isoformat()
}
@app.get("/.well-known/agent.json")
async def get_agent_card(request: Request):
"""获取Agent Card (A2A发现协议)"""
base_url = str(request.base_url).rstrip("/")
card = AgentCard(
name=self.agent_config.name,
description=self.agent_config.description,
version=self.agent_config.version,
url=base_url,
capabilities=AgentCapabilities(
text=True,
streaming=self.agent_config.enable_streaming,
push_notifications=False
),
skills=[
AgentSkill(
id="general-assistant",
name="通用助手",
description="回答问题、提供建议、协助完成各种任务"
),
AgentSkill(
id="code-helper",
name="代码助手",
description="编写、解释和调试代码"
)
]
)
return card.model_dump()
@app.post("/message/send")
async def send_message(request: Request):
"""A2A message/send 端点"""
body = await request.json()
# 解析JSON-RPC请求
try:
rpc_request = A2ARequest(**body)
except Exception as e:
return JSONResponse({
"jsonrpc": "2.0",
"id": body.get("id", "unknown"),
"error": {
"code": -32600,
"message": f"Invalid Request: {str(e)}"
}
})
# 处理 message/send 方法
if rpc_request.method == "message/send":
return await self._handle_message_send(rpc_request)
elif rpc_request.method == "message/stream":
return await self._handle_message_stream(rpc_request)
else:
return JSONResponse({
"jsonrpc": "2.0",
"id": rpc_request.id,
"error": {
"code": -32601,
"message": f"Method not found: {rpc_request.method}"
}
})
@app.post("/message/stream")
async def stream_message(request: Request):
"""A2A message/stream 端点 (SSE流式响应)"""
body = await request.json()
try:
rpc_request = A2ARequest(**body)
except Exception as e:
return JSONResponse({
"jsonrpc": "2.0",
"id": body.get("id", "unknown"),
"error": {
"code": -32600,
"message": f"Invalid Request: {str(e)}"
}
})
return await self._handle_message_stream(rpc_request)
@app.get("/tasks/{task_id}")
async def get_task(task_id: str):
"""获取任务状态"""
if task_id not in self.tasks:
raise HTTPException(status_code=404, detail="Task not found")
return self.tasks[task_id].model_dump()
async def _handle_message_send(self, request: A2ARequest) -> JSONResponse:
"""处理 message/send 请求"""
params = request.params or {}
message_data = params.get("message", {})
# 提取API key和model(如果提供)
api_key = params.get("api_key") or os.getenv("LITELLM_API_KEY")
model = params.get("model") or os.getenv("LITELLM_MODEL")
# 提取用户消息文本
user_text = ""
parts = message_data.get("parts", [])
for part in parts:
if part.get("kind") == "text":
user_text += part.get("text", "")
if not user_text:
return JSONResponse({
"jsonrpc": "2.0",
"id": request.id,
"error": {
"code": -32602,
"message": "Invalid params: no text content found"
}
})
# 创建任务
task_id = uuid.uuid4().hex
context_id = params.get("contextId", uuid.uuid4().hex)
task = A2ATask(
id=task_id,
contextId=context_id,
status=A2ATaskStatus(state="working")
)
self.tasks[task_id] = task
try:
# 获取Agent实例(如果提供了api_key或model,使用新的实例)
agent = self._get_agent(api_key, model)
# 调用Agent获取响应
logger.info("处理消息", task_id=task_id, message_preview=user_text[:50])
response_text = await agent.chat(
message=user_text,
conversation_id=context_id
)
# 如果创建了新Agent,关闭它
if api_key or model:
await agent.close()
# 更新任务状态
task.status = A2ATaskStatus(state="completed")
task.artifacts = [
A2AArtifact(
name="response",
parts=[A2APart(kind="text", text=response_text)]
)
]
self.tasks[task_id] = task
return JSONResponse({
"jsonrpc": "2.0",
"id": request.id,
"result": task.model_dump()
})
except Exception as e:
logger.error("处理消息失败", error=str(e))
task.status = A2ATaskStatus(state="failed", message=str(e))
self.tasks[task_id] = task
return JSONResponse({
"jsonrpc": "2.0",
"id": request.id,
"error": {
"code": -32000,
"message": f"Agent error: {str(e)}"
}
})
async def _handle_message_stream(self, request: A2ARequest) -> StreamingResponse:
"""处理 message/stream 请求 (SSE)"""
params = request.params or {}
message_data = params.get("message", {})
# 提取API key和model(如果提供)
api_key = params.get("api_key") or os.getenv("LITELLM_API_KEY")
model = params.get("model") or os.getenv("LITELLM_MODEL")
# 提取用户消息
user_text = ""
parts = message_data.get("parts", [])
for part in parts:
if part.get("kind") == "text":
user_text += part.get("text", "")
task_id = uuid.uuid4().hex
context_id = params.get("contextId", uuid.uuid4().hex)
async def event_generator() -> AsyncGenerator[str, None]:
"""生成SSE事件流"""
agent = None
try:
# 获取Agent实例
agent = self._get_agent(api_key, model)
# 发送任务开始事件
start_event = {
"kind": "task-start",
"taskId": task_id,
"contextId": context_id
}
yield f"data: {json.dumps(start_event)}\n\n"
# 获取流式响应
stream = await agent.chat(
message=user_text,
conversation_id=context_id,
stream=True
)
full_response = ""
async for chunk in stream:
full_response += chunk
# 发送文本增量事件
delta_event = {
"kind": "artifact-delta",
"taskId": task_id,
"contextId": context_id,
"data": {
"kind": "text",
"text": chunk
}
}
yield f"data: {json.dumps(delta_event)}\n\n"
# 发送完成事件
complete_event = {
"kind": "task-complete",
"taskId": task_id,
"contextId": context_id,
"data": {
"status": "completed",
"artifacts": [{
"name": "response",
"parts": [{"kind": "text", "text": full_response}]
}]
}
}
yield f"data: {json.dumps(complete_event)}\n\n"
except Exception as e:
# 发送错误事件
error_event = {
"kind": "task-error",
"taskId": task_id,
"contextId": context_id,
"data": {
"error": str(e)
}
}
yield f"data: {json.dumps(error_event)}\n\n"
finally:
# 如果创建了新Agent,关闭它
if agent and (api_key or model):
await agent.close()
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no"
}
)
def run(self, host: Optional[str] = None, port: Optional[int] = None):
"""运行服务器"""
import uvicorn
host = host or self.agent_config.host
port = port or self.agent_config.port
logger.info(f"启动A2A Agent服务", host=host, port=port)
uvicorn.run(self.app, host=host, port=port)
def create_app(api_key: Optional[str] = None, model: Optional[str] = None) -> FastAPI:
"""
创建FastAPI应用(用于uvicorn启动)
使用方式:
uvicorn a2a_server:app --host 0.0.0.0 --port 8080
或设置环境变量后:
export LITELLM_API_KEY="your-key"
export LITELLM_MODEL="your-model"
uvicorn a2a_server:app --host 0.0.0.0 --port 8080
"""
server = A2AAgentServer(api_key=api_key, model=model)
return server.app
# uvicorn 启动入口
# 环境变量: LITELLM_API_KEY, LITELLM_MODEL
app = create_app()
@@ -0,0 +1,282 @@
"""
LiteLLM Agent 核心模块
基于LiteLLM框架的Agent实现,支持A2A协议
"""
import asyncio
import json
import uuid
from typing import AsyncGenerator, Optional, Dict, Any, List
from dataclasses import dataclass, field
from datetime import datetime
import httpx
import structlog
from config import LiteLLMConfig, AgentConfig, get_config
# 配置日志
logger = structlog.get_logger()
@dataclass
class Message:
"""消息数据结构"""
role: str # user, assistant, system
content: str
message_id: str = field(default_factory=lambda: uuid.uuid4().hex)
timestamp: datetime = field(default_factory=datetime.now)
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class Conversation:
"""对话上下文"""
conversation_id: str = field(default_factory=lambda: uuid.uuid4().hex)
messages: List[Message] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.now)
def add_message(self, role: str, content: str) -> Message:
"""添加消息到对话"""
msg = Message(role=role, content=content)
self.messages.append(msg)
return msg
def to_openai_format(self) -> List[Dict[str, str]]:
"""转换为OpenAI格式的消息列表"""
return [{"role": m.role, "content": m.content} for m in self.messages]
class LiteLLMAgent:
"""
基于LiteLLM的Agent实现
支持功能:
- 多轮对话
- 流式响应
- 工具调用
- A2A协议兼容
"""
def __init__(
self,
api_key: Optional[str] = None,
model: Optional[str] = None,
litellm_config: Optional[LiteLLMConfig] = None,
agent_config: Optional[AgentConfig] = None
):
"""
初始化Agent
Args:
api_key: LiteLLM API密钥(可选,优先使用,否则从环境变量获取)
model: 模型名称(可选,优先使用,否则从环境变量获取)
litellm_config: LiteLLM配置对象
agent_config: Agent配置对象
"""
if litellm_config:
self.llm_config = litellm_config
else:
self.llm_config = LiteLLMConfig(api_key=api_key, model=model)
if agent_config:
self.agent_config = agent_config
else:
self.agent_config = AgentConfig()
# 验证配置
self.llm_config.validate()
# HTTP客户端
self._client: Optional[httpx.AsyncClient] = None
# 对话管理
self.conversations: Dict[str, Conversation] = {}
# 工具注册
self.tools: Dict[str, callable] = {}
logger.info(
"Agent初始化完成",
agent_name=self.agent_config.name,
model=self.llm_config.model,
base_url=self.llm_config.base_url
)
async def _get_client(self) -> httpx.AsyncClient:
"""获取或创建HTTP客户端"""
if self._client is None or self._client.is_closed:
self._client = httpx.AsyncClient(
timeout=httpx.Timeout(self.llm_config.timeout),
headers={
"Authorization": f"Bearer {self.llm_config.api_key}",
"Content-Type": "application/json"
}
)
return self._client
async def close(self):
"""关闭资源"""
if self._client and not self._client.is_closed:
await self._client.aclose()
def register_tool(self, name: str, func: callable, description: str = ""):
"""注册工具函数"""
self.tools[name] = {
"function": func,
"description": description
}
logger.info(f"注册工具: {name}")
def get_or_create_conversation(self, conversation_id: Optional[str] = None) -> Conversation:
"""获取或创建对话"""
if conversation_id and conversation_id in self.conversations:
return self.conversations[conversation_id]
conv = Conversation(conversation_id=conversation_id or uuid.uuid4().hex)
# 添加系统提示
conv.add_message("system", self.agent_config.system_prompt)
self.conversations[conv.conversation_id] = conv
return conv
async def chat(
self,
message: str,
conversation_id: Optional[str] = None,
stream: bool = False
) -> str | AsyncGenerator[str, None]:
"""
发送消息并获取回复
Args:
message: 用户消息
conversation_id: 对话ID(用于多轮对话)
stream: 是否流式响应
Returns:
如果stream=False,返回完整回复字符串
如果stream=True,返回异步生成器
"""
# 获取对话上下文
conversation = self.get_or_create_conversation(conversation_id)
conversation.add_message("user", message)
if stream:
return self._stream_chat(conversation)
else:
return await self._simple_chat(conversation)
async def _simple_chat(self, conversation: Conversation) -> str:
"""非流式对话"""
client = await self._get_client()
request_body = {
"model": self.llm_config.model,
"messages": conversation.to_openai_format(),
"temperature": self.llm_config.temperature,
"max_tokens": self.llm_config.max_tokens
}
logger.debug("发送请求", endpoint=self.llm_config.chat_endpoint)
try:
response = await client.post(
self.llm_config.chat_endpoint,
json=request_body
)
response.raise_for_status()
result = response.json()
assistant_message = result["choices"][0]["message"]["content"]
# 保存助手回复到对话
conversation.add_message("assistant", assistant_message)
logger.info("收到回复", length=len(assistant_message))
return assistant_message
except httpx.HTTPStatusError as e:
logger.error("HTTP错误", status_code=e.response.status_code, detail=e.response.text)
raise
except Exception as e:
logger.error("请求失败", error=str(e))
raise
async def _stream_chat(self, conversation: Conversation) -> AsyncGenerator[str, None]:
"""流式对话"""
client = await self._get_client()
request_body = {
"model": self.llm_config.model,
"messages": conversation.to_openai_format(),
"temperature": self.llm_config.temperature,
"max_tokens": self.llm_config.max_tokens,
"stream": True
}
full_response = ""
try:
async with client.stream(
"POST",
self.llm_config.chat_endpoint,
json=request_body
) as response:
response.raise_for_status()
async for line in response.aiter_lines():
if line.startswith("data: "):
data = line[6:]
if data == "[DONE]":
break
try:
chunk = json.loads(data)
delta = chunk.get("choices", [{}])[0].get("delta", {})
content = delta.get("content", "")
if content:
full_response += content
yield content
except json.JSONDecodeError:
continue
# 保存完整回复到对话
conversation.add_message("assistant", full_response)
except Exception as e:
logger.error("流式请求失败", error=str(e))
raise
async def invoke_tool(self, tool_name: str, **kwargs) -> Any:
"""调用注册的工具"""
if tool_name not in self.tools:
raise ValueError(f"未找到工具: {tool_name}")
tool = self.tools[tool_name]
func = tool["function"]
logger.info(f"调用工具: {tool_name}", kwargs=kwargs)
if asyncio.iscoroutinefunction(func):
return await func(**kwargs)
else:
return func(**kwargs)
# 示例工具函数
def tool_get_current_time() -> str:
"""获取当前时间"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def tool_calculate(expression: str) -> str:
"""计算数学表达式"""
try:
# 安全的数学计算
allowed_chars = set("0123456789+-*/.() ")
if not all(c in allowed_chars for c in expression):
return "错误: 不支持的字符"
result = eval(expression)
return str(result)
except Exception as e:
return f"计算错误: {str(e)}"
@@ -0,0 +1,145 @@
"""
LiteLLM Agent 配置模块
支持用户传入密钥和模型名称,同时支持从环境变量获取
"""
import os
from dataclasses import dataclass, field
from typing import Optional
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
@dataclass
class LiteLLMConfig:
"""LiteLLM 配置"""
# 基础URL - 用户提供的LiteLLM服务地址
base_url: str = "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io"
# 完整的chat completions端点
chat_endpoint: str = field(init=False)
# API密钥 - 优先使用传入的,否则从环境变量获取
api_key: Optional[str] = None
# 模型名称 - 优先使用传入的,否则从环境变量获取
model: Optional[str] = None
# 请求超时时间(秒)
timeout: int = 120
# 最大重试次数
max_retries: int = 3
# 温度参数
temperature: float = 0.7
# 最大token数
max_tokens: int = 4096
def __post_init__(self):
self.chat_endpoint = f"{self.base_url}/chat/completions"
# 从环境变量读取(如果未直接提供)
if self.api_key is None:
self.api_key = os.getenv("LITELLM_API_KEY")
if self.model is None:
self.model = os.getenv("LITELLM_MODEL", "gpt-4")
def validate(self) -> bool:
"""验证配置是否完整"""
if not self.api_key:
raise ValueError("API密钥未设置! 请设置 LITELLM_API_KEY 环境变量或直接传入 api_key")
if not self.model:
raise ValueError("模型名称未设置! 请设置 LITELLM_MODEL 环境变量或直接传入 model")
return True
@dataclass
class AgentConfig:
"""Agent 配置"""
# Agent名称
name: str = "xiaohei-agent"
# Agent描述
description: str = "一个基于LiteLLM的智能Agent,支持A2A协议"
# Agent版本
version: str = "1.0.0"
# 服务端口
port: int = 8080
# 服务主机
host: str = "0.0.0.0"
# 是否启用流式响应
enable_streaming: bool = True
# 系统提示词
system_prompt: str = """你是小黑Agent,一个智能助手。
你可以帮助用户完成各种任务,包括:
- 回答问题
- 代码编写和解释
- 文档分析
- 任务规划
请用中文回答用户的问题,保持友好和专业。"""
@dataclass
class A2AConfig:
"""A2A协议配置"""
# A2A协议版本
protocol_version: str = "1.0"
# Agent Card配置
agent_card: dict = field(default_factory=lambda: {
"name": "xiaohei-agent",
"description": "基于LiteLLM的智能Agent,支持A2A协议通信",
"version": "1.0.0",
"capabilities": {
"text": True,
"streaming": True,
"push_notifications": False
},
"skills": [
{
"id": "general-assistant",
"name": "通用助手",
"description": "回答问题、提供建议、协助任务"
},
{
"id": "code-helper",
"name": "代码助手",
"description": "编写、解释和调试代码"
}
]
})
def get_config(
api_key: Optional[str] = None,
model: Optional[str] = None
) -> tuple[LiteLLMConfig, AgentConfig, A2AConfig]:
"""
获取完整配置
Args:
api_key: LiteLLM API密钥(可选,优先使用,否则从环境变量获取)
model: 模型名称(可选,优先使用,否则从环境变量获取)
Returns:
(LiteLLMConfig, AgentConfig, A2AConfig) 配置元组
"""
litellm_config = LiteLLMConfig(api_key=api_key, model=model)
agent_config = AgentConfig()
a2a_config = A2AConfig()
return litellm_config, agent_config, a2a_config
@@ -0,0 +1,43 @@
"""
A2A LiteLLM Agent 主入口
支持从环境变量或请求传入 API key
"""
import os
import uvicorn
from a2a_server import create_app
# 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0")
SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))
POD_NAME = os.getenv("POD_NAME", "a2a-litellm-agent")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "a2a_litellm_agent")
# 从环境变量获取默认配置(可选)
default_api_key = os.getenv("LITELLM_API_KEY")
default_model = os.getenv("LITELLM_MODEL")
# 创建应用
app = create_app(api_key=default_api_key, model=default_model)
def main():
"""主函数"""
print(f"🚀 启动 A2A LiteLLM Agent")
print(f" - Pod名称: {POD_NAME}")
print(f" - 模板类型: {TEMPLATE_TYPE}")
print(f" - 服务地址: http://{SERVICE_HOST}:{SERVICE_PORT}")
if default_api_key:
print(f" - 已配置默认 API key(可通过请求覆盖)")
else:
print(f" - 未配置默认 API key,需在请求中传入")
uvicorn.run(
app,
host=SERVICE_HOST,
port=SERVICE_PORT,
log_level="info"
)
if __name__ == "__main__":
main()
@@ -0,0 +1,7 @@
# LiteLLM Agent API服务依赖
httpx>=0.27.0
fastapi>=0.115.0
uvicorn[standard]>=0.32.0
pydantic>=2.0.0
python-dotenv>=1.0.0
structlog>=24.0.0
@@ -18,8 +18,10 @@ RUN pip install --no-cache-dir \
azure-storage-blob==12.19.0 \
azure-identity==1.15.0
# 复制agent代码
COPY azure_blob_agent.py .
# 复制agent代码和共享工具
COPY agents/azure_blob_agent/azure_blob_agent.py .
COPY common/agent_callback_utils.py /app/common/
COPY common/api_key_utils.py /app/common/
# 设置环境变量
ENV PYTHONUNBUFFERED=1
@@ -4,6 +4,7 @@ Azure Blob Storage AI Agent - 使用LangChain + LiteLLM实现
"""
import os
import logging
import time
from typing import Optional, Dict, Any, List
from datetime import datetime
from fastapi import FastAPI, HTTPException
@@ -13,6 +14,7 @@ from langchain.agents import Tool, AgentExecutor, create_react_agent
from langchain.prompts import PromptTemplate
from langchain_community.chat_models import ChatLiteLLM
import uvicorn
from agent_callback_utils import AgentCallbackHandler, CallbackContextManager
# 配置日志
logging.basicConfig(
@@ -27,17 +29,17 @@ SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))
POD_NAME = os.getenv("POD_NAME", "azure-blob-agent")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent")
# LiteLLM配置
# LiteLLM配置(从环境变量获取)
LITELLM_API_BASE = os.getenv("LITELLM_API_BASE", "http://localhost:4000")
LITELLM_MODEL = os.getenv("LITELLM_MODEL", "gpt-3.5-turbo")
LITELLM_API_KEY = os.getenv("LITELLM_API_KEY", "sk-1234")
# Azure Storage 连接字符串(可选,也可通过API动态传入)
# Azure Storage 连接字符串(从环境变量获取)
AZURE_STORAGE_CONNECTION_STRING = os.getenv("AZURE_STORAGE_CONNECTION_STRING", "")
# 全局存储客户端
# 全局变量
blob_service_client: Optional[BlobServiceClient] = None
connection_string: Optional[str] = None
callback_handler: Optional[AgentCallbackHandler] = None
# FastAPI应用
app = FastAPI(
@@ -57,6 +59,8 @@ class ConnectRequest(BaseModel):
class QueryRequest(BaseModel):
"""查询请求"""
query: str = Field(..., description="自然语言查询或操作指令")
litellm_api_key: str = Field(..., description="LiteLLM API密钥")
user_id: Optional[str] = Field(None, description="用户ID(用于计费回调)")
container_name: Optional[str] = Field(None, description="指定容器名称")
@@ -244,7 +248,7 @@ def get_storage_stats() -> str:
# ==================== 创建LangChain Agent ====================
def create_blob_agent() -> Optional[AgentExecutor]:
def create_blob_agent(litellm_api_key: str) -> Optional[AgentExecutor]:
"""创建Azure Blob Storage Agent"""
global blob_service_client
@@ -257,7 +261,7 @@ def create_blob_agent() -> Optional[AgentExecutor]:
llm = ChatLiteLLM(
model=LITELLM_MODEL,
api_base=LITELLM_API_BASE,
api_key=LITELLM_API_KEY,
api_key=litellm_api_key,
temperature=0
)
logger.info(f"✅ LiteLLM初始化成功: {LITELLM_MODEL} @ {LITELLM_API_BASE}")
@@ -415,7 +419,7 @@ async def connect_to_storage(request: ConnectRequest):
@app.post("/query")
async def query_storage(request: QueryRequest):
"""使用自然语言查询存储"""
global blob_service_client
global blob_service_client, callback_handler
if not blob_service_client:
raise HTTPException(
@@ -423,9 +427,21 @@ async def query_storage(request: QueryRequest):
detail="未连接到Azure Blob Storage,请先调用 /connect"
)
# 初始化回调处理器
if not callback_handler:
callback_handler = AgentCallbackHandler()
# 使用上下文管理器自动处理回调
with CallbackContextManager(
handler=callback_handler,
user_id=request.user_id,
request_id=f"blob-{int(time.time())}"
) as ctx:
try:
ctx.add_tool("azure_blob_storage")
# 创建Agent
agent = create_blob_agent()
agent = create_blob_agent(request.litellm_api_key)
if not agent:
raise HTTPException(status_code=500, detail="Agent创建失败")
@@ -493,15 +509,21 @@ def init_storage_connection():
def main():
"""启动服务"""
global callback_handler
logger.info(f"🚀 启动 Azure Blob Storage AI Agent")
logger.info(f" - Pod名称: {POD_NAME}")
logger.info(f" - 模板类型: {TEMPLATE_TYPE}")
logger.info(f" - LiteLLM: {LITELLM_MODEL} @ {LITELLM_API_BASE}")
logger.info(f" - 服务地址: http://{SERVICE_HOST}:{SERVICE_PORT}")
logger.info(f" ℹ️ API key 将从请求中获取")
# 初始化存储连接
init_storage_connection()
# 初始化回调处理器
callback_handler = AgentCallbackHandler()
uvicorn.run(
app,
host=SERVICE_HOST,
@@ -9,13 +9,14 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# 复制requirements文件
COPY requirements_a2a.txt /app/
COPY common/requirements_a2a.txt /app/
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements_a2a.txt
# 复制应用代码
COPY azure_blob_agent_a2a.py /app/
# 复制应用代码和共享工具
COPY agents/azure_blob_agent_a2a/azure_blob_agent_a2a.py /app/
COPY common/api_key_utils.py /app/common/
# 暴露端口
EXPOSE 8080
@@ -12,6 +12,7 @@ from fastapi import FastAPI, HTTPException, Header
from pydantic import BaseModel, Field
from azure.storage.blob import BlobServiceClient, ContainerClient
import uvicorn
from api_key_utils import get_api_key
# 配置日志
logging.basicConfig(
@@ -84,6 +85,7 @@ class A2AMessage(BaseModel):
parameters: Dict[str, Any] = Field(default_factory=dict, description="参数")
context: Optional[Dict] = Field(default_factory=dict, description="上下文")
timestamp: Optional[str] = None
model_api_key: Optional[str] = Field(None, description="模型 API 密钥(可选,优先使用,否则从环境变量获取)")
class A2AQueryRequest(BaseModel):
@@ -92,6 +94,7 @@ class A2AQueryRequest(BaseModel):
container_name: Optional[str] = None
requester_agent: Optional[str] = Field(None, description="请求者 Agent ID")
context: Optional[Dict] = Field(default_factory=dict)
model_api_key: Optional[str] = Field(None, description="模型 API 密钥(可选,优先使用,否则从环境变量获取)")
class A2ARegisterRequest(BaseModel):
@@ -432,6 +435,9 @@ async def handle_a2a_message(message: A2AMessage):
detail="未连接到 Azure Blob Storage,请先调用 /connect"
)
# 获取 API key(优先使用请求传入的,否则从环境变量获取)
api_key = get_api_key(message.model_api_key, "MODEL_API_KEY", MODEL_API_KEY)
# 验证消息目标
if message.to_agent != AGENT_ID:
raise HTTPException(
@@ -462,7 +468,8 @@ async def handle_a2a_message(message: A2AMessage):
"message_type": "response",
"action": action,
"result": result,
"timestamp": str(datetime.now())
"timestamp": str(datetime.now()),
"api_key_used": "request" if message.model_api_key else ("env" if MODEL_API_KEY else "none")
}
except Exception as e:
logger.error(f"处理 A2A 消息失败: {str(e)}")
@@ -488,6 +495,9 @@ async def query_storage(request: A2AQueryRequest):
detail="未连接到 Azure Blob Storage,请先调用 /connect"
)
# 获取 API key(优先使用请求传入的,否则从环境变量获取)
api_key = get_api_key(request.model_api_key, "MODEL_API_KEY", MODEL_API_KEY)
try:
query = request.query.lower()
result = None
@@ -512,7 +522,8 @@ async def query_storage(request: A2AQueryRequest):
"result": result,
"agent_id": AGENT_ID,
"requester": request.requester_agent,
"framework": AGENT_FRAMEWORK
"framework": AGENT_FRAMEWORK,
"api_key_used": "request" if request.model_api_key else ("env" if MODEL_API_KEY else "none")
}
except Exception as e:
logger.error(f"查询执行失败: {str(e)}")
@@ -9,13 +9,13 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# 复制requirements文件
COPY requirements_mcp.txt /app/
COPY common/requirements_mcp.txt /app/
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements_mcp.txt
# 复制应用代码
COPY azure_blob_agent_mcp.py /app/
COPY agents/azure_blob_agent_mcp/azure_blob_agent_mcp.py /app/
# 暴露端口
EXPOSE 8080
@@ -0,0 +1,38 @@
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# 复制requirements文件
COPY search_agent/requirements.txt /app/search_agent_requirements.txt
# 安装Python依赖
RUN pip install --no-cache-dir \
fastapi==0.109.0 \
uvicorn[standard]==0.27.0 \
pydantic==2.5.3 \
&& pip install --no-cache-dir -r /app/search_agent_requirements.txt
# 复制search_agent目录
COPY search_agent/ /app/search_agent/
# 复制主agent文件和回调工具
COPY search_agent_main.py /app/
COPY agent_callback_utils.py /app/
# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV SERVICE_HOST=0.0.0.0
ENV SERVICE_PORT=8080
ENV PYTHONPATH=/app
# 健康检查 - 使用Python避免僵尸进程
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health').read()" || exit 1
# 运行agent (直接使用Python,避免shell)
CMD ["python3", "-u", "search_agent_main.py"]
@@ -0,0 +1,312 @@
"""
智能搜索 AI Agent - FastAPI版本
通过HTTP API接收搜索请求,提供智能搜索功能
"""
import os
import sys
import logging
from typing import Optional, Dict, Any, List
from datetime import datetime
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
import uvicorn
import asyncio
# 添加search_agent目录到Python路径
search_agent_dir = os.path.join(os.path.dirname(__file__), 'search_agent')
if search_agent_dir not in sys.path:
sys.path.insert(0, search_agent_dir)
# 直接导入,避免与文件名冲突
from config import Config
from agent.search_agent import SearchAgent
from agent_callback_utils import AgentCallbackHandler, CallbackContextManager
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0")
SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))
POD_NAME = os.getenv("POD_NAME", "search-agent")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "search_agent")
# 全局搜索Agent和回调处理器
search_agent: Optional[SearchAgent] = None
config: Optional[Config] = None
callback_handler: Optional[AgentCallbackHandler] = None
# FastAPI应用
app = FastAPI(
title="Intelligent Search AI Agent",
description="智能搜索代理",
version="1.0.0"
)
# ==================== 请求/响应模型 ====================
class ConfigRequest(BaseModel):
"""配置请求(其他配置从环境变量获取)"""
llm_base_url: str = Field(..., description="LLM API基础URL")
llm_model: str = Field(default="xchat52", description="LLM模型名称")
serper_api_key: str = Field(..., description="Serper API密钥")
jina_api_key: str = Field(..., description="Jina API密钥")
max_iterations: int = Field(default=3, description="最大迭代次数")
max_results_per_query: int = Field(default=10, description="每次搜索最大结果数")
content_max_length: int = Field(default=5000, description="内容最大长度")
log_level: str = Field(default="INFO", description="日志级别")
timeout: int = Field(default=30, description="超时时间(秒)")
class SearchRequest(BaseModel):
"""搜索请求"""
query: str = Field(..., description="搜索查询")
llm_api_key: str = Field(..., description="LLM API密钥")
user_id: Optional[str] = Field(None, description="用户ID(用于计费回调)")
auto_configure: bool = Field(default=False, description="是否自动从环境变量配置")
class Source(BaseModel):
"""搜索来源"""
index: int
title: str
url: str
class SearchResponse(BaseModel):
"""搜索响应"""
query: str
answer: str
sources: List[Source]
confidence: str
iterations: int
total_sources: int
search_queries: List[str]
timestamp: str
class StatusResponse(BaseModel):
"""状态响应"""
status: str
pod_name: str
template_type: str
configured: bool
timestamp: str
class ErrorResponse(BaseModel):
"""错误响应"""
error: str
detail: Optional[str] = None
# ==================== Agent操作函数 ====================
def initialize_agent_from_env():
"""从环境变量初始化Agent"""
global search_agent, config
try:
config = Config.from_env()
config.validate()
search_agent = SearchAgent(config)
logger.info("Search Agent从环境变量初始化成功")
return True
except Exception as e:
logger.error(f"从环境变量初始化Agent失败: {str(e)}")
return False
def initialize_agent_from_config(config_data: Dict[str, Any]):
"""从配置数据初始化Agent"""
global search_agent, config
try:
# 创建配置对象
config = Config(
llm_base_url=config_data.get("llm_base_url", ""),
llm_api_key=config_data.get("llm_api_key", ""),
llm_model=config_data.get("llm_model", "xchat52"),
serper_api_key=config_data.get("serper_api_key", ""),
jina_api_key=config_data.get("jina_api_key", ""),
max_iterations=config_data.get("max_iterations", 3),
max_results_per_query=config_data.get("max_results_per_query", 10),
content_max_length=config_data.get("content_max_length", 5000),
log_level=config_data.get("log_level", "INFO"),
timeout=config_data.get("timeout", 30)
)
config.validate()
search_agent = SearchAgent(config)
logger.info("Search Agent从配置初始化成功")
return True
except Exception as e:
logger.error(f"从配置初始化Agent失败: {str(e)}")
raise
# ==================== API端点 ====================
@app.get("/health")
async def health_check():
"""健康检查"""
return {
"status": "healthy",
"pod_name": POD_NAME,
"template_type": TEMPLATE_TYPE,
"configured": search_agent is not None,
"timestamp": datetime.utcnow().isoformat()
}
@app.get("/status", response_model=StatusResponse)
async def get_status():
"""获取状态"""
return StatusResponse(
status="running" if search_agent else "not_configured",
pod_name=POD_NAME,
template_type=TEMPLATE_TYPE,
configured=search_agent is not None,
timestamp=datetime.utcnow().isoformat()
)
@app.post("/configure")
async def configure_agent(config_req: ConfigRequest):
"""配置Agent"""
try:
initialize_agent_from_config(config_req.dict())
return {
"status": "success",
"message": "Agent配置成功",
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"配置Agent失败: {str(e)}")
raise HTTPException(status_code=400, detail=f"配置失败: {str(e)}")
@app.post("/search", response_model=SearchResponse)
async def search(request: SearchRequest):
"""执行搜索"""
global search_agent, callback_handler, config
# 如果未配置且需要自动配置
if not search_agent and request.auto_configure:
if not initialize_agent_from_env():
raise HTTPException(
status_code=400,
detail="Agent未配置且自动配置失败,请先调用/configure接口"
)
if not search_agent:
raise HTTPException(
status_code=400,
detail="Agent未配置,请先调用/configure接口"
)
# 初始化回调处理器(如果尚未初始化)
if not callback_handler:
callback_handler = AgentCallbackHandler()
# 使用上下文管理器自动处理回调
try:
with CallbackContextManager(
handler=callback_handler,
user_id=request.user_id,
request_id=f"search-{int(datetime.utcnow().timestamp())}"
) as ctx:
# 临时更新API key
original_api_key = config.llm_api_key if config else None
if config:
config.llm_api_key = request.llm_api_key
search_agent.config.llm_api_key = request.llm_api_key
try:
# 执行搜索
ctx.add_tool("web_search")
ctx.add_tool("content_reader")
result = await search_agent.search(request.query)
# 转换响应
sources = [
Source(
index=s.index,
title=s.title,
url=s.url
)
for s in result.answer.sources
]
return SearchResponse(
query=request.query,
answer=result.answer.content,
sources=sources,
confidence=result.answer.confidence,
iterations=result.iterations,
total_sources=result.total_sources_consulted,
search_queries=result.search_queries_used,
timestamp=datetime.utcnow().isoformat()
)
finally:
# 恢复原始API key
if config and original_api_key:
config.llm_api_key = original_api_key
search_agent.config.llm_api_key = original_api_key
except Exception as e:
logger.error(f"搜索失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"搜索失败: {str(e)}")
@app.post("/chat")
async def chat(request: SearchRequest):
"""聊天接口(别名)"""
return await search(request)
@app.get("/")
async def root():
"""根路径"""
return {
"name": "Intelligent Search AI Agent",
"version": "1.0.0",
"endpoints": {
"health": "/health",
"status": "/status",
"configure": "/configure",
"search": "/search",
"chat": "/chat"
}
}
# ==================== 启动函数 ====================
def main():
"""主函数"""
logger.info(f"启动 Search Agent - {POD_NAME}")
logger.info(f"Template Type: {TEMPLATE_TYPE}")
# 尝试从环境变量初始化
if os.getenv("LLM_API_KEY"):
logger.info("检测到环境变量配置,尝试自动初始化...")
initialize_agent_from_env()
else:
logger.info("未检测到环境变量配置,等待通过API配置...")
# 启动服务
uvicorn.run(
app,
host=SERVICE_HOST,
port=SERVICE_PORT,
log_level="info"
)
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,253 @@
# 🔍 智能AI搜索Agent
一个基于大语言模型的智能搜索代理,能够理解用户查询意图、自动规划搜索策略、从多个来源获取信息,并生成高质量、有来源引用的答案。
## ✨ 功能特点
| 能力 | 描述 |
|------|------|
| 🧠 查询理解 | 分析用户意图,提取关键实体,生成扩展查询 |
| 📋 搜索规划 | 智能分解问题,制定搜索策略 |
| 🔎 多源搜索 | 支持Web搜索和新闻搜索 |
| 📄 内容提取 | 智能提取网页核心内容 |
| 🎯 结果排序 | 基于相关性重排搜索结果 |
| ✍️ 答案生成 | 综合信息生成结构化回答 |
| 🔄 自我反思 | 评估答案质量,决定是否迭代 |
## 🛠️ 技术栈
| 组件 | 选型 | 说明 |
|------|------|------|
| LLM | xchat52 (GPT-5.2) | 主推理引擎 |
| Web搜索 | Serper API | Google搜索代理 |
| 内容提取 | Jina Reader | 网页转Markdown |
| 重排序 | Jina Reranker | 结果相关性排序 |
| 框架 | Python原生 + asyncio | 异步高效执行 |
## 📁 项目结构
```
search_agent/
├── main.py # 程序入口
├── config.py # 配置管理
├── requirements.txt # Python依赖
├── .env # 环境变量配置
│
├── agent/
│ ├── __init__.py
│ ├── search_agent.py # 主Agent类
│ └── prompts.py # Prompt模板
│
├── modules/
│ ├── __init__.py
│ ├── query_analyzer.py # 查询理解模块
│ ├── search_planner.py # 搜索规划模块
│ ├── search_executor.py # 搜索执行模块
│ ├── content_extractor.py # 内容提取模块
│ ├── result_processor.py # 结果处理模块
│ ├── answer_generator.py # 答案生成模块
│ └── reflector.py # 反思迭代模块
│
├── tools/
│ ├── __init__.py
│ ├── serper.py # Serper API封装
│ ├── jina_reader.py # Jina Reader封装
│ └── jina_reranker.py # Jina Reranker封装
│
├── models/
│ ├── __init__.py
│ └── schemas.py # 数据模型定义
│
└── utils/
├── __init__.py
├── llm_client.py # LLM客户端
└── helpers.py # 工具函数
```
## 🚀 快速开始
### 1. 安装依赖
```bash
cd search_agent
pip install -r requirements.txt
```
### 2. 配置环境变量
创建 `.env` 文件:
```bash
# LLM配置 (xchat52)
LLM_BASE_URL=https://apis.openroutex.com/openai/deployments/xchat52
LLM_API_KEY=你的API密钥
LLM_MODEL=xchat52
# Serper配置 (Google搜索)
SERPER_API_KEY=你的Serper_API_KEY
# Jina配置 (内容提取和重排序)
JINA_API_KEY=你的Jina_API_KEY
# Agent配置
MAX_ITERATIONS=3 # 最大迭代次数
MAX_RESULTS_PER_QUERY=10 # 每次搜索返回结果数
CONTENT_MAX_LENGTH=5000 # 提取内容最大长度
# 日志配置
LOG_LEVEL=INFO
TIMEOUT=30
```
### 3. 运行程序
**交互模式**(推荐):
```bash
python main.py
```
**单次查询**:
```bash
python main.py "你的问题"
```
## 📖 使用示例
```
🔍 智能AI搜索Agent
======================================================================
输入您的问题进行搜索,输入 'quit' 或 'exit' 退出
======================================================================
🔎 请输入问题: 什么是大语言模型?
======================================================================
📝 答案:
======================================================================
## 大语言模型(LLM)是什么?
**大语言模型(Large Language Model, LLM)**是一类用**海量文本数据**进行
**预训练**的**超大规模深度学习模型**...
----------------------------------------------------------------------
📚 来源:
----------------------------------------------------------------------
[1] 大语言模型 (LLM)
🔗 https://www.ibm.com/cn-zh/think/topics/large-language-models
[2] 什么是 LLM(大型语言模型)?
🔗 https://aws.amazon.com/cn/what-is/large-language-model/
...
----------------------------------------------------------------------
📊 统计:
----------------------------------------------------------------------
• 置信度: high
• 迭代次数: 1
• 参考来源数: 10
• 搜索查询数: 3
======================================================================
```
## 🔄 工作流程
```
用户查询
│
▼
┌───────────────────┐
│ 查询理解 │ ──▶ 分析意图、提取实体、生成扩展查询
└───────────────────┘
│
▼
┌───────────────────┐
│ 搜索规划 │ ──▶ 制定搜索策略(Web/新闻、并行/串行)
└───────────────────┘
│
▼
┌───────────────────┐
│ 搜索执行 │ ──▶ 调用Serper API执行搜索
└───────────────────┘
│
▼
┌───────────────────┐
│ 内容提取 │ ──▶ 使用Jina Reader提取网页内容
└───────────────────┘
│
▼
┌───────────────────┐
│ 结果处理 │ ──▶ 去重 + Jina Reranker重排序
└───────────────────┘
│
▼
┌───────────────────┐
│ 答案生成 │ ──▶ LLM综合生成结构化答案
└───────────────────┘
│
▼
┌───────────────────┐
│ 反思评估 │ ──▶ 评估完整性,决定是否继续迭代
└───────────────────┘
│
├──(完整)──▶ 返回最终答案
│
└──(不完整)──▶ 补充搜索(回到搜索规划)
```
## ⚙️ 配置说明
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `MAX_ITERATIONS` | 3 | 最大迭代次数,防止无限循环 |
| `MAX_RESULTS_PER_QUERY` | 10 | 每次搜索返回的结果数量 |
| `CONTENT_MAX_LENGTH` | 5000 | 提取内容的最大字符数 |
| `LOG_LEVEL` | INFO | 日志级别 (DEBUG/INFO/WARNING/ERROR) |
| `TIMEOUT` | 30 | API请求超时时间(秒)|
## 🔧 API说明
### Serper API
- **Web搜索**: `POST https://google.serper.dev/search`
- **新闻搜索**: `POST https://google.serper.dev/news`
- [获取API Key](https://serper.dev/)
### Jina API
- **内容提取**: `GET https://r.jina.ai/{URL}`
- **重排序**: `POST https://api.jina.ai/v1/rerank`
- [获取API Key](https://jina.ai/)
### LLM API (Azure OpenAI风格)
- **Chat**: `POST {BASE_URL}/chat/completions?api-version=2024-10-21`
## 📝 编程接口
```python
import asyncio
from config import Config
from agent.search_agent import SearchAgent
async def main():
# 加载配置
config = Config.from_env()
# 创建Agent
agent = SearchAgent(config)
# 执行搜索
response = await agent.search("你的问题")
# 获取答案
print(response.answer.content)
print(response.answer.sources)
print(response.answer.confidence)
asyncio.run(main())
```
## 📄 License
MIT License
## 🤝 贡献
欢迎提交Issue和Pull Request!
@@ -0,0 +1,18 @@
"""
Agent模块
"""
from .search_agent import SearchAgent
from .prompts import (
QUERY_ANALYSIS_PROMPT,
ANSWER_GENERATION_PROMPT,
REFLECTION_PROMPT,
)
__all__ = [
"SearchAgent",
"QUERY_ANALYSIS_PROMPT",
"ANSWER_GENERATION_PROMPT",
"REFLECTION_PROMPT",
]
@@ -0,0 +1,126 @@
"""
Prompt模板汇总
集中管理所有LLM Prompt模板
"""
# ==================== 查询分析 Prompt ====================
QUERY_ANALYSIS_PROMPT = """你是一个查询分析专家。分析用户的搜索查询,提取以下信息。
请输出JSON格式:
{
"intent": "查询意图,必须是以下之一: fact_check(事实核查), comparison(对比分析), how_to(操作指南), news(新闻资讯), research(深度研究)",
"entities": ["关键实体列表,提取查询中的核心概念、人名、产品名等"],
"expanded_queries": ["扩展查询1", "扩展查询2", "扩展查询3"],
"need_news": true或false,
"time_filter": "时间过滤器,null表示不限时间,qdr:d(过去24小时), qdr:w(过去一周), qdr:m(过去一月), qdr:y(过去一年)"
}
扩展查询要求:
1. 生成2-4个扩展查询,包含不同角度或同义表达
2. 至少包含一个英文查询(如果原查询是中文)
3. 保持查询的核心意图
时间过滤器选择规则:
- 查询涉及"最新"、"近期"、"今年"等时效性词语 → 设置相应的时间过滤器
- 查询涉及具体年份(如"2024年") → qdr:y
- 一般性查询 → null"""
# ==================== 搜索规划 Prompt ====================
SEARCH_PLANNING_PROMPT = """你是一个搜索规划专家。根据查询分析结果,制定搜索计划。
输入信息:
- 原始查询
- 查询意图
- 关键实体
- 是否需要新闻
输出搜索任务列表,每个任务包含:
- query: 搜索词
- source: web 或 news
- time_filter: 时间过滤器(可选)
搜索策略规则:
1. 简单事实查询 → 单次Web搜索
2. 时效性查询 → Web搜索 + 新闻搜索
3. 复杂分析查询 → 多个扩展查询
4. 对比类查询 → 分别搜索各对比对象"""
# ==================== 答案生成 Prompt ====================
ANSWER_GENERATION_PROMPT = """你是一个专业的信息整合专家。根据以下搜索结果,回答用户的问题。
## 要求
1. 综合多个来源的信息,给出全面准确的回答
2. 使用清晰的结构组织答案(标题、列表、重点标注等)
3. 在答案中标注信息来源,格式:[来源1]、[来源2]
4. 如果信息有冲突,说明不同观点
5. 如果信息不足以完整回答问题,明确指出缺失的部分
6. 回答使用中文
## 输出JSON格式
{
"answer": "结构化的答案(Markdown格式,包含来源引用)",
"sources": [
{"index": 1, "title": "来源标题", "url": "来源URL"},
{"index": 2, "title": "来源标题", "url": "来源URL"}
],
"confidence": "high/medium/low,基于信息质量和一致性判断"
}"""
# ==================== 反思评估 Prompt ====================
REFLECTION_PROMPT = """你是一个质量评估专家。评估以下答案是否充分回答了用户的问题。
## 评估维度
1. **完整性**: 答案是否覆盖了问题的所有方面?
2. **准确性**: 答案内容是否有明确的来源支持?
3. **深度**: 答案是否提供了足够的细节和解释?
## 输出JSON格式
{
"completeness": 0.0-1.0,
"missing_aspects": ["如果有缺失,列出缺失的方面"],
"needs_more_search": true或false,
"suggested_queries": ["如果需要补充搜索,建议的搜索词"]
}
## 判断标准
- completeness >= 0.8 且没有重要信息缺失 → needs_more_search = false
- completeness < 0.8 或有重要信息缺失 → needs_more_search = true
- 建议的搜索词应该针对缺失的方面"""
# ==================== 工具函数 ====================
def format_query_analysis_prompt(query: str) -> str:
"""格式化查询分析Prompt"""
return f"{QUERY_ANALYSIS_PROMPT}\n\n用户查询: {query}"
def format_answer_generation_prompt(query: str, documents: str) -> str:
"""格式化答案生成Prompt"""
return f"""{ANSWER_GENERATION_PROMPT}
## 用户问题
{query}
## 搜索结果
{documents}"""
def format_reflection_prompt(query: str, answer: str, sources_count: int, confidence: str) -> str:
"""格式化反思评估Prompt"""
return f"""{REFLECTION_PROMPT}
## 用户问题
{query}
## 生成的答案
{answer}
## 答案的来源数量
{sources_count} 个来源
## 答案的置信度
{confidence}"""
@@ -0,0 +1,209 @@
"""
搜索Agent主类
协调各模块执行智能搜索
"""
from typing import List, Optional
from loguru import logger
from config import Config
from models.schemas import (
QueryAnalysis,
SearchPlan,
SearchResult,
Document,
RankedDocument,
Answer,
AgentResponse,
)
from modules.query_analyzer import QueryAnalyzer
from modules.search_planner import SearchPlanner
from modules.search_executor import SearchExecutor
from modules.content_extractor import ContentExtractor
from modules.result_processor import ResultProcessor
from modules.answer_generator import AnswerGenerator
from modules.reflector import Reflector
class SearchAgent:
"""智能搜索Agent"""
def __init__(self, config: Config):
"""
初始化搜索Agent
Args:
config: 配置对象
"""
self.config = config
# 初始化各模块
self.query_analyzer = QueryAnalyzer(config)
self.search_planner = SearchPlanner(config)
self.search_executor = SearchExecutor(config)
self.content_extractor = ContentExtractor(config)
self.result_processor = ResultProcessor(config)
self.answer_generator = AnswerGenerator(config)
self.reflector = Reflector(config)
logger.info("SearchAgent 初始化完成")
async def search(self, query: str) -> AgentResponse:
"""
执行智能搜索
Args:
query: 用户查询
Returns:
AgentResponse对象
"""
logger.info(f"="*60)
logger.info(f"开始搜索: {query}")
logger.info(f"="*60)
iteration = 0
all_documents: List[Document] = []
all_queries: List[str] = []
# 1. 查询理解
analysis = await self.query_analyzer.analyze(query)
logger.info(f"查询分析完成: intent={analysis.intent.value}")
answer: Optional[Answer] = None
while iteration < self.config.max_iterations:
iteration += 1
logger.info(f"\n--- 迭代 {iteration}/{self.config.max_iterations} ---")
# 2. 搜索规划
if iteration == 1:
plan = await self.search_planner.plan(analysis)
else:
# 后续迭代使用建议的补充查询
plan = self.search_planner.plan_supplementary(
query,
analysis.expanded_queries
)
all_queries.extend([t.query for t in plan.tasks])
logger.info(f"搜索计划: {len(plan.tasks)} 个任务")
# 3. 执行搜索
search_results = await self.search_executor.execute(plan)
logger.info(f"搜索结果: {len(search_results)} 条")
if not search_results:
logger.warning("没有搜索结果")
if answer is None:
answer = self.answer_generator._empty_answer()
break
# 4. 内容提取
documents = await self.content_extractor.extract_batch(
search_results,
max_urls=10
)
all_documents.extend(documents)
logger.info(f"提取文档: {len(documents)} 个")
if not documents:
logger.warning("没有成功提取到文档内容")
continue
# 5. 结果处理(去重+重排序)
ranked_docs = await self.result_processor.process(
query=query,
documents=all_documents,
top_k=5
)
logger.info(f"排序结果: {len(ranked_docs)} 个")
if not ranked_docs:
logger.warning("没有有效的排序结果")
continue
# 6. 生成答案
answer = await self.answer_generator.generate(
query=query,
documents=ranked_docs
)
logger.info(f"答案生成完成: confidence={answer.confidence}")
# 7. 反思评估
assessment = await self.reflector.assess(query, answer)
# 8. 判断是否继续迭代
if not self.reflector.should_continue(assessment, iteration):
break
# 更新分析,准备下一轮搜索
if assessment.suggested_queries:
analysis.expanded_queries = assessment.suggested_queries
logger.info(f"补充搜索: {assessment.suggested_queries}")
# 确保有答案返回
if answer is None:
answer = self.answer_generator._empty_answer()
# 去重统计
unique_urls = set(d.url for d in all_documents)
response = AgentResponse(
answer=answer,
iterations=iteration,
total_sources_consulted=len(unique_urls),
search_queries_used=list(set(all_queries))
)
logger.info(f"\n{'='*60}")
logger.info(f"搜索完成!")
logger.info(f"迭代次数: {iteration}")
logger.info(f"参考来源: {len(unique_urls)}")
logger.info(f"搜索查询: {len(response.search_queries_used)}")
logger.info(f"{'='*60}\n")
return response
async def quick_search(self, query: str) -> Answer:
"""
快速搜索(单次迭代)
Args:
query: 用户查询
Returns:
Answer对象
"""
# 简化分析
analysis = await self.query_analyzer.analyze(query)
# 只执行一次搜索
plan = await self.search_planner.plan(analysis)
plan.tasks = plan.tasks[:2] # 限制搜索任务数量
# 执行搜索
search_results = await self.search_executor.execute(plan)
if not search_results:
return self.answer_generator._empty_answer()
# 提取内容
documents = await self.content_extractor.extract_batch(
search_results,
max_urls=5
)
if not documents:
return self.answer_generator._empty_answer()
# 处理结果
ranked_docs = await self.result_processor.process(
query=query,
documents=documents,
top_k=3
)
# 生成答案
return await self.answer_generator.generate(query, ranked_docs)
@@ -0,0 +1,81 @@
"""
配置管理模块
负责加载和管理所有配置项
"""
import os
from dataclasses import dataclass
from typing import Optional
from dotenv import load_dotenv
@dataclass
class Config:
"""Agent配置类"""
# LLM配置
llm_base_url: str
llm_api_key: str
llm_model: str
# Serper配置
serper_api_key: str
# Jina配置
jina_api_key: str
# Agent配置
max_iterations: int
max_results_per_query: int
content_max_length: int
# 可选配置
log_level: str = "INFO"
timeout: int = 30
@classmethod
def from_env(cls, env_path: Optional[str] = None) -> "Config":
"""从环境变量加载配置"""
if env_path:
load_dotenv(env_path)
else:
load_dotenv()
return cls(
# LLM配置
llm_base_url=os.getenv("LLM_BASE_URL", ""),
llm_api_key=os.getenv("LLM_API_KEY", ""),
llm_model=os.getenv("LLM_MODEL", "xchat52"),
# Serper配置
serper_api_key=os.getenv("SERPER_API_KEY", ""),
# Jina配置
jina_api_key=os.getenv("JINA_API_KEY", ""),
# Agent配置
max_iterations=int(os.getenv("MAX_ITERATIONS", "3")),
max_results_per_query=int(os.getenv("MAX_RESULTS_PER_QUERY", "10")),
content_max_length=int(os.getenv("CONTENT_MAX_LENGTH", "5000")),
# 可选配置
log_level=os.getenv("LOG_LEVEL", "INFO"),
timeout=int(os.getenv("TIMEOUT", "30"))
)
def validate(self) -> bool:
"""验证配置是否完整"""
required_fields = [
("llm_base_url", self.llm_base_url),
("llm_api_key", self.llm_api_key),
("serper_api_key", self.serper_api_key),
("jina_api_key", self.jina_api_key),
]
missing = [name for name, value in required_fields if not value]
if missing:
raise ValueError(f"缺少必要的配置项: {', '.join(missing)}")
return True
@@ -0,0 +1,125 @@
"""
智能AI搜索Agent - 程序入口
"""
import asyncio
import sys
from loguru import logger
from config import Config
from agent.search_agent import SearchAgent
def setup_logging(level: str = "INFO"):
"""配置日志"""
logger.remove()
logger.add(
sys.stderr,
level=level,
format="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
def print_response(response):
"""格式化输出响应"""
print("\n" + "=" * 70)
print("📝 答案:")
print("=" * 70)
print(response.answer.content)
print("\n" + "-" * 70)
print("📚 来源:")
print("-" * 70)
for source in response.answer.sources:
print(f" [{source.index}] {source.title}")
print(f" 🔗 {source.url}")
print("\n" + "-" * 70)
print("📊 统计:")
print("-" * 70)
print(f" • 置信度: {response.answer.confidence}")
print(f" • 迭代次数: {response.iterations}")
print(f" • 参考来源数: {response.total_sources_consulted}")
print(f" • 搜索查询数: {len(response.search_queries_used)}")
print("=" * 70 + "\n")
async def main():
"""主函数"""
# 加载配置
config = Config.from_env()
# 配置日志
setup_logging(config.log_level)
# 验证配置
try:
config.validate()
except ValueError as e:
logger.error(f"配置错误: {e}")
logger.info("请检查 .env 文件中的配置项")
return
# 创建Agent
agent = SearchAgent(config)
# 交互式搜索
print("\n" + "=" * 70)
print("🔍 智能AI搜索Agent")
print("=" * 70)
print("输入您的问题进行搜索,输入 'quit' 或 'exit' 退出")
print("=" * 70 + "\n")
while True:
try:
query = input("🔎 请输入问题: ").strip()
if not query:
continue
if query.lower() in ['quit', 'exit', 'q']:
print("\n👋 再见!")
break
# 执行搜索
response = await agent.search(query)
# 输出结果
print_response(response)
except KeyboardInterrupt:
print("\n\n👋 再见!")
break
except Exception as e:
logger.error(f"搜索出错: {e}")
continue
async def search_once(query: str):
"""
单次搜索(用于脚本调用)
Args:
query: 搜索查询
"""
config = Config.from_env()
setup_logging(config.log_level)
config.validate()
agent = SearchAgent(config)
response = await agent.search(query)
print_response(response)
return response
if __name__ == "__main__":
# 检查命令行参数
if len(sys.argv) > 1:
# 命令行传入查询
query = " ".join(sys.argv[1:])
asyncio.run(search_once(query))
else:
# 交互模式
asyncio.run(main())
@@ -0,0 +1,34 @@
"""
数据模型模块
"""
from .schemas import (
SearchSource,
Intent,
QueryAnalysis,
SearchTask,
SearchPlan,
SearchResult,
Document,
RankedDocument,
Source,
Answer,
QualityAssessment,
AgentResponse,
)
__all__ = [
"SearchSource",
"Intent",
"QueryAnalysis",
"SearchTask",
"SearchPlan",
"SearchResult",
"Document",
"RankedDocument",
"Source",
"Answer",
"QualityAssessment",
"AgentResponse",
]
@@ -0,0 +1,202 @@
"""
数据模型定义
定义Agent使用的所有数据结构
"""
from dataclasses import dataclass, field
from typing import List, Optional
from enum import Enum
class SearchSource(Enum):
"""搜索来源枚举"""
WEB = "web"
NEWS = "news"
class Intent(Enum):
"""查询意图枚举"""
FACT_CHECK = "fact_check" # 事实核查
COMPARISON = "comparison" # 对比分析
HOW_TO = "how_to" # 操作指南
NEWS = "news" # 新闻资讯
RESEARCH = "research" # 深度研究
@dataclass
class QueryAnalysis:
"""查询分析结果"""
original_query: str # 原始查询
intent: Intent # 查询意图
entities: List[str] # 关键实体
expanded_queries: List[str] # 扩展查询列表
need_news: bool # 是否需要新闻搜索
time_filter: Optional[str] = None # 时间过滤器
def to_dict(self) -> dict:
"""转换为字典"""
return {
"original_query": self.original_query,
"intent": self.intent.value,
"entities": self.entities,
"expanded_queries": self.expanded_queries,
"need_news": self.need_news,
"time_filter": self.time_filter
}
@dataclass
class SearchTask:
"""搜索任务"""
query: str # 搜索查询
source: SearchSource # 搜索来源
time_filter: Optional[str] = None # 时间过滤器
num_results: int = 10 # 结果数量
def to_dict(self) -> dict:
"""转换为字典"""
return {
"query": self.query,
"source": self.source.value,
"time_filter": self.time_filter,
"num_results": self.num_results
}
@dataclass
class SearchPlan:
"""搜索计划"""
tasks: List[SearchTask] # 搜索任务列表
strategy: str = "parallel" # 执行策略: parallel/sequential
def to_dict(self) -> dict:
"""转换为字典"""
return {
"tasks": [t.to_dict() for t in self.tasks],
"strategy": self.strategy
}
@dataclass
class SearchResult:
"""搜索结果"""
title: str # 标题
url: str # URL
snippet: str # 摘要
source: SearchSource # 来源类型
position: int # 排名位置
date: Optional[str] = None # 日期(新闻)
def to_dict(self) -> dict:
"""转换为字典"""
return {
"title": self.title,
"url": self.url,
"snippet": self.snippet,
"source": self.source.value,
"position": self.position,
"date": self.date
}
@dataclass
class Document:
"""提取的文档内容"""
url: str # URL
title: str # 标题
content: str # 内容
source: SearchSource # 来源类型
def to_dict(self) -> dict:
"""转换为字典"""
return {
"url": self.url,
"title": self.title,
"content": self.content,
"source": self.source.value
}
@dataclass
class RankedDocument:
"""排序后的文档"""
document: Document # 文档
relevance_score: float # 相关性分数
rank: int # 排名
def to_dict(self) -> dict:
"""转换为字典"""
return {
"document": self.document.to_dict(),
"relevance_score": self.relevance_score,
"rank": self.rank
}
@dataclass
class Source:
"""来源引用"""
index: int # 索引
title: str # 标题
url: str # URL
def to_dict(self) -> dict:
"""转换为字典"""
return {
"index": self.index,
"title": self.title,
"url": self.url
}
@dataclass
class Answer:
"""生成的答案"""
content: str # Markdown格式的答案内容
sources: List[Source] # 来源列表
confidence: str # 置信度: high/medium/low
def to_dict(self) -> dict:
"""转换为字典"""
return {
"content": self.content,
"sources": [s.to_dict() for s in self.sources],
"confidence": self.confidence
}
@dataclass
class QualityAssessment:
"""质量评估"""
completeness: float # 完整性 0-1
missing_aspects: List[str] # 缺失的方面
needs_more_search: bool # 是否需要更多搜索
suggested_queries: List[str] # 建议的补充搜索
def to_dict(self) -> dict:
"""转换为字典"""
return {
"completeness": self.completeness,
"missing_aspects": self.missing_aspects,
"needs_more_search": self.needs_more_search,
"suggested_queries": self.suggested_queries
}
@dataclass
class AgentResponse:
"""Agent最终响应"""
answer: Answer # 答案
iterations: int # 迭代次数
total_sources_consulted: int # 参考来源总数
search_queries_used: List[str] # 使用的搜索查询
def to_dict(self) -> dict:
"""转换为字典"""
return {
"answer": self.answer.to_dict(),
"iterations": self.iterations,
"total_sources_consulted": self.total_sources_consulted,
"search_queries_used": self.search_queries_used
}
@@ -0,0 +1,22 @@
"""
核心模块
"""
from .query_analyzer import QueryAnalyzer
from .search_planner import SearchPlanner
from .search_executor import SearchExecutor
from .content_extractor import ContentExtractor
from .result_processor import ResultProcessor
from .answer_generator import AnswerGenerator
from .reflector import Reflector
__all__ = [
"QueryAnalyzer",
"SearchPlanner",
"SearchExecutor",
"ContentExtractor",
"ResultProcessor",
"AnswerGenerator",
"Reflector",
]
@@ -0,0 +1,151 @@
"""
答案生成模块
综合多个来源的信息生成结构化答案
"""
from typing import List
from loguru import logger
from config import Config
from models.schemas import RankedDocument, Answer, Source
from utils.llm_client import LLMClient
from utils.helpers import format_documents_for_prompt
# 答案生成Prompt
ANSWER_GENERATION_PROMPT = """你是一个专业的信息整合专家。根据以下搜索结果,回答用户的问题。
## 要求
1. 综合多个来源的信息,给出全面准确的回答
2. 使用清晰的结构组织答案(标题、列表、重点标注等)
3. 在答案中标注信息来源,格式:[来源1]、[来源2]
4. 如果信息有冲突,说明不同观点
5. 如果信息不足以完整回答问题,明确指出缺失的部分
6. 回答使用中文
## 输出JSON格式
{
"answer": "结构化的答案(Markdown格式,包含来源引用)",
"sources": [
{"index": 1, "title": "来源标题", "url": "来源URL"},
{"index": 2, "title": "来源标题", "url": "来源URL"}
],
"confidence": "high/medium/low,基于信息质量和一致性判断"
}"""
class AnswerGenerator:
"""答案生成模块"""
def __init__(self, config: Config):
"""
初始化答案生成器
Args:
config: 配置对象
"""
self.config = config
self.llm = LLMClient(
base_url=config.llm_base_url,
api_key=config.llm_api_key,
model=config.llm_model,
timeout=120 # 答案生成可能需要更长时间
)
async def generate(
self,
query: str,
documents: List[RankedDocument]
) -> Answer:
"""
根据文档生成答案
Args:
query: 用户查询
documents: 排序后的文档列表
Returns:
Answer对象
"""
if not documents:
return self._empty_answer()
logger.info(f"开始生成答案,使用 {len(documents)} 个文档")
# 格式化文档
formatted_docs = format_documents_for_prompt(
documents,
max_length=self.config.content_max_length // len(documents)
)
user_message = f"""## 用户问题
{query}
## 搜索结果
{formatted_docs}"""
try:
result = await self.llm.chat_json(
system_prompt=ANSWER_GENERATION_PROMPT,
user_message=user_message,
temperature=0.5
)
# 解析来源
sources = [
Source(
index=s.get("index", i + 1),
title=s.get("title", ""),
url=s.get("url", "")
)
for i, s in enumerate(result.get("sources", []))
]
answer = Answer(
content=result.get("answer", ""),
sources=sources,
confidence=result.get("confidence", "medium")
)
logger.info(f"答案生成完成,置信度: {answer.confidence}")
return answer
except Exception as e:
logger.error(f"答案生成失败: {e}")
return self._fallback_answer(query, documents)
def _empty_answer(self) -> Answer:
"""生成空答案(无文档时)"""
return Answer(
content="抱歉,未能找到相关信息来回答您的问题。",
sources=[],
confidence="low"
)
def _fallback_answer(
self,
query: str,
documents: List[RankedDocument]
) -> Answer:
"""后备答案生成(LLM失败时)"""
# 简单汇总文档内容
content_parts = [f"关于「{query}」,以下是搜索到的相关信息:\n"]
sources = []
for i, doc in enumerate(documents[:5], 1):
actual_doc = doc.document
content_parts.append(f"### 来源 [{i}]: {actual_doc.title}\n")
content_parts.append(f"{actual_doc.content[:500]}...\n\n")
sources.append(Source(
index=i,
title=actual_doc.title,
url=actual_doc.url
))
return Answer(
content="".join(content_parts),
sources=sources,
confidence="low"
)
@@ -0,0 +1,102 @@
"""
内容提取模块
使用Jina Reader提取网页内容
"""
from typing import List
from loguru import logger
from config import Config
from models.schemas import Document, SearchResult, SearchSource
from tools.jina_reader import JinaReaderClient
class ContentExtractor:
"""内容提取模块"""
def __init__(self, config: Config):
"""
初始化内容提取器
Args:
config: 配置对象
"""
self.config = config
self.jina_reader = JinaReaderClient(
api_key=config.jina_api_key,
timeout=config.timeout,
max_content_length=config.content_max_length
)
async def extract(self, search_result: SearchResult) -> Document | None:
"""
从搜索结果提取内容
Args:
search_result: 搜索结果
Returns:
Document对象,如果提取失败则返回None
"""
return await self.jina_reader.extract_content(
url=search_result.url,
source=search_result.source
)
async def extract_batch(
self,
search_results: List[SearchResult],
max_urls: int = 10
) -> List[Document]:
"""
批量提取内容
Args:
search_results: 搜索结果列表
max_urls: 最大提取URL数量
Returns:
Document列表
"""
# 去重并限制数量
seen_urls = set()
unique_results = []
for result in search_results:
if result.url not in seen_urls and len(unique_results) < max_urls:
seen_urls.add(result.url)
unique_results.append(result)
logger.info(f"开始提取 {len(unique_results)} 个URL的内容")
# 提取内容
urls = [r.url for r in unique_results]
# 保存source信息以便后续使用
url_to_source = {r.url: r.source for r in unique_results}
documents = await self.jina_reader.extract_batch(urls)
# 更新document的source信息
for doc in documents:
if doc.url in url_to_source:
doc.source = url_to_source[doc.url]
return documents
async def extract_urls(
self,
urls: List[str],
source: SearchSource = SearchSource.WEB
) -> List[Document]:
"""
直接从URL列表提取内容
Args:
urls: URL列表
source: 来源类型
Returns:
Document列表
"""
return await self.jina_reader.extract_batch(urls, source)
@@ -0,0 +1,117 @@
"""
查询理解模块
负责分析用户查询意图、提取关键实体、生成扩展查询
"""
from typing import Optional
from loguru import logger
from config import Config
from models.schemas import QueryAnalysis, Intent
from utils.llm_client import LLMClient
# 查询分析Prompt
QUERY_ANALYSIS_PROMPT = """你是一个查询分析专家。分析用户的搜索查询,提取以下信息。
请输出JSON格式:
{
"intent": "查询意图,必须是以下之一: fact_check(事实核查), comparison(对比分析), how_to(操作指南), news(新闻资讯), research(深度研究)",
"entities": ["关键实体列表,提取查询中的核心概念、人名、产品名等"],
"expanded_queries": ["扩展查询1", "扩展查询2", "扩展查询3"],
"need_news": true或false,
"time_filter": "时间过滤器,null表示不限时间,qdr:d(过去24小时), qdr:w(过去一周), qdr:m(过去一月), qdr:y(过去一年)"
}
扩展查询要求:
1. 生成2-4个扩展查询,包含不同角度或同义表达
2. 至少包含一个英文查询(如果原查询是中文)
3. 保持查询的核心意图
时间过滤器选择规则:
- 查询涉及"最新"、"近期"、"今年"等时效性词语 → 设置相应的时间过滤器
- 查询涉及具体年份(如"2024年") → qdr:y
- 一般性查询 → null"""
class QueryAnalyzer:
"""查询理解模块"""
def __init__(self, config: Config):
"""
初始化查询分析器
Args:
config: 配置对象
"""
self.config = config
self.llm = LLMClient(
base_url=config.llm_base_url,
api_key=config.llm_api_key,
model=config.llm_model
)
async def analyze(self, query: str) -> QueryAnalysis:
"""
分析用户查询
Args:
query: 用户查询字符串
Returns:
QueryAnalysis对象
"""
logger.info(f"开始分析查询: {query}")
try:
result = await self.llm.chat_json(
system_prompt=QUERY_ANALYSIS_PROMPT,
user_message=f"用户查询: {query}",
temperature=0.3
)
# 解析意图
intent_str = result.get("intent", "research")
intent = self._parse_intent(intent_str)
# 构建分析结果
analysis = QueryAnalysis(
original_query=query,
intent=intent,
entities=result.get("entities", []),
expanded_queries=result.get("expanded_queries", [query]),
need_news=result.get("need_news", False),
time_filter=result.get("time_filter")
)
logger.info(f"查询分析完成: intent={intent.value}, entities={analysis.entities}")
return analysis
except Exception as e:
logger.error(f"查询分析失败: {e}")
# 返回默认分析结果
return self._default_analysis(query)
def _parse_intent(self, intent_str: str) -> Intent:
"""解析意图字符串为枚举"""
intent_mapping = {
"fact_check": Intent.FACT_CHECK,
"comparison": Intent.COMPARISON,
"how_to": Intent.HOW_TO,
"news": Intent.NEWS,
"research": Intent.RESEARCH
}
return intent_mapping.get(intent_str.lower(), Intent.RESEARCH)
def _default_analysis(self, query: str) -> QueryAnalysis:
"""生成默认的查询分析结果"""
return QueryAnalysis(
original_query=query,
intent=Intent.RESEARCH,
entities=[],
expanded_queries=[query],
need_news=False,
time_filter=None
)
@@ -0,0 +1,166 @@
"""
反思迭代模块
评估答案质量,决定是否需要补充搜索
"""
from typing import List
from loguru import logger
from config import Config
from models.schemas import Answer, QualityAssessment
from utils.llm_client import LLMClient
# 反思评估Prompt
REFLECTION_PROMPT = """你是一个质量评估专家。评估以下答案是否充分回答了用户的问题。
## 评估维度
1. **完整性**: 答案是否覆盖了问题的所有方面?
2. **准确性**: 答案内容是否有明确的来源支持?
3. **深度**: 答案是否提供了足够的细节和解释?
## 输出JSON格式
{
"completeness": 0.0-1.0,
"missing_aspects": ["如果有缺失,列出缺失的方面"],
"needs_more_search": true或false,
"suggested_queries": ["如果需要补充搜索,建议的搜索词"]
}
## 判断标准
- completeness >= 0.8 且没有重要信息缺失 → needs_more_search = false
- completeness < 0.8 或有重要信息缺失 → needs_more_search = true
- 建议的搜索词应该针对缺失的方面"""
class Reflector:
"""反思迭代模块"""
# 质量阈值
COMPLETENESS_THRESHOLD = 0.8
def __init__(self, config: Config):
"""
初始化反思器
Args:
config: 配置对象
"""
self.config = config
self.llm = LLMClient(
base_url=config.llm_base_url,
api_key=config.llm_api_key,
model=config.llm_model
)
async def assess(
self,
query: str,
answer: Answer
) -> QualityAssessment:
"""
评估答案质量
Args:
query: 原始查询
answer: 生成的答案
Returns:
QualityAssessment对象
"""
logger.info("开始评估答案质量")
# 如果答案置信度已经很低,直接建议补充搜索
if answer.confidence == "low" and not answer.content:
return QualityAssessment(
completeness=0.0,
missing_aspects=["缺少相关信息"],
needs_more_search=True,
suggested_queries=[query]
)
user_message = f"""## 用户问题
{query}
## 生成的答案
{answer.content}
## 答案的来源数量
{len(answer.sources)} 个来源
## 答案的置信度
{answer.confidence}"""
try:
result = await self.llm.chat_json(
system_prompt=REFLECTION_PROMPT,
user_message=user_message,
temperature=0.3
)
assessment = QualityAssessment(
completeness=float(result.get("completeness", 0.5)),
missing_aspects=result.get("missing_aspects", []),
needs_more_search=result.get("needs_more_search", False),
suggested_queries=result.get("suggested_queries", [])
)
logger.info(
f"质量评估: completeness={assessment.completeness:.2f}, "
f"needs_more_search={assessment.needs_more_search}"
)
return assessment
except Exception as e:
logger.error(f"质量评估失败: {e}")
return self._default_assessment(answer)
def _default_assessment(self, answer: Answer) -> QualityAssessment:
"""默认评估结果"""
# 根据答案置信度估计完整性
confidence_score = {
"high": 0.9,
"medium": 0.7,
"low": 0.4
}.get(answer.confidence, 0.5)
return QualityAssessment(
completeness=confidence_score,
missing_aspects=[],
needs_more_search=confidence_score < self.COMPLETENESS_THRESHOLD,
suggested_queries=[]
)
def should_continue(
self,
assessment: QualityAssessment,
current_iteration: int
) -> bool:
"""
判断是否应该继续迭代
Args:
assessment: 质量评估结果
current_iteration: 当前迭代次数
Returns:
是否继续迭代
"""
# 达到最大迭代次数
if current_iteration >= self.config.max_iterations:
logger.info(f"达到最大迭代次数 ({self.config.max_iterations}),停止迭代")
return False
# 完整性达标
if assessment.completeness >= self.COMPLETENESS_THRESHOLD:
logger.info(f"完整性达标 ({assessment.completeness:.2f}),停止迭代")
return False
# 没有建议的补充搜索
if not assessment.suggested_queries:
logger.info("没有建议的补充搜索,停止迭代")
return False
return assessment.needs_more_search
@@ -0,0 +1,107 @@
"""
结果处理模块
负责结果去重、相关性排序、筛选
"""
from typing import List
from loguru import logger
from config import Config
from models.schemas import Document, RankedDocument
from tools.jina_reranker import JinaRerankerClient
from utils.helpers import deduplicate_by_url
class ResultProcessor:
"""结果处理模块"""
def __init__(self, config: Config):
"""
初始化结果处理器
Args:
config: 配置对象
"""
self.config = config
self.reranker = JinaRerankerClient(
api_key=config.jina_api_key,
timeout=config.timeout
)
async def process(
self,
query: str,
documents: List[Document],
top_k: int = 5
) -> List[RankedDocument]:
"""
处理文档:去重 + 重排序 + 筛选
Args:
query: 原始查询
documents: 文档列表
top_k: 返回前k个结果
Returns:
排序后的RankedDocument列表
"""
if not documents:
logger.warning("没有文档需要处理")
return []
logger.info(f"开始处理 {len(documents)} 个文档")
# 1. 去重
unique_docs = self._deduplicate(documents)
logger.debug(f"去重后: {len(unique_docs)} 个文档")
# 2. 过滤空内容
valid_docs = [d for d in unique_docs if d.content and len(d.content.strip()) > 50]
logger.debug(f"有效文档: {len(valid_docs)} 个")
if not valid_docs:
logger.warning("没有有效文档")
return []
# 3. 重排序
ranked_docs = await self.reranker.rerank(
query=query,
documents=valid_docs,
top_k=top_k,
content_max_length=self.config.content_max_length // 5 # 使用较短内容进行排序
)
logger.info(f"处理完成,返回 {len(ranked_docs)} 个排序结果")
return ranked_docs
def _deduplicate(self, documents: List[Document]) -> List[Document]:
"""去重文档"""
return deduplicate_by_url(documents, "url")
async def process_without_rerank(
self,
documents: List[Document],
top_k: int = 5
) -> List[RankedDocument]:
"""
处理文档(不进行重排序)
Args:
documents: 文档列表
top_k: 返回前k个结果
Returns:
RankedDocument列表(按原始顺序)
"""
unique_docs = self._deduplicate(documents)
valid_docs = [d for d in unique_docs if d.content and len(d.content.strip()) > 50]
return [
RankedDocument(
document=doc,
relevance_score=1.0 - (i * 0.1),
rank=i + 1
)
for i, doc in enumerate(valid_docs[:top_k])
]
@@ -0,0 +1,89 @@
"""
搜索执行模块
执行搜索计划,调用Serper API
"""
import asyncio
from typing import List
from loguru import logger
from config import Config
from models.schemas import SearchPlan, SearchTask, SearchResult
from tools.serper import SerperClient
class SearchExecutor:
"""搜索执行模块"""
def __init__(self, config: Config):
"""
初始化搜索执行器
Args:
config: 配置对象
"""
self.config = config
self.serper = SerperClient(
api_key=config.serper_api_key,
timeout=config.timeout
)
async def execute(self, plan: SearchPlan) -> List[SearchResult]:
"""
执行搜索计划
Args:
plan: 搜索计划
Returns:
搜索结果列表
"""
logger.info(f"开始执行搜索计划: {len(plan.tasks)} 个任务")
if plan.strategy == "parallel":
results = await self._execute_parallel(plan.tasks)
else:
results = await self._execute_sequential(plan.tasks)
logger.info(f"搜索完成,共获取 {len(results)} 条结果")
return results
async def _execute_parallel(self, tasks: List[SearchTask]) -> List[SearchResult]:
"""并行执行搜索任务"""
coroutines = [self._execute_task(task) for task in tasks]
results_list = await asyncio.gather(*coroutines, return_exceptions=True)
# 合并结果
all_results = []
for results in results_list:
if isinstance(results, list):
all_results.extend(results)
elif isinstance(results, Exception):
logger.warning(f"搜索任务失败: {results}")
return all_results
async def _execute_sequential(self, tasks: List[SearchTask]) -> List[SearchResult]:
"""串行执行搜索任务"""
all_results = []
for task in tasks:
try:
results = await self._execute_task(task)
all_results.extend(results)
except Exception as e:
logger.warning(f"搜索任务失败: {e}")
return all_results
async def _execute_task(self, task: SearchTask) -> List[SearchResult]:
"""执行单个搜索任务"""
logger.debug(f"执行搜索: {task.query} [{task.source.value}]")
return await self.serper.search(
query=task.query,
source=task.source,
num_results=task.num_results,
time_filter=task.time_filter
)
@@ -0,0 +1,137 @@
"""
搜索规划模块
根据查询分析结果制定搜索计划
"""
from typing import List
from loguru import logger
from config import Config
from models.schemas import (
QueryAnalysis,
SearchPlan,
SearchTask,
SearchSource,
Intent
)
class SearchPlanner:
"""搜索规划模块"""
def __init__(self, config: Config):
"""
初始化搜索规划器
Args:
config: 配置对象
"""
self.config = config
self.max_results = config.max_results_per_query
async def plan(self, analysis: QueryAnalysis) -> SearchPlan:
"""
根据查询分析制定搜索计划
Args:
analysis: 查询分析结果
Returns:
SearchPlan对象
"""
logger.info(f"开始制定搜索计划: intent={analysis.intent.value}")
tasks = []
# 根据意图确定搜索策略
strategy = self._determine_strategy(analysis)
# 构建搜索任务
tasks.extend(self._create_web_tasks(analysis))
if analysis.need_news:
tasks.extend(self._create_news_tasks(analysis))
plan = SearchPlan(
tasks=tasks,
strategy=strategy
)
logger.info(f"搜索计划: {len(tasks)} 个任务, 策略={strategy}")
return plan
def _determine_strategy(self, analysis: QueryAnalysis) -> str:
"""确定执行策略"""
# 大多数情况使用并行策略
if analysis.intent == Intent.COMPARISON:
# 对比类查询可能需要串行以获取更相关的结果
return "parallel"
return "parallel"
def _create_web_tasks(self, analysis: QueryAnalysis) -> List[SearchTask]:
"""创建Web搜索任务"""
tasks = []
# 原始查询
tasks.append(SearchTask(
query=analysis.original_query,
source=SearchSource.WEB,
time_filter=analysis.time_filter,
num_results=self.max_results
))
# 扩展查询(限制数量避免过多请求)
for query in analysis.expanded_queries[:2]:
if query != analysis.original_query:
tasks.append(SearchTask(
query=query,
source=SearchSource.WEB,
time_filter=analysis.time_filter,
num_results=self.max_results
))
return tasks
def _create_news_tasks(self, analysis: QueryAnalysis) -> List[SearchTask]:
"""创建新闻搜索任务"""
tasks = []
# 新闻搜索使用原始查询
tasks.append(SearchTask(
query=analysis.original_query,
source=SearchSource.NEWS,
time_filter=analysis.time_filter or "qdr:m", # 默认过去一个月
num_results=self.max_results
))
return tasks
def plan_supplementary(
self,
original_query: str,
suggested_queries: List[str]
) -> SearchPlan:
"""
创建补充搜索计划
Args:
original_query: 原始查询
suggested_queries: 建议的补充查询
Returns:
SearchPlan对象
"""
tasks = []
for query in suggested_queries[:3]: # 限制补充搜索数量
tasks.append(SearchTask(
query=query,
source=SearchSource.WEB,
num_results=self.max_results
))
return SearchPlan(
tasks=tasks,
strategy="parallel"
)
@@ -0,0 +1,19 @@
# HTTP客户端
aiohttp>=3.9.0
requests>=2.31.0
# 环境变量
python-dotenv>=1.0.0
# JSON处理
orjson>=3.9.0
# 类型提示
typing-extensions>=4.9.0
# 日志
loguru>=0.7.0
# 异步工具
asyncio-throttle>=1.0.2
@@ -0,0 +1,16 @@
import requests
import json
url = "https://google.serper.dev/search"
payload = json.dumps({
"q": "apple inc"
})
headers = {
'X-API-KEY': '8253b4f240b520194065312f90e85f9be0fa205f',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
@@ -0,0 +1,14 @@
"""
外部API工具封装模块
"""
from .serper import SerperClient
from .jina_reader import JinaReaderClient
from .jina_reranker import JinaRerankerClient
__all__ = [
"SerperClient",
"JinaReaderClient",
"JinaRerankerClient",
]
@@ -0,0 +1,180 @@
"""
Jina Reader API封装
提供网页内容提取功能
"""
import asyncio
from typing import List, Optional
import aiohttp
from loguru import logger
from models.schemas import Document, SearchSource
class JinaReaderClient:
"""Jina Reader API客户端"""
BASE_URL = "https://r.jina.ai"
def __init__(
self,
api_key: str,
timeout: int = 30,
max_concurrent: int = 5,
max_content_length: int = 5000
):
"""
初始化Jina Reader客户端
Args:
api_key: Jina API密钥
timeout: 请求超时时间(秒)
max_concurrent: 最大并发请求数
max_content_length: 最大内容长度
"""
self.api_key = api_key
self.timeout = timeout
self.max_concurrent = max_concurrent
self.max_content_length = max_content_length
self._semaphore = asyncio.Semaphore(max_concurrent)
async def extract_content(
self,
url: str,
source: SearchSource = SearchSource.WEB
) -> Optional[Document]:
"""
提取单个URL的内容
Args:
url: 要提取的网页URL
source: 来源类型
Returns:
Document对象,如果提取失败则返回None
"""
reader_url = f"{self.BASE_URL}/{url}"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Accept": "application/json"
}
try:
async with self._semaphore:
async with aiohttp.ClientSession() as session:
async with session.get(
reader_url,
headers=headers,
timeout=aiohttp.ClientTimeout(total=self.timeout)
) as response:
if response.status != 200:
logger.warning(f"Jina Reader提取失败 [{response.status}]: {url}")
return None
# Jina Reader可能返回JSON或纯文本
content_type = response.headers.get("Content-Type", "")
if "application/json" in content_type:
result = await response.json()
# 处理嵌套的data字段
if "data" in result:
result = result["data"]
content = result.get("content", "")
title = result.get("title", "")
else:
# 纯文本响应(Markdown格式)
content = await response.text()
# 从内容中提取标题(第一行通常是标题)
lines = content.strip().split("\n")
title = lines[0].lstrip("#").strip() if lines else ""
# 限制内容长度
if len(content) > self.max_content_length:
content = content[:self.max_content_length]
logger.debug(f"提取成功: {url[:50]}... 内容长度: {len(content)}")
return Document(
url=url,
title=title,
content=content,
source=source
)
except aiohttp.ClientError as e:
logger.warning(f"Jina Reader网络错误 [{url}]: {e}")
return None
except asyncio.TimeoutError:
logger.warning(f"Jina Reader超时: {url}")
return None
except Exception as e:
logger.warning(f"Jina Reader异常 [{url}]: {e}")
return None
async def extract_batch(
self,
urls: List[str],
source: SearchSource = SearchSource.WEB
) -> List[Document]:
"""
批量提取多个URL的内容
Args:
urls: URL列表
source: 来源类型
Returns:
成功提取的Document列表
"""
logger.info(f"批量提取 {len(urls)} 个URL的内容")
tasks = [
self.extract_content(url, source)
for url in urls
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# 过滤掉失败的结果
documents = []
for result in results:
if isinstance(result, Document):
documents.append(result)
elif isinstance(result, Exception):
logger.warning(f"提取异常: {result}")
logger.info(f"成功提取 {len(documents)}/{len(urls)} 个文档")
return documents
async def extract_with_retry(
self,
url: str,
source: SearchSource = SearchSource.WEB,
max_retries: int = 2,
retry_delay: float = 1.0
) -> Optional[Document]:
"""
带重试的内容提取
Args:
url: 要提取的网页URL
source: 来源类型
max_retries: 最大重试次数
retry_delay: 重试延迟(秒)
Returns:
Document对象,如果最终失败则返回None
"""
for attempt in range(max_retries + 1):
result = await self.extract_content(url, source)
if result is not None:
return result
if attempt < max_retries:
logger.debug(f"重试提取 [{attempt + 1}/{max_retries}]: {url}")
await asyncio.sleep(retry_delay)
return None
@@ -0,0 +1,191 @@
"""
Jina Reranker API封装
提供搜索结果重排序功能
"""
from typing import List, Tuple
import aiohttp
from loguru import logger
from models.schemas import Document, RankedDocument
class JinaRerankerClient:
"""Jina Reranker API客户端"""
BASE_URL = "https://api.jina.ai/v1/rerank"
MODEL = "jina-reranker-v2-base-multilingual"
def __init__(self, api_key: str, timeout: int = 30):
"""
初始化Jina Reranker客户端
Args:
api_key: Jina API密钥
timeout: 请求超时时间(秒)
"""
self.api_key = api_key
self.timeout = timeout
async def rerank(
self,
query: str,
documents: List[Document],
top_k: int = 5,
content_max_length: int = 1000
) -> List[RankedDocument]:
"""
对文档进行相关性重排序
Args:
query: 查询字符串
documents: 文档列表
top_k: 返回前k个结果
content_max_length: 用于排序的内容最大长度
Returns:
排序后的RankedDocument列表
"""
if not documents:
return []
# 准备文档内容(截断到合适长度)
doc_contents = [
doc.content[:content_max_length] if doc.content else doc.title
for doc in documents
]
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.MODEL,
"query": query,
"documents": doc_contents,
"top_n": min(top_k, len(documents))
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
self.BASE_URL,
headers=headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=self.timeout)
) as response:
if response.status != 200:
error_text = await response.text()
logger.error(f"Jina Reranker API错误: {response.status} - {error_text}")
# 如果重排序失败,返回原始顺序
return self._fallback_ranking(documents, top_k)
result = await response.json()
return self._parse_rerank_results(documents, result, top_k)
except aiohttp.ClientError as e:
logger.error(f"Jina Reranker网络错误: {e}")
return self._fallback_ranking(documents, top_k)
except Exception as e:
logger.error(f"Jina Reranker异常: {e}")
return self._fallback_ranking(documents, top_k)
def _parse_rerank_results(
self,
documents: List[Document],
response: dict,
top_k: int
) -> List[RankedDocument]:
"""解析重排序结果"""
results = []
reranked = response.get("results", [])
for rank, item in enumerate(reranked[:top_k], 1):
index = item.get("index", 0)
score = item.get("relevance_score", 0.0)
if 0 <= index < len(documents):
ranked_doc = RankedDocument(
document=documents[index],
relevance_score=score,
rank=rank
)
results.append(ranked_doc)
logger.debug(f"重排序返回 {len(results)} 个结果")
return results
def _fallback_ranking(
self,
documents: List[Document],
top_k: int
) -> List[RankedDocument]:
"""后备排序:保持原始顺序"""
logger.warning("使用后备排序(原始顺序)")
return [
RankedDocument(
document=doc,
relevance_score=1.0 - (i * 0.1), # 模拟递减分数
rank=i + 1
)
for i, doc in enumerate(documents[:top_k])
]
async def rerank_texts(
self,
query: str,
texts: List[str],
top_k: int = 5
) -> List[Tuple[int, float]]:
"""
对纯文本列表进行重排序
Args:
query: 查询字符串
texts: 文本列表
top_k: 返回前k个结果
Returns:
(原始索引, 相关性分数) 的列表
"""
if not texts:
return []
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.MODEL,
"query": query,
"documents": texts,
"top_n": min(top_k, len(texts))
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
self.BASE_URL,
headers=headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=self.timeout)
) as response:
if response.status != 200:
logger.error(f"Reranker API错误: {response.status}")
return [(i, 1.0 - i * 0.1) for i in range(min(top_k, len(texts)))]
result = await response.json()
return [
(item["index"], item["relevance_score"])
for item in result.get("results", [])[:top_k]
]
except Exception as e:
logger.error(f"Reranker异常: {e}")
return [(i, 1.0 - i * 0.1) for i in range(min(top_k, len(texts)))]
@@ -0,0 +1,212 @@
"""
Serper API封装
提供Google搜索和新闻搜索功能
"""
from typing import List, Optional, Dict, Any
import aiohttp
from loguru import logger
from models.schemas import SearchResult, SearchSource
class SerperClient:
"""Serper API客户端"""
BASE_URL = "https://google.serper.dev"
ENDPOINTS = {
"web": "/search",
"news": "/news"
}
def __init__(self, api_key: str, timeout: int = 30):
"""
初始化Serper客户端
Args:
api_key: Serper API密钥
timeout: 请求超时时间(秒)
"""
self.api_key = api_key
self.timeout = timeout
async def _request(
self,
endpoint: str,
payload: Dict[str, Any]
) -> Dict[str, Any]:
"""
发送请求到Serper API
Args:
endpoint: API端点
payload: 请求体
Returns:
API响应
"""
url = f"{self.BASE_URL}{endpoint}"
headers = {
"X-API-KEY": self.api_key,
"Content-Type": "application/json"
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
url,
headers=headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=self.timeout)
) as response:
if response.status != 200:
error_text = await response.text()
logger.error(f"Serper API错误: {response.status} - {error_text}")
raise Exception(f"Serper API请求失败: {response.status}")
return await response.json()
except aiohttp.ClientError as e:
logger.error(f"Serper请求网络错误: {e}")
raise
async def search_web(
self,
query: str,
num_results: int = 10,
gl: str = "cn",
hl: str = "zh-cn",
time_filter: Optional[str] = None
) -> List[SearchResult]:
"""
执行Web搜索
Args:
query: 搜索查询
num_results: 返回结果数量
gl: 地区代码
hl: 语言代码
time_filter: 时间过滤器 (qdr:d/qdr:w/qdr:m/qdr:y)
Returns:
搜索结果列表
"""
payload = {
"q": query,
"num": num_results,
"gl": gl,
"hl": hl
}
if time_filter:
payload["tbs"] = time_filter
logger.info(f"执行Web搜索: {query}")
result = await self._request(self.ENDPOINTS["web"], payload)
return self._parse_web_results(result)
async def search_news(
self,
query: str,
num_results: int = 10,
gl: str = "cn",
hl: str = "zh-cn",
time_filter: Optional[str] = None
) -> List[SearchResult]:
"""
执行新闻搜索
Args:
query: 搜索查询
num_results: 返回结果数量
gl: 地区代码
hl: 语言代码
time_filter: 时间过滤器
Returns:
搜索结果列表
"""
payload = {
"q": query,
"num": num_results,
"gl": gl,
"hl": hl
}
if time_filter:
payload["tbs"] = time_filter
logger.info(f"执行新闻搜索: {query}")
result = await self._request(self.ENDPOINTS["news"], payload)
return self._parse_news_results(result)
def _parse_web_results(self, response: Dict[str, Any]) -> List[SearchResult]:
"""解析Web搜索结果"""
results = []
organic = response.get("organic", [])
for item in organic:
result = SearchResult(
title=item.get("title", ""),
url=item.get("link", ""),
snippet=item.get("snippet", ""),
source=SearchSource.WEB,
position=item.get("position", 0),
date=None
)
results.append(result)
logger.debug(f"Web搜索返回 {len(results)} 条结果")
return results
def _parse_news_results(self, response: Dict[str, Any]) -> List[SearchResult]:
"""解析新闻搜索结果"""
results = []
news = response.get("news", [])
for i, item in enumerate(news, 1):
result = SearchResult(
title=item.get("title", ""),
url=item.get("link", ""),
snippet=item.get("snippet", ""),
source=SearchSource.NEWS,
position=i,
date=item.get("date")
)
results.append(result)
logger.debug(f"新闻搜索返回 {len(results)} 条结果")
return results
async def search(
self,
query: str,
source: SearchSource,
num_results: int = 10,
time_filter: Optional[str] = None
) -> List[SearchResult]:
"""
统一搜索接口
Args:
query: 搜索查询
source: 搜索来源类型
num_results: 返回结果数量
time_filter: 时间过滤器
Returns:
搜索结果列表
"""
if source == SearchSource.NEWS:
return await self.search_news(query, num_results, time_filter=time_filter)
else:
return await self.search_web(query, num_results, time_filter=time_filter)
@@ -0,0 +1,22 @@
"""
工具函数模块
"""
from .llm_client import LLMClient
from .helpers import (
flatten,
deduplicate_by_url,
truncate_text,
extract_json_from_text,
format_documents_for_prompt,
)
__all__ = [
"LLMClient",
"flatten",
"deduplicate_by_url",
"truncate_text",
"extract_json_from_text",
"format_documents_for_prompt",
]
@@ -0,0 +1,197 @@
"""
通用工具函数
"""
import re
import json
from typing import List, TypeVar, Optional, Dict, Any
T = TypeVar('T')
def flatten(nested_list: List[List[T]]) -> List[T]:
"""
将嵌套列表展平为一维列表
Args:
nested_list: 嵌套列表
Returns:
展平后的一维列表
"""
return [item for sublist in nested_list for item in sublist]
def deduplicate_by_url(items: List[Any], url_attr: str = "url") -> List[Any]:
"""
根据URL去重
Args:
items: 包含URL属性的对象列表
url_attr: URL属性名
Returns:
去重后的列表
"""
seen_urls = set()
unique_items = []
for item in items:
url = getattr(item, url_attr, None) or item.get(url_attr)
if url and url not in seen_urls:
seen_urls.add(url)
unique_items.append(item)
return unique_items
def truncate_text(text: str, max_length: int, suffix: str = "...") -> str:
"""
截断文本到指定长度
Args:
text: 原始文本
max_length: 最大长度
suffix: 截断后缀
Returns:
截断后的文本
"""
if len(text) <= max_length:
return text
return text[:max_length - len(suffix)] + suffix
def extract_json_from_text(text: str) -> Optional[Dict[str, Any]]:
"""
从文本中提取JSON对象
Args:
text: 可能包含JSON的文本
Returns:
提取的JSON字典,如果提取失败则返回None
"""
# 尝试直接解析
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# 尝试提取```json ... ```块
json_block_pattern = r'```(?:json)?\s*([\s\S]*?)```'
matches = re.findall(json_block_pattern, text)
for match in matches:
try:
return json.loads(match.strip())
except json.JSONDecodeError:
continue
# 尝试提取{ ... }块
brace_pattern = r'\{[\s\S]*\}'
matches = re.findall(brace_pattern, text)
for match in matches:
try:
return json.loads(match)
except json.JSONDecodeError:
continue
return None
def format_documents_for_prompt(documents: List[Any], max_length: int = 2000) -> str:
"""
格式化文档列表为Prompt中使用的文本
Args:
documents: 文档列表(RankedDocument或Document对象)
max_length: 每个文档的最大内容长度
Returns:
格式化后的文本
"""
formatted_parts = []
for i, doc in enumerate(documents, 1):
# 支持RankedDocument和Document两种类型
if hasattr(doc, 'document'):
# RankedDocument
actual_doc = doc.document
score = f" (相关性: {doc.relevance_score:.2f})"
else:
# Document
actual_doc = doc
score = ""
content = truncate_text(actual_doc.content, max_length)
part = f"""### 来源 [{i}]{score}
**标题**: {actual_doc.title}
**URL**: {actual_doc.url}
**内容**:
{content}
"""
formatted_parts.append(part)
return "\n---\n".join(formatted_parts)
def clean_url(url: str) -> str:
"""
清理和标准化URL
Args:
url: 原始URL
Returns:
清理后的URL
"""
# 移除末尾的斜杠
url = url.rstrip("/")
# 移除锚点
if "#" in url:
url = url.split("#")[0]
return url
def is_valid_url(url: str) -> bool:
"""
验证URL是否有效
Args:
url: URL字符串
Returns:
是否有效
"""
url_pattern = re.compile(
r'^https?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain
r'localhost|' # localhost
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # IP
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
return bool(url_pattern.match(url))
def merge_dicts(base: Dict, override: Dict) -> Dict:
"""
合并两个字典,override中的值会覆盖base中的值
Args:
base: 基础字典
override: 覆盖字典
Returns:
合并后的字典
"""
result = base.copy()
result.update(override)
return result
@@ -0,0 +1,159 @@
"""
LLM客户端模块
封装与xchat52 LLM的交互(支持Azure OpenAI风格API)
"""
import json
from typing import Optional, List, Dict, Any
import aiohttp
from loguru import logger
class LLMClient:
"""LLM客户端,用于与xchat52 API交互"""
# API版本
API_VERSION = "2024-10-21"
def __init__(
self,
base_url: str,
api_key: str,
model: str = "xchat52",
timeout: int = 60
):
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.model = model
self.timeout = timeout
async def chat(
self,
messages: List[Dict[str, str]],
temperature: float = 0.7,
max_tokens: int = 4096,
response_format: Optional[Dict[str, str]] = None
) -> str:
"""
发送聊天请求到LLM
Args:
messages: 消息列表,格式 [{"role": "user", "content": "..."}]
temperature: 温度参数
max_tokens: 最大token数
response_format: 响应格式(如 {"type": "json_object"})
Returns:
LLM的响应文本
"""
# Azure OpenAI 风格的URL
url = f"{self.base_url}/chat/completions?api-version={self.API_VERSION}"
# Azure OpenAI 使用 api-key 头
headers = {
"api-key": self.api_key,
"Content-Type": "application/json"
}
payload = {
"model": self.model,
"messages": messages,
"temperature": temperature,
"max_completion_tokens": max_tokens # 新版API使用 max_completion_tokens
}
if response_format:
payload["response_format"] = response_format
try:
async with aiohttp.ClientSession() as session:
async with session.post(
url,
headers=headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=self.timeout)
) as response:
if response.status != 200:
error_text = await response.text()
logger.error(f"LLM API错误: {response.status} - {error_text}")
raise Exception(f"LLM API请求失败: {response.status}")
result = await response.json()
return result["choices"][0]["message"]["content"]
except aiohttp.ClientError as e:
logger.error(f"LLM请求网络错误: {e}")
raise
except Exception as e:
logger.error(f"LLM请求异常: {e}")
raise
async def chat_with_system(
self,
system_prompt: str,
user_message: str,
temperature: float = 0.7,
max_tokens: int = 4096,
response_format: Optional[Dict[str, str]] = None
) -> str:
"""
使用系统提示和用户消息进行对话
Args:
system_prompt: 系统提示
user_message: 用户消息
temperature: 温度参数
max_tokens: 最大token数
response_format: 响应格式
Returns:
LLM的响应文本
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
return await self.chat(
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
response_format=response_format
)
async def chat_json(
self,
system_prompt: str,
user_message: str,
temperature: float = 0.3
) -> Dict[str, Any]:
"""
请求JSON格式的响应
Args:
system_prompt: 系统提示
user_message: 用户消息
temperature: 温度参数(JSON响应建议使用较低温度)
Returns:
解析后的JSON字典
"""
from .helpers import extract_json_from_text
response = await self.chat_with_system(
system_prompt=system_prompt,
user_message=user_message,
temperature=temperature,
response_format={"type": "json_object"}
)
try:
return json.loads(response)
except json.JSONDecodeError:
# 尝试从文本中提取JSON
extracted = extract_json_from_text(response)
if extracted:
return extracted
logger.error(f"无法解析LLM响应为JSON: {response[:200]}")
raise ValueError("LLM响应不是有效的JSON格式")
@@ -0,0 +1,312 @@
"""
智能搜索 AI Agent - FastAPI版本
通过HTTP API接收搜索请求,提供智能搜索功能
"""
import os
import sys
import logging
from typing import Optional, Dict, Any, List
from datetime import datetime
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
import uvicorn
import asyncio
# 添加search_agent目录到Python路径
search_agent_dir = os.path.join(os.path.dirname(__file__), 'search_agent')
if search_agent_dir not in sys.path:
sys.path.insert(0, search_agent_dir)
# 直接导入,避免与文件名冲突
from config import Config
from agent.search_agent import SearchAgent
from agent_callback_utils import AgentCallbackHandler, CallbackContextManager
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0")
SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))
POD_NAME = os.getenv("POD_NAME", "search-agent")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "search_agent")
# 全局搜索Agent和回调处理器
search_agent: Optional[SearchAgent] = None
config: Optional[Config] = None
callback_handler: Optional[AgentCallbackHandler] = None
# FastAPI应用
app = FastAPI(
title="Intelligent Search AI Agent",
description="智能搜索代理",
version="1.0.0"
)
# ==================== 请求/响应模型 ====================
class ConfigRequest(BaseModel):
"""配置请求(其他配置从环境变量获取)"""
llm_base_url: str = Field(..., description="LLM API基础URL")
llm_model: str = Field(default="xchat52", description="LLM模型名称")
serper_api_key: str = Field(..., description="Serper API密钥")
jina_api_key: str = Field(..., description="Jina API密钥")
max_iterations: int = Field(default=3, description="最大迭代次数")
max_results_per_query: int = Field(default=10, description="每次搜索最大结果数")
content_max_length: int = Field(default=5000, description="内容最大长度")
log_level: str = Field(default="INFO", description="日志级别")
timeout: int = Field(default=30, description="超时时间(秒)")
class SearchRequest(BaseModel):
"""搜索请求"""
query: str = Field(..., description="搜索查询")
llm_api_key: str = Field(..., description="LLM API密钥")
user_id: Optional[str] = Field(None, description="用户ID(用于计费回调)")
auto_configure: bool = Field(default=False, description="是否自动从环境变量配置")
class Source(BaseModel):
"""搜索来源"""
index: int
title: str
url: str
class SearchResponse(BaseModel):
"""搜索响应"""
query: str
answer: str
sources: List[Source]
confidence: str
iterations: int
total_sources: int
search_queries: List[str]
timestamp: str
class StatusResponse(BaseModel):
"""状态响应"""
status: str
pod_name: str
template_type: str
configured: bool
timestamp: str
class ErrorResponse(BaseModel):
"""错误响应"""
error: str
detail: Optional[str] = None
# ==================== Agent操作函数 ====================
def initialize_agent_from_env():
"""从环境变量初始化Agent"""
global search_agent, config
try:
config = Config.from_env()
config.validate()
search_agent = SearchAgent(config)
logger.info("Search Agent从环境变量初始化成功")
return True
except Exception as e:
logger.error(f"从环境变量初始化Agent失败: {str(e)}")
return False
def initialize_agent_from_config(config_data: Dict[str, Any]):
"""从配置数据初始化Agent"""
global search_agent, config
try:
# 创建配置对象
config = Config(
llm_base_url=config_data.get("llm_base_url", ""),
llm_api_key=config_data.get("llm_api_key", ""),
llm_model=config_data.get("llm_model", "xchat52"),
serper_api_key=config_data.get("serper_api_key", ""),
jina_api_key=config_data.get("jina_api_key", ""),
max_iterations=config_data.get("max_iterations", 3),
max_results_per_query=config_data.get("max_results_per_query", 10),
content_max_length=config_data.get("content_max_length", 5000),
log_level=config_data.get("log_level", "INFO"),
timeout=config_data.get("timeout", 30)
)
config.validate()
search_agent = SearchAgent(config)
logger.info("Search Agent从配置初始化成功")
return True
except Exception as e:
logger.error(f"从配置初始化Agent失败: {str(e)}")
raise
# ==================== API端点 ====================
@app.get("/health")
async def health_check():
"""健康检查"""
return {
"status": "healthy",
"pod_name": POD_NAME,
"template_type": TEMPLATE_TYPE,
"configured": search_agent is not None,
"timestamp": datetime.utcnow().isoformat()
}
@app.get("/status", response_model=StatusResponse)
async def get_status():
"""获取状态"""
return StatusResponse(
status="running" if search_agent else "not_configured",
pod_name=POD_NAME,
template_type=TEMPLATE_TYPE,
configured=search_agent is not None,
timestamp=datetime.utcnow().isoformat()
)
@app.post("/configure")
async def configure_agent(config_req: ConfigRequest):
"""配置Agent"""
try:
initialize_agent_from_config(config_req.dict())
return {
"status": "success",
"message": "Agent配置成功",
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"配置Agent失败: {str(e)}")
raise HTTPException(status_code=400, detail=f"配置失败: {str(e)}")
@app.post("/search", response_model=SearchResponse)
async def search(request: SearchRequest):
"""执行搜索"""
global search_agent, callback_handler, config
# 如果未配置且需要自动配置
if not search_agent and request.auto_configure:
if not initialize_agent_from_env():
raise HTTPException(
status_code=400,
detail="Agent未配置且自动配置失败,请先调用/configure接口"
)
if not search_agent:
raise HTTPException(
status_code=400,
detail="Agent未配置,请先调用/configure接口"
)
# 初始化回调处理器(如果尚未初始化)
if not callback_handler:
callback_handler = AgentCallbackHandler()
# 使用上下文管理器自动处理回调
try:
with CallbackContextManager(
handler=callback_handler,
user_id=request.user_id,
request_id=f"search-{int(datetime.utcnow().timestamp())}"
) as ctx:
# 临时更新API key
original_api_key = config.llm_api_key if config else None
if config:
config.llm_api_key = request.llm_api_key
search_agent.config.llm_api_key = request.llm_api_key
try:
# 执行搜索
ctx.add_tool("web_search")
ctx.add_tool("content_reader")
result = await search_agent.search(request.query)
# 转换响应
sources = [
Source(
index=s.index,
title=s.title,
url=s.url
)
for s in result.answer.sources
]
return SearchResponse(
query=request.query,
answer=result.answer.content,
sources=sources,
confidence=result.answer.confidence,
iterations=result.iterations,
total_sources=result.total_sources_consulted,
search_queries=result.search_queries_used,
timestamp=datetime.utcnow().isoformat()
)
finally:
# 恢复原始API key
if config and original_api_key:
config.llm_api_key = original_api_key
search_agent.config.llm_api_key = original_api_key
except Exception as e:
logger.error(f"搜索失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"搜索失败: {str(e)}")
@app.post("/chat")
async def chat(request: SearchRequest):
"""聊天接口(别名)"""
return await search(request)
@app.get("/")
async def root():
"""根路径"""
return {
"name": "Intelligent Search AI Agent",
"version": "1.0.0",
"endpoints": {
"health": "/health",
"status": "/status",
"configure": "/configure",
"search": "/search",
"chat": "/chat"
}
}
# ==================== 启动函数 ====================
def main():
"""主函数"""
logger.info(f"启动 Search Agent - {POD_NAME}")
logger.info(f"Template Type: {TEMPLATE_TYPE}")
# 尝试从环境变量初始化
if os.getenv("LLM_API_KEY"):
logger.info("检测到环境变量配置,尝试自动初始化...")
initialize_agent_from_env()
else:
logger.info("未检测到环境变量配置,等待通过API配置...")
# 启动服务
uvicorn.run(
app,
host=SERVICE_HOST,
port=SERVICE_PORT,
log_level="info"
)
if __name__ == "__main__":
main()
Submodule agent_templates/aks_agent added at b45aa748ee
-622
View File
@@ -1,622 +0,0 @@
"""
Azure Blob Storage AI Agent - MCP (Model Context Protocol) 版本
使用 MCP 协议实现智能文件操作功能
"""
import os
import logging
import json
from typing import Optional, Dict, Any, List
from datetime import datetime
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from azure.storage.blob import BlobServiceClient, ContainerClient
import uvicorn
import asyncio
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0")
SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))
POD_NAME = os.getenv("POD_NAME", "azure-blob-agent-mcp")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent_mcp")
AGENT_FRAMEWORK = os.getenv("AGENT_FRAMEWORK", "mcp")
# 工具配置 (从环境变量传入的 JSON)
TOOLS_CONFIG = json.loads(os.getenv("TOOLS_CONFIG", "{}"))
TOOL_ENDPOINT = os.getenv("TOOL_ENDPOINT", "")
TOOL_API_KEY = os.getenv("TOOL_API_KEY", "")
# 模型配置
MODEL_PROVIDER = os.getenv("MODEL_PROVIDER", "openai")
MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4")
MODEL_API_KEY = os.getenv("MODEL_API_KEY", "")
MODEL_ENDPOINT = os.getenv("MODEL_ENDPOINT", "https://api.openai.com/v1")
# 存储配置
AZURE_STORAGE_CONNECTION_STRING = os.getenv("AZURE_STORAGE_CONNECTION_STRING", "")
STORAGE_ACCOUNT_NAME = os.getenv("STORAGE_ACCOUNT_NAME", "")
# 用户标识
USER_ID = os.getenv("USER_ID", "")
TENANT_ID = os.getenv("TENANT_ID", "")
NAMESPACE = os.getenv("NAMESPACE", "ai-agents")
# 全局存储客户端
blob_service_client: Optional[BlobServiceClient] = None
connection_string: Optional[str] = None
# MCP 工具注册表
mcp_tools: Dict[str, Any] = {}
# FastAPI应用
app = FastAPI(
title="Azure Blob Storage AI Agent (MCP)",
description="基于 MCP 协议的智能 Azure Blob 存储管理代理",
version="1.0.0"
)
# ==================== 请求/响应模型 ====================
class ConnectRequest(BaseModel):
"""连接请求"""
connection_string: str = Field(..., description="Azure Storage连接字符串")
class MCPToolRequest(BaseModel):
"""MCP 工具调用请求"""
tool_name: str = Field(..., description="工具名称")
parameters: Dict[str, Any] = Field(default_factory=dict, description="工具参数")
class MCPQueryRequest(BaseModel):
"""MCP 查询请求"""
query: str = Field(..., description="自然语言查询或操作指令")
container_name: Optional[str] = Field(None, description="指定容器名称")
context: Optional[Dict] = Field(default_factory=dict, description="上下文信息")
class HealthResponse(BaseModel):
"""健康检查响应"""
status: str
connected: bool
framework: str
user_id: Optional[str] = None
namespace: Optional[str] = None
connection_info: Optional[Dict] = None
# ==================== MCP 工具定义 ====================
class MCPTool:
"""MCP 工具基类"""
def __init__(self, name: str, description: str, parameters_schema: Dict):
self.name = name
self.description = description
self.parameters_schema = parameters_schema
async def execute(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
"""执行工具"""
raise NotImplementedError
def to_mcp_spec(self) -> Dict:
"""转换为 MCP 工具规范"""
return {
"name": self.name,
"description": self.description,
"inputSchema": {
"type": "object",
"properties": self.parameters_schema,
"required": list(self.parameters_schema.keys())
}
}
class ListContainersTool(MCPTool):
"""列出所有容器工具"""
def __init__(self):
super().__init__(
name="list_containers",
description="列出 Azure Blob Storage 中的所有容器",
parameters_schema={}
)
async def execute(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
global blob_service_client
if not blob_service_client:
return {"error": "未连接到 Azure Blob Storage"}
try:
containers = blob_service_client.list_containers()
container_list = []
for container in containers:
container_list.append({
"name": container.name,
"last_modified": str(container.last_modified)
})
return {
"success": True,
"containers": container_list,
"count": len(container_list)
}
except Exception as e:
logger.error(f"列出容器失败: {str(e)}")
return {"error": str(e)}
class ListBlobsTool(MCPTool):
"""列出容器中的 blob 工具"""
def __init__(self):
super().__init__(
name="list_blobs",
description="列出指定容器中的所有文件",
parameters_schema={
"container_name": {
"type": "string",
"description": "容器名称"
}
}
)
async def execute(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
global blob_service_client
if not blob_service_client:
return {"error": "未连接到 Azure Blob Storage"}
container_name = parameters.get("container_name")
if not container_name:
return {"error": "缺少参数: container_name"}
try:
container_client = blob_service_client.get_container_client(container_name)
blobs = container_client.list_blobs()
blob_list = []
total_size = 0
for blob in blobs:
blob_info = {
"name": blob.name,
"size": blob.size,
"size_mb": round(blob.size / (1024 * 1024), 2),
"content_type": blob.content_settings.content_type if blob.content_settings else "unknown",
"last_modified": str(blob.last_modified)
}
blob_list.append(blob_info)
total_size += blob.size
return {
"success": True,
"container": container_name,
"blobs": blob_list,
"count": len(blob_list),
"total_size_mb": round(total_size / (1024 * 1024), 2)
}
except Exception as e:
logger.error(f"列出 blob 失败: {str(e)}")
return {"error": str(e)}
class GetBlobInfoTool(MCPTool):
"""获取 blob 信息工具"""
def __init__(self):
super().__init__(
name="get_blob_info",
description="获取特定文件的详细信息",
parameters_schema={
"container_name": {
"type": "string",
"description": "容器名称"
},
"blob_name": {
"type": "string",
"description": "文件名称"
}
}
)
async def execute(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
global blob_service_client
if not blob_service_client:
return {"error": "未连接到 Azure Blob Storage"}
container_name = parameters.get("container_name")
blob_name = parameters.get("blob_name")
if not container_name or not blob_name:
return {"error": "缺少参数: container_name 或 blob_name"}
try:
blob_client = blob_service_client.get_blob_client(container_name, blob_name)
properties = blob_client.get_blob_properties()
return {
"success": True,
"blob_name": blob_name,
"container": container_name,
"size": properties.size,
"size_mb": round(properties.size / (1024 * 1024), 2),
"content_type": properties.content_settings.content_type if properties.content_settings else "unknown",
"creation_time": str(properties.creation_time),
"last_modified": str(properties.last_modified),
"etag": properties.etag,
"metadata": properties.metadata if properties.metadata else {}
}
except Exception as e:
logger.error(f"获取 blob 信息失败: {str(e)}")
return {"error": str(e)}
class SearchBlobsTool(MCPTool):
"""搜索 blob 工具"""
def __init__(self):
super().__init__(
name="search_blobs",
description="在容器中搜索包含关键字的文件",
parameters_schema={
"container_name": {
"type": "string",
"description": "容器名称"
},
"keyword": {
"type": "string",
"description": "搜索关键字"
}
}
)
async def execute(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
global blob_service_client
if not blob_service_client:
return {"error": "未连接到 Azure Blob Storage"}
container_name = parameters.get("container_name")
keyword = parameters.get("keyword")
if not container_name or not keyword:
return {"error": "缺少参数: container_name 或 keyword"}
try:
container_client = blob_service_client.get_container_client(container_name)
blobs = container_client.list_blobs()
matched_blobs = []
for blob in blobs:
if keyword.lower() in blob.name.lower():
matched_blobs.append({
"name": blob.name,
"size": blob.size,
"size_kb": round(blob.size / 1024, 2),
"last_modified": str(blob.last_modified)
})
return {
"success": True,
"container": container_name,
"keyword": keyword,
"results": matched_blobs,
"count": len(matched_blobs)
}
except Exception as e:
logger.error(f"搜索 blob 失败: {str(e)}")
return {"error": str(e)}
class GetStorageStatsTool(MCPTool):
"""获取存储统计工具"""
def __init__(self):
super().__init__(
name="get_storage_stats",
description="获取存储的统计信息,包括容器数量、文件数量、总大小等",
parameters_schema={}
)
async def execute(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
global blob_service_client
if not blob_service_client:
return {"error": "未连接到 Azure Blob Storage"}
try:
containers = list(blob_service_client.list_containers())
total_containers = len(containers)
total_blobs = 0
total_size = 0
container_stats = []
for container in containers:
container_client = blob_service_client.get_container_client(container.name)
blobs = list(container_client.list_blobs())
blob_count = len(blobs)
container_size = sum(blob.size for blob in blobs)
total_blobs += blob_count
total_size += container_size
container_stats.append({
"name": container.name,
"blobs": blob_count,
"size_mb": round(container_size / (1024 * 1024), 2)
})
return {
"success": True,
"total_containers": total_containers,
"total_blobs": total_blobs,
"total_size_mb": round(total_size / (1024 * 1024), 2),
"container_stats": container_stats
}
except Exception as e:
logger.error(f"获取统计信息失败: {str(e)}")
return {"error": str(e)}
# ==================== MCP 工具注册 ====================
def register_tools():
"""注册所有 MCP 工具"""
global mcp_tools
tools = [
ListContainersTool(),
ListBlobsTool(),
GetBlobInfoTool(),
SearchBlobsTool(),
GetStorageStatsTool()
]
for tool in tools:
mcp_tools[tool.name] = tool
logger.info(f"✅ 注册了 {len(mcp_tools)} 个 MCP 工具")
# ==================== API 端点 ====================
@app.get("/health", response_model=HealthResponse)
async def health_check():
"""健康检查"""
global blob_service_client, connection_string
connected = blob_service_client is not None
connection_info = None
if connected:
try:
account_info = blob_service_client.get_account_information()
connection_info = {
"account_kind": account_info.get('account_kind', 'unknown'),
"sku_name": account_info.get('sku_name', 'unknown'),
"connected_at": str(datetime.now())
}
except Exception as e:
logger.error(f"获取账户信息失败: {str(e)}")
return HealthResponse(
status="healthy" if connected else "not_connected",
connected=connected,
framework=AGENT_FRAMEWORK,
user_id=USER_ID,
namespace=NAMESPACE,
connection_info=connection_info
)
@app.post("/connect")
async def connect_to_storage(request: ConnectRequest):
"""连接到 Azure Blob Storage"""
global blob_service_client, connection_string
try:
blob_service_client = BlobServiceClient.from_connection_string(
request.connection_string
)
account_info = blob_service_client.get_account_information()
connection_string = request.connection_string
logger.info(f"✅ 成功连接到 Azure Blob Storage (User: {USER_ID})")
return {
"status": "connected",
"message": "成功连接到 Azure Blob Storage",
"framework": AGENT_FRAMEWORK,
"user_id": USER_ID,
"account_info": {
"account_kind": account_info.get('account_kind'),
"sku_name": account_info.get('sku_name')
}
}
except Exception as e:
logger.error(f"❌ 连接失败: {str(e)}")
blob_service_client = None
connection_string = None
raise HTTPException(status_code=400, detail=f"连接失败: {str(e)}")
@app.get("/mcp/tools")
async def list_mcp_tools():
"""列出所有可用的 MCP 工具"""
if not blob_service_client:
raise HTTPException(
status_code=400,
detail="未连接到 Azure Blob Storage,请先调用 /connect"
)
tools_spec = [tool.to_mcp_spec() for tool in mcp_tools.values()]
return {
"tools": tools_spec,
"count": len(tools_spec),
"framework": AGENT_FRAMEWORK
}
@app.post("/mcp/call")
async def call_mcp_tool(request: MCPToolRequest):
"""调用 MCP 工具"""
if not blob_service_client:
raise HTTPException(
status_code=400,
detail="未连接到 Azure Blob Storage,请先调用 /connect"
)
tool_name = request.tool_name
if tool_name not in mcp_tools:
raise HTTPException(
status_code=404,
detail=f"工具 '{tool_name}' 不存在"
)
try:
tool = mcp_tools[tool_name]
result = await tool.execute(request.parameters)
return {
"tool": tool_name,
"result": result,
"timestamp": str(datetime.now())
}
except Exception as e:
logger.error(f"工具调用失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"工具调用失败: {str(e)}")
@app.post("/query")
async def query_storage(request: MCPQueryRequest):
"""使用自然语言查询存储 (简化版 - 实际应集成 LLM)"""
if not blob_service_client:
raise HTTPException(
status_code=400,
detail="未连接到 Azure Blob Storage,请先调用 /connect"
)
try:
query = request.query.lower()
result = None
# 简单的规则匹配 (实际应使用 LLM 进行意图识别)
if "容器" in query and ("列出" in query or "显示" in query or "有哪些" in query):
tool = mcp_tools["list_containers"]
result = await tool.execute({})
elif "统计" in query or "有多少" in query or "占用" in query:
tool = mcp_tools["get_storage_stats"]
result = await tool.execute({})
elif request.container_name:
if "文件" in query or "blob" in query.lower():
tool = mcp_tools["list_blobs"]
result = await tool.execute({"container_name": request.container_name})
if result:
return {
"status": "success",
"query": request.query,
"result": result,
"framework": AGENT_FRAMEWORK
}
else:
return {
"status": "info",
"query": request.query,
"message": "未能匹配到合适的工具,请使用 /mcp/tools 查看可用工具",
"available_tools": list(mcp_tools.keys())
}
except Exception as e:
logger.error(f"查询执行失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"查询失败: {str(e)}")
@app.get("/")
async def root():
"""根端点"""
return {
"service": "Azure Blob Storage AI Agent",
"version": "1.0.0",
"framework": AGENT_FRAMEWORK,
"pod_name": POD_NAME,
"template": TEMPLATE_TYPE,
"user_id": USER_ID,
"namespace": NAMESPACE,
"connected": blob_service_client is not None,
"tools_count": len(mcp_tools),
"endpoints": {
"health": "/health",
"connect": "POST /connect",
"list_tools": "GET /mcp/tools",
"call_tool": "POST /mcp/call",
"query": "POST /query"
}
}
# ==================== 主函数 ====================
def init_storage_connection():
"""启动时初始化存储连接"""
global blob_service_client, connection_string
if AZURE_STORAGE_CONNECTION_STRING:
try:
logger.info("检测到环境变量中的连接字符串,尝试连接...")
blob_service_client = BlobServiceClient.from_connection_string(
AZURE_STORAGE_CONNECTION_STRING
)
account_info = blob_service_client.get_account_information()
connection_string = AZURE_STORAGE_CONNECTION_STRING
logger.info(f"✅ 成功连接到 Azure Blob Storage")
logger.info(f" - Account Kind: {account_info.get('account_kind')}")
logger.info(f" - SKU: {account_info.get('sku_name')}")
except Exception as e:
logger.error(f"❌ 启动时连接失败: {str(e)}")
logger.info("💡 提示: 可以稍后通过 /connect API 手动连接")
blob_service_client = None
connection_string = None
else:
logger.info("💡 未设置 AZURE_STORAGE_CONNECTION_STRING,需通过 /connect API 手动连接")
def main():
"""启动服务"""
logger.info(f"🚀 启动 Azure Blob Storage AI Agent (MCP)")
logger.info(f" - Framework: {AGENT_FRAMEWORK}")
logger.info(f" - Pod名称: {POD_NAME}")
logger.info(f" - 模板类型: {TEMPLATE_TYPE}")
logger.info(f" - User ID: {USER_ID}")
logger.info(f" - Namespace: {NAMESPACE}")
logger.info(f" - 模型: {MODEL_NAME} @ {MODEL_PROVIDER}")
logger.info(f" - 服务地址: http://{SERVICE_HOST}:{SERVICE_PORT}")
# 注册 MCP 工具
register_tools()
# 初始化存储连接
init_storage_connection()
uvicorn.run(
app,
host=SERVICE_HOST,
port=SERVICE_PORT,
log_level="info"
)
if __name__ == "__main__":
main()
-40
View File
@@ -1,40 +0,0 @@
#!/bin/bash
# 构建并推送AI Agent镜像到ACR
set -e # 遇到错误立即退出
# 配置变量
ACR_NAME="agnettaiji" # 你的ACR名称
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
# 登录到ACR
echo "登录到Azure Container Registry..."
az acr login --name ${ACR_NAME}
echo "当前目录: $(pwd)"
echo ""
# 构建并推送MySQL Agent
echo "构建MySQL Agent镜像..."
docker build -f mysql_agent.Dockerfile -t ${ACR_LOGIN_SERVER}/ai-agents/mysql-agent:latest .
echo "推送MySQL Agent镜像..."
docker push ${ACR_LOGIN_SERVER}/ai-agents/mysql-agent:latest
# 构建并推送PostgreSQL Agent
echo "构建PostgreSQL Agent镜像..."
docker build -f postgresql_agent.Dockerfile -t ${ACR_LOGIN_SERVER}/ai-agents/postgresql-agent:latest .
echo "推送PostgreSQL Agent镜像..."
docker push ${ACR_LOGIN_SERVER}/ai-agents/postgresql-agent:latest
# 构建并推送Jina Search Agent
echo "构建Jina Search Agent镜像..."
docker build -f jina_search_agent.Dockerfile -t ${ACR_LOGIN_SERVER}/ai-agents/jina-search-agent:latest .
echo "推送Jina Search Agent镜像..."
docker push ${ACR_LOGIN_SERVER}/ai-agents/jina-search-agent:latest
echo "✅ 所有镜像构建并推送完成!"
echo ""
echo "已推送的镜像:"
echo " - ${ACR_LOGIN_SERVER}/ai-agents/mysql-agent:latest"
echo " - ${ACR_LOGIN_SERVER}/ai-agents/postgresql-agent:latest"
echo " - ${ACR_LOGIN_SERVER}/ai-agents/jina-search-agent:latest"
-41
View File
@@ -1,41 +0,0 @@
#!/bin/bash
# 构建并推送 Azure Blob Agent A2A 版本到 ACR
# 用法: ./build_azure_blob_a2a.sh
set -e
echo "🚀 构建 Azure Blob Agent (A2A版本)..."
# Azure Container Registry 配置
ACR_NAME="agnettaiji"
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
IMAGE_NAME="ai-agents/azure-blob-agent-a2a"
IMAGE_TAG="latest"
# 完整镜像名称
FULL_IMAGE_NAME="${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}"
echo "📦 镜像名称: ${FULL_IMAGE_NAME}"
# 构建镜像
echo "🔨 构建 Docker 镜像 (ARM64)..."
docker buildx build \
--platform linux/arm64 \
-f azure_blob_agent_a2a.Dockerfile \
-t ${FULL_IMAGE_NAME} \
--load \
.
echo "✅ 镜像构建成功"
# 登录到 ACR
echo "🔐 登录到 Azure Container Registry..."
az acr login --name ${ACR_NAME}
# 推送镜像
echo "📤 推送镜像到 ACR..."
docker push ${FULL_IMAGE_NAME}
echo "✅ 镜像推送成功"
echo "🎉 完成!镜像: ${FULL_IMAGE_NAME}"
-88
View File
@@ -1,88 +0,0 @@
#!/bin/bash
# Azure Blob Storage Agent 构建和推送脚本
# 使用方法: ./build_azure_blob_agent.sh [TAG]
set -e
# 默认配置
ACR_NAME="${ACR_NAME:-agnettaiji.azurecr.io}"
IMAGE_NAME="ai-agents/azure-blob-agent"
TAG="${1:-latest}"
FULL_IMAGE="${ACR_NAME}/${IMAGE_NAME}:${TAG}"
echo "=========================================="
echo "构建 Azure Blob Storage Agent"
echo "=========================================="
echo "镜像: ${FULL_IMAGE}"
echo ""
# 构建镜像
echo "📦 开始构建镜像..."
docker build \
-f azure_blob_agent.Dockerfile \
-t "${FULL_IMAGE}" \
.
echo ""
echo "✅ 镜像构建成功: ${FULL_IMAGE}"
echo ""
# 询问是否推送
read -p "是否推送到 ACR? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "🚀 推送镜像到 ACR..."
# 登录 ACR (如果需要)
echo "登录到 ACR..."
az acr login --name $(echo ${ACR_NAME} | cut -d'.' -f1)
# 推送镜像
docker push "${FULL_IMAGE}"
echo ""
echo "✅ 镜像推送成功!"
else
echo "⏭️ 跳过推送"
fi
echo ""
echo "=========================================="
echo "本地测试命令:"
echo "=========================================="
echo ""
echo "# 启动容器 (需要 LiteLLM 服务)"
echo "docker run -d --name azure-blob-agent \\"
echo " -p 8080:8080 \\"
echo " -e LITELLM_API_BASE=http://host.docker.internal:4000 \\"
echo " -e LITELLM_MODEL=gpt-3.5-turbo \\"
echo " -e LITELLM_API_KEY=sk-1234 \\"
echo " -e AZURE_STORAGE_CONNECTION_STRING='YOUR_CONNECTION_STRING' \\"
echo " ${FULL_IMAGE}"
echo ""
echo "# 或者不提供连接字符串,稍后通过 API 连接"
echo "docker run -d --name azure-blob-agent \\"
echo " -p 8080:8080 \\"
echo " -e LITELLM_API_BASE=http://host.docker.internal:4000 \\"
echo " -e LITELLM_MODEL=gpt-3.5-turbo \\"
echo " -e LITELLM_API_KEY=sk-1234 \\"
echo " ${FULL_IMAGE}"
echo ""
echo "# 检查健康状态"
echo "curl http://localhost:8080/health"
echo ""
echo "# 连接到 Azure Storage"
echo "curl -X POST http://localhost:8080/connect \\"
echo " -H 'Content-Type: application/json' \\"
echo " -d '{\"connection_string\": \"YOUR_CONNECTION_STRING\"}'"
echo ""
echo "# 执行自然语言查询"
echo "curl -X POST http://localhost:8080/query \\"
echo " -H 'Content-Type: application/json' \\"
echo " -d '{\"query\": \"列出所有容器\"}'"
echo ""
echo "# 查看日志"
echo "docker logs -f azure-blob-agent"
echo ""
echo "=========================================="
-41
View File
@@ -1,41 +0,0 @@
#!/bin/bash
# 构建并推送 Azure Blob Agent MCP 版本到 ACR
# 用法: ./build_azure_blob_mcp.sh
set -e
echo "🚀 构建 Azure Blob Agent (MCP版本)..."
# Azure Container Registry 配置
ACR_NAME="agnettaiji"
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
IMAGE_NAME="ai-agents/azure-blob-agent-mcp"
IMAGE_TAG="latest"
# 完整镜像名称
FULL_IMAGE_NAME="${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}"
echo "📦 镜像名称: ${FULL_IMAGE_NAME}"
# 构建镜像
echo "🔨 构建 Docker 镜像 (ARM64)..."
docker buildx build \
--platform linux/arm64 \
-f azure_blob_agent_mcp.Dockerfile \
-t ${FULL_IMAGE_NAME} \
--load \
.
echo "✅ 镜像构建成功"
# 登录到 ACR
echo "🔐 登录到 Azure Container Registry..."
az acr login --name ${ACR_NAME}
# 推送镜像
echo "📤 推送镜像到 ACR..."
docker push ${FULL_IMAGE_NAME}
echo "✅ 镜像推送成功"
echo "🎉 完成!镜像: ${FULL_IMAGE_NAME}"
-54
View File
@@ -1,54 +0,0 @@
#!/bin/bash
# 构建并推送Jina Search Agent镜像到ACR
set -e
# 配置变量
ACR_NAME="agnettaiji"
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
IMAGE_NAME="ai-agents/jina-search-agent"
IMAGE_TAG="latest"
echo "=========================================="
echo "Jina Search Agent Docker镜像构建脚本"
echo "=========================================="
# 检查是否在正确的目录
if [ ! -f "jina_search_agent.py" ]; then
echo "错误: 请在agent_templates目录下运行此脚本"
exit 1
fi
# 登录到ACR
echo ""
echo "步骤1: 登录到Azure Container Registry..."
az acr login --name ${ACR_NAME}
# 构建镜像
echo ""
echo "步骤2: 构建Docker镜像..."
docker build -f jina_search_agent.Dockerfile -t ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG} .
# 推送镜像
echo ""
echo "步骤3: 推送镜像到ACR..."
docker push ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}
echo ""
echo "=========================================="
echo "构建完成!"
echo "镜像: ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}"
echo "=========================================="
echo ""
echo "使用示例:"
echo "curl -X POST 'http://localhost:8000/agents' \\"
echo " -H 'Content-Type: application/json' \\"
echo " -d '{"
echo " \"name\": \"my-jina-agent\","
echo " \"template\": \"jina_search_agent\","
echo " \"config\": {"
echo " \"env\": {"
echo " \"JINA_API_KEY\": \"your-jina-api-key\""
echo " }"
echo " }"
echo " }'"
+19
View File
@@ -0,0 +1,19 @@
FROM python:3.11-slim
WORKDIR /app
# 安装基本依赖
RUN pip install --no-cache-dir \
python-dotenv \
loguru \
aiohttp \
requests \
orjson \
typing-extensions \
asyncio-throttle
COPY search_agent/ /app/search_agent/
COPY agent_callback_utils.py /app/
COPY test_search_import.py /app/
CMD ["python3", "/app/test_search_import.py"]
@@ -0,0 +1,205 @@
"""
Agent回调工具 - 用于向Agent Manager回调运行时长记录
"""
import os
import time
import logging
import requests
from typing import Optional, List
from datetime import datetime, timezone
logger = logging.getLogger(__name__)
class AgentCallbackHandler:
"""Agent回调处理器"""
def __init__(
self,
agent_name: Optional[str] = None,
user_id: Optional[str] = None,
callback_url: Optional[str] = None
):
"""
初始化回调处理器
Args:
agent_name: Agent名称,默认从环境变量 POD_NAME 获取
user_id: 用户ID,默认从环境变量 USER_ID 获取
callback_url: 回调URL,默认从环境变量 AGENT_CALLBACK_URL 获取
"""
self.agent_name = agent_name or os.getenv("POD_NAME", "unknown-agent")
self.user_id = user_id or os.getenv("USER_ID", "")
self.callback_url = callback_url or os.getenv(
"AGENT_CALLBACK_URL",
"http://mcp-server:8002/api/v1/billing/agent-callback"
)
self.start_time: Optional[datetime] = None
self.tools_used: List[str] = []
self.request_id: Optional[str] = None
logger.info(f"AgentCallbackHandler initialized: agent={self.agent_name}, callback_url={self.callback_url}")
def start_request(self, request_id: Optional[str] = None, user_id: Optional[str] = None):
"""
开始一次请求处理
Args:
request_id: 请求ID
user_id: 用户ID(如果提供则覆盖默认值)
"""
self.start_time = datetime.now(timezone.utc)
self.tools_used = []
self.request_id = request_id or f"req-{int(time.time())}"
if user_id:
self.user_id = user_id
logger.info(f"Request started: request_id={self.request_id}, user_id={self.user_id}")
def add_tool_used(self, tool_name: str):
"""
记录使用的工具
Args:
tool_name: 工具名称
"""
if tool_name not in self.tools_used:
self.tools_used.append(tool_name)
logger.debug(f"Tool used: {tool_name}")
def end_request(self, tools_used: Optional[List[str]] = None) -> bool:
"""
结束请求并发送回调
Args:
tools_used: 使用的工具列表(可选,如果提供则覆盖内部记录)
Returns:
是否成功发送回调
"""
if not self.start_time:
logger.warning("Cannot end request: no start time recorded")
return False
if not self.user_id:
logger.warning("Cannot send callback: user_id not set")
return False
end_time = datetime.now(timezone.utc)
running_time = (end_time - self.start_time).total_seconds()
# 使用提供的工具列表或内部记录
final_tools_used = tools_used if tools_used is not None else self.tools_used
# 发送回调
success = self._send_callback(
running_time_seconds=int(running_time),
start_time=self.start_time,
end_time=end_time,
tools_used=final_tools_used
)
# 重置状态
self.start_time = None
self.tools_used = []
self.request_id = None
return success
def _send_callback(
self,
running_time_seconds: int,
start_time: datetime,
end_time: datetime,
tools_used: List[str]
) -> bool:
"""
发送回调到Agent Manager
Args:
running_time_seconds: 运行时长(秒)
start_time: 开始时间
end_time: 结束时间
tools_used: 使用的工具列表
Returns:
是否成功发送
"""
try:
payload = {
"agentName": self.agent_name,
"userId": self.user_id,
"podRunningTimeSeconds": running_time_seconds,
"toolsUsed": tools_used,
"startTime": start_time.isoformat(),
"endTime": end_time.isoformat(),
"requestId": self.request_id
}
logger.info(f"Sending callback: {payload}")
response = requests.post(
self.callback_url,
json=payload,
timeout=5
)
if response.status_code == 200:
logger.info(f"Callback sent successfully: {response.json()}")
return True
else:
logger.error(f"Callback failed with status {response.status_code}: {response.text}")
return False
except requests.exceptions.RequestException as e:
logger.error(f"Failed to send callback: {str(e)}")
return False
except Exception as e:
logger.error(f"Unexpected error sending callback: {str(e)}")
return False
class CallbackContextManager:
"""回调上下文管理器 - 使用with语句自动处理开始和结束"""
def __init__(
self,
handler: AgentCallbackHandler,
request_id: Optional[str] = None,
user_id: Optional[str] = None,
tools_used: Optional[List[str]] = None
):
"""
初始化上下文管理器
Args:
handler: AgentCallbackHandler实例
request_id: 请求ID
user_id: 用户ID
tools_used: 使用的工具列表(可选)
"""
self.handler = handler
self.request_id = request_id
self.user_id = user_id
self.tools_used = tools_used or []
def __enter__(self):
"""进入上下文时开始计时"""
self.handler.start_request(
request_id=self.request_id,
user_id=self.user_id
)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""退出上下文时发送回调"""
self.handler.end_request(tools_used=self.tools_used)
return False # 不抑制异常
def add_tool(self, tool_name: str):
"""添加使用的工具"""
self.handler.add_tool_used(tool_name)
if tool_name not in self.tools_used:
self.tools_used.append(tool_name)
+121
View File
@@ -0,0 +1,121 @@
"""
统一的 API Key 配置工具模块
支持从环境变量或请求参数获取 API key
优先使用请求传入的 key,如果未提供则从环境变量获取
"""
import os
from typing import Optional
def get_api_key(
request_key: Optional[str] = None,
env_key_name: str = "API_KEY",
default: Optional[str] = None
) -> Optional[str]:
"""
获取 API key,优先使用请求传入的,否则从环境变量获取
Args:
request_key: 请求中传入的 API key(优先使用)
env_key_name: 环境变量名称
default: 默认值(如果都未设置)
Returns:
API key 字符串,如果都未设置则返回 None 或 default
"""
# 优先使用请求传入的 key
if request_key:
return request_key
# 从环境变量获取
env_key = os.getenv(env_key_name)
if env_key:
return env_key
# 返回默认值
return default
def get_llm_api_key(request_key: Optional[str] = None) -> Optional[str]:
"""
获取 LLM API key
Args:
request_key: 请求中传入的 LLM API key
Returns:
LLM API key
"""
return get_api_key(request_key, "LLM_API_KEY")
def get_openai_api_key(request_key: Optional[str] = None) -> Optional[str]:
"""
获取 OpenAI API key
Args:
request_key: 请求中传入的 OpenAI API key
Returns:
OpenAI API key
"""
return get_api_key(request_key, "OPENAI_API_KEY")
def get_litellm_api_key(request_key: Optional[str] = None) -> Optional[str]:
"""
获取 LiteLLM API key
Args:
request_key: 请求中传入的 LiteLLM API key
Returns:
LiteLLM API key
"""
return get_api_key(request_key, "LITELLM_API_KEY")
def get_serper_api_key(request_key: Optional[str] = None) -> Optional[str]:
"""
获取 Serper API key
Args:
request_key: 请求中传入的 Serper API key
Returns:
Serper API key
"""
return get_api_key(request_key, "SERPER_API_KEY")
def get_jina_api_key(request_key: Optional[str] = None) -> Optional[str]:
"""
获取 Jina API key
Args:
request_key: 请求中传入的 Jina API key
Returns:
Jina API key
"""
return get_api_key(request_key, "JINA_API_KEY")
def validate_api_key(api_key: Optional[str], key_name: str = "API key") -> str:
"""
验证 API key 是否存在,如果不存在则抛出异常
Args:
api_key: 要验证的 API key
key_name: key 的名称(用于错误消息)
Returns:
验证通过的 API key
Raises:
ValueError: 如果 API key 未设置
"""
if not api_key:
raise ValueError(f"{key_name} 未设置!请通过请求参数传入或设置环境变量")
return api_key
@@ -0,0 +1,416 @@
# Azure Blob Agent A2A 请求调用示例
## 服务信息
- **服务名称**: Azure Blob Storage AI Agent (A2A)
- **版本**: 1.0.0
- **框架**: Agent-to-Agent (A2A)
- **默认端口**: 8080
## 概述
A2A 版本支持 Agent 之间的协作和通信,允许多个 Agent 互相调用和协作完成复杂任务。
---
## API 端点
### 1. 健康检查
**端点**: `GET /health`
**请求示例** (curl):
```bash
curl http://localhost:8080/health
```
**响应示例**:
```json
{
"status": "healthy",
"connected": true,
"framework": "a2a",
"agent_id": "azure-blob-agent-a2a",
"agent_role": "storage_manager",
"capabilities": ["blob_storage", "file_operations"],
"namespace": "ai-agents",
"connection_info": {
"account_kind": "StorageV2",
"sku_name": "Standard_LRS"
}
}
```
---
### 2. A2A Agent 注册
**端点**: `POST /a2a/register`
注册其他 Agent 以便协作。
**请求体**:
```json
{
"agent_id": "search-agent",
"agent_role": "search_provider",
"capabilities": ["web_search", "content_extraction"],
"endpoint": "http://search-agent:8080"
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/a2a/register \
-H "Content-Type: application/json" \
-d '{
"agent_id": "search-agent",
"agent_role": "search_provider",
"capabilities": ["web_search"],
"endpoint": "http://search-agent:8080"
}'
```
**请求示例** (Python):
```python
import requests
register_data = {
"agent_id": "search-agent",
"agent_role": "search_provider",
"capabilities": ["web_search", "content_extraction"],
"endpoint": "http://search-agent:8080"
}
response = requests.post(
"http://localhost:8080/a2a/register",
json=register_data
)
print(response.json())
```
**响应示例**:
```json
{
"status": "success",
"message": "Agent registered successfully",
"agent_id": "search-agent"
}
```
---
### 3. A2A 消息发送
**端点**: `POST /a2a/message`
发送 A2A 协议消息给此 Agent。
**请求体**:
```json
{
"message_id": "msg-12345",
"from_agent": "orchestrator-agent",
"to_agent": "azure-blob-agent-a2a",
"message_type": "request",
"action": "list_containers",
"parameters": {},
"context": {
"user_id": "user123",
"session_id": "sess-456"
},
"timestamp": "2026-01-15T10:30:00Z"
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/a2a/message \
-H "Content-Type: application/json" \
-d '{
"message_id": "msg-001",
"from_agent": "orchestrator",
"to_agent": "azure-blob-agent-a2a",
"message_type": "request",
"action": "list_containers",
"parameters": {},
"context": {"user_id": "user123"}
}'
```
**支持的 Actions**:
- `list_containers` - 列出所有容器
- `list_blobs` - 列出容器中的文件
- `upload_blob` - 上传文件
- `download_blob` - 下载文件
- `delete_blob` - 删除文件
- `create_container` - 创建容器
**请求示例** (Python):
```python
import requests
from datetime import datetime
message = {
"message_id": f"msg-{int(datetime.now().timestamp())}",
"from_agent": "my-orchestrator",
"to_agent": "azure-blob-agent-a2a",
"message_type": "request",
"action": "list_blobs",
"parameters": {
"container_name": "mycontainer"
},
"context": {
"user_id": "user123",
"tenant_id": "tenant-001"
},
"timestamp": datetime.utcnow().isoformat()
}
response = requests.post(
"http://localhost:8080/a2a/message",
json=message
)
print(response.json())
```
**响应示例**:
```json
{
"message_id": "msg-001",
"from_agent": "azure-blob-agent-a2a",
"to_agent": "orchestrator",
"message_type": "response",
"status": "success",
"result": {
"blobs": [
{"name": "file1.txt", "size": 1024},
{"name": "file2.pdf", "size": 2048}
]
},
"timestamp": "2026-01-15T10:30:05Z"
}
```
---
### 4. A2A 自然语言查询
**端点**: `POST /a2a/query`
使用自然语言查询,支持 A2A 上下文。
**请求体**:
```json
{
"query": "列出所有容器中的文件",
"container_name": "mycontainer",
"requester_agent": "orchestrator-agent",
"context": {
"user_id": "user123",
"session_id": "sess-456"
}
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/a2a/query \
-H "Content-Type: application/json" \
-d '{
"query": "列出所有容器",
"requester_agent": "orchestrator",
"context": {"user_id": "user123"}
}'
```
**请求示例** (Python):
```python
import requests
query_data = {
"query": "上传文件到 documents 容器",
"container_name": "documents",
"requester_agent": "file-processor",
"context": {
"user_id": "user123",
"file_path": "/tmp/report.pdf"
}
}
response = requests.post(
"http://localhost:8080/a2a/query",
json=query_data
)
print(response.json())
```
**响应示例**:
```json
{
"status": "success",
"query": "列出所有容器",
"answer": "找到 3 个容器: documents, images, backups",
"context": {
"containers": ["documents", "images", "backups"]
}
}
```
---
### 5. 获取已注册的 Agents
**端点**: `GET /a2a/agents`
**请求示例** (curl):
```bash
curl http://localhost:8080/a2a/agents
```
**响应示例**:
```json
{
"registered_agents": [
{
"agent_id": "search-agent",
"agent_role": "search_provider",
"capabilities": ["web_search", "content_extraction"],
"endpoint": "http://search-agent:8080"
},
{
"agent_id": "mysql-agent",
"agent_role": "database_manager",
"capabilities": ["sql_query", "data_analysis"],
"endpoint": "http://mysql-agent:8080"
}
]
}
```
---
## A2A 协作场景示例
### 场景 1: Orchestrator 协调多个 Agents
```python
import requests
# 1. Orchestrator 注册到 Blob Agent
orchestrator_info = {
"agent_id": "orchestrator-001",
"agent_role": "task_coordinator",
"capabilities": ["workflow", "coordination"],
"endpoint": "http://orchestrator:8080"
}
requests.post("http://blob-agent:8080/a2a/register", json=orchestrator_info)
# 2. Orchestrator 发送任务给 Blob Agent
task_message = {
"message_id": "task-001",
"from_agent": "orchestrator-001",
"to_agent": "azure-blob-agent-a2a",
"message_type": "request",
"action": "list_containers",
"parameters": {},
"context": {
"workflow_id": "wf-123",
"user_id": "user456"
}
}
response = requests.post("http://blob-agent:8080/a2a/message", json=task_message)
containers = response.json()
# 3. 基于结果继续下一步
for container in containers.get("result", {}).get("containers", []):
list_message = {
"message_id": f"task-{container}",
"from_agent": "orchestrator-001",
"to_agent": "azure-blob-agent-a2a",
"message_type": "request",
"action": "list_blobs",
"parameters": {"container_name": container},
"context": {"workflow_id": "wf-123"}
}
files = requests.post("http://blob-agent:8080/a2a/message", json=list_message)
print(f"Container {container}: {files.json()}")
```
---
### 场景 2: Agent 间数据传输
```python
import requests
# Search Agent 找到需要存储的内容
search_result = {
"content": "Important data from web search",
"source": "https://example.com"
}
# 通过 A2A 消息让 Blob Agent 存储结果
store_message = {
"message_id": "store-001",
"from_agent": "search-agent",
"to_agent": "azure-blob-agent-a2a",
"message_type": "request",
"action": "upload_blob",
"parameters": {
"container_name": "search-results",
"blob_name": "result-2026-01-15.json",
"content": search_result
},
"context": {
"source_agent": "search-agent",
"timestamp": "2026-01-15T10:30:00Z"
}
}
response = requests.post(
"http://blob-agent:8080/a2a/message",
json=store_message
)
print(response.json())
```
---
## 环境变量配置
```bash
# 服务配置
export SERVICE_HOST="0.0.0.0"
export SERVICE_PORT="8080"
export POD_NAME="azure-blob-agent-a2a"
export TEMPLATE_TYPE="azure_blob_agent_a2a"
export AGENT_FRAMEWORK="a2a"
# A2A Agent 配置
export AGENT_ID="azure-blob-agent-a2a"
export AGENT_ROLE="storage_manager"
export AGENT_CAPABILITIES='["blob_storage", "file_operations"]'
# 模型配置
export MODEL_PROVIDER="openai"
export MODEL_NAME="gpt-4"
export MODEL_API_KEY="sk-xxx"
export MODEL_ENDPOINT="https://api.openai.com/v1"
# 存储配置
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=https;..."
export STORAGE_ACCOUNT_NAME="myaccount"
# 用户标识
export USER_ID="default-user"
export TENANT_ID="tenant-001"
export NAMESPACE="ai-agents"
# 启动服务
python azure_blob_agent_a2a.py
```
---
## 注意事项
1. **Agent 注册**: 协作前需要先注册其他 Agent
2. **消息格式**: 严格遵循 A2A 消息格式
3. **异步通信**: 支持异步消息传递
4. **安全性**: 建议在生产环境中添加身份验证
5. **超时处理**: 设置合理的超时时间
6. **错误重试**: 实现重试机制处理网络问题
@@ -0,0 +1,273 @@
# Azure Blob Agent 请求调用示例
## 服务信息
- **服务名称**: Azure Blob Storage AI Agent
- **版本**: 1.0.0
- **框架**: LangChain + LiteLLM
- **默认端口**: 8080
## API 端点
### 1. 健康检查
**端点**: `GET /health`
**请求示例** (curl):
```bash
curl http://localhost:8080/health
```
**请求示例** (Python):
```python
import requests
response = requests.get("http://localhost:8080/health")
print(response.json())
```
**响应示例**:
```json
{
"status": "healthy",
"connected": true,
"connection_info": {
"account_kind": "StorageV2",
"sku_name": "Standard_LRS"
}
}
```
---
### 2. 连接到 Azure Blob Storage
**端点**: `POST /connect`
**请求体**:
```json
{
"connection_string": "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=xxx;EndpointSuffix=core.windows.net"
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/connect \
-H "Content-Type: application/json" \
-d '{
"connection_string": "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=your_key;EndpointSuffix=core.windows.net"
}'
```
**请求示例** (Python):
```python
import requests
connection_data = {
"connection_string": "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=your_key;EndpointSuffix=core.windows.net"
}
response = requests.post(
"http://localhost:8080/connect",
json=connection_data
)
print(response.json())
```
**响应示例**:
```json
{
"status": "connected",
"message": "成功连接到Azure Blob Storage",
"account_info": {
"account_kind": "StorageV2",
"sku_name": "Standard_LRS"
}
}
```
---
### 3. 自然语言查询/操作
**端点**: `POST /query`
**请求体**:
```json
{
"query": "列出所有容器",
"litellm_api_key": "your-litellm-api-key",
"user_id": "user123",
"container_name": "mycontainer"
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/query \
-H "Content-Type: application/json" \
-d '{
"query": "列出所有容器",
"litellm_api_key": "sk-xxx",
"user_id": "user123"
}'
```
**请求示例** (Python):
```python
import requests
query_data = {
"query": "列出 mycontainer 容器中的所有文件",
"litellm_api_key": "sk-xxx",
"user_id": "user123",
"container_name": "mycontainer"
}
response = requests.post(
"http://localhost:8080/query",
json=query_data
)
print(response.json())
```
**查询示例**:
1. 列出所有容器: `"列出所有容器"`
2. 列出容器中的文件: `"列出 mycontainer 容器中的所有文件"`
3. 上传文件: `"上传 test.txt 文件到 mycontainer 容器"`
4. 下载文件: `"下载 mycontainer/test.txt 文件"`
5. 删除文件: `"删除 mycontainer/old-file.txt"`
**响应示例**:
```json
{
"status": "success",
"query": "列出所有容器",
"answer": "容器列表:\n1. container1 (最后修改: 2026-01-15 10:30:00)\n2. container2 (最后修改: 2026-01-14 15:20:00)",
"intermediate_steps": "[...]"
}
```
---
### 4. 服务信息
**端点**: `GET /`
**请求示例** (curl):
```bash
curl http://localhost:8080/
```
**响应示例**:
```json
{
"service": "Azure Blob Storage AI Agent",
"version": "1.0.0",
"pod_name": "azure-blob-agent",
"template": "azure_blob_agent",
"connected": true,
"endpoints": {
"health": "/health",
"connect": "POST /connect",
"query": "POST /query"
}
}
```
---
## 完整使用流程示例
### Python 完整示例:
```python
import requests
# 服务地址
base_url = "http://localhost:8080"
# 1. 检查服务健康状态
health = requests.get(f"{base_url}/health")
print("Health:", health.json())
# 2. 连接到 Azure Blob Storage
connect_data = {
"connection_string": "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=your_key;EndpointSuffix=core.windows.net"
}
connect_response = requests.post(f"{base_url}/connect", json=connect_data)
print("Connect:", connect_response.json())
# 3. 执行查询 - 列出所有容器
query_data = {
"query": "列出所有容器",
"litellm_api_key": "sk-xxx",
"user_id": "user123"
}
query_response = requests.post(f"{base_url}/query", json=query_data)
print("Query Result:", query_response.json())
# 4. 执行操作 - 列出容器中的文件
list_files_data = {
"query": "列出 mycontainer 容器中的所有文件",
"litellm_api_key": "sk-xxx",
"user_id": "user123",
"container_name": "mycontainer"
}
files_response = requests.post(f"{base_url}/query", json=list_files_data)
print("Files:", files_response.json())
```
---
## 环境变量配置
启动服务时可配置的环境变量:
```bash
# 服务配置
export SERVICE_HOST="0.0.0.0"
export SERVICE_PORT="8080"
export POD_NAME="azure-blob-agent"
export TEMPLATE_TYPE="azure_blob_agent"
# LiteLLM 配置
export LITELLM_API_BASE="http://localhost:4000"
export LITELLM_MODEL="gpt-3.5-turbo"
# Azure Storage 连接字符串 (可选,也可通过 API 连接)
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=https;AccountName=xxx;AccountKey=xxx;EndpointSuffix=core.windows.net"
# 启动服务
python azure_blob_agent.py
```
---
## 错误处理
### 未连接错误:
```json
{
"detail": "未连接到Azure Blob Storage,请先调用 /connect"
}
```
### 连接失败:
```json
{
"detail": "连接失败: Invalid connection string"
}
```
### 查询失败:
```json
{
"detail": "查询失败: Container not found"
}
```
---
## 注意事项
1. **API Key 安全**: litellm_api_key 从请求中传入,不建议硬编码
2. **连接字符串**: 建议使用环境变量或密钥管理服务
3. **用户ID**: 用于计费回调,可选参数
4. **容器名称**: 某些操作需要指定容器名称
5. **并发请求**: 服务支持并发请求处理
@@ -0,0 +1,490 @@
# Azure Blob Agent MCP 请求调用示例
## 服务信息
- **服务名称**: Azure Blob Storage AI Agent (MCP)
- **版本**: 1.0.0
- **框架**: Model Context Protocol (MCP)
- **默认端口**: 8080
## 概述
MCP 版本使用 Model Context Protocol 协议实现智能文件操作,提供标准化的工具调用接口。
---
## API 端点
### 1. 健康检查
**端点**: `GET /health`
**请求示例** (curl):
```bash
curl http://localhost:8080/health
```
**响应示例**:
```json
{
"status": "healthy",
"connected": true,
"framework": "mcp",
"user_id": "user123",
"namespace": "ai-agents",
"connection_info": {
"account_kind": "StorageV2",
"sku_name": "Standard_LRS"
}
}
```
---
### 2. 获取 MCP 工具列表
**端点**: `GET /mcp/tools`
列出所有可用的 MCP 工具及其参数。
**请求示例** (curl):
```bash
curl http://localhost:8080/mcp/tools
```
**请求示例** (Python):
```python
import requests
response = requests.get("http://localhost:8080/mcp/tools")
tools = response.json()
for tool in tools["tools"]:
print(f"Tool: {tool['name']}")
print(f"Description: {tool['description']}")
print(f"Parameters: {tool['parameters_schema']}")
```
**响应示例**:
```json
{
"tools": [
{
"name": "list_containers",
"description": "列出所有 Blob 容器",
"parameters_schema": {
"type": "object",
"properties": {},
"required": []
}
},
{
"name": "list_blobs",
"description": "列出容器中的所有 Blob",
"parameters_schema": {
"type": "object",
"properties": {
"container_name": {
"type": "string",
"description": "容器名称"
}
},
"required": ["container_name"]
}
},
{
"name": "upload_blob",
"description": "上传文件到 Blob 容器",
"parameters_schema": {
"type": "object",
"properties": {
"container_name": {"type": "string"},
"blob_name": {"type": "string"},
"content": {"type": "string"}
},
"required": ["container_name", "blob_name", "content"]
}
},
{
"name": "download_blob",
"description": "从 Blob 容器下载文件",
"parameters_schema": {
"type": "object",
"properties": {
"container_name": {"type": "string"},
"blob_name": {"type": "string"}
},
"required": ["container_name", "blob_name"]
}
}
]
}
```
---
### 3. 调用 MCP 工具
**端点**: `POST /mcp/tool`
调用特定的 MCP 工具。
**请求体**:
```json
{
"tool_name": "list_blobs",
"parameters": {
"container_name": "mycontainer"
}
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/mcp/tool \
-H "Content-Type: application/json" \
-d '{
"tool_name": "list_blobs",
"parameters": {
"container_name": "documents"
}
}'
```
**请求示例** (Python):
```python
import requests
# 示例 1: 列出容器
list_containers = {
"tool_name": "list_containers",
"parameters": {}
}
response = requests.post("http://localhost:8080/mcp/tool", json=list_containers)
print(response.json())
# 示例 2: 列出容器中的文件
list_blobs = {
"tool_name": "list_blobs",
"parameters": {
"container_name": "documents"
}
}
response = requests.post("http://localhost:8080/mcp/tool", json=list_blobs)
print(response.json())
# 示例 3: 上传文件
upload_blob = {
"tool_name": "upload_blob",
"parameters": {
"container_name": "documents",
"blob_name": "report.txt",
"content": "This is the report content"
}
}
response = requests.post("http://localhost:8080/mcp/tool", json=upload_blob)
print(response.json())
# 示例 4: 下载文件
download_blob = {
"tool_name": "download_blob",
"parameters": {
"container_name": "documents",
"blob_name": "report.txt"
}
}
response = requests.post("http://localhost:8080/mcp/tool", json=download_blob)
print(response.json())
```
**响应示例** (list_blobs):
```json
{
"status": "success",
"tool_name": "list_blobs",
"result": {
"blobs": [
{
"name": "file1.txt",
"size": 1024,
"last_modified": "2026-01-15T10:30:00Z"
},
{
"name": "file2.pdf",
"size": 2048,
"last_modified": "2026-01-14T15:20:00Z"
}
],
"count": 2
}
}
```
---
### 4. MCP 查询 (自然语言)
**端点**: `POST /mcp/query`
使用自然语言查询,MCP 会自动选择合适的工具。
**请求体**:
```json
{
"query": "显示 documents 容器中的所有文件",
"container_name": "documents",
"context": {
"user_id": "user123",
"session_id": "sess-456"
}
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/mcp/query \
-H "Content-Type: application/json" \
-d '{
"query": "列出所有容器",
"context": {"user_id": "user123"}
}'
```
**请求示例** (Python):
```python
import requests
query_data = {
"query": "上传一个名为 test.txt 的文件到 mycontainer,内容是 Hello World",
"context": {
"user_id": "user123",
"operation": "upload"
}
}
response = requests.post(
"http://localhost:8080/mcp/query",
json=query_data
)
print(response.json())
```
**响应示例**:
```json
{
"status": "success",
"query": "上传一个名为 test.txt 的文件到 mycontainer,内容是 Hello World",
"answer": "成功上传文件 test.txt 到容器 mycontainer",
"tools_used": ["upload_blob"],
"context": {
"container_name": "mycontainer",
"blob_name": "test.txt",
"size": 11
}
}
```
---
## 完整使用流程示例
### Python SDK 风格的完整示例:
```python
import requests
import json
class AzureBlobMCPClient:
"""Azure Blob MCP Agent 客户端"""
def __init__(self, base_url: str):
self.base_url = base_url.rstrip('/')
def health_check(self):
"""健康检查"""
response = requests.get(f"{self.base_url}/health")
return response.json()
def get_tools(self):
"""获取可用工具列表"""
response = requests.get(f"{self.base_url}/mcp/tools")
return response.json()
def call_tool(self, tool_name: str, parameters: dict):
"""调用工具"""
data = {
"tool_name": tool_name,
"parameters": parameters
}
response = requests.post(f"{self.base_url}/mcp/tool", json=data)
return response.json()
def query(self, query: str, context: dict = None):
"""自然语言查询"""
data = {
"query": query,
"context": context or {}
}
response = requests.post(f"{self.base_url}/mcp/query", json=data)
return response.json()
# 便捷方法
def list_containers(self):
"""列出所有容器"""
return self.call_tool("list_containers", {})
def list_blobs(self, container_name: str):
"""列出容器中的文件"""
return self.call_tool("list_blobs", {"container_name": container_name})
def upload_blob(self, container_name: str, blob_name: str, content: str):
"""上传文件"""
return self.call_tool("upload_blob", {
"container_name": container_name,
"blob_name": blob_name,
"content": content
})
def download_blob(self, container_name: str, blob_name: str):
"""下载文件"""
return self.call_tool("download_blob", {
"container_name": container_name,
"blob_name": blob_name
})
# 使用示例
client = AzureBlobMCPClient("http://localhost:8080")
# 1. 健康检查
print("Health:", client.health_check())
# 2. 获取工具列表
print("Tools:", client.get_tools())
# 3. 列出容器
containers = client.list_containers()
print("Containers:", containers)
# 4. 列出文件
files = client.list_blobs("documents")
print("Files:", files)
# 5. 上传文件
upload_result = client.upload_blob(
"documents",
"report.txt",
"This is my report content"
)
print("Upload:", upload_result)
# 6. 下载文件
download_result = client.download_blob("documents", "report.txt")
print("Download:", download_result)
# 7. 自然语言查询
query_result = client.query(
"统计 documents 容器中有多少个文件",
context={"user_id": "user123"}
)
print("Query:", query_result)
```
---
## MCP 协议集成示例
### 与 LangChain 集成:
```python
from langchain.tools import Tool
import requests
class MCPBlobTool:
"""MCP Blob 工具包装器"""
def __init__(self, base_url: str):
self.base_url = base_url
def _call_mcp_tool(self, tool_name: str, **kwargs):
response = requests.post(
f"{self.base_url}/mcp/tool",
json={"tool_name": tool_name, "parameters": kwargs}
)
return response.json()
def list_containers(self):
return self._call_mcp_tool("list_containers")
def list_blobs(self, container_name: str):
return self._call_mcp_tool("list_blobs", container_name=container_name)
# 创建 LangChain 工具
mcp_blob = MCPBlobTool("http://localhost:8080")
tools = [
Tool(
name="ListContainers",
func=mcp_blob.list_containers,
description="列出所有 Azure Blob 容器"
),
Tool(
name="ListBlobs",
func=lambda x: mcp_blob.list_blobs(x),
description="列出指定容器中的所有文件。输入: 容器名称"
)
]
# 在 LangChain Agent 中使用
from langchain.agents import initialize_agent
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(temperature=0)
agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True)
result = agent.run("列出所有容器,然后显示第一个容器中的文件")
print(result)
```
---
## 环境变量配置
```bash
# 服务配置
export SERVICE_HOST="0.0.0.0"
export SERVICE_PORT="8080"
export POD_NAME="azure-blob-agent-mcp"
export TEMPLATE_TYPE="azure_blob_agent_mcp"
export AGENT_FRAMEWORK="mcp"
# 模型配置
export MODEL_PROVIDER="openai"
export MODEL_NAME="gpt-4"
export MODEL_API_KEY="sk-xxx"
export MODEL_ENDPOINT="https://api.openai.com/v1"
# 工具配置 (JSON 格式)
export TOOLS_CONFIG='{
"blob_storage": {
"enabled": true,
"default_container": "documents"
}
}'
# 存储配置
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=https;..."
export STORAGE_ACCOUNT_NAME="myaccount"
# 用户标识
export USER_ID="default-user"
export TENANT_ID="tenant-001"
export NAMESPACE="ai-agents"
# 启动服务
python azure_blob_agent_mcp.py
```
---
## 注意事项
1. **工具发现**: 先调用 `/mcp/tools` 了解可用工具
2. **参数验证**: 严格按照工具的 schema 传递参数
3. **错误处理**: MCP 返回标准化的错误格式
4. **上下文传递**: 通过 context 传递会话信息
5. **异步支持**: 支持异步工具调用
6. **工具组合**: 可以组合多个工具完成复杂任务
@@ -0,0 +1,267 @@
# Agent 回调功能实现总结
## 概述
根据 `/home/taiji/tools/agent-manager/plans/LiteLLM和AgentManager回调接口文档.md` 的要求,已为所有 agent 模板添加了运行时长回调功能。
## 修改内容
### 1. 新增工具模块
**文件**: `agent_callback_utils.py`
提供两个核心类:
- **AgentCallbackHandler**: 回调处理器,负责记录请求开始/结束时间、使用的工具、并发送回调到 Agent Manager
- **CallbackContextManager**: 上下文管理器,支持 `with` 语句自动处理回调的开始和结束
**关键功能**:
- 自动记录运行时长(Pod running time)
- 记录使用的工具列表
- 向回调接口发送 POST 请求:`http://mcp-server:8002/api/v1/billing/agent-callback`
- 支持用户ID、请求ID追踪
### 2. API Key 传递方式改进
**之前**: 所有配置包括 API key 都从环境变量获取
**现在**:
- **API key**: 从用户请求中传入(每次请求携带)
- **其他配置**: 依然从环境变量获取(数据库连接信息、服务端口等)
### 3. 修改的 Agent 文件
#### 3.1 search_agent.py
**请求模型修改**:
```python
class SearchRequest(BaseModel):
query: str
llm_api_key: str # 新增:从请求传入
user_id: Optional[str] # 新增:用于计费回调
auto_configure: bool
```
**回调集成**:
- 使用 `CallbackContextManager` 自动处理回调
- 记录使用的工具:`web_search`, `content_reader`
- 临时更新 API key 后执行搜索,完成后恢复原值
#### 3.2 jina_search_agent.py
**请求模型修改**:
```python
class SearchRequest(BaseModel):
url: str
jina_api_key: str # 新增:从请求传入
user_id: Optional[str] # 新增
timeout: int
```
**回调集成**:
- 使用 `CallbackContextManager` 自动处理回调
- 记录使用的工具:`jina_reader`
- 移除了全局 `JINA_API_KEY` 环境变量依赖
#### 3.3 mysql_agent.py
**重大改动**: 从循环示例查询模式改为 FastAPI HTTP 服务
**请求模型**:
```python
class QueryRequest(BaseModel):
query: str
openai_api_key: str # 从请求传入
user_id: Optional[str] # 用于计费回调
model: str = "gpt-3.5-turbo"
```
**新增端点**:
- `GET /health` - 健康检查
- `POST /query` - 执行SQL查询(带回调)
- `GET /` - 服务信息
**回调集成**:
- 使用 `CallbackContextManager`
- 记录使用的工具:`sql_database`
#### 3.4 postgresql_agent.py
**修改内容与 mysql_agent.py 类似**
**请求模型**:
```python
class QueryRequest(BaseModel):
query: str
openai_api_key: str # 从请求传入
user_id: Optional[str] # 用于计费回调
model: str = "gpt-3.5-turbo"
```
**回调集成**: 同 MySQL Agent
## 回调接口规范
根据文档,每次请求结束后自动发送以下回调:
```json
{
"agentName": "pod-name",
"userId": "user-123",
"podRunningTimeSeconds": 120,
"toolsUsed": ["web_search", "content_reader"],
"startTime": "2026-01-15T10:00:00Z",
"endTime": "2026-01-15T10:02:00Z",
"requestId": "search-1737801600"
}
```
**回调地址**:
- 默认: `http://mcp-server:8002/api/v1/billing/agent-callback`
- 可通过环境变量 `AGENT_CALLBACK_URL` 覆盖
## 环境变量配置
### 必需环境变量(所有 Agent)
```bash
POD_NAME=agent-name # Pod 名称(用于回调)
USER_ID=default-user-id # 默认用户ID(可被请求中的user_id覆盖)
```
### 可选环境变量
```bash
AGENT_CALLBACK_URL=http://mcp-server:8002/api/v1/billing/agent-callback # 回调地址
```
### Agent 特定环境变量
**Search Agent**:
```bash
SERPER_API_KEY=xxx # Serper API(从环境变量)
JINA_API_KEY=xxx # Jina API(从环境变量,可被请求覆盖)
LLM_BASE_URL=xxx # LLM API基础URL
```
**Jina Search Agent**:
```bash
SERVICE_HOST=0.0.0.0
SERVICE_PORT=8080
```
**MySQL Agent**:
```bash
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=password
MYSQL_DATABASE=test
SERVICE_HOST=0.0.0.0
SERVICE_PORT=8080
```
**PostgreSQL Agent**:
```bash
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_DATABASE=postgres
SERVICE_HOST=0.0.0.0
SERVICE_PORT=8080
```
## 使用示例
### Search Agent
```bash
curl -X POST http://agent-ip:8080/search \
-H "Content-Type: application/json" \
-d '{
"query": "什么是人工智能?",
"llm_api_key": "sk-xxx",
"user_id": "user-123"
}'
```
### Jina Search Agent
```bash
curl -X POST http://agent-ip:8080/search \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"jina_api_key": "jina_xxx",
"user_id": "user-123"
}'
```
### MySQL/PostgreSQL Agent
```bash
curl -X POST http://agent-ip:8080/query \
-H "Content-Type: application/json" \
-d '{
"query": "列出所有表",
"openai_api_key": "sk-xxx",
"user_id": "user-123",
"model": "gpt-3.5-turbo"
}'
```
## 回调流程
1. **请求开始**:
- 创建 `CallbackContextManager` 上下文
- 记录开始时间
- 设置 user_id 和 request_id
2. **执行过程**:
- 临时更新 API key(如需要)
- 执行 Agent 逻辑
- 记录使用的工具(通过 `ctx.add_tool()`)
3. **请求结束**:
- 自动计算运行时长
- 发送 POST 请求到回调接口
- 包含所有必需字段(agentName, userId, podRunningTimeSeconds, toolsUsed, startTime, endTime, requestId)
4. **错误处理**:
- 回调失败不影响主流程
- 错误日志记录
## 注意事项
1. **API Key 安全**: API key 仅在请求期间临时使用,不持久化
2. **回调可选**: 如果 `user_id` 未提供,回调处理器会跳过发送
3. **幂等性**: 使用 `request_id` 确保回调幂等性
4. **时区**: 所有时间戳使用 UTC 时区
5. **兼容性**: 保持向后兼容,不强制要求 user_id
## 依赖要求
所有 Agent 需要添加以下 Python 包依赖:
```txt
fastapi
uvicorn
requests
pydantic
```
已有依赖的 Agent 无需额外安装。
## 测试建议
1. **单元测试**: 测试回调函数的正确性
2. **集成测试**: 验证回调接口能正常接收数据
3. **压力测试**: 确保回调不影响 Agent 性能
4. **错误测试**: 验证回调失败时 Agent 仍能正常工作
---
**修改完成时间**: 2026-01-15
**修改人**: GitHub Copilot
**版本**: v1.0
+194
View File
@@ -0,0 +1,194 @@
# Agent Templates 目录结构
## 概述
`agent_templates` 目录已重新组织,按照功能分类到不同的文件夹中,便于管理和维护。
## 目录结构
```
agent_templates/
├── agents/ # 所有 Agent 实现
│ ├── search_agent/ # 智能搜索 Agent
│ │ ├── search_agent.py
│ │ ├── search_agent_main.py
│ │ ├── search_agent.Dockerfile
│ │ └── search_agent/ # 搜索 Agent 核心模块
│ ├── jina_search_agent/ # Jina 搜索 Agent
│ │ ├── jina_search_agent.py
│ │ └── jina_search_agent.Dockerfile
│ ├── azure_blob_agent/ # Azure Blob 存储 Agent
│ │ ├── azure_blob_agent.py
│ │ └── azure_blob_agent.Dockerfile
│ ├── azure_blob_agent_a2a/ # Azure Blob Agent (A2A 协议)
│ │ ├── azure_blob_agent_a2a.py
│ │ └── azure_blob_agent_a2a.Dockerfile
│ ├── azure_blob_agent_mcp/ # Azure Blob Agent (MCP 协议)
│ │ ├── azure_blob_agent_mcp.py
│ │ └── azure_blob_agent_mcp.Dockerfile
│ ├── postgresql_agent/ # PostgreSQL 数据库 Agent
│ │ ├── postgresql_agent.py
│ │ └── postgresql_agent.Dockerfile
│ ├── mysql_agent/ # MySQL 数据库 Agent
│ │ ├── mysql_agent.py
│ │ └── mysql_agent.Dockerfile
│ └── a2a_litellm_agent/ # A2A LiteLLM Agent
│ ├── a2a_server.py
│ ├── agent.py
│ ├── config.py
│ ├── main.py
│ ├── requirements.txt
│ ├── a2a_litellm_agent.Dockerfile
│ └── __init__.py
│
├── common/ # 共享代码和工具
│ ├── agent_callback_utils.py # Agent 回调工具
│ ├── api_key_utils.py # API Key 配置工具
│ ├── requirements_a2a.txt # A2A 协议依赖
│ ├── requirements_mcp.txt # MCP 协议依赖
│ └── Dockerfile.test # 测试 Dockerfile
│
├── docs/ # 文档文件
│ ├── A2A_LITELLM_AGENT_USAGE.md
│ ├── API_KEY_CONFIGURATION_SUMMARY.md
│ ├── AZURE_BLOB_AGENT_*.md
│ ├── SEARCH_AGENT_*.md
│ ├── JINA_SEARCH_AGENT_EXAMPLES.md
│ ├── MYSQL_AGENT_EXAMPLES.md
│ ├── POSTGRESQL_AGENT_EXAMPLES.md
│ └── ...
│
├── scripts/ # 构建和工具脚本
│ ├── build_*.sh # 各 Agent 的构建脚本
│ ├── build_all_agents.sh # 批量构建脚本
│ ├── rebuild_all.sh # 重建所有 Agent
│ └── check_image_content.sh # 镜像内容检查
│
└── tests/ # 测试文件
├── test_*.py # Python 测试文件
├── test_*.sh # Shell 测试脚本
└── test_client.py # 测试客户端
```
## 各目录说明
### agents/
包含所有 Agent 的实现代码。每个 Agent 都有自己的子目录,包含:
- Agent 主程序文件(`.py`)
- Dockerfile(`.Dockerfile`)
- 相关的配置和依赖文件
### common/
包含所有 Agent 共享的工具代码和依赖:
- `agent_callback_utils.py` - Agent 回调处理工具
- `api_key_utils.py` - 统一的 API Key 配置管理
- `requirements_*.txt` - 各协议的依赖文件
### docs/
包含所有文档文件:
- 使用指南(`*_USAGE.md`)
- 示例文档(`*_EXAMPLES.md`)
- 配置说明(`*_SUMMARY.md`)
### scripts/
包含构建和工具脚本:
- 各 Agent 的独立构建脚本
- 批量构建脚本
- 工具脚本
### tests/
包含测试文件:
- Python 单元测试
- 集成测试脚本
- 测试客户端
## 使用说明
### 构建单个 Agent
```bash
cd agent_templates
./scripts/build_search_agent.sh v1.0
```
### 构建所有 Agent
```bash
cd agent_templates
./scripts/build_all_agents.sh v1.0
```
### 查看文档
```bash
# 查看搜索 Agent 使用指南
cat docs/SEARCH_AGENT_USAGE.md
# 查看 API Key 配置说明
cat docs/API_KEY_CONFIGURATION_SUMMARY.md
```
## 导入路径说明
### 在 Agent 代码中导入共享工具
```python
# 在 Docker 容器中运行时,common/ 目录会被复制到 /app/common/
from common.agent_callback_utils import AgentCallbackHandler
from common.api_key_utils import get_llm_api_key
```
### 在本地开发时
```python
# 需要将 common/ 目录添加到 Python 路径
import sys
sys.path.insert(0, '../common')
from agent_callback_utils import AgentCallbackHandler
```
## Dockerfile 路径更新
所有构建脚本已更新为使用新的路径结构:
```bash
# 旧路径
-f search_agent.Dockerfile
# 新路径
-f agents/search_agent/search_agent.Dockerfile
```
## 迁移指南
如果您有现有的脚本或代码引用旧路径,请更新为:
| 旧路径 | 新路径 |
|--------|--------|
| `search_agent.py` | `agents/search_agent/search_agent.py` |
| `search_agent.Dockerfile` | `agents/search_agent/search_agent.Dockerfile` |
| `agent_callback_utils.py` | `common/agent_callback_utils.py` |
| `api_key_utils.py` | `common/api_key_utils.py` |
| `*.md` | `docs/*.md` |
| `build_*.sh` | `scripts/build_*.sh` |
| `test_*.py` | `tests/test_*.py` |
## 注意事项
1. **构建脚本**: 所有构建脚本已更新路径,可以直接使用
2. **Dockerfile**: 需要确保 Dockerfile 中的 COPY 路径正确
3. **导入路径**: 在容器中运行时,common/ 目录会被复制到正确位置
4. **文档**: 所有文档已移动到 docs/ 目录
## 维护建议
1. **添加新 Agent**: 在 `agents/` 目录下创建新的子目录
2. **共享代码**: 放在 `common/` 目录
3. **文档**: 放在 `docs/` 目录
4. **脚本**: 放在 `scripts/` 目录
5. **测试**: 放在 `tests/` 目录
+293
View File
@@ -0,0 +1,293 @@
# Agent Templates 请求调用示例文档索引
本目录包含所有 Agent 的详细请求调用示例文档。
---
## 📚 文档列表
### 1. Azure Blob Storage Agents
#### 1.1 Azure Blob Agent (标准版)
**文件**: [AZURE_BLOB_AGENT_EXAMPLES.md](./AZURE_BLOB_AGENT_EXAMPLES.md)
- **框架**: LangChain + LiteLLM
- **功能**: 智能 Azure Blob 存储管理
- **端点**:
- `GET /health` - 健康检查
- `POST /connect` - 连接存储
- `POST /query` - 自然语言查询
- **特点**: 支持容器管理、文件上传下载、智能搜索
#### 1.2 Azure Blob Agent A2A (Agent-to-Agent)
**文件**: [AZURE_BLOB_AGENT_A2A_EXAMPLES.md](./AZURE_BLOB_AGENT_A2A_EXAMPLES.md)
- **框架**: A2A (Agent-to-Agent)
- **功能**: 支持 Agent 间协作的存储管理
- **端点**:
- `POST /a2a/register` - 注册协作 Agent
- `POST /a2a/message` - A2A 消息通信
- `POST /a2a/query` - A2A 自然语言查询
- `GET /a2a/agents` - 获取已注册的 Agents
- **特点**: Agent 间通信、协作任务、消息传递
#### 1.3 Azure Blob Agent MCP (Model Context Protocol)
**文件**: [AZURE_BLOB_AGENT_MCP_EXAMPLES.md](./AZURE_BLOB_AGENT_MCP_EXAMPLES.md)
- **框架**: MCP (Model Context Protocol)
- **功能**: 基于 MCP 协议的存储管理
- **端点**:
- `GET /mcp/tools` - 获取工具列表
- `POST /mcp/tool` - 调用 MCP 工具
- `POST /mcp/query` - MCP 自然语言查询
- **特点**: 标准化工具接口、与 LangChain 集成
---
### 2. Search Agents
#### 2.1 Search Agent (智能搜索)
**文件**: [SEARCH_AGENT_EXAMPLES.md](./SEARCH_AGENT_EXAMPLES.md)
- **框架**: LangChain + Serper + Jina
- **功能**: 智能网络搜索和问答
- **端点**:
- `GET /health` - 健康检查
- `POST /configure` - 配置 Agent
- `POST /search` - 执行搜索
- **特点**: Google 搜索、内容提取、智能答案生成
- **需要**: Serper API Key, Jina API Key
#### 2.2 Jina Search Agent (网页内容提取)
**文件**: [JINA_SEARCH_AGENT_EXAMPLES.md](./JINA_SEARCH_AGENT_EXAMPLES.md)
- **框架**: Jina Reader API
- **功能**: 网页内容提取和解析
- **端点**:
- `GET /health` - 健康检查
- `POST /search` - 提取网页内容
- **特点**: 纯文本提取、内容清理、快速响应
- **需要**: Jina API Key
---
### 3. Database Agents
#### 3.1 MySQL Agent
**文件**: [MYSQL_AGENT_EXAMPLES.md](./MYSQL_AGENT_EXAMPLES.md)
- **框架**: LangChain + OpenAI
- **功能**: MySQL 数据库自然语言查询
- **端点**:
- `GET /health` - 健康检查
- `POST /query` - 自然语言查询
- **特点**: SQL 自动生成、智能查询、数据分析
- **需要**: MySQL 数据库, OpenAI API Key
#### 3.2 PostgreSQL Agent
**文件**: [POSTGRESQL_AGENT_EXAMPLES.md](./POSTGRESQL_AGENT_EXAMPLES.md)
- **框架**: LangChain + OpenAI
- **功能**: PostgreSQL 数据库自然语言查询
- **端点**:
- `GET /health` - 健康检查
- `POST /query` - 自然语言查询
- **特点**: 支持 PostgreSQL 特性 (JSON, 数组, 全文搜索等)
- **需要**: PostgreSQL 数据库, OpenAI API Key
---
## 🚀 快速开始
### 选择合适的 Agent
**存储管理**:
- 基础使用 → `Azure Blob Agent`
- Agent 协作 → `Azure Blob Agent A2A`
- 工具集成 → `Azure Blob Agent MCP`
**搜索功能**:
- 智能问答 → `Search Agent`
- 内容提取 → `Jina Search Agent`
**数据库查询**:
- MySQL → `MySQL Agent`
- PostgreSQL → `PostgreSQL Agent`
---
## 📖 文档结构
每个示例文档包含:
1. **服务信息** - 基本配置和描述
2. **API 端点** - 所有可用端点和参数
3. **请求示例** - curl 和 Python 示例
4. **响应示例** - 标准响应格式
5. **完整使用流程** - 端到端示例
6. **环境变量配置** - 必需的配置项
7. **注意事项** - 最佳实践和限制
---
## 💡 使用建议
### 通用模式
所有 Agent 都遵循类似的模式:
```python
import requests
# 1. 健康检查
health = requests.get("http://localhost:8080/health")
print(health.json())
# 2. 执行操作 (具体端点因 Agent 而异)
result = requests.post(
"http://localhost:8080/query", # 或其他端点
json={
"query": "your query here",
"api_key": "your-api-key", # API key 名称因 Agent 而异
"user_id": "user123"
}
)
print(result.json())
```
### API Key 管理
不同的 Agent 需要不同的 API Keys:
| Agent | 需要的 API Keys |
|-------|----------------|
| Azure Blob Agent | LiteLLM API Key, Azure Connection String |
| Azure Blob Agent A2A | Model API Key, Azure Connection String |
| Azure Blob Agent MCP | Model API Key, Azure Connection String |
| Search Agent | LLM API Key, Serper API Key, Jina API Key |
| Jina Search Agent | Jina API Key |
| MySQL Agent | OpenAI API Key |
| PostgreSQL Agent | OpenAI API Key |
---
## 🔧 环境配置示例
### Azure Blob Agent
```bash
export LITELLM_API_BASE="http://localhost:4000"
export LITELLM_MODEL="gpt-3.5-turbo"
export AZURE_STORAGE_CONNECTION_STRING="..."
python azure_blob_agent.py
```
### Search Agent
```bash
export LLM_BASE_URL="http://localhost:4000"
export SERPER_API_KEY="your-key"
export JINA_API_KEY="your-key"
python search_agent.py
```
### MySQL/PostgreSQL Agent
```bash
export MYSQL_HOST="localhost"
export MYSQL_DATABASE="mydb"
# 或
export POSTGRES_HOST="localhost"
export POSTGRES_DATABASE="mydb"
python mysql_agent.py
```
---
## 🐳 Docker 部署
所有 Agent 都提供 Dockerfile:
```bash
# 构建镜像
docker build -f azure_blob_agent.Dockerfile -t azure-blob-agent .
# 运行容器
docker run -p 8080:8080 \
-e LITELLM_API_BASE="http://host.docker.internal:4000" \
-e AZURE_STORAGE_CONNECTION_STRING="..." \
azure-blob-agent
```
---
## 📊 功能对比
| 功能 | Azure Blob | Azure A2A | Azure MCP | Search | Jina | MySQL | PostgreSQL |
|------|-----------|-----------|-----------|---------|------|-------|------------|
| 存储管理 | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Agent 协作 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 工具标准化 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| 网络搜索 | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| 内容提取 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
| 数据库查询 | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| 自然语言 | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
---
## 🔗 相关文档
- [Agent Manager API 文档](../plans/API_DOCUMENTATION.md)
- [多框架支持指南](./MULTI_FRAMEWORK_GUIDE.md)
- [快速开始](./QUICKSTART.md)
- [快速参考](./QUICK_REFERENCE.md)
---
## 📝 示例代码仓库
每个文档都包含完整的 Python 客户端示例,可直接使用:
```python
# 标准模式 - 所有 Agent 通用
from agent_templates.examples import create_client
# 创建客户端
client = create_client(
agent_type="azure_blob", # 或 "search", "mysql", "postgresql"
base_url="http://localhost:8080",
api_key="your-key"
)
# 使用客户端
result = client.query("your query")
print(result)
```
---
## ⚠️ 注意事项
1. **API Key 安全**: 不要在代码中硬编码 API Keys
2. **速率限制**: 注意各 API 服务的速率限制
3. **错误处理**: 始终检查响应的 `success` 字段
4. **超时设置**: 根据操作复杂度设置合理的超时时间
5. **成本控制**: 监控 API 使用量以控制成本
6. **并发限制**: 避免过多并发请求
---
## 🆘 获取帮助
遇到问题?
1. 查看对应的示例文档
2. 检查健康检查端点 (`GET /health`)
3. 查看服务日志
4. 确认环境变量配置正确
5. 验证 API Keys 有效性
---
## 📅 更新日志
- **2026-01-15**: 创建所有 Agent 的示例文档
- Azure Blob Agent (标准版、A2A、MCP)
- Search Agent
- Jina Search Agent
- MySQL Agent
- PostgreSQL Agent
---
**Happy Coding! 🎉**
@@ -0,0 +1,503 @@
# Jina Search Agent 请求调用示例
## 服务信息
- **服务名称**: Jina Search Agent
- **版本**: 1.0.0
- **功能**: 使用 Jina Reader API 获取网站内容
- **默认端口**: 8080
## 概述
Jina Search Agent 使用 Jina Reader API 提取和解析网页内容,返回纯文本格式的内容,适合用于内容分析、摘要生成等场景。
---
## API 端点
### 1. 健康检查
**端点**: `GET /health`
**请求示例** (curl):
```bash
curl http://localhost:8080/health
```
**响应示例**:
```json
{
"status": "healthy",
"pod_name": "jina-search-agent",
"template_type": "jina_search_agent",
"jina_api_configured": true
}
```
---
### 2. 服务信息
**端点**: `GET /`
**请求示例** (curl):
```bash
curl http://localhost:8080/
```
**响应示例**:
```json
{
"service": "Jina Search Agent",
"version": "1.0.0",
"description": "使用Jina Reader API获取网站内容的AI Agent",
"pod_name": "jina-search-agent",
"template_type": "jina_search_agent",
"required_env": {
"JINA_API_KEY": {
"description": "Jina API密钥,从 https://jina.ai/ 获取",
"required": true,
"configured": true
}
},
"optional_env": {
"SERVICE_PORT": {
"description": "HTTP服务端口",
"default": "8080"
}
}
}
```
---
### 3. 搜索/提取网页内容
**端点**: `POST /search`
使用 Jina Reader API 提取指定 URL 的网页内容。
**请求体**:
```json
{
"url": "https://example.com",
"jina_api_key": "your-jina-api-key",
"user_id": "user123",
"timeout": 30
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/search \
-H "Content-Type: application/json" \
-d '{
"url": "https://python.org",
"jina_api_key": "jina_xxx",
"user_id": "user123"
}'
```
**请求示例** (Python):
```python
import requests
search_data = {
"url": "https://www.python.org/",
"jina_api_key": "jina_xxx",
"user_id": "user123",
"timeout": 30
}
response = requests.post(
"http://localhost:8080/search",
json=search_data
)
result = response.json()
if result["success"]:
print(f"URL: {result['url']}")
print(f"Content Type: {result['content_type']}")
print(f"Content Length: {len(result['content'])}")
print(f"\nContent Preview:\n{result['content'][:500]}...")
else:
print(f"Error: {result.get('error')}")
```
**响应示例**:
```json
{
"url": "https://www.python.org/",
"content": "Welcome to Python.org\n\nPython is a programming language that lets you work quickly and integrate systems more effectively...\n\n# Latest News\n- Python 3.12 Released\n- PyCon 2026 Announced\n...",
"status_code": 200,
"content_type": "text/plain",
"success": true
}
```
---
## 完整使用示例
### Python 完整示例:
```python
import requests
import json
class JinaSearchClient:
"""Jina Search Agent 客户端"""
def __init__(self, base_url: str, jina_api_key: str):
self.base_url = base_url.rstrip('/')
self.jina_api_key = jina_api_key
def health_check(self):
"""健康检查"""
response = requests.get(f"{self.base_url}/health")
return response.json()
def get_info(self):
"""获取服务信息"""
response = requests.get(f"{self.base_url}/")
return response.json()
def fetch_content(self, url: str, user_id: str = None, timeout: int = 30):
"""提取网页内容"""
data = {
"url": url,
"jina_api_key": self.jina_api_key,
"user_id": user_id,
"timeout": timeout
}
response = requests.post(f"{self.base_url}/search", json=data)
return response.json()
# 使用示例
client = JinaSearchClient(
base_url="http://localhost:8080",
jina_api_key="jina_xxx"
)
# 1. 健康检查
print("Health:", client.health_check())
# 2. 获取服务信息
print("Info:", client.get_info())
# 3. 提取网页内容
urls = [
"https://www.python.org/",
"https://kubernetes.io/docs/",
"https://docs.docker.com/",
"https://github.com/",
]
for url in urls:
print(f"\n{'='*60}")
print(f"Fetching: {url}")
print('='*60)
result = client.fetch_content(url, user_id="user123")
if result["success"]:
print(f"Status: {result['status_code']}")
print(f"Content Type: {result['content_type']}")
print(f"Content Length: {len(result['content'])} chars")
print(f"\nContent Preview:")
print(result['content'][:300])
print("...")
else:
print(f"Error: Failed to fetch content")
```
---
### 批量内容提取:
```python
import requests
import concurrent.futures
from typing import List, Dict
def fetch_url(url: str, jina_api_key: str, user_id: str = None) -> Dict:
"""提取单个 URL 的内容"""
try:
response = requests.post(
"http://localhost:8080/search",
json={
"url": url,
"jina_api_key": jina_api_key,
"user_id": user_id,
"timeout": 30
},
timeout=60
)
result = response.json()
return {
"url": url,
"success": result.get("success", False),
"content": result.get("content", ""),
"error": None
}
except Exception as e:
return {
"url": url,
"success": False,
"content": "",
"error": str(e)
}
# 批量提取
urls = [
"https://www.python.org/",
"https://www.docker.com/",
"https://kubernetes.io/",
"https://www.tensorflow.org/",
"https://pytorch.org/"
]
jina_api_key = "jina_xxx"
# 并行提取
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
futures = [
executor.submit(fetch_url, url, jina_api_key, f"user-{i}")
for i, url in enumerate(urls)
]
results = [future.result() for future in concurrent.futures.as_completed(futures)]
# 处理结果
successful = [r for r in results if r["success"]]
failed = [r for r in results if not r["success"]]
print(f"Successful: {len(successful)}/{len(urls)}")
print(f"Failed: {len(failed)}/{len(urls)}")
for result in successful:
print(f"\n{result['url']}")
print(f"Content length: {len(result['content'])} chars")
print(f"Preview: {result['content'][:100]}...")
```
---
### 与 LLM 结合进行内容分析:
```python
import requests
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
class ContentAnalyzer:
"""使用 Jina 提取内容并用 LLM 分析"""
def __init__(self, jina_base_url: str, jina_api_key: str, openai_api_key: str):
self.jina_base_url = jina_base_url
self.jina_api_key = jina_api_key
self.llm = ChatOpenAI(
temperature=0,
model="gpt-3.5-turbo",
openai_api_key=openai_api_key
)
def fetch_content(self, url: str) -> str:
"""使用 Jina 提取内容"""
response = requests.post(
f"{self.jina_base_url}/search",
json={
"url": url,
"jina_api_key": self.jina_api_key
}
)
result = response.json()
return result.get("content", "") if result.get("success") else ""
def summarize(self, url: str) -> str:
"""提取并摘要网页内容"""
content = self.fetch_content(url)
if not content:
return "无法提取内容"
prompt = PromptTemplate(
input_variables=["content"],
template="请用中文总结以下网页内容,保持简洁明了:\n\n{content}\n\n摘要:"
)
# 限制内容长度
content = content[:4000]
result = self.llm.invoke(prompt.format(content=content))
return result.content
def extract_key_points(self, url: str) -> List[str]:
"""提取关键要点"""
content = self.fetch_content(url)
if not content:
return []
prompt = PromptTemplate(
input_variables=["content"],
template="请从以下内容中提取5个关键要点,每个要点一行:\n\n{content}\n\n关键要点:"
)
content = content[:4000]
result = self.llm.invoke(prompt.format(content=content))
# 解析要点
points = [line.strip() for line in result.content.split('\n') if line.strip()]
return points
# 使用示例
analyzer = ContentAnalyzer(
jina_base_url="http://localhost:8080",
jina_api_key="jina_xxx",
openai_api_key="sk-xxx"
)
# 摘要网页
url = "https://www.python.org/about/"
summary = analyzer.summarize(url)
print(f"URL: {url}")
print(f"Summary: {summary}")
# 提取关键要点
key_points = analyzer.extract_key_points(url)
print("\nKey Points:")
for i, point in enumerate(key_points, 1):
print(f"{i}. {point}")
```
---
### 监控和内容变更检测:
```python
import requests
import time
import hashlib
from datetime import datetime
class ContentMonitor:
"""监控网页内容变化"""
def __init__(self, jina_base_url: str, jina_api_key: str):
self.jina_base_url = jina_base_url
self.jina_api_key = jina_api_key
self.cache = {}
def get_content_hash(self, url: str) -> str:
"""获取内容哈希值"""
response = requests.post(
f"{self.jina_base_url}/search",
json={
"url": url,
"jina_api_key": self.jina_api_key
}
)
result = response.json()
if result.get("success"):
content = result["content"]
return hashlib.md5(content.encode()).hexdigest()
return ""
def check_updates(self, urls: List[str]) -> Dict:
"""检查 URL 列表是否有更新"""
updates = {}
for url in urls:
current_hash = self.get_content_hash(url)
previous_hash = self.cache.get(url)
if previous_hash is None:
updates[url] = {"status": "new", "hash": current_hash}
elif current_hash != previous_hash:
updates[url] = {"status": "updated", "hash": current_hash}
else:
updates[url] = {"status": "unchanged", "hash": current_hash}
self.cache[url] = current_hash
return updates
# 使用示例
monitor = ContentMonitor(
jina_base_url="http://localhost:8080",
jina_api_key="jina_xxx"
)
urls_to_monitor = [
"https://www.python.org/downloads/",
"https://kubernetes.io/blog/",
"https://github.com/trending"
]
# 定期检查更新
while True:
print(f"\n[{datetime.now()}] Checking for updates...")
updates = monitor.check_updates(urls_to_monitor)
for url, info in updates.items():
if info["status"] == "updated":
print(f"⚠️ UPDATED: {url}")
elif info["status"] == "new":
print(f"🆕 NEW: {url}")
else:
print(f"✓ No change: {url}")
# 每 5 分钟检查一次
time.sleep(300)
```
---
## 环境变量配置
```bash
# 服务配置
export SERVICE_HOST="0.0.0.0"
export SERVICE_PORT="8080"
export POD_NAME="jina-search-agent"
export TEMPLATE_TYPE="jina_search_agent"
# Jina API (从请求传入,也可以预配置)
# export JINA_API_KEY="jina_xxx"
# 启动服务
python jina_search_agent.py
```
---
## 获取 Jina API Key
1. 访问: https://jina.ai/
2. 注册账号
3. 在控制台获取 API key
4. 免费套餐: 1,000 次请求/天
---
## 支持的网站类型
Jina Reader API 支持多种网站:
- 新闻网站
- 博客文章
- 文档网站
- GitHub 页面
- 维基百科
- 论文网站 (arXiv, etc.)
---
## 注意事项
1. **API Key**: 从请求中传入,保证安全性
2. **速率限制**: 注意 Jina API 的速率限制
3. **超时设置**: 大型网页可能需要更长时间
4. **内容格式**: 返回纯文本格式,已清理 HTML
5. **用户ID**: 可选,用于计费回调
6. **错误处理**: 检查 success 字段确认是否成功
7. **并发限制**: 建议最多 3-5 个并发请求
@@ -0,0 +1,537 @@
# MySQL Agent 请求调用示例
## 服务信息
- **服务名称**: MySQL AI Agent
- **版本**: 1.0.0
- **框架**: LangChain + OpenAI
- **默认端口**: 8080
## 概述
MySQL AI Agent 使用 LangChain 和自然语言处理技术,允许用户使用自然语言查询 MySQL 数据库。
---
## API 端点
### 1. 健康检查
**端点**: `GET /health`
**请求示例** (curl):
```bash
curl http://localhost:8080/health
```
**响应示例**:
```json
{
"status": "healthy",
"pod_name": "mysql-agent",
"template_type": "mysql_agent",
"database_connected": true,
"database_info": "localhost:3306/mydb"
}
```
---
### 2. 服务信息
**端点**: `GET /`
**请求示例** (curl):
```bash
curl http://localhost:8080/
```
**响应示例**:
```json
{
"name": "MySQL AI Agent",
"version": "1.0.0",
"database": "localhost:3306/mydb",
"endpoints": {
"health": "/health",
"query": "/query"
}
}
```
---
### 3. 自然语言查询
**端点**: `POST /query`
使用自然语言查询 MySQL 数据库。
**请求体**:
```json
{
"query": "显示所有用户",
"openai_api_key": "sk-xxx",
"user_id": "user123",
"model": "gpt-3.5-turbo"
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/query \
-H "Content-Type: application/json" \
-d '{
"query": "有多少个用户?",
"openai_api_key": "sk-xxx",
"user_id": "user123"
}'
```
**请求示例** (Python):
```python
import requests
query_data = {
"query": "显示年龄大于30的所有用户",
"openai_api_key": "sk-xxx",
"user_id": "user123",
"model": "gpt-3.5-turbo"
}
response = requests.post(
"http://localhost:8080/query",
json=query_data
)
result = response.json()
print(f"Query: {result['query']}")
print(f"Result: {result['result']}")
print(f"Success: {result['success']}")
print(f"Timestamp: {result['timestamp']}")
```
**响应示例**:
```json
{
"query": "有多少个用户?",
"result": "数据库中有 150 个用户",
"success": true,
"timestamp": "2026-01-15T10:30:00.000Z"
}
```
---
## 查询示例
### 基础查询:
```python
queries = [
"显示所有用户",
"有多少个用户?",
"列出所有表",
"显示 users 表的结构",
"查看最近注册的 10 个用户"
]
```
### 统计查询:
```python
queries = [
"每个部门有多少员工?",
"统计每个城市的用户数量",
"计算订单总金额",
"找出销售额最高的产品",
"显示月度销售趋势"
]
```
### 条件查询:
```python
queries = [
"显示年龄大于30的用户",
"查找北京的所有客户",
"列出未支付的订单",
"显示价格在100到500之间的产品",
"找出最近一周的订单"
]
```
### 关联查询:
```python
queries = [
"显示每个用户的订单数量",
"列出购买了特定产品的用户",
"显示每个部门的平均工资",
"查找有订单但未支付的用户"
]
```
---
## 完整使用示例
### Python 客户端:
```python
import requests
from typing import Optional, Dict, Any
class MySQLAgentClient:
"""MySQL Agent 客户端"""
def __init__(self, base_url: str, openai_api_key: str):
self.base_url = base_url.rstrip('/')
self.openai_api_key = openai_api_key
def health_check(self) -> Dict[str, Any]:
"""健康检查"""
response = requests.get(f"{self.base_url}/health")
return response.json()
def get_info(self) -> Dict[str, Any]:
"""获取服务信息"""
response = requests.get(f"{self.base_url}/")
return response.json()
def query(
self,
query: str,
user_id: Optional[str] = None,
model: str = "gpt-3.5-turbo"
) -> Dict[str, Any]:
"""执行自然语言查询"""
data = {
"query": query,
"openai_api_key": self.openai_api_key,
"user_id": user_id,
"model": model
}
response = requests.post(f"{self.base_url}/query", json=data)
return response.json()
# 使用示例
client = MySQLAgentClient(
base_url="http://localhost:8080",
openai_api_key="sk-xxx"
)
# 1. 健康检查
health = client.health_check()
print(f"Database Connected: {health['database_connected']}")
print(f"Database Info: {health['database_info']}")
# 2. 执行查询
queries = [
"显示所有表",
"users 表有多少条记录?",
"显示最近注册的5个用户",
"统计每个城市的用户数量",
"找出年龄最大的用户"
]
for query in queries:
print(f"\n{'='*60}")
print(f"Query: {query}")
print('='*60)
result = client.query(query, user_id="user123")
if result['success']:
print(f"Result:\n{result['result']}")
else:
print(f"Error: Query failed")
```
---
### 交互式查询工具:
```python
import requests
from prompt_toolkit import prompt
from prompt_toolkit.history import InMemoryHistory
from rich.console import Console
from rich.table import Table
class InteractiveMySQLClient:
"""交互式 MySQL 查询客户端"""
def __init__(self, base_url: str, openai_api_key: str):
self.base_url = base_url
self.openai_api_key = openai_api_key
self.console = Console()
self.history = InMemoryHistory()
def query(self, query_text: str) -> Dict:
"""执行查询"""
response = requests.post(
f"{self.base_url}/query",
json={
"query": query_text,
"openai_api_key": self.openai_api_key
}
)
return response.json()
def display_result(self, result: Dict):
"""显示查询结果"""
if result['success']:
self.console.print(f"[green]✓ Success[/green]")
self.console.print(f"\n{result['result']}\n")
else:
self.console.print(f"[red]✗ Failed[/red]")
def run(self):
"""运行交互式会话"""
self.console.print("[bold blue]MySQL AI Agent - Interactive Client[/bold blue]")
self.console.print("Type 'exit' or 'quit' to end session\n")
while True:
try:
# 获取用户输入
query_text = prompt(
"mysql> ",
history=self.history
)
# 检查退出命令
if query_text.lower() in ['exit', 'quit']:
break
if not query_text.strip():
continue
# 执行查询
result = self.query(query_text)
self.display_result(result)
except KeyboardInterrupt:
continue
except EOFError:
break
self.console.print("\n[yellow]Goodbye![/yellow]")
# 使用交互式客户端
if __name__ == "__main__":
client = InteractiveMySQLClient(
base_url="http://localhost:8080",
openai_api_key="sk-xxx"
)
client.run()
```
---
### 数据分析工具:
```python
import requests
import pandas as pd
import matplotlib.pyplot as plt
from typing import List, Dict
class MySQLDataAnalyzer:
"""MySQL 数据分析工具"""
def __init__(self, base_url: str, openai_api_key: str):
self.base_url = base_url
self.openai_api_key = openai_api_key
def query(self, query: str) -> str:
"""执行查询"""
response = requests.post(
f"{self.base_url}/query",
json={
"query": query,
"openai_api_key": self.openai_api_key
}
)
result = response.json()
return result.get('result', '') if result.get('success') else ''
def get_statistics(self, table: str, column: str) -> Dict:
"""获取列的统计信息"""
queries = {
"count": f"{table} 表的 {column} 列有多少条记录?",
"avg": f"{table} 表的 {column} 列的平均值是多少?",
"min": f"{table} 表的 {column} 列的最小值是多少?",
"max": f"{table} 表的 {column} 列的最大值是多少?"
}
stats = {}
for stat_name, query in queries.items():
result = self.query(query)
stats[stat_name] = result
return stats
def get_distribution(self, table: str, column: str) -> Dict:
"""获取数据分布"""
query = f"统计 {table} 表中 {column} 列的值分布"
result = self.query(query)
return {"distribution": result}
# 使用示例
analyzer = MySQLDataAnalyzer(
base_url="http://localhost:8080",
openai_api_key="sk-xxx"
)
# 获取统计信息
stats = analyzer.get_statistics("users", "age")
print("Statistics:")
for stat, value in stats.items():
print(f" {stat}: {value}")
# 获取分布
distribution = analyzer.get_distribution("users", "city")
print(f"\nDistribution: {distribution}")
```
---
### 批量查询和导出:
```python
import requests
import csv
from datetime import datetime
class MySQLBatchExporter:
"""批量查询和导出工具"""
def __init__(self, base_url: str, openai_api_key: str):
self.base_url = base_url
self.openai_api_key = openai_api_key
def execute_queries(self, queries: List[str]) -> List[Dict]:
"""批量执行查询"""
results = []
for query in queries:
response = requests.post(
f"{self.base_url}/query",
json={
"query": query,
"openai_api_key": self.openai_api_key
}
)
result = response.json()
results.append({
"query": query,
"result": result.get('result', ''),
"success": result.get('success', False),
"timestamp": result.get('timestamp', '')
})
return results
def export_to_csv(self, results: List[Dict], filename: str):
"""导出结果到 CSV"""
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['query', 'result', 'success', 'timestamp'])
writer.writeheader()
writer.writerows(results)
print(f"Results exported to {filename}")
# 使用示例
exporter = MySQLBatchExporter(
base_url="http://localhost:8080",
openai_api_key="sk-xxx"
)
# 批量查询
queries = [
"统计总用户数",
"统计每个城市的用户数",
"显示最近一周的注册用户数",
"计算平均年龄",
"显示活跃用户占比"
]
results = exporter.execute_queries(queries)
# 导出结果
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
exporter.export_to_csv(results, f"mysql_queries_{timestamp}.csv")
# 打印摘要
successful = sum(1 for r in results if r['success'])
print(f"\nSummary: {successful}/{len(queries)} queries successful")
```
---
## 环境变量配置
```bash
# 服务配置
export SERVICE_HOST="0.0.0.0"
export SERVICE_PORT="8080"
export POD_NAME="mysql-agent"
export TEMPLATE_TYPE="mysql_agent"
# MySQL 数据库配置
export MYSQL_HOST="localhost"
export MYSQL_PORT="3306"
export MYSQL_USER="root"
export MYSQL_PASSWORD="your-password"
export MYSQL_DATABASE="mydb"
# 启动服务
python mysql_agent.py
```
---
## Docker Compose 示例
```yaml
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: testdb
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
mysql-agent:
build:
context: .
dockerfile: mysql_agent.Dockerfile
environment:
MYSQL_HOST: mysql
MYSQL_PORT: 3306
MYSQL_USER: root
MYSQL_PASSWORD: rootpassword
MYSQL_DATABASE: testdb
SERVICE_PORT: 8080
ports:
- "8080:8080"
depends_on:
- mysql
volumes:
mysql_data:
```
---
## 注意事项
1. **API Key**: OpenAI API key 从请求传入,确保安全
2. **数据库连接**: 需要正确配置数据库连接参数
3. **权限控制**: 建议使用只读用户进行查询
4. **查询限制**: 设置合理的查询超时和结果限制
5. **错误处理**: 检查 success 字段确认查询是否成功
6. **SQL注入**: Agent 会自动处理,但仍需注意安全
7. **成本控制**: 监控 OpenAI API 使用量
8. **模型选择**: gpt-4 更准确但成本更高,gpt-3.5-turbo 更经济
@@ -0,0 +1,610 @@
# PostgreSQL Agent 请求调用示例
## 服务信息
- **服务名称**: PostgreSQL AI Agent
- **版本**: 1.0.0
- **框架**: LangChain + OpenAI
- **默认端口**: 8080
## 概述
PostgreSQL AI Agent 使用 LangChain 和自然语言处理技术,允许用户使用自然语言查询 PostgreSQL 数据库。
---
## API 端点
### 1. 健康检查
**端点**: `GET /health`
**请求示例** (curl):
```bash
curl http://localhost:8080/health
```
**响应示例**:
```json
{
"status": "healthy",
"pod_name": "postgresql-agent",
"template_type": "postgresql_agent",
"database_connected": true,
"database_info": "localhost:5432/mydb"
}
```
---
### 2. 服务信息
**端点**: `GET /`
**请求示例** (curl):
```bash
curl http://localhost:8080/
```
**响应示例**:
```json
{
"name": "PostgreSQL AI Agent",
"version": "1.0.0",
"database": "localhost:5432/postgres",
"endpoints": {
"health": "/health",
"query": "/query"
}
}
```
---
### 3. 自然语言查询
**端点**: `POST /query`
使用自然语言查询 PostgreSQL 数据库。
**请求体**:
```json
{
"query": "显示所有用户",
"openai_api_key": "sk-xxx",
"user_id": "user123",
"model": "gpt-3.5-turbo"
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/query \
-H "Content-Type: application/json" \
-d '{
"query": "数据库中有多少个表?",
"openai_api_key": "sk-xxx",
"user_id": "user123"
}'
```
**请求示例** (Python):
```python
import requests
query_data = {
"query": "显示 users 表中年龄大于25的所有用户",
"openai_api_key": "sk-xxx",
"user_id": "user123",
"model": "gpt-3.5-turbo"
}
response = requests.post(
"http://localhost:8080/query",
json=query_data
)
result = response.json()
print(f"Query: {result['query']}")
print(f"Result: {result['result']}")
print(f"Success: {result['success']}")
print(f"Timestamp: {result['timestamp']}")
```
**响应示例**:
```json
{
"query": "数据库中有多少个表?",
"result": "数据库中有 12 个表",
"success": true,
"timestamp": "2026-01-15T10:30:00.000Z"
}
```
---
## 查询示例
### 基础查询:
```python
queries = [
"显示所有表",
"列出所有schema",
"显示 users 表的结构",
"users 表有多少条记录?",
"显示最近创建的10条记录"
]
```
### PostgreSQL 特定功能:
```python
queries = [
"显示所有视图",
"列出所有索引",
"显示表的大小",
"查看数据库的大小",
"显示所有触发器",
"列出所有存储过程",
"显示表的统计信息"
]
```
### 统计查询:
```python
queries = [
"统计每个部门的员工数量",
"计算订单的总金额",
"显示每月的销售额",
"找出销量最高的产品",
"计算用户的平均年龄"
]
```
### 条件查询:
```python
queries = [
"显示状态为活跃的用户",
"查找创建时间在最近一周的订单",
"列出价格高于1000的产品",
"显示评分大于4.5的商品",
"查找北京地区的所有客户"
]
```
### 关联查询:
```python
queries = [
"显示每个用户的订单数量",
"列出有订单的用户",
"显示每个类别的产品数量",
"查找购买了特定产品的用户",
"统计每个城市的订单总额"
]
```
### JSON 查询 (PostgreSQL 特性):
```python
queries = [
"从 users 表的 metadata JSON 字段中提取 age",
"查找 metadata 包含特定键的记录",
"统计 JSON 数组的长度"
]
```
---
## 完整使用示例
### Python 客户端:
```python
import requests
from typing import Optional, Dict, Any, List
class PostgreSQLAgentClient:
"""PostgreSQL Agent 客户端"""
def __init__(self, base_url: str, openai_api_key: str):
self.base_url = base_url.rstrip('/')
self.openai_api_key = openai_api_key
def health_check(self) -> Dict[str, Any]:
"""健康检查"""
response = requests.get(f"{self.base_url}/health")
return response.json()
def get_info(self) -> Dict[str, Any]:
"""获取服务信息"""
response = requests.get(f"{self.base_url}/")
return response.json()
def query(
self,
query: str,
user_id: Optional[str] = None,
model: str = "gpt-3.5-turbo"
) -> Dict[str, Any]:
"""执行自然语言查询"""
data = {
"query": query,
"openai_api_key": self.openai_api_key,
"user_id": user_id,
"model": model
}
response = requests.post(f"{self.base_url}/query", json=data)
return response.json()
def batch_query(self, queries: List[str], user_id: Optional[str] = None) -> List[Dict]:
"""批量查询"""
results = []
for q in queries:
result = self.query(q, user_id)
results.append(result)
return results
# 使用示例
client = PostgreSQLAgentClient(
base_url="http://localhost:8080",
openai_api_key="sk-xxx"
)
# 1. 健康检查
health = client.health_check()
print(f"Status: {health['status']}")
print(f"Database: {health['database_info']}")
print(f"Connected: {health['database_connected']}\n")
# 2. 单个查询
result = client.query("显示所有表")
print(f"Query: {result['query']}")
print(f"Result: {result['result']}\n")
# 3. 批量查询
queries = [
"数据库中有多少个表?",
"users 表有多少条记录?",
"显示 users 表的前5条记录",
"统计每个城市的用户数量"
]
print("Batch Queries:")
results = client.batch_query(queries, user_id="user123")
for i, result in enumerate(results, 1):
print(f"\n{i}. {result['query']}")
if result['success']:
print(f" {result['result']}")
else:
print(f" Error: Failed to execute query")
```
---
### 数据库监控工具:
```python
import requests
import time
from datetime import datetime
from rich.console import Console
from rich.table import Table
class PostgreSQLMonitor:
"""PostgreSQL 数据库监控工具"""
def __init__(self, base_url: str, openai_api_key: str):
self.base_url = base_url
self.openai_api_key = openai_api_key
self.console = Console()
def query(self, query: str) -> str:
"""执行查询"""
response = requests.post(
f"{self.base_url}/query",
json={
"query": query,
"openai_api_key": self.openai_api_key
}
)
result = response.json()
return result.get('result', '') if result.get('success') else 'N/A'
def get_database_stats(self) -> Dict:
"""获取数据库统计信息"""
stats = {
"database_size": self.query("数据库的大小是多少?"),
"table_count": self.query("有多少个表?"),
"connection_count": self.query("当前有多少个数据库连接?"),
"cache_hit_ratio": self.query("缓存命中率是多少?")
}
return stats
def display_stats(self, stats: Dict):
"""显示统计信息"""
table = Table(title="PostgreSQL Database Statistics")
table.add_column("Metric", style="cyan")
table.add_column("Value", style="green")
for metric, value in stats.items():
table.add_row(metric.replace('_', ' ').title(), str(value))
self.console.print(table)
def monitor(self, interval: int = 60):
"""持续监控"""
self.console.print("[bold blue]PostgreSQL Monitor Started[/bold blue]")
self.console.print(f"Refresh interval: {interval} seconds\n")
try:
while True:
self.console.clear()
self.console.print(f"[yellow]Last Update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}[/yellow]\n")
stats = self.get_database_stats()
self.display_stats(stats)
time.sleep(interval)
except KeyboardInterrupt:
self.console.print("\n[yellow]Monitoring stopped[/yellow]")
# 使用示例
monitor = PostgreSQLMonitor(
base_url="http://localhost:8080",
openai_api_key="sk-xxx"
)
# 获取一次统计信息
stats = monitor.get_database_stats()
monitor.display_stats(stats)
# 或者持续监控 (每60秒刷新)
# monitor.monitor(interval=60)
```
---
### 数据迁移辅助工具:
```python
import requests
from typing import List, Dict
class PostgreSQLMigrationHelper:
"""PostgreSQL 数据迁移辅助工具"""
def __init__(self, base_url: str, openai_api_key: str):
self.base_url = base_url
self.openai_api_key = openai_api_key
def query(self, query: str) -> str:
"""执行查询"""
response = requests.post(
f"{self.base_url}/query",
json={
"query": query,
"openai_api_key": self.openai_api_key
}
)
result = response.json()
return result.get('result', '') if result.get('success') else ''
def get_table_schema(self, table_name: str) -> str:
"""获取表结构"""
return self.query(f"显示 {table_name} 表的详细结构")
def get_all_tables(self) -> str:
"""获取所有表名"""
return self.query("列出所有表名")
def get_table_constraints(self, table_name: str) -> str:
"""获取表约束"""
return self.query(f"显示 {table_name} 表的所有约束")
def get_table_indexes(self, table_name: str) -> str:
"""获取表索引"""
return self.query(f"显示 {table_name} 表的所有索引")
def get_foreign_keys(self, table_name: str) -> str:
"""获取外键关系"""
return self.query(f"显示 {table_name} 表的外键关系")
def generate_migration_report(self, table_name: str) -> Dict:
"""生成迁移报告"""
return {
"table": table_name,
"schema": self.get_table_schema(table_name),
"constraints": self.get_table_constraints(table_name),
"indexes": self.get_table_indexes(table_name),
"foreign_keys": self.get_foreign_keys(table_name)
}
# 使用示例
helper = PostgreSQLMigrationHelper(
base_url="http://localhost:8080",
openai_api_key="sk-xxx"
)
# 获取所有表
tables = helper.get_all_tables()
print(f"All Tables:\n{tables}\n")
# 生成特定表的迁移报告
table_name = "users"
report = helper.generate_migration_report(table_name)
print(f"Migration Report for '{table_name}':")
print(f"\nSchema:\n{report['schema']}")
print(f"\nConstraints:\n{report['constraints']}")
print(f"\nIndexes:\n{report['indexes']}")
print(f"\nForeign Keys:\n{report['foreign_keys']}")
```
---
### 性能分析工具:
```python
import requests
from typing import List, Dict
import pandas as pd
class PostgreSQLPerformanceAnalyzer:
"""PostgreSQL 性能分析工具"""
def __init__(self, base_url: str, openai_api_key: str):
self.base_url = base_url
self.openai_api_key = openai_api_key
def query(self, query: str) -> str:
"""执行查询"""
response = requests.post(
f"{self.base_url}/query",
json={
"query": query,
"openai_api_key": self.openai_api_key
}
)
result = response.json()
return result.get('result', '') if result.get('success') else ''
def get_slow_queries(self) -> str:
"""获取慢查询"""
return self.query("显示最慢的10个查询")
def get_table_sizes(self) -> str:
"""获取表大小"""
return self.query("显示所有表的大小,按大小降序排列")
def get_index_usage(self) -> str:
"""获取索引使用情况"""
return self.query("显示索引使用统计")
def get_cache_stats(self) -> str:
"""获取缓存统计"""
return self.query("显示缓存命中率统计")
def get_connection_stats(self) -> str:
"""获取连接统计"""
return self.query("显示数据库连接统计信息")
def analyze_table(self, table_name: str) -> str:
"""分析表性能"""
return self.query(f"分析 {table_name} 表的性能")
# 使用示例
analyzer = PostgreSQLPerformanceAnalyzer(
base_url="http://localhost:8080",
openai_api_key="sk-xxx"
)
print("=== Performance Analysis ===\n")
# 1. 慢查询
print("Slow Queries:")
print(analyzer.get_slow_queries())
print()
# 2. 表大小
print("Table Sizes:")
print(analyzer.get_table_sizes())
print()
# 3. 索引使用
print("Index Usage:")
print(analyzer.get_index_usage())
print()
# 4. 缓存统计
print("Cache Statistics:")
print(analyzer.get_cache_stats())
print()
# 5. 分析特定表
print("Analyze 'users' table:")
print(analyzer.analyze_table("users"))
```
---
## 环境变量配置
```bash
# 服务配置
export SERVICE_HOST="0.0.0.0"
export SERVICE_PORT="8080"
export POD_NAME="postgresql-agent"
export TEMPLATE_TYPE="postgresql_agent"
# PostgreSQL 数据库配置
export POSTGRES_HOST="localhost"
export POSTGRES_PORT="5432"
export POSTGRES_USER="postgres"
export POSTGRES_PASSWORD="your-password"
export POSTGRES_DATABASE="mydb"
# 启动服务
python postgresql_agent.py
```
---
## Docker Compose 示例
```yaml
version: '3.8'
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
postgresql-agent:
build:
context: .
dockerfile: postgresql_agent.Dockerfile
environment:
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DATABASE: testdb
SERVICE_PORT: 8080
ports:
- "8080:8080"
depends_on:
- postgres
volumes:
postgres_data:
```
---
## PostgreSQL 特性支持
Agent 支持 PostgreSQL 的特殊功能:
- ✅ JSON/JSONB 查询
- ✅ 数组类型
- ✅ 全文搜索
- ✅ 窗口函数
- ✅ CTEs (Common Table Expressions)
- ✅ 视图和物化视图
- ✅ 触发器和存储过程
- ✅ 分区表
---
## 注意事项
1. **API Key**: OpenAI API key 从请求传入,确保安全
2. **数据库连接**: 需要正确配置数据库连接参数
3. **权限控制**: 建议使用只读用户进行查询
4. **查询限制**: 设置合理的查询超时和结果限制
5. **错误处理**: 检查 success 字段确认查询是否成功
6. **PostgreSQL 版本**: 支持 PostgreSQL 12+
7. **成本控制**: 监控 OpenAI API 使用量
8. **模型选择**: gpt-4 更准确但成本更高
+165
View File
@@ -0,0 +1,165 @@
# Agent 镜像推送总结
## 推送时间
2026-01-15
## 推送到的 ACR
agnettaiji.azurecr.io
## 已推送的镜像
### 1. Search Agent
- **镜像**: `agnettaiji.azurecr.io/ai-agents/search-agent:latest`
- **平台**: linux/arm64
- **状态**: ✅ 已推送
- **更新内容**:
- 添加 agent_callback_utils.py
- 支持从请求参数传递 `llm_api_key` 和 `user_id`
- 集成回调功能,自动追踪工具使用(web_search, content_reader)
### 2. Jina Search Agent
- **镜像**: `agnettaiji.azurecr.io/ai-agents/jina-search-agent:latest`
- **平台**: linux/arm64
- **状态**: ✅ 已推送
- **更新内容**:
- 添加 agent_callback_utils.py
- 支持从请求参数传递 `jina_api_key` 和 `user_id`
- 移除全局 JINA_API_KEY 环境变量依赖
- 集成回调功能,追踪工具使用(jina_reader)
### 3. MySQL Agent
- **镜像**: `agnettaiji.azurecr.io/ai-agents/mysql-agent:latest`
- **平台**: linux/arm64
- **状态**: ✅ 已推送
- **更新内容**:
- **重大重构**: 从循环模式改为 FastAPI HTTP 服务
- 添加 agent_callback_utils.py
- 新增 FastAPI 端点: `/health`, `/query`, `/`
- 支持从请求参数传递 `openai_api_key` 和 `user_id`
- 集成回调功能,追踪工具使用(sql_database)
- 新增依赖: fastapi, uvicorn, requests, pydantic
### 4. PostgreSQL Agent
- **镜像**: `agnettaiji.azurecr.io/ai-agents/postgresql-agent:latest`
- **平台**: linux/arm64
- **状态**: ✅ 已推送
- **更新内容**:
- **重大重构**: 从循环模式改为 FastAPI HTTP 服务
- 添加 agent_callback_utils.py
- 新增 FastAPI 端点: `/health`, `/query`, `/`
- 支持从请求参数传递 `openai_api_key` 和 `user_id`
- 集成回调功能,追踪工具使用(sql_database)
- 新增依赖: fastapi, uvicorn, requests, pydantic
### 5. Azure Blob Agent
- **镜像**: `agnettaiji.azurecr.io/ai-agents/azure-blob-agent:latest`
- **平台**: linux/arm64
- **状态**: ✅ 已推送
- **更新内容**:
- 添加 agent_callback_utils.py
- 支持从请求参数传递 `litellm_api_key` 和 `user_id`
- 移除全局 LITELLM_API_KEY 环境变量依赖
- 集成回调功能,追踪工具使用(azure_blob_storage)
## 回调功能说明
所有 Agent 现在都支持:
1. **自动时间追踪**: 自动记录 Pod 运行时间
2. **工具使用追踪**: 记录使用的工具列表
3. **用户 ID 追踪**: 支持多租户计费
4. **自动回调**: 在请求结束时自动发送 POST 请求到回调 URL
### 回调 URL
- 默认: `http://mcp-server:8002/api/v1/billing/agent-callback`
- 可通过环境变量 `AGENT_CALLBACK_URL` 自定义
### 回调数据格式
```json
{
"agentName": "pod-name",
"userId": "user-123",
"podRunningTimeSeconds": 120,
"toolsUsed": ["tool1", "tool2"],
"startTime": "2026-01-15T10:00:00Z",
"endTime": "2026-01-15T10:02:00Z",
"requestId": "req-xxx"
}
```
## API 密钥处理变化
### 之前
- 所有 API 密钥都通过环境变量设置
- 不支持多租户
### 现在
- API 密钥通过**请求参数**传递
- 支持多租户场景
- 其他配置仍然通过环境变量
### 请求示例
#### Search Agent
```json
{
"query": "search query",
"llm_api_key": "sk-xxx",
"user_id": "user-123"
}
```
#### MySQL/PostgreSQL Agent
```json
{
"query": "SQL query",
"openai_api_key": "sk-xxx",
"user_id": "user-123"
}
```
#### Azure Blob Agent
```json
{
"query": "blob query",
"litellm_api_key": "sk-xxx",
"user_id": "user-123"
}
```
## 环境变量要求
所有 Agent 部署时需要设置:
- `POD_NAME`: Pod 名称(必需,用于回调中的 agentName)
- `AGENT_CALLBACK_URL`: 回调 URL(可选,默认 http://mcp-server:8002/api/v1/billing/agent-callback)
- `USER_ID`: 默认用户 ID(可选,请求中未提供时使用)
## 验证命令
```bash
# 列出所有镜像
az acr repository list --name agnettaiji --output table
# 查看特定镜像的标签
az acr repository show-tags --name agnettaiji --repository ai-agents/search-agent --output table
```
## 后续步骤
1. ✅ 所有 Agent 镜像已推送到 ACR
2. ⏳ 需要更新 Kubernetes 部署文件以使用新镜像
3. ⏳ 需要测试回调功能是否正常工作
4. ⏳ 需要验证多租户 API 密钥处理
## 构建脚本
创建了批量构建脚本: `build_all_agents.sh`
```bash
# 使用方法
./build_all_agents.sh [TAG]
# 默认使用 latest 标签
./build_all_agents.sh
```
@@ -0,0 +1,168 @@
# Agent Templates 目录重组总结
## 完成时间
2026-01-15
## 重组概述
已将 `agent_templates` 目录中的所有文件按照功能分类整理到不同的文件夹中,提高了代码的可维护性和可读性。
## 新的目录结构
```
agent_templates/
├── agents/ # 所有 Agent 实现(8个)
├── common/ # 共享代码和工具
├── docs/ # 所有文档文件
├── scripts/ # 构建和工具脚本
└── tests/ # 测试文件
```
## 详细变更
### 1. Agents 目录 (`agents/`)
所有 Agent 实现已移动到各自的子目录:
- `agents/search_agent/` - 智能搜索 Agent
- `agents/jina_search_agent/` - Jina 搜索 Agent
- `agents/azure_blob_agent/` - Azure Blob 存储 Agent
- `agents/azure_blob_agent_a2a/` - Azure Blob Agent (A2A)
- `agents/azure_blob_agent_mcp/` - Azure Blob Agent (MCP)
- `agents/postgresql_agent/` - PostgreSQL 数据库 Agent
- `agents/mysql_agent/` - MySQL 数据库 Agent
- `agents/a2a_litellm_agent/` - A2A LiteLLM Agent
每个 Agent 目录包含:
- Agent 主程序文件(`.py`)
- Dockerfile(`.Dockerfile`)
- 相关配置和依赖文件
### 2. Common 目录 (`common/`)
共享代码和工具:
- `agent_callback_utils.py` - Agent 回调处理工具
- `api_key_utils.py` - API Key 配置管理工具
- `requirements_a2a.txt` - A2A 协议依赖
- `requirements_mcp.txt` - MCP 协议依赖
- `Dockerfile.test` - 测试 Dockerfile
### 3. Docs 目录 (`docs/`)
所有文档文件(18个):
- 使用指南(`*_USAGE.md`)
- 示例文档(`*_EXAMPLES.md`)
- 配置说明(`*_SUMMARY.md`)
- 快速参考(`QUICK_REFERENCE.md`)
- 目录结构说明(`DIRECTORY_STRUCTURE.md`)
### 4. Scripts 目录 (`scripts/`)
构建和工具脚本(10个):
- `build_*.sh` - 各 Agent 的构建脚本
- `build_all_agents.sh` - 批量构建脚本
- `rebuild_all.sh` - 重建所有 Agent
- `check_image_content.sh` - 镜像内容检查
### 5. Tests 目录 (`tests/`)
测试文件(6个):
- `test_*.py` - Python 测试文件
- `test_*.sh` - Shell 测试脚本
- `test_client.py` - 测试客户端
## 更新的文件
### 构建脚本
所有构建脚本已更新路径引用:
| 脚本 | 更新内容 |
|------|---------|
| `build_search_agent.sh` | `search_agent.Dockerfile` → `agents/search_agent/search_agent.Dockerfile` |
| `build_jina_agent.sh` | `jina_search_agent.Dockerfile` → `agents/jina_search_agent/jina_search_agent.Dockerfile` |
| `build_all_agents.sh` | 所有 Dockerfile 路径已更新 |
| `rebuild_all.sh` | 所有 Dockerfile 路径已更新 |
| `build_a2a_litellm_agent.sh` | 路径已更新,构建上下文改为 `agents/a2a_litellm_agent` |
### Dockerfile
所有 Dockerfile 已更新 COPY 路径:
| Dockerfile | 更新内容 |
|-----------|---------|
| `search_agent.Dockerfile` | 更新为从 `agents/search_agent/` 和 `common/` 复制 |
| `jina_search_agent.Dockerfile` | 更新为从 `agents/jina_search_agent/` 和 `common/` 复制 |
| `postgresql_agent.Dockerfile` | 更新为从 `agents/postgresql_agent/` 和 `common/` 复制 |
| `mysql_agent.Dockerfile` | 更新为从 `agents/mysql_agent/` 和 `common/` 复制 |
| `azure_blob_agent.Dockerfile` | 更新为从 `agents/azure_blob_agent/` 和 `common/` 复制 |
| `azure_blob_agent_a2a.Dockerfile` | 更新为从 `agents/azure_blob_agent_a2a/` 和 `common/` 复制 |
| `azure_blob_agent_mcp.Dockerfile` | 更新为从 `agents/azure_blob_agent_mcp/` 和 `common/` 复制 |
| `a2a_litellm_agent.Dockerfile` | 更新为从 `agents/a2a_litellm_agent/` 复制 |
## 使用说明
### 构建 Agent
所有构建脚本需要在 `agent_templates` 目录下运行:
```bash
cd agent_templates
./scripts/build_search_agent.sh v1.0
```
### 查看文档
```bash
# 查看目录结构说明
cat docs/DIRECTORY_STRUCTURE.md
# 查看特定 Agent 的使用指南
cat docs/SEARCH_AGENT_USAGE.md
```
### 导入共享工具
在 Agent 代码中,共享工具会被复制到 `/app/common/`:
```python
# 在容器中运行时
from common.agent_callback_utils import AgentCallbackHandler
from common.api_key_utils import get_llm_api_key
```
## 迁移检查清单
- [x] 所有文件已移动到对应目录
- [x] 所有构建脚本路径已更新
- [x] 所有 Dockerfile 路径已更新
- [x] 创建了目录结构说明文档
- [x] 创建了重组总结文档
## 注意事项
1. **构建脚本**: 必须在 `agent_templates` 目录下运行
2. **Dockerfile**: 构建上下文是 `agent_templates` 目录
3. **导入路径**: 在容器中,`common/` 目录会被复制到 `/app/common/`
4. **向后兼容**: 如果外部代码直接引用文件,需要更新路径
## 优势
1. **清晰的目录结构**: 按功能分类,易于查找和维护
2. **模块化设计**: 每个 Agent 独立目录,便于管理
3. **共享代码集中**: `common/` 目录统一管理共享工具
4. **文档集中**: 所有文档在 `docs/` 目录
5. **脚本集中**: 所有构建脚本在 `scripts/` 目录
## 后续建议
1. 添加新 Agent 时,在 `agents/` 目录下创建新的子目录
2. 共享代码放在 `common/` 目录
3. 文档放在 `docs/` 目录
4. 构建脚本放在 `scripts/` 目录
5. 测试文件放在 `tests/` 目录
## 相关文档
- `DIRECTORY_STRUCTURE.md` - 详细的目录结构说明
- `API_KEY_CONFIGURATION_SUMMARY.md` - API Key 配置说明
@@ -0,0 +1,487 @@
# Search Agent 请求调用示例
## 服务信息
- **服务名称**: Intelligent Search AI Agent
- **版本**: 1.0.0
- **框架**: LangChain + Serper + Jina
- **默认端口**: 8080
## 概述
智能搜索代理集成了 Google 搜索 (Serper API) 和网页内容提取 (Jina API),提供智能问答服务。
---
## API 端点
### 1. 健康检查
**端点**: `GET /health`
**请求示例** (curl):
```bash
curl http://localhost:8080/health
```
**响应示例**:
```json
{
"status": "healthy",
"pod_name": "search-agent",
"template_type": "search_agent",
"configured": true,
"timestamp": "2026-01-15T10:30:00.000Z"
}
```
---
### 2. 获取状态
**端点**: `GET /status`
**请求示例** (curl):
```bash
curl http://localhost:8080/status
```
**响应示例**:
```json
{
"status": "running",
"pod_name": "search-agent",
"template_type": "search_agent",
"configured": true,
"timestamp": "2026-01-15T10:30:00.000Z"
}
```
---
### 3. 配置 Agent
**端点**: `POST /configure`
配置搜索代理的参数(可选,也可以从环境变量自动配置)。
**请求体**:
```json
{
"llm_base_url": "http://localhost:4000",
"llm_model": "xchat52",
"serper_api_key": "your-serper-api-key",
"jina_api_key": "your-jina-api-key",
"max_iterations": 3,
"max_results_per_query": 10,
"content_max_length": 5000,
"log_level": "INFO",
"timeout": 30
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/configure \
-H "Content-Type: application/json" \
-d '{
"llm_base_url": "http://localhost:4000",
"llm_model": "gpt-3.5-turbo",
"serper_api_key": "xxx",
"jina_api_key": "xxx",
"max_iterations": 3,
"max_results_per_query": 10
}'
```
**请求示例** (Python):
```python
import requests
config_data = {
"llm_base_url": "http://localhost:4000",
"llm_model": "xchat52",
"serper_api_key": "your-serper-key",
"jina_api_key": "your-jina-key",
"max_iterations": 3,
"max_results_per_query": 10,
"content_max_length": 5000,
"timeout": 30
}
response = requests.post(
"http://localhost:8080/configure",
json=config_data
)
print(response.json())
```
**响应示例**:
```json
{
"status": "success",
"message": "Agent配置成功",
"timestamp": "2026-01-15T10:30:00.000Z"
}
```
---
### 4. 执行搜索
**端点**: `POST /search`
执行智能搜索并返回答案。
**请求体**:
```json
{
"query": "什么是Kubernetes?",
"llm_api_key": "your-llm-api-key",
"user_id": "user123",
"auto_configure": false
}
```
**请求示例** (curl):
```bash
curl -X POST http://localhost:8080/search \
-H "Content-Type: application/json" \
-d '{
"query": "Python最新版本是什么?",
"llm_api_key": "sk-xxx",
"user_id": "user123"
}'
```
**请求示例** (Python):
```python
import requests
search_data = {
"query": "2026年最新的AI技术趋势是什么?",
"llm_api_key": "sk-xxx",
"user_id": "user123",
"auto_configure": False # 设为 True 从环境变量自动配置
}
response = requests.post(
"http://localhost:8080/search",
json=search_data
)
result = response.json()
print(f"Query: {result['query']}")
print(f"Answer: {result['answer']}")
print(f"Confidence: {result['confidence']}")
print(f"Sources: {len(result['sources'])}")
for i, source in enumerate(result['sources'], 1):
print(f"{i}. {source['title']}: {source['url']}")
```
**响应示例**:
```json
{
"query": "什么是Kubernetes?",
"answer": "Kubernetes 是一个开源的容器编排平台,用于自动化容器化应用程序的部署、扩展和管理。它最初由 Google 开发,现在由 Cloud Native Computing Foundation (CNCF) 维护。Kubernetes 提供了容器调度、服务发现、负载均衡、自动伸缩等功能。",
"sources": [
{
"index": 1,
"title": "Kubernetes Documentation",
"url": "https://kubernetes.io/docs/"
},
{
"index": 2,
"title": "What is Kubernetes? - Red Hat",
"url": "https://www.redhat.com/en/topics/containers/what-is-kubernetes"
}
],
"confidence": "high",
"iterations": 2,
"total_sources": 5,
"search_queries": [
"什么是Kubernetes",
"Kubernetes 容器编排"
],
"timestamp": "2026-01-15T10:30:00.000Z"
}
```
---
## 完整使用流程示例
### Python 完整示例:
```python
import requests
import json
class SearchAgentClient:
"""Search Agent 客户端"""
def __init__(self, base_url: str, llm_api_key: str):
self.base_url = base_url.rstrip('/')
self.llm_api_key = llm_api_key
def health_check(self):
"""健康检查"""
response = requests.get(f"{self.base_url}/health")
return response.json()
def get_status(self):
"""获取状态"""
response = requests.get(f"{self.base_url}/status")
return response.json()
def configure(self, config: dict):
"""配置 Agent"""
response = requests.post(f"{self.base_url}/configure", json=config)
return response.json()
def search(self, query: str, user_id: str = None, auto_configure: bool = False):
"""执行搜索"""
data = {
"query": query,
"llm_api_key": self.llm_api_key,
"user_id": user_id,
"auto_configure": auto_configure
}
response = requests.post(f"{self.base_url}/search", json=data)
return response.json()
# 使用示例
client = SearchAgentClient(
base_url="http://localhost:8080",
llm_api_key="sk-xxx"
)
# 1. 健康检查
print("Health:", client.health_check())
# 2. 配置 Agent (可选)
config = {
"llm_base_url": "http://localhost:4000",
"llm_model": "xchat52",
"serper_api_key": "your-serper-key",
"jina_api_key": "your-jina-key",
"max_iterations": 3,
"max_results_per_query": 10
}
print("Configure:", client.configure(config))
# 3. 执行搜索
queries = [
"2026年最新的AI技术有哪些?",
"Docker和Kubernetes的区别是什么?",
"Python 3.12的新特性"
]
for query in queries:
print(f"\n{'='*60}")
print(f"Query: {query}")
print('='*60)
result = client.search(query, user_id="user123")
print(f"\n答案: {result['answer']}")
print(f"\n置信度: {result['confidence']}")
print(f"迭代次数: {result['iterations']}")
print(f"总来源: {result['total_sources']}")
print("\n来源:")
for source in result['sources']:
print(f" {source['index']}. {source['title']}")
print(f" {source['url']}")
```
---
### 批量搜索示例:
```python
import requests
import concurrent.futures
import time
def search_query(query, llm_api_key, user_id=None):
"""执行单次搜索"""
try:
response = requests.post(
"http://localhost:8080/search",
json={
"query": query,
"llm_api_key": llm_api_key,
"user_id": user_id,
"auto_configure": True
},
timeout=60
)
return {
"query": query,
"success": True,
"result": response.json()
}
except Exception as e:
return {
"query": query,
"success": False,
"error": str(e)
}
# 批量查询
queries = [
"什么是机器学习?",
"深度学习和机器学习的区别",
"PyTorch vs TensorFlow",
"Transformer模型的原理",
"GPT-4的主要特性"
]
llm_api_key = "sk-xxx"
# 并行执行搜索
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
futures = [
executor.submit(search_query, query, llm_api_key, f"user-{i}")
for i, query in enumerate(queries)
]
results = [future.result() for future in concurrent.futures.as_completed(futures)]
# 输出结果
for result in results:
if result["success"]:
data = result["result"]
print(f"\nQuery: {data['query']}")
print(f"Answer: {data['answer'][:200]}...")
print(f"Sources: {len(data['sources'])}")
else:
print(f"\nQuery: {result['query']}")
print(f"Error: {result['error']}")
```
---
### 流式搜索示例 (如果支持):
```python
import requests
import json
def stream_search(query: str, llm_api_key: str):
"""流式搜索 (假设支持 SSE)"""
response = requests.post(
"http://localhost:8080/search/stream",
json={
"query": query,
"llm_api_key": llm_api_key
},
stream=True
)
for line in response.iter_lines():
if line:
try:
data = json.loads(line.decode('utf-8'))
if data.get('type') == 'progress':
print(f"Progress: {data['message']}")
elif data.get('type') == 'answer':
print(f"Answer: {data['content']}")
elif data.get('type') == 'source':
print(f"Source: {data['title']} - {data['url']}")
except json.JSONDecodeError:
continue
# 使用流式搜索
stream_search("什么是Kubernetes?", "sk-xxx")
```
---
## 环境变量配置
```bash
# 服务配置
export SERVICE_HOST="0.0.0.0"
export SERVICE_PORT="8080"
export POD_NAME="search-agent"
export TEMPLATE_TYPE="search_agent"
# LLM 配置
export LLM_BASE_URL="http://localhost:4000"
export LLM_API_KEY="sk-xxx"
export LLM_MODEL="xchat52"
# Serper API (Google 搜索)
export SERPER_API_KEY="your-serper-api-key"
# Jina API (网页内容提取)
export JINA_API_KEY="your-jina-api-key"
# 搜索配置
export MAX_ITERATIONS="3"
export MAX_RESULTS_PER_QUERY="10"
export CONTENT_MAX_LENGTH="5000"
export TIMEOUT="30"
export LOG_LEVEL="INFO"
# 启动服务
python search_agent.py
```
---
## 获取 API Keys
### 1. Serper API Key
- 访问: https://serper.dev/
- 注册并获取 API key
- 免费套餐: 2,500 次查询/月
### 2. Jina API Key
- 访问: https://jina.ai/
- 注册并获取 API key
- 免费套餐: 1,000 次请求/天
---
## 查询示例
### 技术问题:
```python
queries = [
"什么是Docker容器?",
"Kubernetes的核心组件有哪些?",
"微服务架构的优缺点",
"RESTful API设计最佳实践",
"GraphQL和REST的区别"
]
```
### 新闻和事实:
```python
queries = [
"2026年AI领域的最新进展",
"最新的Python版本特性",
"云计算市场份额排名",
"开源许可证的类型和区别"
]
```
### 比较和分析:
```python
queries = [
"React vs Vue.js 框架对比",
"PostgreSQL和MySQL的性能比较",
"AWS、Azure、GCP云服务对比",
"敏捷开发和瀑布模型的区别"
]
```
---
## 注意事项
1. **API Keys**: 需要有效的 Serper 和 Jina API keys
2. **速率限制**: 注意 API 调用速率限制
3. **超时设置**: 根据查询复杂度调整超时时间
4. **结果质量**: 置信度 (confidence) 表示答案质量
5. **迭代次数**: max_iterations 控制搜索深度
6. **并发限制**: 建议最多 3-5 个并发搜索
7. **成本控制**: 监控 API 使用量以控制成本
@@ -0,0 +1,117 @@
# Search Agent 集成完成总结
## ✅ 已完成的工作
### 1. 代码转换
- ✅ 将 aks_agent/search_agent 转换为 FastAPI 服务
- ✅ 创建 search_agent.py 作为 HTTP API 入口
- ✅ 支持健康检查、状态查询和搜索功能
### 2. Docker 镜像
- ✅ 创建支持 ARM64 和 AMD64 双架构的 Dockerfile
- ✅ 成功构建镜像: `agnettaiji.azurecr.io/ai-agents/search-agent:v1.0`
- ✅ 推送到 ACR
### 3. K8s 集成
- ✅ 创建 K8s YAML 模板 (`agent_manager/templates/search_agent.yaml`)
- ✅ 添加到数据库模板配置
- ✅ 可通过 agent-manager Web API 创建和管理
### 4. 构建和测试脚本
- ✅ `build_search_agent.sh` - 多架构构建和推送
- ✅ `test_search_agent.sh` - 集成测试脚本
- ✅ `SEARCH_AGENT_USAGE.md` - 使用文档
## 📁 文件结构
```
agent_templates/
├── search_agent.py # FastAPI 主服务
├── search_agent.Dockerfile # 多架构 Dockerfile
├── build_search_agent.sh # 构建脚本
├── test_search_agent.sh # 测试脚本
├── SEARCH_AGENT_USAGE.md # 使用文档
└── search_agent/ # 原始代码
├── agent/
├── modules/
├── tools/
├── models/
├── utils/
├── config.py
└── requirements.txt
agent_manager/templates/
└── search_agent.yaml # K8s 部署模板
```
## 🚀 使用方法
### 1. 通过 Agent Manager 创建
```bash
curl -X POST http://localhost:8000/agents \
-H "Content-Type: application/json" \
-d '{
"name": "my-search-agent",
"template": "search_agent",
"config": {
"cpu_request": "500m",
"memory_request": "512Mi"
},
"env": {
"LLM_BASE_URL": "https://apis.openroutex.com/openai/deployments/xchat52",
"LLM_API_KEY": "your-key",
"SERPER_API_KEY": "your-key",
"JINA_API_KEY": "your-key"
}
}'
```
### 2. 使用 Agent
```bash
# 健康检查
curl http://my-search-agent.ai-agents.svc.cluster.local:8080/health
# 执行搜索
curl -X POST http://my-search-agent.ai-agents.svc.cluster.local:8080/search \
-H "Content-Type: application/json" \
-d '{"query": "什么是Kubernetes?", "auto_configure": true}'
```
## 🔧 环境变量
### 必需
- `LLM_BASE_URL` - LLM API基础URL
- `LLM_API_KEY` - LLM API密钥
- `SERPER_API_KEY` - Serper API密钥(Google搜索)
- `JINA_API_KEY` - Jina API密钥(内容提取和重排序)
### 可选
- `LLM_MODEL` - LLM模型名称(默认: xchat52)
- `MAX_ITERATIONS` - 最大迭代次数(默认: 3)
- `MAX_RESULTS_PER_QUERY` - 每次搜索最大结果数(默认: 10)
- `LOG_LEVEL` - 日志级别(默认: INFO)
## 📊 资源配置
- **CPU Request**: 500m
- **CPU Limit**: 1000m
- **Memory Request**: 512Mi
- **Memory Limit**: 1Gi
- **Port**: 8080
- **支持架构**: linux/arm64, linux/amd64
## 🎯 下一步
1. 配置真实的 API keys
2. 测试搜索功能
3. 根据需要调整资源配置
4. 监控性能和日志
## 📝 注意事项
- ARM K8s 集群已支持
- 镜像已推送到 ACR
- 健康检查配置为 30-40 秒启动时间
- 支持自动配置和手动配置两种模式
+380
View File
@@ -0,0 +1,380 @@
# Search Agent 使用指南
## 简介
智能搜索 AI Agent 是一个基于大语言模型的搜索代理,能够理解用户查询意图、自动规划搜索策略、从多个来源获取信息,并生成高质量、有来源引用的答案。
## 架构
- **基础镜像**: `agnettaiji.azurecr.io/ai-agents/search-agent:v1.0`
- **支持平台**: linux/amd64, linux/arm64
- **端口**: 8080
- **协议**: HTTP/REST API
## 功能特点
- 🧠 **智能查询理解**: 分析用户意图,提取关键实体
- 📋 **搜索规划**: 智能分解问题,制定搜索策略
- 🔎 **多源搜索**: 支持 Web 搜索和新闻搜索
- 📄 **内容提取**: 智能提取网页核心内容
- 🎯 **结果排序**: 基于相关性重排搜索结果
- ✍️ **答案生成**: 综合信息生成结构化回答
- 🔄 **自我反思**: 评估答案质量,决定是否迭代
## 环境变量配置
### 必需配置
| 变量名 | 说明 | 示例 |
|--------|------|------|
| `LLM_BASE_URL` | LLM API 基础URL | `https://apis.openroutex.com/openai/deployments/xchat52` |
| `LLM_API_KEY` | LLM API 密钥 | `sk-xxx` |
| `SERPER_API_KEY` | Serper API 密钥(Google搜索) | `xxx` |
| `JINA_API_KEY` | Jina API 密钥(内容提取和重排序) | `jina_xxx` |
### 可选配置
| 变量名 | 说明 | 默认值 |
|--------|------|--------|
| `LLM_MODEL` | LLM 模型名称 | `xchat52` |
| `MAX_ITERATIONS` | 最大迭代次数 | `3` |
| `MAX_RESULTS_PER_QUERY` | 每次搜索最大结果数 | `10` |
| `CONTENT_MAX_LENGTH` | 内容最大长度 | `5000` |
| `LOG_LEVEL` | 日志级别 | `INFO` |
| `TIMEOUT` | 超时时间(秒) | `30` |
## 部署方式
### 方式 1: 通过 agent-manager Web API
```bash
curl -X POST http://localhost:8000/api/v2/agents \
-H "Content-Type: application/json" \
-d '{
"name": "my-search-agent",
"template_type": "search_agent",
"image": "agnettaiji.azurecr.io/ai-agents/search-agent:v1.0",
"replicas": 1,
"env_vars": {
"LLM_BASE_URL": "https://apis.openroutex.com/openai/deployments/xchat52",
"LLM_API_KEY": "your-llm-key",
"LLM_MODEL": "xchat52",
"SERPER_API_KEY": "your-serper-key",
"JINA_API_KEY": "your-jina-key",
"MAX_ITERATIONS": "3",
"LOG_LEVEL": "INFO"
},
"resources": {
"cpu_request": "500m",
"memory_request": "512Mi",
"cpu_limit": "1000m",
"memory_limit": "1Gi"
}
}'
```
### 方式 2: 直接使用 kubectl
```bash
# 创建命名空间(如果不存在)
kubectl create namespace agents
# 应用 YAML 配置
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-search-agent
namespace: agents
spec:
replicas: 1
selector:
matchLabels:
app: my-search-agent
template:
metadata:
labels:
app: my-search-agent
spec:
imagePullSecrets:
- name: acr-secret
containers:
- name: search-agent
image: agnettaiji.azurecr.io/ai-agents/search-agent:v1.0
ports:
- containerPort: 8080
env:
- name: LLM_BASE_URL
value: "https://apis.openroutex.com/openai/deployments/xchat52"
- name: LLM_API_KEY
value: "your-llm-key"
- name: SERPER_API_KEY
value: "your-serper-key"
- name: JINA_API_KEY
value: "your-jina-key"
---
apiVersion: v1
kind: Service
metadata:
name: my-search-agent
namespace: agents
spec:
selector:
app: my-search-agent
ports:
- port: 8080
targetPort: 8080
EOF
```
### 方式 3: 使用 Docker 本地测试
```bash
docker run -p 8080:8080 \
-e LLM_BASE_URL='https://apis.openroutex.com/openai/deployments/xchat52' \
-e LLM_API_KEY='your-llm-key' \
-e SERPER_API_KEY='your-serper-key' \
-e JINA_API_KEY='your-jina-key' \
agnettaiji.azurecr.io/ai-agents/search-agent:v1.0
```
## API 接口
### 健康检查
```bash
GET /health
# 响应示例
{
"status": "healthy",
"pod_name": "search-agent-xxx",
"template_type": "search_agent",
"configured": true,
"timestamp": "2026-01-13T04:45:00.000000"
}
```
### 获取状态
```bash
GET /status
# 响应示例
{
"status": "running",
"pod_name": "search-agent-xxx",
"template_type": "search_agent",
"configured": true,
"timestamp": "2026-01-13T04:45:00.000000"
}
```
### 配置 Agent(如果未通过环境变量配置)
```bash
POST /configure
Content-Type: application/json
{
"llm_base_url": "https://apis.openroutex.com/openai/deployments/xchat52",
"llm_api_key": "your-llm-key",
"llm_model": "xchat52",
"serper_api_key": "your-serper-key",
"jina_api_key": "your-jina-key",
"max_iterations": 3,
"max_results_per_query": 10,
"content_max_length": 5000,
"log_level": "INFO",
"timeout": 30
}
```
### 执行搜索
```bash
POST /search
Content-Type: application/json
{
"query": "什么是 Kubernetes?",
"auto_configure": false
}
# 响应示例
{
"query": "什么是 Kubernetes?",
"answer": "Kubernetes 是一个开源的容器编排平台...",
"sources": [
{
"index": 1,
"title": "Kubernetes 官方文档",
"url": "https://kubernetes.io/docs/"
}
],
"confidence": "high",
"iterations": 1,
"total_sources": 5,
"search_queries": ["Kubernetes 是什么", "Kubernetes 容器编排"],
"timestamp": "2026-01-13T04:45:00.000000"
}
```
### 聊天接口(别名)
```bash
POST /chat
Content-Type: application/json
{
"query": "Python 和 Go 语言的区别是什么?"
}
```
## 使用示例
### Python 客户端
```python
import requests
# Agent 服务地址
agent_url = "http://my-search-agent.agents.svc.cluster.local:8080"
# 执行搜索
response = requests.post(
f"{agent_url}/search",
json={
"query": "什么是微服务架构?",
"auto_configure": False
}
)
result = response.json()
print(f"答案: {result['answer']}")
print(f"来源数: {len(result['sources'])}")
print(f"置信度: {result['confidence']}")
```
### cURL 示例
```bash
# 搜索
curl -X POST http://my-search-agent:8080/search \
-H "Content-Type: application/json" \
-d '{
"query": "Docker 容器的优势是什么?"
}'
# 健康检查
curl http://my-search-agent:8080/health
```
## 构建和推送
### 构建镜像
```bash
cd /home/taiji/tools/agent-manager/agent_templates
./build_search_agent.sh v1.0
```
### 推送到 ACR
在构建过程中选择 `y` 推送,或手动推送:
```bash
# 登录 ACR
az acr login --name agnettaiji
# 构建并推送
docker buildx build \
--platform linux/amd64,linux/arm64 \
-f search_agent.Dockerfile \
-t agnettaiji.azurecr.io/ai-agents/search-agent:v1.0 \
--push \
.
```
## 测试
### 使用测试脚本
```bash
cd /home/taiji/tools/agent-manager/agent_templates
./test_search_agent.sh
```
### 手动测试
```bash
# 1. 创建 Agent
curl -X POST http://localhost:8000/api/v2/agents \
-H "Content-Type: application/json" \
-d @search_agent_config.json
# 2. 查看状态
curl http://localhost:8000/api/v2/agents/my-search-agent
# 3. 测试搜索
curl -X POST http://my-search-agent:8080/search \
-H "Content-Type: application/json" \
-d '{"query": "测试查询"}'
# 4. 删除 Agent
curl -X DELETE http://localhost:8000/api/v2/agents/my-search-agent
```
## 故障排查
### 查看日志
```bash
kubectl logs -n agents deployment/my-search-agent
```
### 查看 Pod 状态
```bash
kubectl get pods -n agents -l app=my-search-agent
kubectl describe pod -n agents <pod-name>
```
### 常见问题
1. **Agent 无法启动**
- 检查环境变量是否正确配置
- 确认 ACR secret 已创建
- 查看 Pod 事件和日志
2. **搜索失败**
- 确认 API keys 有效
- 检查网络连接
- 查看日志中的错误信息
3. **健康检查失败**
- 确认端口 8080 正常监听
- 检查容器资源是否充足
- 查看启动日志
## 相关文件
- `/home/taiji/tools/agent-manager/agent_templates/search_agent.py` - 主程序
- `/home/taiji/tools/agent-manager/agent_templates/search_agent.Dockerfile` - Dockerfile
- `/home/taiji/tools/agent-manager/agent_templates/build_search_agent.sh` - 构建脚本
- `/home/taiji/tools/agent-manager/agent_templates/test_search_agent.sh` - 测试脚本
- `/home/taiji/tools/agent-manager/agent_manager/templates/search_agent.yaml` - K8s 模板
## 技术栈
- **语言**: Python 3.11
- **框架**: FastAPI, Uvicorn
- **LLM**: xchat52 (GPT-5.2)
- **搜索**: Serper API (Google 搜索代理)
- **内容提取**: Jina Reader
- **重排序**: Jina Reranker
- **异步**: asyncio
## 许可
遵循项目主许可证。
+99
View File
@@ -0,0 +1,99 @@
# Agent Templates 更新总结
## 更新时间
2026-01-15
## 更新内容
### 1. 新增文件
- **agent_callback_utils.py**: 通用回调处理工具模块
- AgentCallbackHandler: 回调处理器类
- CallbackContextManager: 上下文管理器
### 2. 已更新的 Agent 文件
#### ✅ search_agent.py
- 添加回调功能
- API key 从请求传入
- 记录工具: web_search, content_reader
#### ✅ jina_search_agent.py
- 添加回调功能
- API key 从请求传入
- 记录工具: jina_reader
#### ✅ mysql_agent.py
- 从循环模式改为 FastAPI HTTP 服务
- 添加回调功能
- API key 从请求传入
- 记录工具: sql_database
#### ✅ postgresql_agent.py
- 从循环模式改为 FastAPI HTTP 服务
- 添加回调功能
- API key 从请求传入
- 记录工具: sql_database
#### ✅ azure_blob_agent.py
- 添加回调功能
- API key 从请求传入
- 记录工具: azure_blob_storage
### 3. 已更新的 Dockerfile
所有 Dockerfile 已更新以包含 agent_callback_utils.py:
- ✅ search_agent.Dockerfile
- ✅ mysql_agent.Dockerfile (添加 fastapi, uvicorn, requests)
- ✅ postgresql_agent.Dockerfile (添加 fastapi, uvicorn, requests)
- ✅ jina_search_agent.Dockerfile
- ✅ azure_blob_agent.Dockerfile
### 4. 请求模型更新
所有 agent 的请求模型都添加了:
```python
litellm_api_key/openai_api_key/jina_api_key: str # API key 从请求传入
user_id: Optional[str] # 用于计费回调
```
### 5. 回调数据格式
```json
{
"agentName": "pod-name",
"userId": "user-123",
"podRunningTimeSeconds": 120,
"toolsUsed": ["tool1", "tool2"],
"startTime": "2026-01-15T10:00:00Z",
"endTime": "2026-01-15T10:02:00Z",
"requestId": "req-xxx"
}
```
### 6. 环境变量
所有 Agent 需要设置:
```bash
POD_NAME=agent-name
USER_ID=default-user-id # 可选
AGENT_CALLBACK_URL=http://mcp-server:8002/api/v1/billing/agent-callback # 可选
```
## 下一步
1. 构建并推送更新后的镜像:
```bash
cd /home/taiji/tools/agent-manager/agent_templates
./build_search_agent.sh
./build_jina_agent.sh
# 等等...
```
2. 测试回调功能
3. 部署到 Kubernetes
---
详细文档请参考: CALLBACK_IMPLEMENTATION_SUMMARY.md
@@ -1,24 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# 安装Python依赖
RUN pip install --no-cache-dir \
fastapi==0.109.0 \
uvicorn==0.27.0 \
requests==2.31.0 \
pydantic==2.5.3
# 复制agent代码
COPY jina_search_agent.py .
# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV SERVICE_HOST=0.0.0.0
ENV SERVICE_PORT=8080
# 暴露端口
EXPOSE 8080
# 运行agent
CMD ["python", "jina_search_agent.py"]
-230
View File
@@ -1,230 +0,0 @@
"""
Jina Search Agent - 使用Jina Reader API获取网站内容的HTTP服务
需要设置环境变量: JINA_API_KEY
"""
import os
import time
import logging
import requests
from typing import Optional
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
import uvicorn
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 环境变量配置
POD_NAME = os.getenv("POD_NAME", "unknown")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "jina_search_agent")
JINA_API_KEY = os.getenv("JINA_API_KEY", "")
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0")
SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))
# Jina Reader API基础URL
JINA_BASE_URL = "https://r.jina.ai"
# 创建FastAPI应用
app = FastAPI(
title="Jina Search Agent",
description="使用Jina Reader API获取网站内容的AI Agent",
version="1.0.0"
)
class SearchRequest(BaseModel):
"""搜索请求模型"""
url: str = Field(..., description="要搜索的网站URL")
timeout: int = Field(default=30, description="请求超时时间(秒)")
class SearchResponse(BaseModel):
"""搜索响应模型"""
url: str
content: str
status_code: int
content_type: Optional[str] = None
success: bool
class HealthResponse(BaseModel):
"""健康检查响应"""
status: str
pod_name: str
template_type: str
jina_api_configured: bool
@app.get("/health", response_model=HealthResponse)
async def health_check():
"""
健康检查端点
Returns:
服务状态信息
"""
return HealthResponse(
status="healthy",
pod_name=POD_NAME,
template_type=TEMPLATE_TYPE,
jina_api_configured=bool(JINA_API_KEY)
)
@app.get("/")
async def root():
"""根路径 - 返回服务信息和所需参数"""
return {
"service": "Jina Search Agent",
"version": "1.0.0",
"description": "使用Jina Reader API获取网站内容的AI Agent",
"pod_name": POD_NAME,
"template_type": TEMPLATE_TYPE,
"required_env": {
"JINA_API_KEY": {
"description": "Jina API密钥,从 https://jina.ai/ 获取",
"required": True,
"configured": bool(JINA_API_KEY)
}
},
"optional_env": {
"SERVICE_PORT": {
"description": "HTTP服务端口",
"default": "8080"
},
"SERVICE_HOST": {
"description": "HTTP服务监听地址",
"default": "0.0.0.0"
}
},
"endpoints": {
"health": {
"method": "GET",
"path": "/health",
"description": "健康检查"
},
"search": {
"method": "POST",
"path": "/search",
"description": "搜索网站内容",
"body": {
"url": "要搜索的网站URL (必填)",
"timeout": "请求超时时间,默认30秒 (可选)"
}
},
"fetch": {
"method": "GET",
"path": "/fetch",
"description": "快速获取网站内容",
"params": {
"url": "要获取的网站URL (必填)",
"timeout": "请求超时时间,默认30秒 (可选)"
}
}
},
"example_usage": {
"search": 'curl -X POST "http://<pod-ip>:8080/search" -H "Content-Type: application/json" -d \'{"url": "https://www.example.com"}\'',
"fetch": 'curl "http://<pod-ip>:8080/fetch?url=https://www.example.com"'
}
}
@app.post("/search", response_model=SearchResponse)
async def search(request: SearchRequest):
"""
搜索网站内容
使用Jina Reader API获取指定URL的网站内容
Args:
request: 包含URL和选项的搜索请求
Returns:
网站内容和元数据
"""
if not JINA_API_KEY:
raise HTTPException(
status_code=500,
detail="JINA_API_KEY未配置,请设置环境变量"
)
logger.info(f"[{POD_NAME}] 搜索请求: {request.url}")
try:
# 构建Jina Reader API请求
jina_url = f"{JINA_BASE_URL}/{request.url}"
headers = {
"Authorization": f"Bearer {JINA_API_KEY}"
}
# 发送请求
response = requests.get(
jina_url,
headers=headers,
timeout=request.timeout
)
logger.info(f"[{POD_NAME}] Jina API响应状态: {response.status_code}")
return SearchResponse(
url=request.url,
content=response.text,
status_code=response.status_code,
content_type=response.headers.get("Content-Type"),
success=response.status_code == 200
)
except requests.exceptions.Timeout:
logger.error(f"[{POD_NAME}] 请求超时: {request.url}")
raise HTTPException(
status_code=504,
detail=f"请求超时({request.timeout}秒)"
)
except requests.exceptions.RequestException as e:
logger.error(f"[{POD_NAME}] 请求失败: {str(e)}")
raise HTTPException(
status_code=502,
detail=f"请求失败: {str(e)}"
)
@app.get("/fetch", response_model=SearchResponse)
async def fetch(
url: str = Query(..., description="要获取的网站URL"),
timeout: int = Query(default=30, description="请求超时时间(秒)")
):
"""
快速获取网站内容(GET方式)
Args:
url: 要获取的网站URL
timeout: 请求超时时间
Returns:
网站内容和元数据
"""
request = SearchRequest(url=url, timeout=timeout)
return await search(request)
def main():
"""主函数 - 启动HTTP服务"""
logger.info(f"Jina Search Agent启动: {POD_NAME} (模板: {TEMPLATE_TYPE})")
logger.info(f"服务地址: {SERVICE_HOST}:{SERVICE_PORT}")
logger.info(f"JINA_API_KEY已配置: {bool(JINA_API_KEY)}")
if not JINA_API_KEY:
logger.warning("⚠️ JINA_API_KEY未设置,API调用将失败")
# 启动uvicorn服务
uvicorn.run(
app,
host=SERVICE_HOST,
port=SERVICE_PORT,
log_level="info"
)
if __name__ == "__main__":
main()
-28
View File
@@ -1,28 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
default-libmysqlclient-dev \
build-essential \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
RUN pip install --no-cache-dir \
langchain==0.1.0 \
langchain-community==0.0.10 \
langchain-openai==0.0.2 \
openai==1.7.2 \
pymysql==1.1.0 \
sqlalchemy==2.0.23
# 复制agent代码
COPY mysql_agent.py .
# 设置环境变量
ENV PYTHONUNBUFFERED=1
# 运行agent
CMD ["python", "mysql_agent.py"]
-129
View File
@@ -1,129 +0,0 @@
"""
MySQL AI Agent - 使用LangChain实现的MySQL数据库查询代理
需要设置环境变量: MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, OPENAI_API_KEY
"""
import os
import time
import logging
from langchain_community.utilities import SQLDatabase
from langchain.agents import create_sql_agent
from langchain.agents.agent_toolkits import SQLDatabaseToolkit
from langchain_openai import ChatOpenAI
from langchain.agents.agent_types import AgentType
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
POD_NAME = os.getenv("POD_NAME", "unknown")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "mysql_agent")
# MySQL数据库配置
MYSQL_HOST = os.getenv("MYSQL_HOST", "localhost")
MYSQL_PORT = os.getenv("MYSQL_PORT", "3306")
MYSQL_USER = os.getenv("MYSQL_USER", "root")
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "")
MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "test")
# OpenAI配置
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
def create_mysql_agent():
"""创建MySQL数据库Agent"""
# 构建数据库URI
db_uri = f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}"
try:
# 连接数据库
db = SQLDatabase.from_uri(db_uri)
logger.info(f"✅ 成功连接到MySQL数据库: {MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}")
# 显示可用的表
tables = db.get_usable_table_names()
logger.info(f"可用的表: {tables}")
except Exception as e:
logger.error(f"❌ 数据库连接失败: {str(e)}")
return None
# 初始化LLM
if not OPENAI_API_KEY:
logger.error("❌ 未设置OPENAI_API_KEY")
return None
llm = ChatOpenAI(
temperature=0,
model="gpt-3.5-turbo",
openai_api_key=OPENAI_API_KEY
)
# 创建SQL工具包
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
# 创建SQL Agent
agent_executor = create_sql_agent(
llm=llm,
toolkit=toolkit,
agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
handle_parsing_errors=True,
max_iterations=5
)
return agent_executor
def main():
"""主函数 - MySQL Agent主循环"""
logger.info(f"MySQL Agent启动: {POD_NAME} (模板: {TEMPLATE_TYPE})")
logger.info(f"数据库配置: {MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}")
# 创建Agent
agent = create_mysql_agent()
if agent is None:
logger.error("Agent创建失败,请检查配置")
# 保持容器运行
while True:
logger.info(f"[{POD_NAME}] 等待正确的配置...")
time.sleep(30)
return
logger.info("✅ MySQL Agent创建成功,开始运行...")
# 示例查询列表
sample_queries = [
"列出数据库中所有的表",
"描述第一个表的结构",
"统计每个表的记录数",
"显示最近的5条记录",
]
query_index = 0
while True:
try:
# 每2分钟执行一次示例查询
query = sample_queries[query_index % len(sample_queries)]
logger.info(f"\n{'='*60}")
logger.info(f"📊 执行查询: {query}")
logger.info(f"{'='*60}\n")
# 执行Agent
result = agent.invoke({"input": query})
logger.info(f"\n✅ 结果:\n{result['output']}\n")
query_index += 1
except Exception as e:
logger.error(f"❌ 查询执行失败: {str(e)}")
# 等待120秒后执行下一个查询
logger.info(f"[{POD_NAME}] 等待下一次查询...")
time.sleep(120)
if __name__ == "__main__":
main()
@@ -1,27 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
libpq-dev \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
RUN pip install --no-cache-dir \
langchain==0.1.0 \
langchain-community==0.0.10 \
langchain-openai==0.0.2 \
openai==1.7.2 \
psycopg2-binary==2.9.9 \
sqlalchemy==2.0.23
# 复制agent代码
COPY postgresql_agent.py .
# 设置环境变量
ENV PYTHONUNBUFFERED=1
# 运行agent
CMD ["python", "postgresql_agent.py"]
-130
View File
@@ -1,130 +0,0 @@
"""
PostgreSQL AI Agent - 使用LangChain实现的PostgreSQL数据库查询代理
需要设置环境变量: POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DATABASE, OPENAI_API_KEY
"""
import os
import time
import logging
from langchain_community.utilities import SQLDatabase
from langchain.agents import create_sql_agent
from langchain.agents.agent_toolkits import SQLDatabaseToolkit
from langchain_openai import ChatOpenAI
from langchain.agents.agent_types import AgentType
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
POD_NAME = os.getenv("POD_NAME", "unknown")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "postgresql_agent")
# PostgreSQL数据库配置
POSTGRES_HOST = os.getenv("POSTGRES_HOST", "localhost")
POSTGRES_PORT = os.getenv("POSTGRES_PORT", "5432")
POSTGRES_USER = os.getenv("POSTGRES_USER", "postgres")
POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD", "")
POSTGRES_DATABASE = os.getenv("POSTGRES_DATABASE", "postgres")
# OpenAI配置
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
def create_postgresql_agent():
"""创建PostgreSQL数据库Agent"""
# 构建数据库URI
db_uri = f"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DATABASE}"
try:
# 连接数据库
db = SQLDatabase.from_uri(db_uri)
logger.info(f"✅ 成功连接到PostgreSQL数据库: {POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DATABASE}")
# 显示可用的表
tables = db.get_usable_table_names()
logger.info(f"可用的表: {tables}")
except Exception as e:
logger.error(f"❌ 数据库连接失败: {str(e)}")
return None
# 初始化LLM
if not OPENAI_API_KEY:
logger.error("❌ 未设置OPENAI_API_KEY")
return None
llm = ChatOpenAI(
temperature=0,
model="gpt-3.5-turbo",
openai_api_key=OPENAI_API_KEY
)
# 创建SQL工具包
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
# 创建SQL Agent
agent_executor = create_sql_agent(
llm=llm,
toolkit=toolkit,
agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
handle_parsing_errors=True,
max_iterations=5
)
return agent_executor
def main():
"""主函数 - PostgreSQL Agent主循环"""
logger.info(f"PostgreSQL Agent启动: {POD_NAME} (模板: {TEMPLATE_TYPE})")
logger.info(f"数据库配置: {POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DATABASE}")
# 创建Agent
agent = create_postgresql_agent()
if agent is None:
logger.error("Agent创建失败,请检查配置")
# 保持容器运行
while True:
logger.info(f"[{POD_NAME}] 等待正确的配置...")
time.sleep(30)
return
logger.info("✅ PostgreSQL Agent创建成功,开始运行...")
# 示例查询列表
sample_queries = [
"列出数据库中所有的表和视图",
"描述每个表的结构和主键",
"统计每个表的记录数",
"查询数据库的版本信息",
"显示最大的3个表",
]
query_index = 0
while True:
try:
# 每2分钟执行一次示例查询
query = sample_queries[query_index % len(sample_queries)]
logger.info(f"\n{'='*60}")
logger.info(f"🐘 执行查询: {query}")
logger.info(f"{'='*60}\n")
# 执行Agent
result = agent.invoke({"input": query})
logger.info(f"\n✅ 结果:\n{result['output']}\n")
query_index += 1
except Exception as e:
logger.error(f"❌ 查询执行失败: {str(e)}")
# 等待120秒后执行下一个查询
logger.info(f"[{POD_NAME}] 等待下一次查询...")
time.sleep(120)
if __name__ == "__main__":
main()
-6
View File
@@ -1,6 +0,0 @@
# Requirements for Azure Blob Agent - A2A Version
fastapi==0.109.0
uvicorn[standard]==0.27.0
pydantic==2.5.3
azure-storage-blob==12.19.0
httpx==0.26.0
-5
View File
@@ -1,5 +0,0 @@
# Requirements for Azure Blob Agent - MCP Version
fastapi==0.109.0
uvicorn[standard]==0.27.0
pydantic==2.5.3
azure-storage-blob==12.19.0
+126
View File
@@ -0,0 +1,126 @@
#!/bin/bash
# 批量构建所有更新的 Agent 并推送到 ACR (ARM64)
# 使用方法: ./build_all_agents.sh
set -e
# 默认配置
ACR_NAME="${ACR_NAME:-agnettaiji.azurecr.io}"
TAG="${1:-latest}"
PLATFORM="linux/arm64"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 批量构建 Agent Templates (ARM64)${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# 登录 ACR
echo -e "${GREEN}==> 登录到 ACR...${NC}"
az acr login --name $(echo ${ACR_NAME} | cut -d'.' -f1)
# 检查 Docker buildx
if ! docker buildx version > /dev/null 2>&1; then
echo -e "${RED}错误: Docker buildx 未安装${NC}"
exit 1
fi
# 创建 builder
if ! docker buildx ls | grep -q multiarch-builder; then
echo -e "${YELLOW}创建 multiarch-builder...${NC}"
docker buildx create --name multiarch-builder --use
fi
docker buildx use multiarch-builder
# Agent 列表
declare -A AGENTS=(
["search-agent"]="search_agent.Dockerfile"
["jina-search-agent"]="jina_search_agent.Dockerfile"
["mysql-agent"]="mysql_agent.Dockerfile"
["postgresql-agent"]="postgresql_agent.Dockerfile"
["azure-blob-agent"]="azure_blob_agent.Dockerfile"
)
# 构建函数
build_agent() {
local name=$1
local dockerfile=$2
local image="${ACR_NAME}/ai-agents/${name}:${TAG}"
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}构建: ${name}${NC}"
echo -e "${BLUE}========================================${NC}"
echo "Dockerfile: ${dockerfile}"
echo "镜像: ${image}"
echo "平台: ${PLATFORM}"
echo ""
# 构建并推送
docker buildx build \
--platform ${PLATFORM} \
-f ${dockerfile} \
-t ${image} \
--push \
.
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ ${name} 构建成功${NC}"
else
echo -e "${RED}❌ ${name} 构建失败${NC}"
return 1
fi
}
# 构建所有 Agent
SUCCESS_COUNT=0
FAIL_COUNT=0
FAILED_AGENTS=()
for name in "${!AGENTS[@]}"; do
dockerfile="${AGENTS[$name]}"
if [ -f "${dockerfile}" ]; then
if build_agent "${name}" "${dockerfile}"; then
((SUCCESS_COUNT++))
else
((FAIL_COUNT++))
FAILED_AGENTS+=("${name}")
fi
else
echo -e "${YELLOW}⚠️ 跳过 ${name}: Dockerfile ${dockerfile} 不存在${NC}"
fi
done
# 总结
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 构建总结${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}成功: ${SUCCESS_COUNT}${NC}"
echo -e "${RED}失败: ${FAIL_COUNT}${NC}"
if [ ${FAIL_COUNT} -gt 0 ]; then
echo -e "${RED}失败的 Agents:${NC}"
for agent in "${FAILED_AGENTS[@]}"; do
echo -e " - ${agent}"
done
exit 1
fi
echo ""
echo -e "${GREEN}✅ 所有 Agent 构建成功!${NC}"
echo ""
echo "已推送的镜像:"
for name in "${!AGENTS[@]}"; do
echo " - ${ACR_NAME}/ai-agents/${name}:${TAG}"
done
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
# Intelligent Search Agent 构建和推送脚本(支持多架构)
# 使用方法: ./build_search_agent.sh [TAG]
set -e
# 默认配置
ACR_NAME="${ACR_NAME:-agnettaiji.azurecr.io}"
IMAGE_NAME="ai-agents/search-agent"
TAG="${1:-latest}"
FULL_IMAGE="${ACR_NAME}/${IMAGE_NAME}:${TAG}"
# 支持的平台
PLATFORMS="linux/amd64,linux/arm64"
echo "=========================================="
echo "构建 Intelligent Search Agent"
echo "=========================================="
echo "镜像: ${FULL_IMAGE}"
echo "平台: ${PLATFORMS}"
echo ""
# 检查Docker buildx
if ! docker buildx version > /dev/null 2>&1; then
echo "错误: Docker buildx未安装或未启用"
echo "请运行: docker buildx create --use"
exit 1
fi
# 创建builder(如果不存在)
if ! docker buildx ls | grep -q multiarch-builder; then
echo "创建 multiarch-builder..."
docker buildx create --name multiarch-builder --use
fi
# 使用multiarch-builder
docker buildx use multiarch-builder
# 询问是否推送
read -p "是否推送到 ACR? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "🚀 构建并推送多架构镜像到 ACR..."
# 登录 ACR (如果需要)
echo "登录到 ACR..."
az acr login --name $(echo ${ACR_NAME} | cut -d'.' -f1)
# 构建并推送镜像(多架构)
docker buildx build \
--platform "${PLATFORMS}" \
-f search_agent.Dockerfile \
-t "${FULL_IMAGE}" \
--push \
.
echo ""
echo "✅ 镜像构建并推送成功!"
echo ""
echo "部署到 K8s (ARM64):"
echo " kubectl set image deployment/search-agent search-agent=${FULL_IMAGE}"
else
echo "⏭️ 只构建本地镜像 (linux/arm64)..."
docker buildx build \
--platform "linux/arm64" \
-f search_agent.Dockerfile \
-t "${FULL_IMAGE}" \
--load \
.
echo ""
echo "✅ 本地镜像构建成功!"
fi
echo ""
echo "=========================================="
echo "完成!"
echo "=========================================="
echo "镜像: ${FULL_IMAGE}"
echo "支持平台: ${PLATFORMS}"
echo ""
echo "测试命令:"
echo " docker run -p 8080:8080 \\"
echo " -e LLM_BASE_URL='your_llm_url' \\"
echo " -e LLM_API_KEY='your_llm_key' \\"
echo " -e SERPER_API_KEY='your_serper_key' \\"
echo " -e JINA_API_KEY='your_jina_key' \\"
echo " ${FULL_IMAGE}"
echo ""
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
# 创建临时容器检查镜像内容
kubectl run test-image-content --rm -i --image=agnettaiji.azurecr.io/ai-agents/search-agent:v2.0-1768476670 --restart=Never -- sh -c "head -20 /app/search_agent.py | grep -E '^from|^import'"
+83
View File
@@ -0,0 +1,83 @@
#!/bin/bash
# 重新构建所有 Agent 镜像并推送到 ACR (ARM64)
set -e
ACR_NAME="agnettaiji.azurecr.io"
TAG="latest"
PLATFORM="linux/arm64"
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 重新构建所有 Agent 镜像${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# 登录 ACR
echo -e "${GREEN}登录 ACR...${NC}"
az acr login --name $(echo ${ACR_NAME} | cut -d'.' -f1)
# Agent 列表
declare -a AGENTS=(
"search-agent:search_agent.Dockerfile"
"jina-search-agent:jina_search_agent.Dockerfile"
"mysql-agent:mysql_agent.Dockerfile"
"postgresql-agent:postgresql_agent.Dockerfile"
"azure-blob-agent:azure_blob_agent.Dockerfile"
)
SUCCESS=0
FAILED=0
for item in "${AGENTS[@]}"; do
IFS=':' read -r name dockerfile <<< "$item"
image="${ACR_NAME}/ai-agents/${name}:${TAG}"
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}构建: ${name}${NC}"
echo -e "${BLUE}========================================${NC}"
echo "镜像: ${image}"
echo "平台: ${PLATFORM}"
echo ""
if docker buildx build --platform ${PLATFORM} \
-f ${dockerfile} \
-t ${image} \
--push \
. ; then
echo -e "${GREEN}✅ ${name} 构建成功${NC}"
((SUCCESS++))
else
echo -e "${RED}❌ ${name} 构建失败${NC}"
((FAILED++))
fi
done
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 构建总结${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}成功: ${SUCCESS}${NC}"
echo -e "${RED}失败: ${FAILED}${NC}"
echo ""
if [ ${FAILED} -eq 0 ]; then
echo -e "${GREEN}✅ 所有镜像构建成功!${NC}"
echo ""
echo "已推送的镜像:"
for item in "${AGENTS[@]}"; do
IFS=':' read -r name _ <<< "$item"
echo " - ${ACR_NAME}/ai-agents/${name}:${TAG}"
done
else
echo -e "${RED}❌ 部分镜像构建失败${NC}"
exit 1
fi
-148
View File
@@ -1,148 +0,0 @@
#!/bin/bash
# Azure Blob Storage Agent 本地测试脚本
# 使用方法: ./test_azure_blob_agent.sh
set -e
AGENT_HOST="localhost"
AGENT_PORT="8080"
BASE_URL="http://${AGENT_HOST}:${AGENT_PORT}"
echo "=========================================="
echo "Azure Blob Storage Agent 测试脚本"
echo "=========================================="
echo ""
# 颜色定义
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 测试函数
test_endpoint() {
local name=$1
local method=$2
local endpoint=$3
local data=$4
echo -e "${YELLOW}测试: ${name}${NC}"
echo "请求: ${method} ${endpoint}"
if [ -z "$data" ]; then
response=$(curl -s -w "\n%{http_code}" -X ${method} "${BASE_URL}${endpoint}")
else
response=$(curl -s -w "\n%{http_code}" -X ${method} "${BASE_URL}${endpoint}" \
-H 'Content-Type: application/json' \
-d "${data}")
fi
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" -eq 200 ] || [ "$http_code" -eq 201 ]; then
echo -e "${GREEN}✅ 成功 (HTTP $http_code)${NC}"
echo "响应: $body" | jq '.' 2>/dev/null || echo "$body"
else
echo -e "${RED}❌ 失败 (HTTP $http_code)${NC}"
echo "响应: $body"
fi
echo ""
}
# 1. 检查容器是否运行
echo "1️⃣ 检查容器状态..."
if docker ps | grep -q azure-blob-agent; then
echo -e "${GREEN}✅ 容器正在运行${NC}"
else
echo -e "${RED}❌ 容器未运行${NC}"
echo "请先启动容器:"
echo "docker run -d --name azure-blob-agent -p 8080:8080 \\"
echo " -e LITELLM_API_BASE=http://host.docker.internal:4000 \\"
echo " -e LITELLM_MODEL=gpt-3.5-turbo \\"
echo " -e LITELLM_API_KEY=sk-1234 \\"
echo " azure-blob-agent:latest"
exit 1
fi
echo ""
# 2. 等待服务就绪
echo "2️⃣ 等待服务就绪..."
max_attempts=30
attempt=0
while [ $attempt -lt $max_attempts ]; do
if curl -s "${BASE_URL}/health" > /dev/null 2>&1; then
echo -e "${GREEN}✅ 服务已就绪${NC}"
break
fi
attempt=$((attempt + 1))
echo -n "."
sleep 1
done
if [ $attempt -eq $max_attempts ]; then
echo -e "${RED}❌ 服务启动超时${NC}"
exit 1
fi
echo ""
# 3. 健康检查
test_endpoint "健康检查" "GET" "/health"
# 4. 根端点
test_endpoint "根端点" "GET" "/"
# 5. 连接到 Azure Storage(需要用户提供连接字符串)
echo -e "${YELLOW}=========================================="
echo "连接到 Azure Storage"
echo "==========================================${NC}"
echo ""
echo "请输入 Azure Storage 连接字符串:"
echo "(格式: DefaultEndpointsProtocol=https;AccountName=xxx;AccountKey=xxx;EndpointSuffix=core.windows.net)"
echo ""
read -r CONNECTION_STRING
if [ -z "$CONNECTION_STRING" ]; then
echo -e "${YELLOW}⏭️ 跳过连接测试${NC}"
else
connect_data="{\"connection_string\": \"${CONNECTION_STRING}\"}"
test_endpoint "连接 Azure Storage" "POST" "/connect" "$connect_data"
# 6. 查询测试(仅在连接成功后)
echo -e "${YELLOW}=========================================="
echo "自然语言查询测试"
echo "==========================================${NC}"
echo ""
# 列出容器
query_data='{"query": "列出所有容器"}'
test_endpoint "查询: 列出所有容器" "POST" "/query" "$query_data"
# 获取统计信息
query_data='{"query": "显示存储统计信息"}'
test_endpoint "查询: 存储统计" "POST" "/query" "$query_data"
# 自定义查询
echo -e "${YELLOW}输入自定义查询(按Enter跳过):${NC}"
read -r CUSTOM_QUERY
if [ ! -z "$CUSTOM_QUERY" ]; then
query_data="{\"query\": \"${CUSTOM_QUERY}\"}"
test_endpoint "自定义查询" "POST" "/query" "$query_data"
fi
fi
echo ""
echo "=========================================="
echo -e "${GREEN}测试完成!${NC}"
echo "=========================================="
echo ""
echo "查看日志:"
echo " docker logs -f azure-blob-agent"
echo ""
echo "停止容器:"
echo " docker stop azure-blob-agent"
echo " docker rm azure-blob-agent"
echo ""
-164
View File
@@ -1,164 +0,0 @@
#!/usr/bin/env python3
"""
Azure Blob Storage Agent 客户端示例
演示如何使用 Python 调用 agent API
"""
import requests
import json
import os
import sys
# Agent 配置
AGENT_BASE_URL = os.getenv("AGENT_URL", "http://localhost:8080")
class AzureBlobAgentClient:
"""Azure Blob Storage Agent 客户端"""
def __init__(self, base_url: str = AGENT_BASE_URL):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.connected = False
def health_check(self) -> dict:
"""健康检查"""
response = self.session.get(f"{self.base_url}/health")
response.raise_for_status()
return response.json()
def connect(self, connection_string: str) -> dict:
"""连接到 Azure Storage"""
response = self.session.post(
f"{self.base_url}/connect",
json={"connection_string": connection_string}
)
response.raise_for_status()
result = response.json()
self.connected = True
return result
def query(self, query_text: str, container_name: str = None) -> dict:
"""执行自然语言查询"""
if not self.connected:
raise Exception("未连接到 Azure Storage,请先调用 connect()")
payload = {"query": query_text}
if container_name:
payload["container_name"] = container_name
response = self.session.post(
f"{self.base_url}/query",
json=payload
)
response.raise_for_status()
return response.json()
def get_info(self) -> dict:
"""获取 agent 信息"""
response = self.session.get(f"{self.base_url}/")
response.raise_for_status()
return response.json()
def print_response(title: str, response: dict):
"""格式化打印响应"""
print(f"\n{'='*60}")
print(f"📋 {title}")
print('='*60)
print(json.dumps(response, indent=2, ensure_ascii=False))
def main():
"""主函数"""
print("🚀 Azure Blob Storage Agent 客户端")
print(f"连接到: {AGENT_BASE_URL}\n")
# 创建客户端
client = AzureBlobAgentClient()
try:
# 1. 健康检查
print("1️⃣ 执行健康检查...")
health = client.health_check()
print_response("健康检查", health)
# 2. 获取 agent 信息
print("\n2️⃣ 获取 Agent 信息...")
info = client.get_info()
print_response("Agent 信息", info)
# 3. 连接到 Azure Storage
print("\n3️⃣ 连接到 Azure Storage...")
# 从环境变量获取连接字符串
connection_string = os.getenv("AZURE_STORAGE_CONNECTION_STRING")
if not connection_string:
print("\n⚠️ 未设置 AZURE_STORAGE_CONNECTION_STRING 环境变量")
print("请输入 Azure Storage 连接字符串:")
connection_string = input().strip()
if not connection_string:
print("❌ 未提供连接字符串,退出")
sys.exit(1)
connect_result = client.connect(connection_string)
print_response("连接结果", connect_result)
# 4. 执行查询
print("\n4️⃣ 执行自然语言查询...\n")
queries = [
"列出所有容器",
"显示存储统计信息",
]
for query_text in queries:
print(f"\n💬 查询: {query_text}")
result = client.query(query_text)
print(f"\n✅ 答案:\n{result.get('answer', 'N/A')}")
print(f"\n状态: {result.get('status')}")
# 5. 交互式查询
print("\n5️⃣ 交互式查询")
print("="*60)
print("输入自然语言查询(输入 'quit' 或 'exit' 退出):")
print("例如:")
print(" - 列出所有容器")
print(" - 显示 images 容器中的文件")
print(" - 在 documents 容器中搜索 report")
print(" - 获取存储统计信息")
print("="*60)
while True:
try:
query_text = input("\n💬 > ").strip()
if query_text.lower() in ['quit', 'exit', 'q']:
print("👋 再见!")
break
if not query_text:
continue
result = client.query(query_text)
print(f"\n✅ 答案:\n{result.get('answer', 'N/A')}")
except KeyboardInterrupt:
print("\n\n👋 再见!")
break
except Exception as e:
print(f"\n❌ 查询失败: {str(e)}")
except requests.exceptions.ConnectionError:
print(f"\n❌ 无法连接到 Agent: {AGENT_BASE_URL}")
print("请确保 Agent 正在运行")
sys.exit(1)
except Exception as e:
print(f"\n❌ 错误: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
-172
View File
@@ -1,172 +0,0 @@
#!/bin/bash
# 测试 Azure Blob Agent 多框架版本
# 用法: ./test_multi_framework.sh
set -e
echo "🧪 测试 Azure Blob Agent 多框架版本"
echo "======================================"
# 配置
AGENT_MANAGER_URL="http://localhost:8000"
OWNER_ID="test-user"
NAMESPACE="ai-agents"
# Azure Storage 连接字符串(从环境变量获取)
STORAGE_CONN_STRING="${AZURE_STORAGE_CONNECTION_STRING}"
if [ -z "$STORAGE_CONN_STRING" ]; then
echo "❌ 错误: 请设置环境变量 AZURE_STORAGE_CONNECTION_STRING"
exit 1
fi
# 模型配置(从环境变量获取)
MODEL_API_KEY="${OPENAI_API_KEY:-sk-test}"
echo ""
echo "📋 配置信息:"
echo " - Agent Manager: $AGENT_MANAGER_URL"
echo " - Owner ID: $OWNER_ID"
echo " - Namespace: $NAMESPACE"
echo ""
# 测试函数
test_agent() {
local framework=$1
local template=$2
local agent_name=$3
local extra_config=$4
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🧪 测试 $framework 版本"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# 构建请求 JSON
local request_json=$(cat <<EOF
{
"name": "$agent_name",
"template_name": "$template",
"owner_id": "$OWNER_ID",
"namespace": "$NAMESPACE",
"agent_framework": "$framework",
"storage_connection_string": "$STORAGE_CONN_STRING",
"model_provider": "openai",
"model_name": "gpt-4",
"model_api_key": "$MODEL_API_KEY",
"tools_config": {
"max_iterations": 5
}
$extra_config
}
EOF
)
echo "📤 创建 Agent..."
response=$(curl -s -X POST "$AGENT_MANAGER_URL/v2/agents/platform" \
-H "Content-Type: application/json" \
-d "$request_json")
echo "✅ 响应: $response"
# 检查是否创建成功
if echo "$response" | grep -q "id"; then
echo "✅ Agent 创建成功"
# 等待 Agent 启动
echo "⏳ 等待 Agent 启动..."
sleep 10
# 获取 Agent 状态
echo "📊 获取 Agent 状态..."
status_response=$(curl -s "$AGENT_MANAGER_URL/v2/agents/$agent_name")
echo "$status_response" | jq '.'
# 提取 service_url
service_url=$(echo "$status_response" | jq -r '.service_url // empty')
if [ -n "$service_url" ]; then
echo "🌐 Service URL: $service_url"
# 测试健康检查
echo "💓 测试健康检查..."
health_response=$(curl -s "$service_url/health")
echo "$health_response" | jq '.'
# 根据框架测试特定功能
case $framework in
"mcp")
echo "🔧 测试 MCP 工具列表..."
curl -s "$service_url/mcp/tools" | jq '.'
echo "🔧 测试 MCP 工具调用..."
curl -s -X POST "$service_url/mcp/call" \
-H "Content-Type: application/json" \
-d '{"tool_name": "list_containers", "parameters": {}}' | jq '.'
;;
"a2a")
echo "🤝 测试 A2A 能力..."
curl -s "$service_url/a2a/capabilities" | jq '.'
echo "🤝 测试 A2A 消息..."
curl -s -X POST "$service_url/a2a/message" \
-H "Content-Type: application/json" \
-d '{
"message_id": "test-001",
"from_agent": "test-agent",
"to_agent": "blob-agent",
"message_type": "request",
"action": "list_containers",
"parameters": {}
}' | jq '.'
;;
"langchain")
echo "🔗 测试查询..."
curl -s -X POST "$service_url/query" \
-H "Content-Type: application/json" \
-d '{"query": "列出所有容器"}' | jq '.'
;;
esac
else
echo "⚠️ 警告: 未找到 service_url"
fi
# 删除测试 Agent
echo "🗑️ 删除测试 Agent..."
delete_response=$(curl -s -X DELETE "$AGENT_MANAGER_URL/v2/agents/$agent_name")
echo "$delete_response" | jq '.'
else
echo "❌ Agent 创建失败"
echo "$response"
return 1
fi
echo ""
}
# 运行测试
echo "🚀 开始测试..."
echo ""
# 测试 MCP 版本
test_agent "mcp" "azure_blob_agent_mcp" "test-blob-mcp" ""
# 测试 A2A 版本
test_agent "a2a" "azure_blob_agent_a2a" "test-blob-a2a" ',
"query_params": {
"agent_id": "test-blob-a2a",
"agent_role": "storage_manager"
}'
# 测试 LangChain 版本(如果已部署)
# test_agent "langchain" "azure_blob_agent" "test-blob-langchain" ',
# "environment_vars": {
# "LITELLM_API_BASE": "http://litellm-service:4000",
# "LITELLM_MODEL": "gpt-3.5-turbo",
# "LITELLM_API_KEY": "sk-test"
# }'
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ 所有测试完成"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+118
View File
@@ -0,0 +1,118 @@
#!/bin/bash
# Search Agent 测试脚本
# 测试通过 agent-manager 创建和使用 search agent
set -e
AGENT_MANAGER_URL="${AGENT_MANAGER_URL:-http://localhost:8000}"
AGENT_NAME="test-search-agent-$(date +%s)"
IMAGE="agnettaiji.azurecr.io/ai-agents/search-agent:v1.0"
echo "=========================================="
echo "Search Agent 集成测试"
echo "=========================================="
echo "Agent Manager: ${AGENT_MANAGER_URL}"
echo "Agent Name: ${AGENT_NAME}"
echo "Image: ${IMAGE}"
echo ""
# 1. 创建 Agent
echo "📝 步骤 1: 创建 Search Agent..."
CREATE_RESPONSE=$(curl -s -X POST "${AGENT_MANAGER_URL}/agents" \
-H "Content-Type: application/json" \
-d '{
"name": "'"${AGENT_NAME}"'",
"template": "search_agent",
"config": {
"cpu_request": "500m",
"memory_request": "512Mi",
"cpu_limit": "1000m",
"memory_limit": "1Gi"
},
"env": {
"LLM_BASE_URL": "https://apis.openroutex.com/openai/deployments/xchat52",
"LLM_API_KEY": "your-llm-key",
"LLM_MODEL": "xchat52",
"SERPER_API_KEY": "your-serper-key",
"JINA_API_KEY": "your-jina-key",
"MAX_ITERATIONS": "3",
"LOG_LEVEL": "INFO"
}
}')
echo "响应: ${CREATE_RESPONSE}"
echo ""
# 检查创建是否成功
if echo "${CREATE_RESPONSE}" | grep -q "error"; then
echo "❌ 创建失败"
exit 1
fi
echo "✅ Agent 创建成功"
echo ""
# 2. 等待 Agent 启动
echo "⏳ 步骤 2: 等待 Agent 启动..."
sleep 10
# 3. 获取 Agent 状态
echo "📊 步骤 3: 获取 Agent 状态..."
STATUS_RESPONSE=$(curl -s "${AGENT_MANAGER_URL}/agents/${AGENT_NAME}/status")
echo "状态: ${STATUS_RESPONSE}"
echo ""
# 4. 获取 Agent 服务 URL
SERVICE_URL=$(echo "${STATUS_RESPONSE}" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('service_url', ''))" 2>/dev/null || echo "")
echo "Service URL: ${SERVICE_URL}"
echo ""
if [ -z "${SERVICE_URL}" ]; then
echo "❌ 无法获取 Service URL"
exit 1
fi
# 5. 测试健康检查
echo "🏥 步骤 4: 测试健康检查..."
HEALTH_RESPONSE=$(curl -s "${SERVICE_URL}/health")
echo "健康检查响应: ${HEALTH_RESPONSE}"
echo ""
# 6. 测试搜索功能(如果有配置的 API keys)
echo "🔍 步骤 5: 测试搜索功能..."
SEARCH_RESPONSE=$(curl -s -X POST "${SERVICE_URL}/search" \
-H "Content-Type: application/json" \
-d '{
"query": "什么是Kubernetes?",
"auto_configure": true
}')
echo "搜索响应: ${SEARCH_RESPONSE}"
echo ""
# 7. 列出所有 Agents
echo "📋 步骤 6: 列出所有 Agents..."
LIST_RESPONSE=$(curl -s "${AGENT_MANAGER_URL}/agents")
echo "Agent 列表: ${LIST_RESPONSE}"
echo ""
# 8. 清理(可选)
read -p "是否删除测试 Agent? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "🗑️ 删除 Agent..."
DELETE_RESPONSE=$(curl -s -X DELETE "${AGENT_MANAGER_URL}/agents/${AGENT_NAME}")
echo "删除响应: ${DELETE_RESPONSE}"
echo ""
echo "✅ Agent 已删除"
else
echo "⏭️ 保留 Agent: ${AGENT_NAME}"
echo ""
echo "手动删除命令:"
echo " curl -X DELETE ${AGENT_MANAGER_URL}/agents/${AGENT_NAME}"
fi
echo ""
echo "=========================================="
echo "测试完成!"
echo "=========================================="
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""测试 search_agent.py 的导入"""
import sys
import os
# 模拟 Docker 容器中的路径结构
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'search_agent'))
print("当前工作目录:", os.getcwd())
print("Python 路径:", sys.path[:3])
print("")
try:
print("测试导入 config...")
from config import Config
print("✅ config.Config 导入成功")
print("\n测试导入 agent.search_agent...")
from agent.search_agent import SearchAgent
print("✅ agent.search_agent.SearchAgent 导入成功")
print("\n测试导入 agent_callback_utils...")
from agent_callback_utils import AgentCallbackHandler, CallbackContextManager
print("✅ agent_callback_utils 导入成功")
print("\n" + "="*50)
print("✅ 所有导入测试通过!")
print("="*50)
except Exception as e:
print(f"\n❌ 导入失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
+317
View File
@@ -0,0 +1,317 @@
#!/bin/bash
##############################################################################
# Agent Manager - Kubernetes 部署脚本 (ARM64 架构)
# 用途: 自动构建 Docker 镜像并部署到 Kubernetes
##############################################################################
set -e # 遇到错误立即退出
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 配置变量
ACR_NAME="agnettaiji"
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
IMAGE_NAME="agent-manager"
IMAGE_TAG="latest-arm64"
FULL_IMAGE_NAME="${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}"
NAMESPACE="agent-manager"
K8S_DIR="./k8s"
# Azure 凭据配置(需要替换为实际值)
AZURE_TENANT_ID="${AZURE_TENANT_ID:-your-tenant-id}"
AZURE_CLIENT_ID="${AZURE_CLIENT_ID:-your-client-id}"
AZURE_CLIENT_SECRET="${AZURE_CLIENT_SECRET:-your-client-secret}"
AZURE_SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID:-your-subscription-id}"
AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-your-resource-group}"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Agent Manager K8s 部署 (ARM64)${NC}"
echo -e "${BLUE}========================================${NC}"
# 函数: 打印步骤
print_step() {
echo -e "\n${GREEN}==>${NC} ${BLUE}$1${NC}"
}
# 函数: 打印错误
print_error() {
echo -e "${RED}❌ 错误: $1${NC}"
}
# 函数: 打印成功
print_success() {
echo -e "${GREEN}✅ $1${NC}"
}
# 函数: 打印警告
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
# 检查必要的命令
check_prerequisites() {
print_step "检查必要的工具..."
local tools=("docker" "kubectl" "az")
for tool in "${tools[@]}"; do
if ! command -v $tool &> /dev/null; then
print_error "$tool 未安装,请先安装"
exit 1
fi
print_success "$tool 已安装"
done
}
# 登录 Azure Container Registry
login_acr() {
print_step "登录到 Azure Container Registry..."
if az acr login --name ${ACR_NAME}; then
print_success "ACR 登录成功"
else
print_error "ACR 登录失败"
exit 1
fi
}
# 构建 Docker 镜像
build_image() {
print_step "构建 ARM64 Docker 镜像..."
echo "镜像名称: ${FULL_IMAGE_NAME}"
# 使用 buildx 支持多架构构建
if ! docker buildx version &> /dev/null; then
print_warning "docker buildx 未启用,尝试启用..."
docker buildx create --use
fi
# 构建镜像
if docker buildx build \
--platform linux/arm64 \
-f Dockerfile.arm64 \
-t ${FULL_IMAGE_NAME} \
--push \
.; then
print_success "镜像构建并推送成功"
else
print_error "镜像构建失败"
exit 1
fi
}
# 创建 Kubernetes 命名空间
create_namespace() {
print_step "创建 Kubernetes 命名空间..."
if kubectl get namespace ${NAMESPACE} &> /dev/null; then
print_warning "命名空间 ${NAMESPACE} 已存在"
else
kubectl apply -f ${K8S_DIR}/agent-manager-namespace.yaml
print_success "命名空间创建成功"
fi
}
# 创建 ACR Secret
create_acr_secret() {
print_step "创建 ACR 拉取凭据..."
if kubectl get secret acr-secret -n ${NAMESPACE} &> /dev/null; then
print_warning "ACR secret 已存在,删除重建"
kubectl delete secret acr-secret -n ${NAMESPACE}
fi
# 获取 ACR 凭据
ACR_USERNAME=$(az acr credential show --name ${ACR_NAME} --query username -o tsv)
ACR_PASSWORD=$(az acr credential show --name ${ACR_NAME} --query passwords[0].value -o tsv)
kubectl create secret docker-registry acr-secret \
--namespace=${NAMESPACE} \
--docker-server=${ACR_LOGIN_SERVER} \
--docker-username=${ACR_USERNAME} \
--docker-password=${ACR_PASSWORD}
print_success "ACR Secret 创建成功"
}
# 更新 ConfigMap 和 Secret
update_config() {
print_step "更新配置..."
# 检查是否需要更新 Azure 凭据
if [ "$AZURE_TENANT_ID" = "your-tenant-id" ]; then
print_warning "请在脚本中设置 Azure 凭据环境变量"
read -p "是否继续部署(不含 Azure DNS 功能)?[y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# 创建临时 secret 文件
cat > /tmp/agent-manager-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
name: agent-manager-secret
namespace: agent-manager
type: Opaque
stringData:
AZURE_TENANT_ID: "${AZURE_TENANT_ID}"
AZURE_CLIENT_ID: "${AZURE_CLIENT_ID}"
AZURE_CLIENT_SECRET: "${AZURE_CLIENT_SECRET}"
EOF
kubectl apply -f ${K8S_DIR}/agent-manager-configmap.yaml
kubectl apply -f /tmp/agent-manager-secret.yaml
rm -f /tmp/agent-manager-secret.yaml
print_success "配置已更新"
}
# 创建 kubeconfig Secret(如果需要)
create_kubeconfig_secret() {
print_step "创建 kubeconfig Secret..."
if [ -f ~/.kube/config ]; then
if kubectl get secret kubeconfig-secret -n ${NAMESPACE} &> /dev/null; then
print_warning "kubeconfig secret 已存在"
else
kubectl create secret generic kubeconfig-secret \
--from-file=config=/home/${USER}/.kube/config \
-n ${NAMESPACE}
print_success "kubeconfig Secret 创建成功"
fi
else
print_warning "未找到 kubeconfig 文件,跳过"
fi
}
# 部署 RBAC
deploy_rbac() {
print_step "部署 RBAC 权限..."
kubectl apply -f ${K8S_DIR}/agent-manager-rbac.yaml
print_success "RBAC 部署成功"
}
# 部署应用
deploy_app() {
print_step "部署 Agent Manager 应用..."
# 更新 Deployment 中的镜像
kubectl apply -f ${K8S_DIR}/agent-manager-deployment.yaml
kubectl apply -f ${K8S_DIR}/agent-manager-service.yaml
print_success "应用部署成功"
}
# 等待部署完成
wait_for_deployment() {
print_step "等待部署完成..."
kubectl rollout status deployment/agent-manager -n ${NAMESPACE} --timeout=300s
print_success "部署已就绪"
}
# 显示部署信息
show_deployment_info() {
print_step "部署信息:"
echo -e "\n${BLUE}Pods:${NC}"
kubectl get pods -n ${NAMESPACE} -o wide
echo -e "\n${BLUE}Services:${NC}"
kubectl get svc -n ${NAMESPACE}
echo -e "\n${BLUE}获取外网访问地址:${NC}"
EXTERNAL_IP=$(kubectl get svc agent-manager -n ${NAMESPACE} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
if [ -z "$EXTERNAL_IP" ]; then
print_warning "LoadBalancer IP 正在分配中..."
echo "运行以下命令查看 IP: kubectl get svc agent-manager -n ${NAMESPACE}"
else
print_success "外网访问地址: http://${EXTERNAL_IP}"
echo -e "\n测试访问:"
echo " curl http://${EXTERNAL_IP}/"
fi
}
# 查看日志
show_logs() {
print_step "最近的日志:"
kubectl logs -n ${NAMESPACE} -l app=agent-manager --tail=50
}
# 主函数
main() {
local skip_build=false
local skip_deploy=false
# 解析参数
while [[ $# -gt 0 ]]; do
case $1 in
--skip-build)
skip_build=true
shift
;;
--skip-deploy)
skip_deploy=true
shift
;;
--help)
echo "用法: $0 [选项]"
echo "选项:"
echo " --skip-build 跳过镜像构建"
echo " --skip-deploy 跳过应用部署"
echo " --help 显示帮助"
exit 0
;;
*)
print_error "未知参数: $1"
exit 1
;;
esac
done
# 执行部署
check_prerequisites
if [ "$skip_build" = false ]; then
login_acr
build_image
else
print_warning "跳过镜像构建"
fi
if [ "$skip_deploy" = false ]; then
create_namespace
create_acr_secret
update_config
create_kubeconfig_secret
deploy_rbac
deploy_app
wait_for_deployment
show_deployment_info
echo -e "\n${GREEN}========================================${NC}"
echo -e "${GREEN} 部署完成!${NC}"
echo -e "${GREEN}========================================${NC}"
read -p "是否查看日志?[y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
show_logs
fi
else
print_warning "跳过应用部署"
fi
}
# 运行主函数
main "$@"

Some files were not shown because too many files have changed in this diff Show More