mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 10:12:51 +00:00
Compare commits
1737 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2367b8747e | ||
|
|
8ac5f409e4 | ||
|
|
e84a215114 | ||
|
|
e923422cae | ||
|
|
b3f7d5ed4f | ||
|
|
7de8afc3a7 | ||
|
|
3c885ddf3c | ||
|
|
885b65cbbe | ||
|
|
e4575389eb | ||
|
|
bd589e50e2 | ||
|
|
ca3815e6fe | ||
|
|
da9ed39fa9 | ||
|
|
ec12ad0c2e | ||
|
|
f61a567bc3 | ||
|
|
a4971428b3 | ||
|
|
901b88887d | ||
|
|
8222cf42fa | ||
|
|
44645734f5 | ||
|
|
5426e90234 | ||
|
|
81e9c596b7 | ||
|
|
1d53b768ac | ||
|
|
47c51c9466 | ||
|
|
2337dee02d | ||
|
|
c48aa77f2f | ||
|
|
1a17c7c5df | ||
|
|
f43fa25f6a | ||
|
|
6ba6ed065e | ||
|
|
f668daa902 | ||
|
|
9a626efe35 | ||
|
|
319bd37b10 | ||
|
|
d2978906a6 | ||
|
|
1febd0a957 | ||
|
|
3edc68471b | ||
|
|
fcc61f0ab2 | ||
|
|
27262c9478 | ||
|
|
f46205bece | ||
|
|
3b51778dcd | ||
|
|
a1c4aa3733 | ||
|
|
c1c0c6a5a5 | ||
|
|
bbf321fc96 | ||
|
|
ab10ac9bc6 | ||
|
|
93b63dd18c | ||
|
|
eac7a6bbb0 | ||
|
|
7dba06ed82 | ||
|
|
f7623dcfdc | ||
|
|
b5fe64d619 | ||
|
|
6e1fb1d4ce | ||
|
|
fe94ca578e | ||
|
|
be8d5fc715 | ||
|
|
a8645b6721 | ||
|
|
d635cc24b4 | ||
|
|
0e684f2946 | ||
|
|
39ac62745e | ||
|
|
cbf7dd00cf | ||
|
|
ecd019aa14 | ||
|
|
40866f28d0 | ||
|
|
6660552618 | ||
|
|
d4312ee9ea | ||
|
|
83a1ffb891 | ||
|
|
0e78245bfb | ||
|
|
27a26f34b3 | ||
|
|
b709de30b9 | ||
|
|
5901e7fc02 | ||
|
|
1540fcd0de | ||
|
|
efbcec98bc | ||
|
|
2de1d817d7 | ||
|
|
aee0b251dc | ||
|
|
4e6d19235b | ||
|
|
7fc0bd2396 | ||
|
|
3d1ebad70b | ||
|
|
651b96dbb5 | ||
|
|
6dd2e9a6b7 | ||
|
|
356229c6ad | ||
|
|
525af67387 | ||
|
|
6184058e4d | ||
|
|
1742db681d | ||
|
|
021442d586 | ||
|
|
30942108bd | ||
|
|
52a98ae9b5 | ||
|
|
86ad2c54c6 | ||
|
|
3951853104 | ||
|
|
9b54fcb048 | ||
|
|
d978fd851f | ||
|
|
549aa62349 | ||
|
|
dd7b2a7bda | ||
|
|
9f31832b4d | ||
|
|
83a53e61b6 | ||
|
|
87eb5a879b | ||
|
|
00a4b09c99 | ||
|
|
1f1a1bbad9 | ||
|
|
5530b81f8b | ||
|
|
2281202921 | ||
|
|
1b23fb4472 | ||
|
|
f020ab4b28 | ||
|
|
e28ea0d849 | ||
|
|
a3a124d581 | ||
|
|
03fbba0952 | ||
|
|
5022b2a872 | ||
|
|
caa08e136f | ||
|
|
7205c7fda7 | ||
|
|
b3fa1e559a | ||
|
|
6adc051cad | ||
|
|
a62ed23315 | ||
|
|
392aaf9d87 | ||
|
|
fbef7232da | ||
|
|
53f83d3292 | ||
|
|
f13dcbcc3c | ||
|
|
aa7af2bcd3 | ||
|
|
139473e77c | ||
|
|
c2b626774c | ||
|
|
d33ae01d5a | ||
|
|
9cc051ded4 | ||
|
|
cac05c747b | ||
|
|
23327d7260 | ||
|
|
f4ef5f227c | ||
|
|
7942b12afa | ||
|
|
899e37b3e4 | ||
|
|
dc9ae84466 | ||
|
|
41cb4fedb1 | ||
|
|
60c7f78a90 | ||
|
|
ca070d7466 | ||
|
|
bf7d5c2e61 | ||
|
|
f744e8b109 | ||
|
|
0eb1cf701e | ||
|
|
65ab0ef170 | ||
|
|
9a706c7ac5 | ||
|
|
9ec60782aa | ||
|
|
4d18b78d29 | ||
|
|
b3a5dc4a1c | ||
|
|
6ce1eb7116 | ||
|
|
fc4a7df669 | ||
|
|
063920ce59 | ||
|
|
3d08263f79 | ||
|
|
567a7612ed | ||
|
|
3ca8c6ef73 | ||
|
|
f6b271e929 | ||
|
|
42a4ab4e9a | ||
|
|
cbb5e69ead | ||
|
|
b41b054c49 | ||
|
|
47dd177b7e | ||
|
|
04f3fc5213 | ||
|
|
ad90479278 | ||
|
|
1df333bffc | ||
|
|
18c0be36a6 | ||
|
|
03f05ced27 | ||
|
|
a725783579 | ||
|
|
e85d5b9714 | ||
|
|
9a6be423ac | ||
|
|
2ec98d9c3b | ||
|
|
fa5434cc79 | ||
|
|
8c88d70911 | ||
|
|
b5d2982812 | ||
|
|
a4d1a5067a | ||
|
|
aee2f43acd | ||
|
|
ecd4d63aee | ||
|
|
073e7e31e1 | ||
|
|
a25d7b9aa0 | ||
|
|
1e14a00754 | ||
|
|
6f2db6f488 | ||
|
|
12f69966b2 | ||
|
|
4cf2b74cb8 | ||
|
|
6e16c74a5e | ||
|
|
c363003aed | ||
|
|
7e78b7fa81 | ||
|
|
638a09d4f4 | ||
|
|
03ba4df625 | ||
|
|
a86d286a34 | ||
|
|
95be524066 | ||
|
|
4711527222 | ||
|
|
0435e1e494 | ||
|
|
7eeb42c9fd | ||
|
|
19a1b5a788 | ||
|
|
9ef4977ada | ||
|
|
9e80a652c4 | ||
|
|
6f6be4c90a | ||
|
|
fe751f5fd0 | ||
|
|
a3862471ac | ||
|
|
9c125a9649 | ||
|
|
ddbafa4afa | ||
|
|
7ff90917b6 | ||
|
|
c96de7a5f5 | ||
|
|
59e6fcaed7 | ||
|
|
c14aa518fb | ||
|
|
ff810db283 | ||
|
|
b85ba190db | ||
|
|
8a9e0abe3b | ||
|
|
d672cf8eac | ||
|
|
243af4d866 | ||
|
|
925a9b02fa | ||
|
|
b93c5ab80f | ||
|
|
f0ce039499 | ||
|
|
1ea86202b8 | ||
|
|
b59762ac49 | ||
|
|
fa55cdc143 | ||
|
|
ca6630f258 | ||
|
|
ec8f33df83 | ||
|
|
8231ae66e4 | ||
|
|
173cf52584 | ||
|
|
778b706776 | ||
|
|
c7a9a4d4bd | ||
|
|
c249f7501f | ||
|
|
c817df393f | ||
|
|
91f884b8a3 | ||
|
|
faebf60478 | ||
|
|
8adca34c88 | ||
|
|
8d28ac27d2 | ||
|
|
e7f61994ec | ||
|
|
f648457ba5 | ||
|
|
69f004359b | ||
|
|
f385bb3aec | ||
|
|
4715e4d84e | ||
|
|
948ec2fde6 | ||
|
|
caf89e39a7 | ||
|
|
e982bb0ca1 | ||
|
|
b1144d5ee9 | ||
|
|
2b89fc05d1 | ||
|
|
2c606b9190 | ||
|
|
2febd83a68 | ||
|
|
15cb1f4d35 | ||
|
|
f91afd593b | ||
|
|
0fc4a59e96 | ||
|
|
d643e7e683 | ||
|
|
dd3e671b50 | ||
|
|
d4506d7036 | ||
|
|
d76c85ca3b | ||
|
|
232e90fd12 | ||
|
|
97061073c1 | ||
|
|
69ff7f9af4 | ||
|
|
2fbeb4300f | ||
|
|
e5397af848 | ||
|
|
c4d37a8790 | ||
|
|
8869cca9de | ||
|
|
ab14cf2e9d | ||
|
|
526b4081bc | ||
|
|
bc879441d2 | ||
|
|
dc170db6a1 | ||
|
|
b044554559 | ||
|
|
e4c1c39972 | ||
|
|
c035dc5f39 | ||
|
|
bd8700477f | ||
|
|
43c7d1c3c4 | ||
|
|
42a7b1040a | ||
|
|
7317d5510d | ||
|
|
5cb21a69e5 | ||
|
|
bf657aced0 | ||
|
|
5cb0b3018f | ||
|
|
c1a403b583 | ||
|
|
902a97c0f8 | ||
|
|
79087a34d1 | ||
|
|
b4569fc88d | ||
|
|
df9245a5a4 | ||
|
|
5c1f105cab | ||
|
|
eae2b3e944 | ||
|
|
89dcac64e6 | ||
|
|
dd2fbb5c98 | ||
|
|
41a0b1adca | ||
|
|
816825d5de | ||
|
|
922ecb983c | ||
|
|
158bd2f43b | ||
|
|
2ead8b3ed2 | ||
|
|
949c6d5ce1 | ||
|
|
5df3f6552e | ||
|
|
d0eb28d5fa | ||
|
|
48c59b59a8 | ||
|
|
371ee61c24 | ||
|
|
fbbe1b4fc3 | ||
|
|
3b05746a14 | ||
|
|
d2560c664c | ||
|
|
5d99224f04 | ||
|
|
a67b39c87e | ||
|
|
8e5ff3b1f4 | ||
|
|
8a6bac396b | ||
|
|
b352e495a1 | ||
|
|
d1d9f2404d | ||
|
|
0434a18905 | ||
|
|
ce213d78af | ||
|
|
28b97cd2dd | ||
|
|
ad44d4d6b2 | ||
|
|
6ca6c5b6d3 | ||
|
|
390faf2bf5 | ||
|
|
1b94f16e2a | ||
|
|
dc960d1628 | ||
|
|
c2f52d0759 | ||
|
|
2520461544 | ||
|
|
26df300105 | ||
|
|
05409b8f90 | ||
|
|
55985a2460 | ||
|
|
82cbe08cc3 | ||
|
|
21c8f6ffbf | ||
|
|
95263d147b | ||
|
|
243745dc07 | ||
|
|
9cce06b9f1 | ||
|
|
ee8412f34d | ||
|
|
5a8c7a0e00 | ||
|
|
11b7672bf4 | ||
|
|
7ee7a51200 | ||
|
|
88fd875a23 | ||
|
|
350095a822 | ||
|
|
86675e9ec3 | ||
|
|
bd1f75a4ae | ||
|
|
12dbed5f7c | ||
|
|
cef7075382 | ||
|
|
a7fd1ac650 | ||
|
|
2490e85bc5 | ||
|
|
67859e9873 | ||
|
|
a7afdb769b | ||
|
|
3cc272c5e0 | ||
|
|
bf1942e0f9 | ||
|
|
ca073fd0d6 | ||
|
|
d8254f9e45 | ||
|
|
340ca6c436 | ||
|
|
a22a16eea0 | ||
|
|
b3698b30ca | ||
|
|
939da5b94b | ||
|
|
9fc20ca53f | ||
|
|
0356dfed1f | ||
|
|
930f9b083a | ||
|
|
ce7afda73c | ||
|
|
fdc9f9eb0b | ||
|
|
3f1c3bc68f | ||
|
|
df9b7f208b | ||
|
|
097cabbd04 | ||
|
|
1ebfc5882d | ||
|
|
58c617691b | ||
|
|
ea199e23f1 | ||
|
|
b53034727f | ||
|
|
074375478d | ||
|
|
958924e488 | ||
|
|
48959a1e77 | ||
|
|
117f882521 | ||
|
|
77af823879 | ||
|
|
921b9e0d7e | ||
|
|
65d2045751 | ||
|
|
f554205f87 | ||
|
|
b299ef0c53 | ||
|
|
ea56fd6212 | ||
|
|
07b19fe90e | ||
|
|
92fba08185 | ||
|
|
bb4ad680fc | ||
|
|
86da8918f5 | ||
|
|
1ac7fc4669 | ||
|
|
f0357e5944 | ||
|
|
e618b37309 | ||
|
|
368c175f62 | ||
|
|
1a792dd2e7 | ||
|
|
34842e4ae4 | ||
|
|
4ffd80f184 | ||
|
|
07ee09c50f | ||
|
|
be4f0ffb6b | ||
|
|
34e84602ff | ||
|
|
a3ae33bf7c | ||
|
|
3d36c3fa2d | ||
|
|
224fe26f28 | ||
|
|
178064f42c | ||
|
|
58db4ed901 | ||
|
|
41ad2eeef2 | ||
|
|
2d734d0aaa | ||
|
|
1374ed4171 | ||
|
|
a0e2403e77 | ||
|
|
4c9d024b67 | ||
|
|
827c06c03f | ||
|
|
1a2a02b808 | ||
|
|
d8c35fffd7 | ||
|
|
592ae54e82 | ||
|
|
109e8507f1 | ||
|
|
0f4ebc5981 | ||
|
|
2bda87b5a7 | ||
|
|
d781797555 | ||
|
|
28e89ca6ec | ||
|
|
fb92a07d62 | ||
|
|
aa43967c08 | ||
|
|
ff48a75830 | ||
|
|
cd88d9d099 | ||
|
|
2cbd8af6ad | ||
|
|
d7a3b7fd3f | ||
|
|
f5166523de | ||
|
|
6d97faed60 | ||
|
|
bf1062ff04 | ||
|
|
7ec7984b9f | ||
|
|
570bcfbaf2 | ||
|
|
31f87119b6 | ||
|
|
02afb6a0bc | ||
|
|
6369b4fb45 | ||
|
|
b89ee3d174 | ||
|
|
91eb873312 | ||
|
|
63c91138ed | ||
|
|
10b8bd67a2 | ||
|
|
4a36f78468 | ||
|
|
723aaf30c3 | ||
|
|
caa03dc489 | ||
|
|
07a049dddc | ||
|
|
a178986d9b | ||
|
|
2783d7dcde | ||
|
|
cdd146dc58 | ||
|
|
414769f42d | ||
|
|
c4df5cea5f | ||
|
|
99d94abe85 | ||
|
|
ebb475c19a | ||
|
|
1e20526819 | ||
|
|
c43e1fea50 | ||
|
|
210e09681f | ||
|
|
c3ecddcd1b | ||
|
|
2b74b8415f | ||
|
|
8c705b03b3 | ||
|
|
4437361eed | ||
|
|
f83336df8c | ||
|
|
a35ddad7f3 | ||
|
|
bc350c2eb2 | ||
|
|
d2ce1ba276 | ||
|
|
c370e99304 | ||
|
|
691d7ceaa5 | ||
|
|
a2726a9367 | ||
|
|
3324d4ab35 | ||
|
|
d94972bc9c | ||
|
|
17e1cbcf48 | ||
|
|
803b49e891 | ||
|
|
f926de4f91 | ||
|
|
12feaac958 | ||
|
|
0bcf7e311c | ||
|
|
4f4565f85b | ||
|
|
4d87e01682 | ||
|
|
0b48880f9e | ||
|
|
e6600a4b6b | ||
|
|
64f61a3beb | ||
|
|
8f5c68dcbc | ||
|
|
4c9756839a | ||
|
|
fe78de5d3c | ||
|
|
28a909e226 | ||
|
|
79537cade9 | ||
|
|
7329c72802 | ||
|
|
d26872aefb | ||
|
|
e697ddafbc | ||
|
|
9534247713 | ||
|
|
c78c2b39fe | ||
|
|
f1e8ba6e2d | ||
|
|
ed5cc3a0fa | ||
|
|
9d2fd05604 | ||
|
|
4f31977d7d | ||
|
|
6773be4fe3 | ||
|
|
b2b144af7c | ||
|
|
399e1dc6d4 | ||
|
|
6e426426c3 | ||
|
|
b24f4c9e44 | ||
|
|
1611bc7f75 | ||
|
|
7c47f4d0b3 | ||
|
|
f8de009344 | ||
|
|
3b33078555 | ||
|
|
3450ef78a4 | ||
|
|
857f988992 | ||
|
|
0316afa299 | ||
|
|
d0722dc048 | ||
|
|
bddc3cfd53 | ||
|
|
265840017c | ||
|
|
580adc5a88 | ||
|
|
19b7ae24c9 | ||
|
|
1703923f58 | ||
|
|
a43bde9a01 | ||
|
|
16088975fa | ||
|
|
06e759a320 | ||
|
|
3eb5a49e63 | ||
|
|
5d8e64cbb2 | ||
|
|
078bb41fc5 | ||
|
|
14eea7df42 | ||
|
|
c4293c34d1 | ||
|
|
2ba043e801 | ||
|
|
eceb8f8375 | ||
|
|
a5cd50fdf2 | ||
|
|
e0118f1ce9 | ||
|
|
693ed6004a | ||
|
|
b9aa36ed1e | ||
|
|
8e8726cd9d | ||
|
|
6c27fe68fe | ||
|
|
dc04d46518 | ||
|
|
4da75ac3e0 | ||
|
|
7ff65aef76 | ||
|
|
5258bd79d5 | ||
|
|
8625dd543a | ||
|
|
217e3ce58d | ||
|
|
f0903ac309 | ||
|
|
6411f32a3d | ||
|
|
4eda4a0d1b | ||
|
|
ee967606cf | ||
|
|
6a87ccd908 | ||
|
|
ec2ddedf93 | ||
|
|
cff1f6c80b | ||
|
|
3b4702e533 | ||
|
|
87045603ff | ||
|
|
2196d82060 | ||
|
|
543210bc64 | ||
|
|
bc85b91196 | ||
|
|
41131d57e1 | ||
|
|
1482d8674b | ||
|
|
49d40bb480 | ||
|
|
edca190a06 | ||
|
|
6866ff03b3 | ||
|
|
dc51cc1f2a | ||
|
|
9fb5ef4f80 | ||
|
|
a98cde9912 | ||
|
|
573ec2b706 | ||
|
|
dc4cdec8a1 | ||
|
|
c23e18d461 | ||
|
|
f370840e8b | ||
|
|
c7d1ba6a73 | ||
|
|
31147c0f6e | ||
|
|
0bd648578e | ||
|
|
92df83771e | ||
|
|
521fff2818 | ||
|
|
fce2f50a01 | ||
|
|
f04de2328a | ||
|
|
334883c2b2 | ||
|
|
99a6de0bcf | ||
|
|
81d9cc3e3b | ||
|
|
57b12a20bc | ||
|
|
f8249cd601 | ||
|
|
246123c506 | ||
|
|
c2767addd2 | ||
|
|
b396d95c6f | ||
|
|
b8cf3fb561 | ||
|
|
4be308078b | ||
|
|
b6cf80d6ef | ||
|
|
f627daf231 | ||
|
|
c99993795d | ||
|
|
8274dcb985 | ||
|
|
7bd1e48465 | ||
|
|
a942090f30 | ||
|
|
a27c09586e | ||
|
|
e346137a48 | ||
|
|
8f00184122 | ||
|
|
90abe70e2e | ||
|
|
8465ac67f0 | ||
|
|
8aa2c1437c | ||
|
|
337461b683 | ||
|
|
4143d1f2c5 | ||
|
|
1c364c22cf | ||
|
|
b40c76d3f3 | ||
|
|
754c309b52 | ||
|
|
5ba00faf82 | ||
|
|
1f1d4721eb | ||
|
|
30dc77650a | ||
|
|
7f7704042e | ||
|
|
b52c972ac0 | ||
|
|
bfdbc74611 | ||
|
|
66807217c1 | ||
|
|
1c18b99429 | ||
|
|
4aef0f9d21 | ||
|
|
b6c7fe8b88 | ||
|
|
85194599c8 | ||
|
|
c8650030fb | ||
|
|
010a7e6d3b | ||
|
|
4d90a5c280 | ||
|
|
fabef7df57 | ||
|
|
3601b7e8ac | ||
|
|
ad58a86f8e | ||
|
|
9172afc476 | ||
|
|
f6b36b7801 | ||
|
|
1bb2a1ae12 | ||
|
|
348e588428 | ||
|
|
6e012e6469 | ||
|
|
a772714814 | ||
|
|
114c2985c0 | ||
|
|
da8a12cae7 | ||
|
|
2d4e660c66 | ||
|
|
6effb8f0fb | ||
|
|
50e93e490a | ||
|
|
570c891ad2 | ||
|
|
8ee575b32e | ||
|
|
67f719747c | ||
|
|
880697bf35 | ||
|
|
73ab7214eb | ||
|
|
d8132fa9bf | ||
|
|
bed9c500f1 | ||
|
|
ccd4bdd888 | ||
|
|
181173e2a5 | ||
|
|
4d064f6a37 | ||
|
|
25d395d74a | ||
|
|
78a0a0bd50 | ||
|
|
13596ed7e8 | ||
|
|
e7a30903ba | ||
|
|
0c874bd2d5 | ||
|
|
3722027357 | ||
|
|
fa842b0d13 | ||
|
|
ef51afb96e | ||
|
|
e7dda56b9f | ||
|
|
a097d23299 | ||
|
|
ce1884ec60 | ||
|
|
9f25619fae | ||
|
|
580c6ac641 | ||
|
|
9ae64d871c | ||
|
|
168fb2e9a8 | ||
|
|
3ba172652a | ||
|
|
722776623d | ||
|
|
66bddc8ed3 | ||
|
|
005a6229fb | ||
|
|
519e0dc60b | ||
|
|
e939b47604 | ||
|
|
1c4fae42be | ||
|
|
cb40d62162 | ||
|
|
d7b4f90798 | ||
|
|
ad19518108 | ||
|
|
146d4e2624 | ||
|
|
e4a8047a05 | ||
|
|
8842319805 | ||
|
|
8f851aab61 | ||
|
|
35ee6eee02 | ||
|
|
d114b0013a | ||
|
|
1231cad2cc | ||
|
|
bc0b1fb209 | ||
|
|
ea085a3832 | ||
|
|
a7f55e6c00 | ||
|
|
4214043674 | ||
|
|
f3e5f26caf | ||
|
|
0ef5ccc4c2 | ||
|
|
57c4f74501 | ||
|
|
6bd5b9af6a | ||
|
|
628e7cdfed | ||
|
|
e21d53b746 | ||
|
|
77d90e5b46 | ||
|
|
45444bb256 | ||
|
|
be759701cc | ||
|
|
46b7e5c38f | ||
|
|
9c7eb22c61 | ||
|
|
cb28cfd427 | ||
|
|
d58a2239e3 | ||
|
|
5f2bb37626 | ||
|
|
60e3005aa9 | ||
|
|
82779d1719 | ||
|
|
ba3a2153e0 | ||
|
|
bf9918250c | ||
|
|
85afde5e61 | ||
|
|
7d7f085e3a | ||
|
|
a0b71de188 | ||
|
|
d54184cf71 | ||
|
|
dd621b59b9 | ||
|
|
a3c5cf452c | ||
|
|
6dd1e68efc | ||
|
|
08695cea0c | ||
|
|
b9a3cae4b4 | ||
|
|
7b1a52beff | ||
|
|
fb98259881 | ||
|
|
d40294d5a4 | ||
|
|
7dc20732d5 | ||
|
|
0b06e20527 | ||
|
|
0b049ada9c | ||
|
|
b2e4d2dc20 | ||
|
|
fe008e055f | ||
|
|
3f60135144 | ||
|
|
cf8df8a1f7 | ||
|
|
6f04b99aef | ||
|
|
22bc1931c8 | ||
|
|
9d1549fc76 | ||
|
|
97269d5445 | ||
|
|
d93b47d6f5 | ||
|
|
a67820bc48 | ||
|
|
1ef0146389 | ||
|
|
e61e4eb937 | ||
|
|
1d35700d2a | ||
|
|
f186b25afe | ||
|
|
c8d901ba4d | ||
|
|
7e3589a81a | ||
|
|
43c7350415 | ||
|
|
8f1b0e0da5 | ||
|
|
23b835fa19 | ||
|
|
c2256a5305 | ||
|
|
e0075c3dd5 | ||
|
|
bd8424ae8b | ||
|
|
df6adb1cd2 | ||
|
|
a4d4707845 | ||
|
|
f545a51397 | ||
|
|
df49c5ba5c | ||
|
|
0b27836ccb | ||
|
|
54cd93d927 | ||
|
|
66699ef497 | ||
|
|
ce17833b0a | ||
|
|
e1df14b16d | ||
|
|
1639d4b31c | ||
|
|
32f9becb7c | ||
|
|
cc40b5c275 | ||
|
|
79dc3e4909 | ||
|
|
50cc851f4f | ||
|
|
1064e8bc63 | ||
|
|
4c3b2b6040 | ||
|
|
557f82af7c | ||
|
|
47368a5f99 | ||
|
|
aa901c7fc7 | ||
|
|
da0be4b0ce | ||
|
|
9166528504 | ||
|
|
471ac59f29 | ||
|
|
0ee4b7fae2 | ||
|
|
f01bf625ef | ||
|
|
33a88be6d3 | ||
|
|
9912b85df0 | ||
|
|
75f75f0111 | ||
|
|
b011cd7d26 | ||
|
|
873d4af46b | ||
|
|
5ab5ab71a5 | ||
|
|
32dd4b41b6 | ||
|
|
464890abc5 | ||
|
|
ef15d819cf | ||
|
|
da0d633121 | ||
|
|
3b4b658934 | ||
|
|
b5efa495bf | ||
|
|
621cd37385 | ||
|
|
8d52166307 | ||
|
|
6473f27cae | ||
|
|
aff1ab7d1b | ||
|
|
191e62f96d | ||
|
|
25125ca571 | ||
|
|
a98f20e5b9 | ||
|
|
ac4bf303f4 | ||
|
|
a34c089b73 | ||
|
|
e8418f6f5c | ||
|
|
8b40762218 | ||
|
|
7abfebe927 | ||
|
|
081ca691d8 | ||
|
|
315fdd5f9c | ||
|
|
de1ec6addb | ||
|
|
179b8b51d8 | ||
|
|
d8c7d248d6 | ||
|
|
5ac0b2be3d | ||
|
|
6068530cdd | ||
|
|
0ad38e2540 | ||
|
|
b71a854d7c | ||
|
|
6110bf314a | ||
|
|
1a06264930 | ||
|
|
868b4b6eab | ||
|
|
e794874d82 | ||
|
|
bdba4bc34c | ||
|
|
de68eef276 | ||
|
|
9541b27d76 | ||
|
|
58dde6629b | ||
|
|
0e339701da | ||
|
|
baa9828950 | ||
|
|
c5155666c5 | ||
|
|
c7214a6e56 | ||
|
|
3e3c40de76 | ||
|
|
1f8cffb189 | ||
|
|
10eac10404 | ||
|
|
746fbe8705 | ||
|
|
3f6faa7592 | ||
|
|
f59cc9425e | ||
|
|
487e7eda6f | ||
|
|
28411e5c92 | ||
|
|
411a0446b3 | ||
|
|
d7421ec27b | ||
|
|
9f4e480102 | ||
|
|
816bf19d86 | ||
|
|
a4143eed23 | ||
|
|
8c0361bbc2 | ||
|
|
f9a8b9c270 | ||
|
|
63c836da04 | ||
|
|
f73721c73e | ||
|
|
d5bfe98c23 | ||
|
|
15574bd38d | ||
|
|
6cfb2a248b | ||
|
|
9b00ebd95a | ||
|
|
a58ee19fab | ||
|
|
2dc3d91a6c | ||
|
|
cecd3f90c6 | ||
|
|
295eb88947 | ||
|
|
cc06d07016 | ||
|
|
2ad5883aef | ||
|
|
9b89e8f75c | ||
|
|
38c3dcae7e | ||
|
|
75fd815e31 | ||
|
|
9f6cde37c3 | ||
|
|
b0b269f3d4 | ||
|
|
089915f488 | ||
|
|
407fe83582 | ||
|
|
b1dd321e28 | ||
|
|
ad5c8122d3 | ||
|
|
e724ffbebd | ||
|
|
fedf1ef6fc | ||
|
|
62233b34c4 | ||
|
|
5dc51836ef | ||
|
|
2cf01dd12c | ||
|
|
94847bb8f0 | ||
|
|
636c219979 | ||
|
|
ea5fae09c4 | ||
|
|
07937fcaf5 | ||
|
|
f0ef375010 | ||
|
|
d655028f9e | ||
|
|
56932b3afd | ||
|
|
bc87101bc8 | ||
|
|
4c04be7be7 | ||
|
|
482fbf4003 | ||
|
|
05f4eb532e | ||
|
|
b89b950c0d | ||
|
|
219ea75244 | ||
|
|
71a5de4e21 | ||
|
|
52b044608c | ||
|
|
3205f14dfb | ||
|
|
bd75a3d5bc | ||
|
|
305cdea34b | ||
|
|
60a0c6039e | ||
|
|
b2667998a4 | ||
|
|
a6f0081c94 | ||
|
|
e5d26301c7 | ||
|
|
3e4aac3557 | ||
|
|
d8a9c9ccdd | ||
|
|
2aa86ebfaa | ||
|
|
7e80b951ac | ||
|
|
da3c945b56 | ||
|
|
c27995c172 | ||
|
|
e6f9238005 | ||
|
|
18eff85215 | ||
|
|
a16375781b | ||
|
|
87415e96c8 | ||
|
|
2c0a8afd5f | ||
|
|
71ad83a267 | ||
|
|
f53db818d7 | ||
|
|
3cf6dde72b | ||
|
|
91f68781fe | ||
|
|
0f1d57f0cb | ||
|
|
82a2203be6 | ||
|
|
049c0e0bb0 | ||
|
|
91e2dd7ea6 | ||
|
|
4cb694fa65 | ||
|
|
1107b70a4a | ||
|
|
8e29562e30 | ||
|
|
64efc05c41 | ||
|
|
cc692ffbe1 | ||
|
|
a66cdc02d5 | ||
|
|
f5ee7b5c25 | ||
|
|
74b76d9914 | ||
|
|
e8d29e2cef | ||
|
|
8e89658b04 | ||
|
|
e85aee434c | ||
|
|
7862c17b21 | ||
|
|
1dc6d6e8ba | ||
|
|
d4d7bdde41 | ||
|
|
c5bdada6dc | ||
|
|
873a9a953f | ||
|
|
4a737556a9 | ||
|
|
a1abf33fa2 | ||
|
|
78bfee7c53 | ||
|
|
327b6cc563 | ||
|
|
4b4691d689 | ||
|
|
22d86f046a | ||
|
|
65545a8f15 | ||
|
|
1e26032e55 | ||
|
|
8a827b4294 | ||
|
|
e1841c6189 | ||
|
|
6c6aa20ff6 | ||
|
|
cd25bcf074 | ||
|
|
276378a9ae | ||
|
|
8468d08eea | ||
|
|
9bd490c7ea | ||
|
|
a7cbba161c | ||
|
|
db58ed8aba | ||
|
|
efa2335b8d | ||
|
|
62cb3704cf | ||
|
|
644247b1cb | ||
|
|
8f70bacc02 | ||
|
|
c1e01e005b | ||
|
|
e25544e032 | ||
|
|
c5c06254b7 | ||
|
|
36e715d319 | ||
|
|
8e6ca99ef6 | ||
|
|
d251d2bff4 | ||
|
|
d0e447774f | ||
|
|
cc20e20f61 | ||
|
|
0eeb79a977 | ||
|
|
8bc776af2b | ||
|
|
86ee4598ee | ||
|
|
594ca6cd89 | ||
|
|
173f7e8321 | ||
|
|
d2f6d34a18 | ||
|
|
fdb3bc19c1 | ||
|
|
89cf0ca41a | ||
|
|
c95a49c972 | ||
|
|
3447da58bd | ||
|
|
ac545f9122 | ||
|
|
f3fff4a26e | ||
|
|
d90805acbb | ||
|
|
530928af38 | ||
|
|
d626601672 | ||
|
|
c2c1d4eeb9 | ||
|
|
20010ee227 | ||
|
|
e1c37636d6 | ||
|
|
3bab501f08 | ||
|
|
3f1a60f388 | ||
|
|
0c2a17ed56 | ||
|
|
75df321783 | ||
|
|
8fc074fecb | ||
|
|
8b960b96a1 | ||
|
|
094386a613 | ||
|
|
06095ad94f | ||
|
|
f13a29a611 | ||
|
|
15bfe239a4 | ||
|
|
974f61d2d9 | ||
|
|
8ce3a44ef8 | ||
|
|
cf674ec981 | ||
|
|
e8f80094ce | ||
|
|
d49f448a5f | ||
|
|
6b167d7830 | ||
|
|
0886459bad | ||
|
|
37ac79dc5a | ||
|
|
91dbe1907c | ||
|
|
7a7d353433 | ||
|
|
e24a2001c5 | ||
|
|
56fe631ed6 | ||
|
|
dfa66c1d23 | ||
|
|
abcf96a29c | ||
|
|
683b0bcf18 | ||
|
|
bf4e98b7fc | ||
|
|
137fc01c76 | ||
|
|
ce5850b6d4 | ||
|
|
ac7e39fb18 | ||
|
|
b08a1088a2 | ||
|
|
ad72517ebe | ||
|
|
4320270857 | ||
|
|
183d6c74d5 | ||
|
|
f463786710 | ||
|
|
dd63b853d0 | ||
|
|
b5bbf9d0bb | ||
|
|
ea7b8547be | ||
|
|
c00cf22900 | ||
|
|
4c66fa998f | ||
|
|
bdba507090 | ||
|
|
bac0f79c93 | ||
|
|
f6c5d6d9b7 | ||
|
|
67bb79079f | ||
|
|
f9452830d3 | ||
|
|
c4bf19c92b | ||
|
|
a26c4320dd | ||
|
|
6cdd8ffe39 | ||
|
|
4a38b2563b | ||
|
|
86e87e554e | ||
|
|
40fcc5bcb9 | ||
|
|
3a2294e53b | ||
|
|
c3ee492176 | ||
|
|
a73e0b10f9 | ||
|
|
6724f6e9d1 | ||
|
|
4a90127a92 | ||
|
|
d252a2546e | ||
|
|
fe8f87834d | ||
|
|
fe066aea68 | ||
|
|
3aa0847506 | ||
|
|
13dc156a58 | ||
|
|
0e3ae6a8f8 | ||
|
|
b06d5685f2 | ||
|
|
f1e6b5adec | ||
|
|
7f09a22518 | ||
|
|
d1fe600d80 | ||
|
|
33a2a683e1 | ||
|
|
68f26d63d7 | ||
|
|
8f62257fee | ||
|
|
2f9052279a | ||
|
|
9f684dadd2 | ||
|
|
44c318e8c8 | ||
|
|
69e8537d9c | ||
|
|
c990ad7215 | ||
|
|
46fb1b1b4e | ||
|
|
2570114edf | ||
|
|
6ce02abc33 | ||
|
|
83e9a8ce98 | ||
|
|
66ab7fdf34 | ||
|
|
87a141f5cc | ||
|
|
12e8242365 | ||
|
|
2364bd275a | ||
|
|
73ee9bf602 | ||
|
|
758f424000 | ||
|
|
93530e0a9b | ||
|
|
656c035b53 | ||
|
|
cd145b68d8 | ||
|
|
e16524d574 | ||
|
|
1c96897d01 | ||
|
|
bba7a60789 | ||
|
|
be6dd24b4b | ||
|
|
65959c8caf | ||
|
|
86489f4b25 | ||
|
|
1daf84e954 | ||
|
|
90249257c6 | ||
|
|
9ddee4e70f | ||
|
|
70f8abe4db | ||
|
|
30c4849ae4 | ||
|
|
f248697794 | ||
|
|
0daa4e0091 | ||
|
|
fc6b087d75 | ||
|
|
1bbc693618 | ||
|
|
5a1acc1a2b | ||
|
|
7210e5d1d5 | ||
|
|
0d06343dc3 | ||
|
|
0fd171ff2f | ||
|
|
ef8c555108 | ||
|
|
263872d5a3 | ||
|
|
01e89eed30 | ||
|
|
0c54f5ba34 | ||
|
|
089b6900ed | ||
|
|
db2eaf4681 | ||
|
|
31c92fb492 | ||
|
|
d0f70ab466 | ||
|
|
1dab1ebc47 | ||
|
|
275ec80701 | ||
|
|
d51ef49f54 | ||
|
|
9c866b1907 | ||
|
|
324a1b54c6 | ||
|
|
da0d4199af | ||
|
|
85ebd22c21 | ||
|
|
a85cf51f42 | ||
|
|
b646163a62 | ||
|
|
343d66d6d8 | ||
|
|
01a423235f | ||
|
|
2fe953092f | ||
|
|
0a60b87800 | ||
|
|
996ebea316 | ||
|
|
bb0ad0a27b | ||
|
|
77295d2c13 | ||
|
|
359f9c546d | ||
|
|
8d5c3e6068 | ||
|
|
214499372c | ||
|
|
277142c6f0 | ||
|
|
4747dc6c75 | ||
|
|
58a44b315e | ||
|
|
c253e8b696 | ||
|
|
69e60b5800 | ||
|
|
66bb8f605d | ||
|
|
e98ecf4c2f | ||
|
|
e7c0d8eaf1 | ||
|
|
f4410b25ce | ||
|
|
a89f28a2ae | ||
|
|
6dfcc5e54d | ||
|
|
db7b267326 | ||
|
|
11fa3c4814 | ||
|
|
2ab57802f4 | ||
|
|
28efdb49f7 | ||
|
|
d459beb1a0 | ||
|
|
af6f88a616 | ||
|
|
3a91562289 | ||
|
|
4a6d8c9067 | ||
|
|
1a5db6b5ea | ||
|
|
a8127efd86 | ||
|
|
50245bc723 | ||
|
|
f1f8229d33 | ||
|
|
869812d87b | ||
|
|
4001d0cded | ||
|
|
7f9caca5c7 | ||
|
|
fd6b9c00f6 | ||
|
|
22e3bc1aa4 | ||
|
|
dca48d1388 | ||
|
|
01ed7045e0 | ||
|
|
05f1bbbde6 | ||
|
|
eeb101d150 | ||
|
|
6d1bbd9db7 | ||
|
|
abf8c6a62e | ||
|
|
0b64e92a33 | ||
|
|
2468717809 | ||
|
|
add71a4bc2 | ||
|
|
0f8fe31e52 | ||
|
|
92fd481aff | ||
|
|
716b073b80 | ||
|
|
4a722575df | ||
|
|
516c8ea9d2 | ||
|
|
b2465b28dc | ||
|
|
b971a2c15f | ||
|
|
37a2041d8d | ||
|
|
2c20733957 | ||
|
|
f84aa3ec6c | ||
|
|
c0979e2363 | ||
|
|
f32f158876 | ||
|
|
a00860528a | ||
|
|
bb109d65cf | ||
|
|
4ae803e7c5 | ||
|
|
3e9c3c0602 | ||
|
|
1a60d8c972 | ||
|
|
7e67fbc592 | ||
|
|
06e910b630 | ||
|
|
b496a2b2c5 | ||
|
|
47a3489a0a | ||
|
|
23b47f24e7 | ||
|
|
0e79d2eb37 | ||
|
|
c598a1586e | ||
|
|
cb60220cb8 | ||
|
|
65a65de03e | ||
|
|
190d25857f | ||
|
|
9c73c8dea0 | ||
|
|
145e96594f | ||
|
|
181848d219 | ||
|
|
1468bc2056 | ||
|
|
0bed184a2c | ||
|
|
f77dd47403 | ||
|
|
687fd6cb84 | ||
|
|
1287da6ed0 | ||
|
|
9777c92e34 | ||
|
|
f303fc4594 | ||
|
|
2336f381c1 | ||
|
|
717c204d22 | ||
|
|
35d86c6484 | ||
|
|
7f1f9ccd3b | ||
|
|
66606bcaec | ||
|
|
ee5c199449 | ||
|
|
0c8a80897b | ||
|
|
c46c631d52 | ||
|
|
21bee7daeb | ||
|
|
4c41a40438 | ||
|
|
c8a5048c7c | ||
|
|
961e67e98d | ||
|
|
5e67c0b40a | ||
|
|
1f5587eeeb | ||
|
|
9f0b0c1247 | ||
|
|
83e9b45e79 | ||
|
|
327d34ae1e | ||
|
|
a39010bc4a | ||
|
|
01a25efe1c | ||
|
|
9a89565931 | ||
|
|
f646a5caad | ||
|
|
a9d106ddd4 | ||
|
|
63abffec3c | ||
|
|
e13bd094fa | ||
|
|
f54c873c20 | ||
|
|
c38906530d | ||
|
|
6c12acd5d4 | ||
|
|
d2ec6d558b | ||
|
|
4af876f6a8 | ||
|
|
f40e625ee1 | ||
|
|
7acdd887bf | ||
|
|
73df001e41 | ||
|
|
72d9833038 | ||
|
|
ba078a9aab | ||
|
|
ec66640616 | ||
|
|
4e170c6ed7 | ||
|
|
7aa73e7110 | ||
|
|
2c3eb20592 | ||
|
|
80b3be46fd | ||
|
|
35b30c2009 | ||
|
|
c9bdb9ca8a | ||
|
|
4e87048053 | ||
|
|
b656d05036 | ||
|
|
37c9ef3087 | ||
|
|
a6b8cc4a31 | ||
|
|
317db5ae18 | ||
|
|
fd73ede7c8 | ||
|
|
5237f8b5fc | ||
|
|
004f78c2ba | ||
|
|
2e15552853 | ||
|
|
e02d417af9 | ||
|
|
046e1c72d6 | ||
|
|
52ba4d6342 | ||
|
|
1e6fabf6c4 | ||
|
|
c9c2cc8f4c | ||
|
|
dbfbcdd0b6 | ||
|
|
c4642a3595 | ||
|
|
598b72fd12 | ||
|
|
03ec0f3fdf | ||
|
|
af61494779 | ||
|
|
9891c96e54 | ||
|
|
59e4710a42 | ||
|
|
6d9068c378 | ||
|
|
11739f1649 | ||
|
|
b14c870fd7 | ||
|
|
698f23c716 | ||
|
|
2eff491f15 | ||
|
|
db4c10cc56 | ||
|
|
f8f17ed652 | ||
|
|
28f315fc3c | ||
|
|
85138e4e2b | ||
|
|
45908b91ff | ||
|
|
c0b770f41e | ||
|
|
7dc3cb98ef | ||
|
|
ef9e80f900 | ||
|
|
ede1e3d645 | ||
|
|
acd0adaf3c | ||
|
|
3af2dc7f7c | ||
|
|
516c88a510 | ||
|
|
34448a2623 | ||
|
|
fdedcc1ee8 | ||
|
|
8048e73741 | ||
|
|
d9bd35e664 | ||
|
|
4e5285ce23 | ||
|
|
395f3f91ec | ||
|
|
d0af58d866 | ||
|
|
9ec3a72352 | ||
|
|
4a58954567 | ||
|
|
a7ec04eb8b | ||
|
|
c2081c66e9 | ||
|
|
9ac2710b24 | ||
|
|
89023d14fe | ||
|
|
63d8906639 | ||
|
|
0c881be76d | ||
|
|
f161082be5 | ||
|
|
dbbd2f95af | ||
|
|
b9304f0fda | ||
|
|
13c5833d42 | ||
|
|
7207e20246 | ||
|
|
c793f429de | ||
|
|
c1ca6eafe6 | ||
|
|
0af7da36a1 | ||
|
|
4975c4b2bf | ||
|
|
d0be683443 | ||
|
|
f7a0ee6184 | ||
|
|
c17dfeda28 | ||
|
|
acb00e92d8 | ||
|
|
9068c1fca0 | ||
|
|
689314f881 | ||
|
|
ff0cd18521 | ||
|
|
ee1ca6fb23 | ||
|
|
e6c2e4fb13 | ||
|
|
3498d799cf | ||
|
|
e0cf134bcc | ||
|
|
45508b08a3 | ||
|
|
f4e15d76a0 | ||
|
|
707094359b | ||
|
|
4eb594598c | ||
|
|
c056cd85ac | ||
|
|
39d8d5aab2 | ||
|
|
b16d1d68dc | ||
|
|
4205dc902f | ||
|
|
fb4880f3f4 | ||
|
|
5e9a7583a3 | ||
|
|
dca1df59fc | ||
|
|
a08dd98ffb | ||
|
|
de22ffabc1 | ||
|
|
449aa63f85 | ||
|
|
3360c49f4c | ||
|
|
35433b4752 | ||
|
|
fbc37edf3c | ||
|
|
f1d9bfa648 | ||
|
|
d0ba49456f | ||
|
|
0badeb8cef | ||
|
|
fef23579ac | ||
|
|
f2f6f82240 | ||
|
|
62d0089c9e | ||
|
|
037431487d | ||
|
|
89ad56744b | ||
|
|
19e8e76386 | ||
|
|
0d36704f63 | ||
|
|
6c48e803fa | ||
|
|
2e24409e5b | ||
|
|
9c284366ff | ||
|
|
bdedf49a71 | ||
|
|
3aa804f666 | ||
|
|
9221c7c93a | ||
|
|
9e7f6fc7a7 | ||
|
|
2998b2fc2d | ||
|
|
63b783c8f5 | ||
|
|
e4a8f371ab | ||
|
|
4ba5fa324a | ||
|
|
845ba0707e | ||
|
|
a93d7ca6d7 | ||
|
|
9ab90a7b21 | ||
|
|
555091aa5d | ||
|
|
695773df7d | ||
|
|
3c39c508cf | ||
|
|
93daee5bc7 | ||
|
|
315ee4bc3e | ||
|
|
119f678224 | ||
|
|
00e486c3fe | ||
|
|
6fc0558f63 | ||
|
|
edee4bae48 | ||
|
|
d86ad90e31 | ||
|
|
a892f3075f | ||
|
|
d6a301c184 | ||
|
|
863e08120a | ||
|
|
8f3efea077 | ||
|
|
19d15da910 | ||
|
|
63eefa3ffa | ||
|
|
721f9384ce | ||
|
|
fcba4c376f | ||
|
|
77a824235f | ||
|
|
310fd12e87 | ||
|
|
1b5d0f78ef | ||
|
|
4fba596591 | ||
|
|
189564de21 | ||
|
|
cbc552f971 | ||
|
|
11dfcd4d60 | ||
|
|
3540ad8550 | ||
|
|
6bfee8d072 | ||
|
|
baf1c2cdc2 | ||
|
|
8e93e1ea2e | ||
|
|
fc89b91ab3 | ||
|
|
f773792424 | ||
|
|
60b3bcdd64 | ||
|
|
d8ca74a2f5 | ||
|
|
1ea78ea0c7 | ||
|
|
2782a7720c | ||
|
|
8fd4541d67 | ||
|
|
9b2452ccfe | ||
|
|
b8f80931d2 | ||
|
|
9e24e8c467 | ||
|
|
cbcad3cc83 | ||
|
|
357c887c93 | ||
|
|
7aa0b10f93 | ||
|
|
34a096ccf4 | ||
|
|
1b98e352b6 | ||
|
|
afada6fa72 | ||
|
|
26be536b40 | ||
|
|
e364070082 | ||
|
|
719ad9c9c1 | ||
|
|
16b1a79c0b | ||
|
|
5acf2f60e5 | ||
|
|
f4efdca5ed | ||
|
|
0837d606a1 | ||
|
|
20e11f5c87 | ||
|
|
bce83f7545 | ||
|
|
43087e7ca3 | ||
|
|
a76c561769 | ||
|
|
b6fb059512 | ||
|
|
6a37d22540 | ||
|
|
75410459eb | ||
|
|
ac6716b29b | ||
|
|
67ec3c15f8 | ||
|
|
9558e4053d | ||
|
|
4948051f67 | ||
|
|
f7349d2b3d | ||
|
|
748420167a | ||
|
|
e9aa3a311c | ||
|
|
8def4f3e4a | ||
|
|
0260b40fa1 | ||
|
|
79c8ee591c | ||
|
|
466175fb19 | ||
|
|
d9dc3db242 | ||
|
|
54578eff33 | ||
|
|
64b1f3d33a | ||
|
|
625b4d6b5d | ||
|
|
336d77ff87 | ||
|
|
9e881b3596 | ||
|
|
201e6f5da2 | ||
|
|
bf630b9d07 | ||
|
|
c7e30bb098 | ||
|
|
b624de75be | ||
|
|
02e7cacb10 | ||
|
|
e0a7fe8feb | ||
|
|
8875bc9b83 | ||
|
|
48e37148f0 | ||
|
|
156b490f1a | ||
|
|
42c6157404 | ||
|
|
dad309193a | ||
|
|
a3e2e8b548 | ||
|
|
10cbfcf795 | ||
|
|
bbcc45181c | ||
|
|
31f6da272f | ||
|
|
14ae7864aa | ||
|
|
399e00415c | ||
|
|
bba9e62d43 | ||
|
|
4e203f8f95 | ||
|
|
0a0941a49b | ||
|
|
9a7281212d | ||
|
|
e5e993986c | ||
|
|
31838f8099 | ||
|
|
64d5217dc9 | ||
|
|
6342fd97da | ||
|
|
e4e58bc3bd | ||
|
|
b00ad14214 | ||
|
|
3f5180d54d | ||
|
|
6ad9682465 | ||
|
|
e0d08ac590 | ||
|
|
39ab4e155b | ||
|
|
3b74cc440b | ||
|
|
e81a7962b8 | ||
|
|
babd6d5bde | ||
|
|
569d16d735 | ||
|
|
ace03e5b5a | ||
|
|
0695b281d8 | ||
|
|
16482af519 | ||
|
|
6c342e02f0 | ||
|
|
a9381ab914 | ||
|
|
ba0dcf6b1b | ||
|
|
874dd34cba | ||
|
|
6f61c18ff6 | ||
|
|
25cc01d51f | ||
|
|
59d78679b0 | ||
|
|
c758059fe4 | ||
|
|
9877c825bc | ||
|
|
b63bd8bbea | ||
|
|
238821e96b | ||
|
|
e9527bdd2b | ||
|
|
fa818fc6e8 | ||
|
|
7ca61274f2 | ||
|
|
f5baae4117 | ||
|
|
c07fd323bf | ||
|
|
bec1cbd000 | ||
|
|
840fa68b98 | ||
|
|
e0bfe0cd73 | ||
|
|
87ab2178bf | ||
|
|
ab256b980d | ||
|
|
e0dcc4b29d | ||
|
|
cfafc3f0ba | ||
|
|
fd694b2162 | ||
|
|
70b5782033 | ||
|
|
649e2ce1f6 | ||
|
|
e6e3acdfea | ||
|
|
982604d39a | ||
|
|
bc91e94079 | ||
|
|
3233058146 | ||
|
|
c3723c3962 | ||
|
|
5f19924f9f | ||
|
|
6a0a57e785 | ||
|
|
e8886d5c65 | ||
|
|
dfba894e88 | ||
|
|
76e3eeba14 | ||
|
|
f0bbe1c2f8 | ||
|
|
56dc0c89de | ||
|
|
68201d3f87 | ||
|
|
42509f46ab | ||
|
|
c68e35e9da | ||
|
|
9ccb501a90 | ||
|
|
5da47a140d | ||
|
|
fff46e5c28 | ||
|
|
f1e19a1ace | ||
|
|
b1610a00bf | ||
|
|
2b097e5c97 | ||
|
|
c83bce3cef | ||
|
|
6e0e0d2366 | ||
|
|
66c868e71f | ||
|
|
3807cf1960 | ||
|
|
5f10b70e24 | ||
|
|
07caf1ffa5 | ||
|
|
387967ca69 | ||
|
|
c0eac6d97c | ||
|
|
515e8555aa | ||
|
|
82d4a3376b | ||
|
|
0342aee292 | ||
|
|
06216b1596 | ||
|
|
980a27bb74 | ||
|
|
8d40004c67 | ||
|
|
b8b14884bd | ||
|
|
cebdfd6123 | ||
|
|
003b9115a3 | ||
|
|
1390ddb36e | ||
|
|
7d898d870c | ||
|
|
b2b5c331fa | ||
|
|
165de1ad73 | ||
|
|
7abd9dfb71 | ||
|
|
c3c6a551ae | ||
|
|
2fccb9db67 | ||
|
|
66d4b6c81a | ||
|
|
8ab4456ec3 | ||
|
|
16dc78d5a4 | ||
|
|
c0ea174275 | ||
|
|
927b12a5ca | ||
|
|
643200cc06 | ||
|
|
a2cc83ec31 | ||
|
|
fd441b6986 | ||
|
|
4d76f08b14 | ||
|
|
448bbb3aae | ||
|
|
fc11fcf0e1 | ||
|
|
b17cf3fea8 | ||
|
|
93833849c1 | ||
|
|
fb0d5b0de7 | ||
|
|
6f2f4d93bb | ||
|
|
9c205048d4 | ||
|
|
e9d7decef8 | ||
|
|
8f98b7188d | ||
|
|
d33bc56db1 | ||
|
|
0cb33dad5a | ||
|
|
40df595173 | ||
|
|
d05d82106e | ||
|
|
fee5993c22 | ||
|
|
269960649d | ||
|
|
2fe1803f19 | ||
|
|
636f96178c | ||
|
|
a4b6570110 | ||
|
|
1817537269 | ||
|
|
e63572b89b | ||
|
|
09dfedac9b | ||
|
|
491b06a43b | ||
|
|
5cb7fc8d9a | ||
|
|
f5bc003aaf | ||
|
|
7998133e35 | ||
|
|
0ca2c72e52 | ||
|
|
6cb3ae74d9 | ||
|
|
74ce98c39b | ||
|
|
aebbc794cd | ||
|
|
8b6ce9fc41 | ||
|
|
26f84338d6 | ||
|
|
1641039fd2 | ||
|
|
8b9e03d7cf | ||
|
|
40137f147b | ||
|
|
64e26dd2cc | ||
|
|
61359e078f | ||
|
|
fd2b1245b8 | ||
|
|
c775f2a57f | ||
|
|
578fb34361 | ||
|
|
a84c287963 | ||
|
|
a0291bea88 | ||
|
|
7a34ba6fe3 | ||
|
|
824e02521c | ||
|
|
445a87143b | ||
|
|
28c09ae220 | ||
|
|
bfa15c2221 | ||
|
|
fb6314cd69 | ||
|
|
faeee1b92c | ||
|
|
d92fdb999c | ||
|
|
c2e9e5480a | ||
|
|
bc03deabfc | ||
|
|
596dbedd39 | ||
|
|
d686bc3625 | ||
|
|
e525f77fa0 | ||
|
|
56a9e69912 | ||
|
|
642920e916 | ||
|
|
f4f39d8059 | ||
|
|
b6ef007b45 | ||
|
|
852313217d | ||
|
|
070a5d4ed3 | ||
|
|
efd273b3bc | ||
|
|
cb4b1c927c | ||
|
|
4de998dee2 | ||
|
|
aa593f3790 | ||
|
|
a8a19f357a | ||
|
|
af8172a7b5 | ||
|
|
3f565602a4 | ||
|
|
11ce2e26b5 | ||
|
|
15eea403ec | ||
|
|
787da980b8 | ||
|
|
3ded9092ca | ||
|
|
43654bddaf | ||
|
|
b16353fbe8 | ||
|
|
1dfff30daf | ||
|
|
b1a6c463d0 | ||
|
|
54e3804ca8 | ||
|
|
1dadc06220 | ||
|
|
1b36e43ac5 | ||
|
|
165efaa1ab | ||
|
|
345e588ef6 | ||
|
|
743b04aebf | ||
|
|
129d52dff2 | ||
|
|
b45095f7ec | ||
|
|
896fb5fa52 | ||
|
|
0c9d8ccf71 | ||
|
|
6bbb181927 | ||
|
|
30a60c9ab8 | ||
|
|
080a6031bd | ||
|
|
60ff19be17 | ||
|
|
ba79dd8582 | ||
|
|
bbc7dbc834 | ||
|
|
41dd3c4f3d | ||
|
|
da65ce57b5 | ||
|
|
356dbeb2b9 | ||
|
|
9e7279e336 | ||
|
|
b7843fd2bf | ||
|
|
1aa8fe28ef | ||
|
|
c0b676ed16 | ||
|
|
2da3eac1b5 | ||
|
|
8a088e6c75 | ||
|
|
75b31ecba9 | ||
|
|
6b02e5fa9d | ||
|
|
9dc06909bd | ||
|
|
abadff25e5 | ||
|
|
3c5b38dc44 | ||
|
|
f2b0af25da | ||
|
|
f618dd4f99 | ||
|
|
0a1d5c4fbf | ||
|
|
fc0599fe01 | ||
|
|
e56a0481e0 | ||
|
|
e50b5cc051 | ||
|
|
aa7bc349ab | ||
|
|
8fb8c06e0b | ||
|
|
e24f211674 | ||
|
|
74fac81c7b | ||
|
|
7780d89c55 | ||
|
|
d7b61c61e6 | ||
|
|
b13799d584 | ||
|
|
d85d20694d | ||
|
|
25da46d13f | ||
|
|
6b41d8cff1 | ||
|
|
e6dc404867 | ||
|
|
d14b47373a | ||
|
|
4eed42b6a0 | ||
|
|
c3581be9bd | ||
|
|
bd71ca9eb4 | ||
|
|
d24532d679 | ||
|
|
554b81b59a | ||
|
|
680475cb97 | ||
|
|
5ca3151e90 | ||
|
|
b11e3398da | ||
|
|
796eff7759 | ||
|
|
f21d301140 | ||
|
|
6f81d80389 | ||
|
|
a5fb2e15c6 | ||
|
|
f5fc51cb12 | ||
|
|
cb2fb496b4 | ||
|
|
5ebfe9b5dd | ||
|
|
f6ccc95240 | ||
|
|
1f8907e1cb | ||
|
|
1e80a6136a | ||
|
|
53048774ee | ||
|
|
deb7268be1 | ||
|
|
40d9954c3b | ||
|
|
94c5c4197c | ||
|
|
1a34c67d46 | ||
|
|
6f209d740d | ||
|
|
a75501737f | ||
|
|
ac698e0c4c | ||
|
|
54836d549c | ||
|
|
b6f5a8378a | ||
|
|
7dcd1a71e8 | ||
|
|
0ae489a50b | ||
|
|
9f004defd9 | ||
|
|
ab479c422c | ||
|
|
4d44637ae5 | ||
|
|
4b30b5b1b5 | ||
|
|
0401c01c93 | ||
|
|
3010fa835f | ||
|
|
06d5c10ac6 | ||
|
|
906407a15b | ||
|
|
a90f6c89cb | ||
|
|
a09d0d5171 | ||
|
|
6cdd5b4508 | ||
|
|
a6c74024b0 | ||
|
|
4995721399 | ||
|
|
fd7fd36626 | ||
|
|
ecc25796a6 | ||
|
|
a04c2efa41 | ||
|
|
36da813eea | ||
|
|
10105ce5ab | ||
|
|
bcc7c1acf1 | ||
|
|
bcf6ccee20 | ||
|
|
442dd209a5 | ||
|
|
1448927f4e | ||
|
|
4e77ebcc91 | ||
|
|
b219c72c4a | ||
|
|
4cb513dd3f | ||
|
|
115e11052c | ||
|
|
bd865ceafc | ||
|
|
6f5645e16c | ||
|
|
c3b979decc | ||
|
|
e702af8c2b | ||
|
|
3e9069aac1 | ||
|
|
0e66b8a186 | ||
|
|
24e734fb36 | ||
|
|
ba5028858c | ||
|
|
ee2ac7835c | ||
|
|
0d3eb431f6 | ||
|
|
43bef216d5 | ||
|
|
2a5cd4ca14 | ||
|
|
48297d81e5 | ||
|
|
c8ce7e28d8 | ||
|
|
7be1173843 | ||
|
|
1ca395094e | ||
|
|
adba1829c0 | ||
|
|
ced932b07c | ||
|
|
4dcd150c8d | ||
|
|
132fd1b84d | ||
|
|
3b5a02307c | ||
|
|
870fabe187 | ||
|
|
770089e432 | ||
|
|
8c362b0f99 | ||
|
|
69e35b36aa | ||
|
|
ab46d7ab61 | ||
|
|
06f834b7e3 | ||
|
|
5481b8a2aa | ||
|
|
4369be9867 | ||
|
|
118db9dd14 | ||
|
|
0df7c7247d | ||
|
|
ee37d7c320 | ||
|
|
99fd32897c | ||
|
|
c79479d80b | ||
|
|
7bda26d92a | ||
|
|
7aa294db38 | ||
|
|
1e86919882 | ||
|
|
3e7bc1dae0 | ||
|
|
bca128ec39 | ||
|
|
a91d8016f8 | ||
|
|
f805f6dce1 | ||
|
|
b1d13f501f | ||
|
|
bb4b35bfe8 | ||
|
|
f7a4aca0e8 | ||
|
|
724686762e | ||
|
|
83ace97ecc | ||
|
|
7f492b4d92 | ||
|
|
57c6c9916e | ||
|
|
fdb7aeb47b | ||
|
|
3205ff2a65 | ||
|
|
ec643fca7f | ||
|
|
f08b551a72 | ||
|
|
a084b12ac4 | ||
|
|
15ff1acc2c | ||
|
|
ff41b1ff6e | ||
|
|
3d1c43c020 | ||
|
|
4466343e97 | ||
|
|
52a7f40ba8 | ||
|
|
166309de09 | ||
|
|
11faca22f9 | ||
|
|
f05a4a67c5 | ||
|
|
4c38e8a82b | ||
|
|
fbb4e7d449 | ||
|
|
21097d7164 | ||
|
|
91f16aa394 | ||
|
|
a9d962c1f1 | ||
|
|
cc0e431605 | ||
|
|
11331a60e0 | ||
|
|
bbd7079166 | ||
|
|
55705593a9 | ||
|
|
6d4f5dc1c3 | ||
|
|
3f7fb88217 | ||
|
|
8071d2dcd5 | ||
|
|
5e8dd48032 | ||
|
|
220168c4bf | ||
|
|
ab0f5e09b1 | ||
|
|
a8cda7d4a4 | ||
|
|
39841cb344 | ||
|
|
d3346deb62 | ||
|
|
16cafec6af | ||
|
|
2c0cb5b6e6 | ||
|
|
7c15082e53 | ||
|
|
83b494f700 | ||
|
|
fe032822f4 | ||
|
|
6f6b163416 | ||
|
|
9b0f9b04b7 | ||
|
|
1ebcc664bb | ||
|
|
0b8f5d7597 | ||
|
|
f89e18578f | ||
|
|
32111f6a8f | ||
|
|
8f0dd16949 | ||
|
|
ac4df1421a | ||
|
|
90d2d90658 | ||
|
|
4f4c8c43b9 | ||
|
|
ffe8900fb2 | ||
|
|
9b21a36e1a | ||
|
|
3554bdf7df | ||
|
|
e69341ed3c | ||
|
|
4ca557fe5d | ||
|
|
4876a45733 | ||
|
|
b3c12fb0b8 | ||
|
|
cbebef774c | ||
|
|
d49fd74305 | ||
|
|
dcb77bbe16 | ||
|
|
063393d659 | ||
|
|
946de675ff | ||
|
|
934871f5c6 | ||
|
|
9108971fdc | ||
|
|
4c23b9aded | ||
|
|
9778fd2bb4 | ||
|
|
342ef6df30 | ||
|
|
3351968969 | ||
|
|
5f902ef5de | ||
|
|
475a05e8b0 | ||
|
|
342f984a3b | ||
|
|
04642eb497 | ||
|
|
5ffd3423ff | ||
|
|
5fa0734c66 | ||
|
|
aafcd5eb28 | ||
|
|
e58a5507a9 | ||
|
|
d8cbf0d879 | ||
|
|
e8d5a4a653 | ||
|
|
3502522086 | ||
|
|
c78a6babb6 |
|
|
@ -1,2 +1,2 @@
|
||||||
.git*
|
.git*
|
||||||
.idea*
|
.idea*
|
||||||
|
|
@ -6,12 +6,4 @@ updates:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
timezone: "Asia/Shanghai"
|
timezone: "Asia/Shanghai"
|
||||||
day: "friday"
|
day: "friday"
|
||||||
target-branch: "v2"
|
target-branch: "v3"
|
||||||
groups:
|
|
||||||
python-dependencies:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
# ignore:
|
|
||||||
# - dependency-name: "pymupdf"
|
|
||||||
# versions: ["*"]
|
|
||||||
|
|
||||||
|
|
@ -14,33 +14,19 @@ on:
|
||||||
- linux/amd64,linux/arm64
|
- linux/amd64,linux/arm64
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push-python-pg-to-ghcr:
|
build-and-push-python-pg-to-ghcr:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check Disk Space
|
|
||||||
run: df -h
|
|
||||||
- name: Free Disk Space (Ubuntu)
|
|
||||||
uses: jlumbroso/free-disk-space@main
|
|
||||||
with:
|
|
||||||
tool-cache: true
|
|
||||||
android: true
|
|
||||||
dotnet: true
|
|
||||||
haskell: true
|
|
||||||
large-packages: true
|
|
||||||
docker-images: true
|
|
||||||
swap-storage: true
|
|
||||||
- name: Check Disk Space
|
|
||||||
run: df -h
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: ${{ github.ref_name }}
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
id: prepare
|
id: prepare
|
||||||
run: |
|
run: |
|
||||||
DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-python-pg
|
DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-base
|
||||||
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
|
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
|
||||||
TAG_NAME=python3.11-pg15.8
|
TAG_NAME=python3.11-pg17.5
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
||||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
||||||
echo ::set-output name=version::${TAG_NAME}
|
echo ::set-output name=version::${TAG_NAME}
|
||||||
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \
|
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \
|
||||||
|
|
@ -51,8 +37,7 @@ jobs:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
# Until https://github.com/tonistiigi/binfmt/issues/215
|
cache-image: false
|
||||||
image: tonistiigi/binfmt:qemu-v7.0.0-28
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
|
|
@ -63,4 +48,4 @@ jobs:
|
||||||
password: ${{ secrets.GH_TOKEN }}
|
password: ${{ secrets.GH_TOKEN }}
|
||||||
- name: Docker Buildx (build-and-push)
|
- name: Docker Buildx (build-and-push)
|
||||||
run: |
|
run: |
|
||||||
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile-python-pg
|
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile-base
|
||||||
|
|
@ -5,7 +5,7 @@ on:
|
||||||
inputs:
|
inputs:
|
||||||
dockerImageTag:
|
dockerImageTag:
|
||||||
description: 'Docker Image Tag'
|
description: 'Docker Image Tag'
|
||||||
default: 'v1.0.1'
|
default: 'v2.0.2'
|
||||||
required: true
|
required: true
|
||||||
architecture:
|
architecture:
|
||||||
description: 'Architecture'
|
description: 'Architecture'
|
||||||
|
|
@ -19,26 +19,12 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push-vector-model-to-ghcr:
|
build-and-push-vector-model-to-ghcr:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check Disk Space
|
|
||||||
run: df -h
|
|
||||||
- name: Free Disk Space (Ubuntu)
|
|
||||||
uses: jlumbroso/free-disk-space@main
|
|
||||||
with:
|
|
||||||
tool-cache: true
|
|
||||||
android: true
|
|
||||||
dotnet: true
|
|
||||||
haskell: true
|
|
||||||
large-packages: true
|
|
||||||
docker-images: true
|
|
||||||
swap-storage: true
|
|
||||||
- name: Check Disk Space
|
|
||||||
run: df -h
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: ${{ github.ref_name }}
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
id: prepare
|
id: prepare
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -56,8 +42,7 @@ jobs:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
# Until https://github.com/tonistiigi/binfmt/issues/215
|
cache-image: false
|
||||||
image: tonistiigi/binfmt:qemu-v7.0.0-28
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
name: build-and-push
|
name: build-and-push
|
||||||
|
|
||||||
run-name: 构建镜像并推送仓库 ${{ github.event.inputs.dockerImageTag }} (${{ github.event.inputs.registry }})
|
run-name: 构建镜像并推送仓库 ${{ github.event.inputs.dockerImageTag }} (${{ github.event.inputs.registry }}) (${{ github.event.inputs.architecture }})
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
dockerImageTag:
|
dockerImageTag:
|
||||||
description: 'Image Tag'
|
description: 'Image Tag'
|
||||||
default: 'v1.10.7-dev'
|
default: 'v2.0.1-dev'
|
||||||
required: true
|
required: true
|
||||||
dockerImageTagWithLatest:
|
dockerImageTagWithLatest:
|
||||||
description: '是否发布latest tag(正式发版时选择,测试版本切勿选择)'
|
description: '是否发布latest tag(正式发版时选择,测试版本切勿选择)'
|
||||||
|
|
@ -41,6 +41,7 @@ jobs:
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Free Disk Space (Ubuntu)
|
- name: Free Disk Space (Ubuntu)
|
||||||
|
if: ${{ contains(github.event.inputs.architecture, ',') }}
|
||||||
uses: jlumbroso/free-disk-space@main
|
uses: jlumbroso/free-disk-space@main
|
||||||
with:
|
with:
|
||||||
tool-cache: true
|
tool-cache: true
|
||||||
|
|
@ -48,8 +49,8 @@ jobs:
|
||||||
dotnet: true
|
dotnet: true
|
||||||
haskell: true
|
haskell: true
|
||||||
large-packages: true
|
large-packages: true
|
||||||
docker-images: true
|
docker-images: false
|
||||||
swap-storage: true
|
swap-storage: false
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -64,15 +65,17 @@ jobs:
|
||||||
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
||||||
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
|
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
|
||||||
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
|
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*}"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*} --tag ${DOCKER_IMAGE}:latest"
|
||||||
else
|
else
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
||||||
fi
|
fi
|
||||||
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
|
echo "buildx_args=--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
|
||||||
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \
|
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \
|
||||||
${DOCKER_IMAGE_TAGS} .
|
${DOCKER_IMAGE_TAGS} ." >> $GITHUB_OUTPUT
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
cache-image: false
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
|
|
@ -99,6 +102,7 @@ jobs:
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Free Disk Space (Ubuntu)
|
- name: Free Disk Space (Ubuntu)
|
||||||
|
if: ${{ contains(github.event.inputs.architecture, ',') }}
|
||||||
uses: jlumbroso/free-disk-space@main
|
uses: jlumbroso/free-disk-space@main
|
||||||
with:
|
with:
|
||||||
tool-cache: true
|
tool-cache: true
|
||||||
|
|
@ -106,8 +110,8 @@ jobs:
|
||||||
dotnet: true
|
dotnet: true
|
||||||
haskell: true
|
haskell: true
|
||||||
large-packages: true
|
large-packages: true
|
||||||
docker-images: true
|
docker-images: false
|
||||||
swap-storage: true
|
swap-storage: false
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -122,15 +126,17 @@ jobs:
|
||||||
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
||||||
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
|
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
|
||||||
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
|
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*}"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*} --tag ${DOCKER_IMAGE}:latest"
|
||||||
else
|
else
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
||||||
fi
|
fi
|
||||||
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
|
echo "buildx_args=--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
|
||||||
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \
|
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \
|
||||||
${DOCKER_IMAGE_TAGS} .
|
${DOCKER_IMAGE_TAGS} ." >> $GITHUB_OUTPUT
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
cache-image: false
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
name: Typos Check
|
name: Typos Check
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
|
@ -12,7 +11,19 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Actions Repository
|
- name: Checkout Actions Repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref_name }}
|
||||||
|
- name: Create config file
|
||||||
|
run: |
|
||||||
|
cat <<EOF > typo-check-config.toml
|
||||||
|
[files]
|
||||||
|
extend-exclude = [
|
||||||
|
"**/*_svg",
|
||||||
|
"**/migrations/**"
|
||||||
|
]
|
||||||
|
EOF
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@master
|
uses: crate-ci/typos@master
|
||||||
|
with:
|
||||||
|
config: ./typo-check-config.toml
|
||||||
|
|
|
||||||
|
|
@ -137,9 +137,9 @@ celerybeat.pid
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
.venv
|
.venv
|
||||||
env/
|
# env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
# ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
|
|
@ -183,5 +183,6 @@ apps/xpack
|
||||||
data
|
data
|
||||||
.dev
|
.dev
|
||||||
poetry.lock
|
poetry.lock
|
||||||
apps/setting/models_provider/impl/*/icon/
|
apps/models_provider/impl/*/icon/
|
||||||
tmp/
|
tmp/
|
||||||
|
config.yml
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
[files]
|
|
||||||
extend-exclude = [
|
|
||||||
'apps/setting/models_provider/impl/*/icon/*'
|
|
||||||
]
|
|
||||||
|
|
@ -27,4 +27,4 @@ When reporting issues, always include:
|
||||||
* Snapshots or log files if needed
|
* Snapshots or log files if needed
|
||||||
|
|
||||||
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
|
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
|
||||||
replace those parts with "REDACTED" or other strings like "****".
|
replace those parts with "REDACTED" or other strings like "****".
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
|
||||||
may consider it more useful to permit linking proprietary applications with
|
may consider it more useful to permit linking proprietary applications with
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License. But first, please read
|
Public License instead of this License. But first, please read
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
75
README.md
75
README.md
|
|
@ -14,7 +14,7 @@
|
||||||
MaxKB = Max Knowledge Brain, it is an open-source platform for building enterprise-grade agents. MaxKB integrates Retrieval-Augmented Generation (RAG) pipelines, supports robust workflows, and provides advanced MCP tool-use capabilities. MaxKB is widely applied in scenarios such as intelligent customer service, corporate internal knowledge bases, academic research, and education.
|
MaxKB = Max Knowledge Brain, it is an open-source platform for building enterprise-grade agents. MaxKB integrates Retrieval-Augmented Generation (RAG) pipelines, supports robust workflows, and provides advanced MCP tool-use capabilities. MaxKB is widely applied in scenarios such as intelligent customer service, corporate internal knowledge bases, academic research, and education.
|
||||||
|
|
||||||
- **RAG Pipeline**: Supports direct uploading of documents / automatic crawling of online documents, with features for automatic text splitting, vectorization. This effectively reduces hallucinations in large models, providing a superior smart Q&A interaction experience.
|
- **RAG Pipeline**: Supports direct uploading of documents / automatic crawling of online documents, with features for automatic text splitting, vectorization. This effectively reduces hallucinations in large models, providing a superior smart Q&A interaction experience.
|
||||||
- **Agentic Workflow**: Equipped with a powerful workflow engine, function library and MCP tool-use, enabling the orchestration of AI processes to meet the needs of complex business scenarios.
|
- **Agentic Workflow**: Equipped with a powerful workflow engine, function library and MCP tool-use, enabling the orchestration of AI processes to meet the needs of complex business scenarios.
|
||||||
- **Seamless Integration**: Facilitates zero-coding rapid integration into third-party business systems, quickly equipping existing systems with intelligent Q&A capabilities to enhance user satisfaction.
|
- **Seamless Integration**: Facilitates zero-coding rapid integration into third-party business systems, quickly equipping existing systems with intelligent Q&A capabilities to enhance user satisfaction.
|
||||||
- **Model-Agnostic**: Supports various large models, including private models (such as DeepSeek, Llama, Qwen, etc.) and public models (like OpenAI, Claude, Gemini, etc.).
|
- **Model-Agnostic**: Supports various large models, including private models (such as DeepSeek, Llama, Qwen, etc.) and public models (like OpenAI, Claude, Gemini, etc.).
|
||||||
- **Multi Modal**: Native support for input and output text, image, audio and video.
|
- **Multi Modal**: Native support for input and output text, image, audio and video.
|
||||||
|
|
@ -24,7 +24,7 @@ MaxKB = Max Knowledge Brain, it is an open-source platform for building enterpri
|
||||||
Execute the script below to start a MaxKB container using Docker:
|
Execute the script below to start a MaxKB container using Docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages 1panel/maxkb
|
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/opt/maxkb 1panel/maxkb
|
||||||
```
|
```
|
||||||
|
|
||||||
Access MaxKB web interface at `http://your_server_ip:8080` with default admin credentials:
|
Access MaxKB web interface at `http://your_server_ip:8080` with default admin credentials:
|
||||||
|
|
@ -32,18 +32,18 @@ Access MaxKB web interface at `http://your_server_ip:8080` with default admin cr
|
||||||
- username: admin
|
- username: admin
|
||||||
- password: MaxKB@123..
|
- password: MaxKB@123..
|
||||||
|
|
||||||
中国用户如遇到 Docker 镜像 Pull 失败问题,请参照该 [离线安装文档](https://maxkb.cn/docs/installation/offline_installtion/) 进行安装。
|
中国用户如遇到 Docker 镜像 Pull 失败问题,请参照该 [离线安装文档](https://maxkb.cn/docs/v2/installation/offline_installtion/) 进行安装。
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<table style="border-collapse: collapse; border: 1px solid black;">
|
<table style="border-collapse: collapse; border: 1px solid black;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/overview.png" alt="MaxKB Demo1" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/eb285512-a66a-4752-8941-c65ed1592238" alt="MaxKB Demo1" /></td>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/screenshot-models.png" alt="MaxKB Demo2" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/f732f1f5-472c-4fd2-93c1-a277eda83d04" alt="MaxKB Demo2" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/screenshot-knowledge.png" alt="MaxKB Demo3" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/c927474a-9a23-4830-822f-5db26025c9b2" alt="MaxKB Demo3" /></td>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/screenshot-function.png" alt="MaxKB Demo4" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/e6268996-a46d-4e58-9f30-31139df78ad2" alt="MaxKB Demo4" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
@ -54,67 +54,6 @@ Access MaxKB web interface at `http://your_server_ip:8080` with default admin cr
|
||||||
- LLM Framework:[LangChain](https://www.langchain.com/)
|
- LLM Framework:[LangChain](https://www.langchain.com/)
|
||||||
- Database:[PostgreSQL + pgvector](https://www.postgresql.org/)
|
- Database:[PostgreSQL + pgvector](https://www.postgresql.org/)
|
||||||
|
|
||||||
## Feature Comparison
|
|
||||||
|
|
||||||
<table style="width: 100%;">
|
|
||||||
<tr>
|
|
||||||
<th align="center">Feature</th>
|
|
||||||
<th align="center">LangChain</th>
|
|
||||||
<th align="center">Dify.AI</th>
|
|
||||||
<th align="center">Flowise</th>
|
|
||||||
<th align="center">MaxKB <br>(Built upon LangChain)</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center">Supported LLMs</td>
|
|
||||||
<td align="center">Rich Variety</td>
|
|
||||||
<td align="center">Rich Variety</td>
|
|
||||||
<td align="center">Rich Variety</td>
|
|
||||||
<td align="center">Rich Variety</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center">RAG Engine</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center">Agent</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">❌</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center">Workflow</td>
|
|
||||||
<td align="center">❌</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center">Observability</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">❌</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center">SSO/Access control</td>
|
|
||||||
<td align="center">❌</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">❌</td>
|
|
||||||
<td align="center">✅ (Pro)</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center">On-premise Deployment</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
<td align="center">✅</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#1Panel-dev/MaxKB&Date)
|
[](https://star-history.com/#1Panel-dev/MaxKB&Date)
|
||||||
|
|
|
||||||
20
README_CN.md
20
README_CN.md
|
|
@ -14,12 +14,12 @@
|
||||||
</p>
|
</p>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
MaxKB = Max Knowledge Brain,是一款强大易用的企业级智能体平台,支持 RAG 检索增强生成、工作流编排、MCP 工具调用能力。MaxKB 支持对接各种主流大语言模型,广泛应用于智能客服、企业内部知识库问答、员工助手、学术研究与教育等场景。
|
MaxKB = Max Knowledge Brain,是一个强大易用的企业级智能体平台,致力于解决企业 AI 落地面临的技术门槛高、部署成本高、迭代周期长等问题,助力企业在人工智能时代赢得先机。秉承“开箱即用,伴随成长”的设计理念,MaxKB 支持企业快速接入主流大模型,高效构建专属知识库,并提供从基础问答(RAG)、复杂流程自动化(工作流)到智能体(Agent)的渐进式升级路径,全面赋能智能客服、智能办公助手等多种应用场景。
|
||||||
|
|
||||||
- **RAG 检索增强生成**:高效搭建本地 AI 知识库,支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化,有效减少大模型幻觉,提升问答效果;
|
- **RAG 检索增强生成**:高效搭建本地 AI 知识库,支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化,有效减少大模型幻觉,提升问答效果;
|
||||||
- **灵活编排**:内置强大的工作流引擎、函数库和 MCP 工具调用能力,支持编排 AI 工作过程,满足复杂业务场景下的需求;
|
- **灵活编排**:内置强大的工作流引擎、函数库和 MCP 工具调用能力,支持编排 AI 工作过程,满足复杂业务场景下的需求;
|
||||||
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度;
|
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度;
|
||||||
- **模型中立**:支持对接各种大模型,包括本地私有大模型(DeepSeek R1 / Llama 3 / Qwen 2 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等)和国外公共大模型(OpenAI / Claude / Gemini 等)。
|
- **模型中立**:支持对接各种大模型,包括本地私有大模型(DeepSeek R1 / Qwen 3 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等)和国外公共大模型(OpenAI / Claude / Gemini 等)。
|
||||||
|
|
||||||
MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/
|
MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/
|
||||||
|
|
||||||
|
|
@ -27,10 +27,10 @@ MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/
|
||||||
|
|
||||||
```
|
```
|
||||||
# Linux 机器
|
# Linux 机器
|
||||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb
|
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/opt/maxkb registry.fit2cloud.com/maxkb/maxkb
|
||||||
|
|
||||||
# Windows 机器
|
# Windows 机器
|
||||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/postgresql/data -v C:/python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb
|
docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/opt/maxkb registry.fit2cloud.com/maxkb/maxkb
|
||||||
|
|
||||||
# 用户名: admin
|
# 用户名: admin
|
||||||
# 密码: MaxKB@123..
|
# 密码: MaxKB@123..
|
||||||
|
|
@ -38,8 +38,8 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/po
|
||||||
|
|
||||||
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB;
|
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB;
|
||||||
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
|
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
|
||||||
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html);
|
- MaxKB 不同产品产品版本的对比请参见:[MaxKB 产品版本对比](https://maxkb.cn/price);
|
||||||
- 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202503.pdf)。
|
- 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://fit2cloud.com/maxkb/download/introduce-maxkb_202507.pdf)。
|
||||||
|
|
||||||
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
|
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
|
||||||
|
|
||||||
|
|
@ -54,12 +54,12 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/po
|
||||||
|
|
||||||
<table style="border-collapse: collapse; border: 1px solid black;">
|
<table style="border-collapse: collapse; border: 1px solid black;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/d87395fa-a8d7-401c-82bf-c6e475d10ae9" alt="MaxKB Demo1" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/eb285512-a66a-4752-8941-c65ed1592238" alt="MaxKB Demo1" /></td>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/47c35ee4-3a3b-4bd4-9f4f-ee20788b2b9a" alt="MaxKB Demo2" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/f732f1f5-472c-4fd2-93c1-a277eda83d04" alt="MaxKB Demo2" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/9a1043cb-fa62-4f71-b9a3-0b46fa59a70e" alt="MaxKB Demo3" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/c927474a-9a23-4830-822f-5db26025c9b2" alt="MaxKB Demo3" /></td>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/3407ce9a-779c-4eb4-858e-9441a2ddc664" alt="MaxKB Demo4" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/e6268996-a46d-4e58-9f30-31139df78ad2" alt="MaxKB Demo4" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,4 @@
|
||||||
- [MaxKB 应用案例:重磅!陕西广电网络“秦岭云”平台实现DeepSeek本地化部署](https://mp.weixin.qq.com/s/ZKmEU_wWShK1YDomKJHQeA)
|
- [MaxKB 应用案例:重磅!陕西广电网络“秦岭云”平台实现DeepSeek本地化部署](https://mp.weixin.qq.com/s/ZKmEU_wWShK1YDomKJHQeA)
|
||||||
- [MaxKB 应用案例:粤海集团完成DeepSeek私有化部署,助力集团智能化管理](https://mp.weixin.qq.com/s/2JbVp0-kr9Hfp-0whH4cvg)
|
- [MaxKB 应用案例:粤海集团完成DeepSeek私有化部署,助力集团智能化管理](https://mp.weixin.qq.com/s/2JbVp0-kr9Hfp-0whH4cvg)
|
||||||
- [MaxKB 应用案例:建筑材料工业信息中心完成DeepSeek本地化部署,推动行业数智化转型新发展](https://mp.weixin.qq.com/s/HThGSnND3qDF8ySEqiM4jw)
|
- [MaxKB 应用案例:建筑材料工业信息中心完成DeepSeek本地化部署,推动行业数智化转型新发展](https://mp.weixin.qq.com/s/HThGSnND3qDF8ySEqiM4jw)
|
||||||
- [MaxKB 应用案例:一起DeepSeek!福建设计以AI大模型开启新篇章](https://mp.weixin.qq.com/s/m67e-H7iQBg3d24NM82UjA)
|
- [MaxKB 应用案例:一起DeepSeek!福建设计以AI大模型开启新篇章](https://mp.weixin.qq.com/s/m67e-H7iQBg3d24NM82UjA)
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_access_token.py
|
||||||
|
@date:2025/6/9 17:46
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from application.serializers.application_access_token import AccessTokenEditSerializer
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationAccessTokenAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
), OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="应用id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return AccessTokenEditSerializer
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application.py
|
||||||
|
@date:2025/5/26 16:59
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \
|
||||||
|
ApplicationImportRequest, ApplicationEditSerializer, TextToSpeechRequest, SpeechToTextRequest, PlayDemoTextRequest
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
from common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest):
|
||||||
|
work_flow = serializers.DictField(required=True, label=_("Workflow Objects"))
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationCreateResponse(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationCreateSerializer.ApplicationResponse()
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationListResult(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationListResponse(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationPageResult(ResultPageSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationListResponse(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationQueryAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="current_page",
|
||||||
|
description=_("Current page"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="page_size",
|
||||||
|
description=_("Page size"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="folder_id",
|
||||||
|
description=_("folder id"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='query',
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="name",
|
||||||
|
description=_("Application Name"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='query',
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="desc",
|
||||||
|
description=_("Application Description"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='query',
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="user_id",
|
||||||
|
description=_("User ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='query',
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="publish_status",
|
||||||
|
description=_("Publish status") + '(published|unpublished)',
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='query',
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationListResult
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_page_response():
|
||||||
|
return ApplicationPageResult
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationCreateAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationCreateRequest
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationCreateResponse
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationImportAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
ApplicationCreateAPI.get_parameters()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationImportRequest
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationOperateAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="应用id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationExportAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return ApplicationOperateAPI.get_parameters()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return DefaultResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationEditAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationEditSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class TextToSpeechAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return ApplicationOperateAPI.get_parameters()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return TextToSpeechRequest
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return DefaultResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SpeechToTextAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return ApplicationOperateAPI.get_parameters()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return SpeechToTextRequest
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return DefaultResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PlayDemoTextAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return ApplicationOperateAPI.get_parameters()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return PlayDemoTextRequest
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return DefaultResultSerializer
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from application.serializers.application_api_key import EditApplicationKeySerializer, ApplicationKeySerializerModel
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
from common.result import ResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationKeyListResult(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationKeySerializerModel(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationKeyResult(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationKeySerializerModel()
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationKeyAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationKeyResult
|
||||||
|
|
||||||
|
class List(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationKeyListResult
|
||||||
|
|
||||||
|
class Operate(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [*ApplicationKeyAPI.get_parameters(), OpenApiParameter(
|
||||||
|
name="api_key_id",
|
||||||
|
description="ApiKeyId",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return EditApplicationKeySerializer
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_chat.py
|
||||||
|
@date:2025/6/10 13:54
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from application.serializers.application_chat import ApplicationChatQuerySerializers, \
|
||||||
|
ApplicationChatResponseSerializers, ApplicationChatRecordExportRequest
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
from common.result import ResultSerializer, ResultPageSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatListResponseSerializers(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationChatResponseSerializers(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatPageResponseSerializers(ResultPageSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationChatResponseSerializers(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatQueryAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatQuerySerializers
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
), OpenApiParameter(
|
||||||
|
name="start_time",
|
||||||
|
description="start Time",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="end_time",
|
||||||
|
description="end Time",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="abstract",
|
||||||
|
description="summary",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="min_star",
|
||||||
|
description=_("Minimum number of likes"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="min_trample",
|
||||||
|
description=_("Minimum number of clicks"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="comparer",
|
||||||
|
description=_("Comparator"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationChatListResponseSerializers
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatQueryPageAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatQueryAPI.get_request()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
*ApplicationChatQueryAPI.get_parameters(),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="current_page",
|
||||||
|
description=_("Current page"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="page_size",
|
||||||
|
description=_("Page size"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationChatPageResponseSerializers
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatExportAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatRecordExportRequest
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return ApplicationChatQueryAPI.get_parameters()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return None
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_chat_record.py
|
||||||
|
@date:2025/6/10 15:19
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from application.serializers.application_chat_record import ApplicationChatRecordAddKnowledgeSerializer, \
|
||||||
|
ApplicationChatRecordImproveInstanceSerializer
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordQueryAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="Application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="chat_id",
|
||||||
|
description=_("Chat ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="order_asc",
|
||||||
|
description=_("Is it in order"),
|
||||||
|
type=OpenApiTypes.BOOL,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordPageQueryAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [*ApplicationChatRecordQueryAPI.get_parameters(),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="current_page",
|
||||||
|
description=_("Current page"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="page_size",
|
||||||
|
description=_("Page size"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordImproveParagraphAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatRecordImproveInstanceSerializer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="Application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="chat_id",
|
||||||
|
description=_("Chat ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="chat_record_id",
|
||||||
|
description=_("Chat Record ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="knowledge_id",
|
||||||
|
description=_("Knowledge ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="document_id",
|
||||||
|
description=_("Document ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
class Operate(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [*ApplicationChatRecordImproveParagraphAPI.get_parameters(), OpenApiParameter(
|
||||||
|
name="paragraph_id",
|
||||||
|
description=_("Paragraph ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordAddKnowledgeAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatRecordAddKnowledgeSerializer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="Application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)]
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_stats.py
|
||||||
|
@date:2025/6/9 20:45
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from application.serializers.application_stats import ApplicationStatsSerializer
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
from common.result import ResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationStatsResult(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationStatsSerializer(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationStatsAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="start_time",
|
||||||
|
description="start Time",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="end_time",
|
||||||
|
description="end Time",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationStatsResult
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_version.py
|
||||||
|
@date:2025/6/4 17:33
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from application.serializers.application_version import ApplicationVersionModelSerializer
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
from common.result import ResultSerializer, PageDataResponse, ResultPageSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationListVersionResult(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationVersionModelSerializer(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationPageVersionResult(ResultPageSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationVersionModelSerializer(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationWorkflowVersionResult(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationVersionModelSerializer()
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationVersionAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationVersionOperateAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_version_id",
|
||||||
|
description="工作流版本id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
, *ApplicationVersionAPI.get_parameters()
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationWorkflowVersionResult
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationVersionListAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="name",
|
||||||
|
description="Version Name",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
, *ApplicationVersionAPI.get_parameters()]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationListVersionResult
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationVersionPageAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return ApplicationVersionListAPI.get_parameters()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationPageVersionResult
|
||||||
|
|
@ -12,42 +12,45 @@ from typing import Type
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from dataset.models import Paragraph
|
from knowledge.models import Paragraph
|
||||||
|
|
||||||
|
|
||||||
class ParagraphPipelineModel:
|
class ParagraphPipelineModel:
|
||||||
|
|
||||||
def __init__(self, _id: str, document_id: str, dataset_id: str, content: str, title: str, status: str,
|
def __init__(self, _id: str, document_id: str, knowledge_id: str, content: str, title: str, status: str,
|
||||||
is_active: bool, comprehensive_score: float, similarity: float, dataset_name: str, document_name: str,
|
is_active: bool, comprehensive_score: float, similarity: float, knowledge_name: str,
|
||||||
hit_handling_method: str, directly_return_similarity: float, meta: dict = None):
|
document_name: str,
|
||||||
|
hit_handling_method: str, directly_return_similarity: float, knowledge_type, meta: dict = None):
|
||||||
self.id = _id
|
self.id = _id
|
||||||
self.document_id = document_id
|
self.document_id = document_id
|
||||||
self.dataset_id = dataset_id
|
self.knowledge_id = knowledge_id
|
||||||
self.content = content
|
self.content = content
|
||||||
self.title = title
|
self.title = title
|
||||||
self.status = status,
|
self.status = status,
|
||||||
self.is_active = is_active
|
self.is_active = is_active
|
||||||
self.comprehensive_score = comprehensive_score
|
self.comprehensive_score = comprehensive_score
|
||||||
self.similarity = similarity
|
self.similarity = similarity
|
||||||
self.dataset_name = dataset_name
|
self.knowledge_name = knowledge_name
|
||||||
self.document_name = document_name
|
self.document_name = document_name
|
||||||
self.hit_handling_method = hit_handling_method
|
self.hit_handling_method = hit_handling_method
|
||||||
self.directly_return_similarity = directly_return_similarity
|
self.directly_return_similarity = directly_return_similarity
|
||||||
self.meta = meta
|
self.meta = meta
|
||||||
|
self.knowledge_type = knowledge_type
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'document_id': self.document_id,
|
'document_id': self.document_id,
|
||||||
'dataset_id': self.dataset_id,
|
'knowledge_id': self.knowledge_id,
|
||||||
'content': self.content,
|
'content': self.content,
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'is_active': self.is_active,
|
'is_active': self.is_active,
|
||||||
'comprehensive_score': self.comprehensive_score,
|
'comprehensive_score': self.comprehensive_score,
|
||||||
'similarity': self.similarity,
|
'similarity': self.similarity,
|
||||||
'dataset_name': self.dataset_name,
|
'knowledge_name': self.knowledge_name,
|
||||||
'document_name': self.document_name,
|
'document_name': self.document_name,
|
||||||
|
'knowledge_type': self.knowledge_type,
|
||||||
'meta': self.meta,
|
'meta': self.meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +60,8 @@ class ParagraphPipelineModel:
|
||||||
self.paragraph = {}
|
self.paragraph = {}
|
||||||
self.comprehensive_score = None
|
self.comprehensive_score = None
|
||||||
self.document_name = None
|
self.document_name = None
|
||||||
self.dataset_name = None
|
self.knowledge_name = None
|
||||||
|
self.knowledge_type = None
|
||||||
self.hit_handling_method = None
|
self.hit_handling_method = None
|
||||||
self.directly_return_similarity = 0.9
|
self.directly_return_similarity = 0.9
|
||||||
self.meta = {}
|
self.meta = {}
|
||||||
|
|
@ -66,7 +70,7 @@ class ParagraphPipelineModel:
|
||||||
if isinstance(paragraph, Paragraph):
|
if isinstance(paragraph, Paragraph):
|
||||||
self.paragraph = {'id': paragraph.id,
|
self.paragraph = {'id': paragraph.id,
|
||||||
'document_id': paragraph.document_id,
|
'document_id': paragraph.document_id,
|
||||||
'dataset_id': paragraph.dataset_id,
|
'knowledge_id': paragraph.knowledge_id,
|
||||||
'content': paragraph.content,
|
'content': paragraph.content,
|
||||||
'title': paragraph.title,
|
'title': paragraph.title,
|
||||||
'status': paragraph.status,
|
'status': paragraph.status,
|
||||||
|
|
@ -76,8 +80,12 @@ class ParagraphPipelineModel:
|
||||||
self.paragraph = paragraph
|
self.paragraph = paragraph
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_dataset_name(self, dataset_name):
|
def add_knowledge_name(self, knowledge_name):
|
||||||
self.dataset_name = dataset_name
|
self.knowledge_name = knowledge_name
|
||||||
|
return self
|
||||||
|
|
||||||
|
def add_knowledge_type(self, knowledge_type):
|
||||||
|
self.knowledge_type = knowledge_type
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_document_name(self, document_name):
|
def add_document_name(self, document_name):
|
||||||
|
|
@ -106,12 +114,13 @@ class ParagraphPipelineModel:
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
return ParagraphPipelineModel(str(self.paragraph.get('id')), str(self.paragraph.get('document_id')),
|
return ParagraphPipelineModel(str(self.paragraph.get('id')), str(self.paragraph.get('document_id')),
|
||||||
str(self.paragraph.get('dataset_id')),
|
str(self.paragraph.get('knowledge_id')),
|
||||||
self.paragraph.get('content'), self.paragraph.get('title'),
|
self.paragraph.get('content'), self.paragraph.get('title'),
|
||||||
self.paragraph.get('status'),
|
self.paragraph.get('status'),
|
||||||
self.paragraph.get('is_active'),
|
self.paragraph.get('is_active'),
|
||||||
self.comprehensive_score, self.similarity, self.dataset_name,
|
self.comprehensive_score, self.similarity, self.knowledge_name,
|
||||||
self.document_name, self.hit_handling_method, self.directly_return_similarity,
|
self.document_name, self.hit_handling_method, self.directly_return_similarity,
|
||||||
|
self.knowledge_type,
|
||||||
self.meta)
|
self.meta)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,14 @@ from common.handle.impl.response.system_to_response import SystemToResponse
|
||||||
|
|
||||||
class PipelineManage:
|
class PipelineManage:
|
||||||
def __init__(self, step_list: List[Type[IBaseChatPipelineStep]],
|
def __init__(self, step_list: List[Type[IBaseChatPipelineStep]],
|
||||||
base_to_response: BaseToResponse = SystemToResponse()):
|
base_to_response: BaseToResponse = SystemToResponse(),
|
||||||
|
debug=False):
|
||||||
# 步骤执行器
|
# 步骤执行器
|
||||||
self.step_list = [step() for step in step_list]
|
self.step_list = [step() for step in step_list]
|
||||||
# 上下文
|
# 上下文
|
||||||
self.context = {'message_tokens': 0, 'answer_tokens': 0}
|
self.context = {'message_tokens': 0, 'answer_tokens': 0}
|
||||||
self.base_to_response = base_to_response
|
self.base_to_response = base_to_response
|
||||||
|
self.debug = debug
|
||||||
|
|
||||||
def run(self, context: Dict = None):
|
def run(self, context: Dict = None):
|
||||||
self.context['start_time'] = time.time()
|
self.context['start_time'] = time.time()
|
||||||
|
|
@ -44,6 +46,7 @@ class PipelineManage:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.step_list: List[Type[IBaseChatPipelineStep]] = []
|
self.step_list: List[Type[IBaseChatPipelineStep]] = []
|
||||||
self.base_to_response = SystemToResponse()
|
self.base_to_response = SystemToResponse()
|
||||||
|
self.debug = False
|
||||||
|
|
||||||
def append_step(self, step: Type[IBaseChatPipelineStep]):
|
def append_step(self, step: Type[IBaseChatPipelineStep]):
|
||||||
self.step_list.append(step)
|
self.step_list.append(step)
|
||||||
|
|
@ -53,5 +56,9 @@ class PipelineManage:
|
||||||
self.base_to_response = base_to_response
|
self.base_to_response = base_to_response
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def add_debug(self, debug):
|
||||||
|
self.debug = debug
|
||||||
|
return self
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
return PipelineManage(step_list=self.step_list, base_to_response=self.base_to_response)
|
return PipelineManage(step_list=self.step_list, base_to_response=self.base_to_response, debug=self.debug)
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,8 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel
|
from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel
|
||||||
from application.chat_pipeline.pipeline_manage import PipelineManage
|
from application.chat_pipeline.pipeline_manage import PipelineManage
|
||||||
from application.serializers.application_serializers import NoReferencesSetting
|
from application.serializers.application import NoReferencesSetting
|
||||||
from common.field.common import InstanceField
|
from common.field.common import InstanceField
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
|
|
||||||
class ModelField(serializers.Field):
|
class ModelField(serializers.Field):
|
||||||
|
|
@ -45,7 +44,7 @@ class PostResponseHandler:
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def handler(self, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], problem_text: str,
|
def handler(self, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], problem_text: str,
|
||||||
answer_text,
|
answer_text,
|
||||||
manage, step, padding_problem_text: str = None, client_id=None, **kwargs):
|
manage, step, padding_problem_text: str = None, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,35 +52,36 @@ class IChatStep(IBaseChatPipelineStep):
|
||||||
class InstanceSerializer(serializers.Serializer):
|
class InstanceSerializer(serializers.Serializer):
|
||||||
# 对话列表
|
# 对话列表
|
||||||
message_list = serializers.ListField(required=True, child=MessageField(required=True),
|
message_list = serializers.ListField(required=True, child=MessageField(required=True),
|
||||||
error_messages=ErrMessage.list(_("Conversation list")))
|
label=_("Conversation list"))
|
||||||
model_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid(_("Model id")))
|
model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Model id"))
|
||||||
# 段落列表
|
# 段落列表
|
||||||
paragraph_list = serializers.ListField(error_messages=ErrMessage.list(_("Paragraph List")))
|
paragraph_list = serializers.ListField(label=_("Paragraph List"))
|
||||||
# 对话id
|
# 对话id
|
||||||
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID")))
|
chat_id = serializers.UUIDField(required=True, label=_("Conversation ID"))
|
||||||
# 用户问题
|
# 用户问题
|
||||||
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.uuid(_("User Questions")))
|
problem_text = serializers.CharField(required=True, label=_("User Questions"))
|
||||||
# 后置处理器
|
# 后置处理器
|
||||||
post_response_handler = InstanceField(model_type=PostResponseHandler,
|
post_response_handler = InstanceField(model_type=PostResponseHandler,
|
||||||
error_messages=ErrMessage.base(_("Post-processor")))
|
label=_("Post-processor"))
|
||||||
# 补全问题
|
# 补全问题
|
||||||
padding_problem_text = serializers.CharField(required=False,
|
padding_problem_text = serializers.CharField(required=False,
|
||||||
error_messages=ErrMessage.base(_("Completion Question")))
|
label=_("Completion Question"))
|
||||||
# 是否使用流的形式输出
|
# 是否使用流的形式输出
|
||||||
stream = serializers.BooleanField(required=False, error_messages=ErrMessage.base(_("Streaming Output")))
|
stream = serializers.BooleanField(required=False, label=_("Streaming Output"))
|
||||||
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client id")))
|
chat_user_id = serializers.CharField(required=True, label=_("Chat user id"))
|
||||||
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client Type")))
|
|
||||||
|
chat_user_type = serializers.CharField(required=True, label=_("Chat user Type"))
|
||||||
# 未查询到引用分段
|
# 未查询到引用分段
|
||||||
no_references_setting = NoReferencesSetting(required=True,
|
no_references_setting = NoReferencesSetting(required=True,
|
||||||
error_messages=ErrMessage.base(_("No reference segment settings")))
|
label=_("No reference segment settings"))
|
||||||
|
|
||||||
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID")))
|
workspace_id = serializers.CharField(required=True, label=_("Workspace ID"))
|
||||||
|
|
||||||
model_setting = serializers.DictField(required=True, allow_null=True,
|
model_setting = serializers.DictField(required=True, allow_null=True,
|
||||||
error_messages=ErrMessage.dict(_("Model settings")))
|
label=_("Model settings"))
|
||||||
|
|
||||||
model_params_setting = serializers.DictField(required=False, allow_null=True,
|
model_params_setting = serializers.DictField(required=False, allow_null=True,
|
||||||
error_messages=ErrMessage.dict(_("Model parameter settings")))
|
label=_("Model parameter settings"))
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
def is_valid(self, *, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
|
|
@ -102,9 +102,9 @@ class IChatStep(IBaseChatPipelineStep):
|
||||||
chat_id, problem_text,
|
chat_id, problem_text,
|
||||||
post_response_handler: PostResponseHandler,
|
post_response_handler: PostResponseHandler,
|
||||||
model_id: str = None,
|
model_id: str = None,
|
||||||
user_id: str = None,
|
workspace_id: str = None,
|
||||||
paragraph_list=None,
|
paragraph_list=None,
|
||||||
manage: PipelineManage = None,
|
manage: PipelineManage = None,
|
||||||
padding_problem_text: str = None, stream: bool = True, client_id=None, client_type=None,
|
padding_problem_text: str = None, stream: bool = True, chat_user_id=None, chat_user_type=None,
|
||||||
no_references_setting=None, model_params_setting=None, model_setting=None, **kwargs):
|
no_references_setting=None, model_params_setting=None, model_setting=None, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import uuid
|
import uuid_utils.compat as uuid
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
@ -18,22 +18,24 @@ from django.utils.translation import gettext as _
|
||||||
from langchain.chat_models.base import BaseChatModel
|
from langchain.chat_models.base import BaseChatModel
|
||||||
from langchain.schema import BaseMessage
|
from langchain.schema import BaseMessage
|
||||||
from langchain.schema.messages import HumanMessage, AIMessage
|
from langchain.schema.messages import HumanMessage, AIMessage
|
||||||
from langchain_core.messages import AIMessageChunk
|
from langchain_core.messages import AIMessageChunk, SystemMessage
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel
|
from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel
|
||||||
from application.chat_pipeline.pipeline_manage import PipelineManage
|
from application.chat_pipeline.pipeline_manage import PipelineManage
|
||||||
from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler
|
from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler
|
||||||
from application.flow.tools import Reasoning
|
from application.flow.tools import Reasoning
|
||||||
from application.models.api_key_model import ApplicationPublicAccessClient
|
from application.models import ApplicationChatUserStats, ChatUserType
|
||||||
from common.constants.authentication_type import AuthenticationType
|
from common.utils.logger import maxkb_logger
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||||
|
|
||||||
|
|
||||||
def add_access_num(client_id=None, client_type=None, application_id=None):
|
def add_access_num(chat_user_id=None, chat_user_type=None, application_id=None):
|
||||||
if client_type == AuthenticationType.APPLICATION_ACCESS_TOKEN.value and application_id is not None:
|
if [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__(
|
||||||
application_public_access_client = (QuerySet(ApplicationPublicAccessClient).filter(client_id=client_id,
|
chat_user_type) and application_id is not None:
|
||||||
application_id=application_id)
|
application_public_access_client = (QuerySet(ApplicationChatUserStats).filter(chat_user_id=chat_user_id,
|
||||||
|
chat_user_type=chat_user_type,
|
||||||
|
application_id=application_id)
|
||||||
.first())
|
.first())
|
||||||
if application_public_access_client is not None:
|
if application_public_access_client is not None:
|
||||||
application_public_access_client.access_num = application_public_access_client.access_num + 1
|
application_public_access_client.access_num = application_public_access_client.access_num + 1
|
||||||
|
|
@ -63,7 +65,7 @@ def event_content(response,
|
||||||
message_list: List[BaseMessage],
|
message_list: List[BaseMessage],
|
||||||
problem_text: str,
|
problem_text: str,
|
||||||
padding_problem_text: str = None,
|
padding_problem_text: str = None,
|
||||||
client_id=None, client_type=None,
|
chat_user_id=None, chat_user_type=None,
|
||||||
is_ai_chat: bool = None,
|
is_ai_chat: bool = None,
|
||||||
model_setting=None):
|
model_setting=None):
|
||||||
if model_setting is None:
|
if model_setting is None:
|
||||||
|
|
@ -124,26 +126,24 @@ def event_content(response,
|
||||||
request_token = 0
|
request_token = 0
|
||||||
response_token = 0
|
response_token = 0
|
||||||
write_context(step, manage, request_token, response_token, all_text)
|
write_context(step, manage, request_token, response_token, all_text)
|
||||||
asker = manage.context.get('form_data', {}).get('asker', None)
|
|
||||||
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
|
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
|
||||||
all_text, manage, step, padding_problem_text, client_id,
|
all_text, manage, step, padding_problem_text,
|
||||||
reasoning_content=reasoning_content if reasoning_content_enable else ''
|
reasoning_content=reasoning_content if reasoning_content_enable else '')
|
||||||
, asker=asker)
|
|
||||||
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
|
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
|
||||||
[], '', True,
|
[], '', True,
|
||||||
request_token, response_token,
|
request_token, response_token,
|
||||||
{'node_is_end': True, 'view_type': 'many_view',
|
{'node_is_end': True, 'view_type': 'many_view',
|
||||||
'node_type': 'ai-chat-node'})
|
'node_type': 'ai-chat-node'})
|
||||||
add_access_num(client_id, client_type, manage.context.get('application_id'))
|
if not manage.debug:
|
||||||
|
add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.getLogger("max_kb_error").error(f'{str(e)}:{traceback.format_exc()}')
|
maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')
|
||||||
all_text = 'Exception:' + str(e)
|
all_text = 'Exception:' + str(e)
|
||||||
write_context(step, manage, 0, 0, all_text)
|
write_context(step, manage, 0, 0, all_text)
|
||||||
asker = manage.context.get('form_data', {}).get('asker', None)
|
|
||||||
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
|
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
|
||||||
all_text, manage, step, padding_problem_text, client_id, reasoning_content='',
|
all_text, manage, step, padding_problem_text, reasoning_content='')
|
||||||
asker=asker)
|
if not manage.debug:
|
||||||
add_access_num(client_id, client_type, manage.context.get('application_id'))
|
add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))
|
||||||
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
|
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
|
||||||
[], all_text,
|
[], all_text,
|
||||||
False,
|
False,
|
||||||
|
|
@ -160,27 +160,28 @@ class BaseChatStep(IChatStep):
|
||||||
problem_text,
|
problem_text,
|
||||||
post_response_handler: PostResponseHandler,
|
post_response_handler: PostResponseHandler,
|
||||||
model_id: str = None,
|
model_id: str = None,
|
||||||
user_id: str = None,
|
workspace_id: str = None,
|
||||||
paragraph_list=None,
|
paragraph_list=None,
|
||||||
manage: PipelineManage = None,
|
manage: PipelineManage = None,
|
||||||
padding_problem_text: str = None,
|
padding_problem_text: str = None,
|
||||||
stream: bool = True,
|
stream: bool = True,
|
||||||
client_id=None, client_type=None,
|
chat_user_id=None, chat_user_type=None,
|
||||||
no_references_setting=None,
|
no_references_setting=None,
|
||||||
model_params_setting=None,
|
model_params_setting=None,
|
||||||
model_setting=None,
|
model_setting=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
chat_model = get_model_instance_by_model_user_id(model_id, user_id,
|
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
|
||||||
**model_params_setting) if model_id is not None else None
|
**model_params_setting) if model_id is not None else None
|
||||||
if stream:
|
if stream:
|
||||||
return self.execute_stream(message_list, chat_id, problem_text, post_response_handler, chat_model,
|
return self.execute_stream(message_list, chat_id, problem_text, post_response_handler, chat_model,
|
||||||
paragraph_list,
|
paragraph_list,
|
||||||
manage, padding_problem_text, client_id, client_type, no_references_setting,
|
manage, padding_problem_text, chat_user_id, chat_user_type,
|
||||||
|
no_references_setting,
|
||||||
model_setting)
|
model_setting)
|
||||||
else:
|
else:
|
||||||
return self.execute_block(message_list, chat_id, problem_text, post_response_handler, chat_model,
|
return self.execute_block(message_list, chat_id, problem_text, post_response_handler, chat_model,
|
||||||
paragraph_list,
|
paragraph_list,
|
||||||
manage, padding_problem_text, client_id, client_type, no_references_setting,
|
manage, padding_problem_text, chat_user_id, chat_user_type, no_references_setting,
|
||||||
model_setting)
|
model_setting)
|
||||||
|
|
||||||
def get_details(self, manage, **kwargs):
|
def get_details(self, manage, **kwargs):
|
||||||
|
|
@ -197,7 +198,8 @@ class BaseChatStep(IChatStep):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_message_list(message_list: List[BaseMessage], answer_text):
|
def reset_message_list(message_list: List[BaseMessage], answer_text):
|
||||||
result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for
|
result = [{'role': 'user' if isinstance(message, HumanMessage) else (
|
||||||
|
'system' if isinstance(message, SystemMessage) else 'ai'), 'content': message.content} for
|
||||||
message
|
message
|
||||||
in
|
in
|
||||||
message_list]
|
message_list]
|
||||||
|
|
@ -235,16 +237,17 @@ class BaseChatStep(IChatStep):
|
||||||
paragraph_list=None,
|
paragraph_list=None,
|
||||||
manage: PipelineManage = None,
|
manage: PipelineManage = None,
|
||||||
padding_problem_text: str = None,
|
padding_problem_text: str = None,
|
||||||
client_id=None, client_type=None,
|
chat_user_id=None, chat_user_type=None,
|
||||||
no_references_setting=None,
|
no_references_setting=None,
|
||||||
model_setting=None):
|
model_setting=None):
|
||||||
chat_result, is_ai_chat = self.get_stream_result(message_list, chat_model, paragraph_list,
|
chat_result, is_ai_chat = self.get_stream_result(message_list, chat_model, paragraph_list,
|
||||||
no_references_setting, problem_text)
|
no_references_setting, problem_text)
|
||||||
chat_record_id = uuid.uuid1()
|
chat_record_id = uuid.uuid7()
|
||||||
r = StreamingHttpResponse(
|
r = StreamingHttpResponse(
|
||||||
streaming_content=event_content(chat_result, chat_id, chat_record_id, paragraph_list,
|
streaming_content=event_content(chat_result, chat_id, chat_record_id, paragraph_list,
|
||||||
post_response_handler, manage, self, chat_model, message_list, problem_text,
|
post_response_handler, manage, self, chat_model, message_list, problem_text,
|
||||||
padding_problem_text, client_id, client_type, is_ai_chat, model_setting),
|
padding_problem_text, chat_user_id, chat_user_type, is_ai_chat,
|
||||||
|
model_setting),
|
||||||
content_type='text/event-stream;charset=utf-8')
|
content_type='text/event-stream;charset=utf-8')
|
||||||
|
|
||||||
r['Cache-Control'] = 'no-cache'
|
r['Cache-Control'] = 'no-cache'
|
||||||
|
|
@ -280,14 +283,14 @@ class BaseChatStep(IChatStep):
|
||||||
paragraph_list=None,
|
paragraph_list=None,
|
||||||
manage: PipelineManage = None,
|
manage: PipelineManage = None,
|
||||||
padding_problem_text: str = None,
|
padding_problem_text: str = None,
|
||||||
client_id=None, client_type=None, no_references_setting=None,
|
chat_user_id=None, chat_user_type=None, no_references_setting=None,
|
||||||
model_setting=None):
|
model_setting=None):
|
||||||
reasoning_content_enable = model_setting.get('reasoning_content_enable', False)
|
reasoning_content_enable = model_setting.get('reasoning_content_enable', False)
|
||||||
reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')
|
reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')
|
||||||
reasoning_content_end = model_setting.get('reasoning_content_end', '</think>')
|
reasoning_content_end = model_setting.get('reasoning_content_end', '</think>')
|
||||||
reasoning = Reasoning(reasoning_content_start,
|
reasoning = Reasoning(reasoning_content_start,
|
||||||
reasoning_content_end)
|
reasoning_content_end)
|
||||||
chat_record_id = uuid.uuid1()
|
chat_record_id = uuid.uuid7()
|
||||||
# 调用模型
|
# 调用模型
|
||||||
try:
|
try:
|
||||||
chat_result, is_ai_chat = self.get_block_result(message_list, chat_model, paragraph_list,
|
chat_result, is_ai_chat = self.get_block_result(message_list, chat_model, paragraph_list,
|
||||||
|
|
@ -307,12 +310,11 @@ class BaseChatStep(IChatStep):
|
||||||
else:
|
else:
|
||||||
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get(
|
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get(
|
||||||
'reasoning_content')
|
'reasoning_content')
|
||||||
asker = manage.context.get('form_data', {}).get('asker', None)
|
|
||||||
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
|
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
|
||||||
content, manage, self, padding_problem_text, client_id,
|
content, manage, self, padding_problem_text,
|
||||||
reasoning_content=reasoning_content if reasoning_content_enable else '',
|
reasoning_content=reasoning_content)
|
||||||
asker=asker)
|
if not manage.debug:
|
||||||
add_access_num(client_id, client_type, manage.context.get('application_id'))
|
add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))
|
||||||
return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id),
|
return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id),
|
||||||
content, True,
|
content, True,
|
||||||
request_token, response_token,
|
request_token, response_token,
|
||||||
|
|
@ -325,10 +327,9 @@ class BaseChatStep(IChatStep):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
all_text = 'Exception:' + str(e)
|
all_text = 'Exception:' + str(e)
|
||||||
write_context(self, manage, 0, 0, all_text)
|
write_context(self, manage, 0, 0, all_text)
|
||||||
asker = manage.context.get('form_data', {}).get('asker', None)
|
|
||||||
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
|
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
|
||||||
all_text, manage, self, padding_problem_text, client_id, reasoning_content='',
|
all_text, manage, self, padding_problem_text, reasoning_content='')
|
||||||
asker=asker)
|
if not manage.debug:
|
||||||
add_access_num(client_id, client_type, manage.context.get('application_id'))
|
add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))
|
||||||
return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), all_text, True, 0,
|
return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), all_text, True, 0,
|
||||||
0, _status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
0, _status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
|
||||||
|
|
@ -16,34 +16,35 @@ from rest_framework import serializers
|
||||||
from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel
|
from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel
|
||||||
from application.chat_pipeline.pipeline_manage import PipelineManage
|
from application.chat_pipeline.pipeline_manage import PipelineManage
|
||||||
from application.models import ChatRecord
|
from application.models import ChatRecord
|
||||||
from application.serializers.application_serializers import NoReferencesSetting
|
from application.serializers.application import NoReferencesSetting
|
||||||
from common.field.common import InstanceField
|
from common.field.common import InstanceField
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
|
|
||||||
class IGenerateHumanMessageStep(IBaseChatPipelineStep):
|
class IGenerateHumanMessageStep(IBaseChatPipelineStep):
|
||||||
class InstanceSerializer(serializers.Serializer):
|
class InstanceSerializer(serializers.Serializer):
|
||||||
# 问题
|
# 问题
|
||||||
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_("question")))
|
problem_text = serializers.CharField(required=True, label=_("question"))
|
||||||
# 段落列表
|
# 段落列表
|
||||||
paragraph_list = serializers.ListField(child=InstanceField(model_type=ParagraphPipelineModel, required=True),
|
paragraph_list = serializers.ListField(child=InstanceField(model_type=ParagraphPipelineModel, required=True),
|
||||||
error_messages=ErrMessage.list(_("Paragraph List")))
|
label=_("Paragraph List"))
|
||||||
# 历史对答
|
# 历史对答
|
||||||
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
||||||
error_messages=ErrMessage.list(_("History Questions")))
|
label=_("History Questions"))
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations")))
|
dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations"))
|
||||||
# 最大携带知识库段落长度
|
# 最大携带知识库段落长度
|
||||||
max_paragraph_char_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(
|
max_paragraph_char_number = serializers.IntegerField(required=True,
|
||||||
_("Maximum length of the knowledge base paragraph")))
|
label=_("Maximum length of the knowledge base paragraph"))
|
||||||
# 模板
|
# 模板
|
||||||
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
|
prompt = serializers.CharField(required=True, label=_("Prompt word"))
|
||||||
system = serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
system = serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
||||||
error_messages=ErrMessage.char(_("System prompt words (role)")))
|
label=_("System prompt words (role)"))
|
||||||
# 补齐问题
|
# 补齐问题
|
||||||
padding_problem_text = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Completion problem")))
|
padding_problem_text = serializers.CharField(required=False,
|
||||||
|
label=_("Completion problem"))
|
||||||
# 未查询到引用分段
|
# 未查询到引用分段
|
||||||
no_references_setting = NoReferencesSetting(required=True, error_messages=ErrMessage.base(_("No reference segment settings")))
|
no_references_setting = NoReferencesSetting(required=True,
|
||||||
|
label=_("No reference segment settings"))
|
||||||
|
|
||||||
def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:
|
def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:
|
||||||
return self.InstanceSerializer
|
return self.InstanceSerializer
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineMode
|
||||||
from application.chat_pipeline.step.generate_human_message_step.i_generate_human_message_step import \
|
from application.chat_pipeline.step.generate_human_message_step.i_generate_human_message_step import \
|
||||||
IGenerateHumanMessageStep
|
IGenerateHumanMessageStep
|
||||||
from application.models import ChatRecord
|
from application.models import ChatRecord
|
||||||
from common.util.split_model import flat_map
|
from common.utils.common import flat_map
|
||||||
|
|
||||||
|
|
||||||
class BaseGenerateHumanMessageStep(IGenerateHumanMessageStep):
|
class BaseGenerateHumanMessageStep(IGenerateHumanMessageStep):
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,20 @@ from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep
|
||||||
from application.chat_pipeline.pipeline_manage import PipelineManage
|
from application.chat_pipeline.pipeline_manage import PipelineManage
|
||||||
from application.models import ChatRecord
|
from application.models import ChatRecord
|
||||||
from common.field.common import InstanceField
|
from common.field.common import InstanceField
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
|
|
||||||
class IResetProblemStep(IBaseChatPipelineStep):
|
class IResetProblemStep(IBaseChatPipelineStep):
|
||||||
class InstanceSerializer(serializers.Serializer):
|
class InstanceSerializer(serializers.Serializer):
|
||||||
# 问题文本
|
# 问题文本
|
||||||
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.float(_("question")))
|
problem_text = serializers.CharField(required=True, label=_("question"))
|
||||||
# 历史对答
|
# 历史对答
|
||||||
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
||||||
error_messages=ErrMessage.list(_("History Questions")))
|
label=_("History Questions"))
|
||||||
# 大语言模型
|
# 大语言模型
|
||||||
model_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid(_("Model id")))
|
model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Model id"))
|
||||||
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID")))
|
workspace_id = serializers.CharField(required=True, label=_("User ID"))
|
||||||
problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
|
problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
|
||||||
error_messages=ErrMessage.char(
|
label=_("Question completion prompt"))
|
||||||
_("Question completion prompt")))
|
|
||||||
|
|
||||||
def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:
|
def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:
|
||||||
return self.InstanceSerializer
|
return self.InstanceSerializer
|
||||||
|
|
@ -52,6 +50,6 @@ class IResetProblemStep(IBaseChatPipelineStep):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,
|
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,
|
||||||
problem_optimization_prompt=None,
|
problem_optimization_prompt=None,
|
||||||
user_id=None,
|
workspace_id=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ from langchain.schema import HumanMessage
|
||||||
|
|
||||||
from application.chat_pipeline.step.reset_problem_step.i_reset_problem_step import IResetProblemStep
|
from application.chat_pipeline.step.reset_problem_step.i_reset_problem_step import IResetProblemStep
|
||||||
from application.models import ChatRecord
|
from application.models import ChatRecord
|
||||||
from common.util.split_model import flat_map
|
from common.utils.split_model import flat_map
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||||
|
|
||||||
prompt = _(
|
prompt = _(
|
||||||
"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag")
|
"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag")
|
||||||
|
|
@ -23,9 +23,9 @@ prompt = _(
|
||||||
class BaseResetProblemStep(IResetProblemStep):
|
class BaseResetProblemStep(IResetProblemStep):
|
||||||
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,
|
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,
|
||||||
problem_optimization_prompt=None,
|
problem_optimization_prompt=None,
|
||||||
user_id=None,
|
workspace_id=None,
|
||||||
**kwargs) -> str:
|
**kwargs) -> str:
|
||||||
chat_model = get_model_instance_by_model_user_id(model_id, user_id) if model_id is not None else None
|
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id) if model_id is not None else None
|
||||||
if chat_model is None:
|
if chat_model is None:
|
||||||
return problem_text
|
return problem_text
|
||||||
start_index = len(history_chat_record) - 3
|
start_index = len(history_chat_record) - 3
|
||||||
|
|
|
||||||
|
|
@ -16,62 +16,62 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel
|
from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel
|
||||||
from application.chat_pipeline.pipeline_manage import PipelineManage
|
from application.chat_pipeline.pipeline_manage import PipelineManage
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
|
|
||||||
class ISearchDatasetStep(IBaseChatPipelineStep):
|
class ISearchDatasetStep(IBaseChatPipelineStep):
|
||||||
class InstanceSerializer(serializers.Serializer):
|
class InstanceSerializer(serializers.Serializer):
|
||||||
# 原始问题文本
|
# 原始问题文本
|
||||||
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_("question")))
|
problem_text = serializers.CharField(required=True, label=_("question"))
|
||||||
# 系统补全问题文本
|
# 系统补全问题文本
|
||||||
padding_problem_text = serializers.CharField(required=False,
|
padding_problem_text = serializers.CharField(required=False,
|
||||||
error_messages=ErrMessage.char(_("System completes question text")))
|
label=_("System completes question text"))
|
||||||
# 需要查询的数据集id列表
|
# 需要查询的数据集id列表
|
||||||
dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
knowledge_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
||||||
error_messages=ErrMessage.list(_("Dataset id list")))
|
label=_("Dataset id list"))
|
||||||
# 需要排除的文档id
|
# 需要排除的文档id
|
||||||
exclude_document_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
exclude_document_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
||||||
error_messages=ErrMessage.list(_("List of document ids to exclude")))
|
label=_("List of document ids to exclude"))
|
||||||
# 需要排除向量id
|
# 需要排除向量id
|
||||||
exclude_paragraph_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
exclude_paragraph_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
||||||
error_messages=ErrMessage.list(_("List of exclusion vector ids")))
|
label=_("List of exclusion vector ids"))
|
||||||
# 需要查询的条数
|
# 需要查询的条数
|
||||||
top_n = serializers.IntegerField(required=True,
|
top_n = serializers.IntegerField(required=True,
|
||||||
error_messages=ErrMessage.integer(_("Reference segment number")))
|
label=_("Reference segment number"))
|
||||||
# 相似度 0-1之间
|
# 相似度 0-1之间
|
||||||
similarity = serializers.FloatField(required=True, max_value=1, min_value=0,
|
similarity = serializers.FloatField(required=True, max_value=1, min_value=0,
|
||||||
error_messages=ErrMessage.float(_("Similarity")))
|
label=_("Similarity"))
|
||||||
search_mode = serializers.CharField(required=True, validators=[
|
search_mode = serializers.CharField(required=True, validators=[
|
||||||
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
|
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
|
||||||
message=_("The type only supports embedding|keywords|blend"), code=500)
|
message=_("The type only supports embedding|keywords|blend"), code=500)
|
||||||
], error_messages=ErrMessage.char(_("Retrieval Mode")))
|
], label=_("Retrieval Mode"))
|
||||||
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID")))
|
workspace_id = serializers.CharField(required=True, label=_("Workspace ID"))
|
||||||
|
|
||||||
def get_step_serializer(self, manage: PipelineManage) -> Type[InstanceSerializer]:
|
def get_step_serializer(self, manage: PipelineManage) -> Type[InstanceSerializer]:
|
||||||
return self.InstanceSerializer
|
return self.InstanceSerializer
|
||||||
|
|
||||||
def _run(self, manage: PipelineManage):
|
def _run(self, manage: PipelineManage):
|
||||||
paragraph_list = self.execute(**self.context['step_args'])
|
paragraph_list = self.execute(**self.context['step_args'], manage=manage)
|
||||||
manage.context['paragraph_list'] = paragraph_list
|
manage.context['paragraph_list'] = paragraph_list
|
||||||
self.context['paragraph_list'] = paragraph_list
|
self.context['paragraph_list'] = paragraph_list
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self, problem_text: str, dataset_id_list: list[str], exclude_document_id_list: list[str],
|
def execute(self, problem_text: str, knowledge_id_list: list[str], exclude_document_id_list: list[str],
|
||||||
exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,
|
exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,
|
||||||
search_mode: str = None,
|
search_mode: str = None,
|
||||||
user_id=None,
|
workspace_id=None,
|
||||||
|
manage: PipelineManage = None,
|
||||||
**kwargs) -> List[ParagraphPipelineModel]:
|
**kwargs) -> List[ParagraphPipelineModel]:
|
||||||
"""
|
"""
|
||||||
关于 用户和补全问题 说明: 补全问题如果有就使用补全问题去查询 反之就用用户原始问题查询
|
关于 用户和补全问题 说明: 补全问题如果有就使用补全问题去查询 反之就用用户原始问题查询
|
||||||
:param similarity: 相关性
|
:param similarity: 相关性
|
||||||
:param top_n: 查询多少条
|
:param top_n: 查询多少条
|
||||||
:param problem_text: 用户问题
|
:param problem_text: 用户问题
|
||||||
:param dataset_id_list: 需要查询的数据集id列表
|
:param knowledge_id_list: 需要查询的数据集id列表
|
||||||
:param exclude_document_id_list: 需要排除的文档id
|
:param exclude_document_id_list: 需要排除的文档id
|
||||||
:param exclude_paragraph_id_list: 需要排除段落id
|
:param exclude_paragraph_id_list: 需要排除段落id
|
||||||
:param padding_problem_text 补全问题
|
:param padding_problem_text 补全问题
|
||||||
:param search_mode 检索模式
|
:param search_mode 检索模式
|
||||||
:param user_id 用户id
|
:param workspace_id 工作空间id
|
||||||
:return: 段落列表
|
:return: 段落列表
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -16,51 +16,52 @@ from rest_framework.utils.formatting import lazy_format
|
||||||
from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel
|
from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel
|
||||||
from application.chat_pipeline.step.search_dataset_step.i_search_dataset_step import ISearchDatasetStep
|
from application.chat_pipeline.step.search_dataset_step.i_search_dataset_step import ISearchDatasetStep
|
||||||
from common.config.embedding_config import VectorStore, ModelManage
|
from common.config.embedding_config import VectorStore, ModelManage
|
||||||
|
from common.constants.permission_constants import RoleConstants
|
||||||
|
from common.database_model_manage.database_model_manage import DatabaseModelManage
|
||||||
from common.db.search import native_search
|
from common.db.search import native_search
|
||||||
from common.util.file_util import get_file_content
|
from common.utils.common import get_file_content
|
||||||
from dataset.models import Paragraph, DataSet
|
from knowledge.models import Paragraph, Knowledge
|
||||||
from embedding.models import SearchMode
|
from knowledge.models import SearchMode
|
||||||
from setting.models import Model
|
from maxkb.conf import PROJECT_DIR
|
||||||
from setting.models_provider import get_model
|
from models_provider.models import Model
|
||||||
from smartdoc.conf import PROJECT_DIR
|
from models_provider.tools import get_model, get_model_by_id
|
||||||
|
|
||||||
|
|
||||||
def get_model_by_id(_id, user_id):
|
def get_embedding_id(knowledge_id_list):
|
||||||
model = QuerySet(Model).filter(id=_id).first()
|
knowledge_list = QuerySet(Knowledge).filter(id__in=knowledge_id_list)
|
||||||
if model is None:
|
if len(set([knowledge.embedding_model_id for knowledge in knowledge_list])) > 1:
|
||||||
raise Exception(_("Model does not exist"))
|
raise Exception(
|
||||||
if model.permission_type == 'PRIVATE' and str(model.user_id) != str(user_id):
|
_("The vector model of the associated knowledge base is inconsistent and the segmentation cannot be recalled."))
|
||||||
message = lazy_format(_('No permission to use this model {model_name}'), model_name=model.name)
|
if len(knowledge_list) == 0:
|
||||||
raise Exception(message)
|
|
||||||
return model
|
|
||||||
|
|
||||||
|
|
||||||
def get_embedding_id(dataset_id_list):
|
|
||||||
dataset_list = QuerySet(DataSet).filter(id__in=dataset_id_list)
|
|
||||||
if len(set([dataset.embedding_mode_id for dataset in dataset_list])) > 1:
|
|
||||||
raise Exception(_("The vector model of the associated knowledge base is inconsistent and the segmentation cannot be recalled."))
|
|
||||||
if len(dataset_list) == 0:
|
|
||||||
raise Exception(_("The knowledge base setting is wrong, please reset the knowledge base"))
|
raise Exception(_("The knowledge base setting is wrong, please reset the knowledge base"))
|
||||||
return dataset_list[0].embedding_mode_id
|
return knowledge_list[0].embedding_model_id
|
||||||
|
|
||||||
|
|
||||||
class BaseSearchDatasetStep(ISearchDatasetStep):
|
class BaseSearchDatasetStep(ISearchDatasetStep):
|
||||||
|
|
||||||
def execute(self, problem_text: str, dataset_id_list: list[str], exclude_document_id_list: list[str],
|
def execute(self, problem_text: str, knowledge_id_list: list[str], exclude_document_id_list: list[str],
|
||||||
exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,
|
exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,
|
||||||
search_mode: str = None,
|
search_mode: str = None,
|
||||||
user_id=None,
|
workspace_id=None,
|
||||||
|
manage=None,
|
||||||
**kwargs) -> List[ParagraphPipelineModel]:
|
**kwargs) -> List[ParagraphPipelineModel]:
|
||||||
if len(dataset_id_list) == 0:
|
get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized')
|
||||||
|
chat_user_type = manage.context.get('chat_user_type')
|
||||||
|
if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type:
|
||||||
|
knowledge_id_list = get_knowledge_list_of_authorized(manage.context.get('chat_user_id'),
|
||||||
|
knowledge_id_list)
|
||||||
|
if len(knowledge_id_list) == 0:
|
||||||
return []
|
return []
|
||||||
exec_problem_text = padding_problem_text if padding_problem_text is not None else problem_text
|
exec_problem_text = padding_problem_text if padding_problem_text is not None else problem_text
|
||||||
model_id = get_embedding_id(dataset_id_list)
|
model_id = get_embedding_id(knowledge_id_list)
|
||||||
model = get_model_by_id(model_id, user_id)
|
model = get_model_by_id(model_id, workspace_id)
|
||||||
|
if model.model_type != "EMBEDDING":
|
||||||
|
raise Exception(_("Model does not exist"))
|
||||||
self.context['model_name'] = model.name
|
self.context['model_name'] = model.name
|
||||||
embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model))
|
embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model))
|
||||||
embedding_value = embedding_model.embed_query(exec_problem_text)
|
embedding_value = embedding_model.embed_query(exec_problem_text)
|
||||||
vector = VectorStore.get_embedding_vector()
|
vector = VectorStore.get_embedding_vector()
|
||||||
embedding_list = vector.query(exec_problem_text, embedding_value, dataset_id_list, exclude_document_id_list,
|
embedding_list = vector.query(exec_problem_text, embedding_value, knowledge_id_list, exclude_document_id_list,
|
||||||
exclude_paragraph_id_list, True, top_n, similarity, SearchMode(search_mode))
|
exclude_paragraph_id_list, True, top_n, similarity, SearchMode(search_mode))
|
||||||
if embedding_list is None:
|
if embedding_list is None:
|
||||||
return []
|
return []
|
||||||
|
|
@ -78,7 +79,8 @@ class BaseSearchDatasetStep(ISearchDatasetStep):
|
||||||
.add_paragraph(paragraph)
|
.add_paragraph(paragraph)
|
||||||
.add_similarity(find_embedding.get('similarity'))
|
.add_similarity(find_embedding.get('similarity'))
|
||||||
.add_comprehensive_score(find_embedding.get('comprehensive_score'))
|
.add_comprehensive_score(find_embedding.get('comprehensive_score'))
|
||||||
.add_dataset_name(paragraph.get('dataset_name'))
|
.add_knowledge_name(paragraph.get('knowledge_name'))
|
||||||
|
.add_knowledge_type(paragraph.get('knowledge_type'))
|
||||||
.add_document_name(paragraph.get('document_name'))
|
.add_document_name(paragraph.get('document_name'))
|
||||||
.add_hit_handling_method(paragraph.get('hit_handling_method'))
|
.add_hit_handling_method(paragraph.get('hit_handling_method'))
|
||||||
.add_directly_return_similarity(paragraph.get('directly_return_similarity'))
|
.add_directly_return_similarity(paragraph.get('directly_return_similarity'))
|
||||||
|
|
@ -102,7 +104,7 @@ class BaseSearchDatasetStep(ISearchDatasetStep):
|
||||||
paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),
|
paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),
|
||||||
get_file_content(
|
get_file_content(
|
||||||
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
||||||
'list_dataset_paragraph_by_paragraph_id.sql')),
|
'list_knowledge_paragraph_by_paragraph_id.sql')),
|
||||||
with_table_name=True)
|
with_table_name=True)
|
||||||
# 如果向量库中存在脏数据 直接删除
|
# 如果向量库中存在脏数据 直接删除
|
||||||
if len(paragraph_list) != len(paragraph_id_list):
|
if len(paragraph_list) != len(paragraph_id_list):
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,21 @@
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||||
|
|
||||||
|
from common.exception.app_exception import AppApiException
|
||||||
|
from common.utils.common import group_by
|
||||||
|
from models_provider.models import Model
|
||||||
|
from models_provider.tools import get_model_credential
|
||||||
|
from tools.models.tool import Tool
|
||||||
|
|
||||||
|
end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node',
|
||||||
|
'image-understand-node', 'speech-to-text-node', 'text-to-speech-node', 'image-generate-node']
|
||||||
|
|
||||||
|
|
||||||
class Answer:
|
class Answer:
|
||||||
def __init__(self, content, view_type, runtime_node_id, chat_record_id, child_node, real_node_id,
|
def __init__(self, content, view_type, runtime_node_id, chat_record_id, child_node, real_node_id,
|
||||||
|
|
@ -42,3 +57,207 @@ class NodeChunk:
|
||||||
|
|
||||||
def is_end(self):
|
def is_end(self):
|
||||||
return self.status == 200
|
return self.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
class Edge:
|
||||||
|
def __init__(self, _id: str, _type: str, sourceNodeId: str, targetNodeId: str, **keywords):
|
||||||
|
self.id = _id
|
||||||
|
self.type = _type
|
||||||
|
self.sourceNodeId = sourceNodeId
|
||||||
|
self.targetNodeId = targetNodeId
|
||||||
|
for keyword in keywords:
|
||||||
|
self.__setattr__(keyword, keywords.get(keyword))
|
||||||
|
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
def __init__(self, _id: str, _type: str, x: int, y: int, properties: dict, **kwargs):
|
||||||
|
self.id = _id
|
||||||
|
self.type = _type
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.properties = properties
|
||||||
|
for keyword in kwargs:
|
||||||
|
self.__setattr__(keyword, kwargs.get(keyword))
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeNode:
|
||||||
|
edge: Edge
|
||||||
|
node: Node
|
||||||
|
|
||||||
|
def __init__(self, edge, node):
|
||||||
|
self.edge = edge
|
||||||
|
self.node = node
|
||||||
|
|
||||||
|
|
||||||
|
class Workflow:
|
||||||
|
"""
|
||||||
|
节点列表
|
||||||
|
"""
|
||||||
|
nodes: List[Node]
|
||||||
|
"""
|
||||||
|
线列表
|
||||||
|
"""
|
||||||
|
edges: List[Edge]
|
||||||
|
"""
|
||||||
|
节点id:node
|
||||||
|
"""
|
||||||
|
node_map: Dict[str, Node]
|
||||||
|
"""
|
||||||
|
节点id:当前节点id上面的所有节点
|
||||||
|
"""
|
||||||
|
up_node_map: Dict[str, List[EdgeNode]]
|
||||||
|
"""
|
||||||
|
节点id:当前节点id下面的所有节点
|
||||||
|
"""
|
||||||
|
next_node_map: Dict[str, List[EdgeNode]]
|
||||||
|
|
||||||
|
def __init__(self, nodes: List[Node], edges: List[Edge]):
|
||||||
|
self.nodes = nodes
|
||||||
|
self.edges = edges
|
||||||
|
self.node_map = {node.id: node for node in nodes}
|
||||||
|
|
||||||
|
self.up_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.sourceNodeId)) for
|
||||||
|
edge in edges] for
|
||||||
|
key, edges in
|
||||||
|
group_by(edges, key=lambda edge: edge.targetNodeId).items()}
|
||||||
|
|
||||||
|
self.next_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.targetNodeId)) for edge in edges] for
|
||||||
|
key, edges in
|
||||||
|
group_by(edges, key=lambda edge: edge.sourceNodeId).items()}
|
||||||
|
|
||||||
|
def get_node(self, node_id):
|
||||||
|
"""
|
||||||
|
根据node_id 获取节点信息
|
||||||
|
@param node_id: node_id
|
||||||
|
@return: 节点信息
|
||||||
|
"""
|
||||||
|
return self.node_map.get(node_id)
|
||||||
|
|
||||||
|
def get_up_edge_nodes(self, node_id) -> List[EdgeNode]:
|
||||||
|
"""
|
||||||
|
根据节点id 获取当前连接前置节点和连线
|
||||||
|
@param node_id: 节点id
|
||||||
|
@return: 节点连线列表
|
||||||
|
"""
|
||||||
|
return self.up_node_map.get(node_id)
|
||||||
|
|
||||||
|
def get_next_edge_nodes(self, node_id) -> List[EdgeNode]:
|
||||||
|
"""
|
||||||
|
根据节点id 获取当前连接目标节点和连线
|
||||||
|
@param node_id: 节点id
|
||||||
|
@return: 节点连线列表
|
||||||
|
"""
|
||||||
|
return self.next_node_map.get(node_id)
|
||||||
|
|
||||||
|
def get_up_nodes(self, node_id) -> List[Node]:
|
||||||
|
"""
|
||||||
|
根据节点id 获取当前连接前置节点
|
||||||
|
@param node_id: 节点id
|
||||||
|
@return: 节点列表
|
||||||
|
"""
|
||||||
|
return [en.node for en in self.up_node_map.get(node_id)]
|
||||||
|
|
||||||
|
def get_next_nodes(self, node_id) -> List[Node]:
|
||||||
|
"""
|
||||||
|
根据节点id 获取当前连接目标节点
|
||||||
|
@param node_id: 节点id
|
||||||
|
@return: 节点列表
|
||||||
|
"""
|
||||||
|
return [en.node for en in self.next_node_map.get(node_id, [])]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_instance(flow_obj: Dict):
|
||||||
|
nodes = flow_obj.get('nodes')
|
||||||
|
edges = flow_obj.get('edges')
|
||||||
|
nodes = [Node(node.get('id'), node.get('type'), **node)
|
||||||
|
for node in nodes]
|
||||||
|
edges = [Edge(edge.get('id'), edge.get('type'), **edge) for edge in edges]
|
||||||
|
return Workflow(nodes, edges)
|
||||||
|
|
||||||
|
def get_start_node(self):
|
||||||
|
return self.get_node('start-node')
|
||||||
|
|
||||||
|
def get_search_node(self):
|
||||||
|
return [node for node in self.nodes if node.type == 'search-dataset-node']
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
"""
|
||||||
|
校验工作流数据
|
||||||
|
"""
|
||||||
|
self.is_valid_model_params()
|
||||||
|
self.is_valid_start_node()
|
||||||
|
self.is_valid_base_node()
|
||||||
|
self.is_valid_work_flow()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_node_params(node: Node):
|
||||||
|
from application.flow.step_node import get_node
|
||||||
|
get_node(node.type)(node, None, None)
|
||||||
|
|
||||||
|
def is_valid_node(self, node: Node):
|
||||||
|
self.is_valid_node_params(node)
|
||||||
|
if node.type == 'condition-node':
|
||||||
|
branch_list = node.properties.get('node_data').get('branch')
|
||||||
|
for branch in branch_list:
|
||||||
|
source_anchor_id = f"{node.id}_{branch.get('id')}_right"
|
||||||
|
edge_list = [edge for edge in self.edges if edge.sourceAnchorId == source_anchor_id]
|
||||||
|
if len(edge_list) == 0:
|
||||||
|
raise AppApiException(500,
|
||||||
|
_('The branch {branch} of the {node} node needs to be connected').format(
|
||||||
|
node=node.properties.get("stepName"), branch=branch.get("type")))
|
||||||
|
|
||||||
|
else:
|
||||||
|
edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id]
|
||||||
|
if len(edge_list) == 0 and not end_nodes.__contains__(node.type):
|
||||||
|
raise AppApiException(500, _("{node} Nodes cannot be considered as end nodes").format(
|
||||||
|
node=node.properties.get("stepName")))
|
||||||
|
|
||||||
|
def is_valid_work_flow(self, up_node=None):
|
||||||
|
if up_node is None:
|
||||||
|
up_node = self.get_start_node()
|
||||||
|
self.is_valid_node(up_node)
|
||||||
|
next_nodes = self.get_next_nodes(up_node)
|
||||||
|
for next_node in next_nodes:
|
||||||
|
self.is_valid_work_flow(next_node)
|
||||||
|
|
||||||
|
def is_valid_start_node(self):
|
||||||
|
start_node_list = [node for node in self.nodes if node.id == 'start-node']
|
||||||
|
if len(start_node_list) == 0:
|
||||||
|
raise AppApiException(500, _('The starting node is required'))
|
||||||
|
if len(start_node_list) > 1:
|
||||||
|
raise AppApiException(500, _('There can only be one starting node'))
|
||||||
|
|
||||||
|
def is_valid_model_params(self):
|
||||||
|
node_list = [node for node in self.nodes if (node.type == 'ai-chat-node' or node.type == 'question-node')]
|
||||||
|
for node in node_list:
|
||||||
|
model = QuerySet(Model).filter(id=node.properties.get('node_data', {}).get('model_id')).first()
|
||||||
|
if model is None:
|
||||||
|
raise ValidationError(ErrorDetail(
|
||||||
|
_('The node {node} model does not exist').format(node=node.properties.get("stepName"))))
|
||||||
|
credential = get_model_credential(model.provider, model.model_type, model.model_name)
|
||||||
|
model_params_setting = node.properties.get('node_data', {}).get('model_params_setting')
|
||||||
|
model_params_setting_form = credential.get_model_params_setting_form(
|
||||||
|
model.model_name)
|
||||||
|
if model_params_setting is None:
|
||||||
|
model_params_setting = model_params_setting_form.get_default_form_data()
|
||||||
|
node.properties.get('node_data', {})['model_params_setting'] = model_params_setting
|
||||||
|
if node.properties.get('status', 200) != 200:
|
||||||
|
raise ValidationError(
|
||||||
|
ErrorDetail(_("Node {node} is unavailable").format(node.properties.get("stepName"))))
|
||||||
|
node_list = [node for node in self.nodes if (node.type == 'function-lib-node')]
|
||||||
|
for node in node_list:
|
||||||
|
function_lib_id = node.properties.get('node_data', {}).get('function_lib_id')
|
||||||
|
if function_lib_id is None:
|
||||||
|
raise ValidationError(ErrorDetail(
|
||||||
|
_('The library ID of node {node} cannot be empty').format(node=node.properties.get("stepName"))))
|
||||||
|
f_lib = QuerySet(Tool).filter(id=function_lib_id).first()
|
||||||
|
if f_lib is None:
|
||||||
|
raise ValidationError(ErrorDetail(_("The function library for node {node} is not available").format(
|
||||||
|
node=node.properties.get("stepName"))))
|
||||||
|
|
||||||
|
def is_valid_base_node(self):
|
||||||
|
base_node_list = [node for node in self.nodes if node.id == 'base-node']
|
||||||
|
if len(base_node_list) == 0:
|
||||||
|
raise AppApiException(500, _('Basic information node is required'))
|
||||||
|
if len(base_node_list) > 1:
|
||||||
|
raise AppApiException(500, _('There can only be one basic information node'))
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,11 @@ from rest_framework import serializers
|
||||||
from rest_framework.exceptions import ValidationError, ErrorDetail
|
from rest_framework.exceptions import ValidationError, ErrorDetail
|
||||||
|
|
||||||
from application.flow.common import Answer, NodeChunk
|
from application.flow.common import Answer, NodeChunk
|
||||||
from application.models import ChatRecord
|
from application.models import ApplicationChatUserStats
|
||||||
from application.models.api_key_model import ApplicationPublicAccessClient
|
from application.models import ChatRecord, ChatUserType
|
||||||
from common.constants.authentication_type import AuthenticationType
|
|
||||||
from common.field.common import InstanceField
|
from common.field.common import InstanceField
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
chat_cache = cache.caches['chat_cache']
|
chat_cache = cache
|
||||||
|
|
||||||
|
|
||||||
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
|
|
@ -46,16 +44,14 @@ def is_interrupt(node, step_variable: Dict, global_variable: Dict):
|
||||||
|
|
||||||
|
|
||||||
class WorkFlowPostHandler:
|
class WorkFlowPostHandler:
|
||||||
def __init__(self, chat_info, client_id, client_type):
|
def __init__(self, chat_info):
|
||||||
self.chat_info = chat_info
|
self.chat_info = chat_info
|
||||||
self.client_id = client_id
|
|
||||||
self.client_type = client_type
|
|
||||||
|
|
||||||
def handler(self, chat_id,
|
def handler(self, workflow):
|
||||||
chat_record_id,
|
workflow_body = workflow.get_body()
|
||||||
answer,
|
question = workflow_body.get('question')
|
||||||
workflow):
|
chat_record_id = workflow_body.get('chat_record_id')
|
||||||
question = workflow.params['question']
|
chat_id = workflow_body.get('chat_id')
|
||||||
details = workflow.get_runtime_details()
|
details = workflow.get_runtime_details()
|
||||||
message_tokens = sum([row.get('message_tokens') for row in details.values() if
|
message_tokens = sum([row.get('message_tokens') for row in details.values() if
|
||||||
'message_tokens' in row and row.get('message_tokens') is not None])
|
'message_tokens' in row and row.get('message_tokens') is not None])
|
||||||
|
|
@ -84,15 +80,16 @@ class WorkFlowPostHandler:
|
||||||
answer_text_list=answer_text_list,
|
answer_text_list=answer_text_list,
|
||||||
run_time=time.time() - workflow.context['start_time'],
|
run_time=time.time() - workflow.context['start_time'],
|
||||||
index=0)
|
index=0)
|
||||||
asker = workflow.context.get('asker', None)
|
|
||||||
self.chat_info.append_chat_record(chat_record, self.client_id, asker)
|
self.chat_info.append_chat_record(chat_record)
|
||||||
# 重新设置缓存
|
self.chat_info.set_cache()
|
||||||
chat_cache.set(chat_id,
|
|
||||||
self.chat_info, timeout=60 * 30)
|
if not self.chat_info.debug and [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__(
|
||||||
if self.client_type == AuthenticationType.APPLICATION_ACCESS_TOKEN.value:
|
workflow_body.get('chat_user_type')):
|
||||||
application_public_access_client = (QuerySet(ApplicationPublicAccessClient)
|
application_public_access_client = (QuerySet(ApplicationChatUserStats)
|
||||||
.filter(client_id=self.client_id,
|
.filter(chat_user_id=workflow_body.get('chat_user_id'),
|
||||||
application_id=self.chat_info.application.id).first())
|
chat_user_type=workflow_body.get('chat_user_type'),
|
||||||
|
application_id=self.chat_info.application_id).first())
|
||||||
if application_public_access_client is not None:
|
if application_public_access_client is not None:
|
||||||
application_public_access_client.access_num = application_public_access_client.access_num + 1
|
application_public_access_client.access_num = application_public_access_client.access_num + 1
|
||||||
application_public_access_client.intraday_access_num = application_public_access_client.intraday_access_num + 1
|
application_public_access_client.intraday_access_num = application_public_access_client.intraday_access_num + 1
|
||||||
|
|
@ -123,31 +120,36 @@ class NodeResult:
|
||||||
|
|
||||||
|
|
||||||
class ReferenceAddressSerializer(serializers.Serializer):
|
class ReferenceAddressSerializer(serializers.Serializer):
|
||||||
node_id = serializers.CharField(required=True, error_messages=ErrMessage.char("节点id"))
|
node_id = serializers.CharField(required=True, label="节点id")
|
||||||
fields = serializers.ListField(
|
fields = serializers.ListField(
|
||||||
child=serializers.CharField(required=True, error_messages=ErrMessage.char("节点字段")), required=True,
|
child=serializers.CharField(required=True, label="节点字段"), required=True,
|
||||||
error_messages=ErrMessage.list("节点字段数组"))
|
label="节点字段数组")
|
||||||
|
|
||||||
|
|
||||||
class FlowParamsSerializer(serializers.Serializer):
|
class FlowParamsSerializer(serializers.Serializer):
|
||||||
# 历史对答
|
# 历史对答
|
||||||
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
||||||
error_messages=ErrMessage.list("历史对答"))
|
label="历史对答")
|
||||||
|
|
||||||
question = serializers.CharField(required=True, error_messages=ErrMessage.list("用户问题"))
|
question = serializers.CharField(required=True, label="用户问题")
|
||||||
|
|
||||||
chat_id = serializers.CharField(required=True, error_messages=ErrMessage.list("对话id"))
|
chat_id = serializers.CharField(required=True, label="对话id")
|
||||||
|
|
||||||
chat_record_id = serializers.CharField(required=True, error_messages=ErrMessage.char("对话记录id"))
|
chat_record_id = serializers.CharField(required=True, label="对话记录id")
|
||||||
|
|
||||||
stream = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("流式输出"))
|
stream = serializers.BooleanField(required=True, label="流式输出")
|
||||||
|
|
||||||
client_id = serializers.CharField(required=False, error_messages=ErrMessage.char("客户端id"))
|
chat_user_id = serializers.CharField(required=False, label="对话用户id")
|
||||||
|
|
||||||
client_type = serializers.CharField(required=False, error_messages=ErrMessage.char("客户端类型"))
|
chat_user_type = serializers.CharField(required=False, label="对话用户类型")
|
||||||
|
|
||||||
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
workspace_id = serializers.CharField(required=True, label="工作空间id")
|
||||||
re_chat = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("换个答案"))
|
|
||||||
|
application_id = serializers.CharField(required=True, label="应用id")
|
||||||
|
|
||||||
|
re_chat = serializers.BooleanField(required=True, label="换个答案")
|
||||||
|
|
||||||
|
debug = serializers.BooleanField(required=True, label="是否debug")
|
||||||
|
|
||||||
|
|
||||||
class INode:
|
class INode:
|
||||||
|
|
|
||||||
|
|
@ -10,26 +10,24 @@ from .ai_chat_step_node import *
|
||||||
from .application_node import BaseApplicationNode
|
from .application_node import BaseApplicationNode
|
||||||
from .condition_node import *
|
from .condition_node import *
|
||||||
from .direct_reply_node import *
|
from .direct_reply_node import *
|
||||||
|
from .document_extract_node import *
|
||||||
from .form_node import *
|
from .form_node import *
|
||||||
from .function_lib_node import *
|
from .image_generate_step_node import *
|
||||||
from .function_node import *
|
from .image_understand_step_node import *
|
||||||
|
from .mcp_node import BaseMcpNode
|
||||||
from .question_node import *
|
from .question_node import *
|
||||||
from .reranker_node import *
|
from .reranker_node import *
|
||||||
|
from .search_knowledge_node import *
|
||||||
from .document_extract_node import *
|
|
||||||
from .image_understand_step_node import *
|
|
||||||
from .image_generate_step_node import *
|
|
||||||
|
|
||||||
from .search_dataset_node import *
|
|
||||||
from .speech_to_text_step_node import BaseSpeechToTextNode
|
from .speech_to_text_step_node import BaseSpeechToTextNode
|
||||||
from .start_node import *
|
from .start_node import *
|
||||||
from .text_to_speech_step_node.impl.base_text_to_speech_node import BaseTextToSpeechNode
|
from .text_to_speech_step_node.impl.base_text_to_speech_node import BaseTextToSpeechNode
|
||||||
|
from .tool_lib_node import *
|
||||||
|
from .tool_node import *
|
||||||
from .variable_assign_node import BaseVariableAssignNode
|
from .variable_assign_node import BaseVariableAssignNode
|
||||||
from .mcp_node import BaseMcpNode
|
|
||||||
|
|
||||||
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode,
|
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchKnowledgeNode, BaseQuestionNode,
|
||||||
BaseConditionNode, BaseReplyNode,
|
BaseConditionNode, BaseReplyNode,
|
||||||
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode,
|
BaseToolNodeNode, BaseToolLibNodeNode, BaseRerankerNode, BaseApplicationNode,
|
||||||
BaseDocumentExtractNode,
|
BaseDocumentExtractNode,
|
||||||
BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,
|
BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,
|
||||||
BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode]
|
BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode]
|
||||||
|
|
|
||||||
|
|
@ -12,30 +12,28 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
|
|
||||||
class ChatNodeSerializer(serializers.Serializer):
|
class ChatNodeSerializer(serializers.Serializer):
|
||||||
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
model_id = serializers.CharField(required=True, label=_("Model id"))
|
||||||
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
error_messages=ErrMessage.char(_("Role Setting")))
|
label=_("Role Setting"))
|
||||||
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
|
prompt = serializers.CharField(required=True, label=_("Prompt word"))
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(
|
dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations"))
|
||||||
_("Number of multi-round conversations")))
|
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False,
|
||||||
error_messages=ErrMessage.boolean(_('Whether to return content')))
|
label=_('Whether to return content'))
|
||||||
|
|
||||||
model_params_setting = serializers.DictField(required=False,
|
model_params_setting = serializers.DictField(required=False,
|
||||||
error_messages=ErrMessage.dict(_("Model parameter settings")))
|
label=_("Model parameter settings"))
|
||||||
model_setting = serializers.DictField(required=False,
|
model_setting = serializers.DictField(required=False,
|
||||||
error_messages=ErrMessage.dict('Model settings'))
|
label='Model settings')
|
||||||
dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
error_messages=ErrMessage.char(_("Context Type")))
|
label=_("Context Type"))
|
||||||
mcp_enable = serializers.BooleanField(required=False,
|
mcp_enable = serializers.BooleanField(required=False,
|
||||||
error_messages=ErrMessage.boolean(_("Whether to enable MCP")))
|
label=_("Whether to enable MCP"))
|
||||||
mcp_servers = serializers.JSONField(required=False, error_messages=ErrMessage.list(_("MCP Server")))
|
mcp_servers = serializers.JSONField(required=False, label=_("MCP Server"))
|
||||||
|
|
||||||
|
|
||||||
class IChatNode(INode):
|
class IChatNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from types import AsyncGeneratorType
|
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
@ -23,9 +23,9 @@ from langgraph.prebuilt import create_react_agent
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
|
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
|
||||||
from application.flow.tools import Reasoning
|
from application.flow.tools import Reasoning
|
||||||
from setting.models import Model
|
from models_provider.models import Model
|
||||||
from setting.models_provider import get_model_credential
|
from models_provider.tools import get_model_credential, get_model_instance_by_model_workspace_id
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from common.utils.logger import maxkb_logger
|
||||||
|
|
||||||
tool_message_template = """
|
tool_message_template = """
|
||||||
<details>
|
<details>
|
||||||
|
|
@ -33,13 +33,25 @@ tool_message_template = """
|
||||||
<strong>Called MCP Tool: <em>%s</em></strong>
|
<strong>Called MCP Tool: <em>%s</em></strong>
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
```json
|
|
||||||
%s
|
%s
|
||||||
```
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
tool_message_json_template = """
|
||||||
|
```json
|
||||||
|
%s
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def generate_tool_message_template(name, context):
|
||||||
|
if '```' in context:
|
||||||
|
return tool_message_template % (name, context)
|
||||||
|
else:
|
||||||
|
return tool_message_template % (name, tool_message_json_template % (context))
|
||||||
|
|
||||||
|
|
||||||
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,
|
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,
|
||||||
reasoning_content: str):
|
reasoning_content: str):
|
||||||
|
|
@ -104,16 +116,17 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
|
||||||
|
|
||||||
|
|
||||||
async def _yield_mcp_response(chat_model, message_list, mcp_servers):
|
async def _yield_mcp_response(chat_model, message_list, mcp_servers):
|
||||||
async with MultiServerMCPClient(json.loads(mcp_servers)) as client:
|
client = MultiServerMCPClient(json.loads(mcp_servers))
|
||||||
agent = create_react_agent(chat_model, client.get_tools())
|
tools = await client.get_tools()
|
||||||
response = agent.astream({"messages": message_list}, stream_mode='messages')
|
agent = create_react_agent(chat_model, tools)
|
||||||
async for chunk in response:
|
response = agent.astream({"messages": message_list}, stream_mode='messages')
|
||||||
if isinstance(chunk[0], ToolMessage):
|
async for chunk in response:
|
||||||
content = tool_message_template % (chunk[0].name, chunk[0].content)
|
if isinstance(chunk[0], ToolMessage):
|
||||||
chunk[0].content = content
|
content = generate_tool_message_template(chunk[0].name, chunk[0].content)
|
||||||
yield chunk[0]
|
chunk[0].content = content
|
||||||
if isinstance(chunk[0], AIMessageChunk):
|
yield chunk[0]
|
||||||
yield chunk[0]
|
if isinstance(chunk[0], AIMessageChunk):
|
||||||
|
yield chunk[0]
|
||||||
|
|
||||||
|
|
||||||
def mcp_response_generator(chat_model, message_list, mcp_servers):
|
def mcp_response_generator(chat_model, message_list, mcp_servers):
|
||||||
|
|
@ -127,7 +140,7 @@ def mcp_response_generator(chat_model, message_list, mcp_servers):
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'exception: {e}')
|
maxkb_logger.error(f'Exception: {e}')
|
||||||
finally:
|
finally:
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|
||||||
|
|
@ -152,8 +165,9 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
|
||||||
reasoning_result = reasoning.get_reasoning_content(response)
|
reasoning_result = reasoning.get_reasoning_content(response)
|
||||||
reasoning_result_end = reasoning.get_end_reasoning_content()
|
reasoning_result_end = reasoning.get_end_reasoning_content()
|
||||||
content = reasoning_result.get('content') + reasoning_result_end.get('content')
|
content = reasoning_result.get('content') + reasoning_result_end.get('content')
|
||||||
if 'reasoning_content' in response.response_metadata:
|
meta = {**response.response_metadata, **response.additional_kwargs}
|
||||||
reasoning_content = response.response_metadata.get('reasoning_content', '')
|
if 'reasoning_content' in meta:
|
||||||
|
reasoning_content = meta.get('reasoning_content', '')
|
||||||
else:
|
else:
|
||||||
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get('reasoning_content')
|
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get('reasoning_content')
|
||||||
_write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)
|
_write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)
|
||||||
|
|
@ -207,8 +221,9 @@ class BaseChatNode(IChatNode):
|
||||||
model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
|
model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
|
||||||
'reasoning_content_start': '<think>'}
|
'reasoning_content_start': '<think>'}
|
||||||
self.context['model_setting'] = model_setting
|
self.context['model_setting'] = model_setting
|
||||||
chat_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'),
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
**model_params_setting)
|
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
|
||||||
|
**model_params_setting)
|
||||||
history_message = self.get_history_message(history_chat_record, dialogue_number, dialogue_type,
|
history_message = self.get_history_message(history_chat_record, dialogue_number, dialogue_type,
|
||||||
self.runtime_node_id)
|
self.runtime_node_id)
|
||||||
self.context['history_message'] = history_message
|
self.context['history_message'] = history_message
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,23 @@ from typing import Type
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ApplicationNodeSerializer(serializers.Serializer):
|
class ApplicationNodeSerializer(serializers.Serializer):
|
||||||
application_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Application ID")))
|
application_id = serializers.CharField(required=True, label=_("Application ID"))
|
||||||
question_reference_address = serializers.ListField(required=True,
|
question_reference_address = serializers.ListField(required=True,
|
||||||
error_messages=ErrMessage.list(_("User Questions")))
|
label=_("User Questions"))
|
||||||
api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("API Input Fields")))
|
api_input_field_list = serializers.ListField(required=False, label=_("API Input Fields"))
|
||||||
user_input_field_list = serializers.ListField(required=False,
|
user_input_field_list = serializers.ListField(required=False,
|
||||||
error_messages=ErrMessage.uuid(_("User Input Fields")))
|
label=_("User Input Fields"))
|
||||||
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture")))
|
image_list = serializers.ListField(required=False, label=_("picture"))
|
||||||
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document")))
|
document_list = serializers.ListField(required=False, label=_("document"))
|
||||||
audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio")))
|
audio_list = serializers.ListField(required=False, label=_("Audio"))
|
||||||
child_node = serializers.DictField(required=False, allow_null=True,
|
child_node = serializers.DictField(required=False, allow_null=True,
|
||||||
error_messages=ErrMessage.dict(_("Child Nodes")))
|
label=_("Child Nodes"))
|
||||||
node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data")))
|
node_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data"))
|
||||||
|
|
||||||
|
|
||||||
class IApplicationNode(INode):
|
class IApplicationNode(INode):
|
||||||
|
|
@ -75,7 +74,7 @@ class IApplicationNode(INode):
|
||||||
if 'file_id' not in audio:
|
if 'file_id' not in audio:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails."))
|
_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails."))
|
||||||
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,
|
return self.execute(**{**self.flow_params_serializer.data, **self.node_params_serializer.data},
|
||||||
app_document_list=app_document_list, app_image_list=app_image_list,
|
app_document_list=app_document_list, app_image_list=app_image_list,
|
||||||
app_audio_list=app_audio_list,
|
app_audio_list=app_audio_list,
|
||||||
message=str(question), **kwargs)
|
message=str(question), **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import re
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from application.flow.common import Answer
|
from application.flow.common import Answer
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.application_node.i_application_node import IApplicationNode
|
from application.flow.step_node.application_node.i_application_node import IApplicationNode
|
||||||
|
|
@ -171,16 +171,21 @@ class BaseApplicationNode(IApplicationNode):
|
||||||
if self.node_params.get('is_result', False):
|
if self.node_params.get('is_result', False):
|
||||||
self.answer_text = details.get('answer')
|
self.answer_text = details.get('answer')
|
||||||
|
|
||||||
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
|
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat,
|
||||||
|
chat_user_id,
|
||||||
|
chat_user_type,
|
||||||
app_document_list=None, app_image_list=None, app_audio_list=None, child_node=None, node_data=None,
|
app_document_list=None, app_image_list=None, app_audio_list=None, child_node=None, node_data=None,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
from application.serializers.chat_message_serializers import ChatMessageSerializer
|
from chat.serializers.chat import ChatSerializers
|
||||||
|
if application_id == self.workflow_manage.get_body().get('application_id'):
|
||||||
|
raise Exception(_("The sub application cannot use the current node"))
|
||||||
# 生成嵌入应用的chat_id
|
# 生成嵌入应用的chat_id
|
||||||
current_chat_id = string_to_uuid(chat_id + application_id)
|
current_chat_id = string_to_uuid(chat_id + application_id)
|
||||||
Chat.objects.get_or_create(id=current_chat_id, defaults={
|
Chat.objects.get_or_create(id=current_chat_id, defaults={
|
||||||
'application_id': application_id,
|
'application_id': application_id,
|
||||||
'abstract': message[0:1024],
|
'abstract': message[0:1024],
|
||||||
'client_id': client_id,
|
'chat_user_id': chat_user_id,
|
||||||
|
'chat_user_type': chat_user_type
|
||||||
})
|
})
|
||||||
if app_document_list is None:
|
if app_document_list is None:
|
||||||
app_document_list = []
|
app_document_list = []
|
||||||
|
|
@ -197,22 +202,26 @@ class BaseApplicationNode(IApplicationNode):
|
||||||
child_node_value = child_node.get('child_node')
|
child_node_value = child_node.get('child_node')
|
||||||
application_node_dict = self.context.get('application_node_dict')
|
application_node_dict = self.context.get('application_node_dict')
|
||||||
reset_application_node_dict(application_node_dict, runtime_node_id, node_data)
|
reset_application_node_dict(application_node_dict, runtime_node_id, node_data)
|
||||||
|
response = ChatSerializers(data={
|
||||||
|
"chat_id": current_chat_id,
|
||||||
|
"chat_user_id": chat_user_id,
|
||||||
|
'chat_user_type': chat_user_type,
|
||||||
|
'application_id': application_id,
|
||||||
|
'debug': False
|
||||||
|
}).chat(instance=
|
||||||
|
{'message': message,
|
||||||
|
're_chat': re_chat,
|
||||||
|
'stream': stream,
|
||||||
|
'document_list': app_document_list,
|
||||||
|
'image_list': app_image_list,
|
||||||
|
'audio_list': app_audio_list,
|
||||||
|
'runtime_node_id': runtime_node_id,
|
||||||
|
'chat_record_id': record_id,
|
||||||
|
'child_node': child_node_value,
|
||||||
|
'node_data': node_data,
|
||||||
|
'form_data': kwargs}
|
||||||
|
)
|
||||||
|
|
||||||
response = ChatMessageSerializer(
|
|
||||||
data={'chat_id': current_chat_id, 'message': message,
|
|
||||||
're_chat': re_chat,
|
|
||||||
'stream': stream,
|
|
||||||
'application_id': application_id,
|
|
||||||
'client_id': client_id,
|
|
||||||
'client_type': client_type,
|
|
||||||
'document_list': app_document_list,
|
|
||||||
'image_list': app_image_list,
|
|
||||||
'audio_list': app_audio_list,
|
|
||||||
'runtime_node_id': runtime_node_id,
|
|
||||||
'chat_record_id': record_id,
|
|
||||||
'child_node': child_node_value,
|
|
||||||
'node_data': node_data,
|
|
||||||
'form_data': kwargs}).chat()
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
if stream:
|
if stream:
|
||||||
content_generator = response.streaming_content
|
content_generator = response.streaming_content
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,18 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode
|
from application.flow.i_step_node import INode
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
|
|
||||||
class ConditionSerializer(serializers.Serializer):
|
class ConditionSerializer(serializers.Serializer):
|
||||||
compare = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Comparator")))
|
compare = serializers.CharField(required=True, label=_("Comparator"))
|
||||||
value = serializers.CharField(required=True, error_messages=ErrMessage.char(_("value")))
|
value = serializers.CharField(required=True, label=_("value"))
|
||||||
field = serializers.ListField(required=True, error_messages=ErrMessage.char(_("Fields")))
|
field = serializers.ListField(required=True, label=_("Fields"))
|
||||||
|
|
||||||
|
|
||||||
class ConditionBranchSerializer(serializers.Serializer):
|
class ConditionBranchSerializer(serializers.Serializer):
|
||||||
id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch id")))
|
id = serializers.CharField(required=True, label=_("Branch id"))
|
||||||
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch Type")))
|
type = serializers.CharField(required=True, label=_("Branch Type"))
|
||||||
condition = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Condition or|and")))
|
condition = serializers.CharField(required=True, label=_("Condition or|and"))
|
||||||
conditions = ConditionSerializer(many=True)
|
conditions = ConditionSerializer(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,17 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.exception.app_exception import AppApiException
|
from common.exception.app_exception import AppApiException
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ReplyNodeParamsSerializer(serializers.Serializer):
|
class ReplyNodeParamsSerializer(serializers.Serializer):
|
||||||
reply_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Response Type")))
|
reply_type = serializers.CharField(required=True, label=_("Response Type"))
|
||||||
fields = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Reference Field")))
|
fields = serializers.ListField(required=False, label=_("Reference Field"))
|
||||||
content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
error_messages=ErrMessage.char(_("Direct answer content")))
|
label=_("Direct answer content"))
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
is_result = serializers.BooleanField(required=False,
|
||||||
|
label=_('Whether to return content'))
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
def is_valid(self, *, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentExtractNodeSerializer(serializers.Serializer):
|
class DocumentExtractNodeSerializer(serializers.Serializer):
|
||||||
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document")))
|
document_list = serializers.ListField(required=False, label=_("document"))
|
||||||
|
|
||||||
|
|
||||||
class IDocumentExtractNode(INode):
|
class IDocumentExtractNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ from django.db.models import QuerySet
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
|
from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
|
||||||
from dataset.models import File
|
from knowledge.models import File, FileSourceType
|
||||||
from dataset.serializers.document_serializers import split_handles, parse_table_handle_list, FileBufferHandle
|
from knowledge.serializers.document import split_handles, parse_table_handle_list, FileBufferHandle
|
||||||
from dataset.serializers.file_serializers import FileSerializer
|
from oss.serializers.file import FileSerializer
|
||||||
|
|
||||||
|
|
||||||
def bytes_to_uploaded_file(file_bytes, file_name="file.txt"):
|
def bytes_to_uploaded_file(file_bytes, file_name="file.txt"):
|
||||||
|
|
@ -37,11 +37,11 @@ def bytes_to_uploaded_file(file_bytes, file_name="file.txt"):
|
||||||
|
|
||||||
splitter = '\n`-----------------------------------`\n'
|
splitter = '\n`-----------------------------------`\n'
|
||||||
|
|
||||||
|
|
||||||
class BaseDocumentExtractNode(IDocumentExtractNode):
|
class BaseDocumentExtractNode(IDocumentExtractNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
self.context['content'] = details.get('content')
|
self.context['content'] = details.get('content')
|
||||||
|
|
||||||
|
|
||||||
def execute(self, document, chat_id, **kwargs):
|
def execute(self, document, chat_id, **kwargs):
|
||||||
get_buffer = FileBufferHandle().get_buffer
|
get_buffer = FileBufferHandle().get_buffer
|
||||||
|
|
||||||
|
|
@ -61,12 +61,18 @@ class BaseDocumentExtractNode(IDocumentExtractNode):
|
||||||
'application_id': str(application.id) if application.id else None,
|
'application_id': str(application.id) if application.id else None,
|
||||||
'file_id': str(image.id)
|
'file_id': str(image.id)
|
||||||
}
|
}
|
||||||
file = bytes_to_uploaded_file(image.image, image.image_name)
|
file_bytes = image.meta.pop('content')
|
||||||
FileSerializer(data={'file': file, 'meta': meta}).upload()
|
f = bytes_to_uploaded_file(file_bytes, image.file_name)
|
||||||
|
FileSerializer(data={
|
||||||
|
'file': f,
|
||||||
|
'meta': meta,
|
||||||
|
'source_id': meta['application_id'],
|
||||||
|
'source_type': FileSourceType.APPLICATION.value
|
||||||
|
}).upload()
|
||||||
|
|
||||||
for doc in document:
|
for doc in document:
|
||||||
file = QuerySet(File).filter(id=doc['file_id']).first()
|
file = QuerySet(File).filter(id=doc['file_id']).first()
|
||||||
buffer = io.BytesIO(file.get_byte().tobytes())
|
buffer = io.BytesIO(file.get_bytes())
|
||||||
buffer.name = doc['name'] # this is the important line
|
buffer.name = doc['name'] # this is the important line
|
||||||
|
|
||||||
for split_handle in (parse_table_handle_list + split_handles):
|
for split_handle in (parse_table_handle_list + split_handles):
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@ from typing import Type
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class FormNodeParamsSerializer(serializers.Serializer):
|
class FormNodeParamsSerializer(serializers.Serializer):
|
||||||
form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Form Configuration")))
|
form_field_list = serializers.ListField(required=True, label=_("Form Configuration"))
|
||||||
form_content_format = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Form output content')))
|
form_content_format = serializers.CharField(required=True, label=_('Form output content'))
|
||||||
form_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data")))
|
form_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data"))
|
||||||
|
|
||||||
|
|
||||||
class IFormNode(INode):
|
class IFormNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,31 @@
|
||||||
|
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class ImageGenerateNodeSerializer(serializers.Serializer):
|
class ImageGenerateNodeSerializer(serializers.Serializer):
|
||||||
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
model_id = serializers.CharField(required=True, label=_("Model id"))
|
||||||
|
|
||||||
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word (positive)")))
|
prompt = serializers.CharField(required=True, label=_("Prompt word (positive)"))
|
||||||
|
|
||||||
negative_prompt = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Prompt word (negative)")),
|
negative_prompt = serializers.CharField(required=False, label=_("Prompt word (negative)"),
|
||||||
allow_null=True, allow_blank=True, )
|
allow_null=True, allow_blank=True, )
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=False, default=0,
|
dialogue_number = serializers.IntegerField(required=False, default=0,
|
||||||
error_messages=ErrMessage.integer(_("Number of multi-round conversations")))
|
label=_("Number of multi-round conversations"))
|
||||||
|
|
||||||
dialogue_type = serializers.CharField(required=False, default='NODE',
|
dialogue_type = serializers.CharField(required=False, default='NODE',
|
||||||
error_messages=ErrMessage.char(_("Conversation storage type")))
|
label=_("Conversation storage type"))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
is_result = serializers.BooleanField(required=False,
|
||||||
|
label=_('Whether to return content'))
|
||||||
|
|
||||||
model_params_setting = serializers.JSONField(required=False, default=dict,
|
model_params_setting = serializers.JSONField(required=False, default=dict,
|
||||||
error_messages=ErrMessage.json(_("Model parameter settings")))
|
label=_("Model parameter settings"))
|
||||||
|
|
||||||
|
|
||||||
class IImageGenerateNode(INode):
|
class IImageGenerateNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode
|
from application.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode
|
||||||
from common.util.common import bytes_to_uploaded_file
|
from common.utils.common import bytes_to_uploaded_file
|
||||||
from dataset.serializers.file_serializers import FileSerializer
|
from knowledge.models import FileSourceType
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from oss.serializers.file import FileSerializer
|
||||||
|
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||||
|
|
||||||
|
|
||||||
class BaseImageGenerateNode(IImageGenerateNode):
|
class BaseImageGenerateNode(IImageGenerateNode):
|
||||||
|
|
@ -23,10 +24,10 @@ class BaseImageGenerateNode(IImageGenerateNode):
|
||||||
model_params_setting,
|
model_params_setting,
|
||||||
chat_record_id,
|
chat_record_id,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
print(model_params_setting)
|
|
||||||
application = self.workflow_manage.work_flow_post_handler.chat_info.application
|
application = self.workflow_manage.work_flow_post_handler.chat_info.application
|
||||||
tti_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'),
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
**model_params_setting)
|
tti_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
|
||||||
|
**model_params_setting)
|
||||||
history_message = self.get_history_message(history_chat_record, dialogue_number)
|
history_message = self.get_history_message(history_chat_record, dialogue_number)
|
||||||
self.context['history_message'] = history_message
|
self.context['history_message'] = history_message
|
||||||
question = self.generate_prompt_question(prompt)
|
question = self.generate_prompt_question(prompt)
|
||||||
|
|
@ -34,19 +35,26 @@ class BaseImageGenerateNode(IImageGenerateNode):
|
||||||
message_list = self.generate_message_list(question, history_message)
|
message_list = self.generate_message_list(question, history_message)
|
||||||
self.context['message_list'] = message_list
|
self.context['message_list'] = message_list
|
||||||
self.context['dialogue_type'] = dialogue_type
|
self.context['dialogue_type'] = dialogue_type
|
||||||
print(message_list)
|
self.context['negative_prompt'] = negative_prompt
|
||||||
image_urls = tti_model.generate_image(question, negative_prompt)
|
image_urls = tti_model.generate_image(question, negative_prompt)
|
||||||
# 保存图片
|
# 保存图片
|
||||||
file_urls = []
|
file_urls = []
|
||||||
for image_url in image_urls:
|
for image_url in image_urls:
|
||||||
file_name = 'generated_image.png'
|
file_name = 'generated_image.png'
|
||||||
file = bytes_to_uploaded_file(requests.get(image_url).content, file_name)
|
if isinstance(image_url, str) and image_url.startswith('http'):
|
||||||
|
image_url = requests.get(image_url).content
|
||||||
|
file = bytes_to_uploaded_file(image_url, file_name)
|
||||||
meta = {
|
meta = {
|
||||||
'debug': False if application.id else True,
|
'debug': False if application.id else True,
|
||||||
'chat_id': chat_id,
|
'chat_id': chat_id,
|
||||||
'application_id': str(application.id) if application.id else None,
|
'application_id': str(application.id) if application.id else None,
|
||||||
}
|
}
|
||||||
file_url = FileSerializer(data={'file': file, 'meta': meta}).upload()
|
file_url = FileSerializer(data={
|
||||||
|
'file': file,
|
||||||
|
'meta': meta,
|
||||||
|
'source_id': meta['application_id'],
|
||||||
|
'source_type': FileSourceType.APPLICATION.value
|
||||||
|
}).upload()
|
||||||
file_urls.append(file_url)
|
file_urls.append(file_url)
|
||||||
self.context['image_list'] = [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls]
|
self.context['image_list'] = [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls]
|
||||||
answer = ' '.join([f"" for path in file_urls])
|
answer = ' '.join([f"" for path in file_urls])
|
||||||
|
|
@ -118,5 +126,6 @@ class BaseImageGenerateNode(IImageGenerateNode):
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'err_message': self.err_message,
|
'err_message': self.err_message,
|
||||||
'image_list': self.context.get('image_list'),
|
'image_list': self.context.get('image_list'),
|
||||||
'dialogue_type': self.context.get('dialogue_type')
|
'dialogue_type': self.context.get('dialogue_type'),
|
||||||
|
'negative_prompt': self.context.get('negative_prompt'),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,27 @@ from typing import Type
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ImageUnderstandNodeSerializer(serializers.Serializer):
|
class ImageUnderstandNodeSerializer(serializers.Serializer):
|
||||||
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
model_id = serializers.CharField(required=True, label=_("Model id"))
|
||||||
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
error_messages=ErrMessage.char(_("Role Setting")))
|
label=_("Role Setting"))
|
||||||
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
|
prompt = serializers.CharField(required=True, label=_("Prompt word"))
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations")))
|
dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations"))
|
||||||
|
|
||||||
dialogue_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Conversation storage type")))
|
dialogue_type = serializers.CharField(required=True, label=_("Conversation storage type"))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
is_result = serializers.BooleanField(required=False,
|
||||||
|
label=_('Whether to return content'))
|
||||||
|
|
||||||
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture")))
|
image_list = serializers.ListField(required=False, label=_("picture"))
|
||||||
|
|
||||||
model_params_setting = serializers.JSONField(required=False, default=dict,
|
model_params_setting = serializers.JSONField(required=False, default=dict,
|
||||||
error_messages=ErrMessage.json(_("Model parameter settings")))
|
label=_("Model parameter settings"))
|
||||||
|
|
||||||
|
|
||||||
class IImageUnderstandNode(INode):
|
class IImageUnderstandNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import base64
|
import base64
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from imghdr import what
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
@ -10,9 +10,8 @@ from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AI
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
|
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
|
||||||
from dataset.models import File
|
from knowledge.models import File
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||||
from imghdr import what
|
|
||||||
|
|
||||||
|
|
||||||
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
|
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
|
||||||
|
|
@ -60,9 +59,9 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
|
||||||
|
|
||||||
def file_id_to_base64(file_id: str):
|
def file_id_to_base64(file_id: str):
|
||||||
file = QuerySet(File).filter(id=file_id).first()
|
file = QuerySet(File).filter(id=file_id).first()
|
||||||
file_bytes = file.get_byte()
|
file_bytes = file.get_bytes()
|
||||||
base64_image = base64.b64encode(file_bytes).decode("utf-8")
|
base64_image = base64.b64encode(file_bytes).decode("utf-8")
|
||||||
return [base64_image, what(None, file_bytes.tobytes())]
|
return [base64_image, what(None, file_bytes)]
|
||||||
|
|
||||||
|
|
||||||
class BaseImageUnderstandNode(IImageUnderstandNode):
|
class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
|
|
@ -80,8 +79,9 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
# 处理不正确的参数
|
# 处理不正确的参数
|
||||||
if image is None or not isinstance(image, list):
|
if image is None or not isinstance(image, list):
|
||||||
image = []
|
image = []
|
||||||
print(model_params_setting)
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
image_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'), **model_params_setting)
|
image_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
|
||||||
|
**model_params_setting)
|
||||||
# 执行详情中的历史消息不需要图片内容
|
# 执行详情中的历史消息不需要图片内容
|
||||||
history_message = self.get_history_message_for_details(history_chat_record, dialogue_number)
|
history_message = self.get_history_message_for_details(history_chat_record, dialogue_number)
|
||||||
self.context['history_message'] = history_message
|
self.context['history_message'] = history_message
|
||||||
|
|
@ -130,7 +130,7 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
file_id_list = [image.get('file_id') for image in image_list]
|
file_id_list = [image.get('file_id') for image in image_list]
|
||||||
return HumanMessage(content=[
|
return HumanMessage(content=[
|
||||||
{'type': 'text', 'text': data['question']},
|
{'type': 'text', 'text': data['question']},
|
||||||
*[{'type': 'image_url', 'image_url': {'url': f'/api/file/{file_id}'}} for file_id in file_id_list]
|
*[{'type': 'image_url', 'image_url': {'url': f'./oss/file/{file_id}'}} for file_id in file_id_list]
|
||||||
|
|
||||||
])
|
])
|
||||||
return HumanMessage(content=chat_record.problem_text)
|
return HumanMessage(content=chat_record.problem_text)
|
||||||
|
|
@ -155,7 +155,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
return HumanMessage(
|
return HumanMessage(
|
||||||
content=[
|
content=[
|
||||||
{'type': 'text', 'text': data['question']},
|
{'type': 'text', 'text': data['question']},
|
||||||
*[{'type': 'image_url', 'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for
|
*[{'type': 'image_url',
|
||||||
|
'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for
|
||||||
base64_image in image_base64_list]
|
base64_image in image_base64_list]
|
||||||
])
|
])
|
||||||
return HumanMessage(content=chat_record.problem_text)
|
return HumanMessage(content=chat_record.problem_text)
|
||||||
|
|
@ -170,10 +171,11 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
for img in image:
|
for img in image:
|
||||||
file_id = img['file_id']
|
file_id = img['file_id']
|
||||||
file = QuerySet(File).filter(id=file_id).first()
|
file = QuerySet(File).filter(id=file_id).first()
|
||||||
image_bytes = file.get_byte()
|
image_bytes = file.get_bytes()
|
||||||
base64_image = base64.b64encode(image_bytes).decode("utf-8")
|
base64_image = base64.b64encode(image_bytes).decode("utf-8")
|
||||||
image_format = what(None, image_bytes.tobytes())
|
image_format = what(None, image_bytes)
|
||||||
images.append({'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}})
|
images.append(
|
||||||
|
{'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}})
|
||||||
messages = [HumanMessage(
|
messages = [HumanMessage(
|
||||||
content=[
|
content=[
|
||||||
{'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)},
|
{'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)},
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,23 @@
|
||||||
|
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class McpNodeSerializer(serializers.Serializer):
|
class McpNodeSerializer(serializers.Serializer):
|
||||||
mcp_servers = serializers.JSONField(required=True,
|
mcp_servers = serializers.JSONField(required=True,
|
||||||
error_messages=ErrMessage.char(_("Mcp servers")))
|
label=_("Mcp servers"))
|
||||||
|
|
||||||
mcp_server = serializers.CharField(required=True,
|
mcp_server = serializers.CharField(required=True,
|
||||||
error_messages=ErrMessage.char(_("Mcp server")))
|
label=_("Mcp server"))
|
||||||
|
|
||||||
mcp_tool = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Mcp tool")))
|
mcp_tool = serializers.CharField(required=True, label=_("Mcp tool"))
|
||||||
|
|
||||||
tool_params = serializers.DictField(required=True,
|
tool_params = serializers.DictField(required=True,
|
||||||
error_messages=ErrMessage.char(_("Tool parameters")))
|
label=_("Tool parameters"))
|
||||||
|
|
||||||
|
|
||||||
class IMcpNode(INode):
|
class IMcpNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -14,20 +14,18 @@ class BaseMcpNode(IMcpNode):
|
||||||
self.context['result'] = details.get('result')
|
self.context['result'] = details.get('result')
|
||||||
self.context['tool_params'] = details.get('tool_params')
|
self.context['tool_params'] = details.get('tool_params')
|
||||||
self.context['mcp_tool'] = details.get('mcp_tool')
|
self.context['mcp_tool'] = details.get('mcp_tool')
|
||||||
if self.node_params.get('is_result', False):
|
|
||||||
self.answer_text = details.get('result')
|
|
||||||
|
|
||||||
def execute(self, mcp_servers, mcp_server, mcp_tool, tool_params, **kwargs) -> NodeResult:
|
def execute(self, mcp_servers, mcp_server, mcp_tool, tool_params, **kwargs) -> NodeResult:
|
||||||
servers = json.loads(mcp_servers)
|
servers = json.loads(mcp_servers)
|
||||||
params = json.loads(json.dumps(tool_params))
|
params = json.loads(json.dumps(tool_params))
|
||||||
params = self.handle_variables(params)
|
params = self.handle_variables(params)
|
||||||
|
|
||||||
async def call_tool(s, session, t, a):
|
async def call_tool(t, a):
|
||||||
async with MultiServerMCPClient(s) as client:
|
client = MultiServerMCPClient(servers)
|
||||||
s = await client.sessions[session].call_tool(t, a)
|
async with client.session(mcp_server) as s:
|
||||||
return s
|
return await s.call_tool(t, a)
|
||||||
|
|
||||||
res = asyncio.run(call_tool(servers, mcp_server, mcp_tool, params))
|
res = asyncio.run(call_tool(mcp_tool, params))
|
||||||
return NodeResult(
|
return NodeResult(
|
||||||
{'result': [content.text for content in res.content], 'tool_params': params, 'mcp_tool': mcp_tool}, {})
|
{'result': [content.text for content in res.content], 'tool_params': params, 'mcp_tool': mcp_tool}, {})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,23 +8,25 @@
|
||||||
"""
|
"""
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class QuestionNodeSerializer(serializers.Serializer):
|
class QuestionNodeSerializer(serializers.Serializer):
|
||||||
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
model_id = serializers.CharField(required=True, label=_("Model id"))
|
||||||
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
error_messages=ErrMessage.char(_("Role Setting")))
|
label=_("Role Setting"))
|
||||||
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
|
prompt = serializers.CharField(required=True, label=_("Prompt word"))
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations")))
|
dialogue_number = serializers.IntegerField(required=True, label=
|
||||||
|
_("Number of multi-round conversations"))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
is_result = serializers.BooleanField(required=False,
|
||||||
model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.integer(_("Model parameter settings")))
|
label=_('Whether to return content'))
|
||||||
|
model_params_setting = serializers.DictField(required=False,
|
||||||
|
label=_("Model parameter settings"))
|
||||||
|
|
||||||
|
|
||||||
class IQuestionNode(INode):
|
class IQuestionNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,8 @@ from langchain_core.messages import BaseMessage
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.question_node.i_question_node import IQuestionNode
|
from application.flow.step_node.question_node.i_question_node import IQuestionNode
|
||||||
from setting.models import Model
|
from models_provider.models import Model
|
||||||
from setting.models_provider import get_model_credential
|
from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
|
||||||
|
|
||||||
|
|
||||||
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
|
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
|
||||||
|
|
@ -88,8 +87,9 @@ class BaseQuestionNode(IQuestionNode):
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
if model_params_setting is None:
|
if model_params_setting is None:
|
||||||
model_params_setting = get_default_model_params_setting(model_id)
|
model_params_setting = get_default_model_params_setting(model_id)
|
||||||
chat_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'),
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
**model_params_setting)
|
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
|
||||||
|
**model_params_setting)
|
||||||
history_message = self.get_history_message(history_chat_record, dialogue_number)
|
history_message = self.get_history_message(history_chat_record, dialogue_number)
|
||||||
self.context['history_message'] = history_message
|
self.context['history_message'] = history_message
|
||||||
question = self.generate_prompt_question(prompt)
|
question = self.generate_prompt_question(prompt)
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@ from typing import Type
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class RerankerSettingSerializer(serializers.Serializer):
|
class RerankerSettingSerializer(serializers.Serializer):
|
||||||
# 需要查询的条数
|
# 需要查询的条数
|
||||||
top_n = serializers.IntegerField(required=True,
|
top_n = serializers.IntegerField(required=True,
|
||||||
error_messages=ErrMessage.integer(_("Reference segment number")))
|
label=_("Reference segment number"))
|
||||||
# 相似度 0-1之间
|
# 相似度 0-1之间
|
||||||
similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
|
similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
|
||||||
error_messages=ErrMessage.float(_("Reference segment number")))
|
label=_("Reference segment number"))
|
||||||
max_paragraph_char_number = serializers.IntegerField(required=True,
|
max_paragraph_char_number = serializers.IntegerField(required=True,
|
||||||
error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment")))
|
label=_("Maximum number of words in a quoted segment"))
|
||||||
|
|
||||||
|
|
||||||
class RerankerStepNodeSerializer(serializers.Serializer):
|
class RerankerStepNodeSerializer(serializers.Serializer):
|
||||||
|
|
@ -32,6 +32,8 @@ class RerankerStepNodeSerializer(serializers.Serializer):
|
||||||
question_reference_address = serializers.ListField(required=True)
|
question_reference_address = serializers.ListField(required=True)
|
||||||
reranker_model_id = serializers.UUIDField(required=True)
|
reranker_model_id = serializers.UUIDField(required=True)
|
||||||
reranker_reference_list = serializers.ListField(required=True, child=serializers.ListField(required=True))
|
reranker_reference_list = serializers.ListField(required=True, child=serializers.ListField(required=True))
|
||||||
|
show_knowledge = serializers.BooleanField(required=True,
|
||||||
|
label=_("The results are displayed in the knowledge sources"))
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
def is_valid(self, *, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
|
|
@ -55,6 +57,6 @@ class IRerankerNode(INode):
|
||||||
|
|
||||||
reranker_list=reranker_list)
|
reranker_list=reranker_list)
|
||||||
|
|
||||||
def execute(self, question, reranker_setting, reranker_list, reranker_model_id,
|
def execute(self, question, reranker_setting, reranker_list, reranker_model_id,show_knowledge,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from langchain_core.documents import Document
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.reranker_node.i_reranker_node import IRerankerNode
|
from application.flow.step_node.reranker_node.i_reranker_node import IRerankerNode
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||||
|
|
||||||
|
|
||||||
def merge_reranker_list(reranker_list, result=None):
|
def merge_reranker_list(reranker_list, result=None):
|
||||||
|
|
@ -24,11 +24,9 @@ def merge_reranker_list(reranker_list, result=None):
|
||||||
elif isinstance(document, dict):
|
elif isinstance(document, dict):
|
||||||
content = document.get('title', '') + document.get('content', '')
|
content = document.get('title', '') + document.get('content', '')
|
||||||
title = document.get("title")
|
title = document.get("title")
|
||||||
dataset_name = document.get("dataset_name")
|
|
||||||
document_name = document.get('document_name')
|
|
||||||
result.append(
|
result.append(
|
||||||
Document(page_content=str(document) if len(content) == 0 else content,
|
Document(page_content=str(document) if len(content) == 0 else content,
|
||||||
metadata={'title': title, 'dataset_name': dataset_name, 'document_name': document_name}))
|
metadata={'title': title, **document}))
|
||||||
else:
|
else:
|
||||||
result.append(Document(page_content=str(document), metadata={}))
|
result.append(Document(page_content=str(document), metadata={}))
|
||||||
return result
|
return result
|
||||||
|
|
@ -63,6 +61,12 @@ def reset_result_list(result_list: List[Document], document_list: List[Document]
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def get_none_result(question):
|
||||||
|
return NodeResult(
|
||||||
|
{'document_list': [], 'question': question,
|
||||||
|
'result_list': [], 'result': ''}, {})
|
||||||
|
|
||||||
|
|
||||||
class BaseRerankerNode(IRerankerNode):
|
class BaseRerankerNode(IRerankerNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
self.context['document_list'] = details.get('document_list', [])
|
self.context['document_list'] = details.get('document_list', [])
|
||||||
|
|
@ -71,16 +75,21 @@ class BaseRerankerNode(IRerankerNode):
|
||||||
self.context['result_list'] = details.get('result_list')
|
self.context['result_list'] = details.get('result_list')
|
||||||
self.context['result'] = details.get('result')
|
self.context['result'] = details.get('result')
|
||||||
|
|
||||||
def execute(self, question, reranker_setting, reranker_list, reranker_model_id,
|
def execute(self, question, reranker_setting, reranker_list, reranker_model_id, show_knowledge,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
|
self.context['show_knowledge'] = show_knowledge
|
||||||
documents = merge_reranker_list(reranker_list)
|
documents = merge_reranker_list(reranker_list)
|
||||||
|
documents = [d for d in documents if d.page_content and len(d.page_content) > 0]
|
||||||
|
if len(documents) == 0:
|
||||||
|
return get_none_result(question)
|
||||||
top_n = reranker_setting.get('top_n', 3)
|
top_n = reranker_setting.get('top_n', 3)
|
||||||
self.context['document_list'] = [{'page_content': document.page_content, 'metadata': document.metadata} for
|
self.context['document_list'] = [{'page_content': document.page_content, 'metadata': document.metadata} for
|
||||||
document in documents]
|
document in documents]
|
||||||
self.context['question'] = question
|
self.context['question'] = question
|
||||||
reranker_model = get_model_instance_by_model_user_id(reranker_model_id,
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
self.flow_params_serializer.data.get('user_id'),
|
reranker_model = get_model_instance_by_model_workspace_id(reranker_model_id,
|
||||||
top_n=top_n)
|
workspace_id,
|
||||||
|
top_n=top_n)
|
||||||
result = reranker_model.compress_documents(
|
result = reranker_model.compress_documents(
|
||||||
documents,
|
documents,
|
||||||
question)
|
question)
|
||||||
|
|
@ -92,6 +101,7 @@ class BaseRerankerNode(IRerankerNode):
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
return {
|
return {
|
||||||
|
'show_knowledge': self.context.get('show_knowledge'),
|
||||||
'name': self.node.properties.get('stepName'),
|
'name': self.node.properties.get('stepName'),
|
||||||
"index": index,
|
"index": index,
|
||||||
'document_list': self.context.get('document_list'),
|
'document_list': self.context.get('document_list'),
|
||||||
|
|
|
||||||
|
|
@ -13,34 +13,37 @@ from django.core import validators
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.common import flat_map
|
from common.utils.common import flat_map
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class DatasetSettingSerializer(serializers.Serializer):
|
class DatasetSettingSerializer(serializers.Serializer):
|
||||||
# 需要查询的条数
|
# 需要查询的条数
|
||||||
top_n = serializers.IntegerField(required=True,
|
top_n = serializers.IntegerField(required=True,
|
||||||
error_messages=ErrMessage.integer(_("Reference segment number")))
|
label=_("Reference segment number"))
|
||||||
# 相似度 0-1之间
|
# 相似度 0-1之间
|
||||||
similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
|
similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
|
||||||
error_messages=ErrMessage.float(_('similarity')))
|
label=_('similarity'))
|
||||||
search_mode = serializers.CharField(required=True, validators=[
|
search_mode = serializers.CharField(required=True, validators=[
|
||||||
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
|
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
|
||||||
message=_("The type only supports embedding|keywords|blend"), code=500)
|
message=_("The type only supports embedding|keywords|blend"), code=500)
|
||||||
], error_messages=ErrMessage.char(_("Retrieval Mode")))
|
], label=_("Retrieval Mode"))
|
||||||
max_paragraph_char_number = serializers.IntegerField(required=True,
|
max_paragraph_char_number = serializers.IntegerField(required=True,
|
||||||
error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment")))
|
label=_("Maximum number of words in a quoted segment"))
|
||||||
|
|
||||||
|
|
||||||
class SearchDatasetStepNodeSerializer(serializers.Serializer):
|
class SearchDatasetStepNodeSerializer(serializers.Serializer):
|
||||||
# 需要查询的数据集id列表
|
# 需要查询的数据集id列表
|
||||||
dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
knowledge_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
||||||
error_messages=ErrMessage.list(_("Dataset id list")))
|
label=_("Dataset id list"))
|
||||||
dataset_setting = DatasetSettingSerializer(required=True)
|
knowledge_setting = DatasetSettingSerializer(required=True)
|
||||||
|
|
||||||
question_reference_address = serializers.ListField(required=True)
|
question_reference_address = serializers.ListField(required=True)
|
||||||
|
|
||||||
|
show_knowledge = serializers.BooleanField(required=True,
|
||||||
|
label=_("The results are displayed in the knowledge sources"))
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
def is_valid(self, *, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
|
@ -52,8 +55,8 @@ def get_paragraph_list(chat_record, node_id):
|
||||||
'paragraph_list', []) is not None and key == node_id])
|
'paragraph_list', []) is not None and key == node_id])
|
||||||
|
|
||||||
|
|
||||||
class ISearchDatasetStepNode(INode):
|
class ISearchKnowledgeStepNode(INode):
|
||||||
type = 'search-dataset-node'
|
type = 'search-knowledge-node'
|
||||||
|
|
||||||
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
||||||
return SearchDatasetStepNodeSerializer
|
return SearchDatasetStepNodeSerializer
|
||||||
|
|
@ -73,7 +76,7 @@ class ISearchDatasetStepNode(INode):
|
||||||
return self.execute(**self.node_params_serializer.data, question=str(question),
|
return self.execute(**self.node_params_serializer.data, question=str(question),
|
||||||
exclude_paragraph_id_list=exclude_paragraph_id_list)
|
exclude_paragraph_id_list=exclude_paragraph_id_list)
|
||||||
|
|
||||||
def execute(self, dataset_id_list, dataset_setting, question,
|
def execute(self, dataset_id_list, dataset_setting, question, show_knowledge,
|
||||||
exclude_paragraph_id_list=None,
|
exclude_paragraph_id_list=None,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
@date:2024/6/11 15:35
|
@date:2024/6/11 15:35
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .base_search_dataset_node import BaseSearchDatasetNode
|
from .base_search_knowledge_node import BaseSearchKnowledgeNode
|
||||||
|
|
@ -9,26 +9,28 @@
|
||||||
import os
|
import os
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.search_dataset_node.i_search_dataset_node import ISearchDatasetStepNode
|
from application.flow.step_node.search_knowledge_node.i_search_knowledge_node import ISearchKnowledgeStepNode
|
||||||
from common.config.embedding_config import VectorStore
|
from common.config.embedding_config import VectorStore
|
||||||
|
from common.constants.permission_constants import RoleConstants
|
||||||
|
from common.database_model_manage.database_model_manage import DatabaseModelManage
|
||||||
from common.db.search import native_search
|
from common.db.search import native_search
|
||||||
from common.util.file_util import get_file_content
|
from common.utils.common import get_file_content
|
||||||
from dataset.models import Document, Paragraph, DataSet
|
from knowledge.models import Document, Paragraph, Knowledge, SearchMode
|
||||||
from embedding.models import SearchMode
|
from maxkb.conf import PROJECT_DIR
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||||
from smartdoc.conf import PROJECT_DIR
|
|
||||||
|
|
||||||
|
|
||||||
def get_embedding_id(dataset_id_list):
|
def get_embedding_id(dataset_id_list):
|
||||||
dataset_list = QuerySet(DataSet).filter(id__in=dataset_id_list)
|
dataset_list = QuerySet(Knowledge).filter(id__in=dataset_id_list)
|
||||||
if len(set([dataset.embedding_mode_id for dataset in dataset_list])) > 1:
|
if len(set([dataset.embedding_model_id for dataset in dataset_list])) > 1:
|
||||||
raise Exception("关联知识库的向量模型不一致,无法召回分段。")
|
raise Exception("关联知识库的向量模型不一致,无法召回分段。")
|
||||||
if len(dataset_list) == 0:
|
if len(dataset_list) == 0:
|
||||||
raise Exception("知识库设置错误,请重新设置知识库")
|
raise Exception("知识库设置错误,请重新设置知识库")
|
||||||
return dataset_list[0].embedding_mode_id
|
return dataset_list[0].embedding_model_id
|
||||||
|
|
||||||
|
|
||||||
def get_none_result(question):
|
def get_none_result(question):
|
||||||
|
|
@ -44,10 +46,10 @@ def reset_title(title):
|
||||||
return f"#### {title}\n"
|
return f"#### {title}\n"
|
||||||
|
|
||||||
|
|
||||||
class BaseSearchDatasetNode(ISearchDatasetStepNode):
|
class BaseSearchKnowledgeNode(ISearchKnowledgeStepNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
result = details.get('paragraph_list', [])
|
result = details.get('paragraph_list', [])
|
||||||
dataset_setting = self.node_params_serializer.data.get('dataset_setting')
|
knowledge_setting = self.node_params_serializer.data.get('knowledge_setting')
|
||||||
directly_return = '\n'.join(
|
directly_return = '\n'.join(
|
||||||
[f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in result if
|
[f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in result if
|
||||||
paragraph.get('is_hit_handling_method')])
|
paragraph.get('is_hit_handling_method')])
|
||||||
|
|
@ -57,26 +59,34 @@ class BaseSearchDatasetNode(ISearchDatasetStepNode):
|
||||||
self.context['is_hit_handling_method_list'] = [row for row in result if row.get('is_hit_handling_method')]
|
self.context['is_hit_handling_method_list'] = [row for row in result if row.get('is_hit_handling_method')]
|
||||||
self.context['data'] = '\n'.join(
|
self.context['data'] = '\n'.join(
|
||||||
[f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in
|
[f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in
|
||||||
result])[0:dataset_setting.get('max_paragraph_char_number', 5000)]
|
result])[0:knowledge_setting.get('max_paragraph_char_number', 5000)]
|
||||||
self.context['directly_return'] = directly_return
|
self.context['directly_return'] = directly_return
|
||||||
|
|
||||||
def execute(self, dataset_id_list, dataset_setting, question,
|
def execute(self, knowledge_id_list, knowledge_setting, question, show_knowledge,
|
||||||
exclude_paragraph_id_list=None,
|
exclude_paragraph_id_list=None,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
self.context['question'] = question
|
self.context['question'] = question
|
||||||
if len(dataset_id_list) == 0:
|
self.context['show_knowledge'] = show_knowledge
|
||||||
|
get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized')
|
||||||
|
chat_user_type = self.workflow_manage.get_body().get('chat_user_type')
|
||||||
|
if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type:
|
||||||
|
knowledge_id_list = get_knowledge_list_of_authorized(self.workflow_manage.get_body().get('chat_user_id'),
|
||||||
|
knowledge_id_list)
|
||||||
|
if len(knowledge_id_list) == 0:
|
||||||
return get_none_result(question)
|
return get_none_result(question)
|
||||||
model_id = get_embedding_id(dataset_id_list)
|
model_id = get_embedding_id(knowledge_id_list)
|
||||||
embedding_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'))
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
|
embedding_model = get_model_instance_by_model_workspace_id(model_id, workspace_id)
|
||||||
embedding_value = embedding_model.embed_query(question)
|
embedding_value = embedding_model.embed_query(question)
|
||||||
vector = VectorStore.get_embedding_vector()
|
vector = VectorStore.get_embedding_vector()
|
||||||
exclude_document_id_list = [str(document.id) for document in
|
exclude_document_id_list = [str(document.id) for document in
|
||||||
QuerySet(Document).filter(
|
QuerySet(Document).filter(
|
||||||
dataset_id__in=dataset_id_list,
|
knowledge_id__in=knowledge_id_list,
|
||||||
is_active=False)]
|
is_active=False)]
|
||||||
embedding_list = vector.query(question, embedding_value, dataset_id_list, exclude_document_id_list,
|
embedding_list = vector.query(question, embedding_value, knowledge_id_list, exclude_document_id_list,
|
||||||
exclude_paragraph_id_list, True, dataset_setting.get('top_n'),
|
exclude_paragraph_id_list, True, knowledge_setting.get('top_n'),
|
||||||
dataset_setting.get('similarity'), SearchMode(dataset_setting.get('search_mode')))
|
knowledge_setting.get('similarity'),
|
||||||
|
SearchMode(knowledge_setting.get('search_mode')))
|
||||||
# 手动关闭数据库连接
|
# 手动关闭数据库连接
|
||||||
connection.close()
|
connection.close()
|
||||||
if embedding_list is None:
|
if embedding_list is None:
|
||||||
|
|
@ -88,7 +98,7 @@ class BaseSearchDatasetNode(ISearchDatasetStepNode):
|
||||||
'is_hit_handling_method_list': [row for row in result if row.get('is_hit_handling_method')],
|
'is_hit_handling_method_list': [row for row in result if row.get('is_hit_handling_method')],
|
||||||
'data': '\n'.join(
|
'data': '\n'.join(
|
||||||
[f"{reset_title(paragraph.get('title', ''))}{paragraph.get('content')}" for paragraph in
|
[f"{reset_title(paragraph.get('title', ''))}{paragraph.get('content')}" for paragraph in
|
||||||
result])[0:dataset_setting.get('max_paragraph_char_number', 5000)],
|
result])[0:knowledge_setting.get('max_paragraph_char_number', 5000)],
|
||||||
'directly_return': '\n'.join(
|
'directly_return': '\n'.join(
|
||||||
[paragraph.get('content') for paragraph in
|
[paragraph.get('content') for paragraph in
|
||||||
result if
|
result if
|
||||||
|
|
@ -111,7 +121,7 @@ class BaseSearchDatasetNode(ISearchDatasetStepNode):
|
||||||
'update_time': paragraph.get('update_time').strftime("%Y-%m-%d %H:%M:%S"),
|
'update_time': paragraph.get('update_time').strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
'create_time': paragraph.get('create_time').strftime("%Y-%m-%d %H:%M:%S"),
|
'create_time': paragraph.get('create_time').strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
'id': str(paragraph.get('id')),
|
'id': str(paragraph.get('id')),
|
||||||
'dataset_id': str(paragraph.get('dataset_id')),
|
'knowledge_id': str(paragraph.get('knowledge_id')),
|
||||||
'document_id': str(paragraph.get('document_id'))
|
'document_id': str(paragraph.get('document_id'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +133,7 @@ class BaseSearchDatasetNode(ISearchDatasetStepNode):
|
||||||
paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),
|
paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),
|
||||||
get_file_content(
|
get_file_content(
|
||||||
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
||||||
'list_dataset_paragraph_by_paragraph_id.sql')),
|
'list_knowledge_paragraph_by_paragraph_id.sql')),
|
||||||
with_table_name=True)
|
with_table_name=True)
|
||||||
# 如果向量库中存在脏数据 直接删除
|
# 如果向量库中存在脏数据 直接删除
|
||||||
if len(paragraph_list) != len(paragraph_id_list):
|
if len(paragraph_list) != len(paragraph_id_list):
|
||||||
|
|
@ -136,6 +146,7 @@ class BaseSearchDatasetNode(ISearchDatasetStepNode):
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
return {
|
return {
|
||||||
'name': self.node.properties.get('stepName'),
|
'name': self.node.properties.get('stepName'),
|
||||||
|
'show_knowledge': self.context.get('show_knowledge'),
|
||||||
'question': self.context.get('question'),
|
'question': self.context.get('question'),
|
||||||
"index": index,
|
"index": index,
|
||||||
'run_time': self.context.get('run_time'),
|
'run_time': self.context.get('run_time'),
|
||||||
|
|
@ -5,16 +5,17 @@ from typing import Type
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class SpeechToTextNodeSerializer(serializers.Serializer):
|
class SpeechToTextNodeSerializer(serializers.Serializer):
|
||||||
stt_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
stt_model_id = serializers.CharField(required=True, label=_("Model id"))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
is_result = serializers.BooleanField(required=False,
|
||||||
|
label=_('Whether to return content'))
|
||||||
|
|
||||||
audio_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("The audio file cannot be empty")))
|
audio_list = serializers.ListField(required=True,
|
||||||
|
label=_("The audio file cannot be empty"))
|
||||||
|
|
||||||
|
|
||||||
class ISpeechToTextNode(INode):
|
class ISpeechToTextNode(INode):
|
||||||
|
|
@ -28,7 +29,8 @@ class ISpeechToTextNode(INode):
|
||||||
self.node_params_serializer.data.get('audio_list')[1:])
|
self.node_params_serializer.data.get('audio_list')[1:])
|
||||||
for audio in res:
|
for audio in res:
|
||||||
if 'file_id' not in audio:
|
if 'file_id' not in audio:
|
||||||
raise ValueError(_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails"))
|
raise ValueError(
|
||||||
|
_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails"))
|
||||||
|
|
||||||
return self.execute(audio=res, **self.node_params_serializer.data, **self.flow_params_serializer.data)
|
return self.execute(audio=res, **self.node_params_serializer.data, **self.flow_params_serializer.data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
import io
|
|
||||||
from typing import List, Dict
|
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from pydub import AudioSegment
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
|
||||||
from application.flow.step_node.speech_to_text_step_node.i_speech_to_text_node import ISpeechToTextNode
|
from application.flow.step_node.speech_to_text_step_node.i_speech_to_text_node import ISpeechToTextNode
|
||||||
from common.util.common import split_and_transcribe, any_to_mp3
|
from common.utils.common import split_and_transcribe, any_to_mp3
|
||||||
from dataset.models import File
|
from knowledge.models import File
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||||
|
|
||||||
|
|
||||||
class BaseSpeechToTextNode(ISpeechToTextNode):
|
class BaseSpeechToTextNode(ISpeechToTextNode):
|
||||||
|
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
self.context['answer'] = details.get('answer')
|
self.context['answer'] = details.get('answer')
|
||||||
|
self.context['result'] = details.get('answer')
|
||||||
if self.node_params.get('is_result', False):
|
if self.node_params.get('is_result', False):
|
||||||
self.answer_text = details.get('answer')
|
self.answer_text = details.get('answer')
|
||||||
|
|
||||||
def execute(self, stt_model_id, chat_id, audio, **kwargs) -> NodeResult:
|
def execute(self, stt_model_id, chat_id, audio, **kwargs) -> NodeResult:
|
||||||
stt_model = get_model_instance_by_model_user_id(stt_model_id, self.flow_params_serializer.data.get('user_id'))
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
|
stt_model = get_model_instance_by_model_workspace_id(stt_model_id, workspace_id)
|
||||||
audio_list = audio
|
audio_list = audio
|
||||||
self.context['audio_list'] = audio
|
self.context['audio_list'] = audio
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ class BaseSpeechToTextNode(ISpeechToTextNode):
|
||||||
# 根据file_name 吧文件转成mp3格式
|
# 根据file_name 吧文件转成mp3格式
|
||||||
file_format = file.file_name.split('.')[-1]
|
file_format = file.file_name.split('.')[-1]
|
||||||
with tempfile.NamedTemporaryFile(delete=False, suffix=f'.{file_format}') as temp_file:
|
with tempfile.NamedTemporaryFile(delete=False, suffix=f'.{file_format}') as temp_file:
|
||||||
temp_file.write(file.get_byte().tobytes())
|
temp_file.write(file.get_bytes())
|
||||||
temp_file_path = temp_file.name
|
temp_file_path = temp_file.name
|
||||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_amr_file:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_amr_file:
|
||||||
temp_mp3_path = temp_amr_file.name
|
temp_mp3_path = temp_amr_file.name
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,32 @@ from application.flow.step_node.start_node.i_start_node import IStarNode
|
||||||
|
|
||||||
|
|
||||||
def get_default_global_variable(input_field_list: List):
|
def get_default_global_variable(input_field_list: List):
|
||||||
return {item.get('variable'): item.get('default_value') for item in input_field_list if
|
return {
|
||||||
item.get('default_value', None) is not None}
|
item.get('variable') or item.get('field'): item.get('default_value')
|
||||||
|
for item in input_field_list
|
||||||
|
if item.get('default_value', None) is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_global_variable(node):
|
def get_global_variable(node):
|
||||||
|
body = node.workflow_manage.get_body()
|
||||||
history_chat_record = node.flow_params_serializer.data.get('history_chat_record', [])
|
history_chat_record = node.flow_params_serializer.data.get('history_chat_record', [])
|
||||||
history_context = [{'question': chat_record.problem_text, 'answer': chat_record.answer_text} for chat_record in
|
history_context = [{'question': chat_record.problem_text, 'answer': chat_record.answer_text} for chat_record in
|
||||||
history_chat_record]
|
history_chat_record]
|
||||||
chat_id = node.flow_params_serializer.data.get('chat_id')
|
chat_id = node.flow_params_serializer.data.get('chat_id')
|
||||||
return {'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'start_time': time.time(),
|
return {'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'start_time': time.time(),
|
||||||
'history_context': history_context, 'chat_id': str(chat_id), **node.workflow_manage.form_data}
|
'history_context': history_context, 'chat_id': str(chat_id), **node.workflow_manage.form_data,
|
||||||
|
'chat_user_id': body.get('chat_user_id'),
|
||||||
|
'chat_user_type': body.get('chat_user_type'),
|
||||||
|
'chat_user': body.get('chat_user')}
|
||||||
|
|
||||||
|
|
||||||
class BaseStartStepNode(IStarNode):
|
class BaseStartStepNode(IStarNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
base_node = self.workflow_manage.get_base_node()
|
base_node = self.workflow_manage.get_base_node()
|
||||||
default_global_variable = get_default_global_variable(base_node.properties.get('input_field_list', []))
|
default_global_variable = get_default_global_variable(base_node.properties.get('user_input_field_list', []))
|
||||||
workflow_variable = {**default_global_variable, **get_global_variable(self)}
|
default_api_global_variable = get_default_global_variable(base_node.properties.get('api_input_field_list', []))
|
||||||
|
workflow_variable = {**default_global_variable, **default_api_global_variable, **get_global_variable(self)}
|
||||||
self.context['question'] = details.get('question')
|
self.context['question'] = details.get('question')
|
||||||
self.context['run_time'] = details.get('run_time')
|
self.context['run_time'] = details.get('run_time')
|
||||||
self.context['document'] = details.get('document_list')
|
self.context['document'] = details.get('document_list')
|
||||||
|
|
@ -53,8 +61,9 @@ class BaseStartStepNode(IStarNode):
|
||||||
|
|
||||||
def execute(self, question, **kwargs) -> NodeResult:
|
def execute(self, question, **kwargs) -> NodeResult:
|
||||||
base_node = self.workflow_manage.get_base_node()
|
base_node = self.workflow_manage.get_base_node()
|
||||||
default_global_variable = get_default_global_variable(base_node.properties.get('input_field_list', []))
|
default_global_variable = get_default_global_variable(base_node.properties.get('user_input_field_list', []))
|
||||||
workflow_variable = {**default_global_variable, **get_global_variable(self)}
|
default_api_global_variable = get_default_global_variable(base_node.properties.get('api_input_field_list', []))
|
||||||
|
workflow_variable = {**default_global_variable, **default_api_global_variable, **get_global_variable(self)}
|
||||||
"""
|
"""
|
||||||
开始节点 初始化全局变量
|
开始节点 初始化全局变量
|
||||||
"""
|
"""
|
||||||
|
|
@ -64,7 +73,9 @@ class BaseStartStepNode(IStarNode):
|
||||||
'document': self.workflow_manage.document_list,
|
'document': self.workflow_manage.document_list,
|
||||||
'audio': self.workflow_manage.audio_list,
|
'audio': self.workflow_manage.audio_list,
|
||||||
'other': self.workflow_manage.other_list,
|
'other': self.workflow_manage.other_list,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable()
|
||||||
return NodeResult(node_variable, workflow_variable)
|
return NodeResult(node_variable, workflow_variable)
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,19 @@ from typing import Type
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class TextToSpeechNodeSerializer(serializers.Serializer):
|
class TextToSpeechNodeSerializer(serializers.Serializer):
|
||||||
tts_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
tts_model_id = serializers.CharField(required=True, label=_("Model id"))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
is_result = serializers.BooleanField(required=False,
|
||||||
|
label=_('Whether to return content'))
|
||||||
|
|
||||||
content_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Text content")))
|
content_list = serializers.ListField(required=True, label=_("Text content"))
|
||||||
model_params_setting = serializers.DictField(required=False,
|
model_params_setting = serializers.DictField(required=False,
|
||||||
error_messages=ErrMessage.integer(_("Model parameter settings")))
|
label=_("Model parameter settings"))
|
||||||
|
|
||||||
|
|
||||||
class ITextToSpeechNode(INode):
|
class ITextToSpeechNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ import mimetypes
|
||||||
|
|
||||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
|
|
||||||
from application.flow.step_node.text_to_speech_step_node.i_text_to_speech_node import ITextToSpeechNode
|
from application.flow.step_node.text_to_speech_step_node.i_text_to_speech_node import ITextToSpeechNode
|
||||||
from dataset.models import File
|
from common.utils.common import _remove_empty_lines
|
||||||
from dataset.serializers.file_serializers import FileSerializer
|
from knowledge.models import FileSourceType
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||||
|
from oss.serializers.file import FileSerializer
|
||||||
|
from pydub import AudioSegment
|
||||||
|
|
||||||
|
|
||||||
def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"):
|
def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"):
|
||||||
|
|
@ -37,31 +38,78 @@ def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"):
|
||||||
class BaseTextToSpeechNode(ITextToSpeechNode):
|
class BaseTextToSpeechNode(ITextToSpeechNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
self.context['answer'] = details.get('answer')
|
self.context['answer'] = details.get('answer')
|
||||||
|
self.context['result'] = details.get('result')
|
||||||
if self.node_params.get('is_result', False):
|
if self.node_params.get('is_result', False):
|
||||||
self.answer_text = details.get('answer')
|
self.answer_text = details.get('answer')
|
||||||
|
|
||||||
def execute(self, tts_model_id, chat_id,
|
def execute(self, tts_model_id, chat_id,
|
||||||
content, model_params_setting=None,
|
content, model_params_setting=None,
|
||||||
**kwargs) -> NodeResult:
|
max_length=1024, **kwargs) -> NodeResult:
|
||||||
self.context['content'] = content
|
# 分割文本为合理片段
|
||||||
model = get_model_instance_by_model_user_id(tts_model_id, self.flow_params_serializer.data.get('user_id'),
|
content = _remove_empty_lines(content)
|
||||||
**model_params_setting)
|
content_chunks = [content[i:i + max_length]
|
||||||
audio_byte = model.text_to_speech(content)
|
for i in range(0, len(content), max_length)]
|
||||||
# 需要把这个音频文件存储到数据库中
|
|
||||||
file_name = 'generated_audio.mp3'
|
# 生成并收集所有音频片段
|
||||||
file = bytes_to_uploaded_file(audio_byte, file_name)
|
audio_segments = []
|
||||||
|
temp_files = []
|
||||||
|
|
||||||
|
for i, chunk in enumerate(content_chunks):
|
||||||
|
self.context['content'] = chunk
|
||||||
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
|
model = get_model_instance_by_model_workspace_id(
|
||||||
|
tts_model_id, workspace_id, **model_params_setting)
|
||||||
|
|
||||||
|
audio_byte = model.text_to_speech(chunk)
|
||||||
|
|
||||||
|
# 保存为临时音频文件用于合并
|
||||||
|
temp_file = io.BytesIO(audio_byte)
|
||||||
|
audio_segment = AudioSegment.from_file(temp_file)
|
||||||
|
audio_segments.append(audio_segment)
|
||||||
|
temp_files.append(temp_file)
|
||||||
|
|
||||||
|
# 合并所有音频片段
|
||||||
|
combined_audio = AudioSegment.empty()
|
||||||
|
for segment in audio_segments:
|
||||||
|
combined_audio += segment
|
||||||
|
|
||||||
|
# 将合并后的音频转为字节流
|
||||||
|
output_buffer = io.BytesIO()
|
||||||
|
combined_audio.export(output_buffer, format="mp3")
|
||||||
|
combined_bytes = output_buffer.getvalue()
|
||||||
|
|
||||||
|
# 存储合并后的音频文件
|
||||||
|
file_name = 'combined_audio.mp3'
|
||||||
|
file = bytes_to_uploaded_file(combined_bytes, file_name)
|
||||||
|
|
||||||
application = self.workflow_manage.work_flow_post_handler.chat_info.application
|
application = self.workflow_manage.work_flow_post_handler.chat_info.application
|
||||||
meta = {
|
meta = {
|
||||||
'debug': False if application.id else True,
|
'debug': False if application.id else True,
|
||||||
'chat_id': chat_id,
|
'chat_id': chat_id,
|
||||||
'application_id': str(application.id) if application.id else None,
|
'application_id': str(application.id) if application.id else None,
|
||||||
}
|
}
|
||||||
file_url = FileSerializer(data={'file': file, 'meta': meta}).upload()
|
|
||||||
# 拼接一个audio标签的src属性
|
file_url = FileSerializer(data={
|
||||||
audio_label = f'<audio src="{file_url}" controls style = "width: 300px; height: 43px"></audio>'
|
'file': file,
|
||||||
|
'meta': meta,
|
||||||
|
'source_id': meta['application_id'],
|
||||||
|
'source_type': FileSourceType.APPLICATION.value
|
||||||
|
}).upload()
|
||||||
|
|
||||||
|
# 生成音频标签
|
||||||
|
audio_label = f'<audio src="{file_url}" controls style="width: 300px; height: 43px"></audio>'
|
||||||
file_id = file_url.split('/')[-1]
|
file_id = file_url.split('/')[-1]
|
||||||
audio_list = [{'file_id': file_id, 'file_name': file_name, 'url': file_url}]
|
audio_list = [{'file_id': file_id, 'file_name': file_name, 'url': file_url}]
|
||||||
return NodeResult({'answer': audio_label, 'result': audio_list}, {})
|
|
||||||
|
# 关闭所有临时文件
|
||||||
|
for temp_file in temp_files:
|
||||||
|
temp_file.close()
|
||||||
|
output_buffer.close()
|
||||||
|
|
||||||
|
return NodeResult({
|
||||||
|
'answer': audio_label,
|
||||||
|
'result': audio_list
|
||||||
|
}, {})
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -9,34 +9,34 @@
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.field.common import ObjectField
|
from common.field.common import ObjectField
|
||||||
from common.util.field_message import ErrMessage
|
from tools.models.tool import Tool
|
||||||
from function_lib.models.function import FunctionLib
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class InputField(serializers.Serializer):
|
class InputField(serializers.Serializer):
|
||||||
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name')))
|
name = serializers.CharField(required=True, label=_('Variable Name'))
|
||||||
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list])
|
value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list])
|
||||||
|
|
||||||
|
|
||||||
class FunctionLibNodeParamsSerializer(serializers.Serializer):
|
class FunctionLibNodeParamsSerializer(serializers.Serializer):
|
||||||
function_lib_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_('Library ID')))
|
tool_lib_id = serializers.UUIDField(required=True, label=_('Library ID'))
|
||||||
input_field_list = InputField(required=True, many=True)
|
input_field_list = InputField(required=True, many=True)
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
is_result = serializers.BooleanField(required=False,
|
||||||
|
label=_('Whether to return content'))
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
def is_valid(self, *, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
f_lib = QuerySet(FunctionLib).filter(id=self.data.get('function_lib_id')).first()
|
f_lib = QuerySet(Tool).filter(id=self.data.get('tool_lib_id')).first()
|
||||||
if f_lib is None:
|
if f_lib is None:
|
||||||
raise Exception(_('The function has been deleted'))
|
raise Exception(_('The function has been deleted'))
|
||||||
|
|
||||||
|
|
||||||
class IFunctionLibNode(INode):
|
class IToolLibNode(INode):
|
||||||
type = 'function-lib-node'
|
type = 'tool-lib-node'
|
||||||
|
|
||||||
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
||||||
return FunctionLibNodeParamsSerializer
|
return FunctionLibNodeParamsSerializer
|
||||||
|
|
@ -44,5 +44,5 @@ class IFunctionLibNode(INode):
|
||||||
def _run(self):
|
def _run(self):
|
||||||
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)
|
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)
|
||||||
|
|
||||||
def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult:
|
def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
@date:2024/8/8 17:48
|
@date:2024/8/8 17:48
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .base_function_lib_node import BaseFunctionLibNodeNode
|
from .base_tool_lib_node import BaseToolLibNodeNode
|
||||||
|
|
@ -14,14 +14,15 @@ from django.db.models import QuerySet
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode
|
from application.flow.step_node.tool_lib_node.i_tool_lib_node import IToolLibNode
|
||||||
|
from common.database_model_manage.database_model_manage import DatabaseModelManage
|
||||||
from common.exception.app_exception import AppApiException
|
from common.exception.app_exception import AppApiException
|
||||||
from common.util.function_code import FunctionExecutor
|
from common.utils.rsa_util import rsa_long_decrypt
|
||||||
from common.util.rsa_util import rsa_long_decrypt
|
from common.utils.tool_code import ToolExecutor
|
||||||
from function_lib.models.function import FunctionLib
|
from maxkb.const import CONFIG
|
||||||
from smartdoc.const import CONFIG
|
from tools.models import Tool
|
||||||
|
|
||||||
function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
|
function_executor = ToolExecutor(CONFIG.get('SANDBOX'))
|
||||||
|
|
||||||
|
|
||||||
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
|
|
@ -45,35 +46,47 @@ def get_field_value(debug_field_list, name, is_required):
|
||||||
|
|
||||||
|
|
||||||
def valid_reference_value(_type, value, name):
|
def valid_reference_value(_type, value, name):
|
||||||
if _type == 'int':
|
try:
|
||||||
instance_type = int | float
|
if _type == 'int':
|
||||||
elif _type == 'float':
|
instance_type = int | float
|
||||||
instance_type = float | int
|
elif _type == 'float':
|
||||||
elif _type == 'dict':
|
instance_type = float | int
|
||||||
instance_type = dict
|
elif _type == 'dict':
|
||||||
elif _type == 'array':
|
value = json.loads(value) if isinstance(value, str) else value
|
||||||
instance_type = list
|
instance_type = dict
|
||||||
elif _type == 'string':
|
elif _type == 'array':
|
||||||
instance_type = str
|
value = json.loads(value) if isinstance(value, str) else value
|
||||||
else:
|
instance_type = list
|
||||||
raise Exception(_('Field: {name} Type: {_type} Value: {value} Unsupported types').format(name=name,
|
elif _type == 'string':
|
||||||
_type=_type))
|
instance_type = str
|
||||||
|
else:
|
||||||
|
raise Exception(_(
|
||||||
|
'Field: {name} Type: {_type} Value: {value} Unsupported types'
|
||||||
|
).format(name=name, _type=_type))
|
||||||
|
except:
|
||||||
|
return value
|
||||||
if not isinstance(value, instance_type):
|
if not isinstance(value, instance_type):
|
||||||
raise Exception(
|
raise Exception(_(
|
||||||
_('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,
|
'Field: {name} Type: {_type} Value: {value} Type error'
|
||||||
value=value))
|
).format(name=name, _type=_type, value=value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def convert_value(name: str, value, _type, is_required, source, node):
|
def convert_value(name: str, value, _type, is_required, source, node):
|
||||||
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)):
|
if not is_required and (value is None or ((isinstance(value, str) or isinstance(value, list)) and len(value) == 0)):
|
||||||
return None
|
|
||||||
if not is_required and source == 'reference' and (value is None or len(value) == 0):
|
|
||||||
return None
|
return None
|
||||||
if source == 'reference':
|
if source == 'reference':
|
||||||
value = node.workflow_manage.get_reference_field(
|
value = node.workflow_manage.get_reference_field(
|
||||||
value[0],
|
value[0],
|
||||||
value[1:])
|
value[1:])
|
||||||
valid_reference_value(_type, value, name)
|
if value is None:
|
||||||
|
if not is_required:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise Exception(_(
|
||||||
|
'Field: {name} Type: {_type} is required'
|
||||||
|
).format(name=name, _type=_type))
|
||||||
|
value = valid_reference_value(_type, value, name)
|
||||||
if _type == 'int':
|
if _type == 'int':
|
||||||
return int(value)
|
return int(value)
|
||||||
if _type == 'float':
|
if _type == 'float':
|
||||||
|
|
@ -101,24 +114,28 @@ def convert_value(name: str, value, _type, is_required, source, node):
|
||||||
value=value))
|
value=value))
|
||||||
|
|
||||||
|
|
||||||
def valid_function(function_lib, user_id):
|
def valid_function(tool_lib, workspace_id):
|
||||||
if function_lib is None:
|
if tool_lib is None:
|
||||||
raise Exception(_('Function does not exist'))
|
raise Exception(_('Tool does not exist'))
|
||||||
if function_lib.permission_type == 'PRIVATE' and str(function_lib.user_id) != str(user_id):
|
get_authorized_tool = DatabaseModelManage.get_model("get_authorized_tool")
|
||||||
raise Exception(_('No permission to use this function {name}').format(name=function_lib.name))
|
if tool_lib and tool_lib.workspace_id != workspace_id and get_authorized_tool is not None:
|
||||||
if not function_lib.is_active:
|
tool_lib = get_authorized_tool(QuerySet(Tool).filter(id=tool_lib.id), workspace_id).first()
|
||||||
raise Exception(_('Function {name} is unavailable').format(name=function_lib.name))
|
if tool_lib is None:
|
||||||
|
raise Exception(_("Tool does not exist"))
|
||||||
|
if not tool_lib.is_active:
|
||||||
|
raise Exception(_("Tool is not active"))
|
||||||
|
|
||||||
|
|
||||||
class BaseFunctionLibNodeNode(IFunctionLibNode):
|
class BaseToolLibNodeNode(IToolLibNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
self.context['result'] = details.get('result')
|
self.context['result'] = details.get('result')
|
||||||
if self.node_params.get('is_result'):
|
if self.node_params.get('is_result'):
|
||||||
self.answer_text = str(details.get('result'))
|
self.answer_text = str(details.get('result'))
|
||||||
|
|
||||||
def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult:
|
def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult:
|
||||||
function_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first()
|
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
||||||
valid_function(function_lib, self.flow_params_serializer.data.get('user_id'))
|
tool_lib = QuerySet(Tool).filter(id=tool_lib_id).first()
|
||||||
|
valid_function(tool_lib, workspace_id)
|
||||||
params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),
|
params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),
|
||||||
field.get('is_required'),
|
field.get('is_required'),
|
||||||
field.get('source'), self)
|
field.get('source'), self)
|
||||||
|
|
@ -126,15 +143,15 @@ class BaseFunctionLibNodeNode(IFunctionLibNode):
|
||||||
[{'value': get_field_value(input_field_list, field.get('name'), field.get('is_required'),
|
[{'value': get_field_value(input_field_list, field.get('name'), field.get('is_required'),
|
||||||
), **field}
|
), **field}
|
||||||
for field in
|
for field in
|
||||||
function_lib.input_field_list]}
|
tool_lib.input_field_list]}
|
||||||
|
|
||||||
self.context['params'] = params
|
self.context['params'] = params
|
||||||
# 合并初始化参数
|
# 合并初始化参数
|
||||||
if function_lib.init_params is not None:
|
if tool_lib.init_params is not None:
|
||||||
all_params = json.loads(rsa_long_decrypt(function_lib.init_params)) | params
|
all_params = json.loads(rsa_long_decrypt(tool_lib.init_params)) | params
|
||||||
else:
|
else:
|
||||||
all_params = params
|
all_params = params
|
||||||
result = function_executor.exec_code(function_lib.code, all_params)
|
result = function_executor.exec_code(tool_lib.code, all_params)
|
||||||
return NodeResult({'result': result}, {}, _write_context=write_context)
|
return NodeResult({'result': result}, {}, _write_context=write_context)
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
|
|
@ -15,23 +15,23 @@ from rest_framework import serializers
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.exception.app_exception import AppApiException
|
from common.exception.app_exception import AppApiException
|
||||||
from common.field.common import ObjectField
|
from common.field.common import ObjectField
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.utils.formatting import lazy_format
|
from rest_framework.utils.formatting import lazy_format
|
||||||
|
|
||||||
|
|
||||||
class InputField(serializers.Serializer):
|
class InputField(serializers.Serializer):
|
||||||
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name')))
|
name = serializers.CharField(required=True, label=_('Variable Name'))
|
||||||
is_required = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean(_("Is this field required")))
|
is_required = serializers.BooleanField(required=True, label=_("Is this field required"))
|
||||||
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("type")), validators=[
|
type = serializers.CharField(required=True, label=_("type"), validators=[
|
||||||
validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"),
|
validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"),
|
||||||
message=_("The field only supports string|int|dict|array|float"), code=500)
|
message=_("The field only supports string|int|dict|array|float"), code=500)
|
||||||
])
|
])
|
||||||
source = serializers.CharField(required=True, error_messages=ErrMessage.char(_("source")), validators=[
|
source = serializers.CharField(required=True, label=_("source"), validators=[
|
||||||
validators.RegexValidator(regex=re.compile("^custom|reference$"),
|
validators.RegexValidator(regex=re.compile("^custom|reference$"),
|
||||||
message=_("The field only supports custom|reference"), code=500)
|
message=_("The field only supports custom|reference"), code=500)
|
||||||
])
|
])
|
||||||
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list])
|
value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list])
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
def is_valid(self, *, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
|
|
@ -43,15 +43,16 @@ class InputField(serializers.Serializer):
|
||||||
|
|
||||||
class FunctionNodeParamsSerializer(serializers.Serializer):
|
class FunctionNodeParamsSerializer(serializers.Serializer):
|
||||||
input_field_list = InputField(required=True, many=True)
|
input_field_list = InputField(required=True, many=True)
|
||||||
code = serializers.CharField(required=True, error_messages=ErrMessage.char(_("function")))
|
code = serializers.CharField(required=True, label=_("function"))
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
is_result = serializers.BooleanField(required=False,
|
||||||
|
label=_('Whether to return content'))
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
def is_valid(self, *, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
|
||||||
class IFunctionNode(INode):
|
class IToolNode(INode):
|
||||||
type = 'function-node'
|
type = 'tool-node'
|
||||||
|
|
||||||
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
||||||
return FunctionNodeParamsSerializer
|
return FunctionNodeParamsSerializer
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
@date:2024/8/13 11:19
|
@date:2024/8/13 11:19
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .base_function_node import BaseFunctionNodeNode
|
from .base_tool_node import BaseToolNodeNode
|
||||||
|
|
@ -8,16 +8,16 @@
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from django.utils.translation import gettext as _
|
||||||
from application.flow.step_node.function_node.i_function_node import IFunctionNode
|
|
||||||
from common.exception.app_exception import AppApiException
|
|
||||||
from common.util.function_code import FunctionExecutor
|
|
||||||
from smartdoc.const import CONFIG
|
|
||||||
|
|
||||||
function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
|
from application.flow.i_step_node import NodeResult
|
||||||
|
from application.flow.step_node.tool_node.i_tool_node import IToolNode
|
||||||
|
from common.utils.tool_code import ToolExecutor
|
||||||
|
from maxkb.const import CONFIG
|
||||||
|
|
||||||
|
function_executor = ToolExecutor(CONFIG.get('SANDBOX'))
|
||||||
|
|
||||||
|
|
||||||
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
|
|
@ -32,30 +32,47 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
|
|
||||||
|
|
||||||
def valid_reference_value(_type, value, name):
|
def valid_reference_value(_type, value, name):
|
||||||
if _type == 'int':
|
try:
|
||||||
instance_type = int | float
|
if _type == 'int':
|
||||||
elif _type == 'float':
|
instance_type = int | float
|
||||||
instance_type = float | int
|
elif _type == 'float':
|
||||||
elif _type == 'dict':
|
instance_type = float | int
|
||||||
instance_type = dict
|
elif _type == 'dict':
|
||||||
elif _type == 'array':
|
value = json.loads(value) if isinstance(value, str) else value
|
||||||
instance_type = list
|
instance_type = dict
|
||||||
elif _type == 'string':
|
elif _type == 'array':
|
||||||
instance_type = str
|
value = json.loads(value) if isinstance(value, str) else value
|
||||||
else:
|
instance_type = list
|
||||||
raise Exception(500, f'字段:{name}类型:{_type} 不支持的类型')
|
elif _type == 'string':
|
||||||
|
instance_type = str
|
||||||
|
else:
|
||||||
|
raise Exception(_(
|
||||||
|
'Field: {name} Type: {_type} Value: {value} Unsupported types'
|
||||||
|
).format(name=name, _type=_type))
|
||||||
|
except:
|
||||||
|
return value
|
||||||
if not isinstance(value, instance_type):
|
if not isinstance(value, instance_type):
|
||||||
raise Exception(f'字段:{name}类型:{_type}值:{value}类型错误')
|
raise Exception(_(
|
||||||
|
'Field: {name} Type: {_type} Value: {value} Type error'
|
||||||
|
).format(name=name, _type=_type, value=value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def convert_value(name: str, value, _type, is_required, source, node):
|
def convert_value(name: str, value, _type, is_required, source, node):
|
||||||
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)):
|
if not is_required and (value is None or ((isinstance(value, str) or isinstance(value, list)) and len(value) == 0)):
|
||||||
return None
|
return None
|
||||||
if source == 'reference':
|
if source == 'reference':
|
||||||
value = node.workflow_manage.get_reference_field(
|
value = node.workflow_manage.get_reference_field(
|
||||||
value[0],
|
value[0],
|
||||||
value[1:])
|
value[1:])
|
||||||
valid_reference_value(_type, value, name)
|
if value is None:
|
||||||
|
if not is_required:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise Exception(_(
|
||||||
|
'Field: {name} Type: {_type} is required'
|
||||||
|
).format(name=name, _type=_type))
|
||||||
|
value = valid_reference_value(_type, value, name)
|
||||||
if _type == 'int':
|
if _type == 'int':
|
||||||
return int(value)
|
return int(value)
|
||||||
if _type == 'float':
|
if _type == 'float':
|
||||||
|
|
@ -70,18 +87,20 @@ def convert_value(name: str, value, _type, is_required, source, node):
|
||||||
v = json.loads(value)
|
v = json.loads(value)
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
return v
|
return v
|
||||||
raise Exception("类型错误")
|
raise Exception(_('type error'))
|
||||||
if _type == 'array':
|
if _type == 'array':
|
||||||
v = json.loads(value)
|
v = json.loads(value)
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
return v
|
return v
|
||||||
raise Exception("类型错误")
|
raise Exception(_('type error'))
|
||||||
return value
|
return value
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f'字段:{name}类型:{_type}值:{value}类型错误')
|
raise Exception(
|
||||||
|
_('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,
|
||||||
|
value=value))
|
||||||
|
|
||||||
|
|
||||||
class BaseFunctionNodeNode(IFunctionNode):
|
class BaseToolNodeNode(IToolNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
self.context['result'] = details.get('result')
|
self.context['result'] = details.get('result')
|
||||||
if self.node_params.get('is_result', False):
|
if self.node_params.get('is_result', False):
|
||||||
|
|
@ -6,12 +6,11 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.util.field_message import ErrMessage
|
|
||||||
|
|
||||||
|
|
||||||
class VariableAssignNodeParamsSerializer(serializers.Serializer):
|
class VariableAssignNodeParamsSerializer(serializers.Serializer):
|
||||||
variable_list = serializers.ListField(required=True,
|
variable_list = serializers.ListField(required=True,
|
||||||
error_messages=ErrMessage.list(_("Reference Field")))
|
label=_("Reference Field"))
|
||||||
|
|
||||||
|
|
||||||
class IVariableAssignNode(INode):
|
class IVariableAssignNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@
|
||||||
import json
|
import json
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode
|
from application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode
|
||||||
|
from application.models import Chat
|
||||||
|
|
||||||
|
|
||||||
class BaseVariableAssignNode(IVariableAssignNode):
|
class BaseVariableAssignNode(IVariableAssignNode):
|
||||||
|
|
@ -11,40 +14,56 @@ class BaseVariableAssignNode(IVariableAssignNode):
|
||||||
self.context['variable_list'] = details.get('variable_list')
|
self.context['variable_list'] = details.get('variable_list')
|
||||||
self.context['result_list'] = details.get('result_list')
|
self.context['result_list'] = details.get('result_list')
|
||||||
|
|
||||||
|
def global_evaluation(self, variable, value):
|
||||||
|
self.workflow_manage.context[variable['fields'][1]] = value
|
||||||
|
|
||||||
|
def chat_evaluation(self, variable, value):
|
||||||
|
self.workflow_manage.chat_context[variable['fields'][1]] = value
|
||||||
|
|
||||||
|
def handle(self, variable, evaluation):
|
||||||
|
result = {
|
||||||
|
'name': variable['name'],
|
||||||
|
'input_value': self.get_reference_content(variable['fields']),
|
||||||
|
}
|
||||||
|
if variable['source'] == 'custom':
|
||||||
|
if variable['type'] == 'json':
|
||||||
|
if isinstance(variable['value'], dict) or isinstance(variable['value'], list):
|
||||||
|
val = variable['value']
|
||||||
|
else:
|
||||||
|
val = json.loads(variable['value'])
|
||||||
|
evaluation(variable, val)
|
||||||
|
result['output_value'] = variable['value'] = val
|
||||||
|
elif variable['type'] == 'string':
|
||||||
|
# 变量解析 例如:{{global.xxx}}
|
||||||
|
val = self.workflow_manage.generate_prompt(variable['value'])
|
||||||
|
evaluation(variable, val)
|
||||||
|
result['output_value'] = val
|
||||||
|
else:
|
||||||
|
val = variable['value']
|
||||||
|
evaluation(variable, val)
|
||||||
|
result['output_value'] = val
|
||||||
|
else:
|
||||||
|
reference = self.get_reference_content(variable['reference'])
|
||||||
|
evaluation(variable, reference)
|
||||||
|
result['output_value'] = reference
|
||||||
|
return result
|
||||||
|
|
||||||
def execute(self, variable_list, stream, **kwargs) -> NodeResult:
|
def execute(self, variable_list, stream, **kwargs) -> NodeResult:
|
||||||
#
|
#
|
||||||
result_list = []
|
result_list = []
|
||||||
|
is_chat = False
|
||||||
for variable in variable_list:
|
for variable in variable_list:
|
||||||
if 'fields' not in variable:
|
if 'fields' not in variable:
|
||||||
continue
|
continue
|
||||||
if 'global' == variable['fields'][0]:
|
if 'global' == variable['fields'][0]:
|
||||||
result = {
|
result = self.handle(variable, self.global_evaluation)
|
||||||
'name': variable['name'],
|
|
||||||
'input_value': self.get_reference_content(variable['fields']),
|
|
||||||
}
|
|
||||||
if variable['source'] == 'custom':
|
|
||||||
if variable['type'] == 'json':
|
|
||||||
if isinstance(variable['value'], dict) or isinstance(variable['value'], list):
|
|
||||||
val = variable['value']
|
|
||||||
else:
|
|
||||||
val = json.loads(variable['value'])
|
|
||||||
self.workflow_manage.context[variable['fields'][1]] = val
|
|
||||||
result['output_value'] = variable['value'] = val
|
|
||||||
elif variable['type'] == 'string':
|
|
||||||
# 变量解析 例如:{{global.xxx}}
|
|
||||||
val = self.workflow_manage.generate_prompt(variable['value'])
|
|
||||||
self.workflow_manage.context[variable['fields'][1]] = val
|
|
||||||
result['output_value'] = val
|
|
||||||
else:
|
|
||||||
val = variable['value']
|
|
||||||
self.workflow_manage.context[variable['fields'][1]] = val
|
|
||||||
result['output_value'] = val
|
|
||||||
else:
|
|
||||||
reference = self.get_reference_content(variable['reference'])
|
|
||||||
self.workflow_manage.context[variable['fields'][1]] = reference
|
|
||||||
result['output_value'] = reference
|
|
||||||
result_list.append(result)
|
result_list.append(result)
|
||||||
|
if 'chat' == variable['fields'][0]:
|
||||||
|
result = self.handle(variable, self.chat_evaluation)
|
||||||
|
result_list.append(result)
|
||||||
|
is_chat = True
|
||||||
|
if is_chat:
|
||||||
|
self.workflow_manage.get_chat_info().set_chat_variable(self.workflow_manage.chat_context)
|
||||||
return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {})
|
return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {})
|
||||||
|
|
||||||
def get_reference_content(self, fields: List[str]):
|
def get_reference_content(self, fields: List[str]):
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from django.http import StreamingHttpResponse
|
||||||
from langchain_core.messages import BaseMessageChunk, BaseMessage
|
from langchain_core.messages import BaseMessageChunk, BaseMessage
|
||||||
|
|
||||||
from application.flow.i_step_node import WorkFlowPostHandler
|
from application.flow.i_step_node import WorkFlowPostHandler
|
||||||
from common.response import result
|
from common.result import result
|
||||||
|
|
||||||
|
|
||||||
class Reasoning:
|
class Reasoning:
|
||||||
|
|
|
||||||
|
|
@ -15,165 +15,21 @@ from functools import reduce
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
from django.db import close_old_connections
|
from django.db import close_old_connections
|
||||||
from django.db.models import QuerySet
|
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from langchain_core.prompts import PromptTemplate
|
from langchain_core.prompts import PromptTemplate
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
|
||||||
|
|
||||||
from application.flow import tools
|
from application.flow import tools
|
||||||
|
from application.flow.common import Workflow
|
||||||
from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult
|
from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult
|
||||||
from application.flow.step_node import get_node
|
from application.flow.step_node import get_node
|
||||||
from common.exception.app_exception import AppApiException
|
|
||||||
from common.handle.base_to_response import BaseToResponse
|
from common.handle.base_to_response import BaseToResponse
|
||||||
from common.handle.impl.response.system_to_response import SystemToResponse
|
from common.handle.impl.response.system_to_response import SystemToResponse
|
||||||
from function_lib.models.function import FunctionLib
|
|
||||||
from setting.models import Model
|
|
||||||
from setting.models_provider import get_model_credential
|
|
||||||
|
|
||||||
executor = ThreadPoolExecutor(max_workers=200)
|
executor = ThreadPoolExecutor(max_workers=200)
|
||||||
|
|
||||||
|
|
||||||
class Edge:
|
|
||||||
def __init__(self, _id: str, _type: str, sourceNodeId: str, targetNodeId: str, **keywords):
|
|
||||||
self.id = _id
|
|
||||||
self.type = _type
|
|
||||||
self.sourceNodeId = sourceNodeId
|
|
||||||
self.targetNodeId = targetNodeId
|
|
||||||
for keyword in keywords:
|
|
||||||
self.__setattr__(keyword, keywords.get(keyword))
|
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
|
||||||
def __init__(self, _id: str, _type: str, x: int, y: int, properties: dict, **kwargs):
|
|
||||||
self.id = _id
|
|
||||||
self.type = _type
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
self.properties = properties
|
|
||||||
for keyword in kwargs:
|
|
||||||
self.__setattr__(keyword, kwargs.get(keyword))
|
|
||||||
|
|
||||||
|
|
||||||
end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node',
|
|
||||||
'image-understand-node', 'speech-to-text-node', 'text-to-speech-node', 'image-generate-node']
|
|
||||||
|
|
||||||
|
|
||||||
class Flow:
|
|
||||||
def __init__(self, nodes: List[Node], edges: List[Edge]):
|
|
||||||
self.nodes = nodes
|
|
||||||
self.edges = edges
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def new_instance(flow_obj: Dict):
|
|
||||||
nodes = flow_obj.get('nodes')
|
|
||||||
edges = flow_obj.get('edges')
|
|
||||||
nodes = [Node(node.get('id'), node.get('type'), **node)
|
|
||||||
for node in nodes]
|
|
||||||
edges = [Edge(edge.get('id'), edge.get('type'), **edge) for edge in edges]
|
|
||||||
return Flow(nodes, edges)
|
|
||||||
|
|
||||||
def get_start_node(self):
|
|
||||||
start_node_list = [node for node in self.nodes if node.id == 'start-node']
|
|
||||||
return start_node_list[0]
|
|
||||||
|
|
||||||
def get_search_node(self):
|
|
||||||
return [node for node in self.nodes if node.type == 'search-dataset-node']
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
"""
|
|
||||||
校验工作流数据
|
|
||||||
"""
|
|
||||||
self.is_valid_model_params()
|
|
||||||
self.is_valid_start_node()
|
|
||||||
self.is_valid_base_node()
|
|
||||||
self.is_valid_work_flow()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_valid_node_params(node: Node):
|
|
||||||
get_node(node.type)(node, None, None)
|
|
||||||
|
|
||||||
def is_valid_node(self, node: Node):
|
|
||||||
self.is_valid_node_params(node)
|
|
||||||
if node.type == 'condition-node':
|
|
||||||
branch_list = node.properties.get('node_data').get('branch')
|
|
||||||
for branch in branch_list:
|
|
||||||
source_anchor_id = f"{node.id}_{branch.get('id')}_right"
|
|
||||||
edge_list = [edge for edge in self.edges if edge.sourceAnchorId == source_anchor_id]
|
|
||||||
if len(edge_list) == 0:
|
|
||||||
raise AppApiException(500,
|
|
||||||
_('The branch {branch} of the {node} node needs to be connected').format(
|
|
||||||
node=node.properties.get("stepName"), branch=branch.get("type")))
|
|
||||||
|
|
||||||
else:
|
|
||||||
edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id]
|
|
||||||
if len(edge_list) == 0 and not end_nodes.__contains__(node.type):
|
|
||||||
raise AppApiException(500, _("{node} Nodes cannot be considered as end nodes").format(
|
|
||||||
node=node.properties.get("stepName")))
|
|
||||||
|
|
||||||
def get_next_nodes(self, node: Node):
|
|
||||||
edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id]
|
|
||||||
node_list = reduce(lambda x, y: [*x, *y],
|
|
||||||
[[node for node in self.nodes if node.id == edge.targetNodeId] for edge in edge_list],
|
|
||||||
[])
|
|
||||||
if len(node_list) == 0 and not end_nodes.__contains__(node.type):
|
|
||||||
raise AppApiException(500,
|
|
||||||
_("The next node that does not exist"))
|
|
||||||
return node_list
|
|
||||||
|
|
||||||
def is_valid_work_flow(self, up_node=None):
|
|
||||||
if up_node is None:
|
|
||||||
up_node = self.get_start_node()
|
|
||||||
self.is_valid_node(up_node)
|
|
||||||
next_nodes = self.get_next_nodes(up_node)
|
|
||||||
for next_node in next_nodes:
|
|
||||||
self.is_valid_work_flow(next_node)
|
|
||||||
|
|
||||||
def is_valid_start_node(self):
|
|
||||||
start_node_list = [node for node in self.nodes if node.id == 'start-node']
|
|
||||||
if len(start_node_list) == 0:
|
|
||||||
raise AppApiException(500, _('The starting node is required'))
|
|
||||||
if len(start_node_list) > 1:
|
|
||||||
raise AppApiException(500, _('There can only be one starting node'))
|
|
||||||
|
|
||||||
def is_valid_model_params(self):
|
|
||||||
node_list = [node for node in self.nodes if (node.type == 'ai-chat-node' or node.type == 'question-node')]
|
|
||||||
for node in node_list:
|
|
||||||
model = QuerySet(Model).filter(id=node.properties.get('node_data', {}).get('model_id')).first()
|
|
||||||
if model is None:
|
|
||||||
raise ValidationError(ErrorDetail(
|
|
||||||
_('The node {node} model does not exist').format(node=node.properties.get("stepName"))))
|
|
||||||
credential = get_model_credential(model.provider, model.model_type, model.model_name)
|
|
||||||
model_params_setting = node.properties.get('node_data', {}).get('model_params_setting')
|
|
||||||
model_params_setting_form = credential.get_model_params_setting_form(
|
|
||||||
model.model_name)
|
|
||||||
if model_params_setting is None:
|
|
||||||
model_params_setting = model_params_setting_form.get_default_form_data()
|
|
||||||
node.properties.get('node_data', {})['model_params_setting'] = model_params_setting
|
|
||||||
if node.properties.get('status', 200) != 200:
|
|
||||||
raise ValidationError(
|
|
||||||
ErrorDetail(_("Node {node} is unavailable").format(node.properties.get("stepName"))))
|
|
||||||
node_list = [node for node in self.nodes if (node.type == 'function-lib-node')]
|
|
||||||
for node in node_list:
|
|
||||||
function_lib_id = node.properties.get('node_data', {}).get('function_lib_id')
|
|
||||||
if function_lib_id is None:
|
|
||||||
raise ValidationError(ErrorDetail(
|
|
||||||
_('The library ID of node {node} cannot be empty').format(node=node.properties.get("stepName"))))
|
|
||||||
f_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first()
|
|
||||||
if f_lib is None:
|
|
||||||
raise ValidationError(ErrorDetail(_("The function library for node {node} is not available").format(
|
|
||||||
node=node.properties.get("stepName"))))
|
|
||||||
|
|
||||||
def is_valid_base_node(self):
|
|
||||||
base_node_list = [node for node in self.nodes if node.id == 'base-node']
|
|
||||||
if len(base_node_list) == 0:
|
|
||||||
raise AppApiException(500, _('Basic information node is required'))
|
|
||||||
if len(base_node_list) > 1:
|
|
||||||
raise AppApiException(500, _('There can only be one basic information node'))
|
|
||||||
|
|
||||||
|
|
||||||
class NodeResultFuture:
|
class NodeResultFuture:
|
||||||
def __init__(self, r, e, status=200):
|
def __init__(self, r, e, status=200):
|
||||||
self.r = r
|
self.r = r
|
||||||
|
|
@ -234,7 +90,7 @@ class NodeChunkManage:
|
||||||
|
|
||||||
|
|
||||||
class WorkflowManage:
|
class WorkflowManage:
|
||||||
def __init__(self, flow: Flow, params, work_flow_post_handler: WorkFlowPostHandler,
|
def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostHandler,
|
||||||
base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None,
|
base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None,
|
||||||
document_list=None,
|
document_list=None,
|
||||||
audio_list=None,
|
audio_list=None,
|
||||||
|
|
@ -261,6 +117,7 @@ class WorkflowManage:
|
||||||
self.params = params
|
self.params = params
|
||||||
self.flow = flow
|
self.flow = flow
|
||||||
self.context = {}
|
self.context = {}
|
||||||
|
self.chat_context = {}
|
||||||
self.node_chunk_manage = NodeChunkManage(self)
|
self.node_chunk_manage = NodeChunkManage(self)
|
||||||
self.work_flow_post_handler = work_flow_post_handler
|
self.work_flow_post_handler = work_flow_post_handler
|
||||||
self.current_node = None
|
self.current_node = None
|
||||||
|
|
@ -275,6 +132,7 @@ class WorkflowManage:
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.field_list = []
|
self.field_list = []
|
||||||
self.global_field_list = []
|
self.global_field_list = []
|
||||||
|
self.chat_field_list = []
|
||||||
self.init_fields()
|
self.init_fields()
|
||||||
if start_node_id is not None:
|
if start_node_id is not None:
|
||||||
self.load_node(chat_record, start_node_id, start_node_data)
|
self.load_node(chat_record, start_node_id, start_node_data)
|
||||||
|
|
@ -284,6 +142,7 @@ class WorkflowManage:
|
||||||
def init_fields(self):
|
def init_fields(self):
|
||||||
field_list = []
|
field_list = []
|
||||||
global_field_list = []
|
global_field_list = []
|
||||||
|
chat_field_list = []
|
||||||
for node in self.flow.nodes:
|
for node in self.flow.nodes:
|
||||||
properties = node.properties
|
properties = node.properties
|
||||||
node_name = properties.get('stepName')
|
node_name = properties.get('stepName')
|
||||||
|
|
@ -298,10 +157,16 @@ class WorkflowManage:
|
||||||
if global_fields is not None:
|
if global_fields is not None:
|
||||||
for global_field in global_fields:
|
for global_field in global_fields:
|
||||||
global_field_list.append({**global_field, 'node_id': node_id, 'node_name': node_name})
|
global_field_list.append({**global_field, 'node_id': node_id, 'node_name': node_name})
|
||||||
field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
|
chat_fields = node_config.get('chatFields')
|
||||||
global_field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
|
if chat_fields is not None:
|
||||||
|
for chat_field in chat_fields:
|
||||||
|
chat_field_list.append({**chat_field, 'node_id': node_id, 'node_name': node_name})
|
||||||
|
field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)
|
||||||
|
global_field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)
|
||||||
|
chat_field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)
|
||||||
self.field_list = field_list
|
self.field_list = field_list
|
||||||
self.global_field_list = global_field_list
|
self.global_field_list = global_field_list
|
||||||
|
self.chat_field_list = chat_field_list
|
||||||
|
|
||||||
def append_answer(self, content):
|
def append_answer(self, content):
|
||||||
self.answer += content
|
self.answer += content
|
||||||
|
|
@ -367,9 +232,7 @@ class WorkflowManage:
|
||||||
'\n\n'.join([a.get('content') for a in answer]) for answer in
|
'\n\n'.join([a.get('content') for a in answer]) for answer in
|
||||||
answer_text_list)
|
answer_text_list)
|
||||||
answer_list = reduce(lambda pre, _n: [*pre, *_n], answer_text_list, [])
|
answer_list = reduce(lambda pre, _n: [*pre, *_n], answer_text_list, [])
|
||||||
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
|
self.work_flow_post_handler.handler(self)
|
||||||
answer_text,
|
|
||||||
self)
|
|
||||||
return self.base_to_response.to_block_response(self.params['chat_id'],
|
return self.base_to_response.to_block_response(self.params['chat_id'],
|
||||||
self.params['chat_record_id'], answer_text, True
|
self.params['chat_record_id'], answer_text, True
|
||||||
, message_tokens, answer_tokens,
|
, message_tokens, answer_tokens,
|
||||||
|
|
@ -384,6 +247,9 @@ class WorkflowManage:
|
||||||
self.run_chain_async(current_node, node_result_future, language)
|
self.run_chain_async(current_node, node_result_future, language)
|
||||||
return tools.to_stream_response_simple(self.await_result())
|
return tools.to_stream_response_simple(self.await_result())
|
||||||
|
|
||||||
|
def get_body(self):
|
||||||
|
return self.params
|
||||||
|
|
||||||
def is_run(self, timeout=0.5):
|
def is_run(self, timeout=0.5):
|
||||||
future_list_len = len(self.future_list)
|
future_list_len = len(self.future_list)
|
||||||
try:
|
try:
|
||||||
|
|
@ -420,9 +286,7 @@ class WorkflowManage:
|
||||||
'message_tokens' in row and row.get('message_tokens') is not None])
|
'message_tokens' in row and row.get('message_tokens') is not None])
|
||||||
answer_tokens = sum([row.get('answer_tokens') for row in details.values() if
|
answer_tokens = sum([row.get('answer_tokens') for row in details.values() if
|
||||||
'answer_tokens' in row and row.get('answer_tokens') is not None])
|
'answer_tokens' in row and row.get('answer_tokens') is not None])
|
||||||
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
|
self.work_flow_post_handler.handler(self)
|
||||||
self.answer,
|
|
||||||
self)
|
|
||||||
yield self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
|
yield self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
|
||||||
self.params['chat_record_id'],
|
self.params['chat_record_id'],
|
||||||
'',
|
'',
|
||||||
|
|
@ -590,6 +454,9 @@ class WorkflowManage:
|
||||||
return current_node.node_params.get('is_result', not self._has_next_node(
|
return current_node.node_params.get('is_result', not self._has_next_node(
|
||||||
current_node, current_node_result)) if current_node.node_params is not None else False
|
current_node, current_node_result)) if current_node.node_params is not None else False
|
||||||
|
|
||||||
|
def get_chat_info(self):
|
||||||
|
return self.work_flow_post_handler.chat_info
|
||||||
|
|
||||||
def get_chunk_content(self, chunk, is_end=False):
|
def get_chunk_content(self, chunk, is_end=False):
|
||||||
return 'data: ' + json.dumps(
|
return 'data: ' + json.dumps(
|
||||||
{'chat_id': self.params['chat_id'], 'id': self.params['chat_record_id'], 'operate': True,
|
{'chat_id': self.params['chat_id'], 'id': self.params['chat_record_id'], 'operate': True,
|
||||||
|
|
@ -599,15 +466,14 @@ class WorkflowManage:
|
||||||
"""
|
"""
|
||||||
是否有下一个可运行的节点
|
是否有下一个可运行的节点
|
||||||
"""
|
"""
|
||||||
if node_result is not None and node_result.is_assertion_result():
|
next_edge_node_list = self.flow.get_next_edge_nodes(current_node.id) or []
|
||||||
for edge in self.flow.edges:
|
for next_edge_node in next_edge_node_list:
|
||||||
|
if node_result is not None and node_result.is_assertion_result():
|
||||||
|
edge = next_edge_node.edge
|
||||||
if (edge.sourceNodeId == current_node.id and
|
if (edge.sourceNodeId == current_node.id and
|
||||||
f"{edge.sourceNodeId}_{node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
f"{edge.sourceNodeId}_{node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
||||||
return True
|
return True
|
||||||
else:
|
return len(next_edge_node_list) > 0
|
||||||
for edge in self.flow.edges:
|
|
||||||
if edge.sourceNodeId == current_node.id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def has_next_node(self, node_result: NodeResult | None):
|
def has_next_node(self, node_result: NodeResult | None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -657,26 +523,6 @@ class WorkflowManage:
|
||||||
return [[]]
|
return [[]]
|
||||||
return [[item.to_dict() for item in r] for r in result]
|
return [[item.to_dict() for item in r] for r in result]
|
||||||
|
|
||||||
def get_next_node(self):
|
|
||||||
"""
|
|
||||||
获取下一个可运行的所有节点
|
|
||||||
"""
|
|
||||||
if self.current_node is None:
|
|
||||||
node = self.get_start_node()
|
|
||||||
node_instance = get_node(node.type)(node, self.params, self)
|
|
||||||
return node_instance
|
|
||||||
if self.current_result is not None and self.current_result.is_assertion_result():
|
|
||||||
for edge in self.flow.edges:
|
|
||||||
if (edge.sourceNodeId == self.current_node.id and
|
|
||||||
f"{edge.sourceNodeId}_{self.current_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
|
||||||
return self.get_node_cls_by_id(edge.targetNodeId)
|
|
||||||
else:
|
|
||||||
for edge in self.flow.edges:
|
|
||||||
if edge.sourceNodeId == self.current_node.id:
|
|
||||||
return self.get_node_cls_by_id(edge.targetNodeId)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dependent_node(up_node_id, node):
|
def dependent_node(up_node_id, node):
|
||||||
if not node.node_chunk.is_end():
|
if not node.node_chunk.is_end():
|
||||||
|
|
@ -713,14 +559,14 @@ class WorkflowManage:
|
||||||
if current_node_result.is_interrupt_exec(current_node):
|
if current_node_result.is_interrupt_exec(current_node):
|
||||||
return []
|
return []
|
||||||
node_list = []
|
node_list = []
|
||||||
|
next_edge_node_list = self.flow.get_next_edge_nodes(current_node.id) or []
|
||||||
if current_node_result is not None and current_node_result.is_assertion_result():
|
if current_node_result is not None and current_node_result.is_assertion_result():
|
||||||
for edge in self.flow.edges:
|
for edge_node in next_edge_node_list:
|
||||||
if (edge.sourceNodeId == current_node.id and
|
edge = edge_node.edge
|
||||||
|
next_node = edge_node.node
|
||||||
|
if (
|
||||||
f"{edge.sourceNodeId}_{current_node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
f"{edge.sourceNodeId}_{current_node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
||||||
next_node = [node for node in self.flow.nodes if node.id == edge.targetNodeId]
|
if next_node.properties.get('condition', "AND") == 'AND':
|
||||||
if len(next_node) == 0:
|
|
||||||
continue
|
|
||||||
if next_node[0].properties.get('condition', "AND") == 'AND':
|
|
||||||
if self.dependent_node_been_executed(edge.targetNodeId):
|
if self.dependent_node_been_executed(edge.targetNodeId):
|
||||||
node_list.append(
|
node_list.append(
|
||||||
self.get_node_cls_by_id(edge.targetNodeId,
|
self.get_node_cls_by_id(edge.targetNodeId,
|
||||||
|
|
@ -730,20 +576,19 @@ class WorkflowManage:
|
||||||
self.get_node_cls_by_id(edge.targetNodeId,
|
self.get_node_cls_by_id(edge.targetNodeId,
|
||||||
[*current_node.up_node_id_list, current_node.node.id]))
|
[*current_node.up_node_id_list, current_node.node.id]))
|
||||||
else:
|
else:
|
||||||
for edge in self.flow.edges:
|
for edge_node in next_edge_node_list:
|
||||||
if edge.sourceNodeId == current_node.id:
|
edge = edge_node.edge
|
||||||
next_node = [node for node in self.flow.nodes if node.id == edge.targetNodeId]
|
next_node = edge_node.node
|
||||||
if len(next_node) == 0:
|
if next_node.properties.get('condition', "AND") == 'AND':
|
||||||
continue
|
if self.dependent_node_been_executed(edge.targetNodeId):
|
||||||
if next_node[0].properties.get('condition', "AND") == 'AND':
|
|
||||||
if self.dependent_node_been_executed(edge.targetNodeId):
|
|
||||||
node_list.append(
|
|
||||||
self.get_node_cls_by_id(edge.targetNodeId,
|
|
||||||
[*current_node.up_node_id_list, current_node.node.id]))
|
|
||||||
else:
|
|
||||||
node_list.append(
|
node_list.append(
|
||||||
self.get_node_cls_by_id(edge.targetNodeId,
|
self.get_node_cls_by_id(edge.targetNodeId,
|
||||||
[*current_node.up_node_id_list, current_node.node.id]))
|
[*current_node.up_node_id_list, current_node.node.id]))
|
||||||
|
else:
|
||||||
|
node_list.append(
|
||||||
|
self.get_node_cls_by_id(edge.targetNodeId,
|
||||||
|
[*current_node.up_node_id_list, current_node.node.id]))
|
||||||
|
|
||||||
return node_list
|
return node_list
|
||||||
|
|
||||||
def get_reference_field(self, node_id: str, fields: List[str]):
|
def get_reference_field(self, node_id: str, fields: List[str]):
|
||||||
|
|
@ -754,12 +599,18 @@ class WorkflowManage:
|
||||||
"""
|
"""
|
||||||
if node_id == 'global':
|
if node_id == 'global':
|
||||||
return INode.get_field(self.context, fields)
|
return INode.get_field(self.context, fields)
|
||||||
|
elif node_id == 'chat':
|
||||||
|
return INode.get_field(self.chat_context, fields)
|
||||||
else:
|
else:
|
||||||
return self.get_node_by_id(node_id).get_reference_field(fields)
|
node = self.get_node_by_id(node_id)
|
||||||
|
if node:
|
||||||
|
return node.get_reference_field(fields)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_workflow_content(self):
|
def get_workflow_content(self):
|
||||||
context = {
|
context = {
|
||||||
'global': self.context,
|
'global': self.context,
|
||||||
|
'chat': self.chat_context
|
||||||
}
|
}
|
||||||
|
|
||||||
for node in self.node_context:
|
for node in self.node_context:
|
||||||
|
|
@ -777,6 +628,10 @@ class WorkflowManage:
|
||||||
globeLabelNew = f"global.{field.get('value')}"
|
globeLabelNew = f"global.{field.get('value')}"
|
||||||
globeValue = f"context.get('global').get('{field.get('value', '')}','')"
|
globeValue = f"context.get('global').get('{field.get('value', '')}','')"
|
||||||
prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue)
|
prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue)
|
||||||
|
for field in self.chat_field_list:
|
||||||
|
chatLabel = f"chat.{field.get('value')}"
|
||||||
|
chatValue = f"context.get('chat').get('{field.get('value', '')}','')"
|
||||||
|
prompt = prompt.replace(chatLabel, chatValue)
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
def generate_prompt(self, prompt: str):
|
def generate_prompt(self, prompt: str):
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,20 @@
|
||||||
# Generated by Django 4.1.10 on 2024-03-18 16:02
|
# Generated by Django 5.2.4 on 2025-07-14 11:45
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
import application.models.application
|
import application.models.application
|
||||||
|
import application.models.application_chat
|
||||||
|
import common.encoder.encoder
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import uuid
|
import mptt.fields
|
||||||
|
import uuid_utils.compat
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def insert_default_data(apps, schema_editor):
|
||||||
|
# 创建一个根模块(没有父节点)
|
||||||
|
QuerySet(application.models.application.ApplicationFolder).create(id='default', name='根目录',
|
||||||
|
user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', workspace_id='default')
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
@ -12,8 +22,8 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('dataset', '0001_initial'),
|
('knowledge', '0001_initial'),
|
||||||
('setting', '0001_initial'),
|
('models_provider', '0001_initial'),
|
||||||
('users', '0001_initial'),
|
('users', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -21,65 +31,196 @@ class Migration(migrations.Migration):
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Application',
|
name='Application',
|
||||||
fields=[
|
fields=[
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
('name', models.CharField(max_length=128, verbose_name='应用名称')),
|
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
|
||||||
|
('is_publish', models.BooleanField(default=False, verbose_name='是否发布')),
|
||||||
|
('name', models.CharField(db_index=True, max_length=128, verbose_name='应用名称')),
|
||||||
('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),
|
('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),
|
||||||
('prologue', models.CharField(default='', max_length=1024, verbose_name='开场白')),
|
('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')),
|
||||||
('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')),
|
('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')),
|
||||||
('dataset_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')),
|
('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')),
|
||||||
('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')),
|
('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')),
|
||||||
|
('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),
|
||||||
|
('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),
|
||||||
('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')),
|
('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')),
|
||||||
('model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='setting.model')),
|
('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user')),
|
('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),
|
||||||
|
('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')),
|
||||||
|
('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词')),
|
||||||
|
('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')),
|
||||||
|
('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')),
|
||||||
|
('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')),
|
||||||
|
('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')),
|
||||||
|
('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')),
|
||||||
|
('clean_time', models.IntegerField(default=180, verbose_name='清理时间')),
|
||||||
|
('publish_time', models.DateTimeField(blank=True, default=None, null=True, verbose_name='发布时间')),
|
||||||
|
('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')),
|
||||||
|
('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')),
|
||||||
|
('model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='models_provider.model')),
|
||||||
|
('stt_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stt_model_id', to='models_provider.model')),
|
||||||
|
('tts_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tts_model_id', to='models_provider.model')),
|
||||||
|
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'db_table': 'application',
|
'db_table': 'application',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationAccessToken',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
|
('application', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='application.application', verbose_name='应用id')),
|
||||||
|
('access_token', models.CharField(max_length=128, unique=True, verbose_name='用户公开访问 认证token')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='是否开启公开访问')),
|
||||||
|
('access_num', models.IntegerField(default=100, verbose_name='访问次数')),
|
||||||
|
('white_active', models.BooleanField(default=False, verbose_name='是否开启白名单')),
|
||||||
|
('white_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='白名单列表')),
|
||||||
|
('show_source', models.BooleanField(default=False, verbose_name='是否显示知识来源')),
|
||||||
|
('show_exec', models.BooleanField(default=False, verbose_name='是否显示执行详情')),
|
||||||
|
('authentication', models.BooleanField(default=False, verbose_name='是否需要认证')),
|
||||||
|
('authentication_value', models.JSONField(default=dict, verbose_name='认证的值')),
|
||||||
|
('language', models.CharField(default=None, max_length=10, null=True, verbose_name='语言')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_access_token',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationApiKey',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
|
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('secret_key', models.CharField(max_length=1024, unique=True, verbose_name='秘钥')),
|
||||||
|
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='是否开启')),
|
||||||
|
('allow_cross_domain', models.BooleanField(default=False, verbose_name='是否允许跨域')),
|
||||||
|
('cross_domain_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表')),
|
||||||
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_api_key',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationFolder',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
|
('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('name', models.CharField(db_index=True, max_length=64, verbose_name='文件夹名称')),
|
||||||
|
('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')),
|
||||||
|
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
|
||||||
|
('lft', models.PositiveIntegerField(editable=False)),
|
||||||
|
('rght', models.PositiveIntegerField(editable=False)),
|
||||||
|
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
|
||||||
|
('level', models.PositiveIntegerField(editable=False)),
|
||||||
|
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='application.applicationfolder')),
|
||||||
|
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_folder',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='folder',
|
||||||
|
field=models.ForeignKey(default='default', on_delete=django.db.models.deletion.DO_NOTHING, to='application.applicationfolder', verbose_name='文件夹id'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationKnowledgeMapping',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
|
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='application.application')),
|
||||||
|
('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_knowledge_mapping',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationVersion',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
|
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('name', models.CharField(default='', max_length=128, verbose_name='版本名称')),
|
||||||
|
('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')),
|
||||||
|
('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')),
|
||||||
|
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
|
||||||
|
('application_name', models.CharField(max_length=128, verbose_name='应用名称')),
|
||||||
|
('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),
|
||||||
|
('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')),
|
||||||
|
('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')),
|
||||||
|
('model_id', models.UUIDField(blank=True, null=True, verbose_name='大语言模型')),
|
||||||
|
('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')),
|
||||||
|
('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')),
|
||||||
|
('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),
|
||||||
|
('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),
|
||||||
|
('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')),
|
||||||
|
('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')),
|
||||||
|
('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),
|
||||||
|
('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')),
|
||||||
|
('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词')),
|
||||||
|
('tts_model_id', models.UUIDField(blank=True, null=True, verbose_name='文本转语音模型id')),
|
||||||
|
('stt_model_id', models.UUIDField(blank=True, null=True, verbose_name='语音转文本模型id')),
|
||||||
|
('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')),
|
||||||
|
('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')),
|
||||||
|
('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')),
|
||||||
|
('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')),
|
||||||
|
('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')),
|
||||||
|
('clean_time', models.IntegerField(default=180, verbose_name='清理时间')),
|
||||||
|
('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')),
|
||||||
|
('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')),
|
||||||
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
||||||
|
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_version',
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Chat',
|
name='Chat',
|
||||||
fields=[
|
fields=[
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
('abstract', models.CharField(max_length=256, verbose_name='摘要')),
|
('abstract', models.CharField(max_length=1024, verbose_name='摘要')),
|
||||||
|
('chat_user_id', models.CharField(default=None, null=True, verbose_name='对话用户id')),
|
||||||
|
('chat_user_type', models.CharField(choices=[('ANONYMOUS_USER', '匿名用户'), ('CHAT_USER', '对话用户'), ('SYSTEM_API_KEY', '系统API_KEY'), ('APPLICATION_API_KEY', '应用API_KEY'), ('PLATFORM_USER', '平台用户')], default='ANONYMOUS_USER', max_length=64, verbose_name='客户端类型')),
|
||||||
|
('is_deleted', models.BooleanField(default=False, verbose_name='逻辑删除')),
|
||||||
|
('asker', models.JSONField(default=application.models.application_chat.default_asker, encoder=common.encoder.encoder.SystemEncoder, verbose_name='访问者')),
|
||||||
|
('meta', models.JSONField(default=dict, verbose_name='元数据')),
|
||||||
|
('star_num', models.IntegerField(default=0, verbose_name='点赞数量')),
|
||||||
|
('trample_num', models.IntegerField(default=0, verbose_name='点踩数量')),
|
||||||
|
('chat_record_count', models.IntegerField(default=0, verbose_name='对话次数')),
|
||||||
|
('mark_sum', models.IntegerField(default=0, verbose_name='标记数量')),
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'db_table': 'application_chat',
|
'db_table': 'application_chat',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationAccessToken',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
|
||||||
('application', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='application.application', verbose_name='应用id')),
|
|
||||||
('access_token', models.CharField(max_length=128, unique=True, verbose_name='用户公开访问 认证token')),
|
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='是否开启公开访问')),
|
|
||||||
('access_num', models.IntegerField(default=100, verbose_name='访问次数')),
|
|
||||||
('white_active', models.BooleanField(default=False, verbose_name='是否开启白名单')),
|
|
||||||
('white_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='白名单列表')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_access_token',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ChatRecord',
|
name='ChatRecord',
|
||||||
fields=[
|
fields=[
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
('vote_status', models.CharField(choices=[('-1', '未投票'), ('0', '赞同'), ('1', '反对')], default='-1', max_length=10, verbose_name='投票')),
|
('vote_status', models.CharField(choices=[('-1', '未投票'), ('0', '赞同'), ('1', '反对')], default='-1', max_length=10, verbose_name='投票')),
|
||||||
('problem_text', models.CharField(max_length=1024, verbose_name='问题')),
|
('problem_text', models.CharField(max_length=10240, verbose_name='问题')),
|
||||||
('answer_text', models.CharField(max_length=4096, verbose_name='答案')),
|
('answer_text', models.CharField(max_length=40960, verbose_name='答案')),
|
||||||
|
('answer_text_list', django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(), default=list, size=None, verbose_name='改进标注列表')),
|
||||||
('message_tokens', models.IntegerField(default=0, verbose_name='请求token数量')),
|
('message_tokens', models.IntegerField(default=0, verbose_name='请求token数量')),
|
||||||
('answer_tokens', models.IntegerField(default=0, verbose_name='响应token数量')),
|
('answer_tokens', models.IntegerField(default=0, verbose_name='响应token数量')),
|
||||||
('const', models.IntegerField(default=0, verbose_name='总费用')),
|
('const', models.IntegerField(default=0, verbose_name='总费用')),
|
||||||
('details', models.JSONField(default=dict, verbose_name='对话详情')),
|
('details', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='对话详情')),
|
||||||
('improve_paragraph_id_list', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(blank=True), default=list, size=None, verbose_name='改进标注列表')),
|
('improve_paragraph_id_list', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(blank=True), default=list, size=None, verbose_name='改进标注列表')),
|
||||||
('run_time', models.FloatField(default=0, verbose_name='运行时长')),
|
('run_time', models.FloatField(default=0, verbose_name='运行时长')),
|
||||||
('index', models.IntegerField(verbose_name='对话下标')),
|
('index', models.IntegerField(verbose_name='对话下标')),
|
||||||
|
|
@ -90,45 +231,21 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ApplicationPublicAccessClient',
|
name='ApplicationChatUserStats',
|
||||||
fields=[
|
fields=[
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||||
('id', models.UUIDField(primary_key=True, serialize=False, verbose_name='公共访问链接客户端id')),
|
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('chat_user_id', models.UUIDField(default=uuid_utils.compat.uuid7, verbose_name='对话用户id')),
|
||||||
|
('chat_user_type', models.CharField(choices=[('ANONYMOUS_USER', '匿名用户'), ('CHAT_USER', '对话用户'), ('SYSTEM_API_KEY', '系统API_KEY'), ('APPLICATION_API_KEY', '应用API_KEY'), ('PLATFORM_USER', '平台用户')], default='ANONYMOUS_USER', max_length=64, verbose_name='对话用户类型')),
|
||||||
('access_num', models.IntegerField(default=0, verbose_name='访问总次数次数')),
|
('access_num', models.IntegerField(default=0, verbose_name='访问总次数次数')),
|
||||||
('intraday_access_num', models.IntegerField(default=0, verbose_name='当日访问次数')),
|
('intraday_access_num', models.IntegerField(default=0, verbose_name='当日访问次数')),
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'db_table': 'application_public_access_client',
|
'db_table': 'application_chat_user_stats',
|
||||||
},
|
'indexes': [models.Index(fields=['application_id', 'chat_user_id'], name='application_applica_1652ba_idx')],
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationDatasetMapping',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
|
||||||
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
|
||||||
('dataset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dataset.dataset')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_dataset_mapping',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationApiKey',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
|
||||||
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
|
||||||
('secret_key', models.CharField(max_length=1024, unique=True, verbose_name='秘钥')),
|
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='是否开启')),
|
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_api_key',
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.RunPython(insert_default_data)
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.1.13 on 2024-03-28 13:59
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='chat',
|
|
||||||
name='client_id',
|
|
||||||
field=models.UUIDField(default=None, null=True, verbose_name='客户端id'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.1.13 on 2024-04-23 11:16
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0002_chat_client_id'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='icon',
|
|
||||||
field=models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='应用icon'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.1.13 on 2024-04-25 11:28
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0003_application_icon'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='applicationaccesstoken',
|
|
||||||
name='show_source',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='是否显示知识来源'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 4.1.13 on 2024-04-29 13:33
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0004_applicationaccesstoken_show_source'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='chat',
|
|
||||||
name='abstract',
|
|
||||||
field=models.CharField(max_length=1024, verbose_name='摘要'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='chatrecord',
|
|
||||||
name='answer_text',
|
|
||||||
field=models.CharField(max_length=40960, verbose_name='答案'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# Generated by Django 4.1.13 on 2024-05-08 13:57
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0005_alter_chat_abstract_alter_chatrecord_answer_text'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='applicationapikey',
|
|
||||||
name='allow_cross_domain',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='是否允许跨域'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='applicationapikey',
|
|
||||||
name='cross_domain_list',
|
|
||||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.1.13 on 2024-05-24 11:00
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0006_applicationapikey_allow_cross_domain_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='application',
|
|
||||||
name='prologue',
|
|
||||||
field=models.CharField(default='', max_length=4096, verbose_name='开场白'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.1.13 on 2024-06-13 11:46
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0007_alter_application_prologue'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='chat',
|
|
||||||
name='is_deleted',
|
|
||||||
field=models.BooleanField(default=False, verbose_name=''),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
# Generated by Django 4.1.13 on 2024-06-25 16:30
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0008_chat_is_deleted'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='work_flow',
|
|
||||||
field=models.JSONField(default=dict, verbose_name='工作流数据'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='WorkFlowVersion',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
|
||||||
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
|
||||||
('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),
|
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_work_flow_version',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
# Generated by Django 4.2.13 on 2024-07-15 15:52
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
import common.encoder.encoder
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('application', '0009_application_type_application_work_flow_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='chatrecord',
|
|
||||||
name='details',
|
|
||||||
field=models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='对话详情'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-08-23 14:17
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0010_alter_chatrecord_details'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='model_params_setting',
|
|
||||||
field=models.JSONField(default=dict, verbose_name='模型参数相关设置'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-09-05 14:35
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('setting', '0006_alter_model_status'),
|
|
||||||
('application', '0011_application_model_params_setting'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='stt_model',
|
|
||||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stt_model_id', to='setting.model'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='stt_model_enable',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='语音识别模型是否启用'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='tts_model',
|
|
||||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tts_model_id', to='setting.model'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='tts_model_enable',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='语音合成模型是否启用'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-09-12 11:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0012_application_stt_model_application_stt_model_enable_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='tts_type',
|
|
||||||
field=models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-09-13 18:57
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0013_application_tts_type'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='problem_optimization_prompt',
|
|
||||||
field=models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-09-18 16:14
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import psycopg2
|
|
||||||
from django.db import migrations
|
|
||||||
from psycopg2 import extensions
|
|
||||||
|
|
||||||
from smartdoc.const import CONFIG
|
|
||||||
|
|
||||||
|
|
||||||
def get_connect(db_name):
|
|
||||||
conn_params = {
|
|
||||||
"dbname": db_name,
|
|
||||||
"user": CONFIG.get('DB_USER'),
|
|
||||||
"password": CONFIG.get('DB_PASSWORD'),
|
|
||||||
"host": CONFIG.get('DB_HOST'),
|
|
||||||
"port": CONFIG.get('DB_PORT')
|
|
||||||
}
|
|
||||||
# 建立连接
|
|
||||||
connect = psycopg2.connect(**conn_params)
|
|
||||||
return connect
|
|
||||||
|
|
||||||
|
|
||||||
def sql_execute(conn, reindex_sql: str, alter_database_sql: str):
|
|
||||||
"""
|
|
||||||
执行一条sql
|
|
||||||
@param reindex_sql:
|
|
||||||
@param conn:
|
|
||||||
@param alter_database_sql:
|
|
||||||
"""
|
|
||||||
conn.set_isolation_level(extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
|
||||||
with conn.cursor() as cursor:
|
|
||||||
cursor.execute(reindex_sql, [])
|
|
||||||
cursor.execute(alter_database_sql, [])
|
|
||||||
cursor.close()
|
|
||||||
|
|
||||||
|
|
||||||
def re_index(apps, schema_editor):
|
|
||||||
app_db_name = CONFIG.get('DB_NAME')
|
|
||||||
try:
|
|
||||||
re_index_database(app_db_name)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f'reindex database {app_db_name}发送错误:{str(e)}')
|
|
||||||
try:
|
|
||||||
re_index_database('root')
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f'reindex database root 发送错误:{str(e)}')
|
|
||||||
|
|
||||||
|
|
||||||
def re_index_database(db_name):
|
|
||||||
db_conn = get_connect(db_name)
|
|
||||||
sql_execute(db_conn, f'REINDEX DATABASE "{db_name}";', f'ALTER DATABASE "{db_name}" REFRESH COLLATION VERSION;')
|
|
||||||
db_conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('application', '0014_application_problem_optimization_prompt'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(re_index, atomic=False)
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-09-26 13:19
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0015_re_database_index'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='chatrecord',
|
|
||||||
name='problem_text',
|
|
||||||
field=models.CharField(max_length=10240, verbose_name='问题'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-10-16 13:10
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0016_alter_chatrecord_problem_text'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='tts_model_params_setting',
|
|
||||||
field=models.JSONField(default=dict, verbose_name='模型参数相关设置'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-10-16 15:17
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
sql = """
|
|
||||||
UPDATE "public".application_work_flow_version
|
|
||||||
SET "name" = TO_CHAR(create_time, 'YYYY-MM-DD HH24:MI:SS');
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('application', '0017_application_tts_model_params_setting'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='clean_time',
|
|
||||||
field=models.IntegerField(default=180, verbose_name='清理时间'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='workflowversion',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(default='', max_length=128, verbose_name='版本名称'),
|
|
||||||
),
|
|
||||||
migrations.RunSQL(sql),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='workflowversion',
|
|
||||||
name='publish_user_id',
|
|
||||||
field=models.UUIDField(default=None, null=True, verbose_name='发布者id'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='workflowversion',
|
|
||||||
name='publish_user_name',
|
|
||||||
field=models.CharField(default='', max_length=128, verbose_name='发布者名称'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-11-13 10:13
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
sql = """
|
|
||||||
UPDATE application_chat_record
|
|
||||||
SET answer_text_list=ARRAY[jsonb_build_object('content',answer_text)]
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('application', '0018_workflowversion_name'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='file_upload_enable',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='文件上传是否启用'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='file_upload_setting',
|
|
||||||
field=models.JSONField(default=dict, verbose_name='文件上传相关设置'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='chatrecord',
|
|
||||||
name='answer_text_list',
|
|
||||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(), default=list, size=None, verbose_name='改进标注列表')
|
|
||||||
),
|
|
||||||
migrations.RunSQL(sql)
|
|
||||||
]
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
from django.db import migrations, connection
|
|
||||||
|
|
||||||
batch_update_update_time = """
|
|
||||||
UPDATE application_chat ac
|
|
||||||
SET update_time = acr_max.max_update_time
|
|
||||||
FROM (
|
|
||||||
SELECT chat_id, MAX(update_time) AS max_update_time
|
|
||||||
FROM application_chat_record
|
|
||||||
GROUP BY chat_id
|
|
||||||
) acr_max
|
|
||||||
WHERE ac.id = acr_max.chat_id;
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('application', '0019_application_file_upload_enable_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunSQL(batch_update_update_time),
|
|
||||||
]
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-12-27 18:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
run_sql = """
|
|
||||||
UPDATE application_public_access_client
|
|
||||||
SET client_id="id"
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('application', '0020_application_record_update_time'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='applicationpublicaccessclient',
|
|
||||||
name='client_id',
|
|
||||||
field=models.UUIDField(default=uuid.uuid1, verbose_name='公共访问链接客户端id'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='applicationpublicaccessclient',
|
|
||||||
name='id',
|
|
||||||
field=models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False,
|
|
||||||
verbose_name='主键id'),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='applicationpublicaccessclient',
|
|
||||||
index=models.Index(fields=['client_id'], name='application_client__4de9af_idx'),
|
|
||||||
),
|
|
||||||
migrations.RunSQL(run_sql)
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2025-01-03 14:07
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0021_applicationpublicaccessclient_client_id_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='tts_autoplay',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='自动播放'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2025-01-06 10:37
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0022_application_tts_autoplay'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='stt_autosend',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='自动发送'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2025-01-20 06:59
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('application', '0023_application_stt_autosend'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='applicationaccesstoken',
|
|
||||||
name='language',
|
|
||||||
field=models.CharField(default=None, max_length=10, null=True, verbose_name='语言')
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.18 on 2025-01-22 09:53
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0024_applicationaccesstoken_language'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='application',
|
|
||||||
name='prologue',
|
|
||||||
field=models.CharField(default='', max_length=40960, verbose_name='开场白'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
# Generated by Django 4.2.18 on 2025-03-18 06:05
|
|
||||||
|
|
||||||
import application.models.application
|
|
||||||
import common.encoder.encoder
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('application', '0025_alter_application_prologue'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='chat',
|
|
||||||
name='asker',
|
|
||||||
field=models.JSONField(default=application.models.application.default_asker, encoder=common.encoder.encoder.SystemEncoder, verbose_name='访问者'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
"""
|
"""
|
||||||
@project: maxkb
|
@project: MaxKB
|
||||||
@Author:虎
|
@Author:虎虎
|
||||||
@file: __init__.py
|
@file: __init__.py
|
||||||
@date:2023/9/25 14:25
|
@date:2025/5/7 15:14
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .application import *
|
from .application import *
|
||||||
|
from .application_access_token import *
|
||||||
|
from .application_chat import *
|
||||||
|
from .application_api_key import *
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue