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

8.0 KiB
Raw Blame History

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)
  • 无需手动调用转换函数

多级回退机制

  1. 优先解析为指定的结构化类型
  2. 失败时尝试通用JSON解析
  3. 最终回退到纯文本响应

向后兼容

  • 不指定 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 模型使用不同的响应格式处理