mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-07-10 17:59:07 +00:00
feat: 增加一些边界提示
This commit is contained in:
parent
a217a6f439
commit
b680c29ba4
5 changed files with 109 additions and 10 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/open-code-review/open-code-review/internal/agent"
|
||||
"github.com/open-code-review/open-code-review/internal/model"
|
||||
"github.com/open-code-review/open-code-review/internal/suggestdiff"
|
||||
)
|
||||
|
|
@ -20,6 +21,15 @@ func outputText(comments []model.LlmComment) {
|
|||
}
|
||||
}
|
||||
|
||||
func outputTextWithWarnings(comments []model.LlmComment, warnings []agent.AgentWarning) {
|
||||
outputText(comments)
|
||||
if len(warnings) > 0 {
|
||||
for _, w := range warnings {
|
||||
fmt.Fprintf(os.Stderr, "[ocr] WARNING [%s] %s: %s\n", w.Type, w.File, w.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderComment(comment model.LlmComment) {
|
||||
lines := buildDiffLines(comment)
|
||||
if len(lines) == 0 && comment.Content == "" {
|
||||
|
|
@ -133,9 +143,10 @@ func buildDiffLines(comment model.LlmComment) []suggestdiff.DiffLine {
|
|||
}
|
||||
|
||||
type jsonOutput struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Comments []model.LlmComment `json:"comments"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Comments []model.LlmComment `json:"comments"`
|
||||
Warnings []agent.AgentWarning `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
func outputJSON(comments []model.LlmComment) error {
|
||||
|
|
@ -151,6 +162,23 @@ func outputJSON(comments []model.LlmComment) error {
|
|||
return enc.Encode(out)
|
||||
}
|
||||
|
||||
func outputJSONWithWarnings(comments []model.LlmComment, warnings []agent.AgentWarning) error {
|
||||
out := jsonOutput{
|
||||
Status: "success",
|
||||
Comments: comments,
|
||||
}
|
||||
if len(comments) == 0 {
|
||||
out.Message = "No comments generated."
|
||||
}
|
||||
if len(warnings) > 0 {
|
||||
out.Warnings = warnings
|
||||
out.Status = "completed_with_warnings"
|
||||
}
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(out)
|
||||
}
|
||||
|
||||
func outputJSONNoFiles() error {
|
||||
out := jsonOutput{
|
||||
Status: "skipped",
|
||||
|
|
|
|||
|
|
@ -141,9 +141,9 @@ func runReview(args []string) error {
|
|||
telemetry.PrintTraceSummary(ag.FilesReviewed(), int64(len(comments)), ag.TotalInputTokens(), ag.TotalOutputTokens(), ag.TotalTokensUsed(), duration)
|
||||
|
||||
if opts.outputFormat == "json" {
|
||||
return outputJSON(comments)
|
||||
return outputJSONWithWarnings(comments, ag.Warnings())
|
||||
}
|
||||
outputText(comments)
|
||||
outputTextWithWarnings(comments, ag.Warnings())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,13 @@ type Args struct {
|
|||
Session *session.SessionHistory
|
||||
}
|
||||
|
||||
// AgentWarning describes a non-fatal warning recorded during review.
|
||||
type AgentWarning struct {
|
||||
File string `json:"file"`
|
||||
Message string `json:"message"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Agent orchestrates the AI-powered code review.
|
||||
type Agent struct {
|
||||
args Args
|
||||
|
|
@ -99,6 +106,8 @@ type Agent struct {
|
|||
totalTokensUsed int64 // accumulated total tokens from all LLM calls, accessed atomically
|
||||
totalInputTokens int64 // accumulated input/prompt tokens, accessed atomically
|
||||
totalOutputTokens int64 // accumulated completion tokens, accessed atomically
|
||||
warningsMu sync.Mutex
|
||||
warnings []AgentWarning
|
||||
}
|
||||
|
||||
// CommentWorkerPool manages a fixed-size pool of workers dedicated to
|
||||
|
|
@ -241,6 +250,26 @@ func (a *Agent) TotalOutputTokens() int64 {
|
|||
return atomic.LoadInt64(&a.totalOutputTokens)
|
||||
}
|
||||
|
||||
// Warnings returns a copy of non-fatal warnings recorded during review.
|
||||
func (a *Agent) Warnings() []AgentWarning {
|
||||
a.warningsMu.Lock()
|
||||
defer a.warningsMu.Unlock()
|
||||
out := make([]AgentWarning, len(a.warnings))
|
||||
copy(out, a.warnings)
|
||||
return out
|
||||
}
|
||||
|
||||
// recordWarning adds a non-fatal warning to the agent's warning list.
|
||||
func (a *Agent) recordWarning(warningType, file, message string) {
|
||||
a.warningsMu.Lock()
|
||||
a.warnings = append(a.warnings, AgentWarning{
|
||||
File: file,
|
||||
Message: message,
|
||||
Type: warningType,
|
||||
})
|
||||
a.warningsMu.Unlock()
|
||||
}
|
||||
|
||||
// loadDiffs populates the diff-related fields.
|
||||
func (a *Agent) loadDiffs() error {
|
||||
var provider *diff.Provider
|
||||
|
|
@ -406,8 +435,9 @@ func (a *Agent) executeSubtask(ctx context.Context, d model.Diff) error {
|
|||
|
||||
tokenCount := countMessagesTokens(messages)
|
||||
if tokenCount > a.args.Template.TokenWarningThreshold {
|
||||
fmt.Fprintf(stdout.Writer(), "[ocr] WARNING: prompt tokens (%d) exceed threshold (%d) for %s\n",
|
||||
tokenCount, a.args.Template.TokenWarningThreshold, newPath)
|
||||
msg := fmt.Sprintf("prompt tokens (%d) exceed threshold (%d)", tokenCount, a.args.Template.TokenWarningThreshold)
|
||||
fmt.Fprintf(stdout.Writer(), "[ocr] WARNING: %s for %s\n", msg, newPath)
|
||||
a.recordWarning("token_threshold_exceeded", newPath, msg)
|
||||
telemetry.Event(ctx, "token.threshold.exceeded",
|
||||
telemetry.AnyToAttr("file.path", newPath),
|
||||
telemetry.AnyToAttr("tokens", tokenCount),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
|
@ -80,7 +81,9 @@ func finalizeDiff(d *model.Diff, repoDir string) {
|
|||
}
|
||||
fullPath := filepath.Join(repoDir, d.NewPath)
|
||||
content, err := os.ReadFile(fullPath)
|
||||
if err == nil {
|
||||
d.NewFileContent = string(content)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[ocr] WARNING: cannot read file %s for review: %v\n", d.NewPath, err)
|
||||
return
|
||||
}
|
||||
d.NewFileContent = string(content)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -497,7 +497,8 @@ func (c *Client) doRequestCtx(ctx context.Context, model string, req ChatRequest
|
|||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, string(bodyBytes))
|
||||
detail := extractErrorMessage(bodyBytes)
|
||||
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, detail)
|
||||
}
|
||||
|
||||
var apiResp struct {
|
||||
|
|
@ -517,3 +518,40 @@ func (c *Client) doRequestCtx(ctx context.Context, model string, req ChatRequest
|
|||
Usage: resolveUsage(bodyBytes),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// extractErrorMessage attempts to pull a human-readable error message from
|
||||
// a JSON API error response body. Falls back to truncating the raw body if
|
||||
// the structure is not recognised or decoding fails.
|
||||
func extractErrorMessage(body []byte) string {
|
||||
type openAIError struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
type anthropicError struct {
|
||||
Type string `json:"type"`
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
return "(empty body)"
|
||||
}
|
||||
|
||||
var oe openAIError
|
||||
if err := json.Unmarshal(body, &oe); err == nil && oe.Error.Message != "" {
|
||||
return oe.Error.Message
|
||||
}
|
||||
var ae anthropicError
|
||||
if err := json.Unmarshal(body, &ae); err == nil && ae.Error.Message != "" {
|
||||
return ae.Error.Message
|
||||
}
|
||||
|
||||
// Truncate raw body to avoid excessively noisy errors.
|
||||
bodyText := string(body)
|
||||
if len(bodyText) > 512 {
|
||||
bodyText = bodyText[:512] + "... (truncated)"
|
||||
}
|
||||
return bodyText
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue