Compare commits

..

17 Commits
0.3.2 ... 0.3.4

Author SHA1 Message Date
zyxucp
73b65f7305 Merge pull request #84 from AIDotNet/feature_llamasharp
Feature llamasharp
2024-04-28 20:38:01 +08:00
zyxucp
0ea52eced9 fix 修改聊天为chathistory 2024-04-28 20:37:37 +08:00
zyxucp
498e9ba9f6 Merge branch 'main' into feature_llamasharp 2024-04-28 20:24:30 +08:00
zyxucp
125695665b add 修改水印影响样式问题 2024-04-28 16:53:10 +08:00
zyxucp
0e08b3ae85 add 水印 2024-04-28 15:37:51 +08:00
zyxucp
7cb8f99e7e fix 处理聊天对话 2024-04-27 23:03:46 +08:00
zyxucp
d15cb527d0 add 教程视频 2024-04-24 23:56:45 +08:00
zyxucp
9cb36174fd Update README.md 2024-04-23 14:16:50 +08:00
zyxucp
6265f94ef2 fix 修改文档导入问答单独的index 2024-04-23 13:32:57 +08:00
zyxucp
09d90b654c fix 处理文档问答问题 2024-04-23 13:28:38 +08:00
zyxucp
64e2bca2e6 Merge pull request #80 from AIDotNet/fix_chatkmsbug
fix 修改聊天记录知识库保存bug
2024-04-23 12:54:49 +08:00
zyxucp
328ece6d73 fix 修改聊天记录知识库保存bug 2024-04-23 12:53:51 +08:00
zyxucp
fabb8c2044 fix 处理合并 2024-04-23 11:51:08 +08:00
zyxucp
6ca75df880 fix 处理合并 2024-04-23 11:50:46 +08:00
zyxucp
3d4dfaced1 margin 2024-04-23 11:48:43 +08:00
zyxucp
d532bf3bb6 add 聊天历史记录搜索 2024-04-22 23:50:08 +08:00
zyxucp
af2930a371 fix 修改WithLog 2024-04-19 18:37:03 +08:00
19 changed files with 571 additions and 187 deletions

View File

@@ -42,9 +42,11 @@ AntSK 适用于多种业务场景,例如:
## ✏️功能示例
### 在线演示
```
https://antsk.ai-dotnet.com/
```
[文档地址](http://antsk.cn/)
[体验地址](https://antsk.ai-dotnet.com/)
```
默认账号test

View File

@@ -199,7 +199,7 @@
<member name="M:AntSK.Domain.Domain.Other.QAHandler.InvokeAsync(Microsoft.KernelMemory.Pipeline.DataPipeline,System.Threading.CancellationToken)">
<inheritdoc />
</member>
<member name="M:AntSK.Domain.Domain.Service.ChatService.SendChatByAppAsync(AntSK.Domain.Repositories.Apps,System.String,Microsoft.SemanticKernel.ChatCompletion.ChatHistory)">
<member name="M:AntSK.Domain.Domain.Service.ChatService.SendChatByAppAsync(AntSK.Domain.Repositories.Apps,Microsoft.SemanticKernel.ChatCompletion.ChatHistory)">
<summary>
发送消息
</summary>

View File

@@ -14,10 +14,10 @@ namespace AntSK.Domain.Domain.Interface
{
public interface IChatService
{
IAsyncEnumerable<StreamingKernelContent> SendChatByAppAsync(Apps app, string questions, ChatHistory history);
IAsyncEnumerable<string> SendChatByAppAsync(Apps app, ChatHistory history);
IAsyncEnumerable<StreamingKernelContent> SendKmsByAppAsync(Apps app, string questions, ChatHistory history, string filePath, List<RelevantSource> relevantSources = null);
Task<string> SendImgByAppAsync(Apps app, string questions);
Task<ChatHistory> GetChatHistory(List<Chats> MessageList);
Task<ChatHistory> GetChatHistory(List<Chats> MessageList, ChatHistory history);
}
}

View File

@@ -9,7 +9,10 @@ namespace AntSK.Domain.Domain.Model.Constant
public class KmsConstantcs
{
public const string KmsIdTag = "kmsid";
public const string FileIdTag = "fileid";
public const string AppIdTag = "appid";
public const string KmsIndex = "kms";
public const string FileIndex = "kms";
public const string KmsSearchNull="知识库未搜索到相关内容";
public const string KmsPrompt = @"使用<data></data>标记的内容作为你的知识:

View File

@@ -10,6 +10,7 @@ using AntSK.LLM.StableDiffusion;
using Markdig;
using Microsoft.KernelMemory;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.Diagnostics;
using System.Drawing;
@@ -35,45 +36,54 @@ namespace AntSK.Domain.Domain.Service
/// <param name="questions"></param>
/// <param name="history"></param>
/// <returns></returns>
public async IAsyncEnumerable<StreamingKernelContent> SendChatByAppAsync(Apps app, string questions, ChatHistory history)
public async IAsyncEnumerable<string> SendChatByAppAsync(Apps app, ChatHistory history)
{
if (string.IsNullOrEmpty(app.Prompt) || !app.Prompt.Contains("{{$input}}"))
{
//如果模板为空,给默认提示词
app.Prompt = app.Prompt.ConvertToString() + "{{$input}}";
}
KernelArguments args = new KernelArguments();
if (history.Count > 10)
{
app.Prompt = @"${{ConversationSummaryPlugin.SummarizeConversation $history}}" + app.Prompt;
args = new() {
{ "history", string.Join("\n", history.Select(x => x.Role + ": " + x.Content)) },
{ "input", questions }
};
}
else
{
args = new()
{
{ "input", $"{string.Join("\n", history.Select(x => x.Role + ": " + x.Content))}{Environment.NewLine} user:{questions}" }
};
}
var _kernel = _kernelService.GetKernelByApp(app);
var chat = _kernel.GetRequiredService<IChatCompletionService>();
var temperature = app.Temperature / 100;//存的是0~100需要缩小
OpenAIPromptExecutionSettings settings = new() { Temperature = temperature };
List<string> completionList = new List<string>();
if (!string.IsNullOrEmpty(app.ApiFunctionList) || !string.IsNullOrEmpty(app.NativeFunctionList))//这里还需要加上本地插件的
{
_kernelService.ImportFunctionsByApp(app, _kernel);
settings.ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions;
settings.ToolCallBehavior = ToolCallBehavior.EnableKernelFunctions;
while (true)
{
ChatMessageContent result = await chat.GetChatMessageContentAsync(history, settings, _kernel);
if (result.Content is not null)
{
string chunkCompletion = result.Content.ConvertToString();
completionList.Add(chunkCompletion);
foreach (var content in completionList)
{
yield return content.ConvertToString();
}
break;
}
history.Add(result);
IEnumerable<FunctionCallContent> functionCalls = FunctionCallContent.GetFunctionCalls(result);
if (!functionCalls.Any())
{
break;
}
foreach (var functionCall in functionCalls)
{
FunctionResultContent resultContent = await functionCall.InvokeAsync(_kernel);
history.Add(resultContent.ToChatMessage());
}
}
}
var func = _kernel.CreateFunctionFromPrompt(app.Prompt, settings);
var chatResult = _kernel.InvokeStreamingAsync(function: func,
arguments: args);
await foreach (var content in chatResult)
else
{
yield return content;
var chatResult = chat.GetStreamingChatMessageContentsAsync(history, settings, _kernel);
await foreach (var content in chatResult)
{
yield return content.ConvertToString();
}
}
}
@@ -85,20 +95,39 @@ namespace AntSK.Domain.Domain.Service
if (!string.IsNullOrWhiteSpace(filePath))
{
var memory = _kMService.GetMemoryByApp(app);
var fileId = Guid.NewGuid().ToString();
var result = await memory.ImportDocumentAsync(new Microsoft.KernelMemory.Document(fileId).AddFile(filePath)
.AddTag(KmsConstantcs.KmsIdTag, app.Id)
, index: KmsConstantcs.KmsIndex);
var filters = new MemoryFilter().ByTag(KmsConstantcs.KmsIdTag, app.Id);
// 匹配GUID的正则表达式
string pattern = @"\b[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\b";
var searchResult = await memory.SearchAsync(questions, index: KmsConstantcs.KmsIndex, filters: [filters]);
relevantSourceList.AddRange(searchResult.Results.SelectMany(item => item.Partitions.Select(part => new RelevantSource()
// 使用正则表达式找到匹配
Match match = Regex.Match(filePath, pattern);
if (match.Success)
{
SourceName = item.SourceName,
Text = Markdown.ToHtml(part.Text),
Relevance = part.Relevance
})));
var fileId = match.Value;
var status=await memory.IsDocumentReadyAsync(fileId, index: KmsConstantcs.KmsIndex);
if (!status)
{
var result = await memory.ImportDocumentAsync(new Document(fileId).AddFile(filePath)
.AddTag(KmsConstantcs.AppIdTag, app.Id)
.AddTag(KmsConstantcs.FileIdTag, fileId)
, index: KmsConstantcs.FileIndex);
}
var filters = new List<MemoryFilter>() {
new MemoryFilter().ByTag(KmsConstantcs.AppIdTag, app.Id),
new MemoryFilter().ByTag(KmsConstantcs.FileIdTag, fileId)
};
var searchResult = await memory.SearchAsync(questions, index: KmsConstantcs.FileIndex, filters: filters);
relevantSourceList.AddRange(searchResult.Results.SelectMany(item => item.Partitions.Select(part => new RelevantSource()
{
SourceName = item.SourceName,
Text = Markdown.ToHtml(part.Text),
Relevance = part.Relevance
})));
app.Prompt = KmsConstantcs.KmsPrompt;
}
}
@@ -155,11 +184,13 @@ namespace AntSK.Domain.Domain.Service
}
else
{
string fileName = _kmsDetails_Repositories.GetFirst(p => p.FileGuidName == item.SourceName).FileName;
fileDic.Add(item.SourceName, fileName);
item.SourceName = fileName;
var fileDetail = _kmsDetails_Repositories.GetFirst(p => p.FileGuidName == item.SourceName);
if (fileDetail.IsNotNull())
{
string fileName = fileDetail.FileName;
fileDic.Add(item.SourceName, fileName);
item.SourceName = fileName;
}
}
item.Text = Markdown.ToHtml(item.Text);
}
@@ -169,7 +200,7 @@ namespace AntSK.Domain.Domain.Service
//KernelFunction jsonFun = _kernel.Plugins.GetFunction("KMSPlugin", "Ask1");
var temperature = app.Temperature / 100;//存的是0~100需要缩小
OpenAIPromptExecutionSettings settings = new() { Temperature = temperature };
var func = _kernel.CreateFunctionFromPrompt(app.Prompt, settings);
var func = _kernel.CreateFunctionFromPrompt(app.Prompt , settings);
var chatResult = _kernel.InvokeStreamingAsync(function: func,
arguments: new KernelArguments() { ["doc"] = dataMsg.ToString(), ["history"] = string.Join("\n", history.Select(x => x.Role + ": " + x.Content)), ["input"] = questions });
@@ -297,9 +328,8 @@ namespace AntSK.Domain.Domain.Service
}
}
public async Task<ChatHistory> GetChatHistory(List<Chats> MessageList)
public async Task<ChatHistory> GetChatHistory(List<Chats> MessageList, ChatHistory history)
{
ChatHistory history = new ChatHistory();
if (MessageList.Count > 1)
{

View File

@@ -20,6 +20,8 @@ using System.Reflection;
using DocumentFormat.OpenXml.Drawing;
using Microsoft.KernelMemory;
using OpenCvSharp.ML;
using LLamaSharp.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.ChatCompletion;
namespace AntSK.Domain.Domain.Service
{
@@ -105,11 +107,13 @@ namespace AntSK.Domain.Domain.Service
var (weights, parameters) = LLamaConfig.GetLLamaConfig(chatModel.ModelName);
var ex = new StatelessExecutor(weights, parameters);
builder.Services.AddKeyedSingleton<ITextGenerationService>("local-llama", new LLamaSharpTextCompletion(ex));
builder.Services.AddKeyedSingleton<IChatCompletionService>("local-llama-chat", new LLamaSharpChatCompletion(ex));
break;
case Model.Enum.AIType.SparkDesk:
var options = new SparkDeskOptions { AppId = chatModel.EndPoint, ApiSecret = chatModel.ModelKey, ApiKey = chatModel.ModelName, ModelVersion = Sdcb.SparkDesk.ModelVersion.V3_5 };
builder.Services.AddKeyedSingleton<ITextGenerationService>("spark-desk", new SparkDeskTextCompletion(options, chatModel.Id));
builder.Services.AddKeyedSingleton<IChatCompletionService>("spark-desk-chat", new SparkDeskChatCompletion(options, chatModel.Id));
break;
case Model.Enum.AIType.DashScope:
@@ -118,6 +122,7 @@ namespace AntSK.Domain.Domain.Service
case Model.Enum.AIType.Mock:
builder.Services.AddKeyedSingleton<ITextGenerationService>("mock", new MockTextCompletion());
builder.Services.AddKeyedSingleton<IChatCompletionService>("mock-chat", new MockChatCompletion());
break;
case Model.Enum.AIType.LLamaFactory:
builder.AddOpenAIChatCompletion(

View File

@@ -53,7 +53,7 @@
<Button Type="@ButtonType.Link" OnClick="NavigateModelList">去创建</Button>
</FormItem>
<FormItem Label="提示词" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<TextArea MinRows="4" Placeholder="请输入提示词,用户输入使用{{$input}} 来做占位符" @bind-Value="@context.Prompt" />
<TextArea MinRows="4" Placeholder="请输入角色信息" @bind-Value="@context.Prompt" />
</FormItem>
<FormItem Label="温度系数" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<span>更确定</span>

View File

@@ -25,7 +25,9 @@
<Body>
@if (!string.IsNullOrEmpty(AppId))
{
<ChatView AppId="@AppId" ShowTitle=false OnRelevantSources="OnRelevantSources"></ChatView>
<Watermark Content="AntSK">
<ChatView AppId="@AppId" ShowTitle=false OnRelevantSources="OnRelevantSources"></ChatView>
</Watermark>
}
</Body>
</Card>
@@ -42,11 +44,11 @@
<AntList Bordered DataSource="@_relevantSources" Style="padding:10px;">
<ChildContent Context="item">
<span> <b>@item.SourceName </b> </span>
<br/>
<br />
<span>相似度:<Text Mark> @item.Relevance</Text></span>
@if (@item.RerankScore != 0)
{
<br/>
<br />
<span> Rerank相关性<Text Mark> @item.RerankScore</Text></span>
}
<Body>

View File

@@ -5,6 +5,7 @@
@layout OpenLayout
@inherits AntDomComponentBase
<div id="chat">
@if (ShowTitle)
{
@@ -64,7 +65,7 @@
<Flex Justify="end">
<AntDesign.Input @bind-Value="@(_messageInput)" DebounceMilliseconds="@(-1)" Placeholder="输入消息回车发送" OnPressEnter="@(async () => await OnSendAsync())" Disabled="@Sendding"></AntDesign.Input>
@if (app.EmbeddingModelID!=null)
@if (app.EmbeddingModelID != null)
{
<Upload Action="@("api/File/UploadFile")"
Name="file"

View File

@@ -8,6 +8,7 @@ using AntSK.Domain.Utils;
using AntSK.LLM.StableDiffusion;
using AntSK.Models;
using Blazored.LocalStorage;
using DocumentFormat.OpenXml.InkML;
using Markdig;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
@@ -85,7 +86,6 @@ namespace AntSK.Pages.ChatPage.Components
/// <returns></returns>
private async Task GetMsgList()
{
MessageList.Clear();
List<Chats> msgs = new List<Chats>();
if (string.IsNullOrEmpty(_userName))
{
@@ -122,7 +122,7 @@ namespace AntSK.Pages.ChatPage.Components
/// 保存聊天记录
/// </summary>
/// <returns></returns>
private async Task SaveMsg()
private async Task SaveMsg(List<Chats> MessageList)
{
if (string.IsNullOrEmpty(_userName))
{
@@ -130,7 +130,10 @@ namespace AntSK.Pages.ChatPage.Components
}
else
{
await _chats_Repositories.InsertAsync(MessageList.LastOrDefault());
if (MessageList.Count() > 0)
{
await _chats_Repositories.InsertAsync(MessageList.LastOrDefault());
}
}
}
@@ -221,32 +224,50 @@ namespace AntSK.Pages.ChatPage.Components
/// <returns></returns>
protected async Task<bool> SendAsync(string questions, string? filePath)
{
ChatHistory history = new ChatHistory();
//处理多轮会话
Apps app = _apps_Repositories.GetFirst(p => p.Id == AppId);
if (MessageList.Count > 0)
{
history = await _chatService.GetChatHistory(MessageList);
}
ChatHistory history;
if (app.Type == AppType.chat.ToString() && (filePath == null || app.EmbeddingModelID.IsNull()))
{
await SendChat(questions, history, app);
if (string.IsNullOrEmpty(app.Prompt))
{
app.Prompt = "你叫AntSK,是一个人工智能助手";
}
//聊天应用增加系统角色
history = new ChatHistory(app.Prompt.ConvertToString());
if (MessageList.Count > 0)
{
history = await _chatService.GetChatHistory(MessageList, history);
}
await SendChat(history, app);
}
else if (app.Type == AppType.kms.ToString() || filePath != null || app.EmbeddingModelID.IsNotNull())
{
history = new ChatHistory();
if (MessageList.Count > 0)
{
history = await _chatService.GetChatHistory(MessageList, history);
}
await SendKms(questions, history, app, filePath);
}
else if (app.Type == AppType.img.ToString())
{
await SendImg(questions,app);
await SendImg(questions, app);
}
//缓存消息记录
if (app.Type != AppType.img.ToString())
{
await SaveMsg();
await SaveMsg(MessageList);
if (OnRelevantSources.IsNotNull())
{
await OnRelevantSources.InvokeAsync(_relevantSources);
}
}
@@ -287,29 +308,23 @@ namespace AntSK.Pages.ChatPage.Components
/// <returns></returns>
private async Task SendKms(string questions, ChatHistory history, Apps app, string? filePath)
{
Chats info = null;
Chats info = new Chats()
{
Id = Guid.NewGuid().ToString(),
AppId = AppId,
UserName = _userName,
CreateTime = DateTime.Now,
Context=""
};
MessageList.Add(info);
var chatResult = _chatService.SendKmsByAppAsync(app, questions, history, filePath, _relevantSources);
await foreach (var content in chatResult)
{
if (info == null)
{
info = new Chats();
info.Id = Guid.NewGuid().ToString();
info.UserName = _userName;
info.AppId = AppId;
info.Context = content.ConvertToString();
info.CreateTime = DateTime.Now;
MessageList.Add(info);
}
else
{
info.Context += content.ConvertToString();
await Task.Delay(50);
}
info.Context += content.ConvertToString();
await Task.Delay(50);
await InvokeAsync(StateHasChanged);
}
await OnRelevantSources.InvokeAsync(_relevantSources);
//全部处理完后再处理一次Markdown
await MarkDown(info);
}
@@ -317,14 +332,13 @@ namespace AntSK.Pages.ChatPage.Components
/// <summary>
/// 发送普通对话
/// </summary>
/// <param name="questions"></param>
/// <param name="history"></param>
/// <param name="app"></param>
/// <returns></returns>
private async Task SendChat(string questions, ChatHistory history, Apps app)
private async Task SendChat(ChatHistory history, Apps app)
{
Chats info = null;
var chatResult = _chatService.SendChatByAppAsync(app, questions, history);
var chatResult = _chatService.SendChatByAppAsync(app, history);
await foreach (var content in chatResult)
{
if (info == null)

View File

@@ -6,14 +6,16 @@
@page "/OpenChat/{AppId}"
@layout OpenLayout
<ChatView AppId="@AppId" ShowTitle=true>
</ChatView>
<style>
#chat{
height:100% ;
display:flex;
#chat {
height: 100%;
display: flex;
flex-direction: column;
overflow-x: hidden;
font-family: Arial, sans-serif;
@@ -21,7 +23,7 @@
padding: 10px;
}
</style>
@code{
@code {
[Parameter]
public string AppId { get; set; }
}

View File

@@ -85,12 +85,13 @@
<FormItem Label="APPID" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入APPID" @bind-Value="@context.EndPoint" />
</FormItem>
<FormItem Label="APIKey" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入请输入APIKey" @bind-Value="@context.ModelName" />
</FormItem>
<FormItem Label="APISecret" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<InputPassword @bind-Value="@context.ModelKey" Placeholder="APISecret" Size="@InputSize.Large" />
</FormItem>
<FormItem Label="APIKey" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入请输入APIKey" @bind-Value="@context.ModelName" />
</FormItem>
}
@if (context.AIType == AIType.DashScope)
{

View File

@@ -8,34 +8,42 @@
@using AntSK.Domain.Repositories
@using AntSK.Domain.Common.Map
@attribute [Authorize(Roles = "AntSKAdmin")]
<div>
<PageContainer Title="聊天记录">
<ChildContent>
<Table @ref="table"
TItem="ChatsDto"
DataSource="@chatDtoList"
Total="_total"
@bind-PageIndex="_pageIndex"
@bind-PageSize="_pageSize"
OnChange="OnChange"
Size="TableSize.Small"
RowKey="x=>x.Id">
<PropertyColumn Property="c=>c.Id" />
<PropertyColumn Title="用户名" Property="c=>c.UserName" />
<PropertyColumn Title="应用名称" Property="c=>c.AppName" />
<PropertyColumn Title="发送/接收" Property="c=>c.SendReveice" />
<ActionColumn Title="消息内容" Width="30%">
<Space>
<SpaceItem>
@((MarkupString)(context.Context))
</SpaceItem>
</Space>
</ActionColumn>
<PropertyColumn Title="时间" Property="c=>c.CreateTime" Format="yyyy-MM-dd HH:mm:ss" />
</Table>
</ChildContent>
</PageContainer>
</div>
<Table @ref="table"
TItem="ChatsDto"
DataSource="@chatDtoList"
Total="_total"
@bind-PageIndex="_pageIndex"
@bind-PageSize="_pageSize"
OnChange="OnChange"
Size="TableSize.Small"
RowKey="x=>x.Id">
<TitleTemplate>
<GridRow>
<GridCol Span="4">
<Title Level="3">聊天记录</Title>
</GridCol>
<GridCol Span="8" Offset="12">
<Search Placeholder="搜索" @bind-Value="searchString" OnSearch="Search" />
</GridCol>
</GridRow>
</TitleTemplate>
<ColumnDefinitions>
<PropertyColumn Property="c=>c.Id" />
<PropertyColumn Title="用户名" Property="c=>c.UserName" />
<PropertyColumn Title="应用名称" Property="c=>c.AppName" />
<PropertyColumn Title="发送/接收" Property="c=>c.SendReveice" />
<ActionColumn Title="消息内容" Width="30%">
<Space>
<SpaceItem>
@((MarkupString)(context.Context))
</SpaceItem>
</Space>
</ActionColumn>
<PropertyColumn Title="时间" Property="c=>c.CreateTime" Format="yyyy-MM-dd HH:mm:ss" />
</ColumnDefinitions>
</Table>
@using System.Text.Json;
@code {
[Inject] IChats_Repositories _chats_Repositories { get; set; }
@@ -47,6 +55,7 @@
int _pageIndex = 1;
int _pageSize = 10;
int _total = 0;
string searchString;
protected override async Task OnInitializedAsync()
{
@@ -60,21 +69,47 @@
private async Task InitData()
{
_total = _chats_Repositories.Count(p => true);
var chatList = _chats_Repositories.GetDB().Queryable<Chats, Apps>((c, a) => new object[] {
if (string.IsNullOrEmpty(searchString))
{
_total = _chats_Repositories.Count(p => true);
var chatList = _chats_Repositories.GetDB().Queryable<Chats, Apps>((c, a) => new object[] {
SqlSugar.JoinType.Left,c.AppId==a.Id
}).Select((c, a) => new ChatsDto
{
Id = c.Id,
UserName = c.UserName,
AppId = c.AppId,
IsSend = c.IsSend,
SendReveice = c.IsSend ? "发送" : "接收",
Context = c.Context,
CreateTime = c.CreateTime,
AppName = a.Name
}).ToPageList(_pageIndex, _pageSize);
chatDtoList = chatList.ToArray();
}
else
{
_total = _chats_Repositories.Count(p => p.UserName.Contains(searchString) || p.Context.Contains(searchString));
var chatList = _chats_Repositories.GetDB().Queryable<Chats, Apps>((c, a) => new object[] {
SqlSugar.JoinType.Left,c.AppId==a.Id
}).Select((c, a) => new ChatsDto
{
Id = c.Id,
UserName = c.UserName,
AppId = c.AppId,
IsSend = c.IsSend,
SendReveice = c.IsSend ? "发送" : "接收",
Context = c.Context,
CreateTime = c.CreateTime,
AppName = a.Name
}).Where(c => c.UserName.Contains(searchString) || c.Context.Contains(searchString)).ToPageList(_pageIndex, _pageSize);
chatDtoList = chatList.ToArray();
}
}
private async Task Search(string searchKey)
{
Id = c.Id,
UserName = c.UserName,
AppId = c.AppId,
IsSend = c.IsSend,
SendReveice = c.IsSend ? "发送" : "接收",
Context=c.Context,
CreateTime = c.CreateTime,
AppName = a.Name
}).ToPageList(_pageIndex, _pageSize);
chatDtoList = chatList.ToArray();
await InitData();
}
public class ChatsDto : Chats

View File

@@ -72,6 +72,7 @@
}
kmsDetails_Repositories.GetDB().Ado.ExecuteCommand("DROP TABLE IF EXISTS \"km-kms\"");
kmsDetails_Repositories.GetDB().Ado.ExecuteCommand("DROP TABLE IF EXISTS \"km-file\"");
var kmsDetails = await kmsDetails_Repositories.GetListAsync();
foreach (var detail in kmsDetails)
{

View File

@@ -68,17 +68,17 @@ builder.Services.AddBackgroundTaskBroker().AddHandler<ImportKMSTaskReq, BackGrou
.WithCuda(false)
.WithLogCallback((level, message) => {
Console.WriteLine($"[llama {level}]: {message.TrimEnd('\n')}");
});
});
}
else if (LLamaSharpOption.RunType.ToUpper() == "GPU")
{
NativeLibraryConfig
.Instance
.WithCuda(true)
.WithAvx(NativeLibraryConfig.AvxLevel.Avx)
.WithLogCallback((level, message) => {
Console.WriteLine($"[llama {level}]: {message.TrimEnd('\n')}");
});
})
.WithAvx(NativeLibraryConfig.AvxLevel.Avx);
}
}

View File

@@ -41,13 +41,14 @@ namespace AntSK.Services.OpenApi
{
case "chat":
//普通会话
history.AddUserMessage(questions);
if (model.stream)
{
OpenAIStreamResult result1 = new OpenAIStreamResult();
result1.created = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result1.choices = new List<StreamChoicesModel>()
{ new StreamChoicesModel() { delta = new OpenAIMessage() { role = "assistant" } } };
await SendChatStream(HttpContext, result1, app, questions,history);
await SendChatStream(HttpContext, result1, app,history);
HttpContext.Response.ContentType = "application/json";
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(result1));
await HttpContext.Response.CompleteAsync();
@@ -59,14 +60,12 @@ namespace AntSK.Services.OpenApi
result2.created = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result2.choices = new List<ChoicesModel>()
{ new ChoicesModel() { message = new OpenAIMessage() { role = "assistant" } } };
result2.choices[0].message.content = await SendChat(questions,history, app);
result2.choices[0].message.content = await SendChat(history, app);
HttpContext.Response.ContentType = "application/json";
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(result2));
await HttpContext.Response.CompleteAsync();
}
break;
case "kms":
//知识库问答
if (model.stream)
@@ -91,16 +90,15 @@ namespace AntSK.Services.OpenApi
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(result4));
await HttpContext.Response.CompleteAsync();
}
break;
}
}
}
private async Task SendChatStream(HttpContext HttpContext, OpenAIStreamResult result, Apps app,string questions, ChatHistory history)
private async Task SendChatStream(HttpContext HttpContext, OpenAIStreamResult result, Apps app, ChatHistory history)
{
HttpContext.Response.Headers.Add("Content-Type", "text/event-stream");
var chatResult = _chatService.SendChatByAppAsync(app, questions, history);
var chatResult = _chatService.SendChatByAppAsync(app, history);
await foreach (var content in chatResult)
{
result.choices[0].delta.content = content.ConvertToString();
@@ -113,7 +111,6 @@ namespace AntSK.Services.OpenApi
await HttpContext.Response.WriteAsync("data: [DONE]");
await HttpContext.Response.Body.FlushAsync();
await HttpContext.Response.CompleteAsync();
}
@@ -124,49 +121,48 @@ namespace AntSK.Services.OpenApi
/// <param name="history"></param>
/// <param name="app"></param>
/// <returns></returns>
private async Task<string> SendChat(string questions, ChatHistory history, Apps app)
private async Task<string> SendChat(ChatHistory history, Apps app)
{
string result = "";
if (string.IsNullOrEmpty(app.Prompt) || !app.Prompt.Contains("{{$input}}"))
{
//如果模板为空,给默认提示词
app.Prompt = app.Prompt.ConvertToString() + "{{$input}}";
}
KernelArguments args = new KernelArguments();
if (history.Count > 10)
{
app.Prompt = @"${{ConversationSummaryPlugin.SummarizeConversation $history}}" + app.Prompt;
args = new() {
{ "history", string.Join("\n", history.Select(x => x.Role + ": " + x.Content)) },
{ "input", questions }
};
}
else
{
args = new()
{
{ "input", $"{string.Join("\n", history.Select(x => x.Role + ": " + x.Content))}{Environment.NewLine} user:{questions}" }
};
}
var _kernel = _kernelService.GetKernelByApp(app);
var temperature = app.Temperature / 100; //存的是0~100需要缩小
var chat = _kernel.GetRequiredService<IChatCompletionService>();
var temperature = app.Temperature / 100;//存的是0~100需要缩小
OpenAIPromptExecutionSettings settings = new() { Temperature = temperature };
List<string> completionList = new List<string>();
if (!string.IsNullOrEmpty(app.ApiFunctionList) || !string.IsNullOrEmpty(app.NativeFunctionList))//这里还需要加上本地插件的
{
_kernelService.ImportFunctionsByApp(app, _kernel);
settings.ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions;
}
var func = _kernel.CreateFunctionFromPrompt(app.Prompt, settings);
var chatResult =await _kernel.InvokeAsync(function: func, arguments: args);
if (chatResult.IsNotNull())
{
string answers = chatResult.GetValue<string>();
result = answers;
}
settings.ToolCallBehavior = ToolCallBehavior.EnableKernelFunctions;
while (true)
{
ChatMessageContent result = await chat.GetChatMessageContentAsync(history, settings, _kernel);
if (result.Content is not null)
{
string chunkCompletion = result.Content.ConvertToString();
completionList.Add(chunkCompletion);
return chunkCompletion;
}
history.Add(result);
IEnumerable<FunctionCallContent> functionCalls = FunctionCallContent.GetFunctionCalls(result);
if (!functionCalls.Any())
{
break;
}
return result;
foreach (var functionCall in functionCalls)
{
FunctionResultContent resultContent = await functionCall.InvokeAsync(_kernel);
history.Add(resultContent.ToChatMessage());
}
}
}
else
{
ChatMessageContent result = await chat.GetChatMessageContentAsync(history, settings, _kernel);
return result.Content.ConvertToString();
}
return "";
}
private async Task SendKmsStream(HttpContext HttpContext, OpenAIStreamResult result, Apps app, string questions,ChatHistory history)

View File

@@ -78,8 +78,14 @@
},
{
"path": "http://antsk.cn/",
"name": "文档",
"name": "使用文档",
"key": "antskdoc",
"icon": "question-circle"
},
{
"path": "https://www.bilibili.com/video/BV1vK421b7NF",
"name": "教程视频",
"key": "antskvideo",
"icon": "video-camera"
}
]

View File

@@ -0,0 +1,55 @@
using AntSK.LLM.SparkDesk;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel;
using Sdcb.SparkDesk;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Text.Unicode;
using System.Threading.Tasks;
namespace AntSK.LLM.Mock
{
public class MockChatCompletion : IChatCompletionService
{
private readonly Dictionary<string, object?> _attributes = new();
private readonly SparkDeskClient _client;
private string _chatId;
private readonly SparkDeskOptions _options;
private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
};
public IReadOnlyDictionary<string, object?> Attributes => _attributes;
public MockChatCompletion()
{
}
public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
StringBuilder sb = new();
string result = $"这是一条Mock数据便于聊天测试你的消息是{chatHistory.LastOrDefault().ToString()}";
return [new(AuthorRole.Assistant, result.ToString())];
}
public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
StringBuilder sb = new();
string result = $"这是一条Mock数据便于聊天测试你的消息是{chatHistory.LastOrDefault().ToString()}";
foreach (var c in result)
{
yield return new StreamingChatMessageContent(AuthorRole.Assistant, c.ToString());
}
}
}
}

View File

@@ -0,0 +1,231 @@
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel;
using Sdcb.SparkDesk;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Text.Unicode;
using System.Threading.Tasks;
namespace AntSK.LLM.SparkDesk
{
public class SparkDeskChatCompletion : IChatCompletionService
{
private readonly Dictionary<string, object?> _attributes = new();
private readonly SparkDeskClient _client;
private string _chatId;
private readonly SparkDeskOptions _options;
private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
};
public IReadOnlyDictionary<string, object?> Attributes => _attributes;
public SparkDeskChatCompletion(SparkDeskOptions options, string chatId)
{
_options = options;
_chatId = chatId;
_client = new(options.AppId, options.ApiKey, options.ApiSecret);
}
public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
{
StringBuilder sb = new();
var parameters = new ChatRequestParameters
{
ChatId = _chatId,
};
OpenAIPromptExecutionSettings chatExecutionSettings = OpenAIPromptExecutionSettings.FromExecutionSettings(executionSettings);
parameters.Temperature = (float)chatExecutionSettings.Temperature;
parameters.MaxTokens = chatExecutionSettings.MaxTokens ?? parameters.MaxTokens;
IList<KernelFunctionMetadata> functions = kernel?.Plugins.GetFunctionsMetadata().Where(x => x.PluginName == "AntSkFunctions").ToList() ?? [];
var functionDefs = functions.Select(func => new FunctionDef(func.Name, func.Description, func.Parameters.Select(p => new FunctionParametersDef(p.Name, p.ParameterType?.IsClass == true ? "object" : "string", p.Description, p.IsRequired)).ToList())).ToList();
List<ChatMessage> messages = GetSparkMessage(chatHistory);
var result = await _client.ChatAsync(_options.ModelVersion, messages.ToArray(), parameters, functionDefs.Count > 0 ? [.. functionDefs] : null, cancellationToken: cancellationToken);
if (result.FunctionCall != null)
{
var func = functions.Where(x => x.Name == result.FunctionCall.Name).FirstOrDefault();
if (func == null)
{
return new List<ChatMessageContent> { new(AuthorRole.Assistant, $"插件{result.FunctionCall.Name}未注册") }.AsReadOnly();
}
if (kernel.Plugins.TryGetFunction(func.PluginName, func.Name, out var function))
{
var arguments = new KernelArguments();
var JsonElement = JsonDocument.Parse(result.FunctionCall.Arguments).RootElement;
foreach (var parameter in func.Parameters)
{
var error = "";
try
{
if (JsonElement.TryGetProperty(parameter.Name, out var property))
{
arguments.Add(parameter.Name, property.Deserialize(parameter.ParameterType!, _jsonSerializerOptions));
}
}
catch (Exception ex)
{
error = $"参数{parameter.Name}解析错误:{ex.Message}";
}
if (!string.IsNullOrEmpty(error))
{
return new List<ChatMessageContent> { new(AuthorRole.Assistant, error) }.AsReadOnly();
}
}
var functionResult = await function.InvokeAsync(kernel, arguments, cancellationToken);
messages = [ ChatMessage.FromUser(messages.LastOrDefault().Content),
ChatMessage.FromSystem($@"
执行函数调用成功
函数描述:{func.Description}
函数执行结果:{functionResult}
"),
ChatMessage.FromUser("请根据函数调用结果回答我的问题,不要超出函数调用结果的返回,以及不要有多余描述:")];
var callResult = await _client.ChatAsync(_options.ModelVersion, messages.ToArray(), parameters, null);
ChatMessageContent chatMessageContent = new(AuthorRole.Assistant, callResult.Text.ToString(), modelId: "SparkDesk");
return new List<ChatMessageContent> { chatMessageContent }.AsReadOnly();
}
return new List<ChatMessageContent> { new(AuthorRole.Assistant, "未找到插件") }.AsReadOnly();
}
else
{
ChatMessageContent chatMessageContent = new(AuthorRole.Assistant, result.Text.ToString(), modelId: "SparkDesk");
return new List<ChatMessageContent> { chatMessageContent }.AsReadOnly();
}
}
public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var parameters = new ChatRequestParameters
{
ChatId = _chatId,
};
OpenAIPromptExecutionSettings chatExecutionSettings = OpenAIPromptExecutionSettings.FromExecutionSettings(executionSettings);
parameters.Temperature = (float)chatExecutionSettings.Temperature;
parameters.MaxTokens = chatExecutionSettings.MaxTokens ?? parameters.MaxTokens;
IList<KernelFunctionMetadata> functions = kernel?.Plugins.GetFunctionsMetadata().Where(x => x.PluginName == "AntSkFunctions").ToList() ?? [];
var functionDefs = functions.Select(func => new FunctionDef(func.Name, func.Description, func.Parameters.Select(p => new FunctionParametersDef(p.Name, p.ParameterType?.IsClass == true ? "object" : "string", p.Description, p.IsRequired)).ToList())).ToList();
List<ChatMessage> messages = GetSparkMessage(chatHistory);
await foreach (StreamedChatResponse msg in _client.ChatAsStreamAsync(_options.ModelVersion, messages.ToArray(), parameters, functionDefs.Count > 0 ? [.. functionDefs] : null, cancellationToken: cancellationToken))
{
yield return new StreamingChatMessageContent(AuthorRole.Assistant, msg);
};
}
private static List<ChatMessage> GetSparkMessage(ChatHistory chatHistory)
{
List<ChatMessage> messages = new List<ChatMessage>();
foreach (var msg in chatHistory.ToList())
{
string role = "";
if (msg.Role == AuthorRole.User)
{
role = "user";
}
else if (msg.Role == AuthorRole.System)
{
role = "system";
}
else
{
role = "assistant";
}
messages.Add(new ChatMessage(role, msg.ToString()));
}
return messages;
}
private static string? ProcessFunctionResult(object functionResult, ToolCallBehavior? toolCallBehavior)
{
if (functionResult is string stringResult)
{
return stringResult;
}
if (functionResult is ChatMessageContent chatMessageContent)
{
return chatMessageContent.ToString();
}
return JsonSerializer.Serialize(functionResult, _jsonSerializerOptions);
}
public static Dictionary<string, object> ParseJsonElement(JsonElement element, string propertyName)
{
Dictionary<string, object> dict = new();
switch (element.ValueKind)
{
case JsonValueKind.Object:
foreach (JsonProperty property in element.EnumerateObject())
{
dict.Add(property.Name, ParseJsonElement(property.Value, property.Name));
}
break;
case JsonValueKind.Array:
List<object> list = new List<object>();
foreach (JsonElement arrayElement in element.EnumerateArray())
{
list.Add(ParseJsonElement(arrayElement, ""));
}
dict.Add(propertyName, list);
break;
case JsonValueKind.String:
dict.Add(propertyName, element.GetString());
break;
case JsonValueKind.Number:
dict.Add(propertyName, element.GetInt32());
break;
case JsonValueKind.True:
case JsonValueKind.False:
dict.Add(propertyName, element.GetBoolean());
break;
default:
dict.Add(propertyName, "Unsupported value type");
break;
}
return dict;
}
}
}