1. 项目背景与核心价值
金融领域的数据搜索一直是个高门槛的技术活。传统金融分析师要查询一家初创公司的融资历史,往往需要在搜索框里填上十几项筛选条件:融资轮次、金额区间、行业分类、地域分布...这不仅操作繁琐,更可怕的是——稍有遗漏就可能错过关键标的。去年一家知名风投就曾因查询条件设置不当,漏掉了一个后来估值翻20倍的AI医疗项目。
这个项目要解决的正是这个痛点:让金融从业者用自然语言提问,AI自动解析意图并生成精准的Elasticsearch查询。比如你说"找旧金山A轮融资500-1000万美元的金融科技公司",系统就能自动转换成结构化查询语句。这背后是LangGraph.js的工作流编排能力和Elasticsearch 9.x的混合搜索技术(结合关键词与向量搜索)在支撑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 核心组件分工
整个系统像一条精密的流水线:
- LangGraph.js:负责流程控制,把自然语言理解、查询生成、结果处理等模块串联成有状态的工作流
- Elasticsearch:担任数据引擎,既处理结构化字段的精确匹配,也支持语义搜索
- LLM(如GPT-4):作为"翻译官",把用户口语转换成机器可理解的查询参数
2.2 工作流设计
典型的查询会经历以下节点:
- 策略决策节点:判断查询偏向财务维度(融资/估值)还是市场维度(行业/地域)
- 参数提取节点:从自然语言中抽取出关键过滤条件
- 模板填充节点:将参数注入预定义的ES查询模板
- 搜索执行节点:向Elasticsearch发送最终查询
- 结果渲染节点:把原始JSON转换成易读的表格
关键创新点在于使用了搜索模板而非动态生成DSL。这就像给SQL查询准备了预编译语句,既避免语法错误,又防止LLM"自由发挥"导致结果不稳定。
3. 关键实现细节
3.1 Elasticsearch配置
金融数据需要特殊处理:
json复制// 索引映射示例
{
"mappings": {
"properties": {
"funding_round": { "type": "keyword" },
"amount_range": { "type": "double_range" },
"business_model": {
"type": "text",
"fields": {
"vector": {
"type": "dense_vector",
"dims": 768
}
}
}
}
}
}
特别注意:
- 金额类字段用range类型便于区间查询
- 文本字段同时保留keyword和vector两种形式
- 为行业分类等字段预建聚合,加速筛选值获取
3.2 LangGraph节点实现
以策略决策节点为例:
typescript复制// 使用Zod定义输出结构
const strategySchema = z.object({
strategy: z.enum(["investment", "market"])
});
async function decideStrategy(state: GraphState) {
const filters = await getAvailableFilters(); // 获取当前可用的筛选值
const llm = new OpenAI({
modelName: "gpt-4",
temperature: 0
}).withStructuredOutput(strategySchema);
const prompt = ChatPromptTemplate.fromMessages([
["system", `你是一位金融数据分析专家。根据用户问题和以下可用筛选器决定搜索策略:
可用筛选器: ${JSON.stringify(filters)}`],
["human", "{input}"]
]);
const chain = prompt.pipe(llm);
const { strategy } = await chain.invoke({ input: state.query });
return { ...state, strategy };
}
这个设计有三处精妙:
- 动态获取可用筛选值避免LLM臆造不存在的条件
- 低温运行(temperature=0)保证决策稳定性
- 结构化输出确保返回结果可直接用于流程控制
4. 性能优化实践
4.1 混合搜索策略
对于"医疗健康领域A轮融资"这类查询:
- 精确匹配"融资轮次=A轮"
- 语义搜索找出"医疗健康"相关文档
- 通过function_score组合两种结果
对应的ES查询模板:
json复制{
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{ "term": { "funding_round": "{{funding_round}}" }},
{
"text_expansion": {
"business_model.vector": {
"model_id": ".elser_model_2",
"model_text": "{{industry}}"
}
}
}
]
}
},
"functions": [
{
"filter": { "term": { "funding_round": "{{funding_round}}" }},
"weight": 2
}
]
}
}
}
4.2 缓存机制
高频查询模板预编译:
typescript复制const templateCache = new Map();
async function getSearchTemplate(strategy: string) {
if (templateCache.has(strategy)) {
return templateCache.get(strategy);
}
const template = await loadTemplateFromES(strategy);
templateCache.set(strategy, template);
return template;
}
实测显示,模板缓存使平均响应时间从320ms降至180ms。
5. 踩坑实录
5.1 数值区间处理
初期直接让LLM输出ES查询时,经常出现这种错误:
json复制// 错误示例
{
"range": {
"amount": {
"gte": "800万", // 字符串而非数字
"lte": "2500万"
}
}
}
解决方案:
- 在提示词中明确要求数值类型
- 添加后置校验逻辑:
typescript复制function validateRange(range: any) {
if (isNaN(Number(range.gte)) || isNaN(Number(range.lte))) {
throw new Error("区间值必须是数字");
}
}
5.2 行业术语映射
用户说"AI医疗",但数据库存的是"人工智能在医疗健康领域的应用"。我们通过以下方式解决:
- 建立同义词词典
- 在向量搜索前进行术语扩展
- 对关键字段使用cross_fields查询:
json复制{
"query": {
"multi_match": {
"query": "AI医疗",
"type": "cross_fields",
"fields": ["industry", "tags", "description"],
"operator": "and"
}
}
}
6. 部署建议
6.1 硬件配置
根据数据量级推荐:
- 测试环境:8核CPU/32GB内存/500GB SSD
- 生产环境:16核CPU/64GB内存/1TB SSD × 3节点集群
关键参数调整:
yaml复制# elasticsearch.yml
thread_pool.search.size: 16
thread_pool.search.queue_size: 1000
indices.query.bool.max_clause_count: 10000
6.2 监控指标
必须监控的四个黄金指标:
- 查询延迟P99 < 500ms
- 错误率 < 0.1%
- 节点CPU利用率 < 70%
- JVM内存使用率 < 75%
推荐使用Elasticsearch自带的Prometheus exporter配合Grafana展示。
7. 扩展方向
这套架构其实不仅适用于金融场景,稍加改造就能用于:
- 法律文书检索(案由/法条/判决多维筛选)
- 电商商品搜索(属性/评价语义混合查询)
- 人才库匹配(技能/经历/薪资范围组合查询)
最近我们就在尝试加入时间序列分析能力,让系统能回答"哪些行业的B轮融资额在最近半年增长最快"这类动态问题。实现的关键是在Elasticsearch中配置transform实现实时聚合,再通过LangGraph的循环机制定期更新分析结果。
