Compare commits
1240
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85e568e114 | ||
|
|
0026cb404f | ||
|
|
62919a9ff5 | ||
|
|
e13f8da9d5 | ||
|
|
53aca1a922 | ||
|
|
464aee08a1 | ||
|
|
ab56e6b6af | ||
|
|
2a2f4cd4d5 | ||
|
|
b3a4a52a7a | ||
|
|
379f9b56ad | ||
|
|
b705041dda | ||
|
|
fedcfe94ae | ||
|
|
add7bdc877 | ||
|
|
088842dcdb | ||
|
|
c43eaeec06 | ||
|
|
f06554a11e | ||
|
|
3ac37e802a | ||
|
|
aee669f67d | ||
|
|
c8db4d8a08 | ||
|
|
c3bb541a69 | ||
|
|
ba3039d6c9 | ||
|
|
bd3b400121 | ||
|
|
28e813f57f | ||
|
|
3f11e11c2d | ||
|
|
37a282cc1c | ||
|
|
88ef220d4d | ||
|
|
ccb67bffc4 | ||
|
|
20394b3231 | ||
|
|
52a1c4580c | ||
|
|
aca395b97d | ||
|
|
459b690416 | ||
|
|
d48bdf3e14 | ||
|
|
9fea2fd44b | ||
|
|
f291aec2cd | ||
|
|
00d9c48461 | ||
|
|
fef62d937d | ||
|
|
6b06c5b957 | ||
|
|
5a4ea0932a | ||
|
|
f7e4f0a988 | ||
|
|
cf449b31a1 | ||
|
|
f7184c0e36 | ||
|
|
5df66579a8 | ||
|
|
d260e6b064 | ||
|
|
01c6a35966 | ||
|
|
0202469254 | ||
|
|
fb2fa625b4 | ||
|
|
fbdf2ba839 | ||
|
|
716936e37a | ||
|
|
1c004e0df6 | ||
|
|
59d279fbe0 | ||
|
|
2d7c43b60f | ||
|
|
a9f7f88a9a | ||
|
|
56cb9ccec1 | ||
|
|
e0a152164b | ||
|
|
d72524ceb0 | ||
|
|
74538fb3b2 | ||
|
|
f832211e2e | ||
|
|
e46c3a9235 | ||
|
|
36c3774fac | ||
|
|
5e201324d6 | ||
|
|
5adbcacc52 | ||
|
|
8b01796f5d | ||
|
|
34f4f76f67 | ||
|
|
01e832a587 | ||
|
|
1af2d06d29 | ||
|
|
426abe2134 | ||
|
|
f62775bcad | ||
|
|
7a2083a6b7 | ||
|
|
09650b9d47 | ||
|
|
f19219ab8d | ||
|
|
8be04b57f9 | ||
|
|
ef39fa3fb2 | ||
|
|
fe569559ac | ||
|
|
8125d8fc9f | ||
|
|
fd8ed95209 | ||
|
|
cf22842554 | ||
|
|
0fcb746c08 | ||
|
|
9d7ab0400d | ||
|
|
396a3e0456 | ||
|
|
56a1d5330a | ||
|
|
c5e58c713c | ||
|
|
6e0f13f269 | ||
|
|
00728c2b1d | ||
|
|
9fb7100b3f | ||
|
|
4e31d7b64f | ||
|
|
1ab3b73c14 | ||
|
|
b5fa68fdfe | ||
|
|
31f24e1a14 | ||
|
|
e800109c02 | ||
|
|
5c3162cc33 | ||
|
|
04b4be27b7 | ||
|
|
5793855115 | ||
|
|
41cce464ca | ||
|
|
8c55ff392d | ||
|
|
45d5d259a4 | ||
|
|
e08d871837 | ||
|
|
ab2c112059 | ||
|
|
a42f26d857 | ||
|
|
6fea18b4d0 | ||
|
|
7e259600bf | ||
|
|
75e95e09d3 | ||
|
|
51f225e567 | ||
|
|
53c988718b | ||
|
|
74ead43ae1 | ||
|
|
d0509213d1 | ||
|
|
5b81234db8 | ||
|
|
df9e2e478f | ||
|
|
7c64f2cfe0 | ||
|
|
3b3d19dab7 | ||
|
|
806f27c370 | ||
|
|
86a32517e5 | ||
|
|
072967a1d3 | ||
|
|
9147e0046f | ||
|
|
f967203012 | ||
|
|
e2d0ff4784 | ||
|
|
85ce2e8df8 | ||
|
|
c330f89c77 | ||
|
|
ffb698922a | ||
|
|
50a7c2c92c | ||
|
|
0843af2996 | ||
|
|
e9985f0696 | ||
|
|
dfad42075c | ||
|
|
705d765a71 | ||
|
|
e03367a188 | ||
|
|
c20685e669 | ||
|
|
429dd11dd7 | ||
|
|
b394dd6fb0 | ||
|
|
a30a6dda63 | ||
|
|
de3b7e8815 | ||
|
|
142d1be4f8 | ||
|
|
fb364d44d1 | ||
|
|
a647698c32 | ||
|
|
75ffdc9d3f | ||
|
|
0d12e5c795 | ||
|
|
bab88efda9 | ||
|
|
ca6acf2650 | ||
|
|
11b428a73f | ||
|
|
f43faa125b | ||
|
|
6b3bc789b5 | ||
|
|
586488c6a9 | ||
|
|
704cb42869 | ||
|
|
2d26c4745b | ||
|
|
971436d935 | ||
|
|
8a2d04b305 | ||
|
|
7c45f369d1 | ||
|
|
6444d94f41 | ||
|
|
a8236a270a | ||
|
|
760ada399f | ||
|
|
815262b9a6 | ||
|
|
83651a038f | ||
|
|
589ac9b22c | ||
|
|
d673b4c219 | ||
|
|
4e548fff5e | ||
|
|
636603d5af | ||
|
|
950a52f4fc | ||
|
|
b50e897aa0 | ||
|
|
d7be9c0afc | ||
|
|
06a6d398cd | ||
|
|
12ed31be4d | ||
|
|
8d2ac8ff8f | ||
|
|
91b84d8f1e | ||
|
|
46cc635e05 | ||
|
|
1ea8504cf1 | ||
|
|
a32465eeb8 | ||
|
|
f930521d64 | ||
|
|
42ad622a6c | ||
|
|
4eb9027510 | ||
|
|
05bb65bd94 | ||
|
|
85285931e2 | ||
|
|
d7c4032917 | ||
|
|
c1466a7a4d | ||
|
|
51404f9035 | ||
|
|
c1bf4c6405 | ||
|
|
b8818c90b0 | ||
|
|
ead55ce931 | ||
|
|
0a0d9565ac | ||
|
|
4235baf493 | ||
|
|
82a82fff35 | ||
|
|
4934a655dd | ||
|
|
615178dafa | ||
|
|
08650339d7 | ||
|
|
12ea3af242 | ||
|
|
858ab8c8c4 | ||
|
|
63a4ddc251 | ||
|
|
aa4d734244 | ||
|
|
85b45a7cd0 | ||
|
|
de3fd0f382 | ||
|
|
db3f38bc2b | ||
|
|
d239e6bf0f | ||
|
|
9e4b39e19f | ||
|
|
078b10a9f0 | ||
|
|
c70d0546ae | ||
|
|
c3d926e2ed | ||
|
|
78a851d240 | ||
|
|
6256a3fadb | ||
|
|
8def0f8cf2 | ||
|
|
a29d3f2400 | ||
|
|
794c57b938 | ||
|
|
9eff8715fb | ||
|
|
e952e01dfe | ||
|
|
a20d305842 | ||
|
|
17d1e2e5b7 | ||
|
|
1c05d2ef7f | ||
|
|
f9caa09cac | ||
|
|
829a7b0d16 | ||
|
|
83aaacd71d | ||
|
|
a56115a664 | ||
|
|
b5578c754f | ||
|
|
de00245af0 | ||
|
|
2c9d4c8dca | ||
|
|
1ac96564a0 | ||
|
|
c15677634f | ||
|
|
2dd2c8c358 | ||
|
|
8e3be982eb | ||
|
|
46f4e61edc | ||
|
|
a35b5e4fff | ||
|
|
07091c9d33 | ||
|
|
b3db119146 | ||
|
|
28206cac72 | ||
|
|
47f2fe591d | ||
|
|
acd0e22b9e | ||
|
|
2ebd8d9fdc | ||
|
|
b5fe1f7c46 | ||
|
|
d7b2fe1e8b | ||
|
|
fac9459402 | ||
|
|
6cf0e0c242 | ||
|
|
656bd9257d | ||
|
|
38441c930c | ||
|
|
dafdbfa0fd | ||
|
|
a264973366 | ||
|
|
5843b30a13 | ||
|
|
340ae3c52f | ||
|
|
817e16493f | ||
|
|
66cf787755 | ||
|
|
4f3872277c | ||
|
|
9b84086bac | ||
|
|
ce2b2755af | ||
|
|
a91bec033d | ||
|
|
096cc74373 | ||
|
|
34db42ecea | ||
|
|
34e8d2f6bb | ||
|
|
a771d59b1e | ||
|
|
3a00941125 | ||
|
|
8e9ade14df | ||
|
|
a1ec45fdd1 | ||
|
|
9295739dc0 | ||
|
|
4afb16844c | ||
|
|
c4e6ed1aa2 | ||
|
|
95c5848d05 | ||
|
|
8e56096f83 | ||
|
|
c8f51dd6db | ||
|
|
ebbb30de44 | ||
|
|
fe1168d15a | ||
|
|
fd7fded6e5 | ||
|
|
8cffbc6b2a | ||
|
|
fa673f9b4c | ||
|
|
178f1fc5d6 | ||
|
|
0c409e2b9e | ||
|
|
e366e12be0 | ||
|
|
8e0d8fdb3f | ||
|
|
524b19bb3a | ||
|
|
fb2e351c08 | ||
|
|
260fef40c4 | ||
|
|
72818e946d | ||
|
|
e673c64534 | ||
|
|
93bbb2694f | ||
|
|
b038b7aa33 | ||
|
|
02a059bdc6 | ||
|
|
267d9568c6 | ||
|
|
d71eae8f93 | ||
|
|
8bdaab96b1 | ||
|
|
a05fcedd61 | ||
|
|
0db67a2fd3 | ||
|
|
6e56a504fd | ||
|
|
69fa8c9794 | ||
|
|
8dc8650ecb | ||
|
|
40775e27ce | ||
|
|
6fb294202d | ||
|
|
bd409a3caf | ||
|
|
0d0da9a892 | ||
|
|
a7e2f9caf0 | ||
|
|
3d4d60a353 | ||
|
|
75e876b14e | ||
|
|
66fd60bc6f | ||
|
|
c3e7299494 | ||
|
|
8fc576870d | ||
|
|
d4f362164f | ||
|
|
94ca0edb68 | ||
|
|
a0dde6e4da | ||
|
|
17f572f23f | ||
|
|
137746387d | ||
|
|
9b5deaf80a | ||
|
|
1201bef879 | ||
|
|
30a9b8b917 | ||
|
|
77bdc6ffb1 | ||
|
|
a65c99496b | ||
|
|
a8c86b759d | ||
|
|
76081db6e4 | ||
|
|
0606b6f922 | ||
|
|
9ed2a99abf | ||
|
|
8f311b020a | ||
|
|
de6cbc36bb | ||
|
|
0aa984219f | ||
|
|
c9168c19cd | ||
|
|
beca05848c | ||
|
|
2e5acef1b6 | ||
|
|
c4811f921f | ||
|
|
7569346943 | ||
|
|
2919cc9adf | ||
|
|
18883d9faa | ||
|
|
e462ddb805 | ||
|
|
df274416f9 | ||
|
|
b35d9f6c36 | ||
|
|
4c5737fc7f | ||
|
|
7bb1decaf8 | ||
|
|
4bef2eed25 | ||
|
|
0d2a90adf3 | ||
|
|
38a1ea139a | ||
|
|
698eb9671f | ||
|
|
1b857eba29 | ||
|
|
b060b773ef | ||
|
|
ae197fb2ba | ||
|
|
2697454a8e | ||
|
|
2a75258836 | ||
|
|
b50f221327 | ||
|
|
d984eb3648 | ||
|
|
4df8fa0afb | ||
|
|
67b3ee3776 | ||
|
|
8337e3c6ba | ||
|
|
a9b8917e22 | ||
|
|
70698b553e | ||
|
|
b131c5dc73 | ||
|
|
15e2ab9203 | ||
|
|
d5c79e0489 | ||
|
|
884eeebe83 | ||
|
|
9042db301d | ||
|
|
f4f8d6c652 | ||
|
|
20cd3e52d0 | ||
|
|
53fa4ffe73 | ||
|
|
8acd6f2531 | ||
|
|
8d8bbc586e | ||
|
|
df4e1339da | ||
|
|
0183651cd5 | ||
|
|
9f024835aa | ||
|
|
45017f3f35 | ||
|
|
089072432e | ||
|
|
6e600bc0dc | ||
|
|
25995eb735 | ||
|
|
28dc089540 | ||
|
|
8967c4c8f6 | ||
|
|
3969ed6f69 | ||
|
|
56b025ebdd | ||
|
|
aab5566d98 | ||
|
|
a9e44b1fd2 | ||
|
|
bae1bc2e4b | ||
|
|
b9f58d3c1d | ||
|
|
7c7f3958ff | ||
|
|
85da94aac4 | ||
|
|
5350753905 | ||
|
|
e7895cdc53 | ||
|
|
b84d4bdb85 | ||
|
|
66538d8cbd | ||
|
|
4e24e116aa | ||
|
|
3f13c47b9b | ||
|
|
10237c99e4 | ||
|
|
faf936416f | ||
|
|
779f77ccd6 | ||
|
|
758b8bf812 | ||
|
|
c61f51dc5d | ||
|
|
b17e30b1c2 | ||
|
|
0756b09cf5 | ||
|
|
2f16b3600c | ||
|
|
55d2417906 | ||
|
|
49dd5b76f1 | ||
|
|
43429108f5 | ||
|
|
18159b1a4b | ||
|
|
b31da3b195 | ||
|
|
17b7426cc6 | ||
|
|
ba7b9a595b | ||
|
|
7778901630 | ||
|
|
8f7356cc12 | ||
|
|
d49834ee56 | ||
|
|
e4fdf1730e | ||
|
|
f41a619490 | ||
|
|
1607fcfaa7 | ||
|
|
8386abaed1 | ||
|
|
16a1562900 | ||
|
|
9b1869f521 | ||
|
|
3dfcd9ca67 | ||
|
|
74408c4ced | ||
|
|
653917649d | ||
|
|
00f51749a3 | ||
|
|
8d3158a6d5 | ||
|
|
dbaf54c93d | ||
|
|
7762737796 | ||
|
|
133d52deb9 | ||
|
|
0ede136d67 | ||
|
|
8d82d9f7ef | ||
|
|
1532564601 | ||
|
|
24ac4996c0 | ||
|
|
112593119a | ||
|
|
0c100ac0b1 | ||
|
|
45168d0e00 | ||
|
|
dc91b2e3df | ||
|
|
ced6a5c18b | ||
|
|
0b7cdd1e5d | ||
|
|
67de047122 | ||
|
|
4ec14d8d91 | ||
|
|
09516726e9 | ||
|
|
6bfd61a887 | ||
|
|
a436550dff | ||
|
|
f5a3069913 | ||
|
|
cf0c96e0d1 | ||
|
|
978ee93df7 | ||
|
|
f3bf4c7730 | ||
|
|
d2de16fba2 | ||
|
|
90543c458c | ||
|
|
d6e655eaae | ||
|
|
9884466ef0 | ||
|
|
3b4676d8e9 | ||
|
|
3ee9f74cf8 | ||
|
|
1c7573a686 | ||
|
|
2da7cc6928 | ||
|
|
fda802e796 | ||
|
|
5a5beb5b59 | ||
|
|
a0b16e541c | ||
|
|
a67777b8e2 | ||
|
|
eae4c80679 | ||
|
|
ac63b5385a | ||
|
|
f61ceadec5 | ||
|
|
5ff2fbed59 | ||
|
|
d24cf9e56a | ||
|
|
7b9fbccf60 | ||
|
|
0b07c6914a | ||
|
|
f803fb5855 | ||
|
|
cd3c2f6b00 | ||
|
|
587ba27f8c | ||
|
|
1b0acdbe63 | ||
|
|
3e810bc490 | ||
|
|
cc86850ad9 | ||
|
|
fed19db938 | ||
|
|
9175eb455f | ||
|
|
a89287bf20 | ||
|
|
977020f580 | ||
|
|
a2195c813c | ||
|
|
d5a404236a | ||
|
|
202492e5ac | ||
|
|
601e888fde | ||
|
|
4a483a8754 | ||
|
|
a4f367b8ff | ||
|
|
e225a3d33c | ||
|
|
31b6383697 | ||
|
|
e7d6def1e8 | ||
|
|
197c0bb1a3 | ||
|
|
d6953f28d3 | ||
|
|
249f013ca3 | ||
|
|
387a6cfee4 | ||
|
|
81cbf2e713 | ||
|
|
e66c55ba9e | ||
|
|
56044a104c | ||
|
|
c409ab4c3c | ||
|
|
487efcb206 | ||
|
|
4eb7546177 | ||
|
|
4b561aec93 | ||
|
|
34695f02fb | ||
|
|
aa421269c4 | ||
|
|
09cfbe117e | ||
|
|
0ea233edbe | ||
|
|
d69e0a79d4 | ||
|
|
7320ac41af | ||
|
|
08b1f5d7c3 | ||
|
|
61b41ca04b | ||
|
|
8d4f40bc7c | ||
|
|
3e9c7dccc0 | ||
|
|
672b8f14f2 | ||
|
|
513d075ebc | ||
|
|
8d34082246 | ||
|
|
5ed181dd42 | ||
|
|
9d8f9f6f63 | ||
|
|
8e8c39a88c | ||
|
|
41d9fdee50 | ||
|
|
9e37021387 | ||
|
|
bf06be0c75 | ||
|
|
a93a2e2e0c | ||
|
|
02337cbb09 | ||
|
|
c35c0fc6f4 | ||
|
|
1482eb0348 | ||
|
|
cbe7116bb7 | ||
|
|
788550affa | ||
|
|
25949338cb | ||
|
|
958da42f74 | ||
|
|
6e8ea528c2 | ||
|
|
d537efe97a | ||
|
|
0439276866 | ||
|
|
69c8e4ddd1 | ||
|
|
711f7107b4 | ||
|
|
ea4476ac6e | ||
|
|
13dbc7f0ce | ||
|
|
b8ecfd859b | ||
|
|
4daf93ef4f | ||
|
|
90b7ca1df1 | ||
|
|
96f38b2d15 | ||
|
|
8a27e51658 | ||
|
|
8ecec84dcf | ||
|
|
a2b3096159 | ||
|
|
80f1fbba56 | ||
|
|
d8ab611480 | ||
|
|
722ff7795d | ||
|
|
73c2b35dfe | ||
|
|
b99eadecf6 | ||
|
|
843db3dbdf | ||
|
|
3b660f1698 | ||
|
|
a2e30e6aa9 | ||
|
|
9638885a67 | ||
|
|
cd01c890e1 | ||
|
|
552ccb058b | ||
|
|
36180b1001 | ||
|
|
65ed4dc91f | ||
|
|
c709e339b1 | ||
|
|
3cf8416484 | ||
|
|
d2d47d0e0e | ||
|
|
05f97f6e06 | ||
|
|
11dfdb236d | ||
|
|
6df520ebc6 | ||
|
|
a047a98462 | ||
|
|
1df68a546e | ||
|
|
5013ea09d5 | ||
|
|
d92d952e76 | ||
|
|
4af304e6ae | ||
|
|
5c4d1c52ee | ||
|
|
5ee7fc4fde | ||
|
|
b0cf8c00db | ||
|
|
338dd1c714 | ||
|
|
af9ae91934 | ||
|
|
d122daca87 | ||
|
|
ec49da073e | ||
|
|
62846be275 | ||
|
|
6b9cc927c0 | ||
|
|
a577db9ddd | ||
|
|
e5c8743712 | ||
|
|
777cca1a09 | ||
|
|
e3c65c072c | ||
|
|
9b069bd3d4 | ||
|
|
56c53d1f07 | ||
|
|
6146f24932 | ||
|
|
9908a8bf1f | ||
|
|
e33260d2e2 | ||
|
|
5713ee5fce | ||
|
|
129a68bb06 | ||
|
|
aff5ab933b | ||
|
|
4835358f24 | ||
|
|
9b57b4c6c8 | ||
|
|
785dfc5c00 | ||
|
|
12eb236364 | ||
|
|
1cfeb989f7 | ||
|
|
ede65eca4d | ||
|
|
dc53362506 | ||
|
|
74e644be1c | ||
|
|
6e51ce123c | ||
|
|
737321da75 | ||
|
|
72d2f76d24 | ||
|
|
87a36a1fc8 | ||
|
|
c2ded79cb2 | ||
|
|
fb6e5bf4d5 | ||
|
|
6633a92e1a | ||
|
|
44c4d5be72 | ||
|
|
5a67c09b48 | ||
|
|
0614ddde7d | ||
|
|
e5ff06bcb7 | ||
|
|
6b19ba3bb2 | ||
|
|
735b55e61b | ||
|
|
7e53625eae | ||
|
|
5213b0aade | ||
|
|
2b4b6817a3 | ||
|
|
da3e1e9d14 | ||
|
|
e4794e309a | ||
|
|
e6572ef2d7 | ||
|
|
2182533af8 | ||
|
|
d88ac6c238 | ||
|
|
e8751bebfa | ||
|
|
92c1da8dbe | ||
|
|
951aaf5161 | ||
|
|
a420953385 | ||
|
|
b102562614 | ||
|
|
2c2b9e7389 | ||
|
|
513a8655b1 | ||
|
|
d3385a2715 | ||
|
|
ebf9c41adb | ||
|
|
7372776992 | ||
|
|
7f7269d261 | ||
|
|
f2bb0012fd | ||
|
|
33113034ea | ||
|
|
88c29f613f | ||
|
|
f318fa058c | ||
|
|
407bfb8182 | ||
|
|
91845fc9f6 | ||
|
|
f911b1c488 | ||
|
|
7a6fa3655f | ||
|
|
5eb7b4d56a | ||
|
|
5e4bd9fc38 | ||
|
|
f8cba2679e | ||
|
|
e0883302d2 | ||
|
|
a0a1618869 | ||
|
|
be68369983 | ||
|
|
9d0a832e40 | ||
|
|
8031262006 | ||
|
|
751489fa54 | ||
|
|
1e6feadc7e | ||
|
|
2d8497f79b | ||
|
|
61a1aadf9c | ||
|
|
8b832097de | ||
|
|
7e4daf131e | ||
|
|
de3978fdbb | ||
|
|
51f6ab49cf | ||
|
|
2895c3bc8c | ||
|
|
3d5f9b5a1e | ||
|
|
614bc2e075 | ||
|
|
193bcce236 | ||
|
|
a0fd731170 | ||
|
|
2f57d090a1 | ||
|
|
4c5822fb6e | ||
|
|
e90637f67a | ||
|
|
9b0f83f807 | ||
|
|
fc573564b4 | ||
|
|
5596b3b00b | ||
|
|
cb02b1e12e | ||
|
|
736e386f15 | ||
|
|
c152d55f68 | ||
|
|
1a0b6adc2c | ||
|
|
1914dfea77 | ||
|
|
1f986a3abb | ||
|
|
b174f85237 | ||
|
|
2ad9c76093 | ||
|
|
8684b172d2 | ||
|
|
3e54cb26be | ||
|
|
079cc082a3 | ||
|
|
a1bc6b50c5 | ||
|
|
7d28fe8ea5 | ||
|
|
dd50deaa43 | ||
|
|
79a10e9729 | ||
|
|
a5de7b10f3 | ||
|
|
bc4edbfc2b | ||
|
|
75f1355d4c | ||
|
|
1a86e79d4a | ||
|
|
c8a1f923f5 | ||
|
|
df753e84a3 | ||
|
|
3086893ee7 | ||
|
|
242fcf0145 | ||
|
|
de40422205 | ||
|
|
df809ff435 | ||
|
|
75559bcbf9 | ||
|
|
d9b8a938c6 | ||
|
|
e9004a06a5 | ||
|
|
be709d4b84 | ||
|
|
602bc67495 | ||
|
|
e498efce2d | ||
|
|
09dfe80718 | ||
|
|
06b05163f6 | ||
|
|
b80092ea12 | ||
|
|
2eaae6742a | ||
|
|
3f5d1a79c6 | ||
|
|
15c7e0ec2f | ||
|
|
43b0440358 | ||
|
|
9651a208a9 | ||
|
|
7bff65304f | ||
|
|
8a8882ed8d | ||
|
|
9467fe9aa9 | ||
|
|
799db69e4f | ||
|
|
896c200211 | ||
|
|
3badc4423a | ||
|
|
d741527ae4 | ||
|
|
77618823a5 | ||
|
|
dd70aeff24 | ||
|
|
022b7d5dd4 | ||
|
|
11337e51c5 | ||
|
|
7724d010b6 | ||
|
|
124aa9db08 | ||
|
|
20cf075b2d | ||
|
|
bf4a5f6b33 | ||
|
|
03749917f0 | ||
|
|
7d51d6030b | ||
|
|
742b87df5e | ||
|
|
a457faa2bf | ||
|
|
4f50f113dd | ||
|
|
8b529a3ec7 | ||
|
|
84c3ec0ea7 | ||
|
|
c13e8077ba | ||
|
|
9f42892b42 | ||
|
|
27ba5a0bce | ||
|
|
78afba49bf | ||
|
|
a9192bc1c6 | ||
|
|
77f04603b3 | ||
|
|
34ed5e428c | ||
|
|
98cb17e79e | ||
|
|
fce20e483c | ||
|
|
97c4733e79 | ||
|
|
748aa22ee2 | ||
|
|
2ba7ac8bc1 | ||
|
|
7458fde5a5 | ||
|
|
f11bf9153d | ||
|
|
0806b3163a | ||
|
|
45621ba4d7 | ||
|
|
6aecf42b6e | ||
|
|
3b234febf5 | ||
|
|
8642354a2a | ||
|
|
c028e5f889 | ||
|
|
3642dd3a73 | ||
|
|
603b1e9ed4 | ||
|
|
b7c6cba23f | ||
|
|
d430136f65 | ||
|
|
381b3d5016 | ||
|
|
e3f1e143e5 | ||
|
|
9f1cbb2ee7 | ||
|
|
d0ef9e672f | ||
|
|
b5c212f575 | ||
|
|
ff5ab43f9c | ||
|
|
68f947c7e0 | ||
|
|
e98456b025 | ||
|
|
75b332695b | ||
|
|
3e4bb695e4 | ||
|
|
7ba1b37a5a | ||
|
|
2886255c8b | ||
|
|
f7a9564e11 | ||
|
|
c1b0f115d0 | ||
|
|
2203d9a138 | ||
|
|
5adf94bd7d | ||
|
|
5fbf8ee6c6 | ||
|
|
8d9c86ac4c | ||
|
|
946ef4c685 | ||
|
|
c3773bc2d1 | ||
|
|
1b8c8b0a43 | ||
|
|
86d2c1184c | ||
|
|
817aea9f05 | ||
|
|
985c07b25b | ||
|
|
05ac27dfa8 | ||
|
|
bcce53a929 | ||
|
|
58922ba40b | ||
|
|
e6b8b13f2e | ||
|
|
ac675c4443 | ||
|
|
ae6a558662 | ||
|
|
64e44d1709 | ||
|
|
f35ae2355f | ||
|
|
3a857c83e6 | ||
|
|
d9edcb2250 | ||
|
|
d129d7951c | ||
|
|
5c246285da | ||
|
|
19c1722032 | ||
|
|
cd9a58231b | ||
|
|
d85b5b9134 | ||
|
|
8bd74d5abf | ||
|
|
2af2e2be67 | ||
|
|
43a3b827a3 | ||
|
|
3c6de0bf3e | ||
|
|
13a724864d | ||
|
|
baf536eb2b | ||
|
|
5200668336 | ||
|
|
7eeffb16e2 | ||
|
|
300909341e | ||
|
|
277d21cccb | ||
|
|
de184051f0 | ||
|
|
95ee72556f | ||
|
|
f48364914b | ||
|
|
19df70efad | ||
|
|
9813609645 | ||
|
|
975d0a1651 | ||
|
|
3c3571713e | ||
|
|
aa6254a3b4 | ||
|
|
0b05d2939a | ||
|
|
6f33163f88 | ||
|
|
93101b4d9a | ||
|
|
d8222a15ca | ||
|
|
1728513634 | ||
|
|
68fa81ec82 | ||
|
|
c051c89176 | ||
|
|
8b2a63e545 | ||
|
|
fcd470fcac | ||
|
|
fd8fe15d28 | ||
|
|
e80315f504 | ||
|
|
ae9e7acd77 | ||
|
|
149eb38e84 | ||
|
|
d777184fd5 | ||
|
|
1653e5eebe | ||
|
|
1f4826ca01 | ||
|
|
ef700b2688 | ||
|
|
cedc1bada2 | ||
|
|
0c709afe5c | ||
|
|
1c5d07871f | ||
|
|
e11fc8c131 | ||
|
|
2edef89a8d | ||
|
|
cbe7de58ab | ||
|
|
801160c430 | ||
|
|
cb2a814296 | ||
|
|
db78b91ec2 | ||
|
|
25a11c5bb7 | ||
|
|
1f41521c21 | ||
|
|
e686d42262 | ||
|
|
9bca3f8fd7 | ||
|
|
6ef3542c6c | ||
|
|
db299a876e | ||
|
|
737d04361b | ||
|
|
0d2366b432 | ||
|
|
b13345ceb2 | ||
|
|
f15dce9ee3 | ||
|
|
b718e66b26 | ||
|
|
a55a7603dd | ||
|
|
64fa343d16 | ||
|
|
6b02eebe36 | ||
|
|
d0f5318b75 | ||
|
|
5fe0d50cee | ||
|
|
4d7caa3458 | ||
|
|
856466320d | ||
|
|
3bd53556ca | ||
|
|
c74854aec0 | ||
|
|
294128d43a | ||
|
|
3f59a579d7 | ||
|
|
e5cf4ea60e | ||
|
|
768ca2d3f0 | ||
|
|
92e9b1bbb1 | ||
|
|
446932e076 | ||
|
|
0469edcc0c | ||
|
|
3823ae5890 | ||
|
|
14d71fb598 | ||
|
|
a031507443 | ||
|
|
4f5c052dc8 | ||
|
|
90e013554c | ||
|
|
30ea3cb702 | ||
|
|
a5147a382d | ||
|
|
74bf6cd186 | ||
|
|
15ddbb5e6f | ||
|
|
547df0b5fe | ||
|
|
8ae46a8a14 | ||
|
|
9753077661 | ||
|
|
22e7393b9d | ||
|
|
943c676768 | ||
|
|
4ecfe1fec5 | ||
|
|
5fac4f8737 | ||
|
|
a5394fa2ce | ||
|
|
8dc4d122b9 | ||
|
|
0eb482f35b | ||
|
|
bd52937c88 | ||
|
|
d5b321af3f | ||
|
|
f3b46bf7e2 | ||
|
|
2008986f83 | ||
|
|
1a57951d72 | ||
|
|
7655d7f662 | ||
|
|
373857d0f2 | ||
|
|
6719af9ba9 | ||
|
|
19c9091d5b | ||
|
|
84e2071a32 | ||
|
|
b3b9e1dabb | ||
|
|
91a35ded18 | ||
|
|
2d68594a86 | ||
|
|
f3d19f9691 | ||
|
|
94047de8b4 | ||
|
|
1c450e27d3 | ||
|
|
c0ccffa1c3 | ||
|
|
bbc76cb833 | ||
|
|
94f3cf1a4c | ||
|
|
2aa8847b78 | ||
|
|
049e858ef7 | ||
|
|
8e491ace5c | ||
|
|
dcf9d85e8d | ||
|
|
460c0da176 | ||
|
|
295a248561 | ||
|
|
4630f9c746 | ||
|
|
ba66beb487 | ||
|
|
b5cb38641a | ||
|
|
4b37d30c0d | ||
|
|
59ba7917c4 | ||
|
|
8d0ff01a59 | ||
|
|
100fb0c5d6 | ||
|
|
b75cd2514e | ||
|
|
7693ba8797 | ||
|
|
3d6b06696e | ||
|
|
0386061fdf | ||
|
|
3407b4d8dd | ||
|
|
71ff2a8356 | ||
|
|
8b809b8004 | ||
|
|
707a3a0a66 | ||
|
|
b798aa915c | ||
|
|
2db67c4101 | ||
|
|
80b4db08dc | ||
|
|
37cdee5101 | ||
|
|
b5366cba03 | ||
|
|
6cfda369ef | ||
|
|
5a57ed2536 | ||
|
|
13937fc103 | ||
|
|
17cd512284 | ||
|
|
97398ff209 | ||
|
|
2ffb63ff0c | ||
|
|
90bcb241cc | ||
|
|
f4f7cfd45a | ||
|
|
d214c047e9 | ||
|
|
fee8a86880 | ||
|
|
ea883b5e48 | ||
|
|
46296d777c | ||
|
|
79f0e894e9 | ||
|
|
6372183471 | ||
|
|
5963e7d1c5 | ||
|
|
c7618fc377 | ||
|
|
3fc932b041 | ||
|
|
97cdc96f7c | ||
|
|
892fe927c2 | ||
|
|
d51d456d80 | ||
|
|
836376c6c8 | ||
|
|
fa29eadb7a | ||
|
|
0cc0065f8c | ||
|
|
c3eac450ce | ||
|
|
7b738e045e | ||
|
|
3f6c17247f | ||
|
|
0551a9bfcd | ||
|
|
7c149ebf4f | ||
|
|
37b70eb73e | ||
|
|
451ea5308f | ||
|
|
a4d6954d4f | ||
|
|
c786533f22 | ||
|
|
406a625c98 | ||
|
|
171b2bdc20 | ||
|
|
4266ce73cb | ||
|
|
afa920cc94 | ||
|
|
701f116be3 | ||
|
|
5c7ea08bdf | ||
|
|
5e38996222 | ||
|
|
18febeabd1 | ||
|
|
be1500bf7d | ||
|
|
fea549679a | ||
|
|
11e1b569ea | ||
|
|
2bbf96d762 | ||
|
|
70394bae52 | ||
|
|
0c9e112f41 | ||
|
|
607b84d929 | ||
|
|
7a13cd1530 | ||
|
|
9b1afb68eb | ||
|
|
cf9d2965bf | ||
|
|
b1328c193b | ||
|
|
3d222caaae | ||
|
|
77ac6fa356 | ||
|
|
a3b46006a8 | ||
|
|
ea4716d039 | ||
|
|
db9e7a53f8 | ||
|
|
4432e055be | ||
|
|
403c2f436d | ||
|
|
594de43dec | ||
|
|
2e68c3fc11 | ||
|
|
2187f6f62e | ||
|
|
9249c38bf9 | ||
|
|
67e0ba5167 | ||
|
|
9004d8c3cd | ||
|
|
4df424438d | ||
|
|
6e3cd62e31 | ||
|
|
61b4bedc16 | ||
|
|
4e5de036c6 | ||
|
|
8e54b2e3f2 | ||
|
|
d39a51c134 | ||
|
|
6efc3d4913 | ||
|
|
55c31eec31 | ||
|
|
9820dcb201 | ||
|
|
9f29ce9591 | ||
|
|
afb0ff37bd | ||
|
|
67b6f08d89 | ||
|
|
892036bd7d | ||
|
|
d123ddedc8 | ||
|
|
fc243982e5 | ||
|
|
df66cd2205 | ||
|
|
a44d3c3eda | ||
|
|
297b33aa41 | ||
|
|
0d858cc036 | ||
|
|
f067947266 | ||
|
|
9ad6bd78f5 | ||
|
|
b1e220f2d2 | ||
|
|
200dc56c37 | ||
|
|
49992925e2 | ||
|
|
405e99d27f | ||
|
|
90ee7fe201 | ||
|
|
bc90fc885f | ||
|
|
5afa5fb085 | ||
|
|
93e2dc4f5f | ||
|
|
08d2a4279f | ||
|
|
d79b686992 | ||
|
|
1adec7ab51 | ||
|
|
3b029f2387 | ||
|
|
6d6afe8f52 | ||
|
|
e307947dd8 | ||
|
|
04ad1eef79 | ||
|
|
2b475b7916 | ||
|
|
f51f4a5843 | ||
|
|
b5f3bbead2 | ||
|
|
a192ae9314 | ||
|
|
7a07d8c2bc | ||
|
|
17a67e7922 | ||
|
|
328a3e2e6b | ||
|
|
597053c30e | ||
|
|
29bef1e3ab | ||
|
|
e36d62f08c | ||
|
|
50b4c7fa18 | ||
|
|
d86ef15d9a | ||
|
|
fa3eb11b6a | ||
|
|
beff31b003 | ||
|
|
2360fb293b | ||
|
|
1c82e3870a | ||
|
|
49ce9d2200 | ||
|
|
c20c9b53e1 | ||
|
|
89fc90ac80 | ||
|
|
072f5caa06 | ||
|
|
783f7a9b13 | ||
|
|
425e162a91 | ||
|
|
55b5d76e0b | ||
|
|
9693d014ba | ||
|
|
16abcf082c | ||
|
|
173336f256 | ||
|
|
f37316f2a0 | ||
|
|
e044e8efaa | ||
|
|
af99a55552 | ||
|
|
8f3d9d0149 | ||
|
|
344e30bef4 | ||
|
|
45ef4059f0 | ||
|
|
13174aac18 | ||
|
|
74f02363f4 | ||
|
|
10c421a94c | ||
|
|
3162227b54 | ||
|
|
7e647cc6e7 | ||
|
|
ec710d7ffd | ||
|
|
36718c39dc | ||
|
|
fca9753140 | ||
|
|
0529c3d5d2 | ||
|
|
5a27a95f8d | ||
|
|
5ec3a967b5 | ||
|
|
a45ec15a56 | ||
|
|
0164dec438 | ||
|
|
4edaa95cbf | ||
|
|
067e6b5ae7 | ||
|
|
6adb986167 | ||
|
|
cc4ca942c9 | ||
|
|
3202f12cb8 | ||
|
|
6448d71ca6 | ||
|
|
e3a3e07eef | ||
|
|
8a906e2959 | ||
|
|
9839b5cb53 | ||
|
|
430569d486 | ||
|
|
d3dfadbd9b | ||
|
|
e474e02a50 | ||
|
|
54d9cdaabf | ||
|
|
76fe3c1d76 | ||
|
|
261e56e61d | ||
|
|
ede0bb5396 | ||
|
|
186b85cd62 | ||
|
|
eab405af5b | ||
|
|
93999cec56 | ||
|
|
acacc0a4cb | ||
|
|
b2ae7089dc | ||
|
|
d4ab6b294a | ||
|
|
e6d89f6756 | ||
|
|
7ec29bbee7 | ||
|
|
f1d44a4c87 | ||
|
|
466f16eb1d | ||
|
|
04d54c0319 | ||
|
|
0367a2148a | ||
|
|
57e9e229de | ||
|
|
90c8d9d27b | ||
|
|
a30b6acc52 | ||
|
|
0ee7f952ef | ||
|
|
86656de971 | ||
|
|
2e649c3329 | ||
|
|
e868e44025 | ||
|
|
4376813951 | ||
|
|
ccd3e519ea | ||
|
|
c4ca3bd34d | ||
|
|
081baae883 | ||
|
|
a3d4befad4 | ||
|
|
2f13d2775f | ||
|
|
a36a2a1080 | ||
|
|
474c7865d7 | ||
|
|
bd205f63cc | ||
|
|
ac40eb8d87 | ||
|
|
3ea06d286a | ||
|
|
8d6984e286 | ||
|
|
cd773b8cc9 | ||
|
|
9bca69ebfb | ||
|
|
b33da6a09c | ||
|
|
7ae23d5567 | ||
|
|
569315ee3e | ||
|
|
4c7941adef | ||
|
|
93d116a9d0 | ||
|
|
7b2499c292 | ||
|
|
2be2bc5877 | ||
|
|
cfb853efbf | ||
|
|
2691164fc4 | ||
|
|
b113711a86 | ||
|
|
68e9530507 | ||
|
|
0ca23bb840 | ||
|
|
6e3d6c4269 | ||
|
|
c2eaa32036 | ||
|
|
604930db64 | ||
|
|
c3f99779f2 | ||
|
|
0518da1e49 | ||
|
|
6f6f032244 | ||
|
|
0acb2db9b6 | ||
|
|
74d26764f8 | ||
|
|
cf77a89123 | ||
|
|
1b73632f77 | ||
|
|
0a7cbf6fde | ||
|
|
e4701e26c8 | ||
|
|
045156985a | ||
|
|
257e795ca9 | ||
|
|
bafdc510d6 | ||
|
|
1840d05a37 | ||
|
|
6d5618447e | ||
|
|
b2de27b7be | ||
|
|
9c0d44fa09 | ||
|
|
f95eb2df0d | ||
|
|
cbb298ccb6 | ||
|
|
9e6940ed3e | ||
|
|
fbcc769d4e | ||
|
|
65f0378e43 | ||
|
|
f7a90f2660 | ||
|
|
3d825dcb3e | ||
|
|
2094a554f6 | ||
|
|
4837ae4958 | ||
|
|
6da9950b72 | ||
|
|
e8921787b3 | ||
|
|
d2d6904c9b | ||
|
|
510f0593e9 | ||
|
|
7a438f8999 | ||
|
|
113af85c3c | ||
|
|
916bacb60e | ||
|
|
a98b5ca97e | ||
|
|
076fe8ca3a | ||
|
|
b08327cb4b | ||
|
|
f1b868d5d9 | ||
|
|
76ff004ea5 | ||
|
|
df173764d2 | ||
|
|
7fa25934af | ||
|
|
649c3d0732 | ||
|
|
35c56237a0 | ||
|
|
236cc6f526 | ||
|
|
a311f88c99 | ||
|
|
e92bc25216 | ||
|
|
0c06d84e22 | ||
|
|
77c8261fca | ||
|
|
91a2e71fff | ||
|
|
0fb47fed9e | ||
|
|
4519c6ab29 | ||
|
|
32c6431dbc | ||
|
|
925964ac28 | ||
|
|
4d4d3bb965 | ||
|
|
3d526b3a87 | ||
|
|
f91582e060 | ||
|
|
dec60fdd4c | ||
|
|
31930159b8 | ||
|
|
6e2611c86c | ||
|
|
7574107d8c | ||
|
|
58d8b0dd01 | ||
|
|
49f78bacef | ||
|
|
3b190467c1 | ||
|
|
804a090457 | ||
|
|
14cfb310e3 | ||
|
|
f607a334ac | ||
|
|
117b84116e | ||
|
|
171dd5c737 | ||
|
|
b5ed4af25a | ||
|
|
b6c683a1b8 | ||
|
|
5200ec0b9a | ||
|
|
307cbf1d9f | ||
|
|
e7ecdb01a6 | ||
|
|
383bfd7583 | ||
|
|
508ea8bc0a | ||
|
|
077de17cd5 | ||
|
|
f6c07c996b | ||
|
|
7ba0bfffa2 | ||
|
|
9b577fa32c | ||
|
|
94cda3e837 | ||
|
|
f09f91e25a | ||
|
|
235bec6481 | ||
|
|
ee616ee6dd | ||
|
|
5153068a64 | ||
|
|
701e441349 | ||
|
|
5817a035f9 | ||
|
|
ea76f46223 | ||
|
|
0759b29ca2 | ||
|
|
db6074e035 | ||
|
|
6057ba0988 | ||
|
|
2fdcf1756e | ||
|
|
f489736e06 | ||
|
|
15f13209cf | ||
|
|
dbf3b7ad6d | ||
|
|
3341077587 | ||
|
|
e39d7021e0 | ||
|
|
cffaf30760 | ||
|
|
bc60cf0a35 | ||
|
|
9bb9807252 | ||
|
|
8b8fdb48bb | ||
|
|
b4437ccd2b | ||
|
|
65ac4dedcc | ||
|
|
671654da71 | ||
|
|
31490417d1 | ||
|
|
17e8c91267 | ||
|
|
db7dccf349 | ||
|
|
71d3f71e22 | ||
|
|
13a54c3f56 | ||
|
|
d58a1b1359 | ||
|
|
bb87a350ac | ||
|
|
c441a848e7 | ||
|
|
f14a5c7346 | ||
|
|
92219b5aad | ||
|
|
291201db1c | ||
|
|
c8ea6d7bfb | ||
|
|
9c70befaf6 | ||
|
|
fcadb807f6 | ||
|
|
4364775dcb | ||
|
|
2a196e91a6 | ||
|
|
7a0358827a | ||
|
|
62e2deafca | ||
|
|
25b4e68fbb | ||
|
|
c909319413 | ||
|
|
c7ee8ac1c7 | ||
|
|
2386eed703 | ||
|
|
ada558bedc | ||
|
|
6caca3aaf7 | ||
|
|
3d3bc4c512 | ||
|
|
044ed624eb | ||
|
|
4dff0c5dff | ||
|
|
59d8f926c8 | ||
|
|
c6f1900a93 | ||
|
|
d94a9cd864 | ||
|
|
21db8e3be4 | ||
|
|
e05bbec879 | ||
|
|
240e0dfa6f | ||
|
|
ab6a01b476 | ||
|
|
dce01cf002 | ||
|
|
da84ba06c7 | ||
|
|
45ba3ca07b | ||
|
|
56407a910d | ||
|
|
e624c33d51 | ||
|
|
3666462076 | ||
|
|
da0d9aab39 | ||
|
|
ace04b3ef4 | ||
|
|
1a4c2e77c4 | ||
|
|
f3c78fe73d | ||
|
|
a17c0e5bf6 | ||
|
|
20d5fdea2c |
@@ -8,8 +8,6 @@ body:
|
||||
label: Self Checks
|
||||
description: "To make sure we get to you in time, please check the following :)"
|
||||
options:
|
||||
- label: This is only for bug report, if you would like to ask a quesion, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general).
|
||||
required: true
|
||||
- label: I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
|
||||
required: true
|
||||
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: "\U0001F4E7 Discussions"
|
||||
url: https://github.com/langgenius/dify/discussions/categories/general
|
||||
about: General discussions and request help from the community
|
||||
- name: "\U0001F4DA Dify user documentation"
|
||||
url: https://docs.dify.ai/getting-started/readme
|
||||
about: Documentation for users of Dify
|
||||
- name: "\U0001F4DA Dify dev documentation"
|
||||
url: https://docs.dify.ai/getting-started/install-self-hosted
|
||||
about: Documentation for people interested in developing and contributing for Dify
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: "🤝 Help Wanted"
|
||||
description: "Request help from the community [please use English :)]"
|
||||
labels:
|
||||
- help-wanted
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Self Checks
|
||||
description: "To make sure we get to you in time, please check the following :)"
|
||||
options:
|
||||
- label: I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
|
||||
required: true
|
||||
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
|
||||
required: true
|
||||
- label: "Pleas do not modify this template :) and fill in all the required fields."
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Provide a description of the help you need
|
||||
placeholder: Briefly describe what you need help with.
|
||||
validations:
|
||||
required: true
|
||||
@@ -26,7 +26,6 @@ jobs:
|
||||
HUGGINGFACE_TEXT2TEXT_GEN_ENDPOINT_URL: b
|
||||
HUGGINGFACE_EMBEDDINGS_ENDPOINT_URL: c
|
||||
MOCK_SWITCH: true
|
||||
CODE_MAX_STRING_LENGTH: 80000
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -42,11 +41,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pip install -r ./api/requirements.txt
|
||||
|
||||
- name: Run ModelRuntime
|
||||
- name: Run pytest
|
||||
run: pytest api/tests/integration_tests/model_runtime/anthropic api/tests/integration_tests/model_runtime/azure_openai api/tests/integration_tests/model_runtime/openai api/tests/integration_tests/model_runtime/chatglm api/tests/integration_tests/model_runtime/google api/tests/integration_tests/model_runtime/xinference api/tests/integration_tests/model_runtime/huggingface_hub/test_llm.py
|
||||
|
||||
- name: Run Tool
|
||||
run: pytest api/tests/integration_tests/tools/test_all_provider.py
|
||||
|
||||
- name: Run Workflow
|
||||
run: pytest api/tests/integration_tests/workflow
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Run Tool Pytest
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: ./api/requirements.txt
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r ./api/requirements.txt
|
||||
|
||||
- name: Run pytest
|
||||
run: pytest ./api/tests/integration_tests/tools/test_all_provider.py
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Run Pytest
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- deploy/dev
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
MOCK_SWITCH: true
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: ./api/requirements.txt
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r ./api/requirements.txt
|
||||
|
||||
- name: Run pytest
|
||||
run: pytest api/tests/integration_tests/workflow
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- "main"
|
||||
- "deploy/dev"
|
||||
- "feat/workflow"
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
@@ -46,7 +47,7 @@ jobs:
|
||||
with:
|
||||
images: ${{ env[matrix.image_name_env] }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && startsWith(github.ref, 'refs/tags/') }}
|
||||
type=ref,event=branch
|
||||
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
|
||||
type=raw,value=${{ github.ref_name }},enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ In terms of licensing, please take a minute to read our short [License and Contr
|
||||
| Feature Type | Priority |
|
||||
| ------------------------------------------------------------ | --------------- |
|
||||
| High-Priority Features as being labeled by a team member | High Priority |
|
||||
| Popular feature requests from our [community feedback board](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Medium Priority |
|
||||
| Popular feature requests from our [community feedback board](https://feedback.dify.ai/) | Medium Priority |
|
||||
| Non-core features and minor enhancements | Low Priority |
|
||||
| Valuable but not immediate | Future-Feature |
|
||||
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@
|
||||
| Feature Type | Priority |
|
||||
| ------------------------------------------------------------ | --------------- |
|
||||
| High-Priority Features as being labeled by a team member | High Priority |
|
||||
| Popular feature requests from our [community feedback board](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Medium Priority |
|
||||
| Popular feature requests from our [community feedback board](https://feedback.dify.ai/) | Medium Priority |
|
||||
| Non-core features and minor enhancements | Low Priority |
|
||||
| Valuable but not immediate | Future-Feature |
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://dify.ai)
|
||||
[](https://dify.ai)
|
||||
<p align="center">
|
||||
<a href="./README.md">English</a> |
|
||||
<a href="./README_CN.md">简体中文</a> |
|
||||
@@ -29,35 +29,16 @@
|
||||
|
||||
**Dify** is an open-source LLM app development platform. Dify's intuitive interface combines a RAG pipeline, AI workflow orchestration, agent capabilities, model management, observability features and more, letting you quickly go from prototype to production.
|
||||
|
||||
https://github.com/langgenius/dify/assets/13230914/979e7a68-f067-4bbc-b38e-2deb2cc2bbb5
|
||||

|
||||
|
||||
|
||||
## Using Dify Cloud
|
||||
|
||||
You can try out [Dify Cloud](https://dify.ai) now. It provides all the capabilities of the self-deployed version, and includes 200 free GPT-4 calls.
|
||||
## Using our Cloud Services
|
||||
|
||||
## Dify for Enterprise / Organizations
|
||||
|
||||
[Schedule a meeting with us](#Direct-Meetings) or [send us an email](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry) to discuss enterprise needs.
|
||||
|
||||
For startups and small businesses using AWS, check out [Dify Premium on AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6) and deploy it to your own AWS VPC with one-click. It's an affordable AMI offering with the option to create apps with custom logo and branding.
|
||||
|
||||
## Features
|
||||
|
||||

|
||||
|
||||
**1. Workflow**: Create and test complex AI workflows on a visual canvas, with pre-built nodes taking advantage of the power of all the following features and beyond.
|
||||
|
||||
**2. Extensive LLM support**: Seamless integration with hundreds of proprietary / open-source LLMs and dozens of inference providers, including GPT, Mistral, Llama2, and OpenAI API-compatible models. A full list of supported model providers is kept [here](https://docs.dify.ai/getting-started/readme/model-providers).
|
||||
|
||||
**3. Prompt IDE**: Visual orchestration of applications and services based on any LLMs. Easily share with your team.
|
||||
|
||||
**4. RAG Engine**: Includes various RAG capabilities based on full-text indexing or vector database embeddings, allowing direct upload of PDFs, TXTs, and other text formats.
|
||||
|
||||
**5. AI Agent**: Based on Function Calling and ReAct, the Agent inference framework allows users to customize tools, what you see is what you get. Dify provides more than a dozen built-in tools for AI agents, such as Google Search, DELL·E, Stable Diffusion, WolframAlpha, etc.
|
||||
|
||||
**6. LLMOps**: Monitor and analyze application logs and performance, continuously improving Prompts, datasets, or models based on production data.
|
||||
You can try out [Dify.AI Cloud](https://dify.ai) now. It provides all the capabilities of the self-deployed version, and includes 200 free requests to OpenAI GPT-3.5.
|
||||
|
||||
### Looking to purchase via AWS?
|
||||
Check out [Dify Premium on AWS](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6) and deploy it to your own AWS VPC with one-click.
|
||||
|
||||
## Dify vs. LangChain vs. Assistants API
|
||||
|
||||
@@ -71,10 +52,28 @@ For startups and small businesses using AWS, check out [Dify Premium on AWS Mark
|
||||
| **Local Deployment** | Supported | Not Supported | Not Applicable |
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||

|
||||
|
||||
**1. LLM Support**: Integration with OpenAI's GPT family of models, or the open-source Llama2 family models. In fact, Dify supports mainstream commercial models and open-source models (locally deployed or based on MaaS).
|
||||
|
||||
**2. Prompt IDE**: Visual orchestration of applications and services based on LLMs with your team.
|
||||
|
||||
**3. RAG Engine**: Includes various RAG capabilities based on full-text indexing or vector database embeddings, allowing direct upload of PDFs, TXTs, and other text formats.
|
||||
|
||||
**4. AI Agent**: Based on Function Calling and ReAct, the Agent inference framework allows users to customize tools, what you see is what you get. Dify provides more than a dozen built-in tool calling capabilities, such as Google Search, DELL·E, Stable Diffusion, WolframAlpha, etc.
|
||||
|
||||
|
||||
**5. Continuous Operations**: Monitor and analyze application logs and performance, continuously improving Prompts, datasets, or models using production data.
|
||||
|
||||
## Before You Start
|
||||
|
||||
**Star us on GitHub, and be instantly notified for new releases!**
|
||||
|
||||

|
||||
|
||||
- [Website](https://dify.ai)
|
||||
- [Docs](https://docs.dify.ai)
|
||||
- [Deployment Docs](https://docs.dify.ai/getting-started/install-self-hosted)
|
||||
@@ -139,18 +138,21 @@ We are looking for contributors to help with translating Dify to languages other
|
||||
|
||||
## Community & Support
|
||||
|
||||
* [Github Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and asking questions.
|
||||
* [Canny](https://feedback.dify.ai/). Best for: sharing feedback and checking out our feature roadmap.
|
||||
* [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||
* [Email Support](mailto:hello@dify.ai?subject=[GitHub]Questions%20About%20Dify). Best for: questions you have about using Dify.AI.
|
||||
* [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
|
||||
* [Twitter](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
|
||||
* [Business Contact](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry). Best for: business inquiries of licensing Dify.AI for commercial use.
|
||||
|
||||
### Direct Meetings
|
||||
|
||||
**Help us make Dify better. Reach out directly to us**.
|
||||
|
||||
| Point of Contact | Purpose |
|
||||
| :----------------------------------------------------------: | :----------------------------------------------------------: |
|
||||
| <a href='https://cal.com/guchenhe/15min' target='_blank'><img src='https://i.postimg.cc/fWBqSmjP/Git-Hub-README-Button-3x.png' border='0' alt='Git-Hub-README-Button-3x' height="60" width="214"/></a> | Business enquiries & product feedback. |
|
||||
| <a href='https://cal.com/pinkbanana' target='_blank'><img src='https://i.postimg.cc/LsRTh87D/Git-Hub-README-Button-2x.png' border='0' alt='Git-Hub-README-Button-2x' height="60" width="225"/></a> | Contributions, issues & feature requests |
|
||||
| <a href='https://cal.com/guchenhe/15min' target='_blank'><img src='https://i.postimg.cc/fWBqSmjP/Git-Hub-README-Button-3x.png' border='0' alt='Git-Hub-README-Button-3x' height="60" width="214"/></a> | Product design feedback, user experience discussions, feature planning and roadmaps. |
|
||||
| <a href='https://cal.com/pinkbanana' target='_blank'><img src='https://i.postimg.cc/LsRTh87D/Git-Hub-README-Button-2x.png' border='0' alt='Git-Hub-README-Button-2x' height="60" width="225"/></a> | Technical support, issues, or feature requests |
|
||||
|
||||
## Security Disclosure
|
||||
|
||||
|
||||
@@ -114,7 +114,6 @@ docker compose up -d
|
||||
|
||||
我们欢迎您为 Dify 做出贡献,以帮助改善 Dify。包括:提交代码、问题、新想法,或分享您基于 Dify 创建的有趣且有用的 AI 应用程序。同时,我们也欢迎您在不同的活动、会议和社交媒体上分享 Dify。
|
||||
|
||||
- [Github Discussion](https://github.com/langgenius/dify/discussions). 👉:分享您的应用程序并与社区交流。
|
||||
- [GitHub Issues](https://github.com/langgenius/dify/issues)。👉:使用 Dify.AI 时遇到的错误和问题,请参阅[贡献指南](CONTRIBUTING.md)。
|
||||
- [电子邮件支持](mailto:hello@dify.ai?subject=[GitHub]Questions%20About%20Dify)。👉:关于使用 Dify.AI 的问题。
|
||||
- [Discord](https://discord.gg/FngNHpbcY7)。👉:分享您的应用程序并与社区交流。
|
||||
|
||||
@@ -115,7 +115,6 @@ docker compose up -d
|
||||
|
||||
Difyに貢献していただき、コードの提出、問題の報告、新しいアイデアの提供、またはDifyを基に作成した興味深く有用なAIアプリケーションの共有により、Difyをより良いものにするお手伝いを歓迎します。同時に、さまざまなイベント、会議、ソーシャルメディアでDifyを共有することも歓迎します。
|
||||
|
||||
- [Github Discussion](https://github.com/langgenius/dify/discussions). 👉:アプリを共有し、コミュニティとコミュニケーション。
|
||||
- [GitHub Issues](https://github.com/langgenius/dify/issues)。最適な使用法:Dify.AIの使用中に遭遇するバグやエラー、[貢献ガイド](CONTRIBUTING.md)を参照。
|
||||
- [Email サポート](mailto:hello@dify.ai?subject=[GitHub]Questions%20About%20Dify)。最適な使用法:Dify.AIの使用に関する質問。
|
||||
- [Discord](https://discord.gg/FngNHpbcY7)。最適な使用法:アプリケーションの共有とコミュニティとの交流。
|
||||
|
||||
Vendored
+2
-2
@@ -6,7 +6,7 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Celery",
|
||||
"type": "debugpy",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "celery",
|
||||
"justMyCode": true,
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Python: Flask",
|
||||
"type": "debugpy",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "flask",
|
||||
"env": {
|
||||
|
||||
+6
-8
@@ -17,16 +17,16 @@
|
||||
```bash
|
||||
sed -i "/^SECRET_KEY=/c\SECRET_KEY=$(openssl rand -base64 42)" .env
|
||||
```
|
||||
4. If you use Anaconda, create a new environment and activate it
|
||||
3.5 If you use annaconda, create a new environment and activate it
|
||||
```bash
|
||||
conda create --name dify python=3.10
|
||||
conda activate dify
|
||||
```
|
||||
5. Install dependencies
|
||||
4. Install dependencies
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
6. Run migrate
|
||||
5. Run migrate
|
||||
|
||||
Before the first launch, migrate the database to the latest version.
|
||||
|
||||
@@ -47,11 +47,9 @@
|
||||
pip install -r requirements.txt --upgrade --force-reinstall
|
||||
```
|
||||
|
||||
7. Start backend:
|
||||
6. Start backend:
|
||||
```bash
|
||||
flask run --host 0.0.0.0 --port=5001 --debug
|
||||
```
|
||||
8. Setup your application by visiting http://localhost:5001/console/api/setup or other apis...
|
||||
9. If you need to debug local async processing, please start the worker service by running
|
||||
`celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail`.
|
||||
The started celery app handles the async tasks, e.g. dataset importing and documents indexing.
|
||||
7. Setup your application by visiting http://localhost:5001/console/api/setup or other apis...
|
||||
8. If you need to debug local async processing, you can run `celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail`, celery can do dataset importing and other async tasks.
|
||||
|
||||
+1
-3
@@ -67,7 +67,6 @@ DEFAULTS = {
|
||||
'CODE_EXECUTION_ENDPOINT': '',
|
||||
'CODE_EXECUTION_API_KEY': '',
|
||||
'TOOL_ICON_CACHE_MAX_AGE': 3600,
|
||||
'MILVUS_DATABASE': 'default',
|
||||
'KEYWORD_DATA_SOURCE_TYPE': 'database',
|
||||
}
|
||||
|
||||
@@ -99,7 +98,7 @@ class Config:
|
||||
# ------------------------
|
||||
# General Configurations.
|
||||
# ------------------------
|
||||
self.CURRENT_VERSION = "0.6.1"
|
||||
self.CURRENT_VERSION = "0.6.0-preview-workflow.2"
|
||||
self.COMMIT_SHA = get_env('COMMIT_SHA')
|
||||
self.EDITION = "SELF_HOSTED"
|
||||
self.DEPLOY_ENV = get_env('DEPLOY_ENV')
|
||||
@@ -213,7 +212,6 @@ class Config:
|
||||
self.MILVUS_USER = get_env('MILVUS_USER')
|
||||
self.MILVUS_PASSWORD = get_env('MILVUS_PASSWORD')
|
||||
self.MILVUS_SECURE = get_env('MILVUS_SECURE')
|
||||
self.MILVUS_DATABASE = get_env('MILVUS_DATABASE')
|
||||
|
||||
# weaviate settings
|
||||
self.WEAVIATE_ENDPOINT = get_env('WEAVIATE_ENDPOINT')
|
||||
|
||||
@@ -12,7 +12,6 @@ from controllers.console import api
|
||||
from controllers.console.app.wraps import get_app_model
|
||||
from controllers.console.setup import setup_required
|
||||
from controllers.console.wraps import account_initialization_required
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from extensions.ext_database import db
|
||||
from fields.conversation_fields import (
|
||||
conversation_detail_fields,
|
||||
@@ -203,7 +202,7 @@ class ChatConversationApi(Resource):
|
||||
)
|
||||
|
||||
if app_model.mode == AppMode.ADVANCED_CHAT.value:
|
||||
query = query.where(Conversation.invoke_from != InvokeFrom.DEBUGGER.value)
|
||||
query = query.where(Conversation.override_model_configs.is_(None))
|
||||
|
||||
query = query.order_by(Conversation.created_at.desc())
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ from werkzeug.exceptions import NotFound
|
||||
from controllers.console import api
|
||||
from controllers.console.explore.error import NotChatAppError
|
||||
from controllers.console.explore.wraps import InstalledAppResource
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
|
||||
from libs.helper import uuid_value
|
||||
from models.model import AppMode
|
||||
@@ -40,8 +39,8 @@ class ConversationListApi(InstalledAppResource):
|
||||
user=current_user,
|
||||
last_id=args['last_id'],
|
||||
limit=args['limit'],
|
||||
invoke_from=InvokeFrom.EXPLORE,
|
||||
pinned=pinned,
|
||||
exclude_debug_conversation=True
|
||||
)
|
||||
except LastConversationNotExistsError:
|
||||
raise NotFound("Last Conversation Not Exists.")
|
||||
|
||||
@@ -6,7 +6,6 @@ import services
|
||||
from controllers.service_api import api
|
||||
from controllers.service_api.app.error import NotChatAppError
|
||||
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
|
||||
from libs.helper import uuid_value
|
||||
from models.model import App, AppMode, EndUser
|
||||
@@ -28,13 +27,7 @@ class ConversationApi(Resource):
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
return ConversationService.pagination_by_last_id(
|
||||
app_model=app_model,
|
||||
user=end_user,
|
||||
last_id=args['last_id'],
|
||||
limit=args['limit'],
|
||||
invoke_from=InvokeFrom.SERVICE_API
|
||||
)
|
||||
return ConversationService.pagination_by_last_id(app_model, end_user, args['last_id'], args['limit'])
|
||||
except services.errors.conversation.LastConversationNotExistsError:
|
||||
raise NotFound("Last Conversation Not Exists.")
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from werkzeug.exceptions import NotFound
|
||||
from controllers.web import api
|
||||
from controllers.web.error import NotChatAppError
|
||||
from controllers.web.wraps import WebApiResource
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
|
||||
from libs.helper import uuid_value
|
||||
from models.model import AppMode
|
||||
@@ -38,8 +37,7 @@ class ConversationListApi(WebApiResource):
|
||||
user=end_user,
|
||||
last_id=args['last_id'],
|
||||
limit=args['limit'],
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
pinned=pinned,
|
||||
pinned=pinned
|
||||
)
|
||||
except LastConversationNotExistsError:
|
||||
raise NotFound("Last Conversation Not Exists.")
|
||||
|
||||
@@ -687,4 +687,4 @@ class CotAgentRunner(BaseAgentRunner):
|
||||
try:
|
||||
return json.dumps(tools, ensure_ascii=False)
|
||||
except json.JSONDecodeError:
|
||||
return json.dumps(tools)
|
||||
return json.dumps(tools)
|
||||
|
||||
@@ -207,25 +207,19 @@ class FunctionCallAgentRunner(BaseAgentRunner):
|
||||
)
|
||||
)
|
||||
|
||||
assistant_message = AssistantPromptMessage(
|
||||
content='',
|
||||
tool_calls=[]
|
||||
)
|
||||
if tool_calls:
|
||||
assistant_message.tool_calls=[
|
||||
AssistantPromptMessage.ToolCall(
|
||||
prompt_messages.append(AssistantPromptMessage(
|
||||
content='',
|
||||
name='',
|
||||
tool_calls=[AssistantPromptMessage.ToolCall(
|
||||
id=tool_call[0],
|
||||
type='function',
|
||||
function=AssistantPromptMessage.ToolCall.ToolCallFunction(
|
||||
name=tool_call[1],
|
||||
arguments=json.dumps(tool_call[2], ensure_ascii=False)
|
||||
)
|
||||
) for tool_call in tool_calls
|
||||
]
|
||||
else:
|
||||
assistant_message.content = response
|
||||
|
||||
prompt_messages.append(assistant_message)
|
||||
) for tool_call in tool_calls]
|
||||
))
|
||||
|
||||
# save thought
|
||||
self.save_agent_thought(
|
||||
@@ -245,6 +239,12 @@ class FunctionCallAgentRunner(BaseAgentRunner):
|
||||
|
||||
final_answer += response + '\n'
|
||||
|
||||
# update prompt messages
|
||||
if response.strip():
|
||||
prompt_messages.append(AssistantPromptMessage(
|
||||
content=response,
|
||||
))
|
||||
|
||||
# call tools
|
||||
tool_responses = []
|
||||
for tool_call_id, tool_call_name, tool_call_args in tool_calls:
|
||||
|
||||
@@ -215,16 +215,16 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc
|
||||
elif isinstance(event, QueueStopEvent | QueueWorkflowSucceededEvent | QueueWorkflowFailedEvent):
|
||||
workflow_run = self._handle_workflow_finished(event)
|
||||
if workflow_run:
|
||||
yield self._workflow_finish_to_stream_response(
|
||||
task_id=self._application_generate_entity.task_id,
|
||||
workflow_run=workflow_run
|
||||
)
|
||||
|
||||
if workflow_run.status == WorkflowRunStatus.FAILED.value:
|
||||
err_event = QueueErrorEvent(error=ValueError(f'Run failed: {workflow_run.error}'))
|
||||
yield self._error_to_stream_response(self._handle_error(err_event, self._message))
|
||||
break
|
||||
|
||||
yield self._workflow_finish_to_stream_response(
|
||||
task_id=self._application_generate_entity.task_id,
|
||||
workflow_run=workflow_run
|
||||
)
|
||||
|
||||
if isinstance(event, QueueStopEvent):
|
||||
# Save message
|
||||
self._save_message()
|
||||
|
||||
@@ -122,7 +122,7 @@ class MessageEndStreamResponse(StreamResponse):
|
||||
"""
|
||||
event: StreamEvent = StreamEvent.MESSAGE_END
|
||||
id: str
|
||||
metadata: dict = {}
|
||||
metadata: Optional[dict] = None
|
||||
|
||||
|
||||
class MessageFileStreamResponse(StreamResponse):
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
import os
|
||||
from typing import Any, Optional, TextIO, Union
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from langchain.callbacks.base import BaseCallbackHandler
|
||||
from langchain.input import print_text
|
||||
from pydantic import BaseModel
|
||||
|
||||
_TEXT_COLOR_MAPPING = {
|
||||
"blue": "36;1",
|
||||
"yellow": "33;1",
|
||||
"pink": "38;5;200",
|
||||
"green": "32;1",
|
||||
"red": "31;1",
|
||||
}
|
||||
|
||||
def get_colored_text(text: str, color: str) -> str:
|
||||
"""Get colored text."""
|
||||
color_str = _TEXT_COLOR_MAPPING[color]
|
||||
return f"\u001b[{color_str}m\033[1;3m{text}\u001b[0m"
|
||||
|
||||
|
||||
def print_text(
|
||||
text: str, color: Optional[str] = None, end: str = "", file: Optional[TextIO] = None
|
||||
) -> None:
|
||||
"""Print text with highlighting and no end characters."""
|
||||
text_to_print = get_colored_text(text, color) if color else text
|
||||
print(text_to_print, end=end, file=file)
|
||||
if file:
|
||||
file.flush() # ensure all printed content are written to file
|
||||
|
||||
class DifyAgentCallbackHandler(BaseModel):
|
||||
class DifyAgentCallbackHandler(BaseCallbackHandler, BaseModel):
|
||||
"""Callback Handler that prints to std out."""
|
||||
color: Optional[str] = ''
|
||||
current_loop = 1
|
||||
|
||||
@@ -41,8 +41,7 @@ class CacheEmbedding(Embeddings):
|
||||
embedding_queue_embeddings = []
|
||||
try:
|
||||
model_type_instance = cast(TextEmbeddingModel, self._model_instance.model_type_instance)
|
||||
model_schema = model_type_instance.get_model_schema(self._model_instance.model,
|
||||
self._model_instance.credentials)
|
||||
model_schema = model_type_instance.get_model_schema(self._model_instance.model, self._model_instance.credentials)
|
||||
max_chunks = model_schema.model_properties[ModelPropertyKey.MAX_CHUNKS] \
|
||||
if model_schema and ModelPropertyKey.MAX_CHUNKS in model_schema.model_properties else 1
|
||||
for i in range(0, len(embedding_queue_texts), max_chunks):
|
||||
@@ -61,21 +60,15 @@ class CacheEmbedding(Embeddings):
|
||||
db.session.rollback()
|
||||
except Exception as e:
|
||||
logging.exception('Failed transform embedding: ', e)
|
||||
cache_embeddings = []
|
||||
try:
|
||||
for i, embedding in zip(embedding_queue_indices, embedding_queue_embeddings):
|
||||
text_embeddings[i] = embedding
|
||||
hash = helper.generate_text_hash(texts[i])
|
||||
if hash not in cache_embeddings:
|
||||
embedding_cache = Embedding(model_name=self._model_instance.model,
|
||||
hash=hash,
|
||||
provider_name=self._model_instance.provider)
|
||||
embedding_cache.set_embedding(embedding)
|
||||
db.session.add(embedding_cache)
|
||||
cache_embeddings.append(hash)
|
||||
db.session.commit()
|
||||
except IntegrityError:
|
||||
db.session.rollback()
|
||||
for i, embedding in zip(embedding_queue_indices, embedding_queue_embeddings):
|
||||
text_embeddings[i] = embedding
|
||||
hash = helper.generate_text_hash(texts[i])
|
||||
embedding_cache = Embedding(model_name=self._model_instance.model,
|
||||
hash=hash,
|
||||
provider_name=self._model_instance.provider)
|
||||
embedding_cache.set_embedding(embedding)
|
||||
db.session.add(embedding_cache)
|
||||
db.session.commit()
|
||||
except Exception as ex:
|
||||
db.session.rollback()
|
||||
logger.error('Failed to embed documents: ', ex)
|
||||
|
||||
@@ -47,7 +47,7 @@ class CodeExecutor:
|
||||
else:
|
||||
raise CodeExecutionException('Unsupported language')
|
||||
|
||||
runner, preload = template_transformer.transform_caller(code, inputs)
|
||||
runner = template_transformer.transform_caller(code, inputs)
|
||||
url = URL(CODE_EXECUTION_ENDPOINT) / 'v1' / 'sandbox' / 'run'
|
||||
headers = {
|
||||
'X-Api-Key': CODE_EXECUTION_API_KEY
|
||||
@@ -57,7 +57,6 @@ class CodeExecutor:
|
||||
'nodejs' if language == 'javascript' else
|
||||
'python3' if language == 'python3' else None,
|
||||
'code': runner,
|
||||
'preload': preload
|
||||
}
|
||||
|
||||
try:
|
||||
|
||||
@@ -18,27 +18,26 @@ result = `<<RESULT>>${output}<<RESULT>>`
|
||||
console.log(result)
|
||||
"""
|
||||
|
||||
NODEJS_PRELOAD = """"""
|
||||
|
||||
class NodeJsTemplateTransformer(TemplateTransformer):
|
||||
@classmethod
|
||||
def transform_caller(cls, code: str, inputs: dict) -> tuple[str, str]:
|
||||
def transform_caller(cls, code: str, inputs: dict) -> str:
|
||||
"""
|
||||
Transform code to python runner
|
||||
:param code: code
|
||||
:param inputs: inputs
|
||||
:return:
|
||||
"""
|
||||
|
||||
|
||||
# transform inputs to json string
|
||||
inputs_str = json.dumps(inputs, indent=4, ensure_ascii=False)
|
||||
inputs_str = json.dumps(inputs, indent=4)
|
||||
|
||||
# replace code and inputs
|
||||
runner = NODEJS_RUNNER.replace('{{code}}', code)
|
||||
runner = runner.replace('{{inputs}}', inputs_str)
|
||||
|
||||
return runner, NODEJS_PRELOAD
|
||||
|
||||
return runner
|
||||
|
||||
@classmethod
|
||||
def transform_response(cls, response: str) -> dict:
|
||||
"""
|
||||
|
||||
@@ -20,39 +20,9 @@ print(result)
|
||||
|
||||
"""
|
||||
|
||||
JINJA2_PRELOAD_TEMPLATE = """{% set fruits = ['Apple'] %}
|
||||
{{ 'a' }}
|
||||
{% for fruit in fruits %}
|
||||
<li>{{ fruit }}</li>
|
||||
{% endfor %}
|
||||
{% if fruits|length > 1 %}
|
||||
1
|
||||
{% endif %}
|
||||
{% for i in range(5) %}
|
||||
{% if i == 3 %}{{ i }}{% else %}{% endif %}
|
||||
{% endfor %}
|
||||
{% for i in range(3) %}
|
||||
{{ i + 1 }}
|
||||
{% endfor %}
|
||||
{% macro say_hello() %}a{{ 'b' }}{% endmacro %}
|
||||
{{ s }}{{ say_hello() }}"""
|
||||
|
||||
JINJA2_PRELOAD = f"""
|
||||
import jinja2
|
||||
|
||||
def _jinja2_preload_():
|
||||
# prepare jinja2 environment, load template and render before to avoid sandbox issue
|
||||
template = jinja2.Template('''{JINJA2_PRELOAD_TEMPLATE}''')
|
||||
template.render(s='a')
|
||||
|
||||
if __name__ == '__main__':
|
||||
_jinja2_preload_()
|
||||
|
||||
"""
|
||||
|
||||
class Jinja2TemplateTransformer(TemplateTransformer):
|
||||
@classmethod
|
||||
def transform_caller(cls, code: str, inputs: dict) -> tuple[str, str]:
|
||||
def transform_caller(cls, code: str, inputs: dict) -> str:
|
||||
"""
|
||||
Transform code to python runner
|
||||
:param code: code
|
||||
@@ -62,10 +32,10 @@ class Jinja2TemplateTransformer(TemplateTransformer):
|
||||
|
||||
# transform jinja2 template to python code
|
||||
runner = PYTHON_RUNNER.replace('{{code}}', code)
|
||||
runner = runner.replace('{{inputs}}', json.dumps(inputs, indent=4, ensure_ascii=False))
|
||||
|
||||
return runner, JINJA2_PRELOAD
|
||||
runner = runner.replace('{{inputs}}', json.dumps(inputs, indent=4))
|
||||
|
||||
return runner
|
||||
|
||||
@classmethod
|
||||
def transform_response(cls, response: str) -> dict:
|
||||
"""
|
||||
@@ -81,4 +51,4 @@ class Jinja2TemplateTransformer(TemplateTransformer):
|
||||
|
||||
return {
|
||||
'result': result
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,10 @@ result = f'''<<RESULT>>
|
||||
print(result)
|
||||
"""
|
||||
|
||||
PYTHON_PRELOAD = """"""
|
||||
|
||||
|
||||
class PythonTemplateTransformer(TemplateTransformer):
|
||||
@classmethod
|
||||
def transform_caller(cls, code: str, inputs: dict) -> tuple[str, str]:
|
||||
def transform_caller(cls, code: str, inputs: dict) -> str:
|
||||
"""
|
||||
Transform code to python runner
|
||||
:param code: code
|
||||
@@ -34,13 +32,13 @@ class PythonTemplateTransformer(TemplateTransformer):
|
||||
"""
|
||||
|
||||
# transform inputs to json string
|
||||
inputs_str = json.dumps(inputs, indent=4, ensure_ascii=False)
|
||||
inputs_str = json.dumps(inputs, indent=4)
|
||||
|
||||
# replace code and inputs
|
||||
runner = PYTHON_RUNNER.replace('{{code}}', code)
|
||||
runner = runner.replace('{{inputs}}', inputs_str)
|
||||
|
||||
return runner, PYTHON_PRELOAD
|
||||
return runner
|
||||
|
||||
@classmethod
|
||||
def transform_response(cls, response: str) -> dict:
|
||||
|
||||
@@ -4,12 +4,12 @@ from abc import ABC, abstractmethod
|
||||
class TemplateTransformer(ABC):
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def transform_caller(cls, code: str, inputs: dict) -> tuple[str, str]:
|
||||
def transform_caller(cls, code: str, inputs: dict) -> str:
|
||||
"""
|
||||
Transform code to python runner
|
||||
:param code: code
|
||||
:param inputs: inputs
|
||||
:return: runner, preload
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
+11
-40
@@ -19,7 +19,6 @@ from core.model_manager import ModelInstance, ModelManager
|
||||
from core.model_runtime.entities.model_entities import ModelType, PriceType
|
||||
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
|
||||
from core.model_runtime.model_providers.__base.text_embedding_model import TextEmbeddingModel
|
||||
from core.rag.datasource.keyword.keyword_factory import Keyword
|
||||
from core.rag.extractor.entity.extract_setting import ExtractSetting
|
||||
from core.rag.index_processor.index_processor_base import BaseIndexProcessor
|
||||
from core.rag.index_processor.index_processor_factory import IndexProcessorFactory
|
||||
@@ -658,25 +657,18 @@ class IndexingRunner:
|
||||
if embedding_model_instance:
|
||||
embedding_model_type_instance = embedding_model_instance.model_type_instance
|
||||
embedding_model_type_instance = cast(TextEmbeddingModel, embedding_model_type_instance)
|
||||
# create keyword index
|
||||
create_keyword_thread = threading.Thread(target=self._process_keyword_index,
|
||||
args=(current_app._get_current_object(),
|
||||
dataset.id, dataset_document.id, documents))
|
||||
create_keyword_thread.start()
|
||||
if dataset.indexing_technique == 'high_quality':
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
||||
futures = []
|
||||
for i in range(0, len(documents), chunk_size):
|
||||
chunk_documents = documents[i:i + chunk_size]
|
||||
futures.append(executor.submit(self._process_chunk, current_app._get_current_object(), index_processor,
|
||||
chunk_documents, dataset,
|
||||
dataset_document, embedding_model_instance,
|
||||
embedding_model_type_instance))
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
||||
futures = []
|
||||
for i in range(0, len(documents), chunk_size):
|
||||
chunk_documents = documents[i:i + chunk_size]
|
||||
futures.append(executor.submit(self._process_chunk, current_app._get_current_object(), index_processor,
|
||||
chunk_documents, dataset,
|
||||
dataset_document, embedding_model_instance,
|
||||
embedding_model_type_instance))
|
||||
|
||||
for future in futures:
|
||||
tokens += future.result()
|
||||
for future in futures:
|
||||
tokens += future.result()
|
||||
|
||||
create_keyword_thread.join()
|
||||
indexing_end_at = time.perf_counter()
|
||||
|
||||
# update document status to completed
|
||||
@@ -690,27 +682,6 @@ class IndexingRunner:
|
||||
}
|
||||
)
|
||||
|
||||
def _process_keyword_index(self, flask_app, dataset_id, document_id, documents):
|
||||
with flask_app.app_context():
|
||||
dataset = Dataset.query.filter_by(id=dataset_id).first()
|
||||
if not dataset:
|
||||
raise ValueError("no dataset found")
|
||||
keyword = Keyword(dataset)
|
||||
keyword.create(documents)
|
||||
if dataset.indexing_technique != 'high_quality':
|
||||
document_ids = [document.metadata['doc_id'] for document in documents]
|
||||
db.session.query(DocumentSegment).filter(
|
||||
DocumentSegment.document_id == document_id,
|
||||
DocumentSegment.index_node_id.in_(document_ids),
|
||||
DocumentSegment.status == "indexing"
|
||||
).update({
|
||||
DocumentSegment.status: "completed",
|
||||
DocumentSegment.enabled: True,
|
||||
DocumentSegment.completed_at: datetime.datetime.utcnow()
|
||||
})
|
||||
|
||||
db.session.commit()
|
||||
|
||||
def _process_chunk(self, flask_app, index_processor, chunk_documents, dataset, dataset_document,
|
||||
embedding_model_instance, embedding_model_type_instance):
|
||||
with flask_app.app_context():
|
||||
@@ -729,7 +700,7 @@ class IndexingRunner:
|
||||
)
|
||||
|
||||
# load index
|
||||
index_processor.load(dataset, chunk_documents, with_keywords=False)
|
||||
index_processor.load(dataset, chunk_documents)
|
||||
|
||||
document_ids = [document.metadata['doc_id'] for document in chunk_documents]
|
||||
db.session.query(DocumentSegment).filter(
|
||||
|
||||
@@ -166,7 +166,6 @@ class LLMGenerator:
|
||||
response = model_instance.invoke_llm(
|
||||
prompt_messages=prompt_messages,
|
||||
model_parameters={
|
||||
'temperature': 0.01,
|
||||
"max_tokens": 2000
|
||||
},
|
||||
stream=False
|
||||
|
||||
@@ -69,15 +69,13 @@ SUGGESTED_QUESTIONS_AFTER_ANSWER_INSTRUCTION_PROMPT = (
|
||||
)
|
||||
|
||||
GENERATOR_QA_PROMPT = (
|
||||
'<Task> The user will send a long text. Generate a Question and Answer pairs only using the knowledge in the long text. Please think step by step.'
|
||||
'The user will send a long text. Please think step by step.'
|
||||
'Step 1: Understand and summarize the main content of this text.\n'
|
||||
'Step 2: What key information or concepts are mentioned in this text?\n'
|
||||
'Step 3: Decompose or combine multiple pieces of information and concepts.\n'
|
||||
'Step 4: Generate questions and answers based on these key information and concepts.\n'
|
||||
'<Constraints> The questions should be clear and detailed, and the answers should be detailed and complete. '
|
||||
'You must answer in {language}, in a style that is clear and detailed in {language}. No language other than {language} should be used. \n'
|
||||
'<Format> Use the following format: Q1:\nA1:\nQ2:\nA2:...\n'
|
||||
'<QA Pairs>'
|
||||
'Step 4: Generate 20 questions and answers based on these key information and concepts.'
|
||||
'The questions should be clear and detailed, and the answers should be detailed and complete.\n'
|
||||
"Answer MUST according to the the language:{language} and in the following format: Q1:\nA1:\nQ2:\nA2:...\n"
|
||||
)
|
||||
|
||||
RULE_CONFIG_GENERATE_TEMPLATE = """Given MY INTENDED AUDIENCES and HOPING TO SOLVE using a language model, please select \
|
||||
|
||||
@@ -3,7 +3,6 @@ from core.file.message_file_parser import MessageFileParser
|
||||
from core.model_manager import ModelInstance
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
AssistantPromptMessage,
|
||||
ImagePromptMessageContent,
|
||||
PromptMessage,
|
||||
PromptMessageRole,
|
||||
TextPromptMessageContent,
|
||||
@@ -125,17 +124,7 @@ class TokenBufferMemory:
|
||||
else:
|
||||
continue
|
||||
|
||||
if isinstance(m.content, list):
|
||||
inner_msg = ""
|
||||
for content in m.content:
|
||||
if isinstance(content, TextPromptMessageContent):
|
||||
inner_msg += f"{content.data}\n"
|
||||
elif isinstance(content, ImagePromptMessageContent):
|
||||
inner_msg += "[image]\n"
|
||||
|
||||
string_messages.append(f"{role}: {inner_msg.strip()}")
|
||||
else:
|
||||
message = f"{role}: {m.content}"
|
||||
string_messages.append(message)
|
||||
message = f"{role}: {m.content}"
|
||||
string_messages.append(message)
|
||||
|
||||
return "\n".join(string_messages)
|
||||
@@ -5,7 +5,6 @@ model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
- tool-call
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 200000
|
||||
|
||||
@@ -5,7 +5,6 @@ model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
- tool-call
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 200000
|
||||
|
||||
@@ -5,7 +5,6 @@ model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
- tool-call
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 200000
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import base64
|
||||
import json
|
||||
import mimetypes
|
||||
from collections.abc import Generator
|
||||
from typing import Optional, Union, cast
|
||||
@@ -16,7 +15,6 @@ from anthropic.types import (
|
||||
MessageStreamEvent,
|
||||
completion_create_params,
|
||||
)
|
||||
from anthropic.types.beta.tools import ToolsBetaMessage
|
||||
from httpx import Timeout
|
||||
|
||||
from core.model_runtime.callbacks.base_callback import Callback
|
||||
@@ -29,7 +27,6 @@ from core.model_runtime.entities.message_entities import (
|
||||
PromptMessageTool,
|
||||
SystemPromptMessage,
|
||||
TextPromptMessageContent,
|
||||
ToolPromptMessage,
|
||||
UserPromptMessage,
|
||||
)
|
||||
from core.model_runtime.errors.invoke import (
|
||||
@@ -73,11 +70,10 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
||||
:return: full response or stream response chunk generator result
|
||||
"""
|
||||
# invoke model
|
||||
return self._chat_generate(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
|
||||
return self._chat_generate(model, credentials, prompt_messages, model_parameters, stop, stream, user)
|
||||
|
||||
def _chat_generate(self, model: str, credentials: dict,
|
||||
prompt_messages: list[PromptMessage], model_parameters: dict,
|
||||
tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
|
||||
prompt_messages: list[PromptMessage], model_parameters: dict, stop: Optional[list[str]] = None,
|
||||
stream: bool = True, user: Optional[str] = None) -> Union[LLMResult, Generator]:
|
||||
"""
|
||||
Invoke llm chat model
|
||||
@@ -113,26 +109,14 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
||||
if system:
|
||||
extra_model_kwargs['system'] = system
|
||||
|
||||
if tools:
|
||||
extra_model_kwargs['tools'] = [
|
||||
self._transform_tool_prompt(tool) for tool in tools
|
||||
]
|
||||
response = client.beta.tools.messages.create(
|
||||
model=model,
|
||||
messages=prompt_message_dicts,
|
||||
stream=stream,
|
||||
**model_parameters,
|
||||
**extra_model_kwargs
|
||||
)
|
||||
else:
|
||||
# chat model
|
||||
response = client.messages.create(
|
||||
model=model,
|
||||
messages=prompt_message_dicts,
|
||||
stream=stream,
|
||||
**model_parameters,
|
||||
**extra_model_kwargs
|
||||
)
|
||||
# chat model
|
||||
response = client.messages.create(
|
||||
model=model,
|
||||
messages=prompt_message_dicts,
|
||||
stream=stream,
|
||||
**model_parameters,
|
||||
**extra_model_kwargs
|
||||
)
|
||||
|
||||
if stream:
|
||||
return self._handle_chat_generate_stream_response(model, credentials, response, prompt_messages)
|
||||
@@ -164,13 +148,6 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
||||
|
||||
return self._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
|
||||
|
||||
def _transform_tool_prompt(self, tool: PromptMessageTool) -> dict:
|
||||
return {
|
||||
'name': tool.name,
|
||||
'description': tool.description,
|
||||
'input_schema': tool.parameters
|
||||
}
|
||||
|
||||
def _transform_chat_json_prompts(self, model: str, credentials: dict,
|
||||
prompt_messages: list[PromptMessage], model_parameters: dict,
|
||||
tools: list[PromptMessageTool] | None = None, stop: list[str] | None = None,
|
||||
@@ -216,18 +193,7 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
||||
prompt = self._convert_messages_to_prompt_anthropic(prompt_messages)
|
||||
|
||||
client = Anthropic(api_key="")
|
||||
tokens = client.count_tokens(prompt)
|
||||
|
||||
tool_call_inner_prompts_tokens_map = {
|
||||
'claude-3-opus-20240229': 395,
|
||||
'claude-3-haiku-20240307': 264,
|
||||
'claude-3-sonnet-20240229': 159
|
||||
}
|
||||
|
||||
if model in tool_call_inner_prompts_tokens_map and tools:
|
||||
tokens += tool_call_inner_prompts_tokens_map[model]
|
||||
|
||||
return tokens
|
||||
return client.count_tokens(prompt)
|
||||
|
||||
def validate_credentials(self, model: str, credentials: dict) -> None:
|
||||
"""
|
||||
@@ -253,7 +219,7 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
||||
except Exception as ex:
|
||||
raise CredentialsValidateFailedError(str(ex))
|
||||
|
||||
def _handle_chat_generate_response(self, model: str, credentials: dict, response: Union[Message, ToolsBetaMessage],
|
||||
def _handle_chat_generate_response(self, model: str, credentials: dict, response: Message,
|
||||
prompt_messages: list[PromptMessage]) -> LLMResult:
|
||||
"""
|
||||
Handle llm chat response
|
||||
@@ -266,24 +232,9 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
||||
"""
|
||||
# transform assistant message to prompt message
|
||||
assistant_prompt_message = AssistantPromptMessage(
|
||||
content='',
|
||||
tool_calls=[]
|
||||
content=response.content[0].text
|
||||
)
|
||||
|
||||
for content in response.content:
|
||||
if content.type == 'text':
|
||||
assistant_prompt_message.content += content.text
|
||||
elif content.type == 'tool_use':
|
||||
tool_call = AssistantPromptMessage.ToolCall(
|
||||
id=content.id,
|
||||
type='function',
|
||||
function=AssistantPromptMessage.ToolCall.ToolCallFunction(
|
||||
name=content.name,
|
||||
arguments=json.dumps(content.input)
|
||||
)
|
||||
)
|
||||
assistant_prompt_message.tool_calls.append(tool_call)
|
||||
|
||||
# calculate num tokens
|
||||
if response.usage:
|
||||
# transform usage
|
||||
@@ -405,90 +356,69 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
||||
prompt_message_dicts = []
|
||||
for message in prompt_messages:
|
||||
if not isinstance(message, SystemPromptMessage):
|
||||
if isinstance(message, UserPromptMessage):
|
||||
message = cast(UserPromptMessage, message)
|
||||
if isinstance(message.content, str):
|
||||
message_dict = {"role": "user", "content": message.content}
|
||||
prompt_message_dicts.append(message_dict)
|
||||
else:
|
||||
sub_messages = []
|
||||
for message_content in message.content:
|
||||
if message_content.type == PromptMessageContentType.TEXT:
|
||||
message_content = cast(TextPromptMessageContent, message_content)
|
||||
sub_message_dict = {
|
||||
"type": "text",
|
||||
"text": message_content.data
|
||||
}
|
||||
sub_messages.append(sub_message_dict)
|
||||
elif message_content.type == PromptMessageContentType.IMAGE:
|
||||
message_content = cast(ImagePromptMessageContent, message_content)
|
||||
if not message_content.data.startswith("data:"):
|
||||
# fetch image data from url
|
||||
try:
|
||||
image_content = requests.get(message_content.data).content
|
||||
mime_type, _ = mimetypes.guess_type(message_content.data)
|
||||
base64_data = base64.b64encode(image_content).decode('utf-8')
|
||||
except Exception as ex:
|
||||
raise ValueError(f"Failed to fetch image data from url {message_content.data}, {ex}")
|
||||
else:
|
||||
data_split = message_content.data.split(";base64,")
|
||||
mime_type = data_split[0].replace("data:", "")
|
||||
base64_data = data_split[1]
|
||||
|
||||
if mime_type not in ["image/jpeg", "image/png", "image/gif", "image/webp"]:
|
||||
raise ValueError(f"Unsupported image type {mime_type}, "
|
||||
f"only support image/jpeg, image/png, image/gif, and image/webp")
|
||||
|
||||
sub_message_dict = {
|
||||
"type": "image",
|
||||
"source": {
|
||||
"type": "base64",
|
||||
"media_type": mime_type,
|
||||
"data": base64_data
|
||||
}
|
||||
}
|
||||
sub_messages.append(sub_message_dict)
|
||||
prompt_message_dicts.append({"role": "user", "content": sub_messages})
|
||||
elif isinstance(message, AssistantPromptMessage):
|
||||
message = cast(AssistantPromptMessage, message)
|
||||
content = []
|
||||
if message.tool_calls:
|
||||
for tool_call in message.tool_calls:
|
||||
content.append({
|
||||
"type": "tool_use",
|
||||
"id": tool_call.id,
|
||||
"name": tool_call.function.name,
|
||||
"input": json.loads(tool_call.function.arguments)
|
||||
})
|
||||
if message.content:
|
||||
content.append({
|
||||
"type": "text",
|
||||
"text": message.content
|
||||
})
|
||||
|
||||
if prompt_message_dicts[-1]["role"] == "assistant":
|
||||
prompt_message_dicts[-1]["content"].extend(content)
|
||||
else:
|
||||
prompt_message_dicts.append({
|
||||
"role": "assistant",
|
||||
"content": content
|
||||
})
|
||||
elif isinstance(message, ToolPromptMessage):
|
||||
message = cast(ToolPromptMessage, message)
|
||||
message_dict = {
|
||||
"role": "user",
|
||||
"content": [{
|
||||
"type": "tool_result",
|
||||
"tool_use_id": message.tool_call_id,
|
||||
"content": message.content
|
||||
}]
|
||||
}
|
||||
prompt_message_dicts.append(message_dict)
|
||||
else:
|
||||
raise ValueError(f"Got unknown type {message}")
|
||||
prompt_message_dicts.append(self._convert_prompt_message_to_dict(message))
|
||||
|
||||
return system, prompt_message_dicts
|
||||
|
||||
def _convert_prompt_message_to_dict(self, message: PromptMessage) -> dict:
|
||||
"""
|
||||
Convert PromptMessage to dict
|
||||
"""
|
||||
if isinstance(message, UserPromptMessage):
|
||||
message = cast(UserPromptMessage, message)
|
||||
if isinstance(message.content, str):
|
||||
message_dict = {"role": "user", "content": message.content}
|
||||
else:
|
||||
sub_messages = []
|
||||
for message_content in message.content:
|
||||
if message_content.type == PromptMessageContentType.TEXT:
|
||||
message_content = cast(TextPromptMessageContent, message_content)
|
||||
sub_message_dict = {
|
||||
"type": "text",
|
||||
"text": message_content.data
|
||||
}
|
||||
sub_messages.append(sub_message_dict)
|
||||
elif message_content.type == PromptMessageContentType.IMAGE:
|
||||
message_content = cast(ImagePromptMessageContent, message_content)
|
||||
if not message_content.data.startswith("data:"):
|
||||
# fetch image data from url
|
||||
try:
|
||||
image_content = requests.get(message_content.data).content
|
||||
mime_type, _ = mimetypes.guess_type(message_content.data)
|
||||
base64_data = base64.b64encode(image_content).decode('utf-8')
|
||||
except Exception as ex:
|
||||
raise ValueError(f"Failed to fetch image data from url {message_content.data}, {ex}")
|
||||
else:
|
||||
data_split = message_content.data.split(";base64,")
|
||||
mime_type = data_split[0].replace("data:", "")
|
||||
base64_data = data_split[1]
|
||||
|
||||
if mime_type not in ["image/jpeg", "image/png", "image/gif", "image/webp"]:
|
||||
raise ValueError(f"Unsupported image type {mime_type}, "
|
||||
f"only support image/jpeg, image/png, image/gif, and image/webp")
|
||||
|
||||
sub_message_dict = {
|
||||
"type": "image",
|
||||
"source": {
|
||||
"type": "base64",
|
||||
"media_type": mime_type,
|
||||
"data": base64_data
|
||||
}
|
||||
}
|
||||
sub_messages.append(sub_message_dict)
|
||||
|
||||
message_dict = {"role": "user", "content": sub_messages}
|
||||
elif isinstance(message, AssistantPromptMessage):
|
||||
message = cast(AssistantPromptMessage, message)
|
||||
message_dict = {"role": "assistant", "content": message.content}
|
||||
elif isinstance(message, SystemPromptMessage):
|
||||
message = cast(SystemPromptMessage, message)
|
||||
message_dict = {"role": "system", "content": message.content}
|
||||
else:
|
||||
raise ValueError(f"Got unknown type {message}")
|
||||
|
||||
return message_dict
|
||||
|
||||
def _convert_one_message_to_text(self, message: PromptMessage) -> str:
|
||||
"""
|
||||
Convert a single message to a string.
|
||||
@@ -523,8 +453,6 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
||||
message_text += f"{ai_prompt} [IMAGE]"
|
||||
elif isinstance(message, SystemPromptMessage):
|
||||
message_text = content
|
||||
elif isinstance(message, ToolPromptMessage):
|
||||
message_text = f"{human_prompt} {message.content}"
|
||||
else:
|
||||
raise ValueError(f"Got unknown type {message}")
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ provider_credential_schema:
|
||||
label:
|
||||
en_US: Available Model Name
|
||||
zh_Hans: 可用模型名称
|
||||
type: text-input
|
||||
type: secret-input
|
||||
placeholder:
|
||||
en_US: A model you have access to (e.g. amazon.titan-text-lite-v1) for validation.
|
||||
zh_Hans: 为了进行验证,请输入一个您可用的模型名称 (例如:amazon.titan-text-lite-v1)
|
||||
|
||||
@@ -402,25 +402,25 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
:param credentials: model credentials
|
||||
:return:
|
||||
"""
|
||||
required_params = {}
|
||||
if "anthropic" in model:
|
||||
required_params = {
|
||||
"max_tokens": 32,
|
||||
}
|
||||
elif "ai21" in model:
|
||||
# ValidationException: Malformed input request: #/temperature: expected type: Number, found: Null#/maxTokens: expected type: Integer, found: Null#/topP: expected type: Number, found: Null, please reformat your input and try again.
|
||||
required_params = {
|
||||
"temperature": 0.7,
|
||||
"topP": 0.9,
|
||||
"maxTokens": 32,
|
||||
}
|
||||
|
||||
|
||||
if "anthropic.claude-3" in model:
|
||||
try:
|
||||
self._invoke_claude(model=model,
|
||||
credentials=credentials,
|
||||
prompt_messages=[{"role": "user", "content": "ping"}],
|
||||
model_parameters={},
|
||||
stop=None,
|
||||
stream=False)
|
||||
|
||||
except Exception as ex:
|
||||
raise CredentialsValidateFailedError(str(ex))
|
||||
|
||||
try:
|
||||
ping_message = UserPromptMessage(content="ping")
|
||||
self._invoke(model=model,
|
||||
self._generate(model=model,
|
||||
credentials=credentials,
|
||||
prompt_messages=[ping_message],
|
||||
model_parameters=required_params,
|
||||
model_parameters={},
|
||||
stream=False)
|
||||
|
||||
except ClientError as ex:
|
||||
|
||||
@@ -297,6 +297,7 @@ class CohereLargeLanguageModel(LargeLanguageModel):
|
||||
chat_history=chat_histories,
|
||||
model=real_model,
|
||||
stream=stream,
|
||||
return_preamble=True,
|
||||
**model_parameters,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,31 +1,8 @@
|
||||
import json
|
||||
from collections.abc import Generator
|
||||
from typing import Optional, Union, cast
|
||||
from typing import Optional, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.model_runtime.entities.common_entities import I18nObject
|
||||
from core.model_runtime.entities.llm_entities import LLMMode, LLMResult, LLMResultChunk, LLMResultChunkDelta
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
AssistantPromptMessage,
|
||||
ImagePromptMessageContent,
|
||||
PromptMessage,
|
||||
PromptMessageContent,
|
||||
PromptMessageContentType,
|
||||
PromptMessageTool,
|
||||
SystemPromptMessage,
|
||||
ToolPromptMessage,
|
||||
UserPromptMessage,
|
||||
)
|
||||
from core.model_runtime.entities.model_entities import (
|
||||
AIModelEntity,
|
||||
FetchFrom,
|
||||
ModelFeature,
|
||||
ModelPropertyKey,
|
||||
ModelType,
|
||||
ParameterRule,
|
||||
ParameterType,
|
||||
)
|
||||
from core.model_runtime.entities.llm_entities import LLMResult
|
||||
from core.model_runtime.entities.message_entities import PromptMessage, PromptMessageTool
|
||||
from core.model_runtime.model_providers.openai_api_compatible.llm.llm import OAIAPICompatLargeLanguageModel
|
||||
|
||||
|
||||
@@ -36,7 +13,6 @@ class MoonshotLargeLanguageModel(OAIAPICompatLargeLanguageModel):
|
||||
stream: bool = True, user: Optional[str] = None) \
|
||||
-> Union[LLMResult, Generator]:
|
||||
self._add_custom_parameters(credentials)
|
||||
self._add_function_call(model, credentials)
|
||||
user = user[:32] if user else None
|
||||
return super()._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
|
||||
|
||||
@@ -44,293 +20,7 @@ class MoonshotLargeLanguageModel(OAIAPICompatLargeLanguageModel):
|
||||
self._add_custom_parameters(credentials)
|
||||
super().validate_credentials(model, credentials)
|
||||
|
||||
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None:
|
||||
return AIModelEntity(
|
||||
model=model,
|
||||
label=I18nObject(en_US=model, zh_Hans=model),
|
||||
model_type=ModelType.LLM,
|
||||
features=[ModelFeature.TOOL_CALL, ModelFeature.MULTI_TOOL_CALL, ModelFeature.STREAM_TOOL_CALL]
|
||||
if credentials.get('function_calling_type') == 'tool_call'
|
||||
else [],
|
||||
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
|
||||
model_properties={
|
||||
ModelPropertyKey.CONTEXT_SIZE: int(credentials.get('context_size', 4096)),
|
||||
ModelPropertyKey.MODE: LLMMode.CHAT.value,
|
||||
},
|
||||
parameter_rules=[
|
||||
ParameterRule(
|
||||
name='temperature',
|
||||
use_template='temperature',
|
||||
label=I18nObject(en_US='Temperature', zh_Hans='温度'),
|
||||
type=ParameterType.FLOAT,
|
||||
),
|
||||
ParameterRule(
|
||||
name='max_tokens',
|
||||
use_template='max_tokens',
|
||||
default=512,
|
||||
min=1,
|
||||
max=int(credentials.get('max_tokens', 4096)),
|
||||
label=I18nObject(en_US='Max Tokens', zh_Hans='最大标记'),
|
||||
type=ParameterType.INT,
|
||||
),
|
||||
ParameterRule(
|
||||
name='top_p',
|
||||
use_template='top_p',
|
||||
label=I18nObject(en_US='Top P', zh_Hans='Top P'),
|
||||
type=ParameterType.FLOAT,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def _add_custom_parameters(self, credentials: dict) -> None:
|
||||
@staticmethod
|
||||
def _add_custom_parameters(credentials: dict) -> None:
|
||||
credentials['mode'] = 'chat'
|
||||
credentials['endpoint_url'] = 'https://api.moonshot.cn/v1'
|
||||
|
||||
def _add_function_call(self, model: str, credentials: dict) -> None:
|
||||
model_schema = self.get_model_schema(model, credentials)
|
||||
if model_schema and set([
|
||||
ModelFeature.TOOL_CALL, ModelFeature.MULTI_TOOL_CALL
|
||||
]).intersection(model_schema.features or []):
|
||||
credentials['function_calling_type'] = 'tool_call'
|
||||
|
||||
def _convert_prompt_message_to_dict(self, message: PromptMessage) -> dict:
|
||||
"""
|
||||
Convert PromptMessage to dict for OpenAI API format
|
||||
"""
|
||||
if isinstance(message, UserPromptMessage):
|
||||
message = cast(UserPromptMessage, message)
|
||||
if isinstance(message.content, str):
|
||||
message_dict = {"role": "user", "content": message.content}
|
||||
else:
|
||||
sub_messages = []
|
||||
for message_content in message.content:
|
||||
if message_content.type == PromptMessageContentType.TEXT:
|
||||
message_content = cast(PromptMessageContent, message_content)
|
||||
sub_message_dict = {
|
||||
"type": "text",
|
||||
"text": message_content.data
|
||||
}
|
||||
sub_messages.append(sub_message_dict)
|
||||
elif message_content.type == PromptMessageContentType.IMAGE:
|
||||
message_content = cast(ImagePromptMessageContent, message_content)
|
||||
sub_message_dict = {
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": message_content.data,
|
||||
"detail": message_content.detail.value
|
||||
}
|
||||
}
|
||||
sub_messages.append(sub_message_dict)
|
||||
message_dict = {"role": "user", "content": sub_messages}
|
||||
elif isinstance(message, AssistantPromptMessage):
|
||||
message = cast(AssistantPromptMessage, message)
|
||||
message_dict = {"role": "assistant", "content": message.content}
|
||||
if message.tool_calls:
|
||||
message_dict["tool_calls"] = []
|
||||
for function_call in message.tool_calls:
|
||||
message_dict["tool_calls"].append({
|
||||
"id": function_call.id,
|
||||
"type": function_call.type,
|
||||
"function": {
|
||||
"name": f"functions.{function_call.function.name}",
|
||||
"arguments": function_call.function.arguments
|
||||
}
|
||||
})
|
||||
elif isinstance(message, ToolPromptMessage):
|
||||
message = cast(ToolPromptMessage, message)
|
||||
message_dict = {"role": "tool", "content": message.content, "tool_call_id": message.tool_call_id}
|
||||
if not message.name.startswith("functions."):
|
||||
message.name = f"functions.{message.name}"
|
||||
elif isinstance(message, SystemPromptMessage):
|
||||
message = cast(SystemPromptMessage, message)
|
||||
message_dict = {"role": "system", "content": message.content}
|
||||
else:
|
||||
raise ValueError(f"Got unknown type {message}")
|
||||
|
||||
if message.name:
|
||||
message_dict["name"] = message.name
|
||||
|
||||
return message_dict
|
||||
|
||||
def _extract_response_tool_calls(self, response_tool_calls: list[dict]) -> list[AssistantPromptMessage.ToolCall]:
|
||||
"""
|
||||
Extract tool calls from response
|
||||
|
||||
:param response_tool_calls: response tool calls
|
||||
:return: list of tool calls
|
||||
"""
|
||||
tool_calls = []
|
||||
if response_tool_calls:
|
||||
for response_tool_call in response_tool_calls:
|
||||
function = AssistantPromptMessage.ToolCall.ToolCallFunction(
|
||||
name=response_tool_call["function"]["name"] if response_tool_call.get("function", {}).get("name") else "",
|
||||
arguments=response_tool_call["function"]["arguments"] if response_tool_call.get("function", {}).get("arguments") else ""
|
||||
)
|
||||
|
||||
tool_call = AssistantPromptMessage.ToolCall(
|
||||
id=response_tool_call["id"] if response_tool_call.get("id") else "",
|
||||
type=response_tool_call["type"] if response_tool_call.get("type") else "",
|
||||
function=function
|
||||
)
|
||||
tool_calls.append(tool_call)
|
||||
|
||||
return tool_calls
|
||||
|
||||
def _handle_generate_stream_response(self, model: str, credentials: dict, response: requests.Response,
|
||||
prompt_messages: list[PromptMessage]) -> Generator:
|
||||
"""
|
||||
Handle llm stream response
|
||||
|
||||
:param model: model name
|
||||
:param credentials: model credentials
|
||||
:param response: streamed response
|
||||
:param prompt_messages: prompt messages
|
||||
:return: llm response chunk generator
|
||||
"""
|
||||
full_assistant_content = ''
|
||||
chunk_index = 0
|
||||
|
||||
def create_final_llm_result_chunk(index: int, message: AssistantPromptMessage, finish_reason: str) \
|
||||
-> LLMResultChunk:
|
||||
# calculate num tokens
|
||||
prompt_tokens = self._num_tokens_from_string(model, prompt_messages[0].content)
|
||||
completion_tokens = self._num_tokens_from_string(model, full_assistant_content)
|
||||
|
||||
# transform usage
|
||||
usage = self._calc_response_usage(model, credentials, prompt_tokens, completion_tokens)
|
||||
|
||||
return LLMResultChunk(
|
||||
model=model,
|
||||
prompt_messages=prompt_messages,
|
||||
delta=LLMResultChunkDelta(
|
||||
index=index,
|
||||
message=message,
|
||||
finish_reason=finish_reason,
|
||||
usage=usage
|
||||
)
|
||||
)
|
||||
|
||||
tools_calls: list[AssistantPromptMessage.ToolCall] = []
|
||||
finish_reason = "Unknown"
|
||||
|
||||
def increase_tool_call(new_tool_calls: list[AssistantPromptMessage.ToolCall]):
|
||||
def get_tool_call(tool_name: str):
|
||||
if not tool_name:
|
||||
return tools_calls[-1]
|
||||
|
||||
tool_call = next((tool_call for tool_call in tools_calls if tool_call.function.name == tool_name), None)
|
||||
if tool_call is None:
|
||||
tool_call = AssistantPromptMessage.ToolCall(
|
||||
id='',
|
||||
type='',
|
||||
function=AssistantPromptMessage.ToolCall.ToolCallFunction(name=tool_name, arguments="")
|
||||
)
|
||||
tools_calls.append(tool_call)
|
||||
|
||||
return tool_call
|
||||
|
||||
for new_tool_call in new_tool_calls:
|
||||
# get tool call
|
||||
tool_call = get_tool_call(new_tool_call.function.name)
|
||||
# update tool call
|
||||
if new_tool_call.id:
|
||||
tool_call.id = new_tool_call.id
|
||||
if new_tool_call.type:
|
||||
tool_call.type = new_tool_call.type
|
||||
if new_tool_call.function.name:
|
||||
# remove the functions. prefix
|
||||
if new_tool_call.function.name.startswith('functions.'):
|
||||
parts = new_tool_call.function.name.split('functions.')
|
||||
if len(parts) > 1:
|
||||
new_tool_call.function.name = parts[1]
|
||||
tool_call.function.name = new_tool_call.function.name
|
||||
if new_tool_call.function.arguments:
|
||||
tool_call.function.arguments += new_tool_call.function.arguments
|
||||
|
||||
for chunk in response.iter_lines(decode_unicode=True, delimiter="\n\n"):
|
||||
if chunk:
|
||||
# ignore sse comments
|
||||
if chunk.startswith(':'):
|
||||
continue
|
||||
decoded_chunk = chunk.strip().lstrip('data: ').lstrip()
|
||||
chunk_json = None
|
||||
try:
|
||||
chunk_json = json.loads(decoded_chunk)
|
||||
# stream ended
|
||||
except json.JSONDecodeError as e:
|
||||
yield create_final_llm_result_chunk(
|
||||
index=chunk_index + 1,
|
||||
message=AssistantPromptMessage(content=""),
|
||||
finish_reason="Non-JSON encountered."
|
||||
)
|
||||
break
|
||||
if not chunk_json or len(chunk_json['choices']) == 0:
|
||||
continue
|
||||
|
||||
choice = chunk_json['choices'][0]
|
||||
finish_reason = chunk_json['choices'][0].get('finish_reason')
|
||||
chunk_index += 1
|
||||
|
||||
if 'delta' in choice:
|
||||
delta = choice['delta']
|
||||
delta_content = delta.get('content')
|
||||
|
||||
assistant_message_tool_calls = delta.get('tool_calls', None)
|
||||
# assistant_message_function_call = delta.delta.function_call
|
||||
|
||||
# extract tool calls from response
|
||||
if assistant_message_tool_calls:
|
||||
tool_calls = self._extract_response_tool_calls(assistant_message_tool_calls)
|
||||
increase_tool_call(tool_calls)
|
||||
|
||||
if delta_content is None or delta_content == '':
|
||||
continue
|
||||
|
||||
# transform assistant message to prompt message
|
||||
assistant_prompt_message = AssistantPromptMessage(
|
||||
content=delta_content,
|
||||
tool_calls=tool_calls if assistant_message_tool_calls else []
|
||||
)
|
||||
|
||||
full_assistant_content += delta_content
|
||||
elif 'text' in choice:
|
||||
choice_text = choice.get('text', '')
|
||||
if choice_text == '':
|
||||
continue
|
||||
|
||||
# transform assistant message to prompt message
|
||||
assistant_prompt_message = AssistantPromptMessage(content=choice_text)
|
||||
full_assistant_content += choice_text
|
||||
else:
|
||||
continue
|
||||
|
||||
# check payload indicator for completion
|
||||
yield LLMResultChunk(
|
||||
model=model,
|
||||
prompt_messages=prompt_messages,
|
||||
delta=LLMResultChunkDelta(
|
||||
index=chunk_index,
|
||||
message=assistant_prompt_message,
|
||||
)
|
||||
)
|
||||
|
||||
chunk_index += 1
|
||||
|
||||
if tools_calls:
|
||||
yield LLMResultChunk(
|
||||
model=model,
|
||||
prompt_messages=prompt_messages,
|
||||
delta=LLMResultChunkDelta(
|
||||
index=chunk_index,
|
||||
message=AssistantPromptMessage(
|
||||
tool_calls=tools_calls,
|
||||
content=""
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
yield create_final_llm_result_chunk(
|
||||
index=chunk_index,
|
||||
message=AssistantPromptMessage(content=""),
|
||||
finish_reason=finish_reason
|
||||
)
|
||||
@@ -20,7 +20,6 @@ supported_model_types:
|
||||
- llm
|
||||
configurate_methods:
|
||||
- predefined-model
|
||||
- customizable-model
|
||||
provider_credential_schema:
|
||||
credential_form_schemas:
|
||||
- variable: api_key
|
||||
@@ -31,51 +30,3 @@ provider_credential_schema:
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的 API Key
|
||||
en_US: Enter your API Key
|
||||
model_credential_schema:
|
||||
model:
|
||||
label:
|
||||
en_US: Model Name
|
||||
zh_Hans: 模型名称
|
||||
placeholder:
|
||||
en_US: Enter your model name
|
||||
zh_Hans: 输入模型名称
|
||||
credential_form_schemas:
|
||||
- variable: api_key
|
||||
label:
|
||||
en_US: API Key
|
||||
type: secret-input
|
||||
required: true
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的 API Key
|
||||
en_US: Enter your API Key
|
||||
- variable: context_size
|
||||
label:
|
||||
zh_Hans: 模型上下文长度
|
||||
en_US: Model context size
|
||||
required: true
|
||||
type: text-input
|
||||
default: '4096'
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的模型上下文长度
|
||||
en_US: Enter your Model context size
|
||||
- variable: max_tokens
|
||||
label:
|
||||
zh_Hans: 最大 token 上限
|
||||
en_US: Upper bound for max tokens
|
||||
default: '4096'
|
||||
type: text-input
|
||||
- variable: function_calling_type
|
||||
label:
|
||||
en_US: Function calling
|
||||
type: select
|
||||
required: false
|
||||
default: no_call
|
||||
options:
|
||||
- value: no_call
|
||||
label:
|
||||
en_US: Not supported
|
||||
zh_Hans: 不支持
|
||||
- value: tool_call
|
||||
label:
|
||||
en_US: Tool Call
|
||||
zh_Hans: Tool Call
|
||||
|
||||
@@ -378,34 +378,6 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
|
||||
delimiter = credentials.get("stream_mode_delimiter", "\n\n")
|
||||
delimiter = codecs.decode(delimiter, "unicode_escape")
|
||||
|
||||
tools_calls: list[AssistantPromptMessage.ToolCall] = []
|
||||
|
||||
def increase_tool_call(new_tool_calls: list[AssistantPromptMessage.ToolCall]):
|
||||
def get_tool_call(tool_call_id: str):
|
||||
tool_call = next(
|
||||
(tool_call for tool_call in tools_calls if tool_call.id == tool_call_id), None
|
||||
)
|
||||
if tool_call is None:
|
||||
tool_call = AssistantPromptMessage.ToolCall(
|
||||
id='',
|
||||
type='function',
|
||||
function=AssistantPromptMessage.ToolCall.ToolCallFunction(
|
||||
name='',
|
||||
arguments=''
|
||||
)
|
||||
)
|
||||
tools_calls.append(tool_call)
|
||||
return tool_call
|
||||
|
||||
for new_tool_call in new_tool_calls:
|
||||
# get tool call
|
||||
tool_call = get_tool_call(new_tool_call.id)
|
||||
# update tool call
|
||||
tool_call.id = new_tool_call.id
|
||||
tool_call.type = new_tool_call.type
|
||||
tool_call.function.name = new_tool_call.function.name
|
||||
tool_call.function.arguments += new_tool_call.function.arguments
|
||||
|
||||
for chunk in response.iter_lines(decode_unicode=True, delimiter=delimiter):
|
||||
if chunk:
|
||||
# ignore sse comments
|
||||
@@ -433,6 +405,8 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
|
||||
if 'delta' in choice:
|
||||
delta = choice['delta']
|
||||
delta_content = delta.get('content')
|
||||
if delta_content is None or delta_content == '':
|
||||
continue
|
||||
|
||||
assistant_message_tool_calls = delta.get('tool_calls', None)
|
||||
# assistant_message_function_call = delta.delta.function_call
|
||||
@@ -440,11 +414,6 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
|
||||
# extract tool calls from response
|
||||
if assistant_message_tool_calls:
|
||||
tool_calls = self._extract_response_tool_calls(assistant_message_tool_calls)
|
||||
increase_tool_call(tool_calls)
|
||||
|
||||
if delta_content is None or delta_content == '':
|
||||
continue
|
||||
|
||||
# function_call = self._extract_response_function_call(assistant_message_function_call)
|
||||
# tool_calls = [function_call] if function_call else []
|
||||
|
||||
@@ -468,18 +437,6 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
|
||||
|
||||
# check payload indicator for completion
|
||||
if finish_reason is not None:
|
||||
yield LLMResultChunk(
|
||||
model=model,
|
||||
prompt_messages=prompt_messages,
|
||||
delta=LLMResultChunkDelta(
|
||||
index=chunk_index,
|
||||
message=AssistantPromptMessage(
|
||||
tool_calls=tools_calls,
|
||||
),
|
||||
finish_reason=finish_reason
|
||||
)
|
||||
)
|
||||
|
||||
yield create_final_llm_result_chunk(
|
||||
index=chunk_index,
|
||||
message=assistant_prompt_message,
|
||||
@@ -778,4 +735,4 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
|
||||
function=function
|
||||
)
|
||||
|
||||
return tool_call
|
||||
return tool_call
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
model: ernie-3.5-4k-0205
|
||||
model: ernie-3.5-8k
|
||||
label:
|
||||
en_US: Ernie-3.5-4k-0205
|
||||
en_US: Ernie-3.5-8K
|
||||
model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
|
||||
@@ -24,64 +24,56 @@ class Jieba(BaseKeyword):
|
||||
self._config = KeywordTableConfig()
|
||||
|
||||
def create(self, texts: list[Document], **kwargs) -> BaseKeyword:
|
||||
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
|
||||
with redis_client.lock(lock_name, timeout=600):
|
||||
keyword_table_handler = JiebaKeywordTableHandler()
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
for text in texts:
|
||||
keywords = keyword_table_handler.extract_keywords(text.page_content, self._config.max_keywords_per_chunk)
|
||||
self._update_segment_keywords(self.dataset.id, text.metadata['doc_id'], list(keywords))
|
||||
keyword_table = self._add_text_to_keyword_table(keyword_table, text.metadata['doc_id'], list(keywords))
|
||||
keyword_table_handler = JiebaKeywordTableHandler()
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
for text in texts:
|
||||
keywords = keyword_table_handler.extract_keywords(text.page_content, self._config.max_keywords_per_chunk)
|
||||
self._update_segment_keywords(self.dataset.id, text.metadata['doc_id'], list(keywords))
|
||||
keyword_table = self._add_text_to_keyword_table(keyword_table, text.metadata['doc_id'], list(keywords))
|
||||
|
||||
self._save_dataset_keyword_table(keyword_table)
|
||||
self._save_dataset_keyword_table(keyword_table)
|
||||
|
||||
return self
|
||||
return self
|
||||
|
||||
def add_texts(self, texts: list[Document], **kwargs):
|
||||
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
|
||||
with redis_client.lock(lock_name, timeout=600):
|
||||
keyword_table_handler = JiebaKeywordTableHandler()
|
||||
keyword_table_handler = JiebaKeywordTableHandler()
|
||||
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
keywords_list = kwargs.get('keywords_list', None)
|
||||
for i in range(len(texts)):
|
||||
text = texts[i]
|
||||
if keywords_list:
|
||||
keywords = keywords_list[i]
|
||||
else:
|
||||
keywords = keyword_table_handler.extract_keywords(text.page_content, self._config.max_keywords_per_chunk)
|
||||
self._update_segment_keywords(self.dataset.id, text.metadata['doc_id'], list(keywords))
|
||||
keyword_table = self._add_text_to_keyword_table(keyword_table, text.metadata['doc_id'], list(keywords))
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
keywords_list = kwargs.get('keywords_list', None)
|
||||
for i in range(len(texts)):
|
||||
text = texts[i]
|
||||
if keywords_list:
|
||||
keywords = keywords_list[i]
|
||||
else:
|
||||
keywords = keyword_table_handler.extract_keywords(text.page_content, self._config.max_keywords_per_chunk)
|
||||
self._update_segment_keywords(self.dataset.id, text.metadata['doc_id'], list(keywords))
|
||||
keyword_table = self._add_text_to_keyword_table(keyword_table, text.metadata['doc_id'], list(keywords))
|
||||
|
||||
self._save_dataset_keyword_table(keyword_table)
|
||||
self._save_dataset_keyword_table(keyword_table)
|
||||
|
||||
def text_exists(self, id: str) -> bool:
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
return id in set.union(*keyword_table.values())
|
||||
|
||||
def delete_by_ids(self, ids: list[str]) -> None:
|
||||
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
|
||||
with redis_client.lock(lock_name, timeout=600):
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
keyword_table = self._delete_ids_from_keyword_table(keyword_table, ids)
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
keyword_table = self._delete_ids_from_keyword_table(keyword_table, ids)
|
||||
|
||||
self._save_dataset_keyword_table(keyword_table)
|
||||
self._save_dataset_keyword_table(keyword_table)
|
||||
|
||||
def delete_by_document_id(self, document_id: str):
|
||||
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
|
||||
with redis_client.lock(lock_name, timeout=600):
|
||||
# get segment ids by document_id
|
||||
segments = db.session.query(DocumentSegment).filter(
|
||||
DocumentSegment.dataset_id == self.dataset.id,
|
||||
DocumentSegment.document_id == document_id
|
||||
).all()
|
||||
# get segment ids by document_id
|
||||
segments = db.session.query(DocumentSegment).filter(
|
||||
DocumentSegment.dataset_id == self.dataset.id,
|
||||
DocumentSegment.document_id == document_id
|
||||
).all()
|
||||
|
||||
ids = [segment.index_node_id for segment in segments]
|
||||
ids = [segment.index_node_id for segment in segments]
|
||||
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
keyword_table = self._delete_ids_from_keyword_table(keyword_table, ids)
|
||||
keyword_table = self._get_dataset_keyword_table()
|
||||
keyword_table = self._delete_ids_from_keyword_table(keyword_table, ids)
|
||||
|
||||
self._save_dataset_keyword_table(keyword_table)
|
||||
self._save_dataset_keyword_table(keyword_table)
|
||||
|
||||
def search(
|
||||
self, query: str,
|
||||
@@ -114,15 +106,13 @@ class Jieba(BaseKeyword):
|
||||
return documents
|
||||
|
||||
def delete(self) -> None:
|
||||
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
|
||||
with redis_client.lock(lock_name, timeout=600):
|
||||
dataset_keyword_table = self.dataset.dataset_keyword_table
|
||||
if dataset_keyword_table:
|
||||
db.session.delete(dataset_keyword_table)
|
||||
db.session.commit()
|
||||
if dataset_keyword_table.data_source_type != 'database':
|
||||
file_key = 'keyword_files/' + self.dataset.tenant_id + '/' + self.dataset.id + '.txt'
|
||||
storage.delete(file_key)
|
||||
dataset_keyword_table = self.dataset.dataset_keyword_table
|
||||
if dataset_keyword_table:
|
||||
db.session.delete(dataset_keyword_table)
|
||||
db.session.commit()
|
||||
if dataset_keyword_table.data_source_type != 'database':
|
||||
file_key = 'keyword_files/' + self.dataset.tenant_id + '/' + self.dataset.id + '.txt'
|
||||
storage.delete(file_key)
|
||||
|
||||
def _save_dataset_keyword_table(self, keyword_table):
|
||||
keyword_table_dict = {
|
||||
@@ -145,31 +135,33 @@ class Jieba(BaseKeyword):
|
||||
storage.save(file_key, json.dumps(keyword_table_dict, cls=SetEncoder).encode('utf-8'))
|
||||
|
||||
def _get_dataset_keyword_table(self) -> Optional[dict]:
|
||||
dataset_keyword_table = self.dataset.dataset_keyword_table
|
||||
if dataset_keyword_table:
|
||||
keyword_table_dict = dataset_keyword_table.keyword_table_dict
|
||||
if keyword_table_dict:
|
||||
return keyword_table_dict['__data__']['table']
|
||||
else:
|
||||
keyword_data_source_type = current_app.config['KEYWORD_DATA_SOURCE_TYPE']
|
||||
dataset_keyword_table = DatasetKeywordTable(
|
||||
dataset_id=self.dataset.id,
|
||||
keyword_table='',
|
||||
data_source_type=keyword_data_source_type,
|
||||
)
|
||||
if keyword_data_source_type == 'database':
|
||||
dataset_keyword_table.keyword_table = json.dumps({
|
||||
'__type__': 'keyword_table',
|
||||
'__data__': {
|
||||
"index_id": self.dataset.id,
|
||||
"summary": None,
|
||||
"table": {}
|
||||
}
|
||||
}, cls=SetEncoder)
|
||||
db.session.add(dataset_keyword_table)
|
||||
db.session.commit()
|
||||
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
|
||||
with redis_client.lock(lock_name, timeout=20):
|
||||
dataset_keyword_table = self.dataset.dataset_keyword_table
|
||||
if dataset_keyword_table:
|
||||
keyword_table_dict = dataset_keyword_table.keyword_table_dict
|
||||
if keyword_table_dict:
|
||||
return keyword_table_dict['__data__']['table']
|
||||
else:
|
||||
keyword_data_source_type = current_app.config['KEYWORD_DATA_SOURCE_TYPE']
|
||||
dataset_keyword_table = DatasetKeywordTable(
|
||||
dataset_id=self.dataset.id,
|
||||
keyword_table='',
|
||||
data_source_type=keyword_data_source_type,
|
||||
)
|
||||
if keyword_data_source_type == 'database':
|
||||
dataset_keyword_table.keyword_table = json.dumps({
|
||||
'__type__': 'keyword_table',
|
||||
'__data__': {
|
||||
"index_id": self.dataset.id,
|
||||
"summary": None,
|
||||
"table": {}
|
||||
}
|
||||
}, cls=SetEncoder)
|
||||
db.session.add(dataset_keyword_table)
|
||||
db.session.commit()
|
||||
|
||||
return {}
|
||||
return {}
|
||||
|
||||
def _add_text_to_keyword_table(self, keyword_table: dict, id: str, keywords: list[str]) -> dict:
|
||||
for keyword in keywords:
|
||||
|
||||
@@ -20,17 +20,16 @@ class MilvusConfig(BaseModel):
|
||||
password: str
|
||||
secure: bool = False
|
||||
batch_size: int = 100
|
||||
database: str = "default"
|
||||
|
||||
@root_validator()
|
||||
def validate_config(cls, values: dict) -> dict:
|
||||
if not values.get('host'):
|
||||
if not values['host']:
|
||||
raise ValueError("config MILVUS_HOST is required")
|
||||
if not values.get('port'):
|
||||
if not values['port']:
|
||||
raise ValueError("config MILVUS_PORT is required")
|
||||
if not values.get('user'):
|
||||
if not values['user']:
|
||||
raise ValueError("config MILVUS_USER is required")
|
||||
if not values.get('password'):
|
||||
if not values['password']:
|
||||
raise ValueError("config MILVUS_PASSWORD is required")
|
||||
return values
|
||||
|
||||
@@ -40,8 +39,7 @@ class MilvusConfig(BaseModel):
|
||||
'port': self.port,
|
||||
'user': self.user,
|
||||
'password': self.password,
|
||||
'secure': self.secure,
|
||||
'db_name': self.database,
|
||||
'secure': self.secure
|
||||
}
|
||||
|
||||
|
||||
@@ -130,8 +128,7 @@ class MilvusVector(BaseVector):
|
||||
uri = "https://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
else:
|
||||
uri = "http://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password,
|
||||
db_name=self._client_config.database)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password)
|
||||
|
||||
from pymilvus import utility
|
||||
if utility.has_collection(self._collection_name, using=alias):
|
||||
@@ -143,8 +140,7 @@ class MilvusVector(BaseVector):
|
||||
uri = "https://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
else:
|
||||
uri = "http://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password,
|
||||
db_name=self._client_config.database)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password)
|
||||
|
||||
from pymilvus import utility
|
||||
if not utility.has_collection(self._collection_name, using=alias):
|
||||
@@ -196,7 +192,7 @@ class MilvusVector(BaseVector):
|
||||
else:
|
||||
uri = "http://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user,
|
||||
password=self._client_config.password, db_name=self._client_config.database)
|
||||
password=self._client_config.password)
|
||||
if not utility.has_collection(self._collection_name, using=alias):
|
||||
from pymilvus import CollectionSchema, DataType, FieldSchema
|
||||
from pymilvus.orm.types import infer_dtype_bydata
|
||||
|
||||
@@ -110,7 +110,6 @@ class Vector:
|
||||
user=config.get('MILVUS_USER'),
|
||||
password=config.get('MILVUS_PASSWORD'),
|
||||
secure=config.get('MILVUS_SECURE'),
|
||||
database=config.get('MILVUS_DATABASE'),
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
import csv
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from core.rag.extractor.extractor_base import BaseExtractor
|
||||
from core.rag.extractor.helpers import detect_file_encodings
|
||||
from core.rag.models.document import Document
|
||||
@@ -54,23 +52,21 @@ class CSVExtractor(BaseExtractor):
|
||||
|
||||
def _read_from_file(self, csvfile) -> list[Document]:
|
||||
docs = []
|
||||
try:
|
||||
# load csv file into pandas dataframe
|
||||
df = pd.read_csv(csvfile, error_bad_lines=False, **self.csv_args)
|
||||
|
||||
# check source column exists
|
||||
if self.source_column and self.source_column not in df.columns:
|
||||
raise ValueError(f"Source column '{self.source_column}' not found in CSV file.")
|
||||
|
||||
# create document objects
|
||||
|
||||
for i, row in df.iterrows():
|
||||
content = ";".join(f"{col.strip()}: {str(row[col]).strip()}" for col in df.columns)
|
||||
source = row[self.source_column] if self.source_column else ''
|
||||
metadata = {"source": source, "row": i}
|
||||
doc = Document(page_content=content, metadata=metadata)
|
||||
docs.append(doc)
|
||||
except csv.Error as e:
|
||||
raise e
|
||||
csv_reader = csv.DictReader(csvfile, **self.csv_args) # type: ignore
|
||||
for i, row in enumerate(csv_reader):
|
||||
content = "\n".join(f"{k.strip()}: {v.strip()}" for k, v in row.items())
|
||||
try:
|
||||
source = (
|
||||
row[self.source_column]
|
||||
if self.source_column is not None
|
||||
else ''
|
||||
)
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
f"Source column '{self.source_column}' not found in CSV file."
|
||||
)
|
||||
metadata = {"source": source, "row": i}
|
||||
doc = Document(page_content=content, metadata=metadata)
|
||||
docs.append(doc)
|
||||
|
||||
return docs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Abstract interface for document loader implementations."""
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
from openpyxl.reader.excel import load_workbook
|
||||
|
||||
from core.rag.extractor.extractor_base import BaseExtractor
|
||||
from core.rag.models.document import Document
|
||||
@@ -27,21 +27,24 @@ class ExcelExtractor(BaseExtractor):
|
||||
self._autodetect_encoding = autodetect_encoding
|
||||
|
||||
def extract(self) -> list[Document]:
|
||||
"""Load from file path using Pandas."""
|
||||
"""Load from file path."""
|
||||
data = []
|
||||
|
||||
# 使用 Pandas 读取 Excel 文件的每个工作表
|
||||
xls = pd.ExcelFile(self._file_path)
|
||||
for sheet_name in xls.sheet_names:
|
||||
df = pd.read_excel(xls, sheet_name=sheet_name)
|
||||
|
||||
# filter out rows with all NaN values
|
||||
df.dropna(how='all', inplace=True)
|
||||
|
||||
# transform each row into a Document
|
||||
for _, row in df.iterrows():
|
||||
item = ';'.join(f'{k}:{v}' for k, v in row.items() if pd.notna(v))
|
||||
document = Document(page_content=item, metadata={'source': self._file_path})
|
||||
data.append(document)
|
||||
wb = load_workbook(filename=self._file_path, read_only=True)
|
||||
# loop over all sheets
|
||||
for sheet in wb:
|
||||
keys = []
|
||||
if 'A1:A1' == sheet.calculate_dimension():
|
||||
sheet.reset_dimensions()
|
||||
for row in sheet.iter_rows(values_only=True):
|
||||
if all(v is None for v in row):
|
||||
continue
|
||||
if keys == []:
|
||||
keys = list(map(str, row))
|
||||
else:
|
||||
row_dict = dict(zip(keys, list(map(str, row))))
|
||||
row_dict = {k: v for k, v in row_dict.items() if v}
|
||||
item = ''.join(f'{k}:{v};' for k, v in row_dict.items())
|
||||
document = Document(page_content=item, metadata={'source': self._file_path})
|
||||
data.append(document)
|
||||
|
||||
return data
|
||||
|
||||
@@ -26,7 +26,7 @@ class UnstructuredEmailExtractor(BaseExtractor):
|
||||
|
||||
def extract(self) -> list[Document]:
|
||||
from unstructured.partition.email import partition_email
|
||||
elements = partition_email(filename=self._file_path)
|
||||
elements = partition_email(filename=self._file_path, api_url=self._api_url)
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
|
||||
@@ -36,7 +36,7 @@ class UnstructuredMarkdownExtractor(BaseExtractor):
|
||||
def extract(self) -> list[Document]:
|
||||
from unstructured.partition.md import partition_md
|
||||
|
||||
elements = partition_md(filename=self._file_path)
|
||||
elements = partition_md(filename=self._file_path, api_url=self._api_url)
|
||||
from unstructured.chunking.title import chunk_by_title
|
||||
chunks = chunk_by_title(elements, max_characters=2000, combine_text_under_n_chars=2000)
|
||||
documents = []
|
||||
|
||||
@@ -26,7 +26,7 @@ class UnstructuredMsgExtractor(BaseExtractor):
|
||||
def extract(self) -> list[Document]:
|
||||
from unstructured.partition.msg import partition_msg
|
||||
|
||||
elements = partition_msg(filename=self._file_path)
|
||||
elements = partition_msg(filename=self._file_path, api_url=self._api_url)
|
||||
from unstructured.chunking.title import chunk_by_title
|
||||
chunks = chunk_by_title(elements, max_characters=2000, combine_text_under_n_chars=2000)
|
||||
documents = []
|
||||
|
||||
@@ -24,9 +24,9 @@ class UnstructuredPPTExtractor(BaseExtractor):
|
||||
self._api_url = api_url
|
||||
|
||||
def extract(self) -> list[Document]:
|
||||
from unstructured.partition.api import partition_via_api
|
||||
from unstructured.partition.ppt import partition_ppt
|
||||
|
||||
elements = partition_via_api(filename=self._file_path, api_url=self._api_url)
|
||||
elements = partition_ppt(filename=self._file_path, api_url=self._api_url)
|
||||
text_by_page = {}
|
||||
for element in elements:
|
||||
page = element.metadata.page_number
|
||||
|
||||
@@ -26,7 +26,7 @@ class UnstructuredPPTXExtractor(BaseExtractor):
|
||||
def extract(self) -> list[Document]:
|
||||
from unstructured.partition.pptx import partition_pptx
|
||||
|
||||
elements = partition_pptx(filename=self._file_path)
|
||||
elements = partition_pptx(filename=self._file_path, api_url=self._api_url)
|
||||
text_by_page = {}
|
||||
for element in elements:
|
||||
page = element.metadata.page_number
|
||||
|
||||
@@ -26,7 +26,7 @@ class UnstructuredTextExtractor(BaseExtractor):
|
||||
def extract(self) -> list[Document]:
|
||||
from unstructured.partition.text import partition_text
|
||||
|
||||
elements = partition_text(filename=self._file_path)
|
||||
elements = partition_text(filename=self._file_path, api_url=self._api_url)
|
||||
from unstructured.chunking.title import chunk_by_title
|
||||
chunks = chunk_by_title(elements, max_characters=2000, combine_text_under_n_chars=2000)
|
||||
documents = []
|
||||
|
||||
@@ -26,7 +26,7 @@ class UnstructuredXmlExtractor(BaseExtractor):
|
||||
def extract(self) -> list[Document]:
|
||||
from unstructured.partition.xml import partition_xml
|
||||
|
||||
elements = partition_xml(filename=self._file_path, xml_keep_tags=True)
|
||||
elements = partition_xml(filename=self._file_path, xml_keep_tags=True, api_url=self._api_url)
|
||||
from unstructured.chunking.title import chunk_by_title
|
||||
chunks = chunk_by_title(elements, max_characters=2000, combine_text_under_n_chars=2000)
|
||||
documents = []
|
||||
|
||||
@@ -94,7 +94,7 @@ class ApiBasedToolProviderController(ToolProviderController):
|
||||
'icon': db_provider.icon,
|
||||
},
|
||||
'credentials_schema': credentials_schema,
|
||||
'provider_id': db_provider.id or '',
|
||||
'provider_id': db_provider.id,
|
||||
})
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,92 +1,11 @@
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import arxiv
|
||||
from langchain.utilities import ArxivAPIWrapper
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
class ArxivAPIWrapper(BaseModel):
|
||||
"""Wrapper around ArxivAPI.
|
||||
|
||||
To use, you should have the ``arxiv`` python package installed.
|
||||
https://lukasschwab.me/arxiv.py/index.html
|
||||
This wrapper will use the Arxiv API to conduct searches and
|
||||
fetch document summaries. By default, it will return the document summaries
|
||||
of the top-k results.
|
||||
It limits the Document content by doc_content_chars_max.
|
||||
Set doc_content_chars_max=None if you don't want to limit the content size.
|
||||
|
||||
Args:
|
||||
top_k_results: number of the top-scored document used for the arxiv tool
|
||||
ARXIV_MAX_QUERY_LENGTH: the cut limit on the query used for the arxiv tool.
|
||||
load_max_docs: a limit to the number of loaded documents
|
||||
load_all_available_meta:
|
||||
if True: the `metadata` of the loaded Documents contains all available
|
||||
meta info (see https://lukasschwab.me/arxiv.py/index.html#Result),
|
||||
if False: the `metadata` contains only the published date, title,
|
||||
authors and summary.
|
||||
doc_content_chars_max: an optional cut limit for the length of a document's
|
||||
content
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
arxiv = ArxivAPIWrapper(
|
||||
top_k_results = 3,
|
||||
ARXIV_MAX_QUERY_LENGTH = 300,
|
||||
load_max_docs = 3,
|
||||
load_all_available_meta = False,
|
||||
doc_content_chars_max = 40000
|
||||
)
|
||||
arxiv.run("tree of thought llm)
|
||||
"""
|
||||
|
||||
arxiv_search = arxiv.Search #: :meta private:
|
||||
arxiv_exceptions = (
|
||||
arxiv.ArxivError,
|
||||
arxiv.UnexpectedEmptyPageError,
|
||||
arxiv.HTTPError,
|
||||
) # :meta private:
|
||||
top_k_results: int = 3
|
||||
ARXIV_MAX_QUERY_LENGTH = 300
|
||||
load_max_docs: int = 100
|
||||
load_all_available_meta: bool = False
|
||||
doc_content_chars_max: Optional[int] = 4000
|
||||
|
||||
def run(self, query: str) -> str:
|
||||
"""
|
||||
Performs an arxiv search and A single string
|
||||
with the publish date, title, authors, and summary
|
||||
for each article separated by two newlines.
|
||||
|
||||
If an error occurs or no documents found, error text
|
||||
is returned instead. Wrapper for
|
||||
https://lukasschwab.me/arxiv.py/index.html#Search
|
||||
|
||||
Args:
|
||||
query: a plaintext search query
|
||||
""" # noqa: E501
|
||||
try:
|
||||
results = self.arxiv_search( # type: ignore
|
||||
query[: self.ARXIV_MAX_QUERY_LENGTH], max_results=self.top_k_results
|
||||
).results()
|
||||
except self.arxiv_exceptions as ex:
|
||||
return f"Arxiv exception: {ex}"
|
||||
docs = [
|
||||
f"Published: {result.updated.date()}\n"
|
||||
f"Title: {result.title}\n"
|
||||
f"Authors: {', '.join(a.name for a in result.authors)}\n"
|
||||
f"Summary: {result.summary}"
|
||||
for result in results
|
||||
]
|
||||
if docs:
|
||||
return "\n\n".join(docs)[: self.doc_content_chars_max]
|
||||
else:
|
||||
return "No good Arxiv Result was found"
|
||||
|
||||
|
||||
class ArxivSearchInput(BaseModel):
|
||||
query: str = Field(..., description="Search query.")
|
||||
|
||||
@@ -1,95 +1,11 @@
|
||||
import json
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from pydantic import BaseModel, Field
|
||||
from langchain.tools import BraveSearch
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class BraveSearchWrapper(BaseModel):
|
||||
"""Wrapper around the Brave search engine."""
|
||||
|
||||
api_key: str
|
||||
"""The API key to use for the Brave search engine."""
|
||||
search_kwargs: dict = Field(default_factory=dict)
|
||||
"""Additional keyword arguments to pass to the search request."""
|
||||
base_url = "https://api.search.brave.com/res/v1/web/search"
|
||||
"""The base URL for the Brave search engine."""
|
||||
|
||||
def run(self, query: str) -> str:
|
||||
"""Query the Brave search engine and return the results as a JSON string.
|
||||
|
||||
Args:
|
||||
query: The query to search for.
|
||||
|
||||
Returns: The results as a JSON string.
|
||||
|
||||
"""
|
||||
web_search_results = self._search_request(query=query)
|
||||
final_results = [
|
||||
{
|
||||
"title": item.get("title"),
|
||||
"link": item.get("url"),
|
||||
"snippet": item.get("description"),
|
||||
}
|
||||
for item in web_search_results
|
||||
]
|
||||
return json.dumps(final_results)
|
||||
|
||||
def _search_request(self, query: str) -> list[dict]:
|
||||
headers = {
|
||||
"X-Subscription-Token": self.api_key,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
req = requests.PreparedRequest()
|
||||
params = {**self.search_kwargs, **{"q": query}}
|
||||
req.prepare_url(self.base_url, params)
|
||||
if req.url is None:
|
||||
raise ValueError("prepared url is None, this should not happen")
|
||||
|
||||
response = requests.get(req.url, headers=headers)
|
||||
if not response.ok:
|
||||
raise Exception(f"HTTP error {response.status_code}")
|
||||
|
||||
return response.json().get("web", {}).get("results", [])
|
||||
|
||||
class BraveSearch(BaseModel):
|
||||
"""Tool that queries the BraveSearch."""
|
||||
|
||||
name = "brave_search"
|
||||
description = (
|
||||
"a search engine. "
|
||||
"useful for when you need to answer questions about current events."
|
||||
" input should be a search query."
|
||||
)
|
||||
search_wrapper: BraveSearchWrapper
|
||||
|
||||
@classmethod
|
||||
def from_api_key(
|
||||
cls, api_key: str, search_kwargs: Optional[dict] = None, **kwargs: Any
|
||||
) -> "BraveSearch":
|
||||
"""Create a tool from an api key.
|
||||
|
||||
Args:
|
||||
api_key: The api key to use.
|
||||
search_kwargs: Any additional kwargs to pass to the search wrapper.
|
||||
**kwargs: Any additional kwargs to pass to the tool.
|
||||
|
||||
Returns:
|
||||
A tool.
|
||||
"""
|
||||
wrapper = BraveSearchWrapper(api_key=api_key, search_kwargs=search_kwargs or {})
|
||||
return cls(search_wrapper=wrapper, **kwargs)
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
) -> str:
|
||||
"""Use the tool."""
|
||||
return self.search_wrapper.run(query)
|
||||
|
||||
class BraveSearchTool(BuiltinTool):
|
||||
"""
|
||||
Tool for performing a search using Brave search engine.
|
||||
@@ -115,7 +31,7 @@ class BraveSearchTool(BuiltinTool):
|
||||
|
||||
tool = BraveSearch.from_api_key(api_key=api_key, search_kwargs={"count": count})
|
||||
|
||||
results = tool._run(query)
|
||||
results = tool.run(query)
|
||||
|
||||
if not results:
|
||||
return self.create_text_message(f"No results found for '{query}' in Tavily")
|
||||
|
||||
@@ -1,147 +1,16 @@
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from langchain.tools import DuckDuckGoSearchRun
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class DuckDuckGoSearchAPIWrapper(BaseModel):
|
||||
"""Wrapper for DuckDuckGo Search API.
|
||||
|
||||
Free and does not require any setup.
|
||||
"""
|
||||
|
||||
region: Optional[str] = "wt-wt"
|
||||
safesearch: str = "moderate"
|
||||
time: Optional[str] = "y"
|
||||
max_results: int = 5
|
||||
|
||||
def get_snippets(self, query: str) -> list[str]:
|
||||
"""Run query through DuckDuckGo and return concatenated results."""
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
with DDGS() as ddgs:
|
||||
results = ddgs.text(
|
||||
query,
|
||||
region=self.region,
|
||||
safesearch=self.safesearch,
|
||||
timelimit=self.time,
|
||||
)
|
||||
if results is None:
|
||||
return ["No good DuckDuckGo Search Result was found"]
|
||||
snippets = []
|
||||
for i, res in enumerate(results, 1):
|
||||
if res is not None:
|
||||
snippets.append(res["body"])
|
||||
if len(snippets) == self.max_results:
|
||||
break
|
||||
return snippets
|
||||
|
||||
def run(self, query: str) -> str:
|
||||
snippets = self.get_snippets(query)
|
||||
return " ".join(snippets)
|
||||
|
||||
def results(
|
||||
self, query: str, num_results: int, backend: str = "api"
|
||||
) -> list[dict[str, str]]:
|
||||
"""Run query through DuckDuckGo and return metadata.
|
||||
|
||||
Args:
|
||||
query: The query to search for.
|
||||
num_results: The number of results to return.
|
||||
|
||||
Returns:
|
||||
A list of dictionaries with the following keys:
|
||||
snippet - The description of the result.
|
||||
title - The title of the result.
|
||||
link - The link to the result.
|
||||
"""
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
with DDGS() as ddgs:
|
||||
results = ddgs.text(
|
||||
query,
|
||||
region=self.region,
|
||||
safesearch=self.safesearch,
|
||||
timelimit=self.time,
|
||||
backend=backend,
|
||||
)
|
||||
if results is None:
|
||||
return [{"Result": "No good DuckDuckGo Search Result was found"}]
|
||||
|
||||
def to_metadata(result: dict) -> dict[str, str]:
|
||||
if backend == "news":
|
||||
return {
|
||||
"date": result["date"],
|
||||
"title": result["title"],
|
||||
"snippet": result["body"],
|
||||
"source": result["source"],
|
||||
"link": result["url"],
|
||||
}
|
||||
return {
|
||||
"snippet": result["body"],
|
||||
"title": result["title"],
|
||||
"link": result["href"],
|
||||
}
|
||||
|
||||
formatted_results = []
|
||||
for i, res in enumerate(results, 1):
|
||||
if res is not None:
|
||||
formatted_results.append(to_metadata(res))
|
||||
if len(formatted_results) == num_results:
|
||||
break
|
||||
return formatted_results
|
||||
|
||||
|
||||
class DuckDuckGoSearchRun(BaseModel):
|
||||
"""Tool that queries the DuckDuckGo search API."""
|
||||
|
||||
name = "duckduckgo_search"
|
||||
description = (
|
||||
"A wrapper around DuckDuckGo Search. "
|
||||
"Useful for when you need to answer questions about current events. "
|
||||
"Input should be a search query."
|
||||
)
|
||||
api_wrapper: DuckDuckGoSearchAPIWrapper = Field(
|
||||
default_factory=DuckDuckGoSearchAPIWrapper
|
||||
)
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
) -> str:
|
||||
"""Use the tool."""
|
||||
return self.api_wrapper.run(query)
|
||||
|
||||
|
||||
class DuckDuckGoSearchResults(BaseModel):
|
||||
"""Tool that queries the DuckDuckGo search API and gets back json."""
|
||||
|
||||
name = "DuckDuckGo Results JSON"
|
||||
description = (
|
||||
"A wrapper around Duck Duck Go Search. "
|
||||
"Useful for when you need to answer questions about current events. "
|
||||
"Input should be a search query. Output is a JSON array of the query results"
|
||||
)
|
||||
num_results: int = 4
|
||||
api_wrapper: DuckDuckGoSearchAPIWrapper = Field(
|
||||
default_factory=DuckDuckGoSearchAPIWrapper
|
||||
)
|
||||
backend: str = "api"
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
) -> str:
|
||||
"""Use the tool."""
|
||||
res = self.api_wrapper.results(query, self.num_results, backend=self.backend)
|
||||
res_strs = [", ".join([f"{k}: {v}" for k, v in d.items()]) for d in res]
|
||||
return ", ".join([f"[{rs}]" for rs in res_strs])
|
||||
|
||||
class DuckDuckGoInput(BaseModel):
|
||||
query: str = Field(..., description="Search query.")
|
||||
|
||||
|
||||
class DuckDuckGoSearchTool(BuiltinTool):
|
||||
"""
|
||||
Tool for performing a search using DuckDuckGo search engine.
|
||||
@@ -165,7 +34,7 @@ class DuckDuckGoSearchTool(BuiltinTool):
|
||||
|
||||
tool = DuckDuckGoSearchRun(args_schema=DuckDuckGoInput)
|
||||
|
||||
result = tool._run(query)
|
||||
result = tool.run(query)
|
||||
|
||||
return self.create_text_message(self.summary(user_id=user_id, content=result))
|
||||
|
||||
@@ -70,44 +70,43 @@ class SerpAPI:
|
||||
raise ValueError(f"Got error from SerpAPI: {res['error']}")
|
||||
|
||||
if typ == "text":
|
||||
toret = ""
|
||||
if "answer_box" in res.keys() and type(res["answer_box"]) == list:
|
||||
res["answer_box"] = res["answer_box"][0] + "\n"
|
||||
res["answer_box"] = res["answer_box"][0]
|
||||
if "answer_box" in res.keys() and "answer" in res["answer_box"].keys():
|
||||
toret += res["answer_box"]["answer"] + "\n"
|
||||
if "answer_box" in res.keys() and "snippet" in res["answer_box"].keys():
|
||||
toret += res["answer_box"]["snippet"] + "\n"
|
||||
if (
|
||||
toret = res["answer_box"]["answer"]
|
||||
elif "answer_box" in res.keys() and "snippet" in res["answer_box"].keys():
|
||||
toret = res["answer_box"]["snippet"]
|
||||
elif (
|
||||
"answer_box" in res.keys()
|
||||
and "snippet_highlighted_words" in res["answer_box"].keys()
|
||||
):
|
||||
for item in res["answer_box"]["snippet_highlighted_words"]:
|
||||
toret += item + "\n"
|
||||
if (
|
||||
toret = res["answer_box"]["snippet_highlighted_words"][0]
|
||||
elif (
|
||||
"sports_results" in res.keys()
|
||||
and "game_spotlight" in res["sports_results"].keys()
|
||||
):
|
||||
toret += res["sports_results"]["game_spotlight"] + "\n"
|
||||
if (
|
||||
toret = res["sports_results"]["game_spotlight"]
|
||||
elif (
|
||||
"shopping_results" in res.keys()
|
||||
and "title" in res["shopping_results"][0].keys()
|
||||
):
|
||||
toret += res["shopping_results"][:3] + "\n"
|
||||
if (
|
||||
toret = res["shopping_results"][:3]
|
||||
elif (
|
||||
"knowledge_graph" in res.keys()
|
||||
and "description" in res["knowledge_graph"].keys()
|
||||
):
|
||||
toret = res["knowledge_graph"]["description"] + "\n"
|
||||
if "snippet" in res["organic_results"][0].keys():
|
||||
for item in res["organic_results"]:
|
||||
toret += "content: " + item["snippet"] + "\n" + "link: " + item["link"] + "\n"
|
||||
if (
|
||||
toret = res["knowledge_graph"]["description"]
|
||||
elif "snippet" in res["organic_results"][0].keys():
|
||||
toret = res["organic_results"][0]["snippet"]
|
||||
elif "link" in res["organic_results"][0].keys():
|
||||
toret = res["organic_results"][0]["link"]
|
||||
elif (
|
||||
"images_results" in res.keys()
|
||||
and "thumbnail" in res["images_results"][0].keys()
|
||||
):
|
||||
thumbnails = [item["thumbnail"] for item in res["images_results"][:10]]
|
||||
toret = thumbnails
|
||||
if toret == "":
|
||||
else:
|
||||
toret = "No good search result found"
|
||||
elif typ == "link":
|
||||
if "knowledge_graph" in res.keys() and "title" in res["knowledge_graph"].keys() \
|
||||
|
||||
@@ -1,187 +1,16 @@
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
from langchain.tools import PubmedQueryRun
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class PubMedAPIWrapper(BaseModel):
|
||||
"""
|
||||
Wrapper around PubMed API.
|
||||
|
||||
This wrapper will use the PubMed API to conduct searches and fetch
|
||||
document summaries. By default, it will return the document summaries
|
||||
of the top-k results of an input search.
|
||||
|
||||
Parameters:
|
||||
top_k_results: number of the top-scored document used for the PubMed tool
|
||||
load_max_docs: a limit to the number of loaded documents
|
||||
load_all_available_meta:
|
||||
if True: the `metadata` of the loaded Documents gets all available meta info
|
||||
(see https://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.ESearch)
|
||||
if False: the `metadata` gets only the most informative fields.
|
||||
"""
|
||||
|
||||
base_url_esearch = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?"
|
||||
base_url_efetch = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?"
|
||||
max_retry = 5
|
||||
sleep_time = 0.2
|
||||
|
||||
# Default values for the parameters
|
||||
top_k_results: int = 3
|
||||
load_max_docs: int = 25
|
||||
ARXIV_MAX_QUERY_LENGTH = 300
|
||||
doc_content_chars_max: int = 2000
|
||||
load_all_available_meta: bool = False
|
||||
email: str = "your_email@example.com"
|
||||
|
||||
def run(self, query: str) -> str:
|
||||
"""
|
||||
Run PubMed search and get the article meta information.
|
||||
See https://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.ESearch
|
||||
It uses only the most informative fields of article meta information.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Retrieve the top-k results for the query
|
||||
docs = [
|
||||
f"Published: {result['pub_date']}\nTitle: {result['title']}\n"
|
||||
f"Summary: {result['summary']}"
|
||||
for result in self.load(query[: self.ARXIV_MAX_QUERY_LENGTH])
|
||||
]
|
||||
|
||||
# Join the results and limit the character count
|
||||
return (
|
||||
"\n\n".join(docs)[:self.doc_content_chars_max]
|
||||
if docs
|
||||
else "No good PubMed Result was found"
|
||||
)
|
||||
except Exception as ex:
|
||||
return f"PubMed exception: {ex}"
|
||||
|
||||
def load(self, query: str) -> list[dict]:
|
||||
"""
|
||||
Search PubMed for documents matching the query.
|
||||
Return a list of dictionaries containing the document metadata.
|
||||
"""
|
||||
|
||||
url = (
|
||||
self.base_url_esearch
|
||||
+ "db=pubmed&term="
|
||||
+ str({urllib.parse.quote(query)})
|
||||
+ f"&retmode=json&retmax={self.top_k_results}&usehistory=y"
|
||||
)
|
||||
result = urllib.request.urlopen(url)
|
||||
text = result.read().decode("utf-8")
|
||||
json_text = json.loads(text)
|
||||
|
||||
articles = []
|
||||
webenv = json_text["esearchresult"]["webenv"]
|
||||
for uid in json_text["esearchresult"]["idlist"]:
|
||||
article = self.retrieve_article(uid, webenv)
|
||||
articles.append(article)
|
||||
|
||||
# Convert the list of articles to a JSON string
|
||||
return articles
|
||||
|
||||
def retrieve_article(self, uid: str, webenv: str) -> dict:
|
||||
url = (
|
||||
self.base_url_efetch
|
||||
+ "db=pubmed&retmode=xml&id="
|
||||
+ uid
|
||||
+ "&webenv="
|
||||
+ webenv
|
||||
)
|
||||
|
||||
retry = 0
|
||||
while True:
|
||||
try:
|
||||
result = urllib.request.urlopen(url)
|
||||
break
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 429 and retry < self.max_retry:
|
||||
# Too Many Requests error
|
||||
# wait for an exponentially increasing amount of time
|
||||
print(
|
||||
f"Too Many Requests, "
|
||||
f"waiting for {self.sleep_time:.2f} seconds..."
|
||||
)
|
||||
time.sleep(self.sleep_time)
|
||||
self.sleep_time *= 2
|
||||
retry += 1
|
||||
else:
|
||||
raise e
|
||||
|
||||
xml_text = result.read().decode("utf-8")
|
||||
|
||||
# Get title
|
||||
title = ""
|
||||
if "<ArticleTitle>" in xml_text and "</ArticleTitle>" in xml_text:
|
||||
start_tag = "<ArticleTitle>"
|
||||
end_tag = "</ArticleTitle>"
|
||||
title = xml_text[
|
||||
xml_text.index(start_tag) + len(start_tag) : xml_text.index(end_tag)
|
||||
]
|
||||
|
||||
# Get abstract
|
||||
abstract = ""
|
||||
if "<AbstractText>" in xml_text and "</AbstractText>" in xml_text:
|
||||
start_tag = "<AbstractText>"
|
||||
end_tag = "</AbstractText>"
|
||||
abstract = xml_text[
|
||||
xml_text.index(start_tag) + len(start_tag) : xml_text.index(end_tag)
|
||||
]
|
||||
|
||||
# Get publication date
|
||||
pub_date = ""
|
||||
if "<PubDate>" in xml_text and "</PubDate>" in xml_text:
|
||||
start_tag = "<PubDate>"
|
||||
end_tag = "</PubDate>"
|
||||
pub_date = xml_text[
|
||||
xml_text.index(start_tag) + len(start_tag) : xml_text.index(end_tag)
|
||||
]
|
||||
|
||||
# Return article as dictionary
|
||||
article = {
|
||||
"uid": uid,
|
||||
"title": title,
|
||||
"summary": abstract,
|
||||
"pub_date": pub_date,
|
||||
}
|
||||
return article
|
||||
|
||||
|
||||
class PubmedQueryRun(BaseModel):
|
||||
"""Tool that searches the PubMed API."""
|
||||
|
||||
name = "PubMed"
|
||||
description = (
|
||||
"A wrapper around PubMed.org "
|
||||
"Useful for when you need to answer questions about Physics, Mathematics, "
|
||||
"Computer Science, Quantitative Biology, Quantitative Finance, Statistics, "
|
||||
"Electrical Engineering, and Economics "
|
||||
"from scientific articles on PubMed.org. "
|
||||
"Input should be a search query."
|
||||
)
|
||||
api_wrapper: PubMedAPIWrapper = Field(default_factory=PubMedAPIWrapper)
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
) -> str:
|
||||
"""Use the Arxiv tool."""
|
||||
return self.api_wrapper.run(query)
|
||||
|
||||
|
||||
class PubMedInput(BaseModel):
|
||||
query: str = Field(..., description="Search query.")
|
||||
|
||||
|
||||
class PubMedSearchTool(BuiltinTool):
|
||||
"""
|
||||
Tool for performing a search using PubMed search engine.
|
||||
@@ -205,7 +34,7 @@ class PubMedSearchTool(BuiltinTool):
|
||||
|
||||
tool = PubmedQueryRun(args_schema=PubMedInput)
|
||||
|
||||
result = tool._run(query)
|
||||
result = tool.run(query)
|
||||
|
||||
return self.create_text_message(self.summary(user_id=user_id, content=result))
|
||||
|
||||
@@ -6,9 +6,9 @@ identity:
|
||||
zh_Hans: 获取 Stack Exchange 答案
|
||||
description:
|
||||
human:
|
||||
en_US: A tool for retrieving answers for a specific Stack Exchange question ID. Must be used with the searchStackExQuesID tool.
|
||||
zh_Hans: 用于检索特定Stack Exchange问题ID的答案的工具。必须与searchStackExQuesID工具一起使用。
|
||||
llm: A tool for retrieving answers for Stack Exchange question ID.
|
||||
en_US: A tool for retrieving answers for a specific Stack Exchange question ID. Specify the question ID, Stack Exchange site, sorting order, number of results per page, and page number. Must be used with the searchStackExQuesID tool.
|
||||
zh_Hans: 用于检索特定Stack Exchange问题ID的答案的工具。指定问题ID、Stack Exchange站点、排序顺序、每页结果数和页码。 必须与searchStackExQuesID工具一起使用。
|
||||
llm: A tool for retrieving answers for a specific Stack Exchange question ID based on the provided parameters.
|
||||
parameters:
|
||||
- name: id
|
||||
type: string
|
||||
@@ -30,8 +30,108 @@ parameters:
|
||||
human_description:
|
||||
en_US: The Stack Exchange site the question is from, e.g. stackoverflow, unix, etc.
|
||||
zh_Hans: 问题所在的Stack Exchange站点,例如stackoverflow、unix等。
|
||||
llm_description: Stack Exchange site identifier - 'stackoverflow', 'serverfault', 'superuser', 'askubuntu', 'unix', 'cs', 'softwareengineering', 'codegolf', 'codereview', 'cstheory', 'security', 'cryptography', 'reverseengineering', 'datascience', 'devops', 'ux', 'dba', 'gis', 'webmasters', 'arduino', 'raspberrypi', 'networkengineering', 'iot', 'tor', 'sqa', 'mathoverflow', 'math', 'mathematica', 'dsp', 'gamedev', 'robotics', 'genai', 'computergraphics'.
|
||||
form: llm
|
||||
llm_description: The Stack Exchange site identifier.
|
||||
options:
|
||||
- value: stackoverflow
|
||||
label:
|
||||
en_US: stackoverflow
|
||||
- value: serverfault
|
||||
label:
|
||||
en_US: serverfault
|
||||
- value: superuser
|
||||
label:
|
||||
en_US: superuser
|
||||
- value: askubuntu
|
||||
label:
|
||||
en_US: askubuntu
|
||||
- value: unix
|
||||
label:
|
||||
en_US: unix
|
||||
- value: cs
|
||||
label:
|
||||
en_US: cs
|
||||
- value: softwareengineering
|
||||
label:
|
||||
en_US: softwareengineering
|
||||
- value: codegolf
|
||||
label:
|
||||
en_US: codegolf
|
||||
- value: codereview
|
||||
label:
|
||||
en_US: codereview
|
||||
- value: cstheory
|
||||
label:
|
||||
en_US: cstheory
|
||||
- value: security
|
||||
label:
|
||||
en_US: security
|
||||
- value: cryptography
|
||||
label:
|
||||
en_US: cryptography
|
||||
- value: reverseengineering
|
||||
label:
|
||||
en_US: reverseengineering
|
||||
- value: datascience
|
||||
label:
|
||||
en_US: datascience
|
||||
- value: devops
|
||||
label:
|
||||
en_US: devops
|
||||
- value: ux
|
||||
label:
|
||||
en_US: ux
|
||||
- value: dba
|
||||
label:
|
||||
en_US: dba
|
||||
- value: gis
|
||||
label:
|
||||
en_US: gis
|
||||
- value: webmasters
|
||||
label:
|
||||
en_US: webmasters
|
||||
- value: arduino
|
||||
label:
|
||||
en_US: arduino
|
||||
- value: raspberrypi
|
||||
label:
|
||||
en_US: raspberrypi
|
||||
- value: networkengineering
|
||||
label:
|
||||
en_US: networkengineering
|
||||
- value: iot
|
||||
label:
|
||||
en_US: iot
|
||||
- value: tor
|
||||
label:
|
||||
en_US: tor
|
||||
- value: sqa
|
||||
label:
|
||||
en_US: sqa
|
||||
- value: mathoverflow
|
||||
label:
|
||||
en_US: mathoverflow
|
||||
- value: math
|
||||
label:
|
||||
en_US: math
|
||||
- value: mathematica
|
||||
label:
|
||||
en_US: mathematica
|
||||
- value: dsp
|
||||
label:
|
||||
en_US: dsp
|
||||
- value: gamedev
|
||||
label:
|
||||
en_US: gamedev
|
||||
- value: robotics
|
||||
label:
|
||||
en_US: robotics
|
||||
- value: genai
|
||||
label:
|
||||
en_US: genai
|
||||
- value: computergraphics
|
||||
label:
|
||||
en_US: computergraphics
|
||||
form: form
|
||||
- name: filter
|
||||
type: string
|
||||
required: true
|
||||
@@ -40,14 +140,9 @@ parameters:
|
||||
zh_Hans: 过滤器
|
||||
human_description:
|
||||
en_US: This is required in order to actually get the body of the answer.
|
||||
zh_Hans: 为了实际获取答案的正文是必需的。
|
||||
options:
|
||||
- value: "!nNPvSNdWme"
|
||||
label:
|
||||
en_US: Must Select
|
||||
zh_Hans: 必须选择
|
||||
form: form
|
||||
default: "!nNPvSNdWme"
|
||||
zh_Hans: 为了实际获取答案的正文,这是必需的。
|
||||
llm_description: Required in order to actually get the body of the answer. Must be \"!nNPvSNdWme\".
|
||||
form: llm
|
||||
- name: order
|
||||
type: string
|
||||
required: true
|
||||
@@ -57,17 +152,8 @@ parameters:
|
||||
human_description:
|
||||
en_US: The direction to sort the answers - ascending or descending.
|
||||
zh_Hans: 答案的排序方向 - 升序或降序。
|
||||
form: form
|
||||
options:
|
||||
- value: asc
|
||||
label:
|
||||
en_US: Ascending
|
||||
zh_Hans: 升序
|
||||
- value: desc
|
||||
label:
|
||||
en_US: Descending
|
||||
zh_Hans: 降序
|
||||
default: desc
|
||||
llm_description: asc for ascending, desc for descending.
|
||||
form: llm
|
||||
- name: sort
|
||||
type: string
|
||||
required: true
|
||||
@@ -88,10 +174,8 @@ parameters:
|
||||
human_description:
|
||||
en_US: The number of answers to return per page.
|
||||
zh_Hans: 每页返回的答案数。
|
||||
form: form
|
||||
min: 1
|
||||
max: 5
|
||||
default: 1
|
||||
llm_description: The number of answers per page.
|
||||
form: llm
|
||||
- name: page
|
||||
type: number
|
||||
required: true
|
||||
@@ -101,7 +185,5 @@ parameters:
|
||||
human_description:
|
||||
en_US: The page number of answers to retrieve.
|
||||
zh_Hans: 要检索的答案的页码。
|
||||
form: form
|
||||
min: 1
|
||||
max: 5
|
||||
default: 3
|
||||
llm_description: The page number to retrieve.
|
||||
form: llm
|
||||
|
||||
@@ -6,9 +6,9 @@ identity:
|
||||
zh_Hans: 搜索Stack Exchange问题
|
||||
description:
|
||||
human:
|
||||
en_US: A tool for searching questions on a Stack Exchange site.
|
||||
zh_Hans: 在Stack Exchange站点上搜索问题的工具。
|
||||
llm: A tool for searching questions on Stack Exchange site.
|
||||
en_US: A tool for searching questions on a Stack Exchange site. Specify the search query, sorting order, tags to include or exclude, whether to search only for questions with accepted answers, the Stack Exchange site, and number of results per page.
|
||||
zh_Hans: 在Stack Exchange站点上搜索问题的工具。指定搜索查询、排序顺序、要包含或排除的标签、是否仅搜索有已接受答案的问题、Stack Exchange站点以及每页结果数。
|
||||
llm: A tool for searching questions on a Stack Exchange site based on the provided parameters.
|
||||
parameters:
|
||||
- name: intitle
|
||||
type: string
|
||||
@@ -19,7 +19,7 @@ parameters:
|
||||
human_description:
|
||||
en_US: The search query to use for finding questions.
|
||||
zh_Hans: 用于查找问题的搜索查询。
|
||||
llm_description: The search query.
|
||||
llm_description: The search query to use.
|
||||
form: llm
|
||||
- name: sort
|
||||
type: string
|
||||
@@ -30,10 +30,10 @@ parameters:
|
||||
human_description:
|
||||
en_US: The sort order for the search results - relevance, activity, votes, or creation date.
|
||||
zh_Hans: 搜索结果的排序顺序 - 相关性、活动、投票或创建日期。
|
||||
llm_description: The sort order - 'relevance', 'activity', 'votes', or 'creation'.
|
||||
llm_description: The sort order - relevance, activity, votes, or creation.
|
||||
form: llm
|
||||
- name: order
|
||||
type: select
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Sort direction
|
||||
@@ -41,17 +41,8 @@ parameters:
|
||||
human_description:
|
||||
en_US: The direction to sort - ascending or descending.
|
||||
zh_Hans: 排序方向 - 升序或降序。
|
||||
form: form
|
||||
options:
|
||||
- value: asc
|
||||
label:
|
||||
en_US: Ascending
|
||||
zh_Hans: 升序
|
||||
- value: desc
|
||||
label:
|
||||
en_US: Descending
|
||||
zh_Hans: 降序
|
||||
default: desc
|
||||
llm_description: asc for ascending, desc for descending.
|
||||
form: llm
|
||||
- name: site
|
||||
type: string
|
||||
required: true
|
||||
@@ -61,8 +52,108 @@ parameters:
|
||||
human_description:
|
||||
en_US: The Stack Exchange site to search, e.g. stackoverflow, unix, etc.
|
||||
zh_Hans: 要搜索的Stack Exchange站点,例如stackoverflow、unix等。
|
||||
llm_description: Stack Exchange site identifier - 'stackoverflow', 'serverfault', 'superuser', 'askubuntu', 'unix', 'cs', 'softwareengineering', 'codegolf', 'codereview', 'cstheory', 'security', 'cryptography', 'reverseengineering', 'datascience', 'devops', 'ux', 'dba', 'gis', 'webmasters', 'arduino', 'raspberrypi', 'networkengineering', 'iot', 'tor', 'sqa', 'mathoverflow', 'math', 'mathematica', 'dsp', 'gamedev', 'robotics', 'genai', 'computergraphics'.
|
||||
form: llm
|
||||
llm_description: The Stack Exchange site identifier.
|
||||
options:
|
||||
- value: stackoverflow
|
||||
label:
|
||||
en_US: stackoverflow
|
||||
- value: serverfault
|
||||
label:
|
||||
en_US: serverfault
|
||||
- value: superuser
|
||||
label:
|
||||
en_US: superuser
|
||||
- value: askubuntu
|
||||
label:
|
||||
en_US: askubuntu
|
||||
- value: unix
|
||||
label:
|
||||
en_US: unix
|
||||
- value: cs
|
||||
label:
|
||||
en_US: cs
|
||||
- value: softwareengineering
|
||||
label:
|
||||
en_US: softwareengineering
|
||||
- value: codegolf
|
||||
label:
|
||||
en_US: codegolf
|
||||
- value: codereview
|
||||
label:
|
||||
en_US: codereview
|
||||
- value: cstheory
|
||||
label:
|
||||
en_US: cstheory
|
||||
- value: security
|
||||
label:
|
||||
en_US: security
|
||||
- value: cryptography
|
||||
label:
|
||||
en_US: cryptography
|
||||
- value: reverseengineering
|
||||
label:
|
||||
en_US: reverseengineering
|
||||
- value: datascience
|
||||
label:
|
||||
en_US: datascience
|
||||
- value: devops
|
||||
label:
|
||||
en_US: devops
|
||||
- value: ux
|
||||
label:
|
||||
en_US: ux
|
||||
- value: dba
|
||||
label:
|
||||
en_US: dba
|
||||
- value: gis
|
||||
label:
|
||||
en_US: gis
|
||||
- value: webmasters
|
||||
label:
|
||||
en_US: webmasters
|
||||
- value: arduino
|
||||
label:
|
||||
en_US: arduino
|
||||
- value: raspberrypi
|
||||
label:
|
||||
en_US: raspberrypi
|
||||
- value: networkengineering
|
||||
label:
|
||||
en_US: networkengineering
|
||||
- value: iot
|
||||
label:
|
||||
en_US: iot
|
||||
- value: tor
|
||||
label:
|
||||
en_US: tor
|
||||
- value: sqa
|
||||
label:
|
||||
en_US: sqa
|
||||
- value: mathoverflow
|
||||
label:
|
||||
en_US: mathoverflow
|
||||
- value: math
|
||||
label:
|
||||
en_US: math
|
||||
- value: mathematica
|
||||
label:
|
||||
en_US: mathematica
|
||||
- value: dsp
|
||||
label:
|
||||
en_US: dsp
|
||||
- value: gamedev
|
||||
label:
|
||||
en_US: gamedev
|
||||
- value: robotics
|
||||
label:
|
||||
en_US: robotics
|
||||
- value: genai
|
||||
label:
|
||||
en_US: genai
|
||||
- value: computergraphics
|
||||
label:
|
||||
en_US: computergraphics
|
||||
form: form
|
||||
- name: tagged
|
||||
type: string
|
||||
required: false
|
||||
@@ -94,17 +185,8 @@ parameters:
|
||||
human_description:
|
||||
en_US: Whether to limit to only questions that have an accepted answer.
|
||||
zh_Hans: 是否限制为只有已接受答案的问题。
|
||||
form: form
|
||||
options:
|
||||
- value: true
|
||||
label:
|
||||
en_US: Yes
|
||||
zh_Hans: 是
|
||||
- value: false
|
||||
label:
|
||||
en_US: No
|
||||
zh_Hans: 否
|
||||
default: true
|
||||
llm_description: true to limit to only questions with accepted answers, false otherwise.
|
||||
form: llm
|
||||
- name: pagesize
|
||||
type: number
|
||||
required: true
|
||||
@@ -115,7 +197,4 @@ parameters:
|
||||
en_US: The number of results to return per page.
|
||||
zh_Hans: 每页返回的结果数。
|
||||
llm_description: The number of results per page.
|
||||
form: form
|
||||
min: 1
|
||||
max: 50
|
||||
default: 10
|
||||
form: llm
|
||||
|
||||
@@ -1,81 +1,11 @@
|
||||
from typing import Any, Optional, Union
|
||||
from typing import Any, Union
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
from langchain.utilities import TwilioAPIWrapper
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class TwilioAPIWrapper(BaseModel):
|
||||
"""Messaging Client using Twilio.
|
||||
|
||||
To use, you should have the ``twilio`` python package installed,
|
||||
and the environment variables ``TWILIO_ACCOUNT_SID``, ``TWILIO_AUTH_TOKEN``, and
|
||||
``TWILIO_FROM_NUMBER``, or pass `account_sid`, `auth_token`, and `from_number` as
|
||||
named parameters to the constructor.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain.utilities.twilio import TwilioAPIWrapper
|
||||
twilio = TwilioAPIWrapper(
|
||||
account_sid="ACxxx",
|
||||
auth_token="xxx",
|
||||
from_number="+10123456789"
|
||||
)
|
||||
twilio.run('test', '+12484345508')
|
||||
"""
|
||||
|
||||
client: Any #: :meta private:
|
||||
account_sid: Optional[str] = None
|
||||
"""Twilio account string identifier."""
|
||||
auth_token: Optional[str] = None
|
||||
"""Twilio auth token."""
|
||||
from_number: Optional[str] = None
|
||||
"""A Twilio phone number in [E.164](https://www.twilio.com/docs/glossary/what-e164)
|
||||
format, an
|
||||
[alphanumeric sender ID](https://www.twilio.com/docs/sms/send-messages#use-an-alphanumeric-sender-id),
|
||||
or a [Channel Endpoint address](https://www.twilio.com/docs/sms/channels#channel-addresses)
|
||||
that is enabled for the type of message you want to send. Phone numbers or
|
||||
[short codes](https://www.twilio.com/docs/sms/api/short-code) purchased from
|
||||
Twilio also work here. You cannot, for example, spoof messages from a private
|
||||
cell phone number. If you are using `messaging_service_sid`, this parameter
|
||||
must be empty.
|
||||
""" # noqa: E501
|
||||
|
||||
@validator("client", pre=True, always=True)
|
||||
def set_validator(cls, values: dict) -> dict:
|
||||
"""Validate that api key and python package exists in environment."""
|
||||
try:
|
||||
from twilio.rest import Client
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Could not import twilio python package. "
|
||||
"Please install it with `pip install twilio`."
|
||||
)
|
||||
account_sid = values.get("account_sid")
|
||||
auth_token = values.get("auth_token")
|
||||
values["from_number"] = values.get("from_number")
|
||||
values["client"] = Client(account_sid, auth_token)
|
||||
|
||||
return values
|
||||
|
||||
def run(self, body: str, to: str) -> str:
|
||||
"""Run body through Twilio and respond with message sid.
|
||||
|
||||
Args:
|
||||
body: The text of the message you want to send. Can be up to 1,600
|
||||
characters in length.
|
||||
to: The destination phone number in
|
||||
[E.164](https://www.twilio.com/docs/glossary/what-e164) format for
|
||||
SMS/MMS or
|
||||
[Channel user address](https://www.twilio.com/docs/sms/channels#channel-addresses)
|
||||
for other 3rd-party channels.
|
||||
""" # noqa: E501
|
||||
message = self.client.messages.create(to, from_=self.from_number, body=body)
|
||||
return message.sid
|
||||
|
||||
|
||||
class SendMessageTool(BuiltinTool):
|
||||
"""
|
||||
A tool for sending messages using Twilio API.
|
||||
|
||||
@@ -1,79 +1,16 @@
|
||||
from typing import Any, Optional, Union
|
||||
from typing import Any, Union
|
||||
|
||||
import wikipedia
|
||||
from langchain import WikipediaAPIWrapper
|
||||
from langchain.tools import WikipediaQueryRun
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
WIKIPEDIA_MAX_QUERY_LENGTH = 300
|
||||
|
||||
class WikipediaAPIWrapper:
|
||||
"""Wrapper around WikipediaAPI.
|
||||
class WikipediaInput(BaseModel):
|
||||
query: str = Field(..., description="search query.")
|
||||
|
||||
To use, you should have the ``wikipedia`` python package installed.
|
||||
This wrapper will use the Wikipedia API to conduct searches and
|
||||
fetch page summaries. By default, it will return the page summaries
|
||||
of the top-k results.
|
||||
It limits the Document content by doc_content_chars_max.
|
||||
"""
|
||||
|
||||
top_k_results: int = 3
|
||||
lang: str = "en"
|
||||
load_all_available_meta: bool = False
|
||||
doc_content_chars_max: int = 4000
|
||||
|
||||
def __init__(self, doc_content_chars_max: int = 4000):
|
||||
self.doc_content_chars_max = doc_content_chars_max
|
||||
|
||||
def run(self, query: str) -> str:
|
||||
wikipedia.set_lang(self.lang)
|
||||
wiki_client = wikipedia
|
||||
|
||||
"""Run Wikipedia search and get page summaries."""
|
||||
page_titles = wiki_client.search(query[:WIKIPEDIA_MAX_QUERY_LENGTH])
|
||||
summaries = []
|
||||
for page_title in page_titles[: self.top_k_results]:
|
||||
if wiki_page := self._fetch_page(page_title):
|
||||
if summary := self._formatted_page_summary(page_title, wiki_page):
|
||||
summaries.append(summary)
|
||||
if not summaries:
|
||||
return "No good Wikipedia Search Result was found"
|
||||
return "\n\n".join(summaries)[: self.doc_content_chars_max]
|
||||
|
||||
@staticmethod
|
||||
def _formatted_page_summary(page_title: str, wiki_page: Any) -> Optional[str]:
|
||||
return f"Page: {page_title}\nSummary: {wiki_page.summary}"
|
||||
|
||||
def _fetch_page(self, page: str) -> Optional[str]:
|
||||
try:
|
||||
return wikipedia.page(title=page, auto_suggest=False)
|
||||
except (
|
||||
wikipedia.exceptions.PageError,
|
||||
wikipedia.exceptions.DisambiguationError,
|
||||
):
|
||||
return None
|
||||
|
||||
class WikipediaQueryRun:
|
||||
"""Tool that searches the Wikipedia API."""
|
||||
|
||||
name = "Wikipedia"
|
||||
description = (
|
||||
"A wrapper around Wikipedia. "
|
||||
"Useful for when you need to answer general questions about "
|
||||
"people, places, companies, facts, historical events, or other subjects. "
|
||||
"Input should be a search query."
|
||||
)
|
||||
api_wrapper: WikipediaAPIWrapper
|
||||
|
||||
def __init__(self, api_wrapper: WikipediaAPIWrapper):
|
||||
self.api_wrapper = api_wrapper
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
) -> str:
|
||||
"""Use the Wikipedia tool."""
|
||||
return self.api_wrapper.run(query)
|
||||
class WikiPediaSearchTool(BuiltinTool):
|
||||
def _invoke(self,
|
||||
user_id: str,
|
||||
@@ -87,10 +24,14 @@ class WikiPediaSearchTool(BuiltinTool):
|
||||
return self.create_text_message('Please input query')
|
||||
|
||||
tool = WikipediaQueryRun(
|
||||
name="wikipedia",
|
||||
api_wrapper=WikipediaAPIWrapper(doc_content_chars_max=4000),
|
||||
args_schema=WikipediaInput
|
||||
)
|
||||
|
||||
result = tool._run(query)
|
||||
result = tool.run(tool_input={
|
||||
'query': query
|
||||
})
|
||||
|
||||
return self.create_text_message(self.summary(user_id=user_id,content=result))
|
||||
|
||||
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
from pydantic import BaseModel
|
||||
|
||||
from core.tools.entities.tool_entities import (
|
||||
ToolDescription,
|
||||
@@ -23,13 +23,6 @@ class Tool(BaseModel, ABC):
|
||||
description: ToolDescription = None
|
||||
is_team_authorization: bool = False
|
||||
|
||||
@validator('parameters', pre=True, always=True)
|
||||
def set_parameters(cls, v, values):
|
||||
if not v:
|
||||
return []
|
||||
|
||||
return v
|
||||
|
||||
class Runtime(BaseModel):
|
||||
"""
|
||||
Meta data of a tool call processing
|
||||
|
||||
@@ -234,9 +234,6 @@ class CodeNode(BaseNode):
|
||||
parameters_validated = {}
|
||||
for output_name, output_config in output_schema.items():
|
||||
dot = '.' if prefix else ''
|
||||
if output_name not in result:
|
||||
raise ValueError(f'Output {prefix}{dot}{output_name} is missing.')
|
||||
|
||||
if output_config.type == 'object':
|
||||
# check if output is object
|
||||
if not isinstance(result.get(output_name), dict):
|
||||
|
||||
@@ -242,10 +242,7 @@ class KnowledgeRetrievalNode(BaseNode):
|
||||
# get top k
|
||||
top_k = retrieval_model_config['top_k']
|
||||
# get retrieval method
|
||||
if dataset.indexing_technique == "economy":
|
||||
retrival_method = 'keyword_search'
|
||||
else:
|
||||
retrival_method = retrieval_model_config['search_method']
|
||||
retrival_method = retrieval_model_config['search_method']
|
||||
# get reranking model
|
||||
reranking_model=retrieval_model_config['reranking_model'] \
|
||||
if retrieval_model_config['reranking_enable'] else None
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Optional, Union, cast
|
||||
|
||||
from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity
|
||||
@@ -13,7 +12,7 @@ from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, Comp
|
||||
from core.prompt.simple_prompt_transform import ModelMode
|
||||
from core.prompt.utils.prompt_message_util import PromptMessageUtil
|
||||
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
||||
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult, NodeType
|
||||
from core.workflow.entities.node_entities import NodeRunResult, NodeType
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.nodes.llm.llm_node import LLMNode
|
||||
from core.workflow.nodes.question_classifier.entities import QuestionClassifierNodeData
|
||||
@@ -62,15 +61,9 @@ class QuestionClassifierNode(LLMNode):
|
||||
prompt_messages=prompt_messages,
|
||||
stop=stop
|
||||
)
|
||||
categories = [_class.name for _class in node_data.classes]
|
||||
try:
|
||||
result_text_json = json.loads(result_text.strip('```JSON\n'))
|
||||
categories_result = result_text_json.get('categories', [])
|
||||
if categories_result:
|
||||
categories = categories_result
|
||||
except Exception:
|
||||
logging.error(f"Failed to parse result text: {result_text}")
|
||||
try:
|
||||
categories = result_text_json.get('categories', [])
|
||||
process_data = {
|
||||
'model_mode': model_config.mode,
|
||||
'prompts': PromptMessageUtil.prompt_messages_to_prompt_for_saving(
|
||||
@@ -91,24 +84,14 @@ class QuestionClassifierNode(LLMNode):
|
||||
inputs=variables,
|
||||
process_data=process_data,
|
||||
outputs=outputs,
|
||||
edge_source_handle=classes_map.get(categories[0], None),
|
||||
metadata={
|
||||
NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens,
|
||||
NodeRunMetadataKey.TOTAL_PRICE: usage.total_price,
|
||||
NodeRunMetadataKey.CURRENCY: usage.currency
|
||||
}
|
||||
edge_source_handle=classes_map.get(categories[0], None)
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.FAILED,
|
||||
inputs=variables,
|
||||
error=str(e),
|
||||
metadata={
|
||||
NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens,
|
||||
NodeRunMetadataKey.TOTAL_PRICE: usage.total_price,
|
||||
NodeRunMetadataKey.CURRENCY: usage.currency
|
||||
}
|
||||
error=str(e)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -14,26 +14,18 @@ class VariableAssignerNode(BaseNode):
|
||||
|
||||
def _run(self, variable_pool: VariablePool) -> NodeRunResult:
|
||||
node_data: VariableAssignerNodeData = cast(self._node_data_cls, self.node_data)
|
||||
# Get variables
|
||||
outputs = {}
|
||||
inputs = {}
|
||||
for variable in node_data.variables:
|
||||
value = variable_pool.get_variable_value(variable)
|
||||
|
||||
if value is not None:
|
||||
outputs = {
|
||||
"output": value
|
||||
}
|
||||
|
||||
inputs = {
|
||||
'.'.join(variable[1:]): value
|
||||
}
|
||||
break
|
||||
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.SUCCEEDED,
|
||||
outputs=outputs,
|
||||
inputs=inputs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, cast
|
||||
from typing import Optional
|
||||
|
||||
from core.app.app_config.entities import FileExtraConfig
|
||||
from core.app.apps.base_app_queue_manager import GenerateTaskStoppedException
|
||||
from core.file.file_obj import FileTransferMethod, FileType, FileVar
|
||||
from core.file.file_obj import FileVar
|
||||
from core.workflow.callbacks.base_workflow_callback import BaseWorkflowCallback
|
||||
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult, NodeType
|
||||
from core.workflow.entities.variable_pool import VariablePool, VariableValue
|
||||
@@ -17,7 +16,6 @@ from core.workflow.nodes.end.end_node import EndNode
|
||||
from core.workflow.nodes.http_request.http_request_node import HttpRequestNode
|
||||
from core.workflow.nodes.if_else.if_else_node import IfElseNode
|
||||
from core.workflow.nodes.knowledge_retrieval.knowledge_retrieval_node import KnowledgeRetrievalNode
|
||||
from core.workflow.nodes.llm.entities import LLMNodeData
|
||||
from core.workflow.nodes.llm.llm_node import LLMNode
|
||||
from core.workflow.nodes.question_classifier.question_classifier_node import QuestionClassifierNode
|
||||
from core.workflow.nodes.start.start_node import StartNode
|
||||
@@ -221,8 +219,7 @@ class WorkflowEngineManager:
|
||||
raise ValueError('node id not found in workflow graph')
|
||||
|
||||
# Get node class
|
||||
node_type = NodeType.value_of(node_config.get('data', {}).get('type'))
|
||||
node_cls = node_classes.get(node_type)
|
||||
node_cls = node_classes.get(NodeType.value_of(node_config.get('data', {}).get('type')))
|
||||
|
||||
# init workflow run state
|
||||
node_instance = node_cls(
|
||||
@@ -255,40 +252,11 @@ class WorkflowEngineManager:
|
||||
variable_node_id = variable_selector[0]
|
||||
variable_key_list = variable_selector[1:]
|
||||
|
||||
# get value
|
||||
value = user_inputs.get(variable_key)
|
||||
|
||||
# temp fix for image type
|
||||
if node_type == NodeType.LLM:
|
||||
new_value = []
|
||||
if isinstance(value, list):
|
||||
node_data = node_instance.node_data
|
||||
node_data = cast(LLMNodeData, node_data)
|
||||
|
||||
detail = node_data.vision.configs.detail if node_data.vision.configs else None
|
||||
|
||||
for item in value:
|
||||
if isinstance(item, dict) and 'type' in item and item['type'] == 'image':
|
||||
transfer_method = FileTransferMethod.value_of(item.get('transfer_method'))
|
||||
file = FileVar(
|
||||
tenant_id=workflow.tenant_id,
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=transfer_method,
|
||||
url=item.get('url') if transfer_method == FileTransferMethod.REMOTE_URL else None,
|
||||
related_id=item.get(
|
||||
'upload_file_id') if transfer_method == FileTransferMethod.LOCAL_FILE else None,
|
||||
extra_config=FileExtraConfig(image_config={'detail': detail} if detail else None),
|
||||
)
|
||||
new_value.append(file)
|
||||
|
||||
if new_value:
|
||||
value = new_value
|
||||
|
||||
# append variable and value to variable pool
|
||||
variable_pool.append_variable(
|
||||
node_id=variable_node_id,
|
||||
variable_key_list=variable_key_list,
|
||||
value=value
|
||||
value=user_inputs.get(variable_key)
|
||||
)
|
||||
# run node
|
||||
node_run_result = node_instance.run(
|
||||
|
||||
@@ -28,7 +28,6 @@ def init_app(app: Flask) -> Celery:
|
||||
|
||||
celery_app.conf.update(
|
||||
result_backend=app.config["CELERY_RESULT_BACKEND"],
|
||||
broker_connection_retry_on_startup=True,
|
||||
)
|
||||
|
||||
if app.config["BROKER_USE_SSL"]:
|
||||
|
||||
@@ -72,7 +72,6 @@ message_detail_fields = {
|
||||
'created_at': TimestampField,
|
||||
'agent_thoughts': fields.List(fields.Nested(agent_thought_fields)),
|
||||
'message_files': fields.List(fields.Nested(message_file_fields), attribute='files'),
|
||||
'metadata': fields.Raw(attribute='message_metadata_dict'),
|
||||
'status': fields.String,
|
||||
'error': fields.String,
|
||||
}
|
||||
|
||||
+1
-5
@@ -751,10 +751,6 @@ class Message(db.Model):
|
||||
def in_debug_mode(self):
|
||||
return self.override_model_configs is not None
|
||||
|
||||
@property
|
||||
def message_metadata_dict(self) -> dict:
|
||||
return json.loads(self.message_metadata) if self.message_metadata else {}
|
||||
|
||||
@property
|
||||
def agent_thoughts(self):
|
||||
return db.session.query(MessageAgentThought).filter(MessageAgentThought.message_id == self.id) \
|
||||
@@ -815,7 +811,7 @@ class Message(db.Model):
|
||||
@property
|
||||
def workflow_run(self):
|
||||
if self.workflow_run_id:
|
||||
from .workflow import WorkflowRun
|
||||
from api.models.workflow import WorkflowRun
|
||||
return db.session.query(WorkflowRun).filter(WorkflowRun.id == self.workflow_run_id).first()
|
||||
|
||||
return None
|
||||
|
||||
@@ -120,7 +120,7 @@ class Workflow(db.Model):
|
||||
|
||||
@property
|
||||
def updated_by_account(self):
|
||||
return Account.query.get(self.updated_by) if self.updated_by else None
|
||||
return Account.query.get(self.updated_by)
|
||||
|
||||
@property
|
||||
def graph_dict(self):
|
||||
@@ -299,10 +299,6 @@ class WorkflowRun(db.Model):
|
||||
Message.workflow_run_id == self.id
|
||||
).first()
|
||||
|
||||
@property
|
||||
def workflow(self):
|
||||
return db.session.query(Workflow).filter(Workflow.id == self.workflow_id).first()
|
||||
|
||||
|
||||
class WorkflowNodeExecutionTriggeredFrom(Enum):
|
||||
"""
|
||||
|
||||
@@ -18,7 +18,6 @@ pycryptodome==3.19.1
|
||||
python-dotenv==1.0.0
|
||||
pytest~=7.3.1
|
||||
pytest-mock~=3.11.1
|
||||
pytest-benchmark~=4.0.0
|
||||
Authlib==1.2.0
|
||||
boto3==1.28.17
|
||||
tenacity==8.2.2
|
||||
@@ -37,7 +36,7 @@ python-docx~=1.1.0
|
||||
pypdfium2==4.16.0
|
||||
resend~=0.7.0
|
||||
pyjwt~=2.8.0
|
||||
anthropic~=0.23.1
|
||||
anthropic~=0.20.0
|
||||
newspaper3k==0.2.8
|
||||
google-api-python-client==2.90.0
|
||||
wikipedia==1.4.0
|
||||
|
||||
@@ -221,18 +221,24 @@ class AppService:
|
||||
"name": app.name,
|
||||
"mode": app.mode,
|
||||
"icon": app.icon,
|
||||
"icon_background": app.icon_background,
|
||||
"description": app.description
|
||||
"icon_background": app.icon_background
|
||||
}
|
||||
}
|
||||
|
||||
if app_mode in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
|
||||
workflow_service = WorkflowService()
|
||||
workflow = workflow_service.get_draft_workflow(app)
|
||||
export_data['workflow'] = {
|
||||
"graph": workflow.graph_dict,
|
||||
"features": workflow.features_dict
|
||||
}
|
||||
if app.workflow_id:
|
||||
workflow = app.workflow
|
||||
export_data['workflow'] = {
|
||||
"graph": workflow.graph_dict,
|
||||
"features": workflow.features_dict
|
||||
}
|
||||
else:
|
||||
workflow_service = WorkflowService()
|
||||
workflow = workflow_service.get_draft_workflow(app)
|
||||
export_data['workflow'] = {
|
||||
"graph": workflow.graph_dict,
|
||||
"features": workflow.features_dict
|
||||
}
|
||||
else:
|
||||
app_model_config = app.app_model_config
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from sqlalchemy import or_
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.llm_generator.llm_generator import LLMGenerator
|
||||
from extensions.ext_database import db
|
||||
from libs.infinite_scroll_pagination import InfiniteScrollPagination
|
||||
@@ -16,9 +13,8 @@ class ConversationService:
|
||||
@classmethod
|
||||
def pagination_by_last_id(cls, app_model: App, user: Optional[Union[Account, EndUser]],
|
||||
last_id: Optional[str], limit: int,
|
||||
invoke_from: InvokeFrom,
|
||||
include_ids: Optional[list] = None,
|
||||
exclude_ids: Optional[list] = None) -> InfiniteScrollPagination:
|
||||
include_ids: Optional[list] = None, exclude_ids: Optional[list] = None,
|
||||
exclude_debug_conversation: bool = False) -> InfiniteScrollPagination:
|
||||
if not user:
|
||||
return InfiniteScrollPagination(data=[], limit=limit, has_more=False)
|
||||
|
||||
@@ -28,7 +24,6 @@ class ConversationService:
|
||||
Conversation.from_source == ('api' if isinstance(user, EndUser) else 'console'),
|
||||
Conversation.from_end_user_id == (user.id if isinstance(user, EndUser) else None),
|
||||
Conversation.from_account_id == (user.id if isinstance(user, Account) else None),
|
||||
or_(Conversation.invoke_from.is_(None), Conversation.invoke_from == invoke_from.value)
|
||||
)
|
||||
|
||||
if include_ids is not None:
|
||||
@@ -37,6 +32,9 @@ class ConversationService:
|
||||
if exclude_ids is not None:
|
||||
base_query = base_query.filter(~Conversation.id.in_(exclude_ids))
|
||||
|
||||
if exclude_debug_conversation:
|
||||
base_query = base_query.filter(Conversation.override_model_configs == None)
|
||||
|
||||
if last_id:
|
||||
last_conversation = base_query.filter(
|
||||
Conversation.id == last_id,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from extensions.ext_database import db
|
||||
from libs.infinite_scroll_pagination import InfiniteScrollPagination
|
||||
from models.account import Account
|
||||
@@ -12,8 +11,8 @@ from services.conversation_service import ConversationService
|
||||
class WebConversationService:
|
||||
@classmethod
|
||||
def pagination_by_last_id(cls, app_model: App, user: Optional[Union[Account, EndUser]],
|
||||
last_id: Optional[str], limit: int, invoke_from: InvokeFrom,
|
||||
pinned: Optional[bool] = None) -> InfiniteScrollPagination:
|
||||
last_id: Optional[str], limit: int, pinned: Optional[bool] = None,
|
||||
exclude_debug_conversation: bool = False) -> InfiniteScrollPagination:
|
||||
include_ids = None
|
||||
exclude_ids = None
|
||||
if pinned is not None:
|
||||
@@ -33,9 +32,9 @@ class WebConversationService:
|
||||
user=user,
|
||||
last_id=last_id,
|
||||
limit=limit,
|
||||
invoke_from=invoke_from,
|
||||
include_ids=include_ids,
|
||||
exclude_ids=exclude_ids,
|
||||
exclude_debug_conversation=exclude_debug_conversation
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -6,10 +6,6 @@ from core.workflow.nodes.code.code_node import CodeNode
|
||||
from models.workflow import WorkflowNodeExecutionStatus
|
||||
from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock
|
||||
|
||||
from os import getenv
|
||||
|
||||
CODE_MAX_STRING_LENGTH = int(getenv('CODE_MAX_STRING_LENGTH', '10000'))
|
||||
|
||||
@pytest.mark.parametrize('setup_code_executor_mock', [['none']], indirect=True)
|
||||
def test_execute_code(setup_code_executor_mock):
|
||||
code = '''
|
||||
@@ -231,7 +227,7 @@ def test_execute_code_output_validator_depth():
|
||||
# construct result
|
||||
result = {
|
||||
"number_validator": 1,
|
||||
"string_validator": (CODE_MAX_STRING_LENGTH + 1) * "1",
|
||||
"string_validator": "1" * 6000,
|
||||
"number_array_validator": [1, 2, 3, 3.333],
|
||||
"string_array_validator": ["1", "2", "3"],
|
||||
"object_validator": {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import pytest
|
||||
from pydantic.error_wrappers import ValidationError
|
||||
|
||||
from core.rag.datasource.vdb.milvus.milvus_vector import MilvusConfig
|
||||
|
||||
|
||||
def test_default_value():
|
||||
valid_config = {
|
||||
'host': 'localhost',
|
||||
'port': 19530,
|
||||
'user': 'root',
|
||||
'password': 'Milvus'
|
||||
}
|
||||
|
||||
for key in valid_config:
|
||||
config = valid_config.copy()
|
||||
del config[key]
|
||||
with pytest.raises(ValidationError) as e:
|
||||
MilvusConfig(**config)
|
||||
assert e.value.errors()[1]['msg'] == f'config MILVUS_{key.upper()} is required'
|
||||
|
||||
config = MilvusConfig(**valid_config)
|
||||
assert config.secure is False
|
||||
assert config.database == 'default'
|
||||
@@ -50,7 +50,7 @@ services:
|
||||
AUTHORIZATION_ADMINLIST_USERS: 'hello@dify.ai'
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
|
||||
# The DifySandbox
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:latest
|
||||
@@ -75,6 +75,6 @@ services:
|
||||
# volumes:
|
||||
# - ./volumes/qdrant:/qdrant/storage
|
||||
# environment:
|
||||
# QDRANT_API_KEY: 'difyai123456'
|
||||
# QDRANT__API_KEY: 'difyai123456'
|
||||
# ports:
|
||||
# - "6333:6333"
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3'
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:0.6.1
|
||||
image: langgenius/dify-api:0.6.0-preview-workflow.2
|
||||
restart: always
|
||||
environment:
|
||||
# Startup mode, 'api' starts the API server.
|
||||
@@ -150,7 +150,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:0.6.1
|
||||
image: langgenius/dify-api:0.6.0-preview-workflow.2
|
||||
restart: always
|
||||
environment:
|
||||
# Startup mode, 'worker' starts the Celery worker for processing the queue.
|
||||
@@ -232,7 +232,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:0.6.1
|
||||
image: langgenius/dify-web:0.6.0-preview-workflow.2
|
||||
restart: always
|
||||
environment:
|
||||
EDITION: SELF_HOSTED
|
||||
@@ -323,6 +323,8 @@ services:
|
||||
API_KEY: dify-sandbox
|
||||
GIN_MODE: release
|
||||
WORKER_TIMEOUT: 15
|
||||
ports:
|
||||
- "8194:8194"
|
||||
|
||||
# Qdrant vector store.
|
||||
# uncomment to use qdrant as vector store.
|
||||
@@ -334,7 +336,7 @@ services:
|
||||
# volumes:
|
||||
# - ./volumes/qdrant:/qdrant/storage
|
||||
# environment:
|
||||
# QDRANT_API_KEY: 'difyai123456'
|
||||
# QDRANT__API_KEY: 'difyai123456'
|
||||
# # uncomment to expose qdrant port to host
|
||||
# # ports:
|
||||
# # - "6333:6333"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useUnmount } from 'ahooks'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import cn from 'classnames'
|
||||
@@ -81,9 +80,8 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
|
||||
const mode = isMobile ? 'collapse' : 'expand'
|
||||
setAppSiderbarExpand(isMobile ? mode : localeMode)
|
||||
// TODO: consider screen size and mode
|
||||
// if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow'))
|
||||
// setAppSiderbarExpand('collapse')
|
||||
if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow'))
|
||||
setAppSiderbarExpand('collapse')
|
||||
}
|
||||
}, [appDetail, isMobile])
|
||||
|
||||
@@ -104,10 +102,6 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
})
|
||||
}, [appId, isCurrentWorkspaceManager])
|
||||
|
||||
useUnmount(() => {
|
||||
setAppDetail()
|
||||
})
|
||||
|
||||
if (!appDetail) {
|
||||
return (
|
||||
<div className='flex h-full items-center justify-center bg-white'>
|
||||
|
||||
@@ -28,6 +28,7 @@ import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
|
||||
import { getLocaleOnClient } from '@/i18n'
|
||||
import AppSideBar from '@/app/components/app-sidebar'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import FloatPopoverContainer from '@/app/components/base/float-popover-container'
|
||||
@@ -35,9 +36,6 @@ import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { LanguagesSupported } from '@/i18n/language'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
|
||||
|
||||
export type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
@@ -53,31 +51,18 @@ type ILikedItemProps = {
|
||||
|
||||
const LikedItem = ({
|
||||
type = 'app',
|
||||
appStatus = true,
|
||||
detail,
|
||||
isMobile,
|
||||
}: ILikedItemProps) => {
|
||||
return (
|
||||
<Link className={classNames(s.itemWrapper, 'px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
|
||||
<Link className={classNames(s.itemWrapper, 'px-0', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
|
||||
<div className={classNames(s.iconWrapper, 'mr-0')}>
|
||||
<AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background} />
|
||||
{type === 'app' && (
|
||||
<span className='absolute bottom-[-2px] right-[-2px] w-3.5 h-3.5 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
|
||||
{detail.mode === 'advanced-chat' && (
|
||||
<ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
|
||||
)}
|
||||
{detail.mode === 'agent-chat' && (
|
||||
<CuteRobote className='w-2.5 h-2.5 text-indigo-600' />
|
||||
)}
|
||||
{detail.mode === 'chat' && (
|
||||
<ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
|
||||
)}
|
||||
{detail.mode === 'completion' && (
|
||||
<AiText className='w-2.5 h-2.5 text-[#0E9384]' />
|
||||
)}
|
||||
{detail.mode === 'workflow' && (
|
||||
<Route className='w-2.5 h-2.5 text-[#f79009]' />
|
||||
)}
|
||||
</span>
|
||||
<div className={s.statusPoint}>
|
||||
<Indicator color={appStatus ? 'green' : 'gray'} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isMobile && <div className={classNames(s.appInfo, 'ml-2')}>{detail?.name || '--'}</div>}
|
||||
@@ -131,7 +116,7 @@ const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
|
||||
<Divider className='mt-5' />
|
||||
{(relatedApps?.data && relatedApps?.data?.length > 0) && (
|
||||
<>
|
||||
{!isMobile && <div className='w-full px-2 pb-1 pt-4 uppercase text-xs text-gray-500 font-medium'>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>}
|
||||
{!isMobile && <div className={s.subTitle}>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>}
|
||||
{isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
|
||||
{relatedApps?.total || '--'}
|
||||
<PaperClipIcon className='h-4 w-4 text-gray-700' />
|
||||
@@ -213,14 +198,6 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
document.title = `${datasetRes.name || 'Dataset'} - Dify`
|
||||
}, [datasetRes])
|
||||
|
||||
const { setAppSiderbarExpand } = useStore()
|
||||
|
||||
useEffect(() => {
|
||||
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
|
||||
const mode = isMobile ? 'collapse' : 'expand'
|
||||
setAppSiderbarExpand(isMobile ? mode : localeMode)
|
||||
}, [isMobile, setAppSiderbarExpand])
|
||||
|
||||
if (!datasetRes && !error)
|
||||
return <Loading />
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@apply truncate text-gray-700 text-sm font-normal;
|
||||
}
|
||||
.iconWrapper {
|
||||
@apply relative w-6 h-6 rounded-lg;
|
||||
@apply relative w-6 h-6 bg-[#D5F5F6] rounded-md;
|
||||
}
|
||||
.statusPoint {
|
||||
@apply flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded;
|
||||
|
||||
@@ -22,8 +22,6 @@ import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/ap
|
||||
import DuplicateAppModal from '@/app/components/app/duplicate-modal'
|
||||
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
|
||||
import CreateAppModal from '@/app/components/explore/create-app-modal'
|
||||
import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
|
||||
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { getRedirection } from '@/utils/app-redirection'
|
||||
@@ -158,28 +156,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
className='block'
|
||||
>
|
||||
<div className={cn('flex cursor-pointer p-1 rounded-lg hover:bg-gray-100', open && 'bg-gray-100')}>
|
||||
<div className='relative shrink-0 mr-2'>
|
||||
<div className='shrink-0 mr-2'>
|
||||
<AppIcon size={expand ? 'large' : 'small'} icon={appDetail.icon} background={appDetail.icon_background} />
|
||||
<span className={cn(
|
||||
'absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm',
|
||||
!expand && '!w-3.5 !h-3.5 !bottom-[-2px] !right-[-2px]',
|
||||
)}>
|
||||
{appDetail.mode === 'advanced-chat' && (
|
||||
<ChatBot className={cn('w-3 h-3 text-[#1570EF]', !expand && '!w-2.5 !h-2.5')} />
|
||||
)}
|
||||
{appDetail.mode === 'agent-chat' && (
|
||||
<CuteRobote className={cn('w-3 h-3 text-indigo-600', !expand && '!w-2.5 !h-2.5')} />
|
||||
)}
|
||||
{appDetail.mode === 'chat' && (
|
||||
<ChatBot className={cn('w-3 h-3 text-[#1570EF]', !expand && '!w-2.5 !h-2.5')} />
|
||||
)}
|
||||
{appDetail.mode === 'completion' && (
|
||||
<AiText className={cn('w-3 h-3 text-[#0E9384]', !expand && '!w-2.5 !h-2.5')} />
|
||||
)}
|
||||
{appDetail.mode === 'workflow' && (
|
||||
<Route className={cn('w-3 h-3 text-[#f79009]', !expand && '!w-2.5 !h-2.5')} />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{expand && (
|
||||
<div className="grow w-0">
|
||||
@@ -221,25 +199,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
<div className='relative w-[320px] bg-white rounded-2xl shadow-xl'>
|
||||
{/* header */}
|
||||
<div className={cn('flex pl-4 pt-3 pr-3', !appDetail.description && 'pb-2')}>
|
||||
<div className='relative shrink-0 mr-2'>
|
||||
<div className='shrink-0 mr-2'>
|
||||
<AppIcon size="large" icon={appDetail.icon} background={appDetail.icon_background} />
|
||||
<span className='absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
|
||||
{appDetail.mode === 'advanced-chat' && (
|
||||
<ChatBot className='w-3 h-3 text-[#1570EF]' />
|
||||
)}
|
||||
{appDetail.mode === 'agent-chat' && (
|
||||
<CuteRobote className='w-3 h-3 text-indigo-600' />
|
||||
)}
|
||||
{appDetail.mode === 'chat' && (
|
||||
<ChatBot className='w-3 h-3 text-[#1570EF]' />
|
||||
)}
|
||||
{appDetail.mode === 'completion' && (
|
||||
<AiText className='w-3 h-3 text-[#0E9384]' />
|
||||
)}
|
||||
{appDetail.mode === 'workflow' && (
|
||||
<Route className='w-3 h-3 text-[#f79009]' />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className='grow w-0'>
|
||||
<div title={appDetail.name} className='flex justify-between items-center text-sm leading-5 font-medium text-gray-900 truncate'>{appDetail.name}</div>
|
||||
@@ -333,7 +294,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
)}/>
|
||||
<div className='px-4 pb-2'>
|
||||
<div className='flex items-center gap-1 text-gray-700 text-md leading-6 font-semibold'>
|
||||
{showSwitchTip === 'chat' ? t('app.newApp.advanced') : t('app.types.workflow')}
|
||||
{t('app.newApp.advanced')}
|
||||
<span className='px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span>
|
||||
</div>
|
||||
<div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { UserCircleIcon } from '@heroicons/react/24/solid'
|
||||
import cn from 'classnames'
|
||||
@@ -84,7 +84,7 @@ const Answer: FC<IAnswerProps> = ({
|
||||
}) => {
|
||||
const { id, content, more, feedback, adminFeedback, annotation, agent_thoughts } = item
|
||||
const isAgentMode = !!agent_thoughts && agent_thoughts.length > 0
|
||||
const hasAnnotation = useMemo(() => !!annotation, [annotation])
|
||||
const hasAnnotation = !!annotation?.id
|
||||
// const [annotation, setAnnotation] = useState<Annotation | undefined | null>(initAnnotation)
|
||||
// const [inputValue, setInputValue] = useState<string>(initAnnotation?.content ?? '')
|
||||
const [localAdminFeedback, setLocalAdminFeedback] = useState<Feedbacktype | undefined | null>(adminFeedback)
|
||||
@@ -136,6 +136,19 @@ const Answer: FC<IAnswerProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const renderHasAnnotationBtn = () => {
|
||||
return (
|
||||
<div
|
||||
className={cn(s.hasAnnotationBtn, 'relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-[#444CE7]')}
|
||||
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
|
||||
>
|
||||
<div className='p-1 rounded-lg bg-[#EEF4FF] '>
|
||||
<MessageFast className='w-4 h-4' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Different scenarios have different operation items.
|
||||
* @param isWebScene Whether it is web scene
|
||||
@@ -220,50 +233,6 @@ const Answer: FC<IAnswerProps> = ({
|
||||
</div>
|
||||
)
|
||||
|
||||
const [containerWidth, setContainerWidth] = useState(0)
|
||||
const [contentWidth, setContentWidth] = useState(0)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const getContainerWidth = () => {
|
||||
if (containerRef.current)
|
||||
setContainerWidth(containerRef.current?.clientWidth + 24)
|
||||
}
|
||||
const getContentWidth = () => {
|
||||
if (contentRef.current)
|
||||
setContentWidth(contentRef.current?.clientWidth)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getContainerWidth()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isResponding)
|
||||
getContentWidth()
|
||||
}, [isResponding])
|
||||
|
||||
const operationWidth = useMemo(() => {
|
||||
let width = 0
|
||||
if (!item.isOpeningStatement)
|
||||
width += 28
|
||||
if (!item.isOpeningStatement && isShowPromptLog)
|
||||
width += 102 + 8
|
||||
if (!item.isOpeningStatement && isShowTextToSpeech)
|
||||
width += 33
|
||||
if (!item.isOpeningStatement && supportAnnotation)
|
||||
width += 96 + 8
|
||||
if (!feedbackDisabled && !item.feedbackDisabled)
|
||||
width += 60 + 8
|
||||
if (!feedbackDisabled && localAdminFeedback?.rating && !item.isOpeningStatement)
|
||||
width += 60 + 8
|
||||
if (!feedbackDisabled && feedback?.rating && !item.isOpeningStatement)
|
||||
width += 28 + 8
|
||||
return width
|
||||
}, [item.isOpeningStatement, item.feedbackDisabled, isShowPromptLog, isShowTextToSpeech, supportAnnotation, feedbackDisabled, localAdminFeedback?.rating, feedback?.rating])
|
||||
|
||||
const positionRight = useMemo(() => operationWidth < containerWidth - contentWidth - 4, [operationWidth, containerWidth, contentWidth])
|
||||
|
||||
return (
|
||||
// data-id for debug the item message is right
|
||||
<div key={id} data-id={id}>
|
||||
@@ -279,9 +248,9 @@ const Answer: FC<IAnswerProps> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div ref={containerRef} className={cn(s.answerWrapWrap, 'chat-answer-container')}>
|
||||
<div className={cn(s.answerWrapWrap, 'chat-answer-container')}>
|
||||
<div className={cn(s.answerWrap, 'group')}>
|
||||
<div ref={contentRef} className={`${s.answer} relative text-sm text-gray-900`}>
|
||||
<div className={`${s.answer} relative text-sm text-gray-900`}>
|
||||
<div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
|
||||
{(isResponding && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content))
|
||||
? (
|
||||
@@ -318,7 +287,7 @@ const Answer: FC<IAnswerProps> = ({
|
||||
</div>
|
||||
{(hasAnnotation && !annotation?.logAnnotation) && (
|
||||
<EditTitle className='mt-1' title={t('appAnnotation.editBy', {
|
||||
author: annotation?.authorName,
|
||||
author: annotation.authorName,
|
||||
})} />
|
||||
)}
|
||||
{item.isOpeningStatement && item.suggestedQuestions && item.suggestedQuestions.filter(q => !!q && q.trim()).length > 0 && (
|
||||
@@ -342,23 +311,7 @@ const Answer: FC<IAnswerProps> = ({
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{hasAnnotation && (
|
||||
<div
|
||||
className={cn(s.hasAnnotationBtn, 'absolute -top-3.5 -right-3.5 box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-[#444CE7]')}
|
||||
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
|
||||
>
|
||||
<div className='p-1 rounded-lg bg-[#EEF4FF] '>
|
||||
<MessageFast className='w-4 h-4' />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute -top-3.5 flex justify-end gap-1',
|
||||
positionRight ? '!top-[9px]' : '-right-3.5',
|
||||
)}
|
||||
style={positionRight ? { left: contentWidth + 8 } : {}}
|
||||
>
|
||||
<div className='absolute top-[-14px] left-[-6px] flex flex-row justify-end gap-1'>
|
||||
{!item.isOpeningStatement && (
|
||||
<CopyBtn
|
||||
value={content}
|
||||
@@ -366,7 +319,7 @@ const Answer: FC<IAnswerProps> = ({
|
||||
/>
|
||||
)}
|
||||
{((isShowPromptLog && !isResponding) || (!item.isOpeningStatement && isShowTextToSpeech)) && (
|
||||
<div className='hidden group-hover:flex items-center w-max h-[28px] p-0.5 rounded-lg bg-white border-[0.5px] border-gray-100 shadow-md shrink-0'>
|
||||
<div className='hidden group-hover:flex items-center w-max h-[28px] p-0.5 rounded-lg bg-white border-[0.5px] border-gray-100 shadow-md'>
|
||||
{isShowPromptLog && !isResponding && (
|
||||
<Log logItem={item} />
|
||||
)}
|
||||
@@ -386,7 +339,7 @@ const Answer: FC<IAnswerProps> = ({
|
||||
appId={appId!}
|
||||
messageId={id}
|
||||
annotationId={annotation?.id || ''}
|
||||
className={cn(s.annotationBtn, 'ml-1 shrink-0')}
|
||||
className={cn(s.annotationBtn, 'ml-1')}
|
||||
cached={hasAnnotation}
|
||||
query={question}
|
||||
answer={content}
|
||||
@@ -409,6 +362,7 @@ const Answer: FC<IAnswerProps> = ({
|
||||
createdAt={annotation?.created_at}
|
||||
onRemove={() => { }}
|
||||
/>
|
||||
{hasAnnotation && renderHasAnnotationBtn()}
|
||||
|
||||
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation(displayScene !== 'console')}
|
||||
{/* Admin feedback is displayed only in the background. */}
|
||||
|
||||
@@ -20,13 +20,11 @@ import ConfigContext from '@/context/debug-configuration'
|
||||
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
||||
import ConfigVar from '@/app/components/app/configuration/config-var'
|
||||
import { type CitationConfig, type ModelConfig, type ModerationConfig, type MoreLikeThisConfig, type PromptVariable, type SpeechToTextConfig, type SuggestedQuestionsAfterAnswerConfig, type TextToSpeechConfig } from '@/models/debug'
|
||||
import type { AppType } from '@/types/app'
|
||||
import { ModelModeType } from '@/types/app'
|
||||
import { AppType, ModelModeType } from '@/types/app'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import ConfigParamModal from '@/app/components/app/configuration/toolbox/annotation/config-param-modal'
|
||||
import AnnotationFullModal from '@/app/components/billing/annotation-full/modal'
|
||||
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
const Config: FC = () => {
|
||||
const {
|
||||
@@ -61,9 +59,9 @@ const Config: FC = () => {
|
||||
moderationConfig,
|
||||
setModerationConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const isChatApp = ['advanced-chat', 'agent-chat', 'chat'].includes(mode)
|
||||
const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text)
|
||||
const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.tts)
|
||||
const isChatApp = mode !== AppType.completion
|
||||
const { data: speech2textDefaultModel } = useDefaultModel(4)
|
||||
const { data: text2speechDefaultModel } = useDefaultModel(5)
|
||||
const { setShowModerationSettingModal } = useModalContext()
|
||||
const formattingChangedDispatcher = useFormattingChangedDispatcher()
|
||||
|
||||
|
||||
+1
-3
@@ -20,7 +20,6 @@ import type { ModelConfig } from '@/app/components/workflow/types'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
type Props = {
|
||||
datasetConfigs: DatasetConfigs
|
||||
@@ -50,7 +49,7 @@ const ConfigContent: FC<Props> = ({
|
||||
const {
|
||||
modelList: rerankModelList,
|
||||
defaultModel: rerankDefaultModel,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(3)
|
||||
const rerankModel = (() => {
|
||||
if (datasetConfigs.reranking_model) {
|
||||
return {
|
||||
@@ -159,7 +158,6 @@ const ConfigContent: FC<Props> = ({
|
||||
</TooltipPlus>
|
||||
</div>
|
||||
<ModelParameterModal
|
||||
isInWorkflow={isInWorkflow}
|
||||
popupClassName='!w-[387px]'
|
||||
portalToFollowElemContentClassName='!z-[1002]'
|
||||
isAdvancedMode={true}
|
||||
|
||||
@@ -13,7 +13,6 @@ import { RETRIEVE_TYPE } from '@/types/app'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { DATASET_DEFAULT } from '@/config'
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
const ParamsConfig: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -27,7 +26,7 @@ const ParamsConfig: FC = () => {
|
||||
const {
|
||||
defaultModel: rerankDefaultModel,
|
||||
currentModel: isRerankDefaultModelVaild,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(3)
|
||||
|
||||
const isValid = () => {
|
||||
let errMsg = ''
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
useModelList,
|
||||
useModelListAndDefaultModelAndCurrentProviderAndModel,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
type SettingsModalProps = {
|
||||
currentDataset: DataSet
|
||||
@@ -43,12 +42,12 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
onCancel,
|
||||
onSave,
|
||||
}) => {
|
||||
const { data: embeddingsModelList } = useModelList(ModelTypeEnum.textEmbedding)
|
||||
const { data: embeddingsModelList } = useModelList(2)
|
||||
const {
|
||||
modelList: rerankModelList,
|
||||
defaultModel: rerankDefaultModel,
|
||||
currentModel: isRerankDefaultModelVaild,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(3)
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const ref = useRef(null)
|
||||
|
||||
@@ -31,7 +31,7 @@ import { IS_CE_EDITION } from '@/config'
|
||||
import type { Inputs } from '@/models/debug'
|
||||
import { fetchFileUploadConfig } from '@/service/common'
|
||||
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelFeatureEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
@@ -86,7 +86,7 @@ const Debug: FC<IDebug> = ({
|
||||
setVisionConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
|
||||
const { data: text2speechDefaultModel } = useDefaultModel(5)
|
||||
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
|
||||
useEffect(() => {
|
||||
setAutoFreeze(false)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { clone } from 'lodash-es'
|
||||
import produce from 'immer'
|
||||
import type { ChatPromptConfig, CompletionPromptConfig, ConversationHistoriesRole, PromptItem } from '@/models/debug'
|
||||
import { PromptMode } from '@/models/debug'
|
||||
import { ModelModeType } from '@/types/app'
|
||||
import { AppType, ModelModeType } from '@/types/app'
|
||||
import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
|
||||
import { PRE_PROMPT_PLACEHOLDER_TEXT, checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
||||
import { fetchPromptTemplate } from '@/service/debug'
|
||||
@@ -152,7 +152,7 @@ const useAdvancedPromptConfig = ({
|
||||
else
|
||||
draft.prompt.text = completionPromptConfig.prompt?.text.replace(PRE_PROMPT_PLACEHOLDER_TEXT, toReplacePrePrompt)
|
||||
|
||||
if (['advanced-chat', 'agent-chat', 'chat'].includes(appMode) && completionPromptConfig.conversation_histories_role.assistant_prefix && completionPromptConfig.conversation_histories_role.user_prefix)
|
||||
if (appMode === AppType.chat && completionPromptConfig.conversation_histories_role.assistant_prefix && completionPromptConfig.conversation_histories_role.user_prefix)
|
||||
draft.conversation_histories_role = completionPromptConfig.conversation_histories_role
|
||||
})
|
||||
setCompletionPromptConfig(newPromptConfig)
|
||||
|
||||
@@ -388,10 +388,7 @@ const Configuration: FC = () => {
|
||||
const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple
|
||||
doSetPromptMode(promptMode)
|
||||
if (promptMode === PromptMode.advanced) {
|
||||
if (modelConfig.chat_prompt_config && modelConfig.chat_prompt_config.prompt.length > 0)
|
||||
setChatPromptConfig(modelConfig.chat_prompt_config)
|
||||
else
|
||||
setChatPromptConfig(clone(DEFAULT_CHAT_PROMPT_CONFIG) as any)
|
||||
setChatPromptConfig(modelConfig.chat_prompt_config || clone(DEFAULT_CHAT_PROMPT_CONFIG) as any)
|
||||
setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any)
|
||||
setCanReturnToSimpleMode(false)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import type { AnnotationReplyConfig } from '@/models/debug'
|
||||
import { ANNOTATION_DEFAULT } from '@/config'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
type Props = {
|
||||
appId: string
|
||||
@@ -37,7 +36,7 @@ const ConfigParamModal: FC<Props> = ({
|
||||
modelList: embeddingsModelList,
|
||||
defaultModel: embeddingsDefaultModel,
|
||||
currentModel: isEmbeddingsDefaultModelValid,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textEmbedding)
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(2)
|
||||
const [annotationConfig, setAnnotationConfig] = useState(oldAnnotationConfig)
|
||||
|
||||
const [isLoading, setLoading] = useState(false)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user