Add MiniMax runner to the semantic-comparison factory - #682
Conversation
Introduce a direct MiniMax HTTP runner that implements the AgentRunner interface, register it under the "minimax" identifier in the runner factory, and configure its text models and regional endpoints through environment variables. Update the ENGRAM_AGENT_CLI guidance accordingly.
📝 WalkthroughWalkthroughAdds a MiniMax LLM runner with environment-based configuration, chat-completions requests, response parsing, error handling, factory registration, CLI help updates, and unit tests. ChangesMiniMax runner integration
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant MiniMaxRunner
participant MiniMaxAPI
participant parseMiniMaxResponse
participant Verdict
MiniMaxRunner->>MiniMaxAPI: POST chat-completions request
MiniMaxAPI-->>MiniMaxRunner: HTTP response body
MiniMaxRunner->>parseMiniMaxResponse: Parse response envelope and inner verdict
parseMiniMaxResponse-->>Verdict: Return structured verdict
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/llm/minimax_test.go`:
- Around line 55-56: Remove the redundant package-level AgentRunner compile-time
assertion named TestMiniMaxRunner_CompileTimeCheck from minimax_test.go. Retain
the identical interface assertion already present in minimax.go, and do not
replace it with a runnable test.
- Around line 201-245: Extend the MiniMax runner tests with coverage for both
untested branches in parseMiniMaxResponse: add a malformed outer-envelope case
using invalid response JSON and assert ErrInvalidJSON, and add a response whose
envelope Model is empty but inner result Model is populated, then assert Compare
returns the inner model. Place these alongside the existing MiniMax runner tests
and preserve current fakeDo/http response patterns.
In `@internal/llm/minimax.go`:
- Around line 92-111: Validate configuration in miniMaxResolveModel and
miniMaxResolveBaseURL before returning values: accept only model identifiers
present in MiniMaxModelIDs, and accept only recognized region constants,
reporting invalid non-empty values through the existing configuration-error
mechanism instead of silently using defaults. Preserve the current default
model, global fallback for an unset region, and base-URL override behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ab21f643-0fca-4f37-8942-9cae3983f500
📒 Files selected for processing (6)
cmd/engram/llm.gocmd/engram/main.gointernal/llm/factory.gointernal/llm/factory_test.gointernal/llm/minimax.gointernal/llm/minimax_test.go
| // TestMiniMaxRunner_CompileTimeCheck verifies MiniMaxRunner satisfies AgentRunner. | ||
| var _ AgentRunner = (*MiniMaxRunner)(nil) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Misleading "Test" naming on a non-runnable compile-time check.
TestMiniMaxRunner_CompileTimeCheck is just a package-level var declaration, not an actual func Test...(t *testing.T), so it never appears in go test output despite the name suggesting a test. It also duplicates the identical check already present in minimax.go (line 169).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/llm/minimax_test.go` around lines 55 - 56, Remove the redundant
package-level AgentRunner compile-time assertion named
TestMiniMaxRunner_CompileTimeCheck from minimax_test.go. Retain the identical
interface assertion already present in minimax.go, and do not replace it with a
runnable test.
| timeoutErr := fmt.Errorf("Post: %w", context.DeadlineExceeded) | ||
| r := newTestRunner(fakeDo(nil, timeoutErr)) | ||
| _, err := r.Compare(context.Background(), "compare") | ||
| if !errors.Is(err, ErrTimeout) { | ||
| t.Errorf("expected ErrTimeout; got %v", err) | ||
| } | ||
| } | ||
|
|
||
| // ─── configuration resolution ────────────────────────────────────────────────── | ||
|
|
||
| func TestMiniMaxResolveBaseURL(t *testing.T) { | ||
| t.Run("default is global", func(t *testing.T) { | ||
| t.Setenv("ENGRAM_MINIMAX_BASE_URL", "") | ||
| t.Setenv("ENGRAM_MINIMAX_REGION", "") | ||
| if got := miniMaxResolveBaseURL(); got != miniMaxBaseURLGlobal { | ||
| t.Errorf("baseURL = %q; want %q", got, miniMaxBaseURLGlobal) | ||
| } | ||
| }) | ||
| t.Run("china region", func(t *testing.T) { | ||
| t.Setenv("ENGRAM_MINIMAX_BASE_URL", "") | ||
| t.Setenv("ENGRAM_MINIMAX_REGION", MiniMaxRegionChina) | ||
| if got := miniMaxResolveBaseURL(); got != miniMaxBaseURLChina { | ||
| t.Errorf("baseURL = %q; want %q", got, miniMaxBaseURLChina) | ||
| } | ||
| }) | ||
| t.Run("explicit override wins", func(t *testing.T) { | ||
| t.Setenv("ENGRAM_MINIMAX_REGION", MiniMaxRegionChina) | ||
| t.Setenv("ENGRAM_MINIMAX_BASE_URL", "https://example.test/api/") | ||
| if got := miniMaxResolveBaseURL(); got != "https://example.test/api" { | ||
| t.Errorf("baseURL = %q; want trimmed override", got) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| func TestMiniMaxResolveModel(t *testing.T) { | ||
| t.Run("default", func(t *testing.T) { | ||
| t.Setenv("ENGRAM_MINIMAX_MODEL", "") | ||
| if got := miniMaxResolveModel(); got != MiniMaxDefaultModel { | ||
| t.Errorf("model = %q; want %q", got, MiniMaxDefaultModel) | ||
| } | ||
| }) | ||
| t.Run("override", func(t *testing.T) { | ||
| t.Setenv("ENGRAM_MINIMAX_MODEL", MiniMaxModelM27) | ||
| if got := miniMaxResolveModel(); got != MiniMaxModelM27 { | ||
| t.Errorf("model = %q; want %q", got, MiniMaxModelM27) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
Missing test coverage for the outer-envelope decode-failure and model-fallback branches.
parseMiniMaxResponse in minimax.go has two untested branches: (1) the top-level json.Unmarshal(raw, &env) failure at line 203-205 of minimax.go — only the inner JSON invalid case (TestMiniMaxRunner_InvalidInnerJSON) is covered, not a malformed outer envelope; and (2) the Model fallback to iv.Model when env.Model == "" (minimax.go lines 233-236) is never exercised. Both are edge cases directly reachable from this runner.
🧪 Suggested additions
func TestMiniMaxRunner_MalformedEnvelope(t *testing.T) {
r := newTestRunner(fakeDo(fakeHTTPResponse(http.StatusOK, "not json"), nil))
_, err := r.Compare(context.Background(), "compare")
if !errors.Is(err, ErrInvalidJSON) {
t.Errorf("expected ErrInvalidJSON; got %v", err)
}
}
func TestMiniMaxRunner_ModelFallbackToInner(t *testing.T) {
inner := `{"Relation":"related","Confidence":0.5,"Reasoning":"x","Model":"MiniMax-M2.7"}`
body := fmt.Sprintf(`{"choices":[{"message":{"content":%q}}],"base_resp":{"status_code":0}}`, inner)
r := newTestRunner(fakeDo(fakeHTTPResponse(http.StatusOK, body), nil))
v, err := r.Compare(context.Background(), "compare")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if v.Model != "MiniMax-M2.7" {
t.Errorf("Model = %q; want fallback to inner Model", v.Model)
}
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/llm/minimax_test.go` around lines 201 - 245, Extend the MiniMax
runner tests with coverage for both untested branches in parseMiniMaxResponse:
add a malformed outer-envelope case using invalid response JSON and assert
ErrInvalidJSON, and add a response whose envelope Model is empty but inner
result Model is populated, then assert Compare returns the inner model. Place
these alongside the existing MiniMax runner tests and preserve current
fakeDo/http response patterns.
Source: Path instructions
| // miniMaxResolveBaseURL selects the base URL from the environment. | ||
| func miniMaxResolveBaseURL() string { | ||
| if v := strings.TrimSpace(os.Getenv("ENGRAM_MINIMAX_BASE_URL")); v != "" { | ||
| return strings.TrimRight(v, "/") | ||
| } | ||
| switch strings.TrimSpace(os.Getenv("ENGRAM_MINIMAX_REGION")) { | ||
| case MiniMaxRegionChina: | ||
| return miniMaxBaseURLChina | ||
| default: | ||
| return miniMaxBaseURLGlobal | ||
| } | ||
| } | ||
|
|
||
| // miniMaxResolveModel selects the model id from the environment. | ||
| func miniMaxResolveModel() string { | ||
| if v := strings.TrimSpace(os.Getenv("ENGRAM_MINIMAX_MODEL")); v != "" { | ||
| return v | ||
| } | ||
| return MiniMaxDefaultModel | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
No validation of resolved model/region against known constants.
miniMaxResolveModel accepts any ENGRAM_MINIMAX_MODEL value without validating it against MiniMaxModelIDs, and miniMaxResolveBaseURL's region switch silently falls back to the global endpoint on an unrecognized ENGRAM_MINIMAX_REGION value (e.g. a typo). Misconfiguration will only surface as an opaque API error at request time rather than a clear startup/config error.
♻️ Suggested validation
func miniMaxResolveModel() string {
if v := strings.TrimSpace(os.Getenv("ENGRAM_MINIMAX_MODEL")); v != "" {
+ for _, id := range MiniMaxModelIDs {
+ if v == id {
+ return v
+ }
+ }
+ // fall through with a clear signal that the override isn't a known model
return v
}
return MiniMaxDefaultModel
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/llm/minimax.go` around lines 92 - 111, Validate configuration in
miniMaxResolveModel and miniMaxResolveBaseURL before returning values: accept
only model identifiers present in MiniMaxModelIDs, and accept only recognized
region constants, reporting invalid non-empty values through the existing
configuration-error mechanism instead of silently using defaults. Preserve the
current default model, global fallback for an unset region, and base-URL
override behavior.
Reason: The semantic-comparison runner factory had no MiniMax runner or configuration for MiniMax-M3 and MiniMax-M2.7.
What changed
MiniMaxRunner(internal/llm/minimax.go) that implements the existingAgentRunnerinterface by calling the MiniMax hosted chat completions API directly over HTTP. It sends the locked canonical comparison prompt as a single user message and parses the returned single-line Verdict JSON (markdown code fences are stripped), reusing the package's sharedinnerVerdict,validRelations, andfenceREhelpers."minimax"identifier in the runner factory (internal/llm/factory.go) alongside the existing runners, and extend theENGRAM_AGENT_CLIguidance in the CLI error message and--helpoutput.MINIMAX_API_KEY— bearer token (required at compare time; a missing key returns the existing auth-missing sentinel).ENGRAM_MINIMAX_MODEL— text model id; defaults toMiniMax-M3. The configured ids areMiniMax-M3andMiniMax-M2.7.ENGRAM_MINIMAX_REGION—global_en(default,https://api.minimax.io/v1) orcn_zh(https://api.minimaxi.com/v1).ENGRAM_MINIMAX_BASE_URL— optional explicit base URL override.base_resp.status_code, a non-200 HTTP status, an empty choices list, malformed verdict JSON, and relations outside the locked vocabulary all return errors, reusing the package error sentinels where applicable.Tests
internal/llm/minimax_test.gocovers golden parsing, fence stripping, request shape (method, path, authorization header, body), missing key, application and HTTP error paths, unknown relation, transport and timeout errors, and base-URL/model resolution. The HTTP round-trip is injected through a seam, so the tests make no network calls.NewRunner("minimax")returns a*MiniMaxRunner.Checks
go build ./...go vet ./internal/llm/ ./cmd/engram/go test ./internal/llm/ ./cmd/engram/gofmt -lon the changed files (clean)Summary by CodeRabbit
New Features
Tests