2025-05-19 18:10:22
今年年初,我基于一份记录去年消费情况的 CSV 格式表格,使用 Cursor 代码生成工具,根据我的需求生成了 Python 代码,成功分析了去年的年度消费情况,过程清晰且高效。如今,半年过去了,更多的基于大模型的产品层出不穷,近期 MCP(Model Context Protocol)备受关注,各厂商也积极适配自身工具,提供相应的 MCP Server 服务。
最近,我尝试使用了两款基于大型模型的 AI 产品,并选取一份新的表格数据(采用 2025 年的记账记录)来探究它们在使用上的特色。原始的表格样式如下所示。
Vanna 是一个 MIT 协议的开源 RAG(检索增强生成)框架,用于 SQL 生成和相关数据分析展示。Vanna 的工作流程可分为两个主要阶段:首先,利用您的数据训练生成一个基于 RAG 的模型;然后,针对您的每次提问,Vanna 会自动生成并执行相应的 SQL 语句,同时生成可视化图表。如果产生的 SQL 语句有问题,Vanna 允许人工介入更改,并且把更改后的 SQL 语句作为新的训练资料注入到 RAG 的向量数据库中。
首先,需要准备用于分析的数据。Vanna 支持 SQLite、PostgreSQL (PG)、MySQL、BigQuery 等多种数据库。为简化演示,本文使用 SQLite 数据库。先根据表头建立一张表 t_cost,然后把 csv 的数据导入,最后验证一下导入内容。
sqlite3 example.db
drop table t_cost;
CREATE TABLE t_cost (
datetime TEXT, -- 例如 "2025/1/1 18:08"
category TEXT,
item TEXT,
note TEXT,
price REAL,
exchangerate REAL,
account TEXT,
targetaccount TEXT,
targetamount TEXT
);
# 注意设定csv分隔符
.separator ","
.import --skip 1 /xxx/converted_english_headers_corrected.csv t_cost
select datetime from t_cost limit 1;
然后,根据 SQLite 数据库的连结信息,训练 Vanna模型。
client = OpenAI(
# This is the default and can be omitted
# api_key=os.environ.get("OPENAI_API_KEY"),
api_key= api_key,
base_url = "https://burn.hair/v1" # 换成代理
)
config_g = {}
# 可换成其他大模型
config_g['model']= "gpt-3.5-turbo"
class MyVanna(ChromaDB_VectorStore, OpenAI_Chat):
def __init__(self, config=None):
# 这里向量数据库使用ChromaDB
ChromaDB_VectorStore.__init__(self, config=config)
OpenAI_Chat.__init__(self, client=client,config=config_g)
vn = MyVanna()
# vn.connect_to_sqlite('Chinook.sqlite')
vn.connect_to_sqlite('/xx/example.sqlite')
df_ddl = vn.run_sql("SELECT type, sql FROM sqlite_master WHERE sql is not null")
for ddl in df_ddl['sql'].to_list():
vn.train(ddl=ddl)
training_data = vn.get_training_data()
training_data
最后,启动 Vanna 前端页面,开始根据我们提供的数据库,向 Vanna 提问。
from vanna.flask import VannaFlaskApp
app = VannaFlaskApp(vn,allow_llm_to_see_data=True)
app.run()
我先问了一个问题:“2025 年午餐的总消费次数和金额”,从截图可以看到,Vanna 的回答并不正确,原因在于它没有区分出大类别(category)和小项目(item)的区别,而且对于中文的识别也并不是很好,不过我们可以在 Vanna 提供的 SQL 基础上稍微修改一下,再次提交,可以看到,这次的回答就正确了。
我们进一步追问:“晚餐的总消费次数和金额”,可以看到,经过前一次的人工修正,Vanna 已经能够直接给出正确答案。
另一个要说的工具是,使用 Claude 3.5 Sonnet 作为 LLM,配合Duckdb MCP,其中Duckdb MCP如下所示。
"mcp-server-motherduck": {
"command": "uvx",
"args": [
"mcp-server-motherduck",
"--db-path",
":memory:"
]
}
我们使用如下的提示词询问大模型:“加载下面文件到 DuckDB “~/Downloads/2025_05_18 13.24.18.csv”,
在 DuckDB 中新建表 xiaofei,然后统计 2025 年总的消费次数 ”。可以看到大模型可以准确的回答对应的问题。
上面试验的例子都是根据一张数据表做一些简单的分析,从使用过程来看,或许有些大材小用,然而,在处理涉及复杂数据表关联的场景时,这两款工具都应具备广阔的应用前景,因为它们都能满足用户通过自然语言探索和分析数据表的需求。
这两款工具,分别基于 “LLM + RAG” 和 “LLM + MCP” 的技术实现路径,尽管技术细节不同,但其本质都是为了准确刻画上下文信息,从而协助 LLM 精确理解并执行用户的请求。
2025-05-12 14:12:11
受限于训练数据的实时性、隐私数据等因素,大型语言模型(LLM)的能力并非包罗万象。为了扩展其功能边界,通常需要借助外部工具。这些工具可以以 API 的形式接入,也可以是私有向量数据库。大型语言模型可以通过对提示词(Prompt)进行匹配识别,或者依赖于各家厂商自定义的 Function call 结构化匹配,从而在众多已注册的工具中选择最合适的进行调用。
更进一步,我们的任务通常难以通过单一工具独立解决,而往往需要在理解上下文的基础上,协调多个工具按序执行。这就催生了 Agent(智能体)的概念,作为负责协调和管理的模块。我们常用的代码编辑器 Cursor 就是一个代码领域的 Agent,它可以根据用户的需求在大模型的协助下编写相应的代码,如果发现缺少代码运行环境,可以在用户授权的情况下,调用相关工具进行安装和测试。
尽管上述方案能够扩展大型模型的能力,但问题在于不同大型模型之间的具体实现方式存在差异。即使是同一个大型模型,其对不同类型工具的调用方式也可能不同。MCP(Model Context Protocol,模型上下文协议)的出现,正是为了解决这种不规范的情况。通过遵循同一套标准,所有接入大型模型的工具能够实现标准化对接,从而大幅降低了工具与大型模型集成的复杂度和工作量。例如,对于最常用的网络搜索服务,只需编写一个符合 MCP 标准的服务,即可供所有支持 MCP 协议的大型模型使用。
具体来看,MCP 主要包含两个核心部分:MCP Client 和 MCP Server。MCP Client 代表用户使用的应用(例如 Cursor, Claude),其职责包括:
MCP Server 作为连接 LLM 外部能力的服务中介,其职责包括:
资源、提示词和工具是 MCP 可以对外提供的三种主要能力类型。
MCP 的核心在于,可以把 LLM 提供的上下文内容结构化,结合过去的记忆、现有的工具和当前状态,逐步完成上下文中要求的任务目标。
安装 Node,并确保 npx 命令可用。
访问网址https://mcp.composio.dev/,找到你想集成的服务(这里以 Gmail 为例),点击 Generate 按钮,复制生成的命令,打开本地命令行工具,粘贴并回车执行。
打开 Cursor 的MCP 设置页面,可以看到添加的 Gmail 服务。
在对话框中,使用 Agent 模式,提问“昨天收到的 gmail 邮件”,其回复如下图所示。
除了 Composio 平台,Smithery (https://smithery.ai/) 也是一个优秀的 MCP 平台,值得您了解和尝试。
2025-04-16 15:56:09
随着大型语言模型 (LLM) 的兴起,Embedding 成为了一个备受关注的热词。那什么是 embedding 呢?embedding 中文翻译过来是嵌入
,本质上就是一个向量,类似[0.1,0.8,0.6….]这样的形式,在很多文章里,我们可以看到嵌入向量(embedding vector)这样的描述。
Embedding 将所有模型的输入(例如文本或图片)转化为一系列数字向量表示,相似的输入在向量空间中的距离也更接近。近期备受关注的 RAG (Retrieval Augmented Generation) 技术,正是将所有私有知识内容转化为向量,并将用户提出的问题也转化为向量,然后检索出与问题最相似的知识片段,再将这些知识传递给大型模型,使其整合生成完整的答案。
在 embedding 技术广泛应用之前,One-Hot 编码曾是主流方法。如果和我们比较熟悉的 One-Hot 对比起来理解,直观上看,embedding 可以理解为对 One-Hot 编码进行平滑处理的结果,而 One-Hot 编码则类似于对 Embedding 进行最大池化操作。与 One-Hot 的自定义编码方式不同,embedding 其实是在大模型训练过程中,通过对比学习的方式生成的模型输出。这个过程可以看作是 LLM 训练的一个关键组成部分,这也就解释了一个常见问题,在配置本地的大模型客户端的时候,会要求选择 embedding 的模型,这个模型可以和 LLM 保持一个系列,也可以选择其他公司的模型,但一定要保证模型输出的向量维度和 LLM 适配。
对于我们输入大模型的 prompt,首先会被拆分成独立的 token,然后每一个 token会通过一个 embedding 模型得到对应的 embedding,每个 embedding 是一个高维的向量,比如 OpenAI 的 text-embedding-3-small
模型生成的就是 1536 维的向量,不同模型生成 embedding 的方式和维度都不相同,但是每个 embedding 向量都包含了对应 token 的语法和语义的特征。
常用的 embedding 模型性能可以通过https://huggingface.co/spaces/mteb/leaderboard 查到。
使用 OpenAI 原生 SDK
from openai import OpenAI
import os
api_key=os.environ.get("OPENAI_API_KEY")
client = OpenAI(
api_key= api_key,
base_url = "https://burn.hair/v1"
)
input = "This is a test document."
query_result = client.embeddings.create(model="text-embedding-3-small", input=input)
print(query_result.data[0].embedding)
使用 LangChain 的 SDK
def get_openai_embedding():
# 获取当前脚本所在的目录
current_dir = Path(__file__).resolve().parent
# 获取上一层目录的路径(项目根目录)
project_root = current_dir.parent
# 加载项目根目录中的 .env 文件
env_path = project_root / '.env'
load_dotenv(dotenv_path=env_path)
# 创建并返回 OpenAIEmbeddings 实例
return OpenAIEmbeddings(
openai_api_key=os.environ.get("OPENAI_API_KEY"),
model="text-embedding-3-small",
base_url='https://burn.hair/v1'
)
statement = "I am happy"
embedding = get_openai_embedding()
print(embedding.embed_query(statement))
一些开源的模型也可以通过 Hugging Face平台的 SDK 或 API 的方式使用
def generate_embedding(text: str) -> list[float]:
embedding_url = "https://api-inference.huggingface.co/pipeline/feature-extraction/sentence-transformers/all-MiniLM-L6-v2"
response = requests.post(
embedding_url,
headers={"Authorization": f"Bearer {config.hg_token}"},
json={"inputs": text})
if response.status_code != 200:
raise ValueError(f"Request failed with status code {response.status_code}: {response.text}")
return response.json()
a= "i am a boy"
print(generate_embedding(a))