Files
httprunner/uixt/ai/querier.md
lilong.129 7c45acd061 feat: add AI Querier module with custom output schema support and refactor common model calling logic
- 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
2025-06-10 20:41:35 +08:00

299 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HttpRunner AI Querier - 自定义输出格式功能
## 功能概述
HttpRunner 的 AI Querier 模块支持自定义输出格式功能,允许用户指定特定的数据结构,让 AI 模型返回结构化的数据响应。适用于:
- **UI 元素分析**:自动化测试中的界面元素提取
- **游戏界面分析**网格类游戏连连看、消消乐、2048等数据提取
- **表单数据提取**:从表单截图中提取结构化信息
- **图像内容分析**:任何需要从截图中提取结构化信息的场景
## 核心数据结构
```go
// 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时自动转换为指定类型
}
```
## 基本用法
### 标准查询
```go
// 创建查询器
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
```
### 自定义格式查询
```go
// 定义输出结构
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 元素分析
```go
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"`
}
```
### 网格游戏分析
```go
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"`
}
```
### 表单数据提取
```go
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)`
- 无需手动调用转换函数
### 多级回退机制
1. 优先解析为指定的结构化类型
2. 失败时尝试通用JSON解析
3. 最终回退到纯文本响应
### 向后兼容
- 不指定 `OutputSchema` 时行为不变
- 现有代码无需修改
## 最佳实践
### 1. 结构体设计
```go
// 推荐:包含标准字段
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. 查询指令
```go
// 推荐:详细的查询指令
opts := &ai.QueryOptions{
Query: `分析这张截图并提供结构化信息:
1. 识别界面类型和主要元素
2. 提取所有可交互元素的位置和属性
3. 统计各类元素的数量`,
Screenshot: screenshot,
Size: size,
OutputSchema: YourSchema{},
}
```
### 3. 错误处理
```go
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)
}
```
## 完整示例
```go
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)
}
}
```
## 辅助函数
对于特殊情况,提供了类型转换辅助函数:
```go
// 手动类型转换(通常不需要)
converted, err := ai.ConvertQueryResultData[YourType](result)
if err != nil {
return err
}
```
**注意**:使用 `OutputSchema` 时,`Data` 字段已自动转换为正确类型,通常不需要手动调用此函数。
## 技术限制
- 需要支持结构化输出的AI模型如 OpenAI GPT-4
- 复杂嵌套结构需要清晰的查询指令
- AI模型可能不总是严格遵循指定格式
- UI-TARS 模型使用不同的响应格式处理