-
Notifications
You must be signed in to change notification settings - Fork 384
Expand file tree
/
Copy pathOpenAI.cs
More file actions
228 lines (200 loc) · 8.26 KB
/
OpenAI.cs
File metadata and controls
228 lines (200 loc) · 8.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
using System;
using System.ClientModel;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
using CommunityToolkit.Mvvm.ComponentModel;
using OpenAI;
using OpenAI.Chat;
namespace SourceGit.Models
{
public partial class OpenAIResponse
{
public OpenAIResponse(Action<string> onUpdate)
{
_onUpdate = onUpdate;
}
public void Append(string text)
{
var buffer = text;
if (_thinkTail.Length > 0)
{
_thinkTail.Append(buffer);
buffer = _thinkTail.ToString();
_thinkTail.Clear();
}
buffer = REG_COT().Replace(buffer, "");
var startIdx = buffer.IndexOf('<', StringComparison.Ordinal);
if (startIdx >= 0)
{
if (startIdx > 0)
OnReceive(buffer.Substring(0, startIdx));
var endIdx = buffer.IndexOf(">", startIdx + 1, StringComparison.Ordinal);
if (endIdx <= startIdx)
{
if (buffer.Length - startIdx <= 15)
_thinkTail.Append(buffer.Substring(startIdx));
else
OnReceive(buffer.Substring(startIdx));
}
else if (endIdx < startIdx + 15)
{
var tag = buffer.Substring(startIdx + 1, endIdx - startIdx - 1);
if (_thinkTags.Contains(tag))
_thinkTail.Append(buffer.Substring(startIdx));
else
OnReceive(buffer.Substring(startIdx));
}
else
{
OnReceive(buffer.Substring(startIdx));
}
}
else
{
OnReceive(buffer);
}
}
public void End()
{
if (_thinkTail.Length > 0)
{
OnReceive(_thinkTail.ToString());
_thinkTail.Clear();
}
}
private void OnReceive(string text)
{
if (!_hasTrimmedStart)
{
text = text.TrimStart();
if (string.IsNullOrEmpty(text))
return;
_hasTrimmedStart = true;
}
_onUpdate.Invoke(text);
}
[GeneratedRegex(@"<(think|thought|thinking|thought_chain)>.*?</\1>", RegexOptions.Singleline)]
private static partial Regex REG_COT();
private Action<string> _onUpdate = null;
private StringBuilder _thinkTail = new StringBuilder();
private HashSet<string> _thinkTags = ["think", "thought", "thinking", "thought_chain"];
private bool _hasTrimmedStart = false;
}
public class OpenAIService : ObservableObject
{
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public string Server
{
get => _server;
set => SetProperty(ref _server, value);
}
public string ApiKey
{
get => _apiKey;
set => SetProperty(ref _apiKey, value);
}
public string Model
{
get => _model;
set => SetProperty(ref _model, value);
}
public bool Streaming
{
get => _streaming;
set => SetProperty(ref _streaming, value);
}
public string AnalyzeDiffPrompt
{
get => _analyzeDiffPrompt;
set => SetProperty(ref _analyzeDiffPrompt, value);
}
public string GenerateSubjectPrompt
{
get => _generateSubjectPrompt;
set => SetProperty(ref _generateSubjectPrompt, value);
}
public OpenAIService()
{
AnalyzeDiffPrompt = """
You are an expert developer specializing in writing commit messages.
Provide a super concise, one-sentence summary of the user's git diff output, strictly following these rules:
- Do not use code snippets, imports, file paths, or bullet points.
- Do not mention the path of any changed file.
- Write clear, concise, and descriptive messages that explain the main goal of the changes.
- Use the present tense and active voice in the message (for example, "Fix bug" instead of "Fixed bug").
- Use the imperative mood, which gives the message a sense of command (e.g., "Add feature" instead of "Added feature").
- Avoid using general terms like "update" or "change"; be specific about what was updated or changed.
- Avoid using terms like "The main goal of"; just output the summary directly in plain text.
""";
GenerateSubjectPrompt = """
You are an expert developer specializing in writing commit messages.
Your sole goal is to retrieve a single commit message.
Based on the provided user changes, combine them into one single commit message, capturing the global idea, and strictly following these rules:
- Assign the commit {type} according to these conditions:
feat: Only when adding a new feature.
fix: When fixing a bug.
docs: When updating documentation.
style: When changing element styles or design, and/or making changes to the code style (formatting, missing semicolons, etc.) without altering the code logic.
test: When adding or updating tests.
chore: When making changes to the build process or auxiliary tools and libraries.
revert: When undoing a previous commit.
refactor: When restructuring code without changing its external behavior, or when it falls under any other refactor type.
- Do not add any issue numbers, explain your output, or introduce your answer.
- Output directly only one commit message in plain text with the format: {type}: {commit_message}.
- Be as concise as possible, keeping the message under 50 characters.
""";
}
public async Task ChatAsync(string prompt, string question, CancellationToken cancellation, Action<string> onUpdate)
{
var server = new Uri(_server);
var key = new ApiKeyCredential(_apiKey);
var oaiClient = _server.Contains("openai.azure.com/", StringComparison.Ordinal)
? new AzureOpenAIClient(server, key)
: new OpenAIClient(key, new() { Endpoint = server });
var client = oaiClient.GetChatClient(_model);
var messages = new List<ChatMessage>();
messages.Add(_model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt));
messages.Add(new UserChatMessage(question));
try
{
var rsp = new OpenAIResponse(onUpdate);
if (_streaming)
{
var updates = client.CompleteChatStreamingAsync(messages, null, cancellation);
await foreach (var update in updates)
{
if (update.ContentUpdate.Count > 0)
rsp.Append(update.ContentUpdate[0].Text);
}
}
else
{
var completion = await client.CompleteChatAsync(messages, null, cancellation);
if (completion.Value.Content.Count > 0)
rsp.Append(completion.Value.Content[0].Text);
}
rsp.End();
}
catch
{
if (!cancellation.IsCancellationRequested)
throw;
}
}
private string _name;
private string _server;
private string _apiKey;
private string _model;
private bool _streaming = true;
private string _analyzeDiffPrompt;
private string _generateSubjectPrompt;
}
}