├── LICENSE
├── LongTermMemory.yml
├── README.md
├── agent-prompt.md
└── screenshots
├── LongTermMemory-chatbot-demo-config-tool-node-parameters.png
├── LongTermMemory-chatbot-demo-test-long-term-memory-remembering.png
├── LongTermMemory-chatbot-demo-test-long-term-memory-retrieving.png
├── LongTermMemory-chatbot-demo-test-long-term-memory-standalone-space.png
└── LongTermMemory-workflow.png
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Rain Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LongTermMemory.yml:
--------------------------------------------------------------------------------
1 | app:
2 | description: ''
3 | icon: "\U0001F916"
4 | icon_background: '#FFEAD5'
5 | mode: workflow
6 | name: LongTermMemory
7 | workflow:
8 | features:
9 | file_upload:
10 | image:
11 | enabled: false
12 | number_limits: 3
13 | transfer_methods:
14 | - local_file
15 | - remote_url
16 | opening_statement: ''
17 | retriever_resource:
18 | enabled: false
19 | sensitive_word_avoidance:
20 | enabled: false
21 | speech_to_text:
22 | enabled: false
23 | suggested_questions: []
24 | suggested_questions_after_answer:
25 | enabled: false
26 | text_to_speech:
27 | enabled: false
28 | language: ''
29 | voice: ''
30 | graph:
31 | edges:
32 | - data:
33 | isInIteration: false
34 | sourceType: start
35 | targetType: template-transform
36 | id: 1719906575066-source-1719906601173-target
37 | source: '1719906575066'
38 | sourceHandle: source
39 | target: '1719906601173'
40 | targetHandle: target
41 | type: custom
42 | zIndex: 0
43 | - data:
44 | isInIteration: false
45 | sourceType: template-transform
46 | targetType: code
47 | id: 1719906601173-source-1719906754157-target
48 | source: '1719906601173'
49 | sourceHandle: source
50 | target: '1719906754157'
51 | targetHandle: target
52 | type: custom
53 | zIndex: 0
54 | - data:
55 | isInIteration: false
56 | sourceType: code
57 | targetType: http-request
58 | id: 1719906754157-source-1719906843290-target
59 | source: '1719906754157'
60 | sourceHandle: source
61 | target: '1719906843290'
62 | targetHandle: target
63 | type: custom
64 | zIndex: 0
65 | - data:
66 | isInIteration: false
67 | sourceType: http-request
68 | targetType: code
69 | id: 1719906843290-source-1719906924365-target
70 | source: '1719906843290'
71 | sourceHandle: source
72 | target: '1719906924365'
73 | targetHandle: target
74 | type: custom
75 | zIndex: 0
76 | - data:
77 | isInIteration: false
78 | sourceType: code
79 | targetType: template-transform
80 | id: 1719906924365-source-1719907030231-target
81 | source: '1719906924365'
82 | sourceHandle: source
83 | target: '1719907030231'
84 | targetHandle: target
85 | type: custom
86 | zIndex: 0
87 | - data:
88 | isInIteration: false
89 | sourceType: template-transform
90 | targetType: parameter-extractor
91 | id: 1719907030231-source-1719907179295-target
92 | source: '1719907030231'
93 | sourceHandle: source
94 | target: '1719907179295'
95 | targetHandle: target
96 | type: custom
97 | zIndex: 0
98 | - data:
99 | isInIteration: false
100 | sourceType: parameter-extractor
101 | targetType: if-else
102 | id: 1719907179295-source-1719907351349-target
103 | source: '1719907179295'
104 | sourceHandle: source
105 | target: '1719907351349'
106 | targetHandle: target
107 | type: custom
108 | zIndex: 0
109 | - data:
110 | isInIteration: false
111 | sourceType: if-else
112 | targetType: template-transform
113 | id: 1719907351349-true-1719907410938-target
114 | source: '1719907351349'
115 | sourceHandle: 'true'
116 | target: '1719907410938'
117 | targetHandle: target
118 | type: custom
119 | zIndex: 0
120 | - data:
121 | isInIteration: false
122 | sourceType: if-else
123 | targetType: if-else
124 | id: 1719907351349-false-1719907452578-target
125 | source: '1719907351349'
126 | sourceHandle: 'false'
127 | target: '1719907452578'
128 | targetHandle: target
129 | type: custom
130 | zIndex: 0
131 | - data:
132 | isInIteration: false
133 | sourceType: if-else
134 | targetType: http-request
135 | id: 1719907452578-true-1719907482868-target
136 | source: '1719907452578'
137 | sourceHandle: 'true'
138 | target: '1719907482868'
139 | targetHandle: target
140 | type: custom
141 | zIndex: 0
142 | - data:
143 | isInIteration: false
144 | sourceType: if-else
145 | targetType: http-request
146 | id: 1719907452578-false-1719907683628-target
147 | source: '1719907452578'
148 | sourceHandle: 'false'
149 | target: '1719907683628'
150 | targetHandle: target
151 | type: custom
152 | zIndex: 0
153 | - data:
154 | isInIteration: false
155 | sourceType: template-transform
156 | targetType: variable-aggregator
157 | id: 1719907410938-source-1719907835909-target
158 | selected: false
159 | source: '1719907410938'
160 | sourceHandle: source
161 | target: '1719907835909'
162 | targetHandle: target
163 | type: custom
164 | zIndex: 0
165 | - data:
166 | isInIteration: false
167 | sourceType: http-request
168 | targetType: variable-aggregator
169 | id: 1719907482868-source-1719907835909-target
170 | source: '1719907482868'
171 | sourceHandle: source
172 | target: '1719907835909'
173 | targetHandle: target
174 | type: custom
175 | zIndex: 0
176 | - data:
177 | isInIteration: false
178 | sourceType: http-request
179 | targetType: variable-aggregator
180 | id: 1719907683628-source-1719907835909-target
181 | source: '1719907683628'
182 | sourceHandle: source
183 | target: '1719907835909'
184 | targetHandle: target
185 | type: custom
186 | zIndex: 0
187 | - data:
188 | isInIteration: false
189 | sourceType: variable-aggregator
190 | targetType: end
191 | id: 1719907835909-source-1719906705933-target
192 | source: '1719907835909'
193 | sourceHandle: source
194 | target: '1719906705933'
195 | targetHandle: target
196 | type: custom
197 | zIndex: 0
198 | nodes:
199 | - data:
200 | desc: ''
201 | selected: false
202 | title: Start
203 | type: start
204 | variables:
205 | - label: base_url
206 | max_length: 48
207 | options: []
208 | required: true
209 | type: text-input
210 | variable: base_url
211 | - label: dataset_id
212 | max_length: 48
213 | options: []
214 | required: true
215 | type: text-input
216 | variable: dataset_id
217 | - label: document_id
218 | max_length: 48
219 | options: []
220 | required: true
221 | type: text-input
222 | variable: document_id
223 | - label: api_key
224 | max_length: 48
225 | options: []
226 | required: true
227 | type: text-input
228 | variable: api_key
229 | - label: memory_template
230 | max_length: 480
231 | options: []
232 | required: true
233 | type: paragraph
234 | variable: memory_template
235 | - label: user_input
236 | max_length: 2000
237 | options: []
238 | required: true
239 | type: paragraph
240 | variable: user_input
241 | height: 218
242 | id: '1719906575066'
243 | position:
244 | x: 30
245 | y: 309
246 | positionAbsolute:
247 | x: 30
248 | y: 309
249 | selected: false
250 | sourcePosition: right
251 | targetPosition: left
252 | type: custom
253 | width: 243
254 | - data:
255 | desc: config what kind of content should be remember
256 | selected: false
257 | template: '
258 |
259 | {{ memory_template }}
260 |
261 | '
262 | title: Config:MemoryContentTemplate
263 | type: template-transform
264 | variables:
265 | - value_selector:
266 | - sys
267 | - user_id
268 | variable: user_id
269 | - value_selector:
270 | - '1719906575066'
271 | - memory_template
272 | variable: memory_template
273 | height: 100
274 | id: '1719906601173'
275 | position:
276 | x: 333
277 | y: 309
278 | positionAbsolute:
279 | x: 333
280 | y: 309
281 | selected: false
282 | sourcePosition: right
283 | targetPosition: left
284 | type: custom
285 | width: 243
286 | - data:
287 | desc: ''
288 | outputs:
289 | - value_selector:
290 | - '1719906924365'
291 | - longterm_memory_space
292 | variable: longterm_memory_space
293 | selected: false
294 | title: End
295 | type: end
296 | height: 88
297 | id: '1719906705933'
298 | position:
299 | x: 3363
300 | y: 420.5
301 | positionAbsolute:
302 | x: 3363
303 | y: 420.5
304 | selected: false
305 | sourcePosition: right
306 | targetPosition: left
307 | type: custom
308 | width: 243
309 | - data:
310 | code: "def main(base_url: str, dataset_id: str, document_id: str, api_key:\
311 | \ str) -> dict:\n config = {\n \"base_url\": base_url,\n \
312 | \ \"dataset_id\": dataset_id,\n \"document_id\": document_id,\n\
313 | \ \"api_key\": api_key\n }\n\n api_authorization = \"Bearer\
314 | \ %s\" % config[\"api_key\"]\n url_for_search_segment = \"{config[base_url]}/v1/datasets/{config[dataset_id]}/documents/{config[document_id]}/segments\"\
315 | .format(config=config)\n \n return {\n \"config\": config,\n\
316 | \ \"api_authorization\": api_authorization,\n \"url_for_search_segment\"\
317 | : url_for_search_segment,\n }\n"
318 | code_language: python3
319 | desc: Knowledge config for storing long term memory
320 | outputs:
321 | api_authorization:
322 | children: null
323 | type: string
324 | config:
325 | children: null
326 | type: object
327 | url_for_search_segment:
328 | children: null
329 | type: string
330 | selected: false
331 | title: Config:KB
332 | type: code
333 | variables:
334 | - value_selector:
335 | - '1719906575066'
336 | - base_url
337 | variable: base_url
338 | - value_selector:
339 | - '1719906575066'
340 | - dataset_id
341 | variable: dataset_id
342 | - value_selector:
343 | - '1719906575066'
344 | - document_id
345 | variable: document_id
346 | - value_selector:
347 | - '1719906575066'
348 | - api_key
349 | variable: api_key
350 | height: 100
351 | id: '1719906754157'
352 | position:
353 | x: 636
354 | y: 309
355 | positionAbsolute:
356 | x: 636
357 | y: 309
358 | selected: false
359 | sourcePosition: right
360 | targetPosition: left
361 | type: custom
362 | width: 243
363 | - data:
364 | authorization:
365 | config: null
366 | type: no-auth
367 | body:
368 | data: ''
369 | type: none
370 | desc: retrieve memory of user by searching KB by user id
371 | headers: Authorization:{{#1719906754157.api_authorization#}}
372 | method: get
373 | params: keyword:{{#sys.user_id#}}
374 | selected: false
375 | timeout:
376 | max_connect_timeout: 0
377 | max_read_timeout: 0
378 | max_write_timeout: 0
379 | title: Action:retrieve_memory_of_user
380 | type: http-request
381 | url: '{{#1719906754157.url_for_search_segment#}}'
382 | variables: []
383 | height: 139
384 | id: '1719906843290'
385 | position:
386 | x: 939
387 | y: 309
388 | positionAbsolute:
389 | x: 939
390 | y: 309
391 | selected: false
392 | sourcePosition: right
393 | targetPosition: left
394 | type: custom
395 | width: 243
396 | - data:
397 | code: "import json\n\ndef main(api_response: str, config: object) -> dict:\n\
398 | \ url_for_add_segment = \"{config[base_url]}/v1/datasets/{config[dataset_id]}/documents/{config[document_id]}/segments\"\
399 | .format(config=config)\n url_for_update_segment = \"\"\n longterm_memory_space\
400 | \ = \"\"\n parsed_response = json.loads(api_response)\n contents =\
401 | \ [item['content'] for item in parsed_response['data']]\n if contents:\n\
402 | \ longterm_memory_space = \"\\n\".join(contents)\n # expose\
403 | \ segment_id\n if len(parsed_response['data']):\n segment_id\
404 | \ = parsed_response['data'][0]['id']\n longterm_memory_space\
405 | \ = \"\" + segment_id + \"\\n\" + longterm_memory_space\n\
406 | \ url_for_update_segment = \"{config[base_url]}/v1/datasets/{config[dataset_id]}/documents/{config[document_id]}/segments/{segment_id}\"\
407 | .format(config=config, segment_id=segment_id)\n\n return {\n \"\
408 | longterm_memory_space\": longterm_memory_space,\n \"url_for_add_segment\"\
409 | : url_for_add_segment,\n \"url_for_update_segment\": url_for_update_segment\n\
410 | \ }"
411 | code_language: python3
412 | desc: extract content field from KB API response
413 | outputs:
414 | longterm_memory_space:
415 | children: null
416 | type: string
417 | url_for_add_segment:
418 | children: null
419 | type: string
420 | url_for_update_segment:
421 | children: null
422 | type: string
423 | selected: false
424 | title: Code:extract_content_from_api
425 | type: code
426 | variables:
427 | - value_selector:
428 | - '1719906843290'
429 | - body
430 | variable: api_response
431 | - value_selector:
432 | - '1719906754157'
433 | - config
434 | variable: config
435 | height: 100
436 | id: '1719906924365'
437 | position:
438 | x: 1242
439 | y: 309
440 | positionAbsolute:
441 | x: 1242
442 | y: 309
443 | selected: false
444 | sourcePosition: right
445 | targetPosition: left
446 | type: custom
447 | width: 243
448 | - data:
449 | desc: 'prepare agent system prompt
450 |
451 | '
452 | selected: false
453 | template: "# Long-term Memory Capability\n\nYou possess long-term memory capabilities,\
454 | \ maintaining an independent long-term memory space for each user you interact\
455 | \ with. Below is an explanation of long-term memory capabilities:\n\nThere\
456 | \ is a \"Long-term Memory Content Template\" that declares the attribute\
457 | \ tags to be saved. When the user's input content is related to the attribute\
458 | \ tags defined in the template, the system analyzes the user's input based\
459 | \ on the semantic meaning of the attribute tags in the template, extracts\
460 | \ the corresponding attribute tag values, and finally generates the content\
461 | \ for the \"Long-term Memory Space\" according to the template format.\n\
462 | \n## Long-term Memory Content Template\n\nThe following is the specific\
463 | \ long-term memory content template, contained within the ``\
464 | \ tag:\n\n\n{{ longterm_memory_template }}\n\n\
465 | \n## Long-term Memory Content Template Example\n\nFor example, if the \"\
466 | Long-term Memory Content Template\" is set as:\n\n```\n\n\n\t${user_name}\n\t${user_age}\n\
468 | \t${user_hobbies}\n\n\n```\n\
469 | \nThe defined attribute tags in this example are: ``, ``, ``\n\
470 | \nExamples of attribute tags corresponding to automatically extracted values\
471 | \ are:\n\n- The `` attribute tag identifies name-related information\
472 | \ through the word `name`.\n- The `` attribute tag identifies age-related\
473 | \ information through the word `age`.\n- The `` attribute tag identifies\
474 | \ personal interests and hobbies-related information through the word `hobbies`.\n\
475 | \nExamples of extracting attribute tag values:\n\n- For instance, if the\
476 | \ user input is `I'm Tom` or `my name is Tom`, the analysis reveals that\
477 | \ the user's name is `Tom`, so the value of the `` attribute tag is\
478 | \ `Tom`.\n- If the user input is `I'm 30`, the analysis shows that the user's\
479 | \ age is `30`, so the value of the `` attribute tag is `30`.\n- If\
480 | \ the user input is `I like reading`, the analysis indicates that the user's\
481 | \ hobby is `reading`, so the value of the `` attribute tag is `reading`.\n\
482 | \nThus, in this example, the final \"Long-term Memory Space\" content generated\
483 | \ would be:\n\n```\n\n\n\
484 | \tTom\n\t30\n\treading\n\n\
485 | \n```\n\n## Long-term Memory Space\n\nThe content of the long-term\
486 | \ memory space is contained within the ``\
487 | \ tags:\n\n\n{{ longterm_memory_space }}\n\n\
488 | \n## Parameter Extraction\n\n### Extracting the `action` Parameter\n\nThe\
489 | \ following are the conditions for the `action` parameter values (based\
490 | \ on the `Long-term Memory Content Template` and the `Long-term Memory Content\
491 | \ Template Example`):\n\n- `add_user_memory`:\n When the `input_text` mentions\
492 | \ information related to the attribute tags defined in the `Long-term Memory\
493 | \ Content Template`, and the ``\
494 | \ tags are **empty**.\n- `update_user_memory`:\n When the `input_text`\
495 | \ mentions information related to the attribute tags defined in the `Long-term\
496 | \ Memory Content Template`, and the ``\
497 | \ tags are **not empty**.\n- `reply`:\n When the `input_text` is **unrelated**\
498 | \ to the attribute tags defined in the `Long-term Memory Content Template`.\n\
499 | \n### Extracting the `longterm_memory_content` Parameter\n\nWhen the `action`\
500 | \ parameter value is not `reply`, refer to the examples described in the\
501 | \ `Long-term Memory Content Template Example`, use the template defined\
502 | \ in the `Long-term Memory Content Template`, analyze the user's input,\
503 | \ combine it with the existing information in the \"Long-term Memory Space\"\
504 | , extract the values of the attribute tags to generate new \"Long-term Memory\
505 | \ Space\" content (ensure the generated content structure is the same as\
506 | \ described in the template and is in XML format), and finally convert the\
507 | \ long-term memory content into a format suitable for use as a value in\
508 | \ JSON key-value pairs (ensure illegal characters are escaped).\n\n"
509 | title: Agent prompt
510 | type: template-transform
511 | variables:
512 | - value_selector:
513 | - '1719906601173'
514 | - output
515 | variable: longterm_memory_template
516 | - value_selector:
517 | - '1719906924365'
518 | - longterm_memory_space
519 | variable: longterm_memory_space
520 | - value_selector:
521 | - sys
522 | - user_id
523 | variable: user_id
524 | height: 82
525 | id: '1719907030231'
526 | position:
527 | x: 1545
528 | y: 309
529 | positionAbsolute:
530 | x: 1545
531 | y: 309
532 | selected: false
533 | sourcePosition: right
534 | targetPosition: left
535 | type: custom
536 | width: 243
537 | - data:
538 | desc: use LLM to extract action and longterm_memory_content
539 | instruction: '{{#1719907030231.output#}}'
540 | model:
541 | completion_params:
542 | temperature: 0.7
543 | mode: chat
544 | name: deepseek-chat
545 | provider: deepseek
546 | parameters:
547 | - description: 'action to take, should be one of: add_user_memory,update_user_memory,reply'
548 | name: action
549 | required: true
550 | type: string
551 | - description: longterm memory content
552 | name: longterm_memory_content
553 | required: false
554 | type: string
555 | query:
556 | - '1719906575066'
557 | - user_input
558 | reasoning_mode: prompt
559 | selected: false
560 | title: LLM:Parameter Extractor
561 | type: parameter-extractor
562 | variables: []
563 | height: 144
564 | id: '1719907179295'
565 | position:
566 | x: 1848
567 | y: 309
568 | positionAbsolute:
569 | x: 1848
570 | y: 309
571 | selected: false
572 | sourcePosition: right
573 | targetPosition: left
574 | type: custom
575 | width: 243
576 | - data:
577 | conditions:
578 | - comparison_operator: is
579 | id: '1719907361059'
580 | value: reply
581 | variable_selector:
582 | - '1719907179295'
583 | - action
584 | desc: ''
585 | logical_operator: and
586 | selected: false
587 | title: IF:action is reply
588 | type: if-else
589 | height: 124
590 | id: '1719907351349'
591 | position:
592 | x: 2151
593 | y: 309
594 | positionAbsolute:
595 | x: 2151
596 | y: 309
597 | sourcePosition: right
598 | targetPosition: left
599 | type: custom
600 | width: 243
601 | - data:
602 | desc: ''
603 | selected: false
604 | template: just answer user query
605 | title: Action:reply
606 | type: template-transform
607 | variables:
608 | - value_selector:
609 | - '1719907179295'
610 | - action
611 | variable: action
612 | height: 52
613 | id: '1719907410938'
614 | position:
615 | x: 2757
616 | y: 309
617 | positionAbsolute:
618 | x: 2757
619 | y: 309
620 | selected: false
621 | sourcePosition: right
622 | targetPosition: left
623 | type: custom
624 | width: 243
625 | - data:
626 | conditions:
627 | - comparison_operator: is
628 | id: '1719907463190'
629 | value: add_user_memory
630 | variable_selector:
631 | - '1719907179295'
632 | - action
633 | desc: ''
634 | logical_operator: and
635 | selected: false
636 | title: IF:action is add_user_memory
637 | type: if-else
638 | height: 124
639 | id: '1719907452578'
640 | position:
641 | x: 2454
642 | y: 420.5
643 | positionAbsolute:
644 | x: 2454
645 | y: 420.5
646 | selected: false
647 | sourcePosition: right
648 | targetPosition: left
649 | type: custom
650 | width: 243
651 | - data:
652 | authorization:
653 | config: null
654 | type: no-auth
655 | body:
656 | data: '{"segments": [{"content": "{{#1719907179295.longterm_memory_content#}}","keywords":
657 | ["{{#sys.user_id#}}"]}]}'
658 | type: json
659 | desc: ''
660 | headers: Authorization:{{#1719906754157.api_authorization#}}
661 | method: post
662 | params: ''
663 | selected: false
664 | timeout:
665 | max_connect_timeout: 0
666 | max_read_timeout: 0
667 | max_write_timeout: 0
668 | title: Action:add_memory
669 | type: http-request
670 | url: '{{#1719906924365.url_for_add_segment#}}'
671 | variables: []
672 | height: 91
673 | id: '1719907482868'
674 | position:
675 | x: 2757
676 | y: 401
677 | positionAbsolute:
678 | x: 2757
679 | y: 401
680 | selected: false
681 | sourcePosition: right
682 | targetPosition: left
683 | type: custom
684 | width: 243
685 | - data:
686 | authorization:
687 | config: null
688 | type: no-auth
689 | body:
690 | data: '{"segment": {"content": "{{#1719907179295.longterm_memory_content#}}",
691 | "keywords": ["{{#sys.user_id#}}"]}}'
692 | type: json
693 | desc: ''
694 | headers: Authorization:{{#1719906754157.api_authorization#}}
695 | method: post
696 | params: ''
697 | selected: false
698 | timeout:
699 | max_connect_timeout: 0
700 | max_read_timeout: 0
701 | max_write_timeout: 0
702 | title: Action:update_memory
703 | type: http-request
704 | url: '{{#1719906924365.url_for_update_segment#}}'
705 | variables: []
706 | height: 91
707 | id: '1719907683628'
708 | position:
709 | x: 2757
710 | y: 532
711 | positionAbsolute:
712 | x: 2757
713 | y: 532
714 | selected: false
715 | sourcePosition: right
716 | targetPosition: left
717 | type: custom
718 | width: 243
719 | - data:
720 | advanced_settings:
721 | group_enabled: true
722 | groups:
723 | - groupId: 140ac543-c56b-4500-a03d-a8b73903a3a5
724 | group_name: action_result
725 | output_type: string
726 | variables:
727 | - - '1719907410938'
728 | - output
729 | - - '1719907482868'
730 | - body
731 | - - '1719907683628'
732 | - body
733 | desc: ''
734 | output_type: string
735 | selected: false
736 | title: Variable Aggregator
737 | type: variable-aggregator
738 | variables:
739 | - - '1719907410938'
740 | - output
741 | height: 163
742 | id: '1719907835909'
743 | position:
744 | x: 3060
745 | y: 420.5
746 | positionAbsolute:
747 | x: 3060
748 | y: 420.5
749 | selected: false
750 | sourcePosition: right
751 | targetPosition: left
752 | type: custom
753 | width: 243
754 | - data:
755 | author: admin
756 | desc: ''
757 | height: 114
758 | selected: false
759 | showAuthor: false
760 | text: '{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"important
761 | note:","type":"text","version":1},{"type":"linebreak","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"MODEL
762 | for `","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"LLM:Parameter
763 | Extractor","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"
764 | node`, LLM performance must be >= gpt4","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}'
765 | theme: blue
766 | title: ''
767 | type: ''
768 | width: 251
769 | height: 114
770 | id: '1719920303831'
771 | position:
772 | x: 1848
773 | y: 480.37757179352536
774 | positionAbsolute:
775 | x: 1848
776 | y: 480.37757179352536
777 | selected: false
778 | sourcePosition: right
779 | targetPosition: left
780 | type: custom-note
781 | width: 251
782 | - data:
783 | author: admin
784 | desc: ''
785 | height: 177
786 | selected: false
787 | showAuthor: false
788 | text: '{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"memory_template
789 | example:","type":"text","version":1},{"type":"linebreak","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":" ${user_name}","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":" ${user_age}","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":" ${user_hobbies}","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":" ${any_user_info}","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}'
790 | theme: blue
791 | title: ''
792 | type: ''
793 | width: 303
794 | height: 177
795 | id: '1719920815505'
796 | position:
797 | x: 333
798 | y: 445.7167598179409
799 | positionAbsolute:
800 | x: 333
801 | y: 445.7167598179409
802 | selected: true
803 | sourcePosition: right
804 | targetPosition: left
805 | type: custom-note
806 | width: 303
807 | viewport:
808 | x: 143
809 | y: 35
810 | zoom: 0.7
811 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About this repo
2 |
3 | a Dify tool for storing and retrieving long-term-memory, using Dify built-in Knowledge dataset for storing memories, each user has a standalone long-term-memory space.
4 | Make chatbot can persistently remember customized information per user.
5 |
6 | workflow view:
7 |
8 | 
9 |
10 | # How it works
11 |
12 | Here is the major logic of this workflow:
13 |
14 | 1. before each conversation run a "HTTP Request" node named "Action:retrieve_memory_of_user" to request Dify Knowledge API to query user related document segments with filter parameters as `keyword=$current_user_id`
15 | 2. run a "Template" node as "Agent prompt"([agent prompt content](agent-prompt.md)) for preparing agent system prompt, injected customized `$memory_template`
16 |
17 | 2. run a "Parameter Extractor" node to extract `$action`(value will be one of reply/add_user_memory/update_user_memory ) and `$longterm_memory_content` parameters
18 | 3. if the `$action` is not "reply", run a "HTTP Request" node to request Dify Knowledge API to create or update the document segment with `$longterm_memory_content` parameter
19 | 4. if the `$action` is "reply", return the retrieved segments as a output variable `$longterm_memory_space`, so that it can be injected as a LLM node's context in a chatbot
20 |
21 |
22 | # Demo Video
23 |
24 | https://github.com/user-attachments/assets/a61c065f-5e74-4ea3-95ab-5badc87bc9a1
25 |
26 |
27 | # Usage Steps
28 |
29 | ## step 1: create a Knowledge and document for storing long-term-memory
30 |
31 | create a new Knowledge and import an empty document
32 |
33 | - document name: whatever
34 |
35 | - Chunk settings: Automatic
36 |
37 | - Index mode: Economical
38 |
39 | ## step 2: Create a new Secret key for accessing Knowledge API
40 |
41 |
42 |
43 | ## step 3: import the workflow DSL yaml
44 |
45 | 1) download [LongTermMemory.yml](https://raw.githubusercontent.com/rainchen/dify-tool-LongTermMemory/main/LongTermMemory.yml) file and import it using Dify "Import DSL file"
46 |
47 | 2) In the imported workflow, config MODEL for `LLM:Parameter Extractor` node, which LLM performance must be >= gpt4.
48 | Tips: gpt4 series, deepseek-chat are recommended, gpt3.5-turbo was tested but not powerful enough to run this tool.
49 |
50 |
51 | ## step 4: publish the workflow as tool "LongTermMemory"
52 |
53 |
54 |
55 | ## step 5: create new a chatbot app using chatflow
56 |
57 |
58 |
59 | ## step 6: add the "LongTermMemory" tool
60 |
61 | add the "LongTermMemory" tool before LLM node
62 |
63 |
64 |
65 | ## step 7: config parameters for "LongTermMemory" tool
66 |
67 | 
68 |
69 | notes for parameters:
70 |
71 | - base_url: `http://host.docker.internal` for docker-compose deployment on MacOS(use `http://docker.for.win.localhost` for Windows 10/11), append the port like `:8082` if not using default nginx port config
72 |
73 | - dataset_id: dataset id
74 |
75 | - document_id: document id
76 |
77 | - api_key: Dify Knowledge API key, this is not "Chat App API key". You can get one on 'Knowledge -> API ACCESS' page, a Knowledge API key should be look like "dataset-xxx"
78 |
79 | - memory_template: for example
80 |
81 | ```
82 |
83 | ${user_name}
84 | ${user_age}
85 | ${user_hobbies}
86 | ${any_user_info}
87 |
88 | ```
89 |
90 | - user_input: `{{#sys.query#}}`
91 |
92 |
93 |
94 | ## step 8: Config LLM node
95 |
96 |
97 |
98 | CONTEXT:select `LongTermMemory` node's `text`
99 |
100 | SYSTEM prompt:
101 |
102 | ```
103 | {{#context#}}
104 | ```
105 |
106 |
107 |
108 | # Test:
109 |
110 | ### Scenario 1: test long-term-memory remembering
111 |
112 | 
113 |
114 | user input:
115 |
116 | - > who am I?
117 |
118 | - chatbot should reply no idea
119 |
120 | - > I'm Rain
121 |
122 | - check out the knowledge page, there should be a new segment with user_id, name, and keyword attribute tags
123 |
124 | - > I like coding
125 |
126 | - check out the knowledge page, the segment should be updated, tag should be updated
127 |
128 | - > I like reading, also
129 |
130 | - check out the knowledge page, the segment should be updated, tag should be updated
131 |
132 | ### Scenario 2: test long-term-memory retrieving
133 |
134 | 
135 |
136 | Debug and Preview -> Restart (make sure to start a new conversion without chat history)
137 |
138 | user input:
139 |
140 | - what's my name?
141 | - chatbot should reply current user's name
142 | - what do I like?
143 | - chatbot should reply current user's hobbies
144 |
145 |
146 |
147 | ### Scenario 3: each user has a standalone long-term-memory space
148 |
149 | 
150 |
151 | Run App (You can use different browsers to access and impersonate different users)
152 |
153 | user input:
154 |
155 | - I'm Jerry
156 | - I like eating
157 | - check out the knowledge page, there should be a new segment with different user_id, name, and keyword attribute tags
158 |
--------------------------------------------------------------------------------
/agent-prompt.md:
--------------------------------------------------------------------------------
1 | # Long-term Memory Capability
2 |
3 | You possess long-term memory capabilities, maintaining an independent long-term memory space for each user you interact with. Below is an explanation of long-term memory capabilities:
4 |
5 | There is a "Long-term Memory Content Template" that declares the attribute tags to be saved. When the user's input content is related to the attribute tags defined in the template, the system analyzes the user's input based on the semantic meaning of the attribute tags in the template, extracts the corresponding attribute tag values, and finally generates the content for the "Long-term Memory Space" according to the template format.
6 |
7 | ## Long-term Memory Content Template
8 |
9 | The following is the specific long-term memory content template, contained within the `` tag:
10 |
11 |
12 | {{ longterm_memory_template }}
13 |
14 |
15 | ## Long-term Memory Content Template Example
16 |
17 | For example, if the "Long-term Memory Content Template" is set as:
18 |
19 | ```
20 |
21 |
22 | ${user_name}
23 | ${user_age}
24 | ${user_hobbies}
25 |
26 |
27 | ```
28 |
29 | The defined attribute tags in this example are: ``, ``, ``
30 |
31 | Examples of attribute tags corresponding to automatically extracted values are:
32 |
33 | - The `` attribute tag identifies name-related information through the word `name`.
34 | - The `` attribute tag identifies age-related information through the word `age`.
35 | - The `` attribute tag identifies personal interests and hobbies-related information through the word `hobbies`.
36 |
37 | Examples of extracting attribute tag values:
38 |
39 | - For instance, if the user input is `I'm Tom` or `my name is Tom`, the analysis reveals that the user's name is `Tom`, so the value of the `` attribute tag is `Tom`.
40 | - If the user input is `I'm 30`, the analysis shows that the user's age is `30`, so the value of the `` attribute tag is `30`.
41 | - If the user input is `I like reading`, the analysis indicates that the user's hobby is `reading`, so the value of the `` attribute tag is `reading`.
42 |
43 | Thus, in this example, the final "Long-term Memory Space" content generated would be:
44 |
45 | ```
46 |
47 |
48 | Tom
49 | 30
50 | reading
51 |
52 |
53 | ```
54 |
55 | ## Long-term Memory Space
56 |
57 | The content of the long-term memory space is contained within the `` tags:
58 |
59 |
60 | {{ longterm_memory_space }}
61 |
62 |
63 | ## Parameter Extraction
64 |
65 | ### Extracting the `action` Parameter
66 |
67 | The following are the conditions for the `action` parameter values (based on the `Long-term Memory Content Template` and the `Long-term Memory Content Template Example`):
68 |
69 | - `add_user_memory`:
70 | When the `input_text` mentions information related to the attribute tags defined in the `Long-term Memory Content Template`, and the `` tags are **empty**.
71 | - `update_user_memory`:
72 | When the `input_text` mentions information related to the attribute tags defined in the `Long-term Memory Content Template`, and the `` tags are **not empty**.
73 | - `reply`:
74 | When the `input_text` is **unrelated** to the attribute tags defined in the `Long-term Memory Content Template`.
75 |
76 | ### Extracting the `longterm_memory_content` Parameter
77 |
78 | When the `action` parameter value is not `reply`, refer to the examples described in the `Long-term Memory Content Template Example`, use the template defined in the `Long-term Memory Content Template`, analyze the user's input, combine it with the existing information in the "Long-term Memory Space", extract the values of the attribute tags to generate new "Long-term Memory Space" content (ensure the generated content structure is the same as described in the template and is in XML format), and finally convert the long-term memory content into a format suitable for use as a value in JSON key-value pairs (ensure illegal characters are escaped).
79 |
80 |
--------------------------------------------------------------------------------
/screenshots/LongTermMemory-chatbot-demo-config-tool-node-parameters.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainchen/dify-tool-LongTermMemory/85c3b2abca0b8e178b5f326320687fef8c1b1aa4/screenshots/LongTermMemory-chatbot-demo-config-tool-node-parameters.png
--------------------------------------------------------------------------------
/screenshots/LongTermMemory-chatbot-demo-test-long-term-memory-remembering.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainchen/dify-tool-LongTermMemory/85c3b2abca0b8e178b5f326320687fef8c1b1aa4/screenshots/LongTermMemory-chatbot-demo-test-long-term-memory-remembering.png
--------------------------------------------------------------------------------
/screenshots/LongTermMemory-chatbot-demo-test-long-term-memory-retrieving.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainchen/dify-tool-LongTermMemory/85c3b2abca0b8e178b5f326320687fef8c1b1aa4/screenshots/LongTermMemory-chatbot-demo-test-long-term-memory-retrieving.png
--------------------------------------------------------------------------------
/screenshots/LongTermMemory-chatbot-demo-test-long-term-memory-standalone-space.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainchen/dify-tool-LongTermMemory/85c3b2abca0b8e178b5f326320687fef8c1b1aa4/screenshots/LongTermMemory-chatbot-demo-test-long-term-memory-standalone-space.png
--------------------------------------------------------------------------------
/screenshots/LongTermMemory-workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainchen/dify-tool-LongTermMemory/85c3b2abca0b8e178b5f326320687fef8c1b1aa4/screenshots/LongTermMemory-workflow.png
--------------------------------------------------------------------------------