Go 및 Azure DocumentDB를 사용하여 지능형 AI 에이전트를 빌드합니다. 이 빠른 시작에서는 의미 체계 호텔 검색을 수행하고 개인 설정된 권장 사항을 생성하는 2개 에이전트 아키텍처를 보여 줍니다.
Important
이 샘플은 Go의 에이전트 패턴을 보여주는 참조 구현입니다. 프로덕션 에이전트 애플리케이션에 권장되는 접근 방식인 에이전트 프레임워크 대신 사용자 지정 빌드 에이전트 아키텍처를 사용합니다.
필수 조건
Azure 개발자 CLI를 사용하여 샘플 리포지토리에서 명령을 실행 azd 하여 필요한 Azure 리소스를 만들 수 있습니다. 자세한 내용은 Azure Developer CLI를 사용하여 인프라 배포를 참조하세요.
Azure 리소스
Microsoft Foundry에서 다음 모델 배포를 사용하는 Azure OpenAI 리소스:
-
gpt-4o배포 (신시사이저 에이전트) - 권장: 분당 50,000개의 토큰 처리 용량 (TPM) -
gpt-4o-mini배포(Planner 에이전트) - 권장: 분당 토큰 30,000개(TPM) 용량 -
text-embedding-3-small배포(Embeddings) - 권장: 분당 토큰 10,000개(TPM) 용량 -
토큰 할당량: 속도 제한을 방지하기 위해 각 배포에 대해 충분한 TPM 구성
- Azure OpenAI 할당량 관리를 참조하세요.
- 429 오류가 발생하는 경우 TPM 할당량을 늘리거나 요청 빈도를 줄입니다.
-
벡터 검색 지원을 사용하는 Azure DocumentDB(MongoDB 호환성 포함) 클러스터:
- 벡터 인덱스 알고리즘을 기반으로 하는 클러스터 계층 요구 사항:
- IVF(반전된 파일 인덱스): M10 이상(기본 알고리즘)
- HNSW(계층적 탐색 가능한 Small World): M30 이상(그래프 기반)
- DiskANN: M40 이상(대규모에 최적화됨)
-
방화벽 구성: 적절한 방화벽 구성이 없으면 연결 시도가 실패합니다.
- 클러스터의 방화벽 규칙에 클라이언트 IP 주소를 추가합니다. 자세한 내용은 IP 주소에서 액세스 권한 부여를 참조하세요.
- 암호 없는 인증의 경우 RBAC(역할 기반 액세스 제어)를 사용하도록 설정
- 벡터 인덱스 알고리즘을 기반으로 하는 클러스터 계층 요구 사항:
개발 도구
아키텍처
샘플은 각 에이전트에 특정 역할이 있는 2개 에이전트 아키텍처를 사용합니다.
이 샘플에서는 에이전트 프레임워크를 사용하지 않고 OpenAI SDK에서 직접 사용자 지정 구현을 사용합니다. 도구 통합을 호출하는 OpenAI 함수를 활용하고 에이전트와 검색 도구 간의 선형 워크플로를 따릅니다. 실행은 대화 이력 없이 상태 비저장 방식으로 이루어지므로 단일 턴 쿼리 및 응답 시나리오에 적합합니다.
샘플 코드 가져오기
리포지토리 Azure DocumentDB 샘플을 복제하거나 로컬 컴퓨터에 다운로드하여 빠른 시작을 따릅니다.
프로젝트 디렉터리로 이동합니다.
cd ai/vector-search-agent-go
환경 변수 구성
.env 프로젝트 루트에 파일을 만들어 환경 변수를 구성합니다. 리포지토리에서 파일의 .env.sample 복사본을 만들 수 있습니다.
.env 파일을 편집하고 다음 플레이스홀더 값을 바꿉니다.
이 퀵스타트에서는 두 에이전트 아키텍처(Planner + synthesizer)와 세 가지 모델 배포(채팅 모델 2개 + 임베딩)를 사용합니다. 환경 변수는 각 모델 배포에 대해 구성됩니다.
-
AZURE_OPENAI_PLANNER_DEPLOYMENT: gpt-4o-mini의 배포 이름 -
AZURE_OPENAI_SYNTH_DEPLOYMENT: gpt-4o 배포 이름 -
AZURE_OPENAI_EMBEDDING_DEPLOYMENT: text-embedding-3-small 배포 이름
Azure ID를 사용한 암호 없는 인증(권장) 또는 기존 연결 문자열 및 API 키의 두 가지 인증 방법 중에서 선택할 수 있습니다.
옵션 1: 암호 없는 인증
Azure OpenAI 및 Azure DocumentDB 모두에서 암호 없는 인증을 사용합니다. 설정 USE_PASSWORDLESS=true, AZURE_OPENAI_ENDPOINT, 및 AZURE_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 loginID에 다음 역할을 부여합니다.
-
Cognitive Services OpenAI UserAzure OpenAI 리소스에서 - Azure DocumentDB 리소스의
DocumentDB Account Contributor및Cosmos DB Account Reader Role
역할 할당에 대한 자세한 내용은 Azure Portal을 사용하여 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 서비스를 사용합니다.
- 쿼리를 이해하고 권장 사항을 생성하는 AI 모델을 사용하는 Azure OpenAI
- 호텔 데이터를 저장하고 벡터 유사성 검색을 수행하는 Azure DocumentDB
에이전트 및 도구 구성 요소
세 가지 구성 요소가 함께 작동하여 호텔 검색 요청을 처리합니다.
- 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)
}
에이전트
원본 파일은 internal/agents/agents.go 호텔 검색 요청을 처리하기 위해 함께 작동하는 플래너 및 신시사이저 에이전트를 구현합니다.
Planner 에이전트
플래너 에이전트는 호텔 검색 방법을 결정하는 의사 결정자 입니다.
Planner 에이전트는 사용자의 자연어 쿼리를 받아 사용할 수 있는 도구와 함께 AI 모델로 보냅니다. 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는 데이터베이스를 직접 검색하지 않습니다. 이 도구는 검색 도구를 사용하도록 요청하고 도구는 실제 검색을 실행합니다.
도구 정의
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.`
샘플 실행
에이전트를 실행하기 전에 임베딩을 포함하여 호텔 데이터를 업로드하십시오.
cmd/upload/main.go명령은 JSON 파일에서 호텔을 로드하고,text-embedding-3-small를 사용하여 각 호텔에 대한 임베딩을 생성하고, Azure DocumentDB에 문서를 삽입하며, 벡터 인덱스를 만듭니다.go run cmd/upload/main.go명령을 사용하여 호텔 추천 에이전트를 실행합니다
cmd/agent/main.go. 에이전트는 Planner 에이전트, 벡터 검색 및 신시사이저 에이전트를 호출합니다. 출력에는 유사성 점수와 권장 사항이 있는 신시사이저 에이전트의 비교 분석이 포함됩니다.go run cmd/agent/main.goQuery: 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에서 데이터 보기 및 관리
Visual Studio Code에서 DocumentDB 확장을 선택하여 Azure DocumentDB 계정에 연결합니다.
Hotels 데이터베이스에서 데이터 및 인덱스를 봅니다.
자원을 정리하세요
완료되면 정리 명령을 사용하여 테스트 데이터베이스를 삭제합니다. 다음 명령을 실행합니다.
go run cmd/cleanup/main.go
추가 비용을 방지할 필요가 없는 경우 리소스 그룹, DocumentDB 계정 및 Azure OpenAI 리소스를 삭제합니다.