ChatGLM3-6B功能扩展:轻松实现外部API调用

1. 为什么需要让本地大模型“走出去”?

你有没有遇到过这样的场景:
在本地跑着一个响应飞快、隐私安全的 ChatGLM3-6B,它能秒答技术问题、润色文案、解释代码——但一问“今天北京几点了?”“查下我邮箱里上周的会议纪要”“把这份PDF转成Excel”,它就只能礼貌地沉默。

不是模型不够强,而是它被“关在屋里”了。
默认状态下,本地部署的大语言模型就像一位知识渊博却从不联网的学者:记得所有公式,却不知道今天的股价;熟读万卷书,却看不到你手机里刚拍的照片。

而 Function Call(函数调用)机制,就是给这位学者配了一部可编程的电话——它不再只靠记忆回答问题,而是能主动拨号、调用天气服务、查询数据库、发送邮件、甚至控制智能设备。

本篇不讲云端 API 的复杂配置,也不堆砌抽象概念。我们将聚焦于已在 CSDN 星图镜像广场上线的 ChatGLM3-6B 镜像,手把手带你:
在已部署好的 Streamlit 界面中无缝接入自定义函数
不改一行模型代码,仅通过提示工程 + 轻量逻辑扩展能力
实现真正“本地可控、业务可用”的智能助手

全程无需重装环境、无需编译模型、无需接触 CUDA 驱动——你打开浏览器正在用的那个对话框,下一秒就能调用真实服务。


2. 先理解:ChatGLM3 的函数调用不是“插件”,而是“决策引擎”

2.1 它和传统 API 调用有本质区别

很多开发者第一反应是:“那我写个 requests.post 就行了”。但这样只是把模型当成了“输入框+输出框”,完全浪费了它的推理能力。

ChatGLM3-6B 内置的 Function Call 机制,核心在于三步闭环决策

  1. 理解意图:识别用户真实需求(不只是字面意思)
    → “帮我订明天下午三点去上海的高铁票” ≠ “搜索‘高铁票’关键词”
  2. 选择工具:从你预设的多个函数中,精准匹配最合适的那个
    → 可能是 book_train_ticket(),而不是 search_flights()get_weather()
  3. 提取参数:自动结构化提取关键信息(时间、地点、人数),并容错处理模糊表达
    → “明天下午三点” → "date": "2024-06-15", "time": "15:00"

这个过程由模型自身完成,你只需定义好“它能用什么工具”和“每个工具怎么用”,剩下的交给它判断。

2.2 为什么这个能力在本地部署时特别珍贵?

场景 云端 API 方案 本地 ChatGLM3-6B + Function Call
查询公司内网文档 需打通内网代理、配置 OAuth、处理 token 过期 直接调用本地 Python 函数读取 \\server\docs\2024Q2.pdf
控制实验室设备 依赖公网暴露设备接口,存在安全审计风险 通过串口/USB 调用 pyserial 发送指令,全程不触网
处理敏感客户数据 数据需上传至第三方服务器,违反 GDPR/等保要求 所有原始数据留在本地,仅函数返回脱敏结果

本地不是妥协,而是升级——它把“AI 能力”从“远程服务”变成了“可信组件”。


3. 动手实践:在现有镜像中零侵入式添加天气查询功能

重要前提:你已通过 CSDN 星图镜像广场一键启动了 ChatGLM3-6B 镜像,并可通过浏览器访问 Streamlit 页面(如 http://localhost:8501)。本文所有操作均在此基础上进行,无需重启容器、无需修改模型权重、无需重装依赖

3.1 第一步:准备你的“工具箱”——编写可调用函数

进入镜像容器终端(或宿主机挂载的代码目录),新建文件 tools.py

# tools.py
import requests
import json

def get_weather(region: str) -> str:
    """
    获取指定城市实时天气(使用免费公开 API)
    支持中英文城市名,自动做基础清洗
    """
    # 简单中英映射(实际项目可对接 NLP 实体识别)
    region_map = {
        "北京": "beijing",
        "上海": "shanghai",
        "广州": "guangzhou",
        "深圳": "shenzhen",
        "杭州": "hangzhou"
    }
    city_code = region_map.get(region.strip(), region.strip().lower())

    try:
        # 使用 no-key 免费天气 API(测试用,生产请替换为自有服务)
        url = f"http://api.openweathermap.org/data/2.5/weather?q={city_code}&appid=00000000000000000000000000000000&units=metric"
        response = requests.get(url, timeout=5)
        data = response.json()
        
        if response.status_code == 200:
            temp = data['main']['temp']
            desc = data['weather'][0]['description']
            wind = data['wind']['speed']
            return f"{region}当前 {temp:.1f}℃,{desc},风速 {wind} m/s"
        else:
            return f"未查到{region}的天气信息,请检查城市名是否正确"
    except Exception as e:
        return f"获取天气时出错:{str(e)[:50]}"

# 可在此处追加其他函数,例如:
# def search_internal_knowledge(query: str) -> str: ...
# def send_notification(title: str, content: str) -> bool: ...

关键点说明:

  • 函数必须有明确的类型注解region: str),ChatGLM3 会据此生成更准确的参数结构
  • 返回值必须是 str(模型最终需将结果整合进自然语言回复)
  • 错误处理要友好,避免因网络异常导致整个对话中断

3.2 第二步:告诉模型“你有哪些工具可用”

在镜像的 Streamlit 应用主文件(通常是 app.pystreamlit_app.py)中,找到模型加载或对话逻辑部分,在其上方添加工具定义:

# 在 app.py 中合适位置(如 import 区块后、st.title() 前)
from tools import get_weather

# 定义工具描述列表 —— 模型将据此决定何时调用哪个函数
TOOLS = [
    {
        "name": "get_weather",
        "description": "查询指定城市的实时天气情况,包括温度、天气描述和风速",
        "parameters": {
            "type": "object",
            "properties": {
                "region": {
                    "type": "string",
                    "description": "要查询天气的城市名称,支持中文或英文"
                }
            },
            "required": ["region"]
        }
    }
]

注意:required 字段必须严格匹配函数参数名,否则模型可能返回空参数。

3.3 第三步:改造对话流程——插入函数调用判断逻辑

找到 Streamlit 中执行 model.chat() 的核心函数(通常名为 generate_response 或类似)。将其替换为支持 Function Call 的版本:

# 替换原 chat 调用逻辑(保留原有 history、system 等变量)
def call_with_function(model, tokenizer, query, history, system_prompt=None):
    # 构建 messages(保持原有格式)
    messages = []
    if system_prompt:
        messages.append({"role": "system", "content": system_prompt, "tools": TOOLS})
    
    for user_msg, assistant_msg in history:
        messages.append({"role": "user", "content": user_msg})
        if assistant_msg:
            messages.append({"role": "assistant", "content": assistant_msg})
    
    messages.append({"role": "user", "content": query})

    # 关键:启用 tool calling 模式
    response, _ = model.chat(
        tokenizer,
        messages,
        max_length=2048,
        top_p=0.8,
        temperature=0.3,
        repetition_penalty=1.1,
        do_sample=True
    )

    # 判断模型是否返回了 function call 请求
    if isinstance(response, dict) and "name" in response:
        func_name = response["name"]
        params = response.get("parameters", {})
        
        # 安全调用:只允许预定义函数
        if func_name == "get_weather" and "region" in params:
            result = get_weather(params["region"])
            # 将结果喂回模型,让它生成自然语言回复
            messages.append({"role": "function", "name": func_name, "content": result})
            final_response, _ = model.chat(
                tokenizer, messages, 
                max_length=1024, temperature=0.1
            )
            return final_response
        else:
            return f"暂不支持该功能:{func_name}"
    else:
        # 模型直接回答,无需调用函数
        return response

# 在 Streamlit 的 st.chat_input() 回调中调用此函数

效果验证:
在网页对话框中输入:

“上海现在几度?穿薄外套可以吗?”

模型将:
1⃣ 识别出需调用 get_weather,提取参数 "region": "上海"
2⃣ 自动执行 get_weather("上海") → 返回 "上海当前 26.3℃,scattered clouds,风速 2.1 m/s"
3⃣ 结合上下文生成人性化回复:

“上海现在 26.3℃,多云,微风,穿薄外套正合适~不过傍晚可能有小雨,建议带把伞哦。”


4. 进阶技巧:让函数调用更稳定、更实用

4.1 参数鲁棒性增强——应对“说人话”的用户

用户不会按 JSON 格式提问。常见问题:

  • “北京和上海哪个热?” → 需拆解为两次调用
  • “查下我昨天看的那个杭州景点天气” → 需结合历史对话提取“杭州景点”

解决方案:在 call_with_function 中加入预处理逻辑:

def extract_regions_from_text(text: str) -> list:
    """简单规则提取城市名(生产环境建议用 spaCy 或 LTP)"""
    cities = ["北京", "上海", "广州", "深圳", "杭州", "成都", "武汉"]
    found = [c for c in cities if c in text or text.lower().replace(" ", "").find(c.lower()) != -1]
    return found or ["北京"]  # 默认 fallback

# 调用前
regions = extract_regions_from_text(query)
if len(regions) == 1:
    params = {"region": regions[0]}
elif len(regions) >= 2:
    # 并行调用或分步调用逻辑
    pass

4.2 多函数协同——构建真实工作流

比如“会议提醒”场景:

  1. 用户说:“提醒我明天上午十点开项目复盘会”
  2. 模型调用 parse_datetime("明天上午十点")"2024-06-15T10:00:00"
  3. 再调用 add_calendar_event("项目复盘会", "2024-06-15T10:00:00")
  4. 最终回复:“ 已为您创建日程:项目复盘会(2024-06-15 10:00)”

只需在 TOOLS 列表中增加对应函数描述,模型会自动规划调用顺序。

4.3 安全边界控制——防止越权操作

在函数内部加入白名单校验:

def send_email(to: str, subject: str, body: str) -> str:
    # 仅允许发给公司域名邮箱
    if not to.endswith("@yourcompany.com"):
        return "权限拒绝:仅支持向公司邮箱发送"
    # ... 实际发送逻辑

5. 总结:你刚刚完成了一次“AI 能力组装”

我们没有训练新模型,没有部署新服务,甚至没有离开浏览器——但你已经:
🔹 赋予本地大模型“行动力”:它不再只是回答问题,而是能触发真实世界动作
🔹 守住数据主权:所有敏感操作(查内网、控设备、读文件)都在本地闭环完成
🔹 获得业务级扩展性:新增一个函数 = 新增一个 AI 能力,开发成本趋近于零

这正是 ChatGLM3-6B 作为“本地智能基座”的核心价值:
它不追求参数规模上的虚高,而是以极致的稳定性、可解释的可控性、零门槛的可扩展性,成为你数字工作流中真正可靠的“AI 同事”。

下一步,你可以:
→ 把 get_weather 换成 query_sales_db("Q2华东区销售额")
→ 接入企业微信机器人,让 AI 主动推送日报
→ 给设计稿截图,调用 describe_image() + generate_prompt() 自动生成 MidJourney 提示词

能力的边界,只取决于你定义的函数。

6. 常见问题快速排查

Q:模型返回了函数名,但参数是空的?

A:检查 parameters 字段中的 required 是否与函数签名完全一致;确保 description 里明确写出参数用途(如 "region": "城市中文名,例如'北京'"

Q:调用函数后页面卡住?

A:函数内阻塞操作(如 requests.get 无 timeout)会导致 Streamlit 主线程挂起。务必添加 timeout=5 和 try-except 包裹。

Q:如何查看模型到底生成了什么结构?

A:在 call_with_function 中打印 response 变量:

print("=== Model Raw Output ===")
print(response)
print("========================")

Q:能否支持异步函数(如 asyncio)?

A:当前 ChatGLM3-6B 官方实现基于同步推理。如需异步,建议将耗时操作封装为子进程或 FastAPI 微服务,本地函数通过 subprocess.run()httpx.AsyncClient 调用。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐