掘金 人工智能 19小时前
Ai入门-搭建一个专属的ai学习助手
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了大型语言模型(LLM)在知识实时性、事实准确性和外部交互能力方面的局限性,并着重介绍了RAG(检索增强生成)和函数调用技术如何有效解决这些问题。RAG通过检索外部数据源更新模型知识,提供实时准确的答案,减少模型“幻觉”;函数调用则赋予LLM与外部世界交互的能力,使其能执行实际任务。文章还详细阐述了构建AI学习助手的代码设计思路,包括MessageManager、ChatContext、ToolsProviderMixIn和ChatModelMixIn等关键组件,并提供了具体的函数调用实现代码示例,为开发者提供了实践指南。

🎯 **LLM的局限性与解决方案**:大型语言模型(LLM)虽功能强大,但在知识实时性、事实准确性(幻觉问题)以及缺乏外部交互能力方面存在不足。RAG技术通过检索外部数据源(如数据库、文档)来动态更新模型知识,提供实时、准确的信息,并支持引用来源,增强可信度。函数调用则允许LLM根据用户需求生成结构化请求,触发外部工具或API执行实际任务,从而扩展模型能力,实现动态交互。

⚙️ **函数调用实现流程**:函数调用在大模型交互中涉及四个关键步骤:首先是函数定义,告知大模型可用的函数及其功能;其次是大模型根据用户需求决定调用哪个函数,并生成相应的调用参数;接着是执行该函数,获取执行结果;最后将函数执行结果再次发送给大模型,以获得最终响应。在代码实现中,`ChatContext`通过持有`ToolsProviderMixIn`,可以根据不同业务场景动态切换和管理可用的工具。

🔧 **AI学习助手代码设计**:文章提出构建AI学习助手的设计思路,核心组件包括:`MessageManager`用于管理与大模型交互的消息,并通知外部状态变更;`ChatContext`负责与大模型的交互调用,处理模型回调,并集成`ToolsProviderMixIn`和`ChatModelMixIn`以支持不同工具和模型的切换;`ToolsProviderMixIn`是工具提供的抽象基类,定义了获取工具、匹配工具和执行工具调用的接口;`ChatModelMixIn`则是大模型接口的抽象,允许切换不同的模型实现。

💡 **具体工具实现示例**:文章提供了一个具体的`ChatTools`类实现,继承自`ToolsProviderMixIn`,用于演示函数调用。该类实现了`get_current_time`函数,能够获取并返回当前时间。代码展示了如何定义工具(`get_tools`),如何判断模型消息是否需要调用工具(`message_match_tool`),以及如何响应模型调用并返回工具执行结果(`tools_call`和`dispatch_tool_call`)。

在上一篇文章 AI入门-搭建一个本地聊天机器人中我们了解了 提示词与ai的聊天对话过程。这一小节我们将了解 大模型的函数调用和rag开发。

前言

既然在学习ai,那么我们能不能让ai帮助我们学ai呢?为此 决定开发一个专属的ai学习助手。作为一个初学者,我们并不知道学习ai大模型应用开发要学习一些什么,以及怎么学。为此直接通过我们搭建的聊天机器人让他给我们一份学习计划

ai_helper_prompt = """你是一名高级ai开发教练,你严谨,认证细致,幽默。你的主要职责是 1. 帮助制定学习计划2. 检验每日的学习成果,并针对学习效果进行评估。3. 当学习效果较好时,给予鼓励,当学习效果 较差时 针对不足的内容提出针对性的学习方案,并在此检验直到完成4. 能够根据实际情况动态调整学习计划与目标。"""

大型语言模型(LLM)如GPT-4在文本生成、问答和代码编写等任务上表现出色,但仍存在一些关键限制:

    知识实时性不足:LLM的训练数据是静态的,无法实时获取最新信息(如新闻、股价或学术进展)。事实准确性有限:模型可能生成看似合理但实际错误的回答(“幻觉”问题)。缺乏外部交互能力:LLM无法直接调用API、查询数据库或执行具体操作(如订机票、查天气)。

RAG(检索增强生成)和函数调用如何解决这些问题?

🔍 RAG:动态扩展知识库

⚙️ 函数调用:连接现实世界的桥梁

函数调用

函数调用的时序如下

对话时大模型返回的数据结构如下

{  "id": "chatcmpl-ee9c3592-a825-971f-a6a2-7324a1459ab3",  "choices": [    {      "finish_reason": "tool_calls",      "index": 0,      "logprobs": null,      "message": {        "content": "",        "refusal": null,        "role": "assistant",        "annotations": null,        "audio": null,        "function_call": null,        "tool_calls": [          {            "id": "call_386cbbf1db3848e69c8460",            "function": {              "arguments": "{}",              "name": "get_current_time"            },            "type": "function",            "index": 0          }        ]      }    }  ],  "created": 1753097180,  "model": "qwen-plus",  "object": "chat.completion",  "service_tier": null,  "system_fingerprint": null,  "usage": {    "completion_tokens": 16,    "prompt_tokens": 230,    "total_tokens": 246,    "completion_tokens_details": null,    "prompt_tokens_details": {      "audio_tokens": null,      "cached_tokens": 0    }  }}

具体可以参考其中choices 是大模型回调给我们的消息。

ai教学助手 代码设计

虽然我们才接触ai大模型应用,但考虑到 后续持续的开发迭代 我们的ai教学助手,整体代码设计如下

关键解释:MessageManager: 维护管理 与整个大模型关联的消息,并将消息变更通知到外界ChatContext:与大模型的交互调用,响应 MessageManager 消息变更。处理模型的消息回调,ToolsProviderMixIn:工具提供的抽象类,ChatContext 通过持有不同的 工具实现类,可以根据不同的业务场景告知大模型 有不同的工具ChatModelMixIn:大模型抽象接口,ChatContext持有不同的实现可以切换不同的模型

函数调用的实现

大模型交互的过程中有结果 与函数调用相关的参数:

function会逐渐被废弃,因此我们直接使用tools就可以了。函数调用主要有下面3个步骤

    函数定义llm 决定调用函数执行函数将函数执行的结果在次发送给大模型获取模型最终响应结果

接下来看看具体的执行代码:前面的代码结构中ChatContext通过持有ToolsProviderMixIn,切换不同的实现类,从而使用不同的工具(这里不是说大模型一次只能回调一个函数,而是我们可能会根据不同的业务场景给大模型不同批次的工具)ToolsProviderMixIn代码设计

class ToolsProviderMixIn(ABC):    def __init__(self, ):        super().__init__()    @abstractmethod    def get_tools(self) -> List[ChatCompletionToolParam]:        pass        """        返回需要将哪些工具注册到大模型中        """    @abstractmethod    def message_match_tool(self, message: ChatCompletion|ChatCompletionChunk) -> bool:        pass        """        判断当前工具类能否被大模型执行        """    @abstractmethod    def tools_call(self, message: ChatCompletion) -> List[ChatCompletionToolMessageParam]:        pass        """        响应大模型函数调用并返回结果        """           

具体的工具类实现

from datetime import datetimefrom typing import Listfrom openai.types.chat import ChatCompletionToolParam, ChatCompletion, ChatCompletionToolMessageParam, \    ChatCompletionChunkfrom learn02.chat_tools.chat_tool_mixin import ToolsProviderMixInclass ChatTools(ToolsProviderMixIn):    def __init__(self, ):        super().__init__()        self.calling_tools = {}    def tools_call(self, message: ChatCompletion | ChatCompletionChunk) -> List[ChatCompletionToolMessageParam]:        if isinstance(message, ChatCompletion):  # 可能是多个异步函数 当前先这样            return self.dispatch_tool_call(message)        if isinstance(message, ChatCompletionChunk):            if self.calling_tools.get(message.id):                if message.choices[0].finish_reason == "tool_calls":                    self.calling_tools.pop(message.id)                    return []            else:                self.calling_tools[message.id] = message                return self.dispatch_tool_call(message)            return []        return []    def dispatch_tool_call(self, message: ChatCompletion | ChatCompletionChunk):        tool_response: List[ChatCompletionToolMessageParam] = []        if isinstance(message, ChatCompletion):            for tool_call in message.choices[0].message.tool_calls:                if tool_call.function.name == "get_current_time":                    current_time = self.get_current_time()                    tool_response.append(                        ChatCompletionToolMessageParam(role="tool", content=current_time, tool_call_id=tool_call.id))        else:            for tool_call in message.choices[0].delta.tool_calls:                if tool_call.function.name == "get_current_time":                    current_time = self.get_current_time()                    tool_response.append(                        ChatCompletionToolMessageParam(role="tool", content=current_time, tool_call_id=tool_call.id))        return tool_response    def get_current_time(self) -> str:        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")    def message_match_tool(self, message: ChatCompletion | ChatCompletionChunk) -> bool:        if isinstance(message, ChatCompletion):            if message.choices[0].message.tool_calls:# 有tool_calls 存在说明是函数调用                return True        elif isinstance(message, ChatCompletionChunk):            if message.choices[0].delta.tool_calls:                return True        return False    def get_tools(self) -> List[ChatCompletionToolParam]:        return [            {                "type": "function",                "function": {                    "name": "get_current_time",                    "description": "获取当前时间",                    "parameters": {                        "type": "object",                        "properties": {},                        "required": [],                    },                },            },        ]

**注意:**函数执行之后返回给大模型的消息类型是 ChatCompletionToolMessageParam,我们给大模型发送ChatCompletionToolMessageParam 类型消息的前面一定要有 模型回调出来需要调用函数的消息,否则会报错

体验一下效果:

运行 local_input_and_display_chat.py ,命令行输入今日计划 会在当前文件夹下输出 一份md文件。浏览器打开

到此函数调用的过程就结束了,后面会研究基于rag 来实现本地数据 检索,让ai 了解 我们的学习计划,学习结果,从而帮助我们 更好的验证学习情况。参考代码:github.com/xiaolutang/…

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

大模型 函数调用 RAG AI助手 LLM
相关文章