Compare commits

..

129 Commits

Author SHA1 Message Date
83803a72ee fix ollama bug (#999)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-30 18:03:36 +08:00
c3c2515691 Update README (#998)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-05-30 18:00:02 +08:00
117a173fff fix tk_count undefine issue (#996)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-30 16:18:15 +08:00
77363a0875 fix bge rerank normalize issue (#988)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-30 12:55:17 +08:00
843720f958 fix bug in pdf parser (#986)
### What problem does this PR solve?

#963 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-30 11:47:36 +08:00
f077b57f8b set ollama keep_alive (#985)
### What problem does this PR solve?

#980 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-30 11:27:58 +08:00
c62834f870 fix: fixed the issue of error reporting when saving chat configuration #965 (#984)
### What problem does this PR solve?

fix: fixed the issue of error reporting when saving chat configuration
#965

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-30 11:10:54 +08:00
0171082cc5 fix create dialog bug (#982)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-30 09:25:05 +08:00
8dd45459be Add support for HTML file (#973)
### What problem does this PR solve?

Add support for HTML file

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-30 09:12:55 +08:00
dded365b8d Fix:After being idle for a while, new tasks need to be cancel and redo (#958)
### What problem does this PR solve?

After being idle for a while (When Redis Queue exceeds the
SVR_QUEUE_RETENTION(60*60) expiration time), new tasks need to be cancel
and redo.

When use xgroup_create to create a consumer group, set the ID to "$",
meaning that only messages added to the stream after the group is
created will be visible to new consumers. If the application scenario
requires processing messages that already exist in the queue, you might
need to change this ID to "0", so that the new consumer group can read
all messages from the beginning.


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-30 09:03:11 +08:00
9fdd517af6 Update README.md (#978)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-29 20:22:41 +08:00
2604ded2e4 Update README.md (#976)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-29 20:02:16 +08:00
758eb03ccb fix jina adding issure and term weight refinement (#974)
### What problem does this PR solve?

#724 #162

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
- [x] New Feature (non-breaking change which adds functionality)
2024-05-29 19:38:57 +08:00
e0d05a3895 fix: if the conversation name is too long, it will overflow the current item. #607 (#972)
### What problem does this PR solve?

fix: if the conversation name is too long, it will overflow the current
item. #607

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-29 18:32:03 +08:00
614defec21 add rerank model (#969)
### What problem does this PR solve?

feat: add rerank models to the project #724 #162

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-29 16:50:02 +08:00
e1f0644deb feat: add jina (#967)
### What problem does this PR solve?
feat: add jina #650 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-29 16:48:52 +08:00
a135f9f5b6 feat: add rerank models to the project #724 #162 (#966)
### What problem does this PR solve?

Vector similarity weight is displayed incorrectly #965
feat: add rerank models to the project #724 #162
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-29 16:19:08 +08:00
daa4799385 limit the system context length of conversation messages. (#962)
### What problem does this PR solve?

#951 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-29 10:40:07 +08:00
495a6434ec feat: add FlowHeader and delete edge (#959)
### What problem does this PR solve?
feat: add FlowHeader and delete edge #918 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-29 10:01:39 +08:00
21aac545d9 Expanded the supported LLM list (#960)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-28 20:13:03 +08:00
0f317221b4 Update README (#956)
### What problem does this PR solve?

Update README due to support new LLMs.

### Type of change

- [x] Documentation Update

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-05-28 20:05:02 +08:00
a427672229 Fixed a docusaurus display issue (#954)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update

---------

Co-authored-by: KevinHuSh <kevinhu.sh@gmail.com>
2024-05-28 17:26:13 +08:00
196f2b445f fix: fixed the issue of 404 error in the user settings page of the demo site (#948)
### What problem does this PR solve?

fix: fixed the issue of 404 error in the user settings page of the demo
site #947

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-28 11:22:56 +08:00
5041677f11 Add umap-learn, fasttext and volcengine in requirements_arm.txt (#945)
### What problem does this PR solve?

Complete the requirements for ARM

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-28 11:13:48 +08:00
7eee193956 fix #917 #915 (#946)
### What problem does this PR solve?

#917 
#915

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-28 11:13:02 +08:00
9ffd7ae321 Added support for Baichuan LLM (#934)
### What problem does this PR solve?

- Added support for Baichuan LLM

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

Co-authored-by: 海贼宅 <stu_xyx@163.com>
2024-05-28 09:09:37 +08:00
ec6ae744a1 minor editorial updates for clarity (#941)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-27 20:35:08 +08:00
d9bc093df1 feat: test buildNodesAndEdgesFromDSLComponents (#940)
### What problem does this PR solve?
 feat: test buildNodesAndEdgesFromDSLComponents #918

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-27 19:35:14 +08:00
571aaaff22 Add Dockerfile and requirements.txt for arm (#936)
### What problem does this PR solve?

#253 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-27 19:34:49 +08:00
GYH
7d8e03ec38 Update docnm_kwd to doc_name (#939)
### What problem does this PR solve?

Update docnm_kwd to doc_name 
#908 

### Type of change


- [x] Refactoring
2024-05-27 19:14:04 +08:00
65677f65c9 Updated RESTful API Reference (#908)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-27 18:34:16 +08:00
89d296feab Remove duplicated FROM. (#935)
### What problem does this PR solve?
Remove duplicated FROM in Dockerfile.cuda.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-27 17:16:47 +08:00
3ae8a87986 Expanded list of locally deployed embedding models (#930)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-27 14:01:52 +08:00
46454362d7 fix raptor bugs (#928)
### What problem does this PR solve?

#922 
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-27 11:01:20 +08:00
55fb96131e feat: build react flow nodes and edges from mock data #918 (#919)
### What problem does this PR solve?
feat: build react flow nodes and edges from mock data #918

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-27 08:21:30 +08:00
20b57144b0 syntax error (#924)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-27 08:20:32 +08:00
9e3a0e4d03 The fasttext library is missing, and it is used in the operators.py file. (#925)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-27 08:18:47 +08:00
c0d71adaa2 Bug fix for volcengine (#909)
### What problem does this PR solve?
Bug fixes for the VolcEngine

- Bug fix for front-end configuration code of VolcEngine

- Bug fix for tokens counting logic of VolcEngine


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

---------

Co-authored-by: 海贼宅 <stu_xyx@163.com>
2024-05-24 11:34:39 +08:00
735bdf06a4 Update README (#901)
### What problem does this PR solve?

Update README due to implement RAPTOR.

### Type of change

- [x] Documentation Update

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-05-24 08:30:08 +08:00
fe18627ebc Fix some syntax errors, re not import (#904)
re not import

### What problem does this PR solve?

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-23 19:38:13 +08:00
4cda40c3ef feat: fixed issue with threshold translation #882 and add NodeContextMenu (#906)
### What problem does this PR solve?

feat: fixed issue with threshold translation #882
feat: add NodeContextMenu

### Type of change


- [ ] New Feature (non-breaking change which adds functionality)
2024-05-23 18:53:04 +08:00
GYH
1e5c5abe58 Update api_md document/rm (#894)
### What problem does this PR solve?

Update api_md document/rm
#717 

### Type of change

- [x] Documentation Update
2024-05-23 15:19:58 +08:00
6f99bbbb08 add raptor (#899)
### What problem does this PR solve?

#882 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-23 14:31:16 +08:00
3bbdf3b770 fixbug for computing 'not concating feature' (#896)
### What problem does this PR solve?

When pdfparser call `_naive_vertical_merge` method,there is a "not
concating feature " value by computing difference between `b` and `b_`'s
layoutno ,but actually is `b` and `b`. I think it's a bug, so fix it.
Please check again.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-23 14:29:42 +08:00
070b53f3bf feat: RAPTOR is not displayed when the parsing method is picture. (#897)
### What problem does this PR solve?

Implements RAPTOR for better chunking #882

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2024-05-23 14:13:09 +08:00
eb51ad73d6 Add support for VolcEngine - the current version supports SDK2 (#885)
- The main idea is to assemble **ak**, **sk**, and **ep_id** into a
dictionary and store it in the database **api_key** field
- I don’t know much about the front-end, so I learned from Ollama, which
may be redundant.

### Configuration method

- model name

- Format requirements: {"VolcEngine model name":"endpoint_id"}
    - For example: {"Skylark-pro-32K":"ep-xxxxxxxxx"}
    
- Volcano ACCESS_KEY
- Format requirements: VOLC_ACCESSKEY of the volcano engine
corresponding to the model

- Volcano SECRET_KEY
- Format requirements: VOLC_SECRETKEY of the volcano engine
corresponding to the model
    
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-23 11:15:29 +08:00
GYH
fbd0d74053 Add /api/document/rm function (#887)
### What problem does this PR solve?

Delete files from a knowledge base.

#717 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-23 10:32:56 +08:00
170186ee4d feat: remove the space before promptText (#886)
### What problem does this PR solve?

feat: remove the space before promptText #882 


### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-22 18:36:55 +08:00
ed184ed87e Implements RAPTOR for better chunking #882 (#883)
### What problem does this PR solve?

Implements RAPTOR for better chunking #882

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-22 18:04:18 +08:00
GYH
43412571f7 Add api.md:/api/list_kb_docs/ description (#881)
### What problem does this PR solve?

Add api.md:/api/list_kb_docs/ description
#717 

### Type of change

- [x] Documentation Update
2024-05-22 17:37:11 +08:00
17489e6c6c fix import error (#877)
Fix import error for user_app.py

---------

Co-authored-by: yonghui li <yonghui.li@bondex.com.cn>
2024-05-22 16:14:53 +08:00
21453ffff0 fixed: The choices may be empty. (#876)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-22 15:29:07 +08:00
GYH
be13429d05 Add api/list_kb_docs function and modify api/list_chunks (#874)
### What problem does this PR solve?
#717 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-22 14:58:56 +08:00
5178daeeaf Fixed a format issue (#872)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-22 13:39:38 +08:00
d5b8d8e647 fixed a format issue for docusaurus publication (#871)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-22 12:45:34 +08:00
b62a20816e fix: display specific error message when previewing file error #868 (#869)
### What problem does this PR solve?

fix: display specific error message when previewing file error  #868


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-22 11:54:32 +08:00
3cae87a902 Reorganized docs for docusaurus publish (#860)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-21 20:53:55 +08:00
1797f5ce31 fix: the site domain name in the Chat Bot API is hardcoded. #776 (#859)
### What problem does this PR solve?

fix: the site domain name in the Chat Bot API is hardcoded. #776

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-21 17:37:19 +08:00
fe4b2e4670 Updated Launch service from source (#856)
### What problem does this PR solve?

Some nitpicking editorial updates.

### Type of change

- [x] Documentation Update
2024-05-21 16:43:58 +08:00
250119e03a Fix missing docker image version prefix v. (#855)
The variable RAGFLOW_VERSION in docker/.env should start with prefix v
to match docker image tag.

### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-21 14:26:43 +08:00
bae376a479 Update db_models.py 2024-05-21 12:02:22 +08:00
6c32f80bc9 Update before release (#854)
### What problem does this PR solve?

Update version information before release 0.6.0.

### Type of change

- [x] Documentation Update

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-05-21 11:14:02 +08:00
7e74546b73 Set the language default value of the language based on the LANG envi… (#853)
…ronment variable at the initial creation.

1. Set the User's default language based on LANG;
2. Set the Knowledgebase's default language based on LANG; 
3. Set the default language of the Dialog based on LANG;

### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [ ] Bug Fix (non-breaking change which fixes an issue)
- [ ] New Feature (non-breaking change which adds functionality)
- [ ] Documentation Update
- [ ] Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe):
2024-05-21 11:05:41 +08:00
25781113f9 Updated how to handle stalled file parsing (#851)
### What problem does this PR solve?

Refresh file parsing if it is stalled.

### Type of change

- [x] Documentation Update
2024-05-21 09:03:30 +08:00
16fa7db737 Create start_chat.md (#836)
### What problem does this PR solve?

Added instructions on how to set up an AI chat in RAGFlow.

### Type of change

- [x] Documentation Update
2024-05-20 20:06:17 +08:00
a12fcf9156 fix minio helth bug (#850)
### What problem does this PR solve?

#643 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-20 19:35:30 +08:00
GYH
c27c02ea67 Split Excel file into different chunks (#847)
### What problem does this PR solve?


Split Excel into different chunk
### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-20 18:35:15 +08:00
71068895ae Set the number of task_executor processes through the environment variable WS. (#846)
### What problem does this PR solve?


### Type of change

- [x] Other (please describe): Use environment variable to control the
task executor processor number.
2024-05-20 18:32:24 +08:00
93b35f4e58 feat: display the version and backend service status on the page (#848)
### What problem does this PR solve?

#643 feat: display the version and backend service status on the page

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2024-05-20 18:28:36 +08:00
9a01d1b876 The default max tokens of 215 is too small, answers are often cut off.I will modify it to 512 to address this issue. (#845)
### What problem does this PR solve?

### Type of change

- [x] Refactoring
2024-05-20 17:25:19 +08:00
a7bd427116 add locally deployed llm (#841)
### What problem does this PR solve?


### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-20 12:40:59 +08:00
2b36283712 fix english query bug (#840)
### What problem does this PR solve?

#834 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-20 12:23:51 +08:00
6683179d6a fix bug about removing KB. (#839)
### What problem does this PR solve?

#838 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-20 09:23:57 +08:00
673a28e492 fix bug of chat without stream (#830)
### What problem does this PR solve?

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-17 20:03:00 +08:00
2bfacd0469 refine doc about API: completion (#829)
### What problem does this PR solve?
#808 

### Type of change

- [x] Documentation Update
2024-05-17 18:06:20 +08:00
b3c923da6b add doc ids in API: completion (#827)
### What problem does this PR solve?
#808 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-17 17:51:54 +08:00
a1586e0af9 correct mismatched kb doc number (#826)
### What problem does this PR solve?

#620

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-17 17:27:39 +08:00
f6a599461f fix zhipuAI stream issue (#825)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-17 17:07:33 +08:00
GYH
081f922ee6 0517 list chunks (#821)
### What problem does this PR solve?

#717 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-17 15:58:05 +08:00
9f0f5b45cc Default language will be given according to the browse setting and also can be configured #801 (#823)
### What problem does this PR solve?

Default language will be given according to the browse setting and also
can be configured #801
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2024-05-17 15:38:28 +08:00
a2a6a35e94 fix doc number miss-match issue (#822)
### What problem does this PR solve?

#620 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-17 15:35:09 +08:00
9e5d501e83 fix data init error (#820)
### What problem does this PR solve?

#810 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-17 14:33:19 +08:00
4ca176bd41 fix: thumbnails are too large in the chat box #818 (#819)
### What problem does this PR solve?

fix: thumbnails are too large in the chat box #818

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-17 14:16:55 +08:00
c3bc72dfd9 fix too large thumbnail issue (#817)
### What problem does this PR solve?

#709

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-17 14:04:21 +08:00
2dd705fe68 feat: add feishu oauth (#815)
### What problem does this PR solve?

The back-end code adds Feishu oauth

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

Co-authored-by: yonghui li <yonghui.li@bondex.com.cn>
2024-05-17 13:47:05 +08:00
d1614107e2 fix stream chat for ollama (#816)
### What problem does this PR solve?

#709

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-17 12:07:00 +08:00
05fa3aeb08 use smaller docker images (#813)
### What problem does this PR solve?

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-17 09:00:24 +08:00
e73ce39b66 Add 2 embeding models from OpenAI (#812)
### What problem does this PR solve?

#810 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-17 08:51:29 +08:00
d54d1375a5 Initial draft of configure knowledge base (#794)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-16 21:27:09 +08:00
c6c9dbde64 feat: Support for conversational streaming (#809)
### What problem does this PR solve?

feat: Support for conversational streaming
#709

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2024-05-16 20:15:02 +08:00
95f809187e add stream chat (#811)
### What problem does this PR solve?

#709 
### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-16 20:14:53 +08:00
d6772f5dd7 add version (#807)
### What problem does this PR solve?
#709 
### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-16 16:17:48 +08:00
63ca15c595 Fix a bug in 'assistant-setting.tsx' that causes the upload button to… (#796)
… incorrectly appear on the model settings page.

### What problem does this PR solve?

This is an issue with the Upload component on the assistant-setting
page. I use the show variable to explicitly control the button component
within it.

see:

![20240516000417](https://github.com/infiniflow/ragflow/assets/37476944/de88f911-6dbd-412d-a981-86cf60aa2257)


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
- [x] Other (please describe): Add the local models that DeepDoc depends
on to the gitignore file in dev mode.

Signed-off-by: liuchao <lcjia_you@126.com>
2024-05-16 10:49:41 +08:00
7b144cc086 fix: can't capitalize file or folder name (#798)
### What problem does this PR solve?


#792 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-16 09:10:29 +08:00
1c4e92ed35 Knowledge base search is case sensitive (#797)
### What problem does this PR solve?
#793 
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-16 09:00:12 +08:00
10e83f26dc Added file management guide (#788)
### What problem does this PR solve?

Added guide with instructions on managing files in RAGFlow. 

### Type of change

- [x] Documentation Update
2024-05-15 20:02:41 +08:00
6ff63ee2ba Support for code files parse (#789)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-15 16:34:28 +08:00
GYH
12b4c5668c Updated conversation_api.md document/upload (#787)
### What problem does this PR solve?

Updated conversation_api.md document/upload parameter description

### Type of change

- [x] Documentation Update
2024-05-15 16:33:28 +08:00
baad35df30 fix: .knowledgebase folder can be deleted bug and change "Add file to knowledge base" to "Link file to knowledge base" bug (#786)
### What problem does this PR solve?
fix: .knowledgebase folder can be deleted bug 
fix: change "Add file to knowledge base" to "Link file to knowledge
base" bug
#783 #784

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-15 14:53:36 +08:00
5effbfac80 fix: remove Top K in retrieval testing #770 and if the document parsing fails, the error message returned by the backend is displayed (#782)
### What problem does this PR solve?

fix: remove Top K in retrieval testing  #770
fix: if the document parsing fails, the error message returned by the
backend is displayed.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-15 13:58:30 +08:00
4d47b2b459 fix a string format error (#781)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-15 13:02:31 +08:00
d8c080ee52 fix bugs in searching file using keywords (#780)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-15 12:51:57 +08:00
GYH
626ace8639 Updated document upload method (#777)
### What problem does this PR solve?

api_app.py
/document/upload 
add two non mandatory parameters
parser_id:
[naive,qaresume,manual,table,paper,book,laws,presentation,picture,one]
run: 1

### Type of change
- [x] New Feature (non-breaking change which adds functionality)
2024-05-15 12:22:11 +08:00
1e923f1c90 Update README (#779)
### What problem does this PR solve?

#771 

### Type of change

- [x] Documentation Update

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-05-15 12:08:32 +08:00
234afb25d8 feat: support GPT-4o #771 and hide the add button when the folder is a knowledge base (#775)
### What problem does this PR solve?

feat: support GPT-4o  #771 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-15 11:34:57 +08:00
aa1c915d6e support gpt-4o (#773)
### What problem does this PR solve?
#771 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-15 11:16:08 +08:00
77b1520b66 Refactor message output format (#772)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Refactoring

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-05-15 10:48:42 +08:00
6b06ccead4 Miscellaneous updates (#769)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-14 18:46:39 +08:00
282f0857a3 fix: hide the add button when the folder is a knowledge base (#765)
### What problem does this PR solve?

#764 fix: hide the add button when the folder is a knowledge base

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-14 16:53:32 +08:00
d7744f5870 Refactor method name (#760)
### What problem does this PR solve?

#757

### Type of change

- [x] Refactoring

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-05-14 14:48:15 +08:00
9b21b66f23 Create quickstart.md (#743)
### What problem does this PR solve?

Draft quickstart. 

### Type of change

- [x] Documentation Update
2024-05-14 12:22:33 +08:00
aa03dfa453 fix bug of get file (#746)
### What problem does this PR solve?

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-13 14:02:38 +08:00
69b7c61498 fix: typo in user_app.py (#740)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Bug Fix (non-breaking change 
- [x] Other (please describe): Fix typo
2024-05-13 09:25:45 +08:00
8769619bb1 Update readme (#741)
### What problem does this PR solve?

Update readme.

### Type of change

- [x] Documentation Update

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2024-05-12 13:40:47 +08:00
ffe5737f7d let index be batchly. (#733)
### What problem does this PR solve?

let index be batchly.

### Type of change


- [x] Refactoring
2024-05-11 19:47:53 +08:00
04a9e95161 let file in knowledgebases visible in file manager (#714)
### What problem does this PR solve?

Let file in knowledgebases visible in file manager.
#162 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-11 16:04:28 +08:00
91b4a18c47 Make the app name configurable even after the project is built (#731)
### What problem does this PR solve?

Make the app name configurable even after the project is built #730 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-11 16:03:07 +08:00
33eaf6fa2e docs: update README_ja.md (#707)
### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

- [x] Documentation Update
2024-05-10 11:22:40 +08:00
d65ba3e4d7 feat: delete the added model #503 and display an error message when the requested file fails to parse #684 (#708)
### What problem does this PR solve?

feat: delete the added model #503
feat: display an error message when the requested file fails to parse
#684

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2024-05-10 10:38:39 +08:00
bef1bbdf3e Update README with Detailed WebUI Service Launch Instructions (#694)
### What problem does this PR solve?

Improve README by detailing Launch Service from Source section

This commit enhances the README document by adding comprehensive steps
for running the WebUI service in the 'Launch Service from Source'
section. It aims to provide clearer guidance for users attempting to
start the service from the source code, making the setup process more
accessible and understandable.

Key changes include:
- Detailed instructions for setting up and running the WebUI service.
- Necessary prerequisites for launching the service from source.

This update ensures that users have all the information they need to
successfully launch the service, improving the overall usability of our
project.

### Type of change

- [x] Documentation Update
2024-05-10 09:48:50 +08:00
6b36f31f92 Minor editorial updates (#700)
### What problem does this PR solve?

Editorial updates only. 

### Type of change

- [x] Documentation Update
2024-05-10 09:48:24 +08:00
648a2baaa9 fix disabled doc is still retreivalable (#695)
### What problem does this PR solve?

Fix that disabled doc is still retreivalable

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-09 15:32:24 +08:00
9392b8bc8f 0509 faq (#693)
### What problem does this PR solve?

Editorial updates only. 

### Type of change

- [x] Documentation Update
2024-05-09 12:37:45 +08:00
4153a36683 truncate text to fitin embedding model (#692)
### What problem does this PR solve?


### Type of change

- [x] Refactoring
2024-05-09 11:35:08 +08:00
GYH
bca63ad571 Update faq.md (#685)
### What problem does this PR solve?

Updated FAQ: How to upgrade RAGFlow

### Type of change

- [x] Documentation Update
2024-05-09 11:32:36 +08:00
793e29f23a fix: fix uploaded file time error #680 (#690)
### What problem does this PR solve?

fix: fix uploaded file time error #680
feat: support preview of word and excel #684 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-09 11:30:15 +08:00
99be226c7c fix coordinate error (#686)
### What problem does this PR solve?

#683 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2024-05-08 20:00:14 +08:00
7ddb2f19be make sure to raise exception if redis is not there (#674)
### What problem does this PR solve?

### Type of change

- [x] Refactoring
2024-05-08 15:20:45 +08:00
c28f7b5d38 make sure the error will be recorded. (#672)
### What problem does this PR solve?


### Type of change

- [x] Refactoring
2024-05-08 13:58:41 +08:00
207 changed files with 14879 additions and 4373 deletions

1
.gitignore vendored
View File

@ -29,3 +29,4 @@ Cargo.lock
docker/ragflow-logs/
/flask_session
/logs
rag/res/deepdoc

View File

@ -1,4 +1,4 @@
FROM swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow-base:v1.0
FROM infiniflow/ragflow-base:v2.0
USER root
WORKDIR /ragflow
@ -15,6 +15,7 @@ ENV PYTHONPATH=/ragflow/
ENV HF_ENDPOINT=https://hf-mirror.com
ADD docker/entrypoint.sh ./entrypoint.sh
ADD docker/.env ./
RUN chmod +x ./entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]

32
Dockerfile.arm Normal file
View File

@ -0,0 +1,32 @@
FROM python:3.11
USER root
WORKDIR /ragflow
COPY requirements_arm.txt /ragflow/requirements.txt
RUN pip install -i https://mirrors.aliyun.com/pypi/simple/ --default-timeout=1000 -r requirements.txt &&\
python -c "import nltk;nltk.download('punkt');nltk.download('wordnet')"
RUN apt-get update && \
apt-get install -y curl gnupg && \
rm -rf /var/lib/apt/lists/*
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs nginx ffmpeg libsm6 libxext6 libgl1
ADD ./web ./web
RUN cd ./web && npm i --force && npm run build
ADD ./api ./api
ADD ./conf ./conf
ADD ./deepdoc ./deepdoc
ADD ./rag ./rag
ENV PYTHONPATH=/ragflow/
ENV HF_ENDPOINT=https://hf-mirror.com
ADD docker/entrypoint.sh ./entrypoint.sh
ADD docker/.env ./
RUN chmod +x ./entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]

View File

@ -1,4 +1,4 @@
FROM swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow-base:v1.0
FROM infiniflow/ragflow-base:v2.0
USER root
WORKDIR /ragflow

204
README.md
View File

@ -17,16 +17,54 @@
<a href="https://demo.ragflow.io" target="_blank">
<img alt="Static Badge" src="https://img.shields.io/badge/Online-Demo-4e6b99"></a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/badge/docker_pull-ragflow:v0.5.0-brightgreen"
alt="docker pull infiniflow/ragflow:v0.5.0"></a>
<a href="https://github.com/infiniflow/ragflow/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=1570EF" alt="license">
<img src="https://img.shields.io/badge/docker_pull-ragflow:v0.7.0-brightgreen" alt="docker pull infiniflow/ragflow:v0.7.0"></a>
<a href="https://github.com/infiniflow/ragflow/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=2e6cc4" alt="license">
</a>
</p>
<details open>
<summary></b>📕 Table of Contents</b></summary>
- 💡 [What is RAGFlow?](#-what-is-ragflow)
- 🎮 [Demo](#-demo)
- 📌 [Latest Updates](#-latest-updates)
- 🌟 [Key Features](#-key-features)
- 🔎 [System Architecture](#-system-architecture)
- 🎬 [Get Started](#-get-started)
- 🔧 [Configurations](#-configurations)
- 🛠️ [Build from source](#-build-from-source)
- 🛠️ [Launch service from source](#-launch-service-from-source)
- 📚 [Documentation](#-documentation)
- 📜 [Roadmap](#-roadmap)
- 🏄 [Community](#-community)
- 🙌 [Contributing](#-contributing)
</details>
## 💡 What is RAGFlow?
[RAGFlow](https://demo.ragflow.io) is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding. It offers a streamlined RAG workflow for businesses of any scale, combining LLM (Large Language Models) to provide truthful question-answering capabilities, backed by well-founded citations from various complex formatted data.
[RAGFlow](https://ragflow.io/) is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding. It offers a streamlined RAG workflow for businesses of any scale, combining LLM (Large Language Models) to provide truthful question-answering capabilities, backed by well-founded citations from various complex formatted data.
## 🎮 Demo
Try our demo at [https://demo.ragflow.io](https://demo.ragflow.io).
## 📌 Latest Updates
- 2024-05-30 Integrates [BCE](https://github.com/netease-youdao/BCEmbedding), [BGE](https://github.com/FlagOpen/FlagEmbedding), and [Colbert](https://github.com/stanford-futuredata/ColBERT) reranker models.
- 2024-05-28 Supports LLM Baichuan and VolcanoArk.
- 2024-05-23 Supports [RAPTOR](https://arxiv.org/html/2401.18059v1) for better text retrieval.
- 2024-05-21 Supports streaming output and text chunk retrieval API.
- 2024-05-15 Integrates OpenAI GPT-4o.
- 2024-05-08 Integrates LLM DeepSeek-V2.
- 2024-04-26 Adds file management.
- 2024-04-19 Supports conversation API ([detail](./docs/references/api.md)).
- 2024-04-16 Integrates an embedding model 'bce-embedding-base_v1' from [BCEmbedding](https://github.com/netease-youdao/BCEmbedding), and [FastEmbed](https://github.com/qdrant/fastembed), which is designed specifically for light and speedy embedding.
- 2024-04-11 Supports [Xinference](./docs/guides/deploy_local_llm.md) for local LLM deployment.
- 2024-04-10 Adds a new layout recognition model for analyzing legal documents.
- 2024-04-08 Supports [Ollama](./docs/guides/deploy_local_llm.md) for local LLM deployment.
- 2024-04-07 Supports Chinese UI.
## 🌟 Key Features
@ -56,17 +94,6 @@
- Multiple recall paired with fused re-ranking.
- Intuitive APIs for seamless integration with business.
## 📌 Latest Features
- 2024-05-08 Integrates LLM DeepSeek.
- 2024-04-26 Adds file management.
- 2024-04-19 Supports conversation API ([detail](./docs/conversation_api.md)).
- 2024-04-16 Integrates an embedding model 'bce-embedding-base_v1' from [BCEmbedding](https://github.com/netease-youdao/BCEmbedding), and [FastEmbed](https://github.com/qdrant/fastembed), which is designed specifically for light and speedy embedding.
- 2024-04-11 Supports [Xinference](./docs/xinference.md) for local LLM deployment.
- 2024-04-10 Adds a new layout recognition model for analyzing Laws documentation.
- 2024-04-08 Supports [Ollama](./docs/ollama.md) for local LLM deployment.
- 2024-04-07 Supports Chinese UI.
## 🔎 System Architecture
<div align="center" style="margin-top:20px;margin-bottom:20px;">
@ -85,7 +112,7 @@
### 🚀 Start up the server
1. Ensure `vm.max_map_count` >= 262144 ([more](./docs/max_map_count.md)):
1. Ensure `vm.max_map_count` >= 262144 ([more](./docs/guides/max_map_count.md)):
> To check the value of `vm.max_map_count`:
>
@ -114,12 +141,14 @@
3. Build the pre-built Docker images and start up the server:
> Running the following commands automatically downloads the *dev* version RAGFlow Docker image. To download and run a specified Docker version, update `RAGFLOW_VERSION` in **docker/.env** to the intended version, for example `RAGFLOW_VERSION=v0.7.0`, before running the following commands.
```bash
$ cd ragflow/docker
$ chmod +x ./entrypoint.sh
$ docker compose up -d
```
> Please note that running the above commands will automatically download the development version docker image of RAGFlow. If you want to download and run a specific version of docker image, please find the RAGFLOW_VERSION variable in the docker/.env file, change it to the corresponding version, for example, RAGFLOW_VERSION=v0.5.0, and run the above commands.
> The core image is about 9 GB in size and may take a while to load.
@ -150,7 +179,7 @@
> With default settings, you only need to enter `http://IP_OF_YOUR_MACHINE` (**sans** port number) as the default HTTP serving port `80` can be omitted when using the default configurations.
6. In [service_conf.yaml](./docker/service_conf.yaml), select the desired LLM factory in `user_default_llm` and update the `API_KEY` field with the corresponding API key.
> See [./docs/llm_api_key_setup.md](./docs/llm_api_key_setup.md) for more information.
> See [./docs/guides/llm_api_key_setup.md](./docs/guides/llm_api_key_setup.md) for more information.
_The show is now on!_
@ -187,69 +216,104 @@ $ chmod +x ./entrypoint.sh
$ docker compose up -d
```
## 🛠️ Launch Service from Source
## 🛠️ Launch service from source
To launch the service from source, please follow these steps:
To launch the service from source:
1. Clone the repository
```bash
$ git clone https://github.com/infiniflow/ragflow.git
$ cd ragflow/
```
1. Clone the repository:
2. Create a virtual environment (ensure Anaconda or Miniconda is installed)
```bash
$ conda create -n ragflow python=3.11.0
$ conda activate ragflow
$ pip install -r requirements.txt
```
If CUDA version is greater than 12.0, execute the following additional commands:
```bash
$ pip uninstall -y onnxruntime-gpu
$ pip install onnxruntime-gpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
```
```bash
$ git clone https://github.com/infiniflow/ragflow.git
$ cd ragflow/
```
3. Copy the entry script and configure environment variables
```bash
$ cp docker/entrypoint.sh .
$ vi entrypoint.sh
```
Use the following commands to obtain the Python path and the ragflow project path:
```bash
$ which python
$ pwd
```
2. Create a virtual environment, ensuring that Anaconda or Miniconda is installed:
Set the output of `which python` as the value for `PY` and the output of `pwd` as the value for `PYTHONPATH`.
```bash
$ conda create -n ragflow python=3.11.0
$ conda activate ragflow
$ pip install -r requirements.txt
```
```bash
# If your CUDA version is higher than 12.0, run the following additional commands:
$ pip uninstall -y onnxruntime-gpu
$ pip install onnxruntime-gpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
```
If `LD_LIBRARY_PATH` is already configured, it can be commented out.
3. Copy the entry script and configure environment variables:
```bash
# Adjust configurations according to your actual situation; the two export commands are newly added.
PY=${PY}
export PYTHONPATH=${PYTHONPATH}
# Optional: Add Hugging Face mirror
export HF_ENDPOINT=https://hf-mirror.com
```
```bash
# Get the Python path:
$ which python
# Get the ragflow project path:
$ pwd
```
```bash
$ cp docker/entrypoint.sh .
$ vi entrypoint.sh
```
4. Start the base services
```bash
$ cd docker
$ docker compose -f docker-compose-base.yml up -d
```
```bash
# Adjust configurations according to your actual situation (the following two export commands are newly added):
# - Assign the result of `which python` to `PY`.
# - Assign the result of `pwd` to `PYTHONPATH`.
# - Comment out `LD_LIBRARY_PATH`, if it is configured.
# - Optional: Add Hugging Face mirror.
PY=${PY}
export PYTHONPATH=${PYTHONPATH}
export HF_ENDPOINT=https://hf-mirror.com
```
5. Check the configuration files
Ensure that the settings in **docker/.env** match those in **conf/service_conf.yaml**. The IP addresses and ports for related services in **service_conf.yaml** should be changed to the local machine IP and ports exposed by the container.
4. Launch the third-party services (MinIO, Elasticsearch, Redis, and MySQL):
6. Launch the service
```bash
$ chmod +x ./entrypoint.sh
$ bash ./entrypoint.sh
```
```bash
$ cd docker
$ docker compose -f docker-compose-base.yml up -d
```
5. Check the configuration files, ensuring that:
- The settings in **docker/.env** match those in **conf/service_conf.yaml**.
- The IP addresses and ports for related services in **service_conf.yaml** match the local machine IP and ports exposed by the container.
6. Launch the RAGFlow backend service:
```bash
$ chmod +x ./entrypoint.sh
$ bash ./entrypoint.sh
```
7. Launch the frontend service:
```bash
$ cd web
$ npm install --registry=https://registry.npmmirror.com --force
$ vim .umirc.ts
# Update proxy.target to 127.0.0.1:9380
$ npm run dev
```
8. Deploy the frontend service:
```bash
$ cd web
$ npm install --registry=https://registry.npmmirror.com --force
$ umi build
$ mkdir -p /ragflow/web
$ cp -r dist /ragflow/web
$ apt install nginx -y
$ cp ../docker/nginx/proxy.conf /etc/nginx
$ cp ../docker/nginx/nginx.conf /etc/nginx
$ cp ../docker/nginx/ragflow.conf /etc/nginx/conf.d
$ systemctl start nginx
```
## 📚 Documentation
- [FAQ](./docs/faq.md)
- [Quickstart](./docs/quickstart.md)
- [FAQ](./docs/references/faq.md)
## 📜 Roadmap
@ -262,4 +326,4 @@ See the [RAGFlow Roadmap 2024](https://github.com/infiniflow/ragflow/issues/162)
## 🙌 Contributing
RAGFlow flourishes via open-source collaboration. In this spirit, we embrace diverse contributions from the community. If you would like to be a part, review our [Contribution Guidelines](https://github.com/infiniflow/ragflow/blob/main/docs/CONTRIBUTING.md) first.
RAGFlow flourishes via open-source collaboration. In this spirit, we embrace diverse contributions from the community. If you would like to be a part, review our [Contribution Guidelines](./docs/references/CONTRIBUTING.md) first.

View File

@ -17,16 +17,38 @@
<a href="https://demo.ragflow.io" target="_blank">
<img alt="Static Badge" src="https://img.shields.io/badge/Online-Demo-4e6b99"></a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/badge/docker_pull-ragflow:v0.5.0-brightgreen"
alt="docker pull infiniflow/ragflow:v0.5.0"></a>
<img src="https://img.shields.io/badge/docker_pull-ragflow:v0.7.0-brightgreen"
alt="docker pull infiniflow/ragflow:v0.7.0"></a>
<a href="https://github.com/infiniflow/ragflow/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=1570EF" alt="license">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=2e6cc4" alt="license">
</a>
</p>
## 💡 RAGFlow とは?
[RAGFlow](https://demo.ragflow.io) は、深い文書理解に基づいたオープンソースの RAG (Retrieval-Augmented Generation) エンジンである。LLM大規模言語モデルを組み合わせることで、様々な複雑なフォーマットのデータから根拠のある引用に裏打ちされた、信頼できる質問応答機能を実現し、あらゆる規模のビジネスに適した RAG ワークフローを提供します。
[RAGFlow](https://ragflow.io/) は、深い文書理解に基づいたオープンソースの RAG (Retrieval-Augmented Generation) エンジンである。LLM大規模言語モデルを組み合わせることで、様々な複雑なフォーマットのデータから根拠のある引用に裏打ちされた、信頼できる質問応答機能を実現し、あらゆる規模のビジネスに適した RAG ワークフローを提供します。
## 🎮 Demo
デモをお試しください:[https://demo.ragflow.io](https://demo.ragflow.io)。
## 📌 最新情報
- 2024-05-30 [BCE](https://github.com/netease-youdao/BCEmbedding)、[BGE](https://github.com/FlagOpen/FlagEmbedding)、[Colbert](https://github.com/stanford-futuredata/ColBERT) reranker を統合。
- 2024-05-28 LLM BaichuanとVolcanoArkを統合しました。
- 2024-05-23 より良いテキスト検索のために[RAPTOR](https://arxiv.org/html/2401.18059v1)をサポート。
- 2024-05-21 ストリーミング出力とテキストチャンク取得APIをサポート。
- 2024-05-15 OpenAI GPT-4oを統合しました。
- 2024-05-08 LLM DeepSeek-V2を統合しました。
- 2024-04-26 「ファイル管理」機能を追加しました。
- 2024-04-19 会話 API をサポートします ([詳細](./docs/references/api.md))。
- 2024-04-16 [BCEmbedding](https://github.com/netease-youdao/BCEmbedding) から埋め込みモデル「bce-embedding-base_v1」を追加します。
- 2024-04-16 [FastEmbed](https://github.com/qdrant/fastembed) は、軽量かつ高速な埋め込み用に設計されています。
- 2024-04-11 ローカル LLM デプロイメント用に [Xinference](./docs/guides/deploy_local_llm.md) をサポートします。
- 2024-04-10 メソッド「Laws」に新しいレイアウト認識モデルを追加します。
- 2024-04-08 [Ollama](./docs/guides/deploy_local_llm.md) を使用した大規模モデルのローカライズされたデプロイメントをサポートします。
- 2024-04-07 中国語インターフェースをサポートします。
## 🌟 主な特徴
@ -56,18 +78,6 @@
- 複数の想起と融合された再ランク付け。
- 直感的な API によってビジネスとの統合がシームレスに。
## 📌 最新の機能
- 2024-05-08
- 2024-04-26 「ファイル管理」機能を追加しました。
- 2024-04-19 会話 API をサポートします ([詳細](./docs/conversation_api.md))。
- 2024-04-16 [BCEmbedding](https://github.com/netease-youdao/BCEmbedding) から埋め込みモデル「bce-embedding-base_v1」を追加します。
- 2024-04-16 [FastEmbed](https://github.com/qdrant/fastembed) は、軽量かつ高速な埋め込み用に設計されています。
- 2024-04-11 ローカル LLM デプロイメント用に [Xinference](./docs/xinference.md) をサポートします。
- 2024-04-10 メソッド「Laws」に新しいレイアウト認識モデルを追加します。
- 2024-04-08 [Ollama](./docs/ollama.md) を使用した大規模モデルのローカライズされたデプロイメントをサポートします。
- 2024-04-07 中国語インターフェースをサポートします。
## 🔎 システム構成
<div align="center" style="margin-top:20px;margin-bottom:20px;">
@ -86,7 +96,7 @@
### 🚀 サーバーを起動
1. `vm.max_map_count` >= 262144 であることを確認する【[もっと](./docs/max_map_count.md)】:
1. `vm.max_map_count` >= 262144 であることを確認する【[もっと](./docs/guides/max_map_count.md)】:
> `vm.max_map_count` の値をチェックするには:
>
@ -121,7 +131,7 @@
$ docker compose up -d
```
> 上記のコマンドを実行すると、RAGFlowの開発版dockerイメージが自動的にダウンロードされます。 特定のバージョンのDockerイメージをダウンロードして実行したい場合は、docker/.envファイルのRAGFLOW_VERSION変数を見つけて、対応するバージョンに変更してください。 例えば、RAGFLOW_VERSION=v0.5.0として、上記のコマンドを実行してください。
> 上記のコマンドを実行すると、RAGFlowの開発版dockerイメージが自動的にダウンロードされます。 特定のバージョンのDockerイメージをダウンロードして実行したい場合は、docker/.envファイルのRAGFLOW_VERSION変数を見つけて、対応するバージョンに変更してください。 例えば、RAGFLOW_VERSION=v0.7.0として、上記のコマンドを実行してください。
> コアイメージのサイズは約 9 GB で、ロードに時間がかかる場合があります。
@ -152,7 +162,7 @@
> デフォルトの設定を使用する場合、デフォルトの HTTP サービングポート `80` は省略できるので、与えられたシナリオでは、`http://IP_OF_YOUR_MACHINE`(ポート番号は省略)だけを入力すればよい。
6. [service_conf.yaml](./docker/service_conf.yaml) で、`user_default_llm` で希望の LLM ファクトリを選択し、`API_KEY` フィールドを対応する API キーで更新する。
> 詳しくは [./docs/llm_api_key_setup.md](./docs/llm_api_key_setup.md) を参照してください。
> 詳しくは [./docs/guides/llm_api_key_setup.md](./docs/guides/llm_api_key_setup.md) を参照してください。
_これで初期設定完了ショーの開幕です_
@ -183,7 +193,7 @@
```bash
$ git clone https://github.com/infiniflow/ragflow.git
$ cd ragflow/
$ docker build -t infiniflow/ragflow:v0.5.0 .
$ docker build -t infiniflow/ragflow:v0.7.0 .
$ cd ragflow/docker
$ chmod +x ./entrypoint.sh
$ docker compose up -d
@ -251,7 +261,8 @@ $ bash ./entrypoint.sh
## 📚 ドキュメンテーション
- [FAQ](./docs/faq.md)
- [Quickstart](./docs/quickstart.md)
- [FAQ](./docs/references/faq.md)
## 📜 ロードマップ
@ -264,4 +275,4 @@ $ bash ./entrypoint.sh
## 🙌 コントリビュート
RAGFlow はオープンソースのコラボレーションによって発展してきました。この精神に基づき、私たちはコミュニティからの多様なコントリビュートを受け入れています。 参加を希望される方は、まず[コントリビューションガイド](https://github.com/infiniflow/ragflow/blob/main/docs/CONTRIBUTING.md)をご覧ください。
RAGFlow はオープンソースのコラボレーションによって発展してきました。この精神に基づき、私たちはコミュニティからの多様なコントリビュートを受け入れています。 参加を希望される方は、まず[コントリビューションガイド](./docs/references/CONTRIBUTING.md)をご覧ください。

View File

@ -17,16 +17,35 @@
<a href="https://demo.ragflow.io" target="_blank">
<img alt="Static Badge" src="https://img.shields.io/badge/Online-Demo-4e6b99"></a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/badge/docker_pull-ragflow:v0.5.0-brightgreen"
alt="docker pull infiniflow/ragflow:v0.5.0"></a>
<a href="https://github.com/infiniflow/ragflow/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=1570EF" alt="license">
<img src="https://img.shields.io/badge/docker_pull-ragflow:v0.7.0-brightgreen" alt="docker pull infiniflow/ragflow:v0.7.0"></a>
<a href="https://github.com/infiniflow/ragflow/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=2e6cc4" alt="license">
</a>
</p>
## 💡 RAGFlow 是什么?
[RAGFlow](https://demo.ragflow.io) 是一款基于深度文档理解构建的开源 RAGRetrieval-Augmented Generation引擎。RAGFlow 可以为各种规模的企业及个人提供一套精简的 RAG 工作流程结合大语言模型LLM针对用户各类不同的复杂格式数据提供可靠的问答以及有理有据的引用。
[RAGFlow](https://ragflow.io/) 是一款基于深度文档理解构建的开源 RAGRetrieval-Augmented Generation引擎。RAGFlow 可以为各种规模的企业及个人提供一套精简的 RAG 工作流程结合大语言模型LLM针对用户各类不同的复杂格式数据提供可靠的问答以及有理有据的引用。
## 🎮 Demo 试用
请登录网址 [https://demo.ragflow.io](https://demo.ragflow.io) 试用 demo。
## 📌 近期更新
- 2024-05-30 集成 [BCE](https://github.com/netease-youdao/BCEmbedding), [BGE](https://github.com/FlagOpen/FlagEmbedding) 和 [Colbert](https://github.com/stanford-futuredata/ColBERT) 重排序模型。
- 2024-05-28 集成大模型 Baichuan 和火山方舟。
- 2024-05-23 实现 [RAPTOR](https://arxiv.org/html/2401.18059v1) 提供更好的文本检索。
- 2024-05-21 支持流式结果输出和文本块获取API。
- 2024-05-15 集成大模型 OpenAI GPT-4o。
- 2024-05-08 集成大模型 DeepSeek。
- 2024-04-26 增添了'文件管理'功能。
- 2024-04-19 支持对话 API ([更多](./docs/references/api.md))。
- 2024-04-16 集成嵌入模型 [BCEmbedding](https://github.com/netease-youdao/BCEmbedding) 和 专为轻型和高速嵌入而设计的 [FastEmbed](https://github.com/qdrant/fastembed)。
- 2024-04-11 支持用 [Xinference](./docs/guides/deploy_local_llm.md) 本地化部署大模型。
- 2024-04-10 为Laws版面分析增加了底层模型。
- 2024-04-08 支持用 [Ollama](./docs/guides/deploy_local_llm.md) 本地化部署大模型。
- 2024-04-07 支持中文界面。
## 🌟 主要功能
@ -56,17 +75,6 @@
- 基于多路召回、融合重排序。
- 提供易用的 API可以轻松集成到各类企业系统。
## 📌 新增功能
- 2024-05-08 集成大模型 DeepSeek
- 2024-04-26 增添了'文件管理'功能.
- 2024-04-19 支持对话 API ([更多](./docs/conversation_api.md)).
- 2024-04-16 集成嵌入模型 [BCEmbedding](https://github.com/netease-youdao/BCEmbedding) 和 专为轻型和高速嵌入而设计的 [FastEmbed](https://github.com/qdrant/fastembed) 。
- 2024-04-11 支持用 [Xinference](./docs/xinference.md) 本地化部署大模型。
- 2024-04-10 为Laws版面分析增加了底层模型。
- 2024-04-08 支持用 [Ollama](./docs/ollama.md) 本地化部署大模型。
- 2024-04-07 支持中文界面。
## 🔎 系统架构
<div align="center" style="margin-top:20px;margin-bottom:20px;">
@ -85,7 +93,7 @@
### 🚀 启动服务器
1. 确保 `vm.max_map_count` 不小于 262144 【[更多](./docs/max_map_count.md)】:
1. 确保 `vm.max_map_count` 不小于 262144 【[更多](./docs/guides/max_map_count.md)】:
> 如需确认 `vm.max_map_count` 的大小:
>
@ -120,7 +128,7 @@
$ docker compose -f docker-compose-CN.yml up -d
```
> 请注意,运行上述命令会自动下载 RAGFlow 的开发版本 docker 镜像。如果你想下载并运行特定版本的 docker 镜像,请在 docker/.env 文件中找到 RAGFLOW_VERSION 变量,将其改为对应版本。例如 RAGFLOW_VERSION=v0.5.0,然后运行上述命令。
> 请注意,运行上述命令会自动下载 RAGFlow 的开发版本 docker 镜像。如果你想下载并运行特定版本的 docker 镜像,请在 docker/.env 文件中找到 RAGFLOW_VERSION 变量,将其改为对应版本。例如 RAGFLOW_VERSION=v0.7.0,然后运行上述命令。
> 核心镜像文件大约 9 GB可能需要一定时间拉取。请耐心等待。
@ -151,7 +159,7 @@
> 上面这个例子中,您只需输入 http://IP_OF_YOUR_MACHINE 即可:未改动过配置则无需输入端口(默认的 HTTP 服务端口 80
6. 在 [service_conf.yaml](./docker/service_conf.yaml) 文件的 `user_default_llm` 栏配置 LLM factory并在 `API_KEY` 栏填写和你选择的大模型相对应的 API key。
> 详见 [./docs/llm_api_key_setup.md](./docs/llm_api_key_setup.md)。
> 详见 [./docs/guides/llm_api_key_setup.md](./docs/guides/llm_api_key_setup.md)。
_好戏开始接着奏乐接着舞_
@ -182,7 +190,7 @@
```bash
$ git clone https://github.com/infiniflow/ragflow.git
$ cd ragflow/
$ docker build -t infiniflow/ragflow:v0.5.0 .
$ docker build -t infiniflow/ragflow:v0.7.0 .
$ cd ragflow/docker
$ chmod +x ./entrypoint.sh
$ docker compose up -d
@ -247,10 +255,32 @@ $ docker compose -f docker-compose-base.yml up -d
$ chmod +x ./entrypoint.sh
$ bash ./entrypoint.sh
```
7. 启动WebUI服务
```bash
$ cd web
$ npm install --registry=https://registry.npmmirror.com --force
$ vim .umirc.ts
# 修改proxy.target为127.0.0.1:9380
$ npm run dev
```
8. 部署WebUI服务
```bash
$ cd web
$ npm install --registry=https://registry.npmmirror.com --force
$ umi build
$ mkdir -p /ragflow/web
$ cp -r dist /ragflow/web
$ apt install nginx -y
$ cp ../docker/nginx/proxy.conf /etc/nginx
$ cp ../docker/nginx/nginx.conf /etc/nginx
$ cp ../docker/nginx/ragflow.conf /etc/nginx/conf.d
$ systemctl start nginx
```
## 📚 技术文档
- [FAQ](./docs/faq.md)
- [Quickstart](./docs/quickstart.md)
- [FAQ](./docs/references/faq.md)
## 📜 路线图
@ -263,7 +293,7 @@ $ bash ./entrypoint.sh
## 🙌 贡献指南
RAGFlow 只有通过开源协作才能蓬勃发展。秉持这一精神,我们欢迎来自社区的各种贡献。如果您有意参与其中,请查阅我们的[贡献者指南](https://github.com/infiniflow/ragflow/blob/main/docs/CONTRIBUTING.md) 。
RAGFlow 只有通过开源协作才能蓬勃发展。秉持这一精神,我们欢迎来自社区的各种贡献。如果您有意参与其中,请查阅我们的[贡献者指南](./docs/references/CONTRIBUTING.md) 。
## 👥 加入社区

View File

@ -13,21 +13,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import json
import os
import re
from datetime import datetime, timedelta
from flask import request
from flask import request, Response
from flask_login import login_required, current_user
from api.db import FileType, ParserType
from api.db.db_models import APIToken, API4Conversation
from api.db import FileType, ParserType, FileSource
from api.db.db_models import APIToken, API4Conversation, Task, File
from api.db.services import duplicate_name
from api.db.services.api_service import APITokenService, API4ConversationService
from api.db.services.dialog_service import DialogService, chat
from api.db.services.document_service import DocumentService
from api.db.services.file2document_service import File2DocumentService
from api.db.services.file_service import FileService
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.task_service import queue_tasks, TaskService
from api.db.services.user_service import UserTenantService
from api.settings import RetCode
from api.settings import RetCode, retrievaler
from api.utils import get_uuid, current_timestamp, datetime_format
from api.utils.api_utils import server_error_response, get_data_error_result, get_json_result, validate_request
from itsdangerous import URLSafeTimedSerializer
@ -164,6 +168,7 @@ def completion():
e, conv = API4ConversationService.get_by_id(req["conversation_id"])
if not e:
return get_data_error_result(retmsg="Conversation not found!")
if "quote" not in req: req["quote"] = False
msg = []
for m in req["messages"]:
@ -180,13 +185,56 @@ def completion():
return get_data_error_result(retmsg="Dialog not found!")
del req["conversation_id"]
del req["messages"]
ans = chat(dia, msg, **req)
if not conv.reference:
conv.reference = []
conv.reference.append(ans["reference"])
conv.message.append({"role": "assistant", "content": ans["answer"]})
API4ConversationService.append_message(conv.id, conv.to_dict())
return get_json_result(data=ans)
conv.message.append({"role": "assistant", "content": ""})
conv.reference.append({"chunks": [], "doc_aggs": []})
def fillin_conv(ans):
nonlocal conv
if not conv.reference:
conv.reference.append(ans["reference"])
else: conv.reference[-1] = ans["reference"]
conv.message[-1] = {"role": "assistant", "content": ans["answer"]}
def stream():
nonlocal dia, msg, req, conv
try:
for ans in chat(dia, msg, True, **req):
fillin_conv(ans)
for chunk_i in ans['reference'].get('chunks', []):
chunk_i['doc_name'] = chunk_i['docnm_kwd']
chunk_i.pop('docnm_kwd')
yield "data:"+json.dumps({"retcode": 0, "retmsg": "", "data": ans}, ensure_ascii=False) + "\n\n"
API4ConversationService.append_message(conv.id, conv.to_dict())
except Exception as e:
yield "data:" + json.dumps({"retcode": 500, "retmsg": str(e),
"data": {"answer": "**ERROR**: "+str(e), "reference": []}},
ensure_ascii=False) + "\n\n"
yield "data:"+json.dumps({"retcode": 0, "retmsg": "", "data": True}, ensure_ascii=False) + "\n\n"
if req.get("stream", True):
resp = Response(stream(), mimetype="text/event-stream")
resp.headers.add_header("Cache-control", "no-cache")
resp.headers.add_header("Connection", "keep-alive")
resp.headers.add_header("X-Accel-Buffering", "no")
resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8")
return resp
else:
answer = None
for ans in chat(dia, msg, **req):
answer = ans
fillin_conv(ans)
API4ConversationService.append_message(conv.id, conv.to_dict())
break
for chunk_i in answer['reference'].get('chunks',[]):
chunk_i['doc_name'] = chunk_i['docnm_kwd']
chunk_i.pop('docnm_kwd')
return get_json_result(data=answer)
except Exception as e:
return server_error_response(e)
@ -199,7 +247,13 @@ def get(conversation_id):
if not e:
return get_data_error_result(retmsg="Conversation not found!")
return get_json_result(data=conv.to_dict())
conv = conv.to_dict()
for referenct_i in conv['reference']:
for chunk_i in referenct_i['chunks']:
if 'docnm_kwd' in chunk_i.keys():
chunk_i['doc_name'] = chunk_i['docnm_kwd']
chunk_i.pop('docnm_kwd')
return get_json_result(data=conv)
except Exception as e:
return server_error_response(e)
@ -233,6 +287,13 @@ def upload():
if file.filename == '':
return get_json_result(
data=False, retmsg='No file selected!', retcode=RetCode.ARGUMENT_ERROR)
root_folder = FileService.get_root_folder(tenant_id)
pf_id = root_folder["id"]
FileService.init_knowledgebase_docs(pf_id, tenant_id)
kb_root_folder = FileService.get_kb_folder(tenant_id)
kb_folder = FileService.new_a_file_from_kb(kb.tenant_id, kb.name, kb_root_folder["id"])
try:
if DocumentService.get_doc_count(kb.tenant_id) >= int(os.environ.get('MAX_FILE_NUM_PER_USER', 8192)):
return get_data_error_result(
@ -264,11 +325,254 @@ def upload():
"size": len(blob),
"thumbnail": thumbnail(filename, blob)
}
form_data=request.form
if "parser_id" in form_data.keys():
if request.form.get("parser_id").strip() in list(vars(ParserType).values())[1:-3]:
doc["parser_id"] = request.form.get("parser_id").strip()
if doc["type"] == FileType.VISUAL:
doc["parser_id"] = ParserType.PICTURE.value
if re.search(r"\.(ppt|pptx|pages)$", filename):
doc["parser_id"] = ParserType.PRESENTATION.value
doc = DocumentService.insert(doc)
return get_json_result(data=doc.to_json())
doc_result = DocumentService.insert(doc)
FileService.add_file_from_kb(doc, kb_folder["id"], kb.tenant_id)
except Exception as e:
return server_error_response(e)
if "run" in form_data.keys():
if request.form.get("run").strip() == "1":
try:
info = {"run": 1, "progress": 0}
info["progress_msg"] = ""
info["chunk_num"] = 0
info["token_num"] = 0
DocumentService.update_by_id(doc["id"], info)
# if str(req["run"]) == TaskStatus.CANCEL.value:
tenant_id = DocumentService.get_tenant_id(doc["id"])
if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
#e, doc = DocumentService.get_by_id(doc["id"])
TaskService.filter_delete([Task.doc_id == doc["id"]])
e, doc = DocumentService.get_by_id(doc["id"])
doc = doc.to_dict()
doc["tenant_id"] = tenant_id
bucket, name = File2DocumentService.get_minio_address(doc_id=doc["id"])
queue_tasks(doc, bucket, name)
except Exception as e:
return server_error_response(e)
return get_json_result(data=doc_result.to_json())
@manager.route('/list_chunks', methods=['POST'])
# @login_required
def list_chunks():
token = request.headers.get('Authorization').split()[1]
objs = APIToken.query(token=token)
if not objs:
return get_json_result(
data=False, retmsg='Token is not valid!"', retcode=RetCode.AUTHENTICATION_ERROR)
form_data = request.form
try:
if "doc_name" in form_data.keys():
tenant_id = DocumentService.get_tenant_id_by_name(form_data['doc_name'])
doc_id = DocumentService.get_doc_id_by_doc_name(form_data['doc_name'])
elif "doc_id" in form_data.keys():
tenant_id = DocumentService.get_tenant_id(form_data['doc_id'])
doc_id = form_data['doc_id']
else:
return get_json_result(
data=False,retmsg="Can't find doc_name or doc_id"
)
res = retrievaler.chunk_list(doc_id=doc_id, tenant_id=tenant_id)
res = [
{
"content": res_item["content_with_weight"],
"doc_name": res_item["docnm_kwd"],
"img_id": res_item["img_id"]
} for res_item in res
]
except Exception as e:
return server_error_response(e)
return get_json_result(data=res)
@manager.route('/list_kb_docs', methods=['POST'])
# @login_required
def list_kb_docs():
token = request.headers.get('Authorization').split()[1]
objs = APIToken.query(token=token)
if not objs:
return get_json_result(
data=False, retmsg='Token is not valid!"', retcode=RetCode.AUTHENTICATION_ERROR)
tenant_id = objs[0].tenant_id
kb_name = request.form.get("kb_name").strip()
try:
e, kb = KnowledgebaseService.get_by_name(kb_name, tenant_id)
if not e:
return get_data_error_result(
retmsg="Can't find this knowledgebase!")
kb_id = kb.id
except Exception as e:
return server_error_response(e)
page_number = int(request.form.get("page", 1))
items_per_page = int(request.form.get("page_size", 15))
orderby = request.form.get("orderby", "create_time")
desc = request.form.get("desc", True)
keywords = request.form.get("keywords", "")
try:
docs, tol = DocumentService.get_by_kb_id(
kb_id, page_number, items_per_page, orderby, desc, keywords)
docs = [{"doc_id": doc['id'], "doc_name": doc['name']} for doc in docs]
return get_json_result(data={"total": tol, "docs": docs})
except Exception as e:
return server_error_response(e)
@manager.route('/document', methods=['DELETE'])
# @login_required
def document_rm():
token = request.headers.get('Authorization').split()[1]
objs = APIToken.query(token=token)
if not objs:
return get_json_result(
data=False, retmsg='Token is not valid!"', retcode=RetCode.AUTHENTICATION_ERROR)
tenant_id = objs[0].tenant_id
req = request.json
doc_ids = []
try:
doc_ids = [DocumentService.get_doc_id_by_doc_name(doc_name) for doc_name in req.get("doc_names", [])]
for doc_id in req.get("doc_ids", []):
if doc_id not in doc_ids:
doc_ids.append(doc_id)
if not doc_ids:
return get_json_result(
data=False, retmsg="Can't find doc_names or doc_ids"
)
except Exception as e:
return server_error_response(e)
root_folder = FileService.get_root_folder(tenant_id)
pf_id = root_folder["id"]
FileService.init_knowledgebase_docs(pf_id, tenant_id)
errors = ""
for doc_id in doc_ids:
try:
e, doc = DocumentService.get_by_id(doc_id)
if not e:
return get_data_error_result(retmsg="Document not found!")
tenant_id = DocumentService.get_tenant_id(doc_id)
if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
b, n = File2DocumentService.get_minio_address(doc_id=doc_id)
if not DocumentService.remove_document(doc, tenant_id):
return get_data_error_result(
retmsg="Database error (Document removal)!")
f2d = File2DocumentService.get_by_document_id(doc_id)
FileService.filter_delete([File.source_type == FileSource.KNOWLEDGEBASE, File.id == f2d[0].file_id])
File2DocumentService.delete_by_document_id(doc_id)
MINIO.rm(b, n)
except Exception as e:
errors += str(e)
if errors:
return get_json_result(data=False, retmsg=errors, retcode=RetCode.SERVER_ERROR)
return get_json_result(data=True)
@manager.route('/completion_aibotk', methods=['POST'])
@validate_request("Authorization", "conversation_id", "word")
def completion_faq():
import base64
req = request.json
token = req["Authorization"]
objs = APIToken.query(token=token)
if not objs:
return get_json_result(
data=False, retmsg='Token is not valid!"', retcode=RetCode.AUTHENTICATION_ERROR)
e, conv = API4ConversationService.get_by_id(req["conversation_id"])
if not e:
return get_data_error_result(retmsg="Conversation not found!")
if "quote" not in req: req["quote"] = True
msg = []
msg.append({"role": "user", "content": req["word"]})
try:
conv.message.append(msg[-1])
e, dia = DialogService.get_by_id(conv.dialog_id)
if not e:
return get_data_error_result(retmsg="Dialog not found!")
del req["conversation_id"]
if not conv.reference:
conv.reference = []
conv.message.append({"role": "assistant", "content": ""})
conv.reference.append({"chunks": [], "doc_aggs": []})
def fillin_conv(ans):
nonlocal conv
if not conv.reference:
conv.reference.append(ans["reference"])
else: conv.reference[-1] = ans["reference"]
conv.message[-1] = {"role": "assistant", "content": ans["answer"]}
data_type_picture = {
"type": 3,
"url": "base64 content"
}
data = [
{
"type": 1,
"content": ""
}
]
for ans in chat(dia, msg, stream=False, **req):
# answer = ans
data[0]["content"] += re.sub(r'##\d\$\$', '', ans["answer"])
fillin_conv(ans)
API4ConversationService.append_message(conv.id, conv.to_dict())
chunk_idxs = [int(match[2]) for match in re.findall(r'##\d\$\$', ans["answer"])]
for chunk_idx in chunk_idxs[:1]:
if ans["reference"]["chunks"][chunk_idx]["img_id"]:
try:
bkt, nm = ans["reference"]["chunks"][chunk_idx]["img_id"].split("-")
response = MINIO.get(bkt, nm)
data_type_picture["url"] = base64.b64encode(response).decode('utf-8')
data.append(data_type_picture)
except Exception as e:
return server_error_response(e)
break
response = {"code": 200, "msg": "success", "data": data}
return response
except Exception as e:
return server_error_response(e)

View File

@ -38,7 +38,7 @@ import re
@manager.route('/list', methods=['POST'])
@login_required
@validate_request("doc_id")
def list():
def list_chunk():
req = request.json
doc_id = req["doc_id"]
page = int(req.get("page", 1))
@ -150,7 +150,7 @@ def set():
if len(arr) != 2:
return get_data_error_result(
retmsg="Q&A must be separated by TAB/ENTER key.")
q, a = rmPrefix(arr[0]), rmPrefix[arr[1]]
q, a = rmPrefix(arr[0]), rmPrefix(arr[1])
d = beAdoc(d, arr[0], arr[1], not any(
[rag_tokenizer.is_chinese(t) for t in q + a]))
@ -229,6 +229,9 @@ def create():
v = 0.1 * v[0] + 0.9 * v[1]
d["q_%d_vec" % len(v)] = v.tolist()
ELASTICSEARCH.upsert([d], search.index_name(tenant_id))
DocumentService.increment_chunk_num(
doc.id, doc.kb_id, c, 1, 0)
return get_json_result(data={"chunk_id": chunck_id})
except Exception as e:
return server_error_response(e)
@ -254,8 +257,15 @@ def retrieval_test():
embd_mdl = TenantLLMService.model_instance(
kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id)
ranks = retrievaler.retrieval(question, embd_mdl, kb.tenant_id, [kb_id], page, size, similarity_threshold,
vector_similarity_weight, top, doc_ids)
rerank_mdl = None
if req.get("rerank_id"):
rerank_mdl = TenantLLMService.model_instance(
kb.tenant_id, LLMType.RERANK.value, llm_name=req["rerank_id"])
ranks = retrievaler.retrieval(question, embd_mdl, kb.tenant_id, [kb_id], page, size,
similarity_threshold, vector_similarity_weight, top,
doc_ids, rerank_mdl=rerank_mdl)
for c in ranks["chunks"]:
if "vector" in c:
del c["vector"]

View File

@ -13,12 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
from flask import request
from flask import request, Response, jsonify
from flask_login import login_required
from api.db.services.dialog_service import DialogService, ConversationService, chat
from api.utils.api_utils import server_error_response, get_data_error_result, validate_request
from api.utils import get_uuid
from api.utils.api_utils import get_json_result
import json
@manager.route('/set', methods=['POST'])
@ -103,9 +104,12 @@ def list_convsersation():
@manager.route('/completion', methods=['POST'])
@login_required
@validate_request("conversation_id", "messages")
#@validate_request("conversation_id", "messages")
def completion():
req = request.json
#req = {"conversation_id": "9aaaca4c11d311efa461fa163e197198", "messages": [
# {"role": "user", "content": "上海有吗?"}
#]}
msg = []
for m in req["messages"]:
if m["role"] == "system":
@ -123,13 +127,48 @@ def completion():
return get_data_error_result(retmsg="Dialog not found!")
del req["conversation_id"]
del req["messages"]
ans = chat(dia, msg, **req)
if not conv.reference:
conv.reference = []
conv.reference.append(ans["reference"])
conv.message.append({"role": "assistant", "content": ans["answer"]})
ConversationService.update_by_id(conv.id, conv.to_dict())
return get_json_result(data=ans)
conv.message.append({"role": "assistant", "content": ""})
conv.reference.append({"chunks": [], "doc_aggs": []})
def fillin_conv(ans):
nonlocal conv
if not conv.reference:
conv.reference.append(ans["reference"])
else: conv.reference[-1] = ans["reference"]
conv.message[-1] = {"role": "assistant", "content": ans["answer"]}
def stream():
nonlocal dia, msg, req, conv
try:
for ans in chat(dia, msg, True, **req):
fillin_conv(ans)
yield "data:"+json.dumps({"retcode": 0, "retmsg": "", "data": ans}, ensure_ascii=False) + "\n\n"
ConversationService.update_by_id(conv.id, conv.to_dict())
except Exception as e:
yield "data:" + json.dumps({"retcode": 500, "retmsg": str(e),
"data": {"answer": "**ERROR**: "+str(e), "reference": []}},
ensure_ascii=False) + "\n\n"
yield "data:"+json.dumps({"retcode": 0, "retmsg": "", "data": True}, ensure_ascii=False) + "\n\n"
if req.get("stream", True):
resp = Response(stream(), mimetype="text/event-stream")
resp.headers.add_header("Cache-control", "no-cache")
resp.headers.add_header("Connection", "keep-alive")
resp.headers.add_header("X-Accel-Buffering", "no")
resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8")
return resp
else:
answer = None
for ans in chat(dia, msg, **req):
answer = ans
fillin_conv(ans)
ConversationService.update_by_id(conv.id, conv.to_dict())
break
return get_json_result(data=answer)
except Exception as e:
return server_error_response(e)

View File

@ -33,8 +33,12 @@ def set_dialog():
name = req.get("name", "New Dialog")
description = req.get("description", "A helpful Dialog")
top_n = req.get("top_n", 6)
top_k = req.get("top_k", 1024)
rerank_id = req.get("rerank_id", "")
if not rerank_id: req["rerank_id"] = ""
similarity_threshold = req.get("similarity_threshold", 0.1)
vector_similarity_weight = req.get("vector_similarity_weight", 0.3)
if vector_similarity_weight is None: vector_similarity_weight = 0.3
llm_setting = req.get("llm_setting", {})
default_prompt = {
"system": """你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
@ -83,6 +87,8 @@ def set_dialog():
"llm_setting": llm_setting,
"prompt_config": prompt_config,
"top_n": top_n,
"top_k": top_k,
"rerank_id": rerank_id,
"similarity_threshold": similarity_threshold,
"vector_similarity_weight": vector_similarity_weight
}
@ -136,7 +142,7 @@ def get_kb_names(kb_ids):
@manager.route('/list', methods=['GET'])
@login_required
def list():
def list_dialogs():
try:
diags = DialogService.query(
tenant_id=current_user.id,

View File

@ -23,7 +23,7 @@ from elasticsearch_dsl import Q
from flask import request
from flask_login import login_required, current_user
from api.db.db_models import Task
from api.db.db_models import Task, File
from api.db.services.file2document_service import File2DocumentService
from api.db.services.file_service import FileService
from api.db.services.task_service import TaskService, queue_tasks
@ -33,7 +33,7 @@ from api.db.services import duplicate_name
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.utils.api_utils import server_error_response, get_data_error_result, validate_request
from api.utils import get_uuid
from api.db import FileType, TaskStatus, ParserType
from api.db import FileType, TaskStatus, ParserType, FileSource
from api.db.services.document_service import DocumentService
from api.settings import RetCode
from api.utils.api_utils import get_json_result
@ -59,12 +59,19 @@ def upload():
return get_json_result(
data=False, retmsg='No file selected!', retcode=RetCode.ARGUMENT_ERROR)
e, kb = KnowledgebaseService.get_by_id(kb_id)
if not e:
raise LookupError("Can't find this knowledgebase!")
root_folder = FileService.get_root_folder(current_user.id)
pf_id = root_folder["id"]
FileService.init_knowledgebase_docs(pf_id, current_user.id)
kb_root_folder = FileService.get_kb_folder(current_user.id)
kb_folder = FileService.new_a_file_from_kb(kb.tenant_id, kb.name, kb_root_folder["id"])
err = []
for file in file_objs:
try:
e, kb = KnowledgebaseService.get_by_id(kb_id)
if not e:
raise LookupError("Can't find this knowledgebase!")
MAX_FILE_NUM_PER_USER = int(os.environ.get('MAX_FILE_NUM_PER_USER', 0))
if MAX_FILE_NUM_PER_USER > 0 and DocumentService.get_doc_count(kb.tenant_id) >= MAX_FILE_NUM_PER_USER:
raise RuntimeError("Exceed the maximum file number of a free user!")
@ -99,6 +106,8 @@ def upload():
if re.search(r"\.(ppt|pptx|pages)$", filename):
doc["parser_id"] = ParserType.PRESENTATION.value
DocumentService.insert(doc)
FileService.add_file_from_kb(doc, kb_folder["id"], kb.tenant_id)
except Exception as e:
err.append(file.filename + ": " + str(e))
if err:
@ -145,7 +154,7 @@ def create():
@manager.route('/list', methods=['GET'])
@login_required
def list():
def list_docs():
kb_id = request.args.get("kb_id")
if not kb_id:
return get_json_result(
@ -228,34 +237,36 @@ def rm():
req = request.json
doc_ids = req["doc_id"]
if isinstance(doc_ids, str): doc_ids = [doc_ids]
root_folder = FileService.get_root_folder(current_user.id)
pf_id = root_folder["id"]
FileService.init_knowledgebase_docs(pf_id, current_user.id)
errors = ""
for doc_id in doc_ids:
try:
e, doc = DocumentService.get_by_id(doc_id)
if not e:
return get_data_error_result(retmsg="Document not found!")
tenant_id = DocumentService.get_tenant_id(doc_id)
if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
DocumentService.increment_chunk_num(
doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, 0)
if not DocumentService.delete(doc):
b, n = File2DocumentService.get_minio_address(doc_id=doc_id)
if not DocumentService.remove_document(doc, tenant_id):
return get_data_error_result(
retmsg="Database error (Document removal)!")
informs = File2DocumentService.get_by_document_id(doc_id)
if not informs:
MINIO.rm(doc.kb_id, doc.location)
else:
File2DocumentService.delete_by_document_id(doc_id)
f2d = File2DocumentService.get_by_document_id(doc_id)
FileService.filter_delete([File.source_type == FileSource.KNOWLEDGEBASE, File.id == f2d[0].file_id])
File2DocumentService.delete_by_document_id(doc_id)
MINIO.rm(b, n)
except Exception as e:
errors += str(e)
if errors: return server_error_response(e)
if errors:
return get_json_result(data=False, retmsg=errors, retcode=RetCode.SERVER_ERROR)
return get_json_result(data=True)
@ -307,9 +318,10 @@ def rename():
data=False,
retmsg="The extension of file can't be changed",
retcode=RetCode.ARGUMENT_ERROR)
if DocumentService.query(name=req["name"], kb_id=doc.kb_id):
return get_data_error_result(
retmsg="Duplicated document name in the same knowledgebase.")
for d in DocumentService.query(name=req["name"], kb_id=doc.kb_id):
if d.name == req["name"]:
return get_data_error_result(
retmsg="Duplicated document name in the same knowledgebase.")
if not DocumentService.update_by_id(
req["doc_id"], {"name": req["name"]}):
@ -334,12 +346,8 @@ def get(doc_id):
if not e:
return get_data_error_result(retmsg="Document not found!")
informs = File2DocumentService.get_by_document_id(doc_id)
if not informs:
response = flask.make_response(MINIO.get(doc.kb_id, doc.location))
else:
e, file = FileService.get_by_id(informs[0].file_id)
response = flask.make_response(MINIO.get(file.parent_id, doc.location))
b,n = File2DocumentService.get_minio_address(doc_id=doc_id)
response = flask.make_response(MINIO.get(b, n))
ext = re.search(r"\.([^.]+)$", doc.name)
if ext:

View File

@ -58,11 +58,7 @@ def convert():
tenant_id = DocumentService.get_tenant_id(doc_id)
if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
DocumentService.increment_chunk_num(
doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, 0)
if not DocumentService.delete(doc):
if not DocumentService.remove_document(doc, tenant_id):
return get_data_error_result(
retmsg="Database error (Document removal)!")
File2DocumentService.delete_by_file_id(id)
@ -125,11 +121,7 @@ def rm():
tenant_id = DocumentService.get_tenant_id(doc_id)
if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
DocumentService.increment_chunk_num(
doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, 0)
if not DocumentService.delete(doc):
if not DocumentService.remove_document(doc, tenant_id):
return get_data_error_result(
retmsg="Database error (Document removal)!")
return get_json_result(data=True)

View File

@ -26,7 +26,7 @@ from api.db.services.document_service import DocumentService
from api.db.services.file2document_service import File2DocumentService
from api.utils.api_utils import server_error_response, get_data_error_result, validate_request
from api.utils import get_uuid
from api.db import FileType
from api.db import FileType, FileSource
from api.db.services import duplicate_name
from api.db.services.file_service import FileService
from api.settings import RetCode
@ -45,7 +45,7 @@ def upload():
if not pf_id:
root_folder = FileService.get_root_folder(current_user.id)
pf_id = root_folder.id
pf_id = root_folder["id"]
if 'file' not in request.files:
return get_json_result(
@ -132,7 +132,7 @@ def create():
input_file_type = request.json.get("type")
if not pf_id:
root_folder = FileService.get_root_folder(current_user.id)
pf_id = root_folder.id
pf_id = root_folder["id"]
try:
if not FileService.is_parent_folder_exist(pf_id):
@ -165,7 +165,7 @@ def create():
@manager.route('/list', methods=['GET'])
@login_required
def list():
def list_files():
pf_id = request.args.get("parent_id")
keywords = request.args.get("keywords", "")
@ -176,7 +176,8 @@ def list():
desc = request.args.get("desc", True)
if not pf_id:
root_folder = FileService.get_root_folder(current_user.id)
pf_id = root_folder.id
pf_id = root_folder["id"]
FileService.init_knowledgebase_docs(pf_id, current_user.id)
try:
e, file = FileService.get_by_id(pf_id)
if not e:
@ -199,7 +200,7 @@ def list():
def get_root_folder():
try:
root_folder = FileService.get_root_folder(current_user.id)
return get_json_result(data={"root_folder": root_folder.to_json()})
return get_json_result(data={"root_folder": root_folder})
except Exception as e:
return server_error_response(e)
@ -250,6 +251,8 @@ def rm():
return get_data_error_result(retmsg="File or Folder not found!")
if not file.tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
if file.source_type == FileSource.KNOWLEDGEBASE:
continue
if file.type == FileType.FOLDER.value:
file_id_list = FileService.get_all_innermost_file_ids(file_id, [])
@ -274,11 +277,7 @@ def rm():
tenant_id = DocumentService.get_tenant_id(doc_id)
if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
DocumentService.increment_chunk_num(
doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, 0)
if not DocumentService.delete(doc):
if not DocumentService.remove_document(doc, tenant_id):
return get_data_error_result(
retmsg="Database error (Document removal)!")
File2DocumentService.delete_by_file_id(file_id)
@ -303,9 +302,10 @@ def rename():
data=False,
retmsg="The extension of file can't be changed",
retcode=RetCode.ARGUMENT_ERROR)
if FileService.query(name=req["name"], pf_id=file.parent_id):
return get_data_error_result(
retmsg="Duplicated file name in the same folder.")
for file in FileService.query(name=req["name"], pf_id=file.parent_id):
if file.name == req["name"]:
return get_data_error_result(
retmsg="Duplicated file name in the same folder.")
if not FileService.update_by_id(
req["file_id"], {"name": req["name"]}):

View File

@ -19,12 +19,14 @@ from flask_login import login_required, current_user
from api.db.services import duplicate_name
from api.db.services.document_service import DocumentService
from api.db.services.file2document_service import File2DocumentService
from api.db.services.file_service import FileService
from api.db.services.user_service import TenantService, UserTenantService
from api.utils.api_utils import server_error_response, get_data_error_result, validate_request
from api.utils import get_uuid, get_format_time
from api.db import StatusEnum, UserTenantRole
from api.db import StatusEnum, UserTenantRole, FileSource
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.db_models import Knowledgebase
from api.db.db_models import Knowledgebase, File
from api.settings import stat_logger, RetCode
from api.utils.api_utils import get_json_result
from rag.nlp import search
@ -109,7 +111,7 @@ def detail():
@manager.route('/list', methods=['GET'])
@login_required
def list():
def list_kbs():
page_number = request.args.get("page", 1)
items_per_page = request.args.get("page_size", 150)
orderby = request.args.get("orderby", "create_time")
@ -136,17 +138,14 @@ def rm():
data=False, retmsg=f'Only owner of knowledgebase authorized for this operation.', retcode=RetCode.OPERATING_ERROR)
for doc in DocumentService.query(kb_id=req["kb_id"]):
ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=doc.id), idxnm=search.index_name(kbs[0].tenant_id))
DocumentService.increment_chunk_num(
doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, 0)
if not DocumentService.delete(doc):
if not DocumentService.remove_document(doc, kbs[0].tenant_id):
return get_data_error_result(
retmsg="Database error (Document removal)!")
f2d = File2DocumentService.get_by_document_id(doc.id)
FileService.filter_delete([File.source_type == FileSource.KNOWLEDGEBASE, File.id == f2d[0].file_id])
File2DocumentService.delete_by_document_id(doc.id)
if not KnowledgebaseService.update_by_id(
req["kb_id"], {"status": StatusEnum.INVALID.value}):
if not KnowledgebaseService.delete_by_id(req["kb_id"]):
return get_data_error_result(
retmsg="Database error (Knowledgebase removal)!")
return get_json_result(data=True)

View File

@ -20,7 +20,7 @@ from api.utils.api_utils import server_error_response, get_data_error_result, va
from api.db import StatusEnum, LLMType
from api.db.db_models import TenantLLM
from api.utils.api_utils import get_json_result
from rag.llm import EmbeddingModel, ChatModel
from rag.llm import EmbeddingModel, ChatModel, RerankModel
@manager.route('/factories', methods=['GET'])
@ -28,7 +28,7 @@ from rag.llm import EmbeddingModel, ChatModel
def factories():
try:
fac = LLMFactoriesService.get_all()
return get_json_result(data=[f.to_dict() for f in fac if f.name not in ["Youdao", "FastEmbed"]])
return get_json_result(data=[f.to_dict() for f in fac if f.name not in ["Youdao", "FastEmbed", "BAAI"]])
except Exception as e:
return server_error_response(e)
@ -39,17 +39,18 @@ def factories():
def set_api_key():
req = request.json
# test if api key works
chat_passed = False
chat_passed, embd_passed, rerank_passed = False, False, False
factory = req["llm_factory"]
msg = ""
for llm in LLMService.query(fid=factory):
if llm.model_type == LLMType.EMBEDDING.value:
if not embd_passed and llm.model_type == LLMType.EMBEDDING.value:
mdl = EmbeddingModel[factory](
req["api_key"], llm.llm_name, base_url=req.get("base_url"))
try:
arr, tc = mdl.encode(["Test if the api key is available"])
if len(arr[0]) == 0 or tc == 0:
raise Exception("Fail")
embd_passed = True
except Exception as e:
msg += f"\nFail to access embedding model({llm.llm_name}) using this api key." + str(e)
elif not chat_passed and llm.model_type == LLMType.CHAT.value:
@ -60,10 +61,21 @@ def set_api_key():
"temperature": 0.9})
if not tc:
raise Exception(m)
chat_passed = True
except Exception as e:
msg += f"\nFail to access model({llm.llm_name}) using this api key." + str(
e)
chat_passed = True
elif not rerank_passed and llm.model_type == LLMType.RERANK:
mdl = RerankModel[factory](
req["api_key"], llm.llm_name, base_url=req.get("base_url"))
try:
arr, tc = mdl.similarity("What's the weather?", ["Is it sunny today?"])
if len(arr) == 0 or tc == 0:
raise Exception("Fail")
except Exception as e:
msg += f"\nFail to access model({llm.llm_name}) using this api key." + str(
e)
rerank_passed = True
if msg:
return get_data_error_result(retmsg=msg)
@ -96,16 +108,29 @@ def set_api_key():
@validate_request("llm_factory", "llm_name", "model_type")
def add_llm():
req = request.json
factory = req["llm_factory"]
# For VolcEngine, due to its special authentication method
# Assemble volc_ak, volc_sk, endpoint_id into api_key
if factory == "VolcEngine":
temp = list(eval(req["llm_name"]).items())[0]
llm_name = temp[0]
endpoint_id = temp[1]
api_key = '{' + f'"volc_ak": "{req.get("volc_ak", "")}", ' \
f'"volc_sk": "{req.get("volc_sk", "")}", ' \
f'"ep_id": "{endpoint_id}", ' + '}'
else:
llm_name = req["llm_name"]
api_key = "xxxxxxxxxxxxxxx"
llm = {
"tenant_id": current_user.id,
"llm_factory": req["llm_factory"],
"llm_factory": factory,
"model_type": req["model_type"],
"llm_name": req["llm_name"],
"llm_name": llm_name,
"api_base": req.get("api_base", ""),
"api_key": "xxxxxxxxxxxxxxx"
"api_key": api_key
}
factory = req["llm_factory"]
msg = ""
if llm["model_type"] == LLMType.EMBEDDING.value:
mdl = EmbeddingModel[factory](
@ -118,7 +143,10 @@ def add_llm():
msg += f"\nFail to access embedding model({llm['llm_name']})." + str(e)
elif llm["model_type"] == LLMType.CHAT.value:
mdl = ChatModel[factory](
key=None, model_name=llm["llm_name"], base_url=llm["api_base"])
key=llm['api_key'] if factory == "VolcEngine" else None,
model_name=llm["llm_name"],
base_url=llm["api_base"]
)
try:
m, tc = mdl.chat(None, [{"role": "user", "content": "Hello! How are you doing!"}], {
"temperature": 0.9})
@ -134,7 +162,6 @@ def add_llm():
if msg:
return get_data_error_result(retmsg=msg)
if not TenantLLMService.filter_update(
[TenantLLM.tenant_id == current_user.id, TenantLLM.llm_factory == factory, TenantLLM.llm_name == llm["llm_name"]], llm):
TenantLLMService.save(**llm)
@ -142,6 +169,16 @@ def add_llm():
return get_json_result(data=True)
@manager.route('/delete_llm', methods=['POST'])
@login_required
@validate_request("llm_factory", "llm_name")
def delete_llm():
req = request.json
TenantLLMService.filter_delete(
[TenantLLM.tenant_id == current_user.id, TenantLLM.llm_factory == req["llm_factory"], TenantLLM.llm_name == req["llm_name"]])
return get_json_result(data=True)
@manager.route('/my_llms', methods=['GET'])
@login_required
def my_llms():
@ -165,7 +202,7 @@ def my_llms():
@manager.route('/list', methods=['GET'])
@login_required
def list():
def list_app():
model_type = request.args.get("model_type")
try:
objs = TenantLLMService.query(tenant_id=current_user.id)
@ -174,7 +211,7 @@ def list():
llms = [m.to_dict()
for m in llms if m.status == StatusEnum.VALID.value]
for m in llms:
m["available"] = m["fid"] in facts or m["llm_name"].lower() == "flag-embedding" or m["fid"] in ["Youdao","FastEmbed"]
m["available"] = m["fid"] in facts or m["llm_name"].lower() == "flag-embedding" or m["fid"] in ["Youdao","FastEmbed", "BAAI"]
llm_set = set([m["llm_name"] for m in llms])
for o in objs:
@ -184,7 +221,7 @@ def list():
res = {}
for m in llms:
if model_type and m["model_type"] != model_type:
if model_type and m["model_type"].find(model_type)<0:
continue
if m["fid"] not in res:
res[m["fid"]] = []

68
api/apps/system_app.py Normal file
View File

@ -0,0 +1,68 @@
#
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License
#
from flask_login import login_required
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.utils.api_utils import get_json_result
from api.versions import get_rag_version
from rag.settings import SVR_QUEUE_NAME
from rag.utils.es_conn import ELASTICSEARCH
from rag.utils.minio_conn import MINIO
from timeit import default_timer as timer
from rag.utils.redis_conn import REDIS_CONN
@manager.route('/version', methods=['GET'])
@login_required
def version():
return get_json_result(data=get_rag_version())
@manager.route('/status', methods=['GET'])
@login_required
def status():
res = {}
st = timer()
try:
res["es"] = ELASTICSEARCH.health()
res["es"]["elapsed"] = "{:.1f}".format((timer() - st)*1000.)
except Exception as e:
res["es"] = {"status": "red", "elapsed": "{:.1f}".format((timer() - st)*1000.), "error": str(e)}
st = timer()
try:
MINIO.health()
res["minio"] = {"status": "green", "elapsed": "{:.1f}".format((timer() - st)*1000.)}
except Exception as e:
res["minio"] = {"status": "red", "elapsed": "{:.1f}".format((timer() - st)*1000.), "error": str(e)}
st = timer()
try:
KnowledgebaseService.get_by_id("x")
res["mysql"] = {"status": "green", "elapsed": "{:.1f}".format((timer() - st)*1000.)}
except Exception as e:
res["mysql"] = {"status": "red", "elapsed": "{:.1f}".format((timer() - st)*1000.), "error": str(e)}
st = timer()
try:
qinfo = REDIS_CONN.health(SVR_QUEUE_NAME)
res["redis"] = {"status": "green", "elapsed": "{:.1f}".format((timer() - st)*1000.),
"pending": qinfo.get("pending", 0)}
except Exception as e:
res["redis"] = {"status": "red", "elapsed": "{:.1f}".format((timer() - st)*1000.), "error": str(e)}
return get_json_result(data=res)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import json
import re
from datetime import datetime
@ -25,8 +26,9 @@ from api.db.services.llm_service import TenantLLMService, LLMService
from api.utils.api_utils import server_error_response, validate_request
from api.utils import get_uuid, get_format_time, decrypt, download_img, current_timestamp, datetime_format
from api.db import UserTenantRole, LLMType, FileType
from api.settings import RetCode, GITHUB_OAUTH, CHAT_MDL, EMBEDDING_MDL, ASR_MDL, IMAGE2TEXT_MDL, PARSERS, API_KEY, \
LLM_FACTORY, LLM_BASE_URL
from api.settings import RetCode, GITHUB_OAUTH, FEISHU_OAUTH, CHAT_MDL, EMBEDDING_MDL, ASR_MDL, IMAGE2TEXT_MDL, PARSERS, \
API_KEY, \
LLM_FACTORY, LLM_BASE_URL, RERANK_MDL
from api.db.services.user_service import UserService, TenantService, UserTenantService
from api.db.services.file_service import FileService
from api.settings import stat_logger
@ -122,6 +124,79 @@ def github_callback():
return redirect("/?auth=%s" % user.get_id())
@manager.route('/feishu_callback', methods=['GET'])
def feishu_callback():
import requests
app_access_token_res = requests.post(FEISHU_OAUTH.get("app_access_token_url"), data=json.dumps({
"app_id": FEISHU_OAUTH.get("app_id"),
"app_secret": FEISHU_OAUTH.get("app_secret")
}), headers={"Content-Type": "application/json; charset=utf-8"})
app_access_token_res = app_access_token_res.json()
if app_access_token_res['code'] != 0:
return redirect("/?error=%s" % app_access_token_res)
res = requests.post(FEISHU_OAUTH.get("user_access_token_url"), data=json.dumps({
"grant_type": FEISHU_OAUTH.get("grant_type"),
"code": request.args.get('code')
}), headers={"Content-Type": "application/json; charset=utf-8",
'Authorization': f"Bearer {app_access_token_res['app_access_token']}"})
res = res.json()
if res['code'] != 0:
return redirect("/?error=%s" % res["message"])
if "contact:user.email:readonly" not in res["data"]["scope"].split(" "):
return redirect("/?error=contact:user.email:readonly not in scope")
session["access_token"] = res["data"]["access_token"]
session["access_token_from"] = "feishu"
userinfo = user_info_from_feishu(session["access_token"])
users = UserService.query(email=userinfo["email"])
user_id = get_uuid()
if not users:
try:
try:
avatar = download_img(userinfo["avatar_url"])
except Exception as e:
stat_logger.exception(e)
avatar = ""
users = user_register(user_id, {
"access_token": session["access_token"],
"email": userinfo["email"],
"avatar": avatar,
"nickname": userinfo["en_name"],
"login_channel": "feishu",
"last_login_time": get_format_time(),
"is_superuser": False,
})
if not users:
raise Exception('Register user failure.')
if len(users) > 1:
raise Exception('Same E-mail exist!')
user = users[0]
login_user(user)
return redirect("/?auth=%s" % user.get_id())
except Exception as e:
rollback_user_registration(user_id)
stat_logger.exception(e)
return redirect("/?error=%s" % str(e))
user = users[0]
user.access_token = get_uuid()
login_user(user)
user.save()
return redirect("/?auth=%s" % user.get_id())
def user_info_from_feishu(access_token):
import requests
headers = {"Content-Type": "application/json; charset=utf-8",
'Authorization': f"Bearer {access_token}"}
res = requests.get(
f"https://open.feishu.cn/open-apis/authen/v1/user_info",
headers=headers)
user_info = res.json()["data"]
user_info["email"] = None if user_info.get("email") == "" else user_info["email"]
return user_info
def user_info_from_github(access_token):
import requests
headers = {"Accept": "application/json",
@ -200,7 +275,7 @@ def rollback_user_registration(user_id):
except Exception as e:
pass
try:
TenantLLM.delete().where(TenantLLM.tenant_id == user_id).excute()
TenantLLM.delete().where(TenantLLM.tenant_id == user_id).execute()
except Exception as e:
pass
@ -214,7 +289,8 @@ def user_register(user_id, user):
"embd_id": EMBEDDING_MDL,
"asr_id": ASR_MDL,
"parser_ids": PARSERS,
"img2txt_id": IMAGE2TEXT_MDL
"img2txt_id": IMAGE2TEXT_MDL,
"rerank_id": RERANK_MDL
}
usr_tenant = {
"tenant_id": user_id,

View File

@ -54,6 +54,7 @@ class LLMType(StrEnum):
EMBEDDING = 'embedding'
SPEECH2TEXT = 'speech2text'
IMAGE2TEXT = 'image2text'
RERANK = 'rerank'
class ChatStyle(StrEnum):
@ -83,3 +84,11 @@ class ParserType(StrEnum):
NAIVE = "naive"
PICTURE = "picture"
ONE = "one"
class FileSource(StrEnum):
LOCAL = ""
KNOWLEDGEBASE = "knowledgebase"
S3 = "s3"
KNOWLEDGEBASE_FOLDER_NAME=".knowledgebase"

View File

@ -21,14 +21,13 @@ import operator
from functools import wraps
from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer
from flask_login import UserMixin
from playhouse.migrate import MySQLMigrator, migrate
from peewee import (
BigAutoField, BigIntegerField, BooleanField, CharField,
CompositeKey, Insert, IntegerField, TextField, FloatField, DateTimeField,
BigIntegerField, BooleanField, CharField,
CompositeKey, IntegerField, TextField, FloatField, DateTimeField,
Field, Model, Metadata
)
from playhouse.pool import PooledMySQLDatabase
from api.db import SerializedType, ParserType
from api.settings import DATABASE, stat_logger, SECRET_KEY
from api.utils.log_utils import getLogger
@ -344,7 +343,7 @@ class DataBaseModel(BaseModel):
@DB.connection_context()
def init_database_tables():
def init_database_tables(alter_fields=[]):
members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
table_objs = []
create_failed_list = []
@ -361,6 +360,7 @@ def init_database_tables():
if create_failed_list:
LOGGER.info(f"create tables failed: {create_failed_list}")
raise Exception(f"create tables failed: {create_failed_list}")
migrate_db()
def fill_db_model_object(model_object, human_model_dict):
@ -386,7 +386,7 @@ class User(DataBaseModel, UserMixin):
max_length=32,
null=True,
help_text="English|Chinese",
default="English")
default="Chinese" if "zh_CN" in os.getenv("LANG", "") else "English")
color_schema = CharField(
max_length=32,
null=True,
@ -437,6 +437,10 @@ class Tenant(DataBaseModel):
max_length=128,
null=False,
help_text="default image to text model ID")
rerank_id = CharField(
max_length=128,
null=False,
help_text="default rerank model ID")
parser_ids = CharField(
max_length=256,
null=False,
@ -578,7 +582,7 @@ class Knowledgebase(DataBaseModel):
language = CharField(
max_length=32,
null=True,
default="English",
default="Chinese" if "zh_CN" in os.getenv("LANG", "") else "English",
help_text="English|Chinese")
description = TextField(null=True, help_text="KB description")
embd_id = CharField(
@ -699,6 +703,11 @@ class File(DataBaseModel):
help_text="where dose it store")
size = IntegerField(default=0)
type = CharField(max_length=32, null=False, help_text="file extension")
source_type = CharField(
max_length=128,
null=False,
default="",
help_text="where dose this document come from")
class Meta:
db_table = "file"
@ -750,11 +759,11 @@ class Dialog(DataBaseModel):
language = CharField(
max_length=32,
null=True,
default="Chinese",
default="Chinese" if "zh_CN" in os.getenv("LANG", "") else "English",
help_text="English|Chinese")
llm_id = CharField(max_length=128, null=False, help_text="default llm ID")
llm_setting = JSONField(null=False, default={"temperature": 0.1, "top_p": 0.3, "frequency_penalty": 0.7,
"presence_penalty": 0.4, "max_tokens": 215})
"presence_penalty": 0.4, "max_tokens": 512})
prompt_type = CharField(
max_length=16,
null=False,
@ -766,11 +775,16 @@ class Dialog(DataBaseModel):
similarity_threshold = FloatField(default=0.2)
vector_similarity_weight = FloatField(default=0.3)
top_n = IntegerField(default=6)
top_k = IntegerField(default=1024)
do_refer = CharField(
max_length=1,
null=False,
help_text="it needs to insert reference index into answer or not",
default="1")
rerank_id = CharField(
max_length=128,
null=False,
help_text="default rerank model ID")
kb_ids = JSONField(null=False, default=[])
status = CharField(
@ -817,3 +831,32 @@ class API4Conversation(DataBaseModel):
class Meta:
db_table = "api_4_conversation"
def migrate_db():
with DB.transaction():
migrator = MySQLMigrator(DB)
try:
migrate(
migrator.add_column('file', 'source_type', CharField(max_length=128, null=False, default="", help_text="where dose this document come from"))
)
except Exception as e:
pass
try:
migrate(
migrator.add_column('tenant', 'rerank_id', CharField(max_length=128, null=False, default="BAAI/bge-reranker-v2-m3", help_text="default rerank model ID"))
)
except Exception as e:
pass
try:
migrate(
migrator.add_column('dialog', 'rerank_id', CharField(max_length=128, null=False, default="", help_text="default rerank model ID"))
)
except Exception as e:
pass
try:
migrate(
migrator.add_column('dialog', 'top_k', IntegerField(default=1024))
)
except Exception as e:
pass

View File

@ -16,10 +16,13 @@
import os
import time
import uuid
from copy import deepcopy
from api.db import LLMType, UserTenantRole
from api.db.db_models import init_database_tables as init_web_db, LLMFactories, LLM, TenantLLM
from api.db.services import UserService
from api.db.services.document_service import DocumentService
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.llm_service import LLMFactoriesService, LLMService, TenantLLMService, LLMBundle
from api.db.services.user_service import TenantService, UserTenantService
from api.settings import CHAT_MDL, EMBEDDING_MDL, ASR_MDL, IMAGE2TEXT_MDL, PARSERS, LLM_FACTORY, API_KEY, LLM_BASE_URL
@ -129,7 +132,27 @@ factory_infos = [{
"logo": "",
"tags": "LLM",
"status": "1",
},
},{
"name": "VolcEngine",
"logo": "",
"tags": "LLM, TEXT EMBEDDING",
"status": "1",
},{
"name": "BaiChuan",
"logo": "",
"tags": "LLM,TEXT EMBEDDING",
"status": "1",
},{
"name": "Jina",
"logo": "",
"tags": "TEXT EMBEDDING, TEXT RE-RANK",
"status": "1",
},{
"name": "BAAI",
"logo": "",
"tags": "TEXT EMBEDDING, TEXT RE-RANK",
"status": "1",
}
# {
# "name": "文心一言",
# "logo": "",
@ -143,6 +166,12 @@ def init_llm_factory():
llm_infos = [
# ---------------------- OpenAI ------------------------
{
"fid": factory_infos[0]["name"],
"llm_name": "gpt-4o",
"tags": "LLM,CHAT,128K",
"max_tokens": 128000,
"model_type": LLMType.CHAT.value + "," + LLMType.IMAGE2TEXT.value
}, {
"fid": factory_infos[0]["name"],
"llm_name": "gpt-3.5-turbo",
"tags": "LLM,CHAT,4K",
@ -160,6 +189,18 @@ def init_llm_factory():
"tags": "TEXT EMBEDDING,8K",
"max_tokens": 8191,
"model_type": LLMType.EMBEDDING.value
}, {
"fid": factory_infos[0]["name"],
"llm_name": "text-embedding-3-small",
"tags": "TEXT EMBEDDING,8K",
"max_tokens": 8191,
"model_type": LLMType.EMBEDDING.value
}, {
"fid": factory_infos[0]["name"],
"llm_name": "text-embedding-3-large",
"tags": "TEXT EMBEDDING,8K",
"max_tokens": 8191,
"model_type": LLMType.EMBEDDING.value
}, {
"fid": factory_infos[0]["name"],
"llm_name": "whisper-1",
@ -336,6 +377,13 @@ def init_llm_factory():
"max_tokens": 512,
"model_type": LLMType.EMBEDDING.value
},
{
"fid": factory_infos[7]["name"],
"llm_name": "maidalun1020/bce-reranker-base_v1",
"tags": "RE-RANK, 8K",
"max_tokens": 8196,
"model_type": LLMType.RERANK.value
},
# ------------------------ DeepSeek -----------------------
{
"fid": factory_infos[8]["name"],
@ -351,6 +399,143 @@ def init_llm_factory():
"max_tokens": 16385,
"model_type": LLMType.CHAT.value
},
# ------------------------ VolcEngine -----------------------
{
"fid": factory_infos[9]["name"],
"llm_name": "Skylark2-pro-32k",
"tags": "LLM,CHAT,32k",
"max_tokens": 32768,
"model_type": LLMType.CHAT.value
},
{
"fid": factory_infos[9]["name"],
"llm_name": "Skylark2-pro-4k",
"tags": "LLM,CHAT,4k",
"max_tokens": 4096,
"model_type": LLMType.CHAT.value
},
# ------------------------ BaiChuan -----------------------
{
"fid": factory_infos[10]["name"],
"llm_name": "Baichuan2-Turbo",
"tags": "LLM,CHAT,32K",
"max_tokens": 32768,
"model_type": LLMType.CHAT.value
},
{
"fid": factory_infos[10]["name"],
"llm_name": "Baichuan2-Turbo-192k",
"tags": "LLM,CHAT,192K",
"max_tokens": 196608,
"model_type": LLMType.CHAT.value
},
{
"fid": factory_infos[10]["name"],
"llm_name": "Baichuan3-Turbo",
"tags": "LLM,CHAT,32K",
"max_tokens": 32768,
"model_type": LLMType.CHAT.value
},
{
"fid": factory_infos[10]["name"],
"llm_name": "Baichuan3-Turbo-128k",
"tags": "LLM,CHAT,128K",
"max_tokens": 131072,
"model_type": LLMType.CHAT.value
},
{
"fid": factory_infos[10]["name"],
"llm_name": "Baichuan4",
"tags": "LLM,CHAT,128K",
"max_tokens": 131072,
"model_type": LLMType.CHAT.value
},
{
"fid": factory_infos[10]["name"],
"llm_name": "Baichuan-Text-Embedding",
"tags": "TEXT EMBEDDING",
"max_tokens": 512,
"model_type": LLMType.EMBEDDING.value
},
# ------------------------ Jina -----------------------
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-reranker-v1-base-en",
"tags": "RE-RANK,8k",
"max_tokens": 8196,
"model_type": LLMType.RERANK.value
},
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-reranker-v1-turbo-en",
"tags": "RE-RANK,8k",
"max_tokens": 8196,
"model_type": LLMType.RERANK.value
},
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-reranker-v1-tiny-en",
"tags": "RE-RANK,8k",
"max_tokens": 8196,
"model_type": LLMType.RERANK.value
},
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-colbert-v1-en",
"tags": "RE-RANK,8k",
"max_tokens": 8196,
"model_type": LLMType.RERANK.value
},
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-embeddings-v2-base-en",
"tags": "TEXT EMBEDDING",
"max_tokens": 8196,
"model_type": LLMType.EMBEDDING.value
},
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-embeddings-v2-base-de",
"tags": "TEXT EMBEDDING",
"max_tokens": 8196,
"model_type": LLMType.EMBEDDING.value
},
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-embeddings-v2-base-es",
"tags": "TEXT EMBEDDING",
"max_tokens": 8196,
"model_type": LLMType.EMBEDDING.value
},
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-embeddings-v2-base-code",
"tags": "TEXT EMBEDDING",
"max_tokens": 8196,
"model_type": LLMType.EMBEDDING.value
},
{
"fid": factory_infos[11]["name"],
"llm_name": "jina-embeddings-v2-base-zh",
"tags": "TEXT EMBEDDING",
"max_tokens": 8196,
"model_type": LLMType.EMBEDDING.value
},
# ------------------------ BAAI -----------------------
{
"fid": factory_infos[12]["name"],
"llm_name": "BAAI/bge-large-zh-v1.5",
"tags": "TEXT EMBEDDING,",
"max_tokens": 1024,
"model_type": LLMType.EMBEDDING.value
},
{
"fid": factory_infos[12]["name"],
"llm_name": "BAAI/bge-reranker-v2-m3",
"tags": "RE-RANK,2k",
"max_tokens": 2048,
"model_type": LLMType.RERANK.value
},
]
for info in factory_infos:
try:
@ -370,6 +555,25 @@ def init_llm_factory():
LLMFactoriesService.filter_delete([LLMFactoriesService.model.name == "QAnything"])
LLMService.filter_delete([LLMService.model.fid == "QAnything"])
TenantLLMService.filter_update([TenantLLMService.model.llm_factory == "QAnything"], {"llm_factory": "Youdao"})
## insert openai two embedding models to the current openai user.
print("Start to insert 2 OpenAI embedding models...")
tenant_ids = set([row["tenant_id"] for row in TenantLLMService.get_openai_models()])
for tid in tenant_ids:
for row in TenantLLMService.query(llm_factory="OpenAI", tenant_id=tid):
row = row.to_dict()
row["model_type"] = LLMType.EMBEDDING.value
row["llm_name"] = "text-embedding-3-small"
row["used_tokens"] = 0
try:
TenantLLMService.save(**row)
row = deepcopy(row)
row["llm_name"] = "text-embedding-3-large"
TenantLLMService.save(**row)
except Exception as e:
pass
break
for kb_id in KnowledgebaseService.get_all_ids():
KnowledgebaseService.update_by_id(kb_id, {"doc_num": DocumentService.get_kb_doc_count(kb_id)})
"""
drop table llm;
drop table llm_factories;

View File

@ -14,6 +14,7 @@
# limitations under the License.
#
import re
from copy import deepcopy
from api.db import LLMType
from api.db.db_models import Dialog, Conversation
@ -57,21 +58,21 @@ def message_fit_in(msg, max_length=4000):
if c < max_length:
return c, msg
ll = num_tokens_from_string(msg_[0].content)
l = num_tokens_from_string(msg_[-1].content)
ll = num_tokens_from_string(msg_[0]["content"])
l = num_tokens_from_string(msg_[-1]["content"])
if ll / (ll + l) > 0.8:
m = msg_[0].content
m = msg_[0]["content"]
m = encoder.decode(encoder.encode(m)[:max_length - l])
msg[0].content = m
msg[0]["content"] = m
return max_length, msg
m = msg_[1].content
m = msg_[1]["content"]
m = encoder.decode(encoder.encode(m)[:max_length - l])
msg[1].content = m
msg[1]["content"] = m
return max_length, msg
def chat(dialog, messages, **kwargs):
def chat(dialog, messages, stream=True, **kwargs):
assert messages[-1]["role"] == "user", "The last content of this conversation is not from user."
llm = LLMService.query(llm_name=dialog.llm_id)
if not llm:
@ -82,7 +83,9 @@ def chat(dialog, messages, **kwargs):
else: max_tokens = llm[0].max_tokens
kbs = KnowledgebaseService.get_by_ids(dialog.kb_ids)
embd_nms = list(set([kb.embd_id for kb in kbs]))
assert len(embd_nms) == 1, "Knowledge bases use different embedding models."
if len(embd_nms) != 1:
yield {"answer": "**ERROR**: Knowledge bases use different embedding models.", "reference": []}
return {"answer": "**ERROR**: Knowledge bases use different embedding models.", "reference": []}
questions = [m["content"] for m in messages if m["role"] == "user"]
embd_mdl = LLMBundle(dialog.tenant_id, LLMType.EMBEDDING, embd_nms[0])
@ -94,7 +97,9 @@ def chat(dialog, messages, **kwargs):
if field_map:
chat_logger.info("Use SQL to retrieval:{}".format(questions[-1]))
ans = use_sql(questions[-1], field_map, dialog.tenant_id, chat_mdl, prompt_config.get("quote", True))
if ans: return ans
if ans:
yield ans
return
for p in prompt_config["parameters"]:
if p["key"] == "knowledge":
@ -110,53 +115,73 @@ def chat(dialog, messages, **kwargs):
if "knowledge" not in [p["key"] for p in prompt_config["parameters"]]:
kbinfos = {"total": 0, "chunks": [], "doc_aggs": []}
else:
rerank_mdl = None
if dialog.rerank_id:
rerank_mdl = LLMBundle(dialog.tenant_id, LLMType.RERANK, dialog.rerank_id)
kbinfos = retrievaler.retrieval(" ".join(questions), embd_mdl, dialog.tenant_id, dialog.kb_ids, 1, dialog.top_n,
dialog.similarity_threshold,
dialog.vector_similarity_weight, top=1024, aggs=False)
dialog.vector_similarity_weight,
doc_ids=kwargs["doc_ids"].split(",") if "doc_ids" in kwargs else None,
top=1024, aggs=False, rerank_mdl=rerank_mdl)
knowledges = [ck["content_with_weight"] for ck in kbinfos["chunks"]]
chat_logger.info(
"{}->{}".format(" ".join(questions), "\n->".join(knowledges)))
if not knowledges and prompt_config.get("empty_response"):
return {
"answer": prompt_config["empty_response"], "reference": kbinfos}
yield {"answer": prompt_config["empty_response"], "reference": kbinfos}
return {"answer": prompt_config["empty_response"], "reference": kbinfos}
kwargs["knowledge"] = "\n".join(knowledges)
gen_conf = dialog.llm_setting
msg = [{"role": m["role"], "content": m["content"]}
for m in messages if m["role"] != "system"]
msg = [{"role": "system", "content": prompt_config["system"].format(**kwargs)}]
msg.extend([{"role": m["role"], "content": m["content"]}
for m in messages if m["role"] != "system"])
used_token_count, msg = message_fit_in(msg, int(max_tokens * 0.97))
assert len(msg) >= 2, f"message_fit_in has bug: {msg}"
if "max_tokens" in gen_conf:
gen_conf["max_tokens"] = min(
gen_conf["max_tokens"],
max_tokens - used_token_count)
answer = chat_mdl.chat(
prompt_config["system"].format(
**kwargs), msg, gen_conf)
chat_logger.info("User: {}|Assistant: {}".format(
msg[-1]["content"], answer))
if knowledges and (prompt_config.get("quote", True) and kwargs.get("quote", True)):
answer, idx = retrievaler.insert_citations(answer,
[ck["content_ltks"]
for ck in kbinfos["chunks"]],
[ck["vector"]
for ck in kbinfos["chunks"]],
embd_mdl,
tkweight=1 - dialog.vector_similarity_weight,
vtweight=dialog.vector_similarity_weight)
idx = set([kbinfos["chunks"][int(i)]["doc_id"] for i in idx])
recall_docs = [
d for d in kbinfos["doc_aggs"] if d["doc_id"] in idx]
if not recall_docs: recall_docs = kbinfos["doc_aggs"]
kbinfos["doc_aggs"] = recall_docs
def decorate_answer(answer):
nonlocal prompt_config, knowledges, kwargs, kbinfos
if knowledges and (prompt_config.get("quote", True) and kwargs.get("quote", True)):
answer, idx = retrievaler.insert_citations(answer,
[ck["content_ltks"]
for ck in kbinfos["chunks"]],
[ck["vector"]
for ck in kbinfos["chunks"]],
embd_mdl,
tkweight=1 - dialog.vector_similarity_weight,
vtweight=dialog.vector_similarity_weight)
idx = set([kbinfos["chunks"][int(i)]["doc_id"] for i in idx])
recall_docs = [
d for d in kbinfos["doc_aggs"] if d["doc_id"] in idx]
if not recall_docs: recall_docs = kbinfos["doc_aggs"]
kbinfos["doc_aggs"] = recall_docs
for c in kbinfos["chunks"]:
if c.get("vector"):
del c["vector"]
if answer.lower().find("invalid key") >= 0 or answer.lower().find("invalid api")>=0:
answer += " Please set LLM API-Key in 'User Setting -> Model Providers -> API-Key'"
return {"answer": answer, "reference": kbinfos}
refs = deepcopy(kbinfos)
for c in refs["chunks"]:
if c.get("vector"):
del c["vector"]
if answer.lower().find("invalid key") >= 0 or answer.lower().find("invalid api")>=0:
answer += " Please set LLM API-Key in 'User Setting -> Model Providers -> API-Key'"
return {"answer": answer, "reference": refs}
if stream:
answer = ""
for ans in chat_mdl.chat_streamly(msg[0]["content"], msg[1:], gen_conf):
answer = ans
yield {"answer": answer, "reference": {}}
yield decorate_answer(answer)
else:
answer = chat_mdl.chat(
msg[0]["content"], msg[1:], gen_conf)
chat_logger.info("User: {}|Assistant: {}".format(
msg[-1]["content"], answer))
yield decorate_answer(answer)
def use_sql(question, field_map, tenant_id, chat_mdl, quota=True):

View File

@ -16,9 +16,12 @@
import random
from datetime import datetime
from elasticsearch_dsl import Q
from peewee import fn
from api.db.db_utils import bulk_insert_into_db
from api.settings import stat_logger
from api.utils import current_timestamp, get_format_time
from api.utils import current_timestamp, get_format_time, get_uuid
from rag.settings import SVR_QUEUE_NAME
from rag.utils.es_conn import ELASTICSEARCH
from rag.utils.minio_conn import MINIO
from rag.nlp import search
@ -29,6 +32,7 @@ from api.db.db_models import Document
from api.db.services.common_service import CommonService
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db import StatusEnum
from rag.utils.redis_conn import REDIS_CONN
class DocumentService(CommonService):
@ -40,8 +44,9 @@ class DocumentService(CommonService):
orderby, desc, keywords):
if keywords:
docs = cls.model.select().where(
cls.model.kb_id == kb_id,
cls.model.name.like(f"%%{keywords}%%"))
(cls.model.kb_id == kb_id),
(fn.LOWER(cls.model.name).contains(keywords.lower()))
)
else:
docs = cls.model.select().where(cls.model.kb_id == kb_id)
count = docs.count()
@ -68,27 +73,12 @@ class DocumentService(CommonService):
raise RuntimeError("Database error (Knowledgebase)!")
return doc
@classmethod
@DB.connection_context()
def delete(cls, doc):
e, kb = KnowledgebaseService.get_by_id(doc.kb_id)
if not KnowledgebaseService.update_by_id(
kb.id, {"doc_num": kb.doc_num - 1}):
raise RuntimeError("Database error (Knowledgebase)!")
return cls.delete_by_id(doc.id)
@classmethod
@DB.connection_context()
def remove_document(cls, doc, tenant_id):
ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
cls.increment_chunk_num(
doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, 0)
if not cls.delete(doc):
raise RuntimeError("Database error (Document removal)!")
MINIO.rm(doc.kb_id, doc.location)
Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
cls.clear_chunk_num(doc.id)
return cls.delete_by_id(doc.id)
@classmethod
@ -123,7 +113,7 @@ class DocumentService(CommonService):
@classmethod
@DB.connection_context()
def get_unfinished_docs(cls):
fields = [cls.model.id, cls.model.process_begin_at]
fields = [cls.model.id, cls.model.process_begin_at, cls.model.parser_config, cls.model.progress_msg]
docs = cls.model.select(*fields) \
.where(
cls.model.status == StatusEnum.VALID.value,
@ -150,6 +140,22 @@ class DocumentService(CommonService):
Knowledgebase.id == kb_id).execute()
return num
@classmethod
@DB.connection_context()
def clear_chunk_num(cls, doc_id):
doc = cls.model.get_by_id(doc_id)
assert doc, "Can't fine document in database."
num = Knowledgebase.update(
token_num=Knowledgebase.token_num -
doc.token_num,
chunk_num=Knowledgebase.chunk_num -
doc.chunk_num,
doc_num=Knowledgebase.doc_num-1
).where(
Knowledgebase.id == doc.kb_id).execute()
return num
@classmethod
@DB.connection_context()
def get_tenant_id(cls, doc_id):
@ -163,6 +169,30 @@ class DocumentService(CommonService):
return
return docs[0]["tenant_id"]
@classmethod
@DB.connection_context()
def get_tenant_id_by_name(cls, name):
docs = cls.model.select(
Knowledgebase.tenant_id).join(
Knowledgebase, on=(
Knowledgebase.id == cls.model.kb_id)).where(
cls.model.name == name, Knowledgebase.status == StatusEnum.VALID.value)
docs = docs.dicts()
if not docs:
return
return docs[0]["tenant_id"]
@classmethod
@DB.connection_context()
def get_doc_id_by_doc_name(cls, doc_name):
fields = [cls.model.id]
doc_id = cls.model.select(*fields) \
.where(cls.model.name == doc_name)
doc_id = doc_id.dicts()
if not doc_id:
return
return doc_id[0]["id"]
@classmethod
@DB.connection_context()
def get_thumbnails(cls, docids):
@ -233,7 +263,12 @@ class DocumentService(CommonService):
prg = -1
status = TaskStatus.FAIL.value
elif finished:
status = TaskStatus.DONE.value
if d["parser_config"].get("raptor", {}).get("use_raptor") and d["progress_msg"].lower().find(" raptor")<0:
queue_raptor_tasks(d)
prg *= 0.98
msg.append("------ RAPTOR -------")
else:
status = TaskStatus.DONE.value
msg = "\n".join(msg)
info = {
@ -249,3 +284,25 @@ class DocumentService(CommonService):
except Exception as e:
stat_logger.error("fetch task exception:" + str(e))
@classmethod
@DB.connection_context()
def get_kb_doc_count(cls, kb_id):
return len(cls.model.select(cls.model.id).where(
cls.model.kb_id == kb_id).dicts())
def queue_raptor_tasks(doc):
def new_task():
nonlocal doc
return {
"id": get_uuid(),
"doc_id": doc["id"],
"from_page": 0,
"to_page": -1,
"progress_msg": "Start to do RAPTOR (Recursive Abstractive Processing For Tree-Organized Retrieval)."
}
task = new_task()
bulk_insert_into_db(Task, [task], True)
task["type"] = "raptor"
assert REDIS_CONN.queue_product(SVR_QUEUE_NAME, message=task), "Can't access Redis. Please check the Redis' status."

View File

@ -15,12 +15,12 @@
#
from datetime import datetime
from api.db import FileSource
from api.db.db_models import DB
from api.db.db_models import File, Document, File2Document
from api.db.db_models import File, File2Document
from api.db.services.common_service import CommonService
from api.db.services.document_service import DocumentService
from api.db.services.file_service import FileService
from api.utils import current_timestamp, datetime_format
from api.utils import current_timestamp, datetime_format, get_uuid
class File2DocumentService(CommonService):
@ -71,13 +71,15 @@ class File2DocumentService(CommonService):
@DB.connection_context()
def get_minio_address(cls, doc_id=None, file_id=None):
if doc_id:
ids = File2DocumentService.get_by_document_id(doc_id)
f2d = cls.get_by_document_id(doc_id)
else:
ids = File2DocumentService.get_by_file_id(file_id)
if ids:
e, file = FileService.get_by_id(ids[0].file_id)
return file.parent_id, file.location
else:
assert doc_id, "please specify doc_id"
e, doc = DocumentService.get_by_id(doc_id)
return doc.kb_id, doc.location
f2d = cls.get_by_file_id(file_id)
if f2d:
file = File.get_by_id(f2d[0].file_id)
if file.source_type == FileSource.LOCAL:
return file.parent_id, file.location
doc_id = f2d[0].document_id
assert doc_id, "please specify doc_id"
e, doc = DocumentService.get_by_id(doc_id)
return doc.kb_id, doc.location

View File

@ -16,10 +16,12 @@
from flask_login import current_user
from peewee import fn
from api.db import FileType
from api.db import FileType, KNOWLEDGEBASE_FOLDER_NAME, FileSource
from api.db.db_models import DB, File2Document, Knowledgebase
from api.db.db_models import File, Document
from api.db.services.common_service import CommonService
from api.db.services.document_service import DocumentService
from api.db.services.file2document_service import File2DocumentService
from api.utils import get_uuid
@ -32,11 +34,16 @@ class FileService(CommonService):
orderby, desc, keywords):
if keywords:
files = cls.model.select().where(
(cls.model.tenant_id == tenant_id)
& (cls.model.parent_id == pf_id), (fn.LOWER(cls.model.name).like(f"%%{keywords.lower()}%%")))
(cls.model.tenant_id == tenant_id),
(cls.model.parent_id == pf_id),
(fn.LOWER(cls.model.name).contains(keywords.lower())),
~(cls.model.id == pf_id)
)
else:
files = cls.model.select().where((cls.model.tenant_id == tenant_id)
& (cls.model.parent_id == pf_id))
files = cls.model.select().where((cls.model.tenant_id == tenant_id),
(cls.model.parent_id == pf_id),
~(cls.model.id == pf_id)
)
count = files.count()
if desc:
files = files.order_by(cls.model.getter_by(orderby).desc())
@ -135,29 +142,69 @@ class FileService(CommonService):
@classmethod
@DB.connection_context()
def get_root_folder(cls, tenant_id):
file = cls.model.select().where(cls.model.tenant_id == tenant_id and
cls.model.parent_id == cls.model.id)
if not file:
file_id = get_uuid()
file = {
"id": file_id,
"parent_id": file_id,
"tenant_id": tenant_id,
"created_by": tenant_id,
"name": "/",
"type": FileType.FOLDER.value,
"size": 0,
"location": "",
}
cls.save(**file)
else:
file_id = file[0].id
for file in cls.model.select().where((cls.model.tenant_id == tenant_id),
(cls.model.parent_id == cls.model.id)
):
return file.to_dict()
e, file = cls.get_by_id(file_id)
if not e:
raise RuntimeError("Database error (File retrieval)!")
file_id = get_uuid()
file = {
"id": file_id,
"parent_id": file_id,
"tenant_id": tenant_id,
"created_by": tenant_id,
"name": "/",
"type": FileType.FOLDER.value,
"size": 0,
"location": "",
}
cls.save(**file)
return file
@classmethod
@DB.connection_context()
def get_kb_folder(cls, tenant_id):
for root in cls.model.select().where(cls.model.tenant_id == tenant_id and
cls.model.parent_id == cls.model.id):
for folder in cls.model.select().where(cls.model.tenant_id == tenant_id and
cls.model.parent_id == root.id and
cls.model.name == KNOWLEDGEBASE_FOLDER_NAME
):
return folder.to_dict()
assert False, "Can't find the KB folder. Database init error."
@classmethod
@DB.connection_context()
def new_a_file_from_kb(cls, tenant_id, name, parent_id, ty=FileType.FOLDER.value, size=0, location=""):
for file in cls.query(tenant_id=tenant_id, parent_id=parent_id, name=name):
return file.to_dict()
file = {
"id": get_uuid(),
"parent_id": parent_id,
"tenant_id": tenant_id,
"created_by": tenant_id,
"name": name,
"type": ty,
"size": size,
"location": location,
"source_type": FileSource.KNOWLEDGEBASE
}
cls.save(**file)
return file
@classmethod
@DB.connection_context()
def init_knowledgebase_docs(cls, root_id, tenant_id):
for _ in cls.model.select().where((cls.model.name == KNOWLEDGEBASE_FOLDER_NAME)\
& (cls.model.parent_id == root_id)):
return
folder = cls.new_a_file_from_kb(tenant_id, KNOWLEDGEBASE_FOLDER_NAME, root_id)
for kb in Knowledgebase.select(*[Knowledgebase.id, Knowledgebase.name]).where(Knowledgebase.tenant_id==tenant_id):
kb_folder = cls.new_a_file_from_kb(tenant_id, kb.name, folder["id"])
for doc in DocumentService.query(kb_id=kb.id):
FileService.add_file_from_kb(doc.to_dict(), kb_folder["id"], tenant_id)
@classmethod
@DB.connection_context()
def get_parent_folder(cls, file_id):
@ -241,3 +288,20 @@ class FileService(CommonService):
dfs(folder_id)
return size
@classmethod
@DB.connection_context()
def add_file_from_kb(cls, doc, kb_folder_id, tenant_id):
for _ in File2DocumentService.get_by_document_id(doc["id"]): return
file = {
"id": get_uuid(),
"parent_id": kb_folder_id,
"tenant_id": tenant_id,
"created_by": tenant_id,
"name": doc["name"],
"type": doc["type"],
"size": doc["size"],
"location": doc["location"],
"source_type": FileSource.KNOWLEDGEBASE
}
cls.save(**file)
File2DocumentService.save(**{"id": get_uuid(), "file_id": file["id"], "document_id": doc["id"]})

View File

@ -1,67 +0,0 @@
#
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from api.db import TenantPermission
from api.db.db_models import DB, Tenant
from api.db.db_models import Knowledgebase
from api.db.services.common_service import CommonService
from api.db import StatusEnum
class KnowledgebaseService(CommonService):
model = Knowledgebase
@classmethod
@DB.connection_context()
def get_by_tenant_ids(cls, joined_tenant_ids, user_id,
page_number, items_per_page, orderby, desc):
kbs = cls.model.select().where(
((cls.model.tenant_id.in_(joined_tenant_ids) & (cls.model.permission ==
TenantPermission.TEAM.value)) | (cls.model.tenant_id == user_id))
& (cls.model.status == StatusEnum.VALID.value)
)
if desc:
kbs = kbs.order_by(cls.model.getter_by(orderby).desc())
else:
kbs = kbs.order_by(cls.model.getter_by(orderby).asc())
kbs = kbs.paginate(page_number, items_per_page)
return list(kbs.dicts())
@classmethod
@DB.connection_context()
def get_detail(cls, kb_id):
fields = [
cls.model.id,
Tenant.embd_id,
cls.model.avatar,
cls.model.name,
cls.model.description,
cls.model.permission,
cls.model.doc_num,
cls.model.token_num,
cls.model.chunk_num,
cls.model.parser_id]
kbs = cls.model.select(*fields).join(Tenant, on=((Tenant.id == cls.model.tenant_id)&(Tenant.status== StatusEnum.VALID.value))).where(
(cls.model.id == kb_id),
(cls.model.status == StatusEnum.VALID.value)
)
if not kbs:
return
d = kbs[0].to_dict()
d["embd_id"] = kbs[0].tenant.embd_id
return d

View File

@ -112,3 +112,8 @@ class KnowledgebaseService(CommonService):
if kb:
return True, kb[0]
return False, None
@classmethod
@DB.connection_context()
def get_all_ids(cls):
return [m["id"] for m in cls.model.select(cls.model.id).dicts()]

View File

@ -15,7 +15,7 @@
#
from api.db.services.user_service import TenantService
from api.settings import database_logger
from rag.llm import EmbeddingModel, CvModel, ChatModel
from rag.llm import EmbeddingModel, CvModel, ChatModel, RerankModel
from api.db import LLMType
from api.db.db_models import DB, UserTenant
from api.db.db_models import LLMFactories, LLM, TenantLLM
@ -73,21 +73,25 @@ class TenantLLMService(CommonService):
mdlnm = tenant.img2txt_id
elif llm_type == LLMType.CHAT.value:
mdlnm = tenant.llm_id if not llm_name else llm_name
elif llm_type == LLMType.RERANK:
mdlnm = tenant.rerank_id if not llm_name else llm_name
else:
assert False, "LLM type error"
model_config = cls.get_api_key(tenant_id, mdlnm)
if model_config: model_config = model_config.to_dict()
if not model_config:
if llm_type == LLMType.EMBEDDING.value:
if llm_type in [LLMType.EMBEDDING, LLMType.RERANK]:
llm = LLMService.query(llm_name=llm_name)
if llm and llm[0].fid in ["Youdao", "FastEmbed"]:
if llm and llm[0].fid in ["Youdao", "FastEmbed", "BAAI"]:
model_config = {"llm_factory": llm[0].fid, "api_key":"", "llm_name": llm_name, "api_base": ""}
if not model_config:
if llm_name == "flag-embedding":
model_config = {"llm_factory": "Tongyi-Qianwen", "api_key": "",
"llm_name": llm_name, "api_base": ""}
else:
if not mdlnm:
raise LookupError(f"Type of {llm_type} model is not set.")
raise LookupError("Model({}) not authorized".format(mdlnm))
if llm_type == LLMType.EMBEDDING.value:
@ -96,6 +100,12 @@ class TenantLLMService(CommonService):
return EmbeddingModel[model_config["llm_factory"]](
model_config["api_key"], model_config["llm_name"], base_url=model_config["api_base"])
if llm_type == LLMType.RERANK:
if model_config["llm_factory"] not in RerankModel:
return
return RerankModel[model_config["llm_factory"]](
model_config["api_key"], model_config["llm_name"], base_url=model_config["api_base"])
if llm_type == LLMType.IMAGE2TEXT.value:
if model_config["llm_factory"] not in CvModel:
return
@ -125,16 +135,31 @@ class TenantLLMService(CommonService):
mdlnm = tenant.img2txt_id
elif llm_type == LLMType.CHAT.value:
mdlnm = tenant.llm_id if not llm_name else llm_name
elif llm_type == LLMType.RERANK:
mdlnm = tenant.llm_id if not llm_name else llm_name
else:
assert False, "LLM type error"
num = 0
for u in cls.query(tenant_id = tenant_id, llm_name=mdlnm):
num += cls.model.update(used_tokens = u.used_tokens + used_tokens)\
.where(cls.model.tenant_id == tenant_id, cls.model.llm_name == mdlnm)\
.execute()
try:
for u in cls.query(tenant_id = tenant_id, llm_name=mdlnm):
num += cls.model.update(used_tokens = u.used_tokens + used_tokens)\
.where(cls.model.tenant_id == tenant_id, cls.model.llm_name == mdlnm)\
.execute()
except Exception as e:
pass
return num
@classmethod
@DB.connection_context()
def get_openai_models(cls):
objs = cls.model.select().where(
(cls.model.llm_factory == "OpenAI"),
~(cls.model.llm_name == "text-embedding-3-small"),
~(cls.model.llm_name == "text-embedding-3-large")
).dicts()
return list(objs)
class LLMBundle(object):
def __init__(self, tenant_id, llm_type, llm_name=None, lang="Chinese"):
@ -145,6 +170,10 @@ class LLMBundle(object):
tenant_id, llm_type, llm_name, lang=lang)
assert self.mdl, "Can't find mole for {}/{}/{}".format(
tenant_id, llm_type, llm_name)
self.max_length = 512
for lm in LLMService.query(llm_name=llm_name):
self.max_length = lm.max_tokens
break
def encode(self, texts: list, batch_size=32):
emd, used_tokens = self.mdl.encode(texts, batch_size)
@ -162,6 +191,14 @@ class LLMBundle(object):
"Can't update token usage for {}/EMBEDDING".format(self.tenant_id))
return emd, used_tokens
def similarity(self, query: str, texts: list):
sim, used_tokens = self.mdl.similarity(query, texts)
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, used_tokens):
database_logger.error(
"Can't update token usage for {}/RERANK".format(self.tenant_id))
return sim, used_tokens
def describe(self, image, max_tokens=300):
txt, used_tokens = self.mdl.describe(image, max_tokens)
if not TenantLLMService.increase_usage(
@ -172,8 +209,18 @@ class LLMBundle(object):
def chat(self, system, history, gen_conf):
txt, used_tokens = self.mdl.chat(system, history, gen_conf)
if TenantLLMService.increase_usage(
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, used_tokens, self.llm_name):
database_logger.error(
"Can't update token usage for {}/CHAT".format(self.tenant_id))
return txt
def chat_streamly(self, system, history, gen_conf):
for txt in self.mdl.chat_streamly(system, history, gen_conf):
if isinstance(txt, int):
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, txt, self.llm_name):
database_logger.error(
"Can't update token usage for {}/CHAT".format(self.tenant_id))
return
yield txt

View File

@ -53,6 +53,7 @@ class TaskService(CommonService):
Knowledgebase.embd_id,
Tenant.img2txt_id,
Tenant.asr_id,
Tenant.llm_id,
cls.model.update_time]
docs = cls.model.select(*fields) \
.join(Document, on=(cls.model.doc_id == Document.id)) \
@ -96,7 +97,7 @@ class TaskService(CommonService):
return doc.run == TaskStatus.CANCEL.value or doc.progress < 0
except Exception as e:
pass
return True
return False
@classmethod
@DB.connection_context()
@ -159,4 +160,4 @@ def queue_tasks(doc, bucket, name):
DocumentService.begin2parse(doc["id"])
for t in tsks:
REDIS_CONN.queue_product(SVR_QUEUE_NAME, message=t)
assert REDIS_CONN.queue_product(SVR_QUEUE_NAME, message=t), "Can't access Redis. Please check the Redis' status."

View File

@ -93,6 +93,7 @@ class TenantService(CommonService):
cls.model.name,
cls.model.llm_id,
cls.model.embd_id,
cls.model.rerank_id,
cls.model.asr_id,
cls.model.img2txt_id,
cls.model.parser_ids,

View File

@ -86,6 +86,25 @@ default_llm = {
"embedding_model": "",
"image2text_model": "",
"asr_model": "",
},
"DeepSeek": {
"chat_model": "deepseek-chat",
"embedding_model": "",
"image2text_model": "",
"asr_model": "",
},
"VolcEngine": {
"chat_model": "",
"embedding_model": "",
"image2text_model": "",
"asr_model": "",
},
"BAAI": {
"chat_model": "",
"embedding_model": "BAAI/bge-large-zh-v1.5",
"image2text_model": "",
"asr_model": "",
"rerank_model": "BAAI/bge-reranker-v2-m3",
}
}
LLM = get_base_config("user_default_llm", {})
@ -98,7 +117,8 @@ if LLM_FACTORY not in default_llm:
f"LLM factory {LLM_FACTORY} has not supported yet, switch to 'Tongyi-Qianwen/QWen' automatically, and please check the API_KEY in service_conf.yaml.")
LLM_FACTORY = "Tongyi-Qianwen"
CHAT_MDL = default_llm[LLM_FACTORY]["chat_model"]
EMBEDDING_MDL = default_llm[LLM_FACTORY]["embedding_model"]
EMBEDDING_MDL = default_llm["BAAI"]["embedding_model"]
RERANK_MDL = default_llm["BAAI"]["rerank_model"]
ASR_MDL = default_llm[LLM_FACTORY]["asr_model"]
IMAGE2TEXT_MDL = default_llm[LLM_FACTORY]["image2text_model"]
@ -152,6 +172,7 @@ CLIENT_AUTHENTICATION = AUTHENTICATION_CONF.get(
"switch", False)
HTTP_APP_KEY = AUTHENTICATION_CONF.get("client", {}).get("http_app_key")
GITHUB_OAUTH = get_base_config("oauth", {}).get("github")
FEISHU_OAUTH = get_base_config("oauth", {}).get("feishu")
WECHAT_OAUTH = get_base_config("oauth", {}).get("wechat")
# site

View File

@ -25,7 +25,6 @@ from flask import (
from werkzeug.http import HTTP_STATUS_CODES
from api.utils import json_dumps
from api.versions import get_rag_version
from api.settings import RetCode
from api.settings import (
REQUEST_MAX_WAIT_SEC, REQUEST_WAIT_SEC,
@ -84,9 +83,6 @@ def request(**kwargs):
return sess.send(prepped, stream=stream, timeout=timeout)
rag_version = get_rag_version() or ''
def get_exponential_backoff_interval(retries, full_jitter=False):
"""Calculate the exponential backoff wait time."""
# Will be zero if factor equals 0

View File

@ -156,7 +156,7 @@ def filename_type(filename):
return FileType.PDF.value
if re.match(
r".*\.(doc|docx|ppt|pptx|yml|xml|htm|json|csv|txt|ini|xls|xlsx|wps|rtf|hlp|pages|numbers|key|md)$", filename):
r".*\.(doc|docx|ppt|pptx|yml|xml|htm|json|csv|txt|ini|xls|xlsx|wps|rtf|hlp|pages|numbers|key|md|py|js|java|c|cpp|h|php|go|ts|sh|cs|kt|html)$", filename):
return FileType.DOC.value
if re.match(
@ -174,7 +174,7 @@ def thumbnail(filename, blob):
if re.match(r".*\.pdf$", filename):
pdf = pdfplumber.open(BytesIO(blob))
buffered = BytesIO()
pdf.pages[0].to_image().annotated.save(buffered, format="png")
pdf.pages[0].to_image(resolution=32).annotated.save(buffered, format="png")
return "data:image/png;base64," + \
base64.b64encode(buffered.getvalue()).decode("utf-8")

View File

@ -14,17 +14,15 @@
# limitations under the License.
#
import os
import dotenv
import typing
from api.utils.file_utils import get_project_base_directory
def get_versions() -> typing.Mapping[str, typing.Any]:
return dotenv.dotenv_values(
dotenv_path=os.path.join(get_project_base_directory(), "rag.env")
)
dotenv.load_dotenv(dotenv.find_dotenv())
return dotenv.dotenv_values()
def get_rag_version() -> typing.Optional[str]:
return get_versions().get("RAG")
return get_versions().get("RAGFLOW_VERSION", "dev")

View File

@ -28,6 +28,12 @@ oauth:
client_id: xxxxxxxxxxxxxxxxxxxxxxxxx
secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxx
url: https://github.com/login/oauth/access_token
feishu:
app_id: cli_xxxxxxxxxxxxxxxxxxx
app_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxx
app_access_token_url: https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal
user_access_token_url: https://open.feishu.cn/open-apis/authen/v1/oidc/access_token
grant_type: 'authorization_code'
authentication:
client:
switch: false
@ -38,4 +44,4 @@ authentication:
permission:
switch: false
component: false
dataset: false
dataset: false

View File

@ -4,3 +4,4 @@ from .pdf_parser import RAGFlowPdfParser as PdfParser, PlainParser
from .docx_parser import RAGFlowDocxParser as DocxParser
from .excel_parser import RAGFlowExcelParser as ExcelParser
from .ppt_parser import RAGFlowPptParser as PptParser
from .html_parser import RAGFlowHtmlParser as HtmlParser

View File

@ -7,30 +7,39 @@ from rag.nlp import find_codec
class RAGFlowExcelParser:
def html(self, fnm):
def html(self, fnm, chunk_rows=256):
if isinstance(fnm, str):
wb = load_workbook(fnm)
else:
wb = load_workbook(BytesIO(fnm))
tb = ""
tb_chunks = []
for sheetname in wb.sheetnames:
ws = wb[sheetname]
rows = list(ws.rows)
if not rows:continue
tb += f"<table><caption>{sheetname}</caption><tr>"
if not rows: continue
tb_rows_0 = "<tr>"
for t in list(rows[0]):
tb += f"<th>{t.value}</th>"
tb += "</tr>"
for r in list(rows[1:]):
tb += "<tr>"
for i, c in enumerate(r):
if c.value is None:
tb += "<td></td>"
else:
tb += f"<td>{c.value}</td>"
tb += "</tr>"
tb += "</table>\n"
return tb
tb_rows_0 += f"<th>{t.value}</th>"
tb_rows_0 += "</tr>"
for chunk_i in range((len(rows) - 1) // chunk_rows + 1):
tb = ""
tb += f"<table><caption>{sheetname}</caption>"
tb += tb_rows_0
for r in list(rows[1 + chunk_i * chunk_rows:1 + (chunk_i + 1) * chunk_rows]):
tb += "<tr>"
for i, c in enumerate(r):
if c.value is None:
tb += "<td></td>"
else:
tb += f"<td>{c.value}</td>"
tb += "</tr>"
tb += "</table>\n"
tb_chunks.append(tb)
return tb_chunks
def __call__(self, fnm):
if isinstance(fnm, str):

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from rag.nlp import find_codec
import readability
import html_text
import chardet
def get_encoding(file):
with open(file,'rb') as f:
tmp = chardet.detect(f.read())
return tmp['encoding']
class RAGFlowHtmlParser:
def __call__(self, fnm, binary=None):
txt = ""
if binary:
encoding = find_codec(binary)
txt = binary.decode(encoding, errors="ignore")
else:
with open(fnm, "r",encoding=get_encoding(fnm)) as f:
txt = f.read()
html_doc = readability.Document(txt)
title = html_doc.title()
content = html_text.extract_text(html_doc.summary(html_partial=True))
txt = f'{title}\n{content}'
sections = txt.split("\n")
return sections

View File

@ -392,11 +392,11 @@ class RAGFlowPdfParser:
b["text"].strip()[-1] in ",;:'\",、‘“;:-",
len(b["text"].strip()) > 1 and b["text"].strip(
)[-2] in ",;:'\",‘“、;:",
b["text"].strip()[0] in "。;?!?”)),,、:",
b_["text"].strip() and b_["text"].strip()[0] in "。;?!?”)),,、:",
]
# features for not concating
feats = [
b.get("layoutno", 0) != b.get("layoutno", 0),
b.get("layoutno", 0) != b_.get("layoutno", 0),
b["text"].strip()[-1] in "。?!?",
self.is_english and b["text"].strip()[-1] in ".!?",
b["page_number"] == b_["page_number"] and b_["top"] -
@ -749,6 +749,7 @@ class RAGFlowPdfParser:
"layoutno", "")))
left, top, right, bott = b["x0"], b["top"], b["x1"], b["bottom"]
if right < left: right = left + 1
poss.append((pn + self.page_from, left, right, top, bott))
return self.page_images[pn] \
.crop((left * ZM, top * ZM,

View File

@ -1,5 +1,5 @@
import copy
import re
import numpy as np
import cv2
from shapely.geometry import Polygon

View File

@ -29,7 +29,7 @@ REDIS_PASSWORD=infini_rag_flow
SVR_HTTP_PORT=9380
RAGFLOW_VERSION=dev
RAGFLOW_VERSION=v0.7.0
TIMEZONE='Asia/Shanghai'

View File

@ -67,7 +67,7 @@ The serving IP and port inside the docker container. This is not updating until
Newly signed-up users use LLM configured by this part. Otherwise, user need to configure his own LLM in *setting*.
### factory
The LLM suppliers. 'Tongyi-Qianwen', "OpenAI" "Moonshot" and "ZHIPU-AI" are supported.
The LLM suppliers. "OpenAI" "Tongyi-Qianwen", "ZHIPU-AI", "Moonshot", "DeepSeek", "Baichuan", and "VolcEngine" are supported.
### api_key
The corresponding API key of your assigned LLM vendor.

View File

@ -4,18 +4,20 @@
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/
PY=/root/miniconda3/envs/py11/bin/python
PY=python3
if [[ -z "$WS" || $WS -lt 1 ]]; then
WS=1
fi
function task_exe(){
while [ 1 -eq 1 ];do
$PY rag/svr/task_executor.py $1 $2;
$PY rag/svr/task_executor.py ;
done
}
WS=1
for ((i=0;i<WS;i++))
do
task_exe $i $WS &
task_exe &
done
while [ 1 -eq 1 ];do

8
docs/_category_.json Normal file
View File

@ -0,0 +1,8 @@
{
"label": "Get Started",
"position": 1,
"link": {
"type": "generated-index",
"description": "RAGFlow Quick Start"
}
}

View File

@ -0,0 +1,8 @@
{
"label": "User Guides",
"position": 2,
"link": {
"type": "generated-index",
"description": "RAGFlow User Guides"
}
}

View File

@ -0,0 +1,138 @@
---
sidebar_position: 1
slug: /configure_knowledge_base
---
# Configure a knowledge base
Knowledge base, hallucination-free chat, and file management are three pillars of RAGFlow. RAGFlow's AI chats are based on knowledge bases. Each of RAGFlow's knowledge bases serves as a knowledge source, *parsing* files uploaded from your local machine and file references generated in **File Management** into the real 'knowledge' for future AI chats. This guide demonstrates some basic usages of the knowledge base feature, covering the following topics:
- Create a knowledge base
- Configure a knowledge base
- Search for a knowledge base
- Delete a knowledge base
## Create knowledge base
With multiple knowledge bases, you can build more flexible, diversified question answering. To create your first knowledge base:
![create knowledge base](https://github.com/infiniflow/ragflow/assets/93570324/110541ed-6cea-4a03-a11c-414a0948ba80)
_Each time a knowledge base is created, a folder with the same name is generated in the **root/.knowledgebase** directory._
## Configure knowledge base
The following screen shot shows the configuration page of a knowledge base. A proper configuration of your knowledge base is crucial for future AI chats. For example, choosing the wrong embedding model or chunk method would cause unexpected semantic loss or mismatched answers in chats.
![knowledge base configuration](https://github.com/infiniflow/ragflow/assets/93570324/384c671a-8b9c-468c-b1c9-1401128a9b65)
This section covers the following topics:
- Select chunk method
- Select embedding model
- Upload file
- Parse file
- Intervene with file parsing results
- Run retrieval testing
### Select chunk method
RAGFlow offers multiple chunking template to facilitate chunking files of different layouts and ensure semantic integrity. In **Chunk method**, you can choose the default template that suits the layouts and formats of your files. The following table shows the descriptions and the compatible file formats of each supported chunk template:
| **Template** | Description | File format |
| ------------ | ------------------------------------------------------------ | ---------------------------------------------------- |
| General | Files are consecutively chunked based on a preset chunk token number. | DOCX, EXCEL, PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF |
| Q&A | | EXCEL, CSV/TXT |
| Manual | | PDF |
| Table | | EXCEL, CSV/TXT |
| Paper | | PDF |
| Book | | DOCX, PDF, TXT |
| Laws | | DOCX, PDF, TXT |
| Presentation | | PDF, PPTX |
| Picture | | JPEG, JPG, PNG, TIF, GIF |
| One | The entire document is chunked as one. | DOCX, EXCEL, PDF, TXT |
You can also change the chunk template for a particular file on the **Datasets** page.
![change chunk method](https://github.com/infiniflow/ragflow/assets/93570324/ac116353-2793-42b2-b181-65e7082bed42)
### Select embedding model
An embedding model builds vector index on file chunks. Once you have chosen an embedding model and used it to parse a file, you are no longer allowed to change it. To switch to a different embedding model, you *must* deletes all completed file chunks in the knowledge base. The obvious reason is that we must *ensure* that all files in a specific knowledge base are parsed using the *same* embedding model (ensure that they are compared in the same embedding space).
The following embedding models can be deployed locally:
- BAAI/bge-large-zh-v1.5
- BAAI/bge-base-en-v1.5
- BAAI/bge-large-en-v1.5
- BAAI/bge-small-en-v1.5
- BAAI/bge-small-zh-v1.5
- jinaai/jina-embeddings-v2-base-en
- jinaai/jina-embeddings-v2-small-en
- nomic-ai/nomic-embed-text-v1.5
- sentence-transformers/all-MiniLM-L6-v2
- maidalun1020/bce-embedding-base_v1
### Upload file
- RAGFlow's **File Management** allows you to link a file to multiple knowledge bases, in which case each target knowledge base holds a reference to the file.
- In **Knowledge Base**, you are also given the option of uploading a single file or a folder of files (bulk upload) from your local machine to a knowledge base, in which case the knowledge base holds file copies.
While uploading files directly to a knowledge base seems more convenient, we *highly* recommend uploading files to **File Management** and then linking them to the target knowledge bases. This way, you can avoid permanently deleting files uploaded to the knowledge base.
### Parse file
File parsing is a crucial topic in knowledge base configuration. The meaning of file parsing in RAGFlow is twofold: chunking files based on file layout and building embedding and full-text (keyword) indexes on these chunks. After having selected the chunk method and embedding model, you can start parsing an file:
![parse file](https://github.com/infiniflow/ragflow/assets/93570324/5311f166-6426-447f-aa1f-bd488f1cfc7b)
- Click the play button next to **UNSTART** to start file parsing.
- Click the red-cross icon and then refresh, if your file parsing stalls for a long time.
- As shown above, RAGFlow allows you to use a different chunk method for a particular file, offering flexibility beyond the default method.
- As shown above, RAGFlow allows you to enable or disable individual files, offering finer control over knowledge base-based AI chats.
### Intervene with file parsing results
RAGFlow features visibility and explainability, allowing you to view the chunking results and intervene where necessary. To do so:
1. Click on the file that completes file parsing to view the chunking results:
_You are taken to the **Chunk** page:_
![chunks](https://github.com/infiniflow/ragflow/assets/93570324/0547fd0e-e71b-41f8-8e0e-31649c85fd3d)
2. Hover over each snapshot for a quick view of each chunk.
3. Double click the chunked texts to add keywords or make *manual* changes where necessary:
![update chunk](https://github.com/infiniflow/ragflow/assets/93570324/1d84b408-4e9f-46fd-9413-8c1059bf9c76)
4. In Retrieval testing, ask a quick question in **Test text** to double check if your configurations work:
_As you can tell from the following, RAGFlow responds with truthful citations._
![retrieval test](https://github.com/infiniflow/ragflow/assets/93570324/c03f06f6-f41f-4b20-a97e-ae405d3a950c)
### Run retrieval testing
RAGFlow uses multiple recall of both full-text search and vector search in its chats. Prior to setting up an AI chat, consider adjusting the following parameters to ensure that the intended information always turns up in answers:
- Similarity threshold: Chunks with similarities below the threshold will be filtered. Defaultly set to 0.2.
- Vector similarity weight: The percentage by which vector similarity contributes to the overall score. Defaultly set to 0.3.
![retrieval test](https://github.com/infiniflow/ragflow/assets/93570324/c03f06f6-f41f-4b20-a97e-ae405d3a950c)
## Search for knowledge base
As of RAGFlow v0.7.0, the search feature is still in a rudimentary form, supporting only knowledge base search by name.
![search knowledge base](https://github.com/infiniflow/ragflow/assets/93570324/836ae94c-2438-42be-879e-c7ad2a59693e)
## Delete knowledge base
You are allowed to delete a knowledge base. Hover your mouse over the three dot of the intended knowledge base card and the **Delete** option appears. Once you delete a knowledge base, the associated folder under **root/.knowledge** directory is AUTOMATICALLY REMOVED. The consequence is:
- The files uploaded directly to the knowledge base are gone;
- The file references, which you created from within **File Management**, are gone, but the associated files still exist in **File Management**.
![delete knowledge base](https://github.com/infiniflow/ragflow/assets/93570324/fec7a508-6cfe-4bca-af90-81d3fdb94098)

View File

@ -0,0 +1,75 @@
---
sidebar_position: 5
slug: /deploy_local_llm
---
# Deploy a local LLM
RAGFlow supports deploying LLMs locally using Ollama or Xinference.
## Ollama
One-click deployment of local LLMs, that is [Ollama](https://github.com/ollama/ollama).
### Install
- [Ollama on Linux](https://github.com/ollama/ollama/blob/main/docs/linux.md)
- [Ollama Windows Preview](https://github.com/ollama/ollama/blob/main/docs/windows.md)
- [Docker](https://hub.docker.com/r/ollama/ollama)
### Launch Ollama
Decide which LLM you want to deploy ([here's a list for supported LLM](https://ollama.com/library)), say, **mistral**:
```bash
$ ollama run mistral
```
Or,
```bash
$ docker exec -it ollama ollama run mistral
```
### Use Ollama in RAGFlow
- Go to 'Settings > Model Providers > Models to be added > Ollama'.
![](https://github.com/infiniflow/ragflow/assets/12318111/a9df198a-226d-4f30-b8d7-829f00256d46)
> Base URL: Enter the base URL where the Ollama service is accessible, like, `http://<your-ollama-endpoint-domain>:11434`.
- Use Ollama Models.
![](https://github.com/infiniflow/ragflow/assets/12318111/60ff384e-5013-41ff-a573-9a543d237fd3)
## Xinference
Xorbits Inference([Xinference](https://github.com/xorbitsai/inference)) empowers you to unleash the full potential of cutting-edge AI models.
### Install
- [pip install "xinference[all]"](https://inference.readthedocs.io/en/latest/getting_started/installation.html)
- [Docker](https://inference.readthedocs.io/en/latest/getting_started/using_docker_image.html)
To start a local instance of Xinference, run the following command:
```bash
$ xinference-local --host 0.0.0.0 --port 9997
```
### Launch Xinference
Decide which LLM you want to deploy ([here's a list for supported LLM](https://inference.readthedocs.io/en/latest/models/builtin/)), say, **mistral**.
Execute the following command to launch the model, remember to replace `${quantization}` with your chosen quantization method from the options listed above:
```bash
$ xinference launch -u mistral --model-name mistral-v0.1 --size-in-billions 7 --model-format pytorch --quantization ${quantization}
```
### Use Xinference in RAGFlow
- Go to 'Settings > Model Providers > Models to be added > Xinference'.
![](https://github.com/infiniflow/ragflow/assets/12318111/bcbf4d7a-ade6-44c7-ad5f-0a92c8a73789)
> Base URL: Enter the base URL where the Xinference service is accessible, like, `http://<your-xinference-endpoint-domain>:9997/v1`.
- Use Xinference Models.
![](https://github.com/infiniflow/ragflow/assets/12318111/b01fcb6f-47c9-4777-82e0-f1e947ed615a)
![](https://github.com/infiniflow/ragflow/assets/12318111/1763dcd1-044f-438d-badd-9729f5b3a144)

View File

@ -0,0 +1,30 @@
---
sidebar_position: 4
slug: /llm_api_key_setup
---
# Set your LLM API key
You have two ways to input your LLM API key.
## Before Starting The System
In **user_default_llm** of [service_conf.yaml](https://github.com/infiniflow/ragflow/blob/main/docker/service_conf.yaml), you need to specify LLM factory and your own _API_KEY_.
RAGFlow supports the flowing LLMs, with more coming in the pipeline:
- [OpenAI](https://platform.openai.com/login?launch)
- [Tongyi-Qianwen](https://dashscope.console.aliyun.com/model),
- [ZHIPU-AI](https://open.bigmodel.cn/),
- [Moonshot](https://platform.moonshot.cn/docs)
- [DeepSeek](https://platform.deepseek.com/api-docs/)
- [Baichuan](https://www.baichuan-ai.com/home)
- [VolcEngine](https://www.volcengine.com/docs/82379)
After sign in these LLM suppliers, create your own API-Key, they all have a certain amount of free quota.
## After Starting The System
You can also set API-Key in **User Setting** as following:
![](https://github.com/infiniflow/ragflow/assets/12318111/e4e4066c-e964-45ff-bd56-c3fc7fb18bd3)

View File

@ -0,0 +1,84 @@
---
sidebar_position: 3
slug: /manage_files
---
# Manage files
Knowledge base, hallucination-free chat, and file management are three pillars of RAGFlow. RAGFlow's file management allows you to upload files individually or in bulk. You can then link an uploaded file to multiple target knowledge bases. This guide showcases some basic usages of the file management feature.
## Create folder
RAGFlow's file management allows you to establish your file system with nested folder structures. To create a folder in the root directory of RAGFlow:
![create new folder](https://github.com/infiniflow/ragflow/assets/93570324/3a37a5f4-43a6-426d-a62a-e5cd2ff7a533)
> Each knowledge base in RAGFlow has a corresponding folder under the **root/.knowledgebase** directory. You are not allowed to create a subfolder within it.
## Upload file
RAGFlow's file management supports file uploads from your local machine, allowing both individual and bulk uploads:
![upload file](https://github.com/infiniflow/ragflow/assets/93570324/5d7ded14-ce2b-4703-8567-9356a978f45c)
![bulk upload](https://github.com/infiniflow/ragflow/assets/93570324/def0db55-824c-4236-b809-a98d8c8674e3)
## Preview file
RAGFlow's file management supports previewing files in the following formats:
- Documents (PDF, DOCS)
- Tables (XLSX)
- Pictures (JPEG, JPG, PNG, TIF, GIF)
![preview](https://github.com/infiniflow/ragflow/assets/93570324/2e931362-8bbf-482c-ac86-b68b09d331bc)
## Link file to knowledge bases
RAGFlow's file management allows you to *link* an uploaded file to multiple knowledge bases, creating a file reference in each target knowledge base. Therefore, deleting a file in your file management will AUTOMATICALLY REMOVE all related file references across the knowledge bases.
![link knowledgebase](https://github.com/infiniflow/ragflow/assets/93570324/6c6b8db4-3269-4e35-9434-6089887e3e3f)
You can link your file to one knowledge base or multiple knowledge bases at one time:
![link multiple kb](https://github.com/infiniflow/ragflow/assets/93570324/6c508803-fb1f-435d-b688-683066fd7fff)
## Move file to specified folder
As of RAGFlow v0.7.0, this feature is *not* available.
## Search files or folders
As of RAGFlow v0.7.0, the search feature is still in a rudimentary form, supporting only file and folder search in the current directory by name (files or folders in the child directory will not be retrieved).
![search file](https://github.com/infiniflow/ragflow/assets/93570324/77ffc2e5-bd80-4ed1-841f-068e664efffe)
## Rename file or folder
RAGFlow's file management allows you to rename a file or folder:
![rename_file](https://github.com/infiniflow/ragflow/assets/93570324/5abb0704-d9e9-4b43-9ed4-5750ccee011f)
## Delete files or folders
RAGFlow's file management allows you to delete files or folders individually or in bulk.
To delete a file or folder:
![delete file](https://github.com/infiniflow/ragflow/assets/93570324/85872728-125d-45e9-a0ee-21e9d4cedb8b)
To bulk delete files or folders:
![bulk delete](https://github.com/infiniflow/ragflow/assets/93570324/519b99ab-ec7f-4c8a-8cea-e0b6dcb3cb46)
> - You are not allowed to delete the **root/.knowledgebase** folder.
> - Deleting files that have been linked to knowledge bases will AUTOMATICALLY REMOVE all associated file references across the knowledge bases.
## Download uploaded file
RAGFlow's file management allows you to download an uploaded file:
![download_file](https://github.com/infiniflow/ragflow/assets/93570324/cf3b297f-7d9b-4522-bf5f-4f45743e4ed5)
> As of RAGFlow v0.7.0, bulk download is not supported, nor can you download an entire folder.

View File

@ -1,66 +1,71 @@
# Set vm.max_map_count to at least 262144
## Linux
To check the value of `vm.max_map_count`:
```bash
$ sysctl vm.max_map_count
```
Reset `vm.max_map_count` to a value at least 262144 if it is not.
```bash
# In this case, we set it to 262144:
$ sudo sysctl -w vm.max_map_count=262144
```
This change will be reset after a system reboot. To ensure your change remains permanent, add or update the `vm.max_map_count` value in **/etc/sysctl.conf** accordingly:
```bash
vm.max_map_count=262144
```
## Mac
```bash
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
$ sysctl -w vm.max_map_count=262144
```
To exit the screen session, type Ctrl a d.
## Windows and macOS with Docker Desktop
The vm.max_map_count setting must be set via docker-machine:
```bash
$ docker-machine ssh
$ sudo sysctl -w vm.max_map_count=262144
```
## Windows with Docker Desktop WSL 2 backend
To manually set it every time you reboot, you must run the following commands in a command prompt or PowerShell window every time you restart Docker:
```bash
$ wsl -d docker-desktop -u root
$ sysctl -w vm.max_map_count=262144
```
If you are on these versions of WSL and you do not want to have to run those commands every time you restart Docker, you can globally change every WSL distribution with this setting by modifying your %USERPROFILE%\.wslconfig as follows:
```bash
[wsl2]
kernelCommandLine = "sysctl.vm.max_map_count=262144"
```
This will cause all WSL2 VMs to have that setting assigned when they start.
If you are on Windows 11, or Windows 10 version 22H2 and have installed the Microsoft Store version of WSL, you can modify the /etc/sysctl.conf within the "docker-desktop" WSL distribution, perhaps with commands like this:
```bash
$ wsl -d docker-desktop -u root
$ vi /etc/sysctl.conf
```
and appending a line which reads:
```bash
vm.max_map_count = 262144
---
sidebar_position: 7
slug: /max_map_count
---
# Update vm.max_map_count
## Linux
To check the value of `vm.max_map_count`:
```bash
$ sysctl vm.max_map_count
```
Reset `vm.max_map_count` to a value at least 262144 if it is not.
```bash
# In this case, we set it to 262144:
$ sudo sysctl -w vm.max_map_count=262144
```
This change will be reset after a system reboot. To ensure your change remains permanent, add or update the `vm.max_map_count` value in **/etc/sysctl.conf** accordingly:
```bash
vm.max_map_count=262144
```
## Mac
```bash
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
$ sysctl -w vm.max_map_count=262144
```
To exit the screen session, type Ctrl a d.
## Windows and macOS with Docker Desktop
The vm.max_map_count setting must be set via docker-machine:
```bash
$ docker-machine ssh
$ sudo sysctl -w vm.max_map_count=262144
```
## Windows with Docker Desktop WSL 2 backend
To manually set it every time you reboot, you must run the following commands in a command prompt or PowerShell window every time you restart Docker:
```bash
$ wsl -d docker-desktop -u root
$ sysctl -w vm.max_map_count=262144
```
If you are on these versions of WSL and you do not want to have to run those commands every time you restart Docker, you can globally change every WSL distribution with this setting by modifying your %USERPROFILE%\.wslconfig as follows:
```bash
[wsl2]
kernelCommandLine = "sysctl.vm.max_map_count=262144"
```
This will cause all WSL2 VMs to have that setting assigned when they start.
If you are on Windows 11, or Windows 10 version 22H2 and have installed the Microsoft Store version of WSL, you can modify the /etc/sysctl.conf within the "docker-desktop" WSL distribution, perhaps with commands like this:
```bash
$ wsl -d docker-desktop -u root
$ vi /etc/sysctl.conf
```
and appending a line which reads:
```bash
vm.max_map_count = 262144
```

59
docs/guides/start_chat.md Normal file
View File

@ -0,0 +1,59 @@
---
sidebar_position: 2
slug: /start_chat
---
# Start an AI chat
Knowledge base, hallucination-free chat, and file management are three pillars of RAGFlow. Chats in RAGFlow are based on a particular knowledge base or multiple knowledge bases. Once you have created your knowledge base and finished file parsing, you can go ahead and start an AI conversation.
## Start an AI chat
You start an AI conversation by creating an assistant.
1. Click the **Chat** tab in the middle top of the page **>** **Create an assistant** to show the **Chat Configuration** dialogue *of your next dialogue*.
> RAGFlow offers you the flexibility of choosing a different chat model for each dialogue, while allowing you to set the default models in **System Model Settings**.
2. Update **Assistant Setting**:
- **Assistant name** is the name of your chat assistant. Each assistant corresponds to a dialogue with a unique combination of knowledge bases, prompts, hybrid search configurations, and large model settings.
- **Empty response**:
- If you wish to *confine* RAGFlow's answers to your knowledge bases, leave a response here. Then when it doesn't retrieve an answer, it *uniformly* responds with what you set here.
- If you wish RAGFlow to *improvise* when it doesn't retrieve an answer from your knowledge bases, leave it blank, which may give rise to hallucinations.
- **Show Quote**: This is a key feature of RAGFlow and enabled by default. RAGFlow does not work like a black box. instead, it clearly shows the sources of information that its responses are based on.
- Select the corresponding knowledge bases. You can select one or multiple knowledge bases, but ensure that they use the same embedding model, otherwise an error would occur.
3. Update **Prompt Engine**:
- In **System**, you fill in the prompts for your LLM, you can also leave the default prompt as-is for the beginning.
- **Similarity threshold** sets the similarity "bar" for each chunk of text. The default is 0.2. Text chunks with lower similarity scores are filtered out of the final response.
- **Vector similarity weight** is set to 0.3 by default. RAGFlow uses a hybrid score system, combining keyword similarity and vector similarity, for evaluating the relevance of different text chunks. This value sets the weight assigned to the vector similarity component in the hybrid score.
- **Top N** determines the *maximum* number of chunks to feed to the LLM. In other words, even if more chunks are retrieved, only the top N chunks are provided as input.
- **Variable**:
4. Update **Model Setting**:
- In **Model**: you select the chat model. Though you have selected the default chat model in **System Model Settings**, RAGFlow allows you to choose an alternative chat model for your dialogue.
- **Freedom** refers to the level that the LLM improvises. From **Improvise**, **Precise**, to **Balance**, each freedom level corresponds to a unique combination of **Temperature**, **Top P**, **Presence Penalty**, and **Frequency Penalty**.
- **Temperature**: Level of the prediction randomness of the LLM. The higher the value, the more creative the LLM is.
- **Top P** is also known as "nucleus sampling". See [here](https://en.wikipedia.org/wiki/Top-p_sampling) for more information.
- **Max Tokens**: The maximum length of the LLM's responses. Note that the responses may be curtailed if this value is set too low.
5. Now, let's start the show:
![question1](https://github.com/infiniflow/ragflow/assets/93570324/bb72dd67-b35e-4b2a-87e9-4e4edbd6e677)
![question2](https://github.com/infiniflow/ragflow/assets/93570324/7cc585ae-88d0-4aa2-817d-0370b2ad7230)
## Update settings of an existing dialogue
Hover over an intended dialogue **>** **Edit** to show the chat configuration dialogue:
![update chat configuration](https://github.com/infiniflow/ragflow/assets/93570324/e08397c7-2a4c-44e1-9032-13d30e99d741)
## Integrate chat capabilities into your application
RAGFlow also offers conversation APIs. Hover over your dialogue **>** **Chat Bot API** to integrate RAGFlow's chat capabilities into your application:
![chatbot api](https://github.com/infiniflow/ragflow/assets/93570324/fec23715-f9af-4ac2-81e5-942c5035c5e6)

View File

@ -1,19 +0,0 @@
## Set Before Starting The System
In **user_default_llm** of [service_conf.yaml](./docker/service_conf.yaml), you need to specify LLM factory and your own _API_KEY_.
RagFlow supports the flowing LLM factory, and with more coming in the pipeline:
> [OpenAI](https://platform.openai.com/login?launch), [Tongyi-Qianwen](https://dashscope.console.aliyun.com/model),
> [ZHIPU-AI](https://open.bigmodel.cn/), [Moonshot](https://platform.moonshot.cn/docs)
After sign in these LLM suppliers, create your own API-Key, they all have a certain amount of free quota.
## After Starting The System
You can also set API-Key in **User Setting** as following:
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/e4e4066c-e964-45ff-bd56-c3fc7fb18bd3" width="1000"/>
</div>

View File

@ -1,40 +0,0 @@
# Ollama
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/2019e7ee-1e8a-412e-9349-11bbf702e549" width="130"/>
</div>
One-click deployment of local LLMs, that is [Ollama](https://github.com/ollama/ollama).
## Install
- [Ollama on Linux](https://github.com/ollama/ollama/blob/main/docs/linux.md)
- [Ollama Windows Preview](https://github.com/ollama/ollama/blob/main/docs/windows.md)
- [Docker](https://hub.docker.com/r/ollama/ollama)
## Launch Ollama
Decide which LLM you want to deploy ([here's a list for supported LLM](https://ollama.com/library)), say, **mistral**:
```bash
$ ollama run mistral
```
Or,
```bash
$ docker exec -it ollama ollama run mistral
```
## Use Ollama in RAGFlow
- Go to 'Settings > Model Providers > Models to be added > Ollama'.
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/a9df198a-226d-4f30-b8d7-829f00256d46" width="1300"/>
</div>
> Base URL: Enter the base URL where the Ollama service is accessible, like, `http://<your-ollama-endpoint-domain>:11434`.
- Use Ollama Models.
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/60ff384e-5013-41ff-a573-9a543d237fd3" width="530"/>
</div>

214
docs/quickstart.md Normal file
View File

@ -0,0 +1,214 @@
---
sidebar_position: 1
slug: /
---
# Quick start
RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding. When integrated with LLMs, it is capable of providing truthful question-answering capabilities, backed by well-founded citations from various complex formatted data.
This quick start guide describes a general process from:
- Starting up a local RAGFlow server,
- Creating a knowledge base,
- Intervening with file parsing, to
- Establishing an AI chat based on your datasets.
## Prerequisites
- CPU >= 4 cores
- RAM >= 16 GB
- Disk >= 50 GB
- Docker >= 24.0.0 & Docker Compose >= v2.26.1
> If you have not installed Docker on your local machine (Windows, Mac, or Linux), see [Install Docker Engine](https://docs.docker.com/engine/install/).
## Start up the server
This section provides instructions on setting up the RAGFlow server on Linux. If you are on a different operating system, no worries. Most steps are alike.
1. Ensure `vm.max_map_count` >= 262144:
> To check the value of `vm.max_map_count`:
>
> ```bash
> $ sysctl vm.max_map_count
> ```
>
> Reset `vm.max_map_count` to a value at least 262144 if it is not.
>
> ```bash
> # In this case, we set it to 262144:
> $ sudo sysctl -w vm.max_map_count=262144
> ```
>
> This change will be reset after a system reboot. To ensure your change remains permanent, add or update the `vm.max_map_count` value in **/etc/sysctl.conf** accordingly:
>
> ```bash
> vm.max_map_count=262144
> ```
> See [this guide](./guides/max_map_count.md) for instructions on permanently setting `vm.max_map_count` on an operating system other than Linux.
2. Clone the repo:
```bash
$ git clone https://github.com/infiniflow/ragflow.git
```
3. Build the pre-built Docker images and start up the server:
> Running the following commands automatically downloads the *dev* version RAGFlow Docker image. To download and run a specified Docker version, update `RAGFLOW_VERSION` in **docker/.env** to the intended version, for example `RAGFLOW_VERSION=v0.7.0`, before running the following commands.
```bash
$ cd ragflow/docker
$ chmod +x ./entrypoint.sh
$ docker compose up -d
```
> The core image is about 9 GB in size and may take a while to load.
4. Check the server status after having the server up and running:
```bash
$ docker logs -f ragflow-server
```
_The following output confirms a successful launch of the system:_
```bash
____ ______ __
/ __ \ ____ _ ____ _ / ____// /____ _ __
/ /_/ // __ `// __ `// /_ / // __ \| | /| / /
/ _, _// /_/ // /_/ // __/ / // /_/ /| |/ |/ /
/_/ |_| \__,_/ \__, //_/ /_/ \____/ |__/|__/
/____/
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:9380
* Running on http://x.x.x.x:9380
INFO:werkzeug:Press CTRL+C to quit
```
> If you skip this confirmation step and directly log in to RAGFlow, your browser may prompt a `network anomaly` error because, at that moment, your RAGFlow may not be fully initialized.
5. In your web browser, enter the IP address of your server and log in to RAGFlow.
> - With default settings, you only need to enter `http://IP_OF_YOUR_MACHINE` (**sans** port number) as the default HTTP serving port `80` can be omitted when using the default configurations.
## Configure LLMs
RAGFlow is a RAG engine, and it needs to work with an LLM to offer grounded, hallucination-free question-answering capabilities. For now, RAGFlow supports the following LLMs, and the list is expanding:
- OpenAI
- Tongyi-Qianwen
- ZHIPU-AI
- Moonshot
- DeepSeek-V2
- Baichuan
- VolcEngine
> RAGFlow also supports deploying LLMs locally using Ollama or Xinference, but this part is not covered in this quick start guide.
To add and configure an LLM:
1. Click on your logo on the top right of the page **>** **Model Providers**:
![2 add llm](https://github.com/infiniflow/ragflow/assets/93570324/10635088-028b-4b3d-add9-5c5a6e626814)
> Each RAGFlow account is able to use **text-embedding-v2** for free, a embedding model of Tongyi-Qianwen. This is why you can see Tongyi-Qianwen in the **Added models** list. And you may need to update your Tongyi-Qianwen API key at a later point.
2. Click on the desired LLM and update the API key accordingly (DeepSeek-V2 in this case):
![update api key](https://github.com/infiniflow/ragflow/assets/93570324/4e5e13ef-a98d-42e6-bcb1-0c6045fc1666)
*Your added models appear as follows:*
![added available models](https://github.com/infiniflow/ragflow/assets/93570324/d08b80e4-f921-480a-b41d-11832489c916)
3. Click **System Model Settings** to select the default models:
- Chat model,
- Embedding model,
- Image-to-text model.
![system model settings](https://github.com/infiniflow/ragflow/assets/93570324/cdcc1da5-4494-44cd-ad5b-1222ed6acc3f)
> Some models, such as the image-to-text model **qwen-vl-max**, are subsidiary to a specific LLM. And you may need to update your API key to access these models.
## Create your first knowledge base
You are allowed to upload files to a knowledge base in RAGFlow and parse them into datasets. A knowledge base is virtually a collection of datasets. Question answering in RAGFlow can be based on a particular knowledge base or multiple knowledge bases. File formats that RAGFlow supports include documents (PDF, DOC, DOCX, TXT, MD), tables (CSV, XLSX, XLS), pictures (JPEG, JPG, PNG, TIF, GIF), and slides (PPT, PPTX).
To create your first knowledge base:
1. Click the **Knowledge Base** tab in the top middle of the page **>** **Create knowledge base**.
2. Input the name of your knowledge base and click **OK** to confirm your changes.
_You are taken to the **Configuration** page of your knowledge base._
![knowledge base configuration](https://github.com/infiniflow/ragflow/assets/93570324/384c671a-8b9c-468c-b1c9-1401128a9b65)
3. RAGFlow offers multiple chunk templates that cater to different document layouts and file formats. Select the embedding model and chunk method (template) for your knowledge base.
> IMPORTANT: Once you have selected an embedding model and used it to parse a file, you are no longer allowed to change it. The obvious reason is that we must ensure that all files in a specific knowledge base are parsed using the *same* embedding model (ensure that they are being compared in the same embedding space).
_You are taken to the **Dataset** page of your knowledge base._
4. Click **+ Add file** **>** **Local files** to start uploading a particular file to the knowledge base.
5. In the uploaded file entry, click the play button to start file parsing:
![file parsing](https://github.com/infiniflow/ragflow/assets/93570324/19f273fa-0ab0-435e-bdf4-a47fb080a078)
_When the file parsing completes, its parsing status changes to **SUCCESS**._
## Intervene with file parsing
RAGFlow features visibility and explainability, allowing you to view the chunking results and intervene where necessary. To do so:
1. Click on the file that completes file parsing to view the chunking results:
_You are taken to the **Chunk** page:_
![chunks](https://github.com/infiniflow/ragflow/assets/93570324/0547fd0e-e71b-41f8-8e0e-31649c85fd3d)
2. Hover over each snapshot for a quick view of each chunk.
3. Double click the chunked texts to add keywords or make *manual* changes where necessary:
![update chunk](https://github.com/infiniflow/ragflow/assets/93570324/1d84b408-4e9f-46fd-9413-8c1059bf9c76)
4. In Retrieval testing, ask a quick question in **Test text** to double check if your configurations work:
_As you can tell from the following, RAGFlow responds with truthful citations._
![retrieval test](https://github.com/infiniflow/ragflow/assets/93570324/c03f06f6-f41f-4b20-a97e-ae405d3a950c)
## Set up an AI chat
Conversations in RAGFlow are based on a particular knowledge base or multiple knowledge bases. Once you have created your knowledge base and finished file parsing, you can go ahead and start an AI conversation.
1. Click the **Chat** tab in the middle top of the mage **>** **Create an assistant** to show the **Chat Configuration** dialogue *of your next dialogue*.
> RAGFlow offer the flexibility of choosing a different chat model for each dialogue, while allowing you to set the default models in **System Model Settings**.
2. Update **Assistant Setting**:
- Name your assistant and specify your knowledge bases.
- **Empty response**:
- If you wish to *confine* RAGFlow's answers to your knowledge bases, leave a response here. Then when it doesn't retrieve an answer, it *uniformly* responds with what you set here.
- If you wish RAGFlow to *improvise* when it doesn't retrieve an answer from your knowledge bases, leave it blank, which may give rise to hallucinations.
3. Update **Prompt Engine** or leave it as is for the beginning.
4. Update **Model Setting**.
5. RAGFlow also offers conversation APIs. Hover over your dialogue **>** **Chat Bot API** to integrate RAGFlow's chat capabilities into your applications:
![chatbot api](https://github.com/infiniflow/ragflow/assets/93570324/fec23715-f9af-4ac2-81e5-942c5035c5e6)
6. Now, let's start the show:
![question1](https://github.com/infiniflow/ragflow/assets/93570324/bb72dd67-b35e-4b2a-87e9-4e4edbd6e677)
![question2](https://github.com/infiniflow/ragflow/assets/93570324/7cc585ae-88d0-4aa2-817d-0370b2ad7230)

View File

@ -1,52 +1,52 @@
---
sidebar_position: 0
slug: /contribution_guidelines
---
# Contribution Guidelines
Thanks for wanting to contribute to RAGFlow. This document offers guidlines and major considerations for submitting your contributions.
- To report a bug, file a [GitHub issue](https://github.com/infiniflow/ragflow/issues/new/choose) with us.
- For further questions, you can explore existing discussions or initiate a new one in [Discussions](https://github.com/orgs/infiniflow/discussions).
## What you can contribute
The list below mentions some contributions you can make, but it is not a complete list.
- Proposing or implementing new features
- Fixing a bug
- Adding test cases or demos
- Posting a blog or tutorial
- Updates to existing documents, codes, or annotations.
- Suggesting more user-friendly error codes
## File a pull request (PR)
### General workflow
1. Fork our GitHub repository.
2. Clone your fork to your local machine:
`git clone git@github.com:<yourname>/ragflow.git`
3. Create a local branch:
`git checkout -b my-branch`
4. Provide sufficient information in your commit message
`git commit -m 'Provide sufficient info in your commit message'`
5. Commit changes to your local branch, and push to GitHub: (include necessary commit message)
`git push origin my-branch.`
6. Submit a pull request for review.
### Before filing a PR
- Consider splitting a large PR into multiple smaller, standalone PRs to keep a traceable development history.
- Ensure that your PR addresses just one issue, or keep any unrelated changes small.
- Add test cases when contributing new features. They demonstrate that your code functions correctly and protect against potential issues from future changes.
### Describing your PR
- Ensure that your PR title is concise and clear, providing all the required information.
- Refer to a corresponding GitHub issue in your PR description if applicable.
- Include sufficient design details for *breaking changes* or *API changes* in your description.
### Reviewing & merging a PR
---
sidebar_position: 0
slug: /contribution_guidelines
---
# Contribution guidelines
Thanks for wanting to contribute to RAGFlow. This document offers guidlines and major considerations for submitting your contributions.
- To report a bug, file a [GitHub issue](https://github.com/infiniflow/ragflow/issues/new/choose) with us.
- For further questions, you can explore existing discussions or initiate a new one in [Discussions](https://github.com/orgs/infiniflow/discussions).
## What you can contribute
The list below mentions some contributions you can make, but it is not a complete list.
- Proposing or implementing new features
- Fixing a bug
- Adding test cases or demos
- Posting a blog or tutorial
- Updates to existing documents, codes, or annotations.
- Suggesting more user-friendly error codes
## File a pull request (PR)
### General workflow
1. Fork our GitHub repository.
2. Clone your fork to your local machine:
`git clone git@github.com:<yourname>/ragflow.git`
3. Create a local branch:
`git checkout -b my-branch`
4. Provide sufficient information in your commit message
`git commit -m 'Provide sufficient info in your commit message'`
5. Commit changes to your local branch, and push to GitHub: (include necessary commit message)
`git push origin my-branch.`
6. Submit a pull request for review.
### Before filing a PR
- Consider splitting a large PR into multiple smaller, standalone PRs to keep a traceable development history.
- Ensure that your PR addresses just one issue, or keep any unrelated changes small.
- Add test cases when contributing new features. They demonstrate that your code functions correctly and protect against potential issues from future changes.
### Describing your PR
- Ensure that your PR title is concise and clear, providing all the required information.
- Refer to a corresponding GitHub issue in your PR description if applicable.
- Include sufficient design details for *breaking changes* or *API changes* in your description.
### Reviewing & merging a PR
- Ensure that your PR passes all Continuous Integration (CI) tests before merging it.

View File

@ -0,0 +1,8 @@
{
"label": "References",
"position": 3,
"link": {
"type": "generated-index",
"description": "RAGFlow References"
}
}

View File

@ -1,364 +1,506 @@
# Conversation API Instruction
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/df0dcc3d-789a-44f7-89f1-7a5f044ab729" width="830"/>
</div>
## Base URL
```buildoutcfg
https://demo.ragflow.io/v1/
```
## Authorization
All the APIs are authorized with API-Key. Please keep it safe and private. Don't reveal it in any way from the front-end.
The API-Key should put in the header of request:
```buildoutcfg
Authorization: Bearer {API_KEY}
```
## Start a conversation
This should be called whenever there's new user coming to chat.
### Path: /api/new_conversation
### Method: GET
### Parameter:
| name | type | optional | description|
|------|-------|----|----|
| user_id| string | No | It's for identifying user in order to search and calculate statistics.|
### Response
```json
{
"data": {
"create_date": "Fri, 12 Apr 2024 17:26:21 GMT",
"create_time": 1712913981857,
"dialog_id": "4f0a2e4cb9af11ee9ba20aef05f5e94f",
"duration": 0.0,
"id": "b9b2e098f8ae11ee9f45fa163e197198",
"message": [
{
"content": "Hi, I'm your assistant, can I help you?",
"role": "assistant"
}
],
"reference": [],
"tokens": 0,
"update_date": "Fri, 12 Apr 2024 17:26:21 GMT",
"update_time": 1712913981857,
"user_id": "kevinhu"
},
"retcode": 0,
"retmsg": "success"
}
```
> data['id'] in response should be stored and will be used in every round of following conversation.
## Get history of a conversation
### Path: /api/conversation/\<id\>
### Method: GET
### Response
```json
{
"data": {
"create_date": "Mon, 01 Apr 2024 09:28:42 GMT",
"create_time": 1711934922220,
"dialog_id": "df4a4916d7bd11eeaa650242ac180006",
"id": "2cae30fcefc711ee94140242ac180006",
"message": [
{
"content": "Hi! I'm your assistant, what can I do for you?",
"role": "assistant"
},
{
"content": "What's the vit score for GPT-4?",
"role": "user"
},
{
"content": "The ViT Score for GPT-4 in the zero-shot scenario is 0.5058, and in the few-shot scenario, it is 0.6480. ##0$$",
"role": "assistant"
},
{
"content": "How is the nvlink topology like?",
"role": "user"
},
{
"content": "NVLink topology refers to the arrangement of connections between GPUs using NVIDIA's NVLink technology. Correct NVLink topology for NVIDIA A100 cards involves connecting one GPU to another through a series of NVLink bridges ##0$$. Each of the three attached bridges spans two PCIe slots, and for optimal performance and balanced bridge topology, all three NVLink bridges should be used when connecting two adjacent A100 cards.\n\nHere's a summary of the correct and incorrect topologies:\n\n- **Correct**: Both GPUs are connected via all three NVLink bridges, ensuring full bandwidth and proper communication.\n- **Incorrect**: Not using all three bridges or having an uneven connection configuration would result in suboptimal performance.\n\nIt's also important to note that for multi-CPU systems, both A100 cards in a bridged pair should be within the same CPU domain, unless each CPU has a single A100 PCIe card, in which case they can be bridged together.",
"role": "assistant"
}
],
"user_id": "user name",
"reference": [
{
"chunks": [
{
"chunk_id": "d0bc7892c3ec4aeac071544fd56730a8",
"content_ltks": "tabl 1:openagi task-solv perform under differ set for three closed-sourc llm . boldfac denot the highest score under each learn schema . metric gpt-3.5-turbo claude-2 gpt-4 zero few zero few zero few clip score 0.0 0.0 0.0 0.2543 0.0 0.3055 bert score 0.1914 0.3820 0.2111 0.5038 0.2076 0.6307 vit score 0.2437 0.7497 0.4082 0.5416 0.5058 0.6480 overal 0.1450 0.3772 0.2064 0.4332 0.2378 0.5281",
"content_with_weight": "<table><caption>Table 1: OpenAGI task-solving performances under different settings for three closed-source LLMs. Boldface denotes the highest score under each learning schema.</caption>\n<tr><th rowspan=2 >Metrics</th><th >GPT-3.5-turbo</th><th></th><th >Claude-2</th><th >GPT-4</th></tr>\n<tr><th >Zero</th><th >Few</th><th >Zero Few</th><th >Zero Few</th></tr>\n<tr><td >CLIP Score</td><td >0.0</td><td >0.0</td><td >0.0 0.2543</td><td >0.0 0.3055</td></tr>\n<tr><td >BERT Score</td><td >0.1914</td><td >0.3820</td><td >0.2111 0.5038</td><td >0.2076 0.6307</td></tr>\n<tr><td >ViT Score</td><td >0.2437</td><td >0.7497</td><td >0.4082 0.5416</td><td >0.5058 0.6480</td></tr>\n<tr><td >Overall</td><td >0.1450</td><td >0.3772</td><td >0.2064 0.4332</td><td >0.2378 0.5281</td></tr>\n</table>",
"doc_id": "c790da40ea8911ee928e0242ac180005",
"docnm_kwd": "OpenAGI When LLM Meets Domain Experts.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-d0bc7892c3ec4aeac071544fd56730a8",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
9.0,
159.9383341471354,
472.1773274739583,
223.58013916015625,
307.86692301432294
]
],
"similarity": 0.7310340654129031,
"term_similarity": 0.7671974387781668,
"vector_similarity": 0.40556370512552886
},
{
"chunk_id": "7e2345d440383b756670e1b0f43a7007",
"content_ltks": "5.5 experiment analysi the main experiment result are tabul in tab . 1 and 2 , showcas the result for closed-sourc and open-sourc llm , respect . the overal perform is calcul a the averag of cllp 8 bert and vit score . here , onli the task descript of the benchmark task are fed into llm(addit inform , such a the input prompt and llm\u2019output , is provid in fig . a.4 and a.5 in supplementari). broadli speak , closed-sourc llm demonstr superior perform on openagi task , with gpt-4 lead the pack under both zero-and few-shot scenario . in the open-sourc categori , llama-2-13b take the lead , consist post top result across variou learn schema--the perform possibl influenc by it larger model size . notabl , open-sourc llm significantli benefit from the tune method , particularli fine-tun and\u2019rltf . these method mark notic enhanc for flan-t5-larg , vicuna-7b , and llama-2-13b when compar with zero-shot and few-shot learn schema . in fact , each of these open-sourc model hit it pinnacl under the rltf approach . conclus , with rltf tune , the perform of llama-2-13b approach that of gpt-3.5 , illustr it potenti .",
"content_with_weight": "5.5 Experimental Analysis\nThe main experimental results are tabulated in Tab. 1 and 2, showcasing the results for closed-source and open-source LLMs, respectively. The overall performance is calculated as the average of CLlP\n8\nBERT and ViT scores. Here, only the task descriptions of the benchmark tasks are fed into LLMs (additional information, such as the input prompt and LLMs\u2019 outputs, is provided in Fig. A.4 and A.5 in supplementary). Broadly speaking, closed-source LLMs demonstrate superior performance on OpenAGI tasks, with GPT-4 leading the pack under both zero- and few-shot scenarios. In the open-source category, LLaMA-2-13B takes the lead, consistently posting top results across various learning schema--the performance possibly influenced by its larger model size. Notably, open-source LLMs significantly benefit from the tuning methods, particularly Fine-tuning and\u2019 RLTF. These methods mark noticeable enhancements for Flan-T5-Large, Vicuna-7B, and LLaMA-2-13B when compared with zero-shot and few-shot learning schema. In fact, each of these open-source models hits its pinnacle under the RLTF approach. Conclusively, with RLTF tuning, the performance of LLaMA-2-13B approaches that of GPT-3.5, illustrating its potential.",
"doc_id": "c790da40ea8911ee928e0242ac180005",
"docnm_kwd": "OpenAGI When LLM Meets Domain Experts.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-7e2345d440383b756670e1b0f43a7007",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
8.0,
107.3,
508.90000000000003,
686.3,
697.0
],
],
"similarity": 0.6691508616357027,
"term_similarity": 0.6999011754270821,
"vector_similarity": 0.39239803751328806
},
],
"doc_aggs": [
{
"count": 8,
"doc_id": "c790da40ea8911ee928e0242ac180005",
"doc_name": "OpenAGI When LLM Meets Domain Experts.pdf"
}
],
"total": 8
},
{
"chunks": [
{
"chunk_id": "8c11a1edddb21ad2ae0c43b4a5dcfa62",
"content_ltks": "nvlink bridg support nvidia\u00aenvlink\u00aei a high-spe point-to-point peer transfer connect , where one gpu can transfer data to and receiv data from one other gpu . the nvidia a100 card support nvlink bridg connect with a singl adjac a100 card . each of the three attach bridg span two pcie slot . to function correctli a well a to provid peak bridg bandwidth , bridg connect with an adjac a100 card must incorpor all three nvlink bridg . wherev an adjac pair of a100 card exist in the server , for best bridg perform and balanc bridg topolog , the a100 pair should be bridg . figur 4 illustr correct and incorrect a100 nvlink connect topolog . nvlink topolog\u2013top view figur 4. correct incorrect correct incorrect for system that featur multipl cpu , both a100 card of a bridg card pair should be within the same cpu domain\u2014that is , under the same cpu\u2019s topolog . ensur thi benefit workload applic perform . the onli except is for dual cpu system wherein each cpu ha a singl a100 pcie card under it;in that case , the two a100 pcie card in the system may be bridg togeth . a100 nvlink speed and bandwidth are given in the follow tabl . tabl 5. a100 nvlink speed and bandwidth paramet valu total nvlink bridg support by nvidia a100 3 total nvlink rx and tx lane support 96 data rate per nvidia a100 nvlink lane(each direct)50 gbp total maximum nvlink bandwidth 600 gbyte per second pb-10137-001_v03|8 nvidia a100 40gb pcie gpu acceler",
"content_with_weight": "NVLink Bridge Support\nNVIDIA\u00aeNVLink\u00aeis a high-speed point-to-point peer transfer connection, where one GPU can transfer data to and receive data from one other GPU. The NVIDIA A100 card supports NVLink bridge connection with a single adjacent A100 card.\nEach of the three attached bridges spans two PCIe slots. To function correctly as well as to provide peak bridge bandwidth, bridge connection with an adjacent A100 card must incorporate all three NVLink bridges. Wherever an adjacent pair of A100 cards exists in the server, for best bridging performance and balanced bridge topology, the A100 pair should be bridged. Figure 4 illustrates correct and incorrect A100 NVLink connection topologies.\nNVLink Topology \u2013Top Views \nFigure 4. \nCORRECT \nINCORRECT \nCORRECT \nINCORRECT \nFor systems that feature multiple CPUs, both A100 cards of a bridged card pair should be within the same CPU domain\u2014that is, under the same CPU\u2019s topology. Ensuring this benefits workload application performance. The only exception is for dual CPU systems wherein each CPU has a single A100 PCIe card under it; in that case, the two A100 PCIe cards in the system may be bridged together.\nA100 NVLink speed and bandwidth are given in the following table.\n<table><caption>Table 5. A100 NVLink Speed and Bandwidth </caption>\n<tr><th >Parameter </th><th >Value </th></tr>\n<tr><td >Total NVLink bridges supported by NVIDIA A100 </td><td >3 </td></tr>\n<tr><td >Total NVLink Rx and Tx lanes supported </td><td >96 </td></tr>\n<tr><td >Data rate per NVIDIA A100 NVLink lane (each direction)</td><td >50 Gbps </td></tr>\n<tr><td >Total maximum NVLink bandwidth</td><td >600 Gbytes per second </td></tr>\n</table>\nPB-10137-001_v03 |8\nNVIDIA A100 40GB PCIe GPU Accelerator",
"doc_id": "806d1ed0ea9311ee860a0242ac180005",
"docnm_kwd": "A100-PCIE-Prduct-Brief.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-8c11a1edddb21ad2ae0c43b4a5dcfa62",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
12.0,
84.0,
541.3,
76.7,
96.7
],
],
"similarity": 0.3200748779905588,
"term_similarity": 0.3082244010114718,
"vector_similarity": 0.42672917080234146
},
],
"doc_aggs": [
{
"count": 1,
"doc_id": "806d1ed0ea9311ee860a0242ac180005",
"doc_name": "A100-PCIE-Prduct-Brief.pdf"
}
],
"total": 3
}
],
"update_date": "Tue, 02 Apr 2024 09:07:49 GMT",
"update_time": 1712020069421
},
"retcode": 0,
"retmsg": "success"
}
```
- **message**: All the chat history in it.
- role: user or assistant
- content: the text content of user or assistant. The citations are in format like: ##0$$. The number in the middle indicate which part in data.reference.chunks it refers to.
- **user_id**: This is set by the caller.
- **reference**: Every item in it refer to the corresponding message in data.message whose role is assistant.
- chunks
- content_with_weight: The content of chunk.
- docnm_kwd: the document name.
- img_id: the image id of the chunk. It is an optional field only for PDF/pptx/picture. And accessed by 'GET' /document/get/\<id\>.
- positions: [page_number, [upleft corner(x, y)], [right bottom(x, y)]], the chunk position, only for PDF.
- similarity: the hybrid similarity.
- term_similarity: keyword simimlarity
- vector_similarity: embedding similarity
- doc_aggs:
- doc_id: the document can be accessed by 'GET' /document/get/\<id\>
- doc_name: the file name
- count: the chunk number hit in this document.
## Chat
This will be called to get the answer to users' questions.
### Path: /api/completion
### Method: POST
### Parameter:
| name | type | optional | description|
|------|-------|----|----|
| conversation_id| string | No | This is from calling /new_conversation.|
| messages| json | No | All the conversation history stored here including the latest user's question.|
| quote | bool | Yes | Default: true |
### Response
```json
{
"data": {
"answer": "The ViT Score for GPT-4 in the zero-shot scenario is 0.5058, and in the few-shot scenario, it is 0.6480. ##0$$",
"reference": {
"chunks": [
{
"chunk_id": "d0bc7892c3ec4aeac071544fd56730a8",
"content_ltks": "tabl 1:openagi task-solv perform under differ set for three closed-sourc llm . boldfac denot the highest score under each learn schema . metric gpt-3.5-turbo claude-2 gpt-4 zero few zero few zero few clip score 0.0 0.0 0.0 0.2543 0.0 0.3055 bert score 0.1914 0.3820 0.2111 0.5038 0.2076 0.6307 vit score 0.2437 0.7497 0.4082 0.5416 0.5058 0.6480 overal 0.1450 0.3772 0.2064 0.4332 0.2378 0.5281",
"content_with_weight": "<table><caption>Table 1: OpenAGI task-solving performances under different settings for three closed-source LLMs. Boldface denotes the highest score under each learning schema.</caption>\n<tr><th rowspan=2 >Metrics</th><th >GPT-3.5-turbo</th><th></th><th >Claude-2</th><th >GPT-4</th></tr>\n<tr><th >Zero</th><th >Few</th><th >Zero Few</th><th >Zero Few</th></tr>\n<tr><td >CLIP Score</td><td >0.0</td><td >0.0</td><td >0.0 0.2543</td><td >0.0 0.3055</td></tr>\n<tr><td >BERT Score</td><td >0.1914</td><td >0.3820</td><td >0.2111 0.5038</td><td >0.2076 0.6307</td></tr>\n<tr><td >ViT Score</td><td >0.2437</td><td >0.7497</td><td >0.4082 0.5416</td><td >0.5058 0.6480</td></tr>\n<tr><td >Overall</td><td >0.1450</td><td >0.3772</td><td >0.2064 0.4332</td><td >0.2378 0.5281</td></tr>\n</table>",
"doc_id": "c790da40ea8911ee928e0242ac180005",
"docnm_kwd": "OpenAGI When LLM Meets Domain Experts.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-d0bc7892c3ec4aeac071544fd56730a8",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
9.0,
159.9383341471354,
472.1773274739583,
223.58013916015625,
307.86692301432294
]
],
"similarity": 0.7310340654129031,
"term_similarity": 0.7671974387781668,
"vector_similarity": 0.40556370512552886
},
{
"chunk_id": "7e2345d440383b756670e1b0f43a7007",
"content_ltks": "5.5 experiment analysi the main experiment result are tabul in tab . 1 and 2 , showcas the result for closed-sourc and open-sourc llm , respect . the overal perform is calcul a the averag of cllp 8 bert and vit score . here , onli the task descript of the benchmark task are fed into llm(addit inform , such a the input prompt and llm\u2019output , is provid in fig . a.4 and a.5 in supplementari). broadli speak , closed-sourc llm demonstr superior perform on openagi task , with gpt-4 lead the pack under both zero-and few-shot scenario . in the open-sourc categori , llama-2-13b take the lead , consist post top result across variou learn schema--the perform possibl influenc by it larger model size . notabl , open-sourc llm significantli benefit from the tune method , particularli fine-tun and\u2019rltf . these method mark notic enhanc for flan-t5-larg , vicuna-7b , and llama-2-13b when compar with zero-shot and few-shot learn schema . in fact , each of these open-sourc model hit it pinnacl under the rltf approach . conclus , with rltf tune , the perform of llama-2-13b approach that of gpt-3.5 , illustr it potenti .",
"content_with_weight": "5.5 Experimental Analysis\nThe main experimental results are tabulated in Tab. 1 and 2, showcasing the results for closed-source and open-source LLMs, respectively. The overall performance is calculated as the average of CLlP\n8\nBERT and ViT scores. Here, only the task descriptions of the benchmark tasks are fed into LLMs (additional information, such as the input prompt and LLMs\u2019 outputs, is provided in Fig. A.4 and A.5 in supplementary). Broadly speaking, closed-source LLMs demonstrate superior performance on OpenAGI tasks, with GPT-4 leading the pack under both zero- and few-shot scenarios. In the open-source category, LLaMA-2-13B takes the lead, consistently posting top results across various learning schema--the performance possibly influenced by its larger model size. Notably, open-source LLMs significantly benefit from the tuning methods, particularly Fine-tuning and\u2019 RLTF. These methods mark noticeable enhancements for Flan-T5-Large, Vicuna-7B, and LLaMA-2-13B when compared with zero-shot and few-shot learning schema. In fact, each of these open-source models hits its pinnacle under the RLTF approach. Conclusively, with RLTF tuning, the performance of LLaMA-2-13B approaches that of GPT-3.5, illustrating its potential.",
"doc_id": "c790da40ea8911ee928e0242ac180005",
"docnm_kwd": "OpenAGI When LLM Meets Domain Experts.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-7e2345d440383b756670e1b0f43a7007",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
8.0,
107.3,
508.90000000000003,
686.3,
697.0
]
],
"similarity": 0.6691508616357027,
"term_similarity": 0.6999011754270821,
"vector_similarity": 0.39239803751328806
}
],
"doc_aggs": {
"OpenAGI When LLM Meets Domain Experts.pdf": 4
},
"total": 8
}
},
"retcode": 0,
"retmsg": "success"
}
```
- **answer**: The replay of the chat bot.
- **reference**:
- chunks: Every item in it refer to the corresponding message in answer.
- content_with_weight: The content of chunk.
- docnm_kwd: the document name.
- img_id: the image id of the chunk. It is an optional field only for PDF/pptx/picture. And accessed by 'GET' /document/get/\<id\>.
- positions: [page_number, [upleft corner(x, y)], [right bottom(x, y)]], the chunk position, only for PDF.
- similarity: the hybrid similarity.
- term_similarity: keyword simimlarity
- vector_similarity: embedding similarity
- doc_aggs:
- doc_id: the document can be accessed by 'GET' /document/get/\<id\>
- doc_name: the file name
- count: the chunk number hit in this document.
## Get document content or image
This is usually used when display content of citation.
### Path: /api/document/get/\<id\>
### Method: GET
## Upload file
This is usually used when upload a file to.
### Path: /api/document/upload/
### Method: POST
### Parameter:
| name | type | optional | description |
|---------|--------|----------|----------------------------------------|
| file | file | No | Upload file. |
| kb_name | string | No | Choose the upload knowledge base name. |
### Response
```json
{
"data": {
"chunk_num": 0,
"create_date": "Thu, 25 Apr 2024 14:30:06 GMT",
"create_time": 1714026606921,
"created_by": "553ec818fd5711ee8ea63043d7ed348e",
"id": "41e9324602cd11ef9f5f3043d7ed348e",
"kb_id": "06802686c0a311ee85d6246e9694c130",
"location": "readme.txt",
"name": "readme.txt",
"parser_config": {
"field_map": {
},
"pages": [
[
0,
1000000
]
]
},
"parser_id": "general",
"process_begin_at": null,
"process_duation": 0.0,
"progress": 0.0,
"progress_msg": "",
"run": "0",
"size": 929,
"source_type": "local",
"status": "1",
"thumbnail": null,
"token_num": 0,
"type": "doc",
"update_date": "Thu, 25 Apr 2024 14:30:06 GMT",
"update_time": 1714026606921
},
"retcode": 0,
"retmsg": "success"
}
```
---
sidebar_position: 1
slug: /api
---
# API reference
RAGFlow offers RESTful APIs for you to integrate its capabilities into third-party applications.
## Base URL
```
https://demo.ragflow.io/v1/
```
## Authorization
All of RAGFlow's RESTFul APIs use API key for authorization, so keep it safe and do not expose it to the front end.
Put your API key in the request header.
```buildoutcfg
Authorization: Bearer {API_KEY}
```
To get your API key:
1. In RAGFlow, click **Chat** tab in the middle top of the page.
2. Hover over the corresponding dialogue **>** **Chat Bot API** to show the chatbot API configuration page.
3. Click **Api Key** **>** **Create new key** to create your API key.
4. Copy and keep your API key safe.
## Create conversation
This method creates (news) a conversation for a specific user.
### Request
#### Request URI
| Method | Request URI |
|----------|-------------------------------------------------------------|
| GET | `/api/new_conversation` |
:::note
You are *required* to save the `data.id` value returned in the response data, which is the session ID for all upcoming conversations.
:::
#### Request parameter
| Name | Type | Required | Description |
|----------|--------|----------|-------------------------------------------------------------|
| `user_id`| string | Yes | The unique identifier assigned to each user. `user_id` must be less than 32 characters and cannot be empty. The following character sets are supported: <br />- 26 lowercase English letters (a-z)<br />- 26 uppercase English letters (A-Z)<br />- 10 digits (0-9)<br />- "_", "-", "." |
### Response
```json
{
"data": {
"create_date": "Fri, 12 Apr 2024 17:26:21 GMT",
"create_time": 1712913981857,
"dialog_id": "4f0a2e4cb9af11ee9ba20aef05f5e94f",
"duration": 0.0,
"id": "b9b2e098f8ae11ee9f45fa163e197198",
"message": [
{
"content": "Hi, I'm your assistant, what can I do for you?",
"role": "assistant"
}
],
"reference": [],
"tokens": 0,
"update_date": "Fri, 12 Apr 2024 17:26:21 GMT",
"update_time": 1712913981857,
"user_id": "<USER_ID_SET_BY_THE_CALLER>"
},
"retcode": 0,
"retmsg": "success"
}
```
## Get conversation history
This method retrieves the history of a specified conversation session.
### Request
#### Request URI
| Method | Request URI |
|----------|-------------------------------------------------------------|
| GET | `/api/conversation/<id>` |
#### Request parameter
| Name | Type | Required | Description |
|----------|--------|----------|-------------------------------------------------------------|
| `id` | string | Yes | The unique identifier assigned to a conversation session. `id` must be less than 32 characters and cannot be empty. The following character sets are supported: <br />- 26 lowercase English letters (a-z)<br />- 26 uppercase English letters (A-Z)<br />- 10 digits (0-9)<br />- "_", "-", "." |
### Response
#### Response parameter
- `message`: All conversations in the specified conversation session.
- `role`: `"user"` or `"assistant"`.
- `content`: The text content of user or assistant. The citations are in a format like `##0$$`. The number in the middle, 0 in this case, indicates which part in data.reference.chunks it refers to.
- `user_id`: This is set by the caller.
- `reference`: Each reference corresponds to one of the assistant's answers in `data.message`.
- `chunks`
- `content_with_weight`: Content of the chunk.
- `doc_name`: Name of the *hit* document.
- `img_id`: The image ID of the chunk. It is an optional field only for PDF, PPTX, and images. Call ['GET' /document/get/\<id\>](#get-document-content) to retrieve the image.
- positions: [page_number, [upleft corner(x, y)], [right bottom(x, y)]], the chunk position, only for PDF.
- similarity: The hybrid similarity.
- term_similarity: The keyword simimlarity.
- vector_similarity: The embedding similarity.
- `doc_aggs`:
- `doc_id`: ID of the *hit* document. Call ['GET' /document/get/\<id\>](#get-document-content) to retrieve the document.
- `doc_name`: Name of the *hit* document.
- `count`: The number of *hit* chunks in this document.
```json
{
"data": {
"create_date": "Mon, 01 Apr 2024 09:28:42 GMT",
"create_time": 1711934922220,
"dialog_id": "df4a4916d7bd11eeaa650242ac180006",
"id": "2cae30fcefc711ee94140242ac180006",
"message": [
{
"content": "Hi! I'm your assistant, what can I do for you?",
"role": "assistant"
},
{
"content": "What's the vit score for GPT-4?",
"role": "user"
},
{
"content": "The ViT Score for GPT-4 in the zero-shot scenario is 0.5058, and in the few-shot scenario, it is 0.6480. ##0$$",
"role": "assistant"
}
],
"user_id": "<USER_ID_SET_BY_THE_CALLER>",
"reference": [
{
"chunks": [
{
"chunk_id": "d0bc7892c3ec4aeac071544fd56730a8",
"content_ltks": "tabl 1:openagi task-solv perform under differ set for three closed-sourc llm . boldfac denot the highest score under each learn schema . metric gpt-3.5-turbo claude-2 gpt-4 zero few zero few zero few clip score 0.0 0.0 0.0 0.2543 0.0 0.3055 bert score 0.1914 0.3820 0.2111 0.5038 0.2076 0.6307 vit score 0.2437 0.7497 0.4082 0.5416 0.5058 0.6480 overal 0.1450 0.3772 0.2064 0.4332 0.2378 0.5281",
"content_with_weight": "<table><caption>Table 1: OpenAGI task-solving performances under different settings for three closed-source LLMs. Boldface denotes the highest score under each learning schema.</caption>\n<tr><th rowspan=2 >Metrics</th><th >GPT-3.5-turbo</th><th></th><th >Claude-2</th><th >GPT-4</th></tr>\n<tr><th >Zero</th><th >Few</th><th >Zero Few</th><th >Zero Few</th></tr>\n<tr><td >CLIP Score</td><td >0.0</td><td >0.0</td><td >0.0 0.2543</td><td >0.0 0.3055</td></tr>\n<tr><td >BERT Score</td><td >0.1914</td><td >0.3820</td><td >0.2111 0.5038</td><td >0.2076 0.6307</td></tr>\n<tr><td >ViT Score</td><td >0.2437</td><td >0.7497</td><td >0.4082 0.5416</td><td >0.5058 0.6480</td></tr>\n<tr><td >Overall</td><td >0.1450</td><td >0.3772</td><td >0.2064 0.4332</td><td >0.2378 0.5281</td></tr>\n</table>",
"doc_id": "c790da40ea8911ee928e0242ac180005",
"doc_name": "OpenAGI When LLM Meets Domain Experts.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-d0bc7892c3ec4aeac071544fd56730a8",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
9.0,
159.9383341471354,
472.1773274739583,
223.58013916015625,
307.86692301432294
]
],
"similarity": 0.7310340654129031,
"term_similarity": 0.7671974387781668,
"vector_similarity": 0.40556370512552886
},
{
"chunk_id": "7e2345d440383b756670e1b0f43a7007",
"content_ltks": "5.5 experiment analysi the main experiment result are tabul in tab . 1 and 2 , showcas the result for closed-sourc and open-sourc llm , respect . the overal perform is calcul a the averag of cllp 8 bert and vit score . ",
"content_with_weight": "5.5 Experimental Analysis\nThe main experimental results are tabulated in Tab. 1 and 2, showcasing the results for closed-source and open-source LLMs, respectively. The overall performance is calculated as the average of CLlP\n8\nBERT and ViT scores.",
"doc_id": "c790da40ea8911ee928e0242ac180005",
"doc_name": "OpenAGI When LLM Meets Domain Experts.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-7e2345d440383b756670e1b0f43a7007",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
8.0,
107.3,
508.90000000000003,
686.3,
697.0
],
],
"similarity": 0.6691508616357027,
"term_similarity": 0.6999011754270821,
"vector_similarity": 0.39239803751328806
},
],
"doc_aggs": [
{
"count": 8,
"doc_id": "c790da40ea8911ee928e0242ac180005",
"doc_name": "OpenAGI When LLM Meets Domain Experts.pdf"
}
],
"total": 8
},
],
"update_date": "Tue, 02 Apr 2024 09:07:49 GMT",
"update_time": 1712020069421
},
"retcode": 0,
"retmsg": "success"
}
```
## Get answer
This method retrieves from RAGFlow the answer to the user's latest question.
### Request
#### Request URI
| Method | Request URI |
|----------|-------------------------------------------------------------|
| POST | `/api/completion` |
#### Request parameter
| Name | Type | Required | Description |
|------------------|--------|----------|---------------|
| `conversation_id`| string | Yes | The ID of the conversation session. Call ['GET' /new_conversation](#create-conversation) to retrieve the ID.|
| `messages` | json | Yes | The latest question in a JSON form, such as `[{"role": "user", "content": "How are you doing!"}]`|
| `quote` | bool | No | Default: true |
| `stream` | bool | No | Default: true |
| `doc_ids` | string | No | Document IDs delimited by comma, like `c790da40ea8911ee928e0242ac180005,23dsf34ree928e0242ac180005`. The retrieved contents will be confined to these documents. |
### Response
- `answer`: The answer to the user's latest question.
- `reference`:
- `chunks`: The retrieved chunks that contribute to the answer.
- `content_with_weight`: Content of the chunk.
- `doc_name`: Name of the *hit* document.
- `img_id`: The image ID of the chunk. It is an optional field only for PDF, PPTX, and images. Call ['GET' /document/get/\<id\>](#get-document-content) to retrieve the image.
- `positions`: [page_number, [upleft corner(x, y)], [right bottom(x, y)]], the chunk position, only for PDF.
- `similarity`: The hybrid similarity.
- `term_similarity`: The keyword simimlarity.
- `vector_similarity`: The embedding similarity.
- `doc_aggs`:
- `doc_id`: ID of the *hit* document. Call ['GET' /document/get/\<id\>](#get-document-content) to retrieve the document.
- `doc_name`: Name of the *hit* document.
- `count`: The number of *hit* chunks in this document.
```json
{
"data": {
"answer": "The ViT Score for GPT-4 in the zero-shot scenario is 0.5058, and in the few-shot scenario, it is 0.6480. ##0$$",
"reference": {
"chunks": [
{
"chunk_id": "d0bc7892c3ec4aeac071544fd56730a8",
"content_ltks": "tabl 1:openagi task-solv perform under differ set for three closed-sourc llm . boldfac denot the highest score under each learn schema . metric gpt-3.5-turbo claude-2 gpt-4 zero few zero few zero few clip score 0.0 0.0 0.0 0.2543 0.0 0.3055 bert score 0.1914 0.3820 0.2111 0.5038 0.2076 0.6307 vit score 0.2437 0.7497 0.4082 0.5416 0.5058 0.6480 overal 0.1450 0.3772 0.2064 0.4332 0.2378 0.5281",
"content_with_weight": "<table><caption>Table 1: OpenAGI task-solving performances under different settings for three closed-source LLMs. Boldface denotes the highest score under each learning schema.</caption>\n<tr><th rowspan=2 >Metrics</th><th >GPT-3.5-turbo</th><th></th><th >Claude-2</th><th >GPT-4</th></tr>\n<tr><th >Zero</th><th >Few</th><th >Zero Few</th><th >Zero Few</th></tr>\n<tr><td >CLIP Score</td><td >0.0</td><td >0.0</td><td >0.0 0.2543</td><td >0.0 0.3055</td></tr>\n<tr><td >BERT Score</td><td >0.1914</td><td >0.3820</td><td >0.2111 0.5038</td><td >0.2076 0.6307</td></tr>\n<tr><td >ViT Score</td><td >0.2437</td><td >0.7497</td><td >0.4082 0.5416</td><td >0.5058 0.6480</td></tr>\n<tr><td >Overall</td><td >0.1450</td><td >0.3772</td><td >0.2064 0.4332</td><td >0.2378 0.5281</td></tr>\n</table>",
"doc_id": "c790da40ea8911ee928e0242ac180005",
"doc_name": "OpenAGI When LLM Meets Domain Experts.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-d0bc7892c3ec4aeac071544fd56730a8",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
9.0,
159.9383341471354,
472.1773274739583,
223.58013916015625,
307.86692301432294
]
],
"similarity": 0.7310340654129031,
"term_similarity": 0.7671974387781668,
"vector_similarity": 0.40556370512552886
},
{
"chunk_id": "7e2345d440383b756670e1b0f43a7007",
"content_ltks": "5.5 experiment analysi the main experiment result are tabul in tab . 1 and 2 , showcas the result for closed-sourc and open-sourc llm , respect . the overal perform is calcul a the averag of cllp 8 bert and vit score . here , onli the task descript of the benchmark task are fed into llm(addit inform , such a the input prompt and llm\u2019output , is provid in fig . a.4 and a.5 in supplementari). broadli speak , closed-sourc llm demonstr superior perform on openagi task , with gpt-4 lead the pack under both zero-and few-shot scenario . in the open-sourc categori , llama-2-13b take the lead , consist post top result across variou learn schema--the perform possibl influenc by it larger model size . notabl , open-sourc llm significantli benefit from the tune method , particularli fine-tun and\u2019rltf . these method mark notic enhanc for flan-t5-larg , vicuna-7b , and llama-2-13b when compar with zero-shot and few-shot learn schema . in fact , each of these open-sourc model hit it pinnacl under the rltf approach . conclus , with rltf tune , the perform of llama-2-13b approach that of gpt-3.5 , illustr it potenti .",
"content_with_weight": "5.5 Experimental Analysis\nThe main experimental results are tabulated in Tab. 1 and 2, showcasing the results for closed-source and open-source LLMs, respectively. The overall performance is calculated as the average of CLlP\n8\nBERT and ViT scores. Here, only the task descriptions of the benchmark tasks are fed into LLMs (additional information, such as the input prompt and LLMs\u2019 outputs, is provided in Fig. A.4 and A.5 in supplementary). Broadly speaking, closed-source LLMs demonstrate superior performance on OpenAGI tasks, with GPT-4 leading the pack under both zero- and few-shot scenarios. In the open-source category, LLaMA-2-13B takes the lead, consistently posting top results across various learning schema--the performance possibly influenced by its larger model size. Notably, open-source LLMs significantly benefit from the tuning methods, particularly Fine-tuning and\u2019 RLTF. These methods mark noticeable enhancements for Flan-T5-Large, Vicuna-7B, and LLaMA-2-13B when compared with zero-shot and few-shot learning schema. In fact, each of these open-source models hits its pinnacle under the RLTF approach. Conclusively, with RLTF tuning, the performance of LLaMA-2-13B approaches that of GPT-3.5, illustrating its potential.",
"doc_id": "c790da40ea8911ee928e0242ac180005",
"doc_name": "OpenAGI When LLM Meets Domain Experts.pdf",
"img_id": "afab9fdad6e511eebdb20242ac180006-7e2345d440383b756670e1b0f43a7007",
"important_kwd": [],
"kb_id": "afab9fdad6e511eebdb20242ac180006",
"positions": [
[
8.0,
107.3,
508.90000000000003,
686.3,
697.0
]
],
"similarity": 0.6691508616357027,
"term_similarity": 0.6999011754270821,
"vector_similarity": 0.39239803751328806
}
],
"doc_aggs": {
"OpenAGI When LLM Meets Domain Experts.pdf": 4
},
"total": 8
}
},
"retcode": 0,
"retmsg": "success"
}
```
## Get document content
This method retrieves the content of a document.
### Request
#### Request URI
| Method | Request URI |
|----------|-------------------------------------------------------------|
| GET | `/document/get/<id>` |
### Response
A binary file.
## Upload file
This method uploads a specific file to a specified knowledge base.
### Request
#### Request URI
| Method | Request URI |
|----------|-------------------------------------------------------------|
| POST | `/api/document/upload` |
#### Response parameter
| Name | Type | Required | Description |
|-------------|--------|----------|---------------------------------------------------------|
| `file` | file | Yes | The file to upload. |
| `kb_name` | string | Yes | The name of the knowledge base to upload the file to. |
| `parser_id` | string | No | The parsing method (chunk template) to use. <br />- "naive": General;<br />- "qa": Q&A;<br />- "manual": Manual;<br />- "table": Table;<br />- "paper": Paper;<br />- "laws": Laws;<br />- "presentation": Presentation;<br />- "picture": Picture;<br />- "one": One. |
| `run` | string | No | 1: Automatically start file parsing. If `parser_id` is not set, RAGFlow uses the general template by default. |
### Response
```json
{
"data": {
"chunk_num": 0,
"create_date": "Thu, 25 Apr 2024 14:30:06 GMT",
"create_time": 1714026606921,
"created_by": "553ec818fd5711ee8ea63043d7ed348e",
"id": "41e9324602cd11ef9f5f3043d7ed348e",
"kb_id": "06802686c0a311ee85d6246e9694c130",
"location": "readme.txt",
"name": "readme.txt",
"parser_config": {
"field_map": {
},
"pages": [
[
0,
1000000
]
]
},
"parser_id": "general",
"process_begin_at": null,
"process_duation": 0.0,
"progress": 0.0,
"progress_msg": "",
"run": "0",
"size": 929,
"source_type": "local",
"status": "1",
"thumbnail": null,
"token_num": 0,
"type": "doc",
"update_date": "Thu, 25 Apr 2024 14:30:06 GMT",
"update_time": 1714026606921
},
"retcode": 0,
"retmsg": "success"
}
```
## Get document chunks
This method retrieves the chunks of a specific document by `doc_name` or `doc_id`.
### Request
#### Request URI
| Method | Request URI |
|----------|-------------------------------------------------------------|
| GET | `/api/list_chunks` |
#### Request parameter
| Name | Type | Required | Description |
|------------|--------|----------|---------------------------------------------------------------------------------------------|
| `doc_name` | string | No | The name of the document in the knowledge base. It must not be empty if `doc_id` is not set.|
| `doc_id` | string | No | The ID of the document in the knowledge base. It must not be empty if `doc_name` is not set.|
### Response
```json
{
"data": [
{
"content": "Figure 14: Per-request neural-net processingof RL-Cache.\n103\n(sn)\nCPU\n 102\nGPU\n8101\n100\n8\n16 64 256 1K\n4K",
"doc_name": "RL-Cache.pdf",
"img_id": "0335167613f011ef91240242ac120006-b46c3524952f82dbe061ce9b123f2211"
},
{
"content": "4.3 ProcessingOverheadof RL-CacheACKNOWLEDGMENTSThis section evaluates how effectively our RL-Cache implemen-tation leverages modern multi-core CPUs and GPUs to keep the per-request neural-net processing overhead low. Figure 14 depictsThis researchwas supported inpart by the Regional Government of Madrid (grant P2018/TCS-4499, EdgeData-CM)andU.S. National Science Foundation (grants CNS-1763617 andCNS-1717179).REFERENCES",
"doc_name": "RL-Cache.pdf",
"img_id": "0335167613f011ef91240242ac120006-d4c12c43938eb55d2d8278eea0d7e6d7"
}
],
"retcode": 0,
"retmsg": "success"
}
```
## Get document list
This method retrieves a list of documents from a specified knowledge base.
### Request
#### Request URI
| Method | Request URI |
|----------|-------------------------------------------------------------|
| POST | `/api/list_kb_docs` |
#### Request parameter
| Name | Type | Required | Description |
|-------------|--------|----------|-----------------------------------------------------------------------|
| `kb_name` | string | Yes | The name of the knowledge base, from which you get the document list. |
| `page` | int | No | The number of pages, default:1. |
| `page_size` | int | No | The number of docs for each page, default:15. |
| `orderby` | string | No | `chunk_num`, `create_time`, or `size`, default:`create_time` |
| `desc` | bool | No | Default:True. |
| `keywords` | string | No | Keyword of the document name. |
### Response
```json
{
"data": {
"docs": [
{
"doc_id": "bad89a84168c11ef9ce40242ac120006",
"doc_name": "test.xlsx"
},
{
"doc_id": "641a9b4013f111efb53f0242ac120006",
"doc_name": "1111.pdf"
}
],
"total": 2
},
"retcode": 0,
"retmsg": "success"
}
```
## Delete documents
This method deletes documents by document ID or name.
### Request
#### Request URI
| Method | Request URI |
|----------|-------------------------------------------------------------|
| DELETE | `/api/document` |
#### Request parameter
| Name | Type | Required | Description |
|-------------|--------|----------|----------------------------|
| `doc_names` | List | No | A list of document names. It must not be empty if `doc_ids` is not set. |
| `doc_ids` | List | No | A list of document IDs. It must not be empty if `doc_names` is not set. |
### Response
```json
{
"data": true,
"retcode": 0,
"retmsg": "success"
}
```

View File

@ -1,4 +1,9 @@
# Frequently Asked Questions
---
sidebar_position: 3
slug: /faq
---
# Frequently asked questions
## General
@ -13,6 +18,19 @@ The "garbage in garbage out" status quo remains unchanged despite the fact that
English, simplified Chinese, traditional Chinese for now.
### 3. Which embedding models can be deployed locally?
- BAAI/bge-large-zh-v1.5
- BAAI/bge-base-en-v1.5
- BAAI/bge-large-en-v1.5
- BAAI/bge-small-en-v1.5
- BAAI/bge-small-zh-v1.5
- jinaai/jina-embeddings-v2-base-en
- jinaai/jina-embeddings-v2-small-en
- nomic-ai/nomic-embed-text-v1.5
- sentence-transformers/all-MiniLM-L6-v2
- maidalun1020/bce-embedding-base_v1
## Performance
### 1. Why does it take longer for RAGFlow to parse a document than LangChain?
@ -31,7 +49,7 @@ Currently, we only support x86 CPU and Nvidia GPU.
### 2. Do you offer an API for integration with third-party applications?
The corresponding APIs are now available. See the [Conversation API](./conversation_api.md) for more information.
The corresponding APIs are now available. See the [RAGFlow API Reference](./api.md) for more information.
### 3. Do you support stream output?
@ -264,7 +282,7 @@ This is because you forgot to update the `vm.max_map_count` value in **/etc/sysc
#### 4.11 `{"data":null,"retcode":100,"retmsg":"<NotFound '404: Not Found'>"}`
Your IP address or port number may be incorrect. If you are using the default configurations, enter http://<IP_OF_YOUR_MACHINE> (**NOT 9380, AND NO PORT NUMBER REQUIRED!**) in your browser. This should work.
Your IP address or port number may be incorrect. If you are using the default configurations, enter `http://<IP_OF_YOUR_MACHINE>` (**NOT 9380, AND NO PORT NUMBER REQUIRED!**) in your browser. This should work.
#### 4.12 `Ollama - Mistral instance running at 127.0.0.1:11434 but cannot add Ollama as model in RagFlow`
@ -354,7 +372,7 @@ You limit what the system responds to what you specify in **Empty response** if
### 4. How to run RAGFlow with a locally deployed LLM?
You can use Ollama to deploy local LLM. See [here](https://github.com/infiniflow/ragflow/blob/main/docs/ollama.md) for more information.
You can use Ollama to deploy local LLM. See [here](https://github.com/infiniflow/ragflow/blob/main/docs/guides/deploy_local_llm.md) for more information.
### 5. How to link up ragflow and ollama servers?
@ -367,11 +385,11 @@ You can use Ollama to deploy local LLM. See [here](https://github.com/infiniflow
2. Right click the desired knowledge base to display the **Configuration** dialogue.
3. Choose **Q&A** as the chunk method and click **Save** to confirm your change.
### 7 Do I need to connect to Redis?
### 7. Do I need to connect to Redis?
No, connecting to Redis is not required.
### 8 `Error: Range of input length should be [1, 30000]`
### 8. `Error: Range of input length should be [1, 30000]`
This error occurs because there are too many chunks matching your search criteria. Try reducing the **TopN** and increasing **Similarity threshold** to fix this issue:
@ -382,7 +400,15 @@ This error occurs because there are too many chunks matching your search criteri
![topn](https://github.com/infiniflow/ragflow/assets/93570324/7ec72ab3-0dd2-4cff-af44-e2663b67b2fc)
### 9 How to update RAGFlow to the latest version?
### 9. How to upgrade RAGFlow?
You can upgrade RAGFlow to either the dev version or the latest version:
- Dev versions are for developers and contributors. They are published on a nightly basis and may crash because they are not fully tested. We cannot guarantee their validity and you are at your own risk trying out latest, untested features.
- The latest version refers to the most recent, officially published release. It is stable and works best with regular users.
To upgrade RAGFlow to the dev version:
1. Pull the latest source code
```bash
@ -403,3 +429,30 @@ This error occurs because there are too many chunks matching your search criteri
```bash
docker compose -f docker-compose-CN.yml up -d
```
To upgrade RAGFlow to the latest version:
1. Update **ragflow/docker/.env** as follows:
```bash
RAGFLOW_VERSION=latest
```
2. Pull the latest source code:
```bash
cd ragflow
git pull
```
3. If you used `docker compose up -d` to start up RAGFlow server:
```bash
docker pull infiniflow/ragflow:latest
```
```bash
docker compose up ragflow -d
```
4. If you used `docker compose -f docker-compose-CN.yml up -d` to start up RAGFlow server:
```bash
docker pull swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:latest
```
```bash
docker compose -f docker-compose-CN.yml up -d
```

View File

@ -1,43 +0,0 @@
# Xinference
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/2c5e86a7-807b-4d29-bd2b-f73fb1018866" width="130"/>
</div>
Xorbits Inference([Xinference](https://github.com/xorbitsai/inference)) empowers you to unleash the full potential of cutting-edge AI models.
## Install
- [pip install "xinference[all]"](https://inference.readthedocs.io/en/latest/getting_started/installation.html)
- [Docker](https://inference.readthedocs.io/en/latest/getting_started/using_docker_image.html)
To start a local instance of Xinference, run the following command:
```bash
$ xinference-local --host 0.0.0.0 --port 9997
```
## Launch Xinference
Decide which LLM you want to deploy ([here's a list for supported LLM](https://inference.readthedocs.io/en/latest/models/builtin/)), say, **mistral**.
Execute the following command to launch the model, remember to replace ${quantization} with your chosen quantization method from the options listed above:
```bash
$ xinference launch -u mistral --model-name mistral-v0.1 --size-in-billions 7 --model-format pytorch --quantization ${quantization}
```
## Use Xinference in RAGFlow
- Go to 'Settings > Model Providers > Models to be added > Xinference'.
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/bcbf4d7a-ade6-44c7-ad5f-0a92c8a73789" width="1300"/>
</div>
> Base URL: Enter the base URL where the Xinference service is accessible, like, `http://<your-xinference-endpoint-domain>:9997/v1`.
- Use Xinference Models.
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/b01fcb6f-47c9-4777-82e0-f1e947ed615a" width="530"/>
</div>
<div align="center" style="margin-top:20px;margin-bottom:20px;">
<img src="https://github.com/infiniflow/ragflow/assets/12318111/1763dcd1-044f-438d-badd-9729f5b3a144" width="530"/>
</div>

View File

@ -19,7 +19,7 @@ from rag.nlp import bullets_category, is_english, tokenize, remove_contents_tabl
hierarchical_merge, make_colon_as_title, naive_merge, random_choices, tokenize_table, add_positions, \
tokenize_chunks, find_codec
from rag.nlp import rag_tokenizer
from deepdoc.parser import PdfParser, DocxParser, PlainParser
from deepdoc.parser import PdfParser, DocxParser, PlainParser, HtmlParser
class Pdf(PdfParser):
@ -105,6 +105,14 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
random_choices([t for t, _ in sections], k=200)))
callback(0.8, "Finish parsing.")
elif re.search(r"\.(htm|html)$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
sections = HtmlParser()(filename, binary)
sections = [(l, "") for l in sections if l]
remove_contents_table(sections, eng=is_english(
random_choices([t for t, _ in sections], k=200)))
callback(0.8, "Finish parsing.")
elif re.search(r"\.doc$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
binary = BytesIO(binary)
@ -127,7 +135,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
for ck in hierarchical_merge(bull, sections, 5)]
else:
sections = [s.split("@") for s, _ in sections]
sections = [(pr[0], "@" + pr[1]) for pr in sections if len(pr) == 2]
sections = [(pr[0], "@" + pr[1]) if len(pr) == 2 else (pr[0], '') for pr in sections ]
chunks = naive_merge(
sections, kwargs.get(
"chunk_token_num", 256), kwargs.get(

View File

@ -20,7 +20,7 @@ from api.db import ParserType
from rag.nlp import bullets_category, is_english, tokenize, remove_contents_table, hierarchical_merge, \
make_colon_as_title, add_positions, tokenize_chunks, find_codec
from rag.nlp import rag_tokenizer
from deepdoc.parser import PdfParser, DocxParser, PlainParser
from deepdoc.parser import PdfParser, DocxParser, PlainParser, HtmlParser
from rag.settings import cron_logger
@ -125,6 +125,12 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
sections = [l for l in sections if l]
callback(0.8, "Finish parsing.")
elif re.search(r"\.(htm|html)$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
sections = HtmlParser()(filename, binary)
sections = [l for l in sections if l]
callback(0.8, "Finish parsing.")
elif re.search(r"\.doc$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
binary = BytesIO(binary)

View File

@ -17,8 +17,10 @@ from timeit import default_timer as timer
import re
from deepdoc.parser.pdf_parser import PlainParser
from rag.nlp import rag_tokenizer, naive_merge, tokenize_table, tokenize_chunks, find_codec
from deepdoc.parser import PdfParser, ExcelParser, DocxParser
from deepdoc.parser import PdfParser, ExcelParser, DocxParser, HtmlParser
from rag.settings import cron_logger
from rag.utils import num_tokens_from_string
class Docx(DocxParser):
def __init__(self):
@ -134,9 +136,9 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
elif re.search(r"\.xlsx?$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
excel_parser = ExcelParser()
sections = [(excel_parser.html(binary), "")]
sections = [(l, "") for l in excel_parser.html(binary) if l]
elif re.search(r"\.(txt|md)$", filename, re.IGNORECASE):
elif re.search(r"\.(txt|md|py|js|java|c|cpp|h|php|go|ts|sh|cs|kt)$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
txt = ""
if binary:
@ -149,7 +151,19 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
if not l:
break
txt += l
sections = txt.split("\n")
sections = []
for sec in txt.split("\n"):
if num_tokens_from_string(sec) > 10 * parser_config.get("chunk_token_num", 128):
sections.append((sec[:int(len(sec)/2)], ""))
sections.append((sec[int(len(sec)/2):], ""))
else:
sections.append((sec, ""))
callback(0.8, "Finish parsing.")
elif re.search(r"\.(htm|html)$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
sections = HtmlParser()(filename, binary)
sections = [(l, "") for l in sections if l]
callback(0.8, "Finish parsing.")
@ -163,7 +177,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
else:
raise NotImplementedError(
"file type not supported yet(doc, docx, pdf, txt supported)")
"file type not supported yet(pdf, xlsx, doc, docx, txt supported)")
st = timer()
chunks = naive_merge(

View File

@ -15,7 +15,7 @@ from io import BytesIO
import re
from rag.app import laws
from rag.nlp import rag_tokenizer, tokenize, find_codec
from deepdoc.parser import PdfParser, ExcelParser, PlainParser
from deepdoc.parser import PdfParser, ExcelParser, PlainParser, HtmlParser
class Pdf(PdfParser):
@ -78,7 +78,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
elif re.search(r"\.xlsx?$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
excel_parser = ExcelParser()
sections = [excel_parser.html(binary)]
sections = excel_parser.html(binary, 1000000000)
elif re.search(r"\.txt$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
@ -97,6 +97,12 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
sections = [s for s in sections if s]
callback(0.8, "Finish parsing.")
elif re.search(r"\.(htm|html)$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
sections = HtmlParser()(filename, binary)
sections = [s for s in sections if s]
callback(0.8, "Finish parsing.")
elif re.search(r"\.doc$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
binary = BytesIO(binary)

View File

@ -16,16 +16,20 @@
from .embedding_model import *
from .chat_model import *
from .cv_model import *
from .rerank_model import *
EmbeddingModel = {
"Ollama": OllamaEmbed,
"OpenAI": OpenAIEmbed,
"Xinference": XinferenceEmbed,
"Tongyi-Qianwen": DefaultEmbedding, #QWenEmbed,
"Tongyi-Qianwen": DefaultEmbedding,#QWenEmbed,
"ZHIPU-AI": ZhipuEmbed,
"FastEmbed": FastEmbed,
"Youdao": YoudaoEmbed
"Youdao": YoudaoEmbed,
"BaiChuan": BaiChuanEmbed,
"Jina": JinaEmbed,
"BAAI": DefaultEmbedding
}
@ -46,6 +50,13 @@ ChatModel = {
"Ollama": OllamaChat,
"Xinference": XinferenceChat,
"Moonshot": MoonshotChat,
"DeepSeek": DeepSeekChat
"DeepSeek": DeepSeekChat,
"BaiChuan": BaiChuanChat
}
RerankModel = {
"BAAI": DefaultRerank,
"Jina": JinaRerank,
"Youdao": YoudaoRerank,
}

View File

@ -19,6 +19,7 @@ from abc import ABC
from openai import OpenAI
import openai
from ollama import Client
from volcengine.maas.v2 import MaasService
from rag.nlp import is_english
from rag.utils import num_tokens_from_string
@ -44,6 +45,31 @@ class Base(ABC):
except openai.APIError as e:
return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
ans = ""
total_tokens = 0
try:
response = self.client.chat.completions.create(
model=self.model_name,
messages=history,
stream=True,
**gen_conf)
for resp in response:
if not resp.choices or not resp.choices[0].delta.content:continue
ans += resp.choices[0].delta.content
total_tokens += 1
if resp.choices[0].finish_reason == "length":
ans += "...\nFor the content length reason, it stopped, continue?" if is_english(
[ans]) else "······\n由于长度的原因,回答被截断了,要继续吗?"
yield ans
except openai.APIError as e:
yield ans + "\n**ERROR**: " + str(e)
yield total_tokens
class GptTurbo(Base):
def __init__(self, key, model_name="gpt-3.5-turbo", base_url="https://api.openai.com/v1"):
@ -69,6 +95,84 @@ class DeepSeekChat(Base):
super().__init__(key, model_name, base_url)
class BaiChuanChat(Base):
def __init__(self, key, model_name="Baichuan3-Turbo", base_url="https://api.baichuan-ai.com/v1"):
if not base_url:
base_url = "https://api.baichuan-ai.com/v1"
super().__init__(key, model_name, base_url)
@staticmethod
def _format_params(params):
return {
"temperature": params.get("temperature", 0.3),
"max_tokens": params.get("max_tokens", 2048),
"top_p": params.get("top_p", 0.85),
}
def chat(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
try:
response = self.client.chat.completions.create(
model=self.model_name,
messages=history,
extra_body={
"tools": [{
"type": "web_search",
"web_search": {
"enable": True,
"search_mode": "performance_first"
}
}]
},
**self._format_params(gen_conf))
ans = response.choices[0].message.content.strip()
if response.choices[0].finish_reason == "length":
ans += "...\nFor the content length reason, it stopped, continue?" if is_english(
[ans]) else "······\n由于长度的原因,回答被截断了,要继续吗?"
return ans, response.usage.total_tokens
except openai.APIError as e:
return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
ans = ""
total_tokens = 0
try:
response = self.client.chat.completions.create(
model=self.model_name,
messages=history,
extra_body={
"tools": [{
"type": "web_search",
"web_search": {
"enable": True,
"search_mode": "performance_first"
}
}]
},
stream=True,
**self._format_params(gen_conf))
for resp in response:
if resp.choices[0].finish_reason == "stop":
if not resp.choices[0].delta.content:
continue
total_tokens = resp.usage.get('total_tokens', 0)
if not resp.choices[0].delta.content:
continue
ans += resp.choices[0].delta.content
if resp.choices[0].finish_reason == "length":
ans += "...\nFor the content length reason, it stopped, continue?" if is_english(
[ans]) else "······\n由于长度的原因,回答被截断了,要继续吗?"
yield ans
except Exception as e:
yield ans + "\n**ERROR**: " + str(e)
yield total_tokens
class QWenChat(Base):
def __init__(self, key, model_name=Generation.Models.qwen_turbo, **kwargs):
import dashscope
@ -97,6 +201,35 @@ class QWenChat(Base):
return "**ERROR**: " + response.message, tk_count
def chat_streamly(self, system, history, gen_conf):
from http import HTTPStatus
if system:
history.insert(0, {"role": "system", "content": system})
ans = ""
tk_count = 0
try:
response = Generation.call(
self.model_name,
messages=history,
result_format='message',
stream=True,
**gen_conf
)
for resp in response:
if resp.status_code == HTTPStatus.OK:
ans = resp.output.choices[0]['message']['content']
tk_count = resp.usage.total_tokens
if resp.output.choices[0].get("finish_reason", "") == "length":
ans += "...\nFor the content length reason, it stopped, continue?" if is_english(
[ans]) else "······\n由于长度的原因,回答被截断了,要继续吗?"
yield ans
else:
yield ans + "\n**ERROR**: " + resp.message if str(resp.message).find("Access")<0 else "Out of credit. Please set the API key in **settings > Model providers.**"
except Exception as e:
yield ans + "\n**ERROR**: " + str(e)
yield tk_count
class ZhipuChat(Base):
def __init__(self, key, model_name="glm-3-turbo", **kwargs):
@ -122,6 +255,35 @@ class ZhipuChat(Base):
except Exception as e:
return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
if "presence_penalty" in gen_conf: del gen_conf["presence_penalty"]
if "frequency_penalty" in gen_conf: del gen_conf["frequency_penalty"]
ans = ""
tk_count = 0
try:
response = self.client.chat.completions.create(
model=self.model_name,
messages=history,
stream=True,
**gen_conf
)
for resp in response:
if not resp.choices[0].delta.content:continue
delta = resp.choices[0].delta.content
ans += delta
if resp.choices[0].finish_reason == "length":
ans += "...\nFor the content length reason, it stopped, continue?" if is_english(
[ans]) else "······\n由于长度的原因,回答被截断了,要继续吗?"
tk_count = resp.usage.total_tokens
if resp.choices[0].finish_reason == "stop": tk_count = resp.usage.total_tokens
yield ans
except Exception as e:
yield ans + "\n**ERROR**: " + str(e)
yield tk_count
class OllamaChat(Base):
def __init__(self, key, model_name, **kwargs):
@ -141,10 +303,164 @@ class OllamaChat(Base):
response = self.client.chat(
model=self.model_name,
messages=history,
options=options
options=options,
keep_alive=-1
)
ans = response["message"]["content"].strip()
return ans, response["eval_count"] + response.get("prompt_eval_count", 0)
except Exception as e:
return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
options = {}
if "temperature" in gen_conf: options["temperature"] = gen_conf["temperature"]
if "max_tokens" in gen_conf: options["num_predict"] = gen_conf["max_tokens"]
if "top_p" in gen_conf: options["top_k"] = gen_conf["top_p"]
if "presence_penalty" in gen_conf: options["presence_penalty"] = gen_conf["presence_penalty"]
if "frequency_penalty" in gen_conf: options["frequency_penalty"] = gen_conf["frequency_penalty"]
ans = ""
try:
response = self.client.chat(
model=self.model_name,
messages=history,
stream=True,
options=options,
keep_alive=-1
)
for resp in response:
if resp["done"]:
yield resp.get("prompt_eval_count", 0) + resp.get("eval_count", 0)
ans += resp["message"]["content"]
yield ans
except Exception as e:
yield ans + "\n**ERROR**: " + str(e)
yield 0
class LocalLLM(Base):
class RPCProxy:
def __init__(self, host, port):
self.host = host
self.port = int(port)
self.__conn()
def __conn(self):
from multiprocessing.connection import Client
self._connection = Client(
(self.host, self.port), authkey=b'infiniflow-token4kevinhu')
def __getattr__(self, name):
import pickle
def do_rpc(*args, **kwargs):
for _ in range(3):
try:
self._connection.send(
pickle.dumps((name, args, kwargs)))
return pickle.loads(self._connection.recv())
except Exception as e:
self.__conn()
raise Exception("RPC connection lost!")
return do_rpc
def __init__(self, key, model_name="glm-3-turbo"):
self.client = LocalLLM.RPCProxy("127.0.0.1", 7860)
def chat(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
try:
ans = self.client.chat(
history,
gen_conf
)
return ans, num_tokens_from_string(ans)
except Exception as e:
return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
token_count = 0
answer = ""
try:
for ans in self.client.chat_streamly(history, gen_conf):
answer += ans
token_count += 1
yield answer
except Exception as e:
yield answer + "\n**ERROR**: " + str(e)
yield token_count
class VolcEngineChat(Base):
def __init__(self, key, model_name, base_url):
"""
Since do not want to modify the original database fields, and the VolcEngine authentication method is quite special,
Assemble ak, sk, ep_id into api_key, store it as a dictionary type, and parse it for use
model_name is for display only
"""
self.client = MaasService('maas-api.ml-platform-cn-beijing.volces.com', 'cn-beijing')
self.volc_ak = eval(key).get('volc_ak', '')
self.volc_sk = eval(key).get('volc_sk', '')
self.client.set_ak(self.volc_ak)
self.client.set_sk(self.volc_sk)
self.model_name = eval(key).get('ep_id', '')
def chat(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
try:
req = {
"parameters": {
"min_new_tokens": gen_conf.get("min_new_tokens", 1),
"top_k": gen_conf.get("top_k", 0),
"max_prompt_tokens": gen_conf.get("max_prompt_tokens", 30000),
"temperature": gen_conf.get("temperature", 0.1),
"max_new_tokens": gen_conf.get("max_tokens", 1000),
"top_p": gen_conf.get("top_p", 0.3),
},
"messages": history
}
response = self.client.chat(self.model_name, req)
ans = response.choices[0].message.content.strip()
if response.choices[0].finish_reason == "length":
ans += "...\nFor the content length reason, it stopped, continue?" if is_english(
[ans]) else "······\n由于长度的原因,回答被截断了,要继续吗?"
return ans, response.usage.total_tokens
except Exception as e:
return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf):
if system:
history.insert(0, {"role": "system", "content": system})
ans = ""
tk_count = 0
try:
req = {
"parameters": {
"min_new_tokens": gen_conf.get("min_new_tokens", 1),
"top_k": gen_conf.get("top_k", 0),
"max_prompt_tokens": gen_conf.get("max_prompt_tokens", 30000),
"temperature": gen_conf.get("temperature", 0.1),
"max_new_tokens": gen_conf.get("max_tokens", 1000),
"top_p": gen_conf.get("top_p", 0.3),
},
"messages": history
}
stream = self.client.stream_chat(self.model_name, req)
for resp in stream:
if not resp.choices[0].message.content:
continue
ans += resp.choices[0].message.content
if resp.choices[0].finish_reason == "stop":
tk_count = resp.usage.total_tokens
yield ans
except Exception as e:
yield ans + "\n**ERROR**: " + str(e)
yield tk_count

View File

@ -13,8 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
from typing import Optional
import requests
from huggingface_hub import snapshot_download
from zhipuai import ZhipuAI
import os
@ -26,21 +28,8 @@ from FlagEmbedding import FlagModel
import torch
import numpy as np
from api.utils.file_utils import get_project_base_directory, get_home_cache_dir
from rag.utils import num_tokens_from_string
try:
flag_model = FlagModel(os.path.join(get_home_cache_dir(), "bge-large-zh-v1.5"),
query_instruction_for_retrieval="为这个句子生成表示以用于检索相关文章:",
use_fp16=torch.cuda.is_available())
except Exception as e:
model_dir = snapshot_download(repo_id="BAAI/bge-large-zh-v1.5",
local_dir=os.path.join(get_home_cache_dir(), "bge-large-zh-v1.5"),
local_dir_use_symlinks=False)
flag_model = FlagModel(model_dir,
query_instruction_for_retrieval="为这个句子生成表示以用于检索相关文章:",
use_fp16=torch.cuda.is_available())
from api.utils.file_utils import get_home_cache_dir
from rag.utils import num_tokens_from_string, truncate
class Base(ABC):
@ -55,7 +44,9 @@ class Base(ABC):
class DefaultEmbedding(Base):
def __init__(self, *args, **kwargs):
_model = None
def __init__(self, key, model_name, **kwargs):
"""
If you have trouble downloading HuggingFace models, -_^ this might help!!
@ -67,21 +58,32 @@ class DefaultEmbedding(Base):
^_-
"""
self.model = flag_model
if not DefaultEmbedding._model:
try:
self._model = FlagModel(os.path.join(get_home_cache_dir(), re.sub(r"^[a-zA-Z]+/", "", model_name)),
query_instruction_for_retrieval="为这个句子生成表示以用于检索相关文章:",
use_fp16=torch.cuda.is_available())
except Exception as e:
model_dir = snapshot_download(repo_id="BAAI/bge-large-zh-v1.5",
local_dir=os.path.join(get_home_cache_dir(), re.sub(r"^[a-zA-Z]+/", "", model_name)),
local_dir_use_symlinks=False)
self._model = FlagModel(model_dir,
query_instruction_for_retrieval="为这个句子生成表示以用于检索相关文章:",
use_fp16=torch.cuda.is_available())
def encode(self, texts: list, batch_size=32):
texts = [t[:2000] for t in texts]
texts = [truncate(t, 2048) for t in texts]
token_count = 0
for t in texts:
token_count += num_tokens_from_string(t)
res = []
for i in range(0, len(texts), batch_size):
res.extend(self.model.encode(texts[i:i + batch_size]).tolist())
res.extend(self._model.encode(texts[i:i + batch_size]).tolist())
return np.array(res), token_count
def encode_queries(self, text: str):
token_count = num_tokens_from_string(text)
return self.model.encode_queries([text]).tolist()[0], token_count
return self._model.encode_queries([text]).tolist()[0], token_count
class OpenAIEmbed(Base):
@ -93,16 +95,27 @@ class OpenAIEmbed(Base):
self.model_name = model_name
def encode(self, texts: list, batch_size=32):
texts = [truncate(t, 8196) for t in texts]
res = self.client.embeddings.create(input=texts,
model=self.model_name)
return np.array([d.embedding for d in res.data]), res.usage.total_tokens
return np.array([d.embedding for d in res.data]
), res.usage.total_tokens
def encode_queries(self, text):
res = self.client.embeddings.create(input=[text],
res = self.client.embeddings.create(input=[truncate(text, 8196)],
model=self.model_name)
return np.array(res.data[0].embedding), res.usage.total_tokens
class BaiChuanEmbed(OpenAIEmbed):
def __init__(self, key,
model_name='Baichuan-Text-Embedding',
base_url='https://api.baichuan-ai.com/v1'):
if not base_url:
base_url = "https://api.baichuan-ai.com/v1"
super().__init__(key, model_name, base_url)
class QWenEmbed(Base):
def __init__(self, key, model_name="text_embedding_v2", **kwargs):
dashscope.api_key = key
@ -112,7 +125,7 @@ class QWenEmbed(Base):
import dashscope
res = []
token_count = 0
texts = [txt[:2048] for txt in texts]
texts = [truncate(t, 2048) for t in texts]
for i in range(0, len(texts), batch_size):
resp = dashscope.TextEmbedding.call(
model=self.model_name,
@ -179,16 +192,19 @@ class OllamaEmbed(Base):
class FastEmbed(Base):
_model = None
def __init__(
self,
key: Optional[str] = None,
model_name: str = "BAAI/bge-small-en-v1.5",
cache_dir: Optional[str] = None,
threads: Optional[int] = None,
**kwargs,
self,
key: Optional[str] = None,
model_name: str = "BAAI/bge-small-en-v1.5",
cache_dir: Optional[str] = None,
threads: Optional[int] = None,
**kwargs,
):
from fastembed import TextEmbedding
self._model = TextEmbedding(model_name, cache_dir, threads, **kwargs)
if not FastEmbed._model:
self._model = TextEmbedding(model_name, cache_dir, threads, **kwargs)
def encode(self, texts: list, batch_size=32):
# Using the internal tokenizer to encode the texts and get the total
@ -255,3 +271,29 @@ class YoudaoEmbed(Base):
def encode_queries(self, text):
embds = YoudaoEmbed._client.encode([text])
return np.array(embds[0]), num_tokens_from_string(text)
class JinaEmbed(Base):
def __init__(self, key, model_name="jina-embeddings-v2-base-zh",
base_url="https://api.jina.ai/v1/embeddings"):
self.base_url = "https://api.jina.ai/v1/embeddings"
self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {key}"
}
self.model_name = model_name
def encode(self, texts: list, batch_size=None):
texts = [truncate(t, 8196) for t in texts]
data = {
"model": self.model_name,
"input": texts,
'encoding_type': 'float'
}
res = requests.post(self.base_url, headers=self.headers, json=data).json()
return np.array([d["embedding"] for d in res["data"]]), res["usage"]["total_tokens"]
def encode_queries(self, text):
embds, cnt = self.encode([text])
return np.array(embds[0]), cnt

116
rag/llm/rerank_model.py Normal file
View File

@ -0,0 +1,116 @@
#
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
import requests
import torch
from FlagEmbedding import FlagReranker
from huggingface_hub import snapshot_download
import os
from abc import ABC
import numpy as np
from api.utils.file_utils import get_home_cache_dir
from rag.utils import num_tokens_from_string, truncate
def sigmoid(x):
return 1 / (1 + np.exp(-x))
class Base(ABC):
def __init__(self, key, model_name):
pass
def similarity(self, query: str, texts: list):
raise NotImplementedError("Please implement encode method!")
class DefaultRerank(Base):
_model = None
def __init__(self, key, model_name, **kwargs):
"""
If you have trouble downloading HuggingFace models, -_^ this might help!!
For Linux:
export HF_ENDPOINT=https://hf-mirror.com
For Windows:
Good luck
^_-
"""
if not DefaultRerank._model:
try:
self._model = FlagReranker(os.path.join(get_home_cache_dir(), re.sub(r"^[a-zA-Z]+/", "", model_name)),
use_fp16=torch.cuda.is_available())
except Exception as e:
self._model = snapshot_download(repo_id=model_name,
local_dir=os.path.join(get_home_cache_dir(),
re.sub(r"^[a-zA-Z]+/", "", model_name)),
local_dir_use_symlinks=False)
self._model = FlagReranker(os.path.join(get_home_cache_dir(), model_name),
use_fp16=torch.cuda.is_available())
def similarity(self, query: str, texts: list):
pairs = [(query,truncate(t, 2048)) for t in texts]
token_count = 0
for _, t in pairs:
token_count += num_tokens_from_string(t)
batch_size = 32
res = []
for i in range(0, len(pairs), batch_size):
scores = self._model.compute_score(pairs[i:i + batch_size], max_length=2048)
scores = sigmoid(np.array(scores))
res.extend(scores)
return np.array(res), token_count
class JinaRerank(Base):
def __init__(self, key, model_name="jina-reranker-v1-base-en",
base_url="https://api.jina.ai/v1/rerank"):
self.base_url = "https://api.jina.ai/v1/rerank"
self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {key}"
}
self.model_name = model_name
def similarity(self, query: str, texts: list):
texts = [truncate(t, 8196) for t in texts]
data = {
"model": self.model_name,
"query": query,
"documents": texts,
"top_n": len(texts)
}
res = requests.post(self.base_url, headers=self.headers, json=data).json()
return np.array([d["relevance_score"] for d in res["results"]]), res["usage"]["total_tokens"]
class YoudaoRerank(DefaultRerank):
_model = None
def __init__(self, key=None, model_name="maidalun1020/bce-reranker-base_v1", **kwargs):
from BCEmbedding import RerankerModel
if not YoudaoRerank._model:
try:
print("LOADING BCE...")
YoudaoRerank._model = RerankerModel(model_name_or_path=os.path.join(
get_home_cache_dir(),
re.sub(r"^[a-zA-Z]+/", "", model_name)))
except Exception as e:
YoudaoRerank._model = RerankerModel(
model_name_or_path=model_name.replace(
"maidalun1020", "InfiniFlow"))

View File

@ -2,9 +2,10 @@ import argparse
import pickle
import random
import time
from copy import deepcopy
from multiprocessing.connection import Listener
from threading import Thread
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer
def torch_gc():
@ -95,6 +96,32 @@ def chat(messages, gen_conf):
return str(e)
def chat_streamly(messages, gen_conf):
global tokenizer
model = Model()
try:
torch_gc()
conf = deepcopy(gen_conf)
print(messages, conf)
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
streamer = TextStreamer(tokenizer)
conf["inputs"] = model_inputs.input_ids
conf["streamer"] = streamer
conf["max_new_tokens"] = conf["max_tokens"]
del conf["max_tokens"]
thread = Thread(target=model.generate, kwargs=conf)
thread.start()
for _, new_text in enumerate(streamer):
yield new_text
except Exception as e:
yield "**ERROR**: " + str(e)
def Model():
global models
random.seed(time.time())
@ -113,6 +140,7 @@ if __name__ == "__main__":
handler = RPCHandler()
handler.register_function(chat)
handler.register_function(chat_streamly)
models = []
for _ in range(1):

View File

@ -36,7 +36,7 @@ class EsQueryer:
patts = [
(r"是*(什么样的|哪家|一下|那家|啥样|咋样了|什么时候|何时|何地|何人|是否|是不是|多少|哪里|怎么|哪儿|怎么样|如何|哪些|是啥|啥是|啊|吗|呢|吧|咋|什么|有没有|呀)是*", ""),
(r"(^| )(what|who|how|which|where|why)('re|'s)? ", " "),
(r"(^| )('s|'re|is|are|were|was|do|does|did|don't|doesn't|didn't|has|have|be|there|you|me|your|my|mine|just|please|may|i|should|would|wouldn't|will|won't|done|go|for|with|so|the|a|an|by|i'm|it's|he's|she's|they|they're|you're|as|by|on|in|at|up|out|down)", " ")
(r"(^| )('s|'re|is|are|were|was|do|does|did|don't|doesn't|didn't|has|have|be|there|you|me|your|my|mine|just|please|may|i|should|would|wouldn't|will|won't|done|go|for|with|so|the|a|an|by|i'm|it's|he's|she's|they|they're|you're|as|by|on|in|at|up|out|down) ", " ")
]
for r, p in patts:
txt = re.sub(r, p, txt, flags=re.IGNORECASE)
@ -44,7 +44,7 @@ class EsQueryer:
def question(self, txt, tbl="qa", min_match="60%"):
txt = re.sub(
r"[ \r\n\t,,。??/`!&]+",
r"[ :\r\n\t,,。??/`!&\^%%]+",
" ",
rag_tokenizer.tradi2simp(
rag_tokenizer.strQ2B(
@ -53,9 +53,11 @@ class EsQueryer:
if not self.isChinese(txt):
tks = rag_tokenizer.tokenize(txt).split(" ")
q = copy.deepcopy(tks)
for i in range(1, len(tks)):
q.append("\"%s %s\"^2" % (tks[i - 1], tks[i]))
tks_w = self.tw.weights(tks)
tks_w = [(re.sub(r"[ \\\"']+", "", tk), w) for tk, w in tks_w]
q = ["{}^{:.4f}".format(tk, w) for tk, w in tks_w if tk]
for i in range(1, len(tks_w)):
q.append("\"%s %s\"^%.4f" % (tks_w[i - 1][0], tks_w[i][0], max(tks_w[i - 1][1], tks_w[i][1])*2))
if not q:
q.append(txt)
return Q("bool",
@ -135,7 +137,11 @@ class EsQueryer:
from sklearn.metrics.pairwise import cosine_similarity as CosineSimilarity
import numpy as np
sims = CosineSimilarity([avec], bvecs)
tksim = self.token_similarity(atks, btkss)
return np.array(sims[0]) * vtweight + \
np.array(tksim) * tkweight, tksim, sims[0]
def token_similarity(self, atks, btkss):
def toDict(tks):
d = {}
if isinstance(tks, str):
@ -148,9 +154,7 @@ class EsQueryer:
atks = toDict(atks)
btkss = [toDict(tks) for tks in btkss]
tksim = [self.similarity(atks, btks) for btks in btkss]
return np.array(sims[0]) * vtweight + \
np.array(tksim) * tkweight, tksim, sims[0]
return [self.similarity(atks, btks) for btks in btkss]
def similarity(self, qtwt, dtwt):
if isinstance(dtwt, type("")):

View File

@ -24,7 +24,7 @@ class RagTokenizer:
def loadDict_(self, fnm):
print("[HUQIE]:Build trie", fnm, file=sys.stderr)
try:
of = open(fnm, "r")
of = open(fnm, "r", encoding='utf-8')
while True:
line = of.readline()
if not line:
@ -241,11 +241,14 @@ class RagTokenizer:
return self.score_(res[::-1])
def english_normalize_(self, tks):
return [self.stemmer.stem(self.lemmatizer.lemmatize(t)) if re.match(r"[a-zA-Z_-]+$", t) else t for t in tks]
def tokenize(self, line):
line = self._strQ2B(line).lower()
line = self._tradi2simp(line)
zh_num = len([1 for c in line if is_chinese(c)])
if zh_num < len(line) * 0.2:
if zh_num == 0:
return " ".join([self.stemmer.stem(self.lemmatizer.lemmatize(t)) for t in word_tokenize(line)])
arr = re.split(self.SPLIT_CHAR, line)
@ -293,7 +296,7 @@ class RagTokenizer:
i = e + 1
res = " ".join(res)
res = " ".join(self.english_normalize_(res))
if self.DEBUG:
print("[TKS]", self.merge_(res))
return self.merge_(res)
@ -336,7 +339,7 @@ class RagTokenizer:
res.append(stk)
return " ".join(res)
return " ".join(self.english_normalize_(res))
def is_chinese(s):

View File

@ -52,22 +52,27 @@ class Dealer:
def search(self, req, idxnm, emb_mdl=None):
qst = req.get("question", "")
bqry, keywords = self.qryr.question(qst)
if req.get("kb_ids"):
bqry.filter.append(Q("terms", kb_id=req["kb_ids"]))
if req.get("doc_ids"):
bqry.filter.append(Q("terms", doc_id=req["doc_ids"]))
if "available_int" in req:
if req["available_int"] == 0:
bqry.filter.append(Q("range", available_int={"lt": 1}))
else:
bqry.filter.append(
Q("bool", must_not=Q("range", available_int={"lt": 1})))
def add_filters(bqry):
nonlocal req
if req.get("kb_ids"):
bqry.filter.append(Q("terms", kb_id=req["kb_ids"]))
if req.get("doc_ids"):
bqry.filter.append(Q("terms", doc_id=req["doc_ids"]))
if "available_int" in req:
if req["available_int"] == 0:
bqry.filter.append(Q("range", available_int={"lt": 1}))
else:
bqry.filter.append(
Q("bool", must_not=Q("range", available_int={"lt": 1})))
return bqry
bqry = add_filters(bqry)
bqry.boost = 0.05
s = Search()
pg = int(req.get("page", 1)) - 1
ps = int(req.get("size", 1000))
topk = int(req.get("topk", 1024))
ps = int(req.get("size", topk))
src = req.get("fields", ["docnm_kwd", "content_ltks", "kb_id", "img_id", "title_tks", "important_kwd",
"image_id", "doc_id", "q_512_vec", "q_768_vec", "position_int",
"q_1024_vec", "q_1536_vec", "available_int", "content_with_weight"])
@ -117,8 +122,7 @@ class Dealer:
es_logger.info("TOTAL: {}".format(self.es.getTotal(res)))
if self.es.getTotal(res) == 0 and "knn" in s:
bqry, _ = self.qryr.question(qst, min_match="10%")
if req.get("kb_ids"):
bqry.filter.append(Q("terms", kb_id=req["kb_ids"]))
bqry = add_filters(bqry)
s["query"] = bqry.to_dict()
s["knn"]["filter"] = bqry.to_dict()
s["knn"]["similarity"] = 0.17
@ -307,6 +311,26 @@ class Dealer:
ins_tw, tkweight, vtweight)
return sim, tksim, vtsim
def rerank_by_model(self, rerank_mdl, sres, query, tkweight=0.3,
vtweight=0.7, cfield="content_ltks"):
_, keywords = self.qryr.question(query)
for i in sres.ids:
if isinstance(sres.field[i].get("important_kwd", []), str):
sres.field[i]["important_kwd"] = [sres.field[i]["important_kwd"]]
ins_tw = []
for i in sres.ids:
content_ltks = sres.field[i][cfield].split(" ")
title_tks = [t for t in sres.field[i].get("title_tks", "").split(" ") if t]
important_kwd = sres.field[i].get("important_kwd", [])
tks = content_ltks + title_tks + important_kwd
ins_tw.append(tks)
tksim = self.qryr.token_similarity(keywords, ins_tw)
vtsim,_ = rerank_mdl.similarity(" ".join(keywords), [rmSpace(" ".join(tks)) for tks in ins_tw])
return tkweight*np.array(tksim) + vtweight*vtsim, tksim, vtsim
def hybrid_similarity(self, ans_embd, ins_embd, ans, inst):
return self.qryr.hybrid_similarity(ans_embd,
ins_embd,
@ -314,17 +338,22 @@ class Dealer:
rag_tokenizer.tokenize(inst).split(" "))
def retrieval(self, question, embd_mdl, tenant_id, kb_ids, page, page_size, similarity_threshold=0.2,
vector_similarity_weight=0.3, top=1024, doc_ids=None, aggs=True):
vector_similarity_weight=0.3, top=1024, doc_ids=None, aggs=True, rerank_mdl=None):
ranks = {"total": 0, "chunks": [], "doc_aggs": {}}
if not question:
return ranks
req = {"kb_ids": kb_ids, "doc_ids": doc_ids, "size": page_size,
"question": question, "vector": True, "topk": top,
"similarity": similarity_threshold}
"similarity": similarity_threshold,
"available_int": 1}
sres = self.search(req, index_name(tenant_id), embd_mdl)
sim, tsim, vsim = self.rerank(
sres, question, 1 - vector_similarity_weight, vector_similarity_weight)
if rerank_mdl:
sim, tsim, vsim = self.rerank_by_model(rerank_mdl,
sres, question, 1 - vector_similarity_weight, vector_similarity_weight)
else:
sim, tsim, vsim = self.rerank(
sres, question, 1 - vector_similarity_weight, vector_similarity_weight)
idx = np.argsort(sim * -1)
dim = len(sres.query_vector)
@ -403,3 +432,13 @@ class Dealer:
except Exception as e:
chat_logger.error(f"SQL failure: {sql} =>" + str(e))
return {"error": str(e)}
def chunk_list(self, doc_id, tenant_id, max_count=1024, fields=["docnm_kwd", "content_with_weight", "img_id"]):
s = Search()
s = s.query(Q("match", doc_id=doc_id))[0:max_count]
s = s.to_dict()
es_res = self.es.search(s, idxnm=index_name(tenant_id), timeout="600s", src=fields)
res = []
for index, chunk in enumerate(es_res['hits']['hits']):
res.append({fld: chunk['_source'].get(fld) for fld in fields})
return res

View File

@ -104,7 +104,7 @@ class Dealer:
while i < len(tks):
j = i
if i == 0 and oneTerm(tks[i]) and len(
tks) > 1 and len(tks[i + 1]) > 1: # 多 工位
tks) > 1 and (len(tks[i + 1]) > 1 and not re.match(r"[0-9a-zA-Z]", tks[i + 1])): # 多 工位
res.append(" ".join(tks[0:2]))
i = 2
continue

115
rag/raptor.py Normal file
View File

@ -0,0 +1,115 @@
#
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
import traceback
from concurrent.futures import ThreadPoolExecutor, ALL_COMPLETED, wait
from threading import Lock
from typing import Tuple
import umap
import numpy as np
from sklearn.mixture import GaussianMixture
from rag.utils import num_tokens_from_string, truncate
class RecursiveAbstractiveProcessing4TreeOrganizedRetrieval:
def __init__(self, max_cluster, llm_model, embd_model, prompt, max_token=256, threshold=0.1):
self._max_cluster = max_cluster
self._llm_model = llm_model
self._embd_model = embd_model
self._threshold = threshold
self._prompt = prompt
self._max_token = max_token
def _get_optimal_clusters(self, embeddings: np.ndarray, random_state:int):
max_clusters = min(self._max_cluster, len(embeddings))
n_clusters = np.arange(1, max_clusters)
bics = []
for n in n_clusters:
gm = GaussianMixture(n_components=n, random_state=random_state)
gm.fit(embeddings)
bics.append(gm.bic(embeddings))
optimal_clusters = n_clusters[np.argmin(bics)]
return optimal_clusters
def __call__(self, chunks: Tuple[str, np.ndarray], random_state, callback=None):
layers = [(0, len(chunks))]
start, end = 0, len(chunks)
if len(chunks) <= 1: return
def summarize(ck_idx, lock):
nonlocal chunks
try:
texts = [chunks[i][0] for i in ck_idx]
len_per_chunk = int((self._llm_model.max_length - self._max_token)/len(texts))
cluster_content = "\n".join([truncate(t, max(1, len_per_chunk)) for t in texts])
cnt = self._llm_model.chat("You're a helpful assistant.",
[{"role": "user", "content": self._prompt.format(cluster_content=cluster_content)}],
{"temperature": 0.3, "max_tokens": self._max_token}
)
cnt = re.sub("(······\n由于长度的原因,回答被截断了,要继续吗?|For the content length reason, it stopped, continue?)", "", cnt)
print("SUM:", cnt)
embds, _ = self._embd_model.encode([cnt])
with lock:
chunks.append((cnt, embds[0]))
except Exception as e:
print(e, flush=True)
traceback.print_stack(e)
return e
labels = []
while end - start > 1:
embeddings = [embd for _, embd in chunks[start: end]]
if len(embeddings) == 2:
summarize([start, start+1], Lock())
if callback:
callback(msg="Cluster one layer: {} -> {}".format(end-start, len(chunks)-end))
labels.extend([0,0])
layers.append((end, len(chunks)))
start = end
end = len(chunks)
continue
n_neighbors = int((len(embeddings) - 1) ** 0.8)
reduced_embeddings = umap.UMAP(
n_neighbors=max(2, n_neighbors), n_components=min(12, len(embeddings)-2), metric="cosine"
).fit_transform(embeddings)
n_clusters = self._get_optimal_clusters(reduced_embeddings, random_state)
if n_clusters == 1:
lbls = [0 for _ in range(len(reduced_embeddings))]
else:
gm = GaussianMixture(n_components=n_clusters, random_state=random_state)
gm.fit(reduced_embeddings)
probs = gm.predict_proba(reduced_embeddings)
lbls = [np.where(prob > self._threshold)[0] for prob in probs]
lbls = [lbl[0] if isinstance(lbl, np.ndarray) else lbl for lbl in lbls]
lock = Lock()
with ThreadPoolExecutor(max_workers=12) as executor:
threads = []
for c in range(n_clusters):
ck_idx = [i+start for i in range(len(lbls)) if lbls[i] == c]
threads.append(executor.submit(summarize, ck_idx, lock))
wait(threads, return_when=ALL_COMPLETED)
print([t.result() for t in threads])
assert len(chunks) - end == n_clusters, "{} vs. {}".format(len(chunks) - end, n_clusters)
labels.extend(lbls)
layers.append((end, len(chunks)))
if callback:
callback(msg="Cluster one layer: {} -> {}".format(end-start, len(chunks)-end))
start = end
end = len(chunks)

View File

@ -26,20 +26,22 @@ import traceback
from functools import partial
from api.db.services.file2document_service import File2DocumentService
from api.settings import retrievaler
from rag.raptor import RecursiveAbstractiveProcessing4TreeOrganizedRetrieval as Raptor
from rag.utils.minio_conn import MINIO
from api.db.db_models import close_connection
from rag.settings import database_logger, SVR_QUEUE_NAME
from rag.settings import cron_logger, DOC_MAXIMUM_SIZE
from multiprocessing import Pool
import numpy as np
from elasticsearch_dsl import Q
from elasticsearch_dsl import Q, Search
from multiprocessing.context import TimeoutError
from api.db.services.task_service import TaskService
from rag.utils.es_conn import ELASTICSEARCH
from timeit import default_timer as timer
from rag.utils import rmSpace, findMaxTm
from rag.utils import rmSpace, findMaxTm, num_tokens_from_string
from rag.nlp import search
from rag.nlp import search, rag_tokenizer
from io import BytesIO
import pandas as pd
@ -80,7 +82,7 @@ def set_progress(task_id, from_page=0, to_page=-1,
if to_page > 0:
if msg:
msg = f"Page({from_page+1}~{to_page+1}): " + msg
msg = f"Page({from_page + 1}~{to_page + 1}): " + msg
d = {"progress_msg": msg}
if prog is not None:
d["progress"] = prog
@ -109,10 +111,13 @@ def collect():
if not msg: return pd.DataFrame()
if TaskService.do_cancel(msg["id"]):
cron_logger.info("Task {} has been canceled.".format(msg["id"]))
return pd.DataFrame()
tasks = TaskService.get_tasks(msg["id"])
assert tasks, "{} empty task!".format(msg["id"])
tasks = pd.DataFrame(tasks)
if msg.get("type", "") == "raptor":
tasks["task_type"] = "raptor"
return tasks
@ -123,7 +128,7 @@ def get_minio_binary(bucket, name):
def build(row):
if row["size"] > DOC_MAXIMUM_SIZE:
set_progress(row["id"], prog=-1, msg="File size exceeds( <= %dMb )" %
(int(DOC_MAXIMUM_SIZE / 1024 / 1024)))
(int(DOC_MAXIMUM_SIZE / 1024 / 1024)))
return []
callback = partial(
@ -137,12 +142,12 @@ def build(row):
bucket, name = File2DocumentService.get_minio_address(doc_id=row["doc_id"])
binary = get_minio_binary(bucket, name)
cron_logger.info(
"From minio({}) {}/{}".format(timer()-st, row["location"], row["name"]))
"From minio({}) {}/{}".format(timer() - st, row["location"], row["name"]))
cks = chunker.chunk(row["name"], binary=binary, from_page=row["from_page"],
to_page=row["to_page"], lang=row["language"], callback=callback,
kb_id=row["kb_id"], parser_config=row["parser_config"], tenant_id=row["tenant_id"])
cron_logger.info(
"Chunkking({}) {}/{}".format(timer()-st, row["location"], row["name"]))
"Chunkking({}) {}/{}".format(timer() - st, row["location"], row["name"]))
except TimeoutError as e:
callback(-1, f"Internal server error: Fetch file timeout. Could you try it again.")
cron_logger.error(
@ -172,7 +177,7 @@ def build(row):
d.update(ck)
md5 = hashlib.md5()
md5.update((ck["content_with_weight"] +
str(d["doc_id"])).encode("utf-8"))
str(d["doc_id"])).encode("utf-8"))
d["_id"] = md5.hexdigest()
d["create_time"] = str(datetime.datetime.now()).replace("T", " ")[:19]
d["create_timestamp_flt"] = datetime.datetime.now().timestamp()
@ -244,6 +249,47 @@ def embedding(docs, mdl, parser_config={}, callback=None):
return tk_count
def run_raptor(row, chat_mdl, embd_mdl, callback=None):
vts, _ = embd_mdl.encode(["ok"])
vctr_nm = "q_%d_vec"%len(vts[0])
chunks = []
for d in retrievaler.chunk_list(row["doc_id"], row["tenant_id"], fields=["content_with_weight", vctr_nm]):
chunks.append((d["content_with_weight"], np.array(d[vctr_nm])))
raptor = Raptor(
row["parser_config"]["raptor"].get("max_cluster", 64),
chat_mdl,
embd_mdl,
row["parser_config"]["raptor"]["prompt"],
row["parser_config"]["raptor"]["max_token"],
row["parser_config"]["raptor"]["threshold"]
)
original_length = len(chunks)
raptor(chunks, row["parser_config"]["raptor"]["random_seed"], callback)
doc = {
"doc_id": row["doc_id"],
"kb_id": [str(row["kb_id"])],
"docnm_kwd": row["name"],
"title_tks": rag_tokenizer.tokenize(row["name"])
}
res = []
tk_count = 0
for content, vctr in chunks[original_length:]:
d = copy.deepcopy(doc)
md5 = hashlib.md5()
md5.update((content + str(d["doc_id"])).encode("utf-8"))
d["_id"] = md5.hexdigest()
d["create_time"] = str(datetime.datetime.now()).replace("T", " ")[:19]
d["create_timestamp_flt"] = datetime.datetime.now().timestamp()
d[vctr_nm] = vctr.tolist()
d["content_with_weight"] = content
d["content_ltks"] = rag_tokenizer.tokenize(content)
d["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(d["content_ltks"])
res.append(d)
tk_count += num_tokens_from_string(content)
return res, tk_count
def main():
rows = collect()
if len(rows) == 0:
@ -254,38 +300,53 @@ def main():
try:
embd_mdl = LLMBundle(r["tenant_id"], LLMType.EMBEDDING, llm_name=r["embd_id"], lang=r["language"])
except Exception as e:
traceback.print_stack(e)
callback(prog=-1, msg=str(e))
continue
st = timer()
cks = build(r)
cron_logger.info("Build chunks({}): {}".format(r["name"], timer()-st))
if cks is None:
continue
if not cks:
callback(1., "No chunk! Done!")
continue
# TODO: exception handler
## set_progress(r["did"], -1, "ERROR: ")
callback(
msg="Finished slicing files(%d). Start to embedding the content." %
len(cks))
st = timer()
try:
tk_count = embedding(cks, embd_mdl, r["parser_config"], callback)
except Exception as e:
callback(-1, "Embedding error:{}".format(str(e)))
callback(-1, msg=str(e))
cron_logger.error(str(e))
tk_count = 0
cron_logger.info("Embedding elapsed({}): {}".format(r["name"], timer()-st))
continue
if r.get("task_type", "") == "raptor":
try:
chat_mdl = LLMBundle(r["tenant_id"], LLMType.CHAT, llm_name=r["llm_id"], lang=r["language"])
cks, tk_count = run_raptor(r, chat_mdl, embd_mdl, callback)
except Exception as e:
callback(-1, msg=str(e))
cron_logger.error(str(e))
continue
else:
st = timer()
cks = build(r)
cron_logger.info("Build chunks({}): {}".format(r["name"], timer() - st))
if cks is None:
continue
if not cks:
callback(1., "No chunk! Done!")
continue
# TODO: exception handler
## set_progress(r["did"], -1, "ERROR: ")
callback(
msg="Finished slicing files(%d). Start to embedding the content." %
len(cks))
st = timer()
try:
tk_count = embedding(cks, embd_mdl, r["parser_config"], callback)
except Exception as e:
callback(-1, "Embedding error:{}".format(str(e)))
cron_logger.error(str(e))
tk_count = 0
cron_logger.info("Embedding elapsed({}): {:.2f}".format(r["name"], timer() - st))
callback(msg="Finished embedding({:.2f})! Start to build index!".format(timer() - st))
callback(msg="Finished embedding({})! Start to build index!".format(timer()-st))
init_kb(r)
chunk_count = len(set([c["_id"] for c in cks]))
st = timer()
es_r = ELASTICSEARCH.bulk(cks, search.index_name(r["tenant_id"]))
cron_logger.info("Indexing elapsed({}): {}".format(r["name"], timer()-st))
es_r = ""
es_bulk_size = 16
for b in range(0, len(cks), es_bulk_size):
es_r = ELASTICSEARCH.bulk(cks[b:b + es_bulk_size], search.index_name(r["tenant_id"]))
if b % 128 == 0:
callback(prog=0.8 + 0.1 * (b + 1) / len(cks), msg="")
cron_logger.info("Indexing elapsed({}): {:.2f}".format(r["name"], timer() - st))
if es_r:
callback(-1, "Index failure!")
ELASTICSEARCH.deleteByQuery(
@ -300,9 +361,8 @@ def main():
DocumentService.increment_chunk_num(
r["doc_id"], r["kb_id"], tk_count, chunk_count, 0)
cron_logger.info(
"Chunk doc({}), token({}), chunks({}), elapsed:{}".format(
r["id"], tk_count, len(cks), timer()-st))
"Chunk doc({}), token({}), chunks({}), elapsed:{:.2f}".format(
r["id"], tk_count, len(cks), timer() - st))
if __name__ == "__main__":

View File

@ -63,3 +63,7 @@ def num_tokens_from_string(string: str) -> int:
num_tokens = len(encoder.encode(string))
return num_tokens
def truncate(string: str, max_len: int) -> int:
"""Returns truncated text if the length of text exceed max_len."""
return encoder.decode(encoder.encode(string)[:max_len])

View File

@ -43,6 +43,9 @@ class ESConnection:
v = v["number"].split(".")[0]
return int(v) >= 7
def health(self):
return dict(self.es.cluster.health())
def upsert(self, df, idxnm=""):
res = []
for d in df:

View File

@ -34,6 +34,16 @@ class RAGFlowMinio(object):
del self.conn
self.conn = None
def health(self):
bucket, fnm, binary = "txtxtxtxt1", "txtxtxtxt1", b"_t@@@1"
if not self.conn.bucket_exists(bucket):
self.conn.make_bucket(bucket)
r = self.conn.put_object(bucket, fnm,
BytesIO(binary),
len(binary)
)
return r
def put(self, bucket, fnm, binary):
for _ in range(3):
try:

View File

@ -44,6 +44,10 @@ class RedisDB:
logging.warning("Redis can't be connected.")
return self.REDIS
def health(self, queue_name):
self.REDIS.ping()
return self.REDIS.xinfo_groups(queue_name)[0]
def is_alive(self):
return self.REDIS is not None
@ -93,15 +97,17 @@ class RedisDB:
return False
def queue_product(self, queue, message, exp=settings.SVR_QUEUE_RETENTION) -> bool:
try:
payload = {"message": json.dumps(message)}
pipeline = self.REDIS.pipeline()
pipeline.xadd(queue, payload)
pipeline.expire(queue, exp)
pipeline.execute()
return True
except Exception as e:
logging.warning("[EXCEPTION]producer" + str(queue) + "||" + str(e))
for _ in range(3):
try:
payload = {"message": json.dumps(message)}
pipeline = self.REDIS.pipeline()
pipeline.xadd(queue, payload)
pipeline.expire(queue, exp)
pipeline.execute()
return True
except Exception as e:
print(e)
logging.warning("[EXCEPTION]producer" + str(queue) + "||" + str(e))
return False
def queue_consumer(self, queue_name, group_name, consumer_name, msg_id=b">") -> Payload:
@ -111,7 +117,7 @@ class RedisDB:
self.REDIS.xgroup_create(
queue_name,
group_name,
id="$",
id="0",
mkstream=True
)
args = {

View File

@ -134,3 +134,8 @@ yarl==1.9.4
zhipuai==2.0.1
BCEmbedding
loguru==0.7.2
umap-learn
fasttext==0.9.2
volcengine
readability-lxml==0.8.1
html_text==0.6.2

142
requirements_arm.txt Normal file
View File

@ -0,0 +1,142 @@
accelerate==0.27.2
aiohttp==3.9.3
aiosignal==1.3.1
annotated-types==0.6.0
anyio==4.3.0
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
#Aspose.Slides==24.2.0
attrs==23.2.0
blinker==1.7.0
cachelib==0.12.0
cachetools==5.3.3
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
coloredlogs==15.0.1
cryptography==42.0.5
dashscope==1.14.1
datasets==2.17.1
datrie==0.8.2
demjson3==3.0.6
dill==0.3.8
distro==1.9.0
elastic-transport==8.12.0
elasticsearch==8.12.1
elasticsearch-dsl==8.12.0
et-xmlfile==1.1.0
filelock==3.13.1
fastembed==0.2.6
FlagEmbedding==1.2.5
Flask==3.0.2
Flask-Cors==4.0.0
Flask-Login==0.6.3
Flask-Session==0.6.0
flatbuffers==23.5.26
frozenlist==1.4.1
fsspec==2023.10.0
h11==0.14.0
hanziconv==0.3.2
httpcore==1.0.4
httpx==0.27.0
huggingface-hub==0.20.3
humanfriendly==10.0
idna==3.6
install==1.3.5
itsdangerous==2.1.2
Jinja2==3.1.3
joblib==1.3.2
lxml==5.1.0
MarkupSafe==2.1.5
minio==7.2.4
mpmath==1.3.0
multidict==6.0.5
multiprocess==0.70.16
networkx==3.2.1
nltk==3.8.1
numpy==1.26.4
# nvidia-cublas-cu12==12.1.3.1
# nvidia-cuda-cupti-cu12==12.1.105
# nvidia-cuda-nvrtc-cu12==12.1.105
# nvidia-cuda-runtime-cu12==12.1.105
# nvidia-cudnn-cu12==8.9.2.26
# nvidia-cufft-cu12==11.0.2.54
# nvidia-curand-cu12==10.3.2.106
# nvidia-cusolver-cu12==11.4.5.107
# nvidia-cusparse-cu12==12.1.0.106
# nvidia-nccl-cu12==2.19.3
# nvidia-nvjitlink-cu12==12.3.101
# nvidia-nvtx-cu12==12.1.105
ollama==0.1.9
# onnxruntime-gpu==1.17.1
openai==1.12.0
opencv-python==4.9.0.80
openpyxl==3.1.2
packaging==23.2
pandas==2.2.1
pdfminer.six==20221105
pdfplumber==0.10.4
peewee==3.17.1
pillow==10.2.0
protobuf==4.25.3
psutil==5.9.8
pyarrow==15.0.0
pyarrow-hotfix==0.6
pyclipper==1.3.0.post5
pycparser==2.21
pycryptodome==3.20.0
pycryptodome-test-vectors==1.0.14
pycryptodomex==3.20.0
pydantic==2.6.2
pydantic_core==2.16.3
PyJWT==2.8.0
PyMySQL==1.1.0
PyPDF2==3.0.1
pypdfium2==4.27.0
python-dateutil==2.8.2
python-docx==1.1.0
python-dotenv==1.0.1
python-pptx==0.6.23
pytz==2024.1
PyYAML==6.0.1
redis==5.0.3
regex==2023.12.25
requests==2.31.0
ruamel.yaml==0.18.6
ruamel.yaml.clib==0.2.8
safetensors==0.4.2
scikit-learn==1.4.1.post1
scipy==1.12.0
sentence-transformers==2.4.0
shapely==2.0.3
six==1.16.0
sniffio==1.3.1
StrEnum==0.4.15
sympy==1.12
threadpoolctl==3.3.0
tika==2.6.0
tiktoken==0.6.0
tokenizers==0.15.2
torch==2.2.1
tqdm==4.66.2
transformers==4.38.1
# triton==2.2.0
typing_extensions==4.10.0
tzdata==2024.1
urllib3==2.2.1
Werkzeug==3.0.1
xgboost==2.0.3
XlsxWriter==3.2.0
xpinyin==0.7.6
xxhash==3.4.1
yarl==1.9.4
zhipuai==2.0.1
BCEmbedding
loguru==0.7.2
umap-learn
fasttext==0.9.2
volcengine
opencv-python-headless==4.9.0.80
readability-lxml==0.8.1
html_text==0.6.2

View File

@ -78,8 +78,6 @@ pycryptodomex==3.20.0
pydantic==2.6.2
pydantic_core==2.16.3
PyJWT==2.8.0
PyMuPDF==1.23.25
PyMuPDFb==1.23.22
PyMySQL==1.1.0
PyPDF2==3.0.1
pypdfium2==4.27.0
@ -124,3 +122,8 @@ BCEmbedding
loguru==0.7.2
ollama==0.1.8
redis==5.0.4
fasttext==0.9.2
umap-learn
volcengine
readability-lxml==0.8.1
html_text==0.6.2

View File

@ -0,0 +1 @@
PORT=9222

View File

@ -1,7 +1,9 @@
import { defineConfig } from 'umi';
import { appName } from './src/conf.json';
import routes from './src/routes';
export default defineConfig({
title: appName,
outputPath: 'dist',
// alias: { '@': './src' },
npmClient: 'npm',
@ -25,10 +27,13 @@ export default defineConfig({
},
},
devtool: 'source-map',
copy: ['src/conf.json'],
proxy: {
'/v1': {
target: 'http://123.60.95.134:9380/',
target: '',
changeOrigin: true,
ws: true,
logger: console,
// pathRewrite: { '^/v1': '/v1' },
},
},

2
web/jest-setup.ts Normal file
View File

@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'umi/test-setup';

33
web/jest.config.ts Normal file
View File

@ -0,0 +1,33 @@
import { Config, configUmiAlias, createConfig } from 'umi/test';
export default async () => {
return (await configUmiAlias({
...createConfig({
target: 'browser',
jsTransformer: 'esbuild',
// config opts for esbuild , it will pass to esbuild directly
jsTransformerOpts: { jsx: 'automatic' },
}),
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
collectCoverageFrom: [
'**/*.{ts,tsx,js,jsx}',
'!.umi/**',
'!.umi-test/**',
'!.umi-production/**',
'!.umirc.{js,ts}',
'!.umirc.*.{js,ts}',
'!jest.config.{js,ts}',
'!coverage/**',
'!dist/**',
'!config/**',
'!mock/**',
],
// if you require some es-module npm package, please uncomment below line and insert your package name
// transformIgnorePatterns: ['node_modules/(?!.*(lodash-es|your-es-pkg-name)/)']
coverageThreshold: {
global: {
lines: 1,
},
},
})) as Config.InitialOptions;
};

10225
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,11 +3,12 @@
"author": "zhaofengchao <13723060510@163.com>",
"scripts": {
"build": "umi build",
"dev": "cross-env PORT=9200 umi dev",
"dev": "cross-env UMI_DEV_SERVER_COMPRESS=none umi dev",
"postinstall": "umi setup",
"lint": "umi lint --eslint-only",
"setup": "umi setup",
"start": "npm run dev"
"start": "npm run dev",
"test": "jest --no-cache --coverage"
},
"dependencies": {
"@ant-design/icons": "^5.2.6",
@ -18,15 +19,19 @@
"antd": "^5.12.7",
"axios": "^1.6.3",
"classnames": "^2.5.1",
"dagre": "^0.8.5",
"dayjs": "^1.11.10",
"elkjs": "^0.9.3",
"eventsource-parser": "^1.1.2",
"i18next": "^23.7.16",
"i18next-browser-languagedetector": "^8.0.0",
"js-base64": "^3.7.5",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
"mammoth": "^1.7.2",
"rc-tween-one": "^3.0.6",
"react-chat-elements": "^12.0.13",
"react-copy-to-clipboard": "^5.1.0",
"react-file-viewer": "^1.2.1",
"react-i18next": "^14.0.0",
"react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^9.0.1",
@ -43,20 +48,28 @@
},
"devDependencies": {
"@react-dev-inspector/umi4-plugin": "^2.0.1",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@types/dagre": "^0.7.52",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.33",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.0.11",
"@types/react-syntax-highlighter": "^15.5.11",
"@types/testing-library__jest-dom": "^6.0.0",
"@types/uuid": "^9.0.8",
"@types/webpack-env": "^1.18.4",
"@umijs/lint": "^4.1.1",
"@umijs/plugins": "^4.1.0",
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "^3.2.4",
"prettier-plugin-organize-imports": "^3.2.4",
"prettier-plugin-packagejson": "^2.4.9",
"react-dev-inspector": "^2.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.0.3",
"umi-plugin-icons": "^0.1.1"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

24
web/src/assets/svg/es.svg Normal file
View File

@ -0,0 +1,24 @@
<svg t="1716195941333" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7780"
width="200" height="200">
<path
d="M1024 534.4c0-85.76-53.12-160.96-133.44-190.08 3.52-17.92 5.44-36.16 5.44-55.04C896 129.92 766.4 0 606.72 0c-93.12 0-179.84 44.8-234.24 120A153.632 153.632 0 0 0 278.4 87.68a153.632 153.632 0 0 0-144 207.04c-79.68 28.8-134.4 105.6-134.4 190.72 0 86.4 53.44 161.6 133.76 190.72-3.52 17.92-5.12 36.48-5.12 55.04 0 159.04 129.6 288.64 288.64 288.64 93.44 0 180.16-44.8 234.24-120.64a152 152 0 0 0 94.08 32.64 153.632 153.632 0 0 0 144-207.04c79.68-28.48 134.4-105.28 134.4-190.4"
fill="#FFFFFF" p-id="7781"></path>
<path
d="M402.56 439.36l224 102.08 225.92-198.08c3.2-16.32 4.8-32.64 4.8-49.6 0-139.52-113.28-252.8-252.8-252.8-83.52 0-161.28 40.96-208.32 109.76l-37.76 195.2 44.16 93.44z"
fill="#FFD00A" p-id="7782"></path>
<path
d="M170.56 676.48c-3.2 16.32-4.8 33.28-4.8 50.56 0 139.84 113.6 253.44 253.44 253.44 84.16 0 162.24-41.28 209.28-111.04l37.44-194.56-49.92-95.04-224.96-102.4-220.48 199.04z"
fill="#20B9AF" p-id="7783"></path>
<path
d="M169.28 288.96l153.6 36.16 33.6-174.72c-21.12-16-47.04-24.96-73.6-24.96-66.88 0-120.96 54.4-120.96 120.96 0 15.04 2.56 29.12 7.36 42.56"
fill="#EE5096" p-id="7784"></path>
<path
d="M155.84 325.44c-68.48 22.72-116.16 88.64-116.16 160.96 0 70.4 43.52 133.44 108.8 158.08l215.36-194.88-39.68-84.48-168.32-39.68z"
fill="#12A5DF" p-id="7785"></path>
<path
d="M667.84 869.44c21.12 16.32 46.72 24.96 73.28 24.96 66.88 0 120.96-54.4 120.96-120.96 0-14.72-2.56-28.8-7.36-42.24l-153.28-35.84-33.6 174.08z"
fill="#90C640" p-id="7786"></path>
<path
d="M699.2 655.36l168.96 39.36c68.48-22.72 116.48-88.32 116.48-160.96 0-70.4-43.52-133.12-109.12-158.08l-220.8 193.6 44.48 86.08z"
fill="#05799F" p-id="7787"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve"> <image id="image0" width="256" height="256" x="0" y="0"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAAIGNIUk0AAHolAACAgwAA+f8AAIDp
AAB1MAAA6mAAADqYAAAXb5JfxUYAAACuUExURf////349fzy7/77+f349fzy7//+/fXOv/bTxf//
/+2njfjg1u+xmfG6pumXd/jg1uaDXv349fLArfPIt++xmfvv6u6tlP77+frp4uqbfeaIZPPEsvC2
n/LArf////LCr/////G6pvLCr++xmfC2n/zy7/fXyu2njeiPbf318fnl3eudfvG+quykif/////+
/eBnOeN2TON3TuJ0SuFvROFqPeN5UOV+V+R8VP///3/zEGYAAAAwdFJOUwA+RTlbYVqZk1i/Y7yt
4YH5Yquhv27DXnbb86W1plCpJZiOq7lojcvrZnrZrc8yNOA7KFcAAAABYktHRACIBR1IAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH6AUbBTg4QG2qLwAAAyhJREFUeNrt3dlS2zAUgGHaUjvq
ljht4pB0oy3ZoDTgLH3/JytThinjWNJxhwmco/+/RkL6RngSc6GjIyIiIiIiIiIiIiIiIiIiIiIi
IiIiIiIiIvq/nj0X9qI28PilsOPH2FaWe+q42k++Wgl7XRv4Rjrw7WMAdHyruUoDwL1LHKDrXU0a
AFkvbQCX+1eTBEBxnThAP7CaFABcL20A9z60mgQAiiptAPchuBr7AIN12gBuGF6NeYCyCq/GOkDk
CWAfYLRJG8CdxFZjHKAbXY1tgPgBMA4wiD0BjAP4X4QlAtARrMYyQPBrcAoAuWQ1hgHcOHGA+GcA
2wBlL22A8IuwBACKSrYaswDCA2AWIPDPsCQA3ES6mocD+FgJ+3QAgHJ7eIDP4g4AIH0CPCTAk+oL
AAAAAAAAAAAAwAEBvoozCnC6FfbNKMB36cBTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAgP3cDzHAziTA2Vq6mFXfIkCLA3Bd1gdbAMjEtwusJvUDYAFAesHMTbtsb7QB
gLMrMcB07wAYAHAz8f7n+/s3ACC4ZfGuvGG4eoDYTcv3GmcN49UDLOSfAZZN47UDRG9a/te5a5pA
O8BS/ARYDxonUA7g5uIDMGw8ANoBZLcs/m3UPINuAHcu3v+s+QAoBxiJ918VnilUAwiu2r7rwnMA
VAO4qXj/u9I3iWaA7LcYYOI7AKoBJFdt3zYvvJMoBmjxGWDqn0UxQIsD4Pyz6AUQ3bV+WzcwjVoA
1xfv/2fgAOgFkH8NXhehebQCtDgAs9ABUAtQ7qS/fzMKTqQUwF2ID8AweAC0AkjvWr95AizCM+kE
aPEqfBI+AEoB5F+De5H96wRo8Sa0H5tLJcBC/ATYFrG5NAK0eALksb8AnQDiV+Hj6P51Akh/9WYZ
n8w0wEn8AOgEWApbCCa7/CXssjbwSd07TEREREREREREREREREREREREREREevoDtKBqvEP0IEYA
AAAldEVYdGRhdGU6Y3JlYXRlADIwMjQtMDUtMjdUMDU6NTY6NTYrMDA6MDANJESyAAAAJXRFWHRk
YXRlOm1vZGlmeQAyMDI0LTA1LTI3VDA1OjU2OjU2KzAwOjAwfHn8DgAAACh0RVh0ZGF0ZTp0aW1l
c3RhbXAAMjAyNC0wNS0yN1QwNTo1Njo1NiswMDowMCts3dEAAAAASUVORK5CYII=" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,228 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve"> <image id="image0" width="200" height="200" x="0" y="0"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo
AAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAD/AP8A/6C9p5MAAAKjelRYdFJhdyBwcm9maWxl
IHR5cGUgeG1wAABIiZ1WS5bjMAjcc4o+ggQS2Mdx2/Ku35vlHH8K+RP/k+n4tTq2oShKgEJ/f/7Q
Fz6RWUl6a6SzRgcLOmi2pJGD32uvxcTfycCmUZOOypqlm56v1iObFAodh/0FaIaTSpc5paQH2Omd
QzeWcAXpADWS1Q8XgxUXHWFoPEqU1i98C8JYGeswgeC/WAtYsLGGBw/AQk4rdE+8QCqbqOC+tQD3
FikW8JzfI2lWMEKeTpTtDLZNCU5yTgjAGYA9F1qScUOsGY7ZZXTqoIwkucgs6BXvGqCzRKCb3Pna
bE0vag/mDzYE0r22Sxr1LmpR8zSqlFBIFbJmh3JeCRbQDCnBS6pmot80PeAB2FF6HlcJAX/N4GWz
5UlnsyXr56Q3cHVX6bitvy0G2lfDp8WQYi0CgClPRUHbqqg1WnRj5MBzzAJJRx1gha5bCyMshUHn
JHbRXFC4PRdHxh6SxWejuTjmO0yIiLzhmJNBI2wFEsvsQFKVYRDPd9V74OhCZxcAKzYfHYvGJvT8
gAqKWAP6Hdv67HYXiDYu2dCQqa1Rgu+ix8IaJSNMq1ESxB2wttI4AbxDHspOgvCoRVl4NMEqrgUK
8sxsE+aKFR3MMbdmThl8/A9BEMrZRDF8ywgdzoHo5fpe5px8Y8C6WffMpa6g9M7wuc9erX3RtNsy
u6uyk4KR7uv22N/P7U0vx/8b9sf23qW2O7/C9fm1NPZyXC7NTH4S1NNq6ma5H/MztI8QnwmNHxsY
vB0YAxiM5lRaNTxA11V1Lp2elKTPpHyvJH0m5a2S9ceHW9JnUiKBBhMgHOfiC55WyhP2dmwge691
XD1EFT/vDCdc1eVkTXvz3xbmc4vUX0e34IefZwSqhf4BDtczzLoeiJ0AAC2/SURBVHja7b1psCTZ
dd/3P+fcm5lV9dbununZQaAHIGYGM+AYg4UACYEEIJkiFgUXwIJFiaJAh0iFbckhOvRBXsIOhW2F
w6IVFm2RoigjHKBtEjAgkACHIAYCsRADYAgCxEZgMPtM72+tJfMu5/hDVtWr18ts3Q/TaOUvMrpf
V+erzLr3n+eee+45t4COjo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6O
jo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6O
jo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6LQy/0DVwh8P5/6gt9P9/3dMICwAtH
K6nUaesSkRf6Br7XVFWlqsxsZu0rRBApzHwhfecKgL1zBq16XjXPzup4bvz7aLFKXwjzuKmJ6Nix
YzfeeKNwr3CHtrdGjz/+8FMnHlEEoCaGE4kxv9D3+33JVSws3hvOCLC9f1biHeONP/am219xx/U3
3ri2diQFR7q8tnZkd7j54He/cd8nP/qnX/5CTBNimF78bTsuzlU8FBIwH8YYAGj6z7vvfMUv/p33
/tCddxLUDDGyqQ+jPN4ae8ONR695zavvvu6aQ1/7xldyzPtHQp698+Kbd1yAq1pYNO97WlpZy8im
+Ydf+5q/9XN/8+jhI8OtTYG5ouJyiaVaRbkMX6pR01Qcjv3A9be+7NY/+8qXY4p770JMRAQhMBER
gc7jhf7UVwpXr7Bo0aJYiLVpftVrbn/ve3/B1CxmX3gpSXyv9CvLbvX6weFDxWCtWF6tSm22WXT9
8DVr64c+/8ADAIgBAxGmeiXrBsSn5+oVVgsBgHclWVpdwb/4tX9yw423pAwRP2qa3vWHltfWb+xd
d0N55FC1vlqurpRLy1VFaHIKRnLzLS/ZGY4fe+KpnGNZ9nJOIJ0Kqx0Pr2Yn9ZK4aoXFrRNEAEE1
l4y3/9VX/eLf/huTZODyzOltKqqjL3vR6uqhG8pDR/xqj6uSfCnOO/QHtLQyOL2xmQBX9T93/xey
BiZWy+cJqRPWheFLf4srEAZo4bOVvsoZb7rndTsnTq70qrW1tcQ8OHKNW14tVgYspKkGJfEQB3G2
vNzr9f3hw6vD0fby8uDYsWPCvZiaF/pjfT9xdQoLM221FquJ9XVrK4f7RzYe36KQnSN2vjYzYgMH
0eBSlpC5ydIYhyRhnLZHaTtLWD28fNvtL806vorb6iBwL/QNfI84vHZ0iY+efXS0fqNVfRHvQ8yi
DOGxT7GXVzNbjKoQF9mNt9LmdjqjrvE9f/2N6wBESNUMMpsVdM7703HVCovADG2j5o5cQRWnMjbB
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
b29sAENhbnZh6scSsQAAAABJRU5ErkJggg==" />
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,14 @@
<svg width="200" height="200" viewBox="0 0 20 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4967_21175)">
<path d="M20.511 15.3019L17.2442 28.1928C17.2362 28.2282 17.2364 28.2649 17.2447 28.3001C17.2531 28.3354 17.2694 28.3683 17.2923 28.3964C17.3153 28.4244 17.3444 28.4468 17.3773 28.4619C17.4103 28.477 17.4462 28.4844 17.4825 28.4835H24.0137C24.0499 28.4844 24.0859 28.477 24.1188 28.4619C24.1518 28.4468 24.1809 28.4244 24.2038 28.3964C24.2268 28.3683 24.2431 28.3354 24.2514 28.3001C24.2598 28.2649 24.26 28.2282 24.252 28.1928L20.9685 15.3019C20.9541 15.2524 20.924 15.209 20.8827 15.178C20.8415 15.1471 20.7913 15.1304 20.7397 15.1304C20.6882 15.1304 20.638 15.1471 20.5968 15.178C20.5555 15.209 20.5254 15.2524 20.511 15.3019V15.3019Z" fill="#00E5E5"/>
<path d="M2.53051 18.2228L-5.28338e-06 28.1924C-0.00799016 28.2277 -0.00780431 28.2644 0.000538111 28.2997C0.00888053 28.335 0.0251596 28.3679 0.0481365 28.3959C0.0711133 28.4239 0.100182 28.4464 0.133131 28.4615C0.166079 28.4766 0.202039 28.484 0.238273 28.4831H5.28025C5.31649 28.484 5.35245 28.4766 5.38539 28.4615C5.41834 28.4464 5.44741 28.4239 5.47039 28.3959C5.49336 28.3679 5.50964 28.335 5.51799 28.2997C5.52633 28.2644 5.52651 28.2277 5.51853 28.1924L2.98563 18.2228C2.97054 18.1742 2.94032 18.1318 2.89938 18.1016C2.85844 18.0714 2.80892 18.0552 2.75807 18.0552C2.70722 18.0552 2.6577 18.0714 2.61676 18.1016C2.57582 18.1318 2.5456 18.1742 2.53051 18.2228V18.2228Z" fill="#00E5E5"/>
<path d="M6.99344 9.96839L2.38275 28.1919C2.37498 28.2263 2.37494 28.262 2.38262 28.2964C2.3903 28.3308 2.40552 28.363 2.42717 28.3908C2.44882 28.4186 2.47637 28.4413 2.50783 28.4572C2.53929 28.473 2.57388 28.4817 2.60911 28.4826H11.8329C11.8691 28.4835 11.9051 28.4761 11.938 28.461C11.971 28.4459 12 28.4235 12.023 28.3955C12.046 28.3675 12.0623 28.3345 12.0706 28.2993C12.079 28.264 12.0791 28.2273 12.0712 28.1919L7.44855 9.96839C7.43347 9.91982 7.40325 9.87736 7.36231 9.8472C7.32136 9.81705 7.27185 9.80078 7.221 9.80078C7.17015 9.80078 7.12063 9.81705 7.07969 9.8472C7.03874 9.87736 7.00852 9.91982 6.99344 9.96839Z" fill="#006EFF"/>
<path d="M14.9472 4.17346C14.9321 4.1249 14.9019 4.08244 14.861 4.05228C14.82 4.02213 14.7705 4.00586 14.7197 4.00586C14.6688 4.00586 14.6193 4.02213 14.5784 4.05228C14.5374 4.08244 14.5072 4.1249 14.4921 4.17346L8.18963 28.192C8.18165 28.2273 8.18183 28.264 8.19017 28.2993C8.19852 28.3346 8.2148 28.3675 8.23777 28.3955C8.26075 28.4235 8.28982 28.446 8.32277 28.4611C8.35572 28.4762 8.39168 28.4835 8.42791 28.4827H21.0233C21.0596 28.4835 21.0955 28.4762 21.1285 28.4611C21.1614 28.446 21.1905 28.4235 21.2135 28.3955C21.2364 28.3675 21.2527 28.3346 21.2611 28.2993C21.2694 28.264 21.2696 28.2273 21.2616 28.192L14.9472 4.17346Z" fill="#006EFF"/>
<path d="M10.3175 12.6188L6.31915 28.1903C6.31074 28.2258 6.31061 28.2628 6.31875 28.2984C6.3269 28.3339 6.34311 28.3672 6.36614 28.3955C6.38916 28.4238 6.41839 28.4465 6.45155 28.4617C6.48472 28.4769 6.52094 28.4844 6.55743 28.4834H14.535C14.5715 28.4844 14.6077 28.4769 14.6409 28.4617C14.674 28.4465 14.7033 28.4238 14.7263 28.3955C14.7493 28.3672 14.7655 28.3339 14.7737 28.2984C14.7818 28.2628 14.7817 28.2258 14.7733 28.1903L10.7726 12.6188C10.7575 12.5702 10.7273 12.5278 10.6863 12.4976C10.6454 12.4674 10.5959 12.4512 10.545 12.4512C10.4942 12.4512 10.4447 12.4674 10.4037 12.4976C10.3628 12.5278 10.3326 12.5702 10.3175 12.6188Z" fill="#00E5E5"/>
</g>
<defs>
<clipPath id="clip0_4967_21175">
<rect width="99.9412" height="24.5665" fill="white" transform="translate(0 4)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,29 +0,0 @@
<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M3.43265 20.7677C4.15835 21.5062 4.15834 22.7035 3.43262 23.4419L3.39546 23.4797C2.66974 24.2182 1.49312 24.2182 0.767417 23.4797C0.0417107 22.7412 0.0417219 21.544 0.767442 20.8055L0.804608 20.7677C1.53033 20.0292 2.70694 20.0293 3.43265 20.7677Z"
fill="#B2DDFF" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M12.1689 21.3375C12.8933 22.0773 12.8912 23.2746 12.1641 24.0117L7.01662 29.2307C6.2896 29.9678 5.11299 29.9657 4.38859 29.2259C3.66419 28.4861 3.66632 27.2888 4.39334 26.5517L9.54085 21.3327C10.2679 20.5956 11.4445 20.5977 12.1689 21.3375Z"
fill="#53B1FD" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M19.1551 30.3217C19.7244 29.4528 20.8781 29.218 21.7321 29.7973L21.8436 29.8729C22.6975 30.4522 22.9283 31.6262 22.359 32.4952C21.7897 33.3641 20.6359 33.5989 19.782 33.0196L19.6705 32.944C18.8165 32.3647 18.5858 31.1907 19.1551 30.3217Z"
fill="#B2DDFF" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M31.4184 20.6544C32.1441 21.3929 32.1441 22.5902 31.4184 23.3286L28.8911 25.9003C28.1654 26.6388 26.9887 26.6388 26.263 25.9003C25.5373 25.1619 25.5373 23.9646 26.263 23.2261L28.7903 20.6544C29.516 19.916 30.6927 19.916 31.4184 20.6544Z"
fill="#53B1FD" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M31.4557 11.1427C32.1814 11.8812 32.1814 13.0785 31.4557 13.8169L12.7797 32.8209C12.054 33.5594 10.8774 33.5594 10.1517 32.8209C9.42599 32.0825 9.42599 30.8852 10.1517 30.1467L28.8277 11.1427C29.5534 10.4043 30.73 10.4043 31.4557 11.1427Z"
fill="#1570EF" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M27.925 5.29994C28.6508 6.0384 28.6508 7.23568 27.925 7.97414L17.184 18.9038C16.4583 19.6423 15.2817 19.6423 14.556 18.9038C13.8303 18.1653 13.8303 16.9681 14.556 16.2296L25.297 5.29994C26.0227 4.56148 27.1993 4.56148 27.925 5.29994Z"
fill="#1570EF" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M22.256 1.59299C22.9822 2.33095 22.983 3.52823 22.2578 4.26718L8.45055 18.3358C7.72533 19.0748 6.54871 19.0756 5.82251 18.3376C5.09631 17.5996 5.09552 16.4024 5.82075 15.6634L19.6279 1.59478C20.3532 0.855827 21.5298 0.855022 22.256 1.59299Z"
fill="#1570EF" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8.58225 6.09619C9.30671 6.83592 9.30469 8.0332 8.57772 8.77038L3.17006 14.2541C2.4431 14.9913 1.26649 14.9893 0.542025 14.2495C-0.182438 13.5098 -0.180413 12.3125 0.546548 11.5753L5.95421 6.09159C6.68117 5.3544 7.85778 5.35646 8.58225 6.09619Z"
fill="#53B1FD" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M11.893 0.624023C12.9193 0.624023 13.7513 1.47063 13.7513 2.51497V2.70406C13.7513 3.7484 12.9193 4.59501 11.893 4.59501C10.8667 4.59501 10.0347 3.7484 10.0347 2.70406V2.51497C10.0347 1.47063 10.8667 0.624023 11.893 0.624023Z"
fill="#B2DDFF" />
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,10 @@
<svg t="1716195854453" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5857"
width="200" height="200">
<path
d="M638.855218 56.525807s99.268136 159.838523 132.357514 216.623262a2.523766 2.523766 0 0 1 0 2.944394 2.383557 2.383557 0 0 1-3.50523 0L596.231612 97.326693l42.623606-40.800886z"
fill="#dd113c" p-id="5858"></path>
<path
d="M346.518971 639.655999a588.878771 588.878771 0 0 1 116.654081-165.446893 597.291325 597.291325 0 0 1 58.32704-51.176369v126.188308L346.518971 639.655999zM245.568325 756.590498l275.931767-140.209231v321.079139l62.112689 80.760517v-434.648616l37.716283-19.489084a187.179324 187.179324 0 0 0 51.456788-296.121896L530.753901 119.479752a31.547077 31.547077 0 0 1 1.542302-44.446327 31.687286 31.687286 0 0 1 44.586535 1.542302l19.909711 20.750966 42.062769-40.941095c-50.335114-65.337502-112.167385-57.065157-147.64032-24.396407a90.575163 90.575163 0 0 0-3.925859 127.870819l143.574253 149.60325a128.151237 128.151237 0 0 1-28.041846 197.414597l-19.489083 10.095065V314.090164A649.589368 649.589368 0 0 0 245.568325 755.889452v0.701046z"
fill="#dd113c" p-id="5859"></path>
<path d="M583.612781 583.432097v65.617921l-62.112689 31.547077v-65.197293z" fill="#dd113c" p-id="5860"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,9 @@
<svg t="1716195691568" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4834"
width="200" height="200">
<path
d="M1001.632 793.792c-7.84-13.856-26.016-37.536-93.12-83.2a1096.224 1096.224 0 0 0-125.152-74.144c-30.592-82.784-89.824-190.112-176.256-319.36-93.056-139.168-201.12-197.792-321.888-174.56a756.608 756.608 0 0 0-40.928-37.696C213.824 78.688 139.2 56.48 96.32 60.736c-19.424 1.952-34.016 9.056-43.36 21.088-21.664 27.904-14.432 68.064 85.504 198.912 19.008 55.616 23.072 84.672 23.072 99.296 0 30.912 15.968 66.368 49.984 110.752l-32 109.504c-28.544 97.792 23.328 224.288 71.616 268.384 25.76 23.552 47.456 20.032 58.176 15.84 21.504-8.448 38.848-29.472 50.048-89.504 5.728 14.112 11.808 29.312 18.208 45.6 34.56 87.744 68.352 136.288 106.336 152.736a32.032 32.032 0 0 0 25.44-58.688c-9.408-4.096-35.328-23.712-72.288-117.504-31.168-79.136-53.856-132.064-69.376-161.856a32.224 32.224 0 0 0-35.328-16.48 32.032 32.032 0 0 0-25.024 29.92c-3.872 91.04-13.056 130.4-19.2 147.008-26.496-30.464-68.128-125.984-47.232-197.536 20.768-71.232 32.992-112.928 36.64-125.248a31.936 31.936 0 0 0-5.888-29.28c-41.664-51.168-46.176-75.584-46.176-83.712 0-29.472-9.248-70.4-28.288-125.152a31.104 31.104 0 0 0-4.768-8.896c-53.824-70.112-73.6-105.216-80.832-121.888 25.632 1.216 74.336 15.04 91.008 29.376a660.8 660.8 0 0 1 49.024 46.304c8 8.448 19.968 11.872 31.232 8.928 100.192-25.92 188.928 21.152 271.072 144 87.808 131.328 146.144 238.048 173.408 317.216a32 32 0 0 0 16.384 18.432 1004.544 1004.544 0 0 1 128.8 75.264c7.392 5.024 14.048 9.696 20.064 14.016h-98.848a32.032 32.032 0 0 0-24.352 52.736 3098.752 3098.752 0 0 0 97.856 110.464 32 32 0 1 0 46.56-43.872 2237.6 2237.6 0 0 1-50.08-55.328h110.08a32.032 32.032 0 0 0 27.84-47.776z"
p-id="4835"></path>
<path
d="M320 289.472c12.672 21.76 22.464 37.344 29.344 46.784 8.288 16.256 21.184 29.248 29.44 45.536l2.016-1.984c14.528-9.952 25.92-49.504 2.752-75.488-12.032-18.176-51.04-17.664-63.552-14.848z"
p-id="4836"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,6 @@
<svg t="1716195575286" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3818"
width="200" height="200">
<path
d="M959.744 602.16l0.256 0.064v101.952c0 10.24-10.752 21.44-35.072 35.84-22.976 13.696-91.968 47.616-163.328 82.624l-35.712 17.536c-65.088 32-126.016 62.208-149.184 76.032-52.8 31.36-82.048 31.104-123.712 8.32-41.6-22.72-305.28-144.256-352.704-170.176-23.744-12.992-36.224-23.936-36.224-34.24v-103.424c0.384 10.368 12.48 21.248 36.224 34.24C147.776 676.8 411.328 798.4 452.992 821.12c41.664 22.784 70.912 23.04 123.712-8.32 52.672-31.36 300.416-147.712 348.224-176.128 23.232-13.824 34.56-24.768 34.88-34.56l-0.064 0.064z m0-168.576h0.192v101.952c0 10.24-10.752 21.44-35.072 35.968-47.808 28.416-295.552 144.768-348.224 176.128-52.8 31.36-82.048 31.04-123.712 8.32-41.6-22.72-305.28-144.32-352.704-170.24C76.48 572.8 64 561.92 64 551.536v-103.424c0.384 10.24 12.48 21.248 36.224 34.176 47.488 25.92 311.04 147.52 352.704 170.24 41.664 22.72 70.912 23.04 123.712-8.32 52.672-31.36 300.416-147.712 348.224-176.192 23.168-13.824 34.56-24.704 34.88-34.432zM462.656 81.84c55.36-22.72 74.56-23.488 121.664-3.776 47.168 19.776 293.376 131.648 339.968 151.104 24 10.048 35.84 19.2 35.456 29.632H960v101.952c0 10.176-10.816 21.44-35.072 35.904C877.056 425.072 629.376 541.44 576.64 572.8c-52.736 31.36-81.984 31.104-123.648 8.32-41.664-22.656-305.28-144.32-352.768-170.24C76.544 397.936 64 387.056 64 376.688V273.28c-0.32-10.304 11.072-19.968 34.368-30.464 46.656-20.8 308.8-138.24 364.288-160.896v-0.064z m129.792 238.4l-207.552 36.352 144.832 68.608 62.72-104.96z m128.704-113.6l-135.936 61.44 122.688 55.36 13.376-5.952 122.752-55.424-122.88-55.424z m-392.32 13.44c-61.248 0-110.912 22.016-110.912 49.152 0 27.072 49.664 49.088 110.976 49.088s110.912-21.952 110.912-49.088-49.6-49.088-110.912-49.088l-0.064-0.064z m134.656-101.888l20.096 42.304-66.88 27.52 89.6 9.216 28.032 53.248 17.408-47.744 77.632-9.216-60.16-25.728 16-43.712-59.136 22.08-62.592-27.968z"
fill="#D82A1F" p-id="3819"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -15,7 +15,6 @@ import {
Modal,
Select,
Space,
Switch,
Tooltip,
} from 'antd';
import omit from 'lodash/omit';
@ -23,6 +22,10 @@ import React, { useEffect, useMemo } from 'react';
import { useFetchParserListOnMount } from './hooks';
import { useTranslate } from '@/hooks/commonHooks';
import LayoutRecognize from '../layout-recognize';
import ParseConfiguration, {
showRaptorParseConfiguration,
} from '../parse-configuration';
import styles from './index.less';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
@ -111,6 +114,7 @@ const ChunkMethodModal: React.FC<IProps> = ({
onCancel={hideModal}
afterClose={afterClose}
confirmLoading={loading}
width={700}
>
<Space size={[0, 8]} wrap>
<Form.Item label={t('chunkMethod')} className={styles.chunkMethod}>
@ -228,17 +232,7 @@ const ChunkMethodModal: React.FC<IProps> = ({
</Form.List>
</>
)}
{showOne && (
<Form.Item
name={['parser_config', 'layout_recognize']}
label={t('layoutRecognize')}
initialValue={true}
valuePropName="checked"
tooltip={t('layoutRecognizeTip')}
>
<Switch />
</Form.Item>
)}
{showOne && <LayoutRecognize></LayoutRecognize>}
{showPages && (
<Form.Item
noStyle
@ -265,6 +259,9 @@ const ChunkMethodModal: React.FC<IProps> = ({
</Form.Item>
)}
{showMaxTokenNumber && <MaxTokenNumber></MaxTokenNumber>}
{showRaptorParseConfiguration(selectedTag) && (
<ParseConfiguration></ParseConfiguration>
)}
</Form>
</Modal>
);

View File

@ -0,0 +1,19 @@
import { useTranslate } from '@/hooks/commonHooks';
import { Form, Switch } from 'antd';
const LayoutRecognize = () => {
const { t } = useTranslate('knowledgeDetails');
return (
<Form.Item
name={['parser_config', 'layout_recognize']}
label={t('layoutRecognize')}
initialValue={true}
valuePropName="checked"
tooltip={t('layoutRecognizeTip')}
>
<Switch />
</Form.Item>
);
};
export default LayoutRecognize;

View File

@ -1,22 +1,24 @@
import { api_host } from '@/utils/api';
import React from 'react';
interface IProps extends React.PropsWithChildren {
documentId: string;
link: string;
preventDefault?: boolean;
color?: string;
}
const NewDocumentLink = ({
children,
documentId,
link,
preventDefault = false,
color = 'rgb(15, 79, 170)',
}: IProps) => {
return (
<a
target="_blank"
onClick={!preventDefault ? undefined : (e) => e.preventDefault()}
href={`${api_host}/document/get/${documentId}`}
href={link}
rel="noreferrer"
style={{ color, wordBreak: 'break-all' }}
>
{children}
</a>

View File

@ -0,0 +1,206 @@
import { useTranslate } from '@/hooks/commonHooks';
import { PlusOutlined } from '@ant-design/icons';
import {
Button,
Divider,
Flex,
Form,
Input,
InputNumber,
Slider,
Switch,
} from 'antd';
import random from 'lodash/random';
export const excludedParseMethods = ['table', 'resume', 'one',"picture"];
export const showRaptorParseConfiguration = (parserId: string) => {
return !excludedParseMethods.includes(parserId);
};
// The three types "table", "resume" and "one" do not display this configuration.
const ParseConfiguration = () => {
const form = Form.useFormInstance();
const { t } = useTranslate('knowledgeConfiguration');
const handleGenerate = () => {
form.setFieldValue(
['parser_config', 'raptor', 'random_seed'],
random(10000),
);
};
return (
<>
<Divider></Divider>
<Form.Item
name={['parser_config', 'raptor', 'use_raptor']}
label={t('useRaptor')}
initialValue={false}
valuePropName="checked"
tooltip={t('useRaptorTip')}
>
<Switch />
</Form.Item>
<Form.Item
shouldUpdate={(prevValues, curValues) =>
prevValues.parser_config.raptor.use_raptor !==
curValues.parser_config.raptor.use_raptor
}
>
{({ getFieldValue }) => {
const useRaptor = getFieldValue([
'parser_config',
'raptor',
'use_raptor',
]);
return (
useRaptor && (
<>
<Form.Item
name={['parser_config', 'raptor', 'prompt']}
label={t('prompt')}
initialValue={t('promptText')}
tooltip={t('promptTip')}
rules={[
{
required: true,
message: t('promptMessage'),
},
]}
>
<Input.TextArea rows={8} />
</Form.Item>
<Form.Item label={t('maxToken')} tooltip={t('maxTokenTip')}>
<Flex gap={20} align="center">
<Flex flex={1}>
<Form.Item
name={['parser_config', 'raptor', 'max_token']}
noStyle
initialValue={256}
rules={[
{
required: true,
message: t('maxTokenMessage'),
},
]}
>
<Slider max={2048} style={{ width: '100%' }} />
</Form.Item>
</Flex>
<Form.Item
name={['parser_config', 'raptor', 'max_token']}
noStyle
rules={[
{
required: true,
message: t('maxTokenMessage'),
},
]}
>
<InputNumber max={2048} min={0} />
</Form.Item>
</Flex>
</Form.Item>
<Form.Item label={t('threshold')} tooltip={t('thresholdTip')}>
<Flex gap={20} align="center">
<Flex flex={1}>
<Form.Item
name={['parser_config', 'raptor', 'threshold']}
noStyle
initialValue={0.1}
rules={[
{
required: true,
message: t('thresholdMessage'),
},
]}
>
<Slider
min={0}
max={1}
style={{ width: '100%' }}
step={0.01}
/>
</Form.Item>
</Flex>
<Form.Item
name={['parser_config', 'raptor', 'threshold']}
noStyle
rules={[
{
required: true,
message: t('thresholdMessage'),
},
]}
>
<InputNumber max={1} min={0} step={0.01} />
</Form.Item>
</Flex>
</Form.Item>
<Form.Item label={t('maxCluster')} tooltip={t('maxClusterTip')}>
<Flex gap={20} align="center">
<Flex flex={1}>
<Form.Item
name={['parser_config', 'raptor', 'max_cluster']}
noStyle
initialValue={64}
rules={[
{
required: true,
message: t('maxClusterMessage'),
},
]}
>
<Slider min={1} max={1024} style={{ width: '100%' }} />
</Form.Item>
</Flex>
<Form.Item
name={['parser_config', 'raptor', 'max_cluster']}
noStyle
rules={[
{
required: true,
message: t('maxClusterMessage'),
},
]}
>
<InputNumber max={1024} min={1} />
</Form.Item>
</Flex>
</Form.Item>
<Form.Item label={t('randomSeed')}>
<Flex gap={20} align="center">
<Flex flex={1}>
<Form.Item
name={['parser_config', 'raptor', 'random_seed']}
noStyle
initialValue={0}
rules={[
{
required: true,
message: t('randomSeedMessage'),
},
]}
>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
</Flex>
<Form.Item noStyle>
<Button type="primary" onClick={handleGenerate}>
<PlusOutlined />
</Button>
</Form.Item>
</Flex>
</Form.Item>
</>
)
);
}}
</Form.Item>
</>
);
};
export default ParseConfiguration;

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