基于Eino框架理解大模型工具调用
函数调用 Function Calling Function Calling 是一种将大模型与外部工具和 API 相连的关键功能,大模型能够将用户的自然语言智能地转化为对特定工具或 API 的调用,从而高效满足各种场景需求,如动态信息查询、任务自动化等 工具调用的一般步骤: 应用程序将用户问题和tools列表一起发送给大模型,tools列表表明模型可以调用的工具 LLM 对用户意图进行分析,决定是否需要使用工具以及使用哪些工具 a. 无需工具则生成回答响应给应用程序 b. 需要调用工具输出工具名和参数信息响应给应用程序 应用程序解析模型响应 有工具调用,则调用工具并将调用结果和之前的消息记录一并发给模型,继续处理 无工具调用,继续处理程序逻辑或直接给用户 循环上面步骤,达到结束条件则会话完成 火山引擎文档中有一张图多轮工具调用的逻辑图,可以辅助理解 多轮工具调用 MCP 官网 https://modelcontextprotocol.io/ MCP(Model Context Protocol)即模型上下文协议,与 function calling(函数调用)都是实现大语言模型与外部系统交互的关键技术概念 MCP 主要负责规范化函数的具体执行过程,为 AI 模型和外部数据源或工具之间建立统一的通信接口。 二者的关系表现为 function calling 是 MCP 生态下的一种具体功能实现形式。function calling 为 MCP 提供了函数调用的指令来源,而 MCP 则为 function calling 生成的指令提供了标准化的执行框架,确保这些指令能够在不同的外部系统中可靠地执行。 MCP也可以简单理解为function的共享,因此MCP开源社区在最近几个月都非常活跃。 MCP遵循CS架构(Client-Server),几个核心概念: 主机(Host):通常是发起连接的 LLM 应用程序,如 Claude Desktop、IDE 插件等,负责管理客户端实例和安全策略 客户端(Client):位于主机内,是主机与服务器之间的桥梁,与服务器建立 1:1 会话,处理协议协商和消息路由等 服务器(Server):是独立运行的轻量级服务,通过标准化接口提供特定功能,如文件系统访问、数据库查询等 核心架构这块参考官方文档 https://modelcontextprotocol.io/docs/concepts/architecture 传输机制 MCP的client-server间传输层协议当前有两种,都使用JSON-RPC2.0作为消息交换格式: Stdio 进程间通信 适用于命令行等同服务器通信 Client将Server作为子进程启动,Server从其标准输入 (stdin) 读取 JSON-RPC 消息,并将消息发送到其标准输出 (stdout)。Server可以将 UTF-8 字符串写入其标准错误 (stderr) 以进行日志记录,Client可以捕获、转发或忽略此日志记录。Server不能向其 stdout 写入任何不是有效 MCP 消息的内容,Client不能向Server的 stdin 写入任何不是有效 MCP 消息的内容。 ...
实现一个MutatingAdmissionWebhook
官方文档 概述 在 Kubernetes 中,准入控制是保障 API 请求安全和合规性的重要机制。它在 API 请求流程中扮演着关键角色,拦截即将发送到 Kubernetes APIServer的请求,在持久化之前,但在身份验证和授权之后。其位置如下图所示: 图片来源https://sysdig.com/blog/kubernetes-admission-controllers/ 准入控制器适用于创建、删除或修改对象的请求,同时也可以阻止自定义动作。读操作会绕过准入控制层,不会受到准入控制器的影响。当有多个准入控制器存在时,它们会依次被调用,只要其中一个准入控制器拒绝请求,整个请求就会被立即驳回。 准入控制常常被用来自动化设置默认资源请求和限制、应用标签和注释以及强制命名约定等管理任务。 准入控制分两个阶段: 变更准入控制器,用于在请求处理过程中对对象进行修改。 验证准入控制器,负责验证请求是否符合特定规则。 需要注意的是,部分控制器兼具变更准入和验证准入的功能。 启用准入 1 kube-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger ... 关闭准入 1 kube-apiserver --disable-admission-plugins=PodNodeSelector,AlwaysDeny ... 准入的两种类型: 静态准入:由 Kubernetes 内置提供,无需额外配置。 动态准入:允许用户根据自身需求进行扩展。 动态准入控制器: MutatingAdmissionWebhook 此准入控制器调用任何与请求匹配的变更(Mutating) Webhook。匹配的 Webhook 将被顺序调用 ValidatingAdmissionWebhook 此准入控制器调用与请求匹配的所有验证性 Webhook。 匹配的 Webhook 将被并行调用。如果其中任何一个拒绝请求,则整个请求将失败。 该准入控制器仅在验证(Validating)阶段运行 ValidatingAdmissionPolicy 验证准入策略使用通用表达语言 (Common Expression Language,CEL) 来声明策略的验证规则,是一种声明式的、进程内的验证准入 Webhook 方案 MutatingAdmissionPolicy 提供了一种声明式的、进程内的方案, 可以用来替代变更性准入 Webhook 实现 步骤概览 实现一个 MutatingAdmissionWebhook 主要包括以下几个步骤: 生成证书,Kubernetes 和 Webhook 服务使用 TLS 加密通信 APIServer对Webhook的认证 Webhook对APIServer的认证(可选) 实现一个接口,接收AdmissionReview请求,解析并填充response字段,响应相同版本的AdmissionReview 部署上面接口,集群内外都可以 注册准入控制器 监控 Admission Webhook 以下面需求为例: ...
OAuth 2.0 与 OpenID Connect
在现代身份认证与授权体系中,OAuth 2.0 和 OpenID Connect(OIDC) 经常被一起提及,但它们解决的是不同层面的问题。理解两者的区别和协作关系,是构建安全、标准的身份系统的关键。 是什么? OAuth 2.0 不是身份验证协议,而是一个授权框架,允许第三方应用在用户授权下访问受保护资源,而无需获取用户密码。 OpenID Connect(OIDC)是一种基于OAuth 2.0协议构建的身份认证标准,旨在为现代应用程序提供简单、安全且可互操作的用户身份验证方式。它通过在OAuth 2.0的授权框架之上添加一个身份层,解决了OAuth 2.0本身不完善的身份认证问题。 核心角色: Resource Owner(资源所有者):通常是用户自己 Client(客户端):请求访问用户资源的应用(Web、移动、单页应用SPA 等) Authorization Server(授权服务器):颁发令牌(如 Casdoor、Keycloak、Google OAuth) Resource Server(资源服务器):托管用户数据(如 API 服务),接受并验证访问令牌,以确定请求是否合法 重要区分: OAuth 2.0 = 授权 OpenID Connect = 身份认证+授权 OAuth2.0授权流程 OAuth2 定义了好几种授权模式,有Authorization Code、Client Credentials、Device Code、Implicit Flow和Password Credentials,隐式流和密码凭据模式被认为是不安全的,不推荐使用。 授权码模式(Authorization Code Flow) 适用于:服务器端 Web 应用、移动 App、单页应用SPA(配合 PKCE 使用) sequenceDiagram participant User as 用户 participant Client as 客户端 participant AS as 授权服务器 participant RS as 资源服务器 User->>Client: 访问应用 Client-->>User: 重定向至授权服务器<br>(client_id, redirect_uri, scope, state, code_challenge) User->>AS: 登录并同意授权 AS-->>User: 重定向回客户端<br>(?code=xxx&state=yyy) User->>Client: 浏览器携带授权码 Client->>AS: POST /token<br>(code, client_id, client_secret*, code_verifier) AS-->>Client: 返回 access_token (+ refresh_token) Client->>RS: GET /api/data<br>Authorization: Bearer <access_token> RS-->>Client: 返回受保护资源 流程 ...
磁盘IO类型
仅作为个人笔记,不保证完全的准确性和正确性,请自行甄别 顺序写 文件IO中的“顺序写”通常指的是对文件进行连续写入操作的过程,即从文件的一个位置开始,依次向后写入数据,即追加。可以提高写入效率,尤其是在传统的机械硬盘(HDD)上,因为不需要频繁地在不同的位置之间切换,减少了寻道时间和旋转延迟。 需要注意的是,“顺序写”并不直接等同于物理磁盘上的连续空间写入。虽然理想情况下,操作系统和文件系统会尽量将文件的数据块分配到物理上连续的存储空间中,以提高读写性能,但实际上由于多种因素(如文件系统的碎片、先前删除文件留下的空洞、以及其他文件的存在等),很难保证文件的所有部分都能被分配到完全连续的物理空间中。因此,通常说的“顺序写”,更多是指逻辑上的连续写入,即按照文件内部的偏移量顺序写入数据,而不是指物理磁盘上的连续写入。 即,“顺序写”主要关注的是逻辑层面的连续性,而物理层面的连续性则是文件系统和操作系统尽力优化的结果。 示例 顺序写: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package main import ( "fmt" "os" ) func main() { fileName := "t.bin" fileSize := 1 << 30 // 1GB 1073741824 // fileSize := 1073741825 // 测试不按照块的倍数写 // 创建并截断文件(直接分配空间) file, err := os.Create(fileName) if err != nil { fmt.Println("Error creating file:", err) return } defer file.Close() fInfo, err := file.Stat() if err != nil { fmt.Println("Error getting file size:", err) } fmt.Println("create: ", fInfo.Size()) err = file.Truncate(int64(fileSize)) if err != nil { fmt.Println("Error truncating file:", err) return } fInfo, _ = file.Stat() fmt.Println("truncate: ", fInfo.Size()) randomBlock := make([]byte, 0x1<<10<<2) // 4kB for i := 0; i < len(randomBlock); i++ { randomBlock[i] = 0x0 } bytesWritten := 0 for i := 0; i < fileSize; i += len(randomBlock) { n, err := file.Write(randomBlock) if err != nil { fmt.Println("Error writing to file:", err) } fmt.Println(n) bytesWritten += n } file.Sync() fmt.Println("written: ", bytesWritten) fInfo, err = file.Stat() if err != nil { fmt.Println("Error getting file size:", err) } fmt.Println("finally: ", fInfo.Size()) // 文件指针在末尾 n, err := file.Read(randomBlock) fmt.Println("read nums: ", n) fmt.Println("err: ", err) } 输出: ...
golang context包用法理解
同时启很多个goroutine来完成一个任务,在一些必要的情况下如何跟踪或取消这些goroutine?常见的有下面几种方式: WaitGroup,goroutine之间的同步 for select 加上 stop channel来监听消息管理协程 context包 context包就是用来在goroutine之间传递上下文信息的,它提供了超时timeout和取消cancel机制,利用了channel进行信息传递,方便的管理具有复杂层级关系的多个goroutine,这些复杂的层级关系类似于一棵树,可以有多个分叉。Go标准库中的net/http,database/sql等都用到了context包。 接口 context包定义了两个接口 Context 1 2 3 4 5 6 type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} } Deadline 方法,第一个返回值是该上下文执行完的截止时间,第二个返回值是布尔值,表示是否设置了截止日期,没设置返回ok==false,否则为true。该方法幂等 Done,返回一个只读通道,在context被取消或者context到了deadline时,此通道会被关闭 WithCancel 上下文,当调用cancel后此通道关闭关闭 WithDeadline,到达deadline时间点后自动关闭此通道 WithTimeout,指定的时间超时后自动关闭此通道 Err,Done返回的通道未被关闭时,Err的返回值是nil,关闭后,返回 context 取消的原因,被取消时返回Canceled,到了截止时间返回 DeadlineExceeded Value,获取该Context上绑定的值,需要注意的是键和值都是any类型。以上四个方法都是幂等的 canceler ,私有接口,表示一个可以被取消cancel的context对象,包内的*cancelCtx 和 *timerCtx结构体实现了此接口 1 2 3 4 type canceler interface { cancel(removeFromParent bool, err, cause error) Done() <-chan struct{} } 创建 几个实现此接口的结构体关系: ...