文章

Embedding + LLM:用大模型智能清洗地理数据(附代码实战)

用 Embedding 向量模型自动对齐地名,打造更聪明的数据查询系统

Embedding + LLM:用大模型智能清洗地理数据(附代码实战)

地名拼写错误、简称混用、历史名称不一致……地理行业的数据清洗痛点之一就是”名称对不齐”。今天我们分享一种智能方式:用 Embedding 向量模型自动对齐地名,打造更聪明的数据查询系统。


📌 地理行业常见问题场景

在做地理大数据分析或问答接口时,你一定遇到过:

  • 用户输入”成都高新区”,但你数据库里叫”高新技术产业开发区(成都)”;
  • 用户写了”渝北区”,但你表里是”重庆市渝北区”;
  • 数据源来自多系统,有”中山”、”中山市”、”中山(广东)”三种叫法。

这些拼写、格式、简称差异,导致无法正确匹配目标记录。传统 string matching(字符串匹配)方法太死板。怎么办?


💡 解决方案:用 Embedding 模型做智能相似度匹配

我们将所有地名编码为向量,再通过向量检索寻找”最相似”的标准名称,从而实现:

用户输入地名 → 转换为向量 → 检索最相似标准名称 → 返回结果


✅ 第一步:构建基本环境和准备模型

我们首先需要引入必要的组件,并初始化一个大语言模型(本文使用阿里 Qwen)、一个中文文本嵌入模型(DashScope Embedding)以及一个简单的向量数据库(这里用内存实现)。

1
2
3
4
5
6
7
8
9
from config import *  # 包含密钥配置
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore

llm = ChatOpenAI(model="qwen-max")
embeddings = DashScopeEmbeddings(model="text-embedding-v3")
vector_store = InMemoryVectorStore(embeddings)

📌 第二步:构建中文地名数据库并进行向量化

我们使用一个简单的地名列表模拟真实业务中的地理名称标准库。

1
2
3
4
5
6
7
geo_names = [
    "北京市海淀区", "北京市朝阳区", "上海市浦东新区", "广东省深圳市南山区",
    "成都高新区", "重庆市渝北区", "杭州市西湖区", "中山市",
    "武汉市洪山区", "西安市雁塔区"
]

_ = vector_store.add_texts(geo_names)

每个地名会被转换为一个向量,从而可以通过语义进行模糊比对,不再依赖于完全一致的关键词。


🔍 第三步:查询相似地名(Embedding 检索)

现在我们尝试用用户的输入进行相似度查询。以下是一个最基础的检索示例:

1
2
3
4
5
query = "成都高新技术产业开发区"
results = vector_store.similarity_search_with_score(query, k=3)

for name, score in results:
    print(f"候选名称:{name.page_content} | 相似度得分:{score:.4f}")

结果为:

1
2
3
候选名称:成都高新区 | 相似度得分:0.8053
候选名称:上海市浦东新区 | 相似度得分:0.5572
候选名称:中山市 | 相似度得分:0.5292

可以看到,尽管”成都高新技术产业开发区”与”成都高新区”在表述上有所不同,Embedding 依然能够准确识别出它们在语义上的高度一致性。


🛠 第四步:封装为工具组件,用于 Agent

我们可以把这个向量检索过程封装为一个工具,可以集成到 LangChain Agent 框架中,让大模型自主调用:

1
2
3
4
5
6
7
8
9
from langchain.agents.agent_toolkits import create_retriever_tool

retriever = vector_store.as_retriever(search_kwargs={"k": 1})

retriever_tool = create_retriever_tool(
    retriever=retriever,
    name="search_geo_names",
    description="用于查找最相似的地理名称,如城市、区县、开发区等"
)

通过以上代码,我们成功构建了一个用于地名匹配的工具,接下来可以构建一个 Agent 并集成这个工具。


🤖 构建可调用工具的 AI Agent

借助 LangChain 提供的 create_react_agent,我们可以快速创建一个 Agent,使其具备调用我们自定义工具的能力。

1
2
3
4
agent = create_react_agent(
    llm,
    tools=[retriever_tool]
)

接下来,调用 Agent 并传入用户的请求:

1
2
3
4
result = agent.invoke({"messages": [{"role": "user", "content": "介绍难山区"}]})

for m in result['messages']:
    m.pretty_print()

✅ 示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
================================ Human Message =================================
介绍难山区

================================== Ai Message ==================================
Tool Calls:
  search_geo_names (call_48e51e66ad684f88b08eff)
  Call ID: call_48e51e66ad684f88b08eff
  Args:
    query: 难山区

================================= Tool Message =================================
Name: search_geo_names
广东省深圳市南山区

================================== Ai Message ==================================
您提到的"难山区"可能是指广东省深圳市的南山区。南山区是深圳市的一个行政区,
位于深圳西部,毗邻香港,是中国改革开放的窗口,也是高新技术产业的重要基地。
区内有著名的科技园——深圳高新技术产业园区,汇集了大量的高科技企业和研发机构。
此外,南山区还拥有丰富的教育资源、文化设施以及优美的自然环境。

如果"难山区"不是您所指的地方,请提供更多的信息以便我能够更准确地为您提供相关信息。

🧠 第五步:智能匹配与过滤(LLM 二次判断)

尽管向量检索已经大幅提升了匹配的召回率,但在某些情况下,最高分匹配项可能只是”相关”而非”正确”。例如:

  • 用户查询”成都”,数据库里有”成都高新区”,它们虽然相似,但并非同一实体。
  • 用户输入”难山区”(错别字),最高匹配是”深圳市南山区”。

为了进一步提升准确率,我们可以引入 LLM 进行二次判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from typing import Literal, Optional
from pydantic import BaseModel, Field
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda


class MatchResult(BaseModel):
    """地名匹配结果"""
    name: Optional[str] = Field(None, description="最匹配的地名,仅在 accepted=True 时有值")
    accepted: Literal[True, False] = Field(..., description="是否接受该匹配结果")
    reason: Optional[str] = Field(None, description="匹配或拒绝的理由")


parser = PydanticOutputParser(pydantic_object=MatchResult)

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你是一个中文地名智能判断助手。请根据原始查询和候选地名及其相似度,"
            "判断是否存在真正语义等价的匹配项。\n\n"
            "要求:\n"
            "- 仅当你认为候选地名确实与原始查询表达的是同一个地理实体时,"
            "  才将 accepted 设置为 True,并填写对应的 name 和 score;\n"
            "- 如果所有候选项都不匹配(即便相似度较高),请将 accepted 设置为 False,"
            "  name 和 score 留空(设为 null),并简要说明原因;\n"
            "- 匹配标准需综合语义理解、业务常识和上下文逻辑;\n"
            "- 请严格按照以下结构化格式返回结果。\n\n"
            "{format_instructions}"
        ),
        ("human", "{query}")
    ]
).partial(format_instructions=parser.get_format_instructions())

🔗 拼接 Chain,实现自动判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def build_prompt_input(query: str, top_k: int = 3) -> dict:
    results = vector_store.similarity_search_with_score(query, k=top_k)
    formatted = "\n".join([
        f"- {doc.page_content}(相似度: {score:.4f}" for doc, score in results
    ])
    prompt_text = \
        f"""原始查询: {query}
候选地名如下:
{formatted}
"""
    return {"query": prompt_text}


# 拼接完整处理链条
chain = (
    RunnableLambda(build_prompt_input)
    | prompt
    | llm
    | parser
)

chain.invoke("难山区")

输出:

1
MatchResult(name='广东省深圳市南山区', accepted=True, reason=None)

再看一个不匹配的例子:

1
chain.invoke("成都")

✅ 示例输出:

1
MatchResult(name=None, accepted=False, reason="候选地名中没有与原始查询'成都'完全匹配的地名。")

📚 项目源码

如果你想看完整代码或亲自运行一遍,这里是项目的 GitHub 地址👇:

🔗 https://github.com/zhengjie9510/learn-langchain


总结

本文展示了如何结合 Embedding 向量检索LLM 智能判断,解决地理数据中的名称对齐问题:

步骤功能关键技术
向量检索快速召回候选地名Embedding + 相似度计算
LLM 判断精确筛选匹配结果Pydantic 结构化输出
Agent 集成自动化查询流程LangChain + ReAct

这种方法不仅适用于地名匹配,也可以迁移到其他需要模糊匹配的领域,如企业名称对齐、商品名称标准化等。

本文由作者按照 CC BY 4.0 进行授权