玩转MCP(1)-使用篇

1. 引言 MCP可以说是function calling的升级版,它使得大语言模型可以方便的整合和具有工具能力。 简单而言,LLM只是一个大脑,它只能思考。 但是当我们给它提供了MCP之后,它可以轻易的获得各种能力, 宛如给他安上了手和腿,它可以行走了,并对外部世界施加影响。 2. 一些MCP 聊天App 萌叔使用的是 daodao97/chatmcp chatmcp可以很方便的集成其它mcp工具,这里介绍几个MCP 2.1 mcp-searxng ihor-sokoliuk/mcp-searxng SearXNG 是一款免费的互联网元搜索引擎,它聚合了来自各种搜索引擎和服务的结果。 用户既不会被追踪,也不会被画像。传送门 使用mcp-searxng,使得大模型可以通过搜索引擎获取信息。 配置方式 { "mcpServers": { "searxng": { "command": "npx", "args": [ "-y", "mcp-searxng" ], "env": { "SEARXNG_URL": "YOUR_SEARXNG_INSTANCE_URL" } } } } Tools searxng_web_search 利用搜索引擎进行检索 web_url_read 从某个URL读取内容 2.2 mcp-datetime ZeparHyfar/mcp-datetime 很多大模型没有时间概念,这会导致他在处理问题时,出现时间错误,mcp-datetime可以 以各种格式获取当前日期和时间。 配置方式 { "mcpServers": { "mcp-datetime": { "command": "uvx", "args": ["mcp-datetime"] } } } Tools get_datetime 2.3 mcp-server-chart antvis/mcp-server-chart antvis/mcp-server-chart是蚂蚁集团出品的图表工具。 mcp-server-chart生成的图片已经使用CDN加速,与大模型交互只传递URL,可以极大的减少token开销。 配置方式 { "mcpServers": { "mcp-server-chart": { "command": "npx", "args": [ "-y", "@antv/mcp-server-chart" ] } } } Tools 可以画25种图表 ...

July 27, 2025 · 2 min

一文了解RAG(检索增强型生成)

在这篇文章中,萌叔将介绍RAG技术。 将它与传统的搜索引擎进行对比,并介绍一个完整的RAG实现–lightRAG,详述其技术细节。 1. 传统搜索 传统搜索通常基于倒排索引来进行搜索 1.1 倒排索引 倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案, 是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。 倒排索引是Term到DocID的映射。 1.2 搜索语句示例 Lucene针对某个字段进行搜索样例 title:hello world 就意味着,搜索title为hello,或者包含title关键字的文档 伪代码形如 doc.title contains "hello" OR doc.title contains "world" 假定 “hello”-> [1, 5, 8] “world”-> [5, 8, 10, 12] 然后搜索引擎对2个doc ID集合的求并集,并对所有文档进行打分,选出分数最高的前N个文档。 2. RAG RAG (检索增强生成) 是一种人工智能技术,它结合了信息检索和生成式模型的功能, 以提高生成文本的准确性和相关性。 RAG 先从外部知识库中检索相关信息,然后结合这些信息,使用生成式模型, 生成更准确、更有上下文相关的文本。 提到RAG,需要先聊一聊Embedding models 2.1 Embedding models 2.1.1 高维向量表征文本 嵌入式模型,可以把一段文本转换为纯数值的高维向量 通过嵌入模型(如bge-m3)将文本转换为1024维的数值向量,这个向量包含了文本的语义特征 curl --location 'http://192.168.100.2:21434/api/embed' \ --header 'Content-Type: application/json' \ --data '{ "model": "bge-m3:latest", "input": "这个故事的主题是什么?" }' 2.1.2 向量距离反映语义相似度 当两个向量在向量空间中的距离越近(通常用余弦相似度或欧氏距离衡量) 说明它们对应的文本在语义上越相似 例如"猫"和"猫咪"的向量会比"猫"和"汽车"的向量更接近 ...

June 27, 2025 · 2 min

给大语言模型插上翅膀

1.引言 有人说,智慧是 知识 + 运用知识的能力。从这个角度来看,大语言模型(如ChatGPT) 在某种程度上可以被认为具有一定形式的智慧。然而现在针对大语言模型的使用有一个问题, 就是模型所具有的知识无法保持实时更新。比如某个API文档发生变化,大语言模型仍然只能基于历史知识进行回答。 有没有办法让大语言模型获取最新的知识,而不至于stale。 显然如果它能够调用搜索引擎,获取最新知识,就可以解决这个问题。 事实上,现在kimi的联网搜索和ChatGPT的搜索网页都是为了解决这个问题。 下面让我们看看如何利用function calling来实现大语言模型从搜索引擎获取数据,继而回答用户的提问。 2. 大语言模型通过搜索引擎获取数据 首先,我们来看一个问题:“今天武汉和上海的气温,哪一个更高?” 对于人类而言,我们要解决这个问题,必须经过以下步骤 1)获取武汉和上海的气温数据 2)比较武汉和上海的最高气温,得出结论 下面萌叔提供一个完整的Python程序,看看如何使用function calling来回答这个提问 import json from email.policy import strict from openai import OpenAI import requests from openai._utils import maybe_transform from openai.types.chat import ChatCompletion base_url = "{此处请自行替换}" api_key = "{此处请自行替换}" google_search_key = "{此处请自行替换}" google_search_engine = "{此处请自行替换}" # 定义 google_search 函数(示例) def google_search(query: str) -> str: url = "https://www.googleapis.com/customsearch/v1" query_params = { "key": google_search_key, "cx": google_search_engine, "q": query, "num": 5, } response = requests.get(url, params=query_params) print(response.url) return response.text # 调用大模型 def send_messages(messages): # 函数信息,用于 Function Calling tools = [ { "type": "function", "function": { "name": "google_search", "description": "通过 Google 搜索获取信息", "parameters": { # 参数类型必须是object "type": "object", "properties": { "query": { "type": "string", "description": "需要搜索的关键词或短语", } }, "required": ["query"], "additionalProperties": False, }, "strict": True } }, ] client = OpenAI(api_key=api_key, base_url=base_url) response = client.chat.completions.create( model="gpt-4o-mini", # 确保使用支持 Function Calling 的模型 messages=messages, tools=tools, max_tokens=4096, parallel_tool_calls=True, ) dd = maybe_transform(response, ChatCompletion) data = json.dumps(dd, ensure_ascii=False) print(data) return response.choices[0].message if __name__ == "__main__": # 示例对话 # input_text = input("请输入您的问题:") messages = [ {"role": "system", "content": "你是一个智能搜索机器人。你根据用户提出的问题,利用google_search函数,获取额外信息,然后回答用户的提问"}, {"role": "user", "content": "今天武汉和上海的气温,哪一个更高?"}, ] # 1. **第1次调用AI接口** message = send_messages(messages) call_cout = len(message.tool_calls) print(f"将会发起{call_cout}次调用...") # 2. **在client端调用google_search()** messages.append(message) for tool in message.tool_calls: if tool.function.name == "google_search": parsed_arguments = json.loads(tool.function.arguments) query = parsed_arguments.get("query", None) # 获取 query 值 print("query:", query) content = google_search(query) messages.append({"role": "tool", "tool_call_id": tool.id, "content": content}) # 3. **第2次调用AI接口** message = send_messages(messages) print(f"Model>\t {message.content}") 0) 函数说明 这里我们给出了函数google_search()说明, 并把它传递给大模型, 告诉它在处理问题的时候,可以考虑使用上我们提供的函数。 ...

January 14, 2025 · 3 min

玩转Arduino(3)-感应式垃圾桶

思路 arduino接红外线传感器, 舵机、灯,红外线传感器被触发后,红灯亮起,舵机转动一定的角度,垃圾桶盖被打开,等待3秒后,舵机转回初始位置,垃圾桶盖关闭。 代码 #include <Servo.h> int infraredPin = 2; int lightPin = 6; int steerPin = 9; int closeAngle = 0; int openAngle = 80; Servo servo; void setup() { pinMode(infraredPin, INPUT); pinMode(steerPin, OUTPUT); pinMode(lightPin, OUTPUT); servo.attach(steerPin); // 初始状态: 垃圾桶盖子是关闭的 servo.write(closeAngle); } void loop() { if (digitalRead(infraredPin) == 0) { //检测到障碍物,输出低电平 digitalWrite(lightPin, HIGH); //亮灯 // 垃圾桶开盖 servo.write(openAngle); delay(3000); // 等待3秒 // clear digitalWrite(lightPin, LOW); // 灭灯 servo.write(closeAngle); } delay(200); } 演示视频 B站 ...

November 1, 2024 · 1 min

RocketMQ架构设计中的”暴力美学”(2)-故障处理

本文基于rocketmq-all-4.8.0 1.引言 人们在潜意识里,总会觉得复杂且精巧的东西是好东西。但是这个复杂这个词在软件架构设计中,却不一定是好事情。 因为过于精巧和复杂的系统往往意味着系统更难以维护,出现问题后,故障更难排查。 萌叔在阅读和分享RocketMQ的过程中, 发现它有很多设计非常的简单粗暴,堪称”暴力美学”的典范, 同时又给人眼前一亮的感觉(还能这么玩)。 2.故障处理 在介绍故障处理机制时,我们假定一个场景 2.1 架构 假定broker有2组 group1 Master1和Slave1 group2 Master2和Slave2 2.2 故障发生 故障发生前,萌叔创建了1个topic,指定分片数为4 mqadmin updateTopic -n <namesrvAddr> -c <clusterName> -t myTopic -q 4 这里需要强调一下,这里指定的4个分片,并不是全局4个分片,而是每个broker有4个分片,情况如下图。 此时 Topic路由信息形如: { "OrderTopicConf": "", "queueDatas": [{ "brokerName": "group1", "readQueueNums": 4, "writeQueueNums": 4, "perm": 6, // 可读可写 "topicSynFlag": 0 }, { "brokerName": "group2", "readQueueNums": 4, "writeQueueNums": 4, "perm": 6, // 可读可写 "topicSynFlag": 0 }], "brokerDatas": [{ "cluster": "Default_Cluster", "brokerName": "group1", "brokerAddrs": { "0": "192.168.12.123:10911", // master "1": "192.168.12.127:10911" // slave } }, { "cluster": "Default_Cluster", "brokerName": "group2", "brokerAddrs": { "0": "192.168.12.220:10911", "1": "192.168.12.12:10911" } }] } 生产者消息路由策略:Round Robin 消费者消息路由策略:AllocateByAveragely ...

August 7, 2024 · 2 min

使用Skywalking-go自动进行监控增强

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 使用Skywalking-go,我们可以基于Golang build提供的-toolexec参数,实现了编译期劫持,达到在对代码几乎无侵入的情况下,实现埋点。 Skywalking对大量的框架(比如: gin)提供了支持, 然后这种支持仅限于增加了tracing信息。我们知道完整的监控往往离不开3个重要部分Metrics、Tracing、Logging。那些需要埋点的位置,往往这3种信息都是需要的,是否可以修改Skywalking-go,来达到代码增强的过程同时添加Metrics、Tracing、Logging呢?答案是肯定的。 本文的代码可参考 1)改动后的Skywalking-go vearne/skywalking-go 注意: 如果读者有自己改动的计划,建议好好阅读官方文档,并参考萌叔的改动代码 git diff f9929c6c9275b12f70ec2368544244f27a92f768 2) 测试项目 vearne/sw-test 2.解决问题 2.1 Tracing tracing保持不变 2.2 Logging Skywalking默认提供了对zap、logrus的劫持,因此可以直接在插件对应的拦截器中增加对应的日志即可 intercepter.go package gin import ( "fmt" "github.com/gin-gonic/gin" // 需要增加的import "github.com/apache/skywalking-go/plugins/core/log" "github.com/apache/skywalking-go/plugins/core/operator" "github.com/apache/skywalking-go/plugins/core/prom" "github.com/apache/skywalking-go/plugins/core/tracing" ) type HTTPInterceptor struct { } ... func (h *HTTPInterceptor) AfterInvoke(invocation operator.Invocation, result ...interface{}) error { ... // add logging log.Infof("url:%v", context.Request.URL.Path) ... return nil } 经过上述改动以后,插件拦截器新增的日志,也会输出到和业务日志相同的位置 ...

January 8, 2024 · 3 min

apache/skywalking-go 源码分析

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc apache/skywalking-go 源码分析 参考资料 1.劫持 Golang 编译 2.SkyWalking Go Agent 快速开始指南 3.support-plugins 4.Hybrid Compilation 5.Key Principle 6.通过SkyWalking上报Go应用数据 1. 前言 Skywalking是什么? Skywalking 是一个开源的应用性能监控工具,它专注于分布式系统架构中的性能监控和故障排查。 它能够跟踪分布式系统中的请求流,并提供实时的性能指标、调用链追踪、错误分析等功能。 使用 Skywalking 可以帮助开发人员和运维团队更好地理解应用程序的性能特征, 并快速定位和解决潜在的性能问题和故障。 几年前,当我最开始知道Skywalking的时候,它似乎只支持Java,它给我的最大的惊喜时,是直接使用字节码注入的方式来实现埋点, 减少开发人员埋点的工作量,且代码几乎无侵入。 2023年上半年,Skywalking推出一个全新的Go Agent skywalking-go。 它基于Golang build提供的-toolexec参数,实现了编译期劫持,也达到了在对代码几乎无侵入的情况下,实现埋点。 截止2023年12月1日,已经有大量的库得到了支持。参看support-plugins 日志库 zap、logrus 数据库Client gorm、sql HTTP Server gin、http HTTP Client http RPC框架 gRPC 它是如何做到代码几乎无入侵埋点的呢?本文试图解答这个问题 2. skywalking-go实现埋点 关于skywalking-go的使用参看SkyWalking Go Agent 快速开始指南 skywalking-go实现埋点主要依靠在golang build编译阶段的编译劫持。 go build -toolexec="/myopt/bin/skywalking-go-agent" -x -work -a -o test . 这里我们增加了2个额外参数 -x:这个标志告诉 Go 打印它执行的命令。 -work:这个标志告诉 Go 打印临时工作目录的名称,并在退出时不删除它。 ...

December 4, 2023 · 3 min

使用net-byte/vtun搭建VPN服务

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.前言 萌叔发现了一个新库net-byte/vtun vtun可以用来快速部署VPN服务,代码粗略看了下,原理非常简单,就是利用隧道技术来实现的。 2.隧道技术原理图 vtun server通常暴露在公网中,这样客户端可以方便的接入 vtun-client在Client上创建了虚拟网卡tun0 在Client中的用户进程看来,它的流量都是发给tun0,但是实际上它的流量都被vtun-client捕获,从eth0发给vtun-server vtun-server所在Server开启了内核转发功能,因此可以转发给局域网中的其它实例 3.实验 我用青云的虚拟机构建了一个网络环境 vtun-server部署在青云的虚拟机上,IP地址为172.16.1.2, 测试机地址为172.16.3.2, 这2台机器位于2个不同的网段 测试机(IP: 172.16.3.2) 上部署了web服务,端口为8080 vtun-client部署在腾讯云的虚拟机上(用vtun创建虚拟网卡,配置IP为172.16.5.10) 172.16.0.0/24、172.16.1.0/24和172.16.3.0/24 通过路由器连接在一起 验证方式 在腾讯云的虚拟机上,ping 青云的测试机(172.16.3.2) 在腾讯云的虚拟机上,请求青云的测试机(172.16.3.2)上的web服务 3.1 部署 注意: 请自行修改密码 3.1.1 在Server上执行 1) 启动vtun server nohup vtun-linux-amd64 -p tcp -S -l :27080 -c 172.16.5.1/24 -k {password} & 注意:萌叔这里使用的网段172.16.5.1/24 与之前的172.16.1.0/24和172.16.3.0/24均不在一个网段 server上创建了虚拟网卡 tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1500 inet 172.16.5.1 netmask 255.255.255.0 destination 172.16.5.1 inet6 fced:9999::9999 prefixlen 64 scopeid 0x0<global> inet6 fe80::ab1f:1f20:fec5:78b prefixlen 64 scopeid 0x20<link> unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (未指定) RX packets 52 bytes 3162 (3.1 KB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 33 bytes 3961 (3.9 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 这个虚拟网卡使用的驱动明显与其它网卡不同 ...

September 6, 2023 · 2 min

LSM-Tree分享

参考资料 1.The Log-Structured Merge-Tree 2.一文带你看透基于LSM-tree的NoSQL系统优化方向 3.数据库存储与索引技术:分布式数据库基石——LSM树 4.WiscKey: Separating Keys from Values in SSD-conscious 5.dgraph-io/badger 6.badger源码分析 7.B树与LSM树读写和空间放大分析 8.大白话彻底搞懂 HBase Rowkey 设计和实现方式 9.Kvrocks: 一款开源的企业级磁盘KV存储服务 10.Kvrocks data structures design 11.布隆过滤器的原理及完整公式推导 1. LSM-Tree简介 1.1 LSM-Tree的概念 LSM-Tree 全称是Log Structured Merge Tree,是一种分层、有序、面向磁盘的数据结构,其核心思想是充分利用磁盘的顺序写性能要远高于随机写性能这一特性,将批量的随机写转化为一次性的顺序写。 1.2 应用场景 以NoSQL为代表的分布式数据库多采用LSM树用于构建底层的存储系统。 Apache Cassandra:Cassandra 是一个高可用性、高可扩展性的分布式 NoSQL 数据库,它使用了 LSM 树来存储数据。 LevelDB:由 Google 开发的高性能键值对数据库,使用 LSM 树来存储数据。 RocksDB:由 Facebook 开发的高性能嵌入式键值对数据库,它是 LevelDB 的改进版本,同样使用 LSM 树来存储数据。 HBase:HBase 是一个分布式列存储数据库,它是基于 Hadoop 的一个开源项目。HBase 的数据存储是基于 LSM 树实现的。 ScyllaDB:ScyllaDB 是一个高性能的分布式 NoSQL 数据库,使用 LSM 树来存储数据,并且兼容 Cassandra API。 Apache Lucene:一个开源的信息检索库,广泛用于各种搜索引擎和全文检索系统。虽然Lucene主要关注于索引结构,但其底层的数据存储也使用了LSM Tree的设计思想。 另外TiDB的本地存储使用的是RocksDB TiDB本地存储 ...

May 6, 2023 · 3 min

介绍一个grpc记录和重放库 vearne/grpcreply-使用篇

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 gRPC协议应用的非常广泛,针对它流量的记录和重放是一个非常常见的需求。但是到目前为止社区中都没有找到合适的库来实现这个目标。 终于它来了。传送门: vearne/grpcreplay 2.介绍 vearne/grpcreplay是一个网络监控工具,可以记录您的 grpc流量(Unary RPC),并将其用于灰度测试、压测或者流量分析。它有2大特点,使用简单,并且可以直接解析Protobuf的请求,并以JSON形式输出。 在使用vearne/grpcreplay前,你必须要知道vearne/grpcreplay的限制条件: 支持解析Unary RPC,不支持streaming RPC Server端必须开启反射 参考GRPC Server Reflection Protocol 3. 编译&使用 确保server所在的主机上已经安装了libpcap 安装libpcap可以参考以下命令: Ubuntu apt-get install libpcap-dev Centos yum install libpcap-devel Mac brew install libpcap 编译 make build 示例 server package main import ( "context" "encoding/json" "github.com/grpc-ecosystem/go-grpc-middleware" pb "github.com/vearne/grpcreplay/example/search_proto" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" "log" "net" "runtime/debug" "time" ) const PORT = "35001" type SearchServer struct{} func (s SearchServer) Search(ctx context.Context, in *pb.SearchRequest) (*pb.SearchResponse, error) { return &pb.SearchResponse{StaffID: 100, StaffName: in.StaffName}, nil } func (s SearchServer) CurrentTime(ctx context.Context, request *pb.TimeRequest) (*pb.TimeResponse, error) { return &pb.TimeResponse{CurrentTime: time.Now().Format(time.RFC3339)}, nil } func main() { opts := []grpc.ServerOption{ //grpc.Creds(c), grpc_middleware.WithUnaryServerChain( RecoveryInterceptor, LoggingInterceptor, ), //grpc.HeaderTableSize(0), //grpc.WithDisableRetry(), } server := grpc.NewServer(opts...) pb.RegisterSearchServiceServer(server, &SearchServer{}) // 注册反射服务(非常重要) // Register reflection service on gRPC server. reflection.Register(server) lis, err := net.Listen("tcp", ":"+PORT) if err != nil { log.Fatalf("net.Listen err: %v", err) } server.Serve(lis) } func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { log.Printf("gRPC method: %s, %v", info.FullMethod, req) resp, err := handler(ctx, req) bt, _ := json.Marshal(req) log.Println("body", string(bt)) log.Printf("gRPC method: %s, %v", info.FullMethod, resp) return resp, err } func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { defer func() { if e := recover(); e != nil { debug.PrintStack() err = status.Errorf(codes.Internal, "Panic err: %v", e) } }() return handler(ctx, req) } client ...

October 17, 2022 · 2 min