fix: Ignore the Chain-of-Thought in AI response (#952)

- Improve chat response processing to handle thinking patterns using regular expressions.
- Migrate server value by removing trailing '/chat/completions' path.
This commit is contained in:
GadflyFang 2025-02-08 17:30:27 +08:00 committed by GitHub
parent 8cc056d2af
commit ef2e0a8a56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 78 additions and 10 deletions

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Avalonia.Threading; using Avalonia.Threading;
@ -48,16 +49,18 @@ namespace SourceGit.Commands
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd(); var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
if (rs.IsSuccess) if (rs.IsSuccess)
{ {
var hasFirstValidChar = false;
var thinkingBuffer = new StringBuilder();
_service.Chat( _service.Chat(
_service.AnalyzeDiffPrompt, _service.AnalyzeDiffPrompt,
$"Here is the `git diff` output: {rs.StdOut}", $"Here is the `git diff` output: {rs.StdOut}",
_cancelToken, _cancelToken,
update => update =>
{ ProcessChatResponse(update, ref hasFirstValidChar, thinkingBuffer,
responseBuilder.Append(update); (responseBuilder, text =>
summaryBuilder.Append(update); _onResponse?.Invoke(
_onResponse?.Invoke("Waiting for pre-file analyzing to complated...\n\n" + responseBuilder.ToString()); $"Waiting for pre-file analyzing to completed...\n\n{text}")),
}); (summaryBuilder, null)));
} }
responseBuilder.Append("\n"); responseBuilder.Append("\n");
@ -71,15 +74,15 @@ namespace SourceGit.Commands
var responseBody = responseBuilder.ToString(); var responseBody = responseBuilder.ToString();
var subjectBuilder = new StringBuilder(); var subjectBuilder = new StringBuilder();
var hasSubjectFirstValidChar = false;
var subjectThinkingBuffer = new StringBuilder();
_service.Chat( _service.Chat(
_service.GenerateSubjectPrompt, _service.GenerateSubjectPrompt,
$"Here are the summaries changes:\n{summaryBuilder}", $"Here are the summaries changes:\n{summaryBuilder}",
_cancelToken, _cancelToken,
update => update =>
{ ProcessChatResponse(update, ref hasSubjectFirstValidChar, subjectThinkingBuffer,
subjectBuilder.Append(update); (subjectBuilder, text => _onResponse?.Invoke($"{text}\n\n{responseBody}"))));
_onResponse?.Invoke($"{subjectBuilder}\n\n{responseBody}");
});
} }
catch (Exception e) catch (Exception e)
{ {
@ -87,10 +90,67 @@ namespace SourceGit.Commands
} }
} }
private void ProcessChatResponse(
string update,
ref bool hasFirstValidChar,
StringBuilder thinkingBuffer,
params (StringBuilder builder, Action<string> callback)[] outputs)
{
if (!hasFirstValidChar)
{
update = update.TrimStart();
if (string.IsNullOrEmpty(update))
return;
if (update.StartsWith("<", StringComparison.Ordinal))
thinkingBuffer.Append(update);
hasFirstValidChar = true;
}
if (thinkingBuffer.Length > 0)
thinkingBuffer.Append(update);
if (thinkingBuffer.Length > 15)
{
var match = REG_COT.Match(thinkingBuffer.ToString());
if (match.Success)
{
update = REG_COT.Replace(thinkingBuffer.ToString(), "").TrimStart();
if (update.Length > 0)
{
foreach (var output in outputs)
output.builder.Append(update);
thinkingBuffer.Clear();
}
return;
}
match = REG_THINK_START.Match(thinkingBuffer.ToString());
if (!match.Success)
{
foreach (var output in outputs)
output.builder.Append(thinkingBuffer);
thinkingBuffer.Clear();
return;
}
}
if (thinkingBuffer.Length == 0)
{
foreach (var output in outputs)
{
output.builder.Append(update);
output.callback?.Invoke(output.builder.ToString());
}
}
}
private Models.OpenAIService _service; private Models.OpenAIService _service;
private string _repo; private string _repo;
private List<Models.Change> _changes; private List<Models.Change> _changes;
private CancellationToken _cancelToken; private CancellationToken _cancelToken;
private Action<string> _onResponse; private Action<string> _onResponse;
private static readonly Regex REG_COT = new(@"^<(think|thought|thinking|thought_chain)>(.*?)</\1>", RegexOptions.Singleline);
private static readonly Regex REG_THINK_START = new(@"^<(think|thought|thinking|thought_chain)>", RegexOptions.Singleline);
} }
} }

View file

@ -19,7 +19,15 @@ namespace SourceGit.Models
public string Server public string Server
{ {
get => _server; get => _server;
set => SetProperty(ref _server, value); set
{
// migrate old server value
if (!string.IsNullOrEmpty(value) && value.EndsWith("/chat/completions", StringComparison.Ordinal))
{
value = value.Substring(0, value.Length - "/chat/completions".Length);
}
SetProperty(ref _server, value);
}
} }
public string ApiKey public string ApiKey