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

View File

@@ -96,16 +96,15 @@ var (
LoopActionNotFoundError = errors.New("loop action not found error") // 79
)
// AI related: [80, 90)
// CV related: [80, 90)
var (
CVEnvMissedError = errors.New("CV env missed error") // 80
CVRequestError = errors.New("CV prepare request error") // 81
CVServiceConnectionError = errors.New("CV service connect error") // 82
CVResponseError = errors.New("CV parse response error") // 83
CVResultNotFoundError = errors.New("CV result not found") // 84
CVEnvMissedError = errors.New("CV env missed error") // 80
CVPrepareRequestError = errors.New("CV prepare request error") // 81
CVRequestServiceError = errors.New("CV request service error") // 82
CVParseResponseError = errors.New("CV parse response error") // 83
CVResultNotFoundError = errors.New("CV result not found") // 84
LLMEnvMissedError = errors.New("LLM env missed error") // 85
StateUnknowError = errors.New("detect state failed") // 89
StateUnknowError = errors.New("detect state failed") // 85
)
// trackings related: [90, 100)
@@ -121,6 +120,15 @@ var (
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{
// environment
ConfigureError: 3,
@@ -194,14 +202,21 @@ var errorsMap = map[error]int{
MobileUIPopupError: 78,
LoopActionNotFoundError: 79,
// AI related
CVEnvMissedError: 80,
CVRequestError: 81,
CVServiceConnectionError: 82,
CVResponseError: 83,
CVResultNotFoundError: 84,
LLMEnvMissedError: 85,
StateUnknowError: 89,
// CV related
CVEnvMissedError: 80,
CVPrepareRequestError: 81,
CVRequestServiceError: 82,
CVParseResponseError: 83,
CVResultNotFoundError: 84,
StateUnknowError: 85,
// LLM related
LLMEnvMissedError: 110,
LLMPrepareRequestError: 111,
LLMRequestServiceError: 112,
LLMParsePlanningResponseError: 113,
LLMParseAssertionResponseError: 114,
// trackings related
TrackingGetError: 90,

View File

@@ -1 +1 @@
v5.0.0-beta-2504282012
v5.0.0-beta-2504282106

View File

@@ -9,6 +9,7 @@ import (
"github.com/cloudwego/eino-ext/components/model/ark"
"github.com/cloudwego/eino/schema"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/pkg/errors"
@@ -76,14 +77,21 @@ type AssertOptions struct {
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
func (a *UITarsAsserter) Assert(opts *AssertOptions) (*AssertionResponse, error) {
// Validate input parameters
if opts.Assertion == "" {
return nil, errors.New("assertion text is required")
}
if opts.Screenshot == "" {
return nil, errors.New("screenshot is required")
if err := validateAssertionInput(opts); err != nil {
return nil, errors.Wrap(err, "validate assertion parameters failed")
}
// 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()).
Str("model", a.config.Model).Msg("call model service for assertion")
if err != nil {
return nil, fmt.Errorf("request model service failed: %w", err)
return nil, errors.Wrap(code.LLMRequestServiceError, err.Error())
}
logResponse(resp)
// Parse result
result, err := parseAssertionResult(resp.Content)
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

View File

@@ -52,7 +52,7 @@ func (s *vedemCVService) ReadFromPath(imagePath string, opts ...option.ActionOpt
imageResult *CVResult, err error) {
imageBuf, err := os.ReadFile(imagePath)
if err != nil {
err = errors.Wrap(code.CVRequestError,
err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("read image file error: %v", err))
return
}
@@ -116,21 +116,21 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png")
if err != nil {
err = errors.Wrap(code.CVRequestError,
err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("create form file error: %v", err))
return
}
size, err := formWriter.Write(imageBuf.Bytes())
if err != nil {
err = errors.Wrap(code.CVRequestError,
err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("write form error: %v", err))
return
}
err = bodyWriter.Close()
if err != nil {
err = errors.Wrap(code.CVRequestError,
err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("close body writer error: %v", err))
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)
if err != nil {
err = errors.Wrap(code.CVRequestError,
err = errors.Wrap(code.CVPrepareRequestError,
fmt.Sprintf("construct request error: %v", err))
return
}
@@ -192,7 +192,7 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
break
}
if resp == nil {
err = code.CVServiceConnectionError
err = code.CVRequestServiceError
return
}
@@ -200,13 +200,13 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
results, err := io.ReadAll(resp.Body)
if err != nil {
err = errors.Wrap(code.CVResponseError,
err = errors.Wrap(code.CVParseResponseError,
fmt.Sprintf("read response body error: %v", err))
return
}
if resp.StatusCode != http.StatusOK {
err = errors.Wrap(code.CVResponseError,
err = errors.Wrap(code.CVParseResponseError,
fmt.Sprintf("unexpected response status code: %d, results: %v",
resp.StatusCode, string(results)))
return
@@ -215,13 +215,13 @@ func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.A
var imageResponse APIResponseImage
err = json.Unmarshal(results, &imageResponse)
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)))
return
}
if imageResponse.Code != 0 {
err = errors.Wrap(code.CVResponseError,
err = errors.Wrap(code.CVParseResponseError,
fmt.Sprintf("unexpected response data code: %d, message: %s",
imageResponse.Code, imageResponse.Message))
return

View File

@@ -13,7 +13,9 @@ import (
"time"
"github.com/cloudwego/eino/schema"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
@@ -60,20 +62,13 @@ const (
defaultTimeout = 30 * time.Second
)
// Error types
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 {
func validatePlanningInput(opts *PlanningOptions) error {
if opts.UserInstruction == "" {
return ErrEmptyInstruction
return errors.Wrap(code.LLMPrepareRequestError, "user instruction is empty")
}
if opts.Message == nil {
return ErrNoConversationHistory
if opts.Message == nil || opts.Message.Role == "" {
return errors.Wrap(code.LLMPrepareRequestError, "user message is empty")
}
if opts.Message.Role == schema.User {
@@ -81,7 +76,7 @@ func validateInput(opts *PlanningOptions) error {
if len(opts.Message.MultiContent) > 0 {
for _, content := range opts.Message.MultiContent {
if content.Type == schema.ChatMessagePartTypeImageURL && content.ImageURL == nil {
return ErrInvalidImageData
return errors.Wrap(code.LLMPrepareRequestError, "invalid image data")
}
}
}

View File

@@ -116,8 +116,8 @@ type Planner struct {
// Call performs UI planning using Vision Language Model
func (p *Planner) Call(opts *PlanningOptions) (*PlanningResult, error) {
// validate input parameters
if err := validateInput(opts); err != nil {
return nil, errors.Wrap(err, "validate input parameters failed")
if err := validatePlanningInput(opts); err != nil {
return nil, errors.Wrap(err, "validate planning parameters failed")
}
// prepare prompt
@@ -141,14 +141,14 @@ func (p *Planner) Call(opts *PlanningOptions) (*PlanningResult, error) {
log.Info().Float64("elapsed(s)", time.Since(startTime).Seconds()).
Str("model", p.config.Model).Msg("call model service")
if err != nil {
return nil, fmt.Errorf("request model service failed: %w", err)
return nil, errors.Wrap(code.LLMRequestServiceError, err.Error())
}
logResponse(resp)
// parse result
result, err := p.parseResult(resp, opts.Size)
if err != nil {
return nil, errors.Wrap(err, "parse result failed")
return nil, errors.Wrap(code.LLMParsePlanningResponseError, err.Error())
}
// append assistant message

View File

@@ -12,6 +12,7 @@ import (
"testing"
"github.com/cloudwego/eino/schema"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -265,7 +266,7 @@ func TestValidateInput(t *testing.T) {
Role: schema.User,
MultiContent: []schema.ChatMessagePart{
{
Type: "image_url",
Type: schema.ChatMessagePartTypeImageURL,
ImageURL: &schema.ChatMessageImageURL{
URL: imageBase64,
},
@@ -281,41 +282,46 @@ func TestValidateInput(t *testing.T) {
opts: &PlanningOptions{
UserInstruction: "",
Message: &schema.Message{
Role: schema.User,
Content: "",
Role: schema.User,
MultiContent: []schema.ChatMessagePart{},
},
Size: size,
},
wantErr: ErrEmptyInstruction,
wantErr: code.LLMPrepareRequestError,
},
{
name: "empty conversation history",
opts: &PlanningOptions{
UserInstruction: "点击立即卸载按钮",
Message: &schema.Message{},
Size: size,
},
wantErr: ErrNoConversationHistory,
wantErr: code.LLMPrepareRequestError,
},
{
name: "invalid image data",
opts: &PlanningOptions{
UserInstruction: "点击继续使用按钮",
Message: &schema.Message{
Role: schema.User,
Content: "no image",
Role: schema.User,
MultiContent: []schema.ChatMessagePart{
{
Type: schema.ChatMessagePartTypeImageURL,
Text: "no image",
},
},
},
Size: size,
},
wantErr: ErrInvalidImageData,
wantErr: code.LLMPrepareRequestError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateInput(tt.opts)
err := validatePlanningInput(tt.opts)
if tt.wantErr != nil {
assert.Error(t, err)
assert.Equal(t, tt.wantErr, err)
} else {
assert.NoError(t, err)
}
@@ -418,7 +424,7 @@ func TestLoadImage(t *testing.T) {
// loadImage loads image and returns base64 encoded string
func loadImage(imagePath string) (base64Str string, size types.Size, err error) {
// 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 {
return "", types.Size{}, fmt.Errorf("failed to open image file: %w", err)
}

View File

@@ -129,8 +129,8 @@ type UITarsPlanner struct {
// Call performs UI planning using Vision Language Model
func (p *UITarsPlanner) Call(opts *PlanningOptions) (*PlanningResult, error) {
// validate input parameters
if err := validateInput(opts); err != nil {
return nil, errors.Wrap(err, "validate input parameters failed")
if err := validatePlanningInput(opts); err != nil {
return nil, errors.Wrap(err, "validate planning parameters failed")
}
// prepare prompt
@@ -154,14 +154,14 @@ func (p *UITarsPlanner) Call(opts *PlanningOptions) (*PlanningResult, error) {
log.Info().Float64("elapsed(s)", time.Since(startTime).Seconds()).
Str("model", p.config.Model).Msg("call model service")
if err != nil {
return nil, fmt.Errorf("request model service failed: %w", err)
return nil, errors.Wrap(code.LLMRequestServiceError, err.Error())
}
logResponse(resp)
// parse result
result, err := p.parseResult(resp, opts.Size)
if err != nil {
return nil, errors.Wrap(err, "parse result failed")
return nil, errors.Wrap(code.LLMParsePlanningResponseError, err.Error())
}
// append assistant message