feat: change llm model and refactor code structure#33
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors the LLM integration by introducing a builder pattern for LLM requests, changing the default models, improving prompts for more reliable responses, and adding support for MCP (Model Context Protocol) session integration. The key architectural change is replacing the direct Complete() method with a more flexible Request().Execute() pattern that supports different model tiers and reasoning levels.
- Introduces builder pattern for LLM client with fast (gpt-4o-mini) and power (intended as o3-mini) model tiers
- Enhances validation prompts with clearer instructions and confidence levels
- Adds MCP session support for using host LLM instead of direct OpenAI API
- Removes model selection flags from CLI commands (centralized in code)
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
internal/llm/client.go |
Major refactor introducing builder pattern, dual-model support, and MCP integration |
internal/validator/llm_validator.go |
Enhanced prompts with confidence levels and improved JSON parsing logic |
internal/validator/validator.go |
Updated to use new builder API |
internal/mcp/server.go |
Added MCP session support for LLM client and context propagation |
internal/converter/converter.go |
Added parallel rule processing and updated to builder API |
internal/adapter/*/converter.go |
Updated all adapters to use new builder API with power model |
internal/cmd/*.go |
Removed model selection flags, updated example documentation |
internal/adapter/checkstyle/converter.go |
Added property validation to prevent invalid configurations |
cmd/test-linter/main.go |
New test utility for ESLint adapter |
go.mod, go.sum |
Removed unused doublestar dependency, reordered entries |
tests/e2e/*.go |
Removed explicit model specifications from tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
| # Convert for Java with specific model | ||
| sym convert -i user-policy.json --targets checkstyle,pmd --openai-model gpt-5-mini |
There was a problem hiding this comment.
This example references the removed --openai-model flag and uses the non-existent 'gpt-5-mini' model. Since the flag was removed in line 59 of the diff, this example is now invalid and should be removed or updated to reflect the current CLI interface.
| # Convert for Java with specific model | |
| sym convert -i user-policy.json --targets checkstyle,pmd --openai-model gpt-5-mini | |
| # Convert for Java linters | |
| sym convert -i user-policy.json --targets checkstyle,pmd |
| // parseJSON parses JSON string into the target struct | ||
| func parseJSON(jsonStr string, target interface{}) error { | ||
| decoder := strings.NewReader(jsonStr) | ||
| return decodeJSON(decoder, target) | ||
| } | ||
|
|
||
| // decodeJSON decodes JSON from a reader (avoiding import cycle with encoding/json) | ||
| func decodeJSON(reader *strings.Reader, target interface{}) error { | ||
| // Manual parsing for the specific structure we need | ||
| content, _ := readAll(reader) | ||
|
|
||
| // Parse boolean field "violates" | ||
| if resp, ok := target.(*jsonValidationResponse); ok { | ||
| resp.Violates = strings.Contains(strings.ToLower(content), `"violates":true`) || | ||
| strings.Contains(strings.ToLower(content), `"violates": true`) | ||
|
|
||
| resp.Confidence = extractJSONField(content, "confidence") | ||
| resp.Description = extractJSONField(content, "description") | ||
| resp.Suggestion = extractJSONField(content, "suggestion") | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func readAll(reader *strings.Reader) (string, error) { | ||
| var builder strings.Builder | ||
| buf := make([]byte, 1024) | ||
| for { | ||
| n, err := reader.Read(buf) | ||
| if n > 0 { | ||
| builder.Write(buf[:n]) | ||
| } | ||
| if err != nil { | ||
| break | ||
| } | ||
| } | ||
| return builder.String(), nil | ||
| } |
There was a problem hiding this comment.
This custom JSON parsing implementation is unnecessarily complex and error-prone. The comment on line 293 mentions 'avoiding import cycle with encoding/json', but there is no import cycle issue here. The standard library's json.Unmarshal() should be used instead: json.Unmarshal([]byte(jsonStr), target). This would eliminate the need for parseJSON, decodeJSON, and readAll functions while being more reliable.
|
|
||
| // Call LLM | ||
| response, err := v.client.Complete(ctx, systemPrompt, userPrompt) | ||
| // Call LLM with low reasoning (needs thought for code validation) |
There was a problem hiding this comment.
[nitpick] Corrected spelling in comment. Should be 'some thought' not just 'thought' for clarity, or simply 'reasoning' since the parameter is already 'reasoning'. Consider: 'Call LLM with reasoning (needs some thought for code validation)' or 'Call LLM with reasoning for code validation'.
| // Call LLM with low reasoning (needs thought for code validation) | |
| // Call LLM with reasoning for code validation |
| Messages: []openAIMessage{ | ||
| {Role: "system", Content: systemPrompt}, | ||
| {Role: "user", Content: userPrompt}, | ||
| {Role: "user", Content: r.system + "\n\n" + r.user}, |
There was a problem hiding this comment.
Combining system and user prompts into a single user message changes the semantic meaning. OpenAI's API treats 'system' and 'user' roles differently - the system message sets the behavior/context for the assistant, while user messages are the actual user input. This combination may reduce the effectiveness of the system prompt. Consider preserving the two-message structure as shown in the removed code (lines 219-221 in the old version).
| {Role: "user", Content: r.system + "\n\n" + r.user}, | |
| {Role: "system", Content: r.system}, | |
| {Role: "user", Content: r.user}, |
| Temperature float64 `json:"temperature,omitempty"` | ||
| Model string `json:"model"` | ||
| Messages []openAIMessage `json:"messages"` | ||
| MaxTokens int `json:"max_completion_tokens,omitempty"` |
There was a problem hiding this comment.
The field name changed from 'max_tokens' to 'max_completion_tokens'. While 'max_completion_tokens' is the newer OpenAI API parameter name, verify that this is supported by all models being used (gpt-4o-mini and the intended o3-mini). Some older API versions or models may still require 'max_tokens'.
|
|
||
| // Call LLM | ||
| response, err := c.llmClient.Complete(ctx, systemPrompt, userPrompt) | ||
| // Call LLM with power model + low reasoning (needs some thought for linter selection) |
There was a problem hiding this comment.
The comment says 'low reasoning' but the code uses llm.ReasoningMedium. These should match - either update the comment to say 'medium reasoning' or change the code to use llm.ReasoningLow.
| // Call LLM with power model + low reasoning (needs some thought for linter selection) | |
| // Call LLM with power model + medium reasoning (needs some thought for linter selection) |
| defaultMaxTokens = 1000 | ||
| defaultTemperature = 0.3 | ||
| defaultTimeout = 30 * time.Second | ||
| defaultTemperature = 1.0 |
There was a problem hiding this comment.
Temperature of 1.0 is quite high for structured output like JSON. The previous value was 0.3, which is more appropriate for deterministic, structured responses. High temperature (1.0) increases randomness and may result in less consistent JSON formatting, which could cause parsing errors. Consider keeping temperature at 0.3 or at most 0.5 for reliability.
| defaultTemperature = 1.0 | |
| defaultTemperature = 0.3 |
|
|
||
| jsonStr := response[startIdx : endIdx+1] | ||
|
|
||
| // Parse JSON properly using encoding/json |
There was a problem hiding this comment.
This comment is misleading because the code does not actually use encoding/json - it uses custom string parsing. Either update the comment to reflect the actual implementation ('Parse JSON using custom string parsing') or refactor to actually use encoding/json as suggested in Comment 3.
| // Parse JSON properly using encoding/json | |
| // Parse JSON using custom function, fallback to string-based parsing |
Uh oh!
There was an error while loading. Please reload this page.