Skip to content

Commit dc75179

Browse files
author
crypto-analyzer
committed
feat: add MiniMax M2.1 LLM provider support
- Add MiniMaxClient implementing Anthropic-compatible API - Support MiniMax M2.1 model via NewMiniMaxClient() - Update New() factory to support 'minimax' provider - Endpoint: https://api.minimax.chat/v1/text/chatcompletion_v2
1 parent c555186 commit dc75179

2 files changed

Lines changed: 196 additions & 0 deletions

File tree

‎internal/llm/llm.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ func New(cfg *config.Config) (LLM, error) {
3333
switch cfg.LLM.Provider {
3434
case "deepseek":
3535
return NewDeepSeekClient(cfg.LLM.APIKey, cfg.LLM.Model, cfg.LLM.Endpoint, cfg.LLM.Temperature), nil
36+
case "minimax":
37+
return NewMiniMaxClient(cfg.LLM.APIKey, cfg.LLM.Model, cfg.LLM.Endpoint, cfg.LLM.Temperature), nil
3638
// case "openai":
3739
// return NewOpenAIClient(cfg.LLM.APIKey, cfg.LLM.Model), nil
3840
default:

‎internal/llm/minimax.go‎

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package llm
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"strings"
9+
10+
"github.com/zjy-dev/de-fuzz/internal/seed"
11+
)
12+
13+
const (
14+
// DefaultMiniMaxEndpoint is the default API endpoint for MiniMax M2.1
15+
DefaultMiniMaxEndpoint = "https://api.minimax.chat/v1/text/chatcompletion_v2"
16+
)
17+
18+
// MiniMaxClient implements the LLM interface for MiniMax M2.1 (Anthropic-compatible API).
19+
type MiniMaxClient struct {
20+
apiKey string
21+
model string
22+
endpoint string
23+
temperature float64
24+
client *http.Client
25+
}
26+
27+
// NewMiniMaxClient creates a new client for the MiniMax API.
28+
func NewMiniMaxClient(apiKey, model, endpoint string, temperature float64) *MiniMaxClient {
29+
if endpoint == "" {
30+
endpoint = DefaultMiniMaxEndpoint
31+
}
32+
if temperature <= 0 {
33+
temperature = 0.7 // Default temperature
34+
}
35+
return &MiniMaxClient{
36+
apiKey: apiKey,
37+
model: model,
38+
endpoint: endpoint,
39+
temperature: temperature,
40+
client: &http.Client{},
41+
}
42+
}
43+
44+
// GetCompletion sends a prompt to the MiniMax API and returns the response.
45+
func (c *MiniMaxClient) GetCompletion(prompt string) (string, error) {
46+
return c.GetCompletionWithSystem("", prompt)
47+
}
48+
49+
// GetCompletionWithSystem sends a prompt with system context to the MiniMax API.
50+
// MiniMax uses Anthropic-compatible API format with messages array.
51+
func (c *MiniMaxClient) GetCompletionWithSystem(systemPrompt, userPrompt string) (string, error) {
52+
messages := []map[string]string{}
53+
54+
// Add system message if provided
55+
if systemPrompt != "" {
56+
messages = append(messages, map[string]string{
57+
"role": "system",
58+
"content": systemPrompt,
59+
})
60+
}
61+
62+
// Add user message
63+
messages = append(messages, map[string]string{
64+
"role": "user",
65+
"content": userPrompt,
66+
})
67+
68+
// Build request body (Anthropic-compatible format)
69+
reqBody := map[string]interface{}{
70+
"model": c.model,
71+
"messages": messages,
72+
"temperature": c.temperature,
73+
}
74+
75+
jsonBody, err := json.Marshal(reqBody)
76+
if err != nil {
77+
return "", fmt.Errorf("failed to marshal request body: %w", err)
78+
}
79+
80+
req, err := http.NewRequest("POST", c.endpoint, bytes.NewBuffer(jsonBody))
81+
if err != nil {
82+
return "", fmt.Errorf("failed to create request: %w", err)
83+
}
84+
85+
// Set headers (Anthropic-compatible)
86+
req.Header.Set("Authorization", "Bearer "+c.apiKey)
87+
req.Header.Set("Content-Type", "application/json")
88+
// MiniMax may require additional headers
89+
req.Header.Set("Accept", "application/json")
90+
91+
resp, err := c.client.Do(req)
92+
if err != nil {
93+
return "", fmt.Errorf("failed to perform request: %w", err)
94+
}
95+
defer resp.Body.Close()
96+
97+
if resp.StatusCode != http.StatusOK {
98+
// Read error response for debugging
99+
buf := new(bytes.Buffer)
100+
buf.ReadFrom(resp.Body)
101+
return "", fmt.Errorf("api request failed with status %d: %s", resp.StatusCode, buf.String())
102+
}
103+
104+
var result map[string]interface{}
105+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
106+
return "", fmt.Errorf("failed to decode response body: %w", err)
107+
}
108+
109+
// Parse response (Anthropic-compatible format)
110+
if choices, ok := result["choices"].([]interface{}); ok && len(choices) > 0 {
111+
if choice, ok := choices[0].(map[string]interface{}); ok {
112+
if message, ok := choice["message"].(map[string]interface{}); ok {
113+
if content, ok := message["content"].(string); ok {
114+
return strings.TrimSpace(content), nil
115+
}
116+
}
117+
}
118+
}
119+
120+
return "", fmt.Errorf("unexpected response format from API")
121+
}
122+
123+
// Understand processes the initial prompt and returns the LLM's summary.
124+
func (c *MiniMaxClient) Understand(prompt string) (string, error) {
125+
return c.GetCompletion(prompt)
126+
}
127+
128+
// Generate creates a new seed based on the provided context.
129+
func (c *MiniMaxClient) Generate(understanding, prompt string) (*seed.Seed, error) {
130+
completion, err := c.GetCompletionWithSystem(understanding, prompt)
131+
if err != nil {
132+
return nil, err
133+
}
134+
135+
// Parse the completion to extract source code and test cases
136+
sourceCode, testCases, err := seed.ParseSeedFromLLMResponse(completion)
137+
if err != nil {
138+
return nil, fmt.Errorf("failed to parse LLM response: %w", err)
139+
}
140+
141+
return &seed.Seed{
142+
Content: sourceCode,
143+
TestCases: testCases,
144+
}, nil
145+
}
146+
147+
// Mutate modifies an existing seed to create a new variant.
148+
func (c *MiniMaxClient) Mutate(understanding, prompt string, s *seed.Seed) (*seed.Seed, error) {
149+
completion, err := c.GetCompletionWithSystem(understanding, prompt)
150+
if err != nil {
151+
return nil, err
152+
}
153+
154+
// Parse the completion to extract mutated source code and test cases
155+
sourceCode, testCases, err := seed.ParseSeedFromLLMResponse(completion)
156+
if err != nil {
157+
return nil, fmt.Errorf("failed to parse LLM response: %w", err)
158+
}
159+
160+
return &seed.Seed{
161+
Meta: s.Meta, // Preserve parent's metadata for lineage tracking
162+
Content: sourceCode,
163+
TestCases: testCases,
164+
}, nil
165+
}
166+
167+
// Analyze interprets the feedback from a seed execution.
168+
func (c *MiniMaxClient) Analyze(understanding, prompt string, s *seed.Seed, feedback string) (string, error) {
169+
// Construct a specific prompt for analysis that includes the seed content and feedback
170+
analysisPrompt := fmt.Sprintf("%s\n\nSeed Content:\n%s\n\nExecution Feedback:\n%s",
171+
prompt, s.Content, feedback)
172+
173+
return c.GetCompletionWithSystem(understanding, analysisPrompt)
174+
}
175+
176+
// GetAPIKey returns the API key (for testing purposes)
177+
func (c *MiniMaxClient) GetAPIKey() string {
178+
return c.apiKey
179+
}
180+
181+
// GetModel returns the model name (for testing purposes)
182+
func (c *MiniMaxClient) GetModel() string {
183+
return c.model
184+
}
185+
186+
// GetEndpoint returns the endpoint URL (for testing purposes)
187+
func (c *MiniMaxClient) GetEndpoint() string {
188+
return c.endpoint
189+
}
190+
191+
// GetTemperature returns the temperature setting (for testing purposes)
192+
func (c *MiniMaxClient) GetTemperature() float64 {
193+
return c.temperature
194+
}

0 commit comments

Comments
 (0)