Compare commits

...

1240 Commits

Author SHA1 Message Date
jyong
85e568e114 compatible query is None 2024-04-03 18:39:29 +08:00
jyong
0026cb404f compatible query is None 2024-04-03 18:38:27 +08:00
JzoNg
62919a9ff5 app list mutation 2024-04-03 18:06:36 +08:00
StyleZhang
e13f8da9d5 fix: prompt editor 2024-04-03 18:04:46 +08:00
takatost
53aca1a922 add sys variables for start node 2024-04-03 17:53:54 +08:00
StyleZhang
464aee08a1 fix: prompt editor 2024-04-03 17:45:27 +08:00
Joel
ab56e6b6af fix: enable memory add sys query var input 2024-04-03 17:42:21 +08:00
Joel
2a2f4cd4d5 fix: label ui 2024-04-03 17:22:10 +08:00
Joel
b3a4a52a7a fix: valid tool valid empty error 2024-04-03 17:18:16 +08:00
Joel
379f9b56ad fix: start node not show sys files 2024-04-03 16:55:55 +08:00
Joel
b705041dda chore: remove uage output 2024-04-03 16:52:01 +08:00
takatost
fedcfe94ae update version to 0.6.0-preview-workflow.2 2024-04-03 16:50:11 +08:00
Joel
add7bdc877 fix: add input instance id 2024-04-03 16:46:47 +08:00
StyleZhang
088842dcdb fix: prompt editor 2024-04-03 16:13:14 +08:00
Joel
c43eaeec06 fix: can not open 2024-04-03 15:48:57 +08:00
StyleZhang
f06554a11e fix: sync draft 2024-04-03 15:37:19 +08:00
Yeuoly
3ac37e802a fix: sandbox tips 2024-04-03 15:34:05 +08:00
JzoNg
aee669f67d fix max length for paragraph 2024-04-03 15:32:59 +08:00
Joel
c8db4d8a08 fix: editor choose context would blur 2024-04-03 15:15:35 +08:00
StyleZhang
c3bb541a69 fix 2024-04-03 15:13:26 +08:00
StyleZhang
ba3039d6c9 merge feat/workflow 2024-04-03 15:00:26 +08:00
Joel
bd3b400121 chore: support click to show choose add var 2024-04-03 14:57:16 +08:00
StyleZhang
28e813f57f fix 2024-04-03 14:38:15 +08:00
Joel
3f11e11c2d chore: confirm ui 2024-04-03 14:08:36 +08:00
Yeuoly
37a282cc1c fix: add default lora 2024-04-03 13:51:18 +08:00
JzoNg
88ef220d4d fix app list cache 2024-04-03 13:49:29 +08:00
Yeuoly
ccb67bffc4 fix: tools 2024-04-03 13:46:29 +08:00
StyleZhang
20394b3231 fix 2024-04-03 13:39:15 +08:00
nite-knite
52a1c4580c feat: update chat app publishing 2024-04-03 13:12:07 +08:00
JzoNg
aca395b97d fix test run 2024-04-03 12:44:32 +08:00
StyleZhang
459b690416 fix: prompt-editor 2024-04-03 12:31:40 +08:00
Yeuoly
d48bdf3e14 chore: generic 2024-04-03 12:02:51 +08:00
Joel
9fea2fd44b feat: change from creditical schema value from api 2024-04-03 11:56:54 +08:00
Joel
f291aec2cd chore: start input placeholder and bg 2024-04-03 11:41:44 +08:00
takatost
00d9c48461 fix migration version dependency 2024-04-02 22:46:38 +08:00
takatost
fef62d937d Merge branch 'main' into feat/workflow 2024-04-02 22:36:27 +08:00
takatost
6b06c5b957 optimize workflow inputs 2024-04-02 22:36:07 +08:00
takatost
5a4ea0932a add inputs for workflow_started event 2024-04-02 21:13:08 +08:00
StyleZhang
f7e4f0a988 fix: run error 2024-04-02 21:05:04 +08:00
StyleZhang
cf449b31a1 code node rename 2024-04-02 20:48:46 +08:00
StyleZhang
f7184c0e36 fix: tool node check 2024-04-02 20:19:50 +08:00
JzoNg
5df66579a8 fix crash of advanced prompt app 2024-04-02 20:12:43 +08:00
StyleZhang
d260e6b064 fix: prompt-editor 2024-04-02 19:55:58 +08:00
Yeuoly
01c6a35966 chore: encoder 2024-04-02 19:25:52 +08:00
StyleZhang
0202469254 fix: checklist 2024-04-02 19:06:22 +08:00
Joel
fb2fa625b4 fix: use sys query instead user query 2024-04-02 18:46:44 +08:00
Joel
fbdf2ba839 fix: classify default two classifies and empty check 2024-04-02 18:41:31 +08:00
Joel
716936e37a fix: remove key replicate 2024-04-02 18:30:57 +08:00
Yeuoly
1c004e0df6 optimize: sd 2024-04-02 18:21:08 +08:00
Joel
59d279fbe0 fix: remove rename check 2024-04-02 18:12:44 +08:00
Joel
2d7c43b60f feat: http panel 2024-04-02 17:45:12 +08:00
Joel
a9f7f88a9a feat: answer support render var input 2024-04-02 17:38:05 +08:00
nite-knite
56cb9ccec1 feat: update workflow app publishing 2024-04-02 17:33:35 +08:00
StyleZhang
e0a152164b fix: prompt-editor 2024-04-02 17:20:32 +08:00
JzoNg
d72524ceb0 hide result info in chatflow 2024-04-02 17:16:39 +08:00
jyong
74538fb3b2 Merge remote-tracking branch 'origin/feat/workflow' into feat/workflow 2024-04-02 16:59:04 +08:00
jyong
f832211e2e db migrate merge 2024-04-02 16:58:49 +08:00
Yeuoly
e46c3a9235 optimize: tool 2024-04-02 16:58:24 +08:00
JzoNg
36c3774fac modify test run panel 2024-04-02 16:57:28 +08:00
jyong
5e201324d6 Merge branch 'main' into feat/workflow
# Conflicts:
#	api/.env.example
#	docker/docker-compose.yaml
2024-04-02 16:55:43 +08:00
Joel
5adbcacc52 fix: end node can not selector 2024-04-02 15:52:15 +08:00
takatost
8b01796f5d fix external data convert 2024-04-02 15:30:39 +08:00
Joel
34f4f76f67 fix: handle debug run valid 2024-04-02 15:27:03 +08:00
Yeuoly
01e832a587 fix: linter 2024-04-02 15:25:15 +08:00
Yeuoly
1af2d06d29 feat: add tool benchmark 2024-04-02 15:23:54 +08:00
Joel
426abe2134 fix: variable type add missing key 2024-04-02 15:14:00 +08:00
StyleZhang
f62775bcad fix: prompt-editor 2024-04-02 14:12:52 +08:00
Joel
7a2083a6b7 fix: num suuport var insert 2024-04-02 14:12:34 +08:00
jyong
09650b9d47 Merge remote-tracking branch 'origin/feat/workflow' into feat/workflow 2024-04-02 14:03:02 +08:00
jyong
f19219ab8d fix knowledge retrival 2024-04-02 14:02:49 +08:00
Joel
8be04b57f9 fix: http attr key rerender 2024-04-02 13:55:30 +08:00
StyleZhang
ef39fa3fb2 node connect 2024-04-02 13:23:36 +08:00
JzoNg
fe569559ac fix app type label 2024-04-02 12:56:11 +08:00
JzoNg
8125d8fc9f modify params of app switch 2024-04-02 12:45:33 +08:00
JzoNg
fd8ed95209 fix prompt log 2024-04-02 12:34:40 +08:00
JzoNg
cf22842554 support app creation in nav 2024-04-02 12:15:28 +08:00
takatost
0fcb746c08 add created_at for app model config 2024-04-02 12:07:30 +08:00
StyleZhang
9d7ab0400d chat error 2024-04-02 12:01:32 +08:00
Yeuoly
396a3e0456 feat: add tool parameter type converter 2024-04-02 11:58:50 +08:00
Joel
56a1d5330a chore: types 2024-04-02 11:50:03 +08:00
takatost
c5e58c713c remove not necessary error reporting 2024-04-02 11:15:50 +08:00
Joel
6e0f13f269 feat: tool new struct 2024-04-02 11:14:33 +08:00
JzoNg
00728c2b1d support type fitlering for app template 2024-04-01 22:55:53 +08:00
JzoNg
9fb7100b3f modify style of app type tag 2024-04-01 22:55:53 +08:00
StyleZhang
4e31d7b64f chat 2024-04-01 21:17:39 +08:00
takatost
1ab3b73c14 add app info for workflow convert 2024-04-01 21:00:08 +08:00
StyleZhang
b5fa68fdfe node selected 2024-04-01 20:47:59 +08:00
Yeuoly
31f24e1a14 enhance: enable configurate limitation of code 2024-04-01 20:47:26 +08:00
StyleZhang
e800109c02 node selected 2024-04-01 20:33:20 +08:00
Joel
5c3162cc33 fix: http delete btn hide 2024-04-01 19:58:51 +08:00
StyleZhang
04b4be27b7 refresh history 2024-04-01 19:41:37 +08:00
takatost
5793855115 fix http single run 2024-04-01 19:40:49 +08:00
Joel
41cce464ca fix: http var inputs 2024-04-01 19:36:09 +08:00
takatost
8c55ff392d fix bugs 2024-04-01 19:33:53 +08:00
StyleZhang
45d5d259a4 fix prompt editor 2024-04-01 19:03:34 +08:00
Joel
e08d871837 fix: http other params check 2024-04-01 18:48:36 +08:00
Joel
ab2c112059 feat: reuse get vars inputs and http request url 2024-04-01 18:33:17 +08:00
Joel
a42f26d857 fix: object label not pass the right value 2024-04-01 17:52:11 +08:00
Joel
6fea18b4d0 feat: insert var key ui 2024-04-01 17:40:31 +08:00
Joel
7e259600bf fix: debugger form struct and textare line-height 2024-04-01 17:40:31 +08:00
Yeuoly
75e95e09d3 fix: test 2024-04-01 17:07:09 +08:00
StyleZhang
51f225e567 fix 2024-04-01 16:57:42 +08:00
Joel
53c988718b fix: no var caused bugs 2024-04-01 16:57:03 +08:00
Joel
74ead43ae1 fix: query selector set sys value problem 2024-04-01 16:50:09 +08:00
StyleZhang
d0509213d1 prompt editor 2024-04-01 16:47:41 +08:00
Yeuoly
5b81234db8 fix: tool entities 2024-04-01 16:43:10 +08:00
StyleZhang
df9e2e478f workflow template 2024-04-01 16:38:37 +08:00
takatost
7c64f2cfe0 feat: use en-US recommended apps as fallback if using unmaintained language 2024-04-01 16:24:59 +08:00
takatost
3b3d19dab7 Merge branch 'main' into feat/workflow
# Conflicts:
#	api/controllers/console/explore/recommended_app.py
2024-04-01 16:22:49 +08:00
JzoNg
806f27c370 revert automatic prompt 2024-04-01 16:00:34 +08:00
Yeuoly
86a32517e5 fix: tool variable selectors 2024-04-01 15:42:00 +08:00
takatost
072967a1d3 fix node single step run of answer & http request & llm 2024-04-01 15:24:35 +08:00
StyleZhang
9147e0046f node connect 2024-04-01 14:10:05 +08:00
JzoNg
f967203012 remove automatic 2024-04-01 13:31:30 +08:00
Joel
e2d0ff4784 chore: run text font size 2024-04-01 13:19:40 +08:00
StyleZhang
85ce2e8df8 merge main 2024-04-01 13:09:40 +08:00
Joel
c330f89c77 feat: support llm single run 2024-04-01 12:55:20 +08:00
StyleZhang
ffb698922a fix: edge 2024-04-01 12:54:15 +08:00
takatost
50a7c2c92c fix bug 2024-04-01 12:51:01 +08:00
Joel
0843af2996 fix: sys var not show 2024-04-01 12:39:54 +08:00
Joel
e9985f0696 chore: enchance debug show name 2024-04-01 12:29:51 +08:00
StyleZhang
dfad42075c sys variable 2024-04-01 12:06:22 +08:00
Joel
705d765a71 feat: llm show vars 2024-04-01 11:48:42 +08:00
Joel
e03367a188 feat: get input vars 2024-04-01 10:38:12 +08:00
Yeuoly
c20685e669 fix 2024-03-29 22:26:26 +08:00
takatost
429dd11dd7 add icon for tool node execution 2024-03-29 22:22:58 +08:00
takatost
b394dd6fb0 fix convert bug 2024-03-29 21:43:44 +08:00
takatost
a30a6dda63 Merge branch 'main' into feat/workflow
# Conflicts:
#	docker/docker-compose.yaml
2024-03-29 21:18:16 +08:00
takatost
de3b7e8815 http request node support template variable 2024-03-29 20:54:17 +08:00
Yeuoly
142d1be4f8 refactor 2024-03-29 20:53:48 +08:00
Yeuoly
fb364d44d1 refactor 2024-03-29 20:12:26 +08:00
jyong
a647698c32 Merge remote-tracking branch 'origin/feat/workflow' into feat/workflow 2024-03-29 19:44:35 +08:00
jyong
75ffdc9d3f fixed single retrival 2024-03-29 19:44:26 +08:00
StyleZhang
0d12e5c795 run history 2024-03-29 19:32:38 +08:00
jyong
bab88efda9 Merge remote-tracking branch 'origin/feat/workflow' into feat/workflow 2024-03-29 19:29:42 +08:00
jyong
ca6acf2650 fixed single retrival 2024-03-29 19:29:27 +08:00
Yeuoly
11b428a73f feat: agent log 2024-03-29 19:23:48 +08:00
nite-knite
f43faa125b feat: add condition placeholder to if-else node 2024-03-29 19:08:35 +08:00
Joel
6b3bc789b5 fix: http text pass vars 2024-03-29 19:04:53 +08:00
Joel
586488c6a9 feat: llm output and raw text 2024-03-29 19:04:53 +08:00
jyong
704cb42869 Merge remote-tracking branch 'origin/feat/workflow' into feat/workflow
# Conflicts:
#	api/core/workflow/nodes/question_classifier/question_classifier_node.py
2024-03-29 19:00:21 +08:00
jyong
2d26c4745b add history message 2024-03-29 18:58:59 +08:00
takatost
971436d935 llm and answer node support inner variable template 2024-03-29 18:44:30 +08:00
Joel
8a2d04b305 chore: llm editor bg and not flash 2024-03-29 18:32:59 +08:00
StyleZhang
7c45f369d1 checklist 2024-03-29 18:27:41 +08:00
JzoNg
6444d94f41 fix style of app card 2024-03-29 18:24:46 +08:00
Joel
a8236a270a feat: body to json editor 2024-03-29 18:19:45 +08:00
StyleZhang
760ada399f checklist 2024-03-29 18:08:27 +08:00
Joel
815262b9a6 chore: remove input vars 2024-03-29 18:02:06 +08:00
Joel
83651a038f feat: http attr support selct keys 2024-03-29 17:55:59 +08:00
Joel
589ac9b22c feat: http key value inputs 2024-03-29 17:24:36 +08:00
StyleZhang
d673b4c219 fix: prompt editor 2024-03-29 17:20:48 +08:00
Joel
4e548fff5e feat: add insert var tooltip 2024-03-29 16:58:07 +08:00
Joel
636603d5af chore: type picker 2024-03-29 16:32:36 +08:00
Joel
950a52f4fc feat: input var ui 2024-03-29 16:19:09 +08:00
StyleZhang
b50e897aa0 fix: prompt editor 2024-03-29 16:08:10 +08:00
StyleZhang
d7be9c0afc prompt editor 2024-03-29 14:59:13 +08:00
StyleZhang
06a6d398cd checklist 2024-03-29 14:56:47 +08:00
Joel
12ed31be4d feat: api support var logic 2024-03-29 14:56:32 +08:00
Joel
8d2ac8ff8f feat: ignore invalid vars keys 2024-03-29 13:56:13 +08:00
Joel
91b84d8f1e chore: http node check 2024-03-29 13:01:36 +08:00
Joel
46cc635e05 fix: error status code 2024-03-29 12:35:04 +08:00
Joel
1ea8504cf1 chore: code output var empty check 2024-03-29 11:48:45 +08:00
Joel
a32465eeb8 chore: handle key exists check 2024-03-29 11:37:16 +08:00
Joel
f930521d64 chore: start var name check 2024-03-29 11:19:24 +08:00
JzoNg
42ad622a6c fix tool icon 2024-03-28 21:49:56 +08:00
JzoNg
4eb9027510 add icon for tool node in web app 2024-03-28 21:47:10 +08:00
JzoNg
05bb65bd94 add icon for tool node 2024-03-28 21:37:23 +08:00
Yeuoly
85285931e2 feat: add agent tool invoke meta 2024-03-28 20:04:31 +08:00
JzoNg
d7c4032917 fix style of app creation 2024-03-28 19:48:45 +08:00
Yeuoly
c1466a7a4d Merge branch 'feat/merge-tool-engine' into feat/workflow 2024-03-28 18:44:12 +08:00
Yeuoly
51404f9035 refactor: tool engine 2024-03-28 18:36:58 +08:00
Joel
c1bf4c6405 chore: var picker ui 2024-03-28 18:23:05 +08:00
Joel
b8818c90b0 feat: answer use selector vars 2024-03-28 17:41:41 +08:00
Joel
ead55ce931 chore: support hide editor var search 2024-03-28 17:37:11 +08:00
takatost
0a0d9565ac add icon return for tool node in workflow event stream 2024-03-28 17:26:09 +08:00
StyleZhang
4235baf493 editor 2024-03-28 17:11:39 +08:00
Joel
82a82fff35 chore: llm remove var inputs 2024-03-28 16:54:14 +08:00
Joel
4934a655dd chore: xxx 2024-03-28 16:48:54 +08:00
Joel
615178dafa feat: get var list hooks 2024-03-28 16:44:11 +08:00
Joel
08650339d7 feat: split var reference 2024-03-28 15:27:11 +08:00
StyleZhang
12ea3af242 fix: sync draft 2024-03-28 14:47:09 +08:00
takatost
858ab8c8c4 merge main 2024-03-28 14:38:21 +08:00
takatost
63a4ddc251 add text/plain support for draft sync api 2024-03-28 14:36:24 +08:00
StyleZhang
aa4d734244 fix 2024-03-28 12:39:24 +08:00
Joel
85b45a7cd0 fix: http json value would changed 2024-03-28 11:34:31 +08:00
StyleZhang
de3fd0f382 fix 2024-03-27 21:00:53 +08:00
JzoNg
db3f38bc2b workflow webapp result modification 2024-03-27 18:42:07 +08:00
StyleZhang
d239e6bf0f fix 2024-03-27 17:36:16 +08:00
jyong
9e4b39e19f fix question classifier node type 2024-03-27 17:03:49 +08:00
Yeuoly
078b10a9f0 fix: linter 2024-03-27 16:01:09 +08:00
Yeuoly
c70d0546ae sign single step files 2024-03-27 16:00:54 +08:00
JzoNg
c3d926e2ed templates filtering 2024-03-27 15:49:33 +08:00
Joel
78a851d240 fix: hide readonly tooltip 2024-03-27 14:47:23 +08:00
Yeuoly
6256a3fadb fix: missing datasets 2024-03-27 14:44:04 +08:00
jyong
8def0f8cf2 Merge remote-tracking branch 'origin/feat/workflow' into feat/workflow 2024-03-27 14:19:38 +08:00
jyong
a29d3f2400 fix question classifier issue when llm
is completion mode
2024-03-27 14:19:23 +08:00
Joel
794c57b938 fix: code editor readonly can get focus 2024-03-27 13:49:22 +08:00
Joel
9eff8715fb fix: model params in question classify 2024-03-27 13:38:26 +08:00
Yeuoly
e952e01dfe fix: sql 2024-03-27 12:28:07 +08:00
Yeuoly
a20d305842 fix: missing agent 2024-03-27 12:26:00 +08:00
Yeuoly
17d1e2e5b7 fix: template transform node output length 2024-03-27 11:51:12 +08:00
Joel
1c05d2ef7f fix: questioin classify memory limit 2024-03-27 11:06:51 +08:00
StyleZhang
f9caa09cac fix: empty chat 2024-03-27 10:39:41 +08:00
JzoNg
829a7b0d16 Merge branch 'main' into feat/workflow 2024-03-27 10:33:28 +08:00
JzoNg
83aaacd71d app creation update 2024-03-26 18:53:02 +08:00
Joel
a56115a664 chore: message type i18n 2024-03-26 18:07:35 +08:00
Joel
b5578c754f fix: var too long 2024-03-26 17:18:23 +08:00
Joel
de00245af0 fix: not highlight query block 2024-03-26 17:02:41 +08:00
Joel
2c9d4c8dca feat: value support hights 2024-03-26 16:47:38 +08:00
Joel
1ac96564a0 feat: http node hightlight node 2024-03-26 16:35:53 +08:00
StyleZhang
c15677634f merge main 2024-03-26 15:25:02 +08:00
Joel
2dd2c8c358 feat: support url highlight 2024-03-26 15:02:34 +08:00
Joel
8e3be982eb feat: chat memory placeholder 2024-03-26 14:19:50 +08:00
Joel
46f4e61edc fix: tools value too long ui 2024-03-26 13:52:35 +08:00
StyleZhang
a35b5e4fff chat restart 2024-03-25 18:42:51 +08:00
StyleZhang
07091c9d33 node panel resize 2024-03-25 18:02:09 +08:00
Joel
b3db119146 feat: memory example 2024-03-25 16:36:33 +08:00
Joel
28206cac72 feat: move start output var to vars 2024-03-25 15:51:31 +08:00
Joel
47f2fe591d feat: default fold output 2024-03-25 15:24:33 +08:00
Joel
acd0e22b9e feat: handle limit tool var type 2024-03-25 15:00:36 +08:00
Joel
2ebd8d9fdc feat: support var search 2024-03-25 14:30:43 +08:00
Joel
b5fe1f7c46 chore: var reference support portal 2024-03-25 11:34:56 +08:00
takatost
d7b2fe1e8b update docker compose images verison 2024-03-24 00:14:53 +09:00
takatost
fac9459402 add workflow image build 2024-03-23 23:19:33 +09:00
takatost
6cf0e0c242 Merge branch 'main' into feat/workflow 2024-03-23 23:09:36 +09:00
takatost
656bd9257d Merge branch 'feat/workflow-backend' into feat/workflow 2024-03-23 23:09:20 +09:00
Yeuoly
38441c930c fix: tool sort 2024-03-23 17:54:40 +08:00
StyleZhang
dafdbfa0fd fix: next step tool icon 2024-03-23 12:15:38 +08:00
JzoNg
a264973366 hide log panel in web app 2024-03-22 17:23:40 +08:00
Joel
5843b30a13 feat: support var remove in code node 2024-03-22 15:20:09 +08:00
Joel
340ae3c52f feat: remove var check in start node 2024-03-22 15:10:01 +08:00
StyleZhang
817e16493f checklist 2024-03-22 14:58:11 +08:00
Joel
66cf787755 fix: can remove struct 2024-03-22 14:35:22 +08:00
StyleZhang
4f3872277c all tools 2024-03-22 13:08:28 +08:00
Yeuoly
9b84086bac fix: tool provider icon 2024-03-22 12:43:56 +08:00
JzoNg
ce2b2755af add description for workflow 2024-03-22 10:30:20 +08:00
takatost
a91bec033d fix bug 2024-03-21 22:04:43 +08:00
JzoNg
096cc74373 hide node info in chat 2024-03-21 19:56:43 +08:00
takatost
34db42ecea fix bug 2024-03-21 18:37:37 +08:00
takatost
34e8d2f6bb add message error record 2024-03-21 18:30:23 +08:00
Joel
a771d59b1e fix: model param inner ui 2024-03-21 18:14:12 +08:00
JzoNg
3a00941125 fix style of dataset 2024-03-21 18:08:10 +08:00
StyleZhang
8e9ade14df fix: style 2024-03-21 17:49:12 +08:00
JzoNg
a1ec45fdd1 fix style of message log operation 2024-03-21 17:47:23 +08:00
Joel
9295739dc0 fix: model trigger ui 2024-03-21 17:45:45 +08:00
StyleZhang
4afb16844c chat stop 2024-03-21 17:30:59 +08:00
takatost
c4e6ed1aa2 optimize codes 2024-03-21 17:12:52 +08:00
takatost
95c5848d05 update workflow app bind datasets 2024-03-21 17:06:45 +08:00
JzoNg
8e56096f83 fix style of nav 2024-03-21 16:58:00 +08:00
Joel
c8f51dd6db chore: edit code support line wrap 2024-03-21 16:56:49 +08:00
Joel
ebbb30de44 fix: wrong infernece 2024-03-21 16:48:53 +08:00
Joel
fe1168d15a feat: code var sync 2024-03-21 16:43:36 +08:00
StyleZhang
fd7fded6e5 fix: style 2024-03-21 16:34:56 +08:00
Joel
8cffbc6b2a chore: more info to workflows 2024-03-21 16:23:47 +08:00
Yeuoly
fa673f9b4c fix: raw text 2024-03-21 16:21:59 +08:00
Joel
178f1fc5d6 fix: tools var renmae problem 2024-03-21 16:15:50 +08:00
Yeuoly
0c409e2b9e enhance: increase code timeout 2024-03-21 16:14:16 +08:00
StyleZhang
e366e12be0 fix: running line 2024-03-21 16:12:55 +08:00
Joel
8e0d8fdb3f feat: other nodes support rename and fix knonw set var bug 2024-03-21 16:06:45 +08:00
StyleZhang
524b19bb3a node style 2024-03-21 15:59:20 +08:00
JzoNg
fb2e351c08 fix icons 2024-03-21 15:56:20 +08:00
Yeuoly
260fef40c4 enhance: full tools 2024-03-21 15:40:08 +08:00
takatost
72818e946d fix llm memory 2024-03-21 15:36:25 +08:00
Joel
e673c64534 feat: llm rename 2024-03-21 15:23:38 +08:00
Joel
93bbb2694f temp publish not check valid 2024-03-21 15:18:10 +08:00
Joel
b038b7aa33 fix: can not choose var type 2024-03-21 15:14:54 +08:00
Joel
02a059bdc6 feat: var name rename struct 2024-03-21 15:07:44 +08:00
StyleZhang
267d9568c6 fix: running status 2024-03-21 15:03:49 +08:00
takatost
d71eae8f93 fix qc 2024-03-21 15:02:55 +08:00
StyleZhang
8bdaab96b1 fix 2024-03-21 14:25:11 +08:00
takatost
a05fcedd61 fix stop 2024-03-21 14:04:22 +08:00
takatost
0db67a2fd3 fix features not publish 2024-03-21 13:47:10 +08:00
JzoNg
6e56a504fd fix docs for advanced-chat 2024-03-21 12:39:46 +08:00
JzoNg
69fa8c9794 add docs for advanced-chat 2024-03-21 12:36:28 +08:00
JzoNg
8dc8650ecb fix type of workflow process 2024-03-21 09:24:09 +08:00
JzoNg
40775e27ce correct api doc of workflow 2024-03-20 23:46:53 +08:00
JzoNg
6fb294202d modify workflow web app output 2024-03-20 23:07:43 +08:00
Yeuoly
bd409a3caf enhance: code node validator 2024-03-20 23:01:24 +08:00
takatost
0d0da9a892 fix variable assigner multi route 2024-03-20 22:49:24 +08:00
takatost
a7e2f9caf0 fix variable assigner 2024-03-20 22:27:59 +08:00
Joel
3d4d60a353 feat: llm intput only number and str 2024-03-20 22:00:56 +08:00
Joel
75e876b14e chore: reduce more var limit 2024-03-20 21:56:18 +08:00
Joel
66fd60bc6f fix: var objects sorts change 2024-03-20 21:56:18 +08:00
takatost
c3e7299494 fix service api blocking mode 2024-03-20 21:55:06 +08:00
StyleZhang
8fc576870d fix 2024-03-20 20:52:19 +08:00
Joel
d4f362164f fix: memory support switch 2024-03-20 20:50:16 +08:00
StyleZhang
94ca0edb68 run history 2024-03-20 20:27:52 +08:00
takatost
a0dde6e4da fix bug 2024-03-20 20:02:51 +08:00
Joel
17f572f23f feat: can not add context 2024-03-20 19:35:42 +08:00
StyleZhang
137746387d fix style 2024-03-20 19:24:47 +08:00
JzoNg
9b5deaf80a add proccess of workflow in web app 2024-03-20 18:56:03 +08:00
Joel
1201bef879 chore: picker width set 2024-03-20 18:18:34 +08:00
takatost
30a9b8b917 fix bug 2024-03-20 17:52:47 +08:00
takatost
77bdc6ffb1 fix bug 2024-03-20 17:36:56 +08:00
takatost
a65c99496b add extra info for workflow stream output 2024-03-20 17:34:07 +08:00
Joel
a8c86b759d fix: var02 icon show 2024-03-20 17:31:46 +08:00
StyleZhang
76081db6e4 fix: style 2024-03-20 17:27:45 +08:00
Joel
0606b6f922 fix: remove datasets problem 2024-03-20 17:23:18 +08:00
Joel
9ed2a99abf feat: var reference support readonly 2024-03-20 17:01:53 +08:00
Joel
8f311b020a feat: tools readonly 2024-03-20 16:56:09 +08:00
Yeuoly
de6cbc36bb enhance: code return tyoe 2024-03-20 16:54:32 +08:00
Joel
0aa984219f feat: telmplate transform support readonly 2024-03-20 16:52:01 +08:00
Joel
c9168c19cd feat: question classify support readonly 2024-03-20 16:48:52 +08:00
Joel
beca05848c feat: llm support readonly 2024-03-20 16:43:00 +08:00
StyleZhang
2e5acef1b6 fix 2024-03-20 16:42:13 +08:00
Joel
c4811f921f feat: retrieval support readonly 2024-03-20 16:36:49 +08:00
Joel
7569346943 feat: if support readonly 2024-03-20 16:27:41 +08:00
StyleZhang
2919cc9adf fix 2024-03-20 16:25:02 +08:00
JzoNg
18883d9faa fix agent config 2024-03-20 16:20:19 +08:00
Joel
e462ddb805 feat: http support readonly 2024-03-20 16:17:04 +08:00
Joel
df274416f9 feat: end support readonly 2024-03-20 16:00:41 +08:00
JzoNg
b35d9f6c36 fix style of running workflow 2024-03-20 15:54:21 +08:00
Joel
4c5737fc7f feat: code support readonly 2024-03-20 15:53:14 +08:00
Joel
7bb1decaf8 feat: support prompt readonly 2024-03-20 15:46:21 +08:00
StyleZhang
4bef2eed25 chat add workflow process 2024-03-20 15:44:51 +08:00
takatost
0d2a90adf3 fix knowledge retriever return 2024-03-20 15:43:22 +08:00
Joel
38a1ea139a feat: answer support readonly 2024-03-20 15:32:06 +08:00
Joel
698eb9671f feat: start support readonly 2024-03-20 15:09:12 +08:00
Joel
1b857eba29 chore: remove useless 2024-03-20 14:49:39 +08:00
Joel
b060b773ef fix: set default logic error 2024-03-20 14:42:35 +08:00
Joel
ae197fb2ba fix: switch provider call infinate 2024-03-20 14:42:35 +08:00
StyleZhang
2697454a8e fix 2024-03-20 14:10:43 +08:00
Joel
2a75258836 feat: not show var 2024-03-20 13:55:26 +08:00
takatost
b50f221327 fix bug 2024-03-20 12:47:36 +08:00
Joel
d984eb3648 chore: workflow editor not choose outtool in var 2024-03-20 12:05:30 +08:00
Joel
4df8fa0afb feat: if change to defalut operator 2024-03-20 11:42:57 +08:00
Joel
67b3ee3776 feat: ifelse check and item choose var first 2024-03-20 11:35:31 +08:00
takatost
8337e3c6ba fix lint 2024-03-20 11:23:33 +08:00
takatost
a9b8917e22 fix bug 2024-03-20 11:23:25 +08:00
JzoNg
70698b553e fix prompt log in completion debug 2024-03-20 11:09:03 +08:00
Joel
b131c5dc73 fix: code defalut may not switch if not load config 2024-03-20 10:50:43 +08:00
Joel
15e2ab9203 feat: ifelse not set var not change selector 2024-03-20 10:50:43 +08:00
JzoNg
d5c79e0489 fix user-inputs generation 2024-03-20 10:45:30 +08:00
jyong
884eeebe83 fix react response 2024-03-20 04:00:50 +08:00
jyong
9042db301d fix page content is empty 2024-03-20 03:50:28 +08:00
takatost
f4f8d6c652 Merge branch 'main' into feat/workflow-backend
# Conflicts:
#	api/core/model_runtime/model_providers/anthropic/llm/llm.py
2024-03-20 00:06:33 +08:00
takatost
20cd3e52d0 fix qc bug 2024-03-19 23:55:06 +08:00
takatost
53fa4ffe73 fix bug 2024-03-19 21:53:24 +08:00
takatost
8acd6f2531 fix bug 2024-03-19 21:10:19 +08:00
takatost
8d8bbc586e fix bug 2024-03-19 20:57:07 +08:00
takatost
df4e1339da fix convert bug 2024-03-19 20:51:06 +08:00
takatost
0183651cd5 fix stream output 2024-03-19 20:34:43 +08:00
StyleZhang
9f024835aa chat 2024-03-19 20:34:10 +08:00
jyong
45017f3f35 fix knowledge single retrieve when function call response is none 2024-03-19 20:08:16 +08:00
StyleZhang
089072432e chat log 2024-03-19 19:46:14 +08:00
jyong
6e600bc0dc Merge remote-tracking branch 'origin/feat/workflow-backend' into feat/workflow-backend 2024-03-19 19:41:33 +08:00
jyong
25995eb735 fix knowledge single retrieve when function call response is none 2024-03-19 19:41:18 +08:00
JzoNg
28dc089540 fix style of node tracing 2024-03-19 19:24:36 +08:00
StyleZhang
8967c4c8f6 fix 2024-03-19 19:19:47 +08:00
Yeuoly
3969ed6f69 enhance: check valid JSON 2024-03-19 19:01:09 +08:00
JzoNg
56b025ebdd fix import by DSL 2024-03-19 18:57:57 +08:00
JzoNg
aab5566d98 fix app switch 2024-03-19 18:49:51 +08:00
Yeuoly
a9e44b1fd2 fix: missing head 2024-03-19 18:38:06 +08:00
takatost
bae1bc2e4b fix 2024-03-19 18:37:27 +08:00
StyleZhang
b9f58d3c1d Merge branch 'main' into feat/workflow 2024-03-19 18:37:09 +08:00
takatost
7c7f3958ff feat: optimize ollama model default parameters (#2894) 2024-03-19 18:36:30 +08:00
Lance Mao
85da94aac4 fix incorrect exception raised by api tool which leads to incorrect L… (#2886)
Co-authored-by: OSS-MAOLONGDONG\kaihong <maolongdong@kaihong.com>
2024-03-19 18:36:30 +08:00
Su Yang
5350753905 chore: update Qwen model params (#2892) 2024-03-19 18:36:30 +08:00
crazywoola
e7895cdc53 chore: update pr template (#2893) 2024-03-19 18:36:30 +08:00
Su Yang
b84d4bdb85 chore: Update TongYi models prices (#2890) 2024-03-19 18:36:30 +08:00
呆萌闷油瓶
66538d8cbd feat:support azure openai llm 0125 version (#2889) 2024-03-19 18:36:30 +08:00
Su Yang
4e24e116aa chore: use API Key instead of APIKey (#2888) 2024-03-19 18:36:29 +08:00
Bowen Liang
3f13c47b9b Bump tiktoken to 0.6.0 to support text-embedding-3-* in encoding_for_model (#2891) 2024-03-19 18:36:29 +08:00
Su Yang
10237c99e4 fix: anthropic system prompt not working (#2885) 2024-03-19 18:36:28 +08:00
Su Yang
faf936416f fix: Fix the problem of system not working (#2884) 2024-03-19 18:36:14 +08:00
crazywoola
779f77ccd6 feat: add icons for 01.ai (#2883) 2024-03-19 18:36:14 +08:00
Su Yang
758b8bf812 i18n: update bedrock label (#2879) 2024-03-19 18:36:14 +08:00
Su Yang
c61f51dc5d feat: AWS Bedrock Claude3 (#2864)
Co-authored-by: crazywoola <427733928@qq.com>
Co-authored-by: Chenhe Gu <guchenhe@gmail.com>
2024-03-19 18:36:14 +08:00
Yeuoly
b17e30b1c2 fix: form-data 2024-03-19 18:30:13 +08:00
Joel
0756b09cf5 chore: var assigner output 2024-03-19 18:19:07 +08:00
Yeuoly
2f16b3600c fix: avoid space in http key 2024-03-19 18:13:30 +08:00
Yeuoly
55d2417906 fix: invalid http header 2024-03-19 18:12:50 +08:00
Joel
49dd5b76f1 chore: http remove blank to value 2024-03-19 18:08:09 +08:00
Joel
43429108f5 chore: http files 2024-03-19 18:05:32 +08:00
Joel
18159b1a4b feat: valid assignes 2024-03-19 18:03:54 +08:00
StyleZhang
b31da3b195 initial node position 2024-03-19 17:59:35 +08:00
takatost
17b7426cc6 fix external_data_tools bug 2024-03-19 17:58:33 +08:00
Joel
ba7b9a595b fix: tool invaild 2024-03-19 17:52:43 +08:00
takatost
7778901630 fix tool image render 2024-03-19 17:49:26 +08:00
JzoNg
8f7356cc12 fix completion log item 2024-03-19 17:37:17 +08:00
Joel
d49834ee56 feat: if node valid 2024-03-19 17:33:49 +08:00
Joel
e4fdf1730e chore: change output 2024-03-19 17:29:40 +08:00
StyleZhang
f41a619490 check before publish 2024-03-19 17:24:35 +08:00
jyong
1607fcfaa7 fix knowledge single retrieve when function call response is none 2024-03-19 17:18:29 +08:00
Yeuoly
8386abaed1 fix: file 2024-03-19 17:07:44 +08:00
JzoNg
16a1562900 fix style of tip modal 2024-03-19 17:06:33 +08:00
Joel
9b1869f521 feat: template transform code valid 2024-03-19 16:58:13 +08:00
Joel
3dfcd9ca67 feat: valid question classify 2024-03-19 16:58:13 +08:00
takatost
74408c4ced fix app convert 2024-03-19 16:44:28 +08:00
JzoNg
653917649d add beta tag and fix some style 2024-03-19 16:43:24 +08:00
JzoNg
00f51749a3 add switch operation in app list 2024-03-19 16:18:22 +08:00
Joel
8d3158a6d5 feat: tool valid 2024-03-19 16:14:25 +08:00
StyleZhang
dbaf54c93d chat 2024-03-19 16:04:34 +08:00
takatost
7762737796 optimize app list desc 2024-03-19 15:40:03 +08:00
takatost
133d52deb9 fix bug 2024-03-19 15:32:10 +08:00
StyleZhang
0ede136d67 fix: single run sync draft 2024-03-19 15:30:13 +08:00
Joel
8d82d9f7ef fix: overwrite defalut value in tool 2024-03-19 15:27:33 +08:00
JzoNg
1532564601 modify operations in app list 2024-03-19 15:27:14 +08:00
takatost
24ac4996c0 fix bug 2024-03-19 15:20:03 +08:00
takatost
112593119a fix suggested_questions_after_answer 2024-03-19 15:12:29 +08:00
StyleZhang
0c100ac0b1 fix node 2024-03-19 15:07:13 +08:00
Joel
45168d0e00 remove log 2024-03-19 14:55:11 +08:00
Joel
dc91b2e3df fix: retieveal output error and var ref error 2024-03-19 14:54:23 +08:00
StyleZhang
ced6a5c18b answer node 2024-03-19 14:50:33 +08:00
JzoNg
0b7cdd1e5d node collapse 2024-03-19 14:47:03 +08:00
Joel
67de047122 fix: http not pass headers and so on 2024-03-19 14:42:54 +08:00
jyong
4ec14d8d91 fix knowledge single retrieve when function call response is none 2024-03-19 14:17:22 +08:00
Joel
09516726e9 fix: overwrite template 2024-03-19 13:58:51 +08:00
Joel
6bfd61a887 feat: retrieval check valid 2024-03-19 13:58:51 +08:00
StyleZhang
a436550dff workflow info 2024-03-19 13:50:10 +08:00
StyleZhang
f5a3069913 sync draft 2024-03-19 13:18:14 +08:00
JzoNg
cf0c96e0d1 fix workflow outputs 2024-03-19 12:34:03 +08:00
Joel
978ee93df7 fix: not show sys var type 2024-03-19 11:54:47 +08:00
Joel
f3bf4c7730 feat: code default value 2024-03-19 11:36:29 +08:00
Joel
d2de16fba2 fix: var list defalut 2024-03-19 10:57:05 +08:00
Joel
90543c458c feat: valid before run struct 2024-03-19 10:46:28 +08:00
JzoNg
d6e655eaae fix restart button 2024-03-19 09:43:39 +08:00
JzoNg
9884466ef0 fix judgement of app configure 2024-03-19 09:33:18 +08:00
JzoNg
3b4676d8e9 fix agent configuration 2024-03-19 08:55:47 +08:00
JzoNg
3ee9f74cf8 fix style of completion creation 2024-03-19 08:02:56 +08:00
takatost
1c7573a686 add logging callback for workflow 2024-03-19 04:37:29 +08:00
takatost
2da7cc6928 fix file bugs 2024-03-19 03:56:47 +08:00
JzoNg
fda802e796 chore: remove comments 2024-03-18 22:46:19 +08:00
jyong
5a5beb5b59 Merge remote-tracking branch 'origin/feat/workflow-backend' into feat/workflow-backend 2024-03-18 22:38:27 +08:00
jyong
a0b16e541c question classifier 2024-03-18 22:38:12 +08:00
JzoNg
a67777b8e2 app overview 2024-03-18 22:32:21 +08:00
Joel
eae4c80679 fix: input text error 2024-03-18 22:12:46 +08:00
Yeuoly
ac63b5385a fix: set code execution timeout 2024-03-18 22:12:21 +08:00
StyleZhang
f61ceadec5 fix 2024-03-18 22:03:26 +08:00
Yeuoly
5ff2fbed59 fix: linter 2024-03-18 22:00:35 +08:00
Yeuoly
d24cf9e56a limit http response 2024-03-18 22:00:34 +08:00
Joel
7b9fbccf60 feat: support add files and vision 2024-03-18 21:59:51 +08:00
takatost
0b07c6914a fix bugs 2024-03-18 21:52:39 +08:00
jyong
f803fb5855 Merge remote-tracking branch 'origin/feat/workflow-backend' into feat/workflow-backend 2024-03-18 21:51:32 +08:00
jyong
cd3c2f6b00 knowledge fix 2024-03-18 21:51:23 +08:00
takatost
587ba27f8c fix bugs 2024-03-18 21:42:45 +08:00
takatost
1b0acdbe63 fix message resign url 2024-03-18 21:22:58 +08:00
jyong
3e810bc490 knowledge fix 2024-03-18 21:22:16 +08:00
Yeuoly
cc86850ad9 pure: rm file transformer 2024-03-18 21:17:13 +08:00
Yeuoly
fed19db938 feat: http download file 2024-03-18 21:16:21 +08:00
takatost
9175eb455f fix context 2024-03-18 21:11:27 +08:00
StyleZhang
a89287bf20 block icon 2024-03-18 21:03:02 +08:00
takatost
977020f580 lint fix 2024-03-18 20:59:22 +08:00
takatost
a2195c813c fix file render 2024-03-18 20:59:11 +08:00
jyong
d5a404236a knowledge fix 2024-03-18 20:54:50 +08:00
JzoNg
202492e5ac message log style modified 2024-03-18 20:54:18 +08:00
Joel
601e888fde feat: handle sys var to run 2024-03-18 20:40:38 +08:00
jyong
4a483a8754 Merge remote-tracking branch 'origin/feat/workflow-backend' into feat/workflow-backend 2024-03-18 20:35:23 +08:00
jyong
a4f367b8ff knowledge fix 2024-03-18 20:35:10 +08:00
Yeuoly
e225a3d33c linter 2024-03-18 20:22:25 +08:00
Joel
31b6383697 fix: to new sys vars 2024-03-18 20:12:43 +08:00
Yeuoly
e7d6def1e8 fix: trim file extension 2024-03-18 19:59:54 +08:00
Yeuoly
197c0bb1a3 fix: jsonable_encoder 2024-03-18 19:56:38 +08:00
Joel
d6953f28d3 chore: remove not necessary config 2024-03-18 19:52:40 +08:00
StyleZhang
249f013ca3 fix 2024-03-18 19:50:55 +08:00
takatost
387a6cfee4 remove answer as end 2024-03-18 19:25:18 +08:00
StyleZhang
81cbf2e713 node prev available nodes 2024-03-18 19:22:58 +08:00
jyong
e66c55ba9e fix enable annotation reply when collection is None 2024-03-18 19:21:36 +08:00
Joel
56044a104c remove logs 2024-03-18 19:14:21 +08:00
Joel
c409ab4c3c feat: knowledge support one sigle 2024-03-18 18:49:01 +08:00
Yeuoly
487efcb206 fix: support deprecated tools 2024-03-18 18:45:29 +08:00
StyleZhang
4eb7546177 workflow publish 2024-03-18 18:45:24 +08:00
Yeuoly
4b561aec93 feat: workflow statistics 2024-03-18 18:44:27 +08:00
takatost
34695f02fb add model config for conversation 2024-03-18 18:25:46 +08:00
takatost
aa421269c4 deduct llm quota use llm node func 2024-03-18 18:01:57 +08:00
takatost
09cfbe117e fix annotation bugs 2024-03-18 17:57:10 +08:00
takatost
0ea233edbe Merge branch 'main' into feat/workflow-backend 2024-03-18 17:20:25 +08:00
takatost
d69e0a79d4 fix file upload config internal err 2024-03-18 16:55:15 +08:00
Joel
7320ac41af feat: support context and other var reset 2024-03-18 16:50:52 +08:00
takatost
08b1f5d7c3 fix web app bugs 2024-03-18 16:48:31 +08:00
takatost
61b41ca04b fix retriever resource 2024-03-18 16:38:39 +08:00
JzoNg
8d4f40bc7c fix style of chat message log 2024-03-18 16:37:50 +08:00
Joel
3e9c7dccc0 feat: prompt editor set context status setter 2024-03-18 16:25:20 +08:00
StyleZhang
672b8f14f2 chat 2024-03-18 16:14:05 +08:00
StyleZhang
513d075ebc chat 2024-03-18 16:03:57 +08:00
Joel
8d34082246 feat: llm default 2024-03-18 15:56:37 +08:00
jyong
5ed181dd42 knowledge entities fix 2024-03-18 15:54:59 +08:00
JzoNg
9d8f9f6f63 fix app template list filtering 2024-03-18 15:52:02 +08:00
Joel
8e8c39a88c feat: sys var remove nodeid 2024-03-18 15:46:36 +08:00
jyong
41d9fdee50 Merge remote-tracking branch 'origin/feat/workflow-backend' into feat/workflow-backend 2024-03-18 15:40:26 +08:00
jyong
9e37021387 knowledge entities fix 2024-03-18 15:40:11 +08:00
takatost
bf06be0c75 fix migration order 2024-03-18 15:37:23 +08:00
takatost
a93a2e2e0c Merge branch 'main' into feat/workflow-backend 2024-03-18 15:35:04 +08:00
takatost
02337cbb09 fix answer message save 2024-03-18 15:07:56 +08:00
StyleZhang
c35c0fc6f4 chat upload file 2024-03-18 15:04:32 +08:00
Joel
1482eb0348 feat: generation support vars 2024-03-18 14:57:28 +08:00
JzoNg
cbe7116bb7 fix data fetching of app list 2024-03-18 14:54:01 +08:00
StyleZhang
788550affa chat upload file 2024-03-18 14:49:40 +08:00
Joel
25949338cb feat: editor history and query 2024-03-18 14:49:15 +08:00
takatost
958da42f74 fix advanced chat answer 2024-03-18 14:28:07 +08:00
JzoNg
6e8ea528c2 fix loading of run 2024-03-18 14:19:47 +08:00
JzoNg
d537efe97a refactor run data fetching 2024-03-18 14:15:29 +08:00
JzoNg
0439276866 add tracing panel 2024-03-18 13:24:27 +08:00
takatost
69c8e4ddd1 fix source handle 2024-03-18 13:11:58 +08:00
JzoNg
711f7107b4 fix merge 2024-03-18 13:11:17 +08:00
StyleZhang
ea4476ac6e init edges 2024-03-18 12:49:55 +08:00
Joel
13dbc7f0ce feat: handle questioin classify 2024-03-18 11:26:26 +08:00
Joel
b8ecfd859b feat: fill single run form variable with constant value first time 2024-03-18 11:03:23 +08:00
Joel
4daf93ef4f feat: form input var type 2024-03-18 10:52:45 +08:00
Joel
90b7ca1df1 chore: merge main 2024-03-18 10:34:57 +08:00
takatost
96f38b2d15 fix bug 2024-03-18 00:13:34 +08:00
takatost
8a27e51658 add Bad Request when generating 2024-03-17 21:40:59 +08:00
takatost
8ecec84dcf Merge branch 'main' into feat/workflow-backend
# Conflicts:
#	api/core/application_manager.py
2024-03-17 21:38:33 +08:00
takatost
a2b3096159 add text chunk subscribe for advanced chat blocking mode 2024-03-17 21:36:22 +08:00
takatost
80f1fbba56 add image file as markdown stream outupt 2024-03-17 21:27:08 +08:00
Yeuoly
d8ab611480 fix: code 2024-03-17 21:08:41 +08:00
StyleZhang
722ff7795d insert node 2024-03-17 20:19:58 +08:00
takatost
73c2b35dfe add completion app creation back 2024-03-17 16:30:04 +08:00
Yeuoly
b99eadecf6 fix: code template 2024-03-17 16:18:15 +08:00
JzoNg
843db3dbdf fix typo 2024-03-17 15:06:53 +08:00
JzoNg
3b660f1698 chat list api 2024-03-17 15:03:37 +08:00
JzoNg
a2e30e6aa9 message log 2024-03-17 14:05:56 +08:00
JzoNg
9638885a67 fix prompt log 2024-03-17 12:41:23 +08:00
StyleZhang
cd01c890e1 chat record 2024-03-17 09:53:16 +08:00
StyleZhang
552ccb058b stop & restart 2024-03-17 09:25:19 +08:00
takatost
36180b1001 add model support for kr node single_retrieval_config 2024-03-16 22:22:08 +08:00
takatost
65ed4dc91f refactor recommend app api 2024-03-16 22:13:06 +08:00
takatost
c709e339b1 fix route 2024-03-16 18:48:16 +08:00
takatost
3cf8416484 add workflow api for installed app & web api & service api 2024-03-16 16:27:39 +08:00
takatost
d2d47d0e0e fix bug 2024-03-16 15:09:47 +08:00
StyleZhang
05f97f6e06 fix chat 2024-03-16 15:00:30 +08:00
takatost
11dfdb236d lint fix 2024-03-16 14:45:39 +08:00
takatost
6df520ebc6 add skip ran node 2024-03-16 14:45:16 +08:00
takatost
a047a98462 advanced chat support 2024-03-16 14:30:53 +08:00
jyong
1df68a546e variable assigner node 2024-03-16 01:15:40 +08:00
jyong
5013ea09d5 variable assigner node 2024-03-16 00:54:29 +08:00
jyong
d92d952e76 Merge remote-tracking branch 'origin/feat/workflow-backend' into feat/workflow-backend 2024-03-16 00:37:15 +08:00
jyong
4af304e6ae question classifier 2024-03-16 00:36:58 +08:00
takatost
5c4d1c52ee add conversation_id & message_id to advanced-chat workflow-runs API 2024-03-15 22:24:00 +08:00
Joel
5ee7fc4fde feat: tools vars limit 2024-03-15 22:15:36 +08:00
takatost
b0cf8c00db add created_at return in publish workflow 2024-03-15 22:08:25 +08:00
Joel
338dd1c714 feat: http var limit 2024-03-15 22:04:07 +08:00
Joel
af9ae91934 feat: template transform default 2024-03-15 21:58:56 +08:00
takatost
d122daca87 fix conversation filter 2024-03-15 21:56:17 +08:00
Joel
ec49da073e feat: code default value 2024-03-15 21:54:16 +08:00
takatost
62846be275 refactor app generate pipeline 2024-03-15 21:42:22 +08:00
Joel
6b9cc927c0 feat: llm default value 2024-03-15 21:41:27 +08:00
StyleZhang
a577db9ddd stop run 2024-03-15 21:33:51 +08:00
JzoNg
e5c8743712 fix elpased time 2024-03-15 21:15:44 +08:00
Joel
777cca1a09 feat: question classify init 2024-03-15 20:50:55 +08:00
StyleZhang
e3c65c072c node value init 2024-03-15 20:26:00 +08:00
StyleZhang
9b069bd3d4 node value form 2024-03-15 20:18:19 +08:00
StyleZhang
56c53d1f07 node value init 2024-03-15 20:18:19 +08:00
JzoNg
6146f24932 fix tip of workflow 2024-03-15 20:15:57 +08:00
JzoNg
9908a8bf1f prompt log 2024-03-15 19:55:33 +08:00
StyleZhang
e33260d2e2 node value init 2024-03-15 19:51:46 +08:00
jyong
5713ee5fce Merge remote-tracking branch 'origin/feat/workflow-backend' into feat/workflow-backend
# Conflicts:
#	api/constants/languages.py
#	api/controllers/console/app/app.py
#	api/controllers/console/app/model_config.py
2024-03-15 19:50:50 +08:00
StyleZhang
129a68bb06 auto layout 2024-03-15 19:34:50 +08:00
Joel
aff5ab933b feat: knowledge node var init value and limit 2024-03-15 19:22:35 +08:00
JzoNg
4835358f24 modify prompt log 2024-03-15 18:26:56 +08:00
jyong
9b57b4c6c8 dataset retrival 2024-03-15 18:22:48 +08:00
jyong
785dfc5c00 dataset retrival 2024-03-15 18:22:48 +08:00
takatost
12eb236364 answer stream output support 2024-03-15 18:22:48 +08:00
Yeuoly
1cfeb989f7 fix: code default output 2024-03-15 18:22:47 +08:00
Yeuoly
ede65eca4d fix: tool 2024-03-15 18:22:47 +08:00
Yeuoly
dc53362506 fix: conversation_id equals to none 2024-03-15 18:22:47 +08:00
Yeuoly
74e644be1c fix: linter 2024-03-15 18:22:47 +08:00
Yeuoly
6e51ce123c fix: null conversation id 2024-03-15 18:22:47 +08:00
takatost
737321da75 add advanced chat apis support 2024-03-15 18:22:47 +08:00
takatost
72d2f76d24 fix default configs 2024-03-15 18:22:47 +08:00
Yeuoly
87a36a1fc8 fix: linter 2024-03-15 18:22:47 +08:00
Yeuoly
c2ded79cb2 fix: node type 2024-03-15 18:22:47 +08:00
takatost
fb6e5bf4d5 fix publish route 2024-03-15 18:22:47 +08:00
Yeuoly
6633a92e1a fix: http 2024-03-15 18:22:47 +08:00
takatost
44c4d5be72 add answer output parse 2024-03-15 18:22:47 +08:00
takatost
5a67c09b48 use answer node instead of end in advanced chatbot 2024-03-15 18:22:47 +08:00
Yeuoly
0614ddde7d fix: allow None AuthorizationConfig 2024-03-15 18:22:47 +08:00
takatost
e5ff06bcb7 fix err typo 2024-03-15 18:22:47 +08:00
Yeuoly
6b19ba3bb2 enhance: sandbox-docker-compose 2024-03-15 18:22:47 +08:00
takatost
735b55e61b add if-else node 2024-03-15 18:22:47 +08:00
takatost
7e53625eae fix value type 2024-03-15 18:22:47 +08:00
takatost
5213b0aade add sequence_number for workflow_started event 2024-03-15 18:22:47 +08:00
takatost
2b4b6817a3 record inputs and process data when node failed 2024-03-15 18:22:47 +08:00
takatost
da3e1e9d14 add deduct quota for llm node 2024-03-15 18:22:47 +08:00
takatost
e4794e309a add llm node test 2024-03-15 18:22:47 +08:00
Yeuoly
e6572ef2d7 fix: linter 2024-03-15 18:22:47 +08:00
Yeuoly
2182533af8 feat: javascript code 2024-03-15 18:22:47 +08:00
takatost
d88ac6c238 add llm node 2024-03-15 18:22:47 +08:00
takatost
e8751bebfa fix single step run error 2024-03-15 18:22:47 +08:00
Yeuoly
92c1da8dbe fix: remove answer 2024-03-15 18:22:47 +08:00
Yeuoly
951aaf5161 feat: sandbox 2024-03-15 18:22:47 +08:00
Yeuoly
a420953385 feat: docker-compose 2024-03-15 18:22:47 +08:00
Yeuoly
b102562614 fix: forward-ref 2024-03-15 18:22:47 +08:00
Yeuoly
2c2b9e7389 test: template transform 2024-03-15 18:22:47 +08:00
Yeuoly
513a8655b1 test: tool 2024-03-15 18:22:47 +08:00
Yeuoly
d3385a2715 feat 2024-03-15 18:22:47 +08:00
Yeuoly
ebf9c41adb feat: http 2024-03-15 18:22:47 +08:00
jyong
7372776992 knowledge node 2024-03-15 18:22:47 +08:00
takatost
7f7269d261 remove unused params in workflow_run_for_list_fields 2024-03-15 18:22:47 +08:00
takatost
f2bb0012fd add debug code 2024-03-15 18:22:47 +08:00
takatost
33113034ea add single step run 2024-03-15 18:22:47 +08:00
Yeuoly
88c29f613f fix: typing 2024-03-15 18:22:47 +08:00
Yeuoly
f318fa058c feat: add variable selector mapping 2024-03-15 18:22:47 +08:00
Yeuoly
407bfb8182 feat: add user uid 2024-03-15 18:22:47 +08:00
Yeuoly
91845fc9f6 fix: linter 2024-03-15 18:22:47 +08:00
Yeuoly
f911b1c488 feat: support empty code output children 2024-03-15 18:22:47 +08:00
takatost
7a6fa3655f add user for node 2024-03-15 18:22:47 +08:00
Yeuoly
5eb7b4d56a feat: tool entity 2024-03-15 18:22:47 +08:00
Yeuoly
5e4bd9fc38 feat: tool node 2024-03-15 18:22:47 +08:00
Yeuoly
f8cba2679e fix: linter 2024-03-15 18:22:47 +08:00
Yeuoly
e0883302d2 feat: jinja2 2024-03-15 18:22:47 +08:00
takatost
a0a1618869 add tenant_id / app_id / workflow_id for nodes 2024-03-15 18:22:47 +08:00
takatost
be68369983 add workflow_app_log codes 2024-03-15 18:22:47 +08:00
Yeuoly
9d0a832e40 refactor: github actions 2024-03-15 18:22:47 +08:00
Yeuoly
8031262006 feat: workflow mock test 2024-03-15 18:22:47 +08:00
takatost
751489fa54 modify readme 2024-03-15 18:22:47 +08:00
Yeuoly
1e6feadc7e fix: code node dose not work as expected 2024-03-15 18:22:47 +08:00
takatost
2d8497f79b add readme for db connection management in App Runner and Task Pipeline 2024-03-15 18:22:47 +08:00
takatost
61a1aadf9c optimize workflow db connections 2024-03-15 18:22:47 +08:00
takatost
8b832097de optimize db connections 2024-03-15 18:22:45 +08:00
takatost
7e4daf131e optimize db connections 2024-03-15 18:17:05 +08:00
takatost
de3978fdbb optimize db connections 2024-03-15 18:17:05 +08:00
Yeuoly
51f6ab49cf fix: linter 2024-03-15 18:17:05 +08:00
Yeuoly
2895c3bc8c feat: template transform 2024-03-15 18:17:05 +08:00
Yeuoly
3d5f9b5a1e fix: missing _extract_variable_selector_to_variable_mapping 2024-03-15 18:17:05 +08:00
Yeuoly
614bc2e075 feat: http reqeust 2024-03-15 18:17:05 +08:00
Yeuoly
193bcce236 feat: http request 2024-03-15 18:17:05 +08:00
Yeuoly
a0fd731170 feat: mapping variables 2024-03-15 18:17:05 +08:00
takatost
2f57d090a1 refactor pipeline and remove node run run_args 2024-03-15 18:17:05 +08:00
Yeuoly
4c5822fb6e fix: transform 2024-03-15 18:17:05 +08:00
takatost
e90637f67a fix generate bug 2024-03-15 18:17:05 +08:00
Yeuoly
9b0f83f807 fix: add max number array length 2024-03-15 18:17:05 +08:00
takatost
fc573564b4 refactor workflow runner 2024-03-15 18:17:05 +08:00
Yeuoly
5596b3b00b fix: linter 2024-03-15 18:17:05 +08:00
Yeuoly
cb02b1e12e feat: code 2024-03-15 18:17:05 +08:00
Yeuoly
736e386f15 fix: bugs 2024-03-15 18:17:05 +08:00
takatost
c152d55f68 fix workflow app bugs 2024-03-15 18:17:05 +08:00
takatost
1a0b6adc2c fix stream bugs 2024-03-15 18:17:05 +08:00
takatost
1914dfea77 fix bugs 2024-03-15 18:17:05 +08:00
takatost
1f986a3abb fix bugs 2024-03-15 18:17:05 +08:00
takatost
b174f85237 fix bug 2024-03-15 18:17:05 +08:00
takatost
2ad9c76093 modify migrations 2024-03-15 18:17:05 +08:00
takatost
8684b172d2 add start, end, direct answer node 2024-03-15 18:17:05 +08:00
takatost
3e54cb26be move funcs 2024-03-15 18:17:05 +08:00
takatost
079cc082a3 use callback to filter workflow stream output 2024-03-15 18:17:05 +08:00
takatost
a1bc6b50c5 refactor workflow generate pipeline 2024-03-15 18:17:05 +08:00
takatost
7d28fe8ea5 completed workflow engine main logic 2024-03-15 18:17:05 +08:00
takatost
dd50deaa43 fix audio voice arg 2024-03-15 18:17:04 +08:00
takatost
79a10e9729 add updated_at to sync workflow api 2024-03-15 18:17:04 +08:00
takatost
a5de7b10f3 update ruff check 2024-03-15 18:17:04 +08:00
takatost
bc4edbfc2b lint fix 2024-03-15 18:17:04 +08:00
takatost
75f1355d4c add few workflow run codes 2024-03-15 18:17:04 +08:00
takatost
1a86e79d4a lint fix 2024-03-15 18:17:04 +08:00
takatost
c8a1f923f5 lint fix 2024-03-15 18:17:04 +08:00
takatost
df753e84a3 fix workflow api return 2024-03-15 18:17:04 +08:00
takatost
3086893ee7 fix typo 2024-03-15 18:17:04 +08:00
takatost
242fcf0145 fix typo 2024-03-15 18:17:04 +08:00
takatost
de40422205 lint fix 2024-03-15 18:17:04 +08:00
takatost
df809ff435 add get default node config 2024-03-15 18:17:04 +08:00
takatost
75559bcbf9 replace block type to node type 2024-03-15 18:17:04 +08:00
takatost
d9b8a938c6 use enum instead 2024-03-15 18:17:04 +08:00
takatost
e9004a06a5 lint fix 2024-03-15 18:17:04 +08:00
takatost
be709d4b84 add AdvancedChatAppGenerateTaskPipeline 2024-03-15 18:17:04 +08:00
takatost
602bc67495 lint fix 2024-03-15 18:17:04 +08:00
takatost
e498efce2d refactor app generate 2024-03-15 18:17:04 +08:00
takatost
09dfe80718 add app copy api 2024-03-15 18:17:04 +08:00
takatost
06b05163f6 update app import response 2024-03-15 18:17:04 +08:00
takatost
b80092ea12 lint fix 2024-03-15 18:17:04 +08:00
takatost
2eaae6742a lint fix 2024-03-15 18:17:04 +08:00
takatost
3f5d1a79c6 refactor apps 2024-03-15 18:17:04 +08:00
takatost
15c7e0ec2f lint fix 2024-03-15 18:17:04 +08:00
takatost
43b0440358 support workflow features 2024-03-15 18:17:03 +08:00
takatost
9651a208a9 lint fix 2024-03-15 18:15:54 +08:00
takatost
7bff65304f add features structure validate 2024-03-15 18:15:54 +08:00
takatost
8a8882ed8d move workflow_id to app 2024-03-15 18:15:54 +08:00
takatost
9467fe9aa9 lint fix 2024-03-15 18:15:54 +08:00
takatost
799db69e4f refactor app 2024-03-15 18:15:48 +08:00
takatost
896c200211 fix import problem 2024-03-15 18:15:17 +08:00
takatost
3badc4423a fix: wrong default model parameters when creating app 2024-03-15 18:15:17 +08:00
takatost
d741527ae4 lint 2024-03-15 18:15:17 +08:00
takatost
77618823a5 add features update api
refactor app model config validation
2024-03-15 18:15:17 +08:00
takatost
dd70aeff24 lint fix 2024-03-15 18:15:17 +08:00
takatost
022b7d5dd4 optimize default model exceptions 2024-03-15 18:15:17 +08:00
takatost
11337e51c5 lint fix 2024-03-15 18:15:17 +08:00
takatost
7724d010b6 add app description
add update app api
2024-03-15 18:15:16 +08:00
takatost
124aa9db08 lint fix 2024-03-15 18:15:16 +08:00
takatost
20cf075b2d add workflow runs & workflow node executions api 2024-03-15 18:15:16 +08:00
takatost
bf4a5f6b33 lint fix 2024-03-15 18:15:16 +08:00
takatost
03749917f0 add workflow app log api 2024-03-15 18:15:16 +08:00
takatost
7d51d6030b remove publish workflow when app import 2024-03-15 18:15:16 +08:00
takatost
742b87df5e lint fix 2024-03-15 18:15:16 +08:00
takatost
a457faa2bf trigger app_model_config_was_updated when app import 2024-03-15 18:15:16 +08:00
takatost
4f50f113dd lint fix 2024-03-15 18:15:16 +08:00
takatost
8b529a3ec7 refactor app api 2024-03-15 18:15:16 +08:00
takatost
84c3ec0ea7 site init move to event handler 2024-03-15 18:15:16 +08:00
takatost
c13e8077ba fix agent app converter command 2024-03-15 18:15:16 +08:00
takatost
9f42892b42 lint fix 2024-03-15 18:15:16 +08:00
takatost
27ba5a0bce refactor app mode
add app import and export
2024-03-15 18:15:13 +08:00
takatost
78afba49bf lint fix 2024-03-15 18:13:55 +08:00
takatost
a9192bc1c6 make recommended app list api public 2024-03-15 18:13:55 +08:00
takatost
77f04603b3 fix bugs 2024-03-15 18:13:55 +08:00
takatost
34ed5e428c fix bugs 2024-03-15 18:13:55 +08:00
takatost
98cb17e79e lint fix 2024-03-15 18:13:55 +08:00
takatost
fce20e483c restore completion app 2024-03-15 18:13:55 +08:00
takatost
97c4733e79 lint fix 2024-03-15 18:13:55 +08:00
takatost
748aa22ee2 add manual convert logic 2024-03-15 18:13:55 +08:00
takatost
2ba7ac8bc1 add expert mode of chatapp convert command 2024-03-15 18:13:55 +08:00
takatost
7458fde5a5 add agent app convert command 2024-03-15 18:13:55 +08:00
takatost
f11bf9153d add more tests 2024-03-15 18:13:55 +08:00
takatost
0806b3163a add to http request node convert tests 2024-03-15 18:13:55 +08:00
takatost
45621ba4d7 add api extension to http request node convert 2024-03-15 18:13:55 +08:00
takatost
6aecf42b6e fix prompt transform bugs 2024-03-15 18:13:55 +08:00
takatost
3b234febf5 fix bugs and add unit tests 2024-03-15 18:13:55 +08:00
takatost
8642354a2a lint 2024-03-15 18:13:55 +08:00
takatost
c028e5f889 add app convert codes 2024-03-15 18:13:55 +08:00
takatost
3642dd3a73 add workflow logics 2024-03-15 18:13:55 +08:00
takatost
603b1e9ed4 lint 2024-03-15 18:13:55 +08:00
takatost
b7c6cba23f add workflow models 2024-03-15 18:13:55 +08:00
takatost
d430136f65 lint 2024-03-15 18:13:55 +08:00
takatost
381b3d5016 optimize get app model to wraps 2024-03-15 18:13:55 +08:00
Joel
e3f1e143e5 feat: llm context type limit 2024-03-15 18:03:01 +08:00
Joel
9f1cbb2ee7 feat: answer node input limit 2024-03-15 18:03:01 +08:00
StyleZhang
d0ef9e672f llm mode 2024-03-15 17:57:46 +08:00
Joel
b5c212f575 feat: parse to right datatype and show parse json error 2024-03-15 17:42:00 +08:00
Joel
ff5ab43f9c feat: check before run 2024-03-15 17:42:00 +08:00
StyleZhang
68f947c7e0 stop workflow run 2024-03-15 17:24:40 +08:00
StyleZhang
e98456b025 store 2024-03-15 16:58:48 +08:00
Joel
75b332695b feat: support string num seletor to single run debug 2024-03-15 16:37:58 +08:00
jyong
3e4bb695e4 dataset retrival 2024-03-15 16:14:32 +08:00
Joel
7ba1b37a5a feat: show assigner panel 2024-03-15 15:23:27 +08:00
Joel
2886255c8b fix: can not get first var type 2024-03-15 15:10:41 +08:00
Joel
f7a9564e11 feat: can noe choose selected nodes 2024-03-15 14:44:48 +08:00
jyong
c1b0f115d0 dataset retrival 2024-03-15 14:40:53 +08:00
StyleZhang
2203d9a138 available nodes 2024-03-15 14:40:44 +08:00
Joel
5adf94bd7d feat: support filter obj select type 2024-03-15 14:01:25 +08:00
StyleZhang
5fbf8ee6c6 available nodes 2024-03-15 13:24:29 +08:00
Joel
8d9c86ac4c fix: advance setting error 2024-03-15 11:47:53 +08:00
Joel
946ef4c685 chore: remove uselsee lang 2024-03-15 11:31:48 +08:00
Joel
c3773bc2d1 chore: add language placeholder 2024-03-15 11:30:39 +08:00
Joel
1b8c8b0a43 feat: node before and after run 2024-03-15 11:26:19 +08:00
StyleZhang
86d2c1184c Merge branch 'main' into feat/workflow 2024-03-15 11:17:18 +08:00
StyleZhang
817aea9f05 fix 2024-03-15 11:04:34 +08:00
StyleZhang
985c07b25b fix 2024-03-15 11:01:47 +08:00
StyleZhang
05ac27dfa8 fix 2024-03-14 21:07:59 +08:00
JzoNg
bcce53a929 web app support workflow 2024-03-14 21:02:15 +08:00
JzoNg
58922ba40b add route for workflow app 2024-03-14 21:02:15 +08:00
takatost
e6b8b13f2e answer stream output support 2024-03-14 20:50:03 +08:00
Joel
ac675c4443 feat: add checkvalid empty fn 2024-03-14 20:44:51 +08:00
Joel
ae6a558662 feat: add prev and next nodes 2024-03-14 20:29:47 +08:00
Joel
64e44d1709 chore: direct answer to answer 2024-03-14 19:58:17 +08:00
Yeuoly
f35ae2355f fix: code default output 2024-03-14 19:17:27 +08:00
Joel
3a857c83e6 chore: only show has value node in end 2024-03-14 18:46:30 +08:00
Joel
d9edcb2250 feat: change to new end node 2024-03-14 18:43:04 +08:00
StyleZhang
d129d7951c fix 2024-03-14 18:27:58 +08:00
Joel
5c246285da feat: support node type filter 2024-03-14 17:58:26 +08:00
StyleZhang
19c1722032 node default value 2024-03-14 17:27:42 +08:00
Joel
cd9a58231b fix: tool show 2024-03-14 16:40:21 +08:00
Yeuoly
d85b5b9134 fix: tool 2024-03-14 16:38:22 +08:00
Joel
8bd74d5abf feat: var picker support choose type 2024-03-14 16:04:15 +08:00
StyleZhang
2af2e2be67 node add 2024-03-14 15:14:31 +08:00
Joel
43a3b827a3 chore: stringify output 2024-03-14 13:43:13 +08:00
Joel
3c6de0bf3e feat: tool default value 2024-03-14 13:43:13 +08:00
Yeuoly
13a724864d fix: conversation_id equals to none 2024-03-14 13:24:48 +08:00
Yeuoly
baf536eb2b fix: linter 2024-03-14 12:57:14 +08:00
Yeuoly
5200668336 fix: null conversation id 2024-03-14 12:57:14 +08:00
JzoNg
7eeffb16e2 fix url of webapp 2024-03-14 12:35:12 +08:00
JzoNg
300909341e use app store in overview 2024-03-14 12:27:54 +08:00
JzoNg
277d21cccb fix webapp url 2024-03-14 12:22:08 +08:00
takatost
de184051f0 add advanced chat apis support 2024-03-14 12:17:15 +08:00
takatost
95ee72556f fix default configs 2024-03-14 12:12:38 +08:00
Yeuoly
f48364914b fix: linter 2024-03-14 11:59:43 +08:00
Yeuoly
19df70efad fix: node type 2024-03-14 11:59:43 +08:00
StyleZhang
9813609645 publish 2024-03-14 11:48:58 +08:00
takatost
975d0a1651 fix publish route 2024-03-14 11:39:18 +08:00
Yeuoly
3c3571713e fix: http 2024-03-14 11:35:51 +08:00
JzoNg
aa6254a3b4 add doc for workflow app 2024-03-14 11:31:31 +08:00
JzoNg
0b05d2939a add doc for workflow 2024-03-14 11:31:31 +08:00
JzoNg
6f33163f88 fix app siderbar hook 2024-03-14 11:31:31 +08:00
JzoNg
93101b4d9a add running state for step run 2024-03-14 11:31:31 +08:00
JzoNg
d8222a15ca remove useless comments 2024-03-14 11:31:31 +08:00
JzoNg
1728513634 fix sequence number and tokens in result panel 2024-03-14 11:31:31 +08:00
Joel
68fa81ec82 chore: change tool input types 2024-03-14 11:25:27 +08:00
Joel
c051c89176 chore: fix http i18n 2024-03-14 11:25:27 +08:00
StyleZhang
8b2a63e545 fix 2024-03-14 11:19:50 +08:00
takatost
fcd470fcac add answer output parse 2024-03-13 23:00:28 +08:00
takatost
fd8fe15d28 use answer node instead of end in advanced chatbot 2024-03-13 20:54:23 +08:00
Yeuoly
e80315f504 fix: allow None AuthorizationConfig 2024-03-13 20:40:37 +08:00
Joel
ae9e7acd77 feat: other node run 2024-03-13 18:49:22 +08:00
Joel
149eb38e84 feat: tool single run 2024-03-13 18:43:43 +08:00
Joel
d777184fd5 feat: http run 2024-03-13 18:33:08 +08:00
Joel
1653e5eebe feat: template transform 2024-03-13 18:05:17 +08:00
takatost
1f4826ca01 fix err typo 2024-03-13 18:02:19 +08:00
Yeuoly
ef700b2688 enhance: sandbox-docker-compose 2024-03-13 17:46:42 +08:00
Joel
cedc1bada2 fix: temple transform query selec can not choose var 2024-03-13 17:17:28 +08:00
takatost
0c709afe5c add if-else node 2024-03-13 17:10:51 +08:00
StyleZhang
1c5d07871f hooks 2024-03-13 17:07:03 +08:00
StyleZhang
e11fc8c131 init 2024-03-13 17:07:03 +08:00
Joel
2edef89a8d feat: handle system var 2024-03-13 16:38:24 +08:00
StyleZhang
cbe7de58ab backup draft 2024-03-13 16:27:04 +08:00
Joel
801160c430 feat: tool output vars 2024-03-13 16:18:46 +08:00
Joel
cb2a814296 feat: assign output 2024-03-13 15:58:03 +08:00
Joel
db78b91ec2 feat: http output 2024-03-13 15:58:02 +08:00
Joel
25a11c5bb7 feat: question classify output 2024-03-13 15:58:02 +08:00
StyleZhang
1f41521c21 workflow run 2024-03-13 15:38:56 +08:00
Joel
e686d42262 feat: support template transform output var 2024-03-13 15:21:51 +08:00
Joel
9bca3f8fd7 feat: code support output var list 2024-03-13 15:17:03 +08:00
takatost
6ef3542c6c fix value type 2024-03-13 15:08:15 +08:00
takatost
db299a876e add sequence_number for workflow_started event 2024-03-13 15:01:02 +08:00
takatost
737d04361b record inputs and process data when node failed 2024-03-13 14:55:56 +08:00
Joel
0d2366b432 feat: knowledge output var 2024-03-13 14:52:32 +08:00
Joel
b13345ceb2 chore: remove useless and node filter 2024-03-13 14:41:11 +08:00
Joel
f15dce9ee3 feat: llm output and var type 2024-03-13 14:37:58 +08:00
StyleZhang
b718e66b26 fix: drag stop & click 2024-03-13 13:59:57 +08:00
StyleZhang
a55a7603dd split hooks 2024-03-13 11:53:49 +08:00
Joel
64fa343d16 chore: remove log 2024-03-13 11:24:20 +08:00
Joel
6b02eebe36 feat: support start node vars 2024-03-13 11:22:59 +08:00
Joel
d0f5318b75 feat: code node can run 2024-03-13 10:43:18 +08:00
takatost
5fe0d50cee add deduct quota for llm node 2024-03-13 00:08:13 +08:00
takatost
4d7caa3458 add llm node test 2024-03-12 23:08:23 +08:00
Yeuoly
856466320d fix: linter 2024-03-12 22:42:28 +08:00
Yeuoly
3bd53556ca feat: javascript code 2024-03-12 22:41:59 +08:00
JzoNg
c74854aec0 icon fix 2024-03-12 22:24:23 +08:00
JzoNg
294128d43a fix tracing 2024-03-12 22:21:45 +08:00
takatost
3f59a579d7 add llm node 2024-03-12 22:12:03 +08:00
JzoNg
e5cf4ea60e fix result panel 2024-03-12 21:40:41 +08:00
JzoNg
768ca2d3f0 add panel of result 2024-03-12 21:05:15 +08:00
JzoNg
92e9b1bbb1 update style of app list 2024-03-12 21:05:15 +08:00
JzoNg
446932e076 update style of app creation 2024-03-12 21:05:15 +08:00
StyleZhang
0469edcc0c fix 2024-03-12 21:03:45 +08:00
Joel
3823ae5890 chore: prompt to promt template 2024-03-12 20:05:42 +08:00
Joel
14d71fb598 feat: var picker get vars 2024-03-12 20:03:35 +08:00
Joel
a031507443 feat: code show result 2024-03-12 19:45:45 +08:00
takatost
4f5c052dc8 fix single step run error 2024-03-12 19:15:11 +08:00
StyleZhang
90e013554c fix 2024-03-12 18:47:24 +08:00
Joel
30ea3cb702 feat: can run code node 2024-03-12 17:59:14 +08:00
StyleZhang
a5147a382d fix 2024-03-12 17:44:45 +08:00
Joel
74bf6cd186 feat: add single run api 2024-03-12 16:26:30 +08:00
Yeuoly
15ddbb5e6f fix: remove answer 2024-03-12 16:25:07 +08:00
StyleZhang
547df0b5fe fix 2024-03-12 15:04:52 +08:00
StyleZhang
8ae46a8a14 fix 2024-03-12 13:25:57 +08:00
StyleZhang
9753077661 fix 2024-03-12 13:03:39 +08:00
StyleZhang
22e7393b9d fix 2024-03-12 11:56:15 +08:00
Yeuoly
943c676768 feat: sandbox 2024-03-11 22:14:28 +08:00
Yeuoly
4ecfe1fec5 feat: docker-compose 2024-03-11 22:12:13 +08:00
Yeuoly
5fac4f8737 fix: forward-ref 2024-03-11 21:58:54 +08:00
Yeuoly
a5394fa2ce test: template transform 2024-03-11 21:53:08 +08:00
Yeuoly
8dc4d122b9 test: tool 2024-03-11 21:53:08 +08:00
StyleZhang
0eb482f35b chat workflow run 2024-03-11 21:05:54 +08:00
StyleZhang
bd52937c88 chat workflow run 2024-03-11 20:54:29 +08:00
jyong
d5b321af3f Merge remote-tracking branch 'origin/feat/workflow-backend' into feat/workflow-backend 2024-03-11 20:06:49 +08:00
jyong
f3b46bf7e2 knowledge node 2024-03-11 20:06:38 +08:00
Yeuoly
2008986f83 feat 2024-03-11 19:51:31 +08:00
Yeuoly
1a57951d72 feat: http 2024-03-11 19:51:06 +08:00
StyleZhang
7655d7f662 run 2024-03-11 19:35:06 +08:00
takatost
373857d0f2 remove unused params in workflow_run_for_list_fields 2024-03-11 19:04:48 +08:00
takatost
6719af9ba9 add debug code 2024-03-11 18:52:24 +08:00
takatost
19c9091d5b add single step run 2024-03-11 18:49:58 +08:00
StyleZhang
84e2071a32 run 2024-03-11 18:11:01 +08:00
Joel
b3b9e1dabb feat: tools support run 2024-03-11 17:33:17 +08:00
Yeuoly
91a35ded18 fix: typing 2024-03-11 16:51:27 +08:00
Yeuoly
2d68594a86 feat: add variable selector mapping 2024-03-11 16:48:28 +08:00
Yeuoly
f3d19f9691 feat: add user uid 2024-03-11 16:46:11 +08:00
Yeuoly
94047de8b4 fix: linter 2024-03-11 16:44:36 +08:00
Yeuoly
1c450e27d3 feat: support empty code output children 2024-03-11 16:44:22 +08:00
Joel
c0ccffa1c3 chore: no var not show var group 2024-03-11 16:43:13 +08:00
takatost
bbc76cb833 add user for node 2024-03-11 16:31:43 +08:00
Yeuoly
94f3cf1a4c feat: tool entity 2024-03-11 16:13:52 +08:00
Joel
2aa8847b78 mrege main 2024-03-11 14:54:29 +08:00
StyleZhang
049e858ef7 run 2024-03-11 14:43:50 +08:00
Yeuoly
8e491ace5c feat: tool node 2024-03-11 13:54:11 +08:00
Yeuoly
dcf9d85e8d fix: linter 2024-03-10 21:12:07 +08:00
Yeuoly
460c0da176 feat: jinja2 2024-03-10 20:24:16 +08:00
takatost
295a248561 add tenant_id / app_id / workflow_id for nodes 2024-03-10 20:15:49 +08:00
takatost
4630f9c746 add workflow_app_log codes 2024-03-10 20:02:19 +08:00
Yeuoly
ba66beb487 refactor: github actions 2024-03-10 18:41:49 +08:00
Yeuoly
b5cb38641a feat: workflow mock test 2024-03-10 18:41:25 +08:00
takatost
4b37d30c0d modify readme 2024-03-10 18:02:05 +08:00
Yeuoly
59ba7917c4 fix: code node dose not work as expected 2024-03-10 17:55:24 +08:00
takatost
8d0ff01a59 add readme for db connection management in App Runner and Task Pipeline 2024-03-10 17:11:39 +08:00
takatost
100fb0c5d6 optimize workflow db connections 2024-03-10 16:59:17 +08:00
takatost
b75cd2514e optimize db connections 2024-03-10 16:29:55 +08:00
takatost
7693ba8797 optimize db connections 2024-03-10 15:55:14 +08:00
takatost
3d6b06696e optimize db connections 2024-03-10 15:55:14 +08:00
Yeuoly
0386061fdf fix: linter 2024-03-10 15:55:14 +08:00
Yeuoly
3407b4d8dd feat: template transform 2024-03-10 15:55:14 +08:00
Yeuoly
71ff2a8356 fix: missing _extract_variable_selector_to_variable_mapping 2024-03-10 15:55:14 +08:00
Yeuoly
8b809b8004 feat: http reqeust 2024-03-10 15:55:14 +08:00
Yeuoly
707a3a0a66 feat: http request 2024-03-10 15:55:14 +08:00
Yeuoly
b798aa915c feat: mapping variables 2024-03-10 15:55:14 +08:00
takatost
2db67c4101 refactor pipeline and remove node run run_args 2024-03-10 15:55:14 +08:00
Yeuoly
80b4db08dc fix: transform 2024-03-10 15:55:14 +08:00
takatost
37cdee5101 fix generate bug 2024-03-10 15:55:14 +08:00
Yeuoly
b5366cba03 fix: add max number array length 2024-03-10 15:55:14 +08:00
takatost
6cfda369ef refactor workflow runner 2024-03-10 15:55:14 +08:00
Yeuoly
5a57ed2536 fix: linter 2024-03-10 15:55:14 +08:00
Yeuoly
13937fc103 feat: code 2024-03-10 15:55:14 +08:00
Yeuoly
17cd512284 fix: bugs 2024-03-10 15:55:14 +08:00
takatost
97398ff209 fix workflow app bugs 2024-03-10 15:55:14 +08:00
takatost
2ffb63ff0c fix stream bugs 2024-03-10 15:55:14 +08:00
takatost
90bcb241cc fix bugs 2024-03-10 15:55:14 +08:00
takatost
f4f7cfd45a fix bugs 2024-03-10 15:55:14 +08:00
takatost
d214c047e9 fix bug 2024-03-10 15:55:14 +08:00
takatost
fee8a86880 modify migrations 2024-03-10 15:55:14 +08:00
takatost
ea883b5e48 add start, end, direct answer node 2024-03-10 15:55:14 +08:00
takatost
46296d777c move funcs 2024-03-10 15:55:14 +08:00
takatost
79f0e894e9 use callback to filter workflow stream output 2024-03-10 15:55:14 +08:00
takatost
6372183471 refactor workflow generate pipeline 2024-03-10 15:55:14 +08:00
takatost
5963e7d1c5 completed workflow engine main logic 2024-03-10 15:55:14 +08:00
takatost
c7618fc377 fix audio voice arg 2024-03-10 15:55:14 +08:00
takatost
3fc932b041 add updated_at to sync workflow api 2024-03-10 15:55:14 +08:00
takatost
97cdc96f7c update ruff check 2024-03-10 15:55:14 +08:00
takatost
892fe927c2 lint fix 2024-03-10 15:55:14 +08:00
takatost
d51d456d80 add few workflow run codes 2024-03-10 15:55:14 +08:00
takatost
836376c6c8 lint fix 2024-03-10 15:55:14 +08:00
takatost
fa29eadb7a lint fix 2024-03-10 15:55:14 +08:00
takatost
0cc0065f8c fix workflow api return 2024-03-10 15:55:14 +08:00
takatost
c3eac450ce fix typo 2024-03-10 15:55:14 +08:00
takatost
7b738e045e fix typo 2024-03-10 15:55:14 +08:00
takatost
3f6c17247f lint fix 2024-03-10 15:55:14 +08:00
takatost
0551a9bfcd add get default node config 2024-03-10 15:55:14 +08:00
takatost
7c149ebf4f replace block type to node type 2024-03-10 15:55:14 +08:00
takatost
37b70eb73e use enum instead 2024-03-10 15:55:14 +08:00
takatost
451ea5308f lint fix 2024-03-10 15:55:14 +08:00
takatost
a4d6954d4f add AdvancedChatAppGenerateTaskPipeline 2024-03-10 15:55:14 +08:00
takatost
c786533f22 lint fix 2024-03-10 15:55:13 +08:00
takatost
406a625c98 refactor app generate 2024-03-10 15:55:13 +08:00
takatost
171b2bdc20 add app copy api 2024-03-10 15:55:13 +08:00
takatost
4266ce73cb update app import response 2024-03-10 15:55:13 +08:00
takatost
afa920cc94 lint fix 2024-03-10 15:55:13 +08:00
takatost
701f116be3 lint fix 2024-03-10 15:55:13 +08:00
takatost
5c7ea08bdf refactor apps 2024-03-10 15:55:12 +08:00
takatost
5e38996222 lint fix 2024-03-10 15:54:10 +08:00
takatost
18febeabd1 support workflow features 2024-03-10 15:54:10 +08:00
takatost
be1500bf7d lint fix 2024-03-10 15:54:10 +08:00
takatost
fea549679a add features structure validate 2024-03-10 15:54:10 +08:00
takatost
11e1b569ea move workflow_id to app 2024-03-10 15:54:10 +08:00
takatost
2bbf96d762 lint fix 2024-03-10 15:54:10 +08:00
takatost
70394bae52 refactor app 2024-03-10 15:54:08 +08:00
takatost
0c9e112f41 fix import problem 2024-03-10 15:52:45 +08:00
takatost
607b84d929 fix: wrong default model parameters when creating app 2024-03-10 15:52:45 +08:00
takatost
7a13cd1530 lint 2024-03-10 15:52:45 +08:00
takatost
9b1afb68eb add features update api
refactor app model config validation
2024-03-10 15:52:45 +08:00
takatost
cf9d2965bf lint fix 2024-03-10 15:52:45 +08:00
takatost
b1328c193b optimize default model exceptions 2024-03-10 15:52:45 +08:00
takatost
3d222caaae lint fix 2024-03-10 15:52:45 +08:00
takatost
77ac6fa356 add app description
add update app api
2024-03-10 15:52:45 +08:00
takatost
a3b46006a8 lint fix 2024-03-10 15:52:45 +08:00
takatost
ea4716d039 add workflow runs & workflow node executions api 2024-03-10 15:52:45 +08:00
takatost
db9e7a53f8 lint fix 2024-03-10 15:52:45 +08:00
takatost
4432e055be add workflow app log api 2024-03-10 15:52:45 +08:00
takatost
403c2f436d remove publish workflow when app import 2024-03-10 15:52:45 +08:00
takatost
594de43dec lint fix 2024-03-10 15:52:45 +08:00
takatost
2e68c3fc11 trigger app_model_config_was_updated when app import 2024-03-10 15:52:45 +08:00
takatost
2187f6f62e lint fix 2024-03-10 15:52:45 +08:00
takatost
9249c38bf9 refactor app api 2024-03-10 15:52:44 +08:00
takatost
67e0ba5167 site init move to event handler 2024-03-10 15:52:10 +08:00
takatost
9004d8c3cd fix agent app converter command 2024-03-10 15:52:10 +08:00
takatost
4df424438d lint fix 2024-03-10 15:52:10 +08:00
takatost
6e3cd62e31 refactor app mode
add app import and export
2024-03-10 15:52:09 +08:00
takatost
61b4bedc16 lint fix 2024-03-10 15:51:36 +08:00
takatost
4e5de036c6 make recommended app list api public 2024-03-10 15:51:36 +08:00
takatost
8e54b2e3f2 fix bugs 2024-03-10 15:51:36 +08:00
takatost
d39a51c134 fix bugs 2024-03-10 15:51:36 +08:00
takatost
6efc3d4913 lint fix 2024-03-10 15:51:35 +08:00
takatost
55c31eec31 restore completion app 2024-03-10 15:51:35 +08:00
takatost
9820dcb201 lint fix 2024-03-10 15:51:35 +08:00
takatost
9f29ce9591 add manual convert logic 2024-03-10 15:51:35 +08:00
takatost
afb0ff37bd add expert mode of chatapp convert command 2024-03-10 15:51:35 +08:00
takatost
67b6f08d89 add agent app convert command 2024-03-10 15:51:35 +08:00
takatost
892036bd7d add more tests 2024-03-10 15:51:35 +08:00
takatost
d123ddedc8 add to http request node convert tests 2024-03-10 15:51:35 +08:00
takatost
fc243982e5 add api extension to http request node convert 2024-03-10 15:51:35 +08:00
takatost
df66cd2205 fix prompt transform bugs 2024-03-10 15:51:35 +08:00
takatost
a44d3c3eda fix bugs and add unit tests 2024-03-10 15:51:35 +08:00
takatost
297b33aa41 lint 2024-03-10 15:51:35 +08:00
takatost
0d858cc036 add app convert codes 2024-03-10 15:51:35 +08:00
takatost
f067947266 add workflow logics 2024-03-10 15:51:35 +08:00
takatost
9ad6bd78f5 lint 2024-03-10 15:51:35 +08:00
takatost
b1e220f2d2 add workflow models 2024-03-10 15:51:35 +08:00
takatost
200dc56c37 lint 2024-03-10 15:51:35 +08:00
takatost
49992925e2 optimize get app model to wraps 2024-03-10 15:51:33 +08:00
JzoNg
405e99d27f fix api url of workflow run 2024-03-09 13:03:22 +08:00
JzoNg
90ee7fe201 tracing 2024-03-09 12:48:14 +08:00
JzoNg
bc90fc885f tracing node style update 2024-03-09 11:06:25 +08:00
JzoNg
5afa5fb085 app switch 2024-03-09 10:30:26 +08:00
JzoNg
93e2dc4f5f workflow log result 2024-03-08 21:10:11 +08:00
StyleZhang
08d2a4279f cache toolsmap 2024-03-08 18:36:48 +08:00
StyleZhang
d79b686992 block selector 2024-03-08 18:10:30 +08:00
Joel
1adec7ab51 feat: tool auth 2024-03-08 17:13:24 +08:00
Joel
3b029f2387 feat: tool auth 2024-03-08 17:13:24 +08:00
JzoNg
6d6afe8f52 fix app mode in logs 2024-03-08 17:11:33 +08:00
StyleZhang
e307947dd8 node control 2024-03-08 17:02:02 +08:00
JzoNg
04ad1eef79 workflow logs 2024-03-08 16:43:41 +08:00
StyleZhang
2b475b7916 help line 2024-03-08 16:02:28 +08:00
Joel
f51f4a5843 feat: tool inputs 2024-03-08 15:43:11 +08:00
JzoNg
b5f3bbead2 update cache in appNav after app info updated 2024-03-08 14:48:10 +08:00
Joel
a192ae9314 feat: remove useless file 2024-03-08 14:37:29 +08:00
Joel
7a07d8c2bc feat: tool params 2024-03-08 14:32:33 +08:00
StyleZhang
17a67e7922 remove annatation 2024-03-08 13:46:14 +08:00
StyleZhang
328a3e2e6b node about author 2024-03-08 13:24:59 +08:00
JzoNg
597053c30e fix style of app info 2024-03-08 11:46:25 +08:00
JzoNg
29bef1e3ab app sidebar auto collapse 2024-03-08 11:46:25 +08:00
JzoNg
e36d62f08c hide switch modal 2024-03-08 11:46:25 +08:00
JzoNg
50b4c7fa18 switch app 2024-03-08 11:46:25 +08:00
JzoNg
d86ef15d9a add tip for switch 2024-03-08 11:46:25 +08:00
JzoNg
fa3eb11b6a old app do not support duplicate and export dsl 2024-03-08 11:46:25 +08:00
JzoNg
beff31b003 update style app config 2024-03-08 11:46:25 +08:00
StyleZhang
2360fb293b update data 2024-03-08 11:27:05 +08:00
Joel
1c82e3870a feat: not choose model hide in node 2024-03-08 11:06:32 +08:00
Joel
49ce9d2200 feat: http support debug and remove mock init debug data 2024-03-08 10:59:49 +08:00
Joel
c20c9b53e1 feat: template tranform support debug 2024-03-08 10:40:12 +08:00
Joel
89fc90ac80 chore: code support debug 2024-03-08 10:36:57 +08:00
StyleZhang
072f5caa06 init 2024-03-07 19:43:11 +08:00
Joel
783f7a9b13 feat: question classifer support run 2024-03-07 18:43:19 +08:00
Joel
425e162a91 feat: knowledge support single run 2024-03-07 18:36:45 +08:00
Joel
55b5d76e0b chore: move node run data to node hooks 2024-03-07 18:13:56 +08:00
Joel
9693d014ba feat: add llm debug 2024-03-07 17:48:18 +08:00
StyleZhang
16abcf082c node control 2024-03-07 17:09:04 +08:00
StyleZhang
173336f256 node handle 2024-03-07 15:58:46 +08:00
Joel
f37316f2a0 feat: single run modal 2024-03-07 15:15:39 +08:00
StyleZhang
e044e8efaa chat mode 2024-03-07 14:35:13 +08:00
StyleZhang
af99a55552 chat mode 2024-03-07 14:24:27 +08:00
StyleZhang
8f3d9d0149 panel 2024-03-07 13:54:02 +08:00
StyleZhang
344e30bef4 node 2024-03-07 12:15:51 +08:00
StyleZhang
45ef4059f0 block-icon 2024-03-07 11:48:42 +08:00
StyleZhang
13174aac18 debug and preview 2024-03-07 11:21:59 +08:00
StyleZhang
74f02363f4 record 2024-03-07 10:48:11 +08:00
Joel
10c421a94c Merge branch 'main' into feat/workflow 2024-03-07 10:35:05 +08:00
StyleZhang
3162227b54 features 2024-03-06 19:43:47 +08:00
JzoNg
7e647cc6e7 fix page title update 2024-03-06 19:14:25 +08:00
StyleZhang
ec710d7ffd Merge branch 'main' into feat/workflow 2024-03-06 19:05:21 +08:00
StyleZhang
36718c39dc features 2024-03-06 19:04:06 +08:00
JzoNg
fca9753140 fix app detail update 2024-03-06 18:42:37 +08:00
Joel
0529c3d5d2 feat: add role tooltip and fix add prompt error 2024-03-06 18:36:35 +08:00
Joel
5a27a95f8d feat: llm support type select 2024-03-06 18:11:14 +08:00
Joel
5ec3a967b5 feat: all other code expand 2024-03-06 17:59:58 +08:00
StyleZhang
a45ec15a56 features 2024-03-06 17:48:01 +08:00
StyleZhang
0164dec438 features 2024-03-06 17:46:42 +08:00
JzoNg
4edaa95cbf app menu 2024-03-06 17:39:27 +08:00
JzoNg
067e6b5ae7 app detail redirection 2024-03-06 17:39:27 +08:00
Joel
6adb986167 feat: expend toggle 2024-03-06 16:56:18 +08:00
Joel
cc4ca942c9 feat: prompt editor blur and focus ui 2024-03-06 15:38:45 +08:00
Joel
3202f12cb8 feat: config prompt 2024-03-06 15:17:51 +08:00
StyleZhang
6448d71ca6 draft updated at 2024-03-06 14:35:47 +08:00
StyleZhang
e3a3e07eef tool 2024-03-06 14:04:15 +08:00
Joel
8a906e2959 fix: http nodes update error and support json 2024-03-06 11:34:07 +08:00
Joel
9839b5cb53 fix: enchance code editor syle 2024-03-06 11:23:42 +08:00
StyleZhang
430569d486 app detail 2024-03-05 17:38:32 +08:00
Joel
d3dfadbd9b feat: add code editor 2024-03-05 17:37:20 +08:00
StyleZhang
e474e02a50 sync workflow draft 2024-03-05 17:11:54 +08:00
StyleZhang
54d9cdaabf sync workflow draft 2024-03-05 17:11:54 +08:00
Joel
76fe3c1d76 fix: question classifer can not edit 2024-03-05 16:13:24 +08:00
StyleZhang
261e56e61d single run 2024-03-05 15:57:10 +08:00
StyleZhang
ede0bb5396 control run 2024-03-05 15:28:28 +08:00
JzoNg
186b85cd62 add store of app detail 2024-03-05 15:27:52 +08:00
Joel
eab405af5b chore: node add memo 2024-03-05 15:26:28 +08:00
Joel
93999cec56 chore: panel memo 2024-03-05 14:54:59 +08:00
StyleZhang
acacc0a4cb service 2024-03-05 14:44:13 +08:00
Joel
b2ae7089dc fix: var assigner 2024-03-05 14:40:21 +08:00
Joel
d4ab6b294a fix: llm default 2024-03-05 14:35:12 +08:00
Joel
e6d89f6756 fix: start node add 2024-03-05 14:27:52 +08:00
Joel
7ec29bbee7 feat: node add default value 2024-03-05 14:27:52 +08:00
StyleZhang
f1d44a4c87 zoom in out 2024-03-05 14:15:05 +08:00
StyleZhang
466f16eb1d node name 2024-03-05 13:05:36 +08:00
StyleZhang
04d54c0319 fix 2024-03-05 12:40:56 +08:00
StyleZhang
0367a2148a bg 2024-03-05 12:36:59 +08:00
Joel
57e9e229de temp 2024-03-05 12:35:36 +08:00
StyleZhang
90c8d9d27b service 2024-03-05 11:57:51 +08:00
Joel
a30b6acc52 fix: start node 2024-03-05 11:30:45 +08:00
Joel
0ee7f952ef fix: start node 2024-03-05 11:27:24 +08:00
Joel
86656de971 feat: classify data panel node sync 2024-03-05 11:06:35 +08:00
JzoNg
2e649c3329 add icon of yaml 2024-03-05 09:03:52 +08:00
StyleZhang
e868e44025 help line 2024-03-04 20:35:01 +08:00
Joel
4376813951 feat: get and set data use context 2024-03-04 20:14:18 +08:00
StyleZhang
ccd3e519ea edges change 2024-03-04 19:01:38 +08:00
StyleZhang
c4ca3bd34d rename data 2024-03-04 18:18:47 +08:00
StyleZhang
081baae883 operator 2024-03-04 17:45:41 +08:00
StyleZhang
a3d4befad4 service 2024-03-04 17:45:41 +08:00
Joel
2f13d2775f Merge branch 'main' into feat/workflow 2024-03-04 17:40:52 +08:00
Joel
a36a2a1080 feat: handleupdate logic 2024-03-04 17:04:53 +08:00
Joel
474c7865d7 feat: get and set value from store 2024-03-04 15:51:05 +08:00
StyleZhang
bd205f63cc fix: workflow route 2024-03-04 14:53:48 +08:00
Joel
ac40eb8d87 chore: add missing jp files 2024-03-04 10:44:34 +08:00
Joel
3ea06d286a merge main 2024-03-04 10:41:33 +08:00
JzoNg
8d6984e286 create app by import yaml 2024-03-03 15:37:47 +08:00
JzoNg
cd773b8cc9 app import supported 2024-03-03 14:25:50 +08:00
JzoNg
9bca69ebfb app DSL export supported 2024-03-03 14:10:11 +08:00
JzoNg
b33da6a09c duplicate app supported 2024-03-03 13:41:14 +08:00
JzoNg
7ae23d5567 add redirection 2024-03-03 13:24:58 +08:00
JzoNg
569315ee3e add tip for chatbot orchestrate 2024-03-03 12:39:07 +08:00
JzoNg
4c7941adef app creation 2024-03-02 16:47:43 +08:00
JzoNg
93d116a9d0 add tracing panel 2024-03-02 14:05:05 +08:00
JzoNg
7b2499c292 add meta data of run log 2024-03-02 11:16:23 +08:00
JzoNg
2be2bc5877 add run log status 2024-03-02 09:59:31 +08:00
JzoNg
cfb853efbf log detail panel 2024-03-01 20:32:22 +08:00
JzoNg
2691164fc4 workflow log list 2024-03-01 20:32:22 +08:00
StyleZhang
b113711a86 hooks 2024-03-01 20:22:06 +08:00
StyleZhang
68e9530507 run by single 2024-03-01 19:09:27 +08:00
StyleZhang
0ca23bb840 features 2024-03-01 18:20:49 +08:00
StyleZhang
6e3d6c4269 features 2024-03-01 18:20:49 +08:00
Joel
c2eaa32036 temp 2024-03-01 17:09:53 +08:00
Joel
604930db64 feat: support detect when to show vision config 2024-03-01 16:43:56 +08:00
Joel
c3f99779f2 feat: vision config 2024-03-01 16:30:24 +08:00
Joel
0518da1e49 feat: handle llm memory 2024-03-01 15:07:29 +08:00
Joel
6f6f032244 feat: choose context var 2024-03-01 14:35:37 +08:00
StyleZhang
0acb2db9b6 layout 2024-03-01 14:29:02 +08:00
Joel
74d26764f8 feat: knowledge retrieval dataset setting 2024-03-01 13:56:52 +08:00
Joel
cf77a89123 feat: dasetitem ui 2024-03-01 11:38:24 +08:00
StyleZhang
1b73632f77 utils 2024-03-01 11:27:30 +08:00
Joel
0a7cbf6fde feat: dataset list struct 2024-02-29 20:26:51 +08:00
Joel
e4701e26c8 feat: add datasets 2024-02-29 20:03:26 +08:00
Joel
045156985a fix: not show rerank modal picker 2024-02-29 19:39:27 +08:00
Joel
257e795ca9 feat: retrieval config 2024-02-29 18:24:15 +08:00
StyleZhang
bafdc510d6 record panel 2024-02-29 17:33:49 +08:00
StyleZhang
1840d05a37 record panel 2024-02-29 17:27:08 +08:00
Joel
6d5618447e feat: knowledge retirveval output 2024-02-29 16:39:36 +08:00
Joel
b2de27b7be feat: knowledge query var 2024-02-29 15:48:48 +08:00
Joel
9c0d44fa09 feat: llm node support config memroy 2024-02-29 15:36:25 +08:00
Joel
f95eb2df0d feat: filed fold 2024-02-29 15:26:06 +08:00
Joel
cbb298ccb6 feat: config conversation role name 2024-02-29 15:17:20 +08:00
Joel
9e6940ed3e feat: mermory size config 2024-02-29 14:50:36 +08:00
Joel
fbcc769d4e feat: instructions 2024-02-29 11:50:46 +08:00
Joel
65f0378e43 feat: classlist crud 2024-02-29 11:37:53 +08:00
Joel
f7a90f2660 merge main 2024-02-29 10:59:21 +08:00
StyleZhang
3d825dcb3e add features 2024-02-28 20:58:00 +08:00
StyleZhang
2094a554f6 multiple edge 2024-02-28 20:58:00 +08:00
Joel
4837ae4958 feat: question add class 2024-02-28 20:49:12 +08:00
Joel
6da9950b72 feat: workflow to auth 2024-02-28 20:19:22 +08:00
StyleZhang
e8921787b3 hooks 2024-02-28 20:11:24 +08:00
StyleZhang
d2d6904c9b panel-operator 2024-02-28 20:11:23 +08:00
Joel
510f0593e9 chore: question classify 2024-02-28 19:54:20 +08:00
Joel
7a438f8999 chore: assign var 2024-02-28 19:49:01 +08:00
Joel
113af85c3c chore: add requierd 2024-02-28 18:32:36 +08:00
Joel
916bacb60e chore: remove auto show modal 2024-02-28 18:17:39 +08:00
Joel
a98b5ca97e chore: auth i18n 2024-02-28 18:11:09 +08:00
Joel
076fe8ca3a feat: auth struct 2024-02-28 17:37:31 +08:00
Joel
b08327cb4b feat: edit body 2024-02-28 16:25:09 +08:00
StyleZhang
f1b868d5d9 next step 2024-02-28 16:19:57 +08:00
Joel
76ff004ea5 feat: bulk edit 2024-02-28 15:01:58 +08:00
Joel
df173764d2 chore: replace remove btn 2024-02-28 13:54:11 +08:00
Joel
7fa25934af feat: key value input 2024-02-28 11:30:28 +08:00
Joel
649c3d0732 feat: key value struct 2024-02-27 18:48:01 +08:00
Joel
35c56237a0 feat: url selector 2024-02-27 18:34:54 +08:00
StyleZhang
236cc6f526 hooks 2024-02-27 18:20:32 +08:00
StyleZhang
a311f88c99 compute node position 2024-02-27 18:02:49 +08:00
Joel
e92bc25216 fix: if node condition operation i18n 2024-02-27 17:10:52 +08:00
Joel
0c06d84e22 chore: add spacing and hover 2024-02-27 16:22:47 +08:00
Joel
77c8261fca chore: if not align 2024-02-27 16:19:29 +08:00
Joel
91a2e71fff feat: if comparasion 2024-02-27 15:52:07 +08:00
Joel
0fb47fed9e feat: add adn update condition 2024-02-27 15:20:04 +08:00
Joel
4519c6ab29 feat: conditions struct 2024-02-27 11:50:56 +08:00
Joel
32c6431dbc feat: assign node no var list tip 2024-02-27 10:58:46 +08:00
Joel
925964ac28 feat: add default and utils 2024-02-27 10:43:11 +08:00
Joel
4d4d3bb965 feat: add default values and utils and fix ts 2024-02-27 10:34:13 +08:00
StyleZhang
3d526b3a87 handleAddNextNode 2024-02-26 19:03:04 +08:00
StyleZhang
f91582e060 publish button 2024-02-26 18:43:33 +08:00
Joel
dec60fdd4c feat: fin var assigner 2024-02-26 18:19:01 +08:00
Joel
31930159b8 feat: var assigner data logic 2024-02-26 18:19:01 +08:00
StyleZhang
6e2611c86c node title desc 2024-02-26 17:10:18 +08:00
StyleZhang
7574107d8c add run-history 2024-02-26 15:38:49 +08:00
StyleZhang
58d8b0dd01 node handle connection line 2024-02-26 14:33:31 +08:00
JzoNg
49f78bacef update icons of app menu 2024-02-25 14:06:07 +08:00
JzoNg
3b190467c1 update i18n and style of creatioin from app template 2024-02-24 14:58:00 +08:00
JzoNg
804a090457 app templates 2024-02-24 14:21:42 +08:00
JzoNg
14cfb310e3 app creation 2024-02-24 12:45:58 +08:00
JzoNg
f607a334ac create from DSL 2024-02-24 12:45:58 +08:00
JzoNg
117b84116e app list modification 2024-02-24 12:45:58 +08:00
StyleZhang
171dd5c737 node-handle 2024-02-23 19:26:56 +08:00
Joel
b5ed4af25a feat: var assigner node 2024-02-23 18:26:42 +08:00
StyleZhang
b6c683a1b8 next step 2024-02-23 17:20:06 +08:00
Joel
5200ec0b9a feat: end node panle 2024-02-23 17:01:48 +08:00
Joel
307cbf1d9f feat: input no var tip 2024-02-23 16:15:45 +08:00
StyleZhang
e7ecdb01a6 block-selector 2024-02-23 15:20:04 +08:00
Joel
383bfd7583 feat: merge i18n 2024-02-23 15:18:48 +08:00
Joel
508ea8bc0a feat: add number type var 2024-02-23 15:02:00 +08:00
Joel
077de17cd5 feat: support config modal edit 2024-02-23 14:25:08 +08:00
StyleZhang
f6c07c996b workflow store 2024-02-23 14:16:19 +08:00
Joel
7ba0bfffa2 fix: debug set var value error 2024-02-23 11:58:28 +08:00
StyleZhang
9b577fa32c chore 2024-02-23 11:31:46 +08:00
StyleZhang
94cda3e837 chore 2024-02-22 20:10:58 +08:00
Joel
f09f91e25a hide debug 2024-02-22 18:51:25 +08:00
Joel
235bec6481 feat: new var input editor 2024-02-22 18:49:32 +08:00
StyleZhang
ee616ee6dd header 2024-02-22 16:35:54 +08:00
Joel
5153068a64 feat: start var list 2024-02-22 16:17:38 +08:00
StyleZhang
701e441349 panel 2024-02-22 15:37:33 +08:00
Joel
5817a035f9 feat: start build in vars show 2024-02-22 15:06:27 +08:00
StyleZhang
ea76f46223 block-selector 2024-02-22 15:02:02 +08:00
Joel
0759b29ca2 feat: retrial node 2024-02-22 14:40:16 +08:00
Joel
db6074e035 feat: tool node 2024-02-22 14:06:34 +08:00
Joel
6057ba0988 feat: var assigner node struct 2024-02-22 11:42:20 +08:00
Joel
2fdcf1756e feat: end node 2024-02-22 11:04:14 +08:00
StyleZhang
f489736e06 add debug-and-preview 2024-02-21 20:23:03 +08:00
StyleZhang
15f13209cf node handle 2024-02-21 19:02:58 +08:00
Joel
dbf3b7ad6d feat: end node typs and mock 2024-02-21 18:38:52 +08:00
Joel
3341077587 feat: start node 2024-02-21 18:31:45 +08:00
Joel
e39d7021e0 feat: if else node 2024-02-21 17:45:52 +08:00
Joel
cffaf30760 feat: if types 2024-02-21 16:34:38 +08:00
Joel
bc60cf0a35 feat: http node content 2024-02-21 16:00:14 +08:00
Joel
9bb9807252 feat: http node struct 2024-02-21 15:50:36 +08:00
Joel
8b8fdb48bb feat: output var 2024-02-21 14:56:10 +08:00
Joel
b4437ccd2b chore: output lines 2024-02-21 14:11:51 +08:00
Joel
65ac4dedcc feat: template transform code tooltip 2024-02-21 13:55:17 +08:00
StyleZhang
671654da71 add node 2024-02-21 12:28:10 +08:00
Joel
31490417d1 Merge branch 'main' into feat/workflow 2024-02-21 12:05:22 +08:00
Joel
17e8c91267 feat: template transform panel content 2024-02-21 11:48:34 +08:00
Joel
db7dccf349 feat: type selector 2024-02-21 11:33:25 +08:00
Joel
71d3f71e22 feat: code editor base 2024-02-21 11:04:37 +08:00
StyleZhang
13a54c3f56 block-selector edit 2024-02-20 20:12:41 +08:00
Joel
d58a1b1359 feat: code support vars 2024-02-20 18:42:21 +08:00
Joel
bb87a350ac feat: question classify panel 2024-02-20 17:39:09 +08:00
Joel
c441a848e7 feat: question classify node 2024-02-20 17:29:06 +08:00
StyleZhang
f14a5c7346 add node-control 2024-02-20 16:58:29 +08:00
Joel
92219b5aad feat: prompt ide and fin direct ansewer node 2024-02-20 16:52:22 +08:00
Joel
291201db1c chore: llm use config 2024-02-20 15:34:44 +08:00
Joel
c8ea6d7bfb feat: direct answer node 2024-02-20 15:21:35 +08:00
Joel
9c70befaf6 chore: move node type to self struct 2024-02-20 14:59:03 +08:00
Joel
fcadb807f6 feat: llm node content 2024-02-20 14:51:19 +08:00
Joel
4364775dcb feat: output vars 2024-02-20 14:27:40 +08:00
Joel
2a196e91a6 feat: default set var name 2024-02-20 14:04:24 +08:00
Joel
7a0358827a feat: finish choose var 2024-02-20 14:01:20 +08:00
Joel
62e2deafca feat: infinite choose var 2024-02-20 11:23:23 +08:00
StyleZhang
25b4e68fbb delete 2024-02-19 19:58:49 +08:00
StyleZhang
c909319413 base-node base-panel 2024-02-19 19:44:48 +08:00
StyleZhang
c7ee8ac1c7 add app-info-panel 2024-02-19 19:06:42 +08:00
Joel
2386eed703 chore: new block enum 2024-02-19 18:46:21 +08:00
Joel
ada558bedc feat: add picker shower 2024-02-19 18:33:12 +08:00
StyleZhang
6caca3aaf7 base panel 2024-02-19 17:58:54 +08:00
StyleZhang
3d3bc4c512 initial node data 2024-02-19 17:45:36 +08:00
Joel
044ed624eb feat: var picker trigger 2024-02-19 16:36:53 +08:00
Joel
4dff0c5dff feat: to not ignore var 2024-02-19 16:01:12 +08:00
StyleZhang
59d8f926c8 block-selector 2024-02-19 15:54:41 +08:00
Joel
c6f1900a93 chore: merge main 2024-02-19 15:52:51 +08:00
Joel
d94a9cd864 feat: add node icons 2024-02-19 15:44:52 +08:00
Joel
21db8e3be4 feat: add var struct 2024-02-19 11:26:25 +08:00
Joel
e05bbec879 chore: model and params select 2024-02-19 10:35:11 +08:00
StyleZhang
240e0dfa6f next-step 2024-02-18 20:26:16 +08:00
Joel
ab6a01b476 chore: handle llm model type 2024-02-18 18:31:04 +08:00
StyleZhang
dce01cf002 header 2024-02-18 17:56:00 +08:00
StyleZhang
da84ba06c7 add block-icon 2024-02-18 17:36:34 +08:00
Joel
45ba3ca07b feat: add model selector 2024-02-18 17:26:52 +08:00
StyleZhang
56407a910d add block-selector 2024-02-18 16:14:21 +08:00
StyleZhang
e624c33d51 node props 2024-02-18 16:14:21 +08:00
Joel
3666462076 feat: llm input struct 2024-02-18 14:33:44 +08:00
Joel
da0d9aab39 chore: remove node code to panel 2024-02-18 14:08:08 +08:00
Joel
ace04b3ef4 feat: filed and var 2024-02-18 14:01:22 +08:00
Joel
1a4c2e77c4 feat: nodes placeholder 2024-02-06 17:49:07 +08:00
StyleZhang
f3c78fe73d init 2024-02-06 17:17:29 +08:00
StyleZhang
a17c0e5bf6 init 2024-02-06 17:05:26 +08:00
StyleZhang
20d5fdea2c init 2024-02-06 12:41:34 +08:00
1046 changed files with 63320 additions and 10196 deletions

View File

@@ -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

View File

@@ -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/') }}

View File

@@ -137,4 +137,15 @@ SSRF_PROXY_HTTP_URL=
SSRF_PROXY_HTTPS_URL=
BATCH_UPLOAD_LIMIT=10
KEYWORD_DATA_SOURCE_TYPE=database
KEYWORD_DATA_SOURCE_TYPE=database
# CODE EXECUTION CONFIGURATION
CODE_EXECUTION_ENDPOINT=http://127.0.0.1:8194
CODE_EXECUTION_API_KEY=dify-sandbox
CODE_MAX_NUMBER=9223372036854775807
CODE_MIN_NUMBER=-9223372036854775808
CODE_MAX_STRING_LENGTH=80000
TEMPLATE_TRANSFORM_MAX_LENGTH=80000
CODE_MAX_STRING_ARRAY_LENGTH=30
CODE_MAX_OBJECT_ARRAY_LENGTH=30
CODE_MAX_NUMBER_ARRAY_LENGTH=1000

View File

@@ -15,7 +15,7 @@ from libs.rsa import generate_key_pair
from models.account import Tenant
from models.dataset import Dataset, DatasetCollectionBinding, DocumentSegment
from models.dataset import Document as DatasetDocument
from models.model import Account, App, AppAnnotationSetting, MessageAnnotation
from models.model import Account, App, AppAnnotationSetting, AppMode, Conversation, MessageAnnotation
from models.provider import Provider, ProviderModel
@@ -371,8 +371,70 @@ def migrate_knowledge_vector_database():
fg='green'))
@click.command('convert-to-agent-apps', help='Convert Agent Assistant to Agent App.')
def convert_to_agent_apps():
"""
Convert Agent Assistant to Agent App.
"""
click.echo(click.style('Start convert to agent apps.', fg='green'))
proceeded_app_ids = []
while True:
# fetch first 1000 apps
sql_query = """SELECT a.id AS id FROM apps a
INNER JOIN app_model_configs am ON a.app_model_config_id=am.id
WHERE a.mode = 'chat'
AND am.agent_mode is not null
AND (
am.agent_mode like '%"strategy": "function_call"%'
OR am.agent_mode like '%"strategy": "react"%'
)
AND (
am.agent_mode like '{"enabled": true%'
OR am.agent_mode like '{"max_iteration": %'
) ORDER BY a.created_at DESC LIMIT 1000
"""
with db.engine.begin() as conn:
rs = conn.execute(db.text(sql_query))
apps = []
for i in rs:
app_id = str(i.id)
if app_id not in proceeded_app_ids:
proceeded_app_ids.append(app_id)
app = db.session.query(App).filter(App.id == app_id).first()
apps.append(app)
if len(apps) == 0:
break
for app in apps:
click.echo('Converting app: {}'.format(app.id))
try:
app.mode = AppMode.AGENT_CHAT.value
db.session.commit()
# update conversation mode to agent
db.session.query(Conversation).filter(Conversation.app_id == app.id).update(
{Conversation.mode: AppMode.AGENT_CHAT.value}
)
db.session.commit()
click.echo(click.style('Converted app: {}'.format(app.id), fg='green'))
except Exception as e:
click.echo(
click.style('Convert app error: {} {}'.format(e.__class__.__name__,
str(e)), fg='red'))
click.echo(click.style('Congratulations! Converted {} agent apps.'.format(len(proceeded_app_ids)), fg='green'))
def register_commands(app):
app.cli.add_command(reset_password)
app.cli.add_command(reset_email)
app.cli.add_command(reset_encrypt_key_pair)
app.cli.add_command(vdb_migrate)
app.cli.add_command(convert_to_agent_apps)

View File

@@ -28,6 +28,7 @@ DEFAULTS = {
'CHECK_UPDATE_URL': 'https://updates.dify.ai',
'DEPLOY_ENV': 'PRODUCTION',
'SQLALCHEMY_POOL_SIZE': 30,
'SQLALCHEMY_MAX_OVERFLOW': 10,
'SQLALCHEMY_POOL_RECYCLE': 3600,
'SQLALCHEMY_ECHO': 'False',
'SENTRY_TRACES_SAMPLE_RATE': 1.0,
@@ -49,6 +50,8 @@ DEFAULTS = {
'HOSTED_ANTHROPIC_PAID_ENABLED': 'False',
'HOSTED_MODERATION_ENABLED': 'False',
'HOSTED_MODERATION_PROVIDERS': '',
'HOSTED_FETCH_APP_TEMPLATES_MODE': 'remote',
'HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN': 'https://tmpl.dify.ai',
'CLEAN_DAY_SETTING': 30,
'UPLOAD_FILE_SIZE_LIMIT': 15,
'UPLOAD_FILE_BATCH_LIMIT': 5,
@@ -61,6 +64,8 @@ DEFAULTS = {
'ETL_TYPE': 'dify',
'KEYWORD_STORE': 'jieba',
'BATCH_UPLOAD_LIMIT': 20,
'CODE_EXECUTION_ENDPOINT': '',
'CODE_EXECUTION_API_KEY': '',
'TOOL_ICON_CACHE_MAX_AGE': 3600,
'KEYWORD_DATA_SOURCE_TYPE': 'database',
}
@@ -93,7 +98,7 @@ class Config:
# ------------------------
# General Configurations.
# ------------------------
self.CURRENT_VERSION = "0.5.11-fix1"
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')
@@ -149,6 +154,7 @@ class Config:
self.SQLALCHEMY_DATABASE_URI = f"postgresql://{db_credentials['DB_USERNAME']}:{db_credentials['DB_PASSWORD']}@{db_credentials['DB_HOST']}:{db_credentials['DB_PORT']}/{db_credentials['DB_DATABASE']}{db_extras}"
self.SQLALCHEMY_ENGINE_OPTIONS = {
'pool_size': int(get_env('SQLALCHEMY_POOL_SIZE')),
'max_overflow': int(get_env('SQLALCHEMY_MAX_OVERFLOW')),
'pool_recycle': int(get_env('SQLALCHEMY_POOL_RECYCLE'))
}
@@ -294,6 +300,10 @@ class Config:
self.HOSTED_MODERATION_ENABLED = get_bool_env('HOSTED_MODERATION_ENABLED')
self.HOSTED_MODERATION_PROVIDERS = get_env('HOSTED_MODERATION_PROVIDERS')
# fetch app templates mode, remote, builtin, db(only for dify SaaS), default: remote
self.HOSTED_FETCH_APP_TEMPLATES_MODE = get_env('HOSTED_FETCH_APP_TEMPLATES_MODE')
self.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN = get_env('HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN')
self.ETL_TYPE = get_env('ETL_TYPE')
self.UNSTRUCTURED_API_URL = get_env('UNSTRUCTURED_API_URL')
self.BILLING_ENABLED = get_bool_env('BILLING_ENABLED')
@@ -301,6 +311,9 @@ class Config:
self.BATCH_UPLOAD_LIMIT = get_env('BATCH_UPLOAD_LIMIT')
self.CODE_EXECUTION_ENDPOINT = get_env('CODE_EXECUTION_ENDPOINT')
self.CODE_EXECUTION_API_KEY = get_env('CODE_EXECUTION_API_KEY')
self.API_COMPRESSION_ENABLED = get_bool_env('API_COMPRESSION_ENABLED')
self.TOOL_ICON_CACHE_MAX_AGE = get_env('TOOL_ICON_CACHE_MAX_AGE')

View File

@@ -1,6 +1,4 @@
import json
from models.model import AppModelConfig
languages = ['en-US', 'zh-Hans', 'pt-BR', 'es-ES', 'fr-FR', 'de-DE', 'ja-JP', 'ko-KR', 'ru-RU', 'it-IT', 'uk-UA', 'vi-VN']
@@ -27,576 +25,3 @@ def supported_language(lang):
error = ('{lang} is not a valid language.'
.format(lang=lang))
raise ValueError(error)
user_input_form_template = {
"en-US": [
{
"paragraph": {
"label": "Query",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"zh-Hans": [
{
"paragraph": {
"label": "查询内容",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"pt-BR": [
{
"paragraph": {
"label": "Consulta",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"es-ES": [
{
"paragraph": {
"label": "Consulta",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"ua-UK": [
{
"paragraph": {
"label": "Запит",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"vi-VN": [
{
"paragraph": {
"label": "Nội dung truy vấn",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
}
demo_model_templates = {
'en-US': [
{
'name': 'Translation Assistant',
'icon': '',
'icon_background': '',
'description': 'A multilingual translator that provides translation capabilities in multiple languages, translating user input into the language they need.',
'mode': 'completion',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo-instruct',
configs={
'prompt_template': "Please translate the following text into {{target_language}}:\n",
'prompt_variables': [
{
"key": "target_language",
"name": "Target Language",
"description": "The language you want to translate into.",
"type": "select",
"default": "Chinese",
'options': [
'Chinese',
'English',
'Japanese',
'French',
'Russian',
'German',
'Spanish',
'Korean',
'Italian',
]
}
],
'completion_params': {
'max_token': 1000,
'temperature': 0,
'top_p': 0,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='',
suggested_questions=None,
pre_prompt="Please translate the following text into {{target_language}}:\n{{query}}\ntranslate:",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"completion_params": {
"max_tokens": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=json.dumps([
{
"select": {
"label": "Target Language",
"variable": "target_language",
"description": "The language you want to translate into.",
"default": "Chinese",
"required": True,
'options': [
'Chinese',
'English',
'Japanese',
'French',
'Russian',
'German',
'Spanish',
'Korean',
'Italian',
]
}
}, {
"paragraph": {
"label": "Query",
"variable": "query",
"required": True,
"default": ""
}
}
])
)
},
{
'name': 'AI Front-end Interviewer',
'icon': '',
'icon_background': '',
'description': 'A simulated front-end interviewer that tests the skill level of front-end development through questioning.',
'mode': 'chat',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo',
configs={
'introduction': 'Hi, welcome to our interview. I am the interviewer for this technology company, and I will test your web front-end development skills. Next, I will ask you some technical questions. Please answer them as thoroughly as possible. ',
'prompt_template': "You will play the role of an interviewer for a technology company, examining the user's web front-end development skills and posing 5-10 sharp technical questions.\n\nPlease note:\n- Only ask one question at a time.\n- After the user answers a question, ask the next question directly, without trying to correct any mistakes made by the candidate.\n- If you think the user has not answered correctly for several consecutive questions, ask fewer questions.\n- After asking the last question, you can ask this question: Why did you leave your last job? After the user answers this question, please express your understanding and support.\n",
'prompt_variables': [],
'completion_params': {
'max_token': 300,
'temperature': 0.8,
'top_p': 0.9,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='Hi, welcome to our interview. I am the interviewer for this technology company, and I will test your web front-end development skills. Next, I will ask you some technical questions. Please answer them as thoroughly as possible. ',
suggested_questions=None,
pre_prompt="You will play the role of an interviewer for a technology company, examining the user's web front-end development skills and posing 5-10 sharp technical questions.\n\nPlease note:\n- Only ask one question at a time.\n- After the user answers a question, ask the next question directly, without trying to correct any mistakes made by the candidate.\n- If you think the user has not answered correctly for several consecutive questions, ask fewer questions.\n- After asking the last question, you can ask this question: Why did you leave your last job? After the user answers this question, please express your understanding and support.\n",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo",
"mode": "chat",
"completion_params": {
"max_tokens": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=None
)
}
],
'zh-Hans': [
{
'name': '翻译助手',
'icon': '',
'icon_background': '',
'description': '一个多语言翻译器,提供多种语言翻译能力,将用户输入的文本翻译成他们需要的语言。',
'mode': 'completion',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo-instruct',
configs={
'prompt_template': "请将以下文本翻译为{{target_language}}:\n",
'prompt_variables': [
{
"key": "target_language",
"name": "目标语言",
"description": "翻译的目标语言",
"type": "select",
"default": "中文",
"options": [
"中文",
"英文",
"日语",
"法语",
"俄语",
"德语",
"西班牙语",
"韩语",
"意大利语",
]
}
],
'completion_params': {
'max_token': 1000,
'temperature': 0,
'top_p': 0,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='',
suggested_questions=None,
pre_prompt="请将以下文本翻译为{{target_language}}:\n{{query}}\n翻译:",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"completion_params": {
"max_tokens": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=json.dumps([
{
"select": {
"label": "目标语言",
"variable": "target_language",
"description": "翻译的目标语言",
"default": "中文",
"required": True,
'options': [
"中文",
"英文",
"日语",
"法语",
"俄语",
"德语",
"西班牙语",
"韩语",
"意大利语",
]
}
}, {
"paragraph": {
"label": "文本内容",
"variable": "query",
"required": True,
"default": ""
}
}
])
)
},
{
'name': 'AI 前端面试官',
'icon': '',
'icon_background': '',
'description': '一个模拟的前端面试官,通过提问的方式对前端开发的技能水平进行检验。',
'mode': 'chat',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo',
configs={
'introduction': '你好,欢迎来参加我们的面试,我是这家科技公司的面试官,我将考察你的 Web 前端开发技能。接下来我会向您提出一些技术问题,请您尽可能详尽地回答。',
'prompt_template': "你将扮演一个科技公司的面试官,考察用户作为候选人的 Web 前端开发水平,提出 5-10 个犀利的技术问题。\n\n请注意:\n- 每次只问一个问题\n- 用户回答问题后请直接问下一个问题,而不要试图纠正候选人的错误;\n- 如果你认为用户连续几次回答的都不对,就少问一点;\n- 问完最后一个问题后,你可以问这样一个问题:上一份工作为什么离职?用户回答该问题后,请表示理解与支持。\n",
'prompt_variables': [],
'completion_params': {
'max_token': 300,
'temperature': 0.8,
'top_p': 0.9,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='你好,欢迎来参加我们的面试,我是这家科技公司的面试官,我将考察你的 Web 前端开发技能。接下来我会向您提出一些技术问题,请您尽可能详尽地回答。',
suggested_questions=None,
pre_prompt="你将扮演一个科技公司的面试官,考察用户作为候选人的 Web 前端开发水平,提出 5-10 个犀利的技术问题。\n\n请注意:\n- 每次只问一个问题\n- 用户回答问题后请直接问下一个问题,而不要试图纠正候选人的错误;\n- 如果你认为用户连续几次回答的都不对,就少问一点;\n- 问完最后一个问题后,你可以问这样一个问题:上一份工作为什么离职?用户回答该问题后,请表示理解与支持。\n",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo",
"mode": "chat",
"completion_params": {
"max_tokens": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=None
)
}
],
'uk-UA': [
{
"name": "Помічник перекладу",
"icon": "",
"icon_background": "",
"description": "Багатомовний перекладач, який надає можливості перекладу різними мовами, перекладаючи введені користувачем дані на потрібну мову.",
"mode": "completion",
"model_config": AppModelConfig(
provider="openai",
model_id="gpt-3.5-turbo-instruct",
configs={
"prompt_template": "Будь ласка, перекладіть наступний текст на {{target_language}}:\n",
"prompt_variables": [
{
"key": "target_language",
"name": "Цільова мова",
"description": "Мова, на яку ви хочете перекласти.",
"type": "select",
"default": "Ukrainian",
"options": [
"Chinese",
"English",
"Japanese",
"French",
"Russian",
"German",
"Spanish",
"Korean",
"Italian",
],
},
],
"completion_params": {
"max_token": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1,
},
},
opening_statement="",
suggested_questions=None,
pre_prompt="Будь ласка, перекладіть наступний текст на {{target_language}}:\n{{query}}\ntranslate:",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"completion_params": {
"max_tokens": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1,
},
}),
user_input_form=json.dumps([
{
"select": {
"label": "Цільова мова",
"variable": "target_language",
"description": "Мова, на яку ви хочете перекласти.",
"default": "Chinese",
"required": True,
'options': [
'Chinese',
'English',
'Japanese',
'French',
'Russian',
'German',
'Spanish',
'Korean',
'Italian',
]
}
}, {
"paragraph": {
"label": "Запит",
"variable": "query",
"required": True,
"default": ""
}
}
])
)
},
{
"name": "AI інтерв’юер фронтенду",
"icon": "",
"icon_background": "",
"description": "Симульований інтерв’юер фронтенду, який перевіряє рівень кваліфікації у розробці фронтенду через опитування.",
"mode": "chat",
"model_config": AppModelConfig(
provider="openai",
model_id="gpt-3.5-turbo",
configs={
"introduction": "Привіт, ласкаво просимо на наше співбесіду. Я інтерв'юер цієї технологічної компанії, і я перевірю ваші навички веб-розробки фронтенду. Далі я поставлю вам декілька технічних запитань. Будь ласка, відповідайте якомога ретельніше. ",
"prompt_template": "Ви будете грати роль інтерв'юера технологічної компанії, перевіряючи навички розробки фронтенду користувача та ставлячи 5-10 чітких технічних питань.\n\nЗверніть увагу:\n- Ставте лише одне запитання за раз.\n- Після того, як користувач відповість на запитання, ставте наступне запитання безпосередньо, не намагаючись виправити будь-які помилки, допущені кандидатом.\n- Якщо ви вважаєте, що користувач не відповів правильно на кілька питань поспіль, задайте менше запитань.\n- Після того, як ви задали останнє запитання, ви можете поставити таке запитання: Чому ви залишили свою попередню роботу? Після того, як користувач відповість на це питання, висловіть своє розуміння та підтримку.\n",
"prompt_variables": [],
"completion_params": {
"max_token": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1,
},
},
opening_statement="Привіт, ласкаво просимо на наше співбесіду. Я інтерв'юер цієї технологічної компанії, і я перевірю ваші навички веб-розробки фронтенду. Далі я поставлю вам декілька технічних запитань. Будь ласка, відповідайте якомога ретельніше. ",
suggested_questions=None,
pre_prompt="Ви будете грати роль інтерв'юера технологічної компанії, перевіряючи навички розробки фронтенду користувача та ставлячи 5-10 чітких технічних питань.\n\nЗверніть увагу:\n- Ставте лише одне запитання за раз.\n- Після того, як користувач відповість на запитання, ставте наступне запитання безпосередньо, не намагаючись виправити будь-які помилки, допущені кандидатом.\n- Якщо ви вважаєте, що користувач не відповів правильно на кілька питань поспіль, задайте менше запитань.\n- Після того, як ви задали останнє запитання, ви можете поставити таке запитання: Чому ви залишили свою попередню роботу? Після того, як користувач відповість на це питання, висловіть своє розуміння та підтримку.\n",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo",
"mode": "chat",
"completion_params": {
"max_tokens": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1,
},
}),
user_input_form=None
),
}
],
'vi-VN': [
{
'name': 'Trợ lý dịch thuật',
'icon': '',
'icon_background': '',
'description': 'Trình dịch đa ngôn ngữ cung cấp khả năng dịch bằng nhiều ngôn ngữ, dịch thông tin đầu vào của người dùng sang ngôn ngữ họ cần.',
'mode': 'completion',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo-instruct',
configs={
'prompt_template': "Hãy dịch đoạn văn bản sau sang ngôn ngữ {{target_language}}:\n",
'prompt_variables': [
{
"key": "target_language",
"name": "Ngôn ngữ đích",
"description": "Ngôn ngữ bạn muốn dịch sang.",
"type": "select",
"default": "Vietnamese",
'options': [
'Chinese',
'English',
'Japanese',
'French',
'Russian',
'German',
'Spanish',
'Korean',
'Italian',
'Vietnamese',
]
}
],
'completion_params': {
'max_token': 1000,
'temperature': 0,
'top_p': 0,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='',
suggested_questions=None,
pre_prompt="Hãy dịch đoạn văn bản sau sang {{target_language}}:\n{{query}}\ndịch:",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"completion_params": {
"max_tokens": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=json.dumps([
{
"select": {
"label": "Ngôn ngữ đích",
"variable": "target_language",
"description": "Ngôn ngữ bạn muốn dịch sang.",
"default": "Vietnamese",
"required": True,
'options': [
'Chinese',
'English',
'Japanese',
'French',
'Russian',
'German',
'Spanish',
'Korean',
'Italian',
'Vietnamese',
]
}
}, {
"paragraph": {
"label": "Query",
"variable": "query",
"required": True,
"default": ""
}
}
])
)
},
{
'name': 'Phỏng vấn front-end AI',
'icon': '',
'icon_background': '',
'description': 'Một người phỏng vấn front-end mô phỏng để kiểm tra mức độ kỹ năng phát triển front-end thông qua việc đặt câu hỏi.',
'mode': 'chat',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo',
configs={
'introduction': 'Xin chào, chào mừng đến với cuộc phỏng vấn của chúng tôi. Tôi là người phỏng vấn cho công ty công nghệ này và tôi sẽ kiểm tra kỹ năng phát triển web front-end của bạn. Tiếp theo, tôi sẽ hỏi bạn một số câu hỏi kỹ thuật. Hãy trả lời chúng càng kỹ lưỡng càng tốt. ',
'prompt_template': "Bạn sẽ đóng vai người phỏng vấn cho một công ty công nghệ, kiểm tra kỹ năng phát triển web front-end của người dùng và đặt ra 5-10 câu hỏi kỹ thuật sắc bén.\n\nXin lưu ý:\n- Mỗi lần chỉ hỏi một câu hỏi.\n - Sau khi người dùng trả lời một câu hỏi, hãy hỏi trực tiếp câu hỏi tiếp theo mà không cố gắng sửa bất kỳ lỗi nào mà thí sinh mắc phải.\n- Nếu bạn cho rằng người dùng đã không trả lời đúng cho một số câu hỏi liên tiếp, hãy hỏi ít câu hỏi hơn.\n- Sau đặt câu hỏi cuối cùng, bạn có thể hỏi câu hỏi này: Tại sao bạn lại rời bỏ công việc cuối cùng của mình? Sau khi người dùng trả lời câu hỏi này, vui lòng bày tỏ sự hiểu biết và ủng hộ của bạn.\n",
'prompt_variables': [],
'completion_params': {
'max_token': 300,
'temperature': 0.8,
'top_p': 0.9,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='Xin chào, chào mừng đến với cuộc phỏng vấn của chúng tôi. Tôi là người phỏng vấn cho công ty công nghệ này và tôi sẽ kiểm tra kỹ năng phát triển web front-end của bạn. Tiếp theo, tôi sẽ hỏi bạn một số câu hỏi kỹ thuật. Hãy trả lời chúng càng kỹ lưỡng càng tốt. ',
suggested_questions=None,
pre_prompt="Bạn sẽ đóng vai người phỏng vấn cho một công ty công nghệ, kiểm tra kỹ năng phát triển web front-end của người dùng và đặt ra 5-10 câu hỏi kỹ thuật sắc bén.\n\nXin lưu ý:\n- Mỗi lần chỉ hỏi một câu hỏi.\n - Sau khi người dùng trả lời một câu hỏi, hãy hỏi trực tiếp câu hỏi tiếp theo mà không cố gắng sửa bất kỳ lỗi nào mà thí sinh mắc phải.\n- Nếu bạn cho rằng người dùng đã không trả lời đúng cho một số câu hỏi liên tiếp, hãy hỏi ít câu hỏi hơn.\n- Sau đặt câu hỏi cuối cùng, bạn có thể hỏi câu hỏi này: Tại sao bạn lại rời bỏ công việc cuối cùng của mình? Sau khi người dùng trả lời câu hỏi này, vui lòng bày tỏ sự hiểu biết và ủng hộ của bạn.\n",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo",
"mode": "chat",
"completion_params": {
"max_tokens": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=None
)
}
],
}

View File

@@ -1,27 +1,31 @@
import json
model_templates = {
# completion default mode
'completion_default': {
from models.model import AppMode
default_app_templates = {
# workflow default mode
AppMode.WORKFLOW: {
'app': {
'mode': 'completion',
'mode': AppMode.WORKFLOW.value,
'enable_site': True,
'enable_api': True,
'is_demo': False,
'api_rpm': 0,
'api_rph': 0,
'status': 'normal'
'enable_api': True
}
},
# completion default mode
AppMode.COMPLETION: {
'app': {
'mode': AppMode.COMPLETION.value,
'enable_site': True,
'enable_api': True
},
'model_config': {
'provider': '',
'model_id': '',
'configs': {},
'model': json.dumps({
'model': {
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"name": "gpt-4",
"mode": "chat",
"completion_params": {}
}),
},
'user_input_form': json.dumps([
{
"paragraph": {
@@ -33,32 +37,50 @@ model_templates = {
}
]),
'pre_prompt': '{{query}}'
}
},
},
# chat default mode
'chat_default': {
AppMode.CHAT: {
'app': {
'mode': 'chat',
'mode': AppMode.CHAT.value,
'enable_site': True,
'enable_api': True,
'is_demo': False,
'api_rpm': 0,
'api_rph': 0,
'status': 'normal'
'enable_api': True
},
'model_config': {
'provider': '',
'model_id': '',
'configs': {},
'model': json.dumps({
'model': {
"provider": "openai",
"name": "gpt-3.5-turbo",
"name": "gpt-4",
"mode": "chat",
"completion_params": {}
})
}
}
},
# advanced-chat default mode
AppMode.ADVANCED_CHAT: {
'app': {
'mode': AppMode.ADVANCED_CHAT.value,
'enable_site': True,
'enable_api': True
}
},
# agent-chat default mode
AppMode.AGENT_CHAT: {
'app': {
'mode': AppMode.AGENT_CHAT.value,
'enable_site': True,
'enable_api': True
},
'model_config': {
'model': {
"provider": "openai",
"name": "gpt-4",
"mode": "chat",
"completion_params": {}
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -5,10 +5,10 @@ bp = Blueprint('console', __name__, url_prefix='/console/api')
api = ExternalApi(bp)
# Import other controllers
from . import admin, apikey, extension, feature, setup, version
from . import admin, apikey, extension, feature, setup, version, ping
# Import app controllers
from .app import (advanced_prompt_template, annotation, app, audio, completion, conversation, generator, message,
model_config, site, statistic)
model_config, site, statistic, workflow, workflow_run, workflow_app_log, workflow_statistic, agent)
# Import auth controllers
from .auth import activate, data_source_oauth, login, oauth
# Import billing controllers
@@ -16,6 +16,7 @@ from .billing import billing
# Import datasets controllers
from .datasets import data_source, datasets, datasets_document, datasets_segments, file, hit_testing
# Import explore controllers
from .explore import audio, completion, conversation, installed_app, message, parameter, recommended_app, saved_message
from .explore import (audio, completion, conversation, installed_app, message, parameter, recommended_app,
saved_message, workflow)
# Import workspace controllers
from .workspace import account, members, model_providers, models, tool_providers, workspace
from .workspace import account, members, model_providers, models, tool_providers, workspace

View File

@@ -1,21 +0,0 @@
from controllers.console.app.error import AppUnavailableError
from extensions.ext_database import db
from flask_login import current_user
from models.model import App
from werkzeug.exceptions import NotFound
def _get_app(app_id, mode=None):
app = db.session.query(App).filter(
App.id == app_id,
App.tenant_id == current_user.current_tenant_id,
App.status == 'normal'
).first()
if not app:
raise NotFound("App not found")
if mode and app.mode != mode:
raise NotFound("The {} app not found".format(mode))
return app

View File

@@ -0,0 +1,32 @@
from flask_restful import Resource, reqparse
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 libs.helper import uuid_value
from libs.login import login_required
from models.model import AppMode
from services.agent_service import AgentService
class AgentLogApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.AGENT_CHAT])
def get(self, app_model):
"""Get agent logs"""
parser = reqparse.RequestParser()
parser.add_argument('message_id', type=uuid_value, required=True, location='args')
parser.add_argument('conversation_id', type=uuid_value, required=True, location='args')
args = parser.parse_args()
return AgentService.get_agent_logs(
app_model,
args['conversation_id'],
args['message_id']
)
api.add_resource(AgentLogApi, '/apps/<uuid:app_id>/agent/logs')

View File

@@ -1,41 +1,28 @@
import json
import logging
from datetime import datetime
from flask_login import current_user
from flask_restful import Resource, abort, inputs, marshal_with, reqparse
from werkzeug.exceptions import Forbidden
from flask_restful import Resource, inputs, marshal_with, reqparse
from werkzeug.exceptions import Forbidden, BadRequest
from constants.languages import demo_model_templates, languages
from constants.model_template import model_templates
from controllers.console import api
from controllers.console.app.error import AppNotFoundError, ProviderNotInitializeError
from controllers.console.app.wraps import get_app_model
from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
from core.model_manager import ModelManager
from core.model_runtime.entities.model_entities import ModelType
from core.provider_manager import ProviderManager
from events.app_event import app_was_created, app_was_deleted
from core.agent.entities import AgentToolEntity
from extensions.ext_database import db
from fields.app_fields import (
app_detail_fields,
app_detail_fields_with_site,
app_pagination_fields,
template_list_fields,
)
from libs.login import login_required
from models.model import App, AppModelConfig, Site
from services.app_model_config_service import AppModelConfigService
from services.app_service import AppService
from models.model import App, AppModelConfig, AppMode
from core.tools.utils.configuration import ToolParameterConfigurationManager
from core.tools.tool_manager import ToolManager
from core.entities.application_entities import AgentToolEntity
def _get_app(app_id, tenant_id):
app = db.session.query(App).filter(App.id == app_id, App.tenant_id == tenant_id).first()
if not app:
raise AppNotFoundError
return app
ALLOW_CREATE_APP_MODES = ['chat', 'agent-chat', 'advanced-chat', 'workflow', 'completion']
class AppListApi(Resource):
@@ -49,33 +36,15 @@ class AppListApi(Resource):
parser = reqparse.RequestParser()
parser.add_argument('page', type=inputs.int_range(1, 99999), required=False, default=1, location='args')
parser.add_argument('limit', type=inputs.int_range(1, 100), required=False, default=20, location='args')
parser.add_argument('mode', type=str, choices=['chat', 'completion', 'all'], default='all', location='args', required=False)
parser.add_argument('mode', type=str, choices=['chat', 'workflow', 'agent-chat', 'channel', 'all'], default='all', location='args', required=False)
parser.add_argument('name', type=str, location='args', required=False)
args = parser.parse_args()
filters = [
App.tenant_id == current_user.current_tenant_id,
App.is_universal == False
]
# get app list
app_service = AppService()
app_pagination = app_service.get_paginate_apps(current_user.current_tenant_id, args)
if args['mode'] == 'completion':
filters.append(App.mode == 'completion')
elif args['mode'] == 'chat':
filters.append(App.mode == 'chat')
else:
pass
if 'name' in args and args['name']:
filters.append(App.name.ilike(f'%{args["name"]}%'))
app_models = db.paginate(
db.select(App).where(*filters).order_by(App.created_at.desc()),
page=args['page'],
per_page=args['limit'],
error_out=False
)
return app_models
return app_pagination
@setup_required
@login_required
@@ -86,147 +55,49 @@ class AppListApi(Resource):
"""Create app"""
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, location='json')
parser.add_argument('mode', type=str, choices=['completion', 'chat', 'assistant'], location='json')
parser.add_argument('description', type=str, location='json')
parser.add_argument('mode', type=str, choices=ALLOW_CREATE_APP_MODES, location='json')
parser.add_argument('icon', type=str, location='json')
parser.add_argument('icon_background', type=str, location='json')
parser.add_argument('model_config', type=dict, location='json')
args = parser.parse_args()
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
raise Forbidden()
try:
provider_manager = ProviderManager()
default_model_entity = provider_manager.get_default_model(
tenant_id=current_user.current_tenant_id,
model_type=ModelType.LLM
)
except (ProviderTokenNotInitError, LLMBadRequestError):
default_model_entity = None
except Exception as e:
logging.exception(e)
default_model_entity = None
if 'mode' not in args or args['mode'] is None:
raise BadRequest("mode is required")
if args['model_config'] is not None:
# validate config
model_config_dict = args['model_config']
# Get provider configurations
provider_manager = ProviderManager()
provider_configurations = provider_manager.get_configurations(current_user.current_tenant_id)
# get available models from provider_configurations
available_models = provider_configurations.get_models(
model_type=ModelType.LLM,
only_active=True
)
# check if model is available
available_models_names = [f'{model.provider.provider}.{model.model}' for model in available_models]
provider_model = f"{model_config_dict['model']['provider']}.{model_config_dict['model']['name']}"
if provider_model not in available_models_names:
if not default_model_entity:
raise ProviderNotInitializeError(
"No Default System Reasoning Model available. Please configure "
"in the Settings -> Model Provider.")
else:
model_config_dict["model"]["provider"] = default_model_entity.provider.provider
model_config_dict["model"]["name"] = default_model_entity.model
model_configuration = AppModelConfigService.validate_configuration(
tenant_id=current_user.current_tenant_id,
account=current_user,
config=model_config_dict,
app_mode=args['mode']
)
app = App(
enable_site=True,
enable_api=True,
is_demo=False,
api_rpm=0,
api_rph=0,
status='normal'
)
app_model_config = AppModelConfig()
app_model_config = app_model_config.from_model_config_dict(model_configuration)
else:
if 'mode' not in args or args['mode'] is None:
abort(400, message="mode is required")
model_config_template = model_templates[args['mode'] + '_default']
app = App(**model_config_template['app'])
app_model_config = AppModelConfig(**model_config_template['model_config'])
# get model provider
model_manager = ModelManager()
try:
model_instance = model_manager.get_default_model_instance(
tenant_id=current_user.current_tenant_id,
model_type=ModelType.LLM
)
except ProviderTokenNotInitError:
model_instance = None
if model_instance:
model_dict = app_model_config.model_dict
model_dict['provider'] = model_instance.provider
model_dict['name'] = model_instance.model
app_model_config.model = json.dumps(model_dict)
app.name = args['name']
app.mode = args['mode']
app.icon = args['icon']
app.icon_background = args['icon_background']
app.tenant_id = current_user.current_tenant_id
db.session.add(app)
db.session.flush()
app_model_config.app_id = app.id
db.session.add(app_model_config)
db.session.flush()
app.app_model_config_id = app_model_config.id
account = current_user
site = Site(
app_id=app.id,
title=app.name,
default_language=account.interface_language,
customize_token_strategy='not_allow',
code=Site.generate_code(16)
)
db.session.add(site)
db.session.commit()
app_was_created.send(app)
app_service = AppService()
app = app_service.create_app(current_user.current_tenant_id, args, current_user)
return app, 201
class AppTemplateApi(Resource):
class AppImportApi(Resource):
@setup_required
@login_required
@account_initialization_required
@marshal_with(template_list_fields)
def get(self):
"""Get app demo templates"""
account = current_user
interface_language = account.interface_language
@marshal_with(app_detail_fields_with_site)
@cloud_edition_billing_resource_check('apps')
def post(self):
"""Import app"""
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
raise Forbidden()
templates = demo_model_templates.get(interface_language)
if not templates:
templates = demo_model_templates.get(languages[0])
parser = reqparse.RequestParser()
parser.add_argument('data', type=str, required=True, nullable=False, location='json')
parser.add_argument('name', type=str, location='json')
parser.add_argument('description', type=str, location='json')
parser.add_argument('icon', type=str, location='json')
parser.add_argument('icon_background', type=str, location='json')
args = parser.parse_args()
return {'data': templates}
app_service = AppService()
app = app_service.import_app(current_user.current_tenant_id, args['data'], args, current_user)
return app, 201
class AppApi(Resource):
@@ -234,213 +105,198 @@ class AppApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_detail_fields_with_site)
def get(self, app_id):
def get(self, app_model):
"""Get app detail"""
app_id = str(app_id)
app: App = _get_app(app_id, current_user.current_tenant_id)
# get original app model config
model_config: AppModelConfig = app.app_model_config
agent_mode = model_config.agent_mode_dict
# decrypt agent tool parameters if it's secret-input
for tool in agent_mode.get('tools') or []:
if not isinstance(tool, dict) or len(tool.keys()) <= 3:
continue
agent_tool_entity = AgentToolEntity(**tool)
# get tool
try:
tool_runtime = ToolManager.get_agent_tool_runtime(
tenant_id=current_user.current_tenant_id,
agent_tool=agent_tool_entity,
agent_callback=None
)
manager = ToolParameterConfigurationManager(
tenant_id=current_user.current_tenant_id,
tool_runtime=tool_runtime,
provider_name=agent_tool_entity.provider_id,
provider_type=agent_tool_entity.provider_type,
)
if app_model.mode == AppMode.AGENT_CHAT.value or app_model.is_agent:
model_config: AppModelConfig = app_model.app_model_config
agent_mode = model_config.agent_mode_dict
# decrypt agent tool parameters if it's secret-input
for tool in agent_mode.get('tools') or []:
if not isinstance(tool, dict) or len(tool.keys()) <= 3:
continue
agent_tool_entity = AgentToolEntity(**tool)
# get tool
try:
tool_runtime = ToolManager.get_agent_tool_runtime(
tenant_id=current_user.current_tenant_id,
agent_tool=agent_tool_entity,
)
manager = ToolParameterConfigurationManager(
tenant_id=current_user.current_tenant_id,
tool_runtime=tool_runtime,
provider_name=agent_tool_entity.provider_id,
provider_type=agent_tool_entity.provider_type,
)
# get decrypted parameters
if agent_tool_entity.tool_parameters:
parameters = manager.decrypt_tool_parameters(agent_tool_entity.tool_parameters or {})
masked_parameter = manager.mask_tool_parameters(parameters or {})
else:
masked_parameter = {}
# get decrypted parameters
if agent_tool_entity.tool_parameters:
parameters = manager.decrypt_tool_parameters(agent_tool_entity.tool_parameters or {})
masked_parameter = manager.mask_tool_parameters(parameters or {})
else:
masked_parameter = {}
# override tool parameters
tool['tool_parameters'] = masked_parameter
except Exception as e:
pass
# override tool parameters
tool['tool_parameters'] = masked_parameter
except Exception as e:
pass
# override agent mode
model_config.agent_mode = json.dumps(agent_mode)
# override agent mode
model_config.agent_mode = json.dumps(agent_mode)
db.session.commit()
return app
return app_model
@setup_required
@login_required
@account_initialization_required
def delete(self, app_id):
"""Delete app"""
app_id = str(app_id)
@get_app_model
@marshal_with(app_detail_fields_with_site)
def put(self, app_model):
"""Update app"""
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, nullable=False, location='json')
parser.add_argument('description', type=str, location='json')
parser.add_argument('icon', type=str, location='json')
parser.add_argument('icon_background', type=str, location='json')
args = parser.parse_args()
app_service = AppService()
app_model = app_service.update_app(app_model, args)
return app_model
@setup_required
@login_required
@account_initialization_required
@get_app_model
def delete(self, app_model):
"""Delete app"""
if not current_user.is_admin_or_owner:
raise Forbidden()
app = _get_app(app_id, current_user.current_tenant_id)
db.session.delete(app)
db.session.commit()
# todo delete related data??
# model_config, site, api_token, conversation, message, message_feedback, message_annotation
app_was_deleted.send(app)
app_service = AppService()
app_service.delete_app(app_model)
return {'result': 'success'}, 204
class AppCopyApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_detail_fields_with_site)
def post(self, app_model):
"""Copy app"""
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
raise Forbidden()
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, location='json')
parser.add_argument('description', type=str, location='json')
parser.add_argument('icon', type=str, location='json')
parser.add_argument('icon_background', type=str, location='json')
args = parser.parse_args()
app_service = AppService()
data = app_service.export_app(app_model)
app = app_service.import_app(current_user.current_tenant_id, data, args, current_user)
return app, 201
class AppExportApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
def get(self, app_model):
"""Export app"""
app_service = AppService()
return {
"data": app_service.export_app(app_model)
}
class AppNameApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_detail_fields)
def post(self, app_id):
app_id = str(app_id)
app = _get_app(app_id, current_user.current_tenant_id)
def post(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, location='json')
args = parser.parse_args()
app.name = args.get('name')
app.updated_at = datetime.utcnow()
db.session.commit()
return app
app_service = AppService()
app_model = app_service.update_app_name(app_model, args.get('name'))
return app_model
class AppIconApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_detail_fields)
def post(self, app_id):
app_id = str(app_id)
app = _get_app(app_id, current_user.current_tenant_id)
def post(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('icon', type=str, location='json')
parser.add_argument('icon_background', type=str, location='json')
args = parser.parse_args()
app.icon = args.get('icon')
app.icon_background = args.get('icon_background')
app.updated_at = datetime.utcnow()
db.session.commit()
app_service = AppService()
app_model = app_service.update_app_icon(app_model, args.get('icon'), args.get('icon_background'))
return app
return app_model
class AppSiteStatus(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_detail_fields)
def post(self, app_id):
def post(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('enable_site', type=bool, required=True, location='json')
args = parser.parse_args()
app_id = str(app_id)
app = db.session.query(App).filter(App.id == app_id, App.tenant_id == current_user.current_tenant_id).first()
if not app:
raise AppNotFoundError
if args.get('enable_site') == app.enable_site:
return app
app_service = AppService()
app_model = app_service.update_app_site_status(app_model, args.get('enable_site'))
app.enable_site = args.get('enable_site')
app.updated_at = datetime.utcnow()
db.session.commit()
return app
return app_model
class AppApiStatus(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_detail_fields)
def post(self, app_id):
def post(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('enable_api', type=bool, required=True, location='json')
args = parser.parse_args()
app_id = str(app_id)
app = _get_app(app_id, current_user.current_tenant_id)
app_service = AppService()
app_model = app_service.update_app_api_status(app_model, args.get('enable_api'))
if args.get('enable_api') == app.enable_api:
return app
app.enable_api = args.get('enable_api')
app.updated_at = datetime.utcnow()
db.session.commit()
return app
class AppCopy(Resource):
@staticmethod
def create_app_copy(app):
copy_app = App(
name=app.name + ' copy',
icon=app.icon,
icon_background=app.icon_background,
tenant_id=app.tenant_id,
mode=app.mode,
app_model_config_id=app.app_model_config_id,
enable_site=app.enable_site,
enable_api=app.enable_api,
api_rpm=app.api_rpm,
api_rph=app.api_rph
)
return copy_app
@staticmethod
def create_app_model_config_copy(app_config, copy_app_id):
copy_app_model_config = app_config.copy()
copy_app_model_config.app_id = copy_app_id
return copy_app_model_config
@setup_required
@login_required
@account_initialization_required
@marshal_with(app_detail_fields)
def post(self, app_id):
app_id = str(app_id)
app = _get_app(app_id, current_user.current_tenant_id)
copy_app = self.create_app_copy(app)
db.session.add(copy_app)
app_config = db.session.query(AppModelConfig). \
filter(AppModelConfig.app_id == app_id). \
one_or_none()
if app_config:
copy_app_model_config = self.create_app_model_config_copy(app_config, copy_app.id)
db.session.add(copy_app_model_config)
db.session.commit()
copy_app.app_model_config_id = copy_app_model_config.id
db.session.commit()
return copy_app, 201
return app_model
api.add_resource(AppListApi, '/apps')
api.add_resource(AppTemplateApi, '/app-templates')
api.add_resource(AppImportApi, '/apps/import')
api.add_resource(AppApi, '/apps/<uuid:app_id>')
api.add_resource(AppCopy, '/apps/<uuid:app_id>/copy')
api.add_resource(AppCopyApi, '/apps/<uuid:app_id>/copy')
api.add_resource(AppExportApi, '/apps/<uuid:app_id>/export')
api.add_resource(AppNameApi, '/apps/<uuid:app_id>/name')
api.add_resource(AppIconApi, '/apps/<uuid:app_id>/icon')
api.add_resource(AppSiteStatus, '/apps/<uuid:app_id>/site-enable')

View File

@@ -6,7 +6,6 @@ from werkzeug.exceptions import InternalServerError
import services
from controllers.console import api
from controllers.console.app import _get_app
from controllers.console.app.error import (
AppUnavailableError,
AudioTooLargeError,
@@ -18,11 +17,13 @@ from controllers.console.app.error import (
ProviderQuotaExceededError,
UnsupportedAudioTypeError,
)
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.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from libs.login import login_required
from models.model import AppMode
from services.audio_service import AudioService
from services.errors.audio import (
AudioTooLargeServiceError,
@@ -36,15 +37,13 @@ class ChatMessageAudioApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, app_id):
app_id = str(app_id)
app_model = _get_app(app_id, 'chat')
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
def post(self, app_model):
file = request.files['file']
try:
response = AudioService.transcript_asr(
tenant_id=app_model.tenant_id,
app_model=app_model,
file=file,
end_user=None,
)
@@ -80,15 +79,13 @@ class ChatMessageTextApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, app_id):
app_id = str(app_id)
app_model = _get_app(app_id, None)
@get_app_model
def post(self, app_model):
try:
response = AudioService.transcript_tts(
tenant_id=app_model.tenant_id,
app_model=app_model,
text=request.form['text'],
voice=request.form['voice'] if request.form['voice'] else app_model.app_model_config.text_to_speech_dict.get('voice'),
voice=request.form.get('voice'),
streaming=False
)
@@ -120,9 +117,11 @@ class ChatMessageTextApi(Resource):
class TextModesApi(Resource):
def get(self, app_id: str):
app_model = _get_app(str(app_id))
@setup_required
@login_required
@account_initialization_required
@get_app_model
def get(self, app_model):
try:
parser = reqparse.RequestParser()
parser.add_argument('language', type=str, required=True, location='args')

View File

@@ -1,16 +1,11 @@
import json
import logging
from collections.abc import Generator
from typing import Union
import flask_login
from flask import Response, stream_with_context
from flask_restful import Resource, reqparse
from werkzeug.exceptions import InternalServerError, NotFound
import services
from controllers.console import api
from controllers.console.app import _get_app
from controllers.console.app.error import (
AppUnavailableError,
CompletionRequestError,
@@ -19,15 +14,18 @@ from controllers.console.app.error import (
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
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.application_queue_manager import ApplicationQueueManager
from core.entities.application_entities import InvokeFrom
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from libs import helper
from libs.helper import uuid_value
from libs.login import login_required
from services.completion_service import CompletionService
from models.model import AppMode
from services.app_generate_service import AppGenerateService
# define completion message api for user
@@ -36,12 +34,8 @@ class CompletionMessageApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, app_id):
app_id = str(app_id)
# get app info
app_model = _get_app(app_id, 'completion')
@get_app_model(mode=AppMode.COMPLETION)
def post(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, location='json')
parser.add_argument('query', type=str, location='json', default='')
@@ -57,16 +51,15 @@ class CompletionMessageApi(Resource):
account = flask_login.current_user
try:
response = CompletionService.completion(
response = AppGenerateService.generate(
app_model=app_model,
user=account,
args=args,
invoke_from=InvokeFrom.DEBUGGER,
streaming=streaming,
is_model_config_override=True
streaming=streaming
)
return compact_response(response)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
@@ -93,15 +86,11 @@ class CompletionMessageStopApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, app_id, task_id):
app_id = str(app_id)
# get app info
_get_app(app_id, 'completion')
@get_app_model(mode=AppMode.COMPLETION)
def post(self, app_model, task_id):
account = flask_login.current_user
ApplicationQueueManager.set_stop_flag(task_id, InvokeFrom.DEBUGGER, account.id)
AppQueueManager.set_stop_flag(task_id, InvokeFrom.DEBUGGER, account.id)
return {'result': 'success'}, 200
@@ -110,12 +99,8 @@ class ChatMessageApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, app_id):
app_id = str(app_id)
# get app info
app_model = _get_app(app_id, 'chat')
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT])
def post(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, location='json')
parser.add_argument('query', type=str, required=True, location='json')
@@ -132,16 +117,15 @@ class ChatMessageApi(Resource):
account = flask_login.current_user
try:
response = CompletionService.completion(
response = AppGenerateService.generate(
app_model=app_model,
user=account,
args=args,
invoke_from=InvokeFrom.DEBUGGER,
streaming=streaming,
is_model_config_override=True
streaming=streaming
)
return compact_response(response)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
@@ -164,30 +148,15 @@ class ChatMessageApi(Resource):
raise InternalServerError()
def compact_response(response: Union[dict, Generator]) -> Response:
if isinstance(response, dict):
return Response(response=json.dumps(response), status=200, mimetype='application/json')
else:
def generate() -> Generator:
yield from response
return Response(stream_with_context(generate()), status=200,
mimetype='text/event-stream')
class ChatMessageStopApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, app_id, task_id):
app_id = str(app_id)
# get app info
_get_app(app_id, 'chat')
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
def post(self, app_model, task_id):
account = flask_login.current_user
ApplicationQueueManager.set_stop_flag(task_id, InvokeFrom.DEBUGGER, account.id)
AppQueueManager.set_stop_flag(task_id, InvokeFrom.DEBUGGER, account.id)
return {'result': 'success'}, 200

View File

@@ -9,7 +9,7 @@ from sqlalchemy.orm import joinedload
from werkzeug.exceptions import NotFound
from controllers.console import api
from controllers.console.app import _get_app
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 extensions.ext_database import db
@@ -21,7 +21,7 @@ from fields.conversation_fields import (
)
from libs.helper import datetime_string
from libs.login import login_required
from models.model import Conversation, Message, MessageAnnotation
from models.model import AppMode, Conversation, Message, MessageAnnotation
class CompletionConversationApi(Resource):
@@ -29,10 +29,9 @@ class CompletionConversationApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=AppMode.COMPLETION)
@marshal_with(conversation_pagination_fields)
def get(self, app_id):
app_id = str(app_id)
def get(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('keyword', type=str, location='args')
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
@@ -43,10 +42,7 @@ class CompletionConversationApi(Resource):
parser.add_argument('limit', type=int_range(1, 100), default=20, location='args')
args = parser.parse_args()
# get app info
app = _get_app(app_id, 'completion')
query = db.select(Conversation).where(Conversation.app_id == app.id, Conversation.mode == 'completion')
query = db.select(Conversation).where(Conversation.app_id == app_model.id, Conversation.mode == 'completion')
if args['keyword']:
query = query.join(
@@ -106,24 +102,22 @@ class CompletionConversationDetailApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=AppMode.COMPLETION)
@marshal_with(conversation_message_detail_fields)
def get(self, app_id, conversation_id):
app_id = str(app_id)
def get(self, app_model, conversation_id):
conversation_id = str(conversation_id)
return _get_conversation(app_id, conversation_id, 'completion')
return _get_conversation(app_model, conversation_id)
@setup_required
@login_required
@account_initialization_required
def delete(self, app_id, conversation_id):
app_id = str(app_id)
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
def delete(self, app_model, conversation_id):
conversation_id = str(conversation_id)
app = _get_app(app_id, 'chat')
conversation = db.session.query(Conversation) \
.filter(Conversation.id == conversation_id, Conversation.app_id == app.id).first()
.filter(Conversation.id == conversation_id, Conversation.app_id == app_model.id).first()
if not conversation:
raise NotFound("Conversation Not Exists.")
@@ -139,10 +133,9 @@ class ChatConversationApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
@marshal_with(conversation_with_summary_pagination_fields)
def get(self, app_id):
app_id = str(app_id)
def get(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('keyword', type=str, location='args')
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
@@ -154,10 +147,7 @@ class ChatConversationApi(Resource):
parser.add_argument('limit', type=int_range(1, 100), required=False, default=20, location='args')
args = parser.parse_args()
# get app info
app = _get_app(app_id, 'chat')
query = db.select(Conversation).where(Conversation.app_id == app.id, Conversation.mode == 'chat')
query = db.select(Conversation).where(Conversation.app_id == app_model.id)
if args['keyword']:
query = query.join(
@@ -211,6 +201,9 @@ class ChatConversationApi(Resource):
.having(func.count(Message.id) >= args['message_count_gte'])
)
if app_model.mode == AppMode.ADVANCED_CHAT.value:
query = query.where(Conversation.override_model_configs.is_(None))
query = query.order_by(Conversation.created_at.desc())
conversations = db.paginate(
@@ -228,25 +221,22 @@ class ChatConversationDetailApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
@marshal_with(conversation_detail_fields)
def get(self, app_id, conversation_id):
app_id = str(app_id)
def get(self, app_model, conversation_id):
conversation_id = str(conversation_id)
return _get_conversation(app_id, conversation_id, 'chat')
return _get_conversation(app_model, conversation_id)
@setup_required
@login_required
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
@account_initialization_required
def delete(self, app_id, conversation_id):
app_id = str(app_id)
def delete(self, app_model, conversation_id):
conversation_id = str(conversation_id)
# get app info
app = _get_app(app_id, 'chat')
conversation = db.session.query(Conversation) \
.filter(Conversation.id == conversation_id, Conversation.app_id == app.id).first()
.filter(Conversation.id == conversation_id, Conversation.app_id == app_model.id).first()
if not conversation:
raise NotFound("Conversation Not Exists.")
@@ -263,12 +253,9 @@ api.add_resource(ChatConversationApi, '/apps/<uuid:app_id>/chat-conversations')
api.add_resource(ChatConversationDetailApi, '/apps/<uuid:app_id>/chat-conversations/<uuid:conversation_id>')
def _get_conversation(app_id, conversation_id, mode):
# get app info
app = _get_app(app_id, mode)
def _get_conversation(app_model, conversation_id):
conversation = db.session.query(Conversation) \
.filter(Conversation.id == conversation_id, Conversation.app_id == app.id).first()
.filter(Conversation.id == conversation_id, Conversation.app_id == app_model.id).first()
if not conversation:
raise NotFound("Conversation Not Exists.")

View File

@@ -85,3 +85,9 @@ class TooManyFilesError(BaseHTTPException):
error_code = 'too_many_files'
description = "Only one file is allowed."
code = 400
class DraftWorkflowNotExist(BaseHTTPException):
error_code = 'draft_workflow_not_exist'
description = "Draft workflow need to be initialized."
code = 400

View File

@@ -11,7 +11,7 @@ from controllers.console.app.error import (
from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.generator.llm_generator import LLMGenerator
from core.llm_generator.llm_generator import LLMGenerator
from core.model_runtime.errors.invoke import InvokeError
from libs.login import login_required

View File

@@ -1,26 +1,22 @@
import json
import logging
from collections.abc import Generator
from typing import Union
from flask import Response, stream_with_context
from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse
from flask_restful.inputs import int_range
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
from controllers.console import api
from controllers.console.app import _get_app
from controllers.console.app.error import (
AppMoreLikeThisDisabledError,
CompletionRequestError,
ProviderModelCurrentlyNotSupportError,
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
from controllers.console.app.wraps import get_app_model
from controllers.console.explore.error import AppSuggestedQuestionsAfterAnswerDisabledError
from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check
from core.entities.application_entities import InvokeFrom
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from extensions.ext_database import db
@@ -28,12 +24,10 @@ from fields.conversation_fields import annotation_fields, message_detail_fields
from libs.helper import uuid_value
from libs.infinite_scroll_pagination import InfiniteScrollPagination
from libs.login import login_required
from models.model import Conversation, Message, MessageAnnotation, MessageFeedback
from models.model import AppMode, Conversation, Message, MessageAnnotation, MessageFeedback
from services.annotation_service import AppAnnotationService
from services.completion_service import CompletionService
from services.errors.app import MoreLikeThisDisabledError
from services.errors.conversation import ConversationNotExistsError
from services.errors.message import MessageNotExistsError
from services.errors.message import MessageNotExistsError, SuggestedQuestionsAfterAnswerDisabledError
from services.message_service import MessageService
@@ -46,14 +40,10 @@ class ChatMessageListApi(Resource):
@setup_required
@login_required
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
@account_initialization_required
@marshal_with(message_infinite_scroll_pagination_fields)
def get(self, app_id):
app_id = str(app_id)
# get app info
app = _get_app(app_id, 'chat')
def get(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('conversation_id', required=True, type=uuid_value, location='args')
parser.add_argument('first_id', type=uuid_value, location='args')
@@ -62,7 +52,7 @@ class ChatMessageListApi(Resource):
conversation = db.session.query(Conversation).filter(
Conversation.id == args['conversation_id'],
Conversation.app_id == app.id
Conversation.app_id == app_model.id
).first()
if not conversation:
@@ -110,12 +100,8 @@ class MessageFeedbackApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, app_id):
app_id = str(app_id)
# get app info
app = _get_app(app_id)
@get_app_model
def post(self, app_model):
parser = reqparse.RequestParser()
parser.add_argument('message_id', required=True, type=uuid_value, location='json')
parser.add_argument('rating', type=str, choices=['like', 'dislike', None], location='json')
@@ -125,7 +111,7 @@ class MessageFeedbackApi(Resource):
message = db.session.query(Message).filter(
Message.id == message_id,
Message.app_id == app.id
Message.app_id == app_model.id
).first()
if not message:
@@ -141,7 +127,7 @@ class MessageFeedbackApi(Resource):
raise ValueError('rating cannot be None when feedback not exists')
else:
feedback = MessageFeedback(
app_id=app.id,
app_id=app_model.id,
conversation_id=message.conversation_id,
message_id=message.id,
rating=args['rating'],
@@ -160,21 +146,20 @@ class MessageAnnotationApi(Resource):
@login_required
@account_initialization_required
@cloud_edition_billing_resource_check('annotation')
@get_app_model
@marshal_with(annotation_fields)
def post(self, app_id):
def post(self, app_model):
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
raise Forbidden()
app_id = str(app_id)
parser = reqparse.RequestParser()
parser.add_argument('message_id', required=False, type=uuid_value, location='json')
parser.add_argument('question', required=True, type=str, location='json')
parser.add_argument('answer', required=True, type=str, location='json')
parser.add_argument('annotation_reply', required=False, type=dict, location='json')
args = parser.parse_args()
annotation = AppAnnotationService.up_insert_app_annotation_from_message(args, app_id)
annotation = AppAnnotationService.up_insert_app_annotation_from_message(args, app_model.id)
return annotation
@@ -183,93 +168,29 @@ class MessageAnnotationCountApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id):
app_id = str(app_id)
# get app info
app = _get_app(app_id)
@get_app_model
def get(self, app_model):
count = db.session.query(MessageAnnotation).filter(
MessageAnnotation.app_id == app.id
MessageAnnotation.app_id == app_model.id
).count()
return {'count': count}
class MessageMoreLikeThisApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id, message_id):
app_id = str(app_id)
message_id = str(message_id)
parser = reqparse.RequestParser()
parser.add_argument('response_mode', type=str, required=True, choices=['blocking', 'streaming'],
location='args')
args = parser.parse_args()
streaming = args['response_mode'] == 'streaming'
# get app info
app_model = _get_app(app_id, 'completion')
try:
response = CompletionService.generate_more_like_this(
app_model=app_model,
user=current_user,
message_id=message_id,
invoke_from=InvokeFrom.DEBUGGER,
streaming=streaming
)
return compact_response(response)
except MessageNotExistsError:
raise NotFound("Message Not Exists.")
except MoreLikeThisDisabledError:
raise AppMoreLikeThisDisabledError()
except ProviderTokenNotInitError as ex:
raise ProviderNotInitializeError(ex.description)
except QuotaExceededError:
raise ProviderQuotaExceededError()
except ModelCurrentlyNotSupportError:
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception as e:
logging.exception("internal server error.")
raise InternalServerError()
def compact_response(response: Union[dict, Generator]) -> Response:
if isinstance(response, dict):
return Response(response=json.dumps(response), status=200, mimetype='application/json')
else:
def generate() -> Generator:
yield from response
return Response(stream_with_context(generate()), status=200,
mimetype='text/event-stream')
class MessageSuggestedQuestionApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id, message_id):
app_id = str(app_id)
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
def get(self, app_model, message_id):
message_id = str(message_id)
# get app info
app_model = _get_app(app_id, 'chat')
try:
questions = MessageService.get_suggested_questions_after_answer(
app_model=app_model,
message_id=message_id,
user=current_user,
check_enabled=False
invoke_from=InvokeFrom.DEBUGGER
)
except MessageNotExistsError:
raise NotFound("Message not found")
@@ -283,6 +204,8 @@ class MessageSuggestedQuestionApi(Resource):
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except SuggestedQuestionsAfterAnswerDisabledError:
raise AppSuggestedQuestionsAfterAnswerDisabledError()
except Exception:
logging.exception("internal server error.")
raise InternalServerError()
@@ -294,14 +217,11 @@ class MessageApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(message_detail_fields)
def get(self, app_id, message_id):
app_id = str(app_id)
def get(self, app_model, message_id):
message_id = str(message_id)
# get app info
app_model = _get_app(app_id)
message = db.session.query(Message).filter(
Message.id == message_id,
Message.app_id == app_model.id
@@ -313,7 +233,6 @@ class MessageApi(Resource):
return message
api.add_resource(MessageMoreLikeThisApi, '/apps/<uuid:app_id>/completion-messages/<uuid:message_id>/more-like-this')
api.add_resource(MessageSuggestedQuestionApi, '/apps/<uuid:app_id>/chat-messages/<uuid:message_id>/suggested-questions')
api.add_resource(ChatMessageListApi, '/apps/<uuid:app_id>/chat-messages', endpoint='console_chat_messages')
api.add_resource(MessageFeedbackApi, '/apps/<uuid:app_id>/feedbacks')

View File

@@ -5,16 +5,16 @@ from flask_login import current_user
from flask_restful import Resource
from controllers.console import api
from controllers.console.app import _get_app
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.entities.application_entities import AgentToolEntity
from core.agent.entities import AgentToolEntity
from core.tools.tool_manager import ToolManager
from core.tools.utils.configuration import ToolParameterConfigurationManager
from events.app_event import app_model_config_was_updated
from extensions.ext_database import db
from libs.login import login_required
from models.model import AppModelConfig
from models.model import AppMode, AppModelConfig
from services.app_model_config_service import AppModelConfigService
@@ -23,118 +23,113 @@ class ModelConfigResource(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, app_id):
@get_app_model(mode=[AppMode.AGENT_CHAT, AppMode.CHAT, AppMode.COMPLETION])
def post(self, app_model):
"""Modify app model config"""
app_id = str(app_id)
app = _get_app(app_id)
# validate config
model_configuration = AppModelConfigService.validate_configuration(
tenant_id=current_user.current_tenant_id,
account=current_user,
config=request.json,
app_mode=app.mode
app_mode=AppMode.value_of(app_model.mode)
)
new_app_model_config = AppModelConfig(
app_id=app.id,
app_id=app_model.id,
)
new_app_model_config = new_app_model_config.from_model_config_dict(model_configuration)
# get original app model config
original_app_model_config: AppModelConfig = db.session.query(AppModelConfig).filter(
AppModelConfig.id == app.app_model_config_id
).first()
agent_mode = original_app_model_config.agent_mode_dict
# decrypt agent tool parameters if it's secret-input
parameter_map = {}
masked_parameter_map = {}
tool_map = {}
for tool in agent_mode.get('tools') or []:
if not isinstance(tool, dict) or len(tool.keys()) <= 3:
continue
agent_tool_entity = AgentToolEntity(**tool)
# get tool
try:
tool_runtime = ToolManager.get_agent_tool_runtime(
tenant_id=current_user.current_tenant_id,
agent_tool=agent_tool_entity,
agent_callback=None
)
if app_model.mode == AppMode.AGENT_CHAT.value or app_model.is_agent:
# get original app model config
original_app_model_config: AppModelConfig = db.session.query(AppModelConfig).filter(
AppModelConfig.id == app_model.app_model_config_id
).first()
agent_mode = original_app_model_config.agent_mode_dict
# decrypt agent tool parameters if it's secret-input
parameter_map = {}
masked_parameter_map = {}
tool_map = {}
for tool in agent_mode.get('tools') or []:
if not isinstance(tool, dict) or len(tool.keys()) <= 3:
continue
agent_tool_entity = AgentToolEntity(**tool)
# get tool
try:
tool_runtime = ToolManager.get_agent_tool_runtime(
tenant_id=current_user.current_tenant_id,
agent_tool=agent_tool_entity,
)
manager = ToolParameterConfigurationManager(
tenant_id=current_user.current_tenant_id,
tool_runtime=tool_runtime,
provider_name=agent_tool_entity.provider_id,
provider_type=agent_tool_entity.provider_type,
)
except Exception as e:
continue
# get decrypted parameters
if agent_tool_entity.tool_parameters:
parameters = manager.decrypt_tool_parameters(agent_tool_entity.tool_parameters or {})
masked_parameter = manager.mask_tool_parameters(parameters or {})
else:
parameters = {}
masked_parameter = {}
key = f'{agent_tool_entity.provider_id}.{agent_tool_entity.provider_type}.{agent_tool_entity.tool_name}'
masked_parameter_map[key] = masked_parameter
parameter_map[key] = parameters
tool_map[key] = tool_runtime
# encrypt agent tool parameters if it's secret-input
agent_mode = new_app_model_config.agent_mode_dict
for tool in agent_mode.get('tools') or []:
agent_tool_entity = AgentToolEntity(**tool)
# get tool
key = f'{agent_tool_entity.provider_id}.{agent_tool_entity.provider_type}.{agent_tool_entity.tool_name}'
if key in tool_map:
tool_runtime = tool_map[key]
else:
try:
tool_runtime = ToolManager.get_agent_tool_runtime(
tenant_id=current_user.current_tenant_id,
agent_tool=agent_tool_entity,
)
except Exception as e:
continue
manager = ToolParameterConfigurationManager(
tenant_id=current_user.current_tenant_id,
tool_runtime=tool_runtime,
provider_name=agent_tool_entity.provider_id,
provider_type=agent_tool_entity.provider_type,
)
except Exception as e:
continue
manager.delete_tool_parameters_cache()
# get decrypted parameters
if agent_tool_entity.tool_parameters:
parameters = manager.decrypt_tool_parameters(agent_tool_entity.tool_parameters or {})
masked_parameter = manager.mask_tool_parameters(parameters or {})
else:
parameters = {}
masked_parameter = {}
# override parameters if it equals to masked parameters
if agent_tool_entity.tool_parameters:
if key not in masked_parameter_map:
continue
key = f'{agent_tool_entity.provider_id}.{agent_tool_entity.provider_type}.{agent_tool_entity.tool_name}'
masked_parameter_map[key] = masked_parameter
parameter_map[key] = parameters
tool_map[key] = tool_runtime
if agent_tool_entity.tool_parameters == masked_parameter_map[key]:
agent_tool_entity.tool_parameters = parameter_map[key]
# encrypt agent tool parameters if it's secret-input
agent_mode = new_app_model_config.agent_mode_dict
for tool in agent_mode.get('tools') or []:
agent_tool_entity = AgentToolEntity(**tool)
# get tool
key = f'{agent_tool_entity.provider_id}.{agent_tool_entity.provider_type}.{agent_tool_entity.tool_name}'
if key in tool_map:
tool_runtime = tool_map[key]
else:
try:
tool_runtime = ToolManager.get_agent_tool_runtime(
tenant_id=current_user.current_tenant_id,
agent_tool=agent_tool_entity,
agent_callback=None
)
except Exception as e:
continue
manager = ToolParameterConfigurationManager(
tenant_id=current_user.current_tenant_id,
tool_runtime=tool_runtime,
provider_name=agent_tool_entity.provider_id,
provider_type=agent_tool_entity.provider_type,
)
manager.delete_tool_parameters_cache()
# encrypt parameters
if agent_tool_entity.tool_parameters:
tool['tool_parameters'] = manager.encrypt_tool_parameters(agent_tool_entity.tool_parameters or {})
# override parameters if it equals to masked parameters
if agent_tool_entity.tool_parameters:
if key not in masked_parameter_map:
continue
if agent_tool_entity.tool_parameters == masked_parameter_map[key]:
agent_tool_entity.tool_parameters = parameter_map[key]
# encrypt parameters
if agent_tool_entity.tool_parameters:
tool['tool_parameters'] = manager.encrypt_tool_parameters(agent_tool_entity.tool_parameters or {})
# update app model config
new_app_model_config.agent_mode = json.dumps(agent_mode)
# update app model config
new_app_model_config.agent_mode = json.dumps(agent_mode)
db.session.add(new_app_model_config)
db.session.flush()
app.app_model_config_id = new_app_model_config.id
app_model.app_model_config_id = new_app_model_config.id
db.session.commit()
app_model_config_was_updated.send(
app,
app_model,
app_model_config=new_app_model_config
)

View File

@@ -4,7 +4,7 @@ from werkzeug.exceptions import Forbidden, NotFound
from constants.languages import supported_language
from controllers.console import api
from controllers.console.app import _get_app
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 extensions.ext_database import db
@@ -34,13 +34,11 @@ class AppSite(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_site_fields)
def post(self, app_id):
def post(self, app_model):
args = parse_app_site_args()
app_id = str(app_id)
app_model = _get_app(app_id)
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
raise Forbidden()
@@ -82,11 +80,9 @@ class AppSiteAccessTokenReset(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_site_fields)
def post(self, app_id):
app_id = str(app_id)
app_model = _get_app(app_id)
def post(self, app_model):
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
raise Forbidden()

View File

@@ -7,12 +7,13 @@ from flask_login import current_user
from flask_restful import Resource, reqparse
from controllers.console import api
from controllers.console.app import _get_app
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 extensions.ext_database import db
from libs.helper import datetime_string
from libs.login import login_required
from models.model import AppMode
class DailyConversationStatistic(Resource):
@@ -20,10 +21,9 @@ class DailyConversationStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id):
@get_app_model
def get(self, app_model):
account = current_user
app_id = str(app_id)
app_model = _get_app(app_id)
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
@@ -81,10 +81,9 @@ class DailyTerminalsStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id):
@get_app_model
def get(self, app_model):
account = current_user
app_id = str(app_id)
app_model = _get_app(app_id)
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
@@ -141,10 +140,9 @@ class DailyTokenCostStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id):
@get_app_model
def get(self, app_model):
account = current_user
app_id = str(app_id)
app_model = _get_app(app_id)
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
@@ -205,10 +203,9 @@ class AverageSessionInteractionStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id):
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
def get(self, app_model):
account = current_user
app_id = str(app_id)
app_model = _get_app(app_id, 'chat')
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
@@ -271,10 +268,9 @@ class UserSatisfactionRateStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id):
@get_app_model
def get(self, app_model):
account = current_user
app_id = str(app_id)
app_model = _get_app(app_id)
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
@@ -334,10 +330,9 @@ class AverageResponseTimeStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id):
@get_app_model(mode=AppMode.COMPLETION)
def get(self, app_model):
account = current_user
app_id = str(app_id)
app_model = _get_app(app_id, 'completion')
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
@@ -396,10 +391,9 @@ class TokensPerSecondStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, app_id):
@get_app_model
def get(self, app_model):
account = current_user
app_id = str(app_id)
app_model = _get_app(app_id)
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')

View File

@@ -0,0 +1,324 @@
import json
import logging
from flask import abort, request
from flask_restful import Resource, marshal_with, reqparse
from werkzeug.exceptions import InternalServerError, NotFound
import services
from controllers.console import api
from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist
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.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from fields.workflow_fields import workflow_fields
from fields.workflow_run_fields import workflow_run_node_execution_fields
from libs import helper
from libs.helper import TimestampField, uuid_value
from libs.login import current_user, login_required
from models.model import App, AppMode
from services.app_generate_service import AppGenerateService
from services.workflow_service import WorkflowService
logger = logging.getLogger(__name__)
class DraftWorkflowApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
@marshal_with(workflow_fields)
def get(self, app_model: App):
"""
Get draft workflow
"""
# fetch draft workflow by app_model
workflow_service = WorkflowService()
workflow = workflow_service.get_draft_workflow(app_model=app_model)
if not workflow:
raise DraftWorkflowNotExist()
# return workflow, if not found, return None (initiate graph by frontend)
return workflow
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def post(self, app_model: App):
"""
Sync draft workflow
"""
content_type = request.headers.get('Content-Type')
if 'application/json' in content_type:
parser = reqparse.RequestParser()
parser.add_argument('graph', type=dict, required=True, nullable=False, location='json')
parser.add_argument('features', type=dict, required=True, nullable=False, location='json')
args = parser.parse_args()
elif 'text/plain' in content_type:
try:
data = json.loads(request.data.decode('utf-8'))
if 'graph' not in data or 'features' not in data:
raise ValueError('graph or features not found in data')
if not isinstance(data.get('graph'), dict) or not isinstance(data.get('features'), dict):
raise ValueError('graph or features is not a dict')
args = {
'graph': data.get('graph'),
'features': data.get('features')
}
except json.JSONDecodeError:
return {'message': 'Invalid JSON data'}, 400
else:
abort(415)
workflow_service = WorkflowService()
workflow = workflow_service.sync_draft_workflow(
app_model=app_model,
graph=args.get('graph'),
features=args.get('features'),
account=current_user
)
return {
"result": "success",
"updated_at": TimestampField().format(workflow.updated_at or workflow.created_at)
}
class AdvancedChatDraftWorkflowRunApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
def post(self, app_model: App):
"""
Run draft workflow
"""
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, location='json')
parser.add_argument('query', type=str, required=True, location='json', default='')
parser.add_argument('files', type=list, location='json')
parser.add_argument('conversation_id', type=uuid_value, location='json')
args = parser.parse_args()
try:
response = AppGenerateService.generate(
app_model=app_model,
user=current_user,
args=args,
invoke_from=InvokeFrom.DEBUGGER,
streaming=True
)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
raise ConversationCompletedError()
except ValueError as e:
raise e
except Exception as e:
logging.exception("internal server error.")
raise InternalServerError()
class DraftWorkflowRunApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.WORKFLOW])
def post(self, app_model: App):
"""
Run draft workflow
"""
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, nullable=False, location='json')
parser.add_argument('files', type=list, required=False, location='json')
args = parser.parse_args()
try:
response = AppGenerateService.generate(
app_model=app_model,
user=current_user,
args=args,
invoke_from=InvokeFrom.DEBUGGER,
streaming=True
)
return helper.compact_generate_response(response)
except ValueError as e:
raise e
except Exception as e:
logging.exception("internal server error.")
raise InternalServerError()
class WorkflowTaskStopApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def post(self, app_model: App, task_id: str):
"""
Stop workflow task
"""
AppQueueManager.set_stop_flag(task_id, InvokeFrom.DEBUGGER, current_user.id)
return {
"result": "success"
}
class DraftWorkflowNodeRunApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
@marshal_with(workflow_run_node_execution_fields)
def post(self, app_model: App, node_id: str):
"""
Run draft workflow node
"""
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, nullable=False, location='json')
args = parser.parse_args()
workflow_service = WorkflowService()
workflow_node_execution = workflow_service.run_draft_workflow_node(
app_model=app_model,
node_id=node_id,
user_inputs=args.get('inputs'),
account=current_user
)
return workflow_node_execution
class PublishedWorkflowApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
@marshal_with(workflow_fields)
def get(self, app_model: App):
"""
Get published workflow
"""
# fetch published workflow by app_model
workflow_service = WorkflowService()
workflow = workflow_service.get_published_workflow(app_model=app_model)
# return workflow, if not found, return None
return workflow
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def post(self, app_model: App):
"""
Publish workflow
"""
workflow_service = WorkflowService()
workflow = workflow_service.publish_workflow(app_model=app_model, account=current_user)
return {
"result": "success",
"created_at": TimestampField().format(workflow.created_at)
}
class DefaultBlockConfigsApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def get(self, app_model: App):
"""
Get default block config
"""
# Get default block configs
workflow_service = WorkflowService()
return workflow_service.get_default_block_configs()
class DefaultBlockConfigApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def get(self, app_model: App, block_type: str):
"""
Get default block config
"""
parser = reqparse.RequestParser()
parser.add_argument('q', type=str, location='args')
args = parser.parse_args()
filters = None
if args.get('q'):
try:
filters = json.loads(args.get('q'))
except json.JSONDecodeError:
raise ValueError('Invalid filters')
# Get default block configs
workflow_service = WorkflowService()
return workflow_service.get_default_block_config(
node_type=block_type,
filters=filters
)
class ConvertToWorkflowApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.CHAT, AppMode.COMPLETION])
def post(self, app_model: App):
"""
Convert basic mode of chatbot app to workflow mode
Convert expert mode of chatbot app to workflow mode
Convert Completion App to Workflow App
"""
if request.data:
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=False, nullable=True, location='json')
parser.add_argument('icon', type=str, required=False, nullable=True, location='json')
parser.add_argument('icon_background', type=str, required=False, nullable=True, location='json')
args = parser.parse_args()
else:
args = {}
# convert to workflow mode
workflow_service = WorkflowService()
new_app_model = workflow_service.convert_to_workflow(
app_model=app_model,
account=current_user,
args=args
)
# return app id
return {
'new_app_id': new_app_model.id,
}
api.add_resource(DraftWorkflowApi, '/apps/<uuid:app_id>/workflows/draft')
api.add_resource(AdvancedChatDraftWorkflowRunApi, '/apps/<uuid:app_id>/advanced-chat/workflows/draft/run')
api.add_resource(DraftWorkflowRunApi, '/apps/<uuid:app_id>/workflows/draft/run')
api.add_resource(WorkflowTaskStopApi, '/apps/<uuid:app_id>/workflow-runs/tasks/<string:task_id>/stop')
api.add_resource(DraftWorkflowNodeRunApi, '/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/run')
api.add_resource(PublishedWorkflowApi, '/apps/<uuid:app_id>/workflows/publish')
api.add_resource(DefaultBlockConfigsApi, '/apps/<uuid:app_id>/workflows/default-workflow-block-configs')
api.add_resource(DefaultBlockConfigApi, '/apps/<uuid:app_id>/workflows/default-workflow-block-configs'
'/<string:block_type>')
api.add_resource(ConvertToWorkflowApi, '/apps/<uuid:app_id>/convert-to-workflow')

View File

@@ -0,0 +1,41 @@
from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range
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 fields.workflow_app_log_fields import workflow_app_log_pagination_fields
from libs.login import login_required
from models.model import App, AppMode
from services.workflow_app_service import WorkflowAppService
class WorkflowAppLogApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.WORKFLOW])
@marshal_with(workflow_app_log_pagination_fields)
def get(self, app_model: App):
"""
Get workflow app logs
"""
parser = reqparse.RequestParser()
parser.add_argument('keyword', type=str, location='args')
parser.add_argument('status', type=str, choices=['succeeded', 'failed', 'stopped'], location='args')
parser.add_argument('page', type=int_range(1, 99999), default=1, location='args')
parser.add_argument('limit', type=int_range(1, 100), default=20, location='args')
args = parser.parse_args()
# get paginate workflow app logs
workflow_app_service = WorkflowAppService()
workflow_app_log_pagination = workflow_app_service.get_paginate_workflow_app_logs(
app_model=app_model,
args=args
)
return workflow_app_log_pagination
api.add_resource(WorkflowAppLogApi, '/apps/<uuid:app_id>/workflow-app-logs')

View File

@@ -0,0 +1,109 @@
from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range
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 fields.workflow_run_fields import (
advanced_chat_workflow_run_pagination_fields,
workflow_run_detail_fields,
workflow_run_node_execution_list_fields,
workflow_run_pagination_fields,
)
from libs.helper import uuid_value
from libs.login import login_required
from models.model import App, AppMode
from services.workflow_run_service import WorkflowRunService
class AdvancedChatAppWorkflowRunListApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
@marshal_with(advanced_chat_workflow_run_pagination_fields)
def get(self, app_model: App):
"""
Get advanced chat app workflow run list
"""
parser = reqparse.RequestParser()
parser.add_argument('last_id', type=uuid_value, location='args')
parser.add_argument('limit', type=int_range(1, 100), required=False, default=20, location='args')
args = parser.parse_args()
workflow_run_service = WorkflowRunService()
result = workflow_run_service.get_paginate_advanced_chat_workflow_runs(
app_model=app_model,
args=args
)
return result
class WorkflowRunListApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
@marshal_with(workflow_run_pagination_fields)
def get(self, app_model: App):
"""
Get workflow run list
"""
parser = reqparse.RequestParser()
parser.add_argument('last_id', type=uuid_value, location='args')
parser.add_argument('limit', type=int_range(1, 100), required=False, default=20, location='args')
args = parser.parse_args()
workflow_run_service = WorkflowRunService()
result = workflow_run_service.get_paginate_workflow_runs(
app_model=app_model,
args=args
)
return result
class WorkflowRunDetailApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
@marshal_with(workflow_run_detail_fields)
def get(self, app_model: App, run_id):
"""
Get workflow run detail
"""
run_id = str(run_id)
workflow_run_service = WorkflowRunService()
workflow_run = workflow_run_service.get_workflow_run(app_model=app_model, run_id=run_id)
return workflow_run
class WorkflowRunNodeExecutionListApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
@marshal_with(workflow_run_node_execution_list_fields)
def get(self, app_model: App, run_id):
"""
Get workflow run node execution list
"""
run_id = str(run_id)
workflow_run_service = WorkflowRunService()
node_executions = workflow_run_service.get_workflow_run_node_executions(app_model=app_model, run_id=run_id)
return {
'data': node_executions
}
api.add_resource(AdvancedChatAppWorkflowRunListApi, '/apps/<uuid:app_id>/advanced-chat/workflow-runs')
api.add_resource(WorkflowRunListApi, '/apps/<uuid:app_id>/workflow-runs')
api.add_resource(WorkflowRunDetailApi, '/apps/<uuid:app_id>/workflow-runs/<uuid:run_id>')
api.add_resource(WorkflowRunNodeExecutionListApi, '/apps/<uuid:app_id>/workflow-runs/<uuid:run_id>/node-executions')

View File

@@ -0,0 +1,278 @@
from datetime import datetime
from decimal import Decimal
import pytz
from flask import jsonify
from flask_login import current_user
from flask_restful import Resource, reqparse
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 extensions.ext_database import db
from libs.helper import datetime_string
from libs.login import login_required
from models.model import AppMode
from models.workflow import WorkflowRunTriggeredFrom
class WorkflowDailyRunsStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
def get(self, app_model):
account = current_user
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
parser.add_argument('end', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
args = parser.parse_args()
sql_query = '''
SELECT date(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date, count(id) AS runs
FROM workflow_runs
WHERE app_id = :app_id
AND triggered_from = :triggered_from
'''
arg_dict = {'tz': account.timezone, 'app_id': app_model.id, 'triggered_from': WorkflowRunTriggeredFrom.APP_RUN.value}
timezone = pytz.timezone(account.timezone)
utc_timezone = pytz.utc
if args['start']:
start_datetime = datetime.strptime(args['start'], '%Y-%m-%d %H:%M')
start_datetime = start_datetime.replace(second=0)
start_datetime_timezone = timezone.localize(start_datetime)
start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
sql_query += ' and created_at >= :start'
arg_dict['start'] = start_datetime_utc
if args['end']:
end_datetime = datetime.strptime(args['end'], '%Y-%m-%d %H:%M')
end_datetime = end_datetime.replace(second=0)
end_datetime_timezone = timezone.localize(end_datetime)
end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
sql_query += ' and created_at < :end'
arg_dict['end'] = end_datetime_utc
sql_query += ' GROUP BY date order by date'
response_data = []
with db.engine.begin() as conn:
rs = conn.execute(db.text(sql_query), arg_dict)
for i in rs:
response_data.append({
'date': str(i.date),
'runs': i.runs
})
return jsonify({
'data': response_data
})
class WorkflowDailyTerminalsStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
def get(self, app_model):
account = current_user
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
parser.add_argument('end', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
args = parser.parse_args()
sql_query = '''
SELECT date(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date, count(distinct workflow_runs.created_by) AS terminal_count
FROM workflow_runs
WHERE app_id = :app_id
AND triggered_from = :triggered_from
'''
arg_dict = {'tz': account.timezone, 'app_id': app_model.id, 'triggered_from': WorkflowRunTriggeredFrom.APP_RUN.value}
timezone = pytz.timezone(account.timezone)
utc_timezone = pytz.utc
if args['start']:
start_datetime = datetime.strptime(args['start'], '%Y-%m-%d %H:%M')
start_datetime = start_datetime.replace(second=0)
start_datetime_timezone = timezone.localize(start_datetime)
start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
sql_query += ' and created_at >= :start'
arg_dict['start'] = start_datetime_utc
if args['end']:
end_datetime = datetime.strptime(args['end'], '%Y-%m-%d %H:%M')
end_datetime = end_datetime.replace(second=0)
end_datetime_timezone = timezone.localize(end_datetime)
end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
sql_query += ' and created_at < :end'
arg_dict['end'] = end_datetime_utc
sql_query += ' GROUP BY date order by date'
response_data = []
with db.engine.begin() as conn:
rs = conn.execute(db.text(sql_query), arg_dict)
for i in rs:
response_data.append({
'date': str(i.date),
'terminal_count': i.terminal_count
})
return jsonify({
'data': response_data
})
class WorkflowDailyTokenCostStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
def get(self, app_model):
account = current_user
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
parser.add_argument('end', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
args = parser.parse_args()
sql_query = '''
SELECT
date(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
SUM(workflow_runs.total_tokens) as token_count
FROM workflow_runs
WHERE app_id = :app_id
AND triggered_from = :triggered_from
'''
arg_dict = {'tz': account.timezone, 'app_id': app_model.id, 'triggered_from': WorkflowRunTriggeredFrom.APP_RUN.value}
timezone = pytz.timezone(account.timezone)
utc_timezone = pytz.utc
if args['start']:
start_datetime = datetime.strptime(args['start'], '%Y-%m-%d %H:%M')
start_datetime = start_datetime.replace(second=0)
start_datetime_timezone = timezone.localize(start_datetime)
start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
sql_query += ' and created_at >= :start'
arg_dict['start'] = start_datetime_utc
if args['end']:
end_datetime = datetime.strptime(args['end'], '%Y-%m-%d %H:%M')
end_datetime = end_datetime.replace(second=0)
end_datetime_timezone = timezone.localize(end_datetime)
end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
sql_query += ' and created_at < :end'
arg_dict['end'] = end_datetime_utc
sql_query += ' GROUP BY date order by date'
response_data = []
with db.engine.begin() as conn:
rs = conn.execute(db.text(sql_query), arg_dict)
for i in rs:
response_data.append({
'date': str(i.date),
'token_count': i.token_count,
})
return jsonify({
'data': response_data
})
class WorkflowAverageAppInteractionStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.WORKFLOW])
def get(self, app_model):
account = current_user
parser = reqparse.RequestParser()
parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
parser.add_argument('end', type=datetime_string('%Y-%m-%d %H:%M'), location='args')
args = parser.parse_args()
sql_query = """
SELECT
AVG(sub.interactions) as interactions,
sub.date
FROM
(SELECT
date(DATE_TRUNC('day', c.created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
c.created_by,
COUNT(c.id) AS interactions
FROM workflow_runs c
WHERE c.app_id = :app_id
AND c.triggered_from = :triggered_from
{{start}}
{{end}}
GROUP BY date, c.created_by) sub
GROUP BY sub.created_by, sub.date
"""
arg_dict = {'tz': account.timezone, 'app_id': app_model.id, 'triggered_from': WorkflowRunTriggeredFrom.APP_RUN.value}
timezone = pytz.timezone(account.timezone)
utc_timezone = pytz.utc
if args['start']:
start_datetime = datetime.strptime(args['start'], '%Y-%m-%d %H:%M')
start_datetime = start_datetime.replace(second=0)
start_datetime_timezone = timezone.localize(start_datetime)
start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
sql_query = sql_query.replace('{{start}}', ' AND c.created_at >= :start')
arg_dict['start'] = start_datetime_utc
else:
sql_query = sql_query.replace('{{start}}', '')
if args['end']:
end_datetime = datetime.strptime(args['end'], '%Y-%m-%d %H:%M')
end_datetime = end_datetime.replace(second=0)
end_datetime_timezone = timezone.localize(end_datetime)
end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
sql_query = sql_query.replace('{{end}}', ' and c.created_at < :end')
arg_dict['end'] = end_datetime_utc
else:
sql_query = sql_query.replace('{{end}}', '')
response_data = []
with db.engine.begin() as conn:
rs = conn.execute(db.text(sql_query), arg_dict)
for i in rs:
response_data.append({
'date': str(i.date),
'interactions': float(i.interactions.quantize(Decimal('0.01')))
})
return jsonify({
'data': response_data
})
api.add_resource(WorkflowDailyRunsStatistic, '/apps/<uuid:app_id>/workflow/statistics/daily-conversations')
api.add_resource(WorkflowDailyTerminalsStatistic, '/apps/<uuid:app_id>/workflow/statistics/daily-terminals')
api.add_resource(WorkflowDailyTokenCostStatistic, '/apps/<uuid:app_id>/workflow/statistics/token-costs')
api.add_resource(WorkflowAverageAppInteractionStatistic, '/apps/<uuid:app_id>/workflow/statistics/average-app-interactions')

View File

@@ -0,0 +1,55 @@
from collections.abc import Callable
from functools import wraps
from typing import Optional, Union
from controllers.console.app.error import AppNotFoundError
from extensions.ext_database import db
from libs.login import current_user
from models.model import App, AppMode
def get_app_model(view: Optional[Callable] = None, *,
mode: Union[AppMode, list[AppMode]] = None):
def decorator(view_func):
@wraps(view_func)
def decorated_view(*args, **kwargs):
if not kwargs.get('app_id'):
raise ValueError('missing app_id in path parameters')
app_id = kwargs.get('app_id')
app_id = str(app_id)
del kwargs['app_id']
app_model = db.session.query(App).filter(
App.id == app_id,
App.tenant_id == current_user.current_tenant_id,
App.status == 'normal'
).first()
if not app_model:
raise AppNotFoundError()
app_mode = AppMode.value_of(app_model.mode)
if app_mode == AppMode.CHANNEL:
raise AppNotFoundError()
if mode is not None:
if isinstance(mode, list):
modes = mode
else:
modes = [mode]
if app_mode not in modes:
mode_values = {m.value for m in modes}
raise AppNotFoundError(f"App mode is not in the supported list: {mode_values}")
kwargs['app_model'] = app_model
return view_func(*args, **kwargs)
return decorated_view
if view is None:
return decorator
else:
return decorator(view)

View File

@@ -19,7 +19,6 @@ from controllers.console.app.error import (
from controllers.console.explore.wraps import InstalledAppResource
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from models.model import AppModelConfig
from services.audio_service import AudioService
from services.errors.audio import (
AudioTooLargeServiceError,
@@ -32,16 +31,12 @@ from services.errors.audio import (
class ChatAudioApi(InstalledAppResource):
def post(self, installed_app):
app_model = installed_app.app
app_model_config: AppModelConfig = app_model.app_model_config
if not app_model_config.speech_to_text_dict['enabled']:
raise AppUnavailableError()
file = request.files['file']
try:
response = AudioService.transcript_asr(
tenant_id=app_model.tenant_id,
app_model=app_model,
file=file,
end_user=None
)
@@ -76,16 +71,12 @@ class ChatAudioApi(InstalledAppResource):
class ChatTextApi(InstalledAppResource):
def post(self, installed_app):
app_model = installed_app.app
app_model_config: AppModelConfig = app_model.app_model_config
if not app_model_config.text_to_speech_dict['enabled']:
raise AppUnavailableError()
try:
response = AudioService.transcript_tts(
tenant_id=app_model.tenant_id,
app_model=app_model,
text=request.form['text'],
voice=request.form['voice'] if request.form['voice'] else app_model.app_model_config.text_to_speech_dict.get('voice'),
voice=request.form.get('voice'),
streaming=False
)
return {'data': response.data.decode('latin1')}

View File

@@ -1,10 +1,6 @@
import json
import logging
from collections.abc import Generator
from datetime import datetime
from typing import Union
from flask import Response, stream_with_context
from flask_login import current_user
from flask_restful import reqparse
from werkzeug.exceptions import InternalServerError, NotFound
@@ -21,13 +17,15 @@ from controllers.console.app.error import (
)
from controllers.console.explore.error import NotChatAppError, NotCompletionAppError
from controllers.console.explore.wraps import InstalledAppResource
from core.application_queue_manager import ApplicationQueueManager
from core.entities.application_entities import InvokeFrom
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from extensions.ext_database import db
from libs import helper
from libs.helper import uuid_value
from services.completion_service import CompletionService
from models.model import AppMode
from services.app_generate_service import AppGenerateService
# define completion api for user
@@ -53,7 +51,7 @@ class CompletionApi(InstalledAppResource):
db.session.commit()
try:
response = CompletionService.completion(
response = AppGenerateService.generate(
app_model=app_model,
user=current_user,
args=args,
@@ -61,7 +59,7 @@ class CompletionApi(InstalledAppResource):
streaming=streaming
)
return compact_response(response)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
@@ -90,7 +88,7 @@ class CompletionStopApi(InstalledAppResource):
if app_model.mode != 'completion':
raise NotCompletionAppError()
ApplicationQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
return {'result': 'success'}, 200
@@ -98,34 +96,33 @@ class CompletionStopApi(InstalledAppResource):
class ChatApi(InstalledAppResource):
def post(self, installed_app):
app_model = installed_app.app
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, location='json')
parser.add_argument('query', type=str, required=True, location='json')
parser.add_argument('files', type=list, required=False, location='json')
parser.add_argument('response_mode', type=str, choices=['blocking', 'streaming'], location='json')
parser.add_argument('conversation_id', type=uuid_value, location='json')
parser.add_argument('retriever_from', type=str, required=False, default='explore_app', location='json')
args = parser.parse_args()
streaming = args['response_mode'] == 'streaming'
args['auto_generate_name'] = False
installed_app.last_used_at = datetime.utcnow()
db.session.commit()
try:
response = CompletionService.completion(
response = AppGenerateService.generate(
app_model=app_model,
user=current_user,
args=args,
invoke_from=InvokeFrom.EXPLORE,
streaming=streaming
streaming=True
)
return compact_response(response)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
@@ -151,25 +148,15 @@ class ChatApi(InstalledAppResource):
class ChatStopApi(InstalledAppResource):
def post(self, installed_app, task_id):
app_model = installed_app.app
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
ApplicationQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
return {'result': 'success'}, 200
def compact_response(response: Union[dict, Generator]) -> Response:
if isinstance(response, dict):
return Response(response=json.dumps(response), status=200, mimetype='application/json')
else:
def generate() -> Generator:
yield from response
return Response(stream_with_context(generate()), status=200,
mimetype='text/event-stream')
api.add_resource(CompletionApi, '/installed-apps/<uuid:installed_app_id>/completion-messages', endpoint='installed_app_completion')
api.add_resource(CompletionStopApi, '/installed-apps/<uuid:installed_app_id>/completion-messages/<string:task_id>/stop', endpoint='installed_app_stop_completion')
api.add_resource(ChatApi, '/installed-apps/<uuid:installed_app_id>/chat-messages', endpoint='installed_app_chat_completion')

View File

@@ -8,6 +8,7 @@ from controllers.console.explore.error import NotChatAppError
from controllers.console.explore.wraps import InstalledAppResource
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import AppMode
from services.conversation_service import ConversationService
from services.errors.conversation import ConversationNotExistsError, LastConversationNotExistsError
from services.web_conversation_service import WebConversationService
@@ -18,7 +19,8 @@ class ConversationListApi(InstalledAppResource):
@marshal_with(conversation_infinite_scroll_pagination_fields)
def get(self, installed_app):
app_model = installed_app.app
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
@@ -47,7 +49,8 @@ class ConversationListApi(InstalledAppResource):
class ConversationApi(InstalledAppResource):
def delete(self, installed_app, c_id):
app_model = installed_app.app
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)
@@ -65,7 +68,8 @@ class ConversationRenameApi(InstalledAppResource):
@marshal_with(simple_conversation_fields)
def post(self, installed_app, c_id):
app_model = installed_app.app
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)
@@ -91,7 +95,8 @@ class ConversationPinApi(InstalledAppResource):
def patch(self, installed_app, c_id):
app_model = installed_app.app
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)
@@ -107,7 +112,8 @@ class ConversationPinApi(InstalledAppResource):
class ConversationUnPinApi(InstalledAppResource):
def patch(self, installed_app, c_id):
app_model = installed_app.app
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)

View File

@@ -9,7 +9,13 @@ class NotCompletionAppError(BaseHTTPException):
class NotChatAppError(BaseHTTPException):
error_code = 'not_chat_app'
description = "Not Chat App"
description = "App mode is invalid."
code = 400
class NotWorkflowAppError(BaseHTTPException):
error_code = 'not_workflow_app'
description = "Only support workflow app."
code = 400

View File

@@ -34,8 +34,7 @@ class InstalledAppsListApi(Resource):
'is_pinned': installed_app.is_pinned,
'last_used_at': installed_app.last_used_at,
'editable': current_user.role in ["owner", "admin"],
'uninstallable': current_tenant_id == installed_app.app_owner_tenant_id,
'is_agent': installed_app.is_agent
'uninstallable': current_tenant_id == installed_app.app_owner_tenant_id
}
for installed_app in installed_apps
]

View File

@@ -1,9 +1,5 @@
import json
import logging
from collections.abc import Generator
from typing import Union
from flask import Response, stream_with_context
from flask_login import current_user
from flask_restful import marshal_with, reqparse
from flask_restful.inputs import int_range
@@ -24,12 +20,14 @@ from controllers.console.explore.error import (
NotCompletionAppError,
)
from controllers.console.explore.wraps import InstalledAppResource
from core.entities.application_entities import InvokeFrom
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from fields.message_fields import message_infinite_scroll_pagination_fields
from libs import helper
from libs.helper import uuid_value
from services.completion_service import CompletionService
from models.model import AppMode
from services.app_generate_service import AppGenerateService
from services.errors.app import MoreLikeThisDisabledError
from services.errors.conversation import ConversationNotExistsError
from services.errors.message import MessageNotExistsError, SuggestedQuestionsAfterAnswerDisabledError
@@ -41,7 +39,8 @@ class MessageListApi(InstalledAppResource):
def get(self, installed_app):
app_model = installed_app.app
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
@@ -91,14 +90,14 @@ class MessageMoreLikeThisApi(InstalledAppResource):
streaming = args['response_mode'] == 'streaming'
try:
response = CompletionService.generate_more_like_this(
response = AppGenerateService.generate_more_like_this(
app_model=app_model,
user=current_user,
message_id=message_id,
invoke_from=InvokeFrom.EXPLORE,
streaming=streaming
)
return compact_response(response)
return helper.compact_generate_response(response)
except MessageNotExistsError:
raise NotFound("Message Not Exists.")
except MoreLikeThisDisabledError:
@@ -118,22 +117,12 @@ class MessageMoreLikeThisApi(InstalledAppResource):
raise InternalServerError()
def compact_response(response: Union[dict, Generator]) -> Response:
if isinstance(response, dict):
return Response(response=json.dumps(response), status=200, mimetype='application/json')
else:
def generate() -> Generator:
yield from response
return Response(stream_with_context(generate()), status=200,
mimetype='text/event-stream')
class MessageSuggestedQuestionApi(InstalledAppResource):
def get(self, installed_app, message_id):
app_model = installed_app.app
if app_model.mode != 'chat':
raise NotCompletionAppError()
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
message_id = str(message_id)
@@ -141,7 +130,8 @@ class MessageSuggestedQuestionApi(InstalledAppResource):
questions = MessageService.get_suggested_questions_after_answer(
app_model=app_model,
user=current_user,
message_id=message_id
message_id=message_id,
invoke_from=InvokeFrom.EXPLORE
)
except MessageNotExistsError:
raise NotFound("Message not found")

View File

@@ -1,13 +1,12 @@
import json
from flask import current_app
from flask_restful import fields, marshal_with
from controllers.console import api
from controllers.console.app.error import AppUnavailableError
from controllers.console.explore.wraps import InstalledAppResource
from extensions.ext_database import db
from models.model import AppModelConfig, InstalledApp
from models.tools import ApiToolProvider
from models.model import AppMode, InstalledApp
from services.app_service import AppService
class AppParameterApi(InstalledAppResource):
@@ -45,61 +44,52 @@ class AppParameterApi(InstalledAppResource):
def get(self, installed_app: InstalledApp):
"""Retrieve app parameters."""
app_model = installed_app.app
app_model_config = app_model.app_model_config
if app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value]:
workflow = app_model.workflow
if workflow is None:
raise AppUnavailableError()
features_dict = workflow.features_dict
user_input_form = workflow.user_input_form(to_old_structure=True)
else:
app_model_config = app_model.app_model_config
features_dict = app_model_config.to_dict()
user_input_form = features_dict.get('user_input_form', [])
return {
'opening_statement': app_model_config.opening_statement,
'suggested_questions': app_model_config.suggested_questions_list,
'suggested_questions_after_answer': app_model_config.suggested_questions_after_answer_dict,
'speech_to_text': app_model_config.speech_to_text_dict,
'text_to_speech': app_model_config.text_to_speech_dict,
'retriever_resource': app_model_config.retriever_resource_dict,
'annotation_reply': app_model_config.annotation_reply_dict,
'more_like_this': app_model_config.more_like_this_dict,
'user_input_form': app_model_config.user_input_form_list,
'sensitive_word_avoidance': app_model_config.sensitive_word_avoidance_dict,
'file_upload': app_model_config.file_upload_dict,
'opening_statement': features_dict.get('opening_statement'),
'suggested_questions': features_dict.get('suggested_questions', []),
'suggested_questions_after_answer': features_dict.get('suggested_questions_after_answer',
{"enabled": False}),
'speech_to_text': features_dict.get('speech_to_text', {"enabled": False}),
'text_to_speech': features_dict.get('text_to_speech', {"enabled": False}),
'retriever_resource': features_dict.get('retriever_resource', {"enabled": False}),
'annotation_reply': features_dict.get('annotation_reply', {"enabled": False}),
'more_like_this': features_dict.get('more_like_this', {"enabled": False}),
'user_input_form': user_input_form,
'sensitive_word_avoidance': features_dict.get('sensitive_word_avoidance',
{"enabled": False, "type": "", "configs": []}),
'file_upload': features_dict.get('file_upload', {"image": {
"enabled": False,
"number_limits": 3,
"detail": "high",
"transfer_methods": ["remote_url", "local_file"]
}}),
'system_parameters': {
'image_file_size_limit': current_app.config.get('UPLOAD_IMAGE_FILE_SIZE_LIMIT')
}
}
class ExploreAppMetaApi(InstalledAppResource):
def get(self, installed_app: InstalledApp):
"""Get app meta"""
app_model_config: AppModelConfig = installed_app.app.app_model_config
app_model = installed_app.app
return AppService().get_app_meta(app_model)
agent_config = app_model_config.agent_mode_dict or {}
meta = {
'tool_icons': {}
}
# get all tools
tools = agent_config.get('tools', [])
url_prefix = (current_app.config.get("CONSOLE_API_URL")
+ "/console/api/workspaces/current/tool-provider/builtin/")
for tool in tools:
keys = list(tool.keys())
if len(keys) >= 4:
# current tool standard
provider_type = tool.get('provider_type')
provider_id = tool.get('provider_id')
tool_name = tool.get('tool_name')
if provider_type == 'builtin':
meta['tool_icons'][tool_name] = url_prefix + provider_id + '/icon'
elif provider_type == 'api':
try:
provider: ApiToolProvider = db.session.query(ApiToolProvider).filter(
ApiToolProvider.id == provider_id
)
meta['tool_icons'][tool_name] = json.loads(provider.icon)
except:
meta['tool_icons'][tool_name] = {
"background": "#252525",
"content": "\ud83d\ude01"
}
return meta
api.add_resource(AppParameterApi, '/installed-apps/<uuid:installed_app_id>/parameters', endpoint='installed_app_parameters')
api.add_resource(AppParameterApi, '/installed-apps/<uuid:installed_app_id>/parameters',
endpoint='installed_app_parameters')
api.add_resource(ExploreAppMetaApi, '/installed-apps/<uuid:installed_app_id>/meta', endpoint='installed_app_meta')

View File

@@ -1,15 +1,11 @@
from flask_login import current_user
from flask_restful import Resource, fields, marshal_with
from sqlalchemy import and_
from flask_restful import Resource, fields, marshal_with, reqparse
from constants.languages import languages
from controllers.console import api
from controllers.console.app.error import AppNotFoundError
from controllers.console.wraps import account_initialization_required
from extensions.ext_database import db
from libs.login import login_required
from models.model import App, InstalledApp, RecommendedApp
from services.account_service import TenantService
from services.recommended_app_service import RecommendedAppService
app_fields = {
'id': fields.String,
@@ -27,11 +23,7 @@ recommended_app_fields = {
'privacy_policy': fields.String,
'category': fields.String,
'position': fields.Integer,
'is_listed': fields.Boolean,
'install_count': fields.Integer,
'installed': fields.Boolean,
'editable': fields.Boolean,
'is_agent': fields.Boolean
'is_listed': fields.Boolean
}
recommended_app_list_fields = {
@@ -45,102 +37,27 @@ class RecommendedAppListApi(Resource):
@account_initialization_required
@marshal_with(recommended_app_list_fields)
def get(self):
language_prefix = current_user.interface_language if current_user.interface_language else languages[0]
# language args
parser = reqparse.RequestParser()
parser.add_argument('language', type=str, location='args')
args = parser.parse_args()
recommended_apps = db.session.query(RecommendedApp).filter(
RecommendedApp.is_listed == True,
RecommendedApp.language == language_prefix
).all()
if args.get('language') and args.get('language') in languages:
language_prefix = args.get('language')
elif current_user and current_user.interface_language:
language_prefix = current_user.interface_language
else:
language_prefix = languages[0]
if len(recommended_apps) == 0:
recommended_apps = db.session.query(RecommendedApp).filter(
RecommendedApp.is_listed == True,
RecommendedApp.language == languages[0]
).all()
categories = set()
current_user.role = TenantService.get_user_role(current_user, current_user.current_tenant)
recommended_apps_result = []
for recommended_app in recommended_apps:
installed = db.session.query(InstalledApp).filter(
and_(
InstalledApp.app_id == recommended_app.app_id,
InstalledApp.tenant_id == current_user.current_tenant_id
)
).first() is not None
app = recommended_app.app
if not app or not app.is_public:
continue
site = app.site
if not site:
continue
recommended_app_result = {
'id': recommended_app.id,
'app': app,
'app_id': recommended_app.app_id,
'description': site.description,
'copyright': site.copyright,
'privacy_policy': site.privacy_policy,
'category': recommended_app.category,
'position': recommended_app.position,
'is_listed': recommended_app.is_listed,
'install_count': recommended_app.install_count,
'installed': installed,
'editable': current_user.role in ['owner', 'admin'],
"is_agent": app.is_agent
}
recommended_apps_result.append(recommended_app_result)
categories.add(recommended_app.category) # add category to categories
return {'recommended_apps': recommended_apps_result, 'categories': list(categories)}
return RecommendedAppService.get_recommended_apps_and_categories(language_prefix)
class RecommendedAppApi(Resource):
model_config_fields = {
'opening_statement': fields.String,
'suggested_questions': fields.Raw(attribute='suggested_questions_list'),
'suggested_questions_after_answer': fields.Raw(attribute='suggested_questions_after_answer_dict'),
'more_like_this': fields.Raw(attribute='more_like_this_dict'),
'model': fields.Raw(attribute='model_dict'),
'user_input_form': fields.Raw(attribute='user_input_form_list'),
'pre_prompt': fields.String,
'agent_mode': fields.Raw(attribute='agent_mode_dict'),
}
app_simple_detail_fields = {
'id': fields.String,
'name': fields.String,
'icon': fields.String,
'icon_background': fields.String,
'mode': fields.String,
'app_model_config': fields.Nested(model_config_fields),
}
@login_required
@account_initialization_required
@marshal_with(app_simple_detail_fields)
def get(self, app_id):
app_id = str(app_id)
# is in public recommended list
recommended_app = db.session.query(RecommendedApp).filter(
RecommendedApp.is_listed == True,
RecommendedApp.app_id == app_id
).first()
if not recommended_app:
raise AppNotFoundError
# get app detail
app = db.session.query(App).filter(App.id == app_id).first()
if not app or not app.is_public:
raise AppNotFoundError
return app
return RecommendedAppService.get_recommend_app_detail(app_id)
api.add_resource(RecommendedAppListApi, '/explore/apps')

View File

@@ -0,0 +1,85 @@
import logging
from flask_restful import reqparse
from werkzeug.exceptions import InternalServerError
from controllers.console import api
from controllers.console.app.error import (
CompletionRequestError,
ProviderModelCurrentlyNotSupportError,
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
from controllers.console.explore.error import NotWorkflowAppError
from controllers.console.explore.wraps import InstalledAppResource
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from libs import helper
from libs.login import current_user
from models.model import AppMode, InstalledApp
from services.app_generate_service import AppGenerateService
logger = logging.getLogger(__name__)
class InstalledAppWorkflowRunApi(InstalledAppResource):
def post(self, installed_app: InstalledApp):
"""
Run workflow
"""
app_model = installed_app.app
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
raise NotWorkflowAppError()
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, nullable=False, location='json')
parser.add_argument('files', type=list, required=False, location='json')
args = parser.parse_args()
try:
response = AppGenerateService.generate(
app_model=app_model,
user=current_user,
args=args,
invoke_from=InvokeFrom.EXPLORE,
streaming=True
)
return helper.compact_generate_response(response)
except ProviderTokenNotInitError as ex:
raise ProviderNotInitializeError(ex.description)
except QuotaExceededError:
raise ProviderQuotaExceededError()
except ModelCurrentlyNotSupportError:
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception as e:
logging.exception("internal server error.")
raise InternalServerError()
class InstalledAppWorkflowTaskStopApi(InstalledAppResource):
def post(self, installed_app: InstalledApp, task_id: str):
"""
Stop workflow task
"""
app_model = installed_app.app
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
raise NotWorkflowAppError()
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
return {
"result": "success"
}
api.add_resource(InstalledAppWorkflowRunApi, '/installed-apps/<uuid:installed_app_id>/workflows/run')
api.add_resource(InstalledAppWorkflowTaskStopApi, '/installed-apps/<uuid:installed_app_id>/workflows/tasks/<string:task_id>/stop')

View File

@@ -0,0 +1,17 @@
from flask_restful import Resource
from controllers.console import api
class PingApi(Resource):
def get(self):
"""
For connection health check
"""
return {
"result": "pong"
}
api.add_resource(PingApi, '/ping')

View File

@@ -16,26 +16,13 @@ from controllers.console.workspace.error import (
)
from controllers.console.wraps import account_initialization_required
from extensions.ext_database import db
from fields.member_fields import account_fields
from libs.helper import TimestampField, timezone
from libs.login import login_required
from models.account import AccountIntegrate, InvitationCode
from services.account_service import AccountService
from services.errors.account import CurrentPasswordIncorrectError as ServiceCurrentPasswordIncorrectError
account_fields = {
'id': fields.String,
'name': fields.String,
'avatar': fields.String,
'email': fields.String,
'is_password_set': fields.Boolean,
'interface_language': fields.String,
'interface_theme': fields.String,
'timezone': fields.String,
'last_login_at': TimestampField,
'last_login_ip': fields.String,
'created_at': TimestampField
}
class AccountInitApi(Resource):

View File

@@ -1,33 +1,18 @@
from flask import current_app
from flask_login import current_user
from flask_restful import Resource, abort, fields, marshal_with, reqparse
from flask_restful import Resource, abort, marshal_with, reqparse
import services
from controllers.console import api
from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check
from extensions.ext_database import db
from libs.helper import TimestampField
from fields.member_fields import account_with_role_list_fields
from libs.login import login_required
from models.account import Account
from services.account_service import RegisterService, TenantService
from services.errors.account import AccountAlreadyInTenantError
account_fields = {
'id': fields.String,
'name': fields.String,
'avatar': fields.String,
'email': fields.String,
'last_login_at': TimestampField,
'created_at': TimestampField,
'role': fields.String,
'status': fields.String,
}
account_list_fields = {
'accounts': fields.List(fields.Nested(account_fields))
}
class MemberListApi(Resource):
"""List all members of current tenant."""
@@ -35,7 +20,7 @@ class MemberListApi(Resource):
@setup_required
@login_required
@account_initialization_required
@marshal_with(account_list_fields)
@marshal_with(account_with_role_list_fields)
def get(self):
members = TenantService.get_tenant_members(current_user.current_tenant)
return {'result': 'success', 'accounts': members}, 200

View File

@@ -8,6 +8,7 @@ from werkzeug.exceptions import Forbidden
from controllers.console import api
from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required
from core.model_runtime.utils.encoders import jsonable_encoder
from libs.login import login_required
from services.tools_manage_service import ToolManageService
@@ -30,11 +31,11 @@ class ToolBuiltinProviderListToolsApi(Resource):
user_id = current_user.id
tenant_id = current_user.current_tenant_id
return ToolManageService.list_builtin_tool_provider_tools(
return jsonable_encoder(ToolManageService.list_builtin_tool_provider_tools(
user_id,
tenant_id,
provider,
)
))
class ToolBuiltinProviderDeleteApi(Resource):
@setup_required
@@ -75,13 +76,27 @@ class ToolBuiltinProviderUpdateApi(Resource):
provider,
args['credentials'],
)
class ToolBuiltinProviderGetCredentialsApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, provider):
user_id = current_user.id
tenant_id = current_user.current_tenant_id
return ToolManageService.get_builtin_tool_provider_credentials(
user_id,
tenant_id,
provider,
)
class ToolBuiltinProviderIconApi(Resource):
@setup_required
def get(self, provider):
icon_bytes, minetype = ToolManageService.get_builtin_tool_provider_icon(provider)
icon_bytes, mimetype = ToolManageService.get_builtin_tool_provider_icon(provider)
icon_cache_max_age = int(current_app.config.get('TOOL_ICON_CACHE_MAX_AGE'))
return send_file(io.BytesIO(icon_bytes), mimetype=minetype, max_age=icon_cache_max_age)
return send_file(io.BytesIO(icon_bytes), mimetype=mimetype, max_age=icon_cache_max_age)
class ToolModelProviderIconApi(Resource):
@setup_required
@@ -102,11 +117,11 @@ class ToolModelProviderListToolsApi(Resource):
args = parser.parse_args()
return ToolManageService.list_model_tool_provider_tools(
return jsonable_encoder(ToolManageService.list_model_tool_provider_tools(
user_id,
tenant_id,
args['provider'],
)
))
class ToolApiProviderAddApi(Resource):
@setup_required
@@ -171,11 +186,11 @@ class ToolApiProviderListToolsApi(Resource):
args = parser.parse_args()
return ToolManageService.list_api_tool_provider_tools(
return jsonable_encoder(ToolManageService.list_api_tool_provider_tools(
user_id,
tenant_id,
args['provider'],
)
))
class ToolApiProviderUpdateApi(Resource):
@setup_required
@@ -302,10 +317,37 @@ class ToolApiProviderPreviousTestApi(Resource):
args['schema'],
)
class ToolBuiltinListApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self):
user_id = current_user.id
tenant_id = current_user.current_tenant_id
return jsonable_encoder([provider.to_dict() for provider in ToolManageService.list_builtin_tools(
user_id,
tenant_id,
)])
class ToolApiListApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self):
user_id = current_user.id
tenant_id = current_user.current_tenant_id
return jsonable_encoder([provider.to_dict() for provider in ToolManageService.list_api_tools(
user_id,
tenant_id,
)])
api.add_resource(ToolProviderListApi, '/workspaces/current/tool-providers')
api.add_resource(ToolBuiltinProviderListToolsApi, '/workspaces/current/tool-provider/builtin/<provider>/tools')
api.add_resource(ToolBuiltinProviderDeleteApi, '/workspaces/current/tool-provider/builtin/<provider>/delete')
api.add_resource(ToolBuiltinProviderUpdateApi, '/workspaces/current/tool-provider/builtin/<provider>/update')
api.add_resource(ToolBuiltinProviderGetCredentialsApi, '/workspaces/current/tool-provider/builtin/<provider>/credentials')
api.add_resource(ToolBuiltinProviderCredentialsSchemaApi, '/workspaces/current/tool-provider/builtin/<provider>/credentials_schema')
api.add_resource(ToolBuiltinProviderIconApi, '/workspaces/current/tool-provider/builtin/<provider>/icon')
api.add_resource(ToolModelProviderIconApi, '/workspaces/current/tool-provider/model/<provider>/icon')
@@ -313,8 +355,11 @@ api.add_resource(ToolModelProviderListToolsApi, '/workspaces/current/tool-provid
api.add_resource(ToolApiProviderAddApi, '/workspaces/current/tool-provider/api/add')
api.add_resource(ToolApiProviderGetRemoteSchemaApi, '/workspaces/current/tool-provider/api/remote')
api.add_resource(ToolApiProviderListToolsApi, '/workspaces/current/tool-provider/api/tools')
api.add_resource(ToolApiProviderUpdateApi, '/workspaces/current/tool-provider/api/update')
api.add_resource(ToolApiProviderUpdateApi, '/workspaces/current/tool-provider/api/update')
api.add_resource(ToolApiProviderDeleteApi, '/workspaces/current/tool-provider/api/delete')
api.add_resource(ToolApiProviderGetApi, '/workspaces/current/tool-provider/api/get')
api.add_resource(ToolApiProviderSchemaApi, '/workspaces/current/tool-provider/api/schema')
api.add_resource(ToolApiProviderPreviousTestApi, '/workspaces/current/tool-provider/api/test/pre')
api.add_resource(ToolBuiltinListApi, '/workspaces/current/tools/builtin')
api.add_resource(ToolApiListApi, '/workspaces/current/tools/api')

View File

@@ -27,7 +27,7 @@ class ToolFilePreviewApi(Resource):
raise Forbidden('Invalid request.')
try:
result = ToolFileManager.get_file_generator_by_message_file_id(
result = ToolFileManager.get_file_generator_by_tool_file_id(
file_id,
)

View File

@@ -7,5 +7,5 @@ api = ExternalApi(bp)
from . import index
from .app import app, audio, completion, conversation, file, message
from .app import app, audio, completion, conversation, file, message, workflow
from .dataset import dataset, document, segment

View File

@@ -4,10 +4,12 @@ from flask import current_app
from flask_restful import fields, marshal_with, Resource
from controllers.service_api import api
from controllers.service_api.app.error import AppUnavailableError
from controllers.service_api.wraps import validate_app_token
from extensions.ext_database import db
from models.model import App, AppModelConfig
from models.model import App, AppModelConfig, AppMode
from models.tools import ApiToolProvider
from services.app_service import AppService
class AppParameterApi(Resource):
@@ -46,62 +48,50 @@ class AppParameterApi(Resource):
@marshal_with(parameters_fields)
def get(self, app_model: App):
"""Retrieve app parameters."""
app_model_config = app_model.app_model_config
if app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value]:
workflow = app_model.workflow
if workflow is None:
raise AppUnavailableError()
features_dict = workflow.features_dict
user_input_form = workflow.user_input_form(to_old_structure=True)
else:
app_model_config = app_model.app_model_config
features_dict = app_model_config.to_dict()
user_input_form = features_dict.get('user_input_form', [])
return {
'opening_statement': app_model_config.opening_statement,
'suggested_questions': app_model_config.suggested_questions_list,
'suggested_questions_after_answer': app_model_config.suggested_questions_after_answer_dict,
'speech_to_text': app_model_config.speech_to_text_dict,
'text_to_speech': app_model_config.text_to_speech_dict,
'retriever_resource': app_model_config.retriever_resource_dict,
'annotation_reply': app_model_config.annotation_reply_dict,
'more_like_this': app_model_config.more_like_this_dict,
'user_input_form': app_model_config.user_input_form_list,
'sensitive_word_avoidance': app_model_config.sensitive_word_avoidance_dict,
'file_upload': app_model_config.file_upload_dict,
'opening_statement': features_dict.get('opening_statement'),
'suggested_questions': features_dict.get('suggested_questions', []),
'suggested_questions_after_answer': features_dict.get('suggested_questions_after_answer',
{"enabled": False}),
'speech_to_text': features_dict.get('speech_to_text', {"enabled": False}),
'text_to_speech': features_dict.get('text_to_speech', {"enabled": False}),
'retriever_resource': features_dict.get('retriever_resource', {"enabled": False}),
'annotation_reply': features_dict.get('annotation_reply', {"enabled": False}),
'more_like_this': features_dict.get('more_like_this', {"enabled": False}),
'user_input_form': user_input_form,
'sensitive_word_avoidance': features_dict.get('sensitive_word_avoidance',
{"enabled": False, "type": "", "configs": []}),
'file_upload': features_dict.get('file_upload', {"image": {
"enabled": False,
"number_limits": 3,
"detail": "high",
"transfer_methods": ["remote_url", "local_file"]
}}),
'system_parameters': {
'image_file_size_limit': current_app.config.get('UPLOAD_IMAGE_FILE_SIZE_LIMIT')
}
}
class AppMetaApi(Resource):
@validate_app_token
def get(self, app_model: App):
"""Get app meta"""
app_model_config: AppModelConfig = app_model.app_model_config
return AppService().get_app_meta(app_model)
agent_config = app_model_config.agent_mode_dict or {}
meta = {
'tool_icons': {}
}
# get all tools
tools = agent_config.get('tools', [])
url_prefix = (current_app.config.get("CONSOLE_API_URL")
+ "/console/api/workspaces/current/tool-provider/builtin/")
for tool in tools:
keys = list(tool.keys())
if len(keys) >= 4:
# current tool standard
provider_type = tool.get('provider_type')
provider_id = tool.get('provider_id')
tool_name = tool.get('tool_name')
if provider_type == 'builtin':
meta['tool_icons'][tool_name] = url_prefix + provider_id + '/icon'
elif provider_type == 'api':
try:
provider: ApiToolProvider = db.session.query(ApiToolProvider).filter(
ApiToolProvider.id == provider_id
)
meta['tool_icons'][tool_name] = json.loads(provider.icon)
except:
meta['tool_icons'][tool_name] = {
"background": "#252525",
"content": "\ud83d\ude01"
}
return meta
api.add_resource(AppParameterApi, '/parameters')
api.add_resource(AppMetaApi, '/meta')

View File

@@ -20,7 +20,7 @@ from controllers.service_api.app.error import (
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from models.model import App, AppModelConfig, EndUser
from models.model import App, EndUser
from services.audio_service import AudioService
from services.errors.audio import (
AudioTooLargeServiceError,
@@ -33,18 +33,13 @@ from services.errors.audio import (
class AudioApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.FORM))
def post(self, app_model: App, end_user: EndUser):
app_model_config: AppModelConfig = app_model.app_model_config
if not app_model_config.speech_to_text_dict['enabled']:
raise AppUnavailableError()
file = request.files['file']
try:
response = AudioService.transcript_asr(
tenant_id=app_model.tenant_id,
app_model=app_model,
file=file,
end_user=end_user.get_id()
end_user=end_user
)
return response
@@ -79,15 +74,16 @@ class TextApi(Resource):
def post(self, app_model: App, end_user: EndUser):
parser = reqparse.RequestParser()
parser.add_argument('text', type=str, required=True, nullable=False, location='json')
parser.add_argument('voice', type=str, location='json')
parser.add_argument('streaming', type=bool, required=False, nullable=False, location='json')
args = parser.parse_args()
try:
response = AudioService.transcript_tts(
tenant_id=app_model.tenant_id,
app_model=app_model,
text=args['text'],
end_user=end_user.get_id(),
voice=app_model.app_model_config.text_to_speech_dict.get('voice'),
end_user=end_user,
voice=args.get('voice'),
streaming=args['streaming']
)

View File

@@ -1,9 +1,5 @@
import json
import logging
from collections.abc import Generator
from typing import Union
from flask import Response, stream_with_context
from flask_restful import Resource, reqparse
from werkzeug.exceptions import InternalServerError, NotFound
@@ -19,13 +15,14 @@ from controllers.service_api.app.error import (
ProviderQuotaExceededError,
)
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.application_queue_manager import ApplicationQueueManager
from core.entities.application_entities import InvokeFrom
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from libs import helper
from libs.helper import uuid_value
from models.model import App, EndUser
from services.completion_service import CompletionService
from models.model import App, AppMode, EndUser
from services.app_generate_service import AppGenerateService
class CompletionApi(Resource):
@@ -48,7 +45,7 @@ class CompletionApi(Resource):
args['auto_generate_name'] = False
try:
response = CompletionService.completion(
response = AppGenerateService.generate(
app_model=app_model,
user=end_user,
args=args,
@@ -56,7 +53,7 @@ class CompletionApi(Resource):
streaming=streaming,
)
return compact_response(response)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
@@ -85,7 +82,7 @@ class CompletionStopApi(Resource):
if app_model.mode != 'completion':
raise AppUnavailableError()
ApplicationQueueManager.set_stop_flag(task_id, InvokeFrom.SERVICE_API, end_user.id)
AppQueueManager.set_stop_flag(task_id, InvokeFrom.SERVICE_API, end_user.id)
return {'result': 'success'}, 200
@@ -93,7 +90,8 @@ class CompletionStopApi(Resource):
class ChatApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
def post(self, app_model: App, end_user: EndUser):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
@@ -110,7 +108,7 @@ class ChatApi(Resource):
streaming = args['response_mode'] == 'streaming'
try:
response = CompletionService.completion(
response = AppGenerateService.generate(
app_model=app_model,
user=end_user,
args=args,
@@ -118,7 +116,7 @@ class ChatApi(Resource):
streaming=streaming
)
return compact_response(response)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
@@ -144,25 +142,15 @@ class ChatApi(Resource):
class ChatStopApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
def post(self, app_model: App, end_user: EndUser, task_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
ApplicationQueueManager.set_stop_flag(task_id, InvokeFrom.SERVICE_API, end_user.id)
AppQueueManager.set_stop_flag(task_id, InvokeFrom.SERVICE_API, end_user.id)
return {'result': 'success'}, 200
def compact_response(response: Union[dict, Generator]) -> Response:
if isinstance(response, dict):
return Response(response=json.dumps(response), status=200, mimetype='application/json')
else:
def generate() -> Generator:
yield from response
return Response(stream_with_context(generate()), status=200,
mimetype='text/event-stream')
api.add_resource(CompletionApi, '/completion-messages')
api.add_resource(CompletionStopApi, '/completion-messages/<string:task_id>/stop')
api.add_resource(ChatApi, '/chat-messages')

View File

@@ -8,7 +8,7 @@ from controllers.service_api.app.error import NotChatAppError
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import App, EndUser
from models.model import App, AppMode, EndUser
from services.conversation_service import ConversationService
@@ -17,7 +17,8 @@ class ConversationApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY))
@marshal_with(conversation_infinite_scroll_pagination_fields)
def get(self, app_model: App, end_user: EndUser):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
@@ -30,11 +31,13 @@ class ConversationApi(Resource):
except services.errors.conversation.LastConversationNotExistsError:
raise NotFound("Last Conversation Not Exists.")
class ConversationDetailApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
@marshal_with(simple_conversation_fields)
def delete(self, app_model: App, end_user: EndUser, c_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)
@@ -51,7 +54,8 @@ class ConversationRenameApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
@marshal_with(simple_conversation_fields)
def post(self, app_model: App, end_user: EndUser, c_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)

View File

@@ -15,7 +15,13 @@ class NotCompletionAppError(BaseHTTPException):
class NotChatAppError(BaseHTTPException):
error_code = 'not_chat_app'
description = "Please check if your Chat app mode matches the right API route."
description = "Please check if your app mode matches the right API route."
code = 400
class NotWorkflowAppError(BaseHTTPException):
error_code = 'not_workflow_app'
description = "Please check if your app mode matches the right API route."
code = 400

View File

@@ -1,14 +1,18 @@
import logging
from flask_restful import Resource, fields, marshal_with, reqparse
from flask_restful.inputs import int_range
from werkzeug.exceptions import NotFound
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
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 message_file_fields
from libs.helper import TimestampField, uuid_value
from models.model import App, EndUser
from models.model import App, AppMode, EndUser
from services.errors.message import SuggestedQuestionsAfterAnswerDisabledError
from services.message_service import MessageService
@@ -54,12 +58,14 @@ class MessageListApi(Resource):
'conversation_id': fields.String,
'inputs': fields.Raw,
'query': fields.String,
'answer': fields.String,
'answer': fields.String(attribute='re_sign_file_url_answer'),
'message_files': fields.List(fields.Nested(message_file_fields), attribute='files'),
'feedback': fields.Nested(feedback_fields, attribute='user_feedback', allow_null=True),
'retriever_resources': fields.List(fields.Nested(retriever_resource_fields)),
'created_at': TimestampField,
'agent_thoughts': fields.List(fields.Nested(agent_thought_fields))
'agent_thoughts': fields.List(fields.Nested(agent_thought_fields)),
'status': fields.String,
'error': fields.String,
}
message_infinite_scroll_pagination_fields = {
@@ -71,7 +77,8 @@ class MessageListApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY))
@marshal_with(message_infinite_scroll_pagination_fields)
def get(self, app_model: App, end_user: EndUser):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
@@ -110,7 +117,8 @@ class MessageSuggestedApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY))
def get(self, app_model: App, end_user: EndUser, message_id):
message_id = str(message_id)
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
try:
@@ -118,10 +126,15 @@ class MessageSuggestedApi(Resource):
app_model=app_model,
user=end_user,
message_id=message_id,
check_enabled=False
invoke_from=InvokeFrom.SERVICE_API
)
except services.errors.message.MessageNotExistsError:
raise NotFound("Message Not Exists.")
except SuggestedQuestionsAfterAnswerDisabledError:
raise BadRequest("Message Not Exists.")
except Exception:
logging.exception("internal server error.")
raise InternalServerError()
return {'result': 'success', 'data': questions}

View File

@@ -0,0 +1,87 @@
import logging
from flask_restful import Resource, reqparse
from werkzeug.exceptions import InternalServerError
from controllers.service_api import api
from controllers.service_api.app.error import (
CompletionRequestError,
NotWorkflowAppError,
ProviderModelCurrentlyNotSupportError,
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from libs import helper
from models.model import App, AppMode, EndUser
from services.app_generate_service import AppGenerateService
logger = logging.getLogger(__name__)
class WorkflowRunApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
def post(self, app_model: App, end_user: EndUser):
"""
Run workflow
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
raise NotWorkflowAppError()
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, nullable=False, location='json')
parser.add_argument('files', type=list, required=False, location='json')
parser.add_argument('response_mode', type=str, choices=['blocking', 'streaming'], location='json')
args = parser.parse_args()
streaming = args.get('response_mode') == 'streaming'
try:
response = AppGenerateService.generate(
app_model=app_model,
user=end_user,
args=args,
invoke_from=InvokeFrom.SERVICE_API,
streaming=streaming
)
return helper.compact_generate_response(response)
except ProviderTokenNotInitError as ex:
raise ProviderNotInitializeError(ex.description)
except QuotaExceededError:
raise ProviderQuotaExceededError()
except ModelCurrentlyNotSupportError:
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception as e:
logging.exception("internal server error.")
raise InternalServerError()
class WorkflowTaskStopApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
def post(self, app_model: App, end_user: EndUser, task_id: str):
"""
Stop workflow task
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
raise NotWorkflowAppError()
AppQueueManager.set_stop_flag(task_id, InvokeFrom.SERVICE_API, end_user.id)
return {
"result": "success"
}
api.add_resource(WorkflowRunApi, '/workflows/run')
api.add_resource(WorkflowTaskStopApi, '/workflows/tasks/<string:task_id>/stop')

View File

@@ -6,4 +6,4 @@ bp = Blueprint('web', __name__, url_prefix='/api')
api = ExternalApi(bp)
from . import app, audio, completion, conversation, file, message, passport, saved_message, site
from . import app, audio, completion, conversation, file, message, passport, saved_message, site, workflow

View File

@@ -4,10 +4,12 @@ from flask import current_app
from flask_restful import fields, marshal_with
from controllers.web import api
from controllers.web.error import AppUnavailableError
from controllers.web.wraps import WebApiResource
from extensions.ext_database import db
from models.model import App, AppModelConfig
from models.model import App, AppModelConfig, AppMode
from models.tools import ApiToolProvider
from services.app_service import AppService
class AppParameterApi(WebApiResource):
@@ -44,61 +46,49 @@ class AppParameterApi(WebApiResource):
@marshal_with(parameters_fields)
def get(self, app_model: App, end_user):
"""Retrieve app parameters."""
app_model_config = app_model.app_model_config
if app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value]:
workflow = app_model.workflow
if workflow is None:
raise AppUnavailableError()
features_dict = workflow.features_dict
user_input_form = workflow.user_input_form(to_old_structure=True)
else:
app_model_config = app_model.app_model_config
features_dict = app_model_config.to_dict()
user_input_form = features_dict.get('user_input_form', [])
return {
'opening_statement': app_model_config.opening_statement,
'suggested_questions': app_model_config.suggested_questions_list,
'suggested_questions_after_answer': app_model_config.suggested_questions_after_answer_dict,
'speech_to_text': app_model_config.speech_to_text_dict,
'text_to_speech': app_model_config.text_to_speech_dict,
'retriever_resource': app_model_config.retriever_resource_dict,
'annotation_reply': app_model_config.annotation_reply_dict,
'more_like_this': app_model_config.more_like_this_dict,
'user_input_form': app_model_config.user_input_form_list,
'sensitive_word_avoidance': app_model_config.sensitive_word_avoidance_dict,
'file_upload': app_model_config.file_upload_dict,
'opening_statement': features_dict.get('opening_statement'),
'suggested_questions': features_dict.get('suggested_questions', []),
'suggested_questions_after_answer': features_dict.get('suggested_questions_after_answer',
{"enabled": False}),
'speech_to_text': features_dict.get('speech_to_text', {"enabled": False}),
'text_to_speech': features_dict.get('text_to_speech', {"enabled": False}),
'retriever_resource': features_dict.get('retriever_resource', {"enabled": False}),
'annotation_reply': features_dict.get('annotation_reply', {"enabled": False}),
'more_like_this': features_dict.get('more_like_this', {"enabled": False}),
'user_input_form': user_input_form,
'sensitive_word_avoidance': features_dict.get('sensitive_word_avoidance',
{"enabled": False, "type": "", "configs": []}),
'file_upload': features_dict.get('file_upload', {"image": {
"enabled": False,
"number_limits": 3,
"detail": "high",
"transfer_methods": ["remote_url", "local_file"]
}}),
'system_parameters': {
'image_file_size_limit': current_app.config.get('UPLOAD_IMAGE_FILE_SIZE_LIMIT')
}
}
class AppMeta(WebApiResource):
def get(self, app_model: App, end_user):
"""Get app meta"""
app_model_config: AppModelConfig = app_model.app_model_config
return AppService().get_app_meta(app_model)
agent_config = app_model_config.agent_mode_dict or {}
meta = {
'tool_icons': {}
}
# get all tools
tools = agent_config.get('tools', [])
url_prefix = (current_app.config.get("CONSOLE_API_URL")
+ "/console/api/workspaces/current/tool-provider/builtin/")
for tool in tools:
keys = list(tool.keys())
if len(keys) >= 4:
# current tool standard
provider_type = tool.get('provider_type')
provider_id = tool.get('provider_id')
tool_name = tool.get('tool_name')
if provider_type == 'builtin':
meta['tool_icons'][tool_name] = url_prefix + provider_id + '/icon'
elif provider_type == 'api':
try:
provider: ApiToolProvider = db.session.query(ApiToolProvider).filter(
ApiToolProvider.id == provider_id
)
meta['tool_icons'][tool_name] = json.loads(provider.icon)
except:
meta['tool_icons'][tool_name] = {
"background": "#252525",
"content": "\ud83d\ude01"
}
return meta
api.add_resource(AppParameterApi, '/parameters')
api.add_resource(AppMeta, '/meta')

View File

@@ -19,7 +19,7 @@ from controllers.web.error import (
from controllers.web.wraps import WebApiResource
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from models.model import App, AppModelConfig
from models.model import App
from services.audio_service import AudioService
from services.errors.audio import (
AudioTooLargeServiceError,
@@ -31,16 +31,11 @@ from services.errors.audio import (
class AudioApi(WebApiResource):
def post(self, app_model: App, end_user):
app_model_config: AppModelConfig = app_model.app_model_config
if not app_model_config.speech_to_text_dict['enabled']:
raise AppUnavailableError()
file = request.files['file']
try:
response = AudioService.transcript_asr(
tenant_id=app_model.tenant_id,
app_model=app_model,
file=file,
end_user=end_user
)
@@ -74,17 +69,12 @@ class AudioApi(WebApiResource):
class TextApi(WebApiResource):
def post(self, app_model: App, end_user):
app_model_config: AppModelConfig = app_model.app_model_config
if not app_model_config.text_to_speech_dict['enabled']:
raise AppUnavailableError()
try:
response = AudioService.transcript_tts(
tenant_id=app_model.tenant_id,
app_model=app_model,
text=request.form['text'],
end_user=end_user.external_user_id,
voice=request.form['voice'] if request.form['voice'] else app_model.app_model_config.text_to_speech_dict.get('voice'),
voice=request.form.get('voice'),
streaming=False
)

View File

@@ -1,9 +1,5 @@
import json
import logging
from collections.abc import Generator
from typing import Union
from flask import Response, stream_with_context
from flask_restful import reqparse
from werkzeug.exceptions import InternalServerError, NotFound
@@ -20,12 +16,14 @@ from controllers.web.error import (
ProviderQuotaExceededError,
)
from controllers.web.wraps import WebApiResource
from core.application_queue_manager import ApplicationQueueManager
from core.entities.application_entities import InvokeFrom
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from libs import helper
from libs.helper import uuid_value
from services.completion_service import CompletionService
from models.model import AppMode
from services.app_generate_service import AppGenerateService
# define completion api for user
@@ -48,7 +46,7 @@ class CompletionApi(WebApiResource):
args['auto_generate_name'] = False
try:
response = CompletionService.completion(
response = AppGenerateService.generate(
app_model=app_model,
user=end_user,
args=args,
@@ -56,7 +54,7 @@ class CompletionApi(WebApiResource):
streaming=streaming
)
return compact_response(response)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
@@ -84,14 +82,15 @@ class CompletionStopApi(WebApiResource):
if app_model.mode != 'completion':
raise NotCompletionAppError()
ApplicationQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id)
AppQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id)
return {'result': 'success'}, 200
class ChatApi(WebApiResource):
def post(self, app_model, end_user):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
@@ -108,7 +107,7 @@ class ChatApi(WebApiResource):
args['auto_generate_name'] = False
try:
response = CompletionService.completion(
response = AppGenerateService.generate(
app_model=app_model,
user=end_user,
args=args,
@@ -116,7 +115,7 @@ class ChatApi(WebApiResource):
streaming=streaming
)
return compact_response(response)
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
@@ -141,25 +140,15 @@ class ChatApi(WebApiResource):
class ChatStopApi(WebApiResource):
def post(self, app_model, end_user, task_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
ApplicationQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id)
AppQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id)
return {'result': 'success'}, 200
def compact_response(response: Union[dict, Generator]) -> Response:
if isinstance(response, dict):
return Response(response=json.dumps(response), status=200, mimetype='application/json')
else:
def generate() -> Generator:
yield from response
return Response(stream_with_context(generate()), status=200,
mimetype='text/event-stream')
api.add_resource(CompletionApi, '/completion-messages')
api.add_resource(CompletionStopApi, '/completion-messages/<string:task_id>/stop')
api.add_resource(ChatApi, '/chat-messages')

View File

@@ -7,6 +7,7 @@ from controllers.web.error import NotChatAppError
from controllers.web.wraps import WebApiResource
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import AppMode
from services.conversation_service import ConversationService
from services.errors.conversation import ConversationNotExistsError, LastConversationNotExistsError
from services.web_conversation_service import WebConversationService
@@ -16,7 +17,8 @@ class ConversationListApi(WebApiResource):
@marshal_with(conversation_infinite_scroll_pagination_fields)
def get(self, app_model, end_user):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
@@ -43,7 +45,8 @@ class ConversationListApi(WebApiResource):
class ConversationApi(WebApiResource):
def delete(self, app_model, end_user, c_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)
@@ -60,7 +63,8 @@ class ConversationRenameApi(WebApiResource):
@marshal_with(simple_conversation_fields)
def post(self, app_model, end_user, c_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)
@@ -85,7 +89,8 @@ class ConversationRenameApi(WebApiResource):
class ConversationPinApi(WebApiResource):
def patch(self, app_model, end_user, c_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)
@@ -100,7 +105,8 @@ class ConversationPinApi(WebApiResource):
class ConversationUnPinApi(WebApiResource):
def patch(self, app_model, end_user, c_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
conversation_id = str(c_id)

View File

@@ -15,7 +15,13 @@ class NotCompletionAppError(BaseHTTPException):
class NotChatAppError(BaseHTTPException):
error_code = 'not_chat_app'
description = "Please check if your Chat app mode matches the right API route."
description = "Please check if your app mode matches the right API route."
code = 400
class NotWorkflowAppError(BaseHTTPException):
error_code = 'not_workflow_app'
description = "Please check if your Workflow app mode matches the right API route."
code = 400

View File

@@ -1,9 +1,5 @@
import json
import logging
from collections.abc import Generator
from typing import Union
from flask import Response, stream_with_context
from flask_restful import fields, marshal_with, reqparse
from flask_restful.inputs import int_range
from werkzeug.exceptions import InternalServerError, NotFound
@@ -21,13 +17,15 @@ from controllers.web.error import (
ProviderQuotaExceededError,
)
from controllers.web.wraps import WebApiResource
from core.entities.application_entities import InvokeFrom
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from fields.conversation_fields import message_file_fields
from fields.message_fields import agent_thought_fields
from libs import helper
from libs.helper import TimestampField, uuid_value
from services.completion_service import CompletionService
from models.model import AppMode
from services.app_generate_service import AppGenerateService
from services.errors.app import MoreLikeThisDisabledError
from services.errors.conversation import ConversationNotExistsError
from services.errors.message import MessageNotExistsError, SuggestedQuestionsAfterAnswerDisabledError
@@ -63,12 +61,14 @@ class MessageListApi(WebApiResource):
'conversation_id': fields.String,
'inputs': fields.Raw,
'query': fields.String,
'answer': fields.String,
'answer': fields.String(attribute='re_sign_file_url_answer'),
'message_files': fields.List(fields.Nested(message_file_fields), attribute='files'),
'feedback': fields.Nested(feedback_fields, attribute='user_feedback', allow_null=True),
'retriever_resources': fields.List(fields.Nested(retriever_resource_fields)),
'created_at': TimestampField,
'agent_thoughts': fields.List(fields.Nested(agent_thought_fields))
'agent_thoughts': fields.List(fields.Nested(agent_thought_fields)),
'status': fields.String,
'error': fields.String,
}
message_infinite_scroll_pagination_fields = {
@@ -79,7 +79,8 @@ class MessageListApi(WebApiResource):
@marshal_with(message_infinite_scroll_pagination_fields)
def get(self, app_model, end_user):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotChatAppError()
parser = reqparse.RequestParser()
@@ -127,7 +128,7 @@ class MessageMoreLikeThisApi(WebApiResource):
streaming = args['response_mode'] == 'streaming'
try:
response = CompletionService.generate_more_like_this(
response = AppGenerateService.generate_more_like_this(
app_model=app_model,
user=end_user,
message_id=message_id,
@@ -135,7 +136,7 @@ class MessageMoreLikeThisApi(WebApiResource):
streaming=streaming
)
return compact_response(response)
return helper.compact_generate_response(response)
except MessageNotExistsError:
raise NotFound("Message Not Exists.")
except MoreLikeThisDisabledError:
@@ -155,20 +156,10 @@ class MessageMoreLikeThisApi(WebApiResource):
raise InternalServerError()
def compact_response(response: Union[dict, Generator]) -> Response:
if isinstance(response, dict):
return Response(response=json.dumps(response), status=200, mimetype='application/json')
else:
def generate() -> Generator:
yield from response
return Response(stream_with_context(generate()), status=200,
mimetype='text/event-stream')
class MessageSuggestedQuestionApi(WebApiResource):
def get(self, app_model, end_user, message_id):
if app_model.mode != 'chat':
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]:
raise NotCompletionAppError()
message_id = str(message_id)
@@ -177,7 +168,8 @@ class MessageSuggestedQuestionApi(WebApiResource):
questions = MessageService.get_suggested_questions_after_answer(
app_model=app_model,
user=end_user,
message_id=message_id
message_id=message_id,
invoke_from=InvokeFrom.WEB_APP
)
except MessageNotExistsError:
raise NotFound("Message not found")

View File

@@ -83,7 +83,3 @@ class AppSiteInfo:
'remove_webapp_brand': remove_webapp_brand,
'replace_webapp_logo': replace_webapp_logo,
}
if app.enable_site and site.prompt_public:
app_model_config = app.app_model_config
self.model_config = app_model_config

View File

@@ -0,0 +1,82 @@
import logging
from flask_restful import reqparse
from werkzeug.exceptions import InternalServerError
from controllers.web import api
from controllers.web.error import (
CompletionRequestError,
NotWorkflowAppError,
ProviderModelCurrentlyNotSupportError,
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
from controllers.web.wraps import WebApiResource
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.errors.invoke import InvokeError
from libs import helper
from models.model import App, AppMode, EndUser
from services.app_generate_service import AppGenerateService
logger = logging.getLogger(__name__)
class WorkflowRunApi(WebApiResource):
def post(self, app_model: App, end_user: EndUser):
"""
Run workflow
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
raise NotWorkflowAppError()
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, nullable=False, location='json')
parser.add_argument('files', type=list, required=False, location='json')
args = parser.parse_args()
try:
response = AppGenerateService.generate(
app_model=app_model,
user=end_user,
args=args,
invoke_from=InvokeFrom.WEB_APP,
streaming=True
)
return helper.compact_generate_response(response)
except ProviderTokenNotInitError as ex:
raise ProviderNotInitializeError(ex.description)
except QuotaExceededError:
raise ProviderQuotaExceededError()
except ModelCurrentlyNotSupportError:
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception as e:
logging.exception("internal server error.")
raise InternalServerError()
class WorkflowTaskStopApi(WebApiResource):
def post(self, app_model: App, end_user: EndUser, task_id: str):
"""
Stop workflow task
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
raise NotWorkflowAppError()
AppQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id)
return {
"result": "success"
}
api.add_resource(WorkflowRunApi, '/workflows/run')
api.add_resource(WorkflowTaskStopApi, '/workflows/tasks/<string:task_id>/stop')

View File

@@ -2,22 +2,18 @@ import json
import logging
import uuid
from datetime import datetime
from mimetypes import guess_extension
from typing import Optional, Union, cast
from core.app_runner.app_runner import AppRunner
from core.application_queue_manager import ApplicationQueueManager
from core.agent.entities import AgentEntity, AgentToolEntity
from core.app.apps.agent_chat.app_config_manager import AgentChatAppConfig
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.apps.base_app_runner import AppRunner
from core.app.entities.app_invoke_entities import (
AgentChatAppGenerateEntity,
ModelConfigWithCredentialsEntity,
)
from core.callback_handler.agent_tool_callback_handler import DifyAgentCallbackHandler
from core.callback_handler.index_tool_callback_handler import DatasetIndexToolCallbackHandler
from core.entities.application_entities import (
AgentEntity,
AgentToolEntity,
ApplicationGenerateEntity,
AppOrchestrationConfigEntity,
InvokeFrom,
ModelConfigEntity,
)
from core.file.message_file_parser import FileTransferMethod
from core.memory.token_buffer_memory import TokenBufferMemory
from core.model_manager import ModelInstance
from core.model_runtime.entities.llm_entities import LLMUsage
@@ -34,27 +30,25 @@ from core.model_runtime.model_providers.__base.large_language_model import Large
from core.model_runtime.utils.encoders import jsonable_encoder
from core.tools.entities.tool_entities import (
ToolInvokeMessage,
ToolInvokeMessageBinary,
ToolParameter,
ToolRuntimeVariablePool,
)
from core.tools.tool.dataset_retriever_tool import DatasetRetrieverTool
from core.tools.tool.tool import Tool
from core.tools.tool_file_manager import ToolFileManager
from core.tools.tool_manager import ToolManager
from extensions.ext_database import db
from models.model import Message, MessageAgentThought, MessageFile
from models.model import Message, MessageAgentThought
from models.tools import ToolConversationVariables
logger = logging.getLogger(__name__)
class BaseAssistantApplicationRunner(AppRunner):
class BaseAgentRunner(AppRunner):
def __init__(self, tenant_id: str,
application_generate_entity: ApplicationGenerateEntity,
app_orchestration_config: AppOrchestrationConfigEntity,
model_config: ModelConfigEntity,
application_generate_entity: AgentChatAppGenerateEntity,
app_config: AgentChatAppConfig,
model_config: ModelConfigWithCredentialsEntity,
config: AgentEntity,
queue_manager: ApplicationQueueManager,
queue_manager: AppQueueManager,
message: Message,
user_id: str,
memory: Optional[TokenBufferMemory] = None,
@@ -66,7 +60,7 @@ class BaseAssistantApplicationRunner(AppRunner):
"""
Agent runner
:param tenant_id: tenant id
:param app_orchestration_config: app orchestration config
:param app_config: app generate entity
:param model_config: model config
:param config: dataset config
:param queue_manager: queue manager
@@ -78,7 +72,7 @@ class BaseAssistantApplicationRunner(AppRunner):
"""
self.tenant_id = tenant_id
self.application_generate_entity = application_generate_entity
self.app_orchestration_config = app_orchestration_config
self.app_config = app_config
self.model_config = model_config
self.config = config
self.queue_manager = queue_manager
@@ -97,16 +91,16 @@ class BaseAssistantApplicationRunner(AppRunner):
# init dataset tools
hit_callback = DatasetIndexToolCallbackHandler(
queue_manager=queue_manager,
app_id=self.application_generate_entity.app_id,
app_id=self.app_config.app_id,
message_id=message.id,
user_id=user_id,
invoke_from=self.application_generate_entity.invoke_from,
)
self.dataset_tools = DatasetRetrieverTool.get_dataset_tools(
tenant_id=tenant_id,
dataset_ids=app_orchestration_config.dataset.dataset_ids if app_orchestration_config.dataset else [],
retrieve_config=app_orchestration_config.dataset.retrieve_config if app_orchestration_config.dataset else None,
return_resource=app_orchestration_config.show_retrieve_source,
dataset_ids=app_config.dataset.dataset_ids if app_config.dataset else [],
retrieve_config=app_config.dataset.retrieve_config if app_config.dataset else None,
return_resource=app_config.additional_features.show_retrieve_source,
invoke_from=application_generate_entity.invoke_from,
hit_callback=hit_callback
)
@@ -124,14 +118,15 @@ class BaseAssistantApplicationRunner(AppRunner):
else:
self.stream_tool_call = False
def _repack_app_orchestration_config(self, app_orchestration_config: AppOrchestrationConfigEntity) -> AppOrchestrationConfigEntity:
def _repack_app_generate_entity(self, app_generate_entity: AgentChatAppGenerateEntity) \
-> AgentChatAppGenerateEntity:
"""
Repack app orchestration config
Repack app generate entity
"""
if app_orchestration_config.prompt_template.simple_prompt_template is None:
app_orchestration_config.prompt_template.simple_prompt_template = ''
if app_generate_entity.app_config.prompt_template.simple_prompt_template is None:
app_generate_entity.app_config.prompt_template.simple_prompt_template = ''
return app_orchestration_config
return app_generate_entity
def _convert_tool_response_to_str(self, tool_response: list[ToolInvokeMessage]) -> str:
"""
@@ -158,7 +153,6 @@ class BaseAssistantApplicationRunner(AppRunner):
tool_entity = ToolManager.get_agent_tool_runtime(
tenant_id=self.tenant_id,
agent_tool=tool,
agent_callback=self.agent_callback
)
tool_entity.load_variables(self.variables_pool)
@@ -272,87 +266,6 @@ class BaseAssistantApplicationRunner(AppRunner):
prompt_tool.parameters['required'].append(parameter.name)
return prompt_tool
def extract_tool_response_binary(self, tool_response: list[ToolInvokeMessage]) -> list[ToolInvokeMessageBinary]:
"""
Extract tool response binary
"""
result = []
for response in tool_response:
if response.type == ToolInvokeMessage.MessageType.IMAGE_LINK or \
response.type == ToolInvokeMessage.MessageType.IMAGE:
result.append(ToolInvokeMessageBinary(
mimetype=response.meta.get('mime_type', 'octet/stream'),
url=response.message,
save_as=response.save_as,
))
elif response.type == ToolInvokeMessage.MessageType.BLOB:
result.append(ToolInvokeMessageBinary(
mimetype=response.meta.get('mime_type', 'octet/stream'),
url=response.message,
save_as=response.save_as,
))
elif response.type == ToolInvokeMessage.MessageType.LINK:
# check if there is a mime type in meta
if response.meta and 'mime_type' in response.meta:
result.append(ToolInvokeMessageBinary(
mimetype=response.meta.get('mime_type', 'octet/stream') if response.meta else 'octet/stream',
url=response.message,
save_as=response.save_as,
))
return result
def create_message_files(self, messages: list[ToolInvokeMessageBinary]) -> list[tuple[MessageFile, bool]]:
"""
Create message file
:param messages: messages
:return: message files, should save as variable
"""
result = []
for message in messages:
file_type = 'bin'
if 'image' in message.mimetype:
file_type = 'image'
elif 'video' in message.mimetype:
file_type = 'video'
elif 'audio' in message.mimetype:
file_type = 'audio'
elif 'text' in message.mimetype:
file_type = 'text'
elif 'pdf' in message.mimetype:
file_type = 'pdf'
elif 'zip' in message.mimetype:
file_type = 'archive'
# ...
invoke_from = self.application_generate_entity.invoke_from
message_file = MessageFile(
message_id=self.message.id,
type=file_type,
transfer_method=FileTransferMethod.TOOL_FILE.value,
belongs_to='assistant',
url=message.url,
upload_file_id=None,
created_by_role=('account'if invoke_from in [InvokeFrom.EXPLORE, InvokeFrom.DEBUGGER] else 'end_user'),
created_by=self.user_id,
)
db.session.add(message_file)
db.session.commit()
db.session.refresh(message_file)
result.append((
message_file,
message.save_as
))
db.session.close()
return result
def create_agent_thought(self, message_id: str, message: str,
tool_name: str, tool_input: str, messages_ids: list[str]
@@ -366,6 +279,7 @@ class BaseAssistantApplicationRunner(AppRunner):
thought='',
tool=tool_name,
tool_labels_str='{}',
tool_meta_str='{}',
tool_input=tool_input,
message=message,
message_token=0,
@@ -400,7 +314,8 @@ class BaseAssistantApplicationRunner(AppRunner):
tool_name: str,
tool_input: Union[str, dict],
thought: str,
observation: str,
observation: Union[str, str],
tool_invoke_meta: Union[str, dict],
answer: str,
messages_ids: list[str],
llm_usage: LLMUsage = None) -> MessageAgentThought:
@@ -427,6 +342,12 @@ class BaseAssistantApplicationRunner(AppRunner):
agent_thought.tool_input = tool_input
if observation is not None:
if isinstance(observation, dict):
try:
observation = json.dumps(observation, ensure_ascii=False)
except Exception as e:
observation = json.dumps(observation)
agent_thought.observation = observation
if answer is not None:
@@ -460,76 +381,18 @@ class BaseAssistantApplicationRunner(AppRunner):
agent_thought.tool_labels_str = json.dumps(labels)
if tool_invoke_meta is not None:
if isinstance(tool_invoke_meta, dict):
try:
tool_invoke_meta = json.dumps(tool_invoke_meta, ensure_ascii=False)
except Exception as e:
tool_invoke_meta = json.dumps(tool_invoke_meta)
agent_thought.tool_meta_str = tool_invoke_meta
db.session.commit()
db.session.close()
def transform_tool_invoke_messages(self, messages: list[ToolInvokeMessage]) -> list[ToolInvokeMessage]:
"""
Transform tool message into agent thought
"""
result = []
for message in messages:
if message.type == ToolInvokeMessage.MessageType.TEXT:
result.append(message)
elif message.type == ToolInvokeMessage.MessageType.LINK:
result.append(message)
elif message.type == ToolInvokeMessage.MessageType.IMAGE:
# try to download image
try:
file = ToolFileManager.create_file_by_url(user_id=self.user_id, tenant_id=self.tenant_id,
conversation_id=self.message.conversation_id,
file_url=message.message)
url = f'/files/tools/{file.id}{guess_extension(file.mimetype) or ".png"}'
result.append(ToolInvokeMessage(
type=ToolInvokeMessage.MessageType.IMAGE_LINK,
message=url,
save_as=message.save_as,
meta=message.meta.copy() if message.meta is not None else {},
))
except Exception as e:
logger.exception(e)
result.append(ToolInvokeMessage(
type=ToolInvokeMessage.MessageType.TEXT,
message=f"Failed to download image: {message.message}, you can try to download it yourself.",
meta=message.meta.copy() if message.meta is not None else {},
save_as=message.save_as,
))
elif message.type == ToolInvokeMessage.MessageType.BLOB:
# get mime type and save blob to storage
mimetype = message.meta.get('mime_type', 'octet/stream')
# if message is str, encode it to bytes
if isinstance(message.message, str):
message.message = message.message.encode('utf-8')
file = ToolFileManager.create_file_by_raw(user_id=self.user_id, tenant_id=self.tenant_id,
conversation_id=self.message.conversation_id,
file_binary=message.message,
mimetype=mimetype)
url = f'/files/tools/{file.id}{guess_extension(file.mimetype) or ".bin"}'
# check if file is image
if 'image' in mimetype:
result.append(ToolInvokeMessage(
type=ToolInvokeMessage.MessageType.IMAGE_LINK,
message=url,
save_as=message.save_as,
meta=message.meta.copy() if message.meta is not None else {},
))
else:
result.append(ToolInvokeMessage(
type=ToolInvokeMessage.MessageType.LINK,
message=url,
save_as=message.save_as,
meta=message.meta.copy() if message.meta is not None else {},
))
else:
result.append(message)
return result
def update_db_variables(self, tool_variables: ToolRuntimeVariablePool, db_variables: ToolConversationVariables):
"""
convert tool variables to db variables
@@ -569,8 +432,12 @@ class BaseAssistantApplicationRunner(AppRunner):
try:
tool_inputs = json.loads(agent_thought.tool_input)
except Exception as e:
logging.warning("tool execution error: {}, tool_input: {}.".format(str(e), agent_thought.tool_input))
tool_inputs = { agent_thought.tool: agent_thought.tool_input }
tool_inputs = { tool: {} for tool in tools }
try:
tool_responses = json.loads(agent_thought.observation)
except Exception as e:
tool_responses = { tool: agent_thought.observation for tool in tools }
for tool in tools:
# generate a uuid for tool call
tool_call_id = str(uuid.uuid4())
@@ -583,7 +450,7 @@ class BaseAssistantApplicationRunner(AppRunner):
)
))
tool_call_response.append(ToolPromptMessage(
content=agent_thought.observation,
content=tool_responses.get(tool, agent_thought.observation),
name=tool,
tool_call_id=tool_call_id,
))

View File

@@ -3,9 +3,10 @@ import re
from collections.abc import Generator
from typing import Literal, Union
from core.application_queue_manager import PublishFrom
from core.entities.application_entities import AgentPromptEntity, AgentScratchpadUnit
from core.features.assistant_base_runner import BaseAssistantApplicationRunner
from core.agent.base_agent_runner import BaseAgentRunner
from core.agent.entities import AgentPromptEntity, AgentScratchpadUnit
from core.app.apps.base_app_queue_manager import PublishFrom
from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
from core.model_runtime.entities.message_entities import (
AssistantPromptMessage,
@@ -16,18 +17,12 @@ from core.model_runtime.entities.message_entities import (
UserPromptMessage,
)
from core.model_runtime.utils.encoders import jsonable_encoder
from core.tools.errors import (
ToolInvokeError,
ToolNotFoundError,
ToolNotSupportedError,
ToolParameterValidationError,
ToolProviderCredentialValidationError,
ToolProviderNotFoundError,
)
from core.tools.entities.tool_entities import ToolInvokeMeta
from core.tools.tool_engine import ToolEngine
from models.model import Conversation, Message
class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
class CotAgentRunner(BaseAgentRunner):
_is_first_iteration = True
_ignore_observation_providers = ['wenxin']
@@ -39,30 +34,33 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
"""
Run Cot agent application
"""
app_orchestration_config = self.app_orchestration_config
self._repack_app_orchestration_config(app_orchestration_config)
app_generate_entity = self.application_generate_entity
self._repack_app_generate_entity(app_generate_entity)
agent_scratchpad: list[AgentScratchpadUnit] = []
self._init_agent_scratchpad(agent_scratchpad, self.history_prompt_messages)
if 'Observation' not in app_orchestration_config.model_config.stop:
if app_orchestration_config.model_config.provider not in self._ignore_observation_providers:
app_orchestration_config.model_config.stop.append('Observation')
# check model mode
if 'Observation' not in app_generate_entity.model_config.stop:
if app_generate_entity.model_config.provider not in self._ignore_observation_providers:
app_generate_entity.model_config.stop.append('Observation')
app_config = self.app_config
# override inputs
inputs = inputs or {}
instruction = self.app_orchestration_config.prompt_template.simple_prompt_template
instruction = app_config.prompt_template.simple_prompt_template
instruction = self._fill_in_inputs_from_external_data_tools(instruction, inputs)
iteration_step = 1
max_iteration_steps = min(self.app_orchestration_config.agent.max_iteration, 5) + 1
max_iteration_steps = min(app_config.agent.max_iteration, 5) + 1
prompt_messages = self.history_prompt_messages
# convert tools into ModelRuntime Tool format
prompt_messages_tools: list[PromptMessageTool] = []
tool_instances = {}
for tool in self.app_orchestration_config.agent.tools if self.app_orchestration_config.agent else []:
for tool in app_config.agent.tools if app_config.agent else []:
try:
prompt_tool, tool_entity = self._convert_tool_to_prompt_message_tool(tool)
except Exception:
@@ -118,15 +116,17 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
)
if iteration_step > 1:
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
# update prompt messages
prompt_messages = self._organize_cot_prompt_messages(
mode=app_orchestration_config.model_config.mode,
mode=app_generate_entity.model_config.mode,
prompt_messages=prompt_messages,
tools=prompt_messages_tools,
agent_scratchpad=agent_scratchpad,
agent_prompt_message=app_orchestration_config.agent.prompt,
agent_prompt_message=app_config.agent.prompt,
instruction=instruction,
input=query
)
@@ -136,9 +136,9 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
# invoke model
chunks: Generator[LLMResultChunk, None, None] = model_instance.invoke_llm(
prompt_messages=prompt_messages,
model_parameters=app_orchestration_config.model_config.parameters,
model_parameters=app_generate_entity.model_config.parameters,
tools=[],
stop=app_orchestration_config.model_config.stop,
stop=app_generate_entity.model_config.stop,
stream=True,
user=self.user_id,
callbacks=[],
@@ -160,7 +160,9 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
# publish agent thought if it's first iteration
if iteration_step == 1:
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
for chunk in react_chunks:
if isinstance(chunk, dict):
@@ -214,7 +216,10 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
self.save_agent_thought(agent_thought=agent_thought,
tool_name=scratchpad.action.action_name if scratchpad.action else '',
tool_input=scratchpad.action.action_input if scratchpad.action else '',
tool_input={
scratchpad.action.action_name: scratchpad.action.action_input
} if scratchpad.action else '',
tool_invoke_meta={},
thought=scratchpad.thought,
observation='',
answer=scratchpad.agent_response,
@@ -222,7 +227,9 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
llm_usage=usage_dict['usage'])
if scratchpad.action and scratchpad.action.action_name.lower() != "final answer":
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
if not scratchpad.action:
# failed to extract action, return final answer directly
@@ -245,62 +252,65 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
tool_instance = tool_instances.get(tool_call_name)
if not tool_instance:
answer = f"there is not a tool named {tool_call_name}"
self.save_agent_thought(agent_thought=agent_thought,
tool_name='',
tool_input='',
thought=None,
observation=answer,
answer=answer,
messages_ids=[])
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.save_agent_thought(
agent_thought=agent_thought,
tool_name='',
tool_input='',
tool_invoke_meta=ToolInvokeMeta.error_instance(
f"there is not a tool named {tool_call_name}"
).to_dict(),
thought=None,
observation={
tool_call_name: answer
},
answer=answer,
messages_ids=[]
)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
else:
if isinstance(tool_call_args, str):
try:
tool_call_args = json.loads(tool_call_args)
except json.JSONDecodeError:
pass
# invoke tool
error_response = None
try:
if isinstance(tool_call_args, str):
try:
tool_call_args = json.loads(tool_call_args)
except json.JSONDecodeError:
pass
tool_response = tool_instance.invoke(
user_id=self.user_id,
tool_parameters=tool_call_args
)
# transform tool response to llm friendly response
tool_response = self.transform_tool_invoke_messages(tool_response)
# extract binary data from tool invoke message
binary_files = self.extract_tool_response_binary(tool_response)
# create message file
message_files = self.create_message_files(binary_files)
# publish files
for message_file, save_as in message_files:
if save_as:
self.variables_pool.set_file(tool_name=tool_call_name,
value=message_file.id,
name=save_as)
self.queue_manager.publish_message_file(message_file, PublishFrom.APPLICATION_MANAGER)
tool_invoke_response, message_files, tool_invoke_meta = ToolEngine.agent_invoke(
tool=tool_instance,
tool_parameters=tool_call_args,
user_id=self.user_id,
tenant_id=self.tenant_id,
message=self.message,
invoke_from=self.application_generate_entity.invoke_from,
agent_tool_callback=self.agent_callback
)
# publish files
for message_file, save_as in message_files:
if save_as:
self.variables_pool.set_file(tool_name=tool_call_name, value=message_file.id, name=save_as)
message_file_ids = [message_file.id for message_file, _ in message_files]
except ToolProviderCredentialValidationError as e:
error_response = "Please check your tool provider credentials"
except (
ToolNotFoundError, ToolNotSupportedError, ToolProviderNotFoundError
) as e:
error_response = f"there is not a tool named {tool_call_name}"
except (
ToolParameterValidationError
) as e:
error_response = f"tool parameters validation error: {e}, please check your tool parameters"
except ToolInvokeError as e:
error_response = f"tool invoke error: {e}"
except Exception as e:
error_response = f"unknown error: {e}"
# publish message file
self.queue_manager.publish(QueueMessageFileEvent(
message_file_id=message_file.id
), PublishFrom.APPLICATION_MANAGER)
# add message file ids
message_file_ids.append(message_file.id)
if error_response:
observation = error_response
else:
observation = self._convert_tool_response_to_str(tool_response)
# publish files
for message_file, save_as in message_files:
if save_as:
self.variables_pool.set_file(tool_name=tool_call_name,
value=message_file.id,
name=save_as)
self.queue_manager.publish(QueueMessageFileEvent(
message_file_id=message_file.id
), PublishFrom.APPLICATION_MANAGER)
message_file_ids = [message_file.id for message_file, _ in message_files]
observation = tool_invoke_response
# save scratchpad
scratchpad.observation = observation
@@ -309,13 +319,22 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
self.save_agent_thought(
agent_thought=agent_thought,
tool_name=tool_call_name,
tool_input=tool_call_args,
tool_input={
tool_call_name: tool_call_args
},
tool_invoke_meta={
tool_call_name: tool_invoke_meta.to_dict()
},
thought=None,
observation=observation,
observation={
tool_call_name: observation
},
answer=scratchpad.agent_response,
messages_ids=message_file_ids,
)
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
# update prompt tool message
for prompt_tool in prompt_messages_tools:
@@ -340,16 +359,17 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
self.save_agent_thought(
agent_thought=agent_thought,
tool_name='',
tool_input='',
tool_input={},
tool_invoke_meta={},
thought=final_answer,
observation='',
observation={},
answer=final_answer,
messages_ids=[]
)
self.update_db_variables(self.variables_pool, self.db_variables_pool)
# publish end event
self.queue_manager.publish_message_end(LLMResult(
self.queue_manager.publish(QueueMessageEndEvent(llm_result=LLMResult(
model=model_instance.model,
prompt_messages=prompt_messages,
message=AssistantPromptMessage(
@@ -357,7 +377,7 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
),
usage=llm_usage['usage'] if llm_usage['usage'] else LLMUsage.empty_usage(),
system_fingerprint=''
), PublishFrom.APPLICATION_MANAGER)
)), PublishFrom.APPLICATION_MANAGER)
def _handle_stream_react(self, llm_response: Generator[LLMResultChunk, None, None], usage: dict) \
-> Generator[Union[str, dict], None, None]:
@@ -550,7 +570,7 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner):
"""
convert agent scratchpad list to str
"""
next_iteration = self.app_orchestration_config.agent.prompt.next_iteration
next_iteration = self.app_config.agent.prompt.next_iteration
result = ''
for scratchpad in agent_scratchpad:

View File

@@ -0,0 +1,61 @@
from enum import Enum
from typing import Any, Literal, Optional, Union
from pydantic import BaseModel
class AgentToolEntity(BaseModel):
"""
Agent Tool Entity.
"""
provider_type: Literal["builtin", "api"]
provider_id: str
tool_name: str
tool_parameters: dict[str, Any] = {}
class AgentPromptEntity(BaseModel):
"""
Agent Prompt Entity.
"""
first_prompt: str
next_iteration: str
class AgentScratchpadUnit(BaseModel):
"""
Agent First Prompt Entity.
"""
class Action(BaseModel):
"""
Action Entity.
"""
action_name: str
action_input: Union[dict, str]
agent_response: Optional[str] = None
thought: Optional[str] = None
action_str: Optional[str] = None
observation: Optional[str] = None
action: Optional[Action] = None
class AgentEntity(BaseModel):
"""
Agent Entity.
"""
class Strategy(Enum):
"""
Agent Strategy.
"""
CHAIN_OF_THOUGHT = 'chain-of-thought'
FUNCTION_CALLING = 'function-calling'
provider: str
model: str
strategy: Strategy
prompt: Optional[AgentPromptEntity] = None
tools: list[AgentToolEntity] = None
max_iteration: int = 5

View File

@@ -3,8 +3,9 @@ import logging
from collections.abc import Generator
from typing import Any, Union
from core.application_queue_manager import PublishFrom
from core.features.assistant_base_runner import BaseAssistantApplicationRunner
from core.agent.base_agent_runner import BaseAgentRunner
from core.app.apps.base_app_queue_manager import PublishFrom
from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
from core.model_runtime.entities.message_entities import (
AssistantPromptMessage,
@@ -14,19 +15,13 @@ from core.model_runtime.entities.message_entities import (
ToolPromptMessage,
UserPromptMessage,
)
from core.tools.errors import (
ToolInvokeError,
ToolNotFoundError,
ToolNotSupportedError,
ToolParameterValidationError,
ToolProviderCredentialValidationError,
ToolProviderNotFoundError,
)
from core.tools.entities.tool_entities import ToolInvokeMeta
from core.tools.tool_engine import ToolEngine
from models.model import Conversation, Message, MessageAgentThought
logger = logging.getLogger(__name__)
class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
class FunctionCallAgentRunner(BaseAgentRunner):
def run(self, conversation: Conversation,
message: Message,
query: str,
@@ -34,9 +29,11 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
"""
Run FunctionCall agent application
"""
app_orchestration_config = self.app_orchestration_config
app_generate_entity = self.application_generate_entity
prompt_template = self.app_orchestration_config.prompt_template.simple_prompt_template or ''
app_config = self.app_config
prompt_template = app_config.prompt_template.simple_prompt_template or ''
prompt_messages = self.history_prompt_messages
prompt_messages = self.organize_prompt_messages(
prompt_template=prompt_template,
@@ -47,7 +44,7 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
# convert tools into ModelRuntime Tool format
prompt_messages_tools: list[PromptMessageTool] = []
tool_instances = {}
for tool in self.app_orchestration_config.agent.tools if self.app_orchestration_config.agent else []:
for tool in app_config.agent.tools if app_config.agent else []:
try:
prompt_tool, tool_entity = self._convert_tool_to_prompt_message_tool(tool)
except Exception:
@@ -67,7 +64,7 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
tool_instances[dataset_tool.identity.name] = dataset_tool
iteration_step = 1
max_iteration_steps = min(app_orchestration_config.agent.max_iteration, 5) + 1
max_iteration_steps = min(app_config.agent.max_iteration, 5) + 1
# continue to run until there is not any tool call
function_call_state = True
@@ -110,9 +107,9 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
# invoke model
chunks: Union[Generator[LLMResultChunk, None, None], LLMResult] = model_instance.invoke_llm(
prompt_messages=prompt_messages,
model_parameters=app_orchestration_config.model_config.parameters,
model_parameters=app_generate_entity.model_config.parameters,
tools=prompt_messages_tools,
stop=app_orchestration_config.model_config.stop,
stop=app_generate_entity.model_config.stop,
stream=self.stream_tool_call,
user=self.user_id,
callbacks=[],
@@ -133,7 +130,9 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
is_first_chunk = True
for chunk in chunks:
if is_first_chunk:
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
is_first_chunk = False
# check if there is any tool call
if self.check_tool_calls(chunk):
@@ -193,7 +192,9 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
if not result.message.content:
result.message.content = ''
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
yield LLMResultChunk(
model=model_instance.model,
@@ -226,13 +227,15 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
tool_name=tool_call_names,
tool_input=tool_call_inputs,
thought=response,
tool_invoke_meta=None,
observation=None,
answer=response,
messages_ids=[],
llm_usage=current_llm_usage
)
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
final_answer += response + '\n'
@@ -250,65 +253,40 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
tool_response = {
"tool_call_id": tool_call_id,
"tool_call_name": tool_call_name,
"tool_response": f"there is not a tool named {tool_call_name}"
"tool_response": f"there is not a tool named {tool_call_name}",
"meta": ToolInvokeMeta.error_instance(f"there is not a tool named {tool_call_name}").to_dict()
}
tool_responses.append(tool_response)
else:
# invoke tool
error_response = None
try:
tool_invoke_message = tool_instance.invoke(
user_id=self.user_id,
tool_parameters=tool_call_args,
)
# transform tool invoke message to get LLM friendly message
tool_invoke_message = self.transform_tool_invoke_messages(tool_invoke_message)
# extract binary data from tool invoke message
binary_files = self.extract_tool_response_binary(tool_invoke_message)
# create message file
message_files = self.create_message_files(binary_files)
# publish files
for message_file, save_as in message_files:
if save_as:
self.variables_pool.set_file(tool_name=tool_call_name, value=message_file.id, name=save_as)
# publish message file
self.queue_manager.publish_message_file(message_file, PublishFrom.APPLICATION_MANAGER)
# add message file ids
message_file_ids.append(message_file.id)
except ToolProviderCredentialValidationError as e:
error_response = "Please check your tool provider credentials"
except (
ToolNotFoundError, ToolNotSupportedError, ToolProviderNotFoundError
) as e:
error_response = f"there is not a tool named {tool_call_name}"
except (
ToolParameterValidationError
) as e:
error_response = f"tool parameters validation error: {e}, please check your tool parameters"
except ToolInvokeError as e:
error_response = f"tool invoke error: {e}"
except Exception as e:
error_response = f"unknown error: {e}"
if error_response:
observation = error_response
tool_response = {
"tool_call_id": tool_call_id,
"tool_call_name": tool_call_name,
"tool_response": error_response
}
tool_responses.append(tool_response)
else:
observation = self._convert_tool_response_to_str(tool_invoke_message)
tool_response = {
"tool_call_id": tool_call_id,
"tool_call_name": tool_call_name,
"tool_response": observation
}
tool_responses.append(tool_response)
tool_invoke_response, message_files, tool_invoke_meta = ToolEngine.agent_invoke(
tool=tool_instance,
tool_parameters=tool_call_args,
user_id=self.user_id,
tenant_id=self.tenant_id,
message=self.message,
invoke_from=self.application_generate_entity.invoke_from,
agent_tool_callback=self.agent_callback,
)
# publish files
for message_file, save_as in message_files:
if save_as:
self.variables_pool.set_file(tool_name=tool_call_name, value=message_file.id, name=save_as)
# publish message file
self.queue_manager.publish(QueueMessageFileEvent(
message_file_id=message_file.id
), PublishFrom.APPLICATION_MANAGER)
# add message file ids
message_file_ids.append(message_file.id)
tool_response = {
"tool_call_id": tool_call_id,
"tool_call_name": tool_call_name,
"tool_response": tool_invoke_response,
"meta": tool_invoke_meta.to_dict()
}
tool_responses.append(tool_response)
prompt_messages = self.organize_prompt_messages(
prompt_template=prompt_template,
query=None,
@@ -325,11 +303,20 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
tool_name=None,
tool_input=None,
thought=None,
observation=tool_response['tool_response'],
tool_invoke_meta={
tool_response['tool_call_name']: tool_response['meta']
for tool_response in tool_responses
},
observation={
tool_response['tool_call_name']: tool_response['tool_response']
for tool_response in tool_responses
},
answer=None,
messages_ids=message_file_ids
)
self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER)
self.queue_manager.publish(QueueAgentThoughtEvent(
agent_thought_id=agent_thought.id
), PublishFrom.APPLICATION_MANAGER)
# update prompt tool
for prompt_tool in prompt_messages_tools:
@@ -339,15 +326,15 @@ class AssistantFunctionCallApplicationRunner(BaseAssistantApplicationRunner):
self.update_db_variables(self.variables_pool, self.db_variables_pool)
# publish end event
self.queue_manager.publish_message_end(LLMResult(
self.queue_manager.publish(QueueMessageEndEvent(llm_result=LLMResult(
model=model_instance.model,
prompt_messages=prompt_messages,
message=AssistantPromptMessage(
content=final_answer,
content=final_answer
),
usage=llm_usage['usage'] if llm_usage['usage'] else LLMUsage.empty_usage(),
system_fingerprint=''
), PublishFrom.APPLICATION_MANAGER)
)), PublishFrom.APPLICATION_MANAGER)
def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool:
"""

View File

@@ -0,0 +1,76 @@
from typing import Optional, Union
from core.app.app_config.entities import AppAdditionalFeatures, EasyUIBasedAppModelConfigFrom
from core.app.app_config.features.file_upload.manager import FileUploadConfigManager
from core.app.app_config.features.more_like_this.manager import MoreLikeThisConfigManager
from core.app.app_config.features.opening_statement.manager import OpeningStatementConfigManager
from core.app.app_config.features.retrieval_resource.manager import RetrievalResourceConfigManager
from core.app.app_config.features.speech_to_text.manager import SpeechToTextConfigManager
from core.app.app_config.features.suggested_questions_after_answer.manager import (
SuggestedQuestionsAfterAnswerConfigManager,
)
from core.app.app_config.features.text_to_speech.manager import TextToSpeechConfigManager
from models.model import AppMode, AppModelConfig
class BaseAppConfigManager:
@classmethod
def convert_to_config_dict(cls, config_from: EasyUIBasedAppModelConfigFrom,
app_model_config: Union[AppModelConfig, dict],
config_dict: Optional[dict] = None) -> dict:
"""
Convert app model config to config dict
:param config_from: app model config from
:param app_model_config: app model config
:param config_dict: app model config dict
:return:
"""
if config_from != EasyUIBasedAppModelConfigFrom.ARGS:
app_model_config_dict = app_model_config.to_dict()
config_dict = app_model_config_dict.copy()
return config_dict
@classmethod
def convert_features(cls, config_dict: dict, app_mode: AppMode) -> AppAdditionalFeatures:
"""
Convert app config to app model config
:param config_dict: app config
:param app_mode: app mode
"""
config_dict = config_dict.copy()
additional_features = AppAdditionalFeatures()
additional_features.show_retrieve_source = RetrievalResourceConfigManager.convert(
config=config_dict
)
additional_features.file_upload = FileUploadConfigManager.convert(
config=config_dict,
is_vision=app_mode in [AppMode.CHAT, AppMode.COMPLETION, AppMode.AGENT_CHAT]
)
additional_features.opening_statement, additional_features.suggested_questions = \
OpeningStatementConfigManager.convert(
config=config_dict
)
additional_features.suggested_questions_after_answer = SuggestedQuestionsAfterAnswerConfigManager.convert(
config=config_dict
)
additional_features.more_like_this = MoreLikeThisConfigManager.convert(
config=config_dict
)
additional_features.speech_to_text = SpeechToTextConfigManager.convert(
config=config_dict
)
additional_features.text_to_speech = TextToSpeechConfigManager.convert(
config=config_dict
)
return additional_features

View File

@@ -0,0 +1,50 @@
from typing import Optional
from core.app.app_config.entities import SensitiveWordAvoidanceEntity
from core.moderation.factory import ModerationFactory
class SensitiveWordAvoidanceConfigManager:
@classmethod
def convert(cls, config: dict) -> Optional[SensitiveWordAvoidanceEntity]:
sensitive_word_avoidance_dict = config.get('sensitive_word_avoidance')
if not sensitive_word_avoidance_dict:
return None
if 'enabled' in sensitive_word_avoidance_dict and sensitive_word_avoidance_dict['enabled']:
return SensitiveWordAvoidanceEntity(
type=sensitive_word_avoidance_dict.get('type'),
config=sensitive_word_avoidance_dict.get('config'),
)
else:
return None
@classmethod
def validate_and_set_defaults(cls, tenant_id, config: dict, only_structure_validate: bool = False) \
-> tuple[dict, list[str]]:
if not config.get("sensitive_word_avoidance"):
config["sensitive_word_avoidance"] = {
"enabled": False
}
if not isinstance(config["sensitive_word_avoidance"], dict):
raise ValueError("sensitive_word_avoidance must be of dict type")
if "enabled" not in config["sensitive_word_avoidance"] or not config["sensitive_word_avoidance"]["enabled"]:
config["sensitive_word_avoidance"]["enabled"] = False
if config["sensitive_word_avoidance"]["enabled"]:
if not config["sensitive_word_avoidance"].get("type"):
raise ValueError("sensitive_word_avoidance.type is required")
if not only_structure_validate:
typ = config["sensitive_word_avoidance"]["type"]
sensitive_word_avoidance_config = config["sensitive_word_avoidance"]["config"]
ModerationFactory.validate_config(
name=typ,
tenant_id=tenant_id,
config=sensitive_word_avoidance_config
)
return config, ["sensitive_word_avoidance"]

View File

@@ -0,0 +1,78 @@
from typing import Optional
from core.agent.entities import AgentEntity, AgentPromptEntity, AgentToolEntity
from core.tools.prompt.template import REACT_PROMPT_TEMPLATES
class AgentConfigManager:
@classmethod
def convert(cls, config: dict) -> Optional[AgentEntity]:
"""
Convert model config to model config
:param config: model config args
"""
if 'agent_mode' in config and config['agent_mode'] \
and 'enabled' in config['agent_mode']:
agent_dict = config.get('agent_mode', {})
agent_strategy = agent_dict.get('strategy', 'cot')
if agent_strategy == 'function_call':
strategy = AgentEntity.Strategy.FUNCTION_CALLING
elif agent_strategy == 'cot' or agent_strategy == 'react':
strategy = AgentEntity.Strategy.CHAIN_OF_THOUGHT
else:
# old configs, try to detect default strategy
if config['model']['provider'] == 'openai':
strategy = AgentEntity.Strategy.FUNCTION_CALLING
else:
strategy = AgentEntity.Strategy.CHAIN_OF_THOUGHT
agent_tools = []
for tool in agent_dict.get('tools', []):
keys = tool.keys()
if len(keys) >= 4:
if "enabled" not in tool or not tool["enabled"]:
continue
agent_tool_properties = {
'provider_type': tool['provider_type'],
'provider_id': tool['provider_id'],
'tool_name': tool['tool_name'],
'tool_parameters': tool['tool_parameters'] if 'tool_parameters' in tool else {}
}
agent_tools.append(AgentToolEntity(**agent_tool_properties))
if 'strategy' in config['agent_mode'] and \
config['agent_mode']['strategy'] not in ['react_router', 'router']:
agent_prompt = agent_dict.get('prompt', None) or {}
# check model mode
model_mode = config.get('model', {}).get('mode', 'completion')
if model_mode == 'completion':
agent_prompt_entity = AgentPromptEntity(
first_prompt=agent_prompt.get('first_prompt',
REACT_PROMPT_TEMPLATES['english']['completion']['prompt']),
next_iteration=agent_prompt.get('next_iteration',
REACT_PROMPT_TEMPLATES['english']['completion'][
'agent_scratchpad']),
)
else:
agent_prompt_entity = AgentPromptEntity(
first_prompt=agent_prompt.get('first_prompt',
REACT_PROMPT_TEMPLATES['english']['chat']['prompt']),
next_iteration=agent_prompt.get('next_iteration',
REACT_PROMPT_TEMPLATES['english']['chat']['agent_scratchpad']),
)
return AgentEntity(
provider=config['model']['provider'],
model=config['model']['name'],
strategy=strategy,
prompt=agent_prompt_entity,
tools=agent_tools,
max_iteration=agent_dict.get('max_iteration', 5)
)
return None

View File

@@ -0,0 +1,224 @@
from typing import Optional
from core.app.app_config.entities import DatasetEntity, DatasetRetrieveConfigEntity
from core.entities.agent_entities import PlanningStrategy
from models.model import AppMode
from services.dataset_service import DatasetService
class DatasetConfigManager:
@classmethod
def convert(cls, config: dict) -> Optional[DatasetEntity]:
"""
Convert model config to model config
:param config: model config args
"""
dataset_ids = []
if 'datasets' in config.get('dataset_configs', {}):
datasets = config.get('dataset_configs', {}).get('datasets', {
'strategy': 'router',
'datasets': []
})
for dataset in datasets.get('datasets', []):
keys = list(dataset.keys())
if len(keys) == 0 or keys[0] != 'dataset':
continue
dataset = dataset['dataset']
if 'enabled' not in dataset or not dataset['enabled']:
continue
dataset_id = dataset.get('id', None)
if dataset_id:
dataset_ids.append(dataset_id)
if 'agent_mode' in config and config['agent_mode'] \
and 'enabled' in config['agent_mode'] \
and config['agent_mode']['enabled']:
agent_dict = config.get('agent_mode', {})
for tool in agent_dict.get('tools', []):
keys = tool.keys()
if len(keys) == 1:
# old standard
key = list(tool.keys())[0]
if key != 'dataset':
continue
tool_item = tool[key]
if "enabled" not in tool_item or not tool_item["enabled"]:
continue
dataset_id = tool_item['id']
dataset_ids.append(dataset_id)
if len(dataset_ids) == 0:
return None
# dataset configs
dataset_configs = config.get('dataset_configs', {'retrieval_model': 'single'})
query_variable = config.get('dataset_query_variable')
if dataset_configs['retrieval_model'] == 'single':
return DatasetEntity(
dataset_ids=dataset_ids,
retrieve_config=DatasetRetrieveConfigEntity(
query_variable=query_variable,
retrieve_strategy=DatasetRetrieveConfigEntity.RetrieveStrategy.value_of(
dataset_configs['retrieval_model']
)
)
)
else:
return DatasetEntity(
dataset_ids=dataset_ids,
retrieve_config=DatasetRetrieveConfigEntity(
query_variable=query_variable,
retrieve_strategy=DatasetRetrieveConfigEntity.RetrieveStrategy.value_of(
dataset_configs['retrieval_model']
),
top_k=dataset_configs.get('top_k'),
score_threshold=dataset_configs.get('score_threshold'),
reranking_model=dataset_configs.get('reranking_model')
)
)
@classmethod
def validate_and_set_defaults(cls, tenant_id: str, app_mode: AppMode, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for dataset feature
:param tenant_id: tenant ID
:param app_mode: app mode
:param config: app model config args
"""
# Extract dataset config for legacy compatibility
config = cls.extract_dataset_config_for_legacy_compatibility(tenant_id, app_mode, config)
# dataset_configs
if not config.get("dataset_configs"):
config["dataset_configs"] = {'retrieval_model': 'single'}
if not config["dataset_configs"].get("datasets"):
config["dataset_configs"]["datasets"] = {
"strategy": "router",
"datasets": []
}
if not isinstance(config["dataset_configs"], dict):
raise ValueError("dataset_configs must be of object type")
if config["dataset_configs"]['retrieval_model'] == 'multiple':
if not config["dataset_configs"]['reranking_model']:
raise ValueError("reranking_model has not been set")
if not isinstance(config["dataset_configs"]['reranking_model'], dict):
raise ValueError("reranking_model must be of object type")
if not isinstance(config["dataset_configs"], dict):
raise ValueError("dataset_configs must be of object type")
need_manual_query_datasets = (config.get("dataset_configs")
and config["dataset_configs"].get("datasets", {}).get("datasets"))
if need_manual_query_datasets and app_mode == AppMode.COMPLETION:
# Only check when mode is completion
dataset_query_variable = config.get("dataset_query_variable")
if not dataset_query_variable:
raise ValueError("Dataset query variable is required when dataset is exist")
return config, ["agent_mode", "dataset_configs", "dataset_query_variable"]
@classmethod
def extract_dataset_config_for_legacy_compatibility(cls, tenant_id: str, app_mode: AppMode, config: dict) -> dict:
"""
Extract dataset config for legacy compatibility
:param tenant_id: tenant ID
:param app_mode: app mode
:param config: app model config args
"""
# Extract dataset config for legacy compatibility
if not config.get("agent_mode"):
config["agent_mode"] = {
"enabled": False,
"tools": []
}
if not isinstance(config["agent_mode"], dict):
raise ValueError("agent_mode must be of object type")
# enabled
if "enabled" not in config["agent_mode"] or not config["agent_mode"]["enabled"]:
config["agent_mode"]["enabled"] = False
if not isinstance(config["agent_mode"]["enabled"], bool):
raise ValueError("enabled in agent_mode must be of boolean type")
# tools
if not config["agent_mode"].get("tools"):
config["agent_mode"]["tools"] = []
if not isinstance(config["agent_mode"]["tools"], list):
raise ValueError("tools in agent_mode must be a list of objects")
# strategy
if not config["agent_mode"].get("strategy"):
config["agent_mode"]["strategy"] = PlanningStrategy.ROUTER.value
has_datasets = False
if config["agent_mode"]["strategy"] in [PlanningStrategy.ROUTER.value, PlanningStrategy.REACT_ROUTER.value]:
for tool in config["agent_mode"]["tools"]:
key = list(tool.keys())[0]
if key == "dataset":
# old style, use tool name as key
tool_item = tool[key]
if "enabled" not in tool_item or not tool_item["enabled"]:
tool_item["enabled"] = False
if not isinstance(tool_item["enabled"], bool):
raise ValueError("enabled in agent_mode.tools must be of boolean type")
if 'id' not in tool_item:
raise ValueError("id is required in dataset")
try:
uuid.UUID(tool_item["id"])
except ValueError:
raise ValueError("id in dataset must be of UUID type")
if not cls.is_dataset_exists(tenant_id, tool_item["id"]):
raise ValueError("Dataset ID does not exist, please check your permission.")
has_datasets = True
need_manual_query_datasets = has_datasets and config["agent_mode"]["enabled"]
if need_manual_query_datasets and app_mode == AppMode.COMPLETION:
# Only check when mode is completion
dataset_query_variable = config.get("dataset_query_variable")
if not dataset_query_variable:
raise ValueError("Dataset query variable is required when dataset is exist")
return config
@classmethod
def is_dataset_exists(cls, tenant_id: str, dataset_id: str) -> bool:
# verify if the dataset ID exists
dataset = DatasetService.get_dataset(dataset_id)
if not dataset:
return False
if dataset.tenant_id != tenant_id:
return False
return True

View File

@@ -0,0 +1,103 @@
from typing import cast
from core.app.app_config.entities import EasyUIBasedAppConfig
from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity
from core.entities.model_entities import ModelStatus
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.entities.model_entities import ModelType
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from core.provider_manager import ProviderManager
class ModelConfigConverter:
@classmethod
def convert(cls, app_config: EasyUIBasedAppConfig,
skip_check: bool = False) \
-> ModelConfigWithCredentialsEntity:
"""
Convert app model config dict to entity.
:param app_config: app config
:param skip_check: skip check
:raises ProviderTokenNotInitError: provider token not init error
:return: app orchestration config entity
"""
model_config = app_config.model
provider_manager = ProviderManager()
provider_model_bundle = provider_manager.get_provider_model_bundle(
tenant_id=app_config.tenant_id,
provider=model_config.provider,
model_type=ModelType.LLM
)
provider_name = provider_model_bundle.configuration.provider.provider
model_name = model_config.model
model_type_instance = provider_model_bundle.model_type_instance
model_type_instance = cast(LargeLanguageModel, model_type_instance)
# check model credentials
model_credentials = provider_model_bundle.configuration.get_current_credentials(
model_type=ModelType.LLM,
model=model_config.model
)
if model_credentials is None:
if not skip_check:
raise ProviderTokenNotInitError(f"Model {model_name} credentials is not initialized.")
else:
model_credentials = {}
if not skip_check:
# check model
provider_model = provider_model_bundle.configuration.get_provider_model(
model=model_config.model,
model_type=ModelType.LLM
)
if provider_model is None:
model_name = model_config.model
raise ValueError(f"Model {model_name} not exist.")
if provider_model.status == ModelStatus.NO_CONFIGURE:
raise ProviderTokenNotInitError(f"Model {model_name} credentials is not initialized.")
elif provider_model.status == ModelStatus.NO_PERMISSION:
raise ModelCurrentlyNotSupportError(f"Dify Hosted OpenAI {model_name} currently not support.")
elif provider_model.status == ModelStatus.QUOTA_EXCEEDED:
raise QuotaExceededError(f"Model provider {provider_name} quota exceeded.")
# model config
completion_params = model_config.parameters
stop = []
if 'stop' in completion_params:
stop = completion_params['stop']
del completion_params['stop']
# get model mode
model_mode = model_config.mode
if not model_mode:
mode_enum = model_type_instance.get_model_mode(
model=model_config.model,
credentials=model_credentials
)
model_mode = mode_enum.value
model_schema = model_type_instance.get_model_schema(
model_config.model,
model_credentials
)
if not skip_check and not model_schema:
raise ValueError(f"Model {model_name} not exist.")
return ModelConfigWithCredentialsEntity(
provider=model_config.provider,
model=model_config.model,
model_schema=model_schema,
mode=model_mode,
provider_model_bundle=provider_model_bundle,
credentials=model_credentials,
parameters=completion_params,
stop=stop,
)

View File

@@ -0,0 +1,112 @@
from core.app.app_config.entities import ModelConfigEntity
from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
from core.model_runtime.model_providers import model_provider_factory
from core.provider_manager import ProviderManager
class ModelConfigManager:
@classmethod
def convert(cls, config: dict) -> ModelConfigEntity:
"""
Convert model config to model config
:param config: model config args
"""
# model config
model_config = config.get('model')
if not model_config:
raise ValueError("model is required")
completion_params = model_config.get('completion_params')
stop = []
if 'stop' in completion_params:
stop = completion_params['stop']
del completion_params['stop']
# get model mode
model_mode = model_config.get('mode')
return ModelConfigEntity(
provider=config['model']['provider'],
model=config['model']['name'],
mode=model_mode,
parameters=completion_params,
stop=stop,
)
@classmethod
def validate_and_set_defaults(cls, tenant_id: str, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for model config
:param tenant_id: tenant id
:param config: app model config args
"""
if 'model' not in config:
raise ValueError("model is required")
if not isinstance(config["model"], dict):
raise ValueError("model must be of object type")
# model.provider
provider_entities = model_provider_factory.get_providers()
model_provider_names = [provider.provider for provider in provider_entities]
if 'provider' not in config["model"] or config["model"]["provider"] not in model_provider_names:
raise ValueError(f"model.provider is required and must be in {str(model_provider_names)}")
# model.name
if 'name' not in config["model"]:
raise ValueError("model.name is required")
provider_manager = ProviderManager()
models = provider_manager.get_configurations(tenant_id).get_models(
provider=config["model"]["provider"],
model_type=ModelType.LLM
)
if not models:
raise ValueError("model.name must be in the specified model list")
model_ids = [m.model for m in models]
if config["model"]["name"] not in model_ids:
raise ValueError("model.name must be in the specified model list")
model_mode = None
for model in models:
if model.model == config["model"]["name"]:
model_mode = model.model_properties.get(ModelPropertyKey.MODE)
break
# model.mode
if model_mode:
config['model']["mode"] = model_mode
else:
config['model']["mode"] = "completion"
# model.completion_params
if 'completion_params' not in config["model"]:
raise ValueError("model.completion_params is required")
config["model"]["completion_params"] = cls.validate_model_completion_params(
config["model"]["completion_params"]
)
return config, ["model"]
@classmethod
def validate_model_completion_params(cls, cp: dict) -> dict:
# model.completion_params
if not isinstance(cp, dict):
raise ValueError("model.completion_params must be of object type")
# stop
if 'stop' not in cp:
cp["stop"] = []
elif not isinstance(cp["stop"], list):
raise ValueError("stop in model.completion_params must be of list type")
if len(cp["stop"]) > 4:
raise ValueError("stop sequences must be less than 4")
return cp

View File

@@ -0,0 +1,140 @@
from core.app.app_config.entities import (
AdvancedChatPromptTemplateEntity,
AdvancedCompletionPromptTemplateEntity,
PromptTemplateEntity,
)
from core.model_runtime.entities.message_entities import PromptMessageRole
from core.prompt.simple_prompt_transform import ModelMode
from models.model import AppMode
class PromptTemplateConfigManager:
@classmethod
def convert(cls, config: dict) -> PromptTemplateEntity:
if not config.get("prompt_type"):
raise ValueError("prompt_type is required")
prompt_type = PromptTemplateEntity.PromptType.value_of(config['prompt_type'])
if prompt_type == PromptTemplateEntity.PromptType.SIMPLE:
simple_prompt_template = config.get("pre_prompt", "")
return PromptTemplateEntity(
prompt_type=prompt_type,
simple_prompt_template=simple_prompt_template
)
else:
advanced_chat_prompt_template = None
chat_prompt_config = config.get("chat_prompt_config", {})
if chat_prompt_config:
chat_prompt_messages = []
for message in chat_prompt_config.get("prompt", []):
chat_prompt_messages.append({
"text": message["text"],
"role": PromptMessageRole.value_of(message["role"])
})
advanced_chat_prompt_template = AdvancedChatPromptTemplateEntity(
messages=chat_prompt_messages
)
advanced_completion_prompt_template = None
completion_prompt_config = config.get("completion_prompt_config", {})
if completion_prompt_config:
completion_prompt_template_params = {
'prompt': completion_prompt_config['prompt']['text'],
}
if 'conversation_histories_role' in completion_prompt_config:
completion_prompt_template_params['role_prefix'] = {
'user': completion_prompt_config['conversation_histories_role']['user_prefix'],
'assistant': completion_prompt_config['conversation_histories_role']['assistant_prefix']
}
advanced_completion_prompt_template = AdvancedCompletionPromptTemplateEntity(
**completion_prompt_template_params
)
return PromptTemplateEntity(
prompt_type=prompt_type,
advanced_chat_prompt_template=advanced_chat_prompt_template,
advanced_completion_prompt_template=advanced_completion_prompt_template
)
@classmethod
def validate_and_set_defaults(cls, app_mode: AppMode, config: dict) -> tuple[dict, list[str]]:
"""
Validate pre_prompt and set defaults for prompt feature
depending on the config['model']
:param app_mode: app mode
:param config: app model config args
"""
if not config.get("prompt_type"):
config["prompt_type"] = PromptTemplateEntity.PromptType.SIMPLE.value
prompt_type_vals = [typ.value for typ in PromptTemplateEntity.PromptType]
if config['prompt_type'] not in prompt_type_vals:
raise ValueError(f"prompt_type must be in {prompt_type_vals}")
# chat_prompt_config
if not config.get("chat_prompt_config"):
config["chat_prompt_config"] = {}
if not isinstance(config["chat_prompt_config"], dict):
raise ValueError("chat_prompt_config must be of object type")
# completion_prompt_config
if not config.get("completion_prompt_config"):
config["completion_prompt_config"] = {}
if not isinstance(config["completion_prompt_config"], dict):
raise ValueError("completion_prompt_config must be of object type")
if config['prompt_type'] == PromptTemplateEntity.PromptType.ADVANCED.value:
if not config['chat_prompt_config'] and not config['completion_prompt_config']:
raise ValueError("chat_prompt_config or completion_prompt_config is required "
"when prompt_type is advanced")
model_mode_vals = [mode.value for mode in ModelMode]
if config['model']["mode"] not in model_mode_vals:
raise ValueError(f"model.mode must be in {model_mode_vals} when prompt_type is advanced")
if app_mode == AppMode.CHAT and config['model']["mode"] == ModelMode.COMPLETION.value:
user_prefix = config['completion_prompt_config']['conversation_histories_role']['user_prefix']
assistant_prefix = config['completion_prompt_config']['conversation_histories_role']['assistant_prefix']
if not user_prefix:
config['completion_prompt_config']['conversation_histories_role']['user_prefix'] = 'Human'
if not assistant_prefix:
config['completion_prompt_config']['conversation_histories_role']['assistant_prefix'] = 'Assistant'
if config['model']["mode"] == ModelMode.CHAT.value:
prompt_list = config['chat_prompt_config']['prompt']
if len(prompt_list) > 10:
raise ValueError("prompt messages must be less than 10")
else:
# pre_prompt, for simple mode
if not config.get("pre_prompt"):
config["pre_prompt"] = ""
if not isinstance(config["pre_prompt"], str):
raise ValueError("pre_prompt must be of string type")
return config, ["prompt_type", "pre_prompt", "chat_prompt_config", "completion_prompt_config"]
@classmethod
def validate_post_prompt_and_set_defaults(cls, config: dict) -> dict:
"""
Validate post_prompt and set defaults for prompt feature
:param config: app model config args
"""
# post_prompt
if not config.get("post_prompt"):
config["post_prompt"] = ""
if not isinstance(config["post_prompt"], str):
raise ValueError("post_prompt must be of string type")
return config

View File

@@ -0,0 +1,186 @@
import re
from core.app.app_config.entities import ExternalDataVariableEntity, VariableEntity
from core.external_data_tool.factory import ExternalDataToolFactory
class BasicVariablesConfigManager:
@classmethod
def convert(cls, config: dict) -> tuple[list[VariableEntity], list[ExternalDataVariableEntity]]:
"""
Convert model config to model config
:param config: model config args
"""
external_data_variables = []
variables = []
# old external_data_tools
external_data_tools = config.get('external_data_tools', [])
for external_data_tool in external_data_tools:
if 'enabled' not in external_data_tool or not external_data_tool['enabled']:
continue
external_data_variables.append(
ExternalDataVariableEntity(
variable=external_data_tool['variable'],
type=external_data_tool['type'],
config=external_data_tool['config']
)
)
# variables and external_data_tools
for variable in config.get('user_input_form', []):
typ = list(variable.keys())[0]
if typ == 'external_data_tool':
val = variable[typ]
if 'config' not in val:
continue
external_data_variables.append(
ExternalDataVariableEntity(
variable=val['variable'],
type=val['type'],
config=val['config']
)
)
elif typ in [
VariableEntity.Type.TEXT_INPUT.value,
VariableEntity.Type.PARAGRAPH.value,
VariableEntity.Type.NUMBER.value,
]:
variables.append(
VariableEntity(
type=VariableEntity.Type.value_of(typ),
variable=variable[typ].get('variable'),
description=variable[typ].get('description'),
label=variable[typ].get('label'),
required=variable[typ].get('required', False),
max_length=variable[typ].get('max_length'),
default=variable[typ].get('default'),
)
)
elif typ == VariableEntity.Type.SELECT.value:
variables.append(
VariableEntity(
type=VariableEntity.Type.SELECT,
variable=variable[typ].get('variable'),
description=variable[typ].get('description'),
label=variable[typ].get('label'),
required=variable[typ].get('required', False),
options=variable[typ].get('options'),
default=variable[typ].get('default'),
)
)
return variables, external_data_variables
@classmethod
def validate_and_set_defaults(cls, tenant_id: str, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for user input form
:param tenant_id: workspace id
:param config: app model config args
"""
related_config_keys = []
config, current_related_config_keys = cls.validate_variables_and_set_defaults(config)
related_config_keys.extend(current_related_config_keys)
config, current_related_config_keys = cls.validate_external_data_tools_and_set_defaults(tenant_id, config)
related_config_keys.extend(current_related_config_keys)
return config, related_config_keys
@classmethod
def validate_variables_and_set_defaults(cls, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for user input form
:param config: app model config args
"""
if not config.get("user_input_form"):
config["user_input_form"] = []
if not isinstance(config["user_input_form"], list):
raise ValueError("user_input_form must be a list of objects")
variables = []
for item in config["user_input_form"]:
key = list(item.keys())[0]
if key not in ["text-input", "select", "paragraph", "number", "external_data_tool"]:
raise ValueError("Keys in user_input_form list can only be 'text-input', 'paragraph' or 'select'")
form_item = item[key]
if 'label' not in form_item:
raise ValueError("label is required in user_input_form")
if not isinstance(form_item["label"], str):
raise ValueError("label in user_input_form must be of string type")
if 'variable' not in form_item:
raise ValueError("variable is required in user_input_form")
if not isinstance(form_item["variable"], str):
raise ValueError("variable in user_input_form must be of string type")
pattern = re.compile(r"^(?!\d)[\u4e00-\u9fa5A-Za-z0-9_\U0001F300-\U0001F64F\U0001F680-\U0001F6FF]{1,100}$")
if pattern.match(form_item["variable"]) is None:
raise ValueError("variable in user_input_form must be a string, "
"and cannot start with a number")
variables.append(form_item["variable"])
if 'required' not in form_item or not form_item["required"]:
form_item["required"] = False
if not isinstance(form_item["required"], bool):
raise ValueError("required in user_input_form must be of boolean type")
if key == "select":
if 'options' not in form_item or not form_item["options"]:
form_item["options"] = []
if not isinstance(form_item["options"], list):
raise ValueError("options in user_input_form must be a list of strings")
if "default" in form_item and form_item['default'] \
and form_item["default"] not in form_item["options"]:
raise ValueError("default value in user_input_form must be in the options list")
return config, ["user_input_form"]
@classmethod
def validate_external_data_tools_and_set_defaults(cls, tenant_id: str, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for external data fetch feature
:param tenant_id: workspace id
:param config: app model config args
"""
if not config.get("external_data_tools"):
config["external_data_tools"] = []
if not isinstance(config["external_data_tools"], list):
raise ValueError("external_data_tools must be of list type")
for tool in config["external_data_tools"]:
if "enabled" not in tool or not tool["enabled"]:
tool["enabled"] = False
if not tool["enabled"]:
continue
if "type" not in tool or not tool["type"]:
raise ValueError("external_data_tools[].type is required")
typ = tool["type"]
config = tool["config"]
ExternalDataToolFactory.validate_config(
name=typ,
tenant_id=tenant_id,
config=config
)
return config, ["external_data_tools"]

View File

@@ -1,12 +1,10 @@
from enum import Enum
from typing import Any, Literal, Optional, Union
from typing import Any, Optional
from pydantic import BaseModel
from core.entities.provider_configuration import ProviderModelBundle
from core.file.file_obj import FileObj
from core.model_runtime.entities.message_entities import PromptMessageRole
from core.model_runtime.entities.model_entities import AIModelEntity
from models.model import AppMode
class ModelConfigEntity(BaseModel):
@@ -15,10 +13,7 @@ class ModelConfigEntity(BaseModel):
"""
provider: str
model: str
model_schema: AIModelEntity
mode: str
provider_model_bundle: ProviderModelBundle
credentials: dict[str, Any] = {}
mode: Optional[str] = None
parameters: dict[str, Any] = {}
stop: list[str] = []
@@ -86,6 +81,40 @@ class PromptTemplateEntity(BaseModel):
advanced_completion_prompt_template: Optional[AdvancedCompletionPromptTemplateEntity] = None
class VariableEntity(BaseModel):
"""
Variable Entity.
"""
class Type(Enum):
TEXT_INPUT = 'text-input'
SELECT = 'select'
PARAGRAPH = 'paragraph'
NUMBER = 'number'
@classmethod
def value_of(cls, value: str) -> 'VariableEntity.Type':
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for mode in cls:
if mode.value == value:
return mode
raise ValueError(f'invalid variable type value {value}')
variable: str
label: str
description: Optional[str] = None
type: Type
required: bool = False
max_length: Optional[int] = None
options: Optional[list[str]] = None
default: Optional[str] = None
hint: Optional[str] = None
class ExternalDataVariableEntity(BaseModel):
"""
External Data Variable Entity.
@@ -124,7 +153,6 @@ class DatasetRetrieveConfigEntity(BaseModel):
query_variable: Optional[str] = None # Only when app mode is completion
retrieve_strategy: RetrieveStrategy
single_strategy: Optional[str] = None # for temp
top_k: Optional[int] = None
score_threshold: Optional[float] = None
reranking_model: Optional[dict] = None
@@ -155,155 +183,60 @@ class TextToSpeechEntity(BaseModel):
language: Optional[str] = None
class FileUploadEntity(BaseModel):
class FileExtraConfig(BaseModel):
"""
File Upload Entity.
"""
image_config: Optional[dict[str, Any]] = None
class AgentToolEntity(BaseModel):
"""
Agent Tool Entity.
"""
provider_type: Literal["builtin", "api"]
provider_id: str
tool_name: str
tool_parameters: dict[str, Any] = {}
class AgentPromptEntity(BaseModel):
"""
Agent Prompt Entity.
"""
first_prompt: str
next_iteration: str
class AgentScratchpadUnit(BaseModel):
"""
Agent First Prompt Entity.
"""
class Action(BaseModel):
"""
Action Entity.
"""
action_name: str
action_input: Union[dict, str]
agent_response: Optional[str] = None
thought: Optional[str] = None
action_str: Optional[str] = None
observation: Optional[str] = None
action: Optional[Action] = None
class AgentEntity(BaseModel):
"""
Agent Entity.
"""
class Strategy(Enum):
"""
Agent Strategy.
"""
CHAIN_OF_THOUGHT = 'chain-of-thought'
FUNCTION_CALLING = 'function-calling'
provider: str
model: str
strategy: Strategy
prompt: Optional[AgentPromptEntity] = None
tools: list[AgentToolEntity] = None
max_iteration: int = 5
class AppOrchestrationConfigEntity(BaseModel):
"""
App Orchestration Config Entity.
"""
model_config: ModelConfigEntity
prompt_template: PromptTemplateEntity
external_data_variables: list[ExternalDataVariableEntity] = []
agent: Optional[AgentEntity] = None
# features
dataset: Optional[DatasetEntity] = None
file_upload: Optional[FileUploadEntity] = None
class AppAdditionalFeatures(BaseModel):
file_upload: Optional[FileExtraConfig] = None
opening_statement: Optional[str] = None
suggested_questions: list[str] = []
suggested_questions_after_answer: bool = False
show_retrieve_source: bool = False
more_like_this: bool = False
speech_to_text: bool = False
text_to_speech: dict = {}
text_to_speech: Optional[TextToSpeechEntity] = None
class AppConfig(BaseModel):
"""
Application Config Entity.
"""
tenant_id: str
app_id: str
app_mode: AppMode
additional_features: AppAdditionalFeatures
variables: list[VariableEntity] = []
sensitive_word_avoidance: Optional[SensitiveWordAvoidanceEntity] = None
class InvokeFrom(Enum):
class EasyUIBasedAppModelConfigFrom(Enum):
"""
Invoke From.
App Model Config From.
"""
SERVICE_API = 'service-api'
WEB_APP = 'web-app'
EXPLORE = 'explore'
DEBUGGER = 'debugger'
@classmethod
def value_of(cls, value: str) -> 'InvokeFrom':
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for mode in cls:
if mode.value == value:
return mode
raise ValueError(f'invalid invoke from value {value}')
def to_source(self) -> str:
"""
Get source of invoke from.
:return: source
"""
if self == InvokeFrom.WEB_APP:
return 'web_app'
elif self == InvokeFrom.DEBUGGER:
return 'dev'
elif self == InvokeFrom.EXPLORE:
return 'explore_app'
elif self == InvokeFrom.SERVICE_API:
return 'api'
return 'dev'
ARGS = 'args'
APP_LATEST_CONFIG = 'app-latest-config'
CONVERSATION_SPECIFIC_CONFIG = 'conversation-specific-config'
class ApplicationGenerateEntity(BaseModel):
class EasyUIBasedAppConfig(AppConfig):
"""
Application Generate Entity.
Easy UI Based App Config Entity.
"""
task_id: str
tenant_id: str
app_id: str
app_model_config_from: EasyUIBasedAppModelConfigFrom
app_model_config_id: str
# for save
app_model_config_dict: dict
app_model_config_override: bool
model: ModelConfigEntity
prompt_template: PromptTemplateEntity
dataset: Optional[DatasetEntity] = None
external_data_variables: list[ExternalDataVariableEntity] = []
# Converted from app_model_config to Entity object, or directly covered by external input
app_orchestration_config_entity: AppOrchestrationConfigEntity
conversation_id: Optional[str] = None
inputs: dict[str, str]
query: Optional[str] = None
files: list[FileObj] = []
user_id: str
# extras
stream: bool
invoke_from: InvokeFrom
# extra parameters, like: auto_generate_conversation_name
extras: dict[str, Any] = {}
class WorkflowUIBasedAppConfig(AppConfig):
"""
Workflow UI Based App Config Entity.
"""
workflow_id: str

View File

@@ -0,0 +1,68 @@
from typing import Optional
from core.app.app_config.entities import FileExtraConfig
class FileUploadConfigManager:
@classmethod
def convert(cls, config: dict, is_vision: bool = True) -> Optional[FileExtraConfig]:
"""
Convert model config to model config
:param config: model config args
:param is_vision: if True, the feature is vision feature
"""
file_upload_dict = config.get('file_upload')
if file_upload_dict:
if 'image' in file_upload_dict and file_upload_dict['image']:
if 'enabled' in file_upload_dict['image'] and file_upload_dict['image']['enabled']:
image_config = {
'number_limits': file_upload_dict['image']['number_limits'],
'transfer_methods': file_upload_dict['image']['transfer_methods']
}
if is_vision:
image_config['detail'] = file_upload_dict['image']['detail']
return FileExtraConfig(
image_config=image_config
)
return None
@classmethod
def validate_and_set_defaults(cls, config: dict, is_vision: bool = True) -> tuple[dict, list[str]]:
"""
Validate and set defaults for file upload feature
:param config: app model config args
:param is_vision: if True, the feature is vision feature
"""
if not config.get("file_upload"):
config["file_upload"] = {}
if not isinstance(config["file_upload"], dict):
raise ValueError("file_upload must be of dict type")
# check image config
if not config["file_upload"].get("image"):
config["file_upload"]["image"] = {"enabled": False}
if config['file_upload']['image']['enabled']:
number_limits = config['file_upload']['image']['number_limits']
if number_limits < 1 or number_limits > 6:
raise ValueError("number_limits must be in [1, 6]")
if is_vision:
detail = config['file_upload']['image']['detail']
if detail not in ['high', 'low']:
raise ValueError("detail must be in ['high', 'low']")
transfer_methods = config['file_upload']['image']['transfer_methods']
if not isinstance(transfer_methods, list):
raise ValueError("transfer_methods must be of list type")
for method in transfer_methods:
if method not in ['remote_url', 'local_file']:
raise ValueError("transfer_methods must be in ['remote_url', 'local_file']")
return config, ["file_upload"]

View File

@@ -0,0 +1,38 @@
class MoreLikeThisConfigManager:
@classmethod
def convert(cls, config: dict) -> bool:
"""
Convert model config to model config
:param config: model config args
"""
more_like_this = False
more_like_this_dict = config.get('more_like_this')
if more_like_this_dict:
if 'enabled' in more_like_this_dict and more_like_this_dict['enabled']:
more_like_this = True
return more_like_this
@classmethod
def validate_and_set_defaults(cls, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for more like this feature
:param config: app model config args
"""
if not config.get("more_like_this"):
config["more_like_this"] = {
"enabled": False
}
if not isinstance(config["more_like_this"], dict):
raise ValueError("more_like_this must be of dict type")
if "enabled" not in config["more_like_this"] or not config["more_like_this"]["enabled"]:
config["more_like_this"]["enabled"] = False
if not isinstance(config["more_like_this"]["enabled"], bool):
raise ValueError("enabled in more_like_this must be of boolean type")
return config, ["more_like_this"]

View File

@@ -0,0 +1,43 @@
class OpeningStatementConfigManager:
@classmethod
def convert(cls, config: dict) -> tuple[str, list]:
"""
Convert model config to model config
:param config: model config args
"""
# opening statement
opening_statement = config.get('opening_statement')
# suggested questions
suggested_questions_list = config.get('suggested_questions')
return opening_statement, suggested_questions_list
@classmethod
def validate_and_set_defaults(cls, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for opening statement feature
:param config: app model config args
"""
if not config.get("opening_statement"):
config["opening_statement"] = ""
if not isinstance(config["opening_statement"], str):
raise ValueError("opening_statement must be of string type")
# suggested_questions
if not config.get("suggested_questions"):
config["suggested_questions"] = []
if not isinstance(config["suggested_questions"], list):
raise ValueError("suggested_questions must be of list type")
for question in config["suggested_questions"]:
if not isinstance(question, str):
raise ValueError("Elements in suggested_questions list must be of string type")
return config, ["opening_statement", "suggested_questions"]

View File

@@ -0,0 +1,33 @@
class RetrievalResourceConfigManager:
@classmethod
def convert(cls, config: dict) -> bool:
show_retrieve_source = False
retriever_resource_dict = config.get('retriever_resource')
if retriever_resource_dict:
if 'enabled' in retriever_resource_dict and retriever_resource_dict['enabled']:
show_retrieve_source = True
return show_retrieve_source
@classmethod
def validate_and_set_defaults(cls, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for retriever resource feature
:param config: app model config args
"""
if not config.get("retriever_resource"):
config["retriever_resource"] = {
"enabled": False
}
if not isinstance(config["retriever_resource"], dict):
raise ValueError("retriever_resource must be of dict type")
if "enabled" not in config["retriever_resource"] or not config["retriever_resource"]["enabled"]:
config["retriever_resource"]["enabled"] = False
if not isinstance(config["retriever_resource"]["enabled"], bool):
raise ValueError("enabled in retriever_resource must be of boolean type")
return config, ["retriever_resource"]

View File

@@ -0,0 +1,38 @@
class SpeechToTextConfigManager:
@classmethod
def convert(cls, config: dict) -> bool:
"""
Convert model config to model config
:param config: model config args
"""
speech_to_text = False
speech_to_text_dict = config.get('speech_to_text')
if speech_to_text_dict:
if 'enabled' in speech_to_text_dict and speech_to_text_dict['enabled']:
speech_to_text = True
return speech_to_text
@classmethod
def validate_and_set_defaults(cls, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for speech to text feature
:param config: app model config args
"""
if not config.get("speech_to_text"):
config["speech_to_text"] = {
"enabled": False
}
if not isinstance(config["speech_to_text"], dict):
raise ValueError("speech_to_text must be of dict type")
if "enabled" not in config["speech_to_text"] or not config["speech_to_text"]["enabled"]:
config["speech_to_text"]["enabled"] = False
if not isinstance(config["speech_to_text"]["enabled"], bool):
raise ValueError("enabled in speech_to_text must be of boolean type")
return config, ["speech_to_text"]

View File

@@ -0,0 +1,39 @@
class SuggestedQuestionsAfterAnswerConfigManager:
@classmethod
def convert(cls, config: dict) -> bool:
"""
Convert model config to model config
:param config: model config args
"""
suggested_questions_after_answer = False
suggested_questions_after_answer_dict = config.get('suggested_questions_after_answer')
if suggested_questions_after_answer_dict:
if 'enabled' in suggested_questions_after_answer_dict and suggested_questions_after_answer_dict['enabled']:
suggested_questions_after_answer = True
return suggested_questions_after_answer
@classmethod
def validate_and_set_defaults(cls, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for suggested questions feature
:param config: app model config args
"""
if not config.get("suggested_questions_after_answer"):
config["suggested_questions_after_answer"] = {
"enabled": False
}
if not isinstance(config["suggested_questions_after_answer"], dict):
raise ValueError("suggested_questions_after_answer must be of dict type")
if "enabled" not in config["suggested_questions_after_answer"] or not \
config["suggested_questions_after_answer"]["enabled"]:
config["suggested_questions_after_answer"]["enabled"] = False
if not isinstance(config["suggested_questions_after_answer"]["enabled"], bool):
raise ValueError("enabled in suggested_questions_after_answer must be of boolean type")
return config, ["suggested_questions_after_answer"]

View File

@@ -0,0 +1,49 @@
from core.app.app_config.entities import TextToSpeechEntity
class TextToSpeechConfigManager:
@classmethod
def convert(cls, config: dict) -> bool:
"""
Convert model config to model config
:param config: model config args
"""
text_to_speech = False
text_to_speech_dict = config.get('text_to_speech')
if text_to_speech_dict:
if 'enabled' in text_to_speech_dict and text_to_speech_dict['enabled']:
text_to_speech = TextToSpeechEntity(
enabled=text_to_speech_dict.get('enabled'),
voice=text_to_speech_dict.get('voice'),
language=text_to_speech_dict.get('language'),
)
return text_to_speech
@classmethod
def validate_and_set_defaults(cls, config: dict) -> tuple[dict, list[str]]:
"""
Validate and set defaults for text to speech feature
:param config: app model config args
"""
if not config.get("text_to_speech"):
config["text_to_speech"] = {
"enabled": False,
"voice": "",
"language": ""
}
if not isinstance(config["text_to_speech"], dict):
raise ValueError("text_to_speech must be of dict type")
if "enabled" not in config["text_to_speech"] or not config["text_to_speech"]["enabled"]:
config["text_to_speech"]["enabled"] = False
config["text_to_speech"]["voice"] = ""
config["text_to_speech"]["language"] = ""
if not isinstance(config["text_to_speech"]["enabled"], bool):
raise ValueError("enabled in text_to_speech must be of boolean type")
return config, ["text_to_speech"]

View File

@@ -0,0 +1,22 @@
from core.app.app_config.entities import VariableEntity
from models.workflow import Workflow
class WorkflowVariablesConfigManager:
@classmethod
def convert(cls, workflow: Workflow) -> list[VariableEntity]:
"""
Convert workflow start variables to variables
:param workflow: workflow instance
"""
variables = []
# find start node
user_input_form = workflow.user_input_form()
# variables
for variable in user_input_form:
variables.append(VariableEntity(**variable))
return variables

Some files were not shown because too many files have changed in this diff Show More