Go: add balance command (#14262)

### What problem does this PR solve?

```
RAGFlow(user)> list supported models from 'moonshot' 'test';
+---------------------------------+
| model_name                      |
+---------------------------------+
| moonshot-v1-32k-vision-preview  |
| kimi-k2.6                       |
| moonshot-v1-8k                  |
| moonshot-v1-auto                |
| moonshot-v1-128k                |
| moonshot-v1-32k                 |
| kimi-k2.5                       |
| moonshot-v1-8k-vision-preview   |
| moonshot-v1-128k-vision-preview |
+---------------------------------+
RAGFlow(user)> show balance from 'moonshot' 'test';
+---------+----------+
| balance | currency |
+---------+----------+
| 0       | CNY      |
+---------+----------+
```

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-04-21 21:31:50 +08:00
committed by GitHub
parent 2d05475693
commit 74b44e1aa3
15 changed files with 329 additions and 18 deletions

View File

@@ -236,6 +236,8 @@ func (c *RAGFlowClient) ExecuteUserCommand(cmd *Command) (ResponseIf, error) {
return c.ListProviderInstances(cmd)
case "show_provider_instance":
return c.ShowProviderInstance(cmd)
case "show_instance_balance":
return c.ShowInstanceBalance(cmd)
case "alter_provider_instance":
return c.AlterProviderInstance(cmd)
case "drop_provider_instance":

View File

@@ -369,6 +369,8 @@ func (l *Lexer) lookupIdent(ident string) Token {
return Token{Type: TokenSupported, Value: ident}
case "NAME":
return Token{Type: TokenName, Value: ident}
case "BALANCE":
return Token{Type: TokenBalance, Value: ident}
case "INSTANCE":
return Token{Type: TokenInstance, Value: ident}
case "INSTANCES":

View File

@@ -109,6 +109,7 @@ const (
TokenVector
TokenSize
TokenName // For ALTER PROVIDER <name> NAME <new_name>
TokenBalance
TokenInstance
TokenInstances
TokenDisable

View File

@@ -1234,6 +1234,47 @@ func (c *RAGFlowClient) ShowProviderInstance(cmd *Command) (ResponseIf, error) {
return &result, nil
}
// ShowInstanceBalance shows balance of a specific instance
// SHOW BALANCE FROM PROVIDER <provider_name> <instance_name>
func (c *RAGFlowClient) ShowInstanceBalance(cmd *Command) (ResponseIf, error) {
if c.ServerType != "user" {
return nil, fmt.Errorf("this command is only allowed in USER mode")
}
instanceName, ok := cmd.Params["instance_name"].(string)
if !ok {
return nil, fmt.Errorf("instance name not provided")
}
providerName, ok := cmd.Params["provider_name"].(string)
if !ok {
return nil, fmt.Errorf("provider name not provided")
}
url := fmt.Sprintf("/providers/%s/instances/%s/balance", providerName, instanceName)
resp, err := c.HTTPClient.Request("GET", url, true, "web", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to show instance: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to show instance: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
var result CommonDataResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("show instance failed: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("%s", result.Message)
}
result.Duration = resp.Duration
return &result, nil
}
// AlterProviderInstance renames a provider instance
// ALTER INSTANCE <name> NAME <new_name> FROM PROVIDER <name>
func (c *RAGFlowClient) AlterProviderInstance(cmd *Command) (ResponseIf, error) {

View File

@@ -352,6 +352,8 @@ func (p *Parser) parseShowCommand() (*Command, error) {
return p.parseShowModel()
case TokenInstance:
return p.parseShowInstance()
case TokenBalance:
return p.parseShowBalance()
default:
return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value)
}
@@ -1301,6 +1303,45 @@ func (p *Parser) parseShowInstance() (*Command, error) {
return cmd, nil
}
// parseShowInstance parses SHOW BALANCE FROM <provider_name> <instance_name>
func (p *Parser) parseShowBalance() (*Command, error) {
p.nextToken() // consume INSTANCE
if p.curToken.Type != TokenFrom {
return nil, fmt.Errorf("expected FROM")
}
p.nextToken()
if p.curToken.Type != TokenQuotedString {
return nil, fmt.Errorf("expected provider name after FROM PROVIDER")
}
providerName, err := p.parseQuotedString()
if err != nil {
return nil, fmt.Errorf("expected provider name after FROM PROVIDER: %w", err)
}
p.nextToken()
if p.curToken.Type != TokenQuotedString {
return nil, fmt.Errorf("expected instance name")
}
instanceName, err := p.parseQuotedString()
if err != nil {
return nil, fmt.Errorf("expected instance name: %w", err)
}
p.nextToken()
cmd := NewCommand("show_instance_balance")
cmd.Params["instance_name"] = instanceName
cmd.Params["provider_name"] = providerName
p.nextToken()
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
// parseAlterInstance parses ALTER INSTANCE <name> NAME <new_name> FROM PROVIDER <name> command
func (p *Parser) parseAlterInstance() (*Command, error) {
p.nextToken() // consume INSTANCE

View File

@@ -49,6 +49,10 @@ func NewDeepSeekModel(baseURL map[string]string, urlSuffix URLSuffix) *DeepSeekM
}
}
func (z *DeepSeekModel) Name() string {
return "deepseek"
}
// Chat sends a message and returns response
func (z *DeepSeekModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
return nil, fmt.Errorf("not implemented")
@@ -145,3 +149,7 @@ func (z *DeepSeekModel) ListModels(apiConfig *APIConfig) ([]string, error) {
return models, nil
}
func (z *DeepSeekModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
}

View File

@@ -34,6 +34,10 @@ func NewDummyModel(baseURL map[string]string, urlSuffix URLSuffix) *DummyModel {
}
}
func (z *DummyModel) Name() string {
return "dummy"
}
// Chat sends a message and returns response
func (z *DummyModel) Chat(modelName, message *string, apiConfig *APIConfig, modelConfig *ChatConfig) (*ChatResponse, error) {
return nil, fmt.Errorf("not implemented")
@@ -52,3 +56,7 @@ func (z *DummyModel) EncodeToEmbedding(modelName *string, texts []string, apiCon
func (z *DummyModel) ListModels(apiConfig *APIConfig) ([]string, error) {
return nil, fmt.Errorf("not implemented")
}
func (z *DummyModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}

View File

@@ -38,7 +38,7 @@ func (f *ModelFactory) CreateModelDriver(providerName string, baseURL map[string
case "deepseek":
return NewDeepSeekModel(baseURL, urlSuffix), nil
case "moonshot":
return NewMooshotModel(baseURL, urlSuffix), nil
return NewMoonshotModel(baseURL, urlSuffix), nil
default:
return NewDummyModel(baseURL, urlSuffix), nil
}

View File

@@ -25,16 +25,16 @@ import (
"time"
)
// MooshotModel implements ModelDriver for Mooshot
type MooshotModel struct {
// MoonshotModel implements ModelDriver for Moonshot
type MoonshotModel struct {
BaseURL map[string]string
URLSuffix URLSuffix
httpClient *http.Client // Reusable HTTP client with connection pool
}
// NewMooshotModel creates a new Mooshot model instance
func NewMooshotModel(baseURL map[string]string, urlSuffix URLSuffix) *MooshotModel {
return &MooshotModel{
// NewMoonshotModel creates a new Moonshot model instance
func NewMoonshotModel(baseURL map[string]string, urlSuffix URLSuffix) *MoonshotModel {
return &MoonshotModel{
BaseURL: baseURL,
URLSuffix: urlSuffix,
httpClient: &http.Client{
@@ -49,22 +49,26 @@ func NewMooshotModel(baseURL map[string]string, urlSuffix URLSuffix) *MooshotMod
}
}
func (z *MoonshotModel) Name() string {
return "moonshot"
}
// Chat sends a message and returns response
func (z *MooshotModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
func (z *MoonshotModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
return nil, fmt.Errorf("not implemented")
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
func (z *MooshotModel) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
func (z *MoonshotModel) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
return fmt.Errorf("not implemented")
}
// EncodeToEmbedding encodes a list of texts into embeddings
func (z *MooshotModel) EncodeToEmbedding(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([][]float64, error) {
func (z *MoonshotModel) EncodeToEmbedding(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([][]float64, error) {
return nil, fmt.Errorf("not implemented")
}
func (z *MooshotModel) ListModels(apiConfig *APIConfig) ([]string, error) {
func (z *MoonshotModel) ListModels(apiConfig *APIConfig) ([]string, error) {
var region = "default"
if apiConfig.Region != nil {
region = *apiConfig.Region
@@ -80,7 +84,7 @@ func (z *MooshotModel) ListModels(apiConfig *APIConfig) ([]string, error) {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req, err := http.NewRequest("GET", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
@@ -109,10 +113,70 @@ func (z *MooshotModel) ListModels(apiConfig *APIConfig) ([]string, error) {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
models, ok := result["models"].([]string)
if !ok || len(models) == 0 {
return nil, fmt.Errorf("no models in response")
// convert result["data"] to []map[string]interface{}
models := make([]string, 0)
for _, model := range result["data"].([]interface{}) {
modelMap := model.(map[string]interface{})
modelName := modelMap["id"].(string)
models = append(models, modelName)
}
return models, nil
}
func (z *MoonshotModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
var region = "default"
if apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Balance)
// Build request body
reqBody := map[string]interface{}{}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("GET", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
resp, err := z.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
// Parse response
var result map[string]interface{}
if err = json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
data := result["data"].(map[string]interface{})
balance := data["available_balance"].(float64)
var response = map[string]interface{}{
"balance": balance,
"currency": "CNY",
}
return response, nil
}

View File

@@ -2,6 +2,8 @@ package models
// EmbeddingModel interface for embedding models
type ModelDriver interface {
Name() string
// Chat sends a message and returns response
Chat(modelName, message *string, apiConfig *APIConfig, modelConfig *ChatConfig) (*ChatResponse, error)
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
@@ -10,6 +12,8 @@ type ModelDriver interface {
EncodeToEmbedding(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([][]float64, error)
// List suppported models
ListModels(apiConfig *APIConfig) ([]string, error)
Balance(apiConfig *APIConfig) (map[string]interface{}, error)
}
type ChatResponse struct {

View File

@@ -52,6 +52,10 @@ func NewZhipuAIModel(baseURL map[string]string, urlSuffix URLSuffix) *ZhipuAIMod
}
}
func (z *ZhipuAIModel) Name() string {
return "zhipu"
}
// Chat sends a message and returns response
func (z *ZhipuAIModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if message == nil {
@@ -281,7 +285,7 @@ func (z *ZhipuAIModel) ChatStreamlyWithSender(modelName, message *string, apiCon
// Parse the JSON event
var event map[string]interface{}
if err := json.Unmarshal([]byte(data), &event); err != nil {
if err = json.Unmarshal([]byte(data), &event); err != nil {
continue
}
@@ -322,7 +326,7 @@ func (z *ZhipuAIModel) ChatStreamlyWithSender(modelName, message *string, apiCon
// Send [DONE] marker for OpenAI compatibility
endOfStream := "[DONE]"
if err := sender(&endOfStream, nil); err != nil {
if err = sender(&endOfStream, nil); err != nil {
return err
}
@@ -377,7 +381,7 @@ func (z *ZhipuAIModel) EncodeToEmbedding(modelName *string, texts []string, apiC
// Parse response
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
if err = json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
@@ -415,5 +419,9 @@ func (z *ZhipuAIModel) EncodeToEmbedding(modelName *string, texts []string, apiC
}
func (z *ZhipuAIModel) ListModels(apiConfig *APIConfig) ([]string, error) {
return nil, fmt.Errorf("no such method")
return nil, fmt.Errorf("%s, no such method", z.Name())
}
func (z *ZhipuAIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
}

View File

@@ -355,6 +355,44 @@ func (h *ProviderHandler) ShowProviderInstance(c *gin.Context) {
})
}
func (h *ProviderHandler) ShowInstanceBalance(c *gin.Context) {
providerName := c.Param("provider_name")
if providerName == "" {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "Provider name is required",
})
return
}
instanceName := c.Param("instance_name")
if instanceName == "" {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "Instance name is required",
})
return
}
userID := c.GetString("user_id")
// Get tenant ID from user
balance, errorCode, err := h.modelProviderService.ShowInstanceBalance(providerName, instanceName, userID)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": errorCode,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": balance,
})
}
type AlterProviderInstanceRequest struct {
LLMName string `json:"llm_name" binding:"required"`
}

View File

@@ -212,6 +212,7 @@ func (r *Router) Setup(engine *gin.Engine) {
provider.POST("/:provider_name/instances", r.providerHandler.CreateProviderInstance)
provider.GET("/:provider_name/instances", r.providerHandler.ListProviderInstances)
provider.GET("/:provider_name/instances/:instance_name", r.providerHandler.ShowProviderInstance)
provider.GET("/:provider_name/instances/:instance_name/balance", r.providerHandler.ShowInstanceBalance)
provider.PUT("/:provider_name/instances/:instance_name", r.providerHandler.AlterProviderInstance)
provider.DELETE("/:provider_name/instances", r.providerHandler.DropProviderInstance)
provider.GET("/:provider_name/instances/:instance_name/models", r.providerHandler.ListInstanceModels)

View File

@@ -423,6 +423,76 @@ func (m *ModelProviderService) ShowProviderInstance(providerName, instanceName,
return result, common.CodeSuccess, nil
}
func (m *ModelProviderService) ShowInstanceBalance(providerName, instanceName, userID string) (map[string]interface{}, common.ErrorCode, error) {
// Get tenant ID from user
tenants, err := m.userTenantDAO.GetByUserIDAndRole(userID, "owner")
if err != nil {
return nil, common.CodeServerError, err
}
if len(tenants) == 0 {
return nil, common.CodeNotFound, errors.New("user has no tenants")
}
tenantID := tenants[0].TenantID
// Check if provider exists
provider, err := m.modelProviderDAO.GetByTenantIDAndProviderName(tenantID, providerName)
if err != nil {
return nil, common.CodeServerError, err
}
instance, err := m.modelInstanceDAO.GetByProviderIDAndInstanceName(provider.ID, instanceName)
if err != nil {
return nil, common.CodeServerError, err
}
providerInfo := dao.GetModelProviderManager().FindProvider(providerName)
if providerInfo == nil {
return nil, common.CodeServerError, fmt.Errorf("provider %s not found", providerName)
}
var extra map[string]string
err = json.Unmarshal([]byte(instance.Extra), &extra)
if err != nil {
return nil, common.CodeServerError, err
}
apiConfig := &modelModule.APIConfig{
ApiKey: nil,
Region: nil,
}
region := extra["region"]
apiConfig.Region = &region
apiConfig.ApiKey = &instance.APIKey
var result map[string]interface{}
result, err = providerInfo.ModelDriver.Balance(apiConfig)
if err != nil {
return nil, common.CodeServerError, err
}
return result, common.CodeSuccess, nil
// convert instance.Extra (json string) to map
//var extra map[string]string
//err = json.Unmarshal([]byte(instance.Extra), &extra)
//if err != nil {
// return nil, common.CodeServerError, err
//}
//
//result := map[string]interface{}{
// "id": instance.ID,
// "instanceName": instance.InstanceName,
// "providerID": instance.ProviderID,
// "status": instance.Status,
// "region": extra["region"],
//}
//
//return result, common.CodeSuccess, nil
}
func (m *ModelProviderService) AlterProviderInstance(providerName, instanceName, newInstanceName, apiKey, userID string) (common.ErrorCode, error) {
return common.CodeSuccess, nil
}

23
uv.lock generated
View File

@@ -1745,6 +1745,27 @@ version = "0.8.3"
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/51/0b/c0f53a14317b304e2e93b29a831b0c83306caae9af7f0e2e037d17c4f63f/datrie-0.8.3.tar.gz", hash = "sha256:ea021ad4c8a8bf14e08a71c7872a622aa399a510f981296825091c7ca0436e80" }
[[package]]
name = "debugpy"
version = "1.8.20"
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33" }
wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d" },
{ url = "https://mirrors.aliyun.com/pypi/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b" },
{ url = "https://mirrors.aliyun.com/pypi/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390" },
{ url = "https://mirrors.aliyun.com/pypi/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3" },
{ url = "https://mirrors.aliyun.com/pypi/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a" },
{ url = "https://mirrors.aliyun.com/pypi/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf" },
{ url = "https://mirrors.aliyun.com/pypi/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393" },
{ url = "https://mirrors.aliyun.com/pypi/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7" },
{ url = "https://mirrors.aliyun.com/pypi/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173" },
{ url = "https://mirrors.aliyun.com/pypi/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad" },
{ url = "https://mirrors.aliyun.com/pypi/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f" },
{ url = "https://mirrors.aliyun.com/pypi/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be" },
{ url = "https://mirrors.aliyun.com/pypi/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7" },
]
[[package]]
name = "decorator"
version = "5.2.1"
@@ -6549,6 +6570,7 @@ dependencies = [
{ name = "cohere" },
{ name = "crawl4ai" },
{ name = "dashscope" },
{ name = "debugpy" },
{ name = "deepl" },
{ name = "demjson3" },
{ name = "discord-py" },
@@ -6692,6 +6714,7 @@ requires-dist = [
{ name = "cohere", specifier = "==5.6.2" },
{ name = "crawl4ai", specifier = ">=0.4.0,<1.0.0" },
{ name = "dashscope", specifier = "==1.25.11" },
{ name = "debugpy", specifier = ">=1.8.13" },
{ name = "deepl", specifier = "==1.18.0" },
{ name = "demjson3", specifier = "==3.0.6" },
{ name = "discord-py", specifier = "==2.3.2" },