AgentConfig.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. <template>
  2. <a-modal
  3. v-model:visible="visible"
  4. title="智能体配置"
  5. @before-open="handleOpened"
  6. @cancel="handleCancel"
  7. :ok-loading="loading"
  8. @before-ok="handleBeforeOk"
  9. :footer="true"
  10. title-align="start"
  11. width="600px"
  12. >
  13. <a-row class="">
  14. <a-col :span="24">
  15. <a-tabs type="capsule" size="large" :active-key="activeKey" @change="handleTabChange">
  16. <a-tab-pane key="1" title="助理设置">
  17. <a-divider style="margin-bottom: 20px; margin-top: 0" />
  18. <div style="width: 100%" :style="{ height: height }">
  19. <a-form
  20. ref="formRef"
  21. :rules="rules"
  22. :model="form"
  23. @submit="handleSubmit"
  24. :style="{ width: '90%', margin: '0 auto' }"
  25. >
  26. <a-form-item field="name" label="智能体名称">
  27. <a-input v-model="form.name" placeholder="请输入名称" />
  28. </a-form-item>
  29. <a-form-item label="智能体图标">
  30. <a-space direction="vertical" :style="{ width: '100%' }">
  31. <Upload
  32. v-if="avatarShow"
  33. :action="uploadAction"
  34. :limit="1"
  35. :url="form.icon ? httpUrl + form.icon : ''"
  36. @update:fileList="updateFileList"
  37. @success="handleSuccess"
  38. ></Upload>
  39. </a-space>
  40. </a-form-item>
  41. <a-form-item label="空回复">
  42. <a-input v-model="form.prompt_config.empty_response" placeholder="" />
  43. </a-form-item>
  44. <a-form-item label="设置开场白">
  45. <a-textarea v-model="form.prompt_config.prologue" placeholder="" style="height: 100px" />
  46. </a-form-item>
  47. <a-form-item label="显示引文">
  48. <a-space direction="vertical">
  49. <a-switch v-model="form.prompt_config.quote" size="small" />
  50. </a-space>
  51. </a-form-item>
  52. <a-form-item label="Self-RAG">
  53. <a-space direction="vertical">
  54. <a-switch v-model="form.prompt_config.self_rag" size="small" />
  55. </a-space>
  56. </a-form-item>
  57. <a-form-item field="kb_ids" label="知识库">
  58. <a-select v-model="form.kb_ids" placeholder="请选择 ..." multiple>
  59. <a-option v-for="item in tabs" :key="item.id" :value="item.id">
  60. {{ item.name }}
  61. </a-option>
  62. </a-select>
  63. </a-form-item>
  64. </a-form>
  65. </div>
  66. </a-tab-pane>
  67. <a-tab-pane key="2" title="提示引擎">
  68. <a-divider style="margin-bottom: 20px; margin-top: 0" />
  69. <a-scrollbar style="width: 100%; overflow: auto" :style="{ height: height }">
  70. <a-form
  71. ref="formRef1"
  72. :rules="rules"
  73. :model="form"
  74. @submit="handleSubmit"
  75. :style="{ width: '90%', margin: '0 auto' }"
  76. >
  77. <a-form-item field="prompt_config.system" label="系统">
  78. <a-textarea v-model="form.prompt_config.system" placeholder="" style="height: 180px" />
  79. </a-form-item>
  80. <a-divider style="margin: 0; margin-bottom: 10px" />
  81. <a-form-item label="相似度阈值">
  82. <a-slider v-model="form.similarity_threshold" :step="0.01" :min="0" :max="1" />
  83. </a-form-item>
  84. <a-form-item label="关键字相似度权重">
  85. <a-slider v-model="form.vector_similarity_weight" :step="0.01" :min="0" :max="1" />
  86. </a-form-item>
  87. <a-form-item label="Top N">
  88. <a-slider v-model="form.top_n" :min="0" :max="30" />
  89. </a-form-item>
  90. <a-form-item label="Rerank模型">
  91. <a-space direction="vertical" size="large">
  92. <a-select :size="'large'" v-model="form.rerank_id" placeholder="请选择 ..." allow-clear>
  93. <a-optgroup :label="index" v-for="(item, index) in rankModelList" :key="index">
  94. <a-option v-for="obj in item" :key="obj.fid" :disabled="!obj.available" :value="obj.llm_name">
  95. {{ obj.llm_name }}
  96. </a-option>
  97. </a-optgroup>
  98. </a-select>
  99. </a-space>
  100. </a-form-item>
  101. <a-form-item v-if="form.rerank_id" label="Top-K">
  102. <a-slider v-model="form.top_k" :min="1" :max="2048" />
  103. </a-form-item>
  104. <a-form-item label="变量">
  105. <div style="width: 100%; text-align: right">
  106. <a-button type="outline" @click="addVariable" style="" size="mini" shape="round"> 新建 </a-button>
  107. </div>
  108. </a-form-item>
  109. <a-form-item label="">
  110. <a-table :columns="columns" :data="form.prompt_config.parameters" style="width: 100%">
  111. <template #name="{ record, rowIndex }">
  112. <a-input v-model="record.name" placeholder="" />
  113. <!-- <a-input v-model="form.prompt_config.parameters[rowIndex].key" />-->
  114. </template>
  115. <template #optional="{ record }">
  116. <a-switch v-model="record.optional" size="small" />
  117. </template>
  118. <template #action="{ record }">
  119. <a-button type="text" @click="deleteParameters(record)" style="margin-left: 0" size="small">
  120. <template #icon>
  121. <icon-delete />
  122. </template>
  123. </a-button>
  124. </template>
  125. </a-table>
  126. </a-form-item>
  127. </a-form>
  128. </a-scrollbar>
  129. </a-tab-pane>
  130. <a-tab-pane key="3" title="模型设置">
  131. <a-divider style="margin-bottom: 20px; margin-top: 0" />
  132. <div style="width: 100%" :style="{ height: height }">
  133. <a-form
  134. ref="formRef2"
  135. :rules="rules"
  136. :model="form"
  137. @submit="handleSubmit"
  138. :style="{ width: '90%', margin: '0 auto' }"
  139. >
  140. <a-form-item field="llm_id" label="模型">
  141. <a-space direction="vertical" size="large">
  142. <a-select
  143. :size="'large'"
  144. field="llm_id"
  145. v-model="form.llm_id"
  146. style="width: 400px"
  147. placeholder="请选择"
  148. >
  149. <template v-for="(item, index) in modelList" :key="index">
  150. <a-optgroup :label="index" v-if="item.length > 0">
  151. <a-option v-for="obj in item" :key="obj.fid" :value="obj.llm_name">
  152. {{ obj.llm_name }}
  153. </a-option>
  154. </a-optgroup>
  155. </template>
  156. </a-select>
  157. </a-space>
  158. </a-form-item>
  159. <a-divider style="margin-bottom: 20px; margin-top: 0" />
  160. <a-form-item label="自由">
  161. <a-select default-value="2" :style="{ width: '400px' }" placeholder="">
  162. <a-option value="1">即兴创作</a-option>
  163. <a-option value="2">精确</a-option>
  164. <a-option value="3">平衡</a-option>
  165. </a-select>
  166. </a-form-item>
  167. <a-form-item label="温度">
  168. <a-switch size="small" v-model="temperature" />
  169. <a-space direction="vertical" size="large">
  170. <a-slider
  171. :disabled="!temperature"
  172. v-model="form.llm_setting.temperature"
  173. :step="0.01"
  174. :min="0"
  175. :max="1"
  176. :style="{
  177. width: '350px',
  178. marginLeft: '20px',
  179. display: 'flex'
  180. }"
  181. show-input
  182. />
  183. </a-space>
  184. </a-form-item>
  185. <a-form-item label="top P">
  186. <a-switch size="small" v-model="top_p" />
  187. <a-space direction="vertical" size="large">
  188. <a-slider
  189. :disabled="!top_p"
  190. v-model="form.llm_setting.top_p"
  191. :step="0.01"
  192. :min="0"
  193. :max="1"
  194. :style="{
  195. width: '350px',
  196. marginLeft: '20px',
  197. display: 'flex'
  198. }"
  199. show-input
  200. />
  201. </a-space>
  202. </a-form-item>
  203. <a-form-item label="出席处罚">
  204. <a-switch size="small" v-model="presence_penalty" />
  205. <a-space direction="vertical" size="large">
  206. <a-slider
  207. :disabled="!presence_penalty"
  208. v-model="form.llm_setting.presence_penalty"
  209. :step="0.01"
  210. :min="0"
  211. :max="1"
  212. :style="{
  213. width: '350px',
  214. marginLeft: '20px',
  215. display: 'flex'
  216. }"
  217. show-input
  218. />
  219. </a-space>
  220. </a-form-item>
  221. <a-form-item label="频率处罚">
  222. <a-switch size="small" v-model="frequency_penalty" />
  223. <a-space direction="vertical" size="large">
  224. <a-slider
  225. :disabled="!frequency_penalty"
  226. v-model="form.llm_setting.frequency_penalty"
  227. :step="0.01"
  228. :min="0"
  229. :max="1"
  230. :style="{
  231. width: '350px',
  232. marginLeft: '20px',
  233. display: 'flex'
  234. }"
  235. show-input
  236. />
  237. </a-space>
  238. </a-form-item>
  239. <a-form-item label="最大token数">
  240. <a-switch size="small" v-model="max_tokens" />
  241. <a-space direction="vertical" size="large">
  242. <a-slider
  243. :disabled="!max_tokens"
  244. v-model="form.llm_setting.max_tokens"
  245. :min="0"
  246. :max="2048"
  247. :style="{
  248. width: '350px',
  249. marginLeft: '20px',
  250. display: 'flex'
  251. }"
  252. show-input
  253. />
  254. </a-space>
  255. </a-form-item>
  256. </a-form>
  257. </div>
  258. </a-tab-pane>
  259. </a-tabs>
  260. </a-col>
  261. </a-row>
  262. </a-modal>
  263. </template>
  264. <script setup>
  265. import { onMounted, onBeforeMount, reactive, ref, nextTick, onUnmounted, onBeforeUnmount, computed } from 'vue';
  266. import { queryKbList, queryModelList, createRgFlow } from '../../../apis/rgflow-dify';
  267. import { createDialog } from '../../../apis/intelligent';
  268. import { getKnowledgeList } from '../../../apis/knowledge';
  269. import { dialogType } from '../../../views/manage/common';
  270. import useLoading from './loading';
  271. import Upload from './Upload.vue';
  272. import { Message } from '@arco-design/web-vue';
  273. // import { dialogSet } from '@/api/Agent';
  274. const props = defineProps(['show_rg_config', 'typeAngint', 'formData']);
  275. const emit = defineEmits(['queryList', 'update:show_rg_config', 'updateIntelligentList']);
  276. const { loading, setLoading } = useLoading(true);
  277. const visible = computed(() => props.show_rg_config);
  278. const modelList = ref({});
  279. const rankModelList = ref({});
  280. let tabs = ref([]);
  281. const file = ref();
  282. const formRef = ref();
  283. const formRef1 = ref();
  284. const formRef2 = ref();
  285. const activeKey = ref('1');
  286. const form = reactive({
  287. name: '',
  288. icon: '',
  289. language: 'English',
  290. prompt_config: {
  291. empty_response: '',
  292. prologue: '你好! 我是你的助理,有什么可以帮到你的吗?',
  293. quote: true,
  294. self_rag: false,
  295. system:
  296. '你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。' +
  297. '回答需要考虑聊天历史。\n 以下是知识库:\n {knowledge}\n 以上是知识库。',
  298. parameters: [
  299. {
  300. index: 0,
  301. name: 'knowledge',
  302. key: 'knowledge',
  303. optional: false
  304. }
  305. ]
  306. },
  307. kb_ids: [],
  308. llm_id: undefined,
  309. llm_setting: {
  310. temperature: 0.1,
  311. top_p: 0.3,
  312. presence_penalty: 0.4,
  313. frequency_penalty: 0.7,
  314. max_tokens: 512
  315. },
  316. similarity_threshold: 0.2,
  317. vector_similarity_weight: 0.30000000000000004,
  318. top_n: 8,
  319. rerank_id: '',
  320. top_k: 1024
  321. });
  322. let temperature = ref(true);
  323. let top_p = ref(true);
  324. let presence_penalty = ref(true);
  325. let frequency_penalty = ref(true);
  326. let max_tokens = ref(true);
  327. const httpUrl = localStorage.getItem('httpUrl');
  328. let avatarShow = ref(true);
  329. const height = ref('calc(500px)');
  330. const columns = [
  331. {
  332. title: '关键字',
  333. dataIndex: 'name',
  334. slotName: 'name'
  335. },
  336. {
  337. title: '可选',
  338. dataIndex: 'optional',
  339. slotName: 'optional'
  340. },
  341. {
  342. title: '操作',
  343. dataIndex: 'action',
  344. slotName: 'action'
  345. }
  346. ];
  347. const formatter = (value) => {
  348. return `${(value / 100).toFixed(2)}`;
  349. };
  350. const uploadAction = '/api/v1/llm/upload'; // 替换为你的上传API
  351. const fileList = ref([]);
  352. const imageUrls = ref([]);
  353. const updateFileList = (newFileList) => {
  354. fileList.value = newFileList;
  355. console.log(newFileList, 88);
  356. };
  357. const handleSuccess = (urls) => {
  358. imageUrls.value = urls; // 拿到上传的图片地址
  359. form.icon = urls[0];
  360. };
  361. const rules = {
  362. name: [
  363. {
  364. required: true,
  365. message: '名称不允许为空'
  366. }
  367. ],
  368. 'prompt_config.system': [
  369. {
  370. required: true,
  371. message: '系统不允许为空'
  372. }
  373. ],
  374. llm_id: [
  375. {
  376. required: true,
  377. message: '模型不能为空'
  378. }
  379. ],
  380. kb_ids: [
  381. {
  382. required: true,
  383. message: '知识库不能为空'
  384. }
  385. ]
  386. };
  387. const handleSubmit = ({ values, errors }) => {
  388. console.log('values:', values, '\nerrors:', errors);
  389. if (!errors) {
  390. }
  391. };
  392. const handleCancel = () => {
  393. formRef.value.resetFields();
  394. formRef1.value.resetFields();
  395. formRef2.value.resetFields();
  396. form.name = '';
  397. emit('update:show_rg_config', false);
  398. emit('queryList');
  399. };
  400. const handleOpened = (el) => {
  401. // Object.assign(form,{
  402. // name: '',// 用户名
  403. // nameJoin: '',// 昵称
  404. // post: '',// 岗位
  405. // txt: '',// 备注
  406. // });
  407. activeKey.value = '1';
  408. formRef.value.resetFields();
  409. formRef1.value.resetFields();
  410. formRef2.value.resetFields();
  411. nextTick(() => {
  412. if (props.typeAngint == 'add') {
  413. form.name = '';
  414. form.icon = '';
  415. form.prompt_config.system =
  416. '你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。' +
  417. '回答需要考虑聊天历史。\n 以下是知识库:\n {knowledge}\n 以上是知识库。';
  418. }
  419. // console.log(form, '新建智能体弹窗');
  420. avatarShow.value = false;
  421. setTimeout(() => {
  422. avatarShow.value = true;
  423. }, 100);
  424. });
  425. };
  426. const createOwnDialog = async (data, title) => {
  427. console.log(data, 'data');
  428. const res = await createDialog(data);
  429. if (res.code == 200) {
  430. Message.success(title);
  431. handleCancel();
  432. emit('updateIntelligentList');
  433. }
  434. };
  435. const handleBeforeOk = async (done) => {
  436. formRef.value.validate().then((res) => {
  437. // console.log('res:', res)
  438. });
  439. formRef1.value.validate().then((res) => {
  440. // console.log('res:', res)
  441. });
  442. formRef2.value.validate().then((res) => {
  443. // console.log('res:', res)
  444. });
  445. // TODO: 先写死知识库和模型
  446. // form.kb_ids = ['5e96cc5aad3111ef93990242ac130006'];
  447. form.llm_id = 'qwen-2.5-72b-instruct___OpenAI-API@OpenAI-API-Compatible';
  448. if (form.name && form.kb_ids.length > 0 && form.prompt_config.system && form.llm_id) {
  449. let title = '创建成功';
  450. let formNew = { ...form };
  451. if (formNew.rerank_id) {
  452. delete formNew.top_k;
  453. }
  454. if (formNew.rerank_id) {
  455. delete formNew.top_k;
  456. }
  457. formNew.prompt_config.parameters = form.prompt_config.parameters.map((item) => {
  458. return {
  459. ...item,
  460. key: item.name
  461. };
  462. });
  463. if (props.typeAngint == 'edit') {
  464. formNew.dialog_id = form.id;
  465. delete formNew.id;
  466. delete formNew.off;
  467. title = '修改成功';
  468. }
  469. if (!max_tokens.value) {
  470. //formNew.llm_setting.max_tokens = '';
  471. delete formNew.llm_setting.max_tokens;
  472. }
  473. setLoading(true);
  474. try {
  475. const res = await createRgFlow(formNew);
  476. if (res.data.id && res.data.id.length) {
  477. const dialogData = {
  478. id: res.data.id,
  479. name: form.name,
  480. description: '',
  481. icon: form.icon,
  482. dialogType: dialogType.RAGFLOW_TYPE,
  483. mode: 'ragflow'
  484. };
  485. // TODO: 调用自己系统的新增
  486. if (props.typeAngint == 'add') {
  487. createOwnDialog(dialogData, title);
  488. } else {
  489. emit('updateIntelligentList');
  490. handleCancel();
  491. Message.success(title);
  492. }
  493. } else {
  494. Message.error(data.msg);
  495. }
  496. done(true);
  497. setLoading(false);
  498. } catch (err) {
  499. // you can report use errorHandler or other
  500. setLoading(false);
  501. }
  502. } else {
  503. Message.warning('请填写必填项');
  504. done(false);
  505. }
  506. };
  507. const editClick = (data) => {
  508. visible.value = true;
  509. nextTick(() => {
  510. if (props.formData.llm_setting.max_tokens == '') {
  511. max_tokens.value = false;
  512. delete props.formData.llm_setting.max_tokens;
  513. } else {
  514. max_tokens.value = true;
  515. }
  516. Object.assign(form, props.formData);
  517. console.log(form, '表单数据');
  518. });
  519. };
  520. const addVariable = () => {
  521. form.prompt_config.parameters.push({
  522. index: form.prompt_config.parameters.length,
  523. key: '',
  524. name: '',
  525. optional: true
  526. });
  527. nextTick(() => {
  528. formRef.value.validate();
  529. });
  530. };
  531. const deleteParameters = (record) => {
  532. // console.log(record, 'record');
  533. // console.log(form.prompt_config.parameters);
  534. form.prompt_config.parameters = form.prompt_config.parameters.filter((item) => item.index !== record.index);
  535. };
  536. function handleTabChange(key) {
  537. // 处理标签改变事件
  538. activeKey.value = key;
  539. }
  540. const queryModel = async () => {
  541. try {
  542. const data = await queryModelList();
  543. const arrObj = {};
  544. for (const key in data.data) {
  545. if (Object.hasOwnProperty.call(data.data, key)) {
  546. const element = data.data[key];
  547. arrObj[key] = element.filter((item) => item.available === true && item.model_type === 'chat');
  548. }
  549. }
  550. modelList.value = arrObj;
  551. rankModelList.value = {
  552. BAAI: [data.data.BAAI[1]],
  553. Jina: data.data.Jina,
  554. youdao: data.data.Youdao
  555. };
  556. } catch (err) {
  557. // you can report use errorHandler or other
  558. } finally {
  559. }
  560. };
  561. const knowledgeData = async () => {
  562. setLoading(true);
  563. try {
  564. const params = {
  565. current: 1,
  566. pageSize: 1000,
  567. keyword: '',
  568. location: '1'
  569. };
  570. const res = await getKnowledgeList(params.current, params.pageSize, params.keyword, params.location);
  571. if (res.code == 200) {
  572. nextTick(() => {
  573. tabs.value = res.data.rows;
  574. });
  575. }
  576. } catch (err) {
  577. // you can report use errorHandler or other
  578. } finally {
  579. setLoading(false);
  580. }
  581. };
  582. onBeforeMount(() => {
  583. queryModel();
  584. knowledgeData();
  585. });
  586. onMounted(() => {});
  587. onBeforeUnmount(() => {});
  588. defineExpose({
  589. editClick
  590. });
  591. </script>
  592. <style scoped>
  593. :deep .arco-tabs-nav-tab-list {
  594. width: 100%;
  595. .arco-tabs-tab {
  596. width: 33%;
  597. .arco-tabs-tab-title {
  598. width: 100%;
  599. display: inline-block;
  600. text-align: center;
  601. }
  602. }
  603. }
  604. :deep .arco-input-wrapper {
  605. background-color: var(--color-neutral-2);
  606. }
  607. </style>