先说结论:这类“拍照识别万物 + 万物开口说话”的小智 AI 学习机,本质上就是把多模态大模型、提示词工程和语音合成三件事串成一条自动化链路。思路并不复杂,很多开发者卡在“不知道怎么设计提示词”和“不知道怎么把拍照、识别、说话串起来”这两步。本文会把完整链路拆开讲清楚,并给出平台版和代码版两套可落地方案。
像我之前自己做儿童 AI 学习助手原型时,最先想做的功能也是“拿摄像头对着动物、植物、玩具拍一张,然后 AI 用这个物体的口吻跟孩子说话”。当时以为难点在识别,后来才发现识别反而最好解决,真正花时间的是提示词设计和多轮对话的状态管理。这篇文章就把整套经验整理出来,包含提示词模板、后端代码、前端页面和排查清单,适合想自己做 AI 学习机、智能体应用或拍照识物小工具的开发者参考。
1. 背景与核心概念
1.1 一个需求,拆成三个功能点
标题里的需求看起来很长,但拆开之后其实是三个独立功能:
| 功能 | 技术本质 | 关键词 |
|---|---|---|
| 拍照识别万物 | 多模态视觉模型识别图片内容 | 拍照识别、视觉大模型 |
| AI 聊天学习 | 大语言模型多轮对话 | 智能体、AI 对话 |
| 万物开口说话 | 提示词工程 + 语音合成 | 提示词、TTS、角色扮演 |
这里有一个很容易踩的坑:很多人把“万物开口说话”理解成需要额外的语音克隆或者拟人音色库。实际上,只要你让 AI 先识别出“这是什么”,再把自己的身份设定成“这个东西”,然后用第一人称说话,就已经实现了“万物开口”。整个过程不需要新的模型,只需要改提示词。
1.2 拍照识别走的是多模态大模型
所谓“多模态”,简单说就是模型既能理解文字,也能理解图片、音频等非文字信息。你把一张照片传给这类模型,它会输出对图片内容的文字描述,或者直接回答你关于图片的问题。
常见做法是选用支持图片输入的视觉语言模型,比如通义千问 VL、智谱 GLM-4V、OpenAI GPT-4o、豆包视觉理解等。无论选哪个,基本流程都是一样的:
上传图片 → 视觉模型解析图片内容 → 生成结构化描述 → 交给对话模型处理1.3 “万物开口说话”本质是提示词切换
“模拟万物开口说话”并不是让模型真的模拟声音,而是通过系统提示词(System Prompt)告诉模型:
- 你现在是什么角色。
- 你用什么人称说话。
- 你说话的语气和风格。
- 你面对的用户是谁。
- 你的知识边界是什么。
同一样的识别结果,系统提示词不同,AI 说出来的话就完全不同。这就是提示词工程的威力。
例如,同样识别出“一只猫”:
- 普通模式:“这是一只猫,属于猫科动物……”
- 猫的口吻:“喵~我是小猫咪,我喜欢吃小鱼干,你看我的胡须可以测量缝隙……”
- 老师的口吻:“小朋友,这是猫。猫是一种常见的宠物,它的特点是……”
所以,自定义智能体的第一课,就是学会控制和设计系统提示词。
2. 技术选型与执行思路
2.1 平台型方案:低代码快速验证
如果你不想从零搭建后端,推荐先使用智能体开发平台做原型验证。常见的平台有 Dify、Coze 扣子、百度千帆等,它们一般已经内置了多模态模型接入、知识库、插件和工作流编排能力。
平台方案的好处是快,从零到可以演示基本只需要半天。适合:
- 验证产品需求。
- 新手学习智能体开发。
- 不想维护服务器的个人开发者。
平台方案的缺点也比较明显:
- 上线后按调用量计费,成本不可控。
- 提示词和流程被绑定在平台上,迁移困难。
- 多模态识别和语音合成的定制化能力有限。
所以,如果只是做学习项目,平台方案足够;如果要真正部署成学习机或智能硬件,建议走代码方案。
2.2 代码型方案:灵活可控
代码型方案的整体架构推荐这样做:
前端页面/App → 后端服务 → 多模态模型 → 文本结果 → TTS 语音合成 → 音频返回 ↘ 对话上下文存储 ↗以 Python 技术栈为例,后端可以用 FastAPI 搭建 HTTP 服务,前端用一个简单的 H5 页面调用摄像头拍照,再把图片传给后端。
依赖项大致包括:
- FastAPI:提供上传接口和音频接口。
- 多模态模型 SDK 或 HTTP 接口。
- 文本转语音 TTS 服务。
- 简单的内存缓存或 Redis,用于保存多轮对话状态。
2.3 整体流程设计
用户点击拍照 → 图片上传到后端 → 后端调用视觉模型识别 → 结合自定义提示词生成回复文本 → 调 TTS 生成音频 → 前端播放音频为了让“万物开口说话”的效果更好,可以设计一个两阶段提示词:
- 先用“识别提示词”让模型输出图片内容的标准化描述。
- 再用“角色提示词”让模型以识别出的物体身份说话。
这样可以避免角色设定干扰识别准确度,也方便二次开发。
3. 环境准备与项目结构
3.1 运行环境说明
本文以 Python 3.10 以上版本为例,操作系统使用 Windows 或 Ubuntu 均可,只要环境变量配置正确。具体版本不需要严格固定,根据你实际安装的版本调整即可。
需要提前准备好的工具:
- Python 3.10+
- Node.js(可选,前端简单页面可以直接用浏览器打开,不需要构建)
- 可用的视觉大模型 API Key
- 可用的 TTS 服务
3.2 项目目录结构
建议把代码按照下面的结构组织,后面扩展也比较方便:
ai_learning_machine/ ├── main.py # FastAPI 入口 ├── config.py # 配置文件 ├── requirements.txt # Python 依赖 ├── prompts.py # 提示词模板 ├── services/ │ ├── vision_service.py # 图片识别服务 │ └── tts_service.py # 语音合成服务 ├── static/ │ ├── index.html # 拍照页面 │ └── camera.js # 摄像头调用逻辑 └── uploads/ # 临时存放上传的图片接下来逐步创建这些文件。
4. 核心代码与提示词实现
这一节按照核心模块讲解,最后一个章节会给出完整的整合示例。
4.1 拍照上传接口
前端通过摄像头拍照后,会把图片以 Base64 字符串或二进制文件的形式传给后端。推荐用二进制文件上传,传输效率更高,后端也好处理。
# 文件路径:main.py(片段) from fastapi import FastAPI, File, UploadFile import os import uuid app = FastAPI() UPLOAD_DIR = "uploads" os.makedirs(UPLOAD_DIR, exist_ok=True) @app.post("/upload") async def upload_image(file: UploadFile = File(...)): # 生成唯一文件名,避免冲突 ext = file.filename.split(".")[-1] if "." in file.filename else "jpg" filename = f"{uuid.uuid4().hex}.{ext}" file_path = os.path.join(UPLOAD_DIR, filename) # 保存图片到本地 content = await file.read() with open(file_path, "wb") as f: f.write(content) return {"code": 0, "message": "success", "file_path": file_path}这里需要注意:UploadFile 是 FastAPI 对上传文件对象的封装,await file.read() 可以异步读取整个文件内容。对于学习项目,图片通常不会很大,直接读入内存没有问题;生产环境建议限制文件大小,避免内存被打满。
4.2 多模态识别逻辑
以 OpenAI 兼容接口为例,视觉模型调用方式如下:
# 文件路径:services/vision_service.py(示例思路) import base64 import os from openai import OpenAI client = OpenAI( api_key=os.getenv("VISION_API_KEY"), base_url=os.getenv("VISION_BASE_URL", "https://api.openai.com/v1") ) def encode_image_to_base64(file_path: str) -> str: with open(file_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") def recognize_image(file_path: str) -> str: base64_image = encode_image_to_base64(file_path) response = client.chat.completions.create( model=os.getenv("VISION_MODEL", "gpt-4o-mini"), messages=[ { "role": "user", "content": [ {"type": "text", "text": "请描述这张图片里的主体物体是什么?尽量简洁,只告诉我物体名称和主要特征。"}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}} ] } ] ) return response.choices[0].message.content注意:不同平台的模型名称和 Base URL 不同,实际使用时以你所选平台的官方文档为准。上面的代码是 OpenAI 兼容格式,很多国产模型的兼容接口也支持这种格式,可以直接替换 Base URL 和模型名。
识别提示词这里建议写得明确一点,让模型只输出识别结果,不要输出大段百科内容。这样后续“开口说话”时,模型不会因为混杂了识别阶段的解释而显得啰嗦。
4.3 万物开口说话的提示词设计
这是本文最核心的部分。
先看一个效果对比。同样识别出“一棵树”,直接问模型“这是什么”,回答通常很客观:
这是一棵树,属于植物界,是木本植物……但如果你在后面接上这样一条系统提示词:
你现在是一棵树,请用第一人称介绍自己,像一个可爱的朋友一样跟小朋友说话,语气活泼,语句简短。模型就会变成这样:
嗨,我是一棵大树!我的头发是绿色的叶子,夏天的时候可以给你挡太阳哦!核心提示词模板可以这样设计:
# 文件路径:prompts.py DEFAULT_CHARACTER_PROMPT = """ 你是一个{role_name}。 请用第一人称“我”来介绍你自己。 你正在和一位{user_name}说话,请用{language}回答。 你的语气{style}。 你的回答要简短自然,控制在 3 到 5 句话以内。 不要直接说“我是由AI生成的”之类的话。 """.strip() RECOGNITION_PROMPT = """ 请识别这张图片中最主要的物体或场景,输出结果格式为: 实体名称:{名称} 实体类别:{物体/动物/植物/场景/其他} 主要特征:{20字以内的描述} 不要输出其他内容。 """.strip()实际生成最终提示词时,可以这样拼接:
def build_character_prompt(entity_name: str, user_name: str = "小朋友") -> str: return DEFAULT_CHARACTER_PROMPT.format( role_name=entity_name, user_name=user_name, language="中文", style="活泼、亲切、像朋友一样" )需要说明的是,角色提示词并不一定非要单独调用一次模型。如果你用的是支持系统提示词的平台,可以直接把识别结果填进系统提示词,然后让模型一句话完成“识别 + 扮演”两个任务。两阶段方案更适合代码模式,因为可以单独评测识别准度和扮演效果。
4.4 语音合成
最后一步是把 AI 生成的文本转成语音,让设备“开口说话”。TTS 也有多种选择:
- 云端 TTS:阿里云、腾讯云、微软 Azure 等。
- 开源 TTS:Edge-TTS(免费,适合学习)、CosyVoice、ChatTTS 等。
- 一些多模态平台也自带语音生成接口。
以 Edge-TTS 为例,代码非常简单:
# 文件路径:services/tts_service.py import edge_tts import os VOICE = "zh-CN-XiaoxiaoNeural" async def text_to_speech(text: str, output_path: str) -> str: communicate = edge_tts.Communicate(text, VOICE) await communicate.save(output_path) return output_pathEdge-TTS 的优点是免费、不需要 Key,适合本地学习验证。但它的网络请求依赖微软服务,生产环境建议替换为商业 TTS 以保证 SLA。
5. 完整可运行示例
这一节把上面的模块整合起来,形成一个可以直接运行的最小闭环。
5.1 后端整合代码
# 文件路径:main.py(完整版) import os import uuid from fastapi import FastAPI, File, UploadFile from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from prompts import build_character_prompt, RECOGNITION_PROMPT from services.vision_service import recognize_image from services.tts_service import text_to_speech app = FastAPI() UPLOAD_DIR = "uploads" AUDIO_DIR = "audio" os.makedirs(UPLOAD_DIR, exist_ok=True) os.makedirs(AUDIO_DIR, exist_ok=True) app.mount("/static", StaticFiles(directory="static"), name="static") @app.post("/api/recognize") async def recognize_and_speak(file: UploadFile = File(...)): try: # 1. 保存图片 ext = file.filename.split(".")[-1] if "." in file.filename else "jpg" image_path = os.path.join(UPLOAD_DIR, f"{uuid.uuid4().hex}.{ext}") content = await file.read() with open(image_path, "wb") as f: f.write(content) # 2. 识别图片 entity = recognize_image(image_path) # 3. 生成角色提示词 character_prompt = build_character_prompt(entity_name=entity, user_name="小朋友") # 4. 为了演示,这里直接使用识别文本作为回答;实际项目中可以再次调用对话模型 answer_text = f"我{entity},今天很高兴见到你!" # 5. 合成语音 audio_path = os.path.join(AUDIO_DIR, f"{uuid.uuid4().hex}.mp3") await text_to_speech(answer_text, audio_path) return { "code": 0, "entity": entity, "answer_text": answer_text, "audio_url": f"/audio/{os.path.basename(audio_path)}" } except Exception as e: return {"code": 1, "message": str(e)} @app.get("/audio/{filename}") async def get_audio(filename: str): file_path = os.path.join(AUDIO_DIR, filename) if not os.path.exists(file_path): return {"code": 1, "message": "audio not found"} return FileResponse(file_path, media_type="audio/mpeg")注意,上面第 4 步中为了演示,直接拼接了识别结果。实际项目中,应该把识别结果作为上下文,再次调用对话模型生成“万物开口说话”的内容,这样才会真的像在聊天。
5.2 前端摄像头页面
前端页面很朴素,核心是调用getUserMedia获取摄像头画面,然后通过 Canvas 截图并上传。
<!-- 文件路径:static/index.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>小智拍照识别 - 万物开口说话</title> </head> <body> <h2>📷 拍照识别万物</h2> <video id="video" width="480" height="360" autoplay playsinline></video> <br><br> <button id="takePhoto">拍照识别</button> <button id="switchCamera">切换摄像头</button> <h3>识别结果</h3> <div id="result"></div> <audio id="audioPlayer" controls style="display:none;"></audio> <script> let currentStream = null; let usingFrontCamera = true; async function initCamera() { if (currentStream) { currentStream.getTracks().forEach(track => track.stop()); } const constraints = { video: { facingMode: usingFrontCamera ? "environment" : "user" } }; currentStream = await navigator.mediaDevices.getUserMedia(constraints); const video = document.getElementById("video"); video.srcObject = currentStream; } async function takePhoto() { const video = document.getElementById("video"); const canvas = document.createElement("canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const context = canvas.getContext("2d"); context.drawImage(video, 0, 0, canvas.width, canvas.height); const blob = await new Promise(resolve => canvas.toBlob(resolve, "image/jpeg", 0.9)); const formData = new FormData(); formData.append("file", blob, "photo.jpg"); const resultDiv = document.getElementById("result"); resultDiv.innerHTML = "识别中…"; const response = await fetch("/api/recognize", { method: "POST", body: formData }); const data = await response.json(); if (data.code === 0) { resultDiv.innerHTML = ` <p>识别实体:${data.entity}</p> <p>AI 回答:${data.answer_text}</p> `; const audio = document.getElementById("audioPlayer"); audio.src = data.audio_url; audio.style.display = "block"; audio.play(); } else { resultDiv.innerHTML = `识别失败:${data.message}`; } } document.getElementById("takePhoto").addEventListener("click", takePhoto); document.getElementById("switchCamera").addEventListener("click", async () => { usingFrontCamera = !usingFrontCamera; await initCamera(); }); initCamera().catch(err => { document.getElementById("result").innerHTML = `摄像头调用失败:${err.message}`; }); </script> </body> </html>这个页面里,facingMode: "environment"代表使用后置摄像头,适合拍摄物体;"user"代表前置摄像头,适合自拍或对话。切换摄像头时,需要先停掉当前所有轨道,否则会同时占用摄像头导致黑屏。
5.3 运行与验证
首先安装依赖:
pip install fastapi uvicorn python-multipart edge-tts openai然后启动服务:
uvicorn main:app --reload --host 0.0.0.0 --port 8000浏览器访问:
http://localhost:8000/static/index.html预期效果:
- 页面弹出摄像头权限请求,允许后可以看到实时画面。
- 点击“拍照识别”,页面把图片发送到后端。
- 后端返回识别实体、回答文本和音频地址。
- 浏览器自动播放音频,AI 用设定角色开口说话。
如果是在手机上访问,需要把localhost换成电脑的局域网 IP,并且电脑和手机处于同一 Wi-Fi。
6. 提示词优化与效果调试
6.1 识别准确度优化
多模态模型对清晰度非常敏感。如果经常识别错误,优先检查:
- 拍摄距离是否合适,物体是否过小。
- 光线是否充足,是否有严重反光。
- 是否多个物体同时入镜,主体不明确。
- 图片是否压缩过于严重。
可以在识别提示词里增加“如果图片中有多个物体,请优先识别中心位置的物体”这样的约束,效果会有提升。
6.2 开口说话的自然度优化
如果 AI 说话像背稿子,通常是提示词太“死”。可以加入一些社交化、口语化的要求:
你正在和一位 6 岁小朋友聊天。 请使用短句,多用语气词,比如“呀”“哦”“好不好呀”。 不要一次性输出超过 50 个字。 适当使用拟声词。另外,可以让模型在回复前先把识别结果隐藏起来,不要直接说“我是一棵树”,而是从打招呼开始,再慢慢引入自己是谁,这样更自然。
6.3 语气与情感控制
同一个角色可以配置多种语气模板,由上层逻辑决定切换到哪种。比如:
- 教学模式:语气耐心,解释原理。
- 故事模式:语气夸张,富有戏剧性。
- 闲聊模式:语气轻松,简短互动。
这种“性格切换”就是智能体常说的“人格设定”,本质上还是提示词管理。建议把语气模板单独放在一个配置文件里,而不是写死在业务代码中。
6.4 多轮对话设计
如果只是单次识别,不需要上下文。但智能体通常需要连续对话,比如先问“你是什么”,再问“你住在哪里”。这时候需要保存历史消息。
最简单的做法是在内存中维护一个 session_id 到消息列表的映射:
# 示例思路 session_messages = {} def append_message(session_id: str, role: str, content: str): if session_id not in session_messages: session_messages[session_id] = [] session_messages[session_id].append({"role": role, "content": content}) # 限制长度,避免上下文过长 session_messages[session_id] = session_messages[session_id][-10:]生产环境建议用 Redis 保存会话,并设置过期时间。对话轮数多了之后,要给上下文做截断或摘要,否则模型输入越来越长,成本和延迟都会上升。
7. 常见问题与排查思路
7.1 常见报错与解决方案
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 摄像头黑屏 | 未授权摄像头权限,或前置/后置切换时未停掉旧流 | 检查浏览器权限设置;切换前先 stop 所有 track |
| 上传图片后一直转圈 | 后端未启动,或手机访问了错误地址 | 检查 uvicorn 日志,确认端口和局域网 IP |
| 识别结果错误 | 图片主体不清晰、识别提示词约束不足 | 扩大识别主体占比,增加“识别中心物体”提示 |
| AI 回答像百科复制 | 角色提示词没有生效 | 确认系统提示词在请求中是否被正确传递 |
| TTS 语音没有播放 | 音频 URL 不可访问,或媒体类型错误 | 检查 /audio 接口返回值,确认 media_type 是否设置正确 |
| 内存占用过高 | 上传图片过大,或未做文件大小限制 | 后端限制文件大小,前端压缩后再上传 |
7.2 排查清单
如果功能没跑通,可以按下面顺序排查:
- 前端是否能调用摄像头?浏览器是否弹出权限提示?
- 接口 /api/recognize 是否收到请求?返回是否包含 code=0?
- 图片是否保存成功?uploads 目录下有没有文件?
- 视觉模型是否返回内容?查看后端日志。
- 音频文件是否生成成功?audio 目录下有没有 mp3?
- 浏览器能否直接访问 audio_url?在地址栏手动打开试试。
- 如果是局域网访问,检查系统防火墙是否放行了 8000 端口。
7.3 避免问题再次出现
- 所有外部 API 调用都要做异常捕获,避免一个接口失败拖垮整个链路。
- 在识别之前,用 OpenCV 或 Pillow 对图片做预处理,比如统一缩放、旋转校正。
- TTS 生成的音频要缓存,同一个文本不要重复合成,节省成本。
- 开发时把敏感配置放环境变量,不要把 API Key 写进代码。
8. 安全边界与工程建议
8.1 数据与隐私
拍照识物会涉及图像数据上传。学习项目可以忽略,但做生产应用时必须考虑:
- 传输过程使用 HTTPS,避免图片被中间人窃取。
- 图片如果只用于识别,建议识别后立即删除,不持久化保存。
- 如果有儿童用户,需要遵循相关合规要求,尽量做到最小化采集。
- 明确告知用户拍照数据的使用目的和保留期限。
8.2 接口安全
后端 /api/recognize 接口不能完全无鉴权地暴露公网。最低限度也应该做:
- 简单的 Token 校验。
- 上传频率限制,防止接口被刷。
- 文件类型白名单检查,不能只依赖文件后缀。
- 限制单次上传文件大小,比如 10MB。
8.3 成本控制
这类智能体应用的成本大头在模型调用和 TTS 合成。建议:
- 对识别结果做缓存,同一个物体短时间内不要重复识别。
- 使用低成本小模型做前置过滤,再用大模型做精细回答。
- TTS 结果按文本哈希缓存到磁盘,重复文本直接复用音频。
- 对话上下文不过长保存,避免每次请求的输入 token 持续膨胀。
8.4 生产环境建议
代码型方案真正部署时,建议把架构升级为:
Nginx → FastAPI 集群 → 消息队列 → 模型服务池也可以把视觉识别和语音合成拆成独立微服务,通过队列异步处理。学习阶段不必这么做,但要清楚模块之间的解耦边界,否则后面改需求时会比较痛苦。
另外,模型服务建议通过统一网关封装,方便切换供应商,也能在模型故障时快速降级。这一点在依赖大模型 API 的应用里尤其重要。
9. 总结与下一步方向
到这里,一条完整的“拍照识别万物 + 万物开口说话”链路已经讲完了。回顾一下,核心点有四个:
- 拍照识物的底层是多模态视觉模型,不要在传统图像分类上死磕。
- 万物开口说话的本质是提示词切换,识别和扮演最好分成两个阶段。
- 语音合成选择可以根据成本灵活调整,免费方案适合学习,商业方案适合上线。
- 多轮对话需要独立管理上下文,不能依赖单次识别接口顺手完成。
如果要把这个项目做成真正可用的 AI 学习机,下一步建议优先做三件事:
- 把“识别→扮演”改成完整的两次大模型调用,让角色说话内容更丰富。
- 加入多轮对话管理,让 AI 能记住用户名字和偏好。
- 引入知识库,让 AI 在扮演万物时还能穿插正确的科普知识,避免一本正经地胡说八道。
拍照识物只是智能体的一个入口,提示词工程才是决定体验上限的关键。在你真正动手改提示词、跑通第一个对话之前,看再多教程都只是“知道”;跑通一次之后,你自然就理解智能体开发的核心套路了。这篇文章完整版代码结构在前文已经给出,如果你在跑的过程中卡在某一步,大概率问题出在依赖版本或网络访问上,优先看控制台日志,一般都是信息量最大的线索。
动手试一下吧,等你的“万物开口说话”跑通的那一刻,成就感确实不一样。