feat: 修复一些问题

This commit is contained in:
kite 2026-04-29 14:24:09 +08:00
parent a896ba5aca
commit e2d6a54d55
4 changed files with 50 additions and 42 deletions

BIN
dist/opencodereview vendored

Binary file not shown.

View file

@ -433,15 +433,13 @@ func (a *Agent) resolveSystemRule(path string) string {
// executePlanPhase runs the plan task for a single file, sending template messages
// with resolved placeholders and collecting the LLM response as plan guidance.
func (a *Agent) executePlanPhase(_ context.Context, newPath, rawDiff, changeFiles, rule string) (string, error) {
func (a *Agent) executePlanPhase(ctx context.Context, newPath, rawDiff, changeFiles, rule string) (string, error) {
pt := a.args.Template.PlanTask
messages := make([]llm.Message, 0, len(pt.Messages))
for _, m := range pt.Messages {
content := m.Content
content = strings.ReplaceAll(content, "{{current_system_date_time}}", a.currentDate)
content = strings.ReplaceAll(content, "{{current_file_path}}", newPath)
content = strings.ReplaceAll(content, "{{current_file_path}}", newPath)
content = strings.ReplaceAll(content, "{{current_file_path}}", newPath)
content = strings.ReplaceAll(content, "{{system_rule}}", rule)
content = strings.ReplaceAll(content, "{{change_files}}", changeFiles)
content = strings.ReplaceAll(content, "{{diff}}", rawDiff)
@ -453,7 +451,7 @@ func (a *Agent) executePlanPhase(_ context.Context, newPath, rawDiff, changeFile
rec := fs.AppendTaskRecord(session.PlanTask, messages)
startTime := time.Now()
resp, err := a.args.LLMClient.GeneralRequest(messages, a.args.Model, nil)
resp, err := a.args.LLMClient.GeneralRequestWithCtx(ctx, messages, a.args.Model, nil)
if err != nil {
rec.SetError(err, time.Since(startTime))
return "", fmt.Errorf("plan request: %w", err)
@ -772,39 +770,8 @@ func (a *Agent) compressAndRecord(msgs []llm.Message, filePath string) []llm.Mes
return msgs[:2]
}
compressed := msgs[:2]
// Append summary to the original user prompt
userMsg := compressed[1]
currentText := userMsg.ExtractText()
compressed[1] = llm.NewTextMessage(userMsg.Role, currentText+"\n\n"+summary)
return compressed
}
// compressMessages runs the memory compression task and replaces old messages with a summary.
func compressMessages(msgs []llm.Message, compTask template.LlmConversation, client *llm.Client, model string) []llm.Message {
if len(compTask.Messages) == 0 || len(msgs) <= 2 {
return msgs[:min(len(msgs), 2)]
}
contextXML := buildMessageXML(msgs[2:])
compressionMsgs := make([]llm.Message, 0, len(compTask.Messages))
for _, m := range compTask.Messages {
content := strings.ReplaceAll(m.Content, "{{context}}", contextXML)
compressionMsgs = append(compressionMsgs, llm.NewTextMessage(m.Role, content))
}
resp, err := client.GeneralRequest(compressionMsgs, model, nil)
if err != nil {
fmt.Printf("[ocr] Memory compression failed: %v\n", err)
return msgs[:2]
}
summary := resp.Content()
if summary == "" {
return msgs[:2]
}
compressed := msgs[:2]
compressed := make([]llm.Message, 2)
copy(compressed, msgs[:2])
// Append summary to the original user prompt
userMsg := compressed[1]
currentText := userMsg.ExtractText()

View file

@ -6,6 +6,7 @@ package llm
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@ -203,14 +204,19 @@ type ChatRequest struct {
// Completions sends a chat completion request and returns the parsed response.
func (c *Client) Completions(req ChatRequest) (*ChatResponse, error) {
return c.CompletionsWithCtx(context.Background(), req)
}
// CompletionsWithCtx sends a chat completion request with context support for cancellation and timeout.
func (c *Client) CompletionsWithCtx(ctx context.Context, req ChatRequest) (*ChatResponse, error) {
model := req.Model
if model == "" {
model = c.cfg.Model
}
var result *ChatResponse
err := c.withRetry(func() error {
resp, err := c.doRequest(model, req)
err := c.withRetryCtx(ctx, func() error {
resp, err := c.doRequestCtx(ctx, model, req)
if err != nil {
return err
}
@ -222,7 +228,12 @@ func (c *Client) Completions(req ChatRequest) (*ChatResponse, error) {
// GeneralRequest sends a simple chat request without or with optional tool calls (for plan phase, compression, etc.).
func (c *Client) GeneralRequest(messages []Message, model string, tools []ToolDef) (*ChatResponse, error) {
return c.Completions(ChatRequest{
return c.GeneralRequestWithCtx(context.Background(), messages, model, tools)
}
// GeneralRequestWithCtx sends a simple chat request with context support.
func (c *Client) GeneralRequestWithCtx(ctx context.Context, messages []Message, model string, tools []ToolDef) (*ChatResponse, error) {
return c.CompletionsWithCtx(ctx, ChatRequest{
Model: model,
Messages: messages,
Tools: tools,
@ -369,8 +380,18 @@ func stripThinkTags(s string) string {
}
func (c *Client) withRetry(fn func() error) error {
return c.withRetryCtx(context.Background(), fn)
}
func (c *Client) withRetryCtx(ctx context.Context, fn func() error) error {
var lastErr error
for attempt := 0; attempt <= maxRetries; attempt++ {
select {
case <-ctx.Done():
return fmt.Errorf("context cancelled: %w", ctx.Err())
default:
}
lastErr = fn()
if lastErr == nil {
return nil
@ -445,12 +466,17 @@ func min(a, b int) int {
// doRequest builds and sends a non-streaming completion request, returning the parsed response.
func (c *Client) doRequest(model string, req ChatRequest) (*ChatResponse, error) {
return c.doRequestCtx(context.Background(), model, req)
}
// doRequestCtx builds and sends a non-streaming completion request with context support.
func (c *Client) doRequestCtx(ctx context.Context, model string, req ChatRequest) (*ChatResponse, error) {
if model == "" {
model = c.cfg.Model
}
req.Model = model
payload, _ := json.Marshal(req)
httpReq, err := http.NewRequest(http.MethodPost, c.cfg.URL, bytes.NewReader(payload))
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.URL, bytes.NewReader(payload))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}

View file

@ -206,12 +206,27 @@ func (fs *FileSession) AppendTaskRecord(taskType TaskType, messages []llm.Messag
rec := &TaskRecord{
Type: taskType,
RequestNo: len(fs.TaskRecords[taskType]) + 1,
RequestMessages: messages,
RequestMessages: copyMessages(messages),
}
fs.TaskRecords[taskType] = append(fs.TaskRecords[taskType], rec)
return rec
}
// copyMessages returns a deep copy of a messages slice so that future mutations
// don't corrupt stored records.
func copyMessages(msgs []llm.Message) []llm.Message {
cp := make([]llm.Message, len(msgs))
for i, m := range msgs {
cp[i] = llm.Message{
Role: m.Role,
Content: m.Content,
ToolCallID: m.ToolCallID,
ToolCalls: append([]llm.ToolCall(nil), m.ToolCalls...),
}
}
return cp
}
// SetResponse records the LLM response in the most recent TaskRecord of the given type.
// It computes estimated token usage locally using tiktoken, independent of any API format.
func (tr *TaskRecord) SetResponse(resp *llm.ChatResponse, duration time.Duration) {