mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-10 17:43:00 +08:00
feat: add status code for llm
This commit is contained in:
47
code/code.go
47
code/code.go
@@ -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,
|
||||
|
||||
@@ -1 +1 @@
|
||||
v5.0.0-beta-2504282012
|
||||
v5.0.0-beta-2504282106
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user