智谱GLM-4 接金融数据:工具描述多写三个字,模型少犯一类错
作者: TickDB Research · 发布: 2026/5/31 · 阅读: 2
标签: C 类, 知乎, AI 工具
你把智谱开放平台的 Function Calling 示例从“查天气”改成“查行情”。在多次测试中,GLM-4-Flash 不仅调对了工具,还能把用户说的“茅台”映射成
600519.SH——这是中文模型在 A 股数据上的天然优势。但当三个行情工具同时注册时,同一个 query 在第三轮对话里突然调错了函数。优势区和暗坑区,只隔着一套工具描述的写法。
一、GLM-4 的中文红利,在金融场景下是双刃剑
GLM-4 对中文金融术语的理解、对 A 股代码的熟悉程度,明显优于仅靠英文语料做中文适配的模型。你用中文写“查询贵州茅台和宁德时代的最新股价”,它不仅能提取出两只股票,还可能自动补全代码——这是训练语料里 A 股信息密度高的直接体现。
但这层中文红利同时也是隐性债务。
| GLM-4 在金融数据场景下的表现 | 优势面 | 暗坑面 |
|---|---|---|
| 中文名称到代码的映射 | A 股常见股票可自动映射(开发者经验观察,非官方保证) | 港股、美股的映射准确率递减;遇到“苹果”这类歧义词可能选错市场 |
| 中文工具描述解析 | 对“最新价”“成交量”等术语理解准确 | 两个工具的中文描述高度相似时,选择置信度下降 |
| 免费额度 | 零成本验证,适合原型开发 | 免费版在多工具场景下的稳定性可能弱于付费版本(以智谱官网当前文档为准) |
| 错误信息的中文理解 | 能准确理解“请求超限”“Key失效”等提示 | 可能“过度理解”——从“请求超限”推断出“服务端繁忙,建议稍后重试”而不是只告知失败 |
这些特点决定了一件事:用 GLM-4 接金融数据,你需要的不是“把 curl 改成 Python”,而是一套专门针对中文模型行为特征的工具设计方法。
为了让模型的优势不被数据源的不一致性抵消,本文以 TickDB 的统一行情接口作为数据后端——跨市场一致的字段命名、错误码语义和 symbol 格式,让你给 GLM-4 定义的工具只需一套字段映射和一套错误处理逻辑。如果你在 AI 编码环境中工作,TickDB 也提供 MCP 端点
mcp.tickdb.ai,可在 Claude Code、Cursor 等工具中直接调用,无需手写 HTTP 封装。
二、三个设计决策,决定你的 Agent 会不会“自作主张”
决策一:工具描述的第一句话,写功能还是写边界?
GLM-4 的 tool_choice 默认为 auto。模型根据 function.description 判断“这个工具能做什么”。当一个工具时,任何合理描述都能工作;当三个工具共存时,描述里的每一个模糊词都是误选的种子。
金融场景最小工具集通常包括:
| 工具 | 功能 | 如果描述写成这样,模型会混淆 |
|---|---|---|
get_ticker | 实时行情快照 | “获取股票行情数据” |
get_kline | 历史 K 线 | “获取股票历史数据” |
get_kline_latest | 最近一根 K 线 | “获取最新 K 线数据” |
当三个描述分别是“获取行情”“获取历史”“获取最新K线”时,GLM-4 对“现在价格是多少”可能选 get_ticker,也可能选 get_kline_latest——因为两者都能回答“现在价格”。而 get_kline_latest 返回的是最近一根已完成 K 线的收盘价,不是实时价。
正确的写法是把“不要用”写进 description 的第一句:
# ✅ GLM-4 能正确区分
"description": (
"获取品种实时行情快照(最新价、24小时成交量)。"
"不要使用此函数获取历史K线数据——历史K线应使用 get_kline。"
"不要使用此函数获取最近K线——最近K线应使用 get_kline_latest。"
)
对于 GLM-4,在 description 中使用中文否定句划定工具边界,是一种在实践中被验证有效的做法(开发者经验观察)。模型不是理解不了“获取行情”和“获取K线”的区别——它是不确定在当前上下文里你更想要哪个。你把“不要用这个”写在第一句,它就不再犹豫。
决策二:错误返回让模型自行理解,还是写死它的行为?
金融数据接口的返回不是成功/失败二元。get_ticker 可能遇到限流、Key 失效、symbol 不存在等情况。如果你把 API 返回的 raw JSON 直接丢给模型,GLM-4 的中文理解能力反而会成为风险:
| 实际状态 | 如果你把 raw JSON 直接丢给模型 |
|---|---|
限流 3001 | 模型可能回复“请求频率过高,建议稍后重试,根据最近一次查询……”然后编一个价格 |
Key 无效 1001 | 模型可能回复“API Key 配置有误,请检查。在此期间,根据公开信息……” |
| symbol 不存在 | 模型可能回复“该股票代码不存在,您是否想查询……”然后推荐一个代码 |
这不是 GLM-4 的 bug——它只是太想帮忙了。但在金融场景下,任何“帮忙”都是不可接受的。
解决方式:在工具函数内部,把所有错误转换为模型可以直接判断的 success 字段,并在 system prompt 中写死它的行为。
# 无论什么错误,始终返回此结构
{"success": False, "data": [], "error_code": "RATE_LIMITED"}
# system prompt 中的硬规则——不给模型留任何自行发挥的空间
工具返回 success=false 时,你只能说:
"当前无法获取行情数据,错误原因:<error_code>。"
不要说"建议稍后重试"、不要猜测原因、不要提供替代方案、绝对不要编造任何数值。
这条规则在 GLM-4 上需要比在其他模型上写得更“绝”——它的中文能力越强,就越容易从 error_code 里读出额外意思,然后自己扩展回复。你给它留一条缝,它就敢钻进去。
决策三:利用中文映射优势,还是禁止它?
GLM-4 能从“茅台”自动提取 600519.SH,从“宁德时代”提取 300750.SZ——这是训练语料给的天然能力。但这个能力有三个边界:
- A 股覆盖率好,港股美股递减:“腾讯”映射
700.HK成功率较高,“苹果”映射AAPL.US成功率也较高,但“苹果”有歧义——模型需要判断是 AAPL 还是水果。 - 映射结果的 symbol 格式可能不标准:模型可能返回
600519.SSE而非600519.SH。.SSE在 TickDB 不是有效后缀,查询静默失败。 - 你可以通过 system prompt 完全关闭这个行为——GLM-4 的指令跟随能力足够强,只要 prompt 里明确写了“不要自行转换中文名称为代码”,它就会照做。
建议策略:原型验证阶段利用自动映射快速跑通流程。生产环境中通过 system prompt 关闭这个行为,改由专门的映射函数(对接 TickDB 的 GET /v1/symbols/available 品种列表)完成中文名称到标准代码的转换。这样 symbol 格式完全可控。
三、完整实现
以下代码将上述三个决策落实为三层封装:参数校验层在请求前拦截非法格式,工具执行层统一限流退避和结构化错误返回,GLM-4 对话层管理完整的 Function Calling 生命周期。
SDK 调用约定提示:
ZhipuAI类名、chat.completions.create方法签名、tool_choice="auto"、role: "tool"回填格式及glm-4-flash模型名称,均以智谱开放平台官网当前文档为准。正式接入前建议核对你所使用的 SDK 版本的实际行为。
环境准备
pip install zhipuai python-dotenv requests
.env 文件(不要提交到版本控制):
ZHIPUAI_API_KEY=你的智谱API Key
TICKDB_API_KEY=你的TickDB API Key
TICKDB_REST_URL=https://api.tickdb.ai
完整代码:glm4_tickdb.py
# glm4_tickdb.py
# 智谱 GLM-4 Function Calling 接入 TickDB 实时行情
# 三层封装:参数校验 → 工具执行与错误处理 → GLM-4 对话
# Python 3.10+ 可直接运行
import os
import json
import time
import re
import requests
from decimal import Decimal, InvalidOperation
from dotenv import load_dotenv
from zhipuai import ZhipuAI
load_dotenv()
# ================== 配置 ==================
ZHIPUAI_API_KEY = os.getenv("ZHIPUAI_API_KEY")
TICKDB_API_KEY = os.getenv("TICKDB_API_KEY")
TICKDB_REST_URL = os.getenv("TICKDB_REST_URL", "https://api.tickdb.ai")
MAX_RETRIES = 3
# ================== 第一层:Symbol 格式校验 ==================
SYMBOL_PATTERN = re.compile(
r'^(\d{6}\.(SH|SZ|BJ)|[1-9]\d{0,4}\.HK|[A-Z]{1,5}\.US|'
r'[A-Z]{2,4}[A-Z0-9]{2,8}|[A-Z]{2}\d{4})