实验室预约

展示高级编排操作数据库的能力

实验室预约 - 图1实验室预约 - 图2
实验室预约 - 图3实验室预约 - 图4

本示例演示了利用工具调用,自动选择调用知识库搜索实验室相关内容,或调用 HTTP 模块实现数据库的 CRUD 操作。

以一个实验室预约为例,用户可以通过对话系统预约、取消、修改预约和查询预约记录。

1. 全局变量使用

通过设计一个全局变量,让用户输入姓名,模拟用户身份信息。实际使用过程中,通常是直接通过嵌入 Token 来标记用户身份。

2. 工具调用

实验室预约 - 图5

背景知识中,引导模型调用工具去执行不通的操作。

🤗

Tips: 这里需要增加适当的上下文,方便模型结合历史纪录进行判断和决策~

3. HTTP 模块

实验室预约 - 图6

HTTP模块中,需要设置 3 个工具参数:

  • 预约行为:可取 get, put, post, delete 四个值,分别对应查询、修改、新增、删除操作。当然,你也可以写4个HTTP模块,来分别处理。
  • labname: 实验室名。非必填,因为查询和删除时候,不需要。
  • time: 预约时间。

总结

  1. 工具调用模块是非常强大的功能,可以在一定程度上替代问题分类和内容提取。
  2. 通过工具模块,动态的调用不同的工具,可以将复杂业务解耦。

附件

编排配置

可直接复制,导入到 FastGPT 中。

编排配置

  1. {
  2. "nodes": [
  3. {
  4. "nodeId": "userChatInput",
  5. "name": "流程开始",
  6. "intro": "当用户发送一个内容后,流程将会从这个模块开始执行。",
  7. "avatar": "/imgs/workflow/userChatInput.svg",
  8. "flowNodeType": "workflowStart",
  9. "position": {
  10. "x": 309.7143912167367,
  11. "y": 1501.2761754220846
  12. },
  13. "inputs": [
  14. {
  15. "key": "userChatInput",
  16. "renderTypeList": [
  17. "reference",
  18. "textarea"
  19. ],
  20. "valueType": "string",
  21. "label": "问题输入",
  22. "required": true,
  23. "toolDescription": "用户问题",
  24. "type": "systemInput",
  25. "showTargetInApp": false,
  26. "showTargetInPlugin": false,
  27. "connected": false,
  28. "selectedTypeIndex": 0,
  29. "value": [
  30. "userChatInput",
  31. "userChatInput"
  32. ]
  33. }
  34. ],
  35. "outputs": [
  36. {
  37. "id": "userChatInput",
  38. "type": "static",
  39. "key": "userChatInput",
  40. "valueType": "string",
  41. "label": "core.module.input.label.user question"
  42. }
  43. ]
  44. },
  45. {
  46. "nodeId": "eg5upi",
  47. "name": "指定回复",
  48. "intro": "该模块可以直接回复一段指定的内容。常用于引导、提示。非字符串内容传入时,会转成字符串进行输出。",
  49. "avatar": "/imgs/workflow/reply.png",
  50. "flowNodeType": "answerNode",
  51. "position": {
  52. "x": 1962.729630445213,
  53. "y": 2295.9791334948304
  54. },
  55. "inputs": [
  56. {
  57. "key": "text",
  58. "renderTypeList": [
  59. "textarea",
  60. "reference"
  61. ],
  62. "valueType": "any",
  63. "label": "core.module.input.label.Response content",
  64. "description": "core.module.input.description.Response content",
  65. "placeholder": "core.module.input.description.Response content",
  66. "type": "textarea",
  67. "showTargetInApp": true,
  68. "showTargetInPlugin": true,
  69. "connected": true,
  70. "selectedTypeIndex": 1,
  71. "value": [
  72. "40clf3",
  73. "result"
  74. ]
  75. }
  76. ],
  77. "outputs": []
  78. },
  79. {
  80. "nodeId": "kge59i",
  81. "name": "用户引导",
  82. "intro": "可以配置应用的系统参数。",
  83. "avatar": "/imgs/workflow/userGuide.png",
  84. "flowNodeType": "userGuide",
  85. "position": {
  86. "x": -327.218389965887,
  87. "y": 1504.8056414948464
  88. },
  89. "inputs": [
  90. {
  91. "key": "welcomeText",
  92. "renderTypeList": [
  93. "hidden"
  94. ],
  95. "valueType": "string",
  96. "label": "core.app.Welcome Text",
  97. "type": "hidden",
  98. "showTargetInApp": false,
  99. "showTargetInPlugin": false,
  100. "value": "你好,我是实验室助手,请问有什么可以帮助你的么?如需预约或修改预约实验室,请提供姓名、时间和实验室名称。\n[实验室介绍]\n[开放时间]\n[预约]",
  101. "connected": false,
  102. "selectedTypeIndex": 0
  103. },
  104. {
  105. "key": "variables",
  106. "renderTypeList": [
  107. "hidden"
  108. ],
  109. "valueType": "any",
  110. "label": "core.module.Variable",
  111. "value": [
  112. {
  113. "id": "gt9b23",
  114. "key": "name",
  115. "label": "name",
  116. "type": "input",
  117. "required": true,
  118. "maxLen": 50,
  119. "enums": [
  120. {
  121. "value": ""
  122. }
  123. ]
  124. }
  125. ],
  126. "type": "hidden",
  127. "showTargetInApp": false,
  128. "showTargetInPlugin": false,
  129. "connected": false,
  130. "selectedTypeIndex": 0
  131. },
  132. {
  133. "key": "questionGuide",
  134. "valueType": "boolean",
  135. "renderTypeList": [
  136. "hidden"
  137. ],
  138. "label": "",
  139. "type": "switch",
  140. "showTargetInApp": false,
  141. "showTargetInPlugin": false,
  142. "value": false,
  143. "connected": false,
  144. "selectedTypeIndex": 0
  145. },
  146. {
  147. "key": "tts",
  148. "renderTypeList": [
  149. "hidden"
  150. ],
  151. "valueType": "any",
  152. "label": "",
  153. "type": "hidden",
  154. "showTargetInApp": false,
  155. "showTargetInPlugin": false,
  156. "value": {
  157. "type": "model",
  158. "model": "tts-1",
  159. "voice": "alloy"
  160. },
  161. "connected": false,
  162. "selectedTypeIndex": 0
  163. },
  164. {
  165. "key": "whisper",
  166. "renderTypeList": [
  167. "hidden"
  168. ],
  169. "valueType": "any",
  170. "label": "",
  171. "type": "hidden",
  172. "showTargetInApp": false,
  173. "showTargetInPlugin": false,
  174. "connected": false,
  175. "selectedTypeIndex": 0
  176. },
  177. {
  178. "key": "scheduleTrigger",
  179. "renderTypeList": [
  180. "hidden"
  181. ],
  182. "valueType": "any",
  183. "label": "",
  184. "value": null
  185. }
  186. ],
  187. "outputs": []
  188. },
  189. {
  190. "nodeId": "40clf3",
  191. "name": "HTTP请求",
  192. "intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
  193. "avatar": "/imgs/workflow/http.png",
  194. "flowNodeType": "httpRequest468",
  195. "showStatus": true,
  196. "position": {
  197. "x": 1118.6532653446993,
  198. "y": 1955.886106913907
  199. },
  200. "inputs": [
  201. {
  202. "key": "system_httpMethod",
  203. "renderTypeList": [
  204. "custom"
  205. ],
  206. "valueType": "string",
  207. "label": "",
  208. "value": "POST",
  209. "required": true,
  210. "type": "custom",
  211. "showTargetInApp": false,
  212. "showTargetInPlugin": false,
  213. "connected": false,
  214. "selectedTypeIndex": 0
  215. },
  216. {
  217. "valueType": "string",
  218. "renderTypeList": [
  219. "reference"
  220. ],
  221. "key": "action",
  222. "label": "action",
  223. "toolDescription": "预约行为,一共四种:\nget - 查询预约情况\nput - 更新预约\npost - 新增预约\ndelete - 删除预约",
  224. "required": true,
  225. "canEdit": true,
  226. "editField": {
  227. "key": true,
  228. "description": true
  229. }
  230. },
  231. {
  232. "valueType": "string",
  233. "renderTypeList": [
  234. "reference"
  235. ],
  236. "key": "labname",
  237. "label": "labname",
  238. "toolDescription": "实验室名称",
  239. "required": false,
  240. "canEdit": true,
  241. "editField": {
  242. "key": true,
  243. "description": true
  244. }
  245. },
  246. {
  247. "valueType": "string",
  248. "renderTypeList": [
  249. "reference"
  250. ],
  251. "key": "time",
  252. "label": "time",
  253. "toolDescription": "预约时间,按 YYYY/MM/DD HH:mm 格式返回",
  254. "required": false,
  255. "canEdit": true,
  256. "editField": {
  257. "key": true,
  258. "description": true
  259. }
  260. },
  261. {
  262. "key": "system_httpReqUrl",
  263. "renderTypeList": [
  264. "hidden"
  265. ],
  266. "valueType": "string",
  267. "label": "",
  268. "description": "core.module.input.description.Http Request Url",
  269. "placeholder": "https://api.ai.com/getInventory",
  270. "required": false,
  271. "type": "hidden",
  272. "showTargetInApp": false,
  273. "showTargetInPlugin": false,
  274. "value": "https://d8dns0.laf.dev/appointment-lab",
  275. "connected": false,
  276. "selectedTypeIndex": 0
  277. },
  278. {
  279. "key": "system_httpHeader",
  280. "renderTypeList": [
  281. "custom"
  282. ],
  283. "valueType": "any",
  284. "value": [],
  285. "label": "",
  286. "description": "core.module.input.description.Http Request Header",
  287. "placeholder": "core.module.input.description.Http Request Header",
  288. "required": false,
  289. "type": "custom",
  290. "showTargetInApp": false,
  291. "showTargetInPlugin": false,
  292. "connected": false,
  293. "selectedTypeIndex": 0
  294. },
  295. {
  296. "key": "system_httpParams",
  297. "renderTypeList": [
  298. "hidden"
  299. ],
  300. "valueType": "any",
  301. "value": [],
  302. "label": "",
  303. "required": false,
  304. "type": "hidden",
  305. "showTargetInApp": false,
  306. "showTargetInPlugin": false,
  307. "connected": false,
  308. "selectedTypeIndex": 0
  309. },
  310. {
  311. "key": "system_httpJsonBody",
  312. "renderTypeList": [
  313. "hidden"
  314. ],
  315. "valueType": "any",
  316. "value": "{\r\n \"name\": \"{{name}}\",\r\n \"time\": \"{{time}}\",\r\n \"labname\": \"{{labname}}\",\r\n \"action\": \"{{action}}\"\r\n}",
  317. "label": "",
  318. "required": false,
  319. "type": "hidden",
  320. "showTargetInApp": false,
  321. "showTargetInPlugin": false,
  322. "connected": false,
  323. "selectedTypeIndex": 0
  324. },
  325. {
  326. "key": "system_addInputParam",
  327. "renderTypeList": [
  328. "addInputParam"
  329. ],
  330. "valueType": "dynamic",
  331. "label": "",
  332. "required": false,
  333. "description": "core.module.input.description.HTTP Dynamic Input",
  334. "editField": {
  335. "key": true,
  336. "valueType": true
  337. }
  338. }
  339. ],
  340. "outputs": [
  341. {
  342. "id": "system_addOutputParam",
  343. "type": "dynamic",
  344. "key": "system_addOutputParam",
  345. "valueType": "dynamic",
  346. "label": "",
  347. "editField": {
  348. "key": true,
  349. "valueType": true
  350. }
  351. },
  352. {
  353. "id": "result",
  354. "type": "static",
  355. "key": "result",
  356. "valueType": "string",
  357. "label": "result",
  358. "description": "result",
  359. "canEdit": true,
  360. "editField": {
  361. "key": true,
  362. "name": true,
  363. "description": true,
  364. "dataType": true
  365. }
  366. },
  367. {
  368. "id": "httpRawResponse",
  369. "type": "static",
  370. "key": "httpRawResponse",
  371. "valueType": "any",
  372. "label": "原始响应",
  373. "description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。"
  374. }
  375. ]
  376. },
  377. {
  378. "nodeId": "fYxwWym8flYL",
  379. "name": "工具调用(实验)",
  380. "intro": "通过AI模型自动选择一个或多个功能块进行调用,也可以对插件进行调用。",
  381. "avatar": "/imgs/workflow/tool.svg",
  382. "flowNodeType": "tools",
  383. "showStatus": true,
  384. "position": {
  385. "x": 933.9342354248961,
  386. "y": 1229.3563445150553
  387. },
  388. "inputs": [
  389. {
  390. "key": "model",
  391. "renderTypeList": [
  392. "settingLLMModel",
  393. "reference"
  394. ],
  395. "label": "core.module.input.label.aiModel",
  396. "valueType": "string",
  397. "llmModelType": "all",
  398. "value": "gpt-3.5-turbo"
  399. },
  400. {
  401. "key": "temperature",
  402. "renderTypeList": [
  403. "hidden"
  404. ],
  405. "label": "",
  406. "value": 0,
  407. "valueType": "number",
  408. "min": 0,
  409. "max": 10,
  410. "step": 1
  411. },
  412. {
  413. "key": "maxToken",
  414. "renderTypeList": [
  415. "hidden"
  416. ],
  417. "label": "",
  418. "value": 2000,
  419. "valueType": "number",
  420. "min": 100,
  421. "max": 4000,
  422. "step": 50
  423. },
  424. {
  425. "key": "systemPrompt",
  426. "renderTypeList": [
  427. "textarea",
  428. "reference"
  429. ],
  430. "max": 3000,
  431. "valueType": "string",
  432. "label": "core.ai.Prompt",
  433. "description": "core.app.tip.chatNodeSystemPromptTip",
  434. "placeholder": "core.app.tip.chatNodeSystemPromptTip",
  435. "value": "当前时间为: {{cTime}}\n你是实验室助手,用户可能会询问实验室相关介绍或预约实验室。\n请选择合适的工具去帮助他们。"
  436. },
  437. {
  438. "key": "history",
  439. "renderTypeList": [
  440. "numberInput",
  441. "reference"
  442. ],
  443. "valueType": "chatHistory",
  444. "label": "core.module.input.label.chat history",
  445. "required": true,
  446. "min": 0,
  447. "max": 30,
  448. "value": 6
  449. },
  450. {
  451. "key": "userChatInput",
  452. "renderTypeList": [
  453. "reference",
  454. "textarea"
  455. ],
  456. "valueType": "string",
  457. "label": "用户问题",
  458. "required": true,
  459. "value": [
  460. "userChatInput",
  461. "userChatInput"
  462. ]
  463. }
  464. ],
  465. "outputs": []
  466. },
  467. {
  468. "nodeId": "JSSQtDgwmmbE",
  469. "name": "知识库搜索",
  470. "intro": "调用“语义检索”和“全文检索”能力,从“知识库”中查找实验室介绍和使用规则等信息。",
  471. "avatar": "/imgs/workflow/db.png",
  472. "flowNodeType": "datasetSearchNode",
  473. "showStatus": true,
  474. "position": {
  475. "x": 447.0795498711184,
  476. "y": 1971.5311041711186
  477. },
  478. "inputs": [
  479. {
  480. "key": "datasets",
  481. "renderTypeList": [
  482. "selectDataset",
  483. "reference"
  484. ],
  485. "label": "core.module.input.label.Select dataset",
  486. "value": [],
  487. "valueType": "selectDataset",
  488. "list": [],
  489. "required": true
  490. },
  491. {
  492. "key": "similarity",
  493. "renderTypeList": [
  494. "selectDatasetParamsModal"
  495. ],
  496. "label": "",
  497. "value": 0.4,
  498. "valueType": "number"
  499. },
  500. {
  501. "key": "limit",
  502. "renderTypeList": [
  503. "hidden"
  504. ],
  505. "label": "",
  506. "value": 1500,
  507. "valueType": "number"
  508. },
  509. {
  510. "key": "searchMode",
  511. "renderTypeList": [
  512. "hidden"
  513. ],
  514. "label": "",
  515. "valueType": "string",
  516. "value": "embedding"
  517. },
  518. {
  519. "key": "usingReRank",
  520. "renderTypeList": [
  521. "hidden"
  522. ],
  523. "label": "",
  524. "valueType": "boolean",
  525. "value": false
  526. },
  527. {
  528. "key": "datasetSearchUsingExtensionQuery",
  529. "renderTypeList": [
  530. "hidden"
  531. ],
  532. "label": "",
  533. "valueType": "boolean",
  534. "value": false
  535. },
  536. {
  537. "key": "datasetSearchExtensionModel",
  538. "renderTypeList": [
  539. "hidden"
  540. ],
  541. "label": "",
  542. "valueType": "string",
  543. "value": "gpt-3.5-turbo"
  544. },
  545. {
  546. "key": "datasetSearchExtensionBg",
  547. "renderTypeList": [
  548. "hidden"
  549. ],
  550. "label": "",
  551. "valueType": "string",
  552. "value": ""
  553. },
  554. {
  555. "key": "userChatInput",
  556. "renderTypeList": [
  557. "reference",
  558. "textarea"
  559. ],
  560. "valueType": "string",
  561. "label": "用户问题",
  562. "required": true,
  563. "toolDescription": "需要检索的内容"
  564. }
  565. ],
  566. "outputs": [
  567. {
  568. "id": "quoteQA",
  569. "key": "quoteQA",
  570. "label": "core.module.Dataset quote.label",
  571. "description": "特殊数组格式,搜索结果为空时,返回空数组。",
  572. "type": "static",
  573. "valueType": "datasetQuote"
  574. }
  575. ]
  576. },
  577. {
  578. "nodeId": "IdntVQiTopHT",
  579. "name": "工具调用终止",
  580. "intro": "该模块需配置工具调用使用。当该模块被执行时,本次工具调用将会强制结束,并且不再调用AI针对工具调用结果回答问题。",
  581. "avatar": "/imgs/workflow/toolStop.svg",
  582. "flowNodeType": "stopTool",
  583. "position": {
  584. "x": 1969.73331750207,
  585. "y": 2650.0258908119413
  586. },
  587. "inputs": [],
  588. "outputs": []
  589. }
  590. ],
  591. "edges": [
  592. {
  593. "source": "40clf3",
  594. "target": "eg5upi",
  595. "sourceHandle": "40clf3-source-right",
  596. "targetHandle": "eg5upi-target-left"
  597. },
  598. {
  599. "source": "userChatInput",
  600. "target": "fYxwWym8flYL",
  601. "sourceHandle": "userChatInput-source-right",
  602. "targetHandle": "fYxwWym8flYL-target-left"
  603. },
  604. {
  605. "source": "fYxwWym8flYL",
  606. "target": "40clf3",
  607. "sourceHandle": "selectedTools",
  608. "targetHandle": "selectedTools"
  609. },
  610. {
  611. "source": "fYxwWym8flYL",
  612. "target": "JSSQtDgwmmbE",
  613. "sourceHandle": "selectedTools",
  614. "targetHandle": "selectedTools"
  615. },
  616. {
  617. "source": "40clf3",
  618. "target": "IdntVQiTopHT",
  619. "sourceHandle": "40clf3-source-right",
  620. "targetHandle": "IdntVQiTopHT-target-left"
  621. }
  622. ]
  623. }

Laf 云函数代码

可以在 Laf实验室预约 - 图7 中快速构建 HTTP 接口。

函数代码

  1. import cloud from '@lafjs/cloud'
  2. const db = cloud.database()
  3. type RequestType = {
  4. name: string;
  5. time?: string;
  6. labname?: string;
  7. action: 'post' | 'delete' | 'put' | 'get'
  8. }
  9. export default async function (ctx: FunctionContext) {
  10. try {
  11. const { action,...body } = ctx.body as RequestType
  12. if (action === 'get') {
  13. return await getRecord(ctx.body)
  14. }
  15. if (action === 'post') {
  16. return await createRecord(ctx.body)
  17. }
  18. if (action === 'put') {
  19. return await putRecord(ctx.body)
  20. }
  21. if (action === 'delete') {
  22. return await removeRecord(ctx.body)
  23. }
  24. return {
  25. result: "异常"
  26. }
  27. } catch (err) {
  28. return {
  29. result: "异常"
  30. }
  31. }
  32. }
  33. async function putRecord({ name, time, labname }: RequestType) {
  34. const missData = []
  35. if (!name) missData.push("你的姓名")
  36. if (missData.length > 0) {
  37. return {
  38. result: `请提供: ${missData.join("、")}`
  39. }
  40. }
  41. const { data: record } = await db.collection("LabAppointment").where({
  42. name, status: "unStart"
  43. }).getOne()
  44. if (!record) {
  45. return {
  46. result: `${name} 还没有预约记录`
  47. }
  48. }
  49. const updateWhere = {
  50. name,
  51. time: time || record.time,
  52. labname: labname || record.labname
  53. }
  54. await db.collection("LabAppointment").where({
  55. name, status: "unStart"
  56. }).update(updateWhere)
  57. return {
  58. result: `修改预约成功。
  59. 姓名:${name}·
  60. 时间: ${updateWhere.time}
  61. 实验室名: ${updateWhere.labname}
  62. ` }
  63. }
  64. async function getRecord({ name }: RequestType) {
  65. if (!name) {
  66. return {
  67. result: "请提供你的姓名"
  68. }
  69. }
  70. const { data } = await db.collection('LabAppointment').where({ name, status: "unStart" }).getOne()
  71. if (!data) {
  72. return {
  73. result: `${name} 没有预约中的记录`
  74. }
  75. }
  76. return {
  77. result: `${name} 有一条预约记录:
  78. 姓名:${data.name}
  79. 时间: ${data.time}
  80. 实验室名: ${data.labname}
  81. `
  82. }
  83. }
  84. async function removeRecord({ name }: RequestType) {
  85. if (!name) {
  86. return {
  87. result: "请提供你的姓名"
  88. }
  89. }
  90. const { deleted } = await db.collection('LabAppointment').where({ name, status: "unStart" }).remove()
  91. if (deleted > 0) {
  92. return {
  93. result: `取消预约记录成功: ${name}`
  94. }
  95. }
  96. return {
  97. result: ` ${name} 没有预约中的记录`
  98. }
  99. }
  100. async function createRecord({ name, time, labname }: RequestType) {
  101. const missData = []
  102. if (!name) missData.push("你的姓名")
  103. if (!time) missData.push("需要预约的时间")
  104. if (!labname) missData.push("实验室名名称")
  105. if (missData.length > 0) {
  106. return {
  107. result: `请提供: ${missData.join("、")}`
  108. }
  109. }
  110. const { data: record } = await db.collection("LabAppointment").where({
  111. name, status: "unStart"
  112. }).getOne()
  113. if (record) {
  114. return {
  115. result: `您已经有一个预约记录了:
  116. 姓名:${record.name}
  117. 时间: ${record.time}
  118. 实验室名: ${record.labname}
  119. 每人仅能同时预约一个实验室名。
  120. `
  121. }
  122. }
  123. await db.collection("LabAppointment").add({
  124. name, time, labname, status: "unStart"
  125. })
  126. return {
  127. result: `预约成功。
  128. 姓名:${name}
  129. 时间: ${time}
  130. 实验室名: ${labname}
  131. ` }
  132. }