feat: add status code for llm

This commit is contained in:
lilong.129
2025-04-28 21:06:53 +08:00
parent 68dbeb368a
commit 7132eec39e
8 changed files with 89 additions and 65 deletions
+31 -16
View File
@@ -96,16 +96,15 @@ var (
LoopActionNotFoundError = errors.New("loop action not found error") // 79 LoopActionNotFoundError = errors.New("loop action not found error") // 79
) )
// AI related: [80, 90) // CV related: [80, 90)
var ( var (
CVEnvMissedError = errors.New("CV env missed error") // 80 CVEnvMissedError = errors.New("CV env missed error") // 80
CVRequestError = errors.New("CV prepare request error") // 81 CVPrepareRequestError = errors.New("CV prepare request error") // 81
CVServiceConnectionError = errors.New("CV service connect error") // 82 CVRequestServiceError = errors.New("CV request service error") // 82
CVResponseError = errors.New("CV parse response error") // 83 CVParseResponseError = errors.New("CV parse response error") // 83
CVResultNotFoundError = errors.New("CV result not found") // 84 CVResultNotFoundError = errors.New("CV result not found") // 84
LLMEnvMissedError = errors.New("LLM env missed error") // 85 StateUnknowError = errors.New("detect state failed") // 85
StateUnknowError = errors.New("detect state failed") // 89
) )
// trackings related: [90, 100) // trackings related: [90, 100)
@@ -121,6 +120,15 @@ var (
RiskControlAccountActivation = errors.New("risk control account activation") // 102 RiskControlAccountActivation = errors.New("risk control account activation") // 102
) )
// LLM related: [110, 120)
var (
LLMEnvMissedError = errors.New("missed LLM env error") // 110
LLMPrepareRequestError = errors.New("prepare LLM request error") // 111
LLMRequestServiceError = errors.New("request LLM service error") // 112
LLMParsePlanningResponseError = errors.New("parse LLM planning response error") // 113
LLMParseAssertionResponseError = errors.New("parse LLM assertion response error") // 114
)
var errorsMap = map[error]int{ var errorsMap = map[error]int{
// environment // environment
ConfigureError: 3, ConfigureError: 3,
@@ -194,14 +202,21 @@ var errorsMap = map[error]int{
MobileUIPopupError: 78, MobileUIPopupError: 78,
LoopActionNotFoundError: 79, LoopActionNotFoundError: 79,
// AI related // CV related
CVEnvMissedError: 80, CVEnvMissedError: 80,
CVRequestError: 81, CVPrepareRequestError: 81,
CVServiceConnectionError: 82, CVRequestServiceError: 82,
CVResponseError: 83, CVParseResponseError: 83,
CVResultNotFoundError: 84, CVResultNotFoundError: 84,
LLMEnvMissedError: 85,
StateUnknowError: 89, StateUnknowError: 85,
// LLM related
LLMEnvMissedError: 110,
LLMPrepareRequestError: 111,
LLMRequestServiceError: 112,
LLMParsePlanningResponseError: 113,
LLMParseAssertionResponseError: 114,
// trackings related // trackings related
TrackingGetError: 90, TrackingGetError: 90,
+1 -1
View File
@@ -1 +1 @@
v5.0.0-beta-2504282012 v5.0.0-beta-2504282106
+15 -7
View File
@@ -9,6 +9,7 @@ import (
"github.com/cloudwego/eino-ext/components/model/ark" "github.com/cloudwego/eino-ext/components/model/ark"
"github.com/cloudwego/eino/schema" "github.com/cloudwego/eino/schema"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/json" "github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/uixt/types" "github.com/httprunner/httprunner/v5/uixt/types"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -76,14 +77,21 @@ type AssertOptions struct {
Size types.Size `json:"size"` // Screen dimensions Size types.Size `json:"size"` // Screen dimensions
} }
func validateAssertionInput(opts *AssertOptions) error {
if opts.Assertion == "" {
return errors.Wrap(code.LLMPrepareRequestError, "assertion text is required")
}
if opts.Screenshot == "" {
return errors.Wrap(code.LLMPrepareRequestError, "screenshot is required")
}
return nil
}
// Assert performs the assertion check on the screenshot // Assert performs the assertion check on the screenshot
func (a *UITarsAsserter) Assert(opts *AssertOptions) (*AssertionResponse, error) { func (a *UITarsAsserter) Assert(opts *AssertOptions) (*AssertionResponse, error) {
// Validate input parameters // Validate input parameters
if opts.Assertion == "" { if err := validateAssertionInput(opts); err != nil {
return nil, errors.New("assertion text is required") return nil, errors.Wrap(err, "validate assertion parameters failed")
}
if opts.Screenshot == "" {
return nil, errors.New("screenshot is required")
} }
// Reset history for each new assertion // Reset history for each new assertion
@@ -127,14 +135,14 @@ Here is the assertion. Please tell whether it is truthy according to the screens
log.Info().Float64("elapsed(s)", time.Since(startTime).Seconds()). log.Info().Float64("elapsed(s)", time.Since(startTime).Seconds()).
Str("model", a.config.Model).Msg("call model service for assertion") Str("model", a.config.Model).Msg("call model service for assertion")
if err != nil { if err != nil {
return nil, fmt.Errorf("request model service failed: %w", err) return nil, errors.Wrap(code.LLMRequestServiceError, err.Error())
} }
logResponse(resp) logResponse(resp)
// Parse result // Parse result
result, err := parseAssertionResult(resp.Content) result, err := parseAssertionResult(resp.Content)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "parse assertion result failed") return nil, errors.Wrap(code.LLMParseAssertionResponseError, err.Error())
} }
// Append assistant message to history // Append assistant message to history
+10 -10
View File
@@ -52,7 +52,7 @@ func (s *vedemCVService) ReadFromPath(imagePath string, opts ...option.ActionOpt
imageResult *CVResult, err error) { imageResult *CVResult, err error) {
imageBuf, err := os.ReadFile(imagePath) imageBuf, err := os.ReadFile(imagePath)
if err != nil { if err != nil {
err = errors.Wrap(code.CVRequestError, err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("read image file error: %v", err)) fmt.Sprintf("read image file error: %v", err))
return return
} }
@@ -116,21 +116,21 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png") formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png")
if err != nil { if err != nil {
err = errors.Wrap(code.CVRequestError, err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("create form file error: %v", err)) fmt.Sprintf("create form file error: %v", err))
return return
} }
size, err := formWriter.Write(imageBuf.Bytes()) size, err := formWriter.Write(imageBuf.Bytes())
if err != nil { if err != nil {
err = errors.Wrap(code.CVRequestError, err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("write form error: %v", err)) fmt.Sprintf("write form error: %v", err))
return return
} }
err = bodyWriter.Close() err = bodyWriter.Close()
if err != nil { if err != nil {
err = errors.Wrap(code.CVRequestError, err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("close body writer error: %v", err)) fmt.Sprintf("close body writer error: %v", err))
return return
} }
@@ -146,7 +146,7 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
req, err = http.NewRequest("POST", os.Getenv("VEDEM_IMAGE_URL"), copiedBodyBuf) req, err = http.NewRequest("POST", os.Getenv("VEDEM_IMAGE_URL"), copiedBodyBuf)
if err != nil { if err != nil {
err = errors.Wrap(code.CVRequestError, err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("construct request error: %v", err)) fmt.Sprintf("construct request error: %v", err))
return return
} }
@@ -192,7 +192,7 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
break break
} }
if resp == nil { if resp == nil {
err = code.CVServiceConnectionError err = code.CVRequestServiceError
return return
} }
@@ -200,13 +200,13 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
results, err := io.ReadAll(resp.Body) results, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
err = errors.Wrap(code.CVResponseError, err = errors.Wrap(code.CVParseResponseError,
fmt.Sprintf("read response body error: %v", err)) fmt.Sprintf("read response body error: %v", err))
return return
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
err = errors.Wrap(code.CVResponseError, err = errors.Wrap(code.CVParseResponseError,
fmt.Sprintf("unexpected response status code: %d, results: %v", fmt.Sprintf("unexpected response status code: %d, results: %v",
resp.StatusCode, string(results))) resp.StatusCode, string(results)))
return return
@@ -215,13 +215,13 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
var imageResponse APIResponseImage var imageResponse APIResponseImage
err = json.Unmarshal(results, &imageResponse) err = json.Unmarshal(results, &imageResponse)
if err != nil { if err != nil {
err = errors.Wrap(code.CVResponseError, err = errors.Wrap(code.CVParseResponseError,
fmt.Sprintf("json unmarshal veDEM image response body error, response=%s", string(results))) fmt.Sprintf("json unmarshal veDEM image response body error, response=%s", string(results)))
return return
} }
if imageResponse.Code != 0 { if imageResponse.Code != 0 {
err = errors.Wrap(code.CVResponseError, err = errors.Wrap(code.CVParseResponseError,
fmt.Sprintf("unexpected response data code: %d, message: %s", fmt.Sprintf("unexpected response data code: %d, message: %s",
imageResponse.Code, imageResponse.Message)) imageResponse.Code, imageResponse.Message))
return return
+7 -12
View File
@@ -13,7 +13,9 @@ import (
"time" "time"
"github.com/cloudwego/eino/schema" "github.com/cloudwego/eino/schema"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/uixt/types" "github.com/httprunner/httprunner/v5/uixt/types"
"github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -60,20 +62,13 @@ const (
defaultTimeout = 30 * time.Second defaultTimeout = 30 * time.Second
) )
// Error types func validatePlanningInput(opts *PlanningOptions) error {
var (
ErrEmptyInstruction = fmt.Errorf("user instruction is empty")
ErrNoConversationHistory = fmt.Errorf("conversation history is empty")
ErrInvalidImageData = fmt.Errorf("invalid image data")
)
func validateInput(opts *PlanningOptions) error {
if opts.UserInstruction == "" { if opts.UserInstruction == "" {
return ErrEmptyInstruction return errors.Wrap(code.LLMPrepareRequestError, "user instruction is empty")
} }
if opts.Message == nil { if opts.Message == nil || opts.Message.Role == "" {
return ErrNoConversationHistory return errors.Wrap(code.LLMPrepareRequestError, "user message is empty")
} }
if opts.Message.Role == schema.User { if opts.Message.Role == schema.User {
@@ -81,7 +76,7 @@ func validateInput(opts *PlanningOptions) error {
if len(opts.Message.MultiContent) > 0 { if len(opts.Message.MultiContent) > 0 {
for _, content := range opts.Message.MultiContent { for _, content := range opts.Message.MultiContent {
if content.Type == schema.ChatMessagePartTypeImageURL && content.ImageURL == nil { if content.Type == schema.ChatMessagePartTypeImageURL && content.ImageURL == nil {
return ErrInvalidImageData return errors.Wrap(code.LLMPrepareRequestError, "invalid image data")
} }
} }
} }
+4 -4
View File
@@ -116,8 +116,8 @@ type Planner struct {
// Call performs UI planning using Vision Language Model // Call performs UI planning using Vision Language Model
func (p *Planner) Call(opts *PlanningOptions) (*PlanningResult, error) { func (p *Planner) Call(opts *PlanningOptions) (*PlanningResult, error) {
// validate input parameters // validate input parameters
if err := validateInput(opts); err != nil { if err := validatePlanningInput(opts); err != nil {
return nil, errors.Wrap(err, "validate input parameters failed") return nil, errors.Wrap(err, "validate planning parameters failed")
} }
// prepare prompt // prepare prompt
@@ -141,14 +141,14 @@ func (p *Planner) Call(opts *PlanningOptions) (*PlanningResult, error) {
log.Info().Float64("elapsed(s)", time.Since(startTime).Seconds()). log.Info().Float64("elapsed(s)", time.Since(startTime).Seconds()).
Str("model", p.config.Model).Msg("call model service") Str("model", p.config.Model).Msg("call model service")
if err != nil { if err != nil {
return nil, fmt.Errorf("request model service failed: %w", err) return nil, errors.Wrap(code.LLMRequestServiceError, err.Error())
} }
logResponse(resp) logResponse(resp)
// parse result // parse result
result, err := p.parseResult(resp, opts.Size) result, err := p.parseResult(resp, opts.Size)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "parse result failed") return nil, errors.Wrap(code.LLMParsePlanningResponseError, err.Error())
} }
// append assistant message // append assistant message
+17 -11
View File
@@ -12,6 +12,7 @@ import (
"testing" "testing"
"github.com/cloudwego/eino/schema" "github.com/cloudwego/eino/schema"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/uixt/types" "github.com/httprunner/httprunner/v5/uixt/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -265,7 +266,7 @@ func TestValidateInput(t *testing.T) {
Role: schema.User, Role: schema.User,
MultiContent: []schema.ChatMessagePart{ MultiContent: []schema.ChatMessagePart{
{ {
Type: "image_url", Type: schema.ChatMessagePartTypeImageURL,
ImageURL: &schema.ChatMessageImageURL{ ImageURL: &schema.ChatMessageImageURL{
URL: imageBase64, URL: imageBase64,
}, },
@@ -281,41 +282,46 @@ func TestValidateInput(t *testing.T) {
opts: &PlanningOptions{ opts: &PlanningOptions{
UserInstruction: "", UserInstruction: "",
Message: &schema.Message{ Message: &schema.Message{
Role: schema.User, Role: schema.User,
Content: "", MultiContent: []schema.ChatMessagePart{},
}, },
Size: size, Size: size,
}, },
wantErr: ErrEmptyInstruction, wantErr: code.LLMPrepareRequestError,
}, },
{ {
name: "empty conversation history", name: "empty conversation history",
opts: &PlanningOptions{ opts: &PlanningOptions{
UserInstruction: "点击立即卸载按钮", UserInstruction: "点击立即卸载按钮",
Message: &schema.Message{}, Message: &schema.Message{},
Size: size,
}, },
wantErr: ErrNoConversationHistory, wantErr: code.LLMPrepareRequestError,
}, },
{ {
name: "invalid image data", name: "invalid image data",
opts: &PlanningOptions{ opts: &PlanningOptions{
UserInstruction: "点击继续使用按钮", UserInstruction: "点击继续使用按钮",
Message: &schema.Message{ Message: &schema.Message{
Role: schema.User, Role: schema.User,
Content: "no image", MultiContent: []schema.ChatMessagePart{
{
Type: schema.ChatMessagePartTypeImageURL,
Text: "no image",
},
},
}, },
Size: size, Size: size,
}, },
wantErr: ErrInvalidImageData, wantErr: code.LLMPrepareRequestError,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := validateInput(tt.opts) err := validatePlanningInput(tt.opts)
if tt.wantErr != nil { if tt.wantErr != nil {
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, tt.wantErr, err)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
} }
@@ -418,7 +424,7 @@ func TestLoadImage(t *testing.T) {
// loadImage loads image and returns base64 encoded string // loadImage loads image and returns base64 encoded string
func loadImage(imagePath string) (base64Str string, size types.Size, err error) { func loadImage(imagePath string) (base64Str string, size types.Size, err error) {
// Read the image file // Read the image file
imageFile, err := os.OpenFile(imagePath, os.O_RDONLY, 0o666) imageFile, err := os.OpenFile(imagePath, os.O_RDONLY, 0o600)
if err != nil { if err != nil {
return "", types.Size{}, fmt.Errorf("failed to open image file: %w", err) return "", types.Size{}, fmt.Errorf("failed to open image file: %w", err)
} }
+4 -4
View File
@@ -129,8 +129,8 @@ type UITarsPlanner struct {
// Call performs UI planning using Vision Language Model // Call performs UI planning using Vision Language Model
func (p *UITarsPlanner) Call(opts *PlanningOptions) (*PlanningResult, error) { func (p *UITarsPlanner) Call(opts *PlanningOptions) (*PlanningResult, error) {
// validate input parameters // validate input parameters
if err := validateInput(opts); err != nil { if err := validatePlanningInput(opts); err != nil {
return nil, errors.Wrap(err, "validate input parameters failed") return nil, errors.Wrap(err, "validate planning parameters failed")
} }
// prepare prompt // prepare prompt
@@ -154,14 +154,14 @@ func (p *UITarsPlanner) Call(opts *PlanningOptions) (*PlanningResult, error) {
log.Info().Float64("elapsed(s)", time.Since(startTime).Seconds()). log.Info().Float64("elapsed(s)", time.Since(startTime).Seconds()).
Str("model", p.config.Model).Msg("call model service") Str("model", p.config.Model).Msg("call model service")
if err != nil { if err != nil {
return nil, fmt.Errorf("request model service failed: %w", err) return nil, errors.Wrap(code.LLMRequestServiceError, err.Error())
} }
logResponse(resp) logResponse(resp)
// parse result // parse result
result, err := p.parseResult(resp, opts.Size) result, err := p.parseResult(resp, opts.Size)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "parse result failed") return nil, errors.Wrap(code.LLMParsePlanningResponseError, err.Error())
} }
// append assistant message // append assistant message