通过


你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

快速入门:使用 Go 语言在 Azure DocumentDB 中实现带矢量搜索的 AI 智能体

使用 Go 和 Azure DocumentDB 生成智能 AI 代理。 本快速入门演示了一个双代理体系结构,该体系结构执行语义式酒店搜索并生成个性化建议。

重要

此示例是演示 Go 中代理模式的参考实现。 它使用自定义生成的代理体系结构,而不是代理框架,这是生产代理应用程序的建议方法。

先决条件

可以使用 Azure 开发人员 CLI 通过运行 azd 示例存储库中的命令来创建所需的 Azure 资源。 有关详细信息,请参阅 使用 Azure 开发人员 CLI 部署基础结构

Azure 资源

  • 在 Microsoft Foundry 中具有以下模型部署的 Azure OpenAI 资源

    • gpt-4o 部署(合成器智能体)- 建议:每分钟 50,000 令牌 (TPM) 容量
    • gpt-4o-mini 部署(规划器智能体)- 建议:每分钟 30,000 令牌 (TPM) 容量
    • text-embedding-3-small 部署(嵌入)- 建议:每分钟 10,000 个词元(TPM) 容量
    • 令牌配额:为每个部署配置足够的 TPM,以避免速率限制
  • 具有矢量搜索支持的 Azure DocumentDB (与 MongoDB 兼容性)群集

    • 基于矢量索引算法的群集层要求
      • IVF (倒排文件索引):M10 或更高版本(默认算法)
      • HNSW (分层可导航小型世界):M30 或更高版本(基于图形)
      • DiskANN:M40 或更高版本(针对大规模优化)
    • 防火墙配置:无需适当的防火墙配置,连接尝试将失败
    • 对于无密码身份验证,已启用基于角色的访问控制(RBAC)

开发工具

  • Go 1.22 或更高版本
  • 用于身份验证的 Azure CLI

Architecture

此示例使用两个代理体系结构,其中每个代理具有特定角色。

显示具有 Planner 代理、矢量搜索工具和合成器代理的双代理工作流的体系结构图。

此示例直接使用 OpenAI SDK 的自定义实现,而无需依赖代理框架。 它利用 OpenAI 函数调用工具集成,并遵循代理和搜索工具之间的线性工作流。 执行是无状态的,没有会话历史记录,因此它适合单轮查询和响应方案。

获取示例代码

  1. 克隆或下载 Azure DocumentDB 示例存储库到本地计算机,以跟随快速入门操作。

  2. 导航到项目目录:

    cd ai/vector-search-agent-go
    

配置环境变量

在项目根目录中创建文件 .env 以配置环境变量。 可以从存储库中复制 .env.sample 文件。

编辑.env文件,并替换这些占位符值:

本快速入门使用双智能体架构(规划器 + 合成器),包含三个模型部署(两个聊天模型 + 嵌入模型)。 为每个模型部署配置环境变量。

  • AZURE_OPENAI_PLANNER_DEPLOYMENT:你的 gpt-4o-mini 部署名称
  • AZURE_OPENAI_SYNTH_DEPLOYMENT:gpt-4o 部署名称
  • AZURE_OPENAI_EMBEDDING_DEPLOYMENT:你的 text-embedding-3-small 部署名称

可以在两种身份验证方法之间进行选择:使用 Azure 标识(建议)或传统连接字符串和 API 密钥进行无密码身份验证。

选项 1:无密码身份验证

对 Azure OpenAI 和 Azure DocumentDB 使用无密码身份验证。 设置USE_PASSWORDLESS=trueAZURE_OPENAI_ENDPOINTAZURE_DOCUMENTDB_CLUSTER

# Enable passwordless authentication
USE_PASSWORDLESS=true

# Azure OpenAI Configuration (passwordless)
AZURE_OPENAI_ENDPOINT=your-openai-endpoint

# Azure DocumentDB (passwordless)
AZURE_DOCUMENTDB_CLUSTER=your-mongo-cluster-name
AZURE_DOCUMENTDB_DATABASENAME=Hotels
AZURE_DOCUMENTDB_COLLECTION=hotel_data
AZURE_DOCUMENTDB_INDEX_NAME=vectorIndex

无密码身份验证的先决条件:

  • 确保已登录到 Azure: az login

  • 为你的标识授予以下角色:

    • Azure OpenAI 资源上的 Cognitive Services OpenAI User
    • DocumentDB Account ContributorCosmos DB Account Reader Role Azure DocumentDB 资源

    有关分配角色的详细信息,请参阅 使用 Azure 门户分配 Azure 角色

选项 2:连接字符串和 API 密钥身份验证

通过设置 USE_PASSWORDLESS=false (或省略该项)并在 AZURE_OPENAI_API_KEY 文件中提供 AZURE_DOCUMENTDB_CONNECTION_STRING.env 值,使用基于密钥的身份验证。

# Disable passwordless authentication
USE_PASSWORDLESS=false

# Azure OpenAI Configuration (API key)
AZURE_OPENAI_ENDPOINT=your-openai-endpoint
AZURE_OPENAI_API_KEY=your-azure-openai-api-key

# Azure DocumentDB (connection string)
AZURE_DOCUMENTDB_CONNECTION_STRING=mongodb+srv://username:password@cluster.mongocluster.cosmos.azure.com/
AZURE_DOCUMENTDB_DATABASENAME=Hotels
AZURE_DOCUMENTDB_COLLECTION=hotel_data
AZURE_DOCUMENTDB_INDEX_NAME=vectorIndex

项目结构

该项目遵循标准 Go 项目布局。 目录结构应类似于以下结构:

mongo-vcore-agent-go/
├── cmd/
│   ├── agent/          # Main agent application
│   │   └── main.go
│   ├── upload/         # Data upload utility
│   │   └── main.go
│   └── cleanup/        # Database cleanup utility
│       └── main.go
├── internal/
│   ├── agents/         # Agent and tool implementations
│   │   ├── agents.go   # Planner and synthesizer agents
│   │   └── tools.go    # Vector search tool
│   ├── clients/        # Azure OpenAI client
│   │   └── openai.go
│   ├── models/         # Hotel data models
│   │   └── hotel.go
│   ├── prompts/        # System prompts and tool definitions
│   │   └── prompts.go
│   └── vectorstore/    # Azure DocumentDB vector store operations
│       └── store.go
├── .env                # Environment variable configuration
├── go.mod              # Go module file
└── go.sum              # Go module checksum file

浏览代码

本部分介绍 AI 代理工作流的核心组件。 它重点介绍了代理如何处理请求、工具如何将 AI 连接到数据库,以及提示如何指导 AI 的行为。

代理应用程序

该文件 cmd/agent/main.go 协调由 AI 提供支持的酒店建议系统。

该应用程序使用两个 Azure 服务:

  • Azure OpenAI,它使用 AI 模型来了解查询并生成建议
  • 存储酒店数据的 Azure DocumentDB 并执行矢量相似性搜索

代理和工具组件

这三个组件共同处理酒店搜索请求:

  • Planner 代理 - 解释请求并决定如何搜索
  • 矢量搜索工具 - 查找类似于 Planner 代理描述的酒店
  • 合成器代理 - 基于搜索结果编写有用的建议

应用程序工作流

应用程序通过两个步骤处理酒店搜索请求:

  • 规划: 工作流调用 planner 代理,该代理分析用户的查询(如“运行小径附近的酒店”),并搜索数据库以查找匹配的酒店。
  • 合成: 工作流调用合成器代理,该代理会评审搜索结果并编写个性化建议,说明哪些酒店最符合请求。
// Run planner agent
hotelContext, err := plannerAgent.Run(ctx, query, nearestNeighbors)
if err != nil {
    log.Fatalf("Planner agent failed: %v", err)
}

if debug {
    fmt.Printf("\n--- HOTEL CONTEXT ---\n%s\n", hotelContext)
}

// Run synthesizer agent
finalAnswer, err := synthesizerAgent.Run(ctx, query, hotelContext)
if err != nil {
    log.Fatalf("Synthesizer agent failed: %v", err)
}

Agents

internal/agents/agents.go源文件实现规划器和合成器代理,这些代理共同处理酒店搜索请求。

规划器智能体

规划器智能体是决策制定者,负责确定如何搜索酒店。

计划代理接收用户的自然语言查询,并将其发送到人工智能模型以及可用工具。 AI 决定调用矢量搜索工具并提供搜索参数。 然后,代理从 AI 的响应中提取工具名称和参数,执行搜索工具,并返回匹配的酒店。 AI 无需对搜索逻辑进行硬编码,而是解释用户想要的内容,并选择如何搜索,使系统对不同类型的查询灵活。

// PlannerAgent orchestrates the tool calling
type PlannerAgent struct {
    openAIClients *clients.OpenAIClients
    searchTool    *VectorSearchTool
    debug         bool
}

// NewPlannerAgent creates a new planner agent
func NewPlannerAgent(openaiClients *clients.OpenAIClients, searchTool *VectorSearchTool, debug bool) *PlannerAgent {
    return &PlannerAgent{
        openAIClients: openaiClients,
        searchTool:    searchTool,
        debug:         debug,
    }
}

// Run executes the planner agent workflow
func (a *PlannerAgent) Run(ctx context.Context, userQuery string, nearestNeighbors int) (string, error) {
    fmt.Println("\n--- PLANNER ---")

    userMessage := fmt.Sprintf(
        `Search for hotels matching this request: "%s". Use nearestNeighbors=%d.`,
        userQuery,
        nearestNeighbors,
    )

    // Get tool definition
    toolDef := a.searchTool.GetToolDefinition()

    // Call planner with tool definitions
    resp, err := a.openAIClients.ChatCompletionWithTools(ctx, prompts.PlannerSystemPrompt, userMessage, []openai.ChatCompletionToolUnionParam{toolDef})
    if err != nil {
        return "", fmt.Errorf("planner failed: %w", err)
    }

    // Extract tool call
    toolName, argsMap, err := clients.ExtractToolCall(resp)
    if err != nil {
        return "", fmt.Errorf("failed to extract tool call: %w", err)
    }

    if toolName != prompts.ToolName {
        return "", fmt.Errorf("unexpected tool called: %s", toolName)
    }

    // Parse arguments using typed struct
    args, err := parseToolArgumentsFromMap(argsMap)
    if err != nil {
        return "", fmt.Errorf("failed to parse tool arguments: %w", err)
    }

    // Use default if nearestNeighbors not provided
    if args.NearestNeighbors == 0 {
        args.NearestNeighbors = nearestNeighbors
    }

    fmt.Printf("Tool: %s\n", toolName)
    fmt.Printf("Query: %s\n", args.Query)
    fmt.Printf("K: %d\n", args.NearestNeighbors)

    // Execute the tool
    searchResults, err := a.searchTool.Execute(ctx, args.Query, args.NearestNeighbors)
    if err != nil {
        return "", fmt.Errorf("search tool execution failed: %w", err)
    }

    return searchResults, nil
}

合成器智能体

合成器代理是创建有用建议的 编写器

合成器代理接收原始用户查询以及酒店搜索结果。 它向 AI 模型发送所有内容,其中包含编写建议的说明。 它返回一个自然语言响应,用于比较酒店并解释最佳选项。 此方法很重要,因为原始搜索结果对用户不友好。 合成器将数据库记录转换为对话建议,该建议解释了某些酒店为何与用户的需求匹配。

// NewSynthesizerAgent creates a new synthesizer agent
func NewSynthesizerAgent(openaiClients *clients.OpenAIClients, debug bool) *SynthesizerAgent {
    return &SynthesizerAgent{
        openAIClients: openaiClients,
        debug:         debug,
    }
}

// Run executes the synthesizer agent workflow
func (a *SynthesizerAgent) Run(ctx context.Context, userQuery, hotelContext string) (string, error) {
    fmt.Println("\n--- SYNTHESIZER ---")
    fmt.Printf("Context size: %d characters\n", len(hotelContext))

    userMessage := prompts.CreateSynthesizerUserPrompt(userQuery, hotelContext)

    // Call synthesizer (no tools)
    finalAnswer, err := a.openAIClients.ChatCompletion(ctx, prompts.SynthesizerSystemPrompt, userMessage)
    if err != nil {
        return "", fmt.Errorf("synthesizer failed: %w", err)
    }

    return finalAnswer, nil
}

代理工具

internal/agents/tools.go源文件定义 Planner 代理使用的矢量搜索工具。

工具文件定义 AI 代理可用于查找酒店的搜索工具。 此工具是代理连接到数据库的方式。 AI 不会直接搜索数据库。 它要求使用搜索工具,该工具执行实际搜索。

工具定义

该方法 GetToolDefinition 以它理解的格式向 AI 模型描述该工具。 它指定工具的名称、工具用途的说明,以及定义工具所需的输入的参数。 此定义使 AI 知道该工具存在以及如何正确使用它。

// GetToolDefinition returns the Azure OpenAI tool definition
func (t *VectorSearchTool) GetToolDefinition() openai.ChatCompletionToolUnionParam {
    paramSchema := map[string]any{
        "type": "object",
        "properties": map[string]any{
            "query": map[string]any{
                "type":        "string",
                "description": "Natural language search query describing desired hotel characteristics",
            },
            "nearestNeighbors": map[string]any{
                "type":        "integer",
                "description": "Number of results to return (1-20)",
                "default":     5,
            },
        },
        "required": []string{"query", "nearestNeighbors"},
    }

    return openai.ChatCompletionToolUnionParam{
        OfFunction: &openai.ChatCompletionFunctionToolParam{
            Function: openai.FunctionDefinitionParam{
                Name:        prompts.ToolName,
                Description: openai.String(prompts.ToolDescription),
                Parameters:  paramSchema,
            },
        },
    }
}

工具执行

当 AI 调用该工具时,该方法 Execute 将运行。 它通过使用 Azure OpenAI 的嵌入模型将文本查询转换为数值向量来生成嵌入。 然后,它会通过向量发送到 Azure DocumentDB 来搜索数据库,后者查找具有类似向量的酒店,这意味着类似的说明。 最后,它通过将数据库记录转换为合成器代理可以理解的可读文本来设置结果的格式。

// Execute performs the vector search
func (t *VectorSearchTool) Execute(ctx context.Context, query string, nearestNeighbors int) (string, error) {
    // Generate embedding for query
    queryVector, err := t.openAIClients.GenerateEmbedding(ctx, query)
    if err != nil {
        return "", fmt.Errorf("failed to generate embedding: %w", err)
    }

    // Perform vector search
    results, err := t.vectorStore.VectorSearch(ctx, queryVector, nearestNeighbors)
    if err != nil {
        return "", fmt.Errorf("vector search failed: %w", err)
    }

    // Format results for synthesizer
    var formattedResults []string
    for i, result := range results {
        fmt.Printf("Hotel #%d: %s, Score: %.6f\n", i+1, result.Hotel.HotelName, result.Score)
        formattedResults = append(formattedResults, vectorstore.FormatHotelForSynthesizer(result))
    }

    return strings.Join(formattedResults, "\n\n"), nil
}

为何使用此模式?

将工具与代理分离可提供灵活性。 AI 决定何时搜索和搜索内容,而该工具将处理如何搜索。 无需更改代理逻辑即可添加更多工具。

提示语

源文件 internal/prompts/prompts.go 包含代理的系统提示和工具定义。

提示词文件定义了提供给规划器和合成器智能体的 AI 模型的指令和上下文。 这些提示指导 AI 的行为,并确保它了解其在工作流中的角色。

AI 响应的质量在很大程度上取决于明确的说明。 这些提示设置边界、定义输出格式,并将 AI 放在用户做出决策的目标上。 可以自定义这些提示以更改代理的行为方式,而无需修改任何代码。

const PlannerSystemPrompt = `You are a hotel search planner. Your job is to help users find hotels by calling the search tool.

CRITICAL INSTRUCTION: You MUST call the "search_hotels_collection" tool for every request. This is the ONLY way to search the database.

When you call the tool, use these parameters:
- query: A clear, detailed natural language description of what the user is looking for. Expand vague requests (e.g., "nice hotel" → "hotel with high ratings, good reviews, and quality amenities").
- nearestNeighbors: Number of results (1-20). Use 3-5 for specific requests, 10-15 for broader searches.

EXAMPLES of how you should call the tool:
- User: "cheap hotel" → Call tool with query: "budget-friendly hotel with good value and affordable rates", nearestNeighbors: 10
- User: "hotel near downtown with parking" → Call tool with query: "hotel near downtown with good parking and wifi", nearestNeighbors: 5

IMPORTANT: Always call the tool. Do not provide answers without calling the tool first.`

const SynthesizerSystemPrompt = `You are an expert hotel recommendation assistant using vector search results.
Only use the TOP 3 results provided. Do not request additional searches or call other tools.

GOAL: Provide a concise comparative recommendation to help the user choose between the top 3 options.

REQUIREMENTS:
- Compare only the top 3 results across the most important attributes: rating, score, location, price-level (if available), and key tags (parking, wifi, pool).
- Identify the main tradeoffs in one short sentence per tradeoff.
- Give a single clear recommendation with one short justification sentence.
- Provide up to two alternative picks (one sentence each) explaining when they are preferable.

FORMAT CONSTRAINTS:
- Plain text only (no markdown).
- Keep the entire response under 220 words.
- Use simple bullets (•) or numbered lists and short sentences (preferably <25 words per sentence).
- Preserve hotel names exactly as provided in the tool summary.

Do not add extra commentary, marketing language, or follow-up questions. If information is missing and necessary to choose, state it in one sentence and still provide the best recommendation based on available data.`

运行示例

  1. 在运行代理之前,请上传带有嵌入的酒店数据。 该 cmd/upload/main.go 命令从 JSON 文件加载酒店,使用 text-embedding-3-small每个酒店生成嵌入内容,将文档插入 Azure DocumentDB,并创建矢量索引。

    go run cmd/upload/main.go
    
  2. 使用 cmd/agent/main.go 命令运行酒店推荐代理。 代理调用 Planner 代理、矢量搜索和合成器代理。 输出包括相似性分数,以及合成器代理的比较分析与建议。

    go run cmd/agent/main.go
    
    Query: quintessential lodging near running trails, eateries, retail
    Nearest Neighbors: 5
    
    --- PLANNER ---
    Tool: search_hotels_collection
    Query: quintessential lodging near running trails, eateries, and retail shops with good amenities and access to outdoor activities
    K: 5
    Hotel #1: Nordick's Valley Motel, Score: 0.498665
    Hotel #2: White Mountain Lodge & Suites, Score: 0.487320
    Hotel #3: Trails End Motel, Score: 0.479854
    Hotel #4: Country Comfort Inn, Score: 0.474320
    Hotel #5: Lakefront Captain Inn, Score: 0.457873
    
    --- SYNTHESIZER ---
    Context size: 3233 characters
    
    --- FINAL ANSWER ---
    1. COMPARISON SUMMARY:  
    • Nordick's Valley Motel has the highest rating (4.5) and offers free parking, air conditioning, and continental breakfast. It is located in Washington D.C., near historic attractions and trails.
    • White Mountain Lodge & Suites is a resort with unique amenities like a pool, restaurant, and meditation gardens, but has the lowest rating (2.4). It is located in Denver, surrounded by forest trails.
    • Trails End Motel is budget-friendly with a moderate rating (3.2), free parking, free wifi, and a restaurant. It is close to downtown Scottsdale and eateries.
    
    Key tradeoffs:
    - Nordick's Valley Motel excels in rating and proximity to historic attractions but lacks a pool or free wifi.
    - White Mountain Lodge & Suites offers resort-style amenities and forest trails but has the lowest rating.
    - Trails End Motel balances affordability and essential amenities but has fewer unique features compared to the others.
    
    2. BEST OVERALL:
    Nordick's Valley Motel is the best choice for its high rating, proximity to trails and attractions, and free parking.
    
    3. ALTERNATIVE PICKS:
    • Choose White Mountain Lodge & Suites if you prioritize resort amenities and forest trails over rating.
    • Choose Trails End Motel if affordability and proximity to downtown Scottsdale are your main concerns.
    

在 Visual Studio Code 中查看和管理数据

  1. 在 Visual Studio Code 中选择 DocumentDB 扩展 以连接到 Azure DocumentDB 帐户。

  2. 查看 Hotels 数据库中的数据和索引。

    显示矢量搜索索引和酒店文档的 Visual Studio Code DocumentDB 扩展。

清理资源

完成后,使用清理命令删除测试数据库。 运行下面的命令:

go run cmd/cleanup/main.go

当不需要资源组、DocumentDB 帐户和 Azure OpenAI 资源时,请将其删除,以免产生额外费用。