merge tenders which have the same procedure id - worker performance fix
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
# GLM SDK
|
||||
|
||||
A Go SDK for interacting with the GLM (Zhipu AI) API, specifically designed for chat completions and text generation tasks.
|
||||
|
||||
## Features
|
||||
|
||||
- **Chat Completions**: Perform conversational AI interactions
|
||||
- **Streaming Support**: Real-time streaming responses
|
||||
- **Translation Helper**: Built-in convenience methods for translation tasks
|
||||
- **Model Management**: List available models
|
||||
- **Health Checks**: Service availability monitoring
|
||||
- **Retry Logic**: Automatic retry with configurable attempts
|
||||
- **Structured Logging**: Comprehensive logging support
|
||||
- **Error Handling**: Detailed error types and handling
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get tm/pkg/glm
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"tm/pkg/glm"
|
||||
"tm/pkg/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create configuration
|
||||
config := &glm.Config{
|
||||
APIKey: "your-api-key-here",
|
||||
BaseURL: "https://api.z.ai",
|
||||
}
|
||||
|
||||
// Create logger (you can use your preferred logger)
|
||||
logger := logger.NewLogger()
|
||||
|
||||
// Create SDK instance
|
||||
sdk, err := glm.New(config, logger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Simple chat
|
||||
ctx := context.Background()
|
||||
response, err := sdk.QuickChat(ctx, "Hello, how are you?")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(response)
|
||||
|
||||
// Translation example (matching the provided curl request)
|
||||
translated, err := sdk.Translate(ctx, "IT-stöd för projekt- och portföljstyrning", "Swedish", "English")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(translated)
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```go
|
||||
config := &glm.Config{
|
||||
BaseURL: "https://api.z.ai", // API base URL
|
||||
APIKey: "your-api-key", // Authentication key
|
||||
Timeout: 300 * time.Second, // Request timeout
|
||||
RetryAttempts: 3, // Number of retries
|
||||
RetryDelay: 2 * time.Second, // Delay between retries
|
||||
EnableLogging: true, // Enable request logging
|
||||
UserAgent: "MyApp/1.0", // Custom user agent
|
||||
DefaultModel: "glm-4.5", // Default model
|
||||
DefaultMaxTokens: 4096, // Default max tokens
|
||||
DefaultTopP: 1.0, // Default top_p
|
||||
DefaultTemperature: 0.7, // Default temperature
|
||||
EnableStreaming: false, // Enable streaming by default
|
||||
EnableThinking: false, // Enable thinking mode
|
||||
}
|
||||
```
|
||||
|
||||
## API Methods
|
||||
|
||||
### Chat Completion
|
||||
|
||||
```go
|
||||
req := &glm.ChatCompletionRequest{
|
||||
Model: "glm-4.5",
|
||||
Messages: []glm.Message{
|
||||
{
|
||||
Role: glm.MessageRoleSystem,
|
||||
Content: "You are a helpful assistant.",
|
||||
},
|
||||
{
|
||||
Role: glm.MessageRoleUser,
|
||||
Content: "Explain quantum computing.",
|
||||
},
|
||||
},
|
||||
MaxTokens: 4096,
|
||||
TopP: 1.0,
|
||||
Temperature: 0.7,
|
||||
FrequencyPenalty: 0,
|
||||
PresencePenalty: 0,
|
||||
Thinking: &glm.Thinking{
|
||||
Type: "false",
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := sdk.ChatCompletion(ctx, req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.Choices[0].Message.Content)
|
||||
```
|
||||
|
||||
### Streaming Chat Completion
|
||||
|
||||
```go
|
||||
req := &glm.ChatCompletionRequest{
|
||||
Model: "glm-4.5",
|
||||
Messages: []glm.Message{
|
||||
{
|
||||
Role: glm.MessageRoleUser,
|
||||
Content: "Tell me a long story.",
|
||||
},
|
||||
},
|
||||
Stream: true,
|
||||
}
|
||||
|
||||
err := sdk.StreamChatCompletion(ctx, req, func(chunk *glm.StreamChatCompletionResponse) error {
|
||||
if len(chunk.Choices) > 0 {
|
||||
fmt.Print(chunk.Choices[0].Delta.Content)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
### Convenience Methods
|
||||
|
||||
```go
|
||||
// Simple chat
|
||||
response, err := sdk.QuickChat(ctx, "Hello!")
|
||||
|
||||
// Chat with system prompt
|
||||
response, err := sdk.QuickChatWithSystem(ctx, "You are a poet.", "Write a haiku about coding.")
|
||||
|
||||
// Translation (matching the curl example)
|
||||
translated, err := sdk.Translate(ctx, "IT-stöd för projekt- och portföljstyrning", "Swedish", "English")
|
||||
|
||||
// Streaming chat
|
||||
err := sdk.StreamQuickChat(ctx, "Tell me a story", func(chunk string) error {
|
||||
fmt.Print(chunk)
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The SDK provides detailed error types:
|
||||
|
||||
```go
|
||||
resp, err := sdk.ChatCompletion(ctx, req)
|
||||
if err != nil {
|
||||
var glmErr *glm.Error
|
||||
if errors.As(err, &glmErr) {
|
||||
switch glmErr.Code {
|
||||
case glm.ErrCodeAuthentication:
|
||||
// Handle auth errors
|
||||
case glm.ErrCodeRateLimit:
|
||||
// Handle rate limits
|
||||
case glm.ErrCodeServerError:
|
||||
// Handle server errors
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
You can configure the SDK using environment variables:
|
||||
|
||||
```bash
|
||||
export GLM_BASE_URL="https://api.z.ai"
|
||||
export GLM_API_KEY="your-api-key"
|
||||
export GLM_TIMEOUT="300s"
|
||||
export GLM_RETRY_ATTEMPTS="3"
|
||||
export GLM_DEFAULT_MODEL="glm-4.5"
|
||||
export GLM_DEFAULT_MAX_TOKENS="4096"
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
go test ./pkg/glm/...
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This SDK is part of the Tender Management system and follows the same licensing terms.
|
||||
@@ -1,280 +0,0 @@
|
||||
# GLM SDK Usage Guide
|
||||
|
||||
## Matching the Provided Curl Request
|
||||
|
||||
The SDK can replicate the exact curl request provided:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"tm/pkg/glm"
|
||||
"tm/pkg/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := &glm.Config{
|
||||
BaseURL: "https://api.z.ai",
|
||||
APIKey: "4e267ac7897f4a789cacb3dbee15f312.coJtK3397qFv16x5",
|
||||
}
|
||||
|
||||
logger := logger.NewLogger()
|
||||
sdk, err := glm.New(config, logger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
req := &glm.ChatCompletionRequest{
|
||||
Model: "glm-4.5",
|
||||
Messages: []glm.Message{
|
||||
{
|
||||
Role: glm.MessageRoleSystem,
|
||||
Content: "You are a high-precision translator specializing in professional tender documents. Always respond with only the translated English text — no explanations, notes, or markdown formatting.",
|
||||
},
|
||||
{
|
||||
Role: glm.MessageRoleUser,
|
||||
Content: "Translate the following text from Swedish to English accurately and professionally:\n\"IT-stöd för projekt- och portföljstyrning\"",
|
||||
},
|
||||
},
|
||||
Thinking: &glm.Thinking{
|
||||
Type: "false",
|
||||
},
|
||||
MaxTokens: 4096,
|
||||
TopP: 1,
|
||||
FrequencyPenalty: 0,
|
||||
PresencePenalty: 0,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
resp, err := sdk.ChatCompletion(ctx, req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(resp.Choices) > 0 {
|
||||
fmt.Println(resp.Choices[0].Message.Content)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using the Translate Convenience Method
|
||||
|
||||
For the specific translation use case:
|
||||
|
||||
```go
|
||||
translated, err := sdk.Translate(ctx, "IT-stöd för projekt- och portföljstyrning", "Swedish", "English")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(translated) // Output: "IT support for project and portfolio management"
|
||||
```
|
||||
|
||||
### Custom Translation Options
|
||||
|
||||
For more control over translation parameters:
|
||||
|
||||
```go
|
||||
opts := &glm.TranslateOptions{
|
||||
Model: "glm-4.5",
|
||||
MaxTokens: 1024,
|
||||
Temperature: 0.1, // Very low for consistent translations
|
||||
FrequencyPenalty: 0.2,
|
||||
PresencePenalty: 0.2,
|
||||
EnableThinking: &[]bool{false}[0],
|
||||
}
|
||||
|
||||
translated, err := sdk.TranslateWithOptions(ctx, "IT-stöd för projekt- och portföljstyrning", "Swedish", "English", opts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(translated)
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### GLM SDK Configuration
|
||||
|
||||
```go
|
||||
config := &glm.Config{
|
||||
BaseURL: "https://api.z.ai",
|
||||
APIKey: "your-api-key",
|
||||
Timeout: 300 * time.Second,
|
||||
RetryAttempts: 3,
|
||||
RetryDelay: 2 * time.Second,
|
||||
EnableLogging: true,
|
||||
DefaultModel: "glm-4.5",
|
||||
DefaultMaxTokens: 4096,
|
||||
DefaultTopP: 1.0,
|
||||
DefaultTemperature: 0.7,
|
||||
DefaultFrequencyPenalty: 0.0,
|
||||
DefaultPresencePenalty: 0.0,
|
||||
EnableStreaming: false,
|
||||
EnableThinking: false,
|
||||
|
||||
// Translation-specific configuration
|
||||
TranslationMaxTokens: 2048,
|
||||
TranslationTemperature: 0.3,
|
||||
TranslationFrequencyPenalty: 0.1,
|
||||
TranslationPresencePenalty: 0.1,
|
||||
TranslationEnableThinking: false,
|
||||
}
|
||||
```
|
||||
|
||||
### Bootstrap Configuration (Application Level)
|
||||
|
||||
Configure translation parameters via environment variables in your application bootstrap:
|
||||
|
||||
```go
|
||||
// cmd/worker/bootstrap/config.go
|
||||
type GLMConfig struct {
|
||||
// ... basic GLM settings ...
|
||||
|
||||
// Translation options configured via environment variables
|
||||
TranslateOptions TranslateOptions
|
||||
}
|
||||
|
||||
type TranslateOptions struct {
|
||||
MaxTokens int `env:"GLM_TRANSLATE_MAX_TOKENS" default:"2048"`
|
||||
Temperature float64 `env:"GLM_TRANSLATE_TEMPERATURE" default:"0.3"`
|
||||
FrequencyPenalty float64 `env:"GLM_TRANSLATE_FREQUENCY_PENALTY" default:"0.1"`
|
||||
PresencePenalty float64 `env:"GLM_TRANSLATE_PRESENCE_PENALTY" default:"0.1"`
|
||||
EnableThinking bool `env:"GLM_TRANSLATE_ENABLE_THINKING" default:"false"`
|
||||
}
|
||||
```
|
||||
|
||||
Set these environment variables to customize translation behavior:
|
||||
|
||||
```bash
|
||||
export GLM_TRANSLATE_MAX_TOKENS=1024
|
||||
export GLM_TRANSLATE_TEMPERATURE=0.2
|
||||
export GLM_TRANSLATE_FREQUENCY_PENALTY=0.2
|
||||
export GLM_TRANSLATE_PRESENCE_PENALTY=0.2
|
||||
export GLM_TRANSLATE_ENABLE_THINKING=false
|
||||
```
|
||||
|
||||
## Error Handling Examples
|
||||
|
||||
```go
|
||||
resp, err := sdk.ChatCompletion(ctx, req)
|
||||
if err != nil {
|
||||
var glmErr *glm.Error
|
||||
if errors.As(err, &glmErr) {
|
||||
switch glmErr.Code {
|
||||
case glm.ErrCodeAuthentication:
|
||||
log.Printf("Authentication failed: %s", glmErr.Message)
|
||||
// Handle authentication error (e.g., refresh token)
|
||||
case glm.ErrCodeRateLimit:
|
||||
log.Printf("Rate limit exceeded: %s", glmErr.Message)
|
||||
// Implement backoff strategy
|
||||
case glm.ErrCodeInvalidRequest:
|
||||
log.Printf("Invalid request: %s", glmErr.Message)
|
||||
// Fix request parameters
|
||||
case glm.ErrCodeServerError:
|
||||
log.Printf("Server error: %s", glmErr.Message)
|
||||
// Retry or alert administrators
|
||||
default:
|
||||
log.Printf("Unknown error: %s", glmErr.Error())
|
||||
}
|
||||
} else {
|
||||
log.Printf("Non-GLM error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
## Streaming with Progress Tracking
|
||||
|
||||
```go
|
||||
var fullResponse strings.Builder
|
||||
|
||||
err := sdk.StreamChatCompletion(ctx, req, func(chunk *glm.StreamChatCompletionResponse) error {
|
||||
if len(chunk.Choices) > 0 {
|
||||
content := chunk.Choices[0].Delta.Content
|
||||
fmt.Print(content) // Print as it comes
|
||||
fullResponse.WriteString(content)
|
||||
}
|
||||
|
||||
// Check for completion
|
||||
if len(chunk.Choices) > 0 && chunk.Choices[0].FinishReason != nil {
|
||||
fmt.Printf("\n\nFinished with reason: %s\n", *chunk.Choices[0].FinishReason)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Complete response: %s\n", fullResponse.String())
|
||||
```
|
||||
|
||||
## Integration with Tender Management System
|
||||
|
||||
```go
|
||||
// In your service layer
|
||||
type TranslationService struct {
|
||||
glmSDK *glm.SDK
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewTranslationService(glmSDK *glm.SDK, logger logger.Logger) *TranslationService {
|
||||
return &TranslationService{
|
||||
glmSDK: glmSDK,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TranslationService) TranslateTenderDocument(ctx context.Context, text, sourceLang, targetLang string) (string, error) {
|
||||
s.logger.Info("Translating tender document", map[string]interface{}{
|
||||
"text_length": len(text),
|
||||
"source_lang": sourceLang,
|
||||
"target_lang": targetLang,
|
||||
})
|
||||
|
||||
translated, err := s.glmSDK.Translate(ctx, text, sourceLang, targetLang)
|
||||
if err != nil {
|
||||
s.logger.Error("Translation failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.logger.Info("Translation completed", map[string]interface{}{
|
||||
"translated_length": len(translated),
|
||||
})
|
||||
|
||||
return translated, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Health Monitoring
|
||||
|
||||
```go
|
||||
// Check service health
|
||||
err := sdk.Health(ctx)
|
||||
if err != nil {
|
||||
log.Printf("GLM service is unhealthy: %v", err)
|
||||
// Alert monitoring system
|
||||
} else {
|
||||
log.Println("GLM service is healthy")
|
||||
}
|
||||
```
|
||||
|
||||
## Model Management
|
||||
|
||||
```go
|
||||
// List available models
|
||||
models, err := sdk.ListModels(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Available models:")
|
||||
for _, model := range models.Data {
|
||||
fmt.Printf("- %s (owned by %s)\n", model.ID, model.OwnedBy)
|
||||
}
|
||||
```
|
||||
@@ -1,226 +0,0 @@
|
||||
package glm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"tm/pkg/logger"
|
||||
)
|
||||
|
||||
// Client implements the HTTPClient interface for GLM API
|
||||
type Client struct {
|
||||
config *Config
|
||||
httpClient *http.Client
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewClient creates a new GLM HTTP client
|
||||
func NewClient(config *Config, logger logger.Logger) (*Client, error) {
|
||||
if config == nil {
|
||||
config = DefaultConfig()
|
||||
}
|
||||
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Timeout: config.Timeout,
|
||||
}
|
||||
|
||||
return &Client{
|
||||
config: config,
|
||||
httpClient: httpClient,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Post performs a POST request to the GLM API
|
||||
func (c *Client) Post(ctx context.Context, endpoint string, body interface{}, result interface{}) error {
|
||||
return c.makeRequest(ctx, "POST", endpoint, body, result)
|
||||
}
|
||||
|
||||
// Get performs a GET request to the GLM API
|
||||
func (c *Client) Get(ctx context.Context, endpoint string, result interface{}) error {
|
||||
return c.makeRequest(ctx, "GET", endpoint, nil, result)
|
||||
}
|
||||
|
||||
// makeRequest performs an HTTP request with retry logic and logging
|
||||
func (c *Client) makeRequest(ctx context.Context, method, endpoint string, body interface{}, result interface{}) error {
|
||||
url := c.config.BaseURL + endpoint
|
||||
|
||||
var bodyReader io.Reader
|
||||
var bodyBytes []byte
|
||||
if body != nil {
|
||||
var err error
|
||||
bodyBytes, err = json.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
bodyReader = bytes.NewReader(bodyBytes)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= c.config.RetryAttempts; attempt++ {
|
||||
if attempt > 0 {
|
||||
// Wait before retry
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(c.config.RetryDelay):
|
||||
}
|
||||
}
|
||||
|
||||
err := c.doRequest(ctx, method, url, bodyReader, bodyBytes, result)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
|
||||
// Check if error is retryable
|
||||
if !c.isRetryableError(err) {
|
||||
break
|
||||
}
|
||||
|
||||
if c.logger != nil {
|
||||
c.logger.Warn("Request failed, retrying", map[string]interface{}{
|
||||
"attempt": attempt + 1,
|
||||
"max_attempts": c.config.RetryAttempts + 1,
|
||||
"error": err.Error(),
|
||||
"url": url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// doRequest performs the actual HTTP request
|
||||
func (c *Client) doRequest(ctx context.Context, method, url string, bodyReader io.Reader, bodyBytes []byte, result interface{}) error {
|
||||
// Reset body reader for retry
|
||||
if bodyReader != nil {
|
||||
bodyReader = bytes.NewReader(bodyBytes)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// Set headers
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", c.config.UserAgent)
|
||||
if c.config.APIKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
|
||||
}
|
||||
|
||||
// Log request
|
||||
if c.logger != nil && c.config.EnableLogging {
|
||||
c.logger.Debug("Making GLM API request", map[string]interface{}{
|
||||
"method": method,
|
||||
"url": url,
|
||||
"headers": map[string]string{
|
||||
"Content-Type": req.Header.Get("Content-Type"),
|
||||
"User-Agent": req.Header.Get("User-Agent"),
|
||||
"Authorization": strings.Repeat("*", len(req.Header.Get("Authorization"))),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response body
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
// Log response
|
||||
if c.logger != nil && c.config.EnableLogging {
|
||||
c.logger.Debug("GLM API response received", map[string]interface{}{
|
||||
"status_code": resp.StatusCode,
|
||||
"headers": resp.Header,
|
||||
"body_size": len(respBody),
|
||||
})
|
||||
}
|
||||
|
||||
// Check for HTTP errors
|
||||
if resp.StatusCode >= 400 {
|
||||
return c.handleHTTPError(resp.StatusCode, respBody)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
if result != nil {
|
||||
if err := json.Unmarshal(respBody, result); err != nil {
|
||||
return ErrInvalidResponse(fmt.Sprintf("failed to unmarshal response: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleHTTPError handles HTTP error responses
|
||||
func (c *Client) handleHTTPError(statusCode int, body []byte) error {
|
||||
var errorResp struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
Type string `json:"type"`
|
||||
Code string `json:"code"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &errorResp); err != nil {
|
||||
// If we can't parse the error response, return a generic error
|
||||
return ErrHTTPError(statusCode, string(body))
|
||||
}
|
||||
|
||||
message := errorResp.Error.Message
|
||||
if message == "" {
|
||||
message = "Unknown error"
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case 400:
|
||||
return ErrInvalidRequest(message)
|
||||
case 401:
|
||||
return ErrAuthentication(message)
|
||||
case 403:
|
||||
return ErrAuthentication(message)
|
||||
case 404:
|
||||
return ErrModelNotFound(message)
|
||||
case 429:
|
||||
return ErrRateLimit(message)
|
||||
case 500, 502, 503, 504:
|
||||
return ErrServerError(message)
|
||||
default:
|
||||
return ErrHTTPError(statusCode, message)
|
||||
}
|
||||
}
|
||||
|
||||
// isRetryableError determines if an error should be retried
|
||||
func (c *Client) isRetryableError(err error) bool {
|
||||
var glmErr *Error
|
||||
if errors.As(err, &glmErr) {
|
||||
// Retry on server errors and rate limits
|
||||
switch glmErr.Code {
|
||||
case ErrCodeServerError, ErrCodeRateLimit:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Retry on network errors, timeouts, etc.
|
||||
return true
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package glm
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config represents the configuration for the GLM SDK
|
||||
type Config struct {
|
||||
// BaseURL is the base URL of the GLM API server
|
||||
BaseURL string
|
||||
|
||||
// APIKey is the authorization token for GLM API
|
||||
APIKey string
|
||||
|
||||
// Timeout is the timeout for HTTP requests
|
||||
Timeout time.Duration
|
||||
|
||||
// RetryAttempts is the number of retry attempts for failed requests
|
||||
RetryAttempts int
|
||||
|
||||
// RetryDelay is the delay between retry attempts
|
||||
RetryDelay time.Duration
|
||||
|
||||
// EnableLogging enables request/response logging
|
||||
EnableLogging bool
|
||||
|
||||
// UserAgent is the user agent string for HTTP requests
|
||||
UserAgent string
|
||||
|
||||
// DefaultModel is the default model to use if none is specified
|
||||
DefaultModel string
|
||||
|
||||
// DefaultMaxTokens is the default maximum number of tokens to generate
|
||||
DefaultMaxTokens int
|
||||
|
||||
// DefaultTopP is the default top_p value for text generation
|
||||
DefaultTopP float64
|
||||
|
||||
// DefaultTemperature is the default temperature for text generation
|
||||
DefaultTemperature float64
|
||||
|
||||
// DefaultFrequencyPenalty is the default frequency penalty
|
||||
DefaultFrequencyPenalty float64
|
||||
|
||||
// DefaultPresencePenalty is the default presence penalty
|
||||
DefaultPresencePenalty float64
|
||||
|
||||
// EnableStreaming enables streaming responses by default
|
||||
EnableStreaming bool
|
||||
|
||||
// EnableThinking enables thinking mode by default
|
||||
EnableThinking bool
|
||||
|
||||
// TranslationConfig is the translation configuration
|
||||
TranslationOptions TranslationOption
|
||||
}
|
||||
|
||||
// TranslationOption represents the translation options
|
||||
type TranslationOption struct {
|
||||
Model string
|
||||
MaxTokens int
|
||||
Temperature float64
|
||||
FrequencyPenalty float64
|
||||
PresencePenalty float64
|
||||
TopP float64
|
||||
EnableThinking bool
|
||||
}
|
||||
|
||||
// DefaultConfig returns a default configuration for the GLM SDK
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: "https://api.z.ai",
|
||||
APIKey: "",
|
||||
Timeout: 300 * time.Second,
|
||||
RetryAttempts: 3,
|
||||
RetryDelay: 2 * time.Second,
|
||||
EnableLogging: true,
|
||||
UserAgent: "Opplens-GLMSDK/1.0",
|
||||
DefaultModel: "glm-4.5",
|
||||
DefaultMaxTokens: 256,
|
||||
DefaultTopP: 1.0,
|
||||
DefaultTemperature: 0.5,
|
||||
DefaultFrequencyPenalty: 0,
|
||||
DefaultPresencePenalty: 0,
|
||||
EnableStreaming: false,
|
||||
EnableThinking: false,
|
||||
TranslationOptions: TranslationOption{
|
||||
Model: "glm-4.5",
|
||||
MaxTokens: 256,
|
||||
Temperature: 0.5,
|
||||
FrequencyPenalty: 0,
|
||||
PresencePenalty: 0,
|
||||
TopP: 1.0,
|
||||
EnableThinking: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the configuration
|
||||
func (c *Config) Validate() error {
|
||||
if c.BaseURL == "" {
|
||||
return ErrInvalidConfig("BaseURL cannot be empty")
|
||||
}
|
||||
|
||||
if c.Timeout <= 0 {
|
||||
return ErrInvalidConfig("Timeout must be positive")
|
||||
}
|
||||
|
||||
if c.RetryAttempts < 0 {
|
||||
return ErrInvalidConfig("RetryAttempts cannot be negative")
|
||||
}
|
||||
|
||||
if c.RetryDelay < 0 {
|
||||
return ErrInvalidConfig("RetryDelay cannot be negative")
|
||||
}
|
||||
|
||||
if c.DefaultMaxTokens < 1 || c.DefaultMaxTokens > 32768 {
|
||||
return ErrInvalidConfig("DefaultMaxTokens must be between 1 and 32768")
|
||||
}
|
||||
|
||||
if c.DefaultTopP < 0 || c.DefaultTopP > 1 {
|
||||
return ErrInvalidConfig("DefaultTopP must be between 0 and 1")
|
||||
}
|
||||
|
||||
if c.DefaultTemperature < 0 || c.DefaultTemperature > 2 {
|
||||
return ErrInvalidConfig("DefaultTemperature must be between 0 and 2")
|
||||
}
|
||||
|
||||
if c.DefaultFrequencyPenalty < -2 || c.DefaultFrequencyPenalty > 2 {
|
||||
return ErrInvalidConfig("DefaultFrequencyPenalty must be between -2 and 2")
|
||||
}
|
||||
|
||||
if c.DefaultPresencePenalty < -2 || c.DefaultPresencePenalty > 2 {
|
||||
return ErrInvalidConfig("DefaultPresencePenalty must be between -2 and 2")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package glm
|
||||
|
||||
// ChatCompletionRequest represents a request for chat completion
|
||||
type ChatCompletionRequest struct {
|
||||
Model string `json:"model" validate:"required"`
|
||||
Messages []Message `json:"messages" validate:"required,min=1"`
|
||||
Thinking *Thinking `json:"thinking,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty" validate:"min=1,max=32768"`
|
||||
TopP float64 `json:"top_p,omitempty" validate:"min=0,max=1"`
|
||||
FrequencyPenalty float64 `json:"frequency_penalty,omitempty" validate:"min=-2,max=2"`
|
||||
PresencePenalty float64 `json:"presence_penalty,omitempty" validate:"min=-2,max=2"`
|
||||
Temperature float64 `json:"temperature,omitempty" validate:"min=0,max=2"`
|
||||
Stop []string `json:"stop,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
}
|
||||
|
||||
// Message represents a message in a chat conversation
|
||||
type Message struct {
|
||||
Role MessageRole `json:"role" validate:"required,oneof=system user assistant"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
// MessageRole defines the role of a message
|
||||
type MessageRole string
|
||||
|
||||
const (
|
||||
MessageRoleSystem MessageRole = "system"
|
||||
MessageRoleUser MessageRole = "user"
|
||||
MessageRoleAssistant MessageRole = "assistant"
|
||||
)
|
||||
|
||||
// Thinking represents the thinking configuration for GLM models
|
||||
type Thinking struct {
|
||||
Type string `json:"type" validate:"required,oneof=true false"`
|
||||
}
|
||||
|
||||
// ChatCompletionResponse represents the response from a chat completion request
|
||||
type ChatCompletionResponse struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []Choice `json:"choices"`
|
||||
Usage Usage `json:"usage"`
|
||||
SystemFingerprint string `json:"system_fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
// Choice represents a single choice in the chat completion response
|
||||
type Choice struct {
|
||||
Index int `json:"index"`
|
||||
Message Message `json:"message"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
}
|
||||
|
||||
// Usage represents token usage information
|
||||
type Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
}
|
||||
|
||||
// StreamChatCompletionResponse represents a streaming response chunk
|
||||
type StreamChatCompletionResponse struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []StreamChoice `json:"choices"`
|
||||
Usage *Usage `json:"usage,omitempty"`
|
||||
SystemFingerprint string `json:"system_fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
// StreamChoice represents a single choice in a streaming response
|
||||
type StreamChoice struct {
|
||||
Index int `json:"index"`
|
||||
Delta Message `json:"delta"`
|
||||
FinishReason *string `json:"finish_reason,omitempty"`
|
||||
}
|
||||
|
||||
// Model represents information about available models
|
||||
type Model struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
OwnedBy string `json:"owned_by"`
|
||||
}
|
||||
|
||||
// ModelsResponse represents the response from listing available models
|
||||
type ModelsResponse struct {
|
||||
Object string `json:"object"`
|
||||
Data []Model `json:"data"`
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package glm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Error represents a GLM SDK error
|
||||
type Error struct {
|
||||
Code string
|
||||
Message string
|
||||
Cause error
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *Error) Error() string {
|
||||
if e.Cause != nil {
|
||||
return fmt.Sprintf("glm: %s: %s: %v", e.Code, e.Message, e.Cause)
|
||||
}
|
||||
return fmt.Sprintf("glm: %s: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying cause of the error
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
// NewError creates a new GLM error
|
||||
func NewError(code, message string, cause error) *Error {
|
||||
return &Error{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Cause: cause,
|
||||
}
|
||||
}
|
||||
|
||||
// Error codes
|
||||
const (
|
||||
ErrCodeInvalidConfig = "INVALID_CONFIG"
|
||||
ErrCodeHTTPError = "HTTP_ERROR"
|
||||
ErrCodeInvalidRequest = "INVALID_REQUEST"
|
||||
ErrCodeAuthentication = "AUTHENTICATION_ERROR"
|
||||
ErrCodeRateLimit = "RATE_LIMIT_ERROR"
|
||||
ErrCodeServerError = "SERVER_ERROR"
|
||||
ErrCodeInvalidResponse = "INVALID_RESPONSE"
|
||||
ErrCodeChatCompletion = "CHAT_COMPLETION_ERROR"
|
||||
ErrCodeModelNotFound = "MODEL_NOT_FOUND"
|
||||
ErrCodeTokenLimit = "TOKEN_LIMIT_ERROR"
|
||||
ErrCodeStreamError = "STREAM_ERROR"
|
||||
)
|
||||
|
||||
// Error constructors
|
||||
func ErrInvalidConfig(message string) *Error {
|
||||
return NewError(ErrCodeInvalidConfig, message, nil)
|
||||
}
|
||||
|
||||
func ErrHTTPError(statusCode int, message string) *Error {
|
||||
return NewError(ErrCodeHTTPError, fmt.Sprintf("HTTP %d: %s", statusCode, message), nil)
|
||||
}
|
||||
|
||||
func ErrInvalidRequest(message string) *Error {
|
||||
return NewError(ErrCodeInvalidRequest, message, nil)
|
||||
}
|
||||
|
||||
func ErrAuthentication(message string) *Error {
|
||||
return NewError(ErrCodeAuthentication, message, nil)
|
||||
}
|
||||
|
||||
func ErrRateLimit(message string) *Error {
|
||||
return NewError(ErrCodeRateLimit, message, nil)
|
||||
}
|
||||
|
||||
func ErrServerError(message string) *Error {
|
||||
return NewError(ErrCodeServerError, message, nil)
|
||||
}
|
||||
|
||||
func ErrInvalidResponse(message string) *Error {
|
||||
return NewError(ErrCodeInvalidResponse, message, nil)
|
||||
}
|
||||
|
||||
func ErrChatCompletion(message string, cause error) *Error {
|
||||
return NewError(ErrCodeChatCompletion, message, cause)
|
||||
}
|
||||
|
||||
func ErrModelNotFound(model string) *Error {
|
||||
return NewError(ErrCodeModelNotFound, fmt.Sprintf("model '%s' not found", model), nil)
|
||||
}
|
||||
|
||||
func ErrTokenLimit(message string) *Error {
|
||||
return NewError(ErrCodeTokenLimit, message, nil)
|
||||
}
|
||||
|
||||
func ErrStreamError(message string, cause error) *Error {
|
||||
return NewError(ErrCodeStreamError, message, cause)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package glm
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// GLMService defines the interface for GLM service operations
|
||||
type GLMService interface {
|
||||
// ChatCompletion performs a chat completion request
|
||||
ChatCompletion(ctx context.Context, req *ChatCompletionRequest) (*ChatCompletionResponse, error)
|
||||
|
||||
// StreamChatCompletion performs a streaming chat completion request
|
||||
StreamChatCompletion(ctx context.Context, req *ChatCompletionRequest, onChunk func(*StreamChatCompletionResponse) error) error
|
||||
|
||||
// ListModels lists available models
|
||||
ListModels(ctx context.Context) (*ModelsResponse, error)
|
||||
|
||||
// Health checks if the GLM service is available
|
||||
Health(ctx context.Context) error
|
||||
}
|
||||
|
||||
// HTTPClient defines the interface for HTTP operations
|
||||
type HTTPClient interface {
|
||||
// Post performs a POST request
|
||||
Post(ctx context.Context, url string, body interface{}, result interface{}) error
|
||||
|
||||
// Get performs a GET request
|
||||
Get(ctx context.Context, url string, result interface{}) error
|
||||
}
|
||||
-277
@@ -1,277 +0,0 @@
|
||||
package glm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"tm/pkg/logger"
|
||||
)
|
||||
|
||||
// SDK provides a high-level interface for the GLM service
|
||||
type SDK struct {
|
||||
service GLMService
|
||||
client *Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// New creates a new GLM SDK with the provided configuration and logger
|
||||
func New(config *Config, logger logger.Logger) (*SDK, error) {
|
||||
cfg := DefaultConfig()
|
||||
if config.BaseURL != "" {
|
||||
cfg.BaseURL = config.BaseURL
|
||||
}
|
||||
if config.APIKey != "" {
|
||||
cfg.APIKey = config.APIKey
|
||||
}
|
||||
if config.Timeout != 0 {
|
||||
cfg.Timeout = config.Timeout
|
||||
}
|
||||
if config.RetryAttempts != 0 {
|
||||
cfg.RetryAttempts = config.RetryAttempts
|
||||
}
|
||||
if config.RetryDelay != 0 {
|
||||
cfg.RetryDelay = config.RetryDelay
|
||||
}
|
||||
if config.EnableLogging {
|
||||
cfg.EnableLogging = config.EnableLogging
|
||||
}
|
||||
if config.UserAgent != "" {
|
||||
cfg.UserAgent = config.UserAgent
|
||||
}
|
||||
if config.DefaultModel != "" {
|
||||
cfg.DefaultModel = config.DefaultModel
|
||||
}
|
||||
if config.DefaultMaxTokens != 0 {
|
||||
cfg.DefaultMaxTokens = config.DefaultMaxTokens
|
||||
}
|
||||
if config.DefaultTopP != 0 {
|
||||
cfg.DefaultTopP = config.DefaultTopP
|
||||
}
|
||||
if config.DefaultTemperature != 0 {
|
||||
cfg.DefaultTemperature = config.DefaultTemperature
|
||||
}
|
||||
if config.DefaultFrequencyPenalty != 0 {
|
||||
cfg.DefaultFrequencyPenalty = config.DefaultFrequencyPenalty
|
||||
}
|
||||
if config.DefaultPresencePenalty != 0 {
|
||||
cfg.DefaultPresencePenalty = config.DefaultPresencePenalty
|
||||
}
|
||||
if config.EnableStreaming {
|
||||
cfg.EnableStreaming = config.EnableStreaming
|
||||
}
|
||||
if config.EnableThinking {
|
||||
cfg.EnableThinking = config.EnableThinking
|
||||
}
|
||||
if config.TranslationOptions.Model != "" {
|
||||
cfg.TranslationOptions.Model = config.TranslationOptions.Model
|
||||
}
|
||||
if config.TranslationOptions.MaxTokens != 0 {
|
||||
cfg.TranslationOptions.MaxTokens = config.TranslationOptions.MaxTokens
|
||||
}
|
||||
if config.TranslationOptions.Temperature != 0 {
|
||||
cfg.TranslationOptions.Temperature = config.TranslationOptions.Temperature
|
||||
}
|
||||
if config.TranslationOptions.FrequencyPenalty != 0 {
|
||||
cfg.TranslationOptions.FrequencyPenalty = config.TranslationOptions.FrequencyPenalty
|
||||
}
|
||||
if config.TranslationOptions.PresencePenalty != 0 {
|
||||
cfg.TranslationOptions.PresencePenalty = config.TranslationOptions.PresencePenalty
|
||||
}
|
||||
if config.TranslationOptions.TopP != 0 {
|
||||
cfg.TranslationOptions.TopP = config.TranslationOptions.TopP
|
||||
}
|
||||
if config.TranslationOptions.EnableThinking {
|
||||
cfg.TranslationOptions.EnableThinking = config.TranslationOptions.EnableThinking
|
||||
}
|
||||
|
||||
client, err := NewClient(cfg, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := NewService(client)
|
||||
|
||||
return &SDK{
|
||||
service: service,
|
||||
client: client,
|
||||
config: cfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ChatCompletion performs a chat completion request
|
||||
func (s *SDK) ChatCompletion(ctx context.Context, req *ChatCompletionRequest) (*ChatCompletionResponse, error) {
|
||||
return s.service.ChatCompletion(ctx, req)
|
||||
}
|
||||
|
||||
// StreamChatCompletion performs a streaming chat completion request
|
||||
func (s *SDK) StreamChatCompletion(ctx context.Context, req *ChatCompletionRequest, onChunk func(*StreamChatCompletionResponse) error) error {
|
||||
return s.service.StreamChatCompletion(ctx, req, onChunk)
|
||||
}
|
||||
|
||||
// ListModels lists available models
|
||||
func (s *SDK) ListModels(ctx context.Context) (*ModelsResponse, error) {
|
||||
return s.service.ListModels(ctx)
|
||||
}
|
||||
|
||||
// Health checks if the GLM service is available
|
||||
func (s *SDK) Health(ctx context.Context) error {
|
||||
return s.service.Health(ctx)
|
||||
}
|
||||
|
||||
// QuickChat is a convenience method for simple chat interactions
|
||||
func (s *SDK) QuickChat(ctx context.Context, userMessage string) (string, error) {
|
||||
req := &ChatCompletionRequest{
|
||||
Model: s.config.DefaultModel,
|
||||
Messages: []Message{
|
||||
{
|
||||
Role: MessageRoleUser,
|
||||
Content: userMessage,
|
||||
},
|
||||
},
|
||||
MaxTokens: s.config.DefaultMaxTokens,
|
||||
TopP: s.config.DefaultTopP,
|
||||
Temperature: s.config.DefaultTemperature,
|
||||
FrequencyPenalty: s.config.DefaultFrequencyPenalty,
|
||||
PresencePenalty: s.config.DefaultPresencePenalty,
|
||||
}
|
||||
|
||||
if s.config.EnableThinking {
|
||||
req.Thinking = &Thinking{Type: "true"}
|
||||
}
|
||||
|
||||
resp, err := s.ChatCompletion(ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(resp.Choices) == 0 {
|
||||
return "", ErrInvalidResponse("no choices returned")
|
||||
}
|
||||
|
||||
return resp.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
// QuickChatWithSystem is a convenience method for chat with system prompt
|
||||
func (s *SDK) QuickChatWithSystem(ctx context.Context, systemPrompt, userMessage string) (string, error) {
|
||||
req := &ChatCompletionRequest{
|
||||
Model: s.config.DefaultModel,
|
||||
Messages: []Message{
|
||||
{
|
||||
Role: MessageRoleSystem,
|
||||
Content: systemPrompt,
|
||||
},
|
||||
{
|
||||
Role: MessageRoleUser,
|
||||
Content: userMessage,
|
||||
},
|
||||
},
|
||||
MaxTokens: s.config.DefaultMaxTokens,
|
||||
TopP: s.config.DefaultTopP,
|
||||
Temperature: s.config.DefaultTemperature,
|
||||
FrequencyPenalty: s.config.DefaultFrequencyPenalty,
|
||||
PresencePenalty: s.config.DefaultPresencePenalty,
|
||||
}
|
||||
|
||||
if s.config.EnableThinking {
|
||||
req.Thinking = &Thinking{Type: "true"}
|
||||
}
|
||||
|
||||
resp, err := s.ChatCompletion(ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(resp.Choices) == 0 {
|
||||
return "", ErrInvalidResponse("no choices returned")
|
||||
}
|
||||
|
||||
return resp.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
// TranslateOptions represents options for translation
|
||||
type TranslateOptions struct {
|
||||
Model string `json:"model,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
|
||||
PresencePenalty float64 `json:"presence_penalty,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
EnableThinking *bool `json:"enable_thinking,omitempty"`
|
||||
}
|
||||
|
||||
// Translate is a convenience method for translation tasks using dynamic configuration parameters optimized for translation
|
||||
func (s *SDK) Translate(ctx context.Context, text, fromLang, toLang string) (string, error) {
|
||||
systemPrompt := "You are a high-precision translator specializing in professional tender documents. Always respond with only the translated text — no explanations, notes, or markdown formatting."
|
||||
|
||||
userMessage := fmt.Sprintf("Translate the following text from %s to %s accurately and professionally:\n\"%s\"", fromLang, toLang, text)
|
||||
|
||||
// Build request with custom options or defaults
|
||||
req := &ChatCompletionRequest{
|
||||
Model: s.config.TranslationOptions.Model,
|
||||
Messages: []Message{
|
||||
{
|
||||
Role: MessageRoleSystem,
|
||||
Content: systemPrompt,
|
||||
},
|
||||
{
|
||||
Role: MessageRoleUser,
|
||||
Content: userMessage,
|
||||
},
|
||||
},
|
||||
MaxTokens: s.config.TranslationOptions.MaxTokens,
|
||||
TopP: s.config.TranslationOptions.TopP,
|
||||
Temperature: s.config.TranslationOptions.Temperature,
|
||||
FrequencyPenalty: s.config.TranslationOptions.FrequencyPenalty,
|
||||
PresencePenalty: s.config.TranslationOptions.PresencePenalty,
|
||||
}
|
||||
|
||||
resp, err := s.ChatCompletion(ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(resp.Choices) == 0 {
|
||||
return "", ErrInvalidResponse("no choices returned")
|
||||
}
|
||||
|
||||
return resp.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
// StreamQuickChat is a convenience method for streaming chat interactions
|
||||
func (s *SDK) StreamQuickChat(ctx context.Context, userMessage string, onChunk func(string) error) error {
|
||||
req := &ChatCompletionRequest{
|
||||
Model: s.config.DefaultModel,
|
||||
Messages: []Message{
|
||||
{
|
||||
Role: MessageRoleUser,
|
||||
Content: userMessage,
|
||||
},
|
||||
},
|
||||
MaxTokens: s.config.DefaultMaxTokens,
|
||||
TopP: s.config.DefaultTopP,
|
||||
Temperature: s.config.DefaultTemperature,
|
||||
FrequencyPenalty: s.config.DefaultFrequencyPenalty,
|
||||
PresencePenalty: s.config.DefaultPresencePenalty,
|
||||
Stream: true,
|
||||
}
|
||||
|
||||
if s.config.EnableThinking {
|
||||
req.Thinking = &Thinking{Type: "true"}
|
||||
}
|
||||
|
||||
return s.StreamChatCompletion(ctx, req, func(chunk *StreamChatCompletionResponse) error {
|
||||
if len(chunk.Choices) > 0 && chunk.Choices[0].Delta.Content != "" {
|
||||
return onChunk(chunk.Choices[0].Delta.Content)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetService returns the underlying service for advanced usage
|
||||
func (s *SDK) GetService() GLMService {
|
||||
return s.service
|
||||
}
|
||||
|
||||
// GetConfig returns the current configuration
|
||||
func (s *SDK) GetConfig() *Config {
|
||||
return s.config
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
package glm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"tm/pkg/logger"
|
||||
)
|
||||
|
||||
// Service implements the GLMService interface
|
||||
type Service struct {
|
||||
client *Client
|
||||
config *Config
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewService creates a new GLM service
|
||||
func NewService(client *Client) GLMService {
|
||||
return &Service{
|
||||
client: client,
|
||||
config: client.config,
|
||||
logger: client.logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ChatCompletion performs a chat completion request
|
||||
func (s *Service) ChatCompletion(ctx context.Context, req *ChatCompletionRequest) (*ChatCompletionResponse, error) {
|
||||
if s.logger != nil {
|
||||
s.logger.Info("Performing chat completion", map[string]interface{}{
|
||||
"model": req.Model,
|
||||
"message_count": len(req.Messages),
|
||||
"max_tokens": req.MaxTokens,
|
||||
"stream": req.Stream,
|
||||
})
|
||||
}
|
||||
|
||||
var resp ChatCompletionResponse
|
||||
err := s.client.Post(ctx, "/api/coding/paas/v4/chat/completions", req, &resp)
|
||||
if err != nil {
|
||||
if s.logger != nil {
|
||||
s.logger.Error("Chat completion failed", map[string]interface{}{
|
||||
"model": req.Model,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
return nil, ErrChatCompletion("chat completion request failed", err)
|
||||
}
|
||||
|
||||
if s.logger != nil {
|
||||
s.logger.Info("Chat completion completed", map[string]interface{}{
|
||||
"model": req.Model,
|
||||
"choice_count": len(resp.Choices),
|
||||
"usage": resp.Usage,
|
||||
})
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// StreamChatCompletion performs a streaming chat completion request
|
||||
func (s *Service) StreamChatCompletion(ctx context.Context, req *ChatCompletionRequest, onChunk func(*StreamChatCompletionResponse) error) error {
|
||||
if s.logger != nil {
|
||||
s.logger.Info("Starting streaming chat completion", map[string]interface{}{
|
||||
"model": req.Model,
|
||||
"message_count": len(req.Messages),
|
||||
"max_tokens": req.MaxTokens,
|
||||
})
|
||||
}
|
||||
|
||||
// Force streaming
|
||||
req.Stream = true
|
||||
|
||||
return s.streamRequest(ctx, req, onChunk)
|
||||
}
|
||||
|
||||
// streamRequest handles streaming requests
|
||||
func (s *Service) streamRequest(ctx context.Context, req *ChatCompletionRequest, onChunk func(*StreamChatCompletionResponse) error) error {
|
||||
// Create a custom HTTP request for streaming
|
||||
httpReq, err := s.client.createStreamingRequest(ctx, req)
|
||||
if err != nil {
|
||||
return ErrStreamError("failed to create streaming request", err)
|
||||
}
|
||||
|
||||
resp, err := s.client.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return ErrStreamError("failed to execute streaming request", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return s.client.handleHTTPError(resp.StatusCode, body)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
// Skip empty lines
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse SSE format
|
||||
if strings.HasPrefix(line, "data: ") {
|
||||
data := strings.TrimPrefix(line, "data: ")
|
||||
|
||||
// Check for end of stream
|
||||
if data == "[DONE]" {
|
||||
break
|
||||
}
|
||||
|
||||
var chunk StreamChatCompletionResponse
|
||||
if err := json.Unmarshal([]byte(data), &chunk); err != nil {
|
||||
if s.logger != nil {
|
||||
s.logger.Warn("Failed to parse streaming chunk", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := onChunk(&chunk); err != nil {
|
||||
return ErrStreamError("chunk callback failed", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return ErrStreamError("streaming scanner error", err)
|
||||
}
|
||||
|
||||
if s.logger != nil {
|
||||
s.logger.Info("Streaming chat completion finished", map[string]interface{}{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListModels lists available models
|
||||
func (s *Service) ListModels(ctx context.Context) (*ModelsResponse, error) {
|
||||
if s.logger != nil {
|
||||
s.logger.Info("Listing available models", map[string]interface{}{})
|
||||
}
|
||||
|
||||
var resp ModelsResponse
|
||||
err := s.client.Get(ctx, "/api/coding/paas/v4/models", &resp)
|
||||
if err != nil {
|
||||
if s.logger != nil {
|
||||
s.logger.Error("Failed to list models", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
return nil, fmt.Errorf("failed to list models: %w", err)
|
||||
}
|
||||
|
||||
if s.logger != nil {
|
||||
s.logger.Info("Models listed successfully", map[string]interface{}{
|
||||
"model_count": len(resp.Data),
|
||||
})
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Health checks if the GLM service is available
|
||||
func (s *Service) Health(ctx context.Context) error {
|
||||
if s.logger != nil {
|
||||
s.logger.Debug("Performing health check", map[string]interface{}{})
|
||||
}
|
||||
|
||||
// Simple health check by listing models
|
||||
_, err := s.ListModels(ctx)
|
||||
if err != nil {
|
||||
if s.logger != nil {
|
||||
s.logger.Error("Health check failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
return fmt.Errorf("health check failed: %w", err)
|
||||
}
|
||||
|
||||
if s.logger != nil {
|
||||
s.logger.Debug("Health check passed", map[string]interface{}{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createStreamingRequest creates an HTTP request for streaming
|
||||
func (c *Client) createStreamingRequest(ctx context.Context, req *ChatCompletionRequest) (*http.Request, error) {
|
||||
url := c.config.BaseURL + "/api/coding/paas/v4/chat/completions"
|
||||
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// Set headers
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("User-Agent", c.config.UserAgent)
|
||||
if c.config.APIKey != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+c.config.APIKey)
|
||||
}
|
||||
|
||||
return httpReq, nil
|
||||
}
|
||||
Reference in New Issue
Block a user