不写 Python,不搭服务,不训模型。一条 SQL 调用大模型,文本分类从未如此简单。
传统 ML 做文本分类,到底有多累?
数据清洗、分词、特征工程、模型选型、训练调参、部署上线、推理服务维护......一套流程走下来,少则数天,多则数周。模型效果不好?重新来过。换个场景?再训一个。
有没有一种方式,让文本分类像查数据一样简单?
Hologres AI Function 给出了答案:SQL 即 AI,数据在哪,智能就在哪。
什么是 Hologres AI Function?
Hologres AI Function 将大模型推理能力直接内置于数据库引擎。你无需部署任何推理服务,只需通过 SQL 函数(如 ai_gen_structured),就能在查询过程中直接调用大模型,完成分类、摘要、抽取等 AI 任务。
核心优势一句话概括:数据不出库,推理即查询。
实战:用 SQL 对豆瓣影评做情感分类
下面我们用一个完整的例子,展示如何用 Hologres AI Function 对豆瓣影评进行情感分类(正向 / 负向 / 中性),并一步步调优到 95% 准确率。
Step 1:准备数据
本文使用的数据集来自 豆瓣影评数据集,取其中 200 条记录。
豆瓣影评数据集:www.kaggle.com/datasets/ut...
首先在 Hologres 中创建数据表:
plaintext
CREATE TABLE movie_reviews_200(
cn_name text,
review text,
stars bigint,
sentiment_label text
);
其中 sentiment_label 是人工标注的情感标签,用于后续评估模型效果。
通过 psql 将 CSV 数据导入 Hologres:
plaintext
psql -U <db_user_name> -p 80 -h xxx.hologres.aliyuncs.com -d ai_test1 \
-c "COPY movie_reviews_200 FROM STDIN WITH DELIMITER ',' CSV HEADER;" \
< ~/Downloads/movie_reviews_200.csv
数据准备就绪,接下来开始调用大模型。
Step 2:创建提示词表
Hologres 支持将提示词作为数据存储在表中,通过 prompt() 函数动态拼装变量,灵活管理和迭代提示词版本。
plaintext
CREATE TABLE ai_prompts(
name text PRIMARY KEY,
prompt_text text
);
INSERT INTO ai_prompts VALUES(
'classify_prompt1',
$$
你是一名专业的电影评论情感分析专家。你的任务是根据输入的**电影名称、用户影评和 stars 评分**,
判断该影评对电影整体的情感倾向,并输出唯一结果。
## 输出要求
仅输出以下 3 个值之一:
- 正向
- 负向
- 中性
除上述 3 个标签外,**不要输出任何其他内容**,包括解释、分析过程、标点、前缀、空格或重复输入。
下面是本次判定的输入:
- 电影名称:{0}
- 用户影评:{1}
- stars:{2}
$$
);
这种"提示词即数据"的管理方式,让你可以随时新增、修改、对比不同版本的提示词,而无需改动任何代码。
Step 3:一条 SQL 完成批量分类
这是整个流程中最核心的一步。通过 ai_gen_structured 函数,在 SQL 查询中直接调用大模型,对 200 条影评逐条进行情感分类,并将结果写入结果表。
plaintext
-- 创建分类结果表
CREATE TABLE movie_reviews_predict(
cn_name text,
reivew text,
stars bigint,
sentiment_label text,
predict_label text,
usage jsonb
);
-- 批量分类并写入结果
WITH prompts AS(
SELECT cn_name, review, stars,
prompt(prompt_text, cn_name, review, stars)::text AS prompt,
sentiment_label
FROM movie_reviews_200
LEFT JOIN ai_prompts ON ai_prompts.name = 'classify_prompt1'
),
gen_data AS(
SELECT
cn_name, review, stars, sentiment_label,
ai_gen_structured(
model_name => 'qwen37-plus',
message => prompt,
response_format => $${
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"label": {"type": "string"}
}
}
}$$,
params => '{"show_details": true}'
) AS result
FROM prompts
),
per_row AS (
SELECT cn_name, review, stars, sentiment_label,
result->'output'->>'label' AS predict_label,
result->'usage' AS usage
FROM gen_data
)
INSERT INTO movie_reviews_predict SELECT * FROM per_row;
没有 Python 脚本,没有推理服务,没有数据搬迁------一条 SQL 搞定全部。
Step 4:效果评估
分类完成后,用一条简单的 SQL 即可计算准确率:
plaintext
SELECT
COUNT(*) AS total_count,
SUM(CASE WHEN predict_label = sentiment_label THEN 1 ELSE 0 END) AS correct_count,
ROUND(
SUM(CASE WHEN predict_label = sentiment_label THEN 1 ELSE 0 END)::numeric
/ COUNT(*) * 100, 2
) AS predict_accuracy
FROM movie_reviews_predict;

首轮结果:准确率 83.50%。
效果不错,但还有提升空间。关键在于------我们不需要重新训练模型,只需优化提示词。
Step 5:提示词调优------准确率飙升至 95%
在提示词中加入更精细的分类规则,例如:
-
明确输入的含义(电影名称、影评文本、评分各自的权重)
-
设定判定优先级(评论文本 > 结论句 > 整体语气 > 评分)
-
处理特殊表达(反讽、转折句、欲扬先抑、比较句式)
-
模糊时兜底为"中性"
plaintext
INSERT INTO ai_prompts VALUES(
'classify_prompt_opt',
$$
你是一名专业的电影评论情感分析专家。你的任务是根据输入的**电影名称、用户影评和 stars 评分**,判断该影评对电影整体的情感倾向,并输出唯一结果。
## 输入说明
你将收到以下 3 项内容:
1. **电影名称**
用于辅助理解影片题材、风格和评论语境,但不能脱离影评内容单独判断情感。
2. **用户影评**
这是最主要的判断依据。评论可能很短,也可能包含口语、吐槽、反问、玩梗、引用、夸张、反讽、阴阳怪气等表达。你需要理解其真实语义,而不是只看字面词汇。
3. **stars 评分**
范围为 0~5 的整数。通常分数越高越偏正向,越低越偏负向,但它只能作为辅助信息。**当 stars 与评论内容冲突时,必须以评论文本为准。**
---
## 判定原则
### 1. 以评论文本为核心依据
判断的是**用户对电影整体的态度**,不是对某个演员、镜头、宣传或观影环境的单独情绪。只要评论文本能体现明确立场,就优先按文本判断。
### 2. stars 仅作辅助
当评论很短、态度模糊时,可以参考 stars;但若评论文字和 stars 冲突,一律以文字真实含义为准。
### 3. 关注整体语义,不机械看单个词
不要因为出现"好"就判正向,也不要因为出现"差"就判负向。要结合上下文、语气、转折、结论和整体情绪来判断。
### 4. 无法确定时优先中性
如果评论信息不足、态度不明、褒贬难分或语义模糊,不要强行判断,优先输出"中性"。
---
## 标签判定标准
### 正向
当评论整体明确表达喜欢、认可、推荐、满意、惊喜、感动等态度时,判为**正向**。
常见情况:
- 明确夸电影好看、精彩、感人、值得看、超出预期
- 表达"喜欢""推荐""没白看""后劲很大"
- 虽有小缺点,但总体肯定
例如:
- "比预期好很多"
- "虽然节奏慢一点,但真的很好看"
- "有瑕疵,不过整体很打动我"
### 负向
当评论整体明确表达失望、不满、否定、不推荐、厌烦等态度时,判为**负向**。
常见情况:
- 明确说难看、无聊、尴尬、拖沓、失望、看不下去
- 认为剧情、演技、节奏、逻辑等问题严重
- 虽承认局部优点,但总体否定
例如:
- "太无聊了,看得想走"
- "演员不错,但电影真的很难看"
- "本来很期待,结果特别失望"
### 中性
当评论无法体现明确正负态度时,判为**中性**。
常见情况:
- 评论过短,如"还行""一般""就那样"
- 只复述剧情,没有评价
- 只提问、引用台词、玩梗,未表达明确好恶
- 褒贬都有,但无法判断最终倾向
- 重点不在电影本身
例如:
- "所以最后他到底死没死?"
- "看过"
- "有优点也有问题"
---
## 特殊表达处理
### 1. 反讽和阴阳怪气
要按真实语义判断,不按字面判断。
例如:"真是神作,看得我快睡着了" → **负向**
### 2. 转折句
重点关注"但是、不过、然而、可惜"等转折后的内容。
例如:"前面还行,但是后面崩了" → **负向**
### 3. 欲扬先抑 / 欲抑先扬
看最终落点。
例如:"本来没期待,结果意外好看" → **正向**
### 4. 比较表达
通过与前作、同类片比较来表达态度时,提取真实倾向。
例如:"比上一部强太多" → 多数为**正向**
"还不如网大" → **负向**
---
## 冲突信息优先级
当信息不一致时,按以下顺序判断:
**评论文本真实语义 > 明确结论句 > 整体语气 > stars 评分 > 电影名称**
---
## 输出要求
仅输出以下 3 个值之一:
- 正向
- 负向
- 中性
除上述 3 个标签外,**不要输出任何其他内容**,包括解释、分析过程、标点、前缀、空格或重复输入。
下面是本次判定的输入:
- 电影名称:{0}
- 用户影评:{1}
- stars:{2}
$$
);
使用新提示词重新分类:
- 只需将 SQL 中的提示词 name 改成'classify_prompt_opt'
plaintext
-- 清除上次数据
TRUNCATE TABLE movie_reviews_predict;
-- 使用优化后的提示词重新分类
WITH prompts AS(
SELECT cn_name, review, stars,
prompt(prompt_text, cn_name, review, stars)::text AS prompt,
sentiment_label
FROM movie_reviews_200
LEFT JOIN ai_prompts ON ai_prompts.name = 'classify_prompt_opt'
),
gen_data AS(
SELECT
cn_name, review, stars, sentiment_label,
ai_gen_structured(
model_name => 'qwen37-plus',
message => prompt,
response_format => $${
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"label": {"type": "string"}
}
}
}$$,
params => '{"show_details": true}'
) AS result
FROM prompts
),
per_row AS (
SELECT cn_name, review, stars, sentiment_label,
result->'output'->>'label' AS predict_label,
result->'usage' AS usage
FROM gen_data
)
INSERT INTO movie_reviews_predict SELECT * FROM per_row;
再次评估:
plaintext
SELECT
COUNT(*) AS total_count,
SUM(CASE WHEN predict_label = sentiment_label THEN 1 ELSE 0 END) AS correct_count,
ROUND(
SUM(CASE WHEN predict_label = sentiment_label THEN 1 ELSE 0 END)::numeric
/ COUNT(*) * 100, 2
) AS predict_accuracy
FROM movie_reviews_predict;
优化后准确率:95.00%!仅通过调整提示词,准确率提升了 11.5 个百分点。
| 阶段 | 准确率 | 提升 |
|---|---|---|
| 基础提示词 | 83.50% | --- |
| 优化提示词 | 95.00% | +11.5% |
Step 6:成本分析------200 条影评仅花 1 毛钱
分类完成后,我们还可以通过 SQL 查看 Token 消耗和缓存命中情况:
plaintext
SELECT
SUM((usage::jsonb ->> 'input_tokens')::int) AS total_input_tokens,
SUM((usage::jsonb ->> 'cached_tokens')::int) AS total_cached_tokens,
ROUND(
SUM((usage::jsonb ->> 'cached_tokens')::int)::numeric
/ NULLIF(SUM((usage::jsonb ->> 'input_tokens')::int), 0) * 100, 2
) AS cache_hit_rate
FROM movie_reviews_predict;
结果令人惊喜:
| 指标 | 数值 |
|---|---|
| 总输入 Token | ~25.8 万 |
| 缓存命中 Token | ~23.4 万 |
| 缓存命中率 | 90% |
Hologres 针对数据库批量推理的负载特征,对大模型 KV-Cache 命中率进行了深度优化。命中缓存的 Token 仅按 1~2 折 计费,大幅降低推理成本。
最终成本明细:
-
缓存命中部分:2.4 元/百万 Token × 10% × 0.23 百万 = 0.055 元
-
未命中部分:2.4 元/百万 Token × 0.024 百万 = 0.057 元
-
总计:0.11 元
200 条影评,情感分类准确率 95%,成本仅 0.11 元。
TIPS:如何提升 cached token 的命中率?
-
提示词中尽量将固定不变的部分置于提示词顶端;
-
变化的部分放到提示词末尾;
-
推荐使用 prompt()函数做提示词拼装,可参考本文的提示词示例。
三大核心优势总结
1. SQL 即 AI,零门槛上手
不需要 Python,不需要部署推理服务,不需要搭建 ML Pipeline。会写 SQL 的数据分析师,就能直接调用大模型完成文本分类任务。
2. 提示词即数据,迭代快如闪电
提示词存在数据表里,版本管理和 A/B 对比像改一行 SQL 一样简单。效果不好?换个提示词,重新跑一遍------几分钟搞定,无需重训模型。
3. 智能缓存加持,成本降到极致
最高 90% 的 Cache 命中率意味着绝大部分 Token 仅按 1~2 折计费。即便数据量扩大到上万条,成本依然可控。
适用场景
-
舆情监控: 电商评论、社交媒体的情感分析与趋势洞察
-
内容审核: UGC 内容的自动分类与合规检测
-
客服质检: 对话文本的意图识别与满意度评估
-
数据标注: 海量文本的自动化打标,替代高成本人工标注
-
行业分析: 金融资讯、医疗文本、法律文书的智能分类
写在最后
Hologres AI Function 让大模型走进了数据库,让文本分类变成了一条 SQL 的事。从数据准备到推理分类,从效果评估到成本优化,全程 SQL 搞定,全程数据不出库。
一条 SQL,从数据到智能。
本文采用通义千问 qwen3.7-plus 模型,搭配 Hologres 4.2 完成全部推理任务。