mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-19 04:19:31 +08:00
- Add new AI Querier module for structured information extraction from screenshots - Support custom output schema for structured data response - Implement automatic type conversion and data validation - Add comprehensive test suite with various data structure examples - Refactor callModelWithLogging to utils.go as shared function for planner, asserter, and querier - Eliminate code duplication across AI modules (30+ lines of repeated code) - Improve maintainability with unified logging and timing logic - Add environment variable checks in test setup to handle missing API keys gracefully Key features: - Custom output schema support with JSON Schema generation - Automatic data type conversion with reflection - Fallback mechanisms for robust parsing - Comprehensive documentation and usage examples - Backward compatibility with existing functionality
8.0 KiB
8.0 KiB
HttpRunner AI Querier - 自定义输出格式功能
功能概述
HttpRunner 的 AI Querier 模块支持自定义输出格式功能,允许用户指定特定的数据结构,让 AI 模型返回结构化的数据响应。适用于:
- UI 元素分析:自动化测试中的界面元素提取
- 游戏界面分析:网格类游戏(连连看、消消乐、2048等)数据提取
- 表单数据提取:从表单截图中提取结构化信息
- 图像内容分析:任何需要从截图中提取结构化信息的场景
核心数据结构
// QueryOptions - 查询选项
type QueryOptions struct {
Query string `json:"query"` // 查询文本
Screenshot string `json:"screenshot"` // Base64编码的截图
Size types.Size `json:"size"` // 屏幕尺寸
OutputSchema interface{} `json:"outputSchema,omitempty"` // 自定义输出格式(可选)
}
// QueryResult - 查询结果
type QueryResult struct {
Content string `json:"content"` // 人类可读的分析结果
Thought string `json:"thought"` // AI 推理过程
Data interface{} `json:"data,omitempty"` // 结构化数据(使用OutputSchema时自动转换为指定类型)
}
基本用法
标准查询
// 创建查询器
modelConfig, err := ai.GetModelConfig(option.OPENAI_GPT_4O)
querier, err := ai.NewQuerier(ctx, modelConfig)
// 执行查询
result, err := querier.Query(ctx, &ai.QueryOptions{
Query: "请分析这张截图中的内容",
Screenshot: screenshot,
Size: size,
// 不指定 OutputSchema
})
fmt.Printf("分析结果: %s\n", result.Content)
fmt.Printf("推理过程: %s\n", result.Thought)
// result.Data 为 nil
自定义格式查询
// 定义输出结构
type GameAnalysis struct {
Content string `json:"content"` // 分析描述
Thought string `json:"thought"` // 思考过程
Rows int `json:"rows"` // 行数
Cols int `json:"cols"` // 列数
Icons []string `json:"icons"` // 图标类型
}
// 执行查询
result, err := querier.Query(ctx, &ai.QueryOptions{
Query: "分析这个游戏界面的网格结构和图标类型",
Screenshot: screenshot,
Size: size,
OutputSchema: GameAnalysis{}, // 指定输出格式
})
// 直接类型断言获取结构化数据
if gameData, ok := result.Data.(*GameAnalysis); ok {
fmt.Printf("行数: %d, 列数: %d\n", gameData.Rows, gameData.Cols)
fmt.Printf("图标类型: %v\n", gameData.Icons)
}
应用场景示例
UI 元素分析
type UIAnalysis struct {
Content string `json:"content"`
Thought string `json:"thought"`
Elements []UIElement `json:"elements"`
}
type UIElement struct {
Type string `json:"type"` // button, text, input等
Text string `json:"text"` // 文本内容
BoundBox BoundingBox `json:"boundBox"` // 位置坐标
Clickable bool `json:"clickable"` // 是否可点击
}
type BoundingBox struct {
X, Y, Width, Height int `json:"x,y,width,height"`
}
网格游戏分析
type GridGame struct {
Content string `json:"content"`
Thought string `json:"thought"`
Grid [][]Cell `json:"grid"` // 网格数据
Stats Statistics `json:"statistics"` // 统计信息
}
type Cell struct {
Type string `json:"type"` // 单元格类型
Value string `json:"value"` // 单元格值
Row int `json:"row"` // 行索引
Col int `json:"col"` // 列索引
}
type Statistics struct {
TotalCells int `json:"totalCells"`
UniqueTypes int `json:"uniqueTypes"`
}
表单数据提取
type FormAnalysis struct {
Content string `json:"content"`
Thought string `json:"thought"`
Fields []FormField `json:"fields"`
Actions []Action `json:"actions"`
}
type FormField struct {
Label string `json:"label"` // 字段标签
Type string `json:"type"` // 字段类型
Value string `json:"value"` // 当前值
Required bool `json:"required"` // 是否必填
BoundBox BoundingBox `json:"boundBox"` // 位置
}
核心特性
自动类型转换
- 指定
OutputSchema时,QueryResult.Data自动转换为指定类型 - 支持直接类型断言:
result.Data.(*YourType) - 无需手动调用转换函数
多级回退机制
- 优先解析为指定的结构化类型
- 失败时尝试通用JSON解析
- 最终回退到纯文本响应
向后兼容
- 不指定
OutputSchema时行为不变 - 现有代码无需修改
最佳实践
1. 结构体设计
// 推荐:包含标准字段
type YourSchema struct {
Content string `json:"content"` // 必须:人类可读描述
Thought string `json:"thought"` // 必须:AI推理过程
// 自定义字段...
Data CustomData `json:"data"`
}
// 使用描述性的JSON标签
type Element struct {
Type string `json:"elementType"` // 清晰的字段名
Position Point `json:"gridPosition"` // 描述性标签
Visible bool `json:"isVisible"` // 布尔值清晰性
}
2. 查询指令
// 推荐:详细的查询指令
opts := &ai.QueryOptions{
Query: `分析这张截图并提供结构化信息:
1. 识别界面类型和主要元素
2. 提取所有可交互元素的位置和属性
3. 统计各类元素的数量`,
Screenshot: screenshot,
Size: size,
OutputSchema: YourSchema{},
}
3. 错误处理
result, err := querier.Query(ctx, opts)
if err != nil {
return err
}
// 类型断言
if data, ok := result.Data.(*YourSchema); ok {
// 使用结构化数据
processData(data)
} else {
// 回退到文本结果
log.Printf("结构化解析失败,使用文本结果: %s", result.Content)
}
完整示例
package main
import (
"context"
"fmt"
"log"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/uixt/ai"
"github.com/httprunner/httprunner/v5/uixt/option"
)
type ScreenAnalysis struct {
Content string `json:"content"`
Thought string `json:"thought"`
Elements []string `json:"elements"`
Categories []string `json:"categories"`
Count int `json:"count"`
}
func main() {
ctx := context.Background()
// 创建查询器
modelConfig, err := ai.GetModelConfig(option.OPENAI_GPT_4O)
if err != nil {
log.Fatal(err)
}
querier, err := ai.NewQuerier(ctx, modelConfig)
if err != nil {
log.Fatal(err)
}
// 加载截图
screenshot, size, err := builtin.LoadImage("screenshot.png")
if err != nil {
log.Fatal(err)
}
// 执行结构化查询
result, err := querier.Query(ctx, &ai.QueryOptions{
Query: "分析截图中的UI元素,提取元素类型和分类信息",
Screenshot: screenshot,
Size: size,
OutputSchema: ScreenAnalysis{},
})
if err != nil {
log.Fatal(err)
}
// 使用结构化数据
if analysis, ok := result.Data.(*ScreenAnalysis); ok {
fmt.Printf("发现 %d 个元素\n", analysis.Count)
fmt.Printf("元素类型: %v\n", analysis.Elements)
fmt.Printf("分类: %v\n", analysis.Categories)
} else {
fmt.Printf("文本结果: %s\n", result.Content)
}
}
辅助函数
对于特殊情况,提供了类型转换辅助函数:
// 手动类型转换(通常不需要)
converted, err := ai.ConvertQueryResultData[YourType](result)
if err != nil {
return err
}
注意:使用 OutputSchema 时,Data 字段已自动转换为正确类型,通常不需要手动调用此函数。
技术限制
- 需要支持结构化输出的AI模型(如 OpenAI GPT-4)
- 复杂嵌套结构需要清晰的查询指令
- AI模型可能不总是严格遵循指定格式
- UI-TARS 模型使用不同的响应格式处理