4464 lines
126 KiB
C#
4464 lines
126 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks.Sources;
|
|
using AxCopilot.Models;
|
|
using AxCopilot.Services.Agent;
|
|
|
|
namespace AxCopilot.Services;
|
|
|
|
public class LlmService : IDisposable
|
|
{
|
|
public class ContentBlock
|
|
{
|
|
public string Type { get; init; } = "text";
|
|
|
|
public string Text { get; init; } = "";
|
|
|
|
public string ToolName { get; init; } = "";
|
|
|
|
public string ToolId { get; init; } = "";
|
|
|
|
public JsonElement? ToolInput { get; init; }
|
|
}
|
|
|
|
[CompilerGenerated]
|
|
private sealed class _003CStreamAsync_003Ed__34 : IAsyncEnumerable<string>, IAsyncEnumerator<string>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine
|
|
{
|
|
public int _003C_003E1__state;
|
|
|
|
public AsyncIteratorMethodBuilder _003C_003Et__builder;
|
|
|
|
public ManualResetValueTaskSourceCore<bool> _003C_003Ev__promiseOfValueOrEnd;
|
|
|
|
private string _003C_003E2__current;
|
|
|
|
private bool _003C_003Ew__disposeMode;
|
|
|
|
private CancellationTokenSource _003C_003Ex__combinedTokens;
|
|
|
|
private int _003C_003El__initialThreadId;
|
|
|
|
private List<ChatMessage> messages;
|
|
|
|
public List<ChatMessage> _003C_003E3__messages;
|
|
|
|
private CancellationToken ct;
|
|
|
|
public CancellationToken _003C_003E3__ct;
|
|
|
|
public LlmService _003C_003E4__this;
|
|
|
|
private string _003CactiveService_003E5__1;
|
|
|
|
private IAsyncEnumerable<string> _003Cstream_003E5__2;
|
|
|
|
private string _003C_003Es__3;
|
|
|
|
private ConfiguredCancelableAsyncEnumerable<string>.Enumerator _003C_003Es__4;
|
|
|
|
private object _003C_003Es__5;
|
|
|
|
private int _003C_003Es__6;
|
|
|
|
private string _003Cchunk_003E5__7;
|
|
|
|
private bool _003C_003Es__8;
|
|
|
|
private ConfiguredValueTaskAwaitable<bool>.ConfiguredValueTaskAwaiter _003C_003Eu__1;
|
|
|
|
private ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter _003C_003Eu__2;
|
|
|
|
string IAsyncEnumerator<string>.Current
|
|
{
|
|
[DebuggerHidden]
|
|
get
|
|
{
|
|
return _003C_003E2__current;
|
|
}
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
public _003CStreamAsync_003Ed__34(int _003C_003E1__state)
|
|
{
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
this._003C_003E1__state = _003C_003E1__state;
|
|
_003C_003El__initialThreadId = Environment.CurrentManagedThreadId;
|
|
}
|
|
|
|
private void MoveNext()
|
|
{
|
|
int num = _003C_003E1__state;
|
|
try
|
|
{
|
|
ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter;
|
|
switch (num)
|
|
{
|
|
default:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
num = (_003C_003E1__state = -1);
|
|
_003CactiveService_003E5__1 = _003C_003E4__this.ResolveService();
|
|
_003C_003Es__3 = _003CactiveService_003E5__1.ToLowerInvariant();
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
IAsyncEnumerable<string> asyncEnumerable = _003C_003Es__3 switch
|
|
{
|
|
"gemini" => _003C_003E4__this.StreamGeminiAsync(messages, ct),
|
|
"claude" => _003C_003E4__this.StreamClaudeAsync(messages, ct),
|
|
"vllm" => _003C_003E4__this.StreamOpenAiCompatibleAsync(messages, ct),
|
|
_ => _003C_003E4__this.StreamOllamaAsync(messages, ct),
|
|
};
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
_003Cstream_003E5__2 = asyncEnumerable;
|
|
_003C_003Es__3 = null;
|
|
_003C_003Es__4 = _003Cstream_003E5__2.WithCancellation(ct).GetAsyncEnumerator();
|
|
_003C_003Es__5 = null;
|
|
_003C_003Es__6 = 0;
|
|
goto case -4;
|
|
}
|
|
goto end_IL_0007;
|
|
case -4:
|
|
case 0:
|
|
try
|
|
{
|
|
ConfiguredValueTaskAwaitable<bool>.ConfiguredValueTaskAwaiter awaiter2;
|
|
if (num != -4)
|
|
{
|
|
if (num != 0)
|
|
{
|
|
goto IL_01aa;
|
|
}
|
|
awaiter2 = _003C_003Eu__1;
|
|
_003C_003Eu__1 = default(ConfiguredValueTaskAwaitable<bool>.ConfiguredValueTaskAwaiter);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_0216;
|
|
}
|
|
num = (_003C_003E1__state = -1);
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cchunk_003E5__7 = null;
|
|
goto IL_01aa;
|
|
}
|
|
goto end_IL_014c;
|
|
IL_01aa:
|
|
_003C_003E2__current = null;
|
|
awaiter2 = _003C_003Es__4.MoveNextAsync().GetAwaiter();
|
|
if (!awaiter2.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 0);
|
|
_003C_003Eu__1 = awaiter2;
|
|
_003CStreamAsync_003Ed__34 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_0216;
|
|
IL_0216:
|
|
_003C_003Es__8 = awaiter2.GetResult();
|
|
if (_003C_003Es__8)
|
|
{
|
|
_003Cchunk_003E5__7 = _003C_003Es__4.Current;
|
|
_003C_003E2__current = _003Cchunk_003E5__7;
|
|
num = (_003C_003E1__state = -4);
|
|
goto IL_03ef;
|
|
}
|
|
end_IL_014c:;
|
|
}
|
|
catch (object obj)
|
|
{
|
|
_003C_003Es__5 = obj;
|
|
}
|
|
_003C_003E2__current = null;
|
|
awaiter = _003C_003Es__4.DisposeAsync().GetAwaiter();
|
|
if (!awaiter.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 1);
|
|
_003C_003Eu__2 = awaiter;
|
|
_003CStreamAsync_003Ed__34 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
|
return;
|
|
}
|
|
break;
|
|
case 1:
|
|
awaiter = _003C_003Eu__2;
|
|
_003C_003Eu__2 = default(ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter);
|
|
num = (_003C_003E1__state = -1);
|
|
break;
|
|
}
|
|
awaiter.GetResult();
|
|
object obj2 = _003C_003Es__5;
|
|
if (obj2 != null)
|
|
{
|
|
if (!(obj2 is Exception source))
|
|
{
|
|
throw obj2;
|
|
}
|
|
ExceptionDispatchInfo.Capture(source).Throw();
|
|
}
|
|
_ = _003C_003Es__6;
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003C_003Es__5 = null;
|
|
_003C_003Es__4 = default(ConfiguredCancelableAsyncEnumerable<string>.Enumerator);
|
|
}
|
|
end_IL_0007:;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_003C_003E1__state = -2;
|
|
_003CactiveService_003E5__1 = null;
|
|
_003Cstream_003E5__2 = null;
|
|
_003C_003Es__3 = null;
|
|
_003C_003Es__4 = default(ConfiguredCancelableAsyncEnumerable<string>.Enumerator);
|
|
_003C_003Es__5 = null;
|
|
_003Cchunk_003E5__7 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetException(exception);
|
|
return;
|
|
}
|
|
_003C_003E1__state = -2;
|
|
_003CactiveService_003E5__1 = null;
|
|
_003Cstream_003E5__2 = null;
|
|
_003C_003Es__3 = null;
|
|
_003C_003Es__4 = default(ConfiguredCancelableAsyncEnumerable<string>.Enumerator);
|
|
_003C_003Es__5 = null;
|
|
_003Cchunk_003E5__7 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: false);
|
|
return;
|
|
IL_03ef:
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: true);
|
|
}
|
|
|
|
void IAsyncStateMachine.MoveNext()
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
|
|
this.MoveNext();
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
private void SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
}
|
|
|
|
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
|
|
this.SetStateMachine(stateMachine);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
IAsyncEnumerator<string> IAsyncEnumerable<string>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
_003CStreamAsync_003Ed__34 _003CStreamAsync_003Ed__;
|
|
if (_003C_003E1__state == -2 && _003C_003El__initialThreadId == Environment.CurrentManagedThreadId)
|
|
{
|
|
_003C_003E1__state = -3;
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
_003C_003Ew__disposeMode = false;
|
|
_003CStreamAsync_003Ed__ = this;
|
|
}
|
|
else
|
|
{
|
|
_003CStreamAsync_003Ed__ = new _003CStreamAsync_003Ed__34(-3)
|
|
{
|
|
_003C_003E4__this = _003C_003E4__this
|
|
};
|
|
}
|
|
_003CStreamAsync_003Ed__.messages = _003C_003E3__messages;
|
|
if (_003C_003E3__ct.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamAsync_003Ed__.ct = cancellationToken;
|
|
}
|
|
else if (cancellationToken.Equals(_003C_003E3__ct) || cancellationToken.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamAsync_003Ed__.ct = _003C_003E3__ct;
|
|
}
|
|
else
|
|
{
|
|
_003C_003Ex__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(_003C_003E3__ct, cancellationToken);
|
|
_003CStreamAsync_003Ed__.ct = _003C_003Ex__combinedTokens.Token;
|
|
}
|
|
return _003CStreamAsync_003Ed__;
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask<bool> IAsyncEnumerator<string>.MoveNextAsync()
|
|
{
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask<bool>);
|
|
}
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamAsync_003Ed__34 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
short version = _003C_003Ev__promiseOfValueOrEnd.Version;
|
|
if (_003C_003Ev__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded)
|
|
{
|
|
return new ValueTask<bool>(_003C_003Ev__promiseOfValueOrEnd.GetResult(version));
|
|
}
|
|
return new ValueTask<bool>(this, version);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
bool IValueTaskSource<bool>.GetResult(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource<bool>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.GetResult(short token)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask IAsyncDisposable.DisposeAsync()
|
|
{
|
|
if (_003C_003E1__state >= -1)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask);
|
|
}
|
|
_003C_003Ew__disposeMode = true;
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamAsync_003Ed__34 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
return new ValueTask(this, _003C_003Ev__promiseOfValueOrEnd.Version);
|
|
}
|
|
}
|
|
|
|
[CompilerGenerated]
|
|
private sealed class _003CStreamClaudeAsync_003Ed__46 : IAsyncEnumerable<string>, IAsyncEnumerator<string>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine
|
|
{
|
|
public int _003C_003E1__state;
|
|
|
|
public AsyncIteratorMethodBuilder _003C_003Et__builder;
|
|
|
|
public ManualResetValueTaskSourceCore<bool> _003C_003Ev__promiseOfValueOrEnd;
|
|
|
|
private string _003C_003E2__current;
|
|
|
|
private bool _003C_003Ew__disposeMode;
|
|
|
|
private CancellationTokenSource _003C_003Ex__combinedTokens;
|
|
|
|
private int _003C_003El__initialThreadId;
|
|
|
|
private List<ChatMessage> messages;
|
|
|
|
public List<ChatMessage> _003C_003E3__messages;
|
|
|
|
private CancellationToken ct;
|
|
|
|
public CancellationToken _003C_003E3__ct;
|
|
|
|
public LlmService _003C_003E4__this;
|
|
|
|
private LlmSettings _003Cllm_003E5__1;
|
|
|
|
private string _003CapiKey_003E5__2;
|
|
|
|
private object _003Cbody_003E5__3;
|
|
|
|
private string _003Cjson_003E5__4;
|
|
|
|
private HttpRequestMessage _003Creq_003E5__5;
|
|
|
|
private HttpResponseMessage _003Cresp_003E5__6;
|
|
|
|
private Stream _003Cstream_003E5__7;
|
|
|
|
private StreamReader _003Creader_003E5__8;
|
|
|
|
private HttpResponseMessage _003C_003Es__9;
|
|
|
|
private string _003CerrBody_003E5__10;
|
|
|
|
private string _003C_003Es__11;
|
|
|
|
private Stream _003C_003Es__12;
|
|
|
|
private string _003Cline_003E5__13;
|
|
|
|
private string _003Cdata_003E5__14;
|
|
|
|
private string _003Ctext_003E5__15;
|
|
|
|
private string _003C_003Es__16;
|
|
|
|
private JsonDocument _003Cdoc_003E5__17;
|
|
|
|
private string _003Ctype_003E5__18;
|
|
|
|
private JsonElement _003Cdelta_003E5__19;
|
|
|
|
private JsonElement _003Ct_003E5__20;
|
|
|
|
private JsonElement _003Cmsg_003E5__21;
|
|
|
|
private JsonElement _003Cu1_003E5__22;
|
|
|
|
private JsonElement _003Cu2_003E5__23;
|
|
|
|
private JsonException _003Cex_003E5__24;
|
|
|
|
private TaskAwaiter<HttpResponseMessage> _003C_003Eu__1;
|
|
|
|
private TaskAwaiter<string> _003C_003Eu__2;
|
|
|
|
private TaskAwaiter<Stream> _003C_003Eu__3;
|
|
|
|
string IAsyncEnumerator<string>.Current
|
|
{
|
|
[DebuggerHidden]
|
|
get
|
|
{
|
|
return _003C_003E2__current;
|
|
}
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
public _003CStreamClaudeAsync_003Ed__46(int _003C_003E1__state)
|
|
{
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
this._003C_003E1__state = _003C_003E1__state;
|
|
_003C_003El__initialThreadId = Environment.CurrentManagedThreadId;
|
|
}
|
|
|
|
private void MoveNext()
|
|
{
|
|
int num = _003C_003E1__state;
|
|
try
|
|
{
|
|
switch (num)
|
|
{
|
|
default:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
num = (_003C_003E1__state = -1);
|
|
_003Cllm_003E5__1 = _003C_003E4__this._settings.Settings.Llm;
|
|
_003CapiKey_003E5__2 = _003Cllm_003E5__1.ApiKey;
|
|
if (string.IsNullOrEmpty(_003CapiKey_003E5__2))
|
|
{
|
|
throw new InvalidOperationException("Claude API 키가 설정되지 않았습니다.");
|
|
}
|
|
_003Cbody_003E5__3 = _003C_003E4__this.BuildClaudeBody(messages, stream: true);
|
|
_003Cjson_003E5__4 = JsonSerializer.Serialize(_003Cbody_003E5__3);
|
|
_003Creq_003E5__5 = new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages");
|
|
break;
|
|
}
|
|
goto end_IL_0007;
|
|
case -4:
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter<HttpResponseMessage> awaiter;
|
|
switch (num)
|
|
{
|
|
default:
|
|
_003Creq_003E5__5.Content = new StringContent(_003Cjson_003E5__4, Encoding.UTF8, "application/json");
|
|
_003Creq_003E5__5.Headers.Add("x-api-key", _003CapiKey_003E5__2);
|
|
_003Creq_003E5__5.Headers.Add("anthropic-version", "2023-06-01");
|
|
_003C_003E2__current = null;
|
|
awaiter = _003C_003E4__this._http.SendAsync(_003Creq_003E5__5, HttpCompletionOption.ResponseHeadersRead, ct).GetAwaiter();
|
|
if (!awaiter.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 0);
|
|
_003C_003Eu__1 = awaiter;
|
|
_003CStreamClaudeAsync_003Ed__46 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_01d9;
|
|
case 0:
|
|
awaiter = _003C_003Eu__1;
|
|
_003C_003Eu__1 = default(TaskAwaiter<HttpResponseMessage>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_01d9;
|
|
case -4:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
break;
|
|
IL_01d9:
|
|
_003C_003Es__9 = awaiter.GetResult();
|
|
_003Cresp_003E5__6 = _003C_003Es__9;
|
|
_003C_003Es__9 = null;
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter<string> awaiter3;
|
|
TaskAwaiter<Stream> awaiter2;
|
|
switch (num)
|
|
{
|
|
default:
|
|
if (!_003Cresp_003E5__6.IsSuccessStatusCode)
|
|
{
|
|
_003C_003E2__current = null;
|
|
awaiter3 = _003Cresp_003E5__6.Content.ReadAsStringAsync(ct).GetAwaiter();
|
|
if (!awaiter3.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 1);
|
|
_003C_003Eu__2 = awaiter3;
|
|
_003CStreamClaudeAsync_003Ed__46 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_02ad;
|
|
}
|
|
_003C_003E2__current = null;
|
|
awaiter2 = _003Cresp_003E5__6.Content.ReadAsStreamAsync(ct).GetAwaiter();
|
|
if (!awaiter2.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 2);
|
|
_003C_003Eu__3 = awaiter2;
|
|
_003CStreamClaudeAsync_003Ed__46 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_0356;
|
|
case 1:
|
|
awaiter3 = _003C_003Eu__2;
|
|
_003C_003Eu__2 = default(TaskAwaiter<string>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_02ad;
|
|
case 2:
|
|
awaiter2 = _003C_003Eu__3;
|
|
_003C_003Eu__3 = default(TaskAwaiter<Stream>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_0356;
|
|
case -4:
|
|
case 3:
|
|
break;
|
|
IL_02ad:
|
|
_003C_003Es__11 = awaiter3.GetResult();
|
|
_003CerrBody_003E5__10 = _003C_003Es__11;
|
|
_003C_003Es__11 = null;
|
|
throw new HttpRequestException(ClassifyHttpError(_003Cresp_003E5__6, _003CerrBody_003E5__10));
|
|
IL_0356:
|
|
_003C_003Es__12 = awaiter2.GetResult();
|
|
_003Cstream_003E5__7 = _003C_003Es__12;
|
|
_003C_003Es__12 = null;
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
if (num != -4 && num != 3)
|
|
{
|
|
_003Creader_003E5__8 = new StreamReader(_003Cstream_003E5__7);
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter<string> awaiter4;
|
|
if (num != -4)
|
|
{
|
|
if (num != 3)
|
|
{
|
|
goto IL_071e;
|
|
}
|
|
awaiter4 = _003C_003Eu__2;
|
|
_003C_003Eu__2 = default(TaskAwaiter<string>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_041f;
|
|
}
|
|
num = (_003C_003E1__state = -1);
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
goto IL_0708;
|
|
}
|
|
goto end_IL_0398;
|
|
IL_0708:
|
|
_003Cline_003E5__13 = null;
|
|
_003Cdata_003E5__14 = null;
|
|
_003Ctext_003E5__15 = null;
|
|
goto IL_071e;
|
|
IL_06c6:
|
|
if (string.IsNullOrEmpty(_003Ctext_003E5__15))
|
|
{
|
|
goto IL_0708;
|
|
}
|
|
_003C_003E2__current = _003Ctext_003E5__15;
|
|
num = (_003C_003E1__state = -4);
|
|
goto IL_0a2d;
|
|
IL_041f:
|
|
_003C_003Es__16 = awaiter4.GetResult();
|
|
_003Cline_003E5__13 = _003C_003Es__16;
|
|
_003C_003Es__16 = null;
|
|
if (_003Cline_003E5__13 != null)
|
|
{
|
|
if (string.IsNullOrEmpty(_003Cline_003E5__13) || !_003Cline_003E5__13.StartsWith("data: "))
|
|
{
|
|
goto IL_071e;
|
|
}
|
|
string text = _003Cline_003E5__13;
|
|
int length = "data: ".Length;
|
|
_003Cdata_003E5__14 = text.Substring(length, text.Length - length);
|
|
_003Ctext_003E5__15 = null;
|
|
try
|
|
{
|
|
_003Cdoc_003E5__17 = JsonDocument.Parse(_003Cdata_003E5__14);
|
|
try
|
|
{
|
|
_003Ctype_003E5__18 = _003Cdoc_003E5__17.RootElement.GetProperty("type").GetString();
|
|
if (_003Ctype_003E5__18 == "content_block_delta")
|
|
{
|
|
_003Cdelta_003E5__19 = _003Cdoc_003E5__17.RootElement.GetProperty("delta");
|
|
if (_003Cdelta_003E5__19.TryGetProperty("text", out _003Ct_003E5__20))
|
|
{
|
|
_003Ctext_003E5__15 = _003Ct_003E5__20.GetString();
|
|
}
|
|
_003Cdelta_003E5__19 = default(JsonElement);
|
|
_003Ct_003E5__20 = default(JsonElement);
|
|
}
|
|
else
|
|
{
|
|
text = _003Ctype_003E5__18;
|
|
if ((text == "message_start" || text == "message_delta") ? true : false)
|
|
{
|
|
if (_003Cdoc_003E5__17.RootElement.TryGetProperty("message", out _003Cmsg_003E5__21) && _003Cmsg_003E5__21.TryGetProperty("usage", out _003Cu1_003E5__22))
|
|
{
|
|
_003C_003E4__this.TryParseClaudeUsageFromElement(_003Cu1_003E5__22);
|
|
}
|
|
else
|
|
{
|
|
if (_003Cdoc_003E5__17.RootElement.TryGetProperty("usage", out _003Cu2_003E5__23))
|
|
{
|
|
_003C_003E4__this.TryParseClaudeUsageFromElement(_003Cu2_003E5__23);
|
|
}
|
|
_003Cu2_003E5__23 = default(JsonElement);
|
|
}
|
|
_003Cmsg_003E5__21 = default(JsonElement);
|
|
_003Cu1_003E5__22 = default(JsonElement);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cdoc_003E5__17 != null)
|
|
{
|
|
((IDisposable)_003Cdoc_003E5__17).Dispose();
|
|
}
|
|
}
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cdoc_003E5__17 = null;
|
|
_003Ctype_003E5__18 = null;
|
|
goto IL_06c6;
|
|
}
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
_003Cex_003E5__24 = ex;
|
|
LogService.Warn("Claude 스트리밍 JSON 파싱 오류: " + _003Cex_003E5__24.Message);
|
|
goto IL_06c6;
|
|
}
|
|
goto end_IL_0398;
|
|
}
|
|
goto end_IL_0377;
|
|
IL_071e:
|
|
if (!_003Creader_003E5__8.EndOfStream && !ct.IsCancellationRequested)
|
|
{
|
|
_003C_003E2__current = null;
|
|
awaiter4 = ReadLineWithTimeoutAsync(_003Creader_003E5__8, ct).GetAwaiter();
|
|
if (!awaiter4.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 3);
|
|
_003C_003Eu__2 = awaiter4;
|
|
_003CStreamClaudeAsync_003Ed__46 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter4, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_041f;
|
|
}
|
|
goto end_IL_0377;
|
|
end_IL_0398:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Creader_003E5__8 != null)
|
|
{
|
|
((IDisposable)_003Creader_003E5__8).Dispose();
|
|
}
|
|
}
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_0788;
|
|
end_IL_0377:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cstream_003E5__7 != null)
|
|
{
|
|
((IDisposable)_003Cstream_003E5__7).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_01fa;
|
|
IL_0788:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_07ad;
|
|
end_IL_01fa:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cresp_003E5__6 != null)
|
|
{
|
|
((IDisposable)_003Cresp_003E5__6).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_00d6;
|
|
IL_07ad:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_07d2;
|
|
end_IL_00d6:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Creq_003E5__5 != null)
|
|
{
|
|
((IDisposable)_003Creq_003E5__5).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_0007;
|
|
IL_07d2:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cllm_003E5__1 = null;
|
|
_003CapiKey_003E5__2 = null;
|
|
_003Cbody_003E5__3 = null;
|
|
_003Cjson_003E5__4 = null;
|
|
_003Creq_003E5__5 = null;
|
|
_003Cresp_003E5__6 = null;
|
|
_003Cstream_003E5__7 = null;
|
|
_003Creader_003E5__8 = null;
|
|
}
|
|
end_IL_0007:;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_003C_003E1__state = -2;
|
|
_003Cllm_003E5__1 = null;
|
|
_003CapiKey_003E5__2 = null;
|
|
_003Cbody_003E5__3 = null;
|
|
_003Cjson_003E5__4 = null;
|
|
_003Creq_003E5__5 = null;
|
|
_003Cresp_003E5__6 = null;
|
|
_003Cstream_003E5__7 = null;
|
|
_003Creader_003E5__8 = null;
|
|
_003C_003Es__9 = null;
|
|
_003CerrBody_003E5__10 = null;
|
|
_003C_003Es__11 = null;
|
|
_003C_003Es__12 = null;
|
|
_003Cline_003E5__13 = null;
|
|
_003Cdata_003E5__14 = null;
|
|
_003Ctext_003E5__15 = null;
|
|
_003C_003Es__16 = null;
|
|
_003Cdoc_003E5__17 = null;
|
|
_003Ctype_003E5__18 = null;
|
|
_003Cdelta_003E5__19 = default(JsonElement);
|
|
_003Ct_003E5__20 = default(JsonElement);
|
|
_003Cmsg_003E5__21 = default(JsonElement);
|
|
_003Cu1_003E5__22 = default(JsonElement);
|
|
_003Cu2_003E5__23 = default(JsonElement);
|
|
_003Cex_003E5__24 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetException(exception);
|
|
return;
|
|
}
|
|
_003C_003E1__state = -2;
|
|
_003Cllm_003E5__1 = null;
|
|
_003CapiKey_003E5__2 = null;
|
|
_003Cbody_003E5__3 = null;
|
|
_003Cjson_003E5__4 = null;
|
|
_003Creq_003E5__5 = null;
|
|
_003Cresp_003E5__6 = null;
|
|
_003Cstream_003E5__7 = null;
|
|
_003Creader_003E5__8 = null;
|
|
_003C_003Es__9 = null;
|
|
_003CerrBody_003E5__10 = null;
|
|
_003C_003Es__11 = null;
|
|
_003C_003Es__12 = null;
|
|
_003Cline_003E5__13 = null;
|
|
_003Cdata_003E5__14 = null;
|
|
_003Ctext_003E5__15 = null;
|
|
_003C_003Es__16 = null;
|
|
_003Cdoc_003E5__17 = null;
|
|
_003Ctype_003E5__18 = null;
|
|
_003Cdelta_003E5__19 = default(JsonElement);
|
|
_003Ct_003E5__20 = default(JsonElement);
|
|
_003Cmsg_003E5__21 = default(JsonElement);
|
|
_003Cu1_003E5__22 = default(JsonElement);
|
|
_003Cu2_003E5__23 = default(JsonElement);
|
|
_003Cex_003E5__24 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: false);
|
|
return;
|
|
IL_0a2d:
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: true);
|
|
}
|
|
|
|
void IAsyncStateMachine.MoveNext()
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
|
|
this.MoveNext();
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
private void SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
}
|
|
|
|
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
|
|
this.SetStateMachine(stateMachine);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
IAsyncEnumerator<string> IAsyncEnumerable<string>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
_003CStreamClaudeAsync_003Ed__46 _003CStreamClaudeAsync_003Ed__;
|
|
if (_003C_003E1__state == -2 && _003C_003El__initialThreadId == Environment.CurrentManagedThreadId)
|
|
{
|
|
_003C_003E1__state = -3;
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
_003C_003Ew__disposeMode = false;
|
|
_003CStreamClaudeAsync_003Ed__ = this;
|
|
}
|
|
else
|
|
{
|
|
_003CStreamClaudeAsync_003Ed__ = new _003CStreamClaudeAsync_003Ed__46(-3)
|
|
{
|
|
_003C_003E4__this = _003C_003E4__this
|
|
};
|
|
}
|
|
_003CStreamClaudeAsync_003Ed__.messages = _003C_003E3__messages;
|
|
if (_003C_003E3__ct.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamClaudeAsync_003Ed__.ct = cancellationToken;
|
|
}
|
|
else if (cancellationToken.Equals(_003C_003E3__ct) || cancellationToken.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamClaudeAsync_003Ed__.ct = _003C_003E3__ct;
|
|
}
|
|
else
|
|
{
|
|
_003C_003Ex__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(_003C_003E3__ct, cancellationToken);
|
|
_003CStreamClaudeAsync_003Ed__.ct = _003C_003Ex__combinedTokens.Token;
|
|
}
|
|
return _003CStreamClaudeAsync_003Ed__;
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask<bool> IAsyncEnumerator<string>.MoveNextAsync()
|
|
{
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask<bool>);
|
|
}
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamClaudeAsync_003Ed__46 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
short version = _003C_003Ev__promiseOfValueOrEnd.Version;
|
|
if (_003C_003Ev__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded)
|
|
{
|
|
return new ValueTask<bool>(_003C_003Ev__promiseOfValueOrEnd.GetResult(version));
|
|
}
|
|
return new ValueTask<bool>(this, version);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
bool IValueTaskSource<bool>.GetResult(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource<bool>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.GetResult(short token)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask IAsyncDisposable.DisposeAsync()
|
|
{
|
|
if (_003C_003E1__state >= -1)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask);
|
|
}
|
|
_003C_003Ew__disposeMode = true;
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamClaudeAsync_003Ed__46 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
return new ValueTask(this, _003C_003Ev__promiseOfValueOrEnd.Version);
|
|
}
|
|
}
|
|
|
|
[CompilerGenerated]
|
|
private sealed class _003CStreamGeminiAsync_003Ed__43 : IAsyncEnumerable<string>, IAsyncEnumerator<string>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine
|
|
{
|
|
public int _003C_003E1__state;
|
|
|
|
public AsyncIteratorMethodBuilder _003C_003Et__builder;
|
|
|
|
public ManualResetValueTaskSourceCore<bool> _003C_003Ev__promiseOfValueOrEnd;
|
|
|
|
private string _003C_003E2__current;
|
|
|
|
private bool _003C_003Ew__disposeMode;
|
|
|
|
private CancellationTokenSource _003C_003Ex__combinedTokens;
|
|
|
|
private int _003C_003El__initialThreadId;
|
|
|
|
private List<ChatMessage> messages;
|
|
|
|
public List<ChatMessage> _003C_003E3__messages;
|
|
|
|
private CancellationToken ct;
|
|
|
|
public CancellationToken _003C_003E3__ct;
|
|
|
|
public LlmService _003C_003E4__this;
|
|
|
|
private LlmSettings _003Cllm_003E5__1;
|
|
|
|
private string _003CapiKey_003E5__2;
|
|
|
|
private string _003Cmodel_003E5__3;
|
|
|
|
private object _003Cbody_003E5__4;
|
|
|
|
private string _003Curl_003E5__5;
|
|
|
|
private HttpRequestMessage _003Creq_003E5__6;
|
|
|
|
private HttpResponseMessage _003Cresp_003E5__7;
|
|
|
|
private Stream _003Cstream_003E5__8;
|
|
|
|
private StreamReader _003Creader_003E5__9;
|
|
|
|
private HttpResponseMessage _003C_003Es__10;
|
|
|
|
private Stream _003C_003Es__11;
|
|
|
|
private string _003Cline_003E5__12;
|
|
|
|
private string _003Cdata_003E5__13;
|
|
|
|
private string _003Cparsed_003E5__14;
|
|
|
|
private string _003C_003Es__15;
|
|
|
|
private JsonDocument _003Cdoc_003E5__16;
|
|
|
|
private JsonElement _003Ccandidates_003E5__17;
|
|
|
|
private StringBuilder _003Csb_003E5__18;
|
|
|
|
private JsonElement _003Cparts_003E5__19;
|
|
|
|
private JsonElement.ArrayEnumerator _003C_003Es__20;
|
|
|
|
private JsonElement _003Cpart_003E5__21;
|
|
|
|
private JsonElement _003Ct_003E5__22;
|
|
|
|
private string _003Ctext_003E5__23;
|
|
|
|
private JsonException _003Cex_003E5__24;
|
|
|
|
private TaskAwaiter<HttpResponseMessage> _003C_003Eu__1;
|
|
|
|
private TaskAwaiter<Stream> _003C_003Eu__2;
|
|
|
|
private TaskAwaiter<string?> _003C_003Eu__3;
|
|
|
|
string IAsyncEnumerator<string>.Current
|
|
{
|
|
[DebuggerHidden]
|
|
get
|
|
{
|
|
return _003C_003E2__current;
|
|
}
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
public _003CStreamGeminiAsync_003Ed__43(int _003C_003E1__state)
|
|
{
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
this._003C_003E1__state = _003C_003E1__state;
|
|
_003C_003El__initialThreadId = Environment.CurrentManagedThreadId;
|
|
}
|
|
|
|
private void MoveNext()
|
|
{
|
|
int num = _003C_003E1__state;
|
|
try
|
|
{
|
|
switch (num)
|
|
{
|
|
default:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
num = (_003C_003E1__state = -1);
|
|
_003Cllm_003E5__1 = _003C_003E4__this._settings.Settings.Llm;
|
|
_003CapiKey_003E5__2 = _003C_003E4__this.ResolveApiKeyForService("gemini");
|
|
if (string.IsNullOrEmpty(_003CapiKey_003E5__2))
|
|
{
|
|
throw new InvalidOperationException("Gemini API 키가 설정되지 않았습니다.");
|
|
}
|
|
_003Cmodel_003E5__3 = _003C_003E4__this.ResolveModel();
|
|
_003Cbody_003E5__4 = _003C_003E4__this.BuildGeminiBody(messages);
|
|
_003Curl_003E5__5 = "https://generativelanguage.googleapis.com/v1beta/models/" + _003Cmodel_003E5__3 + ":streamGenerateContent?alt=sse&key=" + _003CapiKey_003E5__2;
|
|
_003Creq_003E5__6 = new HttpRequestMessage(HttpMethod.Post, _003Curl_003E5__5)
|
|
{
|
|
Content = JsonContent(_003Cbody_003E5__4)
|
|
};
|
|
break;
|
|
}
|
|
goto end_IL_0007;
|
|
case -4:
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter<HttpResponseMessage> awaiter;
|
|
switch (num)
|
|
{
|
|
default:
|
|
_003C_003E2__current = null;
|
|
awaiter = _003C_003E4__this.SendWithErrorClassificationAsync(_003Creq_003E5__6, ct).GetAwaiter();
|
|
if (!awaiter.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 0);
|
|
_003C_003Eu__1 = awaiter;
|
|
_003CStreamGeminiAsync_003Ed__43 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_01a7;
|
|
case 0:
|
|
awaiter = _003C_003Eu__1;
|
|
_003C_003Eu__1 = default(TaskAwaiter<HttpResponseMessage>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_01a7;
|
|
case -4:
|
|
case 1:
|
|
case 2:
|
|
break;
|
|
IL_01a7:
|
|
_003C_003Es__10 = awaiter.GetResult();
|
|
_003Cresp_003E5__7 = _003C_003Es__10;
|
|
_003C_003Es__10 = null;
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
if (num != -4)
|
|
{
|
|
TaskAwaiter<Stream> awaiter2;
|
|
if (num != 1)
|
|
{
|
|
if (num == 2)
|
|
{
|
|
goto IL_0275;
|
|
}
|
|
_003C_003E2__current = null;
|
|
awaiter2 = _003Cresp_003E5__7.Content.ReadAsStreamAsync(ct).GetAwaiter();
|
|
if (!awaiter2.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 1);
|
|
_003C_003Eu__2 = awaiter2;
|
|
_003CStreamGeminiAsync_003Ed__43 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
awaiter2 = _003C_003Eu__2;
|
|
_003C_003Eu__2 = default(TaskAwaiter<Stream>);
|
|
num = (_003C_003E1__state = -1);
|
|
}
|
|
_003C_003Es__11 = awaiter2.GetResult();
|
|
_003Cstream_003E5__8 = _003C_003Es__11;
|
|
_003C_003Es__11 = null;
|
|
}
|
|
goto IL_0275;
|
|
IL_0699:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_06be;
|
|
IL_0275:
|
|
try
|
|
{
|
|
if (num != -4 && num != 2)
|
|
{
|
|
_003Creader_003E5__9 = new StreamReader(_003Cstream_003E5__8);
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter<string> awaiter3;
|
|
if (num != -4)
|
|
{
|
|
if (num != 2)
|
|
{
|
|
goto IL_062f;
|
|
}
|
|
awaiter3 = _003C_003Eu__3;
|
|
_003C_003Eu__3 = default(TaskAwaiter<string>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_031d;
|
|
}
|
|
num = (_003C_003E1__state = -1);
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
goto IL_0619;
|
|
}
|
|
goto end_IL_0296;
|
|
IL_0619:
|
|
_003Cline_003E5__12 = null;
|
|
_003Cdata_003E5__13 = null;
|
|
_003Cparsed_003E5__14 = null;
|
|
goto IL_062f;
|
|
IL_05dc:
|
|
if (_003Cparsed_003E5__14 == null)
|
|
{
|
|
goto IL_0619;
|
|
}
|
|
_003C_003E2__current = _003Cparsed_003E5__14;
|
|
num = (_003C_003E1__state = -4);
|
|
goto IL_0945;
|
|
IL_031d:
|
|
_003C_003Es__15 = awaiter3.GetResult();
|
|
_003Cline_003E5__12 = _003C_003Es__15;
|
|
_003C_003Es__15 = null;
|
|
if (_003Cline_003E5__12 != null)
|
|
{
|
|
if (!string.IsNullOrEmpty(_003Cline_003E5__12) && _003Cline_003E5__12.StartsWith("data: "))
|
|
{
|
|
string text = _003Cline_003E5__12;
|
|
int length = "data: ".Length;
|
|
_003Cdata_003E5__13 = text.Substring(length, text.Length - length);
|
|
_003Cparsed_003E5__14 = null;
|
|
try
|
|
{
|
|
_003Cdoc_003E5__16 = JsonDocument.Parse(_003Cdata_003E5__13);
|
|
try
|
|
{
|
|
_003C_003E4__this.TryParseGeminiUsage(_003Cdoc_003E5__16.RootElement);
|
|
_003Ccandidates_003E5__17 = _003Cdoc_003E5__16.RootElement.GetProperty("candidates");
|
|
if (_003Ccandidates_003E5__17.GetArrayLength() != 0)
|
|
{
|
|
_003Csb_003E5__18 = new StringBuilder();
|
|
_003Cparts_003E5__19 = _003Ccandidates_003E5__17[0].GetProperty("content").GetProperty("parts");
|
|
_003C_003Es__20 = _003Cparts_003E5__19.EnumerateArray().GetEnumerator();
|
|
try
|
|
{
|
|
while (_003C_003Es__20.MoveNext())
|
|
{
|
|
_003Cpart_003E5__21 = _003C_003Es__20.Current;
|
|
if (_003Cpart_003E5__21.TryGetProperty("text", out _003Ct_003E5__22))
|
|
{
|
|
_003Ctext_003E5__23 = _003Ct_003E5__22.GetString();
|
|
if (!string.IsNullOrEmpty(_003Ctext_003E5__23))
|
|
{
|
|
_003Csb_003E5__18.Append(_003Ctext_003E5__23);
|
|
}
|
|
_003Ctext_003E5__23 = null;
|
|
}
|
|
_003Ct_003E5__22 = default(JsonElement);
|
|
_003Cpart_003E5__21 = default(JsonElement);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1)
|
|
{
|
|
((IDisposable)_003C_003Es__20/*cast due to .constrained prefix*/).Dispose();
|
|
}
|
|
}
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003C_003Es__20 = default(JsonElement.ArrayEnumerator);
|
|
if (_003Csb_003E5__18.Length > 0)
|
|
{
|
|
_003Cparsed_003E5__14 = _003Csb_003E5__18.ToString();
|
|
}
|
|
}
|
|
goto IL_057d;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cdoc_003E5__16 != null)
|
|
{
|
|
((IDisposable)_003Cdoc_003E5__16).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_03b3;
|
|
IL_057d:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cdoc_003E5__16 = null;
|
|
_003Ccandidates_003E5__17 = default(JsonElement);
|
|
_003Csb_003E5__18 = null;
|
|
_003Cparts_003E5__19 = default(JsonElement);
|
|
goto IL_05dc;
|
|
}
|
|
goto end_IL_0296;
|
|
end_IL_03b3:;
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
_003Cex_003E5__24 = ex;
|
|
LogService.Warn("Gemini 스트리밍 JSON 파싱 오류: " + _003Cex_003E5__24.Message);
|
|
goto IL_05dc;
|
|
}
|
|
}
|
|
goto IL_062f;
|
|
}
|
|
goto end_IL_0275;
|
|
IL_062f:
|
|
if (!_003Creader_003E5__9.EndOfStream && !ct.IsCancellationRequested)
|
|
{
|
|
_003C_003E2__current = null;
|
|
awaiter3 = ReadLineWithTimeoutAsync(_003Creader_003E5__9, ct).GetAwaiter();
|
|
if (!awaiter3.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 2);
|
|
_003C_003Eu__3 = awaiter3;
|
|
_003CStreamGeminiAsync_003Ed__43 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_031d;
|
|
}
|
|
goto end_IL_0275;
|
|
end_IL_0296:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Creader_003E5__9 != null)
|
|
{
|
|
((IDisposable)_003Creader_003E5__9).Dispose();
|
|
}
|
|
}
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_0699;
|
|
end_IL_0275:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cstream_003E5__8 != null)
|
|
{
|
|
((IDisposable)_003Cstream_003E5__8).Dispose();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cresp_003E5__7 != null)
|
|
{
|
|
((IDisposable)_003Cresp_003E5__7).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_0109;
|
|
IL_06be:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_06e3;
|
|
end_IL_0109:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Creq_003E5__6 != null)
|
|
{
|
|
((IDisposable)_003Creq_003E5__6).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_0007;
|
|
IL_06e3:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cllm_003E5__1 = null;
|
|
_003CapiKey_003E5__2 = null;
|
|
_003Cmodel_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
}
|
|
end_IL_0007:;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_003C_003E1__state = -2;
|
|
_003Cllm_003E5__1 = null;
|
|
_003CapiKey_003E5__2 = null;
|
|
_003Cmodel_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
_003C_003Es__10 = null;
|
|
_003C_003Es__11 = null;
|
|
_003Cline_003E5__12 = null;
|
|
_003Cdata_003E5__13 = null;
|
|
_003Cparsed_003E5__14 = null;
|
|
_003C_003Es__15 = null;
|
|
_003Cdoc_003E5__16 = null;
|
|
_003Ccandidates_003E5__17 = default(JsonElement);
|
|
_003Csb_003E5__18 = null;
|
|
_003Cparts_003E5__19 = default(JsonElement);
|
|
_003C_003Es__20 = default(JsonElement.ArrayEnumerator);
|
|
_003Cpart_003E5__21 = default(JsonElement);
|
|
_003Ct_003E5__22 = default(JsonElement);
|
|
_003Ctext_003E5__23 = null;
|
|
_003Cex_003E5__24 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetException(exception);
|
|
return;
|
|
}
|
|
_003C_003E1__state = -2;
|
|
_003Cllm_003E5__1 = null;
|
|
_003CapiKey_003E5__2 = null;
|
|
_003Cmodel_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
_003C_003Es__10 = null;
|
|
_003C_003Es__11 = null;
|
|
_003Cline_003E5__12 = null;
|
|
_003Cdata_003E5__13 = null;
|
|
_003Cparsed_003E5__14 = null;
|
|
_003C_003Es__15 = null;
|
|
_003Cdoc_003E5__16 = null;
|
|
_003Ccandidates_003E5__17 = default(JsonElement);
|
|
_003Csb_003E5__18 = null;
|
|
_003Cparts_003E5__19 = default(JsonElement);
|
|
_003C_003Es__20 = default(JsonElement.ArrayEnumerator);
|
|
_003Cpart_003E5__21 = default(JsonElement);
|
|
_003Ct_003E5__22 = default(JsonElement);
|
|
_003Ctext_003E5__23 = null;
|
|
_003Cex_003E5__24 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: false);
|
|
return;
|
|
IL_0945:
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: true);
|
|
}
|
|
|
|
void IAsyncStateMachine.MoveNext()
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
|
|
this.MoveNext();
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
private void SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
}
|
|
|
|
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
|
|
this.SetStateMachine(stateMachine);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
IAsyncEnumerator<string> IAsyncEnumerable<string>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
_003CStreamGeminiAsync_003Ed__43 _003CStreamGeminiAsync_003Ed__;
|
|
if (_003C_003E1__state == -2 && _003C_003El__initialThreadId == Environment.CurrentManagedThreadId)
|
|
{
|
|
_003C_003E1__state = -3;
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
_003C_003Ew__disposeMode = false;
|
|
_003CStreamGeminiAsync_003Ed__ = this;
|
|
}
|
|
else
|
|
{
|
|
_003CStreamGeminiAsync_003Ed__ = new _003CStreamGeminiAsync_003Ed__43(-3)
|
|
{
|
|
_003C_003E4__this = _003C_003E4__this
|
|
};
|
|
}
|
|
_003CStreamGeminiAsync_003Ed__.messages = _003C_003E3__messages;
|
|
if (_003C_003E3__ct.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamGeminiAsync_003Ed__.ct = cancellationToken;
|
|
}
|
|
else if (cancellationToken.Equals(_003C_003E3__ct) || cancellationToken.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamGeminiAsync_003Ed__.ct = _003C_003E3__ct;
|
|
}
|
|
else
|
|
{
|
|
_003C_003Ex__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(_003C_003E3__ct, cancellationToken);
|
|
_003CStreamGeminiAsync_003Ed__.ct = _003C_003Ex__combinedTokens.Token;
|
|
}
|
|
return _003CStreamGeminiAsync_003Ed__;
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask<bool> IAsyncEnumerator<string>.MoveNextAsync()
|
|
{
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask<bool>);
|
|
}
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamGeminiAsync_003Ed__43 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
short version = _003C_003Ev__promiseOfValueOrEnd.Version;
|
|
if (_003C_003Ev__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded)
|
|
{
|
|
return new ValueTask<bool>(_003C_003Ev__promiseOfValueOrEnd.GetResult(version));
|
|
}
|
|
return new ValueTask<bool>(this, version);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
bool IValueTaskSource<bool>.GetResult(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource<bool>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.GetResult(short token)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask IAsyncDisposable.DisposeAsync()
|
|
{
|
|
if (_003C_003E1__state >= -1)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask);
|
|
}
|
|
_003C_003Ew__disposeMode = true;
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamGeminiAsync_003Ed__43 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
return new ValueTask(this, _003C_003Ev__promiseOfValueOrEnd.Version);
|
|
}
|
|
}
|
|
|
|
[CompilerGenerated]
|
|
private sealed class _003CStreamOllamaAsync_003Ed__37 : IAsyncEnumerable<string>, IAsyncEnumerator<string>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine
|
|
{
|
|
public int _003C_003E1__state;
|
|
|
|
public AsyncIteratorMethodBuilder _003C_003Et__builder;
|
|
|
|
public ManualResetValueTaskSourceCore<bool> _003C_003Ev__promiseOfValueOrEnd;
|
|
|
|
private string _003C_003E2__current;
|
|
|
|
private bool _003C_003Ew__disposeMode;
|
|
|
|
private CancellationTokenSource _003C_003Ex__combinedTokens;
|
|
|
|
private int _003C_003El__initialThreadId;
|
|
|
|
private List<ChatMessage> messages;
|
|
|
|
public List<ChatMessage> _003C_003E3__messages;
|
|
|
|
private CancellationToken ct;
|
|
|
|
public CancellationToken _003C_003E3__ct;
|
|
|
|
public LlmService _003C_003E4__this;
|
|
|
|
private LlmSettings _003Cllm_003E5__1;
|
|
|
|
private string _003Cendpoint_003E5__2;
|
|
|
|
private string _003Cep_003E5__3;
|
|
|
|
private object _003Cbody_003E5__4;
|
|
|
|
private string _003Curl_003E5__5;
|
|
|
|
private HttpRequestMessage _003Creq_003E5__6;
|
|
|
|
private HttpResponseMessage _003Cresp_003E5__7;
|
|
|
|
private Stream _003Cstream_003E5__8;
|
|
|
|
private StreamReader _003Creader_003E5__9;
|
|
|
|
private HttpResponseMessage _003C_003Es__10;
|
|
|
|
private Stream _003C_003Es__11;
|
|
|
|
private string _003Cline_003E5__12;
|
|
|
|
private string _003Ctext_003E5__13;
|
|
|
|
private string _003C_003Es__14;
|
|
|
|
private JsonDocument _003Cdoc_003E5__15;
|
|
|
|
private JsonElement _003Cmsg_003E5__16;
|
|
|
|
private JsonElement _003Cc_003E5__17;
|
|
|
|
private JsonElement _003Cdone_003E5__18;
|
|
|
|
private JsonException _003Cex_003E5__19;
|
|
|
|
private TaskAwaiter<HttpResponseMessage> _003C_003Eu__1;
|
|
|
|
private TaskAwaiter<Stream> _003C_003Eu__2;
|
|
|
|
private TaskAwaiter<string?> _003C_003Eu__3;
|
|
|
|
string IAsyncEnumerator<string>.Current
|
|
{
|
|
[DebuggerHidden]
|
|
get
|
|
{
|
|
return _003C_003E2__current;
|
|
}
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
public _003CStreamOllamaAsync_003Ed__37(int _003C_003E1__state)
|
|
{
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
this._003C_003E1__state = _003C_003E1__state;
|
|
_003C_003El__initialThreadId = Environment.CurrentManagedThreadId;
|
|
}
|
|
|
|
private void MoveNext()
|
|
{
|
|
int num = _003C_003E1__state;
|
|
try
|
|
{
|
|
switch (num)
|
|
{
|
|
default:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
num = (_003C_003E1__state = -1);
|
|
_003Cllm_003E5__1 = _003C_003E4__this._settings.Settings.Llm;
|
|
_003Cendpoint_003E5__2 = _003C_003E4__this.ResolveServerInfo().Endpoint;
|
|
_003Cep_003E5__3 = (string.IsNullOrEmpty(_003Cendpoint_003E5__2) ? _003Cllm_003E5__1.Endpoint : _003Cendpoint_003E5__2);
|
|
_003Cbody_003E5__4 = _003C_003E4__this.BuildOllamaBody(messages, stream: true);
|
|
_003Curl_003E5__5 = _003Cep_003E5__3.TrimEnd('/') + "/api/chat";
|
|
_003Creq_003E5__6 = new HttpRequestMessage(HttpMethod.Post, _003Curl_003E5__5)
|
|
{
|
|
Content = JsonContent(_003Cbody_003E5__4)
|
|
};
|
|
break;
|
|
}
|
|
goto end_IL_0007;
|
|
case -4:
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter<HttpResponseMessage> awaiter;
|
|
switch (num)
|
|
{
|
|
default:
|
|
_003C_003E2__current = null;
|
|
awaiter = _003C_003E4__this.SendWithErrorClassificationAsync(_003Creq_003E5__6, ct).GetAwaiter();
|
|
if (!awaiter.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 0);
|
|
_003C_003Eu__1 = awaiter;
|
|
_003CStreamOllamaAsync_003Ed__37 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_01a1;
|
|
case 0:
|
|
awaiter = _003C_003Eu__1;
|
|
_003C_003Eu__1 = default(TaskAwaiter<HttpResponseMessage>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_01a1;
|
|
case -4:
|
|
case 1:
|
|
case 2:
|
|
break;
|
|
IL_01a1:
|
|
_003C_003Es__10 = awaiter.GetResult();
|
|
_003Cresp_003E5__7 = _003C_003Es__10;
|
|
_003C_003Es__10 = null;
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
if (num != -4)
|
|
{
|
|
TaskAwaiter<Stream> awaiter2;
|
|
if (num != 1)
|
|
{
|
|
if (num == 2)
|
|
{
|
|
goto IL_026f;
|
|
}
|
|
_003C_003E2__current = null;
|
|
awaiter2 = _003Cresp_003E5__7.Content.ReadAsStreamAsync(ct).GetAwaiter();
|
|
if (!awaiter2.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 1);
|
|
_003C_003Eu__2 = awaiter2;
|
|
_003CStreamOllamaAsync_003Ed__37 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
awaiter2 = _003C_003Eu__2;
|
|
_003C_003Eu__2 = default(TaskAwaiter<Stream>);
|
|
num = (_003C_003E1__state = -1);
|
|
}
|
|
_003C_003Es__11 = awaiter2.GetResult();
|
|
_003Cstream_003E5__8 = _003C_003Es__11;
|
|
_003C_003Es__11 = null;
|
|
}
|
|
goto IL_026f;
|
|
IL_055b:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_0580;
|
|
IL_026f:
|
|
try
|
|
{
|
|
if (num != -4 && num != 2)
|
|
{
|
|
_003Creader_003E5__9 = new StreamReader(_003Cstream_003E5__8);
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter<string> awaiter3;
|
|
if (num != -4)
|
|
{
|
|
if (num != 2)
|
|
{
|
|
goto IL_04f1;
|
|
}
|
|
awaiter3 = _003C_003Eu__3;
|
|
_003C_003Eu__3 = default(TaskAwaiter<string>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_0317;
|
|
}
|
|
num = (_003C_003E1__state = -1);
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
goto IL_04e2;
|
|
}
|
|
goto end_IL_0290;
|
|
IL_04a0:
|
|
if (string.IsNullOrEmpty(_003Ctext_003E5__13))
|
|
{
|
|
goto IL_04e2;
|
|
}
|
|
_003C_003E2__current = _003Ctext_003E5__13;
|
|
num = (_003C_003E1__state = -4);
|
|
goto IL_07ad;
|
|
IL_04e2:
|
|
_003Cline_003E5__12 = null;
|
|
_003Ctext_003E5__13 = null;
|
|
goto IL_04f1;
|
|
IL_0317:
|
|
_003C_003Es__14 = awaiter3.GetResult();
|
|
_003Cline_003E5__12 = _003C_003Es__14;
|
|
_003C_003Es__14 = null;
|
|
if (_003Cline_003E5__12 != null)
|
|
{
|
|
if (string.IsNullOrEmpty(_003Cline_003E5__12))
|
|
{
|
|
goto IL_04f1;
|
|
}
|
|
_003Ctext_003E5__13 = null;
|
|
try
|
|
{
|
|
_003Cdoc_003E5__15 = JsonDocument.Parse(_003Cline_003E5__12);
|
|
try
|
|
{
|
|
if (_003Cdoc_003E5__15.RootElement.TryGetProperty("message", out _003Cmsg_003E5__16) && _003Cmsg_003E5__16.TryGetProperty("content", out _003Cc_003E5__17))
|
|
{
|
|
_003Ctext_003E5__13 = _003Cc_003E5__17.GetString();
|
|
}
|
|
if (_003Cdoc_003E5__15.RootElement.TryGetProperty("done", out _003Cdone_003E5__18) && _003Cdone_003E5__18.GetBoolean())
|
|
{
|
|
_003C_003E4__this.TryParseOllamaUsage(_003Cdoc_003E5__15.RootElement);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cdoc_003E5__15 != null)
|
|
{
|
|
((IDisposable)_003Cdoc_003E5__15).Dispose();
|
|
}
|
|
}
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cdoc_003E5__15 = null;
|
|
_003Cmsg_003E5__16 = default(JsonElement);
|
|
_003Cc_003E5__17 = default(JsonElement);
|
|
_003Cdone_003E5__18 = default(JsonElement);
|
|
goto IL_04a0;
|
|
}
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
_003Cex_003E5__19 = ex;
|
|
LogService.Warn("Ollama 스트리밍 JSON 파싱 오류: " + _003Cex_003E5__19.Message);
|
|
goto IL_04a0;
|
|
}
|
|
goto end_IL_0290;
|
|
}
|
|
goto end_IL_026f;
|
|
IL_04f1:
|
|
if (!_003Creader_003E5__9.EndOfStream && !ct.IsCancellationRequested)
|
|
{
|
|
_003C_003E2__current = null;
|
|
awaiter3 = ReadLineWithTimeoutAsync(_003Creader_003E5__9, ct).GetAwaiter();
|
|
if (!awaiter3.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 2);
|
|
_003C_003Eu__3 = awaiter3;
|
|
_003CStreamOllamaAsync_003Ed__37 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_0317;
|
|
}
|
|
goto end_IL_026f;
|
|
end_IL_0290:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Creader_003E5__9 != null)
|
|
{
|
|
((IDisposable)_003Creader_003E5__9).Dispose();
|
|
}
|
|
}
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_055b;
|
|
end_IL_026f:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cstream_003E5__8 != null)
|
|
{
|
|
((IDisposable)_003Cstream_003E5__8).Dispose();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cresp_003E5__7 != null)
|
|
{
|
|
((IDisposable)_003Cresp_003E5__7).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_0103;
|
|
IL_0580:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_05a5;
|
|
end_IL_0103:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Creq_003E5__6 != null)
|
|
{
|
|
((IDisposable)_003Creq_003E5__6).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_0007;
|
|
IL_05a5:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cllm_003E5__1 = null;
|
|
_003Cendpoint_003E5__2 = null;
|
|
_003Cep_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
}
|
|
end_IL_0007:;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_003C_003E1__state = -2;
|
|
_003Cllm_003E5__1 = null;
|
|
_003Cendpoint_003E5__2 = null;
|
|
_003Cep_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
_003C_003Es__10 = null;
|
|
_003C_003Es__11 = null;
|
|
_003Cline_003E5__12 = null;
|
|
_003Ctext_003E5__13 = null;
|
|
_003C_003Es__14 = null;
|
|
_003Cdoc_003E5__15 = null;
|
|
_003Cmsg_003E5__16 = default(JsonElement);
|
|
_003Cc_003E5__17 = default(JsonElement);
|
|
_003Cdone_003E5__18 = default(JsonElement);
|
|
_003Cex_003E5__19 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetException(exception);
|
|
return;
|
|
}
|
|
_003C_003E1__state = -2;
|
|
_003Cllm_003E5__1 = null;
|
|
_003Cendpoint_003E5__2 = null;
|
|
_003Cep_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
_003C_003Es__10 = null;
|
|
_003C_003Es__11 = null;
|
|
_003Cline_003E5__12 = null;
|
|
_003Ctext_003E5__13 = null;
|
|
_003C_003Es__14 = null;
|
|
_003Cdoc_003E5__15 = null;
|
|
_003Cmsg_003E5__16 = default(JsonElement);
|
|
_003Cc_003E5__17 = default(JsonElement);
|
|
_003Cdone_003E5__18 = default(JsonElement);
|
|
_003Cex_003E5__19 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: false);
|
|
return;
|
|
IL_07ad:
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: true);
|
|
}
|
|
|
|
void IAsyncStateMachine.MoveNext()
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
|
|
this.MoveNext();
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
private void SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
}
|
|
|
|
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
|
|
this.SetStateMachine(stateMachine);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
IAsyncEnumerator<string> IAsyncEnumerable<string>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
_003CStreamOllamaAsync_003Ed__37 _003CStreamOllamaAsync_003Ed__;
|
|
if (_003C_003E1__state == -2 && _003C_003El__initialThreadId == Environment.CurrentManagedThreadId)
|
|
{
|
|
_003C_003E1__state = -3;
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
_003C_003Ew__disposeMode = false;
|
|
_003CStreamOllamaAsync_003Ed__ = this;
|
|
}
|
|
else
|
|
{
|
|
_003CStreamOllamaAsync_003Ed__ = new _003CStreamOllamaAsync_003Ed__37(-3)
|
|
{
|
|
_003C_003E4__this = _003C_003E4__this
|
|
};
|
|
}
|
|
_003CStreamOllamaAsync_003Ed__.messages = _003C_003E3__messages;
|
|
if (_003C_003E3__ct.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamOllamaAsync_003Ed__.ct = cancellationToken;
|
|
}
|
|
else if (cancellationToken.Equals(_003C_003E3__ct) || cancellationToken.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamOllamaAsync_003Ed__.ct = _003C_003E3__ct;
|
|
}
|
|
else
|
|
{
|
|
_003C_003Ex__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(_003C_003E3__ct, cancellationToken);
|
|
_003CStreamOllamaAsync_003Ed__.ct = _003C_003Ex__combinedTokens.Token;
|
|
}
|
|
return _003CStreamOllamaAsync_003Ed__;
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask<bool> IAsyncEnumerator<string>.MoveNextAsync()
|
|
{
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask<bool>);
|
|
}
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamOllamaAsync_003Ed__37 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
short version = _003C_003Ev__promiseOfValueOrEnd.Version;
|
|
if (_003C_003Ev__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded)
|
|
{
|
|
return new ValueTask<bool>(_003C_003Ev__promiseOfValueOrEnd.GetResult(version));
|
|
}
|
|
return new ValueTask<bool>(this, version);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
bool IValueTaskSource<bool>.GetResult(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource<bool>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.GetResult(short token)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask IAsyncDisposable.DisposeAsync()
|
|
{
|
|
if (_003C_003E1__state >= -1)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask);
|
|
}
|
|
_003C_003Ew__disposeMode = true;
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamOllamaAsync_003Ed__37 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
return new ValueTask(this, _003C_003Ev__promiseOfValueOrEnd.Version);
|
|
}
|
|
}
|
|
|
|
[CompilerGenerated]
|
|
private sealed class _003CStreamOpenAiCompatibleAsync_003Ed__40 : IAsyncEnumerable<string>, IAsyncEnumerator<string>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine
|
|
{
|
|
public int _003C_003E1__state;
|
|
|
|
public AsyncIteratorMethodBuilder _003C_003Et__builder;
|
|
|
|
public ManualResetValueTaskSourceCore<bool> _003C_003Ev__promiseOfValueOrEnd;
|
|
|
|
private string _003C_003E2__current;
|
|
|
|
private bool _003C_003Ew__disposeMode;
|
|
|
|
private CancellationTokenSource _003C_003Ex__combinedTokens;
|
|
|
|
private int _003C_003El__initialThreadId;
|
|
|
|
private List<ChatMessage> messages;
|
|
|
|
public List<ChatMessage> _003C_003E3__messages;
|
|
|
|
private CancellationToken ct;
|
|
|
|
public CancellationToken _003C_003E3__ct;
|
|
|
|
public LlmService _003C_003E4__this;
|
|
|
|
private LlmSettings _003Cllm_003E5__1;
|
|
|
|
private string _003Cendpoint_003E5__2;
|
|
|
|
private string _003Cep_003E5__3;
|
|
|
|
private object _003Cbody_003E5__4;
|
|
|
|
private string _003Curl_003E5__5;
|
|
|
|
private HttpRequestMessage _003Creq_003E5__6;
|
|
|
|
private HttpResponseMessage _003Cresp_003E5__7;
|
|
|
|
private Stream _003Cstream_003E5__8;
|
|
|
|
private StreamReader _003Creader_003E5__9;
|
|
|
|
private HttpResponseMessage _003C_003Es__10;
|
|
|
|
private Stream _003C_003Es__11;
|
|
|
|
private string _003Cline_003E5__12;
|
|
|
|
private string _003Cdata_003E5__13;
|
|
|
|
private string _003Ctext_003E5__14;
|
|
|
|
private string _003C_003Es__15;
|
|
|
|
private JsonDocument _003Cdoc_003E5__16;
|
|
|
|
private JsonElement _003Cchoices_003E5__17;
|
|
|
|
private JsonElement _003Cdelta_003E5__18;
|
|
|
|
private JsonElement _003Cc_003E5__19;
|
|
|
|
private JsonException _003Cex_003E5__20;
|
|
|
|
private TaskAwaiter _003C_003Eu__1;
|
|
|
|
private TaskAwaiter<HttpResponseMessage> _003C_003Eu__2;
|
|
|
|
private TaskAwaiter<Stream> _003C_003Eu__3;
|
|
|
|
private TaskAwaiter<string?> _003C_003Eu__4;
|
|
|
|
string IAsyncEnumerator<string>.Current
|
|
{
|
|
[DebuggerHidden]
|
|
get
|
|
{
|
|
return _003C_003E2__current;
|
|
}
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
public _003CStreamOpenAiCompatibleAsync_003Ed__40(int _003C_003E1__state)
|
|
{
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
this._003C_003E1__state = _003C_003E1__state;
|
|
_003C_003El__initialThreadId = Environment.CurrentManagedThreadId;
|
|
}
|
|
|
|
private void MoveNext()
|
|
{
|
|
int num = _003C_003E1__state;
|
|
try
|
|
{
|
|
switch (num)
|
|
{
|
|
default:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
num = (_003C_003E1__state = -1);
|
|
_003Cllm_003E5__1 = _003C_003E4__this._settings.Settings.Llm;
|
|
_003Cendpoint_003E5__2 = _003C_003E4__this.ResolveServerInfo().Endpoint;
|
|
_003Cep_003E5__3 = (string.IsNullOrEmpty(_003Cendpoint_003E5__2) ? _003Cllm_003E5__1.Endpoint : _003Cendpoint_003E5__2);
|
|
_003Cbody_003E5__4 = _003C_003E4__this.BuildOpenAiBody(messages, stream: true);
|
|
_003Curl_003E5__5 = _003Cep_003E5__3.TrimEnd('/') + "/v1/chat/completions";
|
|
_003Creq_003E5__6 = new HttpRequestMessage(HttpMethod.Post, _003Curl_003E5__5)
|
|
{
|
|
Content = JsonContent(_003Cbody_003E5__4)
|
|
};
|
|
break;
|
|
}
|
|
goto end_IL_0007;
|
|
case -4:
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter awaiter2;
|
|
TaskAwaiter<HttpResponseMessage> awaiter;
|
|
switch (num)
|
|
{
|
|
default:
|
|
_003C_003E2__current = null;
|
|
awaiter2 = _003C_003E4__this.ApplyAuthHeaderAsync(_003Creq_003E5__6, ct).GetAwaiter();
|
|
if (!awaiter2.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 0);
|
|
_003C_003Eu__1 = awaiter2;
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__40 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_01ae;
|
|
case 0:
|
|
awaiter2 = _003C_003Eu__1;
|
|
_003C_003Eu__1 = default(TaskAwaiter);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_01ae;
|
|
case 1:
|
|
awaiter = _003C_003Eu__2;
|
|
_003C_003Eu__2 = default(TaskAwaiter<HttpResponseMessage>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_0229;
|
|
case -4:
|
|
case 2:
|
|
case 3:
|
|
break;
|
|
IL_0229:
|
|
_003C_003Es__10 = awaiter.GetResult();
|
|
_003Cresp_003E5__7 = _003C_003Es__10;
|
|
_003C_003Es__10 = null;
|
|
break;
|
|
IL_01ae:
|
|
awaiter2.GetResult();
|
|
_003C_003E2__current = null;
|
|
awaiter = _003C_003E4__this.SendWithErrorClassificationAsync(_003Creq_003E5__6, ct).GetAwaiter();
|
|
if (!awaiter.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 1);
|
|
_003C_003Eu__2 = awaiter;
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__40 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_0229;
|
|
}
|
|
try
|
|
{
|
|
if (num != -4)
|
|
{
|
|
TaskAwaiter<Stream> awaiter3;
|
|
if (num != 2)
|
|
{
|
|
if (num == 3)
|
|
{
|
|
goto IL_02f7;
|
|
}
|
|
_003C_003E2__current = null;
|
|
awaiter3 = _003Cresp_003E5__7.Content.ReadAsStreamAsync(ct).GetAwaiter();
|
|
if (!awaiter3.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 2);
|
|
_003C_003Eu__3 = awaiter3;
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__40 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
awaiter3 = _003C_003Eu__3;
|
|
_003C_003Eu__3 = default(TaskAwaiter<Stream>);
|
|
num = (_003C_003E1__state = -1);
|
|
}
|
|
_003C_003Es__11 = awaiter3.GetResult();
|
|
_003Cstream_003E5__8 = _003C_003Es__11;
|
|
_003C_003Es__11 = null;
|
|
}
|
|
goto IL_02f7;
|
|
IL_0646:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_066b;
|
|
IL_02f7:
|
|
try
|
|
{
|
|
if (num != -4 && num != 3)
|
|
{
|
|
_003Creader_003E5__9 = new StreamReader(_003Cstream_003E5__8);
|
|
}
|
|
try
|
|
{
|
|
TaskAwaiter<string> awaiter4;
|
|
if (num != -4)
|
|
{
|
|
if (num != 3)
|
|
{
|
|
goto IL_05dc;
|
|
}
|
|
awaiter4 = _003C_003Eu__4;
|
|
_003C_003Eu__4 = default(TaskAwaiter<string>);
|
|
num = (_003C_003E1__state = -1);
|
|
goto IL_039f;
|
|
}
|
|
num = (_003C_003E1__state = -1);
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
goto IL_05c6;
|
|
}
|
|
goto end_IL_0318;
|
|
IL_0584:
|
|
if (string.IsNullOrEmpty(_003Ctext_003E5__14))
|
|
{
|
|
goto IL_05c6;
|
|
}
|
|
_003C_003E2__current = _003Ctext_003E5__14;
|
|
num = (_003C_003E1__state = -4);
|
|
goto IL_08a6;
|
|
IL_05c6:
|
|
_003Cline_003E5__12 = null;
|
|
_003Cdata_003E5__13 = null;
|
|
_003Ctext_003E5__14 = null;
|
|
goto IL_05dc;
|
|
IL_039f:
|
|
_003C_003Es__15 = awaiter4.GetResult();
|
|
_003Cline_003E5__12 = _003C_003Es__15;
|
|
_003C_003Es__15 = null;
|
|
if (_003Cline_003E5__12 != null)
|
|
{
|
|
if (string.IsNullOrEmpty(_003Cline_003E5__12) || !_003Cline_003E5__12.StartsWith("data: "))
|
|
{
|
|
goto IL_05dc;
|
|
}
|
|
string text = _003Cline_003E5__12;
|
|
int length = "data: ".Length;
|
|
_003Cdata_003E5__13 = text.Substring(length, text.Length - length);
|
|
if (!(_003Cdata_003E5__13 == "[DONE]"))
|
|
{
|
|
_003Ctext_003E5__14 = null;
|
|
try
|
|
{
|
|
_003Cdoc_003E5__16 = JsonDocument.Parse(_003Cdata_003E5__13);
|
|
try
|
|
{
|
|
_003C_003E4__this.TryParseOpenAiUsage(_003Cdoc_003E5__16.RootElement);
|
|
_003Cchoices_003E5__17 = _003Cdoc_003E5__16.RootElement.GetProperty("choices");
|
|
if (_003Cchoices_003E5__17.GetArrayLength() > 0)
|
|
{
|
|
_003Cdelta_003E5__18 = _003Cchoices_003E5__17[0].GetProperty("delta");
|
|
if (_003Cdelta_003E5__18.TryGetProperty("content", out _003Cc_003E5__19))
|
|
{
|
|
_003Ctext_003E5__14 = _003Cc_003E5__19.GetString();
|
|
}
|
|
_003Cdelta_003E5__18 = default(JsonElement);
|
|
_003Cc_003E5__19 = default(JsonElement);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cdoc_003E5__16 != null)
|
|
{
|
|
((IDisposable)_003Cdoc_003E5__16).Dispose();
|
|
}
|
|
}
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cdoc_003E5__16 = null;
|
|
_003Cchoices_003E5__17 = default(JsonElement);
|
|
goto IL_0584;
|
|
}
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
_003Cex_003E5__20 = ex;
|
|
LogService.Warn("vLLM 스트리밍 JSON 파싱 오류: " + _003Cex_003E5__20.Message);
|
|
goto IL_0584;
|
|
}
|
|
goto end_IL_0318;
|
|
}
|
|
}
|
|
goto end_IL_02f7;
|
|
IL_05dc:
|
|
if (!_003Creader_003E5__9.EndOfStream && !ct.IsCancellationRequested)
|
|
{
|
|
_003C_003E2__current = null;
|
|
awaiter4 = ReadLineWithTimeoutAsync(_003Creader_003E5__9, ct).GetAwaiter();
|
|
if (!awaiter4.IsCompleted)
|
|
{
|
|
num = (_003C_003E1__state = 3);
|
|
_003C_003Eu__4 = awaiter4;
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__40 stateMachine = this;
|
|
_003C_003Et__builder.AwaitUnsafeOnCompleted(ref awaiter4, ref stateMachine);
|
|
return;
|
|
}
|
|
goto IL_039f;
|
|
}
|
|
goto end_IL_02f7;
|
|
end_IL_0318:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Creader_003E5__9 != null)
|
|
{
|
|
((IDisposable)_003Creader_003E5__9).Dispose();
|
|
}
|
|
}
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_0646;
|
|
end_IL_02f7:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cstream_003E5__8 != null)
|
|
{
|
|
((IDisposable)_003Cstream_003E5__8).Dispose();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Cresp_003E5__7 != null)
|
|
{
|
|
((IDisposable)_003Cresp_003E5__7).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_0107;
|
|
IL_066b:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
}
|
|
goto IL_0690;
|
|
end_IL_0107:;
|
|
}
|
|
finally
|
|
{
|
|
if (num == -1 && _003Creq_003E5__6 != null)
|
|
{
|
|
((IDisposable)_003Creq_003E5__6).Dispose();
|
|
}
|
|
}
|
|
goto end_IL_0007;
|
|
IL_0690:
|
|
if (!_003C_003Ew__disposeMode)
|
|
{
|
|
_003Cllm_003E5__1 = null;
|
|
_003Cendpoint_003E5__2 = null;
|
|
_003Cep_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
}
|
|
end_IL_0007:;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_003C_003E1__state = -2;
|
|
_003Cllm_003E5__1 = null;
|
|
_003Cendpoint_003E5__2 = null;
|
|
_003Cep_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
_003C_003Es__10 = null;
|
|
_003C_003Es__11 = null;
|
|
_003Cline_003E5__12 = null;
|
|
_003Cdata_003E5__13 = null;
|
|
_003Ctext_003E5__14 = null;
|
|
_003C_003Es__15 = null;
|
|
_003Cdoc_003E5__16 = null;
|
|
_003Cchoices_003E5__17 = default(JsonElement);
|
|
_003Cdelta_003E5__18 = default(JsonElement);
|
|
_003Cc_003E5__19 = default(JsonElement);
|
|
_003Cex_003E5__20 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetException(exception);
|
|
return;
|
|
}
|
|
_003C_003E1__state = -2;
|
|
_003Cllm_003E5__1 = null;
|
|
_003Cendpoint_003E5__2 = null;
|
|
_003Cep_003E5__3 = null;
|
|
_003Cbody_003E5__4 = null;
|
|
_003Curl_003E5__5 = null;
|
|
_003Creq_003E5__6 = null;
|
|
_003Cresp_003E5__7 = null;
|
|
_003Cstream_003E5__8 = null;
|
|
_003Creader_003E5__9 = null;
|
|
_003C_003Es__10 = null;
|
|
_003C_003Es__11 = null;
|
|
_003Cline_003E5__12 = null;
|
|
_003Cdata_003E5__13 = null;
|
|
_003Ctext_003E5__14 = null;
|
|
_003C_003Es__15 = null;
|
|
_003Cdoc_003E5__16 = null;
|
|
_003Cchoices_003E5__17 = default(JsonElement);
|
|
_003Cdelta_003E5__18 = default(JsonElement);
|
|
_003Cc_003E5__19 = default(JsonElement);
|
|
_003Cex_003E5__20 = null;
|
|
if (_003C_003Ex__combinedTokens != null)
|
|
{
|
|
_003C_003Ex__combinedTokens.Dispose();
|
|
_003C_003Ex__combinedTokens = null;
|
|
}
|
|
_003C_003E2__current = null;
|
|
_003C_003Et__builder.Complete();
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: false);
|
|
return;
|
|
IL_08a6:
|
|
_003C_003Ev__promiseOfValueOrEnd.SetResult(result: true);
|
|
}
|
|
|
|
void IAsyncStateMachine.MoveNext()
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
|
|
this.MoveNext();
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
private void SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
}
|
|
|
|
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
|
|
{
|
|
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
|
|
this.SetStateMachine(stateMachine);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
IAsyncEnumerator<string> IAsyncEnumerable<string>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__40 _003CStreamOpenAiCompatibleAsync_003Ed__;
|
|
if (_003C_003E1__state == -2 && _003C_003El__initialThreadId == Environment.CurrentManagedThreadId)
|
|
{
|
|
_003C_003E1__state = -3;
|
|
_003C_003Et__builder = AsyncIteratorMethodBuilder.Create();
|
|
_003C_003Ew__disposeMode = false;
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__ = this;
|
|
}
|
|
else
|
|
{
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__ = new _003CStreamOpenAiCompatibleAsync_003Ed__40(-3)
|
|
{
|
|
_003C_003E4__this = _003C_003E4__this
|
|
};
|
|
}
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__.messages = _003C_003E3__messages;
|
|
if (_003C_003E3__ct.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__.ct = cancellationToken;
|
|
}
|
|
else if (cancellationToken.Equals(_003C_003E3__ct) || cancellationToken.Equals(default(CancellationToken)))
|
|
{
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__.ct = _003C_003E3__ct;
|
|
}
|
|
else
|
|
{
|
|
_003C_003Ex__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(_003C_003E3__ct, cancellationToken);
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__.ct = _003C_003Ex__combinedTokens.Token;
|
|
}
|
|
return _003CStreamOpenAiCompatibleAsync_003Ed__;
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask<bool> IAsyncEnumerator<string>.MoveNextAsync()
|
|
{
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask<bool>);
|
|
}
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__40 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
short version = _003C_003Ev__promiseOfValueOrEnd.Version;
|
|
if (_003C_003Ev__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded)
|
|
{
|
|
return new ValueTask<bool>(_003C_003Ev__promiseOfValueOrEnd.GetResult(version));
|
|
}
|
|
return new ValueTask<bool>(this, version);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
bool IValueTaskSource<bool>.GetResult(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource<bool>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.GetResult(short token)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.GetResult(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
|
|
{
|
|
return _003C_003Ev__promiseOfValueOrEnd.GetStatus(token);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
|
{
|
|
_003C_003Ev__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
|
|
}
|
|
|
|
[DebuggerHidden]
|
|
ValueTask IAsyncDisposable.DisposeAsync()
|
|
{
|
|
if (_003C_003E1__state >= -1)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
if (_003C_003E1__state == -2)
|
|
{
|
|
return default(ValueTask);
|
|
}
|
|
_003C_003Ew__disposeMode = true;
|
|
_003C_003Ev__promiseOfValueOrEnd.Reset();
|
|
_003CStreamOpenAiCompatibleAsync_003Ed__40 stateMachine = this;
|
|
_003C_003Et__builder.MoveNext(ref stateMachine);
|
|
return new ValueTask(this, _003C_003Ev__promiseOfValueOrEnd.Version);
|
|
}
|
|
}
|
|
|
|
private readonly HttpClient _http;
|
|
|
|
private readonly SettingsService _settings;
|
|
|
|
private string? _systemPrompt;
|
|
|
|
private const int MaxRetries = 2;
|
|
|
|
private static readonly TimeSpan ChunkTimeout = TimeSpan.FromSeconds(30.0);
|
|
|
|
private string? _serviceOverride;
|
|
|
|
private string? _modelOverride;
|
|
|
|
public TokenUsage? LastTokenUsage { get; private set; }
|
|
|
|
public string? SystemPrompt => _systemPrompt;
|
|
|
|
public string? LastFallbackInfo { get; private set; }
|
|
|
|
public void PushRouteOverride(string service, string model)
|
|
{
|
|
_serviceOverride = service;
|
|
_modelOverride = model;
|
|
}
|
|
|
|
public void ClearRouteOverride()
|
|
{
|
|
_serviceOverride = null;
|
|
_modelOverride = null;
|
|
}
|
|
|
|
public (string service, string model) GetCurrentModelInfo()
|
|
{
|
|
return (service: ResolveService(), model: ResolveModel());
|
|
}
|
|
|
|
private string ResolveService()
|
|
{
|
|
return _serviceOverride ?? _settings.Settings.Llm.Service;
|
|
}
|
|
|
|
private string ResolveModel()
|
|
{
|
|
if (_modelOverride != null)
|
|
{
|
|
return _modelOverride;
|
|
}
|
|
return ResolveModelName();
|
|
}
|
|
|
|
private string ResolveApiKeyForService(string service)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string text = service.ToLowerInvariant();
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string result = text switch
|
|
{
|
|
"gemini" => llm.GeminiApiKey,
|
|
"claude" => llm.ClaudeApiKey,
|
|
"vllm" => CryptoService.DecryptIfEnabled(llm.VllmApiKey, llm.EncryptionEnabled),
|
|
"ollama" => CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled),
|
|
_ => "",
|
|
};
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private string ResolveEndpointForService(string service)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string text = service.ToLowerInvariant();
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string result = ((text == "vllm") ? llm.VllmEndpoint : ((!(text == "ollama")) ? llm.Endpoint : llm.OllamaEndpoint));
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public LlmService(SettingsService settings)
|
|
{
|
|
_settings = settings;
|
|
_http = new HttpClient
|
|
{
|
|
Timeout = TimeSpan.FromMinutes(10.0)
|
|
};
|
|
LoadSystemPrompt();
|
|
}
|
|
|
|
private void LoadSystemPrompt()
|
|
{
|
|
string baseDirectory = AppContext.BaseDirectory;
|
|
string path = Path.Combine(baseDirectory, "system_prompt.txt");
|
|
if (File.Exists(path))
|
|
{
|
|
_systemPrompt = File.ReadAllText(path, Encoding.UTF8).Trim();
|
|
}
|
|
}
|
|
|
|
private string ResolveModelName()
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string service = llm.Service;
|
|
bool flag = ((service == "ollama" || service == "vllm") ? true : false);
|
|
if (flag && !string.IsNullOrEmpty(llm.Model))
|
|
{
|
|
return CryptoService.DecryptIfEnabled(llm.Model, llm.EncryptionEnabled);
|
|
}
|
|
return llm.Model;
|
|
}
|
|
|
|
private (string Endpoint, string ApiKey) ResolveServerInfo()
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string text = ResolveService();
|
|
string modelName = ResolveModel();
|
|
RegisteredModel registeredModel = FindRegisteredModel(llm, text, modelName);
|
|
if (registeredModel != null && !string.IsNullOrEmpty(registeredModel.Endpoint))
|
|
{
|
|
string item = ((!string.IsNullOrEmpty(registeredModel.ApiKey)) ? CryptoService.DecryptIfEnabled(registeredModel.ApiKey, llm.EncryptionEnabled) : GetDefaultApiKey(llm, text));
|
|
return (Endpoint: registeredModel.Endpoint, ApiKey: item);
|
|
}
|
|
string text2 = text.ToLowerInvariant();
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
(string, string) result = ((text2 == "vllm") ? (llm.VllmEndpoint, CryptoService.DecryptIfEnabled(llm.VllmApiKey, llm.EncryptionEnabled)) : ((!(text2 == "ollama")) ? ("", "") : (llm.OllamaEndpoint, CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled))));
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static RegisteredModel? FindRegisteredModel(LlmSettings llm, string service, string modelName)
|
|
{
|
|
return llm.RegisteredModels.FirstOrDefault((RegisteredModel m) => m.Service.Equals(service, StringComparison.OrdinalIgnoreCase) && (CryptoService.DecryptIfEnabled(m.EncryptedModelName, llm.EncryptionEnabled) == modelName || m.Alias == modelName));
|
|
}
|
|
|
|
internal async Task<string?> ResolveAuthTokenAsync(CancellationToken ct = default(CancellationToken))
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string activeService = ResolveService();
|
|
string modelName = ResolveModel();
|
|
RegisteredModel registered = FindRegisteredModel(llm, activeService, modelName);
|
|
if (registered != null && registered.AuthType.Equals("cp4d", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(registered.Cp4dUrl))
|
|
{
|
|
return await Cp4dTokenService.GetTokenAsync(password: CryptoService.DecryptIfEnabled(registered.Cp4dPassword, llm.EncryptionEnabled), cp4dUrl: registered.Cp4dUrl, username: registered.Cp4dUsername, ct: ct);
|
|
}
|
|
string apiKey = ResolveServerInfo().ApiKey;
|
|
return string.IsNullOrEmpty(apiKey) ? null : apiKey;
|
|
}
|
|
|
|
private async Task ApplyAuthHeaderAsync(HttpRequestMessage req, CancellationToken ct)
|
|
{
|
|
string token = await ResolveAuthTokenAsync(ct);
|
|
if (!string.IsNullOrEmpty(token))
|
|
{
|
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
}
|
|
}
|
|
|
|
private static string GetDefaultApiKey(LlmSettings llm, string? service = null)
|
|
{
|
|
string text = service ?? llm.Service;
|
|
string text2 = text.ToLowerInvariant();
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string result = ((text2 == "vllm") ? CryptoService.DecryptIfEnabled(llm.VllmApiKey, llm.EncryptionEnabled) : ((!(text2 == "ollama")) ? "" : CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled)));
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public async Task<string> SendAsync(List<ChatMessage> messages, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string activeService = ResolveService();
|
|
try
|
|
{
|
|
return await SendWithServiceAsync(activeService, messages, ct);
|
|
}
|
|
catch (Exception ex) when (llm.FallbackModels.Count > 0)
|
|
{
|
|
foreach (string fallback in llm.FallbackModels)
|
|
{
|
|
string[] parts = fallback.Split(':', 2);
|
|
string fbService = parts[0].Trim();
|
|
if (parts.Length > 1)
|
|
{
|
|
parts[1].Trim();
|
|
}
|
|
try
|
|
{
|
|
LogService.Warn($"모델 폴백: {activeService} → {fbService} ({ex.Message})");
|
|
LastFallbackInfo = activeService + " → " + fbService;
|
|
return await SendWithServiceAsync(fbService, messages, ct);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private Task<string> SendWithServiceAsync(string service, List<ChatMessage> messages, CancellationToken ct)
|
|
{
|
|
string text = service.ToLowerInvariant();
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
Task<string> result = text switch
|
|
{
|
|
"gemini" => SendGeminiAsync(messages, ct),
|
|
"claude" => SendClaudeAsync(messages, ct),
|
|
"vllm" => SendOpenAiCompatibleAsync(messages, ct),
|
|
_ => SendOllamaAsync(messages, ct),
|
|
};
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
return result;
|
|
}
|
|
|
|
[AsyncIteratorStateMachine(typeof(_003CStreamAsync_003Ed__34))]
|
|
public IAsyncEnumerable<string> StreamAsync(List<ChatMessage> messages, [EnumeratorCancellation] CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return new _003CStreamAsync_003Ed__34(-2)
|
|
{
|
|
_003C_003E4__this = this,
|
|
_003C_003E3__messages = messages,
|
|
_003C_003E3__ct = ct
|
|
};
|
|
}
|
|
|
|
public async Task<(bool ok, string message)> TestConnectionAsync()
|
|
{
|
|
try
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
switch (llm.Service.ToLowerInvariant())
|
|
{
|
|
case "ollama":
|
|
{
|
|
HttpResponseMessage resp = await _http.GetAsync(llm.Endpoint.TrimEnd('/') + "/api/tags");
|
|
return resp.IsSuccessStatusCode ? (ok: true, message: "Ollama 연결 성공") : (ok: false, message: ClassifyHttpError(resp));
|
|
}
|
|
case "vllm":
|
|
{
|
|
HttpResponseMessage vResp = await _http.GetAsync(llm.Endpoint.TrimEnd('/') + "/v1/models");
|
|
return vResp.IsSuccessStatusCode ? (ok: true, message: "vLLM 연결 성공") : (ok: false, message: ClassifyHttpError(vResp));
|
|
}
|
|
case "gemini":
|
|
{
|
|
string gKey = llm.ApiKey;
|
|
if (string.IsNullOrEmpty(gKey))
|
|
{
|
|
return (ok: false, message: "API 키가 설정되지 않았습니다");
|
|
}
|
|
HttpResponseMessage gResp = await _http.GetAsync("https://generativelanguage.googleapis.com/v1beta/models?key=" + gKey);
|
|
return gResp.IsSuccessStatusCode ? (ok: true, message: "Gemini API 연결 성공") : (ok: false, message: ClassifyHttpError(gResp));
|
|
}
|
|
case "claude":
|
|
{
|
|
string cKey = llm.ApiKey;
|
|
if (string.IsNullOrEmpty(cKey))
|
|
{
|
|
return (ok: false, message: "API 키가 설정되지 않았습니다");
|
|
}
|
|
using HttpRequestMessage cReq = new HttpRequestMessage(HttpMethod.Get, "https://api.anthropic.com/v1/models");
|
|
cReq.Headers.Add("x-api-key", cKey);
|
|
cReq.Headers.Add("anthropic-version", "2023-06-01");
|
|
HttpResponseMessage cResp = await _http.SendAsync(cReq);
|
|
return cResp.IsSuccessStatusCode ? (ok: true, message: "Claude API 연결 성공") : (ok: false, message: ClassifyHttpError(cResp));
|
|
}
|
|
default:
|
|
return (ok: false, message: "알 수 없는 서비스");
|
|
}
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
return (ok: false, message: "연결 시간 초과 — 서버가 응답하지 않습니다");
|
|
}
|
|
catch (HttpRequestException ex2)
|
|
{
|
|
HttpRequestException ex3 = ex2;
|
|
return (ok: false, message: "연결 실패 — " + ex3.Message);
|
|
}
|
|
catch (Exception ex4)
|
|
{
|
|
Exception ex5 = ex4;
|
|
return (ok: false, message: ex5.Message);
|
|
}
|
|
}
|
|
|
|
private async Task<string> SendOllamaAsync(List<ChatMessage> messages, CancellationToken ct)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string endpoint = ResolveServerInfo().Endpoint;
|
|
string ep = (string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint);
|
|
return SafeParseJson(await PostJsonWithRetryAsync(body: BuildOllamaBody(messages, stream: false), url: ep.TrimEnd('/') + "/api/chat", ct: ct), delegate(JsonElement root)
|
|
{
|
|
TryParseOllamaUsage(root);
|
|
return root.GetProperty("message").GetProperty("content").GetString() ?? "";
|
|
}, "Ollama 응답");
|
|
}
|
|
|
|
[AsyncIteratorStateMachine(typeof(_003CStreamOllamaAsync_003Ed__37))]
|
|
private IAsyncEnumerable<string> StreamOllamaAsync(List<ChatMessage> messages, [EnumeratorCancellation] CancellationToken ct)
|
|
{
|
|
return new _003CStreamOllamaAsync_003Ed__37(-2)
|
|
{
|
|
_003C_003E4__this = this,
|
|
_003C_003E3__messages = messages,
|
|
_003C_003E3__ct = ct
|
|
};
|
|
}
|
|
|
|
private object BuildOllamaBody(List<ChatMessage> messages, bool stream)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
List<object> messages2 = BuildMessageList(messages);
|
|
return new
|
|
{
|
|
model = ResolveModelName(),
|
|
messages = messages2,
|
|
stream = stream,
|
|
options = new
|
|
{
|
|
temperature = llm.Temperature
|
|
}
|
|
};
|
|
}
|
|
|
|
private async Task<string> SendOpenAiCompatibleAsync(List<ChatMessage> messages, CancellationToken ct)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string endpoint = ResolveServerInfo().Endpoint;
|
|
string ep = (string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint);
|
|
object body = BuildOpenAiBody(messages, stream: false);
|
|
string url = ep.TrimEnd('/') + "/v1/chat/completions";
|
|
string json = JsonSerializer.Serialize(body);
|
|
using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url)
|
|
{
|
|
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
};
|
|
await ApplyAuthHeaderAsync(req, ct);
|
|
using HttpResponseMessage resp = await SendWithErrorClassificationAsync(req, ct);
|
|
return SafeParseJson(await resp.Content.ReadAsStringAsync(ct), delegate(JsonElement root)
|
|
{
|
|
TryParseOpenAiUsage(root);
|
|
JsonElement property = root.GetProperty("choices");
|
|
return (property.GetArrayLength() == 0) ? "(빈 응답)" : (property[0].GetProperty("message").GetProperty("content").GetString() ?? "");
|
|
}, "vLLM 응답");
|
|
}
|
|
|
|
[AsyncIteratorStateMachine(typeof(_003CStreamOpenAiCompatibleAsync_003Ed__40))]
|
|
private IAsyncEnumerable<string> StreamOpenAiCompatibleAsync(List<ChatMessage> messages, [EnumeratorCancellation] CancellationToken ct)
|
|
{
|
|
return new _003CStreamOpenAiCompatibleAsync_003Ed__40(-2)
|
|
{
|
|
_003C_003E4__this = this,
|
|
_003C_003E3__messages = messages,
|
|
_003C_003E3__ct = ct
|
|
};
|
|
}
|
|
|
|
private object BuildOpenAiBody(List<ChatMessage> messages, bool stream)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
List<object> messages2 = BuildMessageList(messages, openAiVision: true);
|
|
return new
|
|
{
|
|
model = ResolveModelName(),
|
|
messages = messages2,
|
|
stream = stream,
|
|
temperature = llm.Temperature,
|
|
max_tokens = llm.MaxContextTokens
|
|
};
|
|
}
|
|
|
|
private async Task<string> SendGeminiAsync(List<ChatMessage> messages, CancellationToken ct)
|
|
{
|
|
_ = _settings.Settings.Llm;
|
|
string apiKey = ResolveApiKeyForService("gemini");
|
|
if (string.IsNullOrEmpty(apiKey))
|
|
{
|
|
throw new InvalidOperationException("Gemini API 키가 설정되지 않았습니다. 설정 > AX Agent에서 API 키를 입력하세요.");
|
|
}
|
|
string model = ResolveModel();
|
|
object body = BuildGeminiBody(messages);
|
|
string url = "https://generativelanguage.googleapis.com/v1beta/models/" + model + ":generateContent?key=" + apiKey;
|
|
return SafeParseJson(await PostJsonWithRetryAsync(url, body, ct), delegate(JsonElement root)
|
|
{
|
|
TryParseGeminiUsage(root);
|
|
JsonElement property = root.GetProperty("candidates");
|
|
if (property.GetArrayLength() == 0)
|
|
{
|
|
return "(빈 응답)";
|
|
}
|
|
JsonElement property2 = property[0].GetProperty("content").GetProperty("parts");
|
|
return (property2.GetArrayLength() == 0) ? "(빈 응답)" : (property2[0].GetProperty("text").GetString() ?? "");
|
|
}, "Gemini 응답");
|
|
}
|
|
|
|
[AsyncIteratorStateMachine(typeof(_003CStreamGeminiAsync_003Ed__43))]
|
|
private IAsyncEnumerable<string> StreamGeminiAsync(List<ChatMessage> messages, [EnumeratorCancellation] CancellationToken ct)
|
|
{
|
|
return new _003CStreamGeminiAsync_003Ed__43(-2)
|
|
{
|
|
_003C_003E4__this = this,
|
|
_003C_003E3__messages = messages,
|
|
_003C_003E3__ct = ct
|
|
};
|
|
}
|
|
|
|
private object BuildGeminiBody(List<ChatMessage> messages)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
List<object> list = new List<object>();
|
|
object obj = null;
|
|
if (!string.IsNullOrEmpty(_systemPrompt))
|
|
{
|
|
obj = new
|
|
{
|
|
parts = new[]
|
|
{
|
|
new
|
|
{
|
|
text = _systemPrompt
|
|
}
|
|
}
|
|
};
|
|
}
|
|
foreach (ChatMessage message in messages)
|
|
{
|
|
if (message.Role == "system")
|
|
{
|
|
continue;
|
|
}
|
|
List<object> list2 = new List<object>
|
|
{
|
|
new
|
|
{
|
|
text = message.Content
|
|
}
|
|
};
|
|
List<ImageAttachment>? images = message.Images;
|
|
if (images != null && images.Count > 0)
|
|
{
|
|
foreach (ImageAttachment image in message.Images)
|
|
{
|
|
list2.Add(new
|
|
{
|
|
inlineData = new
|
|
{
|
|
mimeType = image.MimeType,
|
|
data = image.Base64
|
|
}
|
|
});
|
|
}
|
|
}
|
|
list.Add(new
|
|
{
|
|
role = ((message.Role == "assistant") ? "model" : "user"),
|
|
parts = list2
|
|
});
|
|
}
|
|
if (obj != null)
|
|
{
|
|
return new
|
|
{
|
|
systemInstruction = obj,
|
|
contents = list,
|
|
generationConfig = new
|
|
{
|
|
temperature = llm.Temperature,
|
|
maxOutputTokens = llm.MaxContextTokens
|
|
}
|
|
};
|
|
}
|
|
return new
|
|
{
|
|
contents = list,
|
|
generationConfig = new
|
|
{
|
|
temperature = llm.Temperature,
|
|
maxOutputTokens = llm.MaxContextTokens
|
|
}
|
|
};
|
|
}
|
|
|
|
private async Task<string> SendClaudeAsync(List<ChatMessage> messages, CancellationToken ct)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string apiKey = llm.ApiKey;
|
|
if (string.IsNullOrEmpty(apiKey))
|
|
{
|
|
throw new InvalidOperationException("Claude API 키가 설정되지 않았습니다. 설정 > AX Agent에서 API 키를 입력하세요.");
|
|
}
|
|
object body = BuildClaudeBody(messages, stream: false);
|
|
string json = JsonSerializer.Serialize(body);
|
|
using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages");
|
|
req.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
req.Headers.Add("x-api-key", apiKey);
|
|
req.Headers.Add("anthropic-version", "2023-06-01");
|
|
using HttpResponseMessage resp = await _http.SendAsync(req, ct);
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
throw new HttpRequestException(ClassifyHttpError(resp, await resp.Content.ReadAsStringAsync(ct)));
|
|
}
|
|
return SafeParseJson(await resp.Content.ReadAsStringAsync(ct), delegate(JsonElement root)
|
|
{
|
|
TryParseClaudeUsage(root);
|
|
JsonElement property = root.GetProperty("content");
|
|
return (property.GetArrayLength() == 0) ? "(빈 응답)" : (property[0].GetProperty("text").GetString() ?? "");
|
|
}, "Claude 응답");
|
|
}
|
|
|
|
[AsyncIteratorStateMachine(typeof(_003CStreamClaudeAsync_003Ed__46))]
|
|
private IAsyncEnumerable<string> StreamClaudeAsync(List<ChatMessage> messages, [EnumeratorCancellation] CancellationToken ct)
|
|
{
|
|
return new _003CStreamClaudeAsync_003Ed__46(-2)
|
|
{
|
|
_003C_003E4__this = this,
|
|
_003C_003E3__messages = messages,
|
|
_003C_003E3__ct = ct
|
|
};
|
|
}
|
|
|
|
private object BuildClaudeBody(List<ChatMessage> messages, bool stream)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
List<object> list = new List<object>();
|
|
foreach (ChatMessage message in messages)
|
|
{
|
|
if (message.Role == "system")
|
|
{
|
|
continue;
|
|
}
|
|
List<ImageAttachment>? images = message.Images;
|
|
if (images != null && images.Count > 0)
|
|
{
|
|
List<object> list2 = new List<object>();
|
|
foreach (ImageAttachment image in message.Images)
|
|
{
|
|
list2.Add(new
|
|
{
|
|
type = "image",
|
|
source = new
|
|
{
|
|
type = "base64",
|
|
media_type = image.MimeType,
|
|
data = image.Base64
|
|
}
|
|
});
|
|
}
|
|
list2.Add(new
|
|
{
|
|
type = "text",
|
|
text = message.Content
|
|
});
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = list2
|
|
});
|
|
}
|
|
else
|
|
{
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = message.Content
|
|
});
|
|
}
|
|
}
|
|
string model = ResolveModel();
|
|
if (!string.IsNullOrEmpty(_systemPrompt))
|
|
{
|
|
return new
|
|
{
|
|
model = model,
|
|
max_tokens = llm.MaxContextTokens,
|
|
temperature = llm.Temperature,
|
|
system = _systemPrompt,
|
|
messages = list,
|
|
stream = stream
|
|
};
|
|
}
|
|
return new
|
|
{
|
|
model = model,
|
|
max_tokens = llm.MaxContextTokens,
|
|
temperature = llm.Temperature,
|
|
messages = list,
|
|
stream = stream
|
|
};
|
|
}
|
|
|
|
private List<object> BuildMessageList(List<ChatMessage> messages, bool openAiVision = false)
|
|
{
|
|
List<object> list = new List<object>();
|
|
if (!string.IsNullOrEmpty(_systemPrompt))
|
|
{
|
|
list.Add(new
|
|
{
|
|
role = "system",
|
|
content = _systemPrompt
|
|
});
|
|
}
|
|
foreach (ChatMessage message in messages)
|
|
{
|
|
if (message.Role == "system")
|
|
{
|
|
continue;
|
|
}
|
|
List<ImageAttachment>? images = message.Images;
|
|
if (images != null && images.Count > 0)
|
|
{
|
|
if (openAiVision)
|
|
{
|
|
List<object> list2 = new List<object>();
|
|
list2.Add(new
|
|
{
|
|
type = "text",
|
|
text = message.Content
|
|
});
|
|
foreach (ImageAttachment image in message.Images)
|
|
{
|
|
list2.Add(new
|
|
{
|
|
type = "image_url",
|
|
image_url = new
|
|
{
|
|
url = "data:" + image.MimeType + ";base64," + image.Base64
|
|
}
|
|
});
|
|
}
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = list2
|
|
});
|
|
}
|
|
else
|
|
{
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = message.Content,
|
|
images = message.Images.Select((ImageAttachment i) => i.Base64).ToArray()
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = message.Content
|
|
});
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private async Task<string> PostJsonWithRetryAsync(string url, object body, CancellationToken ct)
|
|
{
|
|
string json = JsonSerializer.Serialize(body);
|
|
Exception lastEx = null;
|
|
for (int attempt = 0; attempt <= 2; attempt++)
|
|
{
|
|
try
|
|
{
|
|
using StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
using HttpResponseMessage resp = await _http.PostAsync(url, content, ct);
|
|
if (resp.IsSuccessStatusCode)
|
|
{
|
|
return await resp.Content.ReadAsStringAsync(ct);
|
|
}
|
|
if (resp.StatusCode != HttpStatusCode.TooManyRequests || attempt >= 2)
|
|
{
|
|
throw new HttpRequestException(ClassifyHttpError(resp, await resp.Content.ReadAsStringAsync(ct)));
|
|
}
|
|
await Task.Delay(1000 * (attempt + 1), ct);
|
|
}
|
|
catch (HttpRequestException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (TaskCanceledException) when (!ct.IsCancellationRequested && attempt < 2)
|
|
{
|
|
lastEx = new TimeoutException("요청 시간 초과");
|
|
await Task.Delay(1000 * (attempt + 1), ct);
|
|
}
|
|
}
|
|
throw lastEx ?? new HttpRequestException("요청 실패");
|
|
}
|
|
|
|
private async Task<HttpResponseMessage> SendWithErrorClassificationAsync(HttpRequestMessage req, CancellationToken ct)
|
|
{
|
|
HttpResponseMessage resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
string errorMsg = ClassifyHttpError(resp, await resp.Content.ReadAsStringAsync(ct));
|
|
resp.Dispose();
|
|
throw new HttpRequestException(errorMsg);
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
private static async Task<string?> ReadLineWithTimeoutAsync(StreamReader reader, CancellationToken ct)
|
|
{
|
|
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
cts.CancelAfter(ChunkTimeout);
|
|
try
|
|
{
|
|
return await reader.ReadLineAsync(cts.Token);
|
|
}
|
|
catch (OperationCanceledException) when (!ct.IsCancellationRequested)
|
|
{
|
|
LogService.Warn("스트리밍 청크 타임아웃 (30초 무응답)");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static string SafeParseJson(string json, Func<JsonElement, string> extractor, string context)
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(json);
|
|
if (jsonDocument.RootElement.TryGetProperty("error", out var value))
|
|
{
|
|
JsonElement value2;
|
|
string text = (value.TryGetProperty("message", out value2) ? value2.GetString() : value.ToString());
|
|
throw new HttpRequestException("[" + context + "] API 에러: " + text);
|
|
}
|
|
return extractor(jsonDocument.RootElement);
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
string value3 = ((json.Length > 200) ? (json.Substring(0, 200) + "…") : json);
|
|
throw new InvalidOperationException($"[{context}] 응답 형식 오류 — 예상하지 못한 JSON 형식입니다.\n파싱 오류: {ex.Message}\n응답 미리보기: {value3}");
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
string text2 = ((json.Length > 200) ? (json.Substring(0, 200) + "…") : json);
|
|
throw new InvalidOperationException("[" + context + "] 응답에 필요한 필드가 없습니다.\n응답 미리보기: " + text2);
|
|
}
|
|
}
|
|
|
|
private static string ClassifyHttpError(HttpResponseMessage resp, string? body = null)
|
|
{
|
|
int statusCode = (int)resp.StatusCode;
|
|
string text = "";
|
|
if (!string.IsNullOrEmpty(body))
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(body);
|
|
if (jsonDocument.RootElement.TryGetProperty("error", out var value))
|
|
{
|
|
if (value.ValueKind == JsonValueKind.Object && value.TryGetProperty("message", out var value2))
|
|
{
|
|
text = value2.GetString() ?? "";
|
|
}
|
|
else if (value.ValueKind == JsonValueKind.String)
|
|
{
|
|
text = value.GetString() ?? "";
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text2;
|
|
switch (statusCode)
|
|
{
|
|
case 400:
|
|
text2 = "잘못된 요청 — 모델 이름이나 요청 형식을 확인하세요";
|
|
break;
|
|
case 401:
|
|
text2 = "인증 실패 — API 키가 유효하지 않습니다";
|
|
break;
|
|
case 403:
|
|
text2 = "접근 거부 — API 키 권한을 확인하세요";
|
|
break;
|
|
case 404:
|
|
text2 = "모델을 찾을 수 없습니다 — 모델 이름을 확인하세요";
|
|
break;
|
|
case 429:
|
|
text2 = "요청 한도 초과 — 잠시 후 다시 시도하세요";
|
|
break;
|
|
case 500:
|
|
text2 = "서버 내부 오류 — LLM 서버 상태를 확인하세요";
|
|
break;
|
|
case 502:
|
|
case 503:
|
|
text2 = "서버 일시 장애 — 잠시 후 다시 시도하세요";
|
|
break;
|
|
default:
|
|
text2 = $"HTTP {statusCode} 오류";
|
|
break;
|
|
}
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text3 = text2;
|
|
return string.IsNullOrEmpty(text) ? text3 : (text3 + "\n상세: " + text);
|
|
}
|
|
|
|
private static StringContent JsonContent(object body)
|
|
{
|
|
string content = JsonSerializer.Serialize(body);
|
|
return new StringContent(content, Encoding.UTF8, "application/json");
|
|
}
|
|
|
|
private void TryParseOllamaUsage(JsonElement root)
|
|
{
|
|
try
|
|
{
|
|
JsonElement value;
|
|
int num = (root.TryGetProperty("prompt_eval_count", out value) ? value.GetInt32() : 0);
|
|
JsonElement value2;
|
|
int num2 = (root.TryGetProperty("eval_count", out value2) ? value2.GetInt32() : 0);
|
|
if (num > 0 || num2 > 0)
|
|
{
|
|
LastTokenUsage = new TokenUsage(num, num2);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private void TryParseOpenAiUsage(JsonElement root)
|
|
{
|
|
try
|
|
{
|
|
if (root.TryGetProperty("usage", out var value))
|
|
{
|
|
JsonElement value2;
|
|
int num = (value.TryGetProperty("prompt_tokens", out value2) ? value2.GetInt32() : 0);
|
|
JsonElement value3;
|
|
int num2 = (value.TryGetProperty("completion_tokens", out value3) ? value3.GetInt32() : 0);
|
|
if (num > 0 || num2 > 0)
|
|
{
|
|
LastTokenUsage = new TokenUsage(num, num2);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private void TryParseGeminiUsage(JsonElement root)
|
|
{
|
|
try
|
|
{
|
|
if (root.TryGetProperty("usageMetadata", out var value))
|
|
{
|
|
JsonElement value2;
|
|
int num = (value.TryGetProperty("promptTokenCount", out value2) ? value2.GetInt32() : 0);
|
|
JsonElement value3;
|
|
int num2 = (value.TryGetProperty("candidatesTokenCount", out value3) ? value3.GetInt32() : 0);
|
|
if (num > 0 || num2 > 0)
|
|
{
|
|
LastTokenUsage = new TokenUsage(num, num2);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private void TryParseClaudeUsage(JsonElement root)
|
|
{
|
|
try
|
|
{
|
|
if (root.TryGetProperty("usage", out var value))
|
|
{
|
|
TryParseClaudeUsageFromElement(value);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private void TryParseClaudeUsageFromElement(JsonElement usage)
|
|
{
|
|
try
|
|
{
|
|
JsonElement value;
|
|
int num = (usage.TryGetProperty("input_tokens", out value) ? value.GetInt32() : 0);
|
|
JsonElement value2;
|
|
int num2 = (usage.TryGetProperty("output_tokens", out value2) ? value2.GetInt32() : 0);
|
|
if (num > 0 || num2 > 0)
|
|
{
|
|
LastTokenUsage = new TokenUsage(num, num2);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_http.Dispose();
|
|
}
|
|
|
|
public async Task<List<ContentBlock>> SendWithToolsAsync(List<ChatMessage> messages, IReadOnlyCollection<IAgentTool> tools, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
string activeService = ResolveService();
|
|
string text = activeService.ToLowerInvariant();
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
List<ContentBlock> result;
|
|
switch (text)
|
|
{
|
|
case "claude":
|
|
result = await SendClaudeWithToolsAsync(messages, tools, ct);
|
|
break;
|
|
case "gemini":
|
|
result = await SendGeminiWithToolsAsync(messages, tools, ct);
|
|
break;
|
|
case "ollama":
|
|
case "vllm":
|
|
result = await SendOpenAiWithToolsAsync(messages, tools, ct);
|
|
break;
|
|
default:
|
|
throw new NotSupportedException("서비스 '" + activeService + "'는 아직 Function Calling을 지원하지 않습니다.");
|
|
}
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static ChatMessage CreateToolResultMessage(string toolId, string toolName, string result)
|
|
{
|
|
string content = JsonSerializer.Serialize(new
|
|
{
|
|
type = "tool_result",
|
|
tool_use_id = toolId,
|
|
tool_name = toolName,
|
|
content = result
|
|
});
|
|
return new ChatMessage
|
|
{
|
|
Role = "user",
|
|
Content = content,
|
|
Timestamp = DateTime.Now
|
|
};
|
|
}
|
|
|
|
private async Task<List<ContentBlock>> SendClaudeWithToolsAsync(List<ChatMessage> messages, IReadOnlyCollection<IAgentTool> tools, CancellationToken ct)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
string apiKey = llm.ApiKey;
|
|
if (string.IsNullOrEmpty(apiKey))
|
|
{
|
|
throw new InvalidOperationException("Claude API 키가 설정되지 않았습니다.");
|
|
}
|
|
object body = BuildClaudeToolBody(messages, tools);
|
|
string json = JsonSerializer.Serialize(body);
|
|
using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages");
|
|
req.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
req.Headers.Add("x-api-key", apiKey);
|
|
req.Headers.Add("anthropic-version", "2023-06-01");
|
|
using HttpResponseMessage resp = await _http.SendAsync(req, ct);
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
throw new HttpRequestException(ClassifyHttpError(resp, await resp.Content.ReadAsStringAsync(ct)));
|
|
}
|
|
using JsonDocument doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync(ct));
|
|
JsonElement root = doc.RootElement;
|
|
if (root.TryGetProperty("usage", out var usage))
|
|
{
|
|
TryParseClaudeUsageFromElement(usage);
|
|
}
|
|
List<ContentBlock> blocks = new List<ContentBlock>();
|
|
if (root.TryGetProperty("content", out var content))
|
|
{
|
|
foreach (JsonElement block in content.EnumerateArray())
|
|
{
|
|
JsonElement tp;
|
|
string type = (block.TryGetProperty("type", out tp) ? tp.GetString() : "");
|
|
if (type == "text")
|
|
{
|
|
blocks.Add(new ContentBlock
|
|
{
|
|
Type = "text",
|
|
Text = (block.TryGetProperty("text", out var txt) ? (txt.GetString() ?? "") : "")
|
|
});
|
|
txt = default(JsonElement);
|
|
}
|
|
else if (type == "tool_use")
|
|
{
|
|
blocks.Add(new ContentBlock
|
|
{
|
|
Type = "tool_use",
|
|
ToolName = (block.TryGetProperty("name", out var nm) ? (nm.GetString() ?? "") : ""),
|
|
ToolId = (block.TryGetProperty("id", out var bid) ? (bid.GetString() ?? "") : ""),
|
|
ToolInput = (block.TryGetProperty("input", out var inp) ? new JsonElement?(inp.Clone()) : ((JsonElement?)null))
|
|
});
|
|
nm = default(JsonElement);
|
|
bid = default(JsonElement);
|
|
inp = default(JsonElement);
|
|
}
|
|
tp = default(JsonElement);
|
|
}
|
|
}
|
|
return blocks;
|
|
}
|
|
|
|
private object BuildClaudeToolBody(List<ChatMessage> messages, IReadOnlyCollection<IAgentTool> tools)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
List<object> list = new List<object>();
|
|
foreach (ChatMessage message in messages)
|
|
{
|
|
if (message.Role == "system")
|
|
{
|
|
continue;
|
|
}
|
|
if (message.Role == "user" && message.Content.StartsWith("{\"type\":\"tool_result\""))
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(message.Content);
|
|
JsonElement rootElement = jsonDocument.RootElement;
|
|
list.Add(new
|
|
{
|
|
role = "user",
|
|
content = new object[1]
|
|
{
|
|
new
|
|
{
|
|
type = "tool_result",
|
|
tool_use_id = (rootElement.TryGetProperty("tool_use_id", out var value) ? value.GetString() : ""),
|
|
content = (rootElement.TryGetProperty("content", out var value2) ? value2.GetString() : "")
|
|
}
|
|
}
|
|
});
|
|
}
|
|
catch
|
|
{
|
|
goto IL_0117;
|
|
}
|
|
continue;
|
|
}
|
|
goto IL_0117;
|
|
IL_0117:
|
|
if (message.Role == "assistant" && message.Content.StartsWith("{\"_tool_use_blocks\""))
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument2 = JsonDocument.Parse(message.Content);
|
|
if (!jsonDocument2.RootElement.TryGetProperty("_tool_use_blocks", out var value3))
|
|
{
|
|
throw new Exception();
|
|
}
|
|
List<object> list2 = new List<object>();
|
|
foreach (JsonElement item in value3.EnumerateArray())
|
|
{
|
|
JsonElement value4;
|
|
string text = (item.TryGetProperty("type", out value4) ? value4.GetString() : "");
|
|
if (text == "text")
|
|
{
|
|
list2.Add(new
|
|
{
|
|
type = "text",
|
|
text = (item.TryGetProperty("text", out var value5) ? (value5.GetString() ?? "") : "")
|
|
});
|
|
}
|
|
else if (text == "tool_use")
|
|
{
|
|
list2.Add(new
|
|
{
|
|
type = "tool_use",
|
|
id = (item.TryGetProperty("id", out var value6) ? (value6.GetString() ?? "") : ""),
|
|
name = (item.TryGetProperty("name", out var value7) ? (value7.GetString() ?? "") : ""),
|
|
input = (item.TryGetProperty("input", out var value8) ? ((object)value8.Clone()) : new { })
|
|
});
|
|
}
|
|
}
|
|
list.Add(new
|
|
{
|
|
role = "assistant",
|
|
content = list2
|
|
});
|
|
}
|
|
catch
|
|
{
|
|
goto IL_0301;
|
|
}
|
|
continue;
|
|
}
|
|
goto IL_0301;
|
|
IL_0301:
|
|
List<ImageAttachment>? images = message.Images;
|
|
if (images != null && images.Count > 0 && message.Role == "user")
|
|
{
|
|
List<object> list3 = new List<object>();
|
|
foreach (ImageAttachment image in message.Images)
|
|
{
|
|
list3.Add(new
|
|
{
|
|
type = "image",
|
|
source = new
|
|
{
|
|
type = "base64",
|
|
media_type = image.MimeType,
|
|
data = image.Base64
|
|
}
|
|
});
|
|
}
|
|
list3.Add(new
|
|
{
|
|
type = "text",
|
|
text = message.Content
|
|
});
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = list3
|
|
});
|
|
}
|
|
else
|
|
{
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = message.Content
|
|
});
|
|
}
|
|
}
|
|
var tools2 = tools.Select((IAgentTool t) => new
|
|
{
|
|
name = t.Name,
|
|
description = t.Description,
|
|
input_schema = new
|
|
{
|
|
type = "object",
|
|
properties = t.Parameters.Properties.ToDictionary<KeyValuePair<string, ToolProperty>, string, object>((KeyValuePair<string, ToolProperty> kv) => kv.Key, (KeyValuePair<string, ToolProperty> kv) => BuildPropertySchema(kv.Value, upperCaseType: false)),
|
|
required = t.Parameters.Required
|
|
}
|
|
}).ToArray();
|
|
string text2 = messages.FirstOrDefault((ChatMessage m) => m.Role == "system")?.Content ?? _systemPrompt;
|
|
string model = ResolveModel();
|
|
if (!string.IsNullOrEmpty(text2))
|
|
{
|
|
return new
|
|
{
|
|
model = model,
|
|
max_tokens = Math.Max(llm.MaxContextTokens, 4096),
|
|
temperature = llm.Temperature,
|
|
system = text2,
|
|
messages = list,
|
|
tools = tools2,
|
|
stream = false
|
|
};
|
|
}
|
|
return new
|
|
{
|
|
model = model,
|
|
max_tokens = Math.Max(llm.MaxContextTokens, 4096),
|
|
temperature = llm.Temperature,
|
|
messages = list,
|
|
tools = tools2,
|
|
stream = false
|
|
};
|
|
}
|
|
|
|
private async Task<List<ContentBlock>> SendGeminiWithToolsAsync(List<ChatMessage> messages, IReadOnlyCollection<IAgentTool> tools, CancellationToken ct)
|
|
{
|
|
_ = _settings.Settings.Llm;
|
|
string apiKey = ResolveApiKeyForService("gemini");
|
|
if (string.IsNullOrEmpty(apiKey))
|
|
{
|
|
throw new InvalidOperationException("Gemini API 키가 설정되지 않았습니다.");
|
|
}
|
|
string activeModel = ResolveModel();
|
|
object body = BuildGeminiToolBody(messages, tools);
|
|
string url = "https://generativelanguage.googleapis.com/v1beta/models/" + activeModel + ":generateContent?key=" + apiKey;
|
|
string json = JsonSerializer.Serialize(body);
|
|
using StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
using HttpResponseMessage resp = await _http.PostAsync(url, content, ct);
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
string errBody = await resp.Content.ReadAsStringAsync(ct);
|
|
throw new HttpRequestException($"Gemini API 오류 ({resp.StatusCode}): {errBody}");
|
|
}
|
|
using JsonDocument doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync(ct));
|
|
JsonElement root = doc.RootElement;
|
|
TryParseGeminiUsage(root);
|
|
List<ContentBlock> blocks = new List<ContentBlock>();
|
|
if (root.TryGetProperty("candidates", out var candidates) && candidates.GetArrayLength() > 0 && candidates[0].TryGetProperty("content", out var contentObj) && contentObj.TryGetProperty("parts", out var parts))
|
|
{
|
|
foreach (JsonElement part in parts.EnumerateArray())
|
|
{
|
|
if (part.TryGetProperty("text", out var text))
|
|
{
|
|
blocks.Add(new ContentBlock
|
|
{
|
|
Type = "text",
|
|
Text = (text.GetString() ?? "")
|
|
});
|
|
}
|
|
else
|
|
{
|
|
if (part.TryGetProperty("functionCall", out var fc))
|
|
{
|
|
blocks.Add(new ContentBlock
|
|
{
|
|
Type = "tool_use",
|
|
ToolName = (fc.TryGetProperty("name", out var fcName) ? (fcName.GetString() ?? "") : ""),
|
|
ToolId = Guid.NewGuid().ToString("N").Substring(0, 12),
|
|
ToolInput = (fc.TryGetProperty("args", out var a) ? new JsonElement?(a.Clone()) : ((JsonElement?)null))
|
|
});
|
|
fcName = default(JsonElement);
|
|
a = default(JsonElement);
|
|
}
|
|
fc = default(JsonElement);
|
|
}
|
|
text = default(JsonElement);
|
|
}
|
|
}
|
|
return blocks;
|
|
}
|
|
|
|
private object BuildGeminiToolBody(List<ChatMessage> messages, IReadOnlyCollection<IAgentTool> tools)
|
|
{
|
|
List<object> list = new List<object>();
|
|
foreach (ChatMessage message in messages)
|
|
{
|
|
if (message.Role == "system")
|
|
{
|
|
continue;
|
|
}
|
|
string role = ((message.Role == "assistant") ? "model" : "user");
|
|
if (message.Role == "user" && message.Content.StartsWith("{\"type\":\"tool_result\""))
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(message.Content);
|
|
JsonElement rootElement = jsonDocument.RootElement;
|
|
JsonElement value;
|
|
string name = (rootElement.TryGetProperty("tool_name", out value) ? (value.GetString() ?? "") : "");
|
|
JsonElement value2;
|
|
string result = (rootElement.TryGetProperty("content", out value2) ? (value2.GetString() ?? "") : "");
|
|
list.Add(new
|
|
{
|
|
role = "function",
|
|
parts = new object[1]
|
|
{
|
|
new
|
|
{
|
|
functionResponse = new
|
|
{
|
|
name = name,
|
|
response = new { result }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
catch
|
|
{
|
|
goto IL_0146;
|
|
}
|
|
continue;
|
|
}
|
|
goto IL_0146;
|
|
IL_0146:
|
|
if (message.Role == "assistant" && message.Content.StartsWith("{\"_tool_use_blocks\""))
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument2 = JsonDocument.Parse(message.Content);
|
|
if (jsonDocument2.RootElement.TryGetProperty("_tool_use_blocks", out var value3))
|
|
{
|
|
List<object> list2 = new List<object>();
|
|
foreach (JsonElement item in value3.EnumerateArray())
|
|
{
|
|
JsonElement value4;
|
|
string text = (item.TryGetProperty("type", out value4) ? value4.GetString() : "");
|
|
if (text == "text")
|
|
{
|
|
list2.Add(new
|
|
{
|
|
text = (item.TryGetProperty("text", out var value5) ? (value5.GetString() ?? "") : "")
|
|
});
|
|
}
|
|
else if (text == "tool_use")
|
|
{
|
|
list2.Add(new
|
|
{
|
|
functionCall = new
|
|
{
|
|
name = (item.TryGetProperty("name", out var value6) ? (value6.GetString() ?? "") : ""),
|
|
args = (item.TryGetProperty("input", out var value7) ? ((object)value7.Clone()) : new { })
|
|
}
|
|
});
|
|
}
|
|
}
|
|
list.Add(new
|
|
{
|
|
role = "model",
|
|
parts = list2
|
|
});
|
|
continue;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
List<ImageAttachment>? images = message.Images;
|
|
if (images != null && images.Count > 0 && message.Role == "user")
|
|
{
|
|
List<object> list3 = new List<object>
|
|
{
|
|
new
|
|
{
|
|
text = message.Content
|
|
}
|
|
};
|
|
foreach (ImageAttachment image in message.Images)
|
|
{
|
|
list3.Add(new
|
|
{
|
|
inlineData = new
|
|
{
|
|
mimeType = image.MimeType,
|
|
data = image.Base64
|
|
}
|
|
});
|
|
}
|
|
list.Add(new
|
|
{
|
|
role = role,
|
|
parts = list3
|
|
});
|
|
}
|
|
else
|
|
{
|
|
list.Add(new
|
|
{
|
|
role = role,
|
|
parts = new[]
|
|
{
|
|
new
|
|
{
|
|
text = message.Content
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
var function_declarations = tools.Select((IAgentTool t) => new
|
|
{
|
|
name = t.Name,
|
|
description = t.Description,
|
|
parameters = new
|
|
{
|
|
type = "OBJECT",
|
|
properties = t.Parameters.Properties.ToDictionary<KeyValuePair<string, ToolProperty>, string, object>((KeyValuePair<string, ToolProperty> kv) => kv.Key, (KeyValuePair<string, ToolProperty> kv) => BuildPropertySchema(kv.Value, upperCaseType: true)),
|
|
required = t.Parameters.Required
|
|
}
|
|
}).ToArray();
|
|
ChatMessage chatMessage = messages.FirstOrDefault((ChatMessage m) => m.Role == "system");
|
|
Dictionary<string, object> dictionary = new Dictionary<string, object>();
|
|
dictionary["contents"] = list;
|
|
dictionary["tools"] = new[]
|
|
{
|
|
new { function_declarations }
|
|
};
|
|
dictionary["generationConfig"] = new
|
|
{
|
|
temperature = _settings.Settings.Llm.Temperature,
|
|
maxOutputTokens = _settings.Settings.Llm.MaxContextTokens
|
|
};
|
|
Dictionary<string, object> dictionary2 = dictionary;
|
|
if (chatMessage != null)
|
|
{
|
|
dictionary2["systemInstruction"] = new
|
|
{
|
|
parts = new[]
|
|
{
|
|
new
|
|
{
|
|
text = chatMessage.Content
|
|
}
|
|
}
|
|
};
|
|
}
|
|
return dictionary2;
|
|
}
|
|
|
|
private async Task<List<ContentBlock>> SendOpenAiWithToolsAsync(List<ChatMessage> messages, IReadOnlyCollection<IAgentTool> tools, CancellationToken ct)
|
|
{
|
|
_ = _settings.Settings.Llm;
|
|
string activeService = ResolveService();
|
|
object body = BuildOpenAiToolBody(messages, tools);
|
|
string resolvedEp = ResolveServerInfo().Endpoint;
|
|
string endpoint = (string.IsNullOrEmpty(resolvedEp) ? ResolveEndpointForService(activeService) : resolvedEp);
|
|
string url = ((activeService.ToLowerInvariant() == "ollama") ? (endpoint.TrimEnd('/') + "/api/chat") : (endpoint.TrimEnd('/') + "/v1/chat/completions"));
|
|
string json = JsonSerializer.Serialize(body);
|
|
using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url)
|
|
{
|
|
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
};
|
|
await ApplyAuthHeaderAsync(req, ct);
|
|
using HttpResponseMessage resp = await _http.SendAsync(req, ct);
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
string errBody = await resp.Content.ReadAsStringAsync(ct);
|
|
string detail = ExtractErrorDetail(errBody);
|
|
LogService.Warn($"[ToolUse] {activeService} API 오류 ({resp.StatusCode}): {errBody}");
|
|
if (resp.StatusCode == HttpStatusCode.BadRequest)
|
|
{
|
|
throw new ToolCallNotSupportedException($"{activeService} API 오류 ({resp.StatusCode}): {detail}");
|
|
}
|
|
throw new HttpRequestException($"{activeService} API 오류 ({resp.StatusCode}): {detail}");
|
|
}
|
|
using JsonDocument doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync(ct));
|
|
JsonElement root = doc.RootElement;
|
|
TryParseOpenAiUsage(root);
|
|
List<ContentBlock> blocks = new List<ContentBlock>();
|
|
JsonElement message;
|
|
if (root.TryGetProperty("message", out var ollamaMsg))
|
|
{
|
|
message = ollamaMsg;
|
|
}
|
|
else
|
|
{
|
|
if (!root.TryGetProperty("choices", out var choices) || choices.GetArrayLength() <= 0)
|
|
{
|
|
return blocks;
|
|
}
|
|
message = (choices[0].TryGetProperty("message", out var choiceMsg) ? choiceMsg : default(JsonElement));
|
|
}
|
|
if (message.TryGetProperty("content", out var content))
|
|
{
|
|
string text = content.GetString();
|
|
if (!string.IsNullOrWhiteSpace(text))
|
|
{
|
|
blocks.Add(new ContentBlock
|
|
{
|
|
Type = "text",
|
|
Text = text
|
|
});
|
|
}
|
|
}
|
|
if (message.TryGetProperty("tool_calls", out var toolCalls))
|
|
{
|
|
foreach (JsonElement tc in toolCalls.EnumerateArray())
|
|
{
|
|
if (!tc.TryGetProperty("function", out var func))
|
|
{
|
|
continue;
|
|
}
|
|
JsonElement? parsedArgs = null;
|
|
if (func.TryGetProperty("arguments", out var argsEl))
|
|
{
|
|
if (argsEl.ValueKind == JsonValueKind.String)
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument argsDoc = JsonDocument.Parse(argsEl.GetString() ?? "{}");
|
|
parsedArgs = argsDoc.RootElement.Clone();
|
|
}
|
|
catch
|
|
{
|
|
parsedArgs = null;
|
|
}
|
|
}
|
|
else if (argsEl.ValueKind == JsonValueKind.Object || argsEl.ValueKind == JsonValueKind.Array)
|
|
{
|
|
parsedArgs = argsEl.Clone();
|
|
}
|
|
}
|
|
blocks.Add(new ContentBlock
|
|
{
|
|
Type = "tool_use",
|
|
ToolName = (func.TryGetProperty("name", out var fnm) ? (fnm.GetString() ?? "") : ""),
|
|
ToolId = (tc.TryGetProperty("id", out var id) ? (id.GetString() ?? Guid.NewGuid().ToString("N").Substring(0, 12)) : Guid.NewGuid().ToString("N").Substring(0, 12)),
|
|
ToolInput = parsedArgs
|
|
});
|
|
func = default(JsonElement);
|
|
argsEl = default(JsonElement);
|
|
fnm = default(JsonElement);
|
|
id = default(JsonElement);
|
|
}
|
|
}
|
|
return blocks;
|
|
}
|
|
|
|
private object BuildOpenAiToolBody(List<ChatMessage> messages, IReadOnlyCollection<IAgentTool> tools)
|
|
{
|
|
LlmSettings llm = _settings.Settings.Llm;
|
|
List<object> list = new List<object>();
|
|
foreach (ChatMessage message in messages)
|
|
{
|
|
if (message.Role == "user" && message.Content.StartsWith("{\"type\":\"tool_result\""))
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(message.Content);
|
|
JsonElement rootElement = jsonDocument.RootElement;
|
|
list.Add(new
|
|
{
|
|
role = "tool",
|
|
tool_call_id = rootElement.GetProperty("tool_use_id").GetString(),
|
|
content = rootElement.GetProperty("content").GetString()
|
|
});
|
|
}
|
|
catch
|
|
{
|
|
goto IL_00d3;
|
|
}
|
|
continue;
|
|
}
|
|
goto IL_00d3;
|
|
IL_0292:
|
|
if (message.Role == "user")
|
|
{
|
|
List<ImageAttachment>? images = message.Images;
|
|
if (images != null && images.Count > 0)
|
|
{
|
|
List<object> list2 = new List<object>();
|
|
foreach (ImageAttachment image in message.Images)
|
|
{
|
|
list2.Add(new
|
|
{
|
|
type = "image_url",
|
|
image_url = new
|
|
{
|
|
url = "data:" + image.MimeType + ";base64," + image.Base64
|
|
}
|
|
});
|
|
}
|
|
list2.Add(new
|
|
{
|
|
type = "text",
|
|
text = message.Content
|
|
});
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = list2
|
|
});
|
|
continue;
|
|
}
|
|
}
|
|
list.Add(new
|
|
{
|
|
role = message.Role,
|
|
content = message.Content
|
|
});
|
|
continue;
|
|
IL_00d3:
|
|
if (message.Role == "assistant" && message.Content.StartsWith("{\"_tool_use_blocks\""))
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument2 = JsonDocument.Parse(message.Content);
|
|
JsonElement property = jsonDocument2.RootElement.GetProperty("_tool_use_blocks");
|
|
string text = "";
|
|
List<object> list3 = new List<object>();
|
|
foreach (JsonElement item in property.EnumerateArray())
|
|
{
|
|
string text2 = item.GetProperty("type").GetString();
|
|
if (text2 == "text")
|
|
{
|
|
text = item.GetProperty("text").GetString() ?? "";
|
|
}
|
|
else if (text2 == "tool_use")
|
|
{
|
|
JsonElement value;
|
|
string arguments = (item.TryGetProperty("input", out value) ? value.GetRawText() : "{}");
|
|
list3.Add(new
|
|
{
|
|
id = (item.GetProperty("id").GetString() ?? ""),
|
|
type = "function",
|
|
function = new
|
|
{
|
|
name = (item.GetProperty("name").GetString() ?? ""),
|
|
arguments = arguments
|
|
}
|
|
});
|
|
}
|
|
}
|
|
list.Add(new
|
|
{
|
|
role = "assistant",
|
|
content = (string.IsNullOrEmpty(text) ? null : text),
|
|
tool_calls = list3
|
|
});
|
|
}
|
|
catch
|
|
{
|
|
goto IL_0292;
|
|
}
|
|
continue;
|
|
}
|
|
goto IL_0292;
|
|
}
|
|
var tools2 = tools.Select(delegate(IAgentTool t)
|
|
{
|
|
Dictionary<string, object> dictionary = new Dictionary<string, object>
|
|
{
|
|
["type"] = "object",
|
|
["properties"] = t.Parameters.Properties.ToDictionary<KeyValuePair<string, ToolProperty>, string, object>((KeyValuePair<string, ToolProperty> kv) => kv.Key, (KeyValuePair<string, ToolProperty> kv) => BuildPropertySchema(kv.Value, upperCaseType: false))
|
|
};
|
|
List<string> required = t.Parameters.Required;
|
|
if (required != null && required.Count > 0)
|
|
{
|
|
dictionary["required"] = t.Parameters.Required;
|
|
}
|
|
return new
|
|
{
|
|
type = "function",
|
|
function = new
|
|
{
|
|
name = t.Name,
|
|
description = t.Description,
|
|
parameters = dictionary
|
|
}
|
|
};
|
|
}).ToArray();
|
|
string text3 = ResolveService();
|
|
string model = ResolveModel();
|
|
if (text3.Equals("ollama", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return new
|
|
{
|
|
model = model,
|
|
messages = list,
|
|
tools = tools2,
|
|
stream = false,
|
|
options = new
|
|
{
|
|
temperature = llm.Temperature
|
|
}
|
|
};
|
|
}
|
|
return new
|
|
{
|
|
model = model,
|
|
messages = list,
|
|
tools = tools2,
|
|
stream = false,
|
|
temperature = llm.Temperature,
|
|
max_tokens = llm.MaxContextTokens
|
|
};
|
|
}
|
|
|
|
private static object BuildPropertySchema(ToolProperty prop, bool upperCaseType)
|
|
{
|
|
string type = (upperCaseType ? prop.Type.ToUpperInvariant() : prop.Type);
|
|
if (prop.Type.Equals("array", StringComparison.OrdinalIgnoreCase) && prop.Items != null)
|
|
{
|
|
return new
|
|
{
|
|
type = type,
|
|
description = prop.Description,
|
|
items = BuildPropertySchema(prop.Items, upperCaseType)
|
|
};
|
|
}
|
|
List<string> list = prop.Enum;
|
|
if (list != null && list.Count > 0)
|
|
{
|
|
return new
|
|
{
|
|
type = type,
|
|
description = prop.Description,
|
|
@enum = prop.Enum
|
|
};
|
|
}
|
|
return new
|
|
{
|
|
type = type,
|
|
description = prop.Description
|
|
};
|
|
}
|
|
|
|
private static string ExtractErrorDetail(string errBody)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(errBody))
|
|
{
|
|
return "응답 없음";
|
|
}
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(errBody);
|
|
if (jsonDocument.RootElement.TryGetProperty("error", out var value))
|
|
{
|
|
if (value.ValueKind == JsonValueKind.String)
|
|
{
|
|
return value.GetString() ?? errBody;
|
|
}
|
|
if (value.ValueKind == JsonValueKind.Object && value.TryGetProperty("message", out var value2))
|
|
{
|
|
return value2.GetString() ?? errBody;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
return (errBody.Length > 500) ? (errBody.Substring(0, 500) + "…") : errBody;
|
|
}
|
|
}
|