mirror of https://github.com/microsoft/autogen.git
[.Net] feature: Ollama integration (#2693)
* [.Net] feature: Ollama integration with * [.Net] ollama agent improvements and reorganization * added ollama fact logic * [.Net] added ollama embeddings service * [.Net] Ollama embeddings integration * cleaned the agent and connector code * [.Net] cleaned ollama agent tests * [.Net] standardize api key fact ollama host variable * [.Net] fixed solution issue --------- Co-authored-by: Xiaoyun Zhang <bigmiao.zhang@gmail.com>
This commit is contained in:
parent
84577570ad
commit
1c3ae92d39
|
@ -37,6 +37,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel.Test
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.DotnetInteractive.Tests", "test\AutoGen.DotnetInteractive.Tests\AutoGen.DotnetInteractive.Tests.csproj", "{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.DotnetInteractive.Tests", "test\AutoGen.DotnetInteractive.Tests\AutoGen.DotnetInteractive.Tests.csproj", "{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Autogen.Ollama", "src\Autogen.Ollama\Autogen.Ollama.csproj", "{A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Autogen.Ollama.Tests", "test\Autogen.Ollama.Tests\Autogen.Ollama.Tests.csproj", "{C24FDE63-952D-4F8E-A807-AF31D43AD675}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -91,6 +95,14 @@ Global
|
||||||
{15441693-3659-4868-B6C1-B106F52FF3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{15441693-3659-4868-B6C1-B106F52FF3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.Build.0 = Release|Any CPU
|
{15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C24FDE63-952D-4F8E-A807-AF31D43AD675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C24FDE63-952D-4F8E-A807-AF31D43AD675}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C24FDE63-952D-4F8E-A807-AF31D43AD675}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C24FDE63-952D-4F8E-A807-AF31D43AD675}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
@ -116,6 +128,8 @@ Global
|
||||||
{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||||
{6585D1A4-3D97-4D76-A688-1933B61AEB19} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
{6585D1A4-3D97-4D76-A688-1933B61AEB19} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||||
{15441693-3659-4868-B6C1-B106F52FF3BA} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
{15441693-3659-4868-B6C1-B106F52FF3BA} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||||
|
{A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||||
|
{C24FDE63-952D-4F8E-A807-AF31D43AD675} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||||
{1DFABC4A-8458-4875-8DCB-59F3802DAC65} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
{1DFABC4A-8458-4875-8DCB-59F3802DAC65} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||||
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|
|
@ -0,0 +1,216 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// OllamaAgent.cs
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AutoGen.Core;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An agent that can interact with ollama models.
|
||||||
|
/// </summary>
|
||||||
|
public class OllamaAgent : IStreamingAgent
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
public string Name { get; }
|
||||||
|
private readonly string _modelName;
|
||||||
|
private readonly string _systemMessage;
|
||||||
|
private readonly OllamaReplyOptions? _replyOptions;
|
||||||
|
|
||||||
|
public OllamaAgent(HttpClient httpClient, string name, string modelName,
|
||||||
|
string systemMessage = "You are a helpful AI assistant",
|
||||||
|
OllamaReplyOptions? replyOptions = null)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_modelName = modelName;
|
||||||
|
_systemMessage = systemMessage;
|
||||||
|
_replyOptions = replyOptions;
|
||||||
|
}
|
||||||
|
public async Task<IMessage> GenerateReplyAsync(
|
||||||
|
IEnumerable<IMessage> messages, GenerateReplyOptions? options = null, CancellationToken cancellation = default)
|
||||||
|
{
|
||||||
|
ChatRequest request = await BuildChatRequest(messages, options);
|
||||||
|
request.Stream = false;
|
||||||
|
using (HttpResponseMessage? response = await _httpClient
|
||||||
|
.SendAsync(BuildRequestMessage(request), HttpCompletionOption.ResponseContentRead, cancellation))
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
Stream? streamResponse = await response.Content.ReadAsStreamAsync();
|
||||||
|
ChatResponse chatResponse = await JsonSerializer.DeserializeAsync<ChatResponse>(streamResponse, cancellationToken: cancellation)
|
||||||
|
?? throw new Exception("Failed to deserialize response");
|
||||||
|
var output = new MessageEnvelope<ChatResponse>(chatResponse, from: Name);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async IAsyncEnumerable<IStreamingMessage> GenerateStreamingReplyAsync(
|
||||||
|
IEnumerable<IMessage> messages,
|
||||||
|
GenerateReplyOptions? options = null,
|
||||||
|
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ChatRequest request = await BuildChatRequest(messages, options);
|
||||||
|
request.Stream = true;
|
||||||
|
HttpRequestMessage message = BuildRequestMessage(request);
|
||||||
|
using (HttpResponseMessage? response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
using Stream? stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
|
using var reader = new StreamReader(stream);
|
||||||
|
|
||||||
|
while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
string? line = await reader.ReadLineAsync();
|
||||||
|
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||||
|
|
||||||
|
ChatResponseUpdate? update = JsonSerializer.Deserialize<ChatResponseUpdate>(line);
|
||||||
|
if (update != null)
|
||||||
|
{
|
||||||
|
yield return new MessageEnvelope<ChatResponseUpdate>(update, from: Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update is { Done: false }) continue;
|
||||||
|
|
||||||
|
ChatResponse? chatMessage = JsonSerializer.Deserialize<ChatResponse>(line);
|
||||||
|
if (chatMessage == null) continue;
|
||||||
|
yield return new MessageEnvelope<ChatResponse>(chatMessage, from: Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task<ChatRequest> BuildChatRequest(IEnumerable<IMessage> messages, GenerateReplyOptions? options)
|
||||||
|
{
|
||||||
|
var request = new ChatRequest
|
||||||
|
{
|
||||||
|
Model = _modelName,
|
||||||
|
Messages = await BuildChatHistory(messages)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options is OllamaReplyOptions replyOptions)
|
||||||
|
{
|
||||||
|
BuildChatRequestOptions(replyOptions, request);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_replyOptions != null)
|
||||||
|
{
|
||||||
|
BuildChatRequestOptions(_replyOptions, request);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
private void BuildChatRequestOptions(OllamaReplyOptions replyOptions, ChatRequest request)
|
||||||
|
{
|
||||||
|
request.Format = replyOptions.Format == FormatType.Json ? OllamaConsts.JsonFormatType : null;
|
||||||
|
request.Template = replyOptions.Template;
|
||||||
|
request.KeepAlive = replyOptions.KeepAlive;
|
||||||
|
|
||||||
|
if (replyOptions.Temperature != null
|
||||||
|
|| replyOptions.MaxToken != null
|
||||||
|
|| replyOptions.StopSequence != null
|
||||||
|
|| replyOptions.Seed != null
|
||||||
|
|| replyOptions.MiroStat != null
|
||||||
|
|| replyOptions.MiroStatEta != null
|
||||||
|
|| replyOptions.MiroStatTau != null
|
||||||
|
|| replyOptions.NumCtx != null
|
||||||
|
|| replyOptions.NumGqa != null
|
||||||
|
|| replyOptions.NumGpu != null
|
||||||
|
|| replyOptions.NumThread != null
|
||||||
|
|| replyOptions.RepeatLastN != null
|
||||||
|
|| replyOptions.RepeatPenalty != null
|
||||||
|
|| replyOptions.TopK != null
|
||||||
|
|| replyOptions.TopP != null
|
||||||
|
|| replyOptions.TfsZ != null)
|
||||||
|
{
|
||||||
|
request.Options = new ModelReplyOptions
|
||||||
|
{
|
||||||
|
Temperature = replyOptions.Temperature,
|
||||||
|
NumPredict = replyOptions.MaxToken,
|
||||||
|
Stop = replyOptions.StopSequence?[0],
|
||||||
|
Seed = replyOptions.Seed,
|
||||||
|
MiroStat = replyOptions.MiroStat,
|
||||||
|
MiroStatEta = replyOptions.MiroStatEta,
|
||||||
|
MiroStatTau = replyOptions.MiroStatTau,
|
||||||
|
NumCtx = replyOptions.NumCtx,
|
||||||
|
NumGqa = replyOptions.NumGqa,
|
||||||
|
NumGpu = replyOptions.NumGpu,
|
||||||
|
NumThread = replyOptions.NumThread,
|
||||||
|
RepeatLastN = replyOptions.RepeatLastN,
|
||||||
|
RepeatPenalty = replyOptions.RepeatPenalty,
|
||||||
|
TopK = replyOptions.TopK,
|
||||||
|
TopP = replyOptions.TopP,
|
||||||
|
TfsZ = replyOptions.TfsZ
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task<List<Message>> BuildChatHistory(IEnumerable<IMessage> messages)
|
||||||
|
{
|
||||||
|
if (!messages.Any(m => m.IsSystemMessage()))
|
||||||
|
{
|
||||||
|
var systemMessage = new TextMessage(Role.System, _systemMessage, from: Name);
|
||||||
|
messages = new[] { systemMessage }.Concat(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
var collection = new List<Message>();
|
||||||
|
foreach (IMessage? message in messages)
|
||||||
|
{
|
||||||
|
Message item;
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case TextMessage tm:
|
||||||
|
item = new Message { Role = tm.Role.ToString(), Value = tm.Content };
|
||||||
|
break;
|
||||||
|
case ImageMessage im:
|
||||||
|
string base64Image = await ImageUrlToBase64(im.Url!);
|
||||||
|
item = new Message { Role = im.Role.ToString(), Images = [base64Image] };
|
||||||
|
break;
|
||||||
|
case MultiModalMessage mm:
|
||||||
|
var textsGroupedByRole = mm.Content.OfType<TextMessage>().GroupBy(tm => tm.Role)
|
||||||
|
.ToDictionary(g => g.Key, g => string.Join(Environment.NewLine, g.Select(tm => tm.Content)));
|
||||||
|
|
||||||
|
string content = string.Join($"{Environment.NewLine}", textsGroupedByRole
|
||||||
|
.Select(g => $"{g.Key}{Environment.NewLine}:{g.Value}"));
|
||||||
|
|
||||||
|
IEnumerable<Task<string>> imagesConversionTasks = mm.Content
|
||||||
|
.OfType<ImageMessage>()
|
||||||
|
.Select(async im => await ImageUrlToBase64(im.Url!));
|
||||||
|
|
||||||
|
string[]? imagesBase64 = await Task.WhenAll(imagesConversionTasks);
|
||||||
|
item = new Message { Role = mm.Role.ToString(), Value = content, Images = imagesBase64 };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
private static HttpRequestMessage BuildRequestMessage(ChatRequest request)
|
||||||
|
{
|
||||||
|
string serialized = JsonSerializer.Serialize(request);
|
||||||
|
return new HttpRequestMessage(HttpMethod.Post, OllamaConsts.ChatCompletionEndpoint)
|
||||||
|
{
|
||||||
|
Content = new StringContent(serialized, Encoding.UTF8, OllamaConsts.JsonMediaType)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private async Task<string> ImageUrlToBase64(string imageUrl)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(imageUrl))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("required parameter", nameof(imageUrl));
|
||||||
|
}
|
||||||
|
byte[] imageBytes = await _httpClient.GetByteArrayAsync(imageUrl);
|
||||||
|
return imageBytes != null
|
||||||
|
? Convert.ToBase64String(imageBytes)
|
||||||
|
: throw new InvalidOperationException("no image byte array");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AutoGen.Core\AutoGen.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// ChatRequest.cs
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public class ChatRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// (required) the model name
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("model")]
|
||||||
|
public string Model { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the messages of the chat, this can be used to keep a chat memory
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("messages")]
|
||||||
|
public IList<Message> Messages { get; set; } = Array.Empty<Message>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the format to return a response in. Currently, the only accepted value is json
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("format")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Format { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// additional model parameters listed in the documentation for the Modelfile such as temperature
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("options")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public ModelReplyOptions? Options { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// the prompt template to use (overrides what is defined in the Modelfile)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("template")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Template { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// if false the response will be returned as a single response object, rather than a stream of objects
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("stream")]
|
||||||
|
public bool Stream { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// controls how long the model will stay loaded into memory following the request (default: 5m)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("keep_alive")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? KeepAlive { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// ChatResponse.cs
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public class ChatResponse : ChatResponseUpdate
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// time spent generating the response
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("total_duration")]
|
||||||
|
public long TotalDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// time spent in nanoseconds loading the model
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("load_duration")]
|
||||||
|
public long LoadDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// number of tokens in the prompt
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("prompt_eval_count")]
|
||||||
|
public int PromptEvalCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// time spent in nanoseconds evaluating the prompt
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("prompt_eval_duration")]
|
||||||
|
public long PromptEvalDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// number of tokens the response
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("eval_count")]
|
||||||
|
public int EvalCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// time in nanoseconds spent generating the response
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("eval_duration")]
|
||||||
|
public long EvalDuration { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// ChatResponseUpdate.cs
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public class ChatResponseUpdate
|
||||||
|
{
|
||||||
|
[JsonPropertyName("model")]
|
||||||
|
public string Model { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("created_at")]
|
||||||
|
public string CreatedAt { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("message")]
|
||||||
|
public Message? Message { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("done")]
|
||||||
|
public bool Done { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// the role of the message, either system, user or assistant
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("role")]
|
||||||
|
public string Role { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// the content of the message
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("content")]
|
||||||
|
public string Value { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// (optional): a list of images to include in the message (for multimodal models such as llava)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("images")]
|
||||||
|
public IList<string>? Images { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// ModelReplyOptions.cs
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
//https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values
|
||||||
|
public class ModelReplyOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("mirostat")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? MiroStat { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Influences how quickly the algorithm responds to feedback from the generated text.
|
||||||
|
/// A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("mirostat_eta")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public float? MiroStatEta { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the balance between coherence and diversity of the output.
|
||||||
|
/// A lower value will result in more focused and coherent text. (Default: 5.0)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("mirostat_tau")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public float? MiroStatTau { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the size of the context window used to generate the next token. (Default: 2048)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("num_ctx")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? NumCtx { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of GQA groups in the transformer layer. Required for some models, for example it is 8 for llama2:70b
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("num_gqa")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? NumGqa { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of layers to send to the GPU(s). On macOS it defaults to 1 to enable metal support, 0 to disable.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("num_gpu")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? NumGpu { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the number of threads to use during computation. By default, Ollama will detect this for optimal performance.
|
||||||
|
/// It is recommended to set this value to the number of physical CPU cores your system has (as opposed to the logical number of cores).
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("num_thread")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? NumThread { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("repeat_last_n")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? RepeatLastN { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets how strongly to penalize repetitions.
|
||||||
|
/// A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("repeat_penalty")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public float? RepeatPenalty { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("temperature")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public float? Temperature { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the random number seed to use for generation.
|
||||||
|
/// Setting this to a specific number will make the model generate the same text for the same prompt. (Default: 0)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("seed")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? Seed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the stop sequences to use. When this pattern is encountered the LLM will stop generating text and return.
|
||||||
|
/// Multiple stop patterns may be set by specifying multiple separate stop parameters in a modelfile.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("stop")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Stop { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tail free sampling is used to reduce the impact of less probable tokens from the output.
|
||||||
|
/// A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (default: 1)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("tfs_z")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public float? TfsZ { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of tokens to predict when generating text. (Default: 128, -1 = infinite generation, -2 = fill context)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("num_predict")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? NumPredict { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("top_k")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? TopK { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("top_p")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? TopP { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// OllamaReplyOptions.cs
|
||||||
|
|
||||||
|
using AutoGen.Core;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public enum FormatType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Json
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OllamaReplyOptions : GenerateReplyOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// the format to return a response in. Currently, the only accepted value is json
|
||||||
|
/// </summary>
|
||||||
|
public FormatType Format { get; set; } = FormatType.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the prompt template to use (overrides what is defined in the Modelfile)
|
||||||
|
/// </summary>
|
||||||
|
public string? Template { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8)
|
||||||
|
/// </summary>
|
||||||
|
public new float? Temperature { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// controls how long the model will stay loaded into memory following the request (default: 5m)
|
||||||
|
/// </summary>
|
||||||
|
public string? KeepAlive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0)
|
||||||
|
/// </summary>
|
||||||
|
public int? MiroStat { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Influences how quickly the algorithm responds to feedback from the generated text.
|
||||||
|
/// A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1)
|
||||||
|
/// </summary>
|
||||||
|
public float? MiroStatEta { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the balance between coherence and diversity of the output.
|
||||||
|
/// A lower value will result in more focused and coherent text. (Default: 5.0)
|
||||||
|
/// </summary>
|
||||||
|
public float? MiroStatTau { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the size of the context window used to generate the next token. (Default: 2048)
|
||||||
|
/// </summary>
|
||||||
|
public int? NumCtx { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of GQA groups in the transformer layer. Required for some models, for example it is 8 for llama2:70b
|
||||||
|
/// </summary>
|
||||||
|
public int? NumGqa { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of layers to send to the GPU(s). On macOS it defaults to 1 to enable metal support, 0 to disable.
|
||||||
|
/// </summary>
|
||||||
|
public int? NumGpu { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the number of threads to use during computation. By default, Ollama will detect this for optimal performance.
|
||||||
|
/// It is recommended to set this value to the number of physical CPU cores your system has (as opposed to the logical number of cores).
|
||||||
|
/// </summary>
|
||||||
|
public int? NumThread { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)
|
||||||
|
/// </summary>
|
||||||
|
public int? RepeatLastN { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets how strongly to penalize repetitions.
|
||||||
|
/// A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1)
|
||||||
|
/// </summary>
|
||||||
|
public float? RepeatPenalty { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the random number seed to use for generation.
|
||||||
|
/// Setting this to a specific number will make the model generate the same text for the same prompt. (Default: 0)
|
||||||
|
/// </summary>
|
||||||
|
public int? Seed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tail free sampling is used to reduce the impact of less probable tokens from the output.
|
||||||
|
/// A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (default: 1)
|
||||||
|
/// </summary>
|
||||||
|
public float? TfsZ { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of tokens to predict when generating text. (Default: 128, -1 = infinite generation, -2 = fill context)
|
||||||
|
/// </summary>
|
||||||
|
public new int? MaxToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)
|
||||||
|
/// </summary>
|
||||||
|
public int? TopK { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)
|
||||||
|
/// </summary>
|
||||||
|
public int? TopP { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// ITextEmbeddingService.cs
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public interface ITextEmbeddingService
|
||||||
|
{
|
||||||
|
public Task<TextEmbeddingsResponse> GenerateAsync(TextEmbeddingsRequest request, CancellationToken cancellationToken);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// OllamaTextEmbeddingService.cs
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public class OllamaTextEmbeddingService : ITextEmbeddingService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
|
||||||
|
public OllamaTextEmbeddingService(HttpClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
public async Task<TextEmbeddingsResponse> GenerateAsync(TextEmbeddingsRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using (HttpResponseMessage? response = await _client
|
||||||
|
.SendAsync(BuildPostRequest(request), HttpCompletionOption.ResponseContentRead, cancellationToken))
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
Stream? streamResponse = await response.Content.ReadAsStreamAsync();
|
||||||
|
TextEmbeddingsResponse output = await JsonSerializer
|
||||||
|
.DeserializeAsync<TextEmbeddingsResponse>(streamResponse, cancellationToken: cancellationToken)
|
||||||
|
?? throw new Exception("Failed to deserialize response");
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static HttpRequestMessage BuildPostRequest(TextEmbeddingsRequest request)
|
||||||
|
{
|
||||||
|
string serialized = JsonSerializer.Serialize(request);
|
||||||
|
return new HttpRequestMessage(HttpMethod.Post, OllamaConsts.EmbeddingsEndpoint)
|
||||||
|
{
|
||||||
|
Content = new StringContent(serialized, Encoding.UTF8, OllamaConsts.JsonMediaType)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// TextEmbeddingsRequest.cs
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public class TextEmbeddingsRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// name of model to generate embeddings from
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("model")]
|
||||||
|
public string Model { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// text to generate embeddings for
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("prompt")]
|
||||||
|
public string Prompt { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// additional model parameters listed in the documentation for the Modelfile such as temperature
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("options")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public ModelReplyOptions? Options { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// controls how long the model will stay loaded into memory following the request (default: 5m)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("keep_alive")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? KeepAlive { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// TextEmbeddingsResponse.cs
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public class TextEmbeddingsResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("embedding")]
|
||||||
|
public double[]? Embedding { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// OllamaMessageConnector.cs
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AutoGen.Core;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public class OllamaMessageConnector : IMiddleware, IStreamingMiddleware
|
||||||
|
{
|
||||||
|
public string Name => nameof(OllamaMessageConnector);
|
||||||
|
|
||||||
|
public async Task<IMessage> InvokeAsync(MiddlewareContext context, IAgent agent,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
IEnumerable<IMessage> messages = context.Messages;
|
||||||
|
IMessage reply = await agent.GenerateReplyAsync(messages, context.Options, cancellationToken);
|
||||||
|
switch (reply)
|
||||||
|
{
|
||||||
|
case IMessage<ChatResponse> messageEnvelope:
|
||||||
|
Message? message = messageEnvelope.Content.Message;
|
||||||
|
return new TextMessage(Role.Assistant, message != null ? message.Value : "EMPTY_CONTENT", messageEnvelope.From);
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<IStreamingMessage> InvokeAsync(MiddlewareContext context, IStreamingAgent agent,
|
||||||
|
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await foreach (IStreamingMessage? update in agent.GenerateStreamingReplyAsync(context.Messages, context.Options, cancellationToken))
|
||||||
|
{
|
||||||
|
switch (update)
|
||||||
|
{
|
||||||
|
case IMessage<ChatResponse> complete:
|
||||||
|
{
|
||||||
|
string? textContent = complete.Content.Message?.Value;
|
||||||
|
yield return new TextMessage(Role.Assistant, textContent!, complete.From);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IMessage<ChatResponseUpdate> updatedMessage:
|
||||||
|
{
|
||||||
|
string? textContent = updatedMessage.Content.Message?.Value;
|
||||||
|
yield return new TextMessageUpdate(Role.Assistant, textContent, updatedMessage.From);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException("Message type not supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// OllamaConsts.cs
|
||||||
|
|
||||||
|
namespace Autogen.Ollama;
|
||||||
|
|
||||||
|
public class OllamaConsts
|
||||||
|
{
|
||||||
|
public const string JsonFormatType = "json";
|
||||||
|
public const string JsonMediaType = "application/json";
|
||||||
|
public const string ChatCompletionEndpoint = "/api/chat";
|
||||||
|
public const string EmbeddingsEndpoint = "/api/embeddings";
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.8.0" />
|
||||||
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0"/>
|
||||||
|
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Autogen.Ollama\Autogen.Ollama.csproj" />
|
||||||
|
<ProjectReference Include="..\AutoGen.Tests\AutoGen.Tests.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// OllamaAgentTests.cs
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using AutoGen.Core;
|
||||||
|
using AutoGen.Tests;
|
||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama.Tests;
|
||||||
|
|
||||||
|
public class OllamaAgentTests
|
||||||
|
{
|
||||||
|
|
||||||
|
[ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
|
||||||
|
public async Task GenerateReplyAsync_ReturnsValidMessage_WhenCalled()
|
||||||
|
{
|
||||||
|
string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
|
||||||
|
?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
|
||||||
|
string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
|
||||||
|
?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
|
||||||
|
OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
|
||||||
|
|
||||||
|
var messages = new IMessage[] { new TextMessage(Role.User, "Hello, how are you") };
|
||||||
|
IMessage result = await ollamaAgent.GenerateReplyAsync(messages);
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().BeOfType<MessageEnvelope<ChatResponse>>();
|
||||||
|
result.From.Should().Be(ollamaAgent.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
|
||||||
|
public async Task GenerateReplyAsync_ReturnsValidJsonMessageContent_WhenCalled()
|
||||||
|
{
|
||||||
|
string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
|
||||||
|
?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
|
||||||
|
string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
|
||||||
|
?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
|
||||||
|
OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
|
||||||
|
|
||||||
|
var messages = new IMessage[] { new TextMessage(Role.User, "Hello, how are you") };
|
||||||
|
IMessage result = await ollamaAgent.GenerateReplyAsync(messages, new OllamaReplyOptions
|
||||||
|
{
|
||||||
|
Format = FormatType.Json
|
||||||
|
});
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().BeOfType<MessageEnvelope<ChatResponse>>();
|
||||||
|
result.From.Should().Be(ollamaAgent.Name);
|
||||||
|
|
||||||
|
string jsonContent = ((MessageEnvelope<ChatResponse>)result).Content.Message!.Value;
|
||||||
|
bool isValidJson = IsValidJsonMessage(jsonContent);
|
||||||
|
isValidJson.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
|
||||||
|
public async Task GenerateStreamingReplyAsync_ReturnsValidMessages_WhenCalled()
|
||||||
|
{
|
||||||
|
string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
|
||||||
|
?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
|
||||||
|
string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
|
||||||
|
?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
|
||||||
|
OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
|
||||||
|
|
||||||
|
var messages = new IMessage[] { new TextMessage(Role.User, "Hello how are you") };
|
||||||
|
IStreamingMessage? finalReply = default;
|
||||||
|
await foreach (IStreamingMessage message in ollamaAgent.GenerateStreamingReplyAsync(messages))
|
||||||
|
{
|
||||||
|
message.Should().NotBeNull();
|
||||||
|
message.From.Should().Be(ollamaAgent.Name);
|
||||||
|
finalReply = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalReply.Should().BeOfType<MessageEnvelope<ChatResponse>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidJsonMessage(string input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JsonDocument.Parse(input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("An unexpected exception occurred: " + ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OllamaAgent BuildOllamaAgent(string host, string modelName)
|
||||||
|
{
|
||||||
|
var httpClient = new HttpClient
|
||||||
|
{
|
||||||
|
BaseAddress = new Uri(host)
|
||||||
|
};
|
||||||
|
return new OllamaAgent(httpClient, "TestAgent", modelName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// OllamaTextEmbeddingServiceTests.cs
|
||||||
|
|
||||||
|
using AutoGen.Tests;
|
||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
namespace Autogen.Ollama.Tests;
|
||||||
|
|
||||||
|
public class OllamaTextEmbeddingServiceTests
|
||||||
|
{
|
||||||
|
[ApiKeyFact("OLLAMA_HOST", "OLLAMA_EMBEDDING_MODEL_NAME")]
|
||||||
|
public async Task GenerateAsync_ReturnsEmbeddings_WhenApiResponseIsSuccessful()
|
||||||
|
{
|
||||||
|
string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
|
||||||
|
?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
|
||||||
|
string embeddingModelName = Environment.GetEnvironmentVariable("OLLAMA_EMBEDDING_MODEL_NAME")
|
||||||
|
?? throw new InvalidOperationException("OLLAMA_EMBEDDING_MODEL_NAME is not set.");
|
||||||
|
var httpClient = new HttpClient
|
||||||
|
{
|
||||||
|
BaseAddress = new Uri(host)
|
||||||
|
};
|
||||||
|
var request = new TextEmbeddingsRequest { Model = embeddingModelName, Prompt = "Llamas are members of the camelid family", };
|
||||||
|
var service = new OllamaTextEmbeddingService(httpClient);
|
||||||
|
TextEmbeddingsResponse response = await service.GenerateAsync(request);
|
||||||
|
response.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue