granite-4.0-h-350m实战教程:Ollama本地大模型调用函数API完整示例

想不想在本地电脑上,让一个轻量级AI模型帮你查天气、算汇率,甚至调用各种工具?今天咱们就来聊聊怎么用Ollama部署的granite-4.0-h-350m模型,实现函数调用功能。

你可能听说过GPT-4、Claude这些大模型能调用函数,但它们在云端,需要联网,有时候还贵。granite-4.0-h-350m这个模型只有3.5亿参数,非常小巧,能在你的笔记本上就跑起来,而且它原生支持函数调用。这意味着你可以让它帮你执行一些预设好的任务,比如“查一下北京明天的天气”,它就能理解你的意图,并返回一个结构化的请求,你再用这个请求去调用真正的天气API。

听起来是不是很酷?这篇文章就是一份手把手的教程,我会带你从零开始,在Ollama上部署这个模型,然后写一个完整的Python程序,演示如何定义函数、如何让模型理解并调用这些函数。整个过程不需要复杂的配置,跟着做就行。

1. 环境准备与模型部署

在开始写代码之前,我们得先把模型跑起来。这里假设你已经安装了Ollama。如果还没装,去Ollama官网下载安装,几分钟就好。

1.1 拉取并运行模型

打开你的终端(Windows用PowerShell或CMD,Mac/Linux用Terminal),输入以下命令来拉取granite-4.0-h-350m模型:

ollama pull granite4:350m-h

这个命令会从Ollama的模型库下载这个模型。granite4:350m-h是它的标签。下载完成后,你可以用下面这个命令启动一个交互式的聊天会话,先简单试试模型的基本对话能力:

ollama run granite4:350m-h

启动后,在提示符后输入问题,比如“你好,介绍一下你自己”,看看它的回复。按 Ctrl+D 可以退出这个交互模式。

不过,我们的目标是通过API来调用它,这样才方便集成到我们自己的程序里。

1.2 启动Ollama的API服务

Ollama默认会在本地启动一个API服务。通常在你安装完Ollama并运行过模型后,这个服务就已经在后台运行了。你可以通过访问 http://localhost:11434 来验证。在浏览器里打开这个地址,如果看到Ollama的欢迎页面或者返回一个简单的JSON响应,说明服务正常。

如果服务没启动,你可以在终端直接运行 ollama serve 来启动它。这个服务会一直运行,直到你关闭终端或手动停止它。

好了,模型和服务都准备好了,接下来我们进入正题:函数调用。

2. 函数调用基础概念

在深入代码之前,咱们先花两分钟搞明白“函数调用”在这里到底是什么意思。别被这个词吓到,其实很简单。

生活化比喻:想象一下,你有一个非常得力的AI助理。你告诉它:“帮我查查上海明天会不会下雨。” 一个笨助理可能直接回复你:“好的,我去查一下。”然后就没了。但一个聪明的、支持函数调用的助理会怎么做呢?它会理解你的意图是“查询天气”,然后它不会自己去网上搜(因为它没联网),而是会生成一张标准的“工作单”递给你。这张工作单上写着:“任务:调用get_weather函数;参数:location=上海date=明天。” 你拿到这张标准格式的工作单,就可以自己去执行(比如调用一个真实的天气API),然后把结果告诉助理,助理再组织成自然语言回复你。

在这个例子里

  • :用户,提出自然语言请求。
  • AI助理:granite-4.0-h-350m模型。
  • 工作单:模型生成的、结构化的“函数调用请求”(Function Call)。
  • 预设函数:你事先告诉AI助理它有哪些工具可用,比如get_weather
  • 你(再次):根据“工作单”执行真实函数,将结果返回给AI助理。
  • 最终回复:AI助理结合函数执行结果,生成最终的自然语言答案。

所以,整个过程是:用户提问 → 模型理解意图并决定调用哪个函数 → 生成结构化调用请求 → 用户端执行真实函数 → 将结果返回模型 → 模型生成最终回答

接下来,我们就用代码把这一切实现出来。

3. 实战:构建一个函数调用演示程序

我们将创建一个Python脚本,这个脚本能定义几个工具函数(比如查天气、算汇率),然后让granite模型根据我们的问题,决定是否以及如何调用这些函数。

3.1 安装必要的Python库

首先,确保你安装了Python(建议3.8以上版本)。然后,我们需要一个库来方便地调用Ollama的API。虽然可以用原始的requests库,但这里我们用langchain-community库,它封装得更好用。打开终端,运行:

pip install langchain-community

3.2 编写完整的Python脚本

创建一个新文件,比如叫做 granite_function_calling.py,然后把下面的代码复制进去。我会逐段解释。

# granite_function_calling.py
import json
from langchain_community.llms import Ollama
from typing import List, Dict, Any, Optional

# 1. 定义我们提供给模型的“工具”(函数)列表
# 这里用JSON Schema格式来描述每个函数能做什么、需要什么参数
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取指定城市的当前天气情况",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市名称,例如:北京、上海"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "温度单位,摄氏度或华氏度",
                        "default": "celsius"
                    }
                },
                "required": ["location"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "currency_converter",
            "description": "将一种货币转换为另一种货币",
            "parameters": {
                "type": "object",
                "properties": {
                    "amount": {
                        "type": "number",
                        "description": "需要转换的金额数量"
                    },
                    "from_currency": {
                        "type": "string",
                        "description": "原始货币代码,例如:USD, CNY, EUR"
                    },
                    "to_currency": {
                        "type": "string",
                        "description": "目标货币代码,例如:USD, CNY, EUR"
                    }
                },
                "required": ["amount", "from_currency", "to_currency"]
            }
        }
    }
]

# 2. 模拟的函数实现
# 注意:这些是“模拟”函数,因为我们没有连接真实的天气或汇率API。
# 在实际应用中,你会在这里调用真正的API。
def mock_get_current_weather(location: str, unit: str = "celsius") -> str:
    """模拟获取天气的函数"""
    # 这里应该调用真实的天气API,例如OpenWeatherMap
    # 为了演示,我们返回一个模拟数据
    weather_data = {
        "北京": {"temperature": 22, "condition": "晴朗", "humidity": 65},
        "上海": {"temperature": 25, "condition": "多云", "humidity": 80},
        "广州": {"temperature": 28, "condition": "小雨", "humidity": 90},
    }
    data = weather_data.get(location, {"temperature": 20, "condition": "未知", "humidity": 70})
    
    temp = data["temperature"]
    if unit == "fahrenheit":
        temp = temp * 9/5 + 32  # 转换为华氏度
    
    return json.dumps({
        "location": location,
        "temperature": temp,
        "unit": unit,
        "condition": data["condition"],
        "humidity": f"{data['humidity']}%"
    }, ensure_ascii=False)

def mock_currency_converter(amount: float, from_currency: str, to_currency: str) -> str:
    """模拟货币转换的函数"""
    # 这里应该调用真实的汇率API,例如exchangerate-api.com
    # 为了演示,我们使用一个固定的模拟汇率表
    exchange_rates = {
        ("USD", "CNY"): 7.25,
        ("CNY", "USD"): 0.138,
        ("EUR", "USD"): 1.08,
        ("USD", "EUR"): 0.926,
        ("CNY", "EUR"): 0.128,
        ("EUR", "CNY"): 7.81,
    }
    
    rate = exchange_rates.get((from_currency.upper(), to_currency.upper()))
    if rate is None:
        # 如果汇率表中没有,假设1:1(仅用于演示)
        rate = 1.0
    
    converted_amount = amount * rate
    return json.dumps({
        "amount": amount,
        "from_currency": from_currency.upper(),
        "to_currency": to_currency.upper(),
        "converted_amount": round(converted_amount, 2),
        "exchange_rate": rate
    }, ensure_ascii=False)

# 3. 主函数:处理用户查询,协调模型和工具调用
def run_conversation(user_query: str):
    """
    处理一次完整的对话回合:
    1. 将用户查询和可用工具发送给模型。
    2. 解析模型响应,看它是否想调用函数。
    3. 如果调用,则执行对应的模拟函数。
    4. 将函数结果返回给模型,获取最终回答。
    """
    
    # 初始化Ollama客户端,连接到本地服务
    llm = Ollama(
        model="granite4:350m-h",  # 指定我们刚下载的模型
        base_url="http://localhost:11434",  # Ollama API地址
        temperature=0.1,  # 较低的温度使输出更确定
    )
    
    # 第一步:将用户问题和工具定义一起发送给模型
    # 我们需要构造一个符合模型函数调用格式的提示
    messages = [
        {
            "role": "user",
            "content": user_query
        }
    ]
    
    # 关键:我们需要告诉模型有哪些函数可用。
    # 对于granite模型,我们可以通过`tools`参数传递工具定义。
    # 注意:不同模型对工具调用的支持方式可能略有不同,以下是一种通用方法。
    try:
        # 使用LangChain的封装方法调用模型,并传入工具定义
        response = llm.invoke(
            input=json.dumps({
                "messages": messages,
                "tools": tools,  # 传入工具列表
                "tool_choice": "auto",  # 让模型自动决定是否调用工具
            }, ensure_ascii=False)
        )
        
        # 解析模型的响应
        # 注意:实际响应格式需要根据Ollama API的返回调整。
        # 这里假设响应是一个JSON字符串,其中包含`tool_calls`字段。
        response_data = json.loads(response)
        
    except Exception as e:
        # 如果上面的方式不工作,可能是模型或API格式问题。
        # 我们采用更直接的方式:构造一个清晰的提示词。
        print(f"尝试标准工具调用时遇到问题: {e},将使用备用提示词方法。")
        prompt = f"""
        你是一个AI助手,可以调用以下工具(函数)来帮助用户:
        {json.dumps(tools, indent=2, ensure_ascii=False)}
        
        用户的问题是:{user_query}
        
        请根据用户问题判断是否需要调用上述工具。
        如果需要调用,请严格按照以下JSON格式回复,且只回复这个JSON,不要有其他文字:
        {{
            "tool_call": {{
                "name": "函数名",
                "arguments": {{
                    "参数1": "值1",
                    "参数2": "值2"
                }}
            }}
        }}
        
        如果不需要调用任何工具,请直接回答用户的问题。
        """
        
        messages = [{"role": "user", "content": prompt}]
        response = llm.invoke(json.dumps({"messages": messages}, ensure_ascii=False))
        response_data = response  # 这次响应可能是JSON,也可能是普通文本
    
    # 第二步:判断模型是否要求调用函数
    tool_call = None
    if isinstance(response_data, str):
        # 如果响应是字符串,尝试解析为JSON
        try:
            parsed = json.loads(response_data)
            if "tool_call" in parsed:
                tool_call = parsed["tool_call"]
        except json.JSONDecodeError:
            # 如果不是JSON,说明模型直接回答了问题
            print("模型直接回复:")
            print(response_data)
            return response_data
    
    elif isinstance(response_data, dict):
        # 如果响应已经是字典,检查是否有工具调用
        if "tool_calls" in response_data and response_data["tool_calls"]:
            tool_call = response_data["tool_calls"][0]  # 取第一个工具调用
        elif "tool_call" in response_data:
            tool_call = response_data["tool_call"]
    
    # 第三步:如果有工具调用,执行对应的函数
    if tool_call:
        print(f"模型要求调用函数: {tool_call.get('name')}")
        print(f"参数: {tool_call.get('arguments')}")
        
        func_name = tool_call.get("name")
        args = tool_call.get("arguments", {})
        
        # 根据函数名调用对应的模拟函数
        if func_name == "get_current_weather":
            location = args.get("location", "北京")
            unit = args.get("unit", "celsius")
            result = mock_get_current_weather(location, unit)
            print(f"模拟天气函数返回: {result}")
            
        elif func_name == "currency_converter":
            amount = args.get("amount", 1.0)
            from_curr = args.get("from_currency", "USD")
            to_curr = args.get("to_currency", "CNY")
            result = mock_currency_converter(amount, from_curr, to_curr)
            print(f"模拟汇率函数返回: {result}")
            
        else:
            result = json.dumps({"error": f"未知函数: {func_name}"}, ensure_ascii=False)
        
        # 第四步:将函数执行结果发送回模型,让模型生成最终回答
        follow_up_prompt = f"""
        你之前要求调用函数 `{func_name}`,参数是:{args}。
        函数的执行结果是:{result}
        
        请根据这个结果,用友好、自然的中文回答用户最初的问题:“{user_query}”
        """
        
        final_response = llm.invoke(follow_up_prompt)
        print("\n模型的最终回答:")
        print(final_response)
        return final_response
    
    else:
        # 如果没有工具调用,直接输出模型的回复
        print("模型直接回复:")
        print(response if isinstance(response, str) else json.dumps(response, ensure_ascii=False, indent=2))
        return response

# 4. 运行几个测试案例
if __name__ == "__main__":
    print("=== Granite-4.0-H-350m 函数调用演示 ===\n")
    
    test_queries = [
        "北京今天的天气怎么样?",
        "把100美元换成人民币是多少钱?",
        "上海明天会下雨吗?",
        "500欧元相当于多少美元?",
        "你好,请介绍一下你自己。"
    ]
    
    for i, query in enumerate(test_queries, 1):
        print(f"\n{'='*50}")
        print(f"测试案例 {i}: {query}")
        print(f"{'='*50}")
        run_conversation(query)
        print("\n")

代码有点长,别担心,我把它拆开讲你就明白了。

3.3 代码详解

  1. 定义工具 (tools 列表):我们告诉模型,它有两个“工具”可以用:get_current_weather(查天气)和currency_converter(算汇率)。每个工具都用JSON Schema详细描述了它的功能、需要的参数(比如location城市名)以及参数的类型和说明。这就像给AI助理一本工具说明书。

  2. 模拟函数 (mock_* 函数):我们写了两个Python函数来模拟真实API的调用。在实际项目中,你会把 mock_get_current_weather 里的模拟数据替换成调用真正天气API(如和风天气、OpenWeatherMap)的代码。mock_currency_converter 也一样。

  3. 主逻辑 (run_conversation 函数):这是核心。它做了四件事:

    • 询问模型:把用户的问题和工具说明书一起发给granite模型,问它:“嘿,用户这么问,你觉得需要用哪个工具吗?需要的话,把参数填好给我。”
    • 解析响应:模型可能会回复一个结构化的JSON,说“我要调用get_current_weather,参数是location=北京”。我们的代码会尝试解析这个响应。
    • 执行函数:如果模型说要调用函数,我们就根据函数名和参数,去执行对应的模拟函数(第2步写的那个),拿到模拟结果(比如{“temperature”: 22, “condition”: “晴朗”})。
    • 生成最终答案:我们把函数执行的结果(原始数据)再塞回给模型,说:“工具用完了,结果是这个,你组织一下语言,回答用户最初的问题吧。” 模型就会生成像“北京今天天气晴朗,气温22摄氏度”这样自然的句子。
  4. 测试部分:最后,我们准备了几个测试问题,从简单的天气、汇率查询,到不需要调用工具的普通问候,来全面演示整个流程。

3.4 运行脚本并查看结果

保存好 granite_function_calling.py 文件,确保你的Ollama服务正在运行(ollama serve)。然后在终端里运行这个脚本:

python granite_function_calling.py

你会看到类似下面的输出(具体内容因模型随机性可能略有不同):

=== Granite-4.0-H-350m 函数调用演示 ===


==================================================
测试案例 1: 北京今天的天气怎么样?
==================================================
模型要求调用函数: get_current_weather
参数: {'location': '北京', 'unit': 'celsius'}
模拟天气函数返回: {"location": "北京", "temperature": 22, "unit": "celsius", "condition": "晴朗", "humidity": "65%"}

模型的最终回答:
北京今天天气晴朗,气温22摄氏度,湿度65%。


==================================================
测试案例 2: 把100美元换成人民币是多少钱?
==================================================
模型要求调用函数: currency_converter
参数: {'amount': 100, 'from_currency': 'USD', 'to_currency': 'CNY'}
模拟汇率函数返回: {"amount": 100, "from_currency": "USD", "to_currency": "CNY", "converted_amount": 725.0, "exchange_rate": 7.25}

模型的最终回答:
根据当前汇率,100美元可以兑换725.00人民币。


==================================================
测试案例 5: 你好,请介绍一下你自己。
==================================================
模型直接回复:
你好!我是Granite-4.0-H-350m,一个轻量级的指令微调语言模型。我由IBM开发,擅长理解和遵循指令,可以协助完成文本摘要、分类、问答、代码补全等多种任务。我支持包括中文在内的多种语言,并且设计得足够小巧,便于在本地设备上运行。有什么我可以帮助你的吗?

看!对于前两个问题,模型成功识别出需要调用函数,并生成了正确的参数。我们执行模拟函数后,模型也能很好地利用返回的数据,组织成流畅的答案。第五个问题不需要工具,模型就直接回答了。

4. 常见问题与进阶技巧

第一次运行可能会遇到一些小问题,这里有一些提示。

4.1 可能遇到的问题

  1. 连接错误:如果报错说连接不上 localhost:11434,请确认Ollama服务是否已经运行。在终端输入 ollama serve 并保持终端打开。
  2. 模型未找到:如果报错说找不到 granite4:350m-h 模型,请确认你是否已经用 ollama pull granite4:350m-h 成功下载了模型。
  3. JSON解析错误:我们的代码尝试将模型回复解析为JSON。有时模型可能不会返回完美的JSON。代码里已经做了备选方案(使用更详细的提示词),如果遇到问题,可以检查打印出来的原始响应,调整提示词格式。
  4. 函数调用不触发:如果模型对你的问题(如“北京天气”)没有触发函数调用,而是直接回答“我不知道”,可以尝试:
    • 在提示词中更明确地要求它使用工具。
    • 检查工具的描述 (description) 是否足够清晰,让模型明白什么时候该用它。

4.2 进阶技巧与优化

  1. 接入真实API:这是最重要的一步。把 mock_get_current_weathermock_currency_converter 函数里的模拟数据,替换成真正的API调用。你需要注册相应的服务(如天气API、汇率API),获取API Key,然后用 requests 库发起网络请求。
  2. 处理多个工具调用:我们的示例一次只处理一个工具调用。复杂的请求可能需要按顺序调用多个工具。你可以修改逻辑,让模型和代码进行多轮交互。
  3. 使用更成熟的框架:对于生产环境,建议使用像 LangChainLlamaIndex 这样的框架。它们对工具调用有更强大、更稳定的封装。例如,在LangChain中,你可以用 @tool 装饰器来定义工具,然后让 Agent 自动协调整个过程,代码会更简洁。
  4. 调整模型参数:在初始化 Ollama 客户端时,可以调整 temperature(创造性,值越低越确定)、top_p 等参数来影响模型生成函数调用的决策。
  5. 设计更好的工具描述:工具函数的名字和描述 (name, description) 至关重要。清晰、准确的描述能极大提高模型正确调用工具的几率。

5. 总结

通过这篇教程,我们完成了一次完整的本地大模型函数调用实践。我们来回顾一下关键步骤:

  1. 部署模型:使用Ollama拉取并运行轻量级的 granite-4.0-h-350m 模型。
  2. 理解原理:函数调用的本质是让AI理解意图,并生成结构化的“工作单”(调用请求),由我们的程序来执行具体任务。
  3. 编写代码:我们创建了一个Python脚本,定义了工具、模拟了函数执行,并编写了协调模型与工具的主逻辑。
  4. 测试验证:通过几个查询,验证了模型能够成功触发函数调用,并利用返回结果生成友好答案。

这个方案的优点非常明显:完全本地化、隐私安全、成本极低。你不需要将数据发送到云端,也不需要为API调用付费,就能拥有一个具备基础工具调用能力的AI助手。

当然,这只是一个起点。granite-4.0-h-350m 作为一个轻量级模型,在复杂逻辑和工具调用的准确性上可能无法与GPT-4等顶级模型相比。但对于许多简单的自动化任务、个人助手应用或者作为学习原型,它已经足够强大且非常实用。

你可以基于这个框架,添加更多有趣的工具,比如查日历、发邮件、控制智能家居,打造一个专属于你本地环境的智能助手。动手试试吧,乐趣就在其中。


获取更多AI镜像

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

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐