在实际的古钱币鉴定场景中,单靠一张图片让大模型直接下结论,结果往往不可靠。真正可用的做法是把“观察、推理、查证、输出报告”拆成多个步骤,让一个 Agent 来编排流程,再让不同的大模型分别承担识别和判断工作。这篇文章记录的是一个可行的演示项目:使用 Claude Agent 负责任务拆解与工具调用,使用智谱 GLM 大模型的多模态能力完成钱币图像识别,最终输出一份结构化的鉴定结果。
适合阅读这篇文章的读者有两类:一类是做 AI 应用开发,想了解 Agent 如何编排多模态识别任务;另一类是收藏、文博或电商场景的技术人员,想把 AI 钱币鉴定从“能跑通”推进到“基本可用”。读完以后,你会得到一套最小可运行的代码框架,也能知道图片输入、模型参数、错误排查和报告输出这些环节分别要如何处理。
需要先说明一点:不同时间开放的模型版本和接口风格可能不同,文中使用的模型名、请求地址和参数是演示值。落地时要以你实际开通的账号和官方文档为准,不要照抄版本号。
1. 先搞清楚:AI 钱币鉴定为什么要用“Agent + 大模型”而不是单模型
1.1 古钱币鉴定的识别难点从哪来
古钱币鉴定不是简单“看图说话”。一枚钱币出现在镜头前,至少包含几层信息:
第一层是图面信息。钱币的材质、锈色、包浆、穿口形状、外廓宽窄、文字书写风格,都会影响判断。不同品相的同一枚钱币,在照片上的表现差异很大。
第二层是文字信息。很多古钱币正面和背面都有钱文,比如“乾隆通宝”“光绪元宝”,但字口可能模糊、磨损,或者被锈覆盖。OCR 模型直接识别经常出错,因为钱文是铸造体,不是现代印刷体。
第三层是背景知识。判断一枚钱币是哪个朝代、哪个局铸造,需要把文字、形制和时代特征连起来判断。比如,同样是“通宝”,宋代的形制和清代就有明显差别。
如果只把一个视觉大模型的输出当作最终结论,很容易出现两个问题:模型只描述了“图里有什么”,却不告诉你“为什么这样判断”;或者模型给出了一个很自信的结论,但其实没有经过任何查证。
所以,这类任务更适合用一个 Agent 做编排:先观察图片,再做知识查证,最后把结论和依据一起输出。
1.2 Claude Agent 和智谱 GLM 在流程里分别承担什么角色
在这个项目中,两个模型的分工非常明确。
Claude Agent 是“调度者”。它负责理解用户的鉴定目标,拆解成多个子任务,决定先调用哪个工具、什么时候需要再观察一次图片、最后如何汇总报告。它的工作方式不是一次性生成答案,而是通过工具调用机制一个回合一个回合地推进。
智谱 GLM 大模型是“识别者”。这里主要用到它的多模态能力,输入图片和提示词,输出钱币特征识别结果,包括钱文、面值、形制、品相、可能的朝代范围等。
为什么不让一个模型把两件事都做完?原因有三点:
- 任务隔离更清晰。调度和识别混在一个提示词里,模型容易顾此失彼。拆分后,每一段提示词的目标都更单一。
- 更容易替换模型。如果后续觉得 GLM 识别效果不好,可以换成其他多模态模型,Agent 侧的代码不用大改。
- 方便人工介入。识别结果和最终报告分开存储,鉴定师可以只审核识别结果,再决定是否采纳。
1.3 为什么不是只调用一个视觉模型
直接调用单个视觉模型也不是不能做,但把它放到真实场景里看,会暴露很多问题。
| 对比项 | 只用单一视觉模型 | Agent + 大模型方案 |
|---|---|---|
| 识别过程 | 输入图片,直接输出结论 | 先观察,再查证,后报告 |
| 结论可解释性 | 低,模型只说结果 | 高,报告里能带识别依据 |
| 复杂图片处理 | 一次失败就要换提示词重试 | Agent 可以调整工具参数或重新观察 |
| 知识查证能力 | 依赖模型内部记忆 | 可接入本地知识库或参考表 |
| 系统扩展性 | 改场景要重写提示词 | 新增工具和规则即可 |
从表里能看出,Agent 方案不是为了制造复杂度,而是为了让整个鉴定链路具备“可暂停、可检查、可修正”的能力。这在涉及交易、收藏、拍卖等场景时尤其重要,因为错误结论可能导致后续决策偏差。
需要注意,Agent 方案并不是魔法。如果没有好的工具函数、清晰的提示词和可靠的识别模型,Agent 也会在错误的路线上走下去。后面几节会逐步把这套链路搭起来。
2. 环境准备和依赖对齐,这一步决定后面能不能跑通
2.1 准备 API 凭证和环境变量
这个项目至少需要两组凭证:一组是 Claude API 的,另一组是智谱开放平台的。不同平台的凭证获取方式不一样,但流程类似:注册账号、开通模型服务、创建 API Key、在控制台确认模型名称和计费方式。
不要在生产环境把密钥直接写进代码。建议统一放在环境变量里,本地开发时可以使用.env文件,但要把.env加入.gitignore。
export ANTHROPIC_API_KEY="你的_claude_api_key" export ZHIPU_API_KEY="你的_智谱_api_key" export CLAUDE_MODEL="claude-sonnet-4-20250514" export ZHIPU_MODEL="glm-5.1"注意:模型版本号请以你账号下实际可用的模型名为准。如果开通的模型不叫这个,后面的请求会直接报模型不存在。
如果使用智谱的 OpenAI 兼容接口,还需要确认接口地址。常见地址格式类似https://open.bigmodel.cn/api/paas/v4/,但不同阶段的入口可能有差异,最好在官方文档里确认后再填。
2.2 Python 依赖和项目结构
这个演示项目使用 Python,主要依赖是anthropic和openai两个客户端库。anthropic用于调用 Claude API,openai用于通过兼容模式调用智谱多模态接口。
pip install anthropic openai python-dotenv pillowpython-dotenv用于读取.env文件,Pillow用于图片的尺寸检查和压缩。
目录结构可以这样组织:
coin-identify-agent/ ├── .env ├── app.py ├── agent/ │ ├── orchestrator.py │ └── prompts.py ├── models/ │ ├── glm_client.py │ └── claude_client.py ├── tools/ │ └── coin_tools.py ├── samples/ │ └── demo_coin.jpg └── output/ └── report.json这个结构把“Agent 编排”“模型调用”“工具函数”分开,避免把所有逻辑塞进一个文件。
2.3 图片输入目录与规范
图片质量直接决定识别效果。在准备样本图片时,建议按以下规范整理:
| 检查项 | 推荐值 | 说明 |
|---|---|---|
| 图片格式 | JPG、PNG | 避免使用过大的 BMP 原图 |
| 单边尺寸 | 不小于 512 像素 | 过小会丢失钱文细节 |
| 文件大小 | 建议压缩到 2MB 以内 | 过大时 base64 编码后请求体可能超限 |
| 背景 | 纯色或浅色背景 | 降低背景干扰 |
| 拍摄角度 | 尽量正视 | 俯拍角度识别效果最好 |
| 命名 | 不包含中文和空格 | 避免文件路径解析异常 |
在代码里,还可以在读取图片时先做一次强制转换,确保输入给模型的图片尺寸不会太大。
from PIL import Image def prepare_image(path: str, max_side: int = 1024) -> str: img = Image.open(path) img.thumbnail((max_side, max_side)) tmp_path = path + ".tmp.jpg" img.convert("RGB").save(tmp_path, "JPEG", quality=85) return tmp_path这段代码会把图片最长边压缩到 1024 像素,并转成 JPEG,既保留了钱币主要细节,又控制了传输体积。如果原图是透明的 PNG,转成 RGB 还可以避免后续编码问题。
3. 系统流程设计:从一张钱币图片到结构化鉴定报告
3.1 一条清晰的主流程
AI 钱币鉴定的主流程可以概括为六步:
- Agent 接收用户输入,包括图片路径和鉴定意图。
- Agent 判断当前任务需要观察图片,于是调用视觉识别工具。
- 视觉识别工具把图片编码成 base64,连同提示词一起发送给智谱 GLM 多模态接口。
- GLM 返回识别结果,包含钱文、面值、形制、品相、可能年代等信息。
- Agent 根据识别结果,决定是否需要查证本地参考数据,或者直接生成最终报告。
- Agent 输出结构化 Markdown 或 JSON 报告,并保留原始识别结果备查。
这个流程不是写死的。Claude Agent 会根据识别结果做分支判断。比如,如果第一次识别时钱文缺失,Agent 可以要求工具重新裁剪图片局部再识别一次;如果识别出多个候选年代,Agent 可以并行查证两个候选信息,再在报告里说明不确定性。
3.2 Agent 的任务拆分
为了让 Agent 能够在不写死脚本的情况下自动决策,需要给它预留几个工具函数:
| 工具名 | 输入 | 输出 | 用途 |
|---|---|---|---|
analyze_coin_image | 图片路径、提示词 | 识别结果 JSON | 调用 GLM 多模态识别 |
search_coin_reference | 关键词、候选范围 | 参考条目 | 从本地知识库查证 |
generate_report | 识别结果、参考条目 | 报告文本 | 组装最终鉴定报告 |
save_report | 报告文本、输出路径 | 文件状态 | 持久化报告 |
Agent 每回合只会调用一个或一组工具,然后把工具返回结果作为新上下文继续推理。整个过程就是“观察—思考—行动—再观察”的循环。
3.3 模型参数与每次调用的含义
在调用大模型时,参数不是随便填的。不同参数影响的是模型行为,而不是单纯的“生成速度”。
| 参数 | 演示值 | 含义与作用 |
|---|---|---|
| temperature | 0.1 | 控制随机性,鉴定场景推荐较低值 |
| max_tokens | 1500 | 单次输出上限,防止报告截断 |
| top_p | 0.9 | 采样范围,与 temperature 配合使用 |
| image_url | data URL | 图片以 data URL 形式传给视觉模型 |
| model | 环境变量控制 | 关键时切换模型版本,不写死在代码里 |
鉴定类任务建议把 temperature 调低到 0.1 左右。如果调高到 0.7 以上,同样的图片可能每次输出都不一样,这对需要稳定结果的场景是不利的。
不要把
temperature=0当作“一定稳定”。部分模型在 API 层仍然会做采样,遇到复杂图片仍可能给出差异描述。为了稳定,更重要的是把提示词写得具体,并要求模型输出结构化字段。
4. 核心代码实现:最小可运行的鉴定链路
4.1 Claude Agent 工具注册与循环
这里先实现一个最简的 Agent 循环,重点是展示 Claude 如何通过工具调用完成任务。
import os import json from anthropic import Anthropic client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) TOOLS = [ { "name": "analyze_coin_image", "description": "分析钱币图片,返回钱文、形制、品相等结构化识别结果", "input_schema": { "type": "object", "properties": { "image_path": {"type": "string", "description": "钱币图片本地路径"}, "prompt": {"type": "string", "description": "本次识别的重点关注内容"} }, "required": ["image_path", "prompt"] } }, { "name": "save_report", "description": "把最终鉴定报告保存到指定路径", "input_schema": { "type": "object", "properties": { "content": {"type": "string", "description": "报告内容"}, "output_path": {"type": "string", "description": "保存路径"} }, "required": ["content", "output_path"] } } ] def run_agent(user_request: str, image_path: str, output_path: str) -> str: messages = [ { "role": "user", "content": [ { "type": "text", "text": f"用户请求:{user_request}\n图片路径:{image_path}\n输出路径:{output_path}" } ] } ] for _ in range(6): response = client.messages.create( model=os.environ["CLAUDE_MODEL"], max_tokens=2000, tools=TOOLS, messages=messages ) stop_reason = response.stop_reason if stop_reason == "tool_use": tool_results = [] for block in response.content: if block.type == "tool_use": tool_name = block.name tool_input = block.input if tool_name == "analyze_coin_image": result = analyze_coin_image( image_path=tool_input["image_path"], prompt=tool_input["prompt"] ) elif tool_name == "save_report": result = save_report( content=tool_input["content"], output_path=tool_input["output_path"] ) else: result = json.dumps({"error": "unknown tool"}) tool_results.append( { "type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result, ensure_ascii=False) } ) messages.append({"role": "user", "content": tool_results}) else: return "".join( block.text for block in response.content if block.type == "text" ) return "Agent reached max steps."这里的循环最多执行 6 轮,避免 Agent 陷入无限工具调用。stop_reason是判断本轮是否触发工具调用的关键。如果返回tool_use,就把工具执行结果拼到消息里继续追问;否则说明 Agent 认为可以输出最终文本了。
4.2 调用智谱 GLM 的多模态识别函数
analyze_coin_image是整个链路里最关键的函数。它负责把图片编码成 data URL,然后调用智谱多模态接口。
import base64 import os import json from openai import OpenAI glm_client = OpenAI( api_key=os.environ["ZHIPU_API_KEY"], base_url=os.environ.get("ZHIPU_BASE_URL", "https://open.bigmodel.cn/api/paas/v4/") ) def encode_image_to_data_url(image_path: str) -> str: with open(image_path, "rb") as f: encoded = base64.b64encode(f.read()).decode("utf-8") return f"data:image/jpeg;base64,{encoded}" def analyze_coin_image(image_path: str, prompt: str) -> dict: data_url = encode_image_to_data_url(image_path) response = glm_client.chat.completions.create( model=os.environ["ZHIPU_MODEL"], temperature=0.1, messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": data_url } }, { "type": "text", "text": prompt } ] } ] ) content = response.choices[0].message.content return parse_model_output(content)parse_model_output负责把模型的文本输出转换成结构化 JSON。这里有两种策略:如果模型已经按 JSON 格式返回,就解析 JSON;如果模型返回了 Markdown,就做一次清洗再解析。演示项目里先按 JSON 解析,并在提示词里明确要求。
import re def parse_model_output(content: str) -> dict: content = content.strip() content = re.sub(r"^```json\s*|\s*```$", "", content) try: return json.loads(content) except json.JSONDecodeError: return {"raw_output": content, "warning": "模型输出不是标准 JSON,需要人工复核"}这个容错很重要。实测中,视觉模型虽然提示了 JSON 格式,但偶尔仍会输出额外的解释文字,解析失败时宁可保留原文,也不要直接丢弃,方便排查。
4.3 鉴定结果组装与报告输出
save_report函数负责落盘。为了避免覆盖历史数据,推荐按时间戳生成文件。
import datetime def save_report(content: str, output_path: str) -> dict: ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") final_path = output_path.replace(".json", f"_{ts}.json") with open(final_path, "w", encoding="utf-8") as f: f.write(content) return {"status": "ok", "path": final_path, "size": len(content)}确保 output 目录存在,否则写入会报错。
import os def ensure_output_dir(path: str): directory = os.path.dirname(path) if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True)在app.py中把整个流程串起来:
from agent.orchestrator import run_agent if __name__ == "__main__": result = run_agent( user_request="请鉴定这枚钱币的朝代、钱文和品相,并输出判断依据", image_path="samples/demo_coin.jpg", output_path="output/report.json" ) print(result)5. 运行验证:预期输出和判定标准
5.1 运行命令
环境准备完成后,在项目根目录执行:
python app.py如果正常,控制台会打印一段鉴定报告文本,同时在output/目录下生成带时间戳的 JSON 文件。
5.2 输入图片样例和期望输出
假设输入图片是一枚清晰的正反面合图,钱文可辨认。GLM 多模态识别返回的 JSON 可能是这样:
{ "coin_name": "乾隆通宝", "dynasty": "清", "reign_period": "乾隆", "currency_system": "制钱", "obverse_script": "乾隆通宝", "reverse_script": "宝泉", "possible_mint": "宝泉局", "condition": { "wear_level": "中", "patina": "有明显包浆", "defects": ["边缘轻微磕碰"] }, "confidence": { "coin_name": 0.92, "mint": 0.71 }, "analysis_basis": "钱文清晰,形制符合清代乾隆时期制钱特征;背面满文疑似宝泉局" }Agent 拿到这个结果后,会把它整理成一份报告,大致结构如下:
## 鉴定结果 - 钱名:乾隆通宝 - 朝代:清 - 推测铸局:宝泉局 - 品相:中,边缘轻微磕碰 ## 判断依据 1. 正面钱文为“乾隆通宝”,字口相对清晰。 2. 背面满文与宝泉局特征相近。 3. 包浆覆盖均匀,符合传世品特征。 ## 不确定性 铸局识别置信度偏低,满文局部略模糊,建议人工复核背面高清图。5.3 验证步骤清单
运行完成后,不要只看“有没有输出”,还要按清单检查:
- 是否生成了
output/report_xxx.json文件。 - JSON 中是否包含
coin_name、dynasty、condition等关键字段。 - 是否有
confidence字段,用于表达不确定度。 - 模型是否在输出里说明判断依据,而不是只给结论。
- 多次运行同一张图,结论是否基本一致。
- 改变图片后,报告里是否出现了对应变化的描述,而不是重复上一张图的结论。
如果第六项失败,基本可以确定是提示词或参数设计有问题,让模型没有真正“看见”新图片。
6. 常见问题排查:从报错倒推原因
6.1 高频报错与处理方案
以下表格按“现象、原因、检查方式、解决建议”维护:
| 问题现象 | 常见原因 | 检查方式 | 解决建议 |
|---|---|---|---|
| API 返回 401 | API Key 无效或已过期 | 检查环境变量是否加载成功,控制台重新生成密钥 | 确认.env已引入,或者直接 export 后重启进程 |
| 模型不存在 | 模型名写错,或账号未开通该模型 | 在智谱控制台查看可开通模型名称 | 把环境变量改成实际模型名,不要沿用演示值 |
| 图片过大导致请求超时 | base64 编码后体积超过接口限制 | 查看请求时间,检查图片原始大小 | 调用prepare_image压缩,再编码 |
| 多模态接口报“image_url 格式不支持” | 传入的是本地路径,而不是 data URL | 打印 data_url 前 50 个字符 | 先转 data URL,再放入消息内容 |
| 模型输出不是 JSON | 提示词约束不够强,或模型生成被截断 | 查看原始输出内容 | 增加 JSON 格式示例,调大 max_tokens |
| Agent 陷入循环 | 工具执行结果没有正确回传 | 打印stop_reason和消息轮次 | 检查tool_use_id是否正确匹配 |
| 输出文件找不到 | output 目录不存在,或路径写错 | 检查os.path.dirname(output_path)是否为空 | 启动时调用ensure_output_dir |
6.2 典型排查路径
如果发现整条链路跑不同,建议按顺序排查,而不是先改提示词。
第一步,确认输入图片本身可读。直接用工具打开图片,确认它不是空文件,也不是损坏文件。
第二步,确认环境变量全部加载。在代码入口打印一段脱敏后的配置摘要,比如是否有 api_key,但不要打印完整密钥。
第三步,单独测试 GLM 识别函数。写一个临时脚本,直接调用analyze_coin_image,不经过 Agent,看能否返回识别 JSON。
from tools.coin_tools import analyze_coin_image result = analyze_coin_image( image_path="samples/demo_coin.jpg", prompt="输出钱文、朝代、品相,用 JSON 格式" ) print(result)这一步可以把问题快速分流:如果单独调用失败,说明问题在模型接口、图片或提示词;如果单独调用成功,说明问题在 Agent 编排层。
第四步,检查 Agent 消息轮次。把每一次 API 调用的stop_reason和执行工具名打印出来,看有没有一直停留在同一个工具上。
print(f"step={step}, stop_reason={stop_reason}, tool={tool_name}")第五步,检查报告文件。重点看confidence和analysis_basis,如果置信度字段缺失,说明提示词没有强调“必须输出不确定度”,这时候补一句“如果没有把握,在 confidence 中降低分数并说明原因”即可。
排错时不要同时修改多个变量。一次只改一个参数或一段提示词,跑通后再改下一个,否则你很难知道是哪一步修复了问题。
7. 最佳实践:从 demo 走向可用的钱币鉴定工具
7.1 数据、提示词与评估集
演示项目能跑通,和“能投入使用”之间还差一套评估机制。
第一,建立小规模评估集。准备 50 到 100 张已经由人工鉴定过的钱币图片,记录每张图的正确朝代、钱文、铸局和品相,作为 ground truth。每次改动提示词或模型参数后,用同一套评估集跑一遍,统计准确率变化。
第二,提示词要固定模板,不要临时发挥。Agent 传给 GLM 的识别提示词应当包含:识别目标、必须输出的字段、不确定时的处理方式、输出格式示例。示例提示词如下:
你是一名古钱币识别助手。请观察用户提供的钱币图片,依次输出以下字段: 1. coin_name:钱文名称,如“乾隆通宝”; 2. dynasty:可能的朝代; 3. mint:推测铸局,如果不确定,输出 null; 4. condition:品相描述; 5. confidence:你对每个字段的置信度,数值范围 0 到 1; 6. analysis_basis:给出判断依据,不超过 3 条。 如果图片不清晰或无法判断,请直接在对应字段输出 null,不要编造。第三,保留每一张图的识别中间结果。不只保留最终报告,还要保留 GLM 返回的原始 JSON、Agent 调用过哪些工具、每轮耗时。这样做的好处是,当报告出问题时,可以回溯是哪一层出的错。
7.2 生产环境额外考虑
从演示项目进入生产环境,至少还要补齐下面几块:
- API Key 管理。用密钥管理服务或环境变量注入,不要写进镜像和代码仓库。
- 日志与监控。每次鉴定请求都应该有日志,记录图片 ID、模型版本、输入参数、识别结果、耗时和错误信息。
- 限流与重试。两套模型 API 都有速率限制,需要增加指数退避重试策略,避免短时间大量请求触发限流。
- 人工复核机制。对
confidence低于阈值的鉴定结果,强制进入人工复核队列。 - 数据合规。使用的图片应来自合法渠道,如有版权要求,要在采集和使用前确认授权。
- 模型版本固定。不要在日常运行中“顺手”切换模型版本,每一次模型变更都要重新跑一遍评估集。
| 考虑项 | 学习方法示例 | 生产要求示例 |
|---|---|---|
| API Key | .env 文件 | 密钥管理服务,权限最小化 |
| 日志 | print 输出 | 结构化日志,可检索 |
| 错误处理 | 直接抛异常 | 重试队列 + 告警 |
| 人工复核 | 无 | 置信度低时强制人工介入 |
| 版本管理 | 手写模型名 | 记录每次请求使用的模型版本 |
7.3 后续扩展方向
这个架构搭好以后,扩展空间比较大。
可以把search_coin_reference工具从本地 JSON 扩展成真正的知识库检索,接入钱币图谱或开放文献数据,让 Agent 在识别前先查证钱文与铸局对应关系。
可以增加局部识别能力。对一张正反面合图先做目标检测,把正面和背面分别切出来,再分别识别。这样比整图输入更容易获得准确的钱文结果。
可以把报告输出成 PDF 或结构化表单,方便交易平台和鉴定机构对接。也可以把 Claude Agent 替换成其他支持工具调用的模型,对比不同调度模型的稳定性。
7.4 风险与合规边界
最后说一条容易被忽视的边界:AI 鉴定的结果应始终作为“辅助判断”,而不应直接等同于“权威鉴定证书”。在收藏和交易场景里,错误鉴定可能引发纠纷。因此,报告里要明确标注“AI 辅助鉴定结果,仅供参考,不构成最终鉴定意见”。
同时,不要使用来源不明的钱币图片作为训练数据,也不要批量抓取商业平台的鉴定图片来做模型优化。数据合规和隐私保护在文博、电商场景里不是可有可无的选项,而是一项必须提前设计的工程要求。
对这个演示项目来说,最值得继续做的不是“让模型更聪明”,而是“让流程更可信”。把每张图的判断依据、置信度和人工复核记录都留下来,这套工具才能真正在钱币鉴定场景里发挥价值。