LangGraph.js와 Elasticsearch를 사용하여 금융 AI 검색 워크플로우 구축
LangGraph.js와 Elasticsearch를 사용하여 자연어 쿼리를 투자 및 시장 분석을 위한 동적인 조건부 필터로 전환하는 AI 기반 금융 검색 워크플로우를 구축하는 방법을 알아보세요.
AI 검색 애플리케이션을 구축할 때는 여러 작업, 데이터 검색 및 데이터 추출을 원활한 워크플로우로 조정하는 것이 중요합니다. LangGraph는 개발자가 노드 기반 구조를 통해 AI 에이전트를 오케스트레이션하여 이 프로세스를 간소화합니다. 이 포스트에서는 LangGraph.js를 사용하여 금융 솔루션을 구축해 보겠습니다.
LangGraph란?
LangGraph는 AI 에이전트를 구축하고 워크플로우에서 이를 오케스트레이션하여 AI 지원 애플리케이션을 생성하는 프레임워크입니다. LangGraph에는 작업을 나타내는 함수를 선언하고 워크플로우의 노드로 할당할 수 있는 노드 아키텍처가 있습니다. 여러 노드가 상호작용을 한 결과는 그래프가 됩니다. LangGraph는 모듈형 및 조합 가능한 AI 시스템을 구축하기 위한 도구를 제공하는 광범위한 LangChain 에코시스템의 일부입니다.
LangGraph가 유용한 이유를 더 잘 이해하기 위해 LangGraph를 사용하여 문제 상황을 해결해 보겠습니다.
솔루션 개요
벤처 캐피털 회사에서는 투자자들이 다양한 필터링 옵션을 갖춘 방대한 데이터베이스를 활용할 수 있지만, 기준을 결합하려고 하면 어렵고 시간이 오래 걸립니다. 이로 인해 일부 관련 스타트업이 투자 대상에서 제외될 수 있습니다. 결국 최적의 후보자를 찾는 데 많은 시간을 허비하거나, 심지어 기회를 놓치게 됩니다.
LangGraph와 Elasticsearch를 사용하면 자연어로 Elasticsearch 검색을 수행할 수 있어 사용자가 수십 개의 필터로 복잡한 요청을 수동으로 작성할 필요가 없습니다. 유연성을 높이기 위해 워크플로우는 사용자 입력에 따라 두 가지 쿼리 유형 중 하나를 자동으로 선택합니다.
투자 중심 쿼리: 펀딩 라운드, 가치 평가 또는 수익과 같은 스타트업의 금융 및 자금 조달 측면을 대상으로 합니다. 예시: "800만~2500만 달러의 시리즈 A 또는 시리즈 B 펀딩을 받고 월 매출이 50만 달러 이상인 스타트업을 찾아주세요."
시장 중심 쿼리: 이는 산업 분야, 지리적 시장 또는 비즈니스 모델에 집중하여 특정 부문이나 지역의 기회를 식별하는 데 도움을 줍니다. 예시: "샌프란시스코, 뉴욕 또는 보스턴에서 핀테크 및 헬스케어 스타트업을 찾으주세요."
쿼리를 견고하게 유지하기 위해 LLM이 검색 템플릿을 작성하도록 하고, 전체 DSL 쿼리 대신 사용합니다. 이렇게 하면 항상 원하는 쿼리를 얻을 수 있으며, LLM은 매번 필요한 쿼리를 작성할 필요 없이 빈칸만 채우면 됩니다.
시작에 필요한 사항
Elasticsearch APIKey
OpenAPI APIKey
Node 18 이상 버전
단계별 지침
이 섹션에서는 앱이 어떻게 보일지 살펴보겠습니다. 이를 위해 TypeScript를 사용하겠습니다. TypeScript는 JavaScript의 상위 집합으로, 정적 타입을 추가하여 코드의 안정성, 유지관리 용이성, 안전성을 높이고 기존 JavaScript와의 완벽한 호환성을 유지하면서도 오류를 조기에 발견할 수 있도록 합니다.
노드의 흐름은 다음과 같습니다.

위 이미지는 LangGraph에서 생성되었으며, 노드 간의 실행 순서와 조건부 로직을 정의하는 워크플로우를 나타냅니다.
decideStrategy: LLM을 사용하여 사용자의 쿼리를 분석하고 두 가지 전문 검색 전략, 즉 투자 중심 또는 시장 중심 중 하나를 결정합니다.
prepareInvestmentSearch: 쿼리에서 필터 값을 추출하고 금융 및 자금 조달 관련 매개변수를 강조하는 사전 정의된 템플릿을 작성합니다.
prepareMarketSearch: 필터 값을 추출할 뿐만 아니라 시장, 산업 및 지리적 컨텍스트를 강조하는 매개변수를 동적으로 구축합니다.
executeSearch: 구성된 쿼리를 검색 템플릿을 사용하여 Elasticsearch로 전송하고 일치하는 스타트업 문서를 검색합니다.
visualizeResults: 최종 결과를 자금 조달, 산업, 수익과 같은 주요 스타트업 속성을 보여주는 명확하고 읽기 쉬운 요약 형식으로 구성합니다.
이 흐름에는 사용자의 입력에 따라 투자 또는 시장 검색 경로를 선택하는 'if' 문 역할을 하는 조건부 분기가 포함되어 있습니다. LLM에 의해 구동되는 이러한 결정 로직은 워크플로우를 적응적이고 컨텍스트에 맞게 만들어줍니다. 다음 섹션에서 이 메커니즘에 대해 더 자세히 살펴보겠습니다.
LangGraph 상태
각 노드를 개별적으로 보기 전에 노드가 어떻게 통신하고 데이터를 공유하는지 이해해야 합니다. 이를 위해 LangGraph를 사용하여 워크플로우 상태를 정의할 수 있습니다. 이는 노드 간에 전달될 공유 상태를 정의합니다.
상태는 워크플로우 전반에 걸쳐 중간 데이터를 저장하는 공유 컨테이너 역할을 합니다. 사용자의 자연어 쿼리로 시작하여 선택한 검색 전략, Elasticsearch에 준비된 매개변수, 검색 결과, 마지막으로 형식화된 출력을 유지합니다.
이 구조는 모든 노드가 상태를 읽고 업데이트할 수 있도록 하여 사용자 입력에서 최종 시각화까지 정보의 일관된 흐름을 보장합니다.
const VCState = Annotation.Root({
input: Annotation<string>(), // User's natural language query
searchStrategy: Annotation<string>(), // Search strategy chosen by LLM
searchParams: Annotation<any>(), // Prepared search parameters
results: Annotation<any[]>(), // Search results
final: Annotation<string>(), // Final formatted response
});애플리케이션 설정
이 섹션의 모든 코드는 elasticsearch-labs 리포지토리에서 확인할 수 있습니다.
앱이 위치할 폴더에서 터미널을 열고 다음 명령어로 Node.js 애플리케이션을 초기화합니다.
npm init -y이제 이 프로젝트에 필요한 필수 종속성을 설치할 수 있습니다.
npm install @elastic/elasticsearch @langchain/langgraph @langchain/openai @langchain/core dotenv zod && npm install --save-dev @types/node tsx typescript@elastic/elasticsearch: 데이터 수집 및 검색과 같은 Elasticsearch 요청을 처리하는 데 도움이 됩니다.@langchain/langgraph: 모든 LangGraph 도구를 제공하기 위한 JS 종속성입니다.@langchain/openai: LangChain을 위한 OpenAI LLM 클라이언트.@langchain/core: 프롬프트 템플릿 등 LangChain 앱을 위한 기본 구성 요소를 제공합니다.
dotenv: JavaScript에서 환경 변수를 사용하기 위한 필수 종속성입니다.zod: 유형 데이터에 대한 종속성입니다.
@types/node tsx typescript TypeScript 코드를 작성하고 실행할 수 있게 해줍니다.
이제 다음 파일을 생성합니다.
elasticsearchSetup.ts: 인덱스 매핑을 생성하고 JSON 파일에서 데이터 세트를 로드한 후 Elasticsearch에 데이터를 수집합니다.main.ts: LangGraph 애플리케이션을 포함합니다..env: 환경 변수를 저장하기 위한 파일
.env 파일에 다음 환경 변수를 추가합시다:
ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"OpenAPI APIKey는 코드에서 직접 사용되지 않으며, 대신 라이브러리 @langchain/openai에서 내부적으로 사용됩니다.
모든 매핑 생성, 검색 템플릿 생성 및 데이터 세트 수집과 관련된 로직은 elasticsearchSetup.ts 파일에서 확인할 수 있습니다. 다음 단계에서는 main.ts 파일에 초점을 맞추겠습니다. 또한 데이터 세트를 확인하여 dataset.json 에서 데이터가 어떻게 보이는지 더 잘 이해할 수 있습니다.
LangGraph 앱
main.ts 파일에서 LangGraph 애플리케이션을 통합하기 위해 필요한 몇 가지 종속성을 가져오겠습니다. 이 파일에는 노드 함수와 상태 선언도 포함해야 합니다. 그래프 선언은 다음 단계에서 main 메서드로 진행됩니다. elasticsearchSetup.ts 파일에는 향후 단계에서 노드 내에서 사용할 Elasticsearch 도우미가 포함됩니다.
import { writeFileSync } from "node:fs";
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import {
esClient,
ingestDocuments,
createSearchTemplates,
INDEX_NAME,
INVESTMENT_FOCUSED_TEMPLATE,
MARKET_FOCUSED_TEMPLATE,
createIndex,
} from "./elasticsearchSetup.js";
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });앞서 언급한 바와 같이 LLM 클라이언트는 사용자의 질문에 따라 Elasticsearch 검색 템플릿 매개변수를 생성하는 데 사용됩니다.
async function saveGraphImage(app: any): Promise<void> {
try {
const drawableGraph = app.getGraph();
const image = await drawableGraph.drawMermaidPng();
const arrayBuffer = await image.arrayBuffer();
const filePath = "./workflow_graph.png";
writeFileSync(filePath, new Uint8Array(arrayBuffer));
console.log(`📊 Workflow graph saved as: ${filePath}`);
} catch (error: any) {
console.log("⚠️ Could not save graph image:", error.message);
}
}해당 메서드는 그래프 이미지를 png 형식으로 생성하고 Mermaid.INK API를 백그라운드에서 사용합니다. 이는 앱 노드가 스타일이 지정된 시각화와 어떻게 상호작용을 하는지 확인하려는 경우에 유용합니다.
LangGraph 노드
이제 각 노드를 자세히 살펴보겠습니다.
decideSearchStrategy 노드
decideSearchStrategy 노드는 사용자 입력을 분석하여 투자 중심 검색을 수행할지 시장 중심 검색을 수행할지 결정합니다. 이 노드는 정형 출력 스키마(Zod로 정의됨)가 있는 LLM을 사용해 쿼리 유형을 분류합니다. 결정을 내리기 전에 집계를 사용하여 인덱스에서 사용 가능한 필터를 검색하여 모델이 산업, 위치 및 자금 조달 데이터에 대한 최신 컨텍스트를 갖출 수 있도록 합니다.
필터 가능한 값을 추출하여 LLM으로 전송하기 위해 집계 쿼리를 사용하여 Elasticsearch 인덱스에서 직접 검색해 보겠습니다. 이 로직은 getAvailableFilters 이라는 메서드에 할당되어 있습니다.
async function getAvailableFilters() {
try {
const response = await esClient.search({
index: INDEX_NAME,
size: 0,
aggs: {
industries: {
terms: { field: "industry", size: 100 },
},
locations: {
terms: { field: "location", size: 100 },
},
funding_stages: {
terms: { field: "funding_stage", size: 20 },
},
business_models: {
terms: { field: "business_model", size: 10 },
},
lead_investors: {
terms: { field: "lead_investor", size: 100 },
},
funding_amount_stats: {
stats: { field: "funding_amount" },
},
},
});
return response.aggregations;
} catch (error) {
console.error("❌ Error getting available filters:", error);
return {};
}
}위의 집계 쿼리로 다음과 같은 결과를 얻었습니다.
{
"industries": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "logistics",
"doc_count": 5
},
...
]
},
"locations": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "San Francisco, CA",
"doc_count": 4
},
{
"key": "New York, NY",
"doc_count": 3
},
...
]
},
"funding_stages": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Series A",
"doc_count": 8
},
...
]
},
"business_models": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "B2B",
"doc_count": 13
},
...
]
},
"lead_investors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Battery Ventures",
"doc_count": 1
},
{
"key": "Benchmark Capital",
"doc_count": 1
},
...
]
},
"funding_amount_stats": {
"count": 20,
"min": 4500000,
"max": 35000000,
"avg": 14075000,
"sum": 281500000
}
}여기에서 모든 결과를 확인하세요.
두 가지 전략 모두 하이브리드 검색을 사용하여 질문의 정형적인 부분(필터)과 보다 주관적인 부분(시맨틱)을 모두 탐지합니다. 다음은 검색 템플릿을 사용한 두 쿼리의 예입니다.
await esClient.putScript({
id: INVESTMENT_FOCUSED_TEMPLATE,
script: {
lang: "mustache",
source: `{
"size": 5,
"retriever": {
"rrf": {
"retrievers": [
{
"standard": {
"query": {
"semantic": {
"field": "semantic_field",
"query": "{{query_text}}"
}
}
}
},
{
"standard": {
"query": {
"bool": {
"filter": [
{"terms": {"funding_stage": {{#join}}{{#toJson}}funding_stage{{/toJson}}{{/join}}}},
{"range": {"funding_amount": {"gte": {{funding_amount_gte}}{{#funding_amount_lte}},"lte": {{funding_amount_lte}}{{/funding_amount_lte}}}}},
{"terms": {"lead_investor": {{#join}}{{#toJson}}lead_investor{{/toJson}}{{/join}}}},
{"range": {"monthly_revenue": {"gte": {{monthly_revenue_gte}}{{#monthly_revenue_lte}},"lte": {{monthly_revenue_lte}}{{/monthly_revenue_lte}}}}}
]
}
}
}
}
],
"rank_window_size": 100,
"rank_constant": 20
}
}
}`,
},
});자세히 설명된 쿼리는 elasticsearchSetup.ts 파일에서 확인하세요. 다음 노드에서는 두 쿼리 중 어떤 쿼리를 사용할지 결정합니다.
// Node 1: Decide search strategy using LLM
async function decideSearchStrategy(state: typeof VCState.State) {
// Zod schema for specialized search strategy decision
const SearchDecisionSchema = z.object({
search_type: z
.enum(["investment_focused", "market_focused"])
.describe("Type of specialized search strategy to use"),
reasoning: z
.string()
.describe("Brief explanation of why this search strategy was chosen"),
});
const decisionLLM = llm.withStructuredOutput(SearchDecisionSchema);
// Get dynamic filters from Elasticsearch
const availableFilters = await getAvailableFilters();
const prompt = `Query: "${state.input}"
Available filters: ${JSON.stringify(availableFilters, null, 2)}
Choose between two specialized search strategies:
- investment_focused: For queries about funding stages, funding amounts, monthly revenue, lead investors, financial performance
- market_focused: For queries about industries, locations, business models, market segments, geographic markets
Analyze the query intent and choose the most appropriate strategy.
`;
try {
const result = await decisionLLM.invoke(prompt);
console.log(
`🤔 Search strategy: ${result.search_type} - ${result.reasoning}`
);
return {
searchStrategy: result.search_type,
};
} catch (error: any) {
console.error("❌ Error in decideSearchStrategy:", error.message);
return {
searchStrategy: "investment_focused",
};
}
}prepareInvestmentSearch 및 prepareMarketSearch 노드
두 노드 모두 extractFilterValues이라는 공유 도우미 함수를 사용하는데, 이 함수는 LLM을 활용하여 사용자 입력에 언급된 관련 필터(산업, 위치, 자금 조달 단계, 비즈니스 모델 등)를 식별합니다. 이 스키마를 사용하여 검색 템플릿을 작성하고 있습니다.
// Extract all possible filter values from user input
async function extractFilterValues(input: string) {
const FilterValuesSchema = z.object({
// Investment-focused filters
funding_stage: z
.array(z.string())
.default([])
.describe("Funding stage values mentioned in query"),
funding_amount_gte: z
.number()
.default(0)
.describe("Minimum funding amount in USD"),
funding_amount_lte: z
.number()
.default(100000000)
.describe("Maximum funding amount in USD"),
lead_investor: z
.array(z.string())
.default([])
.describe("Lead investor values mentioned in query"),
monthly_revenue_gte: z
.number()
.default(0)
.describe("Minimum monthly revenue in USD"),
monthly_revenue_lte: z
.number()
.default(10000000)
.describe("Maximum monthly revenue in USD"),
industry: z
.array(z.string())
.default([])
.describe("Industry values mentioned in query"),
location: z
.array(z.string())
.default([])
.describe("Location values mentioned in query"),
business_model: z
.array(z.string())
.default([])
.describe("Business model values mentioned in query"),
});
const extractorLLM = llm.withStructuredOutput(FilterValuesSchema);
const availableFilters = await getAvailableFilters();
const extractPrompt = `Extract ALL relevant filter values from: "${input}"
Available options: ${JSON.stringify(availableFilters, null, 2)}
Extract only values explicitly mentioned in the query. Leave fields empty if not mentioned.`;
return await extractorLLM.invoke(extractPrompt);
}탐지된 의도에 따라 워크플로우는 다음 두 가지 경로 중 하나를 선택합니다.
prepareInvestmentSearch: 자금 조달 단계, 자금 조달 금액, 투자자 및 갱신 정보 등 금융 지향적 검색 매개변수를 구축합니다. 전체 쿼리 템플릿은 elasticsearchSetup.ts 파일에서 확인할 수 있습니다.
// Node 2A: Prepare Investment-Focused Search Parameters
async function prepareInvestmentSearch(state: typeof VCState.State) {
console.log(
"💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis..."
);
try {
// Extract all filter values from input
const values = await extractFilterValues(state.input);
let searchParams: any = {
template_id: INVESTMENT_FOCUSED_TEMPLATE,
query_text: state.input,
...values,
};
return { searchParams };
} catch (error) {
console.error("❌ Error preparing investment-focused params:", error);
return {
searchParams: {},
};
}
}prepareMarketSearch: 산업, 지역, 비즈니스 모델에 초점을 맞춘 시장 기반 매개변수를 생성합니다. 전체 쿼리는 elasticsearchSetup.ts 에서 확인하세요.
// Node 2B: Prepare Market-Focused Search Parameters
async function prepareMarketSearch(state: typeof VCState.State) {
console.log(
"🔍 Preparing MARKET-FOCUSED search parameters with market emphasis..."
);
try {
// Extract all filter values from input
const values = await extractFilterValues(state.input);
let searchParams: any = {
template_id: MARKET_FOCUSED_TEMPLATE,
query_text: state.input,
...values,
};
return { searchParams };
} catch (error) {
console.error("❌ Error preparing market-focused params:", error);
return {};
}
}executeSearch 노드
이 node는 상태에서 생성된 검색 매개변수를 가져와 먼저 _render API 를 사용하여 디버깅 목적으로 쿼리를 시각화한 후, 결과를 검색하기 위해 Elasticsearch로 요청을 보냅니다.
// Node 3: Execute Search
async function executeSearch(state: typeof VCState.State) {
const { searchParams } = state;
try {
// getting formed query from template for debugging
const renderedTemplate = await esClient.renderSearchTemplate({
id: searchParams.template_id,
params: searchParams,
});
console.log(
"📋 Complete query:",
JSON.stringify(renderedTemplate.template_output, null, 2)
);
const results = await esClient.searchTemplate({
index: INDEX_NAME,
id: searchParams.template_id,
params: searchParams,
});
return {
results: results.hits.hits.map((hit: any) => hit._source),
};
} catch (error: any) {
console.error(`❌ ${state.searchParams.search_type} search error:`, error);
return { results: [] };
}
}visualizeResults 노드
마지막으로, 이 node는 Elasticsearch 결과를 표시합니다.
// Node 4: Visualize results
async function visualizeResults(state: typeof VCState.State) {
const results = state.results || [];
let formattedResults = `🎯 Found ${results.length} startups matching your criteria:\n\n`;
results.forEach((startup: any, index: number) => {
formattedResults += `${index + 1}. **${startup.company_name}**\n`;
formattedResults += ` 📍 ${startup.location} | 🏢 ${startup.industry} | 💼 ${startup.business_model}\n`;
formattedResults += ` 💰 ${startup.funding_stage} - $${(
startup.funding_amount / 1000000
).toFixed(1)}M\n`;
formattedResults += ` 👥 ${startup.employee_count} employees | 📈 $${(
startup.monthly_revenue / 1000
).toFixed(0)}K MRR\n`;
formattedResults += ` 🏦 Lead: ${startup.lead_investor}\n`;
formattedResults += ` 📝 ${startup.description}\n\n`;
});
return {
final: formattedResults,
};
}프로그래밍 방식으로 전체 그래프는 다음과 같습니다.
const workflow = new StateGraph(VCState)
// Register nodes - these are the processing functions
.addNode("decideStrategy", decideSearchStrategy)
.addNode("prepareInvestment", prepareInvestmentSearch)
.addNode("prepareMarket", prepareMarketSearch)
.addNode("executeSearch", executeSearch)
.addNode("visualizeResults", visualizeResults)
// Define execution flow with conditional branching
.addEdge(START, "decideStrategy") // Start with strategy decision
.addConditionalEdges(
"decideStrategy",
(state: typeof VCState.State) => state.searchStrategy, // Conditional function
{
investment_focused: "prepareInvestment", // If investment focused -> RRF template preparation
market_focused: "prepareMarket", // If market focused -> dynamic query preparation
}
)
.addEdge("prepareInvestment", "executeSearch") // Investment prep -> execute
.addEdge("prepareMarket", "executeSearch") // Market prep -> execute
.addEdge("executeSearch", "visualizeResults") // Execute -> visualize
.addEdge("visualizeResults", END); // End workflow보시다시피 앱이 다음에 실행할 '경로' 또는 '노드'를 결정하는 조건부 엣지가 있습니다. 이 기능은 워크플로우에서 여러 도구 중 하나를 선택하거나 인간이 개입하는 단계를 포함하는 등 분기 로직이 필요할 때 유용합니다.
LangGraph의 핵심 기능을 이해하면 다음과 같이 코드가 실행될 애플리케이션을 설정할 수 있습니다.
main 메서드에 모든 것을 통합합니다. 여기서는 변수 워크플로우 아래의 모든 요소를 포함하는 그래프를 선언합니다.
async function main() {
await createIndex();
await createSearchTemplates();
await ingestDocuments();
// Create the workflow graph with shared state
const workflow = new StateGraph(VCState)
// Register nodes - these are the processing functions
.addNode("decideStrategy", decideSearchStrategy)
.addNode("prepareInvestment", prepareInvestmentSearch)
.addNode("prepareMarket", prepareMarketSearch)
.addNode("executeSearch", executeSearch)
.addNode("visualizeResults", visualizeResults)
// Define execution flow with conditional branching
.addEdge(START, "decideStrategy") // Start with strategy decision
.addConditionalEdges(
"decideStrategy",
(state: typeof VCState.State) => state.searchStrategy, // Conditional function
{
investment_focused: "prepareInvestment", // If investment focused -> RRF template preparation
market_focused: "prepareMarket", // If market focused -> dynamic query preparation
}
)
.addEdge("prepareInvestment", "executeSearch") // Investment prep -> execute
.addEdge("prepareMarket", "executeSearch") // Market prep -> execute
.addEdge("executeSearch", "visualizeResults") // Execute -> visualize
.addEdge("visualizeResults", END); // End workflow
const app = workflow.compile();
await saveGraphImage(app);
const query =
"Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K";
const marketResult = await app.invoke({ input: query });
console.log(marketResult.final);
}쿼리 변수는 가상의 검색창에 입력된 사용자 입력을 시뮬레이션합니다.

"800만~2500만 달러의 시리즈 A 또는 시리즈 B 펀딩을 받고 월 매출이 50만 달러 이상인 스타트업을 찾아주세요"라는 자연어 문구에서 모든 필터가 추출됩니다.
마지막으로 다음과 같이 메인 메서드를 호출합니다.
main().catch(console.error);결과
🔍 Checking if index exists...
🏗️ Creating index...
✅ Index created successfully!
Ingesting documents...
✅ Documents ingested successfully!
✅ Investment-focused template created successfully!
✅ Market-focused template created successfully!
📊 Workflow graph saved as: ./workflow_graph.png
🔍 Query: "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"
🤔 Search strategy: investment_focused - The query specifically seeks profitable fintech startups with defined funding amounts and high monthly revenue, which aligns closely with financial performance metrics and investment-related criteria.
💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis...
📋 Complete query: {
"size": 5,
"retriever": {
"rrf": {
"retrievers": [
{
"standard": {
"query": {
"semantic": {
"field": "semantic_field",
"query": "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"
}
}
}
},
{
"standard": {
"query": {
"bool": {
"filter": [
{
"terms": {
"funding_stage": [
"Series A",
"Series B"
]
}
},
{
"range": {
"funding_amount": {
"gte": 8000000,
"lte": 25000000
}
}
},
{
"terms": {
"lead_investor": []
}
},
{
"range": {
"monthly_revenue": {
"gte": 500000,
"lte": 0
}
}
}
]
}
}
}
}
],
"rank_window_size": 100,
"rank_constant": 20
}
}
}
🎯 Found 5 startups matching your criteria:
1. **TechFlow**
📍 San Francisco, CA | 🏢 logistics | 💼 B2B
💰 Series A - $8.0M
👥 45 employees | 📈 $500K MRR
🏦 Lead: Sequoia Capital
📝 TechFlow optimizes supply chain operations using AI-powered route optimization and real-time tracking. Founded in 2023, shows remarkable growth with $500K monthly revenue.
2. **DataViz**
📍 New York, NY | 🏢 enterprise software | 💼 B2B
💰 Series A - $10.0M
👥 42 employees | 📈 $450K MRR
🏦 Lead: Battery Ventures
📝 DataViz creates intuitive data visualization tools for enterprise customers. No-code platform allows business users to create dashboards without technical expertise.
3. **FinanceAI**
📍 San Francisco, CA | 🏢 fintech | 💼 B2C
💰 Series C - $25.0M
👥 120 employees | 📈 $1200K MRR
🏦 Lead: Tiger Global Management
📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.
4. **UrbanMobility**
📍 New York, NY | 🏢 logistics | 💼 B2B2C
💰 Series B - $15.0M
👥 78 employees | 📈 $750K MRR
🏦 Lead: Kleiner Perkins
📝 UrbanMobility revolutionizes urban transportation through autonomous delivery drones and smart logistics hubs. Partners with major retailers for same-day delivery across Manhattan and Brooklyn.
5. **HealthTech Solutions**
📍 Boston, MA | 🏢 healthcare | 💼 B2B
💰 Series B - $18.0M
👥 95 employees | 📈 $900K MRR
🏦 Lead: General Catalyst
📝 HealthTech Solutions develops medical devices and software for remote patient monitoring. Comprehensive telehealth platform reducing hospital readmissions by 30%.
✨ Done in 18.80s.전송된 입력에 대해 애플리케이션은 투자 중심 경로를 선택합니다. 그 결과, LangGraph 워크플로우에서 생성된 Elasticsearch 쿼리를 확인할 수 있으며, 이 쿼리는 사용자 입력에서 값과 범위를 추출합니다. 추출된 값이 적용된 쿼리가 Elasticsearch로 전송된 것을 볼 수 있으며, 마지막으로 visualizeResults 노드에 의해 형식이 지정된 결과를 확인할 수 있습니다.
이제 “샌프란시스코, 뉴욕 또는 보스턴에서 핀테크 및 헬스케어 스타트업을 찾아주세요”라는 쿼리를 사용하여 시장 중심 노드를 테스트해 보겠습니다.
...
🔍 Query: Find fintech and healthcare startups in San Francisco, New York, or Boston
🤔 Search strategy: market_focused - The query is focused on finding fintech startups in San Francisco that are disrupting traditional banking and payment systems, which pertains to specific industries (fintech) and locations (San Francisco). Thus, a market-focused strategy is more appropriate.
🔍 Preparing MARKET-FOCUSED search parameters with market emphasis...
📋 Complete query: {
"size": 5,
"retriever": {
"rrf": {
"retrievers": [
{
"standard": {
"query": {
"semantic": {
"field": "semantic_field",
"query": "Find fintech and healthcare startups in San Francisco, New York, or Boston"
}
}
}
},
{
"standard": {
"query": {
"bool": {
"filter": [
{
"terms": {
"industry": [
"fintech",
"healthcare"
]
}
},
{
"terms": {
"location": [
"San Francisco, CA",
"New York, NY",
"Boston, MA"
]
}
},
{
"terms": {
"business_model": []
}
}
]
}
}
}
}
],
"rank_window_size": 50,
"rank_constant": 10
}
}
}
🎯 Found 5 startups matching your criteria:
1. **FinanceAI**
📍 San Francisco, CA | 🏢 fintech | 💼 B2C
💰 Series C - $25.0M
👥 120 employees | 📈 $1200K MRR
🏦 Lead: Tiger Global Management
📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.
2. **CryptoWallet**
📍 Miami, FL | 🏢 fintech | 💼 B2C
💰 Series B - $16.0M
👥 73 employees | 📈 $820K MRR
🏦 Lead: Coinbase Ventures
📝 CryptoWallet provides secure digital wallet solutions for cryptocurrency trading and storage. Multi-chain support with enterprise-grade security features.
...
✨ Done in 7.41s.학습
글을 쓰는 과정에서 배운 점은 다음과 같습니다.
LLM에 필터의 정확한 값을 표시해야 합니다. 그렇지 않으면 사용자가 정확한 값을 입력해야 합니다. 카디널리티가 낮을 때는 이 방법이 괜찮지만, 카디널리티가 높을 때는 결과를 필터링할 메커니즘이 필요합니다.
검색 템플릿을 사용하면 LLM이 Elasticsearch 쿼리를 작성하게 하는 것보다 훨씬 더 일관되고 더 빠른 결과를 얻을 수 있습니다.
조건부 엣지는 여러 변형과 분기 경로를 갖춘 애플리케이션을 구축하는 강력한 메커니즘입니다.
정형 출력은 예측 가능하고 형식에 안전한 응답을 적용하므로 LLM으로 정보를 생성할 때 매우 유용합니다. 이렇게 하면 안정성이 향상되고 즉각적인 오해를 줄일 수 있습니다.
하이브리드 검색을 통해 시맨틱 및 정형 검색을 결합하면 정확도와 컨텍스트 이해의 균형을 유지하면서 더 정확하고 관련성 높은 결과를 얻을 수 있습니다.
결론
이 예시에서는 LangGraph.js를 Elasticsearch와 결합하여 자연어 쿼리를 해석하고 금융 또는 시장 중심의 검색 전략을 선택할 수 있는 동적 워크플로우를 만듭니다. 이러한 접근 방식은 수동 쿼리의 복잡성을 줄이고 벤처 캐피털 분석가의 유연성과 정확성을 향상합니다.




