feat: 实现 AIQuery 功能并支持 OutputSchema

- 新增 AIQuery 方法到 StepMobile,支持使用自然语言从屏幕中提取信息
- 实现 AIQuery 在 driver_ext_ai.go 中的完整功能,包括屏幕截图和 LLM 查询
- 添加 OutputSchema 支持,允许用户定义自定义输出格式进行结构化查询
- 新增 ToolAIQuery MCP 工具,完整集成到 MCP 服务器中
- 在 ActionOptions 中添加 OutputSchema 字段和 WithOutputSchema 选项函数
- 添加 ACTION_Query 的配置支持和字段映射
- 完善测试覆盖:
  * 添加 TestAIQuery 单元测试,包含多种 OutputSchema 使用场景
  * 添加 TestToolAIQuery MCP 工具测试
  * 定义 GameInfo、UIElementInfo 等结构体用于测试
- 更新文档:
  * 在 docs/uixt/ai.md 中添加完整的 AIQuery 使用指南
  * 包含基本用法、OutputSchema 示例、最佳实践等
- 支持复杂的嵌套结构体和数组类型的 OutputSchema
- 与现有 AIAction、AIAssert 功能保持一致的 API 设计
This commit is contained in:
lilong.129
2025-06-12 23:12:25 +08:00
parent fb0418fa95
commit f6e7e970f8
9 changed files with 502 additions and 11 deletions

View File

@@ -11,6 +11,35 @@ import (
"github.com/stretchr/testify/require"
)
// GameInfo 定义游戏界面分析的输出格式
type GameInfo struct {
Content string `json:"content"` // 必须:人类可读描述
Thought string `json:"thought"` // 必须AI推理过程
GameType string `json:"game_type"` // 游戏类型
Rows int `json:"rows"` // 行数
Cols int `json:"cols"` // 列数
Icons []string `json:"icons"` // 图标类型
TotalIcons int `json:"total_icons"` // 图标总数
}
// UIElementInfo 定义UI元素分析的输出格式
type UIElementInfo struct {
Content string `json:"content"` // 必须:人类可读描述
Thought string `json:"thought"` // 必须AI推理过程
ScreenType string `json:"screen_type"` // 屏幕类型
Elements []UIElement `json:"elements"` // UI元素列表
ButtonCount int `json:"button_count"` // 按钮数量
TextCount int `json:"text_count"` // 文本数量
}
// UIElement 定义单个UI元素
type UIElement struct {
Type string `json:"type"` // 元素类型 (button, text, input等)
Text string `json:"text"` // 元素文本
Clickable bool `json:"clickable"` // 是否可点击
Description string `json:"description"` // 元素描述
}
func TestIOSSettingsAction(t *testing.T) {
testCase := &hrp.TestCase{
Config: hrp.NewConfig("ios ui action on Settings").
@@ -173,3 +202,92 @@ func TestAIAction(t *testing.T) {
err := hrp.NewRunner(t).Run(testCase)
assert.Nil(t, err)
}
func TestAIQuery(t *testing.T) {
testCase := &hrp.TestCase{
Config: hrp.NewConfig("AIQuery Demo with OutputSchema").
SetLLMService(option.DOUBAO_SEED_1_6_250615), // Configure LLM service for AI operations
TestSteps: []hrp.IStep{
// Step 1: Take a screenshot for analysis
hrp.NewStep("Take Screenshot").
Android().
ScreenShot(),
// Step 2: Basic AIQuery without OutputSchema
hrp.NewStep("Basic Query").
Android().
AIQuery("Please describe what is displayed on the screen and identify any interactive elements"),
// Step 3: Use AIQuery to extract specific information
hrp.NewStep("Extract App Information").
Android().
AIQuery("What apps are visible on the screen? List them as a comma-separated string"),
// Step 4: Use AIQuery for UI element analysis
hrp.NewStep("Analyze UI Elements").
Android().
AIQuery("Are there any buttons or clickable elements visible? Describe their locations and purposes"),
// Step 5: Use AIQuery with validation
hrp.NewStep("Query and Validate").
Android().
AIQuery("Is the home screen currently displayed?").
Validate().
AssertAI("The query result should indicate whether home screen is visible"),
// Step 6: Use AIQuery with simple custom OutputSchema
hrp.NewStep("Query with Simple Custom Schema").
Android().
AIQuery("Analyze the screen and provide structured information about UI elements",
option.WithOutputSchema(struct {
Content string `json:"content"`
Thought string `json:"thought"`
ElementType string `json:"element_type"`
ElementText []string `json:"element_text"`
ButtonCount int `json:"button_count"`
}{})),
// Step 7: Use AIQuery with GameInfo OutputSchema
hrp.NewStep("Game Analysis with Custom Schema").
Android().
AIQuery("分析这个游戏界面,告诉我游戏类型、行列数和图标信息",
option.WithOutputSchema(GameInfo{})),
// Step 8: Use AIQuery with UIElementInfo OutputSchema
hrp.NewStep("UI Element Analysis with Custom Schema").
Android().
AIQuery("分析屏幕上的UI元素识别所有按钮、文本和可交互元素",
option.WithOutputSchema(UIElementInfo{})),
// Step 9: Complex analysis with nested structure
hrp.NewStep("Complex Analysis with Nested Schema").
Android().
AIQuery("Provide a comprehensive analysis of this interface including all interactive elements and their properties",
option.WithOutputSchema(struct {
Content string `json:"content"`
Thought string `json:"thought"`
AppName string `json:"app_name"`
ScreenTitle string `json:"screen_title"`
MainActions []struct {
Name string `json:"name"`
Description string `json:"description"`
Available bool `json:"available"`
} `json:"main_actions"`
NavigationElements []struct {
Type string `json:"type"`
Label string `json:"label"`
Position string `json:"position"`
} `json:"navigation_elements"`
ContentSummary struct {
HasImages bool `json:"has_images"`
HasText bool `json:"has_text"`
HasForms bool `json:"has_forms"`
Keywords []string `json:"keywords"`
} `json:"content_summary"`
}{})),
},
}
err := hrp.NewRunner(t).Run(testCase)
assert.Nil(t, err)
}