通过higress AI统计插件学gjson表达式的分享

用过higress的都知道higress是一个功能强大的AI网关,而且支持wasm插件,其中有个插件是AI Statistics(AI统计),可以用来进行大模型调用相关的可观测,可以统计token使用量,可以提取请求内容写入日志。比如记录用户的输入。问题的由来就在这里了。

针对记录用户的输入,AI统计插件默认的配置如下:

bash 复制代码
attributes: 
- apply_to_log: true 
key: "question" 
value: "messages.@reverse.0.content" 
value_source: "request_body"

我们知道大模型调用,用户消息都在messages,它是一个数组,包括用户消息+AI返回,正常来说发送给大模型的messages最新的message是用户消息(role=user)。

但是对于toolcall调用的情况,最新消息就是tool消息(工具调用结果),而且在不规范调用的情况下,也可能最新就是AI消息(role=assistant,实际测试这种情况大模型也是可以正常调用的)。

在上面的情况下,官方默认的配置messages.@reverse.0.content就存在问题了,message先反转再取第0个元素,可能取到的就是AI消息或tool执行结果,而不是用户消息。

看了下插件代码

Go 复制代码
case RequestBody: 
    value = gjson.GetBytes(body, attribute.Value).Value()

可见是通过gjson方式以jsonpath实现的,那么我们就要gjson看是不是支持jsonpath带筛选条件(针对messages数组做role=user的筛选)。

现在都很少去百度搜索、csdn的,多数直接找个AI大模型去问了,可惜啊AI是给出了答案,但是各种试就是不行。

比如messages.@reverse.#(role=="user")#.0.content,结果返回了[],看着是进行了筛选然后取第一个元素,但实际不行

又attributeValue = `messages.@reverse.#(role=="user")#.content`,结果返回["今天天气怎么样?"],变成了数组

这里测试,都得在higress控制台修改配置,然后调用路由api,再去日志查看,比较费劲麻烦,于是写了个测试代码,方便调试。

Go 复制代码
package main

import (
	"fmt"

	"github.com/tidwall/gjson"
)

func main() {
	// 测试场景1: 只有一条 user 消息
	body := []byte(`{
		"messages": [
			{
				"role": "user",
				"content": "今天天气怎么样?"
			}
		],
		"model": "qwen3-30b-a3b-instruct-2507",
		"stream": false,
		"enable_thinking": false
	}`)

	fmt.Println("=== 测试结果 ===")
	attributeValue := `messages.@reverse.#(role=="user")#.0.content`
	attributeValue = `messages.@reverse.#(role=="user")#.0`
	attributeValue = `messages.@reverse.#(role=="user")#.content`
	attributeValue = `messages.@reverse.0.content`
	attributeValue = `messages.@reverse|#(role=="user")#|0.content`
	attributeValue = `messages.#(role=="user")#|@reverse|0.content`
	attributeValue = `messages.@reverse.#(role=="user")#|0.content`
	attributeValue = `messages.#(role=="user")#.@reverse|0.content`

	value := gjson.GetBytes(body, attributeValue).Value()
	fmt.Printf("result: %s\n", value)
}

然后开始启用Claude Code,还是这哥们给力,给出了两个可用的答案,同时还写了测试代码进行验证。

答案1:messages.@reverse.#(role=="user").content

答案2:messages.#(role=="user")#|@reverse|0.content

发现这两个答案筛选部分,一个带1个#一个带了2个#,于是追问,答案逐渐清晰

|--------|----------|----------|
| 特性 | 表达式 1 | 表达式 2 |
| 使用符号 | 单 # | 双 ## |
| 返回类型 | 直接返回对象 | 返回数组 |
| 是否需要索引 | ❌ 不需要 | ✅ 需要 0 |
| 何时取值 | 查询时就取第一个 | 过滤后需要手动取 |

这也解释了为啥答案1不需要带索引0

对于答案2,个人感觉思路更清晰,通过|管道模式,一步步解析得到最终结果。

至于为啥messages.@reverse.#(role="user")#.0.content不行,而且返回是[]?首先,messages.@reverse.0.content是可以返回结果的(不过可能不是role=user的结果),但是在增加#(role="user")#筛选后,无法再应用索引,.0直接识别为了0属性,那么肯定就不符合预期了。

然后,我又想到一个答案messages.#(role=="user")#|@reverse|0.content,就是先筛选再反转,取第0个元素(这里问了AI,也做了代码测试,元素顺序不会出问题)

然后又想到2个#的问题在于无法直接索引0那么只需要通过|进行索引0就好了,于是又有一个新答案:messages.#(role=="user")#.@reverse|0.content

至此,这篇关于gjson的学习分享就结束了。

相关推荐
宇宙帅猴1 天前
【Ubuntu踩坑及解决方案(一)】
linux·运维·ubuntu·go
SomeBottle2 天前
【小记】解决校园网中不同单播互通子网间 LocalSend 的发现问题
计算机网络·go·网络编程·学习笔记·计算机基础
且去填词2 天前
深入理解 GMP 模型:Go 高并发的基石
开发语言·后端·学习·算法·面试·golang·go
大厂技术总监下海3 天前
向量数据库“卷”向何方?从Milvus看“全功能、企业级”的未来
数据库·分布式·go·milvus·增强现实
冷冷的菜哥3 天前
go(golang)调用ffmpeg对视频进行截图、截取、增加水印
后端·golang·ffmpeg·go·音视频·水印截取截图
Grassto3 天前
深入 `modload`:Go 是如何加载并解析 module 的
golang·go·go module
帅猛的Shic4 天前
Kubernetes Service深度解析:为什么Pod需要稳定接入点
kubernetes·go
molaifeng4 天前
Token:AI 时代的数字货币——从原理到计费全解
人工智能·ai·大模型·llm·go·token
天天进步20155 天前
KrillinAI 源码级深度拆解四: 告别违和感:深度剖析 KrillinAI 中的 Lip-sync 唇形对齐技术实现
go