Compare commits
379 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e508e45af | ||
|
|
03d9ec2cad | ||
|
|
86fb48bab7 | ||
|
|
a4bc1e4a55 | ||
|
|
8681e15da5 | ||
|
|
ebc82f8b1b | ||
|
|
3bcd7bd7e1 | ||
|
|
b64d8669b1 | ||
|
|
0489044098 | ||
|
|
17e2062b72 | ||
|
|
4e4f5a698d | ||
|
|
b879d04bcd | ||
|
|
95f918f4c7 | ||
|
|
f0e1ad6088 | ||
|
|
61773af48d | ||
|
|
54cd04c3bf | ||
|
|
cd9f4ae11b | ||
|
|
3f9c748b41 | ||
|
|
d483005531 | ||
|
|
1d2db6a896 | ||
|
|
9a7a263055 | ||
|
|
6beb0b52c7 | ||
|
|
0ea167a204 | ||
|
|
6e6afa2a7c | ||
|
|
7a2a5d86bb | ||
|
|
a1a36c3494 | ||
|
|
4f350081dd | ||
|
|
b3ea0c4e1a | ||
|
|
e72a6acd03 | ||
|
|
9bb8ab89fe | ||
|
|
e78da66d1a | ||
|
|
9ee21fd5e5 | ||
|
|
a22c04c9b2 | ||
|
|
3bb5bfaca7 | ||
|
|
c4bf5ee7e5 | ||
|
|
5e1e688f84 | ||
|
|
80d9bf68f3 | ||
|
|
65f2e3e363 | ||
|
|
68d27ff2bc | ||
|
|
034da30811 | ||
|
|
3db0cdcd19 | ||
|
|
42181a6f1d | ||
|
|
ec8cbf2550 | ||
|
|
9a1bd079da | ||
|
|
4213c4379c | ||
|
|
05cda17e2e | ||
|
|
cda6e54f0b | ||
|
|
51d8ba6408 | ||
|
|
b571c7d22d | ||
|
|
a0c91f565e | ||
|
|
280c750165 | ||
|
|
fec9337fda | ||
|
|
b84f252f2f | ||
|
|
5c998ccce2 | ||
|
|
0e3cfd2cfb | ||
|
|
4040831a23 | ||
|
|
a3a2308659 | ||
|
|
6d43c71d13 | ||
|
|
8315b6f37f | ||
|
|
7bc708e6ae | ||
|
|
e6f2c5c2fe | ||
|
|
5cab781362 | ||
|
|
02d7994bae | ||
|
|
b740957157 | ||
|
|
2480ec1272 | ||
|
|
35c98a0d14 | ||
|
|
0964a5ad5b | ||
|
|
a95131efe9 | ||
|
|
7783cdf3c4 | ||
|
|
7a65f33cb6 | ||
|
|
6efd01db3f | ||
|
|
1e2322b573 | ||
|
|
2cb2241a66 | ||
|
|
64efdd7881 | ||
|
|
be28e32803 | ||
|
|
468422baee | ||
|
|
7b1c6c8c64 | ||
|
|
7ff0ea0bfe | ||
|
|
6bed4356f0 | ||
|
|
73b65f7305 | ||
|
|
0ea52eced9 | ||
|
|
498e9ba9f6 | ||
|
|
125695665b | ||
|
|
0e08b3ae85 | ||
|
|
7cb8f99e7e | ||
|
|
d15cb527d0 | ||
|
|
9cb36174fd | ||
|
|
6265f94ef2 | ||
|
|
09d90b654c | ||
|
|
64e2bca2e6 | ||
|
|
328ece6d73 | ||
|
|
fabb8c2044 | ||
|
|
6ca75df880 | ||
|
|
3d4dfaced1 | ||
|
|
d532bf3bb6 | ||
|
|
e1fd288875 | ||
|
|
91eae9cfa8 | ||
|
|
b0059942d3 | ||
|
|
a716982878 | ||
|
|
3d4e48f9f5 | ||
|
|
1f212d3156 | ||
|
|
7d91ef6ba1 | ||
|
|
2a450b00de | ||
|
|
3a97068248 | ||
|
|
1d9d95899a | ||
|
|
7ae8e52b57 | ||
|
|
f5c195a1d0 | ||
|
|
78a6b662d3 | ||
|
|
5f814eb76c | ||
|
|
d9e5ebb464 | ||
|
|
bce0e9183c | ||
|
|
c40a7bcf22 | ||
|
|
97a7d447ab | ||
|
|
f803b9538b | ||
|
|
1ac34c1702 | ||
|
|
e07b480da1 | ||
|
|
9036af57e3 | ||
|
|
93288f9b5c | ||
|
|
f40dd8b013 | ||
|
|
c6b83d0695 | ||
|
|
592c850198 | ||
|
|
4a3930ac7b | ||
|
|
c05ba0af3e | ||
|
|
630ee51df6 | ||
|
|
d0e75e26c3 | ||
|
|
62c36c3072 | ||
|
|
baef309064 | ||
|
|
d717cbad9c | ||
|
|
5ef0624605 | ||
|
|
af2930a371 | ||
|
|
98f0f9fe84 | ||
|
|
28a23271e9 | ||
|
|
f1ba0bdf10 | ||
|
|
0d5513f374 | ||
|
|
4812cc308c | ||
|
|
584f7faded | ||
|
|
08dcef2d8b | ||
|
|
68218733a2 | ||
|
|
eb64cbf3d4 | ||
|
|
f0e8a55522 | ||
|
|
5ec5a0bde4 | ||
|
|
1cc56dd553 | ||
|
|
64e949a88b | ||
|
|
a2390a7c97 | ||
|
|
559661bb6c | ||
|
|
79326de263 | ||
|
|
3815891b28 | ||
|
|
42d474382a | ||
|
|
fe691f2d44 | ||
|
|
3ee41a8ab1 | ||
|
|
7ca41dff8a | ||
|
|
ba2e86993e | ||
|
|
13878046a2 | ||
|
|
49ff8bf54f | ||
|
|
e9cc5a3993 | ||
|
|
b213964b63 | ||
|
|
bfbed44270 | ||
|
|
9b07d88392 | ||
|
|
3f8ed109f9 | ||
|
|
3f969627a4 | ||
|
|
d92970819a | ||
|
|
23e756fa9b | ||
|
|
5f58126fbf | ||
|
|
dcfd0ffb8f | ||
|
|
17221d056c | ||
|
|
4a9dcfada4 | ||
|
|
bb6c2bb020 | ||
|
|
a8760a34de | ||
|
|
1e432a5782 | ||
|
|
cb861ef2bb | ||
|
|
7cee8fd87a | ||
|
|
8ce0e5d348 | ||
|
|
90bce7c89f | ||
|
|
b840d0bcce | ||
|
|
bfa6d28289 | ||
|
|
f6e6ca9747 | ||
|
|
75f8d39648 | ||
|
|
9a939eba5a | ||
|
|
4e93efe821 | ||
|
|
8bdbee80a0 | ||
|
|
6bdf5dcc03 | ||
|
|
0bf0a9d78a | ||
|
|
38e9fea601 | ||
|
|
d2366b3b46 | ||
|
|
3aff93083a | ||
|
|
eb998199db | ||
|
|
1dd794af1b | ||
|
|
08c9923e7e | ||
|
|
06b109ca87 | ||
|
|
9b039335c7 | ||
|
|
041378e5fd | ||
|
|
6dc5ae10e3 | ||
|
|
5807f4c283 | ||
|
|
8ef4445908 | ||
|
|
8a0609e970 | ||
|
|
9f33b5009b | ||
|
|
50e66db8a1 | ||
|
|
c3e83b569a | ||
|
|
85d1c5ea7e | ||
|
|
ec1d126a02 | ||
|
|
e857695e70 | ||
|
|
fa9b2051fe | ||
|
|
d450efcffe | ||
|
|
2a6c84c200 | ||
|
|
138a952ace | ||
|
|
eb6528ecd2 | ||
|
|
2c30bbfa09 | ||
|
|
c5a78c2135 | ||
|
|
f03362ee41 | ||
|
|
fad3167d97 | ||
|
|
ad949681dd | ||
|
|
27999d76b0 | ||
|
|
83278352d6 | ||
|
|
fcc56f5fef | ||
|
|
4ebe2ecc32 | ||
|
|
e684cba527 | ||
|
|
888dc19ee0 | ||
|
|
731aea702f | ||
|
|
09e22bc76a | ||
|
|
74406d88a0 | ||
|
|
e5f9d97560 | ||
|
|
59e768aaea | ||
|
|
6a7cb24a5b | ||
|
|
1db40d534c | ||
|
|
11d6e30f7e | ||
|
|
9d5214aaae | ||
|
|
010b906271 | ||
|
|
16bf944edf | ||
|
|
5bae5a099a | ||
|
|
f771ea9521 | ||
|
|
994efbf37c | ||
|
|
938cd86c88 | ||
|
|
1339cbadbc | ||
|
|
bd0ad570ad | ||
|
|
234e649a7e | ||
|
|
c431dbc842 | ||
|
|
76283060d9 | ||
|
|
75ba506db4 | ||
|
|
e086ca60df | ||
|
|
04acaa9b12 | ||
|
|
7a824bf18c | ||
|
|
769de2e526 | ||
|
|
c9950609c9 | ||
|
|
ccee6cfea5 | ||
|
|
f626c618be | ||
|
|
3b601a9e3d | ||
|
|
3d5f63d595 | ||
|
|
6933f2f495 | ||
|
|
79c7e8626a | ||
|
|
4a017d311c | ||
|
|
0c8ad5fe8d | ||
|
|
68ce0db011 | ||
|
|
c36de1a1e9 | ||
|
|
44ef759abd | ||
|
|
0c3d9844be | ||
|
|
854c62a4ca | ||
|
|
5ed4fd5299 | ||
|
|
af5ec43571 | ||
|
|
24d685879e | ||
|
|
e801a2ec46 | ||
|
|
d7b56d1590 | ||
|
|
b925f8890b | ||
|
|
5d80ee994a | ||
|
|
da8f955ca2 | ||
|
|
2e04582c5e | ||
|
|
e69994f727 | ||
|
|
d8dc26127d | ||
|
|
f73bd2dfda | ||
|
|
9f08b60348 | ||
|
|
75c2f36b30 | ||
|
|
39c02a6064 | ||
|
|
52c119befd | ||
|
|
23903ded3f | ||
|
|
4799fbac72 | ||
|
|
16a7d55271 | ||
|
|
be0bafcc50 | ||
|
|
defc51a074 | ||
|
|
09709c210d | ||
|
|
8ebb2f54eb | ||
|
|
b831aab115 | ||
|
|
8e322162cc | ||
|
|
6a8a6509b8 | ||
|
|
707dff09f8 | ||
|
|
17c8fca40f | ||
|
|
415f9757e9 | ||
|
|
27394f0699 | ||
|
|
8a9ca40bb6 | ||
|
|
f340ee1088 | ||
|
|
080eb5765e | ||
|
|
36c8ff184a | ||
|
|
0486f67b50 | ||
|
|
aa7e8d545c | ||
|
|
59f6a899a6 | ||
|
|
0fc98d42aa | ||
|
|
edad2644aa | ||
|
|
8a56a0393a | ||
|
|
f4cbf9a40a | ||
|
|
fb5b92f499 | ||
|
|
c286258f2b | ||
|
|
4416651589 | ||
|
|
48a33e8977 | ||
|
|
bd5ca06d8f | ||
|
|
e0985ecec3 | ||
|
|
e56b74d4af | ||
|
|
c417098c2c | ||
|
|
93527215a7 | ||
|
|
0cf3945693 | ||
|
|
ced2a9b2e2 | ||
|
|
987b231c4d | ||
|
|
7a541c1da1 | ||
|
|
74e323158d | ||
|
|
563a7409f6 | ||
|
|
b13b93e04e | ||
|
|
44568c8d65 | ||
|
|
fb277dff80 | ||
|
|
efae890650 | ||
|
|
a146f6059e | ||
|
|
3c67096cd8 | ||
|
|
a993a60f95 | ||
|
|
d3fdc77600 | ||
|
|
b62c56e36f | ||
|
|
7d72911239 | ||
|
|
9e24d7cc67 | ||
|
|
9baa24b496 | ||
|
|
da826525f7 | ||
|
|
62dfab41fd | ||
|
|
04fc811c2c | ||
|
|
8638ecbe29 | ||
|
|
6f1f93fbaf | ||
|
|
dc38d83f89 | ||
|
|
fd780780c5 | ||
|
|
6fd918f33b | ||
|
|
8fcfa8974b | ||
|
|
7e23c32c6c | ||
|
|
7fdfceeea5 | ||
|
|
bf0e62634f | ||
|
|
469ef9aab2 | ||
|
|
f7df26030d | ||
|
|
f61cbe9780 | ||
|
|
56b62cff2a | ||
|
|
0aec21cf03 | ||
|
|
ff4f6be5fc | ||
|
|
964a5022c8 | ||
|
|
849b18f677 | ||
|
|
b8c6a6a626 | ||
|
|
57fc9a9b7e | ||
|
|
068c126a23 | ||
|
|
0ed1662c7b | ||
|
|
a09377814f | ||
|
|
3cc952bb2a | ||
|
|
63c968742b | ||
|
|
a4d6f2a6fd | ||
|
|
d6de64853d | ||
|
|
344128e49d | ||
|
|
56fc9dd517 | ||
|
|
c7c1911eb1 | ||
|
|
dec8b5bef7 | ||
|
|
db7271b519 | ||
|
|
fcbee1f64f | ||
|
|
2d9443a0a1 | ||
|
|
6e3dd00d6f | ||
|
|
7a0656cd81 | ||
|
|
3b89d9e974 | ||
|
|
cd174308cf | ||
|
|
aacef47626 | ||
|
|
fcef01a41f | ||
|
|
7d19b694fa | ||
|
|
966a31b156 | ||
|
|
7538393742 | ||
|
|
3658188be2 | ||
|
|
fcd9fb9079 | ||
|
|
d1168e16d6 | ||
|
|
25a6f00dd2 | ||
|
|
13c474f084 | ||
|
|
8d78270007 | ||
|
|
a94b59c156 | ||
|
|
ae6d61ee6d | ||
|
|
74a7c94619 | ||
|
|
bdd34ac786 |
4
.gitignore
vendored
@@ -324,10 +324,6 @@ ASALocalRun/
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
*.dll
|
||||
*.pdb
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
**/bin/
|
||||
|
||||
@@ -22,4 +22,5 @@ WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
RUN echo 'Asia/Shanghai' >/etc/timezone
|
||||
RUN apt update && apt install -y libpugixml-dev libtbb-dev
|
||||
ENTRYPOINT ["dotnet", "AntSK.dll"]
|
||||
|
||||
29
Dockerfile-py
Normal file
@@ -0,0 +1,29 @@
|
||||
# 1. Define the Python image to use for getting pip
|
||||
FROM pytorch/pytorch AS python-base
|
||||
|
||||
# 2. Define the .NET SDK image to build your application
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/AntSK/AntSK.csproj", "AntSK/"]
|
||||
RUN dotnet restore "AntSK/AntSK.csproj"
|
||||
COPY src/ .
|
||||
WORKDIR "/src/AntSK"
|
||||
RUN dotnet build "AntSK.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish "AntSK.csproj" -c Release -o /app/publish
|
||||
|
||||
# 3. Define the final image that will contain both .NET runtime and Python
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
|
||||
|
||||
# Copy the Python/pip installation from the official Python image
|
||||
COPY --from=python-base /usr/local /usr/local
|
||||
COPY --from=python-base /opt/conda/ /opt/conda/
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
# Make sure the app and Python directories are in PATH
|
||||
ENV PATH="/app:/opt/conda/bin:/usr/local/bin:${PATH}"
|
||||
|
||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
RUN echo 'Asia/Shanghai' >/etc/timezone
|
||||
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
RUN apt update && apt install -y libpugixml-dev libtbb-dev
|
||||
ENTRYPOINT ["dotnet", "AntSK.dll"]
|
||||
2
LICENSE
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright [2024] [许泽宇]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
253
README.en.md
@@ -1,253 +0,0 @@
|
||||
[简体中文](./README.md) | English
|
||||
# AntSK
|
||||
|
||||
## An AI knowledge base/intelligent agent built with .Net 8+AntBlazor+SemanticKernel
|
||||
|
||||
|
||||
|
||||
## Core Features
|
||||
|
||||
|
||||
|
||||
- **Semantic Kernel**: Utilizes advanced natural language processing technologies to accurately understand, process, and respond to complex semantic queries, providing users with precise information retrieval and recommendation services.
|
||||
|
||||
|
||||
- **Kernel Memory**: Capable of continuous learning and knowledge storage, AntSK has a long-term memory function, accumulating experience to offer more personalized interaction experiences.
|
||||
|
||||
|
||||
- **Knowledge base**: Import knowledge into the database through documents (Word, PDF, Excel, Txt, Markdown, Json, PPT) and manage knowledge base documents.
|
||||
|
||||
|
||||
- **GPTs Generation**:The platform supports the creation of personalized GPT models, try building your own GPT model.
|
||||
|
||||
|
||||
- **API Interface Release**: Internal functions are provided as APIs for developers to integrate AntSK into other applications, enhancing application intelligence.
|
||||
|
||||
|
||||
- **API Plugin System**: An open API plugin system allows third-party developers or service providers to easily integrate their services into AntSK, continuously enhancing application functions.
|
||||
|
||||
|
||||
- **.Net Plugin System**: An open dll plugin system allows third-party developers or service providers to integrate their business functions into AntSK by generating dlls with the standard format codes, continuously enhancing application functions.
|
||||
|
||||
|
||||
- **Internet Search**: AntSK can retrieve the latest information in real-time, ensuring that the information users receive is always timely and relevant.
|
||||
|
||||
|
||||
- **Model management**: Adapts and manages different models from various manufacturers. It also supports offline running of models in 'gguf' format supported by llama.cpp.
|
||||
|
||||
|
||||
- **National Information Creation**: AntSK supports domestic models and databases, and can operate under information creation conditions.
|
||||
|
||||
|
||||
|
||||
## Application scenarios
|
||||
|
||||
|
||||
|
||||
AntSK is suitable for various business scenarios, such as:
|
||||
|
||||
- Corporate knowledge management systems
|
||||
|
||||
- Automated customer service and chatbots
|
||||
|
||||
- Enterprise Search Engine
|
||||
|
||||
- Personalized recommendation system
|
||||
|
||||
- Intelligent assisted writing
|
||||
|
||||
- Education and online learning platform
|
||||
|
||||
- Other interesting AI applications
|
||||
|
||||
|
||||
|
||||
## Function example
|
||||
|
||||
|
||||
|
||||
First, you need to create a knowledge base
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
In the knowledge base, you can use documents or urls to import
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
Click View to view the document slicing of the knowledge base
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
Then we need to create applications, which can create dialog applications and knowledge bases.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
The application of knowledge base needs to select the existing knowledge base, which can be multiple
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
Then you can ask questions about the knowledge base documents in the dialogue
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
In addition, we can also create dialogue applications, and configure prompt word templates in corresponding applications
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
Let's see the effect
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## How do I get started?
|
||||
|
||||
Here I am using Postgres as a data and vector store, because the Semantic Kernel and Kernel Memory both support it, though you can switch to others.
|
||||
|
||||
The model by default supports local models in 'gguf' format from openai, azure openai, and llama. If you need to use other models, you can integrate them using the one-api.
|
||||
|
||||
The login configuration in the configuration file is the default account and password.
|
||||
|
||||
The following configuration files are needed:
|
||||
|
||||
## Using Docker Compose
|
||||
A appsettings.json for the pg version and a simplified version (Sqlite+disk) docker-compose.simple.yml are provided.
|
||||
|
||||
Download docker-compose.yml from the project root directory, then place the configuration file appsettings.json in the same directory,
|
||||
|
||||
The pg image has already been prepared. You can modify the default account and password in the docker-compose.yml, and then your appsettings.json database connection needs to be consistent.
|
||||
|
||||
Then you can enter the directory and execute
|
||||
```
|
||||
docker compose up - d
|
||||
```
|
||||
to start AntSK.
|
||||
|
||||
How to mount local models and model download directories in docker
|
||||
|
||||
```
|
||||
# Non-host version, does not use local proxy
|
||||
version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.2.1
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
- antsk
|
||||
depends_on:
|
||||
- antskpg
|
||||
restart: always
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # local configuration file must be placed in the same directory
|
||||
- D://model:/app/model
|
||||
networks:
|
||||
antsk:
|
||||
|
||||
```
|
||||
Using this as an example, the meaning is to mount the local folder D://model from Windows into the container /app/model. If so, your appsettings.json model directory should be configured as
|
||||
|
||||
```
|
||||
model/xxx.gguf
|
||||
```
|
||||
Some meanings of the configuration file
|
||||
// (The rest of the information is omitted as it's unnecessary for the translation example context.)
|
||||
|
||||
Solving the missing style issue:
|
||||
Execute under AntSK/src/AntSK:
|
||||
```
|
||||
dotnet clean
|
||||
dotnet build
|
||||
dotnet publish "AntSK.csproj"
|
||||
```
|
||||
Then go to AntSK/src/AntSK/bin/Release/net8.0/publish
|
||||
```
|
||||
dotnet AntSK.dll
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
{
|
||||
"DBConnection": {
|
||||
"DbType": "Sqlite",
|
||||
"ConnectionStrings": "Data Source=AntSK.db;"
|
||||
},
|
||||
"KernelMemory": {
|
||||
"VectorDb": "Disk",
|
||||
"ConnectionString": "Host=;Port=;Database=antsk;Username=;Password=",
|
||||
"TableNamePrefix": "km-"
|
||||
},
|
||||
"LLamaSharp": {
|
||||
"RunType": "GPU",
|
||||
"Chat": "D:\\Code\\AI\\AntBlazor\\model\\qwen1_5-1_8b-chat-q8_0.gguf",
|
||||
"Embedding": "D:\\Code\\AI\\AntBlazor\\model\\qwen1_5-1_8b-chat-q8_0.gguf",
|
||||
"FileDirectory": "D:\\Code\\AI\\AntBlazor\\model\\"
|
||||
},
|
||||
"Login": {
|
||||
"User": "admin",
|
||||
"Password": "xuzeyu"
|
||||
},
|
||||
"BackgroundTaskBroker": {
|
||||
"ImportKMSTask": {
|
||||
"WorkerCount": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
//Supports multiple databases, including SqlSugar, MySql, SqlServer, Sqlite, Oracle, PostgreSQL, Dm, Kdbndp, Oscar, MySqlConnector, Access, OpenGaussian, QuestDB, HG, ClickHouse, GBase, Odbc, OceanBaseForOracle, TDengine, GaussDB, OceanBase, Tidb, Vastbase, PolarDB, Custom
|
||||
DBConnection DbType
|
||||
//Connection string, corresponding strings need to be used according to different DB types
|
||||
DBConnection ConnectionStrings
|
||||
//The type of vector storage supports Postgres Disk Memory, where Postgres requires the configuration of ConnectionString
|
||||
KernelMemory VectorDb
|
||||
//The running mode used by the local model is GUP CPU. If using an online API, you can freely use one
|
||||
LLamaSharp RunType
|
||||
//The model path of the local session model should pay attention to distinguishing between Linux and Windows drive letters
|
||||
LLamaSharp Chat
|
||||
//The model path of the local vector model should pay attention to distinguishing between Linux and Windows drive letters
|
||||
LLamaSharp Embedding
|
||||
//Default administrator account password
|
||||
Login
|
||||
//The number of threads for importing asynchronous processing can be higher when using online APIs. Local models suggest 1, otherwise memory overflow and crash may occur
|
||||
BackgroundTaskBroker ImportKMSTask WorkerCount
|
||||
|
||||
```
|
||||
|
||||
|
||||
To learn more or start using**AntSK**, you can follow my public account and join the exchange group.
|
||||
|
||||
|
||||
|
||||
## Contact me
|
||||
|
||||
If you have any questions or suggestions, please follow my public account through the following ways, and send a message to me. We also have an exchange group, which can send messages such as joining the group, and then I will bring you into the exchange group
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
We appreciate your interest in**AntSK**and look forward to working with you to create an intelligent future!
|
||||
201
README.md
@@ -1,101 +1,90 @@
|
||||
中文|[English](https://github.com/xuzeyu91/AntSK/blob/main/README.en.md)
|
||||
[简体中文](./README.zh.md) | English
|
||||
# AntSK
|
||||
## 基于.Net8+AntBlazor+SemanticKernel 打造的AI知识库/智能体
|
||||
## AI Knowledge Base/Intelligent Agent built on .Net8+AntBlazor+SemanticKernel
|
||||
|
||||
## 核心功能
|
||||
## ⭐Core Features
|
||||
|
||||
- **语义内核 (Semantic Kernel)**:采用领先的自然语言处理技术,准确理解、处理和响应复杂的语义查询,为用户提供精确的信息检索和推荐服务。
|
||||
- **Semantic Kernel**: Utilizes advanced natural language processing technology to accurately understand, process, and respond to complex semantic queries, providing users with precise information retrieval and recommendation services.
|
||||
|
||||
- **内存内核 (Kernel Memory)**:具备持续学习和存储知识点的能力,AntSK 拥有长期记忆功能,累积经验,提供更个性化的交互体验。
|
||||
- **Kernel Memory**: Capable of continuous learning and storing knowledge points, AntSK has long-term memory function, accumulates experience, and provides a more personalized interaction experience.
|
||||
|
||||
- **知识库**:通过文档(Word、PDF、Excel、Txt、Markdown、Json、PPT)等形式导入知识库,可以进行知识库文档。
|
||||
- **Knowledge Base**: Import knowledge base through documents (Word, PDF, Excel, Txt, Markdown, Json, PPT) and perform knowledge base Q&A.
|
||||
|
||||
- **GPTs 生成**:此平台支持创建个性化的GPT模型,尝试构建您自己的GPT模型。
|
||||
- **GPT Generation**: This platform supports creating personalized GPT models, enabling users to build their own GPT models.
|
||||
|
||||
- **API接口发布**:将内部功能以API的形式对外提供,便于开发者将AntSK 集成进其他应用,增强应用智慧。
|
||||
- **API Interface Publishing**: Exposes internal functions in the form of APIs, enabling developers to integrate AntSK into other applications and enhance application intelligence.
|
||||
|
||||
- **API插件系统**:开放式API插件系统,允许第三方开发者或服务商轻松将其服务集成到AntSK,不断增强应用功能。
|
||||
- **API Plugin System**: Open API plugin system that allows third-party developers or service providers to easily integrate their services into AntSK, continuously enhancing application functionality.
|
||||
|
||||
- **.Net插件系统**:开放式dll插件系统,允许第三方开发者或服务商轻松将其业务功能通过标准格式的代码生成dll后集成到AntSK,不断增强应用功能。
|
||||
- **.Net Plugin System**: Open dll plugin system that allows third-party developers or service providers to easily integrate their business functions by generating dll in standard format code, continuously enhancing application functionality.
|
||||
|
||||
- **联网搜索**:AntSK,实时获取最新信息,确保用户接受到的资料总是最及时、最相关的。
|
||||
- **Online Search**: AntSK, real-time access to the latest information, ensuring users receive the most timely and relevant data.
|
||||
|
||||
- **模型管理**:适配和管理集成不同厂商的不同模型。并且支持llama.cpp所支持的gguf类型的模型离线运行
|
||||
- **Model Management**: Adapts and manages integration of different models from different manufacturers, including gguf types supported by **llama.cpp** and models offline running supported by **llamafactory**.
|
||||
|
||||
- **国产信创**:AntSK支持国产模型,和国产数据库,可以在信创条件下运行
|
||||
- **Domestic Innovation**: AntSK supports domestic models and databases and can run under domestic innovation conditions.
|
||||
|
||||
## 应用场景
|
||||
- **Model Fine-Tuning**: Planned based on llamafactory for model fine-tuning.
|
||||
|
||||
AntSK 适用于多种业务场景,例如:
|
||||
- 企业级知识管理系统
|
||||
- 自动客服与聊天机器人
|
||||
- 企业级搜索引擎
|
||||
- 个性化推荐系统
|
||||
- 智能辅助写作
|
||||
- 教育与在线学习平台
|
||||
- 其他有意思的AI App
|
||||
## ⛪Application Scenarios
|
||||
|
||||
## 功能示例
|
||||
AntSK is suitable for various business scenarios, such as:
|
||||
- Enterprise knowledge management system
|
||||
- Automatic customer service and chatbots
|
||||
- Enterprise search engine
|
||||
- Personalized recommendation system
|
||||
- Intelligent writing assistance
|
||||
- Education and online learning platforms
|
||||
- Other interesting AI Apps
|
||||
|
||||
[视频示例](https://www.bilibili.com/video/BV1zH4y1h7Y9/)
|
||||
## ✏️Function Examples
|
||||
### Online Demo
|
||||
[document](http://antsk.cn/)
|
||||
|
||||
首先需要创建知识库
|
||||

|
||||
[demo](https://antsk.ai-dotnet.com/)
|
||||
|
||||
在知识库里可以使用文档或者url进行导入
|
||||

|
||||
```
|
||||
Default account: test
|
||||
|
||||
点击查看可以查看知识库的文档切片情况
|
||||

|
||||
Default password: test
|
||||
|
||||
然后我们需要创建应用,可以创建对话应用和知识库。
|
||||

|
||||
Due to the low configuration of the cloud server, the local model cannot be run, so the system settings permissions have been closed. You can simply view the interface. If you want to use the local model, please download and use it on your own.
|
||||
```
|
||||
|
||||
知识库应用需要选择已有的知识库,可以选多个
|
||||

|
||||
### Other Function Examples
|
||||
[Video Demonstration](https://www.bilibili.com/video/BV1zH4y1h7Y9/)
|
||||
|
||||
然后再对话中可以对知识库的文档进行提问
|
||||

|
||||
## ❓How to get started?
|
||||
|
||||
另外我们也可以创建对话应用,可以在对应应用中配置提示词模板
|
||||

|
||||
Here I am using Postgres as the data and vector storage because Semantic Kernel and Kernel Memory support it, but you can also use other options.
|
||||
|
||||
下面来看看效果吧
|
||||

|
||||
The model by default supports the local model of openai, azure openai, and llama. If you need to use other models, you can integrate them using one-api.
|
||||
|
||||
## 如何开始?
|
||||
The Login configuration in the configuration file is the default login account and password.
|
||||
|
||||
在这里我使用的是Postgres 作为数据存储和向量存储,因为Semantic Kernel和Kernel Memory都支持他,当然你也可以换成其他的。
|
||||
The following configuration file needs to be configured
|
||||
|
||||
模型默认支持openai、azure openai 和llama支持的gguf本地模型,如果需要使用其他模型,可以使用one-api进行集成。
|
||||
## 1️⃣Using docker-compose
|
||||
|
||||
配置文件中的Login配置是默认的登陆账号和密码
|
||||
Provided the pg version **appsettings.json** and simplified version (Sqlite+disk) **docker-compose.simple.yml**
|
||||
|
||||
需要配置如下的配置文件
|
||||
Download **docker-compose.yml** from the project root directory and place the configuration file **appsettings.json** in the same directory.
|
||||
|
||||
## 使用docker-compose
|
||||
The pg image has already been prepared. You can modify the default username and password in docker-compose.yml, and then the database connection in your **appsettings.json** needs to be consistent.
|
||||
|
||||
提供了pg版本 **appsettings.json** 和 简化版本(Sqlite+disk) **docker-compose.simple.yml**
|
||||
|
||||
从项目根目录下载**docker-compose.yml**,然后把配置文件**appsettings.json**和它放在统一目录,
|
||||
|
||||
这里已经把pg的镜像做好了。在docker-compose.yml中可以修改默认账号密码,然后你的**appsettings.json**的数据库连接需要保持一致。
|
||||
|
||||
然后你可以进入到目录后执行
|
||||
Then you can execute the following command in the directory to start AntSK
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
来启动AntSK
|
||||
|
||||
## 如何在docker中挂载本地模型,和模型下载的目录
|
||||
## 2️⃣How to mount local models and model download directory in docker
|
||||
```
|
||||
# 非 host 版本, 不使用本机代理
|
||||
# Non-host version, do not use local proxy
|
||||
version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.1.5
|
||||
ports:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.1.5ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
- antsk
|
||||
@@ -105,33 +94,35 @@ services:
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # 本地配置文件 需要放在同级目录
|
||||
- ./appsettings.json:/app/appsettings.json # Local configuration file needs to be placed in the same directory
|
||||
- D://model:/app/model
|
||||
networks:
|
||||
antsk:
|
||||
```
|
||||
以这个为示例,意思是把windows本地D://model的文件夹挂载进 容器内/app/model 如果是这样你的appsettings.json中的模型地址应该配置为
|
||||
Taking this as an example, it means mounting the local D://model folder of Windows into the container /app/model. If so, the model address in your appsettings.json should be configured as
|
||||
```
|
||||
model/xxx.gguf
|
||||
```
|
||||
|
||||
## 配置文件的一些含义
|
||||
## 3️⃣Some meanings of configuration file
|
||||
```
|
||||
{
|
||||
"DBConnection": {
|
||||
"DbType": "Sqlite",
|
||||
"DbType": "Sqlite",
|
||||
"ConnectionStrings": "Data Source=AntSK.db;"
|
||||
},
|
||||
"KernelMemory": {
|
||||
"VectorDb": "Disk",
|
||||
"VectorDb": "Disk",
|
||||
"ConnectionString": "Host=;Port=;Database=antsk;Username=;Password=",
|
||||
"TableNamePrefix": "km-"
|
||||
},
|
||||
"FileDir": {
|
||||
"DirectoryPath": "D:\\git\\AntBlazor\\model"
|
||||
},
|
||||
"LLamaSharp": {
|
||||
"RunType": "GPU",
|
||||
"Chat": "D:\\Code\\AI\\AntBlazor\\model\\qwen1_5-1_8b-chat-q8_0.gguf",
|
||||
"Embedding": "D:\\Code\\AI\\AntBlazor\\model\\qwen1_5-1_8b-chat-q8_0.gguf",
|
||||
"FileDirectory": "D:\\Code\\AI\\AntBlazor\\model\\"
|
||||
"RunType": "GPU",
|
||||
"ContextSize": 2048,
|
||||
"GpuLayerCount": 20
|
||||
},
|
||||
"Login": {
|
||||
"User": "admin",
|
||||
@@ -145,53 +136,85 @@ model/xxx.gguf
|
||||
}
|
||||
```
|
||||
```
|
||||
//支持多种数据库,具体可以查看SqlSugar,MySql,SqlServer,Sqlite,Oracle,PostgreSQL,Dm,Kdbndp,Oscar,MySqlConnector,Access,OpenGauss,QuestDB,HG,ClickHouse,GBase,Odbc,OceanBaseForOracle,TDengine,GaussDB,OceanBase,Tidb,Vastbase,PolarDB,Custom
|
||||
// Supports various databases, you can check SqlSugar, MySql, SqlServer, Sqlite, Oracle, PostgreSQL, Dm, Kdbndp, Oscar, MySqlConnector, Access, OpenGauss, QuestDB, HG, ClickHouse, GBase, Odbc, OceanBaseForOracle, TDengine, GaussDB, OceanBase, Tidb, Vastbase, PolarDB, Custom
|
||||
DBConnection.DbType
|
||||
//连接字符串,需要根据不同DB类型,用对应的字符串
|
||||
|
||||
// Connection string, need to use the corresponding string according to the different DB types
|
||||
DBConnection.ConnectionStrings
|
||||
|
||||
//向量存储的类型,支持 Postgres Disk Memory ,其中Postgres需要配置 ConnectionString
|
||||
//The type of vector storage, supporting Postgres, Disk, Memory, Qdrant, Redis, AzureAISearch
|
||||
//Postgres and Redis require ConnectionString configuration
|
||||
//The ConnectionString of Qdrant and AzureAISearch uses Endpoint | APIKey
|
||||
KernelMemory.VectorDb
|
||||
|
||||
//本地模型使用的运行方式 GUP CPU ,如果用在线API 这个随意使用一个即可
|
||||
//Local model execution options: GPU and CPU. When using the online API, any option can be used.
|
||||
LLamaSharp.RunType
|
||||
//本地会话模型的模型路径 注意区分linux和windows盘符不同
|
||||
LLamaSharp.Chat
|
||||
//本地向量模型的模型路径 注意区分linux和windows盘符不同
|
||||
LLamaSharp.Embedding
|
||||
//本地模型路径,用于在选择llama时可以快速选择目录下的模型,以及保存下载的模型
|
||||
|
||||
//Local model path, used for quick selection of models under llama, as well as saving downloaded models.
|
||||
LLamaSharp.FileDirectory
|
||||
|
||||
//默认管理员账号密码
|
||||
//Default admin account password
|
||||
Login
|
||||
//导入异步处理的线程数,使用在线API可以高一点,本地模型建议1 否则容易内存溢出崩掉
|
||||
|
||||
//Import asynchronous processing thread count. A higher count can be used for online API, but for local models, 1 is recommended to avoid memory overflow issues.
|
||||
BackgroundTaskBroker.ImportKMSTask.WorkerCount
|
||||
|
||||
```
|
||||
|
||||
## 找不到样式问题解决:
|
||||
AntSK/src/AntSK下执行:
|
||||
## ⚠️Fixing Style Issues:
|
||||
Run the following in AntSK/src/AntSK:
|
||||
```
|
||||
dotnet clean
|
||||
dotnet build
|
||||
dotnet publish "AntSK.csproj"
|
||||
```
|
||||
再去AntSK/src/AntSK/bin/Release/net8.0/publish下
|
||||
Then navigate to AntSK/src/AntSK/bin/Release/net8.0/publish and run:
|
||||
```
|
||||
dotnet AntSK.dll
|
||||
```
|
||||
然后启动就有样式了
|
||||
The styles should now be applied after starting.
|
||||
|
||||
DB我使用的是CodeFirst模式,只要配置好数据库链接,表结构是自动创建的
|
||||
I'm using CodeFirst mode for the database, so as long as the database connection is properly configured, the table structure will be created automatically.
|
||||
|
||||
## ✔️Using llamafactory
|
||||
```
|
||||
1. First, ensure that Python and pip are installed in your environment. This step is not necessary if using an image, such as version v0.2.3.2, which already includes the complete Python environment.
|
||||
2. Go to the model add page and select llamafactory.
|
||||
3. Click "Initialize" to check whether the 'pip install' environment setup is complete.
|
||||
4. Choose a model that you like.
|
||||
5. Click "Start" to begin downloading the model from the tower. This may involve a somewhat lengthy wait.
|
||||
6. After the model has finished downloading, enter http://localhost:8000/ in the request address. The default port is 8000.
|
||||
7. Click "Save" and start chatting.
|
||||
8. Many people ask about the difference between LLamaSharp and llamafactory. In fact, LLamaSharp is a .NET implementation of llama.cpp, but only supports local gguf models, while llamafactory supports a wider variety of models and uses Python implementation. The main difference lies here. Additionally, llamafactory has the ability to fine-tune models, which is an area we will focus on integrating in the future.
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
[PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
|
||||
|
||||
If you would like to contribute, feel free to create a [Pull Request](https://github.com/AIDotNet/AntSK/pulls), or give us [Bug Report](https://github.com/AIDotNet/AntSK/issues/new).
|
||||
|
||||
|
||||
## 💕 Contributors
|
||||
|
||||
想了解更多信息或开始使用 **AntSK**,可以关注我的公众号以及加入交流群。
|
||||
This project exists thanks to all the people who contribute.
|
||||
|
||||
## 联系我
|
||||
如有任何问题或建议,请通过以下方式关注我的公众号,发消息与我联系,我们也有交流群,可以发送进群等消息,然后我会拉你进交流群
|
||||

|
||||
<a href="https://github.com/AIDotNet/AntSK/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=AIDotNet/AntSK&max=1000&columns=15&anon=1" />
|
||||
</a>
|
||||
|
||||
## 🚨 Use Protocol
|
||||
This warehouse follows the [Apache-2.0 License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) open source protocol.
|
||||
The Apache open source license allows the use of AntSK in commercial environments, provided that the license terms are followed. One of the main terms is to retain the copyright and license statements.
|
||||
If you plan to use AntSK in commercial projects, you need to ensure that you follow the following steps:
|
||||
1. Copyright statement containing Apache license. [Apache-2.0 License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file).
|
||||
2. If you modify the software source code, you need to clearly indicate these modifications in the source code.
|
||||
|
||||
## ☎️Contact Me
|
||||
If you have any questions or suggestions, please contact me through my official WeChat account. We also have a discussion group where you can send a message to join, and then I will add you to the group.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
我们对您在**AntSK**的兴趣表示感谢,并期待与您携手共创智能化的未来!
|
||||
|
||||
We appreciate your interest in **AntSK** and look forward to collaborating with you to create an intelligent future!
|
||||
|
||||
238
README.zh.md
Normal file
@@ -0,0 +1,238 @@
|
||||
中文|[English](./README.md)
|
||||
# AntSK
|
||||
## 使用.Net8+Blazor+SemanticKernel 打造的AI知识库/智能体
|
||||
|
||||
## ⭐核心功能
|
||||
|
||||
- **语义内核 (Semantic Kernel)**:采用领先的自然语言处理技术,准确理解、处理和响应复杂的语义查询,为用户提供精确的信息检索和推荐服务。
|
||||
|
||||
- **内存内核 (Kernel Memory)**:具备持续学习和存储知识点的能力,AntSK 拥有长期记忆功能,累积经验,提供更个性化的交互体验。
|
||||
|
||||
- **知识库**:通过文档(Word、PDF、Excel、Txt、Markdown、Json、PPT)等形式导入知识库,可以进行知识库问答,支持本地bge-embedding 向量模型 ,以及bge-rerank 重排模型。
|
||||
|
||||
- **文生图**:集成**StableDiffusion** 本地模型,可以进行文生图。
|
||||
|
||||
- **GPTs 生成**:此平台支持创建个性化的GPT模型,尝试构建您自己的GPT模型。
|
||||
|
||||
- **API接口发布**:将内部功能以API的形式对外提供,便于开发者将AntSK 集成进其他应用,增强应用智慧。
|
||||
|
||||
- **API插件系统**:开放式API插件系统,允许第三方开发者或服务商轻松将其服务集成到AntSK,不断增强应用功能。
|
||||
|
||||
- **.Net插件系统**:开放式dll插件系统,允许第三方开发者或服务商轻松将其业务功能通过标准格式的代码生成dll后集成到AntSK,不断增强应用功能。
|
||||
|
||||
- **联网搜索**:AntSK,实时获取最新信息,确保用户接受到的资料总是最及时、最相关的。
|
||||
|
||||
- **模型管理**:适配和管理集成不同厂商的不同模型。并且支持**llama.cpp**所支持的gguf类型,以及**llamafactory**所支持的模型离线运行
|
||||
|
||||
- **国产信创**:AntSK支持国产模型,和国产数据库,可以在信创条件下运行
|
||||
|
||||
- **模型微调**:规划中,基于llamafactory进行模型微调
|
||||
|
||||
|
||||
## ⛪应用场景
|
||||
|
||||
AntSK 适用于多种业务场景,例如:
|
||||
- 企业级知识管理系统
|
||||
- 自动客服与聊天机器人
|
||||
- 企业级搜索引擎
|
||||
- 个性化推荐系统
|
||||
- 智能辅助写作
|
||||
- 教育与在线学习平台
|
||||
- 其他有意思的AI App
|
||||
|
||||
## ✏️功能示例
|
||||
### 在线演示
|
||||
|
||||
[文档地址](http://antsk.cn/)
|
||||
|
||||
[体验地址](https://antsk.ai-dotnet.com/)
|
||||
|
||||
```
|
||||
默认账号:test
|
||||
|
||||
默认密码:test
|
||||
|
||||
由于云服务器配置较低,无法运行本地模型,所以把系统设置权限关闭了,大家看看界面即可,要使用本地模型,请下载自行使用
|
||||
|
||||
请勿在演示站点上传敏感信息
|
||||
```
|
||||
|
||||
### 其他功能示例
|
||||
[视频示例](https://www.bilibili.com/video/BV1zH4y1h7Y9/)
|
||||
|
||||
[在线文档:http://antsk.cn](http://antsk.cn)
|
||||
|
||||
## ❓如何开始?
|
||||
|
||||
在这里我使用的是Postgres 作为数据存储和向量存储,因为Semantic Kernel和Kernel Memory都支持他,当然你也可以换成其他的。
|
||||
|
||||
模型默认支持openai、azure openai、讯飞星火、阿里云积、 和llama支持的gguf本地模型 以及llamafactory的本地模型,如果需要使用其他模型,可以使用one-api进行集成。
|
||||
|
||||
配置文件中的Login配置是默认的登录账号和密码
|
||||
|
||||
需要配置如下的配置文件
|
||||
|
||||
## 1️⃣使用docker-compose
|
||||
|
||||
提供了pg版本 **appsettings.json** 和 简化版本(**Sqlite+disk**) **docker-compose.simple.yml**
|
||||
|
||||
从项目根目录下载**docker-compose.yml**,然后把配置文件**appsettings.json**和它放在统一目录,
|
||||
|
||||
这里已经把pg的镜像做好了。在docker-compose.yml中可以修改默认账号密码,然后你的**appsettings.json**的数据库连接需要保持一致。
|
||||
|
||||
然后你可以进入到目录后执行
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
来启动AntSK
|
||||
|
||||
## 2️⃣如何在docker中挂载本地模型,和模型下载的目录
|
||||
```
|
||||
# 非 host 版本, 不使用本机代理
|
||||
version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.3.1
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
- antsk
|
||||
depends_on:
|
||||
- antskpg
|
||||
restart: always
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # 本地配置文件 需要放在同级目录
|
||||
- D://model:/app/model
|
||||
- D://model:/root/.cache/modelscope/hub/AI-ModelScope #使用Llamafactory时需要挂载 否则初始化的环境重启后会丢失
|
||||
networks:
|
||||
antsk:
|
||||
```
|
||||
以这个为示例,意思是把windows本地D://model的文件夹挂载进 容器内/app/model 如果是这样你的appsettings.json中的模型地址应该配置为
|
||||
```
|
||||
model/xxx.gguf
|
||||
```
|
||||
|
||||
## 3️⃣配置文件的一些含义
|
||||
```
|
||||
{
|
||||
"DBConnection": {
|
||||
"DbType": "Sqlite",
|
||||
"ConnectionStrings": "Data Source=AntSK.db;"
|
||||
},
|
||||
"KernelMemory": {
|
||||
"VectorDb": "Disk",
|
||||
"ConnectionString": "Host=;Port=;Database=antsk;Username=;Password=",
|
||||
"TableNamePrefix": "km-"
|
||||
},
|
||||
"FileDir": {
|
||||
"DirectoryPath": "D:\\git\\AntBlazor\\model"
|
||||
},
|
||||
"LLamaSharp": {
|
||||
"RunType": "GPU",
|
||||
"ContextSize": 2048,
|
||||
"GpuLayerCount": 20
|
||||
},
|
||||
"Login": {
|
||||
"User": "admin",
|
||||
"Password": "xuzeyu"
|
||||
},
|
||||
"BackgroundTaskBroker": {
|
||||
"ImportKMSTask": {
|
||||
"WorkerCount": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
//支持多种数据库,具体可以查看SqlSugar,MySql,SqlServer,Sqlite,Oracle,PostgreSQL,Dm,Kdbndp,Oscar,MySqlConnector,Access,OpenGauss,QuestDB,HG,ClickHouse,GBase,Odbc,OceanBaseForOracle,TDengine,GaussDB,OceanBase,Tidb,Vastbase,PolarDB,Custom
|
||||
DBConnection.DbType
|
||||
//连接字符串,需要根据不同DB类型,用对应的字符串
|
||||
DBConnection.ConnectionStrings
|
||||
|
||||
//向量存储的类型,支持 Postgres、Disk、Memory、Qdrant、Redis、AzureAISearch
|
||||
//Postgres、Redis需要配置 ConnectionString
|
||||
//Qdrant 和AzureAISearch 的 ConnectionString 使用 Endpoint|APIKey
|
||||
KernelMemory.VectorDb
|
||||
|
||||
//本地模型使用的运行方式 GUP CPU ,如果用在线API 这个随意使用一个即可
|
||||
LLamaSharp.RunType
|
||||
|
||||
//本地模型路径,用于在选择llama时可以快速选择目录下的模型,以及保存下载的模型
|
||||
LLamaSharp.FileDirectory
|
||||
|
||||
//默认管理员账号密码
|
||||
Login
|
||||
//导入异步处理的线程数,使用在线API可以高一点,本地模型建议1 否则容易内存溢出崩掉
|
||||
BackgroundTaskBroker.ImportKMSTask.WorkerCount
|
||||
```
|
||||
|
||||
## ⚠️找不到样式问题解决:
|
||||
AntSK/src/AntSK下执行:
|
||||
```
|
||||
dotnet clean
|
||||
dotnet build
|
||||
dotnet publish "AntSK.csproj"
|
||||
```
|
||||
再去AntSK/src/AntSK/bin/Release/net8.0/publish下
|
||||
```
|
||||
dotnet AntSK.dll
|
||||
```
|
||||
然后启动就有样式了
|
||||
|
||||
DB我使用的是CodeFirst模式,只要配置好数据库链接,表结构是自动创建的
|
||||
|
||||
## ✔️使用llamafactory
|
||||
```
|
||||
1、首先需要确保你的环境已经安装了python和pip,如果使用镜像,例如p0.2.4版本已经包含了 python全套环境则无需此步骤
|
||||
2、进入模型添加页面选择llamafactory
|
||||
3、点击初始化,可以检查pip install 环境是否完成
|
||||
4、选择一个喜欢的模型
|
||||
5、点击启动,这会开始从魔塔下载模型,你可能需要有一个较为漫长的等待
|
||||
6、等待模型下载完毕后,在请求地址输入 http://localhost:8000/ 这里默认是使用8000端口
|
||||
7、点击保存,然后就可以开始聊天了
|
||||
8、很多人会问 LLamaSharp与llamafactory有什么区别?其实这两者LLamaSharp是llama.cpp的 dotnet实现,但是只支持本地gguf模型, 而llamafactory 支持的模型种类更多,但使用的是python的实现,其主要差异在这里,另外llamafactory具有模型微调的能力,这也是我们下一步需要重点集成的部分。
|
||||
```
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
[](https://github.com/AIDotNet/AntSK/pulls)
|
||||
|
||||
如果你想贡献,可以创建一个[拉取请求](https://github.com/AIDotNet/AntSK/pulls), 或给我们[错误报告](https://github.com/AIDotNet/AntSK/issues/new).
|
||||
|
||||
|
||||
## 💕 贡献者
|
||||
|
||||
这个项目的存在要感谢所有的贡献者。
|
||||
|
||||
<a href="https://github.com/AIDotNet/AntSK/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=AIDotNet/AntSK&max=1000&columns=15&anon=1" />
|
||||
</a>
|
||||
|
||||
## 🚨 使用协议
|
||||
|
||||
本仓库遵循 [Apache-2.0 License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) 开源协议。
|
||||
Apache开源许可证允许在商业环境中使用AntSK,前提是需要遵守许可证的条款。主要条款之一是要保留版权声明和许可证声明。
|
||||
|
||||
如果您打算在商业项目中使用AntSK,您需要确保遵守以下步骤:
|
||||
|
||||
1、包含Apache许可证的版权声明。 [Apache-2.0 License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) 。
|
||||
|
||||
2、如果您修改了软件源代码,您需要在源代码中明确标明这些修改。
|
||||
|
||||
|
||||
## ☎️联系我
|
||||
如有任何问题或建议,请通过以下方式关注我的公众号《许泽宇的技术分享》,发消息与我联系,我们也有AIDotnet交流群,可以发送进群等消息,然后我会拉你进交流群
|
||||

|
||||
|
||||
## 🌟 Star History
|
||||
<a href="https://github.com/AIDotNet/AntSK/stargazers" target="_blank" style="display: block" align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=AIDotNet/AntSK&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=AIDotNet/AntSK&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=AIDotNet/AntSK&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
@@ -3,7 +3,9 @@ version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.2.1
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.4.1
|
||||
# 如果需要pytorch环境需要使用下面这个镜像,镜像比较大
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:p0.4.1
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
@@ -13,5 +15,7 @@ services:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # 本地配置文件 需要放在同级目录
|
||||
- /AntSK/model:/app/model
|
||||
- /AntSK/model:/root/.cache/modelscope/hub/AI-ModelScope # LLamaFactory模型文件
|
||||
networks:
|
||||
antsk:
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
# 非 host 版本, 不使用本机代理
|
||||
version: '3.8'
|
||||
services:
|
||||
aspire-dashboard:
|
||||
container_name: aspire-dashboard
|
||||
image: mcr.microsoft.com/dotnet/aspire-dashboard:8.0
|
||||
networks:
|
||||
- antsk
|
||||
environment:
|
||||
- DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true
|
||||
- ASPIRE_ALLOW_UNSECURED_TRANSPORT=true
|
||||
- DASHBOARD_OTLP_AUTHMODE=ApiKey
|
||||
- DASHBOARD_OTLP_PRIMARYAPIKEY=antsk
|
||||
ports:
|
||||
- 18888:18888
|
||||
- 18889:18889
|
||||
restart: unless-stopped
|
||||
antskpg:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/pg:v0.5.0
|
||||
container_name: antskpg
|
||||
@@ -18,7 +32,9 @@ services:
|
||||
- ./pg/data:/var/lib/postgresql/data
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.2.1
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.4.1
|
||||
# 如果需要pytorch环境需要使用下面这个镜像,镜像比较大
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:p0.4.1
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
@@ -28,7 +44,15 @@ services:
|
||||
restart: always
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
- ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
|
||||
- OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES=true
|
||||
- OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES= true
|
||||
- OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY=in_memory
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://aspire-dashboard:18889
|
||||
- OTEL_SERVICE_NAME=antsk
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # 本地配置文件 需要放在同级目录
|
||||
- /AntSK/model:/app/model
|
||||
- /AntSK/model:/root/.cache/modelscope/hub/AI-ModelScope # LLamaFactory模型文件
|
||||
networks:
|
||||
antsk:
|
||||
|
||||
14
docs/deploy/_category_.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"position": 3,
|
||||
"label": "部署",
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"className": "red",
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"title": "使用案例"
|
||||
},
|
||||
"customProps": {
|
||||
"description": "提供快速使用AntSK的一些案例!"
|
||||
}
|
||||
}
|
||||
56
docs/deploy/settings.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# 配置文件的一些含义
|
||||
```
|
||||
{
|
||||
"DBConnection": {
|
||||
"DbType": "Sqlite",
|
||||
"ConnectionStrings": "Data Source=AntSK.db;"
|
||||
},
|
||||
"KernelMemory": {
|
||||
"VectorDb": "Disk",
|
||||
"ConnectionString": "Host=;Port=;Database=antsk;Username=;Password=",
|
||||
"TableNamePrefix": "km-"
|
||||
},
|
||||
"LLamaSharp": {
|
||||
"RunType": "GPU",
|
||||
"Chat": "D:\\Code\\AI\\AntBlazor\\model\\qwen1_5-1_8b-chat-q8_0.gguf",
|
||||
"Embedding": "D:\\Code\\AI\\AntBlazor\\model\\qwen1_5-1_8b-chat-q8_0.gguf",
|
||||
"FileDirectory": "D:\\Code\\AI\\AntBlazor\\model\\"
|
||||
},
|
||||
"Login": {
|
||||
"User": "admin",
|
||||
"Password": "xuzeyu"
|
||||
},
|
||||
"BackgroundTaskBroker": {
|
||||
"ImportKMSTask": {
|
||||
"WorkerCount": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
//支持多种数据库,具体可以查看SqlSugar,MySql,SqlServer,Sqlite,Oracle,PostgreSQL,Dm,Kdbndp,Oscar,MySqlConnector,Access,OpenGauss,QuestDB,HG,ClickHouse,GBase,Odbc,OceanBaseForOracle,TDengine,GaussDB,OceanBase,Tidb,Vastbase,PolarDB,Custom
|
||||
DBConnection.DbType
|
||||
//连接字符串,需要根据不同DB类型,用对应的字符串
|
||||
DBConnection.ConnectionStrings
|
||||
|
||||
//向量存储的类型,支持 Postgres Disk Memory ,其中Postgres需要配置 ConnectionString
|
||||
KernelMemory.VectorDb
|
||||
|
||||
//本地模型使用的运行方式 GUP CPU ,如果用在线API 这个随意使用一个即可
|
||||
LLamaSharp.RunType
|
||||
//本地会话模型的模型路径 注意区分linux和windows盘符不同
|
||||
LLamaSharp.Chat
|
||||
//本地向量模型的模型路径 注意区分linux和windows盘符不同
|
||||
LLamaSharp.Embedding
|
||||
//本地模型路径,用于在选择llama时可以快速选择目录下的模型,以及保存下载的模型
|
||||
LLamaSharp.FileDirectory
|
||||
|
||||
//默认管理员账号密码
|
||||
Login
|
||||
//导入异步处理的线程数,使用在线API可以高一点,本地模型建议1 否则容易内存溢出崩掉
|
||||
BackgroundTaskBroker.ImportKMSTask.WorkerCount
|
||||
```
|
||||
57
docs/deploy/start.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 如何开始?
|
||||
|
||||
在这里我使用的是Postgres 作为数据存储和向量存储,因为Semantic Kernel和Kernel Memory都支持他,当然你也可以换成其他的。
|
||||
|
||||
模型默认支持openai、azure openai 和llama支持的gguf本地模型,如果需要使用其他模型,可以使用one-api进行集成。
|
||||
|
||||
配置文件中的Login配置是默认的登陆账号和密码
|
||||
|
||||
需要配置如下的配置文件
|
||||
|
||||
## 使用docker-compose
|
||||
|
||||
提供了pg版本 **appsettings.json** 和 简化版本(Sqlite+disk) **docker-compose.simple.yml**
|
||||
|
||||
从项目根目录下载**docker-compose.yml**,然后把配置文件**appsettings.json**和它放在统一目录,
|
||||
|
||||
这里已经把pg的镜像做好了。在docker-compose.yml中可以修改默认账号密码,然后你的**appsettings.json**的数据库连接需要保持一致。
|
||||
|
||||
然后你可以进入到目录后执行
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
来启动AntSK
|
||||
|
||||
## 如何在docker中挂载本地模型,和模型下载的目录
|
||||
```
|
||||
# 非 host 版本, 不使用本机代理
|
||||
version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.1.5
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
- antsk
|
||||
depends_on:
|
||||
- antskpg
|
||||
restart: always
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # 本地配置文件 需要放在同级目录
|
||||
- D://model:/app/model
|
||||
networks:
|
||||
antsk:
|
||||
```
|
||||
以这个为示例,意思是把windows本地D://model的文件夹挂载进 容器内/app/model 如果是这样你的appsettings.json中的模型地址应该配置为
|
||||
```
|
||||
model/xxx.gguf
|
||||
```
|
||||
|
||||
DB我使用的是CodeFirst模式,只要配置好数据库链接,表结构是自动创建的
|
||||
16
docs/deploy/style.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# 找不到样式问题解决
|
||||
AntSK/src/AntSK下执行:
|
||||
```
|
||||
dotnet clean
|
||||
dotnet build
|
||||
dotnet publish "AntSK.csproj"
|
||||
```
|
||||
再去AntSK/src/AntSK/bin/Release/net8.0/publish下
|
||||
```
|
||||
dotnet AntSK.dll
|
||||
```
|
||||
然后启动就有样式了
|
||||
14
docs/develop/_category_.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"position": 2,
|
||||
"label": "快速开发",
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"className": "red",
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"title": "快速开发"
|
||||
},
|
||||
"customProps": {
|
||||
"description": "快速基于项目二次开发!"
|
||||
}
|
||||
}
|
||||
14
docs/introduce/_category_.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"position": 2,
|
||||
"label": "介绍",
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"className": "red",
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"title": "使用案例"
|
||||
},
|
||||
"customProps": {
|
||||
"description": "提供快速使用AntSK的一些案例!"
|
||||
}
|
||||
}
|
||||
BIN
docs/introduce/img/对话效果.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
docs/introduce/img/应用.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/introduce/img/应用配置.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/introduce/img/文档切片.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
docs/introduce/img/知识库.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/introduce/img/知识库详情.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
docs/introduce/img/简单对话.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/introduce/img/问答.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
70
docs/introduce/readme.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# AntSK功能介绍
|
||||
## 基于.Net8+AntBlazor+SemanticKernel 打造的AI知识库/智能体
|
||||
|
||||
## 核心功能
|
||||
|
||||
- **语义内核 (Semantic Kernel)**:采用领先的自然语言处理技术,准确理解、处理和响应复杂的语义查询,为用户提供精确的信息检索和推荐服务。
|
||||
|
||||
- **内存内核 (Kernel Memory)**:具备持续学习和存储知识点的能力,AntSK 拥有长期记忆功能,累积经验,提供更个性化的交互体验。
|
||||
|
||||
- **知识库**:通过文档(Word、PDF、Excel、Txt、Markdown、Json、PPT)等形式导入知识库,可以进行知识库问答。
|
||||
|
||||
- **GPTs 生成**:此平台支持创建个性化的GPT模型,尝试构建您自己的GPT模型。
|
||||
|
||||
- **API接口发布**:将内部功能以API的形式对外提供,便于开发者将AntSK 集成进其他应用,增强应用智慧。
|
||||
|
||||
- **API插件系统**:开放式API插件系统,允许第三方开发者或服务商轻松将其服务集成到AntSK,不断增强应用功能。
|
||||
|
||||
- **.Net插件系统**:开放式dll插件系统,允许第三方开发者或服务商轻松将其业务功能通过标准格式的代码生成dll后集成到AntSK,不断增强应用功能。
|
||||
|
||||
- **联网搜索**:AntSK,实时获取最新信息,确保用户接受到的资料总是最及时、最相关的。
|
||||
|
||||
- **模型管理**:适配和管理集成不同厂商的不同模型。并且支持**llama.cpp**所支持的gguf类型,以及**llamafactory**所支持的模型离线运行
|
||||
|
||||
- **国产信创**:AntSK支持国产模型,和国产数据库,可以在信创条件下运行
|
||||
|
||||
- **模型微调**:规划中,基于llamafactory进行模型微调
|
||||
|
||||
|
||||
## 应用场景
|
||||
|
||||
AntSK 适用于多种业务场景,例如:
|
||||
- 企业级知识管理系统
|
||||
- 自动客服与聊天机器人
|
||||
- 企业级搜索引擎
|
||||
- 个性化推荐系统
|
||||
- 智能辅助写作
|
||||
- 教育与在线学习平台
|
||||
- 其他有意思的AI App
|
||||
|
||||
## 功能示例
|
||||
|
||||
[视频示例](https://www.bilibili.com/video/BV1zH4y1h7Y9/)
|
||||
|
||||
首先需要创建知识库
|
||||

|
||||
|
||||
在知识库里可以使用文档或者url进行导入
|
||||

|
||||
|
||||
点击查看可以查看知识库的文档切片情况
|
||||

|
||||
|
||||
然后我们需要创建应用,可以创建对话应用和知识库。
|
||||

|
||||
|
||||
知识库应用需要选择已有的知识库,可以选多个
|
||||

|
||||
|
||||
然后再对话中可以对知识库的文档进行提问
|
||||

|
||||
|
||||
另外我们也可以创建对话应用,可以在对应应用中配置提示词模板
|
||||

|
||||
|
||||
下面来看看效果吧
|
||||

|
||||
BIN
images/gzh.jpg
Normal file
|
After Width: | Height: | Size: 180 KiB |
20
src/AntSK.AppHost/AntSK.AppHost.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAspireHost>true</IsAspireHost>
|
||||
<UserSecretsId>32ac67c8-178a-4eeb-871d-879023582e06</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AntSK\AntSK.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
5
src/AntSK.AppHost/Program.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
var builder = DistributedApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddProject<Projects.AntSK>("antsk");
|
||||
|
||||
builder.Build().Run();
|
||||
8
src/AntSK.AppHost/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/AntSK.AppHost/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Aspire.Hosting.Dcp": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/AntSK.AppHost/aspirate-output/docker-compose.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
services:
|
||||
aspire-dashboard:
|
||||
container_name: "aspire-dashboard"
|
||||
image: "mcr.microsoft.com/dotnet/aspire-dashboard:8.0"
|
||||
environment:
|
||||
DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS: "true"
|
||||
ports:
|
||||
- target: 18888
|
||||
published: 18888
|
||||
restart: unless-stopped
|
||||
antsk:
|
||||
container_name: "antsk"
|
||||
image: "antsk:latest"
|
||||
environment:
|
||||
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
|
||||
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
|
||||
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
|
||||
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889"
|
||||
OTEL_SERVICE_NAME: "antsk"
|
||||
ports:
|
||||
- target: 8080
|
||||
published: 10000
|
||||
- target: 8443
|
||||
published: 10001
|
||||
restart: unless-stopped
|
||||
17
src/AntSK.AppHost/aspirate-state.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"projectPath": ".",
|
||||
"outputPath": "aspirate-output",
|
||||
"containerImageTags": [
|
||||
"latest"
|
||||
],
|
||||
"containerBuilder": "docker",
|
||||
"outputFormat": "compose",
|
||||
"privateRegistryEmail": "aspir8@aka.ms",
|
||||
"includeDashboard": true,
|
||||
"secrets": {
|
||||
"salt": "fjamZa3pQbM1UyY4",
|
||||
"hash": "QR\u002BSEr3p2SwD/w2oPE21vrWh/EerhNyVyTkr0atIREw=",
|
||||
"secrets": {}
|
||||
},
|
||||
"processAllComponents": true
|
||||
}
|
||||
26
src/AntSK.AppHost/manifest.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"resources": {
|
||||
"antsk": {
|
||||
"type": "project.v0",
|
||||
"path": "../AntSK/AntSK.csproj",
|
||||
"env": {
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
|
||||
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true"
|
||||
},
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http"
|
||||
},
|
||||
"https": {
|
||||
"scheme": "https",
|
||||
"protocol": "tcp",
|
||||
"transport": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/AntSK.Domain/AntSK - Backup.Domain.csproj
Normal file
@@ -0,0 +1,53 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<DocumentationFile>AntSK.Domain.xml</DocumentationFile>
|
||||
<NoWarn>CA1050,CA1707,CA2007,VSTHRD111,CS1591,RCS1110,CA5394,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0042,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.5.1" />
|
||||
<PackageReference Include="AntDesign.ProLayout" Version="0.18.2" />
|
||||
<PackageReference Include="BlazorComponents.Terminal" Version="0.6.0" />
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
|
||||
<PackageReference Include="pythonnet" Version="3.0.3" />
|
||||
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
|
||||
<PackageReference Include="AutoMapper" Version="8.1.0" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.151" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
|
||||
<PackageReference Include="RestSharp" Version="110.2.0" />
|
||||
<PackageReference Include="NPOI" Version="2.7.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.7.1" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Core" Version="1.7.1" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="1.7.1-alpha" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.Core" Version="0.36.240415.2" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Postgres" Version="0.36.240415.2" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Qdrant" Version="0.36.240415.2" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Redis" Version="0.36.240415.2" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.AzureAISearch" Version="0.36.240415.2" />
|
||||
|
||||
<PackageReference Include="LLamaSharp" Version="0.11.2" />
|
||||
<PackageReference Include="LLamaSharp.Backend.Cpu" Version="0.11.2" />
|
||||
<PackageReference Include="LLamaSharp.Backend.Cuda12" Version="0.11.2" />
|
||||
<PackageReference Include="LLamaSharp.kernel-memory" Version="0.11.2" />
|
||||
<PackageReference Include="LLamaSharp.semantic-kernel" Version="0.11.2" />
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AntSK.LLamaFactory\AntSK.LLamaFactory.csproj" />
|
||||
<ProjectReference Include="..\AntSk.LLM\AntSK.LLM.csproj" />
|
||||
<ProjectReference Include="..\AntSK.OCR\AntSK.OCR.csproj" />
|
||||
<ProjectReference Include="..\MiddleWare\AntSK.BackgroundTask\AntSK.BackgroundTask.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -5,36 +5,54 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<DocumentationFile>AntSK.Domain.xml</DocumentationFile>
|
||||
<NoWarn>CA1050,CA1707,CA2007,VSTHRD111,CS1591,RCS1110,CA5394,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0042,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102</NoWarn>
|
||||
<NoWarn>CA1050,CA1707,CA2007,VSTHRD111,CS1591,RCS1110,CA5394,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0042,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102,KMEXP00</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.5.1" />
|
||||
<PackageReference Include="AntDesign.ProLayout" Version="0.18.0" />
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.5.2" />
|
||||
<PackageReference Include="AntDesign.ProLayout" Version="0.19.2" />
|
||||
<PackageReference Include="BlazorComponents.Terminal" Version="0.6.0" />
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
|
||||
<PackageReference Include="pythonnet" Version="3.0.3" />
|
||||
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
|
||||
<PackageReference Include="AutoMapper" Version="8.1.0" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Markdig" Version="0.36.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.145" />
|
||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftVersion)" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.160" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
|
||||
<PackageReference Include="RestSharp" Version="110.2.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.6.2" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Core" Version="1.6.2" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="1.6.2-alpha" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.Core" Version="0.34.240313.1" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Postgres" Version="0.34.240313.1" />
|
||||
<PackageReference Include="RestSharp" Version="$(RestSharpVersion)" />
|
||||
<PackageReference Include="NPOI" Version="2.7.0" />
|
||||
|
||||
<PackageReference Include="LLamaSharp" Version="0.10.0" />
|
||||
<PackageReference Include="LLamaSharp.Backend.Cpu" Version="0.10.0" />
|
||||
<PackageReference Include="LLamaSharp.Backend.Cuda12" Version="0.10.0" />
|
||||
<PackageReference Include="LLamaSharp.kernel-memory" Version="0.10.0" />
|
||||
<PackageReference Include="LLamaSharp.semantic-kernel" Version="0.10.0" />
|
||||
|
||||
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="$(SKVersion)" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Core" Version="$(SKVersion)" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="$(SKVersion)-alpha" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.Core" Version="$(KMVersion)" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Postgres" Version="$(KMVersion)" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Qdrant" Version="$(KMVersion)" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Redis" Version="$(KMVersion)" />
|
||||
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.AzureAISearch" Version="$(KMVersion)" />
|
||||
|
||||
<PackageReference Include="LLamaSharp" Version="$(LLamaSharpVersion)" />
|
||||
<PackageReference Include="LLamaSharp.Backend.Cpu" Version="$(LLamaSharpVersion)" />
|
||||
<PackageReference Include="LLamaSharp.Backend.Cuda12" Version="$(LLamaSharpVersion)" />
|
||||
<PackageReference Include="LLamaSharp.kernel-memory" Version="$(LLamaSharpVersion)" />
|
||||
<PackageReference Include="LLamaSharp.semantic-kernel" Version="$(LLamaSharpVersion)" />
|
||||
|
||||
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.1-dev-10391" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AntSK.LLamaFactory\AntSK.LLamaFactory.csproj" />
|
||||
<ProjectReference Include="..\AntSk.LLM\AntSK.LLM.csproj" />
|
||||
<ProjectReference Include="..\AntSK.OCR\AntSK.OCR.csproj" />
|
||||
<ProjectReference Include="..\MiddleWare\AntSK.BackgroundTask\AntSK.BackgroundTask.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -17,6 +17,27 @@
|
||||
<param name="assemblies">程序集集合</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Common.DependencyInjection.InitExtensions.CodeFirst(Microsoft.AspNetCore.Builder.WebApplication)">
|
||||
<summary>
|
||||
使用codefirst创建数据库表
|
||||
</summary>
|
||||
<param name="services"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Common.DependencyInjection.InitExtensions.LoadFun(Microsoft.AspNetCore.Builder.WebApplication)">
|
||||
<summary>
|
||||
加载数据库的插件
|
||||
</summary>
|
||||
<param name="services"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Common.DependencyInjection.InitExtensions.AddAntSKSwagger(Microsoft.Extensions.DependencyInjection.IServiceCollection)">
|
||||
<summary>
|
||||
swagger 初始化
|
||||
</summary>
|
||||
<param name="serviceCollection"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="F:AntSK.Domain.Common.DependencyInjection.ServiceLifetime.Scoped">
|
||||
<summary>
|
||||
作用域
|
||||
@@ -48,6 +69,84 @@
|
||||
<param name="value"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.ExcelToDataTable(System.String,System.Boolean)">
|
||||
<summary>
|
||||
将excel导入到datatable
|
||||
</summary>
|
||||
<param name="filePath">excel路径</param>
|
||||
<param name="isColumnName">第一行是否是列名</param>
|
||||
<returns>返回datatable</returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.ExcelToDataTable(System.IO.Stream,System.Boolean)">
|
||||
<summary>
|
||||
将excel导入到datatable
|
||||
</summary>
|
||||
<param name="stream">流</param>
|
||||
<param name="isColumnName">第一行是否是列名</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.ExcelToList``1(System.IO.Stream)">
|
||||
<summary>
|
||||
excel转list
|
||||
</summary>
|
||||
<typeparam name="TResult"></typeparam>
|
||||
<param name="stream"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.ExcelToList``1(System.IO.Stream,System.String)">
|
||||
<summary>
|
||||
excel转list-根据sheetName得到List
|
||||
</summary>
|
||||
<typeparam name="TResult"></typeparam>
|
||||
<param name="stream"></param>
|
||||
<param name="sheetName"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.ListToExcel``1(``0[],System.String)">
|
||||
<summary>
|
||||
List导出excel 二进制流
|
||||
</summary>
|
||||
<typeparam name="T">实体</typeparam>
|
||||
<param name="data">List</param>
|
||||
<param name="sheetName">sheetname 可不填,默认Sheet0</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.DataTableToExcel(System.Data.DataTable,System.String,System.String)">
|
||||
<summary>
|
||||
Dt导出excel 二进制流
|
||||
</summary>
|
||||
<param name="dt">datatable</param>
|
||||
<param name="strFile">strFile</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.ListWriteExcel``1(``0[],System.String,System.String)">
|
||||
<summary>
|
||||
List写入excel
|
||||
</summary>
|
||||
<typeparam name="T"></typeparam>
|
||||
<param name="data"></param>
|
||||
<param name="strFile">路径</param>
|
||||
<param name="sheetName"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.DataTableWriteExcel(System.Data.DataTable,System.String,System.String)">
|
||||
<summary>
|
||||
dt写入excel
|
||||
</summary>
|
||||
<param name="dt">datatable</param>
|
||||
<param name="strFile">路径</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.ExeclHelper.SetCellDropdownList(NPOI.SS.UserModel.IWorkbook,NPOI.SS.UserModel.ISheet,System.Collections.Generic.List{System.String},System.String,System.Int32,System.Int32,System.Int32)">
|
||||
<summary>
|
||||
设置单元格下拉框(除去标题行)
|
||||
</summary>
|
||||
<param name="workbook"></param>
|
||||
<param name="sheet"></param>
|
||||
<param name="ddlList"></param>
|
||||
<param name="firstcol"></param>
|
||||
<param name="lastcol"></param>
|
||||
</member>
|
||||
<member name="T:AntSK.Domain.Domain.Model.Enum.AIType">
|
||||
<summary>
|
||||
AI类型
|
||||
@@ -58,11 +157,6 @@
|
||||
模型类型
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Domain.Model.MessageInfo.IsSend">
|
||||
<summary>
|
||||
发送是true 接收是false
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Domain.Model.PageList`1.PageIndex">
|
||||
<summary>
|
||||
当前页,从1开始
|
||||
@@ -78,12 +172,34 @@
|
||||
总数
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Domain.Other.Bge.BegRerankConfig.LoadModel(System.String,System.String)">
|
||||
<summary>
|
||||
模型写死
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Domain.Other.Bge.BgeEmbeddingConfig.LoadModel(System.String,System.String)">
|
||||
<summary>
|
||||
模型写死
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Domain.Other.KMExcelHandler.StepName">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Domain.Other.KMExcelHandler.InvokeAsync(Microsoft.KernelMemory.Pipeline.DataPipeline,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="F:AntSK.Domain.Domain.Other.LLamaConfig.dicLLamaWeights">
|
||||
<summary>
|
||||
避免模型重复加载,本地缓存
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Domain.Service.ChatService.SendChatByAppAsync(AntSK.Domain.Repositories.Apps,System.String,System.String)">
|
||||
<member name="P:AntSK.Domain.Domain.Other.QAHandler.StepName">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<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,Microsoft.SemanticKernel.ChatCompletion.ChatHistory)">
|
||||
<summary>
|
||||
发送消息
|
||||
</summary>
|
||||
@@ -266,6 +382,56 @@
|
||||
API调用秘钥
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Apps.Relevance">
|
||||
<summary>
|
||||
相似度
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Apps.MaxAskPromptSize">
|
||||
<summary>
|
||||
提问最大token数
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Apps.MaxMatchesCount">
|
||||
<summary>
|
||||
向量匹配数
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Apps.AnswerTokens">
|
||||
<summary>
|
||||
回答最大token数
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Chats.UserName">
|
||||
<summary>
|
||||
用户名
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Chats.AppId">
|
||||
<summary>
|
||||
应用ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Chats.Context">
|
||||
<summary>
|
||||
消息内容
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Chats.IsSend">
|
||||
<summary>
|
||||
发送是true 接收是false
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Chats.CreateTime">
|
||||
<summary>
|
||||
创建事件
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Chats.FileName">
|
||||
<summary>
|
||||
文件名
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Funs.Path">
|
||||
<summary>
|
||||
接口描述
|
||||
@@ -750,6 +916,14 @@
|
||||
<param name="parameters"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Utils.ConvertUtils.ComparisonIgnoreCase(System.String,System.String)">
|
||||
<summary>
|
||||
忽略大小写匹配
|
||||
</summary>
|
||||
<param name="s"></param>
|
||||
<param name="value"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:AntSK.Domain.Utils.RepoFiles.SamplePluginsPath">
|
||||
<summary>
|
||||
Scan the local folders from the repo, looking for "samples/plugins" folder.
|
||||
|
||||
175
src/AntSK.Domain/Common/DependencyInjection/InitExtensions.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using AntSK.Domain.Domain.Model.Constant;
|
||||
using AntSK.Domain.Domain.Service;
|
||||
using AntSK.Domain.Repositories;
|
||||
using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using SqlSugar;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Common.DependencyInjection
|
||||
{
|
||||
public static class InitExtensions
|
||||
{
|
||||
private static ILogger _logger;
|
||||
|
||||
public static void InitLog(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
/// <summary>
|
||||
/// 使用codefirst创建数据库表
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static WebApplication CodeFirst(this WebApplication app)
|
||||
{
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
// 获取仓储服务
|
||||
var _repository = scope.ServiceProvider.GetRequiredService<IApps_Repositories>();
|
||||
|
||||
// 创建数据库(如果不存在)
|
||||
_repository.GetDB().DbMaintenance.CreateDatabase();
|
||||
|
||||
// 获取当前应用程序域中所有程序集
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
// 在所有程序集中查找具有[SugarTable]特性的类
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
// 获取该程序集中所有具有SugarTable特性的类型
|
||||
var entityTypes = assembly.GetTypes()
|
||||
.Where(type => TypeIsEntity(type));
|
||||
|
||||
// 为每个找到的类型初始化数据库表
|
||||
foreach (var type in entityTypes)
|
||||
{
|
||||
_repository.GetDB().CodeFirst.InitTables(type);
|
||||
}
|
||||
}
|
||||
//安装向量插件
|
||||
_repository.GetDB().Ado.ExecuteCommandAsync($"CREATE EXTENSION IF NOT EXISTS vector;");
|
||||
|
||||
_logger.LogInformation("初始化表结构完成");
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
public static WebApplication InitDbData(this WebApplication app)
|
||||
{
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
// 初始化字典
|
||||
var _dic_Repository = scope.ServiceProvider.GetRequiredService<IDics_Repositories>();
|
||||
var llamafactoryStart = _dic_Repository.GetFirst(p => p.Type == LLamaFactoryConstantcs.LLamaFactorDic && p.Key == LLamaFactoryConstantcs.IsStartKey);
|
||||
if (llamafactoryStart==null)
|
||||
{
|
||||
llamafactoryStart = new Dics();
|
||||
llamafactoryStart.Id=Guid.NewGuid().ToString();
|
||||
llamafactoryStart.Type = LLamaFactoryConstantcs.LLamaFactorDic;
|
||||
llamafactoryStart.Key = LLamaFactoryConstantcs.IsStartKey;
|
||||
llamafactoryStart.Value = "false";
|
||||
_dic_Repository.Insert(llamafactoryStart);
|
||||
}
|
||||
_logger.LogInformation("初始化数据库初始数据完成");
|
||||
}
|
||||
return app;
|
||||
}
|
||||
/// <summary>
|
||||
/// 加载数据库的插件
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static WebApplication LoadFun(this WebApplication app)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
//codefirst 创建表
|
||||
var funRep = scope.ServiceProvider.GetRequiredService<IFuns_Repositories>();
|
||||
var functionService = scope.ServiceProvider.GetRequiredService<FunctionService>();
|
||||
var funs = funRep.GetList();
|
||||
foreach (var fun in funs)
|
||||
{
|
||||
functionService.FuncLoad(fun.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message + " ---- " + ex.StackTrace);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
private static bool TypeIsEntity(Type type)
|
||||
{
|
||||
// 检查类型是否具有SugarTable特性
|
||||
return type.GetCustomAttributes(typeof(SugarTable), inherit: false).Length > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// swagger 初始化
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddAntSKSwagger(this IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new() { Title = "AntSK.Api", Version = "v1" });
|
||||
//添加Api层注释(true表示显示控制器注释)
|
||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
c.IncludeXmlComments(xmlPath, true);
|
||||
//添加Domain层注释(true表示显示控制器注释)
|
||||
var xmlFile1 = $"{Assembly.GetExecutingAssembly().GetName().Name.Replace("Api", "Domain")}.xml";
|
||||
var xmlPath1 = Path.Combine(AppContext.BaseDirectory, xmlFile1);
|
||||
c.IncludeXmlComments(xmlPath1, true);
|
||||
c.DocInclusionPredicate((docName, apiDes) =>
|
||||
{
|
||||
if (!apiDes.TryGetMethodInfo(out MethodInfo method))
|
||||
return false;
|
||||
var version = method.DeclaringType.GetCustomAttributes(true).OfType<ApiExplorerSettingsAttribute>().Select(m => m.GroupName);
|
||||
if (docName == "v1" && !version.Any())
|
||||
return true;
|
||||
var actionVersion = method.GetCustomAttributes(true).OfType<ApiExplorerSettingsAttribute>().Select(m => m.GroupName);
|
||||
if (actionVersion.Any())
|
||||
return actionVersion.Any(v => v == docName);
|
||||
return version.Any(v => v == docName);
|
||||
});
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
|
||||
{
|
||||
Description = "Directly enter bearer {token} in the box below (note that there is a space between bearer and token)",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference()
|
||||
{
|
||||
Id = "Bearer",
|
||||
Type = ReferenceType.SecurityScheme
|
||||
}
|
||||
}, Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
});
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/AntSK.Domain/Common/Embedding/BuilderBgeExtensions.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using LLamaSharp.KernelMemory;
|
||||
using Microsoft.KernelMemory.AI;
|
||||
using Microsoft.KernelMemory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Common.Embedding
|
||||
{
|
||||
public static class BuilderBgeExtensions
|
||||
{
|
||||
public static IKernelMemoryBuilder WithBgeTextEmbeddingGeneration(this IKernelMemoryBuilder builder, HuggingfaceTextEmbeddingGenerator textEmbeddingGenerator)
|
||||
{
|
||||
builder.AddSingleton((ITextEmbeddingGenerator)textEmbeddingGenerator);
|
||||
builder.AddIngestionEmbeddingGenerator(textEmbeddingGenerator);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using LLama.Common;
|
||||
using LLama;
|
||||
using LLamaSharp.KernelMemory;
|
||||
using Microsoft.KernelMemory.AI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AntSK.Domain.Domain.Other.Bge;
|
||||
|
||||
namespace AntSK.Domain.Common.Embedding
|
||||
{
|
||||
public class HuggingfaceTextEmbeddingGenerator : ITextEmbeddingGenerator, ITextTokenizer, IDisposable
|
||||
{
|
||||
public int MaxTokens => 1024;
|
||||
|
||||
public int MaxTokenTotal => 1024;
|
||||
|
||||
|
||||
private readonly dynamic _embedder;
|
||||
|
||||
public HuggingfaceTextEmbeddingGenerator(string pyDllPath,string modelName)
|
||||
{
|
||||
_embedder = BgeEmbeddingConfig.LoadModel(pyDllPath, modelName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BgeEmbeddingConfig.Dispose();
|
||||
}
|
||||
|
||||
//public async Task<IList<ReadOnlyMemory<float>>> GenerateEmbeddingAsync(IList<string> data, CancellationToken cancellationToken = default)
|
||||
//{
|
||||
// IList<ReadOnlyMemory<float>> results = new List<ReadOnlyMemory<float>>();
|
||||
|
||||
// foreach (var d in data)
|
||||
// {
|
||||
// var embeddings = await EmbeddingConfig.GetEmbedding(d);
|
||||
// results.Add(new ReadOnlyMemory<float>(embeddings));
|
||||
// }
|
||||
// return results;
|
||||
//}
|
||||
|
||||
public async Task<Microsoft.KernelMemory.Embedding> GenerateEmbeddingAsync(string text, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var embeddings = await BgeEmbeddingConfig.GetEmbedding(text);
|
||||
return new Microsoft.KernelMemory.Embedding(embeddings);
|
||||
}
|
||||
|
||||
public int CountTokens(string text)
|
||||
{
|
||||
return BgeEmbeddingConfig.TokenCount(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
822
src/AntSK.Domain/Common/Excel/ExeclHelper.cs
Normal file
@@ -0,0 +1,822 @@
|
||||
using NPOI.HSSF.UserModel;
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.SS.Util;
|
||||
using NPOI.XSSF.Streaming;
|
||||
using NPOI.XSSF.UserModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain
|
||||
{
|
||||
public class ExeclHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 将excel导入到datatable
|
||||
/// </summary>
|
||||
/// <param name="filePath">excel路径</param>
|
||||
/// <param name="isColumnName">第一行是否是列名</param>
|
||||
/// <returns>返回datatable</returns>
|
||||
public static DataTable ExcelToDataTable(string filePath, bool isColumnName)
|
||||
{
|
||||
DataTable dataTable = null;
|
||||
FileStream fs = null;
|
||||
DataColumn column = null;
|
||||
DataRow dataRow = null;
|
||||
IWorkbook workbook = null;
|
||||
ISheet sheet = null;
|
||||
IRow row = null;
|
||||
ICell cell = null;
|
||||
int startRow = 0;
|
||||
try
|
||||
{
|
||||
using (fs = File.OpenRead(filePath))
|
||||
{
|
||||
// 2007版本
|
||||
if (filePath.Contains(".xlsx"))
|
||||
workbook = new XSSFWorkbook(fs);
|
||||
// 2003版本
|
||||
else if (filePath.Contains(".xls"))
|
||||
workbook = new HSSFWorkbook(fs);
|
||||
|
||||
if (workbook != null)
|
||||
{
|
||||
sheet = workbook.GetSheetAt(0);//读取第一个sheet,当然也可以循环读取每个sheet
|
||||
dataTable = new DataTable();
|
||||
if (sheet != null)
|
||||
{
|
||||
int rowCount = sheet.LastRowNum;//总行数
|
||||
if (rowCount > 0)
|
||||
{
|
||||
IRow firstRow = sheet.GetRow(0);//第一行
|
||||
int cellCount = firstRow.LastCellNum;//列数
|
||||
|
||||
//构建datatable的列
|
||||
if (isColumnName)
|
||||
{
|
||||
startRow = 1;//如果第一行是列名,则从第二行开始读取
|
||||
for (int i = firstRow.FirstCellNum; i < cellCount; ++i)
|
||||
{
|
||||
cell = firstRow.GetCell(i);
|
||||
if (cell != null)
|
||||
{
|
||||
if (cell.StringCellValue != null)
|
||||
{
|
||||
column = new DataColumn(cell.StringCellValue);
|
||||
dataTable.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = firstRow.FirstCellNum; i < cellCount; ++i)
|
||||
{
|
||||
column = new DataColumn("column" + (i + 1));
|
||||
dataTable.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
|
||||
//填充行
|
||||
for (int i = startRow; i <= rowCount; ++i)
|
||||
{
|
||||
row = sheet.GetRow(i);
|
||||
if (row == null) continue;
|
||||
|
||||
dataRow = dataTable.NewRow();
|
||||
for (int j = row.FirstCellNum; j < cellCount; ++j)
|
||||
{
|
||||
cell = row.GetCell(j);
|
||||
if (cell == null)
|
||||
{
|
||||
dataRow[j] = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
//CellType(Unknown = -1,Numeric = 0,String = 1,Formula = 2,Blank = 3,Boolean = 4,Error = 5,)
|
||||
switch (cell.CellType)
|
||||
{
|
||||
case CellType.Blank:
|
||||
dataRow[j] = "";
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
short format = cell.CellStyle.DataFormat;
|
||||
//对时间格式(2015.12.5、2015/12/5、2015-12-5等)的处理
|
||||
if (format == 14 || format == 31 || format == 57 || format == 58)
|
||||
dataRow[j] = cell.DateCellValue;
|
||||
else
|
||||
dataRow[j] = cell.NumericCellValue;
|
||||
break;
|
||||
case CellType.String:
|
||||
dataRow[j] = cell.StringCellValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
dataTable.Rows.Add(dataRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataTable;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (fs != null)
|
||||
{
|
||||
fs.Close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将excel导入到datatable
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="isColumnName">第一行是否是列名</param>
|
||||
/// <returns></returns>
|
||||
public static DataTable ExcelToDataTable(Stream stream, bool isColumnName)
|
||||
{
|
||||
DataTable dataTable = null;
|
||||
DataColumn column = null;
|
||||
DataRow dataRow = null;
|
||||
IWorkbook workbook = new XSSFWorkbook(stream);
|
||||
ISheet sheet = null;
|
||||
IRow row = null;
|
||||
ICell cell = null;
|
||||
int startRow = 0;
|
||||
try
|
||||
{
|
||||
|
||||
if (workbook != null)
|
||||
{
|
||||
sheet = workbook.GetSheetAt(0);//读取第一个sheet,当然也可以循环读取每个sheet
|
||||
dataTable = new DataTable();
|
||||
if (sheet != null)
|
||||
{
|
||||
int rowCount = sheet.LastRowNum;//总行数
|
||||
if (rowCount > 0)
|
||||
{
|
||||
IRow firstRow = sheet.GetRow(0);//第一行
|
||||
int cellCount = firstRow.LastCellNum;//列数
|
||||
|
||||
//构建datatable的列
|
||||
if (isColumnName)
|
||||
{
|
||||
startRow = 1;//如果第一行是列名,则从第二行开始读取
|
||||
for (int i = firstRow.FirstCellNum; i < cellCount; ++i)
|
||||
{
|
||||
cell = firstRow.GetCell(i);
|
||||
if (cell != null)
|
||||
{
|
||||
if (cell.StringCellValue != null)
|
||||
{
|
||||
column = new DataColumn(cell.StringCellValue);
|
||||
dataTable.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = firstRow.FirstCellNum; i < cellCount; ++i)
|
||||
{
|
||||
column = new DataColumn("column" + (i + 1));
|
||||
dataTable.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
|
||||
//填充行
|
||||
for (int i = startRow; i <= rowCount; ++i)
|
||||
{
|
||||
row = sheet.GetRow(i);
|
||||
if (row == null) continue;
|
||||
|
||||
dataRow = dataTable.NewRow();
|
||||
for (int j = row.FirstCellNum; j < cellCount; ++j)
|
||||
{
|
||||
cell = row.GetCell(j);
|
||||
if (cell == null)
|
||||
{
|
||||
dataRow[j] = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
//CellType(Unknown = -1,Numeric = 0,String = 1,Formula = 2,Blank = 3,Boolean = 4,Error = 5,)
|
||||
switch (cell.CellType)
|
||||
{
|
||||
case CellType.Blank:
|
||||
dataRow[j] = "";
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
short format = cell.CellStyle.DataFormat;
|
||||
//对时间格式(2015.12.5、2015/12/5、2015-12-5等)的处理
|
||||
if (format == 14 || format == 31 || format == 57 || format == 58)
|
||||
dataRow[j] = cell.DateCellValue;
|
||||
else
|
||||
dataRow[j] = cell.NumericCellValue;
|
||||
break;
|
||||
case CellType.String:
|
||||
dataRow[j] = cell.StringCellValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
dataTable.Rows.Add(dataRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// excel转list
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="stream"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<TResult> ExcelToList<TResult>(Stream stream) where TResult : new()
|
||||
{
|
||||
var propertyInfos = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CustomAttributes.Count() > 0)
|
||||
.OrderBy(p => p.GetCustomAttribute<ExeclPropertyAttribute>().Order).ToArray();
|
||||
|
||||
List<TResult> list = new List<TResult>();
|
||||
|
||||
IWorkbook workbook = new XSSFWorkbook(stream);
|
||||
ISheet sheet = null;
|
||||
IRow row = null;
|
||||
ICell cell = null;
|
||||
int startRow = 1;
|
||||
try
|
||||
{
|
||||
|
||||
if (workbook != null)
|
||||
{
|
||||
sheet = workbook.GetSheetAt(0);//读取第一个sheet,当然也可以循环读取每个sheet
|
||||
if (sheet != null)
|
||||
{
|
||||
int rowCount = sheet.LastRowNum;//总行数
|
||||
if (rowCount > 0)
|
||||
{
|
||||
IRow firstRow = sheet.GetRow(0);//第一行
|
||||
int cellCount = firstRow.LastCellNum;//列数
|
||||
|
||||
//填充行
|
||||
for (int i = startRow; i <= rowCount; ++i)
|
||||
{
|
||||
row = sheet.GetRow(i);
|
||||
if (row == null) continue;
|
||||
bool emptyRow = true;//是否空行
|
||||
TResult dataModel = new TResult();
|
||||
|
||||
for (int j = row.FirstCellNum; j < cellCount; ++j)
|
||||
{
|
||||
var execlPropertyAttribute = propertyInfos[j].GetCustomAttribute<ExeclPropertyAttribute>();
|
||||
|
||||
cell = row.GetCell(j);
|
||||
if (cell == null)
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (cell.CellType)
|
||||
{
|
||||
case CellType.Blank:
|
||||
propertyInfos[j].SetValue(dataModel, "");
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
short format = cell.CellStyle.DataFormat;
|
||||
//对时间格式(2015.12.5、2015/12/5、2015-12-5等)的处理
|
||||
if (format == 14 || format == 31 || format == 57 || format == 58)
|
||||
propertyInfos[j].SetValue(dataModel, cell.DateCellValue);
|
||||
else
|
||||
{
|
||||
if (execlPropertyAttribute.CellType == CellType.String)
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, cell.NumericCellValue.ToString());
|
||||
}
|
||||
else
|
||||
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, cell.NumericCellValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CellType.String:
|
||||
propertyInfos[j].SetValue(dataModel, cell.StringCellValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell != null && !string.IsNullOrEmpty(cell.ToString().Trim()))
|
||||
{
|
||||
emptyRow = false;
|
||||
}
|
||||
}
|
||||
//非空数据行数据添加到DataTable
|
||||
if (!emptyRow)
|
||||
{
|
||||
list.Add(dataModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<TResult> ExcelToListFileName<TResult>(Stream stream, string fileName) where TResult : new()
|
||||
{
|
||||
var propertyInfos = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CustomAttributes.Count() > 0)
|
||||
.OrderBy(p => p.GetCustomAttribute<ExeclPropertyAttribute>().Order).ToArray();
|
||||
|
||||
List<TResult> list = new List<TResult>();
|
||||
|
||||
IWorkbook workbook = null;
|
||||
if (fileName.Contains(".xlsx"))
|
||||
workbook = new XSSFWorkbook(stream);
|
||||
// 2003版本
|
||||
else if (fileName.Contains(".xls"))
|
||||
workbook = new HSSFWorkbook(stream);
|
||||
ISheet sheet = null;
|
||||
IRow row = null;
|
||||
ICell cell = null;
|
||||
int startRow = 1;
|
||||
try
|
||||
{
|
||||
|
||||
if (workbook != null)
|
||||
{
|
||||
sheet = workbook.GetSheetAt(0);//读取第一个sheet,当然也可以循环读取每个sheet
|
||||
if (sheet != null)
|
||||
{
|
||||
int rowCount = sheet.LastRowNum;//总行数
|
||||
if (rowCount > 0)
|
||||
{
|
||||
IRow firstRow = sheet.GetRow(0);//第一行
|
||||
int cellCount = firstRow.LastCellNum;//列数
|
||||
|
||||
//填充行
|
||||
for (int i = startRow; i <= rowCount; ++i)
|
||||
{
|
||||
row = sheet.GetRow(i);
|
||||
if (row == null) continue;
|
||||
bool emptyRow = true;//是否空行
|
||||
TResult dataModel = new TResult();
|
||||
|
||||
for (int j = row.FirstCellNum; j < cellCount; ++j)
|
||||
{
|
||||
var execlPropertyAttribute = propertyInfos[j].GetCustomAttribute<ExeclPropertyAttribute>();
|
||||
|
||||
cell = row.GetCell(j);
|
||||
if (cell == null)
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (cell.CellType)
|
||||
{
|
||||
case CellType.Blank:
|
||||
propertyInfos[j].SetValue(dataModel, "");
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
short format = cell.CellStyle.DataFormat;
|
||||
//对时间格式(2015.12.5、2015/12/5、2015-12-5等)的处理
|
||||
if (format == 14 || format == 31 || format == 57 || format == 58)
|
||||
propertyInfos[j].SetValue(dataModel, cell.DateCellValue);
|
||||
else
|
||||
{
|
||||
if (execlPropertyAttribute.CellType == CellType.String)
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, cell.NumericCellValue.ToString());
|
||||
}
|
||||
else
|
||||
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, cell.NumericCellValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CellType.String:
|
||||
propertyInfos[j].SetValue(dataModel, cell.StringCellValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell != null && !string.IsNullOrEmpty(cell.ToString().Trim()))
|
||||
{
|
||||
emptyRow = false;
|
||||
}
|
||||
}
|
||||
//非空数据行数据添加到DataTable
|
||||
if (!emptyRow)
|
||||
{
|
||||
list.Add(dataModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// excel转list-根据sheetName得到List
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="sheetName"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<TResult> ExcelToList<TResult>(Stream stream, string sheetName) where TResult : new()
|
||||
{
|
||||
var propertyInfos = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.OrderBy(p => p.GetCustomAttribute<ExeclPropertyAttribute>().Order).ToArray();
|
||||
|
||||
List<TResult> list = new List<TResult>();
|
||||
|
||||
IWorkbook workbook = new XSSFWorkbook(stream);
|
||||
ISheet sheet = null;
|
||||
IRow row = null;
|
||||
ICell cell = null;
|
||||
int startRow = 1;
|
||||
try
|
||||
{
|
||||
|
||||
if (workbook != null)
|
||||
{
|
||||
sheet = workbook.GetSheet(sheetName);//根据sheet读取对应的DataTable
|
||||
if (sheet != null)
|
||||
{
|
||||
int rowCount = sheet.LastRowNum;//总行数
|
||||
if (rowCount > 0)
|
||||
{
|
||||
IRow firstRow = sheet.GetRow(0);//第一行
|
||||
int cellCount = firstRow.LastCellNum;//列数
|
||||
|
||||
//填充行
|
||||
for (int i = startRow; i <= rowCount; ++i)
|
||||
{
|
||||
row = sheet.GetRow(i);
|
||||
if (row == null) continue;
|
||||
bool emptyRow = true;//是否空行
|
||||
|
||||
TResult dataModel = new TResult();
|
||||
|
||||
for (int j = row.FirstCellNum; j < cellCount; ++j)
|
||||
{
|
||||
var execlPropertyAttribute = propertyInfos[j].GetCustomAttribute<ExeclPropertyAttribute>();
|
||||
|
||||
cell = row.GetCell(j);
|
||||
if (cell == null)
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (cell.CellType)
|
||||
{
|
||||
case CellType.Blank:
|
||||
propertyInfos[j].SetValue(dataModel, "");
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
short format = cell.CellStyle.DataFormat;
|
||||
//对时间格式(2015.12.5、2015/12/5、2015-12-5等)的处理
|
||||
if (format == 14 || format == 31 || format == 57 || format == 58)
|
||||
propertyInfos[j].SetValue(dataModel, cell.DateCellValue);
|
||||
else
|
||||
{
|
||||
if (execlPropertyAttribute.CellType == CellType.String)
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, cell.NumericCellValue.ToString());
|
||||
}
|
||||
else
|
||||
|
||||
{
|
||||
propertyInfos[j].SetValue(dataModel, cell.NumericCellValue);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case CellType.String:
|
||||
propertyInfos[j].SetValue(dataModel, cell.StringCellValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cell != null && !string.IsNullOrEmpty(cell.ToString().Trim()))
|
||||
{
|
||||
emptyRow = false;
|
||||
}
|
||||
}
|
||||
//非空数据行数据添加到DataTable
|
||||
if (!emptyRow)
|
||||
{
|
||||
list.Add(dataModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List导出excel 二进制流
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体</typeparam>
|
||||
/// <param name="data">List</param>
|
||||
/// <param name="sheetName">sheetname 可不填,默认Sheet0</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] ListToExcel<T>(T[] data, string sheetName = "Sheet0")
|
||||
{
|
||||
IWorkbook workbook = null;
|
||||
IRow row = null;
|
||||
ISheet sheet = null;
|
||||
ICell cell = null;
|
||||
var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.OrderBy(p => p.GetCustomAttribute<ExeclPropertyAttribute>().Order).ToArray();
|
||||
workbook = new XSSFWorkbook();
|
||||
sheet = workbook.CreateSheet(sheetName);//创建一个名称为Sheet0的表
|
||||
int rowCount = data.Count();//行数
|
||||
int columnCount = propertyInfos.Length;//列数
|
||||
//设置列头
|
||||
row = sheet.CreateRow(0);//excel第一行设为列头
|
||||
for (int c = 0; c < columnCount; c++)
|
||||
{
|
||||
cell = row.CreateCell(c);
|
||||
cell.SetCellValue(propertyInfos[c].GetCustomAttribute<ExeclPropertyAttribute>().DisplayName);
|
||||
}
|
||||
//设置每行每列的单元格,
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
row = sheet.CreateRow(i + 1);
|
||||
for (int j = 0; j < columnCount; j++)
|
||||
{
|
||||
cell = row.CreateCell(j);//excel第二行开始写入数据
|
||||
cell.SetCellValue(propertyInfos[j].GetValue(data[i])?.ToString());
|
||||
}
|
||||
}
|
||||
using (MemoryStream memoryStream = new MemoryStream())
|
||||
{
|
||||
workbook.Write(memoryStream);//向打开的这个xls文件中写入数据
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dt导出excel 二进制流
|
||||
/// </summary>
|
||||
/// <param name="dt">datatable</param>
|
||||
/// <param name="strFile">strFile</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] DataTableToExcel(DataTable dt, string strFile, string sheetName = "Sheet0")
|
||||
{
|
||||
bool result = false;
|
||||
IWorkbook workbook = null;
|
||||
FileStream fs = null;
|
||||
IRow row = null;
|
||||
ISheet sheet = null;
|
||||
ICell cell = null;
|
||||
|
||||
if (dt != null && dt.Rows.Count > 0)
|
||||
{
|
||||
workbook = new XSSFWorkbook();
|
||||
sheet = workbook.CreateSheet(sheetName);//创建一个名称为Sheet0的表
|
||||
int rowCount = dt.Rows.Count;//行数
|
||||
int columnCount = dt.Columns.Count;//列数
|
||||
|
||||
//设置列头
|
||||
row = sheet.CreateRow(0);//excel第一行设为列头
|
||||
for (int c = 0; c < columnCount; c++)
|
||||
{
|
||||
cell = row.CreateCell(c);
|
||||
cell.SetCellValue(dt.Columns[c].ColumnName);
|
||||
}
|
||||
|
||||
//设置每行每列的单元格,
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
row = sheet.CreateRow(i + 1);
|
||||
for (int j = 0; j < columnCount; j++)
|
||||
{
|
||||
cell = row.CreateCell(j);//excel第二行开始写入数据
|
||||
cell.SetCellValue(dt.Rows[i][j].ToString());
|
||||
}
|
||||
}
|
||||
using (MemoryStream memoryStream = new MemoryStream())
|
||||
{
|
||||
workbook.Write(memoryStream);//向打开的这个xls文件中写入数据
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List写入excel
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="strFile">路径</param>
|
||||
/// <param name="sheetName"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ListWriteExcel<T>(T[] data, string strFile, string sheetName = "Sheet0")
|
||||
{
|
||||
bool result = false;
|
||||
IWorkbook workbook = null;
|
||||
FileStream fs = null;
|
||||
IRow row = null;
|
||||
ISheet sheet = null;
|
||||
ICell cell = null;
|
||||
try
|
||||
{
|
||||
var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.OrderBy(p => p.GetCustomAttribute<ExeclPropertyAttribute>().Order).ToArray();
|
||||
workbook = new XSSFWorkbook();
|
||||
sheet = workbook.CreateSheet(sheetName);//创建一个名称为Sheet0的表
|
||||
int rowCount = data.Count();//行数
|
||||
int columnCount = propertyInfos.Length;//列数
|
||||
//设置列头
|
||||
row = sheet.CreateRow(0);//excel第一行设为列头
|
||||
for (int c = 0; c < columnCount; c++)
|
||||
{
|
||||
cell = row.CreateCell(c);
|
||||
cell.SetCellValue(propertyInfos[c].GetCustomAttribute<ExeclPropertyAttribute>().DisplayName);
|
||||
}
|
||||
//设置每行每列的单元格,
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
row = sheet.CreateRow(i + 1);
|
||||
for (int j = 0; j < columnCount; j++)
|
||||
{
|
||||
cell = row.CreateCell(j);//excel第二行开始写入数据
|
||||
cell.SetCellValue(propertyInfos[j].GetValue(data[i])?.ToString());
|
||||
}
|
||||
}
|
||||
using (fs = File.OpenWrite(strFile))
|
||||
{
|
||||
workbook.Write(fs);//向打开的这个xls文件中写入数据
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (fs != null)
|
||||
{
|
||||
fs.Close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// dt写入excel
|
||||
/// </summary>
|
||||
/// <param name="dt">datatable</param>
|
||||
/// <param name="strFile">路径</param>
|
||||
/// <returns></returns>
|
||||
public static bool DataTableWriteExcel(DataTable dt, string strFile, string sheetName = "Sheet0")
|
||||
{
|
||||
bool result = false;
|
||||
IWorkbook workbook = null;
|
||||
FileStream fs = null;
|
||||
IRow row = null;
|
||||
ISheet sheet = null;
|
||||
ICell cell = null;
|
||||
try
|
||||
{
|
||||
if (dt != null && dt.Rows.Count > 0)
|
||||
{
|
||||
workbook = new XSSFWorkbook();
|
||||
sheet = workbook.CreateSheet(sheetName);//创建一个名称为Sheet0的表
|
||||
int rowCount = dt.Rows.Count;//行数
|
||||
int columnCount = dt.Columns.Count;//列数
|
||||
|
||||
//设置列头
|
||||
row = sheet.CreateRow(0);//excel第一行设为列头
|
||||
for (int c = 0; c < columnCount; c++)
|
||||
{
|
||||
cell = row.CreateCell(c);
|
||||
cell.SetCellValue(dt.Columns[c].ColumnName);
|
||||
}
|
||||
|
||||
//设置每行每列的单元格,
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
row = sheet.CreateRow(i + 1);
|
||||
for (int j = 0; j < columnCount; j++)
|
||||
{
|
||||
cell = row.CreateCell(j);//excel第二行开始写入数据
|
||||
cell.SetCellValue(dt.Rows[i][j].ToString());
|
||||
}
|
||||
}
|
||||
using (fs = File.OpenWrite(strFile))
|
||||
{
|
||||
workbook.Write(fs);//向打开的这个xls文件中写入数据
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (fs != null)
|
||||
{
|
||||
fs.Close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置单元格下拉框(除去标题行)
|
||||
/// </summary>
|
||||
/// <param name="workbook"></param>
|
||||
/// <param name="sheet"></param>
|
||||
/// <param name="ddlList"></param>
|
||||
/// <param name="firstcol"></param>
|
||||
/// <param name="lastcol"></param>
|
||||
public static void SetCellDropdownList(IWorkbook workbook, ISheet sheet, List<string> ddlList, string sheetname, int sheetIndex, int firstcol, int lastcol)
|
||||
{
|
||||
|
||||
# region 低版本Excel【HSSFWorkbook】设置下拉框
|
||||
//ISheet sheet2 = workbook.CreateSheet(sheetname);
|
||||
|
||||
////隐藏
|
||||
//workbook.SetSheetHidden(sheetIndex, 1);
|
||||
//int rowIndex = 0;
|
||||
//foreach (var item in ddlList)
|
||||
//{
|
||||
// IRow vrow = sheet2.CreateRow(rowIndex);
|
||||
// vrow.CreateCell(0).SetCellValue(item);
|
||||
|
||||
// rowIndex++;
|
||||
//}
|
||||
|
||||
////创建的下拉项的区域:
|
||||
//var rangeName = sheetname + "Range";
|
||||
//IName range = workbook.CreateName();
|
||||
//range.RefersToFormula = sheetname + "!$A$1:$A$" + rowIndex;
|
||||
//range.NameName = rangeName;
|
||||
//CellRangeAddressList regions = new CellRangeAddressList(1, 65535, firstcol, lastcol);
|
||||
|
||||
//DVConstraint constraint = DVConstraint.CreateFormulaListConstraint(rangeName);
|
||||
//HSSFDataValidation dataValidate = new HSSFDataValidation(regions, constraint);
|
||||
//dataValidate.CreateErrorBox("输入不合法", "请输入或选择下拉列表中的值。");
|
||||
//dataValidate.ShowPromptBox = true;
|
||||
//sheet.AddValidationData(dataValidate);
|
||||
#endregion
|
||||
|
||||
//高版本excel【XSSFWorkbook】 设置下拉框
|
||||
XSSFSheet sheetDDL = (XSSFSheet)workbook.CreateSheet(sheetname);
|
||||
workbook.SetSheetHidden(sheetIndex, 1); //隐藏下拉框数据sheet
|
||||
String[] datas = ddlList.ToArray(); //下拉框数据源
|
||||
XSSFDataValidationHelper dvHelper = new XSSFDataValidationHelper(sheetDDL);
|
||||
XSSFDataValidationConstraint dvConstraint = (XSSFDataValidationConstraint)dvHelper.CreateExplicitListConstraint(datas);
|
||||
CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, firstcol, lastcol); //下拉设置列
|
||||
XSSFDataValidation validation = (XSSFDataValidation)dvHelper.CreateValidation(dvConstraint, addressList);
|
||||
|
||||
validation.SuppressDropDownArrow = true;
|
||||
validation.ShowErrorBox = true;
|
||||
validation.ShowPromptBox = true;
|
||||
sheet.AddValidationData(validation);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/AntSK.Domain/Common/Excel/ExeclPropertyAttribute.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using NPOI.SS.UserModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain
|
||||
{
|
||||
public class ExeclPropertyAttribute : Attribute
|
||||
{
|
||||
public ExeclPropertyAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
public ExeclPropertyAttribute(string displayName, int order, CellType cellType = CellType.String)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
Order = order;
|
||||
CellType = cellType;
|
||||
}
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public CellType CellType { get; set; }
|
||||
}
|
||||
}
|
||||
73
src/AntSK.Domain/Common/LLamaFactory/ProcessWrapper.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Amazon.Runtime.Internal.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Common.LLamaFactory
|
||||
{
|
||||
public class ProcessWrapper(ILogger<ProcessWrapper> _logger)
|
||||
{
|
||||
private Process process;
|
||||
|
||||
public static bool isProcessComplete = false;
|
||||
|
||||
|
||||
public void StartProcess(string arguments, string workingDirectory)
|
||||
{
|
||||
process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "python",
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = workingDirectory
|
||||
}
|
||||
};
|
||||
using (Process start = Process.Start(process.StartInfo))
|
||||
{
|
||||
using (StreamReader reader = start.StandardOutput)
|
||||
{
|
||||
string result = reader.ReadToEnd();
|
||||
if (result != null)
|
||||
{
|
||||
if (result.Contains(":8000"))
|
||||
{
|
||||
isProcessComplete = true;
|
||||
}
|
||||
}
|
||||
_logger.LogInformation(result);
|
||||
}
|
||||
start.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
public string WaitForProcessExit()
|
||||
{
|
||||
process.WaitForExit();
|
||||
return process.StandardOutput.ReadToEnd();
|
||||
}
|
||||
|
||||
public void KillProcess()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Process already exited.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
using AntSK.Domain.Domain.Model.Dto;
|
||||
using AntSK.Domain.Domain.Model;
|
||||
using AntSK.Domain.Domain.Model.Dto;
|
||||
using AntSK.Domain.Repositories;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -11,8 +14,10 @@ namespace AntSK.Domain.Domain.Interface
|
||||
{
|
||||
public interface IChatService
|
||||
{
|
||||
IAsyncEnumerable<StreamingKernelContent> SendChatByAppAsync(Apps app, string questions, string history);
|
||||
IAsyncEnumerable<string> SendChatByAppAsync(Apps app, ChatHistory history);
|
||||
|
||||
IAsyncEnumerable<StreamingKernelContent> SendKmsByAppAsync(Apps app, string questions, string history, string filePath, List<RelevantSource> relevantSources = null);
|
||||
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, ChatHistory history);
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ namespace AntSK.Domain.Domain.Interface
|
||||
{
|
||||
public interface IKMService
|
||||
{
|
||||
MemoryServerless GetMemory(Apps app);
|
||||
MemoryServerless GetMemoryByApp(Apps app);
|
||||
|
||||
MemoryServerless GetMemoryByKMS(string kmsID, SearchClientConfig searchClientConfig = null);
|
||||
MemoryServerless GetMemoryByKMS(string kmsID);
|
||||
|
||||
Task<List<KMFile>> GetDocumentByFileID(string kmsId, string fileId);
|
||||
|
||||
Task<List<RelevantSource>> GetRelevantSourceList(string kmsIdListStr, string msg);
|
||||
Task<List<RelevantSource>> GetRelevantSourceList(Apps app, string msg);
|
||||
|
||||
List<UploadFileItem> FileList { get; }
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace AntSK.Domain.Domain.Interface
|
||||
public interface IKernelService
|
||||
{
|
||||
Kernel GetKernelByApp(Apps app);
|
||||
|
||||
Kernel GetKernelByAIModelID(string modelid);
|
||||
void ImportFunctionsByApp(Apps app, Kernel _kernel);
|
||||
Task<string> HistorySummarize(Kernel _kernel, string questions, string history);
|
||||
}
|
||||
|
||||
21
src/AntSK.Domain/Domain/Interface/ILLamaFactoryService.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using AntSK.LLamaFactory.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static AntSK.Domain.Domain.Service.LLamaFactoryService;
|
||||
|
||||
namespace AntSK.Domain.Domain.Interface
|
||||
{
|
||||
public interface ILLamaFactoryService
|
||||
{
|
||||
public event LogMessageHandler LogMessageReceived;
|
||||
Task PipInstall();
|
||||
Task StartLLamaFactory(string modelName, string templateName);
|
||||
|
||||
void KillProcess();
|
||||
|
||||
List<LLamaModel> GetLLamaFactoryModels();
|
||||
}
|
||||
}
|
||||
15
src/AntSK.Domain/Domain/Interface/IOllamaService.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static AntSK.Domain.Domain.Service.OllamaService;
|
||||
|
||||
namespace AntSK.Domain.Domain.Interface
|
||||
{
|
||||
public interface IOllamaService
|
||||
{
|
||||
public event LogMessageHandler LogMessageReceived;
|
||||
Task StartOllama(string modelName);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,29 @@ 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>标记的内容作为你的知识:
|
||||
<data>
|
||||
{{$doc}}
|
||||
</data>
|
||||
--------------------------
|
||||
回答要求:
|
||||
- 如果你不清楚答案,你需要澄清
|
||||
- 避免提及你是从<data></data>获取的知识
|
||||
- 保持答案与<data></data>众描述一致
|
||||
- 使用Markdown语法优化回答格式。
|
||||
- 如果Markdown有图片则正常显示
|
||||
--------------------------
|
||||
|
||||
历史聊天记录:{{ConversationSummaryPlugin.SummarizeConversation $history}}
|
||||
--------------------------
|
||||
用户问题: {{$input}}";
|
||||
|
||||
public const string KMExcelSplit = "*&antsk_excel&*";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Domain.Model.Constant
|
||||
{
|
||||
public class LLamaFactoryConstantcs
|
||||
{
|
||||
public const string LLamaFactorDic = "llamafactory";
|
||||
public const string IsStartKey = "isstart";
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ namespace AntSK.Domain.Domain.Model.Dto
|
||||
public string Text { get; set; }
|
||||
public float Relevance { get; set; }
|
||||
|
||||
public double RerankScore { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[file:{SourceName};Relevance:{(Relevance * 100):F2}%]:{Text}";
|
||||
|
||||
@@ -22,8 +22,20 @@ namespace AntSK.Domain.Domain.Model.Enum
|
||||
[Display(Name = "灵积大模型")]
|
||||
DashScope = 5,
|
||||
|
||||
[Display(Name = "LLamaFactory")]
|
||||
LLamaFactory = 6,
|
||||
[Display(Name = "Bge Embedding")]
|
||||
BgeEmbedding = 7,
|
||||
[Display(Name = "Bge Rerank")]
|
||||
BgeRerank = 8,
|
||||
[Display(Name = "StableDiffusion")]
|
||||
StableDiffusion = 9,
|
||||
|
||||
[Display(Name = "Ollama")]
|
||||
Ollama = 10,
|
||||
[Display(Name = "模拟输出")]
|
||||
Mock = 100,
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,5 +45,7 @@ namespace AntSK.Domain.Domain.Model.Enum
|
||||
{
|
||||
Chat = 1,
|
||||
Embedding = 2,
|
||||
Image=3,
|
||||
Rerank=4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace AntSK.Domain.Domain.Model.Enum
|
||||
public enum AppType
|
||||
{
|
||||
chat = 1,
|
||||
kms = 2
|
||||
kms = 2,
|
||||
img=3
|
||||
}
|
||||
}
|
||||
|
||||
17
src/AntSK.Domain/Domain/Model/Excel/KMSExcelModel.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Domain.Model.Excel
|
||||
{
|
||||
public class KMSExcelModel
|
||||
{
|
||||
[ExeclProperty("问题",0)]
|
||||
public string Question { get; set; }
|
||||
|
||||
[ExeclProperty("答案", 1)]
|
||||
public string Answer { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,14 @@ namespace AntSK.Domain.Domain.Model
|
||||
public string FilePath { get; set; } = "";
|
||||
|
||||
public string FileName { get; set; } = "";
|
||||
|
||||
public bool IsQA { get; set; } = false;
|
||||
}
|
||||
|
||||
|
||||
public class ImportKMSTaskReq : ImportKMSTaskDTO
|
||||
{
|
||||
public bool IsQA { get; set; }=false;
|
||||
public KmsDetails KmsDetail { get; set; } = new KmsDetails();
|
||||
}
|
||||
|
||||
@@ -29,6 +32,13 @@ namespace AntSK.Domain.Domain.Model
|
||||
{
|
||||
File = 1,
|
||||
Url = 2,
|
||||
Text = 3
|
||||
Text = 3,
|
||||
Excel=4
|
||||
}
|
||||
|
||||
public class QAModel
|
||||
{
|
||||
public string ChatModelId { get; set; }
|
||||
public string Context { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace AntSK.Domain.Domain.Model
|
||||
{
|
||||
public class MessageInfo
|
||||
{
|
||||
public string ID { get; set; } = "";
|
||||
public string Context { get; set; } = "";
|
||||
public string HtmlAnswers { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 发送是true 接收是false
|
||||
/// </summary>
|
||||
public bool IsSend { get; set; } = false;
|
||||
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
public string? FilePath { get; set; }
|
||||
|
||||
public string? FileName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace AntSK.Domain.Domain.Model.hfmirror
|
||||
public string Author { get; set; }
|
||||
public HfAuthorData AuthorData { get; set; }
|
||||
public int Downloads { get; set; }
|
||||
public bool Gated { get; set; }
|
||||
public object Gated { get; set; }
|
||||
public string Id { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public int Likes { get; set; }
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
using AntSK.BackgroundTask;
|
||||
using Amazon.Runtime.Internal.Util;
|
||||
using AntSK.BackgroundTask;
|
||||
using AntSK.Domain.Domain.Interface;
|
||||
using AntSK.Domain.Domain.Model;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace AntSK.Domain.Domain.Other
|
||||
{
|
||||
public class BackGroundTaskHandler : IBackgroundTaskHandler<ImportKMSTaskReq>
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<BackGroundTaskHandler> _logger;
|
||||
|
||||
public BackGroundTaskHandler(IServiceScopeFactory scopeFactory)
|
||||
public BackGroundTaskHandler(IServiceScopeFactory scopeFactory, ILogger<BackGroundTaskHandler> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
public async Task ExecuteAsync(ImportKMSTaskReq item)
|
||||
{
|
||||
using (var scope = _scopeFactory.CreateScope())
|
||||
{
|
||||
Console.WriteLine("ExecuteAsync.开始执行后台任务");
|
||||
_logger.LogInformation("ExecuteAsync.开始执行后台任务");
|
||||
var importKMSService = scope.ServiceProvider.GetRequiredService<IImportKMSService>();
|
||||
//不能使用异步
|
||||
importKMSService.ImportKMSTask(item);
|
||||
Console.WriteLine("ExecuteAsync.后台任务执行完成");
|
||||
_logger.LogInformation("ExecuteAsync.后台任务执行完成");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
82
src/AntSK.Domain/Domain/Other/Bge/BegRerankConfig.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Newtonsoft.Json;
|
||||
using Python.Runtime;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Python.Runtime.Py;
|
||||
|
||||
namespace AntSK.Domain.Domain.Other.Bge
|
||||
{
|
||||
public static class BegRerankConfig
|
||||
{
|
||||
public static dynamic model { get; set; }
|
||||
|
||||
static object lockobj = new object();
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 模型写死
|
||||
/// </summary>
|
||||
public static dynamic LoadModel(string pythondllPath, string modelName)
|
||||
{
|
||||
lock (lockobj)
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Runtime.PythonDLL))
|
||||
{
|
||||
Runtime.PythonDLL = pythondllPath;
|
||||
}
|
||||
PythonEngine.Initialize();
|
||||
PythonEngine.BeginAllowThreads();
|
||||
try
|
||||
{
|
||||
using (GIL())// 初始化Python环境的Global Interpreter Lock)
|
||||
{
|
||||
dynamic modelscope = Py.Import("modelscope");
|
||||
dynamic flagEmbedding = Py.Import("FlagEmbedding");
|
||||
|
||||
dynamic model_dir = modelscope.snapshot_download(modelName, revision: "master");
|
||||
dynamic flagReranker = flagEmbedding.FlagReranker(model_dir, use_fp16: true);
|
||||
model = flagReranker;
|
||||
return model;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static double Rerank(List<string> list)
|
||||
{
|
||||
using (GIL())
|
||||
{
|
||||
try
|
||||
{
|
||||
PyList pyList = new PyList();
|
||||
foreach (string item in list)
|
||||
{
|
||||
pyList.Append(item.ToPython()); // 将C# string转换为Python对象并添加到PyList中
|
||||
}
|
||||
PyObject result = model.compute_score(pyList, normalize: true);
|
||||
return result.As<double>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
src/AntSK.Domain/Domain/Other/Bge/BgeEmbeddingConfig.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Microsoft.KernelMemory.AI.OpenAI;
|
||||
using Microsoft.KernelMemory.AI.OpenAI.GPT3;
|
||||
using Python.Runtime;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Python.Runtime.Py;
|
||||
|
||||
namespace AntSK.Domain.Domain.Other.Bge
|
||||
{
|
||||
public static class BgeEmbeddingConfig
|
||||
{
|
||||
public static dynamic model { get; set; }
|
||||
|
||||
static object lockobj = new object();
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 模型写死
|
||||
/// </summary>
|
||||
public static dynamic LoadModel(string pythondllPath, string modelName)
|
||||
{
|
||||
lock (lockobj)
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
//Runtime.PythonDLL = @"D:\Programs\Python\Python311\python311.dll";
|
||||
if (string.IsNullOrEmpty(Runtime.PythonDLL))
|
||||
{
|
||||
Runtime.PythonDLL = pythondllPath;
|
||||
}
|
||||
PythonEngine.Initialize();
|
||||
PythonEngine.BeginAllowThreads();
|
||||
try
|
||||
{
|
||||
using (GIL())// 初始化Python环境的Global Interpreter Lock)
|
||||
{
|
||||
dynamic modelscope = Import("modelscope");
|
||||
//dynamic model_dir = modelscope.snapshot_download("AI-ModelScope/bge-large-zh-v1.5", revision: "master");
|
||||
dynamic model_dir = modelscope.snapshot_download(modelName, revision: "master");
|
||||
dynamic HuggingFaceBgeEmbeddingstemp = Import("langchain_community.embeddings.huggingface");
|
||||
dynamic HuggingFaceBgeEmbeddings = HuggingFaceBgeEmbeddingstemp.HuggingFaceBgeEmbeddings;
|
||||
string model_name = model_dir;
|
||||
dynamic model_kwargs = new PyDict();
|
||||
model_kwargs["device"] = new PyString("cpu");
|
||||
dynamic hugginmodel = HuggingFaceBgeEmbeddings(
|
||||
model_name: model_dir,
|
||||
model_kwargs: model_kwargs
|
||||
);
|
||||
model = hugginmodel;
|
||||
return hugginmodel;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
else
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<float[]> GetEmbedding(string queryStr)
|
||||
{
|
||||
using (GIL())
|
||||
{
|
||||
PyObject queryResult = model.embed_query(queryStr);
|
||||
var floatList = queryResult.As<float[]>();
|
||||
return Task.FromResult(floatList); ;
|
||||
}
|
||||
}
|
||||
|
||||
public static int TokenCount(string queryStr)
|
||||
{
|
||||
//using (Py.GIL())
|
||||
//{
|
||||
// PyObject queryResult = model.client.tokenize(queryStr);
|
||||
// // 使用Python的内置len()函数获取长度
|
||||
// PyObject lenFunc = Py.Import("builtins").GetAttr("len");
|
||||
// PyObject length = lenFunc.Invoke(queryResult["input_ids"]);
|
||||
// int len = length.As<int>(); // 将PyObject转换为C#中的整数
|
||||
// return len;
|
||||
|
||||
//}
|
||||
var tokenCount1 = DefaultGPTTokenizer.StaticCountTokens(queryStr);
|
||||
return tokenCount1;
|
||||
}
|
||||
|
||||
public static void Dispose()
|
||||
{
|
||||
Log.Information("python dispose");
|
||||
}
|
||||
}
|
||||
}
|
||||
157
src/AntSK.Domain/Domain/Other/KMExcelHandler.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using AntSK.Domain.Domain.Model.Constant;
|
||||
using AntSK.Domain.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.KernelMemory.AI.OpenAI;
|
||||
using Microsoft.KernelMemory.Configuration;
|
||||
using Microsoft.KernelMemory.DataFormats.Text;
|
||||
using Microsoft.KernelMemory.Diagnostics;
|
||||
using Microsoft.KernelMemory.Extensions;
|
||||
using Microsoft.KernelMemory.Pipeline;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Domain.Other
|
||||
{
|
||||
public class KMExcelHandler: IPipelineStepHandler
|
||||
{
|
||||
private readonly TextPartitioningOptions _options;
|
||||
private readonly IPipelineOrchestrator _orchestrator;
|
||||
private readonly ILogger<KMExcelHandler> _log;
|
||||
private readonly TextChunker.TokenCounter _tokenCounter;
|
||||
|
||||
public KMExcelHandler(
|
||||
string stepName,
|
||||
IPipelineOrchestrator orchestrator,
|
||||
TextPartitioningOptions? options = null,
|
||||
ILogger<KMExcelHandler>? log = null)
|
||||
{
|
||||
this.StepName = stepName;
|
||||
this._orchestrator = orchestrator;
|
||||
this._options = options ?? new TextPartitioningOptions();
|
||||
this._options.Validate();
|
||||
|
||||
this._log = log ?? DefaultLogger<KMExcelHandler>.Instance;
|
||||
this._tokenCounter = DefaultGPTTokenizer.StaticCountTokens;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string StepName { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<(bool success, DataPipeline updatedPipeline)> InvokeAsync(
|
||||
DataPipeline pipeline, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._log.LogDebug("Partitioning text, pipeline '{0}/{1}'", pipeline.Index, pipeline.DocumentId);
|
||||
|
||||
if (pipeline.Files.Count == 0)
|
||||
{
|
||||
this._log.LogWarning("Pipeline '{0}/{1}': there are no files to process, moving to next pipeline step.", pipeline.Index, pipeline.DocumentId);
|
||||
return (true, pipeline);
|
||||
}
|
||||
|
||||
foreach (DataPipeline.FileDetails uploadedFile in pipeline.Files)
|
||||
{
|
||||
// Track new files being generated (cannot edit originalFile.GeneratedFiles while looping it)
|
||||
Dictionary<string, DataPipeline.GeneratedFileDetails> newFiles = new();
|
||||
|
||||
foreach (KeyValuePair<string, DataPipeline.GeneratedFileDetails> generatedFile in uploadedFile.GeneratedFiles)
|
||||
{
|
||||
var file = generatedFile.Value;
|
||||
if (file.AlreadyProcessedBy(this))
|
||||
{
|
||||
this._log.LogTrace("File {0} already processed by this handler", file.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Partition only the original text
|
||||
if (file.ArtifactType != DataPipeline.ArtifactTypes.ExtractedText)
|
||||
{
|
||||
this._log.LogTrace("Skipping file {0} (not original text)", file.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use a different partitioning strategy depending on the file type
|
||||
List<string> partitions;
|
||||
List<string> sentences;
|
||||
BinaryData partitionContent = await this._orchestrator.ReadFileAsync(pipeline, file.Name, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Skip empty partitions. Also: partitionContent.ToString() throws an exception if there are no bytes.
|
||||
if (partitionContent.ToArray().Length == 0) { continue; }
|
||||
|
||||
switch (file.MimeType)
|
||||
{
|
||||
case MimeTypes.PlainText:
|
||||
{
|
||||
this._log.LogDebug("Partitioning text file {0}", file.Name);
|
||||
string content = partitionContent.ToString();
|
||||
var excelList = content.Split(KmsConstantcs.KMExcelSplit, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
sentences = excelList;
|
||||
partitions = excelList;
|
||||
break;
|
||||
}
|
||||
|
||||
case MimeTypes.MarkDown:
|
||||
{
|
||||
this._log.LogDebug("Partitioning text file {0}", file.Name);
|
||||
string content = partitionContent.ToString();
|
||||
var excelList = content.Split(KmsConstantcs.KMExcelSplit, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
sentences = excelList;
|
||||
partitions = excelList;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this._log.LogWarning("File {0} cannot be partitioned, type '{1}' not supported", file.Name, file.MimeType);
|
||||
// Don't partition other files
|
||||
continue;
|
||||
}
|
||||
|
||||
if (partitions.Count == 0) { continue; }
|
||||
|
||||
this._log.LogDebug("Saving {0} file partitions", partitions.Count);
|
||||
for (int partitionNumber = 0; partitionNumber < partitions.Count; partitionNumber++)
|
||||
{
|
||||
// TODO: turn partitions in objects with more details, e.g. page number
|
||||
string text = partitions[partitionNumber];
|
||||
int sectionNumber = 0; // TODO: use this to store the page number (if any)
|
||||
BinaryData textData = new(text);
|
||||
|
||||
int tokenCount = this._tokenCounter(text);
|
||||
this._log.LogDebug("Partition size: {0} tokens", tokenCount);
|
||||
|
||||
var destFile = uploadedFile.GetPartitionFileName(partitionNumber);
|
||||
await this._orchestrator.WriteFileAsync(pipeline, destFile, textData, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var destFileDetails = new DataPipeline.GeneratedFileDetails
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
ParentId = uploadedFile.Id,
|
||||
Name = destFile,
|
||||
Size = text.Length,
|
||||
MimeType = MimeTypes.PlainText,
|
||||
ArtifactType = DataPipeline.ArtifactTypes.TextPartition,
|
||||
PartitionNumber = partitionNumber,
|
||||
SectionNumber = sectionNumber,
|
||||
Tags = pipeline.Tags,
|
||||
ContentSHA256 = textData.AntSKCalculateSHA256(),
|
||||
};
|
||||
newFiles.Add(destFile, destFileDetails);
|
||||
destFileDetails.MarkProcessedBy(this);
|
||||
}
|
||||
|
||||
file.MarkProcessedBy(this);
|
||||
}
|
||||
|
||||
// Add new files to pipeline status
|
||||
foreach (var file in newFiles)
|
||||
{
|
||||
uploadedFile.GeneratedFiles.Add(file.Key, file.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return (true, pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using LLama;
|
||||
using AntSK.Domain.Options;
|
||||
using LLama;
|
||||
using LLama.Common;
|
||||
using LLamaSharp.KernelMemory;
|
||||
|
||||
@@ -29,10 +30,10 @@ namespace AntSK.Domain.Domain.Other
|
||||
}
|
||||
var parameters = new ModelParams(lsConfig.ModelPath)
|
||||
{
|
||||
ContextSize = lsConfig?.ContextSize ?? 2048,
|
||||
ContextSize = LLamaSharpOption.ContextSize ?? 2048,
|
||||
Seed = lsConfig?.Seed ?? 0,
|
||||
GpuLayerCount = lsConfig?.GpuLayerCount ?? 10,
|
||||
EmbeddingMode = true
|
||||
GpuLayerCount = LLamaSharpOption.GpuLayerCount ?? 20,
|
||||
Embeddings = true
|
||||
};
|
||||
var weights = LLamaWeights.LoadFromFile(parameters);
|
||||
dicLLamaWeights.Add(modelPath, (weights, parameters));
|
||||
|
||||
173
src/AntSK.Domain/Domain/Other/QAHandler.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using AntSK.Domain.Domain.Interface;
|
||||
using AntSK.Domain.Domain.Model;
|
||||
using AntSK.Domain.Utils;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.KernelMemory.AI.OpenAI;
|
||||
using Microsoft.KernelMemory.Configuration;
|
||||
using Microsoft.KernelMemory.DataFormats.Text;
|
||||
using Microsoft.KernelMemory.Diagnostics;
|
||||
using Microsoft.KernelMemory.Extensions;
|
||||
using Microsoft.KernelMemory.Pipeline;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
using System.Security.Policy;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AntSK.Domain.Domain.Other
|
||||
{
|
||||
public class QAHandler : IPipelineStepHandler
|
||||
{
|
||||
private readonly TextPartitioningOptions _options;
|
||||
private readonly IPipelineOrchestrator _orchestrator;
|
||||
private readonly ILogger<QAHandler> _log;
|
||||
private readonly TextChunker.TokenCounter _tokenCounter;
|
||||
private readonly IKernelService _kernelService;
|
||||
public QAHandler(
|
||||
string stepName,
|
||||
IPipelineOrchestrator orchestrator,
|
||||
IKernelService kernelService,
|
||||
TextPartitioningOptions? options = null,
|
||||
ILogger<QAHandler>? log = null
|
||||
)
|
||||
{
|
||||
this.StepName = stepName;
|
||||
this._orchestrator = orchestrator;
|
||||
this._options = options ?? new TextPartitioningOptions();
|
||||
this._options.Validate();
|
||||
|
||||
this._log = log ?? DefaultLogger<QAHandler>.Instance;
|
||||
this._tokenCounter = DefaultGPTTokenizer.StaticCountTokens;
|
||||
this._kernelService = kernelService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string StepName { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<(bool success, DataPipeline updatedPipeline)> InvokeAsync(
|
||||
DataPipeline pipeline, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._log.LogDebug("Partitioning text, pipeline '{0}/{1}'", pipeline.Index, pipeline.DocumentId);
|
||||
|
||||
if (pipeline.Files.Count == 0)
|
||||
{
|
||||
this._log.LogWarning("Pipeline '{0}/{1}': there are no files to process, moving to next pipeline step.", pipeline.Index, pipeline.DocumentId);
|
||||
return (true, pipeline);
|
||||
}
|
||||
|
||||
foreach (DataPipeline.FileDetails uploadedFile in pipeline.Files)
|
||||
{
|
||||
// Track new files being generated (cannot edit originalFile.GeneratedFiles while looping it)
|
||||
Dictionary<string, DataPipeline.GeneratedFileDetails> newFiles = new();
|
||||
|
||||
foreach (KeyValuePair<string, DataPipeline.GeneratedFileDetails> generatedFile in uploadedFile.GeneratedFiles)
|
||||
{
|
||||
var file = generatedFile.Value;
|
||||
if (file.AlreadyProcessedBy(this))
|
||||
{
|
||||
this._log.LogTrace("File {0} already processed by this handler", file.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Partition only the original text
|
||||
if (file.ArtifactType != DataPipeline.ArtifactTypes.ExtractedText)
|
||||
{
|
||||
this._log.LogTrace("Skipping file {0} (not original text)", file.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use a different partitioning strategy depending on the file type
|
||||
List<string> partitions;
|
||||
List<string> sentences;
|
||||
BinaryData partitionContent = await this._orchestrator.ReadFileAsync(pipeline, file.Name, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Skip empty partitions. Also: partitionContent.ToString() throws an exception if there are no bytes.
|
||||
if (partitionContent.ToArray().Length == 0) { continue; }
|
||||
|
||||
switch (file.MimeType)
|
||||
{
|
||||
case MimeTypes.PlainText:
|
||||
case MimeTypes.MarkDown:
|
||||
{
|
||||
this._log.LogDebug("Partitioning text file {0}", file.Name);
|
||||
string content = partitionContent.ToString();
|
||||
|
||||
var kernel = _kernelService.GetKernelByAIModelID(StepName);
|
||||
var lines = TextChunker.SplitPlainTextLines(content, 299);
|
||||
var paragraphs = TextChunker.SplitPlainTextParagraphs(lines, 3000);
|
||||
KernelFunction jsonFun = kernel.Plugins.GetFunction("KMSPlugin", "QA");
|
||||
|
||||
List<string> qaList = new List<string>();
|
||||
foreach (var para in paragraphs)
|
||||
{
|
||||
var qaresult = await kernel.InvokeAsync(function: jsonFun, new KernelArguments() { ["input"] = para });
|
||||
var qaListStr = qaresult.GetValue<string>().ConvertToString();
|
||||
|
||||
string pattern = @"Q\d+:.*?A\d+:.*?(?=(Q\d+:|$))";
|
||||
RegexOptions options = RegexOptions.Singleline;
|
||||
|
||||
foreach (Match match in Regex.Matches(qaListStr, pattern, options))
|
||||
{
|
||||
qaList.Add(match.Value.Trim()); // Trim用于删除可能的首尾空格
|
||||
}
|
||||
}
|
||||
sentences = qaList;
|
||||
partitions = qaList;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this._log.LogWarning("File {0} cannot be partitioned, type '{1}' not supported", file.Name, file.MimeType);
|
||||
// Don't partition other files
|
||||
continue;
|
||||
}
|
||||
|
||||
if (partitions.Count == 0) { continue; }
|
||||
|
||||
this._log.LogDebug("Saving {0} file partitions", partitions.Count);
|
||||
for (int partitionNumber = 0; partitionNumber < partitions.Count; partitionNumber++)
|
||||
{
|
||||
// TODO: turn partitions in objects with more details, e.g. page number
|
||||
string text = partitions[partitionNumber];
|
||||
int sectionNumber = 0; // TODO: use this to store the page number (if any)
|
||||
BinaryData textData = new(text);
|
||||
|
||||
int tokenCount = this._tokenCounter(text);
|
||||
this._log.LogDebug("Partition size: {0} tokens", tokenCount);
|
||||
|
||||
var destFile = uploadedFile.GetPartitionFileName(partitionNumber);
|
||||
await this._orchestrator.WriteFileAsync(pipeline, destFile, textData, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var destFileDetails = new DataPipeline.GeneratedFileDetails
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
ParentId = uploadedFile.Id,
|
||||
Name = destFile,
|
||||
Size = text.Length,
|
||||
MimeType = MimeTypes.PlainText,
|
||||
ArtifactType = DataPipeline.ArtifactTypes.TextPartition,
|
||||
PartitionNumber = partitionNumber,
|
||||
SectionNumber = sectionNumber,
|
||||
Tags = pipeline.Tags,
|
||||
ContentSHA256 = textData.AntSKCalculateSHA256(),
|
||||
};
|
||||
newFiles.Add(destFile, destFileDetails);
|
||||
destFileDetails.MarkProcessedBy(this);
|
||||
}
|
||||
|
||||
file.MarkProcessedBy(this);
|
||||
}
|
||||
|
||||
// Add new files to pipeline status
|
||||
foreach (var file in newFiles)
|
||||
{
|
||||
uploadedFile.GeneratedFiles.Add(file.Key, file.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return (true, pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,23 @@
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Domain.Interface;
|
||||
using AntSK.Domain.Repositories;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using System.Text;
|
||||
using AntSK.Domain.Utils;
|
||||
using AntSK.Domain.Domain.Model.Dto;
|
||||
using AntSK.Domain.Domain.Model;
|
||||
using AntSK.Domain.Domain.Model.Constant;
|
||||
using DocumentFormat.OpenXml.Drawing;
|
||||
using System.Reflection.Metadata;
|
||||
using Microsoft.KernelMemory;
|
||||
using System.Collections.Generic;
|
||||
using AntSK.Domain.Domain.Model.Dto;
|
||||
using AntSK.Domain.Domain.Other.Bge;
|
||||
using AntSK.Domain.Repositories;
|
||||
using AntSK.Domain.Utils;
|
||||
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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using ChatHistory = Microsoft.SemanticKernel.ChatCompletion.ChatHistory;
|
||||
|
||||
namespace AntSK.Domain.Domain.Service
|
||||
{
|
||||
@@ -19,7 +25,8 @@ namespace AntSK.Domain.Domain.Service
|
||||
public class ChatService(
|
||||
IKernelService _kernelService,
|
||||
IKMService _kMService,
|
||||
IKmsDetails_Repositories _kmsDetails_Repositories
|
||||
IKmsDetails_Repositories _kmsDetails_Repositories,
|
||||
IAIModels_Repositories _aIModels_Repositories
|
||||
) : IChatService
|
||||
{
|
||||
/// <summary>
|
||||
@@ -29,68 +36,183 @@ namespace AntSK.Domain.Domain.Service
|
||||
/// <param name="questions"></param>
|
||||
/// <param name="history"></param>
|
||||
/// <returns></returns>
|
||||
public async IAsyncEnumerable<StreamingKernelContent> SendChatByAppAsync(Apps app, string questions, string 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}}";
|
||||
}
|
||||
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: new KernelArguments() { ["input"] = $"{history}{Environment.NewLine} user:{questions}" });
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<StreamingKernelContent> SendKmsByAppAsync(Apps app, string questions, string history, string filePath, List<RelevantSource> relevantSources = null)
|
||||
public async IAsyncEnumerable<StreamingKernelContent> SendKmsByAppAsync(Apps app, string questions, ChatHistory history, string filePath, List<RelevantSource> relevantSources = null)
|
||||
{
|
||||
var relevantSourceList = await _kMService.GetRelevantSourceList(app.KmsIdList, questions);
|
||||
relevantSources?.Clear();
|
||||
var relevantSourceList = await _kMService.GetRelevantSourceList(app, questions);
|
||||
var _kernel = _kernelService.GetKernelByApp(app);
|
||||
if (!string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
var memory = _kMService.GetMemory(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 memory = _kMService.GetMemoryByApp(app);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var dataMsg = new StringBuilder();
|
||||
if (relevantSourceList.Any())
|
||||
{
|
||||
relevantSources?.AddRange(relevantSourceList);
|
||||
foreach (var item in relevantSources)
|
||||
if (!string.IsNullOrEmpty(app.RerankModelID))
|
||||
{
|
||||
dataMsg.AppendLine(item.ToString());
|
||||
var rerankModel=_aIModels_Repositories.GetById(app.RerankModelID);
|
||||
BegRerankConfig.LoadModel(rerankModel.EndPoint, rerankModel.ModelName);
|
||||
//进行rerank
|
||||
foreach (var item in relevantSourceList)
|
||||
{
|
||||
List<string> rerank = new List<string>();
|
||||
rerank.Add(questions);
|
||||
rerank.Add(item.Text);
|
||||
item.RerankScore = BegRerankConfig.Rerank(rerank);
|
||||
|
||||
}
|
||||
relevantSourceList = relevantSourceList.OrderByDescending(p => p.RerankScore).Take(app.MaxMatchesCount).ToList();
|
||||
}
|
||||
|
||||
bool isSearch = false;
|
||||
foreach (var item in relevantSourceList)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(app.RerankModelID))
|
||||
{
|
||||
//匹配重排后相似度
|
||||
if (item.RerankScore >= app.Relevance / 100)
|
||||
{
|
||||
dataMsg.AppendLine(item.ToString());
|
||||
isSearch = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//匹配相似度
|
||||
if (item.Relevance >= app.Relevance / 100)
|
||||
{
|
||||
dataMsg.AppendLine(item.ToString());
|
||||
isSearch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KernelFunction jsonFun = _kernel.Plugins.GetFunction("KMSPlugin", "Ask");
|
||||
var chatResult = _kernel.InvokeStreamingAsync(function: jsonFun,
|
||||
arguments: new KernelArguments() { ["doc"] = dataMsg, ["history"] = history, ["questions"] = questions });
|
||||
|
||||
await foreach (var content in chatResult)
|
||||
//处理markdown显示
|
||||
relevantSources?.AddRange(relevantSourceList);
|
||||
Dictionary<string, string> fileDic = new Dictionary<string, string>();
|
||||
foreach (var item in relevantSourceList)
|
||||
{
|
||||
yield return content;
|
||||
if (fileDic.ContainsKey(item.SourceName))
|
||||
{
|
||||
item.SourceName = fileDic[item.SourceName];
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
if (isSearch)
|
||||
{
|
||||
//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 chatResult = _kernel.InvokeStreamingAsync(function: func,
|
||||
arguments: new KernelArguments() { ["doc"] = dataMsg.ToString(), ["history"] = string.Join("\n", history.Select(x => x.Role + ": " + x.Content)), ["input"] = questions });
|
||||
|
||||
await foreach (var content in chatResult)
|
||||
{
|
||||
yield return content;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new StreamingTextContent(KmsConstantcs.KmsSearchNull);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -98,5 +220,128 @@ namespace AntSK.Domain.Domain.Service
|
||||
yield return new StreamingTextContent(KmsConstantcs.KmsSearchNull);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<string> SendImgByAppAsync(Apps app, string questions)
|
||||
{
|
||||
var imageModel = _aIModels_Repositories.GetFirst(p => p.Id == app.ImageModelID);
|
||||
KernelArguments args = new() {
|
||||
{ "input", questions }
|
||||
};
|
||||
var _kernel = _kernelService.GetKernelByApp(app);
|
||||
var temperature = app.Temperature / 100; //存的是0~100需要缩小
|
||||
OpenAIPromptExecutionSettings settings = new() { Temperature = temperature };
|
||||
var func = _kernel.CreateFunctionFromPrompt("Translate this into English:{{$input}}", settings);
|
||||
var chatResult = await _kernel.InvokeAsync(function: func, arguments: args);
|
||||
if (chatResult.IsNotNull())
|
||||
{
|
||||
//Can Load stable-diffusion library in diffenert environment
|
||||
|
||||
//SDHelper.LoadLibrary()
|
||||
string versionString = string.Empty;
|
||||
string extensionString = string.Empty;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
extensionString = ".dll";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
extensionString = ".so";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("OS Platform no support");
|
||||
}
|
||||
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo("nvcc", "--version");
|
||||
startInfo.RedirectStandardOutput = true;
|
||||
startInfo.UseShellExecute = false;
|
||||
startInfo.CreateNoWindow = true;
|
||||
using (Process process = Process.Start(startInfo))
|
||||
{
|
||||
if (process != null)
|
||||
{
|
||||
string result = process.StandardOutput.ReadToEnd();
|
||||
Regex regex = new Regex(@"release (\d+).[\d]");
|
||||
Match match = regex.Match(result);
|
||||
if (match.Success)
|
||||
{
|
||||
switch (match.Groups[1].Value.ToString())
|
||||
{
|
||||
case "11":
|
||||
versionString = "Cuda11";
|
||||
break;
|
||||
case "12":
|
||||
versionString = "Cuda12";
|
||||
break;
|
||||
default:
|
||||
versionString = "CPU";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("nvcc get an error");
|
||||
}
|
||||
}
|
||||
|
||||
string libraryPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "StableDiffusion", "Backend", versionString, "stable-diffusion" + extensionString);
|
||||
NativeLibrary.TryLoad(libraryPath, out _);
|
||||
string prompt = chatResult.GetValue<string>();
|
||||
if (!SDHelper.IsInitialized)
|
||||
{
|
||||
Structs.ModelParams modelParams = new Structs.ModelParams
|
||||
{
|
||||
ModelPath = imageModel.ModelName,
|
||||
RngType = Structs.RngType.CUDA_RNG,
|
||||
//VaePath = vaePath,
|
||||
//KeepVaeOnCpu = keepVaeOnCpu,
|
||||
//set false can get a better image, otherwise can use lower vram
|
||||
VaeTiling = false,
|
||||
//LoraModelDir = loraModelDir,
|
||||
};
|
||||
bool result = SDHelper.Initialize(modelParams);
|
||||
}
|
||||
|
||||
Structs.TextToImageParams textToImageParams = new Structs.TextToImageParams
|
||||
{
|
||||
Prompt = prompt,
|
||||
NegativePrompt = "bad quality, wrong image, worst quality",
|
||||
SampleMethod = (Structs.SampleMethod)Enum.Parse(typeof(Structs.SampleMethod), "EULER_A"),
|
||||
//the base image size in SD1.5 is 512x512
|
||||
Width = 512,
|
||||
Height = 512,
|
||||
NormalizeInput = true,
|
||||
ClipSkip = -1,
|
||||
CfgScale = 7,
|
||||
SampleSteps = 20,
|
||||
Seed = -1,
|
||||
};
|
||||
Bitmap[] outputImages = SDHelper.TextToImage(textToImageParams);
|
||||
var base64 = ImageUtils.BitmapToBase64(outputImages[0]);
|
||||
return base64;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ChatHistory> GetChatHistory(List<Chats> MessageList, ChatHistory history)
|
||||
{
|
||||
foreach (var item in MessageList)
|
||||
{
|
||||
if (item.IsSend)
|
||||
{
|
||||
history.AddUserMessage(item.Context);
|
||||
}
|
||||
else
|
||||
{
|
||||
history.AddAssistantMessage(item.Context);
|
||||
}
|
||||
}
|
||||
return history;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Text.RegularExpressions;
|
||||
using Microsoft.SemanticKernel;
|
||||
using HtmlAgilityPack;
|
||||
using System.Collections.Generic;
|
||||
using Serilog;
|
||||
|
||||
namespace AntSK.Domain.Domain.Service
|
||||
{
|
||||
@@ -115,7 +116,7 @@ namespace AntSK.Domain.Domain.Service
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message + " ---- " + ex.StackTrace);
|
||||
Log.Error(ex.Message + " ---- " + ex.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
using AntSK.Domain.Domain.Interface;
|
||||
using AntSK.Domain.Domain.Model;
|
||||
using AntSK.Domain.Domain.Model.Constant;
|
||||
using AntSK.Domain.Domain.Model.Excel;
|
||||
using AntSK.Domain.Domain.Other;
|
||||
using AntSK.Domain.Repositories;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.KernelMemory;
|
||||
using Microsoft.KernelMemory.Handlers;
|
||||
using System.Text;
|
||||
|
||||
namespace AntSK.Domain.Domain.Service
|
||||
{
|
||||
@@ -11,7 +16,8 @@ namespace AntSK.Domain.Domain.Service
|
||||
public class ImportKMSService(
|
||||
IKMService _kMService,
|
||||
IKmsDetails_Repositories _kmsDetails_Repositories,
|
||||
IKmss_Repositories _kmss_Repositories
|
||||
IKmss_Repositories _kmss_Repositories,
|
||||
ILogger<ImportKMSService> _logger
|
||||
) : IImportKMSService
|
||||
{
|
||||
|
||||
@@ -20,18 +26,40 @@ namespace AntSK.Domain.Domain.Service
|
||||
try
|
||||
{
|
||||
var km = _kmss_Repositories.GetFirst(p => p.Id == req.KmsId);
|
||||
|
||||
var _memory = _kMService.GetMemoryByKMS(km.Id);
|
||||
string fileid = req.KmsDetail.Id;
|
||||
List<string> step = new List<string>();
|
||||
if (req.IsQA)
|
||||
{
|
||||
_memory.Orchestrator.AddHandler<TextExtractionHandler>("extract_text");
|
||||
_memory.Orchestrator.AddHandler<QAHandler>(km.ChatModelID);
|
||||
_memory.Orchestrator.AddHandler<GenerateEmbeddingsHandler>("generate_embeddings");
|
||||
_memory.Orchestrator.AddHandler<SaveRecordsHandler>("save_memory_records");
|
||||
step.Add("extract_text");
|
||||
step.Add(km.ChatModelID);
|
||||
step.Add("generate_embeddings");
|
||||
step.Add("save_memory_records");
|
||||
}
|
||||
|
||||
switch (req.ImportType)
|
||||
{
|
||||
case ImportType.File:
|
||||
//导入文件
|
||||
{
|
||||
var importResult = _memory.ImportDocumentAsync(new Document(fileid)
|
||||
.AddFile(req.FilePath)
|
||||
.AddTag(KmsConstantcs.KmsIdTag, req.KmsId)
|
||||
, index: KmsConstantcs.KmsIndex).Result;
|
||||
//导入文件
|
||||
if (req.IsQA)
|
||||
{
|
||||
var importResult = _memory.ImportDocumentAsync(new Document(fileid)
|
||||
.AddFile(req.FilePath)
|
||||
.AddTag(KmsConstantcs.KmsIdTag, req.KmsId)
|
||||
,index: KmsConstantcs.KmsIndex ,steps: step.ToArray()).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var importResult = _memory.ImportDocumentAsync(new Document(fileid)
|
||||
.AddFile(req.FilePath)
|
||||
.AddTag(KmsConstantcs.KmsIdTag, req.KmsId)
|
||||
, index: KmsConstantcs.KmsIndex).Result;
|
||||
}
|
||||
//查询文档数量
|
||||
var docTextList = _kMService.GetDocumentByFileID(km.Id, fileid).Result;
|
||||
string fileGuidName = Path.GetFileName(req.FilePath);
|
||||
@@ -44,8 +72,16 @@ namespace AntSK.Domain.Domain.Service
|
||||
case ImportType.Url:
|
||||
{
|
||||
//导入url
|
||||
var importResult = _memory.ImportWebPageAsync(req.Url, fileid, new TagCollection() { { KmsConstantcs.KmsIdTag, req.KmsId } }
|
||||
, index: KmsConstantcs.KmsIndex).Result;
|
||||
if (req.IsQA)
|
||||
{
|
||||
var importResult = _memory.ImportWebPageAsync(req.Url, fileid, new TagCollection() { { KmsConstantcs.KmsIdTag, req.KmsId } }
|
||||
, index: KmsConstantcs.KmsIndex, steps: step.ToArray()).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var importResult = _memory.ImportWebPageAsync(req.Url, fileid, new TagCollection() { { KmsConstantcs.KmsIdTag, req.KmsId } }
|
||||
, index: KmsConstantcs.KmsIndex).Result;
|
||||
}
|
||||
//查询文档数量
|
||||
var docTextList = _kMService.GetDocumentByFileID(km.Id, fileid).Result;
|
||||
req.KmsDetail.Url = req.Url;
|
||||
@@ -55,8 +91,16 @@ namespace AntSK.Domain.Domain.Service
|
||||
case ImportType.Text:
|
||||
//导入文本
|
||||
{
|
||||
var importResult = _memory.ImportTextAsync(req.Text, fileid, new TagCollection() { { KmsConstantcs.KmsIdTag, req.KmsId } }
|
||||
, index: KmsConstantcs.KmsIndex).Result;
|
||||
if (req.IsQA)
|
||||
{
|
||||
var importResult = _memory.ImportTextAsync(req.Text, fileid, new TagCollection() { { KmsConstantcs.KmsIdTag, req.KmsId } }
|
||||
, index: KmsConstantcs.KmsIndex, steps: step.ToArray()).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var importResult = _memory.ImportTextAsync(req.Text, fileid, new TagCollection() { { KmsConstantcs.KmsIdTag, req.KmsId } }
|
||||
, index: KmsConstantcs.KmsIndex).Result;
|
||||
}
|
||||
//查询文档数量
|
||||
var docTextList = _kMService.GetDocumentByFileID(km.Id, fileid).Result;
|
||||
req.KmsDetail.Url = req.Url;
|
||||
@@ -64,17 +108,47 @@ namespace AntSK.Domain.Domain.Service
|
||||
|
||||
}
|
||||
break;
|
||||
case ImportType.Excel:
|
||||
using (var fs = File.OpenRead(req.FilePath))
|
||||
{
|
||||
var excelList= ExeclHelper.ExcelToList<KMSExcelModel>(fs);
|
||||
_memory.Orchestrator.AddHandler<TextExtractionHandler>("extract_text");
|
||||
_memory.Orchestrator.AddHandler<KMExcelHandler>("antsk_excel_split");
|
||||
_memory.Orchestrator.AddHandler<GenerateEmbeddingsHandler>("generate_embeddings");
|
||||
_memory.Orchestrator.AddHandler<SaveRecordsHandler>("save_memory_records");
|
||||
|
||||
StringBuilder text = new StringBuilder();
|
||||
foreach (var item in excelList)
|
||||
{
|
||||
text.AppendLine(@$"Question:{item.Question}{Environment.NewLine}Answer:{item.Answer}{KmsConstantcs.KMExcelSplit}");
|
||||
}
|
||||
var importResult = _memory.ImportTextAsync(text.ToString(), fileid, new TagCollection() { { KmsConstantcs.KmsIdTag, req.KmsId } }
|
||||
, index: KmsConstantcs.KmsIndex,
|
||||
steps: new[]
|
||||
{
|
||||
"extract_text",
|
||||
"antsk_excel_split",
|
||||
"generate_embeddings",
|
||||
"save_memory_records"
|
||||
}
|
||||
).Result;
|
||||
req.KmsDetail.FileName = req.FileName;
|
||||
string fileGuidName = Path.GetFileName(req.FilePath);
|
||||
req.KmsDetail.FileGuidName = fileGuidName;
|
||||
req.KmsDetail.DataCount = excelList.Count();
|
||||
}
|
||||
break;
|
||||
}
|
||||
req.KmsDetail.Status = Model.Enum.ImportKmsStatus.Success;
|
||||
_kmsDetails_Repositories.Update(req.KmsDetail);
|
||||
//_kmsDetails_Repositories.GetList(p => p.KmsId == req.KmsId);
|
||||
Console.WriteLine("后台导入任务成功:" + req.KmsDetail.DataCount);
|
||||
_logger.LogInformation("后台导入任务成功:" + req.KmsDetail.DataCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
req.KmsDetail.Status = Model.Enum.ImportKmsStatus.Fail;
|
||||
_kmsDetails_Repositories.Update(req.KmsDetail);
|
||||
Console.WriteLine("后台导入任务异常:" + ex.Message);
|
||||
_logger.LogError("后台导入任务异常:" + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using AntDesign;
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Common.Embedding;
|
||||
using AntSK.Domain.Domain.Interface;
|
||||
using AntSK.Domain.Domain.Model.Constant;
|
||||
using AntSK.Domain.Domain.Model.Dto;
|
||||
@@ -7,6 +8,7 @@ using AntSK.Domain.Domain.Other;
|
||||
using AntSK.Domain.Options;
|
||||
using AntSK.Domain.Repositories;
|
||||
using AntSK.Domain.Utils;
|
||||
using AntSK.OCR;
|
||||
using DocumentFormat.OpenXml.Drawing.Diagrams;
|
||||
using LLama;
|
||||
using LLamaSharp.KernelMemory;
|
||||
@@ -15,6 +17,7 @@ using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.KernelMemory;
|
||||
using Microsoft.KernelMemory.Configuration;
|
||||
using Microsoft.KernelMemory.DataFormats;
|
||||
using Microsoft.KernelMemory.FileSystem.DevTools;
|
||||
using Microsoft.KernelMemory.MemoryStorage;
|
||||
using Microsoft.KernelMemory.MemoryStorage.DevTools;
|
||||
@@ -26,7 +29,8 @@ namespace AntSK.Domain.Domain.Service
|
||||
public class KMService(
|
||||
IKmss_Repositories _kmss_Repositories,
|
||||
IAIModels_Repositories _aIModels_Repositories,
|
||||
IMessageService? _message
|
||||
IMessageService? _message,
|
||||
IKernelService _kernelService
|
||||
) : IKMService
|
||||
{
|
||||
private MemoryServerless _memory;
|
||||
@@ -35,20 +39,36 @@ namespace AntSK.Domain.Domain.Service
|
||||
|
||||
public List<UploadFileItem> FileList => _fileList;
|
||||
|
||||
public MemoryServerless GetMemory(Apps app)
|
||||
public MemoryServerless GetMemoryByApp(Apps app)
|
||||
{
|
||||
var chatModel = _aIModels_Repositories.GetFirst(p => p.Id == app.ChatModelID);
|
||||
var embedModel = _aIModels_Repositories.GetFirst(p => p.Id == app.EmbeddingModelID);
|
||||
var chatHttpClient = OpenAIHttpClientHandlerUtil.GetHttpClient(chatModel.EndPoint);
|
||||
var embeddingHttpClient = OpenAIHttpClientHandlerUtil.GetHttpClient(embedModel.EndPoint);
|
||||
|
||||
var searchClientConfig = new SearchClientConfig
|
||||
SearchClientConfig searchClientConfig;
|
||||
if (string.IsNullOrEmpty(app.RerankModelID))
|
||||
{
|
||||
MaxAskPromptSize = 2048,
|
||||
MaxMatchesCount = 3,
|
||||
AnswerTokens = 1000,
|
||||
EmptyAnswer = KmsConstantcs.KmsSearchNull
|
||||
};
|
||||
//不重排直接取查询数
|
||||
searchClientConfig = new SearchClientConfig
|
||||
{
|
||||
MaxAskPromptSize = app.MaxAskPromptSize,
|
||||
MaxMatchesCount = app.MaxMatchesCount,
|
||||
AnswerTokens = app.AnswerTokens,
|
||||
EmptyAnswer = KmsConstantcs.KmsSearchNull
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
//重排取rerank数
|
||||
searchClientConfig = new SearchClientConfig
|
||||
{
|
||||
MaxAskPromptSize = app.MaxAskPromptSize,
|
||||
MaxMatchesCount = app.RerankCount,
|
||||
AnswerTokens = app.AnswerTokens,
|
||||
EmptyAnswer = KmsConstantcs.KmsSearchNull
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var memoryBuild = new KernelMemoryBuilder()
|
||||
.WithSearchClientConfig(searchClientConfig)
|
||||
@@ -70,7 +90,7 @@ namespace AntSK.Domain.Domain.Service
|
||||
return _memory;
|
||||
}
|
||||
|
||||
public MemoryServerless GetMemoryByKMS(string kmsID, SearchClientConfig searchClientConfig = null)
|
||||
public MemoryServerless GetMemoryByKMS(string kmsID)
|
||||
{
|
||||
//if (_memory.IsNull())
|
||||
{
|
||||
@@ -84,33 +104,35 @@ namespace AntSK.Domain.Domain.Service
|
||||
var embeddingHttpClient = OpenAIHttpClientHandlerUtil.GetHttpClient(embedModel.EndPoint);
|
||||
|
||||
//搜索配置
|
||||
if (searchClientConfig.IsNull())
|
||||
{
|
||||
searchClientConfig = new SearchClientConfig
|
||||
{
|
||||
MaxAskPromptSize = 2048,
|
||||
MaxMatchesCount = 3,
|
||||
AnswerTokens = 1000,
|
||||
EmptyAnswer = KmsConstantcs.KmsSearchNull
|
||||
};
|
||||
}
|
||||
//if (searchClientConfig.IsNull())
|
||||
//{
|
||||
// searchClientConfig = new SearchClientConfig
|
||||
// {
|
||||
// MaxAskPromptSize = 2048,
|
||||
// MaxMatchesCount = 3,
|
||||
// AnswerTokens = 1000,
|
||||
// EmptyAnswer = KmsConstantcs.KmsSearchNull
|
||||
// };
|
||||
//}
|
||||
|
||||
var memoryBuild = new KernelMemoryBuilder()
|
||||
.WithSearchClientConfig(searchClientConfig)
|
||||
//.WithSearchClientConfig(searchClientConfig)
|
||||
.WithCustomTextPartitioningOptions(new TextPartitioningOptions
|
||||
{
|
||||
MaxTokensPerLine = kms.MaxTokensPerLine,
|
||||
MaxTokensPerParagraph = kms.MaxTokensPerParagraph,
|
||||
OverlappingTokens = kms.OverlappingTokens
|
||||
});
|
||||
//加载OCR
|
||||
WithOcr(memoryBuild, kms);
|
||||
//加载会话模型
|
||||
WithTextGenerationByAIType(memoryBuild, chatModel, chatHttpClient);
|
||||
//加载向量模型
|
||||
WithTextEmbeddingGenerationByAIType(memoryBuild, embedModel, embeddingHttpClient);
|
||||
//加载向量库
|
||||
WithMemoryDbByVectorDB(memoryBuild);
|
||||
|
||||
_memory = memoryBuild.Build<MemoryServerless>();
|
||||
|
||||
_memory = memoryBuild.AddSingleton<IKernelService>(_kernelService).Build<MemoryServerless>();
|
||||
return _memory;
|
||||
}
|
||||
//else {
|
||||
@@ -118,6 +140,14 @@ namespace AntSK.Domain.Domain.Service
|
||||
//}
|
||||
}
|
||||
|
||||
private static void WithOcr(IKernelMemoryBuilder memoryBuild, Kmss kms)
|
||||
{
|
||||
if (kms.IsOCR == 1)
|
||||
{
|
||||
memoryBuild.WithCustomImageOcr(new AntSKOcrEngine());
|
||||
}
|
||||
}
|
||||
|
||||
private void WithTextEmbeddingGenerationByAIType(IKernelMemoryBuilder memory, AIModels embedModel,
|
||||
HttpClient embeddingHttpClient)
|
||||
{
|
||||
@@ -147,6 +177,11 @@ namespace AntSK.Domain.Domain.Service
|
||||
var embedder = new LLamaEmbedder(weights, parameters);
|
||||
memory.WithLLamaSharpTextEmbeddingGeneration(new LLamaSharpTextEmbeddingGenerator(embedder));
|
||||
break;
|
||||
case Model.Enum.AIType.BgeEmbedding:
|
||||
string pyDll = embedModel.EndPoint;
|
||||
string bgeEmbeddingModelName = embedModel.ModelName;
|
||||
memory.WithBgeTextEmbeddingGeneration(new HuggingfaceTextEmbeddingGenerator(pyDll,bgeEmbeddingModelName));
|
||||
break;
|
||||
case Model.Enum.AIType.DashScope:
|
||||
memory.WithDashScopeDefaults(embedModel.ModelKey);
|
||||
break;
|
||||
@@ -183,6 +218,21 @@ namespace AntSK.Domain.Domain.Service
|
||||
var executor = new StatelessExecutor(weights, parameters);
|
||||
memory.WithLLamaSharpTextGeneration(new LlamaSharpTextGenerator(weights, context, executor));
|
||||
break;
|
||||
case Model.Enum.AIType.LLamaFactory:
|
||||
|
||||
memory.WithOpenAITextGeneration(new OpenAIConfig()
|
||||
{
|
||||
APIKey = "NotNull",
|
||||
TextModel = chatModel.ModelName
|
||||
}, null, chatHttpClient);
|
||||
break;
|
||||
case Model.Enum.AIType.Ollama:
|
||||
memory.WithOpenAITextGeneration(new OpenAIConfig()
|
||||
{
|
||||
APIKey = "NotNull",
|
||||
TextModel = chatModel.ModelName
|
||||
}, null, chatHttpClient);
|
||||
break;
|
||||
case Model.Enum.AIType.DashScope:
|
||||
memory.WithDashScopeTextGeneration(new Cnblogs.KernelMemory.AI.DashScope.DashScopeConfig
|
||||
{
|
||||
@@ -220,6 +270,20 @@ namespace AntSK.Domain.Domain.Service
|
||||
StorageType = FileSystemTypes.Volatile
|
||||
});
|
||||
break;
|
||||
case "Qdrant":
|
||||
var qdrantConfig = ConnectionString.Split("|");
|
||||
memory.WithQdrantMemoryDb(qdrantConfig[0],qdrantConfig[1]);
|
||||
break;
|
||||
case "Redis":
|
||||
memory.WithRedisMemoryDb(new RedisConfig()
|
||||
{
|
||||
ConnectionString = ConnectionString,
|
||||
});
|
||||
break;
|
||||
case "AzureAISearch":
|
||||
var aisearchConfig = ConnectionString.Split("|");
|
||||
memory.WithAzureAISearchMemoryDb(aisearchConfig[0], aisearchConfig[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,12 +298,12 @@ namespace AntSK.Domain.Domain.Service
|
||||
{
|
||||
foreach (var memoryDb in memoryDbs)
|
||||
{
|
||||
var items = await memoryDb.GetListAsync(memoryIndex.Name, new List<MemoryFilter>() { new MemoryFilter().ByDocument(fileId) }, 100, true).ToListAsync();
|
||||
var items = await memoryDb.GetListAsync(memoryIndex.Name, new List<MemoryFilter>() { new MemoryFilter().ByDocument(fileId) }, 1000, true).ToListAsync();
|
||||
docTextList.AddRange(items.Select(item => new KMFile()
|
||||
{
|
||||
DocumentId = item.GetDocumentId(),
|
||||
Text = item.GetPartitionText(),
|
||||
Url = item.GetWebPageUrl(),
|
||||
Url = item.GetWebPageUrl(KmsConstantcs.KmsIndex),
|
||||
LastUpdate = item.GetLastUpdate().LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
File = item.GetFileName()
|
||||
}));
|
||||
@@ -249,15 +313,15 @@ namespace AntSK.Domain.Domain.Service
|
||||
return docTextList;
|
||||
}
|
||||
|
||||
public async Task<List<RelevantSource>> GetRelevantSourceList(string kmsIdListStr, string msg)
|
||||
public async Task<List<RelevantSource>> GetRelevantSourceList(Apps app ,string msg)
|
||||
{
|
||||
var result = new List<RelevantSource>();
|
||||
if (string.IsNullOrWhiteSpace(kmsIdListStr))
|
||||
if (string.IsNullOrWhiteSpace(app.KmsIdList))
|
||||
return result;
|
||||
var kmsIdList = kmsIdListStr.Split(",");
|
||||
var kmsIdList = app.KmsIdList.Split(",");
|
||||
if (!kmsIdList.Any()) return result;
|
||||
|
||||
var memory = GetMemoryByKMS(kmsIdList.FirstOrDefault()!);
|
||||
var memory = GetMemoryByApp(app);
|
||||
|
||||
var filters = kmsIdList.Select(kmsId => new MemoryFilter().ByTag(KmsConstantcs.KmsIdTag, kmsId)).ToList();
|
||||
|
||||
@@ -269,7 +333,7 @@ namespace AntSK.Domain.Domain.Service
|
||||
result.AddRange(item.Partitions.Select(part => new RelevantSource()
|
||||
{
|
||||
SourceName = item.SourceName,
|
||||
Text = Markdown.ToHtml(part.Text),
|
||||
Text = part.Text,
|
||||
Relevance = part.Relevance
|
||||
}));
|
||||
}
|
||||
@@ -291,7 +355,10 @@ namespace AntSK.Domain.Domain.Service
|
||||
"application/pdf",
|
||||
"application/json",
|
||||
"text/x-markdown",
|
||||
"text/markdown"
|
||||
"text/markdown",
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/tiff"
|
||||
};
|
||||
|
||||
string[] exceptExts = [".md", ".pdf"];
|
||||
|
||||
@@ -15,8 +15,15 @@ using System;
|
||||
using ServiceLifetime = AntSK.Domain.Common.DependencyInjection.ServiceLifetime;
|
||||
using AntSK.LLM.Mock;
|
||||
using AntSK.Domain.Domain.Model.Enum;
|
||||
using AntSK.LLM.LLamaFactory;
|
||||
using System.Reflection;
|
||||
using DocumentFormat.OpenXml.Drawing;
|
||||
using Microsoft.KernelMemory;
|
||||
using OpenCvSharp.ML;
|
||||
using LLamaSharp.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Amazon.Runtime.Internal.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace AntSK.Domain.Domain.Service
|
||||
{
|
||||
@@ -28,17 +35,20 @@ namespace AntSK.Domain.Domain.Service
|
||||
private readonly FunctionService _functionService;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private Kernel _kernel;
|
||||
private readonly ILogger<KernelService> _logger;
|
||||
|
||||
public KernelService(
|
||||
IApis_Repositories apis_Repositories,
|
||||
IAIModels_Repositories aIModels_Repositories,
|
||||
FunctionService functionService,
|
||||
IServiceProvider serviceProvider)
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<KernelService> logger)
|
||||
{
|
||||
_apis_Repositories = apis_Repositories;
|
||||
_aIModels_Repositories = aIModels_Repositories;
|
||||
_functionService = functionService;
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +66,7 @@ namespace AntSK.Domain.Domain.Service
|
||||
var chatHttpClient = OpenAIHttpClientHandlerUtil.GetHttpClient(chatModel.EndPoint);
|
||||
|
||||
var builder = Kernel.CreateBuilder();
|
||||
WithTextGenerationByAIType(builder, app, chatModel, chatHttpClient);
|
||||
WithTextGenerationByAIType(builder, chatModel, chatHttpClient);
|
||||
|
||||
_kernel = builder.Build();
|
||||
RegisterPluginsWithKernel(_kernel);
|
||||
@@ -68,7 +78,18 @@ namespace AntSK.Domain.Domain.Service
|
||||
//}
|
||||
}
|
||||
|
||||
private void WithTextGenerationByAIType(IKernelBuilder builder, Apps app, AIModels chatModel, HttpClient chatHttpClient)
|
||||
public Kernel GetKernelByAIModelID(string modelid)
|
||||
{
|
||||
var chatModel = _aIModels_Repositories.GetById(modelid);
|
||||
var chatHttpClient = OpenAIHttpClientHandlerUtil.GetHttpClient(chatModel.EndPoint);
|
||||
var builder = Kernel.CreateBuilder();
|
||||
WithTextGenerationByAIType(builder, chatModel, chatHttpClient);
|
||||
_kernel = builder.Build();
|
||||
RegisterPluginsWithKernel(_kernel);
|
||||
return _kernel;
|
||||
}
|
||||
|
||||
private void WithTextGenerationByAIType(IKernelBuilder builder,AIModels chatModel, HttpClient chatHttpClient)
|
||||
{
|
||||
switch (chatModel.AIType)
|
||||
{
|
||||
@@ -91,11 +112,35 @@ 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, app.Id));
|
||||
|
||||
var settings = chatModel.ModelKey.Split("|");
|
||||
|
||||
Sdcb.SparkDesk.ModelVersion modelVersion = Sdcb.SparkDesk.ModelVersion.V3_5;
|
||||
|
||||
switch (chatModel.ModelName)
|
||||
{
|
||||
case "V3_5":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V3_5;
|
||||
break;
|
||||
case "V3":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V3;
|
||||
break;
|
||||
case "V2":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V2;
|
||||
break;
|
||||
case "V1_5":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V1_5;
|
||||
break;
|
||||
}
|
||||
|
||||
SparkDeskOptions options = new SparkDeskOptions { AppId = settings[0], ApiSecret = settings[1], ApiKey = settings[2], ModelVersion = modelVersion };
|
||||
|
||||
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:
|
||||
@@ -104,6 +149,21 @@ 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(
|
||||
modelId: chatModel.ModelName,
|
||||
apiKey: "NotNull",
|
||||
httpClient: chatHttpClient
|
||||
);
|
||||
break;
|
||||
case AIType.Ollama:
|
||||
builder.AddOpenAIChatCompletion(
|
||||
modelId: chatModel.ModelName,
|
||||
apiKey: "NotNull",
|
||||
httpClient: chatHttpClient
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -152,9 +212,8 @@ namespace AntSK.Domain.Domain.Service
|
||||
|
||||
var getParametes = new List<KernelParameterMetadata>() {
|
||||
new KernelParameterMetadata("jsonbody"){
|
||||
Name="json参数字符串",
|
||||
ParameterType=typeof(string),
|
||||
Description=$"需要根据背景文档:{Environment.NewLine}{api.InputPrompt} {Environment.NewLine}提取出对应的json格式字符串,参考如下格式:{Environment.NewLine}{api.Query}"
|
||||
Description=$"背景文档:{Environment.NewLine}{api.InputPrompt} {Environment.NewLine}提取出对应的json格式字符串,参考如下格式:{Environment.NewLine}{api.Query}"
|
||||
}
|
||||
};
|
||||
functions.Add(_kernel.CreateFunctionFromMethod((string jsonbody) =>
|
||||
@@ -191,16 +250,15 @@ namespace AntSK.Domain.Domain.Service
|
||||
//处理json body
|
||||
var postParametes = new List<KernelParameterMetadata>() {
|
||||
new KernelParameterMetadata("jsonbody"){
|
||||
Name="json参数字符串",
|
||||
ParameterType=typeof(string),
|
||||
Description=$"需要根据背景文档:{Environment.NewLine}{api.InputPrompt} {Environment.NewLine}提取出对应的json格式字符串,参考如下格式:{Environment.NewLine}{api.JsonBody}"
|
||||
Description=$"背景文档:{Environment.NewLine}{api.InputPrompt} {Environment.NewLine}提取出对应的json格式字符串,参考如下格式:{Environment.NewLine}{api.JsonBody}"
|
||||
}
|
||||
};
|
||||
functions.Add(_kernel.CreateFunctionFromMethod((string jsonBody) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine(jsonBody);
|
||||
_logger.LogInformation(jsonBody);
|
||||
RestClient client = new RestClient();
|
||||
RestRequest request = new RestRequest(api.Url, Method.Post);
|
||||
foreach (var header in api.Header.ConvertToString().Split("\n"))
|
||||
@@ -279,8 +337,8 @@ namespace AntSK.Domain.Domain.Service
|
||||
KernelFunction sunFun = _kernel.Plugins.GetFunction("ConversationSummaryPlugin", "SummarizeConversation");
|
||||
var summary = await _kernel.InvokeAsync(sunFun, new() { ["input"] = $"内容是:{history.ToString()} {Environment.NewLine} 请注意用中文总结" });
|
||||
string his = summary.GetValue<string>();
|
||||
var msg = $"history:{Environment.NewLine}{history.ToString()}{Environment.NewLine} user:{questions}{Environment.NewLine}"; ;
|
||||
var msg = $"history:{Environment.NewLine}{his}{Environment.NewLine} user:{questions}{Environment.NewLine}";
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
172
src/AntSK.Domain/Domain/Service/LLamaFactoryService.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using Amazon.Runtime.Internal.Util;
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Domain.Interface;
|
||||
using AntSK.Domain.Domain.Model.Dto;
|
||||
using AntSK.Domain.Options;
|
||||
using AntSK.LLamaFactory.Model;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Domain.Service
|
||||
{
|
||||
[ServiceDescription(typeof(ILLamaFactoryService), ServiceLifetime.Singleton)]
|
||||
public class LLamaFactoryService(ILogger<LLamaFactoryService> _logger) : ILLamaFactoryService
|
||||
{
|
||||
private Process process;
|
||||
|
||||
public static bool isProcessComplete = false;
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
private List<LLamaModel> modelList = new List<LLamaModel>();
|
||||
|
||||
|
||||
public delegate Task LogMessageHandler(string message);
|
||||
public event LogMessageHandler LogMessageReceived;
|
||||
protected virtual async Task OnLogMessageReceived(string message)
|
||||
{
|
||||
LogMessageReceived?.Invoke(message);
|
||||
}
|
||||
|
||||
public async Task PipInstall()
|
||||
{
|
||||
|
||||
var cmdTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
|
||||
var isProcessComplete = false;
|
||||
|
||||
process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "pip",
|
||||
Arguments = "install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory,
|
||||
}
|
||||
};
|
||||
process.OutputDataReceived += (sender, eventArgs) =>
|
||||
{
|
||||
_logger.LogInformation($"{eventArgs.Data}");
|
||||
OnLogMessageReceived(eventArgs.Data);
|
||||
};
|
||||
process.ErrorDataReceived += (sender, eventArgs) =>
|
||||
{
|
||||
_logger.LogInformation($"{eventArgs.Data}");
|
||||
OnLogMessageReceived(eventArgs.Data);
|
||||
};
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
OnLogMessageReceived("--------------------完成--------------------");
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
await cmdTask;
|
||||
}
|
||||
|
||||
public async Task StartLLamaFactory(string modelName, string templateName)
|
||||
{
|
||||
var cmdTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
|
||||
var isProcessComplete = false;
|
||||
|
||||
process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "python",
|
||||
Arguments = "api_antsk.py --model_name_or_path " + modelName + " --template " + templateName + " ",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError=true,
|
||||
WorkingDirectory = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "llamafactory"),
|
||||
}
|
||||
};
|
||||
process.StartInfo.Environment["CUDA_VISIBLE_DEVICES"] = Environment.GetEnvironmentVariable("CUDA_VISIBLE_DEVICES") ?? "0";
|
||||
process.StartInfo.Environment["API_PORT"] = "8000";
|
||||
process.StartInfo.EnvironmentVariables["USE_MODELSCOPE_HUB"] = Environment.GetEnvironmentVariable("USE_MODELSCOPE_HUB") ?? "1";
|
||||
process.OutputDataReceived += (sender, eventArgs) =>
|
||||
{
|
||||
_logger.LogInformation($"{eventArgs.Data}");
|
||||
OnLogMessageReceived(eventArgs.Data);
|
||||
};
|
||||
process.ErrorDataReceived += (sender, eventArgs) =>
|
||||
{
|
||||
_logger.LogInformation($"{eventArgs.Data}");
|
||||
OnLogMessageReceived(eventArgs.Data);
|
||||
};
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
OnLogMessageReceived("--------------------完成--------------------");
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
await cmdTask;
|
||||
}
|
||||
|
||||
private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string WaitForProcessExit()
|
||||
{
|
||||
process.WaitForExit();
|
||||
return process.StandardOutput.ReadToEnd();
|
||||
}
|
||||
|
||||
public void KillProcess()
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
Process[] processes = Process.GetProcesses();
|
||||
foreach (Process process1 in processes)
|
||||
{
|
||||
if (process1.ProcessName.ToLower() == "python")
|
||||
{
|
||||
process1.Kill();
|
||||
_logger.LogInformation("kill python");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
// Process already exited.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<LLamaModel> GetLLamaFactoryModels()
|
||||
{
|
||||
if (modelList.Count==0)
|
||||
{
|
||||
string jsonString = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "modelList.json"));
|
||||
|
||||
// 反序列化 JSON 字符串到相应的 C# 对象
|
||||
var Models = JsonConvert.DeserializeObject<List<LLamaFactoryModel>>(jsonString);
|
||||
foreach (var model in Models)
|
||||
{
|
||||
foreach (var m in model.Models)
|
||||
{
|
||||
modelList.Add(new LLamaModel() { Name=m.Key, ModelScope=m.Value.MODELSCOPE });
|
||||
}
|
||||
}
|
||||
}
|
||||
return modelList;
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/AntSK.Domain/Domain/Service/OllamaService.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Domain.Interface;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using AntSK.Domain.Utils;
|
||||
|
||||
namespace AntSK.Domain.Domain.Service
|
||||
{
|
||||
[ServiceDescription(typeof(IOllamaService), ServiceLifetime.Singleton)]
|
||||
public class OllamaService : IOllamaService
|
||||
{
|
||||
private Process process;
|
||||
public delegate Task LogMessageHandler(string message);
|
||||
public event LogMessageHandler LogMessageReceived;
|
||||
protected virtual async Task OnLogMessageReceived(string message)
|
||||
{
|
||||
LogMessageReceived?.Invoke(message);
|
||||
}
|
||||
|
||||
public async Task StartOllama(string modelName)
|
||||
{
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
var cmdTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
|
||||
var isProcessComplete = false;
|
||||
|
||||
process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "ollama",
|
||||
Arguments = "run " + modelName,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
}
|
||||
};
|
||||
process.OutputDataReceived += (sender, eventArgs) =>
|
||||
{
|
||||
Log.Information($"{eventArgs.Data.ConvertToString()}");
|
||||
if (!eventArgs.Data.ConvertToString().Contains("The handle is invalid"))
|
||||
{
|
||||
OnLogMessageReceived(eventArgs.Data.ConvertToString());
|
||||
}
|
||||
};
|
||||
process.ErrorDataReceived += (sender, eventArgs) =>
|
||||
{
|
||||
Log.Error($"{eventArgs.Data.ConvertToString()}");
|
||||
if (!eventArgs.Data.ConvertToString().Contains("The handle is invalid"))
|
||||
{
|
||||
OnLogMessageReceived(eventArgs.Data.ConvertToString());
|
||||
}
|
||||
};
|
||||
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
|
||||
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
OnLogMessageReceived("--------------------完成--------------------");
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
await cmdTask;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
13
src/AntSK.Domain/Options/FileDirOption.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Options
|
||||
{
|
||||
public class FileDirOption
|
||||
{
|
||||
public static string DirectoryPath { get; set; } = Directory.GetCurrentDirectory();
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,7 @@
|
||||
public class LLamaSharpOption
|
||||
{
|
||||
public static string RunType { get; set; }
|
||||
public static string Chat { get; set; }
|
||||
|
||||
public static string Embedding { get; set; }
|
||||
|
||||
public static string FileDirectory { get; set; } = Directory.GetCurrentDirectory();
|
||||
public static uint? ContextSize { get; set; }
|
||||
public static int? GpuLayerCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ namespace AntSK.Domain.Repositories
|
||||
/// </summary>
|
||||
public string? EmbeddingModelID { get; set; }
|
||||
|
||||
public string? RerankModelID { get; set; }
|
||||
|
||||
|
||||
public string? ImageModelID { get; set; }
|
||||
/// <summary>
|
||||
/// 温度
|
||||
/// </summary>
|
||||
@@ -53,6 +57,7 @@ namespace AntSK.Domain.Repositories
|
||||
/// <summary>
|
||||
/// 提示词
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "varchar(2000)")]
|
||||
public string? Prompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -76,5 +81,31 @@ namespace AntSK.Domain.Repositories
|
||||
/// API调用秘钥
|
||||
/// </summary>
|
||||
public string? SecretKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 相似度
|
||||
/// </summary>
|
||||
[SugarColumn(DefaultValue = "70")]
|
||||
public double Relevance { get; set; } = 70f;
|
||||
|
||||
/// <summary>
|
||||
/// 提问最大token数
|
||||
/// </summary>
|
||||
[SugarColumn(DefaultValue = "2048")]
|
||||
public int MaxAskPromptSize { get; set; } = 2048;
|
||||
/// <summary>
|
||||
/// 向量匹配数
|
||||
/// </summary>
|
||||
[SugarColumn(DefaultValue = "3")]
|
||||
public int MaxMatchesCount { get; set; } = 3;
|
||||
|
||||
|
||||
[SugarColumn(DefaultValue = "20")]
|
||||
public int RerankCount { get; set; } = 20;
|
||||
/// <summary>
|
||||
/// 回答最大token数
|
||||
/// </summary>
|
||||
[SugarColumn(DefaultValue = "2048")]
|
||||
public int AnswerTokens { get; set; } = 2048;
|
||||
}
|
||||
}
|
||||
41
src/AntSK.Domain/Repositories/AI/Chat/Chats.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using AntSK.Domain.Domain.Model.Enum;
|
||||
using SqlSugar;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
[SugarTable("Chats")]
|
||||
public partial class Chats
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
/// <summary>
|
||||
/// 应用ID
|
||||
/// </summary>
|
||||
public string AppId { get; set; }
|
||||
/// <summary>
|
||||
/// 消息内容
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "varchar(4000)")]
|
||||
public string Context { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 发送是true 接收是false
|
||||
/// </summary>
|
||||
public bool IsSend { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 创建事件
|
||||
/// </summary>
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件名
|
||||
/// </summary>
|
||||
public string? FileName { get; set; }
|
||||
}
|
||||
}
|
||||
11
src/AntSK.Domain/Repositories/AI/Chat/Chats_Repositories.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
[ServiceDescription(typeof(IChats_Repositories), ServiceLifetime.Scoped)]
|
||||
public class Chats_Repositories : Repository<Chats>, IChats_Repositories
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IChats_Repositories : IRepository<Chats>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ namespace AntSK.Domain.Repositories
|
||||
[SugarColumn(DefaultValue = "49")]
|
||||
public int OverlappingTokens { get; set; } = 49;
|
||||
|
||||
|
||||
[SugarColumn(DefaultValue = "0")]
|
||||
public int IsOCR { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
|
||||
16
src/AntSK.Domain/Repositories/Setting/Dic/Dics.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using AntSK.Domain.Domain.Model.Enum;
|
||||
using SqlSugar;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
[SugarTable("Dics")]
|
||||
public partial class Dics
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public string Id { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
[ServiceDescription(typeof(IDics_Repositories), ServiceLifetime.Scoped)]
|
||||
public class Dics_Repositories : Repository<Dics>, IDics_Repositories
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IDics_Repositories : IRepository<Dics>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Web;
|
||||
using System.Security.Cryptography;
|
||||
using System.Web;
|
||||
|
||||
namespace AntSK.Domain.Utils
|
||||
{
|
||||
@@ -250,5 +251,22 @@ namespace AntSK.Domain.Utils
|
||||
|
||||
return nameValueCollection.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 忽略大小写匹配
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ComparisonIgnoreCase(this string s, string value)
|
||||
{
|
||||
return s.Equals(value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string AntSKCalculateSHA256(this BinaryData binaryData)
|
||||
{
|
||||
byte[] byteArray = SHA256.HashData(binaryData.ToMemory().Span);
|
||||
return Convert.ToHexString(byteArray).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
src/AntSK.Domain/Utils/ImageUtils.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.Domain.Utils
|
||||
{
|
||||
public class ImageUtils
|
||||
{
|
||||
public static string BitmapToBase64(Bitmap bitmap)
|
||||
{
|
||||
using (MemoryStream memoryStream = new MemoryStream())
|
||||
{
|
||||
// 保存为JPEG格式,也可以选择Png,Gif等等
|
||||
bitmap.Save(memoryStream, ImageFormat.Jpeg);
|
||||
|
||||
// 获取内存流的字节数组
|
||||
byte[] imageBytes = memoryStream.ToArray();
|
||||
|
||||
// 将字节转换为Base64字符串
|
||||
string base64String = Convert.ToBase64String(imageBytes);
|
||||
return base64String;
|
||||
}
|
||||
}
|
||||
public static List<string> BitmapListToBase64(Bitmap[] bitmaps)
|
||||
{
|
||||
List<string> base64Strings = new List<string>();
|
||||
|
||||
foreach (Bitmap bitmap in bitmaps)
|
||||
{
|
||||
base64Strings.Add(BitmapToBase64(bitmap));
|
||||
}
|
||||
return base64Strings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Serilog;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AntSK.Domain.Utils
|
||||
{
|
||||
@@ -19,7 +21,7 @@ namespace AntSK.Domain.Utils
|
||||
{
|
||||
string requestBody = await request.Content.ReadAsStringAsync();
|
||||
//便于调试查看请求prompt
|
||||
Console.WriteLine(requestBody);
|
||||
Log.Information(requestBody);
|
||||
}
|
||||
if (match.Success)
|
||||
{
|
||||
|
||||
21
src/AntSK.LLamaFactory/AntSK.LLamaFactory.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="llamafactory\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="modelList.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="requirements.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
27
src/AntSK.LLamaFactory/Model/LLamaFactoryModel.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntSK.LLamaFactory.Model
|
||||
{
|
||||
public class ModelInfo
|
||||
{
|
||||
public string DEFAULT { get; set; }
|
||||
public string MODELSCOPE { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class LLamaFactoryModel
|
||||
{
|
||||
public Dictionary<string, ModelInfo> Models { get; set; }
|
||||
public string Template { get; set; }
|
||||
}
|
||||
|
||||
public class LLamaModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ModelScope { get; set; }
|
||||
}
|
||||
}
|
||||
0
src/AntSK.LLamaFactory/llamafactory/__init__.py
Normal file
19
src/AntSK.LLamaFactory/llamafactory/api_antsk.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
|
||||
from llamafactory.api.app import create_app
|
||||
from llamafactory.chat import ChatModel
|
||||
|
||||
|
||||
def main():
|
||||
chat_model = ChatModel()
|
||||
app = create_app(chat_model)
|
||||
api_host = os.environ.get("API_HOST", "0.0.0.0")
|
||||
api_port = int(os.environ.get("API_PORT", "8000"))
|
||||
print("Visit http://localhost:{}/docs for API document.".format(api_port))
|
||||
uvicorn.run(app, host=api_host, port=api_port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,6 @@
|
||||
# Level: api, webui > chat, eval, train > data, model > hparams > extras
|
||||
|
||||
from .cli import VERSION
|
||||
|
||||
|
||||
__version__ = VERSION
|
||||
108
src/AntSK.LLamaFactory/llamafactory/llamafactory/api/app.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Optional
|
||||
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from ..chat import ChatModel
|
||||
from ..extras.misc import torch_gc
|
||||
from ..extras.packages import is_fastapi_available, is_starlette_available, is_uvicorn_available
|
||||
from .chat import (
|
||||
create_chat_completion_response,
|
||||
create_score_evaluation_response,
|
||||
create_stream_chat_completion_response,
|
||||
)
|
||||
from .protocol import (
|
||||
ChatCompletionRequest,
|
||||
ChatCompletionResponse,
|
||||
ModelCard,
|
||||
ModelList,
|
||||
ScoreEvaluationRequest,
|
||||
ScoreEvaluationResponse,
|
||||
)
|
||||
|
||||
|
||||
if is_fastapi_available():
|
||||
from fastapi import Depends, FastAPI, HTTPException, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
|
||||
if is_starlette_available():
|
||||
from sse_starlette import EventSourceResponse
|
||||
|
||||
|
||||
if is_uvicorn_available():
|
||||
import uvicorn
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: "FastAPI"): # collects GPU memory
|
||||
yield
|
||||
torch_gc()
|
||||
|
||||
|
||||
def create_app(chat_model: "ChatModel") -> "FastAPI":
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
api_key = os.environ.get("API_KEY")
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
async def verify_api_key(auth: Annotated[Optional[HTTPAuthorizationCredentials], Depends(security)]):
|
||||
if api_key and (auth is None or auth.credentials != api_key):
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key.")
|
||||
|
||||
@app.get(
|
||||
"/v1/models",
|
||||
response_model=ModelList,
|
||||
status_code=status.HTTP_200_OK,
|
||||
dependencies=[Depends(verify_api_key)],
|
||||
)
|
||||
async def list_models():
|
||||
model_card = ModelCard(id="gpt-3.5-turbo")
|
||||
return ModelList(data=[model_card])
|
||||
|
||||
@app.post(
|
||||
"/v1/chat/completions",
|
||||
response_model=ChatCompletionResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
dependencies=[Depends(verify_api_key)],
|
||||
)
|
||||
async def create_chat_completion(request: ChatCompletionRequest):
|
||||
if not chat_model.engine.can_generate:
|
||||
raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Not allowed")
|
||||
|
||||
if request.stream:
|
||||
generate = create_stream_chat_completion_response(request, chat_model)
|
||||
return EventSourceResponse(generate, media_type="text/event-stream")
|
||||
else:
|
||||
return await create_chat_completion_response(request, chat_model)
|
||||
|
||||
@app.post(
|
||||
"/v1/score/evaluation",
|
||||
response_model=ScoreEvaluationResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
dependencies=[Depends(verify_api_key)],
|
||||
)
|
||||
async def create_score_evaluation(request: ScoreEvaluationRequest):
|
||||
if chat_model.engine.can_generate:
|
||||
raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Not allowed")
|
||||
|
||||
return await create_score_evaluation_response(request, chat_model)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def run_api() -> None:
|
||||
chat_model = ChatModel()
|
||||
app = create_app(chat_model)
|
||||
api_host = os.environ.get("API_HOST", "0.0.0.0")
|
||||
api_port = int(os.environ.get("API_PORT", "8000"))
|
||||
print("Visit http://localhost:{}/docs for API document.".format(api_port))
|
||||
uvicorn.run(app, host=api_host, port=api_port)
|
||||
219
src/AntSK.LLamaFactory/llamafactory/llamafactory/api/chat.py
Normal file
@@ -0,0 +1,219 @@
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING, AsyncGenerator, Dict, List, Optional, Tuple
|
||||
|
||||
from ..data import Role as DataRole
|
||||
from ..extras.logging import get_logger
|
||||
from ..extras.packages import is_fastapi_available, is_pillow_available, is_requests_available
|
||||
from .common import dictify, jsonify
|
||||
from .protocol import (
|
||||
ChatCompletionMessage,
|
||||
ChatCompletionResponse,
|
||||
ChatCompletionResponseChoice,
|
||||
ChatCompletionResponseUsage,
|
||||
ChatCompletionStreamResponse,
|
||||
ChatCompletionStreamResponseChoice,
|
||||
Finish,
|
||||
Function,
|
||||
FunctionCall,
|
||||
Role,
|
||||
ScoreEvaluationResponse,
|
||||
)
|
||||
|
||||
|
||||
if is_fastapi_available():
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
|
||||
if is_pillow_available():
|
||||
from PIL import Image
|
||||
|
||||
|
||||
if is_requests_available():
|
||||
import requests
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from ..chat import ChatModel
|
||||
from .protocol import ChatCompletionRequest, ScoreEvaluationRequest
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
ROLE_MAPPING = {
|
||||
Role.USER: DataRole.USER.value,
|
||||
Role.ASSISTANT: DataRole.ASSISTANT.value,
|
||||
Role.SYSTEM: DataRole.SYSTEM.value,
|
||||
Role.FUNCTION: DataRole.FUNCTION.value,
|
||||
Role.TOOL: DataRole.OBSERVATION.value,
|
||||
}
|
||||
|
||||
|
||||
def _process_request(
|
||||
request: "ChatCompletionRequest",
|
||||
) -> Tuple[List[Dict[str, str]], Optional[str], Optional[str], Optional["NDArray"]]:
|
||||
logger.info("==== request ====\n{}".format(json.dumps(dictify(request), indent=2, ensure_ascii=False)))
|
||||
|
||||
if len(request.messages) == 0:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid length")
|
||||
|
||||
if request.messages[0].role == Role.SYSTEM:
|
||||
system = request.messages.pop(0).content
|
||||
else:
|
||||
system = None
|
||||
|
||||
if len(request.messages) % 2 == 0:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only supports u/a/u/a/u...")
|
||||
|
||||
input_messages = []
|
||||
image = None
|
||||
for i, message in enumerate(request.messages):
|
||||
if i % 2 == 0 and message.role not in [Role.USER, Role.TOOL]:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid role")
|
||||
elif i % 2 == 1 and message.role not in [Role.ASSISTANT, Role.FUNCTION]:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid role")
|
||||
|
||||
if message.role == Role.ASSISTANT and isinstance(message.tool_calls, list) and len(message.tool_calls):
|
||||
name = message.tool_calls[0].function.name
|
||||
arguments = message.tool_calls[0].function.arguments
|
||||
content = json.dumps({"name": name, "argument": arguments}, ensure_ascii=False)
|
||||
input_messages.append({"role": ROLE_MAPPING[Role.FUNCTION], "content": content})
|
||||
elif isinstance(message.content, list):
|
||||
for input_item in message.content:
|
||||
if input_item.type == "text":
|
||||
input_messages.append({"role": ROLE_MAPPING[message.role], "content": input_item.text})
|
||||
else:
|
||||
image_url = input_item.image_url.url
|
||||
if image_url.startswith("data:image"): # base64 image
|
||||
image_data = base64.b64decode(image_url.split(",", maxsplit=1)[1])
|
||||
image_path = io.BytesIO(image_data)
|
||||
elif os.path.isfile(image_url): # local file
|
||||
image_path = open(image_url, "rb")
|
||||
else: # web uri
|
||||
image_path = requests.get(image_url, stream=True).raw
|
||||
|
||||
image = Image.open(image_path).convert("RGB")
|
||||
else:
|
||||
input_messages.append({"role": ROLE_MAPPING[message.role], "content": message.content})
|
||||
|
||||
tool_list = request.tools
|
||||
if isinstance(tool_list, list) and len(tool_list):
|
||||
try:
|
||||
tools = json.dumps([dictify(tool.function) for tool in tool_list], ensure_ascii=False)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid tools")
|
||||
else:
|
||||
tools = None
|
||||
|
||||
return input_messages, system, tools, image
|
||||
|
||||
|
||||
def _create_stream_chat_completion_chunk(
|
||||
completion_id: str,
|
||||
model: str,
|
||||
delta: "ChatCompletionMessage",
|
||||
index: Optional[int] = 0,
|
||||
finish_reason: Optional["Finish"] = None,
|
||||
) -> str:
|
||||
choice_data = ChatCompletionStreamResponseChoice(index=index, delta=delta, finish_reason=finish_reason)
|
||||
chunk = ChatCompletionStreamResponse(id=completion_id, model=model, choices=[choice_data])
|
||||
return jsonify(chunk)
|
||||
|
||||
|
||||
async def create_chat_completion_response(
|
||||
request: "ChatCompletionRequest", chat_model: "ChatModel"
|
||||
) -> "ChatCompletionResponse":
|
||||
completion_id = "chatcmpl-{}".format(uuid.uuid4().hex)
|
||||
input_messages, system, tools, image = _process_request(request)
|
||||
responses = await chat_model.achat(
|
||||
input_messages,
|
||||
system,
|
||||
tools,
|
||||
image,
|
||||
do_sample=request.do_sample,
|
||||
temperature=request.temperature,
|
||||
top_p=request.top_p,
|
||||
max_new_tokens=request.max_tokens,
|
||||
num_return_sequences=request.n,
|
||||
stop=request.stop,
|
||||
)
|
||||
|
||||
prompt_length, response_length = 0, 0
|
||||
choices = []
|
||||
for i, response in enumerate(responses):
|
||||
if tools:
|
||||
result = chat_model.engine.template.format_tools.extract(response.response_text)
|
||||
else:
|
||||
result = response.response_text
|
||||
|
||||
if isinstance(result, tuple):
|
||||
name, arguments = result
|
||||
function = Function(name=name, arguments=arguments)
|
||||
tool_call = FunctionCall(id="call_{}".format(uuid.uuid4().hex), function=function)
|
||||
response_message = ChatCompletionMessage(role=Role.ASSISTANT, tool_calls=[tool_call])
|
||||
finish_reason = Finish.TOOL
|
||||
else:
|
||||
response_message = ChatCompletionMessage(role=Role.ASSISTANT, content=result)
|
||||
finish_reason = Finish.STOP if response.finish_reason == "stop" else Finish.LENGTH
|
||||
|
||||
choices.append(ChatCompletionResponseChoice(index=i, message=response_message, finish_reason=finish_reason))
|
||||
prompt_length = response.prompt_length
|
||||
response_length += response.response_length
|
||||
|
||||
usage = ChatCompletionResponseUsage(
|
||||
prompt_tokens=prompt_length,
|
||||
completion_tokens=response_length,
|
||||
total_tokens=prompt_length + response_length,
|
||||
)
|
||||
|
||||
return ChatCompletionResponse(id=completion_id, model=request.model, choices=choices, usage=usage)
|
||||
|
||||
|
||||
async def create_stream_chat_completion_response(
|
||||
request: "ChatCompletionRequest", chat_model: "ChatModel"
|
||||
) -> AsyncGenerator[str, None]:
|
||||
completion_id = "chatcmpl-{}".format(uuid.uuid4().hex)
|
||||
input_messages, system, tools, image = _process_request(request)
|
||||
if tools:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot stream function calls.")
|
||||
|
||||
if request.n > 1:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot stream multiple responses.")
|
||||
|
||||
yield _create_stream_chat_completion_chunk(
|
||||
completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(role=Role.ASSISTANT, content="")
|
||||
)
|
||||
async for new_token in chat_model.astream_chat(
|
||||
input_messages,
|
||||
system,
|
||||
tools,
|
||||
image,
|
||||
do_sample=request.do_sample,
|
||||
temperature=request.temperature,
|
||||
top_p=request.top_p,
|
||||
max_new_tokens=request.max_tokens,
|
||||
stop=request.stop,
|
||||
):
|
||||
if len(new_token) != 0:
|
||||
yield _create_stream_chat_completion_chunk(
|
||||
completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(content=new_token)
|
||||
)
|
||||
|
||||
yield _create_stream_chat_completion_chunk(
|
||||
completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(), finish_reason=Finish.STOP
|
||||
)
|
||||
yield "[DONE]"
|
||||
|
||||
|
||||
async def create_score_evaluation_response(
|
||||
request: "ScoreEvaluationRequest", chat_model: "ChatModel"
|
||||
) -> "ScoreEvaluationResponse":
|
||||
if len(request.messages) == 0:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid request")
|
||||
|
||||
scores = await chat_model.aget_scores(request.messages, max_length=request.max_length)
|
||||
return ScoreEvaluationResponse(model=request.model, scores=scores)
|
||||
@@ -0,0 +1,20 @@
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Any, Dict
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def dictify(data: "BaseModel") -> Dict[str, Any]:
|
||||
try: # pydantic v2
|
||||
return data.model_dump(exclude_unset=True)
|
||||
except AttributeError: # pydantic v1
|
||||
return data.dict(exclude_unset=True)
|
||||
|
||||
|
||||
def jsonify(data: "BaseModel") -> str:
|
||||
try: # pydantic v2
|
||||
return json.dumps(data.model_dump(exclude_unset=True), ensure_ascii=False)
|
||||
except AttributeError: # pydantic v1
|
||||
return data.json(exclude_unset=True, ensure_ascii=False)
|
||||
139
src/AntSK.LLamaFactory/llamafactory/llamafactory/api/protocol.py
Normal file
@@ -0,0 +1,139 @@
|
||||
import time
|
||||
from enum import Enum, unique
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import Literal
|
||||
|
||||
|
||||
@unique
|
||||
class Role(str, Enum):
|
||||
USER = "user"
|
||||
ASSISTANT = "assistant"
|
||||
SYSTEM = "system"
|
||||
FUNCTION = "function"
|
||||
TOOL = "tool"
|
||||
|
||||
|
||||
@unique
|
||||
class Finish(str, Enum):
|
||||
STOP = "stop"
|
||||
LENGTH = "length"
|
||||
TOOL = "tool_calls"
|
||||
|
||||
|
||||
class ModelCard(BaseModel):
|
||||
id: str
|
||||
object: Literal["model"] = "model"
|
||||
created: int = Field(default_factory=lambda: int(time.time()))
|
||||
owned_by: Literal["owner"] = "owner"
|
||||
|
||||
|
||||
class ModelList(BaseModel):
|
||||
object: Literal["list"] = "list"
|
||||
data: List[ModelCard] = []
|
||||
|
||||
|
||||
class Function(BaseModel):
|
||||
name: str
|
||||
arguments: str
|
||||
|
||||
|
||||
class FunctionDefinition(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
parameters: Dict[str, Any]
|
||||
|
||||
|
||||
class FunctionAvailable(BaseModel):
|
||||
type: Literal["function", "code_interpreter"] = "function"
|
||||
function: Optional[FunctionDefinition] = None
|
||||
|
||||
|
||||
class FunctionCall(BaseModel):
|
||||
id: str
|
||||
type: Literal["function"] = "function"
|
||||
function: Function
|
||||
|
||||
|
||||
class ImageURL(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
class MultimodalInputItem(BaseModel):
|
||||
type: Literal["text", "image_url"]
|
||||
text: Optional[str] = None
|
||||
image_url: Optional[ImageURL] = None
|
||||
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
role: Role
|
||||
content: Optional[Union[str, List[MultimodalInputItem]]] = None
|
||||
tool_calls: Optional[List[FunctionCall]] = None
|
||||
|
||||
|
||||
class ChatCompletionMessage(BaseModel):
|
||||
role: Optional[Role] = None
|
||||
content: Optional[str] = None
|
||||
tool_calls: Optional[List[FunctionCall]] = None
|
||||
|
||||
|
||||
class ChatCompletionRequest(BaseModel):
|
||||
model: str
|
||||
messages: List[ChatMessage]
|
||||
tools: Optional[List[FunctionAvailable]] = None
|
||||
do_sample: bool = True
|
||||
temperature: Optional[float] = None
|
||||
top_p: Optional[float] = None
|
||||
n: int = 1
|
||||
max_tokens: Optional[int] = None
|
||||
stop: Optional[Union[str, List[str]]] = None
|
||||
stream: bool = False
|
||||
|
||||
|
||||
class ChatCompletionResponseChoice(BaseModel):
|
||||
index: int
|
||||
message: ChatCompletionMessage
|
||||
finish_reason: Finish
|
||||
|
||||
|
||||
class ChatCompletionStreamResponseChoice(BaseModel):
|
||||
index: int
|
||||
delta: ChatCompletionMessage
|
||||
finish_reason: Optional[Finish] = None
|
||||
|
||||
|
||||
class ChatCompletionResponseUsage(BaseModel):
|
||||
prompt_tokens: int
|
||||
completion_tokens: int
|
||||
total_tokens: int
|
||||
|
||||
|
||||
class ChatCompletionResponse(BaseModel):
|
||||
id: str
|
||||
object: Literal["chat.completion"] = "chat.completion"
|
||||
created: int = Field(default_factory=lambda: int(time.time()))
|
||||
model: str
|
||||
choices: List[ChatCompletionResponseChoice]
|
||||
usage: ChatCompletionResponseUsage
|
||||
|
||||
|
||||
class ChatCompletionStreamResponse(BaseModel):
|
||||
id: str
|
||||
object: Literal["chat.completion.chunk"] = "chat.completion.chunk"
|
||||
created: int = Field(default_factory=lambda: int(time.time()))
|
||||
model: str
|
||||
choices: List[ChatCompletionStreamResponseChoice]
|
||||
|
||||
|
||||
class ScoreEvaluationRequest(BaseModel):
|
||||
model: str
|
||||
messages: List[str]
|
||||
max_length: Optional[int] = None
|
||||
|
||||
|
||||
class ScoreEvaluationResponse(BaseModel):
|
||||
id: str
|
||||
object: Literal["score.evaluation"] = "score.evaluation"
|
||||
model: str
|
||||
scores: List[float]
|
||||
@@ -0,0 +1,5 @@
|
||||
from .base_engine import BaseEngine
|
||||
from .chat_model import ChatModel
|
||||
|
||||
|
||||
__all__ = ["BaseEngine", "ChatModel"]
|
||||
@@ -0,0 +1,69 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Literal, Optional, Sequence, Union
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from numpy.typing import NDArray
|
||||
from transformers import PreTrainedModel, PreTrainedTokenizer
|
||||
from vllm import AsyncLLMEngine
|
||||
|
||||
from ..data import Template
|
||||
from ..hparams import DataArguments, FinetuningArguments, GeneratingArguments, ModelArguments
|
||||
|
||||
|
||||
@dataclass
|
||||
class Response:
|
||||
response_text: str
|
||||
response_length: int
|
||||
prompt_length: int
|
||||
finish_reason: Literal["stop", "length"]
|
||||
|
||||
|
||||
class BaseEngine(ABC):
|
||||
model: Union["PreTrainedModel", "AsyncLLMEngine"]
|
||||
tokenizer: "PreTrainedTokenizer"
|
||||
can_generate: bool
|
||||
template: "Template"
|
||||
generating_args: Dict[str, Any]
|
||||
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
model_args: "ModelArguments",
|
||||
data_args: "DataArguments",
|
||||
finetuning_args: "FinetuningArguments",
|
||||
generating_args: "GeneratingArguments",
|
||||
) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
async def start(
|
||||
self,
|
||||
) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
async def chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> List["Response"]: ...
|
||||
|
||||
@abstractmethod
|
||||
async def stream_chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> AsyncGenerator[str, None]: ...
|
||||
|
||||
@abstractmethod
|
||||
async def get_scores(
|
||||
self,
|
||||
batch_input: List[str],
|
||||
**input_kwargs,
|
||||
) -> List[float]: ...
|
||||
@@ -0,0 +1,140 @@
|
||||
import asyncio
|
||||
from threading import Thread
|
||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, Generator, List, Optional, Sequence
|
||||
|
||||
from ..extras.misc import torch_gc
|
||||
from ..hparams import get_infer_args
|
||||
from .hf_engine import HuggingfaceEngine
|
||||
from .vllm_engine import VllmEngine
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from .base_engine import BaseEngine, Response
|
||||
|
||||
|
||||
def _start_background_loop(loop: asyncio.AbstractEventLoop) -> None:
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_forever()
|
||||
|
||||
|
||||
class ChatModel:
|
||||
def __init__(self, args: Optional[Dict[str, Any]] = None) -> None:
|
||||
model_args, data_args, finetuning_args, generating_args = get_infer_args(args)
|
||||
if model_args.infer_backend == "huggingface":
|
||||
self.engine: "BaseEngine" = HuggingfaceEngine(model_args, data_args, finetuning_args, generating_args)
|
||||
elif model_args.infer_backend == "vllm":
|
||||
self.engine: "BaseEngine" = VllmEngine(model_args, data_args, finetuning_args, generating_args)
|
||||
else:
|
||||
raise NotImplementedError("Unknown backend: {}".format(model_args.infer_backend))
|
||||
|
||||
self._loop = asyncio.new_event_loop()
|
||||
self._thread = Thread(target=_start_background_loop, args=(self._loop,), daemon=True)
|
||||
self._thread.start()
|
||||
asyncio.run_coroutine_threadsafe(self.engine.start(), self._loop)
|
||||
|
||||
def chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> List["Response"]:
|
||||
task = asyncio.run_coroutine_threadsafe(self.achat(messages, system, tools, image, **input_kwargs), self._loop)
|
||||
return task.result()
|
||||
|
||||
async def achat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> List["Response"]:
|
||||
return await self.engine.chat(messages, system, tools, image, **input_kwargs)
|
||||
|
||||
def stream_chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> Generator[str, None, None]:
|
||||
generator = self.astream_chat(messages, system, tools, image, **input_kwargs)
|
||||
while True:
|
||||
try:
|
||||
task = asyncio.run_coroutine_threadsafe(generator.__anext__(), self._loop)
|
||||
yield task.result()
|
||||
except StopAsyncIteration:
|
||||
break
|
||||
|
||||
async def astream_chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
async for new_token in self.engine.stream_chat(messages, system, tools, image, **input_kwargs):
|
||||
yield new_token
|
||||
|
||||
def get_scores(
|
||||
self,
|
||||
batch_input: List[str],
|
||||
**input_kwargs,
|
||||
) -> List[float]:
|
||||
task = asyncio.run_coroutine_threadsafe(self.aget_scores(batch_input, **input_kwargs), self._loop)
|
||||
return task.result()
|
||||
|
||||
async def aget_scores(
|
||||
self,
|
||||
batch_input: List[str],
|
||||
**input_kwargs,
|
||||
) -> List[float]:
|
||||
return await self.engine.get_scores(batch_input, **input_kwargs)
|
||||
|
||||
|
||||
def run_chat() -> None:
|
||||
try:
|
||||
import platform
|
||||
|
||||
if platform.system() != "Windows":
|
||||
import readline # noqa: F401
|
||||
except ImportError:
|
||||
print("Install `readline` for a better experience.")
|
||||
|
||||
chat_model = ChatModel()
|
||||
messages = []
|
||||
print("Welcome to the CLI application, use `clear` to remove the history, use `exit` to exit the application.")
|
||||
|
||||
while True:
|
||||
try:
|
||||
query = input("\nUser: ")
|
||||
except UnicodeDecodeError:
|
||||
print("Detected decoding error at the inputs, please set the terminal encoding to utf-8.")
|
||||
continue
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
if query.strip() == "exit":
|
||||
break
|
||||
|
||||
if query.strip() == "clear":
|
||||
messages = []
|
||||
torch_gc()
|
||||
print("History has been removed.")
|
||||
continue
|
||||
|
||||
messages.append({"role": "user", "content": query})
|
||||
print("Assistant: ", end="", flush=True)
|
||||
|
||||
response = ""
|
||||
for new_text in chat_model.stream_chat(messages):
|
||||
print(new_text, end="", flush=True)
|
||||
response += new_text
|
||||
print()
|
||||
messages.append({"role": "assistant", "content": response})
|
||||
@@ -0,0 +1,324 @@
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import os
|
||||
from threading import Thread
|
||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Dict, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
import torch
|
||||
from transformers import GenerationConfig, TextIteratorStreamer
|
||||
|
||||
from ..data import get_template_and_fix_tokenizer
|
||||
from ..extras.logging import get_logger
|
||||
from ..extras.misc import get_logits_processor
|
||||
from ..model import load_model, load_tokenizer
|
||||
from .base_engine import BaseEngine, Response
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from numpy.typing import NDArray
|
||||
from transformers import PreTrainedModel, PreTrainedTokenizer, ProcessorMixin
|
||||
from transformers.image_processing_utils import BaseImageProcessor
|
||||
from trl import PreTrainedModelWrapper
|
||||
|
||||
from ..data import Template
|
||||
from ..hparams import DataArguments, FinetuningArguments, GeneratingArguments, ModelArguments
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class HuggingfaceEngine(BaseEngine):
|
||||
def __init__(
|
||||
self,
|
||||
model_args: "ModelArguments",
|
||||
data_args: "DataArguments",
|
||||
finetuning_args: "FinetuningArguments",
|
||||
generating_args: "GeneratingArguments",
|
||||
) -> None:
|
||||
self.can_generate = finetuning_args.stage == "sft"
|
||||
tokenizer_module = load_tokenizer(model_args)
|
||||
self.tokenizer = tokenizer_module["tokenizer"]
|
||||
self.processor = tokenizer_module["processor"]
|
||||
self.tokenizer.padding_side = "left" if self.can_generate else "right"
|
||||
self.template = get_template_and_fix_tokenizer(self.tokenizer, data_args.template)
|
||||
self.model = load_model(
|
||||
self.tokenizer, model_args, finetuning_args, is_trainable=False, add_valuehead=(not self.can_generate)
|
||||
) # must after fixing tokenizer to resize vocab
|
||||
self.generating_args = generating_args.to_dict()
|
||||
|
||||
@staticmethod
|
||||
def _process_args(
|
||||
model: "PreTrainedModel",
|
||||
tokenizer: "PreTrainedTokenizer",
|
||||
processor: Optional["ProcessorMixin"],
|
||||
template: "Template",
|
||||
generating_args: Dict[str, Any],
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
input_kwargs: Optional[Dict[str, Any]] = {},
|
||||
) -> Tuple[Dict[str, Any], int]:
|
||||
if (
|
||||
processor is not None
|
||||
and image is not None
|
||||
and not hasattr(processor, "image_seq_length")
|
||||
and template.image_token not in messages[0]["content"]
|
||||
): # llava-like models
|
||||
messages[0]["content"] = template.image_token + messages[0]["content"]
|
||||
|
||||
paired_messages = messages + [{"role": "assistant", "content": ""}]
|
||||
system = system or generating_args["default_system"]
|
||||
pixel_values = None
|
||||
prompt_ids, _ = template.encode_oneturn(
|
||||
tokenizer=tokenizer, messages=paired_messages, system=system, tools=tools
|
||||
)
|
||||
if processor is not None and image is not None: # add image features
|
||||
image_processor: "BaseImageProcessor" = getattr(processor, "image_processor")
|
||||
batch_feature = image_processor(image, return_tensors="pt")
|
||||
pixel_values = batch_feature.to(model.device)["pixel_values"] # shape (B, C, H, W)
|
||||
if hasattr(processor, "image_seq_length"): # paligemma models
|
||||
image_token_id = tokenizer.convert_tokens_to_ids(template.image_token)
|
||||
prompt_ids = [image_token_id] * getattr(processor, "image_seq_length") + prompt_ids
|
||||
|
||||
prompt_length = len(prompt_ids)
|
||||
inputs = torch.tensor([prompt_ids], device=model.device)
|
||||
attention_mask = torch.ones_like(inputs, dtype=torch.bool)
|
||||
|
||||
do_sample: Optional[bool] = input_kwargs.pop("do_sample", None)
|
||||
temperature: Optional[float] = input_kwargs.pop("temperature", None)
|
||||
top_p: Optional[float] = input_kwargs.pop("top_p", None)
|
||||
top_k: Optional[float] = input_kwargs.pop("top_k", None)
|
||||
num_return_sequences: int = input_kwargs.pop("num_return_sequences", 1)
|
||||
repetition_penalty: Optional[float] = input_kwargs.pop("repetition_penalty", None)
|
||||
length_penalty: Optional[float] = input_kwargs.pop("length_penalty", None)
|
||||
max_length: Optional[int] = input_kwargs.pop("max_length", None)
|
||||
max_new_tokens: Optional[int] = input_kwargs.pop("max_new_tokens", None)
|
||||
stop: Optional[Union[str, List[str]]] = input_kwargs.pop("stop", None)
|
||||
|
||||
if stop is not None:
|
||||
logger.warning("Stop parameter is not supported in Huggingface engine yet.")
|
||||
|
||||
generating_args = generating_args.copy()
|
||||
generating_args.update(
|
||||
dict(
|
||||
do_sample=do_sample if do_sample is not None else generating_args["do_sample"],
|
||||
temperature=temperature if temperature is not None else generating_args["temperature"],
|
||||
top_p=top_p if top_p is not None else generating_args["top_p"],
|
||||
top_k=top_k if top_k is not None else generating_args["top_k"],
|
||||
num_return_sequences=num_return_sequences,
|
||||
repetition_penalty=repetition_penalty
|
||||
if repetition_penalty is not None
|
||||
else generating_args["repetition_penalty"],
|
||||
length_penalty=length_penalty if length_penalty is not None else generating_args["length_penalty"],
|
||||
eos_token_id=[tokenizer.eos_token_id] + tokenizer.additional_special_tokens_ids,
|
||||
pad_token_id=tokenizer.pad_token_id,
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(num_return_sequences, int) and num_return_sequences > 1: # do_sample needs temperature > 0
|
||||
generating_args["do_sample"] = True
|
||||
generating_args["temperature"] = generating_args["temperature"] or 1.0
|
||||
|
||||
if not generating_args["temperature"]:
|
||||
generating_args["do_sample"] = False
|
||||
|
||||
if not generating_args["do_sample"]:
|
||||
generating_args.pop("temperature", None)
|
||||
generating_args.pop("top_p", None)
|
||||
|
||||
if max_length:
|
||||
generating_args.pop("max_new_tokens", None)
|
||||
generating_args["max_length"] = max_length
|
||||
|
||||
if max_new_tokens:
|
||||
generating_args.pop("max_length", None)
|
||||
generating_args["max_new_tokens"] = max_new_tokens
|
||||
|
||||
gen_kwargs = dict(
|
||||
inputs=inputs,
|
||||
attention_mask=attention_mask,
|
||||
generation_config=GenerationConfig(**generating_args),
|
||||
logits_processor=get_logits_processor(),
|
||||
)
|
||||
|
||||
if pixel_values is not None:
|
||||
gen_kwargs["pixel_values"] = pixel_values
|
||||
|
||||
return gen_kwargs, prompt_length
|
||||
|
||||
@staticmethod
|
||||
@torch.inference_mode()
|
||||
def _chat(
|
||||
model: "PreTrainedModel",
|
||||
tokenizer: "PreTrainedTokenizer",
|
||||
processor: Optional["ProcessorMixin"],
|
||||
template: "Template",
|
||||
generating_args: Dict[str, Any],
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
input_kwargs: Optional[Dict[str, Any]] = {},
|
||||
) -> List["Response"]:
|
||||
gen_kwargs, prompt_length = HuggingfaceEngine._process_args(
|
||||
model, tokenizer, processor, template, generating_args, messages, system, tools, image, input_kwargs
|
||||
)
|
||||
generate_output = model.generate(**gen_kwargs)
|
||||
response_ids = generate_output[:, prompt_length:]
|
||||
response = tokenizer.batch_decode(response_ids, skip_special_tokens=True, clean_up_tokenization_spaces=True)
|
||||
results = []
|
||||
for i in range(len(response)):
|
||||
eos_index = (response_ids[i] == tokenizer.eos_token_id).nonzero()
|
||||
response_length = (eos_index[0].item() + 1) if len(eos_index) else len(response_ids[i])
|
||||
results.append(
|
||||
Response(
|
||||
response_text=response[i],
|
||||
response_length=response_length,
|
||||
prompt_length=prompt_length,
|
||||
finish_reason="stop" if len(eos_index) else "length",
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
@torch.inference_mode()
|
||||
def _stream_chat(
|
||||
model: "PreTrainedModel",
|
||||
tokenizer: "PreTrainedTokenizer",
|
||||
processor: Optional["ProcessorMixin"],
|
||||
template: "Template",
|
||||
generating_args: Dict[str, Any],
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
input_kwargs: Optional[Dict[str, Any]] = {},
|
||||
) -> Callable[[], str]:
|
||||
gen_kwargs, _ = HuggingfaceEngine._process_args(
|
||||
model, tokenizer, processor, template, generating_args, messages, system, tools, image, input_kwargs
|
||||
)
|
||||
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
|
||||
gen_kwargs["streamer"] = streamer
|
||||
thread = Thread(target=model.generate, kwargs=gen_kwargs, daemon=True)
|
||||
thread.start()
|
||||
|
||||
def stream():
|
||||
try:
|
||||
return streamer.__next__()
|
||||
except StopIteration:
|
||||
raise StopAsyncIteration()
|
||||
|
||||
return stream
|
||||
|
||||
@staticmethod
|
||||
@torch.inference_mode()
|
||||
def _get_scores(
|
||||
model: "PreTrainedModelWrapper",
|
||||
tokenizer: "PreTrainedTokenizer",
|
||||
batch_input: List[str],
|
||||
input_kwargs: Optional[Dict[str, Any]] = {},
|
||||
) -> List[float]:
|
||||
max_length = input_kwargs.pop("max_length", None)
|
||||
device = getattr(model.pretrained_model, "device", "cuda")
|
||||
inputs = tokenizer(
|
||||
batch_input,
|
||||
padding=True,
|
||||
truncation=True,
|
||||
max_length=max_length or getattr(model.config, "max_position_embeddings", 1024),
|
||||
return_tensors="pt",
|
||||
add_special_tokens=True,
|
||||
).to(device)
|
||||
|
||||
input_ids: torch.Tensor = inputs["input_ids"]
|
||||
_, _, values = model(**inputs, output_hidden_states=True, return_dict=True)
|
||||
|
||||
if getattr(model.config, "model_type", None) == "chatglm":
|
||||
values = torch.transpose(values, 0, 1)
|
||||
|
||||
scores = []
|
||||
for i in range(input_ids.size(0)):
|
||||
end_indexes = (input_ids[i] != tokenizer.pad_token_id).nonzero()
|
||||
end_index = end_indexes[-1].item() if len(end_indexes) else 0
|
||||
scores.append(values[i, end_index].nan_to_num().item())
|
||||
|
||||
return scores
|
||||
|
||||
async def start(self) -> None:
|
||||
self._semaphore = asyncio.Semaphore(int(os.environ.get("MAX_CONCURRENT", 1)))
|
||||
|
||||
async def chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> List["Response"]:
|
||||
if not self.can_generate:
|
||||
raise ValueError("The current model does not support `chat`.")
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
input_args = (
|
||||
self.model,
|
||||
self.tokenizer,
|
||||
self.processor,
|
||||
self.template,
|
||||
self.generating_args,
|
||||
messages,
|
||||
system,
|
||||
tools,
|
||||
image,
|
||||
input_kwargs,
|
||||
)
|
||||
async with self._semaphore:
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
return await loop.run_in_executor(pool, self._chat, *input_args)
|
||||
|
||||
async def stream_chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
if not self.can_generate:
|
||||
raise ValueError("The current model does not support `stream_chat`.")
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
input_args = (
|
||||
self.model,
|
||||
self.tokenizer,
|
||||
self.processor,
|
||||
self.template,
|
||||
self.generating_args,
|
||||
messages,
|
||||
system,
|
||||
tools,
|
||||
image,
|
||||
input_kwargs,
|
||||
)
|
||||
async with self._semaphore:
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
stream = self._stream_chat(*input_args)
|
||||
while True:
|
||||
try:
|
||||
yield await loop.run_in_executor(pool, stream)
|
||||
except StopAsyncIteration:
|
||||
break
|
||||
|
||||
async def get_scores(
|
||||
self,
|
||||
batch_input: List[str],
|
||||
**input_kwargs,
|
||||
) -> List[float]:
|
||||
if self.can_generate:
|
||||
raise ValueError("Cannot get scores using an auto-regressive model.")
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
input_args = (self.model, self.tokenizer, batch_input, input_kwargs)
|
||||
async with self._semaphore:
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
return await loop.run_in_executor(pool, self._get_scores, *input_args)
|
||||
@@ -0,0 +1,214 @@
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING, AsyncGenerator, AsyncIterator, Dict, List, Optional, Sequence, Union
|
||||
|
||||
from ..data import get_template_and_fix_tokenizer
|
||||
from ..extras.logging import get_logger
|
||||
from ..extras.misc import get_device_count
|
||||
from ..extras.packages import is_vllm_available
|
||||
from ..model import load_config, load_tokenizer
|
||||
from ..model.model_utils.visual import LlavaMultiModalProjectorForYiVLForVLLM
|
||||
from .base_engine import BaseEngine, Response
|
||||
|
||||
|
||||
if is_vllm_available():
|
||||
from vllm import AsyncEngineArgs, AsyncLLMEngine, RequestOutput, SamplingParams
|
||||
from vllm.lora.request import LoRARequest
|
||||
from vllm.sequence import MultiModalData
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from numpy.typing import NDArray
|
||||
from transformers.image_processing_utils import BaseImageProcessor
|
||||
|
||||
from ..hparams import DataArguments, FinetuningArguments, GeneratingArguments, ModelArguments
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class VllmEngine(BaseEngine):
|
||||
def __init__(
|
||||
self,
|
||||
model_args: "ModelArguments",
|
||||
data_args: "DataArguments",
|
||||
finetuning_args: "FinetuningArguments",
|
||||
generating_args: "GeneratingArguments",
|
||||
) -> None:
|
||||
config = load_config(model_args) # may download model from ms hub
|
||||
|
||||
self.can_generate = finetuning_args.stage == "sft"
|
||||
tokenizer_module = load_tokenizer(model_args)
|
||||
self.tokenizer = tokenizer_module["tokenizer"]
|
||||
self.processor = tokenizer_module["processor"]
|
||||
self.tokenizer.padding_side = "left"
|
||||
self.template = get_template_and_fix_tokenizer(self.tokenizer, data_args.template)
|
||||
self.generating_args = generating_args.to_dict()
|
||||
|
||||
engine_args = {
|
||||
"model": model_args.model_name_or_path,
|
||||
"trust_remote_code": True,
|
||||
"download_dir": model_args.cache_dir,
|
||||
"dtype": model_args.vllm_dtype,
|
||||
"max_model_len": model_args.vllm_maxlen,
|
||||
"tensor_parallel_size": get_device_count() or 1,
|
||||
"gpu_memory_utilization": model_args.vllm_gpu_util,
|
||||
"disable_log_stats": True,
|
||||
"disable_log_requests": True,
|
||||
"enforce_eager": model_args.vllm_enforce_eager,
|
||||
"enable_lora": model_args.adapter_name_or_path is not None,
|
||||
"max_lora_rank": model_args.vllm_max_lora_rank,
|
||||
}
|
||||
|
||||
if model_args.visual_inputs:
|
||||
image_size = config.vision_config.image_size
|
||||
patch_size = config.vision_config.patch_size
|
||||
self.image_feature_size = (image_size // patch_size) ** 2
|
||||
engine_args["image_input_type"] = "pixel_values"
|
||||
engine_args["image_token_id"] = self.tokenizer.convert_tokens_to_ids(self.template.image_token)
|
||||
engine_args["image_input_shape"] = "1,3,{},{}".format(image_size, image_size)
|
||||
engine_args["image_feature_size"] = self.image_feature_size
|
||||
if getattr(config, "is_yi_vl_derived_model", None):
|
||||
import vllm.model_executor.models.llava
|
||||
|
||||
logger.info("Detected Yi-VL model, applying projector patch.")
|
||||
vllm.model_executor.models.llava.LlavaMultiModalProjector = LlavaMultiModalProjectorForYiVLForVLLM
|
||||
|
||||
self.model = AsyncLLMEngine.from_engine_args(AsyncEngineArgs(**engine_args))
|
||||
if model_args.adapter_name_or_path is not None:
|
||||
self.lora_request = LoRARequest("default", 1, model_args.adapter_name_or_path[0])
|
||||
else:
|
||||
self.lora_request = None
|
||||
|
||||
async def _generate(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> AsyncIterator["RequestOutput"]:
|
||||
request_id = "chatcmpl-{}".format(uuid.uuid4().hex)
|
||||
|
||||
if (
|
||||
self.processor is not None
|
||||
and image is not None
|
||||
and not hasattr(self.processor, "image_seq_length")
|
||||
and self.template.image_token not in messages[0]["content"]
|
||||
): # llava-like models (TODO: paligemma models)
|
||||
messages[0]["content"] = self.template.image_token * self.image_feature_size + messages[0]["content"]
|
||||
|
||||
paired_messages = messages + [{"role": "assistant", "content": ""}]
|
||||
system = system or self.generating_args["default_system"]
|
||||
prompt_ids, _ = self.template.encode_oneturn(
|
||||
tokenizer=self.tokenizer, messages=paired_messages, system=system, tools=tools
|
||||
)
|
||||
|
||||
if self.processor is not None and image is not None: # add image features
|
||||
image_processor: "BaseImageProcessor" = getattr(self.processor, "image_processor")
|
||||
pixel_values = image_processor(image, return_tensors="pt")["pixel_values"]
|
||||
multi_modal_data = MultiModalData(type=MultiModalData.Type.IMAGE, data=pixel_values)
|
||||
else:
|
||||
multi_modal_data = None
|
||||
|
||||
prompt_length = len(prompt_ids)
|
||||
|
||||
use_beam_search: bool = self.generating_args["num_beams"] > 1
|
||||
temperature: Optional[float] = input_kwargs.pop("temperature", None)
|
||||
top_p: Optional[float] = input_kwargs.pop("top_p", None)
|
||||
top_k: Optional[float] = input_kwargs.pop("top_k", None)
|
||||
num_return_sequences: int = input_kwargs.pop("num_return_sequences", 1)
|
||||
repetition_penalty: Optional[float] = input_kwargs.pop("repetition_penalty", None)
|
||||
length_penalty: Optional[float] = input_kwargs.pop("length_penalty", None)
|
||||
max_length: Optional[int] = input_kwargs.pop("max_length", None)
|
||||
max_new_tokens: Optional[int] = input_kwargs.pop("max_new_tokens", None)
|
||||
stop: Optional[Union[str, List[str]]] = input_kwargs.pop("stop", None)
|
||||
|
||||
if "max_new_tokens" in self.generating_args:
|
||||
max_tokens = self.generating_args["max_new_tokens"]
|
||||
elif "max_length" in self.generating_args:
|
||||
if self.generating_args["max_length"] > prompt_length:
|
||||
max_tokens = self.generating_args["max_length"] - prompt_length
|
||||
else:
|
||||
max_tokens = 1
|
||||
|
||||
if max_length:
|
||||
max_tokens = max_length - prompt_length if max_length > prompt_length else 1
|
||||
|
||||
if max_new_tokens:
|
||||
max_tokens = max_new_tokens
|
||||
|
||||
sampling_params = SamplingParams(
|
||||
n=num_return_sequences,
|
||||
repetition_penalty=(
|
||||
repetition_penalty if repetition_penalty is not None else self.generating_args["repetition_penalty"]
|
||||
)
|
||||
or 1.0, # repetition_penalty must > 0
|
||||
temperature=temperature if temperature is not None else self.generating_args["temperature"],
|
||||
top_p=(top_p if top_p is not None else self.generating_args["top_p"]) or 1.0, # top_p must > 0
|
||||
top_k=top_k if top_k is not None else self.generating_args["top_k"],
|
||||
use_beam_search=use_beam_search,
|
||||
length_penalty=length_penalty if length_penalty is not None else self.generating_args["length_penalty"],
|
||||
stop=stop,
|
||||
stop_token_ids=[self.tokenizer.eos_token_id] + self.tokenizer.additional_special_tokens_ids,
|
||||
max_tokens=max_tokens,
|
||||
skip_special_tokens=True,
|
||||
)
|
||||
|
||||
result_generator = self.model.generate(
|
||||
inputs={"prompt_token_ids": prompt_ids, "multi_modal_data": multi_modal_data},
|
||||
sampling_params=sampling_params,
|
||||
request_id=request_id,
|
||||
lora_request=self.lora_request,
|
||||
)
|
||||
return result_generator
|
||||
|
||||
async def start(self) -> None:
|
||||
pass
|
||||
|
||||
async def chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> List["Response"]:
|
||||
final_output = None
|
||||
generator = await self._generate(messages, system, tools, image, **input_kwargs)
|
||||
async for request_output in generator:
|
||||
final_output = request_output
|
||||
|
||||
results = []
|
||||
for output in final_output.outputs:
|
||||
results.append(
|
||||
Response(
|
||||
response_text=output.text,
|
||||
response_length=len(output.token_ids),
|
||||
prompt_length=len(final_output.prompt_token_ids),
|
||||
finish_reason=output.finish_reason,
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
async def stream_chat(
|
||||
self,
|
||||
messages: Sequence[Dict[str, str]],
|
||||
system: Optional[str] = None,
|
||||
tools: Optional[str] = None,
|
||||
image: Optional["NDArray"] = None,
|
||||
**input_kwargs,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
generated_text = ""
|
||||
generator = await self._generate(messages, system, tools, image, **input_kwargs)
|
||||
async for result in generator:
|
||||
delta_text = result.outputs[0].text[len(generated_text) :]
|
||||
generated_text = result.outputs[0].text
|
||||
yield delta_text
|
||||
|
||||
async def get_scores(
|
||||
self,
|
||||
batch_input: List[str],
|
||||
**input_kwargs,
|
||||
) -> List[float]:
|
||||
raise NotImplementedError("vLLM engine does not support get_scores.")
|
||||
106
src/AntSK.LLamaFactory/llamafactory/llamafactory/cli.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
from enum import Enum, unique
|
||||
|
||||
from . import launcher
|
||||
from .api.app import run_api
|
||||
from .chat.chat_model import run_chat
|
||||
from .eval.evaluator import run_eval
|
||||
from .extras.env import VERSION, print_env
|
||||
from .extras.logging import get_logger
|
||||
from .extras.misc import get_device_count
|
||||
from .train.tuner import export_model, run_exp
|
||||
from .webui.interface import run_web_demo, run_web_ui
|
||||
|
||||
|
||||
USAGE = (
|
||||
"-" * 70
|
||||
+ "\n"
|
||||
+ "| Usage: |\n"
|
||||
+ "| llamafactory-cli api -h: launch an OpenAI-style API server |\n"
|
||||
+ "| llamafactory-cli chat -h: launch a chat interface in CLI |\n"
|
||||
+ "| llamafactory-cli eval -h: evaluate models |\n"
|
||||
+ "| llamafactory-cli export -h: merge LoRA adapters and export model |\n"
|
||||
+ "| llamafactory-cli train -h: train models |\n"
|
||||
+ "| llamafactory-cli webchat -h: launch a chat interface in Web UI |\n"
|
||||
+ "| llamafactory-cli webui: launch LlamaBoard |\n"
|
||||
+ "| llamafactory-cli version: show version info |\n"
|
||||
+ "-" * 70
|
||||
)
|
||||
|
||||
WELCOME = (
|
||||
"-" * 58
|
||||
+ "\n"
|
||||
+ "| Welcome to LLaMA Factory, version {}".format(VERSION)
|
||||
+ " " * (21 - len(VERSION))
|
||||
+ "|\n|"
|
||||
+ " " * 56
|
||||
+ "|\n"
|
||||
+ "| Project page: https://github.com/hiyouga/LLaMA-Factory |\n"
|
||||
+ "-" * 58
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@unique
|
||||
class Command(str, Enum):
|
||||
API = "api"
|
||||
CHAT = "chat"
|
||||
ENV = "env"
|
||||
EVAL = "eval"
|
||||
EXPORT = "export"
|
||||
TRAIN = "train"
|
||||
WEBDEMO = "webchat"
|
||||
WEBUI = "webui"
|
||||
VER = "version"
|
||||
HELP = "help"
|
||||
|
||||
|
||||
def main():
|
||||
command = sys.argv.pop(1)
|
||||
if command == Command.API:
|
||||
run_api()
|
||||
elif command == Command.CHAT:
|
||||
run_chat()
|
||||
elif command == Command.ENV:
|
||||
print_env()
|
||||
elif command == Command.EVAL:
|
||||
run_eval()
|
||||
elif command == Command.EXPORT:
|
||||
export_model()
|
||||
elif command == Command.TRAIN:
|
||||
force_torchrun = os.environ.get("FORCE_TORCHRUN", "0").lower() in ["true", "1"]
|
||||
if force_torchrun or get_device_count() > 1:
|
||||
master_addr = os.environ.get("MASTER_ADDR", "127.0.0.1")
|
||||
master_port = os.environ.get("MASTER_PORT", str(random.randint(20001, 29999)))
|
||||
logger.info("Initializing distributed tasks at: {}:{}".format(master_addr, master_port))
|
||||
subprocess.run(
|
||||
(
|
||||
"torchrun --nnodes {nnodes} --node_rank {node_rank} --nproc_per_node {nproc_per_node} "
|
||||
"--master_addr {master_addr} --master_port {master_port} {file_name} {args}"
|
||||
).format(
|
||||
nnodes=os.environ.get("NNODES", "1"),
|
||||
node_rank=os.environ.get("RANK", "0"),
|
||||
nproc_per_node=os.environ.get("NPROC_PER_NODE", str(get_device_count())),
|
||||
master_addr=master_addr,
|
||||
master_port=master_port,
|
||||
file_name=launcher.__file__,
|
||||
args=" ".join(sys.argv[1:]),
|
||||
),
|
||||
shell=True,
|
||||
)
|
||||
else:
|
||||
run_exp()
|
||||
elif command == Command.WEBDEMO:
|
||||
run_web_demo()
|
||||
elif command == Command.WEBUI:
|
||||
run_web_ui()
|
||||
elif command == Command.VER:
|
||||
print(WELCOME)
|
||||
elif command == Command.HELP:
|
||||
print(USAGE)
|
||||
else:
|
||||
raise NotImplementedError("Unknown command: {}".format(command))
|
||||
@@ -0,0 +1,16 @@
|
||||
from .collator import KTODataCollatorWithPadding, PairwiseDataCollatorWithPadding
|
||||
from .data_utils import Role, split_dataset
|
||||
from .loader import get_dataset
|
||||
from .template import TEMPLATES, Template, get_template_and_fix_tokenizer
|
||||
|
||||
|
||||
__all__ = [
|
||||
"KTODataCollatorWithPadding",
|
||||
"PairwiseDataCollatorWithPadding",
|
||||
"Role",
|
||||
"split_dataset",
|
||||
"get_dataset",
|
||||
"TEMPLATES",
|
||||
"Template",
|
||||
"get_template_and_fix_tokenizer",
|
||||
]
|
||||
221
src/AntSK.LLamaFactory/llamafactory/llamafactory/data/aligner.py
Normal file
@@ -0,0 +1,221 @@
|
||||
import os
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Union
|
||||
|
||||
from datasets import Features
|
||||
|
||||
from ..extras.logging import get_logger
|
||||
from .data_utils import Role
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datasets import Dataset, IterableDataset
|
||||
|
||||
from ..hparams import DataArguments
|
||||
from .parser import DatasetAttr
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def _convert_images(images: List[Any], dataset_attr: "DatasetAttr", data_args: "DataArguments") -> List[Any]:
|
||||
r"""
|
||||
Optionally concatenates image path to dataset dir when loading from local disk.
|
||||
"""
|
||||
outputs = []
|
||||
if dataset_attr.load_from in ["script", "file"]:
|
||||
for image in images:
|
||||
if isinstance(image, str) and os.path.isfile(os.path.join(data_args.dataset_dir, image)):
|
||||
outputs.append(os.path.join(data_args.dataset_dir, image))
|
||||
else:
|
||||
outputs.append(image)
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def convert_alpaca(
|
||||
examples: Dict[str, List[Any]], dataset_attr: "DatasetAttr", data_args: "DataArguments"
|
||||
) -> Dict[str, List[Any]]:
|
||||
r"""
|
||||
Converts alpaca format dataset to the standard format.
|
||||
"""
|
||||
outputs = {"prompt": [], "response": [], "system": [], "tools": [], "images": []}
|
||||
convert_images = partial(_convert_images, dataset_attr=dataset_attr, data_args=data_args)
|
||||
for i in range(len(examples[dataset_attr.prompt])):
|
||||
prompt = []
|
||||
if dataset_attr.history and isinstance(examples[dataset_attr.history][i], list):
|
||||
for old_prompt, old_response in examples[dataset_attr.history][i]:
|
||||
prompt.append({"role": Role.USER.value, "content": old_prompt})
|
||||
prompt.append({"role": Role.ASSISTANT.value, "content": old_response})
|
||||
|
||||
content = []
|
||||
if dataset_attr.prompt and examples[dataset_attr.prompt][i]:
|
||||
content.append(examples[dataset_attr.prompt][i])
|
||||
|
||||
if dataset_attr.query and examples[dataset_attr.query][i]:
|
||||
content.append(examples[dataset_attr.query][i])
|
||||
|
||||
prompt.append({"role": Role.USER.value, "content": "\n".join(content)}) # "prompt\nquery"
|
||||
|
||||
if dataset_attr.kto_tag and isinstance(examples[dataset_attr.kto_tag][i], bool): # kto example
|
||||
response = [{"role": Role.ASSISTANT.value, "content": examples[dataset_attr.response][i]}]
|
||||
if examples[dataset_attr.kto_tag][i]:
|
||||
response = response + [{"role": Role.ASSISTANT.value, "content": ""}]
|
||||
else:
|
||||
response = [{"role": Role.ASSISTANT.value, "content": ""}] + response
|
||||
elif (
|
||||
dataset_attr.ranking
|
||||
and isinstance(examples[dataset_attr.chosen][i], str)
|
||||
and isinstance(examples[dataset_attr.rejected][i], str)
|
||||
): # pairwise example
|
||||
response = [
|
||||
{"role": Role.ASSISTANT.value, "content": examples[dataset_attr.chosen][i]},
|
||||
{"role": Role.ASSISTANT.value, "content": examples[dataset_attr.rejected][i]},
|
||||
]
|
||||
elif dataset_attr.response and isinstance(examples[dataset_attr.response][i], str): # normal example
|
||||
response = [{"role": Role.ASSISTANT.value, "content": examples[dataset_attr.response][i]}]
|
||||
else: # unsupervised
|
||||
response = []
|
||||
|
||||
outputs["prompt"].append(prompt)
|
||||
outputs["response"].append(response)
|
||||
outputs["system"].append(examples[dataset_attr.system][i] if dataset_attr.system else "")
|
||||
outputs["tools"].append(examples[dataset_attr.tools][i] if dataset_attr.tools else "")
|
||||
outputs["images"].append(convert_images(examples[dataset_attr.images][i]) if dataset_attr.images else [])
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def convert_sharegpt(
|
||||
examples: Dict[str, List[Any]], dataset_attr: "DatasetAttr", data_args: "DataArguments"
|
||||
) -> Dict[str, List[Any]]:
|
||||
r"""
|
||||
Converts sharegpt format dataset to the standard format.
|
||||
"""
|
||||
outputs = {"prompt": [], "response": [], "system": [], "tools": [], "images": []}
|
||||
convert_images = partial(_convert_images, dataset_attr=dataset_attr, data_args=data_args)
|
||||
tag_mapping = {
|
||||
dataset_attr.user_tag: Role.USER.value,
|
||||
dataset_attr.assistant_tag: Role.ASSISTANT.value,
|
||||
dataset_attr.observation_tag: Role.OBSERVATION.value,
|
||||
dataset_attr.function_tag: Role.FUNCTION.value,
|
||||
dataset_attr.system_tag: Role.SYSTEM.value,
|
||||
}
|
||||
odd_tags = (dataset_attr.user_tag, dataset_attr.observation_tag)
|
||||
even_tags = (dataset_attr.assistant_tag, dataset_attr.function_tag)
|
||||
accept_tags = (odd_tags, even_tags)
|
||||
for i, messages in enumerate(examples[dataset_attr.messages]):
|
||||
if dataset_attr.system_tag and messages[0][dataset_attr.role_tag] == dataset_attr.system_tag:
|
||||
system = messages[0][dataset_attr.content_tag]
|
||||
messages = messages[1:]
|
||||
else:
|
||||
system = examples[dataset_attr.system][i] if dataset_attr.system else ""
|
||||
|
||||
if len(messages) == 0:
|
||||
continue
|
||||
|
||||
aligned_messages = []
|
||||
broken_data = False
|
||||
for turn_idx, message in enumerate(messages):
|
||||
if message[dataset_attr.role_tag] not in accept_tags[turn_idx % 2]:
|
||||
logger.warning("Invalid role tag in {}.".format(messages))
|
||||
broken_data = True
|
||||
|
||||
aligned_messages.append(
|
||||
{"role": tag_mapping[message[dataset_attr.role_tag]], "content": message[dataset_attr.content_tag]}
|
||||
)
|
||||
|
||||
if (not dataset_attr.ranking and len(aligned_messages) % 2 != 0) or (
|
||||
dataset_attr.ranking and len(aligned_messages) % 2 == 0
|
||||
):
|
||||
logger.warning("Invalid message count in {}.".format(messages))
|
||||
broken_data = True
|
||||
|
||||
if dataset_attr.kto_tag and isinstance(examples[dataset_attr.kto_tag][i], bool): # kto example
|
||||
prompt = aligned_messages[:-1]
|
||||
response = aligned_messages[-1:]
|
||||
if examples[dataset_attr.kto_tag][i]:
|
||||
response = response + [{"role": Role.ASSISTANT.value, "content": ""}]
|
||||
else:
|
||||
response = [{"role": Role.ASSISTANT.value, "content": ""}] + response
|
||||
elif (
|
||||
dataset_attr.ranking
|
||||
and isinstance(examples[dataset_attr.chosen][i], dict)
|
||||
and isinstance(examples[dataset_attr.rejected][i], dict)
|
||||
): # pairwise example
|
||||
chosen = examples[dataset_attr.chosen][i]
|
||||
rejected = examples[dataset_attr.rejected][i]
|
||||
if (
|
||||
chosen[dataset_attr.role_tag] not in accept_tags[-1]
|
||||
or rejected[dataset_attr.role_tag] not in accept_tags[-1]
|
||||
):
|
||||
logger.warning("Invalid role tag in {}.".format([chosen, rejected]))
|
||||
broken_data = True
|
||||
|
||||
prompt = aligned_messages
|
||||
response = [
|
||||
{"role": tag_mapping[chosen[dataset_attr.role_tag]], "content": chosen[dataset_attr.content_tag]},
|
||||
{"role": tag_mapping[rejected[dataset_attr.role_tag]], "content": rejected[dataset_attr.content_tag]},
|
||||
]
|
||||
else: # normal example
|
||||
prompt = aligned_messages[:-1]
|
||||
response = aligned_messages[-1:]
|
||||
|
||||
if broken_data:
|
||||
logger.warning("Skipping this abnormal example.")
|
||||
continue
|
||||
|
||||
outputs["prompt"].append(prompt)
|
||||
outputs["response"].append(response)
|
||||
outputs["system"].append(system)
|
||||
outputs["tools"].append(examples[dataset_attr.tools][i] if dataset_attr.tools else "")
|
||||
outputs["images"].append(convert_images(examples[dataset_attr.images][i]) if dataset_attr.images else [])
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def align_dataset(
|
||||
dataset: Union["Dataset", "IterableDataset"], dataset_attr: "DatasetAttr", data_args: "DataArguments"
|
||||
) -> Union["Dataset", "IterableDataset"]:
|
||||
r"""
|
||||
Aligned dataset:
|
||||
prompt: [{"role": "user", "content": "..."}] * (2T - 1)
|
||||
response: [{"role": "assistant", "content": "..."}] * N (N > 1 for ranking dataset)
|
||||
system: "..."
|
||||
tools: "...",
|
||||
images: [],
|
||||
"""
|
||||
if dataset_attr.formatting == "alpaca":
|
||||
convert_func = partial(convert_alpaca, dataset_attr=dataset_attr, data_args=data_args)
|
||||
else:
|
||||
convert_func = partial(convert_sharegpt, dataset_attr=dataset_attr, data_args=data_args)
|
||||
|
||||
column_names = list(next(iter(dataset)).keys())
|
||||
features = Features.from_dict(
|
||||
{
|
||||
"prompt": [
|
||||
{"role": {"dtype": "string", "_type": "Value"}, "content": {"dtype": "string", "_type": "Value"}}
|
||||
],
|
||||
"response": [
|
||||
{"role": {"dtype": "string", "_type": "Value"}, "content": {"dtype": "string", "_type": "Value"}}
|
||||
],
|
||||
"system": {"dtype": "string", "_type": "Value"},
|
||||
"tools": {"dtype": "string", "_type": "Value"},
|
||||
"images": [{"_type": "Image"}],
|
||||
}
|
||||
)
|
||||
kwargs = {}
|
||||
if not data_args.streaming:
|
||||
kwargs = dict(
|
||||
num_proc=data_args.preprocessing_num_workers,
|
||||
load_from_cache_file=(not data_args.overwrite_cache),
|
||||
desc="Converting format of dataset",
|
||||
)
|
||||
|
||||
return dataset.map(
|
||||
convert_func,
|
||||
batched=True,
|
||||
remove_columns=column_names,
|
||||
features=features,
|
||||
**kwargs,
|
||||
)
|
||||
@@ -0,0 +1,81 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, Sequence
|
||||
|
||||
import torch
|
||||
from transformers import DataCollatorForSeq2Seq
|
||||
|
||||
|
||||
@dataclass
|
||||
class PairwiseDataCollatorWithPadding(DataCollatorForSeq2Seq):
|
||||
r"""
|
||||
Data collator for pairwise data.
|
||||
"""
|
||||
|
||||
def __call__(self, features: Sequence[Dict[str, Any]]) -> Dict[str, torch.Tensor]:
|
||||
r"""
|
||||
Pads batched data to the longest sequence in the batch.
|
||||
|
||||
We generate 2 * n examples where the first n examples represent chosen examples and
|
||||
the last n examples represent rejected examples.
|
||||
"""
|
||||
concatenated_features = []
|
||||
for key in ("chosen", "rejected"):
|
||||
for feature in features:
|
||||
target_feature = {
|
||||
"input_ids": feature["{}_input_ids".format(key)],
|
||||
"attention_mask": feature["{}_attention_mask".format(key)],
|
||||
"labels": feature["{}_labels".format(key)],
|
||||
}
|
||||
if "pixel_values" in feature:
|
||||
target_feature["pixel_values"] = feature["pixel_values"]
|
||||
|
||||
if "{}_token_type_ids".format(key) in feature:
|
||||
target_feature["token_type_ids"] = feature["{}_token_type_ids".format(key)]
|
||||
|
||||
concatenated_features.append(target_feature)
|
||||
|
||||
return super().__call__(concatenated_features)
|
||||
|
||||
|
||||
@dataclass
|
||||
class KTODataCollatorWithPadding(DataCollatorForSeq2Seq):
|
||||
r"""
|
||||
Data collator for KTO data.
|
||||
"""
|
||||
|
||||
def __call__(self, features: Sequence[Dict[str, Any]]) -> Dict[str, torch.Tensor]:
|
||||
target_features = []
|
||||
kl_features = []
|
||||
kto_tags = []
|
||||
for feature in features:
|
||||
target_feature = {
|
||||
"input_ids": feature["input_ids"],
|
||||
"attention_mask": feature["attention_mask"],
|
||||
"labels": feature["labels"],
|
||||
}
|
||||
kl_feature = {
|
||||
"input_ids": feature["kl_input_ids"],
|
||||
"attention_mask": feature["kl_attention_mask"],
|
||||
"labels": feature["kl_labels"],
|
||||
}
|
||||
if "pixel_values" in feature:
|
||||
target_feature["pixel_values"] = feature["pixel_values"]
|
||||
|
||||
if "token_type_ids" in feature:
|
||||
target_feature["token_type_ids"] = feature["token_type_ids"]
|
||||
kl_feature["token_type_ids"] = feature["kl_token_type_ids"]
|
||||
|
||||
target_features.append(target_feature)
|
||||
kl_features.append(kl_feature)
|
||||
kto_tags.append(feature["kto_tags"])
|
||||
|
||||
batch = super().__call__(target_features)
|
||||
kl_batch = super().__call__(kl_features)
|
||||
batch["kl_input_ids"] = kl_batch["input_ids"]
|
||||
batch["kl_attention_mask"] = kl_batch["attention_mask"]
|
||||
batch["kl_labels"] = kl_batch["labels"]
|
||||
if "token_type_ids" in batch:
|
||||
batch["kl_token_type_ids"] = kl_batch["token_type_ids"]
|
||||
|
||||
batch["kto_tags"] = torch.tensor(kto_tags)
|
||||
return batch
|
||||