Bootstrap

Ollama: 使用Langchain的OllamaFunctions

1. 引言

Function call Langchain的Ollama 的实验性包装器OllamaFunctions,提供与 OpenAI Functions 相同的 API。因为网络的原因,OpenAI Functions不一定能访问,但如果能通过Ollama部署的本地模型实现相关的函数调用,还是有很好的实践意义。使用不同的模型可能能够得到不同的结果

2.Function Call

Function Call,或者叫函数调用、工具调用,是大语言模型中比较重要的一项能力,对于扩展大语言模型的能力,或者构建AI Agent,至关重要。
Function Call的简单原理如下:

  • 按照特定规范(这个一般是LLM在训练阶段构造数据的格式),定义函数,一般会包含函数名、函数描述,参数、参数描述、参数类型,必填参数,一般是json格式
  • 将函数定义绑定的大模型上,这一步主要是让LLM感知到工具的存在,好在query来到时选取合适的工具
  • 跟常规使用一样进行chat调用
  • 检查响应中是否用tool、function之类的部分,如果有,则说明触发了函数调用,并且进行了参数解析
  • 根据json中定义的函数名,找到Python中的函数代码,并使用大模型解析的参数进行调用,到这一步,一般已经拿到结果了
  • 通常还会再增加一步,就是拿Python中调用函数的结果,和之前的历史记录,继续调用一次大模型,这样能让最终返回结果更自然

下面这张图简单描述了大语言模型函数调用的流程:

User ChatApp GPTAPI "How's the weather in NYC?" Prompt: "How's the weather in NYC?" Call get_weather("NYC") Result: "Raining, 90% humidity, 57°F" Response: "It is raining in NYC, with a humidity of 90% and a temperature of 57°F" "It is raining in NYC, with a humidity of 90% and a temperature of 57°F" User ChatApp GPTAPI

3. Ollama模型及相关API_KEY的准备

3.1 安装Ollama

Ollama中下载大语言模型,在本试验中,将使用Ollama部署本地模型Qwen2:7b,通过ollama pull qwen2:7b即可下载,在本机运行这个模型推荐16G内存/显存,如果内存或显存不够,可以下载qwen2:7b版本,但Function Call效果可能会下降【测试表明,如果没有足够的显示,模型响应速度会很慢】。

3.2 申请相关的API_KEY

申请高德API,天气查询使用的是高德API。通过网站 https://console.amap.com/dev/key/app

申请Tavily API Key,这是一个为LLM优化的搜索API,免费用户每月有1000次调用额度,地址https://tavily.com/,点击右上角的Try it out. 通过网站:官网:Tavily

3.3 安装引用相关的包

安装langchain, langchain-core, langchain-experimental

from langchain_experimental.llms.ollama_functions import OllamaFunctions

model = OllamaFunctions(model='qwen:14b', base_url='http://192.2.22.55:11434', format='json')
# 定义相关API_Key
amap_api_key = '2b6437dfc4fa7f2af564eb2569a6116f'   # https://console.amap.com/dev/key/app
tavily_api_key = '此处填写Tavily API Key'             #官网:Tavily tavily.com

4. 绑定工具

4.1 定义工具

此处定义了2个工具,一个是网络搜索,一个是天气查询,其中天气查询要发送2次网络请求,首先根据城市名称拿到对应的行政区划码adcode,然后再使用adcode查询天气

# 自定义网络搜索工具
def search_web(query, k=5, max_retry=3):
    import os
    from langchain_community.retrievers import TavilySearchAPIRetriever

    os.environ["TAVILY_API_KEY"] = tavily_api_key
    retriever = TavilySearchAPIRetriever(k=5)
    documents = retriever.invoke(query)
    # return [{'title': doc.metadata['title'], 'abstract': doc.page_content, 'href': doc.metadata['source'], 'score': doc.metadata['score']} for doc in
    #         documents]
    content = '\n\n'.join([doc.page_content for doc in documents])
    prompt = f"""请将下面这段内容(<<<content>>><<</content>>>包裹的部分)进行总结:
    <<<content>>>
    {content}
    <<</content>>>
    """
    print('prompt:')
    print(prompt)

    return model.invoke(prompt).content

# 自定义天气查询工具
def get_current_weather(city):
    import requests
    from datetime import datetime

    url = f'https://restapi.amap.com/v3/config/district?keywords={city}&key={amap_api_key}'
    resp = requests.get(url)
    # print('行政区划:')
    # print(resp.json())

    adcode = resp.json()['districts'][0]['adcode']
    # adcode = '110000'

    url = f'https://restapi.amap.com/v3/weather/weatherInfo?city={adcode}&key={amap_api_key}&extensions=base'
    resp = requests.get(url)
    """样例数据
    {'province': '北京',
     'city': '北京市',
     'adcode': '110000',
     'weather': '晴',
     'temperature': '26',
     'winddirection': '西北',
     'windpower': '4',
     'humidity': '20',
     'reporttime': '2024-05-26 13:38:38',
     'temperature_float': '26.0',
     'humidity_float': '20.0'}
    """
    # print('天气:')
    # print(resp.json())
    weather_json = resp.json()['lives'][0]
    return f"{weather_json['city']}{datetime.strptime(weather_json['reporttime'], '%Y-%m-%d %H:%M:%S').strftime('%m月%d日')}{weather_json['weather']},气温{weather_json['temperature']}摄氏度,{weather_json['winddirection']}{weather_json['windpower']}级"

# 工具映射列表
fn_map = {
    'get_current_weather': get_current_weather,
    'search_web': search_web
}

4.2 绑定工具

使用下面的语句,将自定义的函数,绑定到大语言模型上

llm_with_tool = model.bind_tools(
    tools=[
        {
            "name": "get_current_weather",
            "description": "根据城市名获取天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名,例如北京"
                    }
                },
                "required": ["city"]
            }
        },
        {
            "name": "search_web",
            "description": "搜索互联网",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "要搜素的内容"
                    }
                },
                "required": ["query"]
            }
        },
    ],
    #function_call={"name": "get_current_weather"}
)

5. 使用工具

5.1 天气查询
# 基于大模型获取调用工具及相关参数
import json
ai_msg = llm_with_tool.invoke("北京今天的天气怎么样")
kwargs = json.loads(ai_msg.additional_kwargs['function_call']['arguments'])
print(kwargs)

# 调用自定义函数,获得返回结果
res = fn_map[ai_msg.additional_kwargs['function_call']['name']](**kwargs)
print(res)
5.2 网络搜索

输入比较新的内容后,大语言模型会自动判断使用网络搜索工具

# 基于大模型获取调用工具及相关参数
ai_msg = llm_with_tool.invoke("庆余年2好看吗")
kwargs = json.loads(ai_msg.additional_kwargs['function_call']['arguments'])
print(kwargs)

# 调用自定义函数,获得返回结果【API获取问题未试验】
#res = fn_map[ai_msg.additional_kwargs['function_call']['name']](**kwargs)#
#print(res)

6. 结构化数据分析

在此处使用函数调用做的一件有用的事情是以结构化格式从给定输入中提取属性:

# 结构化信息提取
from langchain_core.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field

# 结构化的返回信息,基于该模型生成JSON
class Person(BaseModel):
    name: str = Field(description="The person's name", required=True)
    height: float = Field(description="The person's height", required=True)
    hair_color: str = Field(description="The person's hair color")


# Prompt template
prompt = PromptTemplate.from_template(
    """Alex is 5 feet tall. 
    Claudia is 1 feet taller than Alex and jumps higher than him. 
    Claudia is a brunette and Alex is blonde.
    
    Human: {question}
    AI: """)

# Chain
llm = OllamaFunctions(model='qwen:14b', base_url='http://192.2.22.55:11434', format='json',temperature=0)
structured_llm = llm.with_structured_output(Person)
chain01 = prompt | structured_llm
alex = chain01.invoke("Describe Alex")
print(alex)
  • 注意对于英文理解,这里不推荐使用qwen14b,推荐使用llama3等模型

参考资料:

https://blog.csdn.net/engchina/article/details/138006370
https://zhuanlan.zhihu.com/p/701616275

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;