├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── docker-compose.yml
├── fe
├── .gitignore
├── index.html
├── package.json
├── src
│ ├── App.vue
│ ├── assets
│ │ └── main.css
│ ├── components
│ │ ├── KnowledgeNameSetting.vue
│ │ └── KnowledgeSelector.vue
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── utils
│ │ ├── knowledgeIdStore.js
│ │ └── knowledgeStore.js
│ └── views
│ │ ├── Chat.vue
│ │ ├── Indexer.vue
│ │ ├── KnowledgeBase.vue
│ │ └── Retriever.vue
└── vite.config.js
└── server
├── README.md
├── api
└── rag
│ ├── rag.go
│ └── v1
│ ├── chat.go
│ ├── indexer.go
│ ├── knowledge_base.go
│ └── retriever.go
├── core
├── common
│ ├── chat_model.go
│ ├── consts.go
│ ├── embedding.go
│ ├── es.go
│ ├── helper.go
│ └── stream.go
├── config
│ └── config.go
├── grader
│ ├── grader.go
│ └── message.go
├── indexer
│ ├── indexer.go
│ ├── lambda_func.go
│ ├── loader.go
│ ├── orchestration.go
│ ├── parser.go
│ └── transformer.go
├── message.go
├── rag.go
├── rag_test.go
├── readme.md
├── retriever
│ ├── orchestration.go
│ ├── retriever.go
│ └── transformer.go
└── test_file
│ ├── readme.html
│ ├── readme.md
│ ├── readme2.md
│ └── test.pdf
├── go.mod
├── go.sum
├── hack
└── config.yaml
├── internal
├── cmd
│ └── cmd.go
├── controller
│ └── rag
│ │ ├── rag.go
│ │ ├── rag_new.go
│ │ ├── rag_v1_chat.go
│ │ ├── rag_v1_chat_stream.go
│ │ ├── rag_v1_indexer.go
│ │ ├── rag_v1_kb.go
│ │ └── rag_v1_retriever.go
├── dao
│ ├── dao.go
│ ├── internal
│ │ └── knowledge_base.go
│ └── knowledge_base.go
├── logic
│ ├── chat
│ │ ├── chat.go
│ │ └── message.go
│ └── rag
│ │ └── retriever.go
├── mcp
│ ├── indexer.go
│ ├── knowledgebase.go
│ ├── mcp.go
│ └── retriever.go
└── model
│ ├── do
│ └── knowledge_base.go
│ ├── entity
│ └── knowledge_base.go
│ └── knowledge_base.go
├── main.go
├── manifest
└── config
│ └── config_demo.yaml
└── static
├── chat.png
├── indexer.png
├── kb-select.png
├── kb.png
├── mcp-cfg.png
├── mcp-use.png
└── retriever.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 | go.work.sum
23 |
24 | # env file
25 | .env
26 | .idea
27 |
28 | server/manifest/config/config.yaml
29 | /server/uploads/
30 | /node_modules/
31 | /fe/package-lock.json
32 | /server/static/fe/
33 | /fe/.vite
34 | /server/go-rag-server
35 | /server/logs/
36 | /data/
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # 多阶段构建
2 | # 阶段2: 构建后端
3 | FROM golang:1.23.9-alpine AS server-builder
4 | WORKDIR /app
5 | # 复制整个项目代码
6 | COPY . .
7 | # 复制前端构建产物到server目录
8 | # 构建后端
9 | RUN cd server && go mod tidy && go build -o go-rag-server main.go
10 |
11 | # 阶段3: 最终镜像
12 | FROM alpine:latest
13 | WORKDIR /app
14 | # 安装运行时依赖
15 | RUN apk --no-cache add ca-certificates tzdata
16 | # 设置时区
17 | ENV TZ=Asia/Shanghai
18 | # 复制后端构建产物
19 | COPY --from=server-builder /app/server/go-rag-server /app/
20 | COPY --from=server-builder /app/server/static/ /app/static/
21 | COPY --from=server-builder /app/server/manifest/ /app/manifest/
22 |
23 | # 暴露端口
24 | EXPOSE 8000
25 |
26 | # 启动命令
27 | CMD ["/app/go-rag-server"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for go-rag project
2 |
3 | .PHONY: build-fe build-server build docker-build clean
4 |
5 | # 默认目标
6 | all: build
7 |
8 | # 构建前端
9 | build-fe:
10 | cd fe && npm install && npm run build
11 | mkdir -p server/static/fe
12 | cp -r fe/dist/* server/static/fe/
13 |
14 | # 构建后端
15 | build-server:
16 | cd server && go mod tidy && go build -o go-rag-server main.go
17 |
18 | # 构建整个项目
19 | build: build-fe build-server
20 |
21 | # 运行
22 | run:
23 | cd server && ./go-rag-server
24 |
25 | # 清理构建产物
26 | clean:
27 | rm -rf fe/dist
28 | rm -rf server/static/fe
29 | rm -f server/go-rag-server
30 |
31 | # 构建Docker镜像
32 | docker-build: build
33 | docker build -t go-rag:latest -f Dockerfile .
34 |
35 | run-local:
36 | cd server && go mod tidy && go run .
37 |
38 | build-linux:
39 | cd server && go mod tidy && GOOS=linux GOARCH=amd64 go build -o go-rag-server
40 |
41 | run-by-docker:
42 | docker compose -f docker-compose.yml up -d
43 |
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-rag
2 | 基于eino+gf+vue实现知识库的rag
3 | 1. 创建知识库
4 | 
5 | 2. 选择需要使用的知识库,上传文档
6 | 
7 | 
8 | 3. 检索
9 | 
10 | 4. 对话
11 | 
12 | 5. mcp (以集成到deepchat为例)
13 | 
14 | 
15 |
16 |
17 | ## 存储层
18 | - [x] es8存储向量相关数据
19 |
20 | ## 功能列表
21 | - [x] md、pdf、html 文档解析
22 | - [x] 网页解析
23 | - [x] 文档检索
24 | - [x] 长文档自动切割(chunk)
25 | - [x] 提供http接口 [rag-api](./server/README.md)
26 | - [x] 提供 index、retrieve、chat 的前端界面
27 | - [x] 多知识库支持
28 |
29 |
30 | ## 未来计划
31 | - [ ] 使用mysql存储chunk和文档的映射关系,目前放在es的ext字段
32 |
33 | ## 使用
34 | ### clone项目
35 | ```bash
36 | git clone https://github.com/wangle201210/go-rag.git
37 | ```
38 |
39 | ### 使用 Docker Compose 快速启动(推荐)
40 | ```bash
41 | cp server/manifest/config/config.example.yaml server/manifest/config/config.yaml
42 | # 修改配置文件中的embedding、chat的配置
43 | docker compose up -d
44 | # 浏览器打开 http://localhost:8000
45 | ```
46 |
47 | ### 使用源码启动
48 | 如果有可用的es8和mysql,可以直接快速启动项目,否则需要先安装es8和mysql
49 | 需要修改`config.yaml`文件的相关配置
50 | ```bash
51 | cp server/manifest/config/config.example.yaml server/manifest/config/config.yaml
52 | # 修改配置文件中的embedding、chat、mysql、es的配置
53 | make build
54 | make run
55 | # 浏览器打开 http://localhost:8000
56 | ````
57 |
58 | ### 安装依赖
59 | *如果有可用的es8和mysql,可以不用安装*
60 | 安装es8
61 | ```bash
62 | docker run -d --name elasticsearch \
63 | -e "discovery.type=single-node" \
64 | -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
65 | -e "xpack.security.enabled=false" \
66 | -p 9200:9200 \
67 | -p 9300:9300 \
68 | elasticsearch:8.18.0
69 | ```
70 | 安装mysql
71 | ```bash
72 | docker run -p 3306:3306 --name mysql \
73 | -v /Users/wanna/docker/mysql/log:/var/log/mysql \
74 | -v /Users/wanna/docker/mysql/data:/var/lib/mysql \
75 | --restart=always \
76 | -e MYSQL_ROOT_PASSWORD=123456 \
77 | -e MYSQL_DATABASE=go-rag \
78 | -d mysql:8.0
79 | ```
80 |
81 | ### 运行 api 项目
82 |
83 | ```bash
84 | cd server
85 | go mod tidy
86 | go run main.go
87 | ```
88 |
89 | ### 运行前端项目
90 |
91 | ```bash
92 | cd fe
93 | npm install
94 | npm run dev
95 | ```
96 |
97 | ## 使用Makefile构建
98 |
99 | - 构建前端并将产物复制到server/static/fe目录 `make build-fe`
100 |
101 | - 构建后端 `make build-server`
102 |
103 | - 构建整个项目(前端+后端)`make build`
104 |
105 | - 清理构建产物 `make clean`
106 |
107 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | go-rag:
5 | image: iwangle/go-rag:v0.0.1
6 | # build:
7 | # context: .
8 | # dockerfile: Dockerfile
9 | ports:
10 | - "8000:8000"
11 | restart: unless-stopped
12 | environment:
13 | - TZ=Asia/Shanghai
14 | - ES_HOST=elasticsearch
15 | - ES_PORT=9200
16 | - MYSQL_HOST=mysql
17 | - MYSQL_PORT=3306
18 | - MYSQL_USER=root
19 | - MYSQL_PASSWORD=123456
20 | - MYSQL_DATABASE=go-rag
21 | volumes:
22 | # 如果需要持久化配置或数据,可以添加相应的卷挂载,目前是在构建镜像时就copy过去的
23 | - ./server/manifest:/app/manifest
24 | depends_on:
25 | - elasticsearch
26 | - mysql
27 |
28 | elasticsearch:
29 | image: elasticsearch:8.11.3
30 | environment:
31 | - discovery.type=single-node
32 | - xpack.security.enabled=false
33 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
34 | ports:
35 | - "9200:9200"
36 | volumes:
37 | - ./data/es_data:/usr/share/elasticsearch/data
38 | restart: unless-stopped
39 |
40 | mysql:
41 | image: mysql:8.0
42 | environment:
43 | - MYSQL_ROOT_PASSWORD=123456
44 | - MYSQL_DATABASE=go-rag
45 | - MYSQL_ROOT_HOST=% # 允许root从任意主机连接
46 | ports:
47 | - "3306:3306"
48 | volumes:
49 | - ./data/mysql_data:/var/lib/mysql
50 | restart: unless-stopped
51 |
--------------------------------------------------------------------------------
/fe/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
--------------------------------------------------------------------------------
/fe/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Go-RAG - 基于检索增强生成的智能问答系统
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/fe/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "go-rag-fe",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "vue": "^3.3.4",
12 | "element-plus": "^2.9.11",
13 | "axios": "^1.4.0",
14 | "vue-router": "^4.2.4",
15 | "@element-plus/icons-vue": "^2.1.0",
16 | "uuid": "^9.0.0",
17 | "marked": "^4.3.0",
18 | "highlight.js": "^11.8.0",
19 | "dompurify": "^3.0.5"
20 | },
21 | "devDependencies": {
22 | "@vitejs/plugin-vue": "^4.2.3",
23 | "vite": "^4.4.6",
24 | "sass": "^1.64.1"
25 | }
26 | }
--------------------------------------------------------------------------------
/fe/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Go-RAG
7 |
8 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
58 |
59 |
--------------------------------------------------------------------------------
/fe/src/assets/main.css:
--------------------------------------------------------------------------------
1 | /* 全局样式 */
2 | * {
3 | margin: 0;
4 | padding: 0;
5 | box-sizing: border-box;
6 | }
7 |
8 | html, body {
9 | height: 100%;
10 | font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | color: #2c3e50;
14 | background-color: #f5f7fa;
15 | }
16 |
17 | #app {
18 | height: 100%;
19 | }
20 |
21 | /* 滚动条样式 */
22 | ::-webkit-scrollbar {
23 | width: 6px;
24 | height: 6px;
25 | }
26 |
27 | ::-webkit-scrollbar-track {
28 | background: #f1f1f1;
29 | border-radius: 3px;
30 | }
31 |
32 | ::-webkit-scrollbar-thumb {
33 | background: #c0c4cc;
34 | border-radius: 3px;
35 | }
36 |
37 | ::-webkit-scrollbar-thumb:hover {
38 | background: #909399;
39 | }
40 |
41 | /* 常用工具类 */
42 | .text-center {
43 | text-align: center;
44 | }
45 |
46 | .text-right {
47 | text-align: right;
48 | }
49 |
50 | .flex-center {
51 | display: flex;
52 | justify-content: center;
53 | align-items: center;
54 | }
55 |
56 | .mb-10 {
57 | margin-bottom: 10px;
58 | }
59 |
60 | .mt-10 {
61 | margin-top: 10px;
62 | }
63 |
64 | .ml-10 {
65 | margin-left: 10px;
66 | }
67 |
68 | .mr-10 {
69 | margin-right: 10px;
70 | }
71 |
72 | .p-10 {
73 | padding: 10px;
74 | }
75 |
76 | /* 动画效果 */
77 | .fade-enter-active,
78 | .fade-leave-active {
79 | transition: opacity 0.3s ease;
80 | }
81 |
82 | .fade-enter-from,
83 | .fade-leave-to {
84 | opacity: 0;
85 | }
--------------------------------------------------------------------------------
/fe/src/components/KnowledgeNameSetting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 | 知识库设置
13 |
14 |
15 |
16 |
17 |
知识库设置
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 保存
34 | 取消
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
74 |
75 |
--------------------------------------------------------------------------------
/fe/src/components/KnowledgeSelector.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 | 知识库设置
14 |
15 |
16 |
17 |
18 |
知识库设置
19 |
20 |
21 |
33 |
40 |
41 | {{ item.name }}
42 |
43 | {{ item.status === 2 ? '禁用' : '启用' }}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
描述: {{ selectedKnowledge.description }}
52 |
分类: {{ selectedKnowledge.category }}
53 |
54 |
55 |
56 | 确认
57 | 取消
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
166 |
167 |
--------------------------------------------------------------------------------
/fe/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import ElementPlus from 'element-plus'
3 | import 'element-plus/dist/index.css'
4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'
5 | import App from './App.vue'
6 | import router from './router'
7 |
8 | // 导入全局样式
9 | import './assets/main.css'
10 |
11 | const app = createApp(App)
12 |
13 | // 注册所有图标
14 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
15 | app.component(key, component)
16 | }
17 |
18 | app.use(ElementPlus)
19 | app.use(router)
20 | app.mount('#app')
--------------------------------------------------------------------------------
/fe/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory } from 'vue-router'
2 |
3 | const routes = [
4 | {
5 | path: '/',
6 | redirect: '/indexer'
7 | },
8 | {
9 | path: '/indexer',
10 | name: 'Indexer',
11 | component: () => import('../views/Indexer.vue')
12 | },
13 | {
14 | path: '/retriever',
15 | name: 'Retriever',
16 | component: () => import('../views/Retriever.vue')
17 | },
18 | {
19 | path: '/chat',
20 | name: 'Chat',
21 | component: () => import('../views/Chat.vue')
22 | },
23 | {
24 | path: '/knowledge-base',
25 | name: 'KnowledgeBase',
26 | component: () => import('../views/KnowledgeBase.vue')
27 | }
28 | ]
29 |
30 | const router = createRouter({
31 | history: createWebHashHistory(),
32 | routes
33 | })
34 |
35 | export default router
--------------------------------------------------------------------------------
/fe/src/utils/knowledgeIdStore.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 知识库ID管理服务
3 | * 用于在不同页面之间共享选中的知识库ID
4 | * 使用localStorage存储,确保页面刷新后仍然可用
5 | */
6 |
7 | const STORAGE_KEY = 'go_rag_selected_knowledge_id'
8 |
9 | /**
10 | * 获取当前选中的知识库ID
11 | * 如果不存在,则返回空字符串
12 | */
13 | export function getSelectedKnowledgeId() {
14 | return localStorage.getItem(STORAGE_KEY) || ''
15 | }
16 |
17 | /**
18 | * 设置选中的知识库ID
19 | */
20 | export function setSelectedKnowledgeId(id) {
21 | localStorage.setItem(STORAGE_KEY, id)
22 | return id
23 | }
24 |
25 | /**
26 | * 清除选中的知识库ID
27 | */
28 | export function clearSelectedKnowledgeId() {
29 | localStorage.removeItem(STORAGE_KEY)
30 | }
--------------------------------------------------------------------------------
/fe/src/utils/knowledgeStore.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 知识库名称管理服务
3 | * 用于在不同页面之间共享knowledge_name参数
4 | * 使用localStorage存储,确保页面刷新后仍然可用
5 | */
6 |
7 | import { v4 as uuidv4 } from 'uuid'
8 |
9 | const STORAGE_KEY = 'go_rag_knowledge_name'
10 |
11 | /**
12 | * 获取当前知识库名称
13 | * 如果不存在,则自动生成一个新的
14 | */
15 | export function getKnowledgeName() {
16 | let knowledgeName = localStorage.getItem(STORAGE_KEY)
17 |
18 | // 如果不存在,则生成一个新的知识库名称
19 | if (!knowledgeName) {
20 | knowledgeName = generateKnowledgeName()
21 | localStorage.setItem(STORAGE_KEY, knowledgeName)
22 | }
23 |
24 | return knowledgeName
25 | }
26 |
27 | /**
28 | * 设置知识库名称
29 | */
30 | export function setKnowledgeName(name) {
31 | localStorage.setItem(STORAGE_KEY, name)
32 | return name
33 | }
34 |
35 | /**
36 | * 生成一个新的知识库名称
37 | * 使用UUID确保唯一性
38 | */
39 | export function generateKnowledgeName() {
40 | return `knowledge_${uuidv4().substring(0, 8)}`
41 | }
--------------------------------------------------------------------------------
/fe/src/views/Chat.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
40 |
41 |
42 |
{{ message.content }}
43 |
44 |
{{ formatTime(message.timestamp) }}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
120 |
121 |
122 |
123 | 会话ID:
124 | {{ sessionId }}
125 |
126 |
131 |
132 |
133 |
134 |
135 |
136 | 消息数:
137 | {{ messages.length }}
138 |
139 |
140 |
141 |
142 |
143 |
参考文档
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
156 |
157 |
158 | {{ ref.meta_data.ext._file_name || '未知来源' }}
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
431 |
432 |
--------------------------------------------------------------------------------
/fe/src/views/Indexer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
24 |
25 |
26 | 拖拽文件到此处或 点击上传
27 |
28 |
29 |
30 | 支持上传 PDF、Markdown、HTML 等文档文件
31 |
32 |
33 |
34 |
35 |
36 |
37 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
53 |
54 |
55 | {{ indexResult.chunks }}
56 |
57 |
58 | {{ indexResult.status === 'success' ? '成功' : '失败' }}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
132 |
133 |
--------------------------------------------------------------------------------
/fe/src/views/KnowledgeBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {{ scope.row.status === 2 ? '禁用': '启用' }}
34 |
35 |
36 |
37 |
38 |
39 |
44 | 编辑
45 |
46 |
51 | 删除
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
73 |
78 |
79 |
80 |
81 |
82 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | 启用
94 | 禁用
95 |
96 |
97 |
98 |
99 |
105 |
106 |
107 |
108 |
109 |
110 |
266 |
267 |
--------------------------------------------------------------------------------
/fe/src/views/Retriever.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 | 检索
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
38 |
39 |
40 |
41 |
42 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
67 |
68 |
69 |
74 |
75 |
76 |
77 | {{ result.meta_data.ext._file_name || '未知来源' }}
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
169 |
170 |
--------------------------------------------------------------------------------
/fe/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import path from 'path'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [vue()],
8 | resolve: {
9 | alias: {
10 | '@': path.resolve(__dirname, 'src'),
11 | },
12 | },
13 | server: {
14 | proxy: {
15 | '/v1': {
16 | target: 'http://localhost:8000',
17 | changeOrigin: true
18 | }
19 | }
20 | }
21 | })
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # go-rag api
2 | rag api 项目
3 |
--------------------------------------------------------------------------------
/server/api/rag/rag.go:
--------------------------------------------------------------------------------
1 | // =================================================================================
2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
3 | // =================================================================================
4 |
5 | package rag
6 |
7 | import (
8 | "context"
9 |
10 | "github.com/wangle201210/go-rag/server/api/rag/v1"
11 | )
12 |
13 | type IRagV1 interface {
14 | Chat(ctx context.Context, req *v1.ChatReq) (res *v1.ChatRes, err error)
15 | ChatStream(ctx context.Context, req *v1.ChatStreamReq) (res *v1.ChatStreamRes, err error)
16 | Indexer(ctx context.Context, req *v1.IndexerReq) (res *v1.IndexerRes, err error)
17 | KBCreate(ctx context.Context, req *v1.KBCreateReq) (res *v1.KBCreateRes, err error)
18 | KBUpdate(ctx context.Context, req *v1.KBUpdateReq) (res *v1.KBUpdateRes, err error)
19 | KBDelete(ctx context.Context, req *v1.KBDeleteReq) (res *v1.KBDeleteRes, err error)
20 | KBGetOne(ctx context.Context, req *v1.KBGetOneReq) (res *v1.KBGetOneRes, err error)
21 | KBGetList(ctx context.Context, req *v1.KBGetListReq) (res *v1.KBGetListRes, err error)
22 | Retriever(ctx context.Context, req *v1.RetrieverReq) (res *v1.RetrieverRes, err error)
23 | }
24 |
--------------------------------------------------------------------------------
/server/api/rag/v1/chat.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "github.com/cloudwego/eino/schema"
5 | "github.com/gogf/gf/v2/frame/g"
6 | )
7 |
8 | type ChatReq struct {
9 | g.Meta `path:"/v1/chat" method:"post" tags:"rag"`
10 | ConvID string `json:"conv_id" v:"required"` // 会话id
11 | Question string `json:"question" v:"required"`
12 | KnowledgeName string `json:"knowledge_name" v:"required"`
13 | TopK int `json:"top_k"` // 默认为5
14 | Score float64 `json:"score"` // 默认为0.2
15 | }
16 |
17 | type ChatRes struct {
18 | g.Meta `mime:"application/json"`
19 | Answer string `json:"answer"`
20 | References []*schema.Document `json:"references"`
21 | }
22 |
23 | // ChatStreamReq 流式输出请求
24 | type ChatStreamReq struct {
25 | g.Meta `path:"/v1/chat/stream" method:"post" tags:"rag"`
26 | ConvID string `json:"conv_id" v:"required"` // 会话id
27 | Question string `json:"question" v:"required"`
28 | KnowledgeName string `json:"knowledge_name" v:"required"`
29 | TopK int `json:"top_k"` // 默认为5
30 | Score float64 `json:"score"` // 默认为0.2
31 | }
32 |
33 | // ChatStreamRes 流式输出响应
34 | type ChatStreamRes struct {
35 | g.Meta `mime:"text/event-stream"`
36 | // 流式输出不需要返回具体内容,内容通过HTTP响应流返回
37 | }
38 |
--------------------------------------------------------------------------------
/server/api/rag/v1/indexer.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "github.com/gogf/gf/v2/frame/g"
5 | "github.com/gogf/gf/v2/net/ghttp"
6 | )
7 |
8 | type IndexerReq struct {
9 | g.Meta `path:"/v1/indexer" method:"post" mime:"multipart/form-data" tags:"rag"`
10 | File *ghttp.UploadFile `p:"file" type:"file" dc:"如果是本地文件,怎上传文件"`
11 | URL string `p:"url" dc:"如果是网络文件则直接输入url即可"`
12 | KnowledgeName string `p:"knowledge_name" dc:"知识库名称" v:"required"`
13 | }
14 |
15 | type IndexerRes struct {
16 | g.Meta `mime:"application/json"`
17 | DocIDs []string `json:"doc_ids"`
18 | }
19 |
--------------------------------------------------------------------------------
/server/api/rag/v1/knowledge_base.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "github.com/gogf/gf/v2/frame/g"
5 | "github.com/wangle201210/go-rag/server/internal/model/entity"
6 | )
7 |
8 | // Status marks kb status.
9 | type Status int
10 |
11 | const (
12 | StatusOK Status = 1
13 | StatusDisabled Status = 2
14 | )
15 |
16 | type KBCreateReq struct {
17 | g.Meta `path:"/v1/kb" method:"post" tags:"kb" summary:"Create kb"`
18 | Name string `v:"required|length:3,20" dc:"kb name"`
19 | Description string `v:"required|length:3,200" dc:"kb description"`
20 | Category string `v:"length:3,10" dc:"kb category"`
21 | }
22 |
23 | type KBCreateRes struct {
24 | Id int64 `json:"id" dc:"kb id"`
25 | }
26 |
27 | type KBUpdateReq struct {
28 | g.Meta `path:"/v1/kb/{id}" method:"put" tags:"kb" summary:"Update kb"`
29 | Id int64 `v:"required" dc:"kb id"`
30 | Name *string `v:"length:3,10" dc:"kb name"`
31 | Description *string `v:"length:3,200" dc:"kb description"`
32 | Category *string `v:"length:3,10" dc:"kb category"`
33 | Status *Status `v:"in:1,2" dc:"kb status"`
34 | }
35 | type KBUpdateRes struct{}
36 |
37 | type KBDeleteReq struct {
38 | g.Meta `path:"/v1/kb/{id}" method:"delete" tags:"kb" summary:"Delete kb"`
39 | Id int64 `v:"required" dc:"kb id"`
40 | }
41 | type KBDeleteRes struct{}
42 |
43 | type KBGetOneReq struct {
44 | g.Meta `path:"/v1/kb/{id}" method:"get" tags:"kb" summary:"Get one kb"`
45 | Id int64 `v:"required" dc:"kb id"`
46 | }
47 | type KBGetOneRes struct {
48 | *entity.KnowledgeBase `dc:"kb"`
49 | }
50 |
51 | type KBGetListReq struct {
52 | g.Meta `path:"/v1/kb" method:"get" tags:"kb" summary:"Get kbs"`
53 | Name *string `v:"length:3,10" dc:"kb name"`
54 | Status *Status `v:"in:1,2" dc:"kb age"`
55 | Category *string `v:"length:3,10" dc:"kb category"`
56 | }
57 |
58 | type KBGetListRes struct {
59 | List []*entity.KnowledgeBase `json:"list" dc:"kb list"`
60 | }
61 |
--------------------------------------------------------------------------------
/server/api/rag/v1/retriever.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "github.com/cloudwego/eino/schema"
5 | "github.com/gogf/gf/v2/frame/g"
6 | )
7 |
8 | type RetrieverReq struct {
9 | g.Meta `path:"/v1/retriever" method:"post" tags:"rag"`
10 | Question string `json:"question" v:"required"`
11 | TopK int `json:"top_k"` // 默认为5
12 | Score float64 `json:"score"` // 默认为0.2
13 | KnowledgeName string `json:"knowledge_name" v:"required"`
14 | }
15 |
16 | type RetrieverRes struct {
17 | g.Meta `mime:"application/json"`
18 | Document []*schema.Document `json:"document"`
19 | }
20 |
--------------------------------------------------------------------------------
/server/core/common/chat_model.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/cloudwego/eino-ext/components/model/openai"
7 | "github.com/cloudwego/eino/components/model"
8 | )
9 |
10 | var chatModel model.BaseChatModel
11 |
12 | func GetChatModel(ctx context.Context, cfg *openai.ChatModelConfig) (model.BaseChatModel, error) {
13 | if chatModel != nil {
14 | return chatModel, nil
15 | }
16 | cm, err := openai.NewChatModel(ctx, cfg)
17 | if err != nil {
18 | return nil, err
19 | }
20 | chatModel = cm
21 | return cm, nil
22 | }
23 |
--------------------------------------------------------------------------------
/server/core/common/consts.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | const (
4 | FieldContent = "content"
5 | FieldContentVector = "content_vector"
6 | FieldExtra = "ext"
7 | DocExtra = "ext"
8 | KnowledgeName = "_knowledge_name"
9 |
10 | Title1 = "h1"
11 | Title2 = "h2"
12 | Title3 = "h3"
13 | )
14 |
--------------------------------------------------------------------------------
/server/core/common/embedding.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "context"
5 | "os"
6 |
7 | "github.com/cloudwego/eino-ext/components/embedding/openai"
8 | "github.com/cloudwego/eino/components/embedding"
9 | "github.com/wangle201210/go-rag/server/core/config"
10 | )
11 |
12 | func NewEmbedding(ctx context.Context, conf *config.Config) (eb embedding.Embedder, err error) {
13 | econf := &openai.EmbeddingConfig{
14 | APIKey: conf.APIKey,
15 | Model: conf.EmbeddingModel,
16 | Dimensions: Of(1024),
17 | Timeout: 0,
18 | BaseURL: conf.BaseURL,
19 | }
20 | if econf.APIKey == "" {
21 | econf.APIKey = os.Getenv("OPENAI_API_KEY")
22 | }
23 | if econf.BaseURL == "" {
24 | econf.BaseURL = os.Getenv("OPENAI_BASE_URL")
25 | }
26 | if econf.Model == "" {
27 | econf.Model = "text-embedding-3-large"
28 | }
29 | eb, err = openai.NewEmbedder(ctx, econf)
30 | if err != nil {
31 | return nil, err
32 | }
33 | return eb, nil
34 | }
35 |
--------------------------------------------------------------------------------
/server/core/common/es.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/elastic/go-elasticsearch/v8"
7 | "github.com/elastic/go-elasticsearch/v8/typedapi/indices/create"
8 | "github.com/elastic/go-elasticsearch/v8/typedapi/indices/exists"
9 | "github.com/elastic/go-elasticsearch/v8/typedapi/types"
10 | )
11 |
12 | // createIndex create index for example in add_documents.go.
13 | func createIndex(ctx context.Context, client *elasticsearch.Client, indexName string) error {
14 | _, err := create.NewCreateFunc(client)(indexName).Request(&create.Request{
15 | Mappings: &types.TypeMapping{
16 | Properties: map[string]types.Property{
17 | FieldContent: types.NewTextProperty(),
18 | FieldExtra: types.NewTextProperty(),
19 | KnowledgeName: types.NewKeywordProperty(),
20 | FieldContentVector: &types.DenseVectorProperty{
21 | Dims: Of(1024), // same as embedding dimensions
22 | Index: Of(true),
23 | Similarity: Of("cosine"),
24 | },
25 | },
26 | },
27 | }).Do(ctx)
28 |
29 | return err
30 | }
31 |
32 | func CreateIndexIfNotExists(ctx context.Context, client *elasticsearch.Client, indexName string) error {
33 | indexExists, err := exists.NewExistsFunc(client)(indexName).Do(ctx)
34 | if err != nil {
35 | return err
36 | }
37 | if indexExists {
38 | return nil
39 | }
40 | err = createIndex(ctx, client, indexName)
41 | return err
42 | }
43 |
--------------------------------------------------------------------------------
/server/core/common/helper.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import "net/url"
4 |
5 | func Of[T any](v T) *T {
6 | return &v
7 | }
8 |
9 | func IsURL(str string) bool {
10 | u, err := url.Parse(str)
11 | if err != nil {
12 | return false
13 | }
14 | return u.Scheme != "" && u.Host != ""
15 | }
16 |
--------------------------------------------------------------------------------
/server/core/common/stream.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import "github.com/cloudwego/eino/schema"
4 |
5 | type StreamData struct {
6 | Id string `json:"id"` // 同一个消息里面的id是相同的
7 | Created int64 `json:"created"` // 消息初始生成时间
8 | Content string `json:"content"` // 消息具体内容
9 | Document []*schema.Document `json:"document"`
10 | }
11 |
--------------------------------------------------------------------------------
/server/core/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/cloudwego/eino-ext/components/model/openai"
5 | "github.com/elastic/go-elasticsearch/v8"
6 | )
7 |
8 | type Config struct {
9 | Client *elasticsearch.Client
10 | IndexName string // es index name
11 | // embedding 时使用
12 | APIKey string
13 | BaseURL string
14 | EmbeddingModel string
15 | ChatModel string
16 | }
17 |
18 | func (x *Config) GetChatModelConfig() *openai.ChatModelConfig {
19 | return &openai.ChatModelConfig{
20 | APIKey: x.APIKey,
21 | BaseURL: x.BaseURL,
22 | Model: x.ChatModel,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/server/core/grader/grader.go:
--------------------------------------------------------------------------------
1 | package grader
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/cloudwego/eino/components/model"
9 | "github.com/cloudwego/eino/schema"
10 | "github.com/gogf/gf/v2/frame/g"
11 | )
12 |
13 | type Grader struct {
14 | cm model.BaseChatModel
15 | }
16 |
17 | func NewGrader(cm model.BaseChatModel) *Grader {
18 | return &Grader{
19 | cm: cm,
20 | }
21 | }
22 |
23 | // Retriever 检查下检索到的结果是否能够回答当前问题
24 | func (x *Grader) Retriever(ctx context.Context, docs []*schema.Document, question string) (pass bool, err error) {
25 | messages, err := retrieverMessages(docs, question)
26 | if err != nil {
27 | return
28 | }
29 | result, err := x.cm.Generate(ctx, messages)
30 | if err != nil {
31 | return false, fmt.Errorf("检查下检索到的结果是否能够回答当前问题失败: %v", err)
32 | }
33 | pass = isPass(result.Content)
34 | return
35 | }
36 |
37 | func (x *Grader) Related(ctx context.Context, doc *schema.Document, question string) (pass bool, err error) {
38 | messages, err := docRelatedMessages(doc, question)
39 | if err != nil {
40 | return
41 | }
42 | result, err := x.cm.Generate(ctx, messages)
43 | if err != nil {
44 | return false, fmt.Errorf("检查下检索到的结果是否和用户问题相关失败: %v", err)
45 | }
46 | pass = isPass(result.Content)
47 | return
48 | }
49 |
50 | func isPass(msg string) bool {
51 | g.Log().Infof(context.Background(), "isPass: %s", msg)
52 | msg = strings.ToLower(msg)
53 | return strings.Contains(msg, "yes")
54 | }
55 |
--------------------------------------------------------------------------------
/server/core/grader/message.go:
--------------------------------------------------------------------------------
1 | package grader
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/cloudwego/eino/components/prompt"
8 | "github.com/cloudwego/eino/schema"
9 | )
10 |
11 | // createRetrieverTemplate 判断检索到的文档是否足够回答用户问题
12 | func createRetrieverTemplate() prompt.ChatTemplate {
13 | // 创建模板,使用 FString 格式
14 | return prompt.FromMessages(schema.FString,
15 | // 系统消息模板
16 | schema.SystemMessage(
17 | "您是一名评估检索到的文档是否足够回答用户问题的专家。"+
18 | "请先仔细理解用户问题"+
19 | "如果检索到的文档足够回答用户问题,请给出 'yes',"+
20 | "如果检索到的文档不足以回答用户问题,请给出 'no'。"+
21 | "不要给出任何其他解释。",
22 | ),
23 | // 用户消息模板
24 | schema.UserMessage(
25 | "这是检索到的文档: \n"+
26 | "{document} \n\n"+
27 | "这是用户的问题: {question}"),
28 | )
29 | }
30 |
31 | // createDocRelatedTemplate 判断检索到的文档是否和用户问题相关
32 | func createDocRelatedTemplate() prompt.ChatTemplate {
33 | // 创建模板,使用 FString 格式
34 | return prompt.FromMessages(schema.FString,
35 | // 系统消息模板
36 | schema.SystemMessage(
37 | "您是一名评估检索到的文档是否和用户问题相关的专家。"+
38 | "这里不需要是一个严格的测试,目标是过滤掉错误的检索。"+
39 | "如果检索到的文档和用户问题相关,请给出 'yes',"+
40 | "如果检索到的文档和用户问题不相关,请给出 'no'。"+
41 | "不要给出任何其他解释。",
42 | ),
43 | // 用户消息模板
44 | schema.UserMessage(
45 | "<|start_documents|> \n"+
46 | "{document} <|end_documents|>\n"+
47 | "<|start_query|>{question}<|end_query|>"),
48 | )
49 | }
50 |
51 | // formatMessages 格式化消息并处理错误
52 | func formatMessages(template prompt.ChatTemplate, data map[string]any) ([]*schema.Message, error) {
53 | messages, err := template.Format(context.Background(), data)
54 | if err != nil {
55 | return nil, fmt.Errorf("格式化模板失败: %w", err)
56 | }
57 | return messages, nil
58 | }
59 |
60 | func retrieverMessages(docs []*schema.Document, question string) ([]*schema.Message, error) {
61 | document := ""
62 | for i, doc := range docs {
63 | document += fmt.Sprintf("docs[%d]: %s", i, doc.Content)
64 | }
65 | template := createRetrieverTemplate()
66 | data := map[string]any{
67 | "question": question,
68 | "document": document,
69 | }
70 | messages, err := formatMessages(template, data)
71 | if err != nil {
72 | return nil, err
73 | }
74 | return messages, nil
75 | }
76 |
77 | func docRelatedMessages(doc *schema.Document, question string) ([]*schema.Message, error) {
78 | template := createDocRelatedTemplate()
79 | data := map[string]any{
80 | "question": question,
81 | "document": doc,
82 | }
83 | messages, err := formatMessages(template, data)
84 | if err != nil {
85 | return nil, err
86 | }
87 | return messages, nil
88 | }
89 |
--------------------------------------------------------------------------------
/server/core/indexer/indexer.go:
--------------------------------------------------------------------------------
1 | package indexer
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/bytedance/sonic"
8 | "github.com/cloudwego/eino-ext/components/indexer/es8"
9 | "github.com/cloudwego/eino/components/indexer"
10 | "github.com/cloudwego/eino/schema"
11 | "github.com/google/uuid"
12 | "github.com/wangle201210/go-rag/server/core/common"
13 | "github.com/wangle201210/go-rag/server/core/config"
14 | )
15 |
16 | // newIndexer component initialization function of node 'Indexer2' in graph 'rag'
17 | func newIndexer(ctx context.Context, conf *config.Config) (idr indexer.Indexer, err error) {
18 | indexerConfig := &es8.IndexerConfig{
19 | Client: conf.Client,
20 | Index: conf.IndexName,
21 | BatchSize: 10,
22 | DocumentToFields: func(ctx context.Context, doc *schema.Document) (field2Value map[string]es8.FieldValue, err error) {
23 | var knowledgeName string
24 | if value, ok := ctx.Value(common.KnowledgeName).(string); ok {
25 | knowledgeName = value
26 | } else {
27 | err = fmt.Errorf("必须提供知识库名称")
28 | return
29 | }
30 | doc.ID = uuid.New().String()
31 | if doc.MetaData != nil {
32 | marshal, _ := sonic.Marshal(doc.MetaData)
33 | doc.MetaData[common.DocExtra] = string(marshal)
34 | }
35 | return map[string]es8.FieldValue{
36 | common.FieldContent: {
37 | Value: getMdContentWithTitle(doc),
38 | EmbedKey: common.FieldContentVector, // vectorize doc content and save vector to field "content_vector"
39 | },
40 | common.FieldExtra: {
41 | Value: doc.MetaData[common.DocExtra],
42 | },
43 | common.KnowledgeName: {
44 | Value: knowledgeName,
45 | },
46 | }, nil
47 | },
48 | }
49 | embeddingIns11, err := common.NewEmbedding(ctx, conf)
50 | if err != nil {
51 | return nil, err
52 | }
53 | indexerConfig.Embedding = embeddingIns11
54 | idr, err = es8.NewIndexer(ctx, indexerConfig)
55 | if err != nil {
56 | return nil, err
57 | }
58 | return idr, nil
59 | }
60 |
61 | func getMdContentWithTitle(doc *schema.Document) string {
62 | if doc.MetaData == nil {
63 | return doc.Content
64 | }
65 | title := ""
66 | list := []string{"h1", "h2", "h3", "h4", "h5", "h6"}
67 | for _, v := range list {
68 | if d, e := doc.MetaData[v].(string); e && len(d) > 0 {
69 | title += fmt.Sprintf("%s:%s ", v, d)
70 | }
71 | }
72 | if len(title) == 0 {
73 | return doc.Content
74 | }
75 | return title + "\n" + doc.Content
76 | }
77 |
--------------------------------------------------------------------------------
/server/core/indexer/lambda_func.go:
--------------------------------------------------------------------------------
1 | package indexer
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/cloudwego/eino-ext/components/document/loader/file"
8 | "github.com/cloudwego/eino/schema"
9 | "github.com/wangle201210/go-rag/server/core/common"
10 | )
11 |
12 | // newLambda component initialization function of node 'Lambda1' in graph 't'
13 | func newLambda(ctx context.Context, docs []*schema.Document) (output []*schema.Document, err error) {
14 | // 不是md文档不处理
15 | if len(docs) == 0 || docs[0].MetaData[file.MetaKeyExtension] != ".md" {
16 | return docs, nil
17 | }
18 | ndocs := make([]*schema.Document, 0, len(docs))
19 | var nd *schema.Document
20 | maxLen := 512
21 | for _, doc := range docs {
22 | // 不是同一个文件的就不要放一起了
23 | if nd != nil && doc.MetaData[file.MetaKeySource] != nd.MetaData[file.MetaKeySource] {
24 | ndocs = append(ndocs, nd)
25 | nd = nil
26 | }
27 | // 两个文档长度之和大于maxLen就不要放一起了
28 | if nd != nil && len(nd.Content)+len(doc.Content) > maxLen {
29 | ndocs = append(ndocs, nd)
30 | nd = nil
31 | }
32 | // 不是同一个一级标题的就不要放一起了
33 | if nd != nil && doc.MetaData[common.Title1] != nd.MetaData[common.Title1] {
34 | ndocs = append(ndocs, nd)
35 | nd = nil
36 | }
37 | // 不是同一个二级标题的就不要放一起了
38 | // 如果nd的h2是nil,证明之前只有h1,且两个的h1相等,则直接合并
39 | if nd != nil && nd.MetaData[common.Title2] != nil && doc.MetaData[common.Title2] != nd.MetaData[common.Title2] {
40 | ndocs = append(ndocs, nd)
41 | nd = nil
42 | }
43 | if nd == nil {
44 | nd = doc
45 | } else {
46 | mergeTitle(nd, doc, common.Title2)
47 | mergeTitle(nd, doc, common.Title3)
48 | nd.Content += doc.Content
49 | }
50 | }
51 | if nd != nil {
52 | ndocs = append(ndocs, nd)
53 | }
54 | return ndocs, nil
55 | }
56 |
57 | func mergeTitle(orgDoc, addDoc *schema.Document, key string) {
58 | // 相等就不管了
59 | if orgDoc.MetaData[key] == addDoc.MetaData[key] {
60 | return
61 | }
62 | var title []string
63 | if orgDoc.MetaData[key] != nil {
64 | title = append(title, orgDoc.MetaData[key].(string))
65 | }
66 | if addDoc.MetaData[key] != nil {
67 | title = append(title, addDoc.MetaData[key].(string))
68 | }
69 | if len(title) > 0 {
70 | orgDoc.MetaData[key] = strings.Join(title, ",")
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/server/core/indexer/loader.go:
--------------------------------------------------------------------------------
1 | package indexer
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/cloudwego/eino-ext/components/document/loader/file"
7 | "github.com/cloudwego/eino-ext/components/document/loader/url"
8 | "github.com/cloudwego/eino/components/document"
9 | "github.com/cloudwego/eino/schema"
10 | "github.com/wangle201210/go-rag/server/core/common"
11 | )
12 |
13 | // newLoader component initialization function of node 'Loader1' in graph 'rag'
14 | func newLoader(ctx context.Context) (ldr document.Loader, err error) {
15 | mldr := &multiLoader{}
16 | parser, err := newParser(ctx)
17 | if err != nil {
18 | return nil, err
19 | }
20 | fldr, err := file.NewFileLoader(ctx, &file.FileLoaderConfig{
21 | UseNameAsID: true,
22 | Parser: parser,
23 | })
24 | if err != nil {
25 | return nil, err
26 | }
27 | mldr.fileLoader = fldr
28 | uldr, err := url.NewLoader(ctx, &url.LoaderConfig{})
29 | if err != nil {
30 | return nil, err
31 | }
32 | mldr.urlLoader = uldr
33 | return mldr, nil
34 | }
35 |
36 | type multiLoader struct {
37 | fileLoader document.Loader
38 | urlLoader document.Loader
39 | }
40 |
41 | func (x *multiLoader) Load(ctx context.Context, src document.Source, opts ...document.LoaderOption) ([]*schema.Document, error) {
42 | if common.IsURL(src.URI) {
43 | return x.urlLoader.Load(ctx, src, opts...)
44 | }
45 | return x.fileLoader.Load(ctx, src, opts...)
46 | }
47 |
--------------------------------------------------------------------------------
/server/core/indexer/orchestration.go:
--------------------------------------------------------------------------------
1 | package indexer
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/cloudwego/eino/compose"
7 | "github.com/wangle201210/go-rag/server/core/config"
8 | )
9 |
10 | func BuildIndexer(ctx context.Context, conf *config.Config) (r compose.Runnable[any, []string], err error) {
11 | const (
12 | Loader1 = "Loader1"
13 | Indexer2 = "Indexer2"
14 | DocumentTransformer3 = "DocumentTransformer3"
15 | Lambda1 = "Lambda1"
16 | )
17 |
18 | g := compose.NewGraph[any, []string]()
19 | loader1KeyOfLoader, err := newLoader(ctx)
20 | if err != nil {
21 | return nil, err
22 | }
23 | _ = g.AddLoaderNode(Loader1, loader1KeyOfLoader)
24 | indexer2KeyOfIndexer, err := newIndexer(ctx, conf)
25 | if err != nil {
26 | return nil, err
27 | }
28 | _ = g.AddIndexerNode(Indexer2, indexer2KeyOfIndexer)
29 | documentTransformer2KeyOfDocumentTransformer, err := newDocumentTransformer(ctx)
30 | if err != nil {
31 | return nil, err
32 | }
33 | _ = g.AddLambdaNode(Lambda1, compose.InvokableLambda(newLambda))
34 |
35 | _ = g.AddDocumentTransformerNode(DocumentTransformer3, documentTransformer2KeyOfDocumentTransformer)
36 | _ = g.AddEdge(compose.START, Loader1)
37 | _ = g.AddEdge(Loader1, DocumentTransformer3)
38 | _ = g.AddEdge(DocumentTransformer3, Lambda1)
39 | _ = g.AddEdge(Lambda1, Indexer2)
40 | _ = g.AddEdge(Indexer2, compose.END)
41 | r, err = g.Compile(ctx, compose.WithGraphName("indexer"))
42 | if err != nil {
43 | return nil, err
44 | }
45 | return r, err
46 | }
47 |
--------------------------------------------------------------------------------
/server/core/indexer/parser.go:
--------------------------------------------------------------------------------
1 | package indexer
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/cloudwego/eino-ext/components/document/parser/html"
7 | "github.com/cloudwego/eino-ext/components/document/parser/pdf"
8 | "github.com/cloudwego/eino/components/document/parser"
9 | "github.com/wangle201210/go-rag/server/core/common"
10 | )
11 |
12 | func newParser(ctx context.Context) (p parser.Parser, err error) {
13 | textParser := parser.TextParser{}
14 |
15 | htmlParser, err := html.NewParser(ctx, &html.Config{
16 | Selector: common.Of("body"),
17 | })
18 | if err != nil {
19 | return nil, err
20 | }
21 |
22 | pdfParser, err := pdf.NewPDFParser(ctx, &pdf.Config{})
23 | if err != nil {
24 | return
25 | }
26 |
27 | // 创建扩展解析器
28 | p, err = parser.NewExtParser(ctx, &parser.ExtParserConfig{
29 | // 注册特定扩展名的解析器
30 | Parsers: map[string]parser.Parser{
31 | ".html": htmlParser,
32 | ".pdf": pdfParser,
33 | },
34 | // 设置默认解析器,用于处理未知格式
35 | FallbackParser: textParser,
36 | })
37 | if err != nil {
38 | return nil, err
39 | }
40 | return
41 | }
42 |
--------------------------------------------------------------------------------
/server/core/indexer/transformer.go:
--------------------------------------------------------------------------------
1 | package indexer
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown"
7 | "github.com/cloudwego/eino-ext/components/document/transformer/splitter/recursive"
8 | "github.com/cloudwego/eino/components/document"
9 | "github.com/cloudwego/eino/schema"
10 | "github.com/wangle201210/go-rag/server/core/common"
11 | )
12 |
13 | // newDocumentTransformer component initialization function of node 'DocumentTransformer3' in graph 'rag'
14 | func newDocumentTransformer(ctx context.Context) (tfr document.Transformer, err error) {
15 | trans := &transformer{}
16 | // 递归分割
17 | config := &recursive.Config{
18 | ChunkSize: 1000, // 每段内容1000字
19 | OverlapSize: 100, // 有10%的重叠
20 | Separators: []string{"\n", "。", "?", "?", "!", "!"},
21 | }
22 | recTrans, err := recursive.NewSplitter(ctx, config)
23 | if err != nil {
24 | return nil, err
25 | }
26 | // md 文档特殊处理
27 | mdTrans, err := markdown.NewHeaderSplitter(ctx, &markdown.HeaderConfig{
28 | Headers: map[string]string{"#": common.Title1, "##": common.Title2, "###": common.Title3},
29 | TrimHeaders: false,
30 | })
31 | if err != nil {
32 | return nil, err
33 | }
34 | trans.recursive = recTrans
35 | trans.markdown = mdTrans
36 | return trans, nil
37 | }
38 |
39 | type transformer struct {
40 | markdown document.Transformer
41 | recursive document.Transformer
42 | }
43 |
44 | func (x *transformer) Transform(ctx context.Context, docs []*schema.Document, opts ...document.TransformerOption) ([]*schema.Document, error) {
45 | isMd := false
46 | for _, doc := range docs {
47 | // 只需要判断第一个是不是.md
48 | if doc.MetaData["_extension"] == ".md" {
49 | isMd = true
50 | break
51 | }
52 | }
53 | if isMd {
54 | return x.markdown.Transform(ctx, docs, opts...)
55 | }
56 | return x.recursive.Transform(ctx, docs, opts...)
57 | }
58 |
--------------------------------------------------------------------------------
/server/core/message.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/cloudwego/eino/components/prompt"
9 | "github.com/cloudwego/eino/schema"
10 | )
11 |
12 | var system = "你非常擅长于使用rag进行数据检索," +
13 | "你的目标是在充分理解用户的问题后进行向量化检索" +
14 | "现在时间{time_now}" +
15 | "你要提取并优化搜索的查询内容。" +
16 | "请遵循以下规则重写查询内容:\n " +
17 | "- 根据用户的问题和上下文,重写应该进行搜索的关键词\n" +
18 | "- 如果需要使用时间,则根据当前时间给出需要查询的具体时间日期信息\n" +
19 | // "- 生成的查询关键词要选择合适的语言,考虑用户的问题类型使用最适合的语言进行搜索,例如某些问题应该保持用户的问题语言,而有一些则更适合翻译成英语或其他语言\n" +
20 | "- 保持查询简洁,查询内容通常不超过3个关键词, 最多不要超过5个关键词\n" +
21 | "- 参考当前搜索引擎的查询习惯重写关键字,直接返回优化后的搜索词,不要有任何额外说明。\n" +
22 | "- 尽量不要使用下面这些已使用过的关键词,因为之前使用这些关键词搜索到的结果不符合预期,\n" +
23 | "- 已使用过的关键词:{used}\n"
24 |
25 | // createTemplate 创建并返回一个配置好的聊天模板
26 | func createTemplate() prompt.ChatTemplate {
27 | return prompt.FromMessages(schema.FString,
28 | // 系统消息模板
29 | schema.SystemMessage("{system}"),
30 | // 用户消息模板
31 | schema.UserMessage(
32 | "如下是用户的问题: {question}"),
33 | )
34 | }
35 |
36 | // formatMessages 格式化消息并处理错误
37 | func formatMessages(template prompt.ChatTemplate, data map[string]any) ([]*schema.Message, error) {
38 | messages, err := template.Format(context.Background(), data)
39 | if err != nil {
40 | return nil, fmt.Errorf("格式化模板失败: %w", err)
41 | }
42 | return messages, nil
43 | }
44 |
45 | func getOptimizedQueryMessages(used string, question string) ([]*schema.Message, error) {
46 | template := createTemplate()
47 | data := map[string]any{
48 | "system": system,
49 | "time_now": time.Now().Format(time.RFC3339),
50 | "question": question,
51 | "used": used,
52 | }
53 | messages, err := formatMessages(template, data)
54 | if err != nil {
55 | return nil, err
56 | }
57 | return messages, nil
58 | }
59 |
--------------------------------------------------------------------------------
/server/core/rag.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "sync"
8 |
9 | "github.com/cloudwego/eino-ext/components/retriever/es8"
10 | "github.com/cloudwego/eino/components/document"
11 | "github.com/cloudwego/eino/components/model"
12 | er "github.com/cloudwego/eino/components/retriever"
13 | "github.com/cloudwego/eino/compose"
14 | "github.com/cloudwego/eino/schema"
15 | "github.com/elastic/go-elasticsearch/v8"
16 | "github.com/elastic/go-elasticsearch/v8/typedapi/core/search"
17 | "github.com/elastic/go-elasticsearch/v8/typedapi/types"
18 | "github.com/gogf/gf/v2/frame/g"
19 | "github.com/wangle201210/go-rag/server/core/common"
20 | "github.com/wangle201210/go-rag/server/core/config"
21 | "github.com/wangle201210/go-rag/server/core/grader"
22 | "github.com/wangle201210/go-rag/server/core/indexer"
23 | "github.com/wangle201210/go-rag/server/core/retriever"
24 | )
25 |
26 | type Rag struct {
27 | idxer compose.Runnable[any, []string]
28 | rtrvr compose.Runnable[string, []*schema.Document]
29 | client *elasticsearch.Client
30 | cm model.BaseChatModel
31 | grader *grader.Grader
32 | }
33 |
34 | func New(ctx context.Context, conf *config.Config) (*Rag, error) {
35 | if len(conf.IndexName) == 0 {
36 | return nil, fmt.Errorf("indexName is empty")
37 | }
38 | // 确保es index存在
39 | err := common.CreateIndexIfNotExists(ctx, conf.Client, conf.IndexName)
40 | if err != nil {
41 | return nil, err
42 | }
43 | buildIndex, err := indexer.BuildIndexer(ctx, conf)
44 | if err != nil {
45 | return nil, err
46 | }
47 | buildRetriever, err := retriever.BuildRetriever(ctx, conf)
48 | if err != nil {
49 | return nil, err
50 | }
51 | cm, err := common.GetChatModel(ctx, conf.GetChatModelConfig())
52 | if err != nil {
53 | g.Log().Error(ctx, "GetChatModel failed, err=%v", err)
54 | return nil, err
55 | }
56 | return &Rag{
57 | idxer: buildIndex,
58 | rtrvr: buildRetriever,
59 | client: conf.Client,
60 | cm: cm,
61 | grader: grader.NewGrader(cm),
62 | }, nil
63 | }
64 |
65 | type IndexReq struct {
66 | URI string // 文档地址,可以是文件路径(pdf,html,md等),也可以是网址
67 | KnowledgeName string // 知识库名称
68 | }
69 |
70 | // Index
71 | // uri:
72 | // ids: 文档id
73 | func (x *Rag) Index(ctx context.Context, req *IndexReq) (ids []string, err error) {
74 | s := document.Source{
75 | URI: req.URI,
76 | }
77 | ctx = context.WithValue(ctx, common.KnowledgeName, req.KnowledgeName)
78 | ids, err = x.idxer.Invoke(ctx, s)
79 | if err != nil {
80 | return
81 | }
82 | return
83 | }
84 |
85 | type RetrieveReq struct {
86 | Query string // 检索关键词
87 | TopK int // 检索结果数量
88 | Score float64 // 分数阀值(0-2, 0 完全相反,1 毫不相干,2 完全相同,一般需要传入一个大于1的数字,如1.5)
89 | KnowledgeName string // 知识库名字
90 | optQuery string // 优化后的检索关键词
91 | excludeIDs []string // 要排除的 _id 列表
92 | }
93 |
94 | // Retrieve 检索
95 | func (x *Rag) Retrieve(ctx context.Context, req *RetrieveReq) (msg []*schema.Document, err error) {
96 | used := ""
97 | relatedDocs := &sync.Map{}
98 | docNum := 0
99 | // 最多尝试N次,后续做成可配置
100 | for i := 0; i < 3; i++ {
101 | question := req.Query
102 | var (
103 | messages []*schema.Message
104 | generate *schema.Message
105 | docs []*schema.Document
106 | pass bool
107 | )
108 | messages, err = getOptimizedQueryMessages(used, question)
109 | if err != nil {
110 | return
111 | }
112 | generate, err = x.cm.Generate(ctx, messages)
113 | if err != nil {
114 | return
115 | }
116 | optimizedQuery := generate.Content
117 | used += optimizedQuery + " "
118 | req.optQuery = optimizedQuery
119 | docs, err = x.retrieve(ctx, req)
120 | if err != nil {
121 | return
122 | }
123 | wg := &sync.WaitGroup{}
124 | for _, doc := range docs {
125 | wg.Add(1)
126 | go func() {
127 | defer wg.Done()
128 | pass, err = x.grader.Related(ctx, doc, req.Query)
129 | if err != nil {
130 | return
131 | }
132 | req.excludeIDs = append(req.excludeIDs, doc.ID) // 后续不要检索这个_id对应的数据
133 | if pass {
134 | relatedDocs.Store(doc.ID, doc)
135 | docNum++
136 | } else {
137 | g.Log().Infof(ctx, "not doc score: %v, related: %v", doc.Score(), doc.Content)
138 | }
139 | }()
140 | }
141 | wg.Wait()
142 | // 数量不够就再次检索
143 | if docNum < req.TopK {
144 | continue
145 | }
146 | // 数量够了,就直接返回
147 | rDocs := make([]*schema.Document, 0, req.TopK)
148 | relatedDocs.Range(func(key, value any) bool {
149 | rDocs = append(rDocs, value.(*schema.Document))
150 | return true
151 | })
152 | pass, err = x.grader.Retriever(ctx, rDocs, req.Query)
153 | if err != nil {
154 | return
155 | }
156 | if pass {
157 | return rDocs, nil
158 | }
159 | }
160 | // 最后数量不够,就返回所有数据
161 | relatedDocs.Range(func(key, value any) bool {
162 | msg = append(msg, value.(*schema.Document))
163 | return true
164 | })
165 | return
166 | }
167 |
168 | func (x *Rag) retrieve(ctx context.Context, req *RetrieveReq) (msg []*schema.Document, err error) {
169 | g.Log().Infof(ctx, "query: %v", req.optQuery)
170 | esQuery := []types.Query{
171 | {
172 | Bool: &types.BoolQuery{
173 | Must: []types.Query{{Match: map[string]types.MatchQuery{common.KnowledgeName: {Query: req.KnowledgeName}}}},
174 | },
175 | },
176 | }
177 | if len(req.excludeIDs) > 0 {
178 | esQuery[0].Bool.MustNot = []types.Query{
179 | {
180 | Terms: &types.TermsQuery{
181 | TermsQuery: map[string]types.TermsQueryField{
182 | "_id": req.excludeIDs,
183 | },
184 | },
185 | },
186 | }
187 | }
188 | msg, err = x.rtrvr.Invoke(ctx, req.optQuery,
189 | compose.WithRetrieverOption(
190 | er.WithScoreThreshold(req.Score),
191 | er.WithTopK(req.TopK),
192 | es8.WithFilters(esQuery),
193 | ),
194 | )
195 | if err != nil {
196 | return
197 | }
198 | return
199 | }
200 |
201 | // GetKnowledgeBaseList 获取知识库列表
202 | func (x *Rag) GetKnowledgeBaseList(ctx context.Context) (list []string, err error) {
203 | names := "distinct_knowledge_names"
204 | query := search.NewRequest()
205 | query.Size = common.Of(0) // 不返回原始文档
206 | query.Aggregations = map[string]types.Aggregations{
207 | names: {
208 | Terms: &types.TermsAggregation{
209 | Field: common.Of(common.KnowledgeName),
210 | Size: common.Of(10000),
211 | },
212 | },
213 | }
214 | res, err := search.NewSearchFunc(x.client)().
215 | Request(query).
216 | Do(ctx)
217 | if err != nil {
218 | return
219 | }
220 | if res.Aggregations == nil {
221 | g.Log().Infof(ctx, "No aggregations found")
222 | return
223 | }
224 | termsAgg, ok := res.Aggregations[names].(*types.StringTermsAggregate)
225 | if !ok || termsAgg == nil {
226 | err = errors.New("failed to parse terms aggregation")
227 | return
228 | }
229 | for _, bucket := range termsAgg.Buckets.([]types.StringTermsBucket) {
230 | list = append(list, bucket.Key.(string))
231 | }
232 | return
233 | }
234 |
--------------------------------------------------------------------------------
/server/core/rag_test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "context"
5 | "log"
6 | "testing"
7 |
8 | "github.com/elastic/go-elasticsearch/v8"
9 | "github.com/gogf/gf/v2/frame/g"
10 | "github.com/wangle201210/go-rag/server/core/config"
11 | )
12 |
13 | var ragSvr = &Rag{}
14 |
15 | func _init() {
16 | ctx := context.Background()
17 | client, err := elasticsearch.NewClient(elasticsearch.Config{
18 | Addresses: []string{"http://localhost:9200"},
19 | })
20 | if err != nil {
21 | log.Printf("NewClient of es8 failed, err=%v", err)
22 | return
23 | }
24 | ragSvr, err = New(context.Background(), &config.Config{
25 | Client: client,
26 | IndexName: "rag-test",
27 | APIKey: g.Cfg().MustGet(ctx, "embedding.apiKey").String(),
28 | BaseURL: g.Cfg().MustGet(ctx, "embedding.baseURL").String(),
29 | EmbeddingModel: g.Cfg().MustGet(ctx, "embedding.model").String(),
30 | ChatModel: g.Cfg().MustGet(ctx, "chat.model").String(),
31 | })
32 | if err != nil {
33 | log.Printf("New of rag failed, err=%v", err)
34 | return
35 | }
36 | }
37 | func TestIndex(t *testing.T) {
38 | ctx := context.Background()
39 | uriList := []string{
40 | "./test_file/readme.md",
41 | "./test_file/readme2.md",
42 | "./test_file/readme.html",
43 | "./test_file/test.pdf",
44 | "https://deepchat.thinkinai.xyz/docs/guide/advanced-features/shortcuts.html",
45 | }
46 | for _, s := range uriList {
47 | req := &IndexReq{
48 | URI: s,
49 | KnowledgeName: "wanna",
50 | }
51 | ids, err := ragSvr.Index(ctx, req)
52 | if err != nil {
53 | t.Fatal(err)
54 | }
55 | for _, id := range ids {
56 | t.Log(id)
57 | }
58 | }
59 | }
60 |
61 | func TestRetriever(t *testing.T) {
62 | _init()
63 | ctx := context.Background()
64 | req := &RetrieveReq{
65 | Query: "deepchat支持哪些国家的语言",
66 | TopK: 5,
67 | Score: 1.2,
68 | KnowledgeName: "deepchat",
69 | }
70 | msg, err := ragSvr.Retrieve(ctx, req)
71 | if err != nil {
72 | t.Fatal(err)
73 | }
74 | for _, m := range msg {
75 | t.Logf("content: %v, score: %v", m.Content, m.Score())
76 | }
77 | }
78 |
79 | func TestRag_GetKnowledgeList(t *testing.T) {
80 | ctx := context.Background()
81 | list, err := ragSvr.GetKnowledgeBaseList(ctx)
82 | if err != nil {
83 | t.Fatal(err)
84 | return
85 | }
86 | t.Logf("list: %v", list)
87 | }
88 |
--------------------------------------------------------------------------------
/server/core/readme.md:
--------------------------------------------------------------------------------
1 | # go-rag 核心逻辑
2 | 此目录本应放在internal目录下,但为了便于被其他项目直接引用,将其放在core目录下
3 |
4 | 详情可以参照 [test文件](../core/rag_test.go)
5 |
--------------------------------------------------------------------------------
/server/core/retriever/orchestration.go:
--------------------------------------------------------------------------------
1 | package retriever
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/cloudwego/eino/compose"
7 | "github.com/cloudwego/eino/schema"
8 | "github.com/wangle201210/go-rag/server/core/config"
9 | )
10 |
11 | func BuildRetriever(ctx context.Context, conf *config.Config) (r compose.Runnable[string, []*schema.Document], err error) {
12 | const (
13 | Retriever1 = "Retriever1"
14 | DocumentTransformer1 = "DocumentTransformer1"
15 | )
16 | g := compose.NewGraph[string, []*schema.Document]()
17 | retriever1KeyOfRetriever, err := newRetriever(ctx, conf)
18 | if err != nil {
19 | return nil, err
20 | }
21 | _ = g.AddRetrieverNode(Retriever1, retriever1KeyOfRetriever)
22 | documentTransformer1KeyOfDocumentTransformer, err := newDocumentTransformer(ctx)
23 | if err != nil {
24 | return nil, err
25 | }
26 | _ = g.AddDocumentTransformerNode(DocumentTransformer1, documentTransformer1KeyOfDocumentTransformer)
27 | _ = g.AddEdge(compose.START, Retriever1)
28 | _ = g.AddEdge(Retriever1, DocumentTransformer1)
29 | _ = g.AddEdge(DocumentTransformer1, compose.END)
30 | r, err = g.Compile(ctx, compose.WithGraphName("retriever"))
31 | if err != nil {
32 | return nil, err
33 | }
34 | return r, err
35 | }
36 |
--------------------------------------------------------------------------------
/server/core/retriever/retriever.go:
--------------------------------------------------------------------------------
1 | package retriever
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | "github.com/cloudwego/eino-ext/components/retriever/es8"
9 | "github.com/cloudwego/eino-ext/components/retriever/es8/search_mode"
10 | "github.com/cloudwego/eino/components/retriever"
11 | "github.com/cloudwego/eino/schema"
12 | "github.com/elastic/go-elasticsearch/v8/typedapi/types"
13 | "github.com/wangle201210/go-rag/server/core/common"
14 | "github.com/wangle201210/go-rag/server/core/config"
15 | )
16 |
17 | // newRetriever component initialization function of node 'Retriever1' in graph 'retriever'
18 | func newRetriever(ctx context.Context, conf *config.Config) (rtr retriever.Retriever, err error) {
19 | retrieverConfig := &es8.RetrieverConfig{
20 | Client: conf.Client,
21 | Index: conf.IndexName,
22 | TopK: 5,
23 | SearchMode: search_mode.SearchModeDenseVectorSimilarity(
24 | search_mode.DenseVectorSimilarityTypeCosineSimilarity,
25 | common.FieldContentVector,
26 | ),
27 | ResultParser: func(ctx context.Context, hit types.Hit) (doc *schema.Document, err error) {
28 | doc = &schema.Document{
29 | ID: *hit.Id_,
30 | Content: "",
31 | MetaData: map[string]any{},
32 | }
33 |
34 | var src map[string]any
35 | if err = json.Unmarshal(hit.Source_, &src); err != nil {
36 | return nil, err
37 | }
38 |
39 | for field, val := range src {
40 | switch field {
41 | case common.FieldContent:
42 | doc.Content = val.(string)
43 | case common.FieldContentVector:
44 | var v []float64
45 | for _, item := range val.([]interface{}) {
46 | v = append(v, item.(float64))
47 | }
48 | doc.WithDenseVector(v)
49 |
50 | case common.FieldExtra:
51 | if val == nil {
52 | continue
53 | }
54 | doc.MetaData[common.DocExtra] = val.(string)
55 | case common.KnowledgeName:
56 | doc.MetaData[common.KnowledgeName] = val.(string)
57 | default:
58 | return nil, fmt.Errorf("unexpected field=%s, val=%v", field, val)
59 | }
60 | }
61 |
62 | if hit.Score_ != nil {
63 | doc.WithScore(float64(*hit.Score_))
64 | }
65 |
66 | return doc, nil
67 | },
68 | }
69 | embeddingIns11, err := common.NewEmbedding(ctx, conf)
70 | if err != nil {
71 | return nil, err
72 | }
73 | retrieverConfig.Embedding = embeddingIns11
74 | rtr, err = es8.NewRetriever(ctx, retrieverConfig)
75 | if err != nil {
76 | return nil, err
77 | }
78 | return rtr, nil
79 | }
80 |
--------------------------------------------------------------------------------
/server/core/retriever/transformer.go:
--------------------------------------------------------------------------------
1 | package retriever
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/cloudwego/eino-ext/components/document/transformer/reranker/score"
7 | "github.com/cloudwego/eino/components/document"
8 | )
9 |
10 | // newDocumentTransformer component initialization function of node 'DocumentTransformer1' in graph 'retriever'
11 | func newDocumentTransformer(ctx context.Context) (tfr document.Transformer, err error) {
12 | config := &score.Config{}
13 | tfr, err = score.NewReranker(ctx, config)
14 | if err != nil {
15 | return nil, err
16 | }
17 | return tfr, nil
18 | }
19 |
--------------------------------------------------------------------------------
/server/core/test_file/readme.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | golang项目搭建
4 |
5 |
6 | 1. 安装环境
7 | 2. 写代码
8 | 3. 执行
9 |
10 |
--------------------------------------------------------------------------------
/server/core/test_file/readme.md:
--------------------------------------------------------------------------------
1 | # 这是一个readme文件,这里有很多内容
--------------------------------------------------------------------------------
/server/core/test_file/readme2.md:
--------------------------------------------------------------------------------
1 | # 开饭了
--------------------------------------------------------------------------------
/server/core/test_file/test.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangle201210/go-rag/29d54bcfff76f50e5f5f8532653a2091d16cbca5/server/core/test_file/test.pdf
--------------------------------------------------------------------------------
/server/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/wangle201210/go-rag/server
2 |
3 | go 1.23.1
4 |
5 | require (
6 | github.com/ThinkInAIXYZ/go-mcp v0.2.14
7 | github.com/bytedance/sonic v1.13.2
8 | github.com/cloudwego/eino v0.3.31
9 | github.com/cloudwego/eino-ext/components/document/loader/file v0.0.0-20250424061409-ccd60fbc7c1c
10 | github.com/cloudwego/eino-ext/components/document/loader/url v0.0.0-20250605072634-0f875e04269d
11 | github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250424061409-ccd60fbc7c1c
12 | github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250424061409-ccd60fbc7c1c
13 | github.com/cloudwego/eino-ext/components/document/transformer/reranker/score v0.0.0-20250513023651-7b19c6ffbf4a
14 | github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown v0.0.0-20250605072634-0f875e04269d
15 | github.com/cloudwego/eino-ext/components/document/transformer/splitter/recursive v0.0.0-20250424061409-ccd60fbc7c1c
16 | github.com/cloudwego/eino-ext/components/embedding/openai v0.0.0-20250424061409-ccd60fbc7c1c
17 | github.com/cloudwego/eino-ext/components/indexer/es8 v0.0.0-20250605072634-0f875e04269d
18 | github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250512035704-1e06fdfda207
19 | github.com/cloudwego/eino-ext/components/retriever/es8 v0.0.0-20250424061409-ccd60fbc7c1c
20 | github.com/elastic/go-elasticsearch/v8 v8.16.0
21 | github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.0
22 | github.com/gogf/gf/v2 v2.9.0
23 | github.com/google/uuid v1.6.0
24 | github.com/wangle201210/chat-history v0.0.0-20250402104704-5eec15d5419e
25 | gorm.io/driver/mysql v1.5.7
26 | gorm.io/gorm v1.25.12
27 | )
28 |
29 | require (
30 | github.com/BurntSushi/toml v1.4.0 // indirect
31 | github.com/PuerkitoBio/goquery v1.8.1 // indirect
32 | github.com/andybalholm/cascadia v1.3.1 // indirect
33 | github.com/aymerick/douceur v0.2.0 // indirect
34 | github.com/bytedance/sonic/loader v0.2.4 // indirect
35 | github.com/clbanning/mxj/v2 v2.7.0 // indirect
36 | github.com/cloudwego/base64x v0.1.5 // indirect
37 | github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250422092704-54e372e1fa3d // indirect
38 | github.com/dslipak/pdf v0.0.2 // indirect
39 | github.com/dustin/go-humanize v1.0.1 // indirect
40 | github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
41 | github.com/emirpasic/gods v1.18.1 // indirect
42 | github.com/fatih/color v1.18.0 // indirect
43 | github.com/fsnotify/fsnotify v1.7.0 // indirect
44 | github.com/getkin/kin-openapi v0.118.0 // indirect
45 | github.com/go-logr/logr v1.4.2 // indirect
46 | github.com/go-logr/stdr v1.2.2 // indirect
47 | github.com/go-openapi/jsonpointer v0.21.1 // indirect
48 | github.com/go-openapi/swag v0.23.1 // indirect
49 | github.com/go-sql-driver/mysql v1.7.1 // indirect
50 | github.com/goph/emperror v0.17.2 // indirect
51 | github.com/gorilla/css v1.0.1 // indirect
52 | github.com/gorilla/websocket v1.5.3 // indirect
53 | github.com/grokify/html-strip-tags-go v0.1.0 // indirect
54 | github.com/invopop/yaml v0.1.0 // indirect
55 | github.com/jinzhu/inflection v1.0.0 // indirect
56 | github.com/jinzhu/now v1.1.5 // indirect
57 | github.com/josharian/intern v1.0.0 // indirect
58 | github.com/json-iterator/go v1.1.12 // indirect
59 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect
60 | github.com/magiconair/properties v1.8.9 // indirect
61 | github.com/mailru/easyjson v0.9.0 // indirect
62 | github.com/mattn/go-colorable v0.1.13 // indirect
63 | github.com/mattn/go-isatty v0.0.20 // indirect
64 | github.com/mattn/go-runewidth v0.0.16 // indirect
65 | github.com/meguminnnnnnnnn/go-openai v0.0.0-20250408071642-761325becfd6 // indirect
66 | github.com/microcosm-cc/bluemonday v1.0.27 // indirect
67 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
68 | github.com/modern-go/reflect2 v1.0.2 // indirect
69 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
70 | github.com/nikolalohinski/gonja v1.5.3 // indirect
71 | github.com/olekukonko/tablewriter v0.0.5 // indirect
72 | github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect
73 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect
74 | github.com/perimeterx/marshmallow v1.1.5 // indirect
75 | github.com/pkg/errors v0.9.1 // indirect
76 | github.com/rivo/uniseg v0.4.7 // indirect
77 | github.com/sirupsen/logrus v1.9.3 // indirect
78 | github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
79 | github.com/tidwall/gjson v1.18.0 // indirect
80 | github.com/tidwall/match v1.1.1 // indirect
81 | github.com/tidwall/pretty v1.2.0 // indirect
82 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
83 | github.com/yargevad/filepathx v1.0.0 // indirect
84 | github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
85 | go.opentelemetry.io/otel v1.32.0 // indirect
86 | go.opentelemetry.io/otel/metric v1.32.0 // indirect
87 | go.opentelemetry.io/otel/sdk v1.32.0 // indirect
88 | go.opentelemetry.io/otel/trace v1.32.0 // indirect
89 | golang.org/x/arch v0.15.0 // indirect
90 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
91 | golang.org/x/net v0.33.0 // indirect
92 | golang.org/x/sys v0.31.0 // indirect
93 | golang.org/x/text v0.21.0 // indirect
94 | gopkg.in/yaml.v3 v3.0.1 // indirect
95 | )
96 |
--------------------------------------------------------------------------------
/server/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
2 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
3 | github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
4 | github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
5 | github.com/ThinkInAIXYZ/go-mcp v0.2.14 h1:gyZ4Dv47Ozr4k4h329Qk8TOSDr4SsyBJn0o21oAs2Ec=
6 | github.com/ThinkInAIXYZ/go-mcp v0.2.14/go.mod h1:KnUWUymko7rmOgzvIjxwX0uB9oiJeLF/Q3W9cRt8fVg=
7 | github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
8 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
9 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
10 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
11 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
12 | github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
13 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
14 | github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
15 | github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
16 | github.com/bytedance/mockey v1.2.14 h1:KZaFgPdiUwW+jOWFieo3Lr7INM1P+6adO3hxZhDswY8=
17 | github.com/bytedance/mockey v1.2.14/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY=
18 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
19 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
20 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
21 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
22 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
23 | github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
24 | github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
25 | github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
26 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
27 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
28 | github.com/cloudwego/eino v0.3.31 h1:CByfu37j7YjYp+T/aS2oCffPCOY986F3Jbb6a6BfO4Y=
29 | github.com/cloudwego/eino v0.3.31/go.mod h1:wUjz990apdsaOraOXdh6CdhVXq8DJsOvLsVlxNTcNfY=
30 | github.com/cloudwego/eino-ext/components/document/loader/file v0.0.0-20250424061409-ccd60fbc7c1c h1:mMJtZczl2Cs8Cou+xzsrbT+I49seDhigISPeB6cQtX8=
31 | github.com/cloudwego/eino-ext/components/document/loader/file v0.0.0-20250424061409-ccd60fbc7c1c/go.mod h1:I4vbBCIMMKeF436Lc+L3DSPQ3f1nmiHD0JS+LhMYCdQ=
32 | github.com/cloudwego/eino-ext/components/document/loader/url v0.0.0-20250605072634-0f875e04269d h1:wUaqvsLQfSVEZka0CPjB+7scZ1tBbHf6eEt4ahmlRDo=
33 | github.com/cloudwego/eino-ext/components/document/loader/url v0.0.0-20250605072634-0f875e04269d/go.mod h1:qnTfPHmDIMJSF6/tbmcKUmVgEfuBrFoTfVgfFHxNZ84=
34 | github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250424061409-ccd60fbc7c1c h1:0Fn0GR1jM9WO4xaY8cwjj25F+T5tS4Jpo2bO1s9aDf8=
35 | github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250424061409-ccd60fbc7c1c/go.mod h1:w6f/1eEScd32tenRKwcYIJKqZ+3cArFOia1QxFsQmVE=
36 | github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250424061409-ccd60fbc7c1c h1:BBdsRDuIf7aSkUSUbrh2ojzIxCBxStPC05ofqjenR2E=
37 | github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250424061409-ccd60fbc7c1c/go.mod h1:Vpoaj8exHtu8EbRaAZTFRT7UaKslXd5nx7Z0EEVDIvY=
38 | github.com/cloudwego/eino-ext/components/document/transformer/reranker/score v0.0.0-20250513023651-7b19c6ffbf4a h1:Qce5GskHlDgAxyecVY8ur/5PFUmDQydusnHhRj4SCCg=
39 | github.com/cloudwego/eino-ext/components/document/transformer/reranker/score v0.0.0-20250513023651-7b19c6ffbf4a/go.mod h1:jdx8WFGep99tjWYAPp36pM8Gnw4pzPy+li2UZZZFjDE=
40 | github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown v0.0.0-20250605072634-0f875e04269d h1:x2NyMDCTXcJOdkKIVoOPXUacwkjznvEuls/epMVILrs=
41 | github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown v0.0.0-20250605072634-0f875e04269d/go.mod h1:qZxYTU/Snj6bvoyOe4ZKhb1ZgLUiuYcKbClTl15rPRI=
42 | github.com/cloudwego/eino-ext/components/document/transformer/splitter/recursive v0.0.0-20250424061409-ccd60fbc7c1c h1:u6iPDaZkgSrlHm0cDm2zTIEF8ODS/2inRRmfeI2Sebs=
43 | github.com/cloudwego/eino-ext/components/document/transformer/splitter/recursive v0.0.0-20250424061409-ccd60fbc7c1c/go.mod h1:a9JdTcj+WJkWjnm94RyhdWVNoiR2ancjsy01S5e/uPo=
44 | github.com/cloudwego/eino-ext/components/embedding/openai v0.0.0-20250424061409-ccd60fbc7c1c h1:HiryaRjOTeTmqT6NH/VQbZSKy03hXdSsDIu0B35g/p8=
45 | github.com/cloudwego/eino-ext/components/embedding/openai v0.0.0-20250424061409-ccd60fbc7c1c/go.mod h1:2vwPuBqNFHMSJUSngiuisoEmS8dGP7rujNqDQuUCFLg=
46 | github.com/cloudwego/eino-ext/components/indexer/es8 v0.0.0-20250605072634-0f875e04269d h1:5wFaXeXKWOO/JxqwSAdAaFUvUbVj5etwwrI6CxPxr/E=
47 | github.com/cloudwego/eino-ext/components/indexer/es8 v0.0.0-20250605072634-0f875e04269d/go.mod h1:gcTplPr8fzw00XS+twNkrQXORfRXL7UYFbAe1Z8v/Ck=
48 | github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250512035704-1e06fdfda207 h1:5/ZodDj/W1Iiw7voJP1ScA+M62ctE5WAA93D6/9rws0=
49 | github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250512035704-1e06fdfda207/go.mod h1:uXIWTFbaAbZ1128EIXjFc4S+tDqmz1idMZd5qt5kkwU=
50 | github.com/cloudwego/eino-ext/components/retriever/es8 v0.0.0-20250424061409-ccd60fbc7c1c h1:dB4WP0krLiz8URDs7//kPGr1wBU+BVKM3wp+/rbbbNY=
51 | github.com/cloudwego/eino-ext/components/retriever/es8 v0.0.0-20250424061409-ccd60fbc7c1c/go.mod h1:GhhtNox5sRTgU9yYfmp0MOyF0SCvO7c21VqIzg78boE=
52 | github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250422092704-54e372e1fa3d h1:unNqPz9vuJmJCZAw5YKFcszRX9e3CdVEjh0lR6QArxk=
53 | github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250422092704-54e372e1fa3d/go.mod h1:Ye0YAqpESCxMlnALNrjeNJjhS9q2PIdxVdJbtFeni8o=
54 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
55 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
56 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
57 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
58 | github.com/dslipak/pdf v0.0.2 h1:djAvcM5neg9Ush+zR6QXB+VMJzR6TdnX766HPIg1JmI=
59 | github.com/dslipak/pdf v0.0.2/go.mod h1:2L3SnkI9cQwnAS9gfPz2iUoLC0rUZwbucpbKi5R1mUo=
60 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
61 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
62 | github.com/elastic/elastic-transport-go/v8 v8.7.0 h1:OgTneVuXP2uip4BA658Xi6Hfw+PeIOod2rY3GVMGoVE=
63 | github.com/elastic/elastic-transport-go/v8 v8.7.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
64 | github.com/elastic/go-elasticsearch/v8 v8.16.0 h1:f7bR+iBz8GTAVhwyFO3hm4ixsz2eMaEy0QroYnXV3jE=
65 | github.com/elastic/go-elasticsearch/v8 v8.16.0/go.mod h1:lGMlgKIbYoRvay3xWBeKahAiJOgmFDsjZC39nmO3H64=
66 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
67 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
68 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
69 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
70 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
71 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
72 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
73 | github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM=
74 | github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
75 | github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
76 | github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
77 | github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
78 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
79 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
80 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
81 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
82 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
83 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
84 | github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
85 | github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
86 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
87 | github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
88 | github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
89 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
90 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
91 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
92 | github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
93 | github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
94 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
95 | github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.0 h1:1f7EeD0lfPHoXfaJDSL7cxRcSRelbsAKgF3MGXY+Uyo=
96 | github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.0/go.mod h1:tToO1PjGkLIR+9DbJ0wrKicYma0H/EUHXOpwel6Dw+0=
97 | github.com/gogf/gf/v2 v2.9.0 h1:semN5Q5qGjDQEv4620VzxcJzJlSD07gmyJ9Sy9zfbHk=
98 | github.com/gogf/gf/v2 v2.9.0/go.mod h1:sWGQw+pLILtuHmbOxoe0D+0DdaXxbleT57axOLH2vKI=
99 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
100 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
101 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
102 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
103 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
104 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
105 | github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
106 | github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
107 | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
108 | github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
109 | github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
110 | github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
111 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
112 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
113 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
114 | github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
115 | github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
116 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
117 | github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
118 | github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
119 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
120 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
121 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
122 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
123 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
124 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
125 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
126 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
127 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
128 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
129 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
130 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
131 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
132 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
133 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
134 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
135 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
136 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
137 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
138 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
139 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
140 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
141 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
142 | github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
143 | github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
144 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
145 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
146 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
147 | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
148 | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
149 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
150 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
151 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
152 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
153 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
154 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
155 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
156 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
157 | github.com/meguminnnnnnnnn/go-openai v0.0.0-20250408071642-761325becfd6 h1:nmdXxiUX48DZ2ELC/jSYzyGUVgxVEF2QJRGhLJ933zA=
158 | github.com/meguminnnnnnnnn/go-openai v0.0.0-20250408071642-761325becfd6/go.mod h1:kyz7fcXqXtccmRAIARn1Q+cKLNXJHC3AoqqJGeCqNI0=
159 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
160 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
161 | github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
162 | github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
163 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
164 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
165 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
166 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
167 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
168 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
169 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
170 | github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
171 | github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
172 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
173 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
174 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
175 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
176 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
177 | github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
178 | github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
179 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
180 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
181 | github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
182 | github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
183 | github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
184 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
185 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
186 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
187 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
188 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
189 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
190 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
191 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
192 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
193 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
194 | github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
195 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
196 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
197 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
198 | github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI=
199 | github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg=
200 | github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY=
201 | github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
202 | github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
203 | github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
204 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
205 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
206 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
207 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
208 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
209 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
210 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
211 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
212 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
213 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
214 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
215 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
216 | github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
217 | github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
218 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
219 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
220 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
221 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
222 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
223 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
224 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
225 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
226 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
227 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
228 | github.com/wangle201210/chat-history v0.0.0-20250402104704-5eec15d5419e h1:pdG2LdTh2toVNke05pGmFtS4ZkuA6xt0rqT4pWE9HZk=
229 | github.com/wangle201210/chat-history v0.0.0-20250402104704-5eec15d5419e/go.mod h1:MzB/53ZNftLNedu73UwySUHUFFCYZNJ3WmBrukalTrU=
230 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
231 | github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
232 | github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
233 | github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
234 | github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
235 | github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
236 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
237 | go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
238 | go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
239 | go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
240 | go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
241 | go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
242 | go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
243 | go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
244 | go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
245 | go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
246 | go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
247 | golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
248 | golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
249 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
250 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
251 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
252 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
253 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
254 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
255 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
256 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
257 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
258 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
259 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
260 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
261 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
262 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
263 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
264 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
265 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
266 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
267 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
268 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
269 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
270 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
271 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
272 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
273 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
274 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
275 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
276 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
277 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
278 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
279 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
280 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
281 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
282 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
283 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
284 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
285 | golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
286 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
287 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
288 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
289 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
290 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
291 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
292 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
293 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
294 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
295 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
296 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
297 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
298 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
299 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
300 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
301 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
302 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
303 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
304 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
305 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
306 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
307 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
308 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
309 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
310 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
311 | gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
312 | gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
313 | gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
314 | gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
315 | gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
316 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
317 |
--------------------------------------------------------------------------------
/server/hack/config.yaml:
--------------------------------------------------------------------------------
1 | gfcli:
2 | gen:
3 | dao:
4 | - link: "mysql:root:123456@tcp(127.0.0.1:3306)/go-rag"
5 | descriptionTag: true
6 | tables: "knowledge_base"
7 |
--------------------------------------------------------------------------------
/server/internal/cmd/cmd.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/ThinkInAIXYZ/go-mcp/server"
7 | "github.com/ThinkInAIXYZ/go-mcp/transport"
8 | "github.com/gogf/gf/v2/frame/g"
9 | "github.com/gogf/gf/v2/net/ghttp"
10 | "github.com/gogf/gf/v2/os/gcmd"
11 | "github.com/wangle201210/go-rag/server/internal/controller/rag"
12 | "github.com/wangle201210/go-rag/server/internal/mcp"
13 | )
14 |
15 | var (
16 | Main = gcmd.Command{
17 | Name: "main",
18 | Usage: "main",
19 | Brief: "start http server",
20 | Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
21 | s := g.Server()
22 | Mcp(ctx, s)
23 | s.Group("/", func(group *ghttp.RouterGroup) {
24 | s.AddStaticPath("", "./static/fe/")
25 | s.SetIndexFiles([]string{"index.html"})
26 | group.Middleware(ghttp.MiddlewareHandlerResponse, ghttp.MiddlewareCORS)
27 | group.Bind(
28 | rag.NewV1(),
29 | )
30 | })
31 | s.Run()
32 | return nil
33 | },
34 | }
35 | )
36 |
37 | func Mcp(ctx context.Context, s *ghttp.Server) {
38 | trans, handler, err := transport.NewStreamableHTTPServerTransportAndHandler()
39 | if err != nil {
40 | g.Log().Panicf(ctx, "new sse transport and hander with error: %v", err)
41 | }
42 | // new mcp server
43 | mcpServer, _ := server.NewServer(trans)
44 | mcpServer.RegisterTool(mcp.GetRetrieverTool(), mcp.HandleRetriever)
45 | mcpServer.RegisterTool(mcp.GetKnowledgeBaseTool(), mcp.HandleKnowledgeBase)
46 | // start mcp Server
47 | go func() {
48 | mcpServer.Run()
49 | }()
50 | // mcpServer.Shutdown(context.Background())
51 | s.Group("/", func(r *ghttp.RouterGroup) {
52 | r.ALL("/mcp", func(r *ghttp.Request) {
53 | handler.HandleMCP().ServeHTTP(r.Response.Writer, r.Request)
54 | })
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/server/internal/controller/rag/rag.go:
--------------------------------------------------------------------------------
1 | // =================================================================================
2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
3 | // =================================================================================
4 |
5 | package rag
6 |
--------------------------------------------------------------------------------
/server/internal/controller/rag/rag_new.go:
--------------------------------------------------------------------------------
1 | // =================================================================================
2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
3 | // =================================================================================
4 |
5 | package rag
6 |
7 | import (
8 | "github.com/wangle201210/go-rag/server/api/rag"
9 | )
10 |
11 | type ControllerV1 struct{}
12 |
13 | func NewV1() rag.IRagV1 {
14 | return &ControllerV1{}
15 | }
16 |
--------------------------------------------------------------------------------
/server/internal/controller/rag/rag_v1_chat.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/wangle201210/go-rag/server/api/rag/v1"
7 | "github.com/wangle201210/go-rag/server/internal/logic/chat"
8 | )
9 |
10 | func (c *ControllerV1) Chat(ctx context.Context, req *v1.ChatReq) (res *v1.ChatRes, err error) {
11 | retriever, err := c.Retriever(ctx, &v1.RetrieverReq{
12 | Question: req.Question,
13 | TopK: req.TopK,
14 | Score: req.Score,
15 | KnowledgeName: req.KnowledgeName,
16 | })
17 | if err != nil {
18 | return
19 | }
20 | chatI := chat.GetChat()
21 | answer, err := chatI.GetAnswer(ctx, req.ConvID, retriever.Document, req.Question)
22 | if err != nil {
23 | return
24 | }
25 | res = &v1.ChatRes{
26 | Answer: answer,
27 | References: retriever.Document,
28 | }
29 | return
30 | }
31 |
--------------------------------------------------------------------------------
/server/internal/controller/rag/rag_v1_chat_stream.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "time"
8 |
9 | "github.com/bytedance/sonic"
10 | "github.com/gogf/gf/v2/frame/g"
11 | "github.com/gogf/gf/v2/net/ghttp"
12 | "github.com/google/uuid"
13 | v1 "github.com/wangle201210/go-rag/server/api/rag/v1"
14 | "github.com/wangle201210/go-rag/server/core/common"
15 | "github.com/wangle201210/go-rag/server/internal/logic/chat"
16 | )
17 |
18 | // ChatStream 流式输出接口
19 | func (c *ControllerV1) ChatStream(ctx context.Context, req *v1.ChatStreamReq) (res *v1.ChatStreamRes, err error) {
20 | // 获取HTTP响应对象
21 | httpReq := ghttp.RequestFromCtx(ctx)
22 | httpResp := httpReq.Response
23 | // 设置响应头
24 | httpResp.Header().Set("Content-Type", "text/event-stream")
25 | httpResp.Header().Set("Cache-Control", "no-cache")
26 | httpResp.Header().Set("Connection", "keep-alive")
27 | httpResp.Header().Set("X-Accel-Buffering", "no") // 禁用Nginx缓冲
28 | httpResp.Header().Set("Access-Control-Allow-Origin", "*")
29 |
30 | // 获取检索结果
31 | retriever, err := c.Retriever(ctx, &v1.RetrieverReq{
32 | Question: req.Question,
33 | TopK: req.TopK,
34 | Score: req.Score,
35 | KnowledgeName: req.KnowledgeName,
36 | })
37 | if err != nil {
38 | writeSSEError(httpResp, err)
39 | return &v1.ChatStreamRes{}, nil
40 | }
41 | sd := &common.StreamData{
42 | Id: uuid.NewString(),
43 | Created: time.Now().Unix(),
44 | Document: retriever.Document,
45 | }
46 | marshal, _ := sonic.Marshal(sd)
47 | writeSSEDocuments(httpResp, string(marshal))
48 | // 获取Chat实例
49 | chatI := chat.GetChat()
50 | // 获取流式响应
51 | streamReader, err := chatI.GetAnswerStream(ctx, req.ConvID, retriever.Document, req.Question)
52 | if err != nil {
53 | writeSSEError(httpResp, err)
54 | return &v1.ChatStreamRes{}, nil
55 | }
56 | defer streamReader.Close()
57 |
58 | // 处理流式响应
59 | for {
60 | chunk, err := streamReader.Recv()
61 | if err == io.EOF {
62 | break
63 | }
64 | if err != nil {
65 | writeSSEError(httpResp, err)
66 | break
67 | }
68 | if len(chunk.Content) == 0 {
69 | continue
70 | }
71 |
72 | sd.Content = chunk.Content
73 | marshal, _ := sonic.Marshal(sd)
74 | // 发送数据事件
75 | writeSSEData(httpResp, string(marshal))
76 | }
77 | // 发送结束事件
78 | writeSSEDone(httpResp)
79 | return &v1.ChatStreamRes{}, nil
80 | }
81 |
82 | // writeSSEData 写入SSE事件
83 | func writeSSEData(resp *ghttp.Response, data string) {
84 | if len(data) == 0 {
85 | return
86 | }
87 | // g.Log().Infof(context.Background(), "data: %s", data)
88 | resp.Writeln(fmt.Sprintf("data:%s\n", data))
89 | resp.Flush()
90 | }
91 |
92 | func writeSSEDone(resp *ghttp.Response) {
93 | resp.Writeln(fmt.Sprintf("data:%s\n", "[DONE]"))
94 | resp.Flush()
95 | }
96 |
97 | func writeSSEDocuments(resp *ghttp.Response, data string) {
98 | resp.Writeln(fmt.Sprintf("documents:%s\n", data))
99 | resp.Flush()
100 | }
101 |
102 | // writeSSEError 写入SSE错误
103 | func writeSSEError(resp *ghttp.Response, err error) {
104 | g.Log().Error(context.Background(), err)
105 | resp.Writeln(fmt.Sprintf("event: error\ndata: %s\n\n", err.Error()))
106 | resp.Flush()
107 | }
108 |
--------------------------------------------------------------------------------
/server/internal/controller/rag/rag_v1_indexer.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 |
6 | gorag "github.com/wangle201210/go-rag/server/core"
7 | "github.com/wangle201210/go-rag/server/internal/logic/rag"
8 |
9 | "github.com/wangle201210/go-rag/server/api/rag/v1"
10 | )
11 |
12 | func (c *ControllerV1) Indexer(ctx context.Context, req *v1.IndexerReq) (res *v1.IndexerRes, err error) {
13 | svr := rag.GetRagSvr()
14 | uri := req.URL
15 | if req.File != nil {
16 | filename, e := req.File.Save("./uploads/")
17 | if e != nil {
18 | err = e
19 | return
20 | }
21 | uri = "./uploads/" + filename
22 | }
23 | indexReq := &gorag.IndexReq{
24 | URI: uri,
25 | KnowledgeName: req.KnowledgeName,
26 | }
27 | ids, err := svr.Index(ctx, indexReq)
28 | if err != nil {
29 | return
30 | }
31 | res = &v1.IndexerRes{
32 | DocIDs: ids,
33 | }
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/server/internal/controller/rag/rag_v1_kb.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/wangle201210/go-rag/server/internal/dao"
7 | "github.com/wangle201210/go-rag/server/internal/model/do"
8 |
9 | "github.com/wangle201210/go-rag/server/api/rag/v1"
10 | )
11 |
12 | func (c *ControllerV1) KBCreate(ctx context.Context, req *v1.KBCreateReq) (res *v1.KBCreateRes, err error) {
13 | insertId, err := dao.KnowledgeBase.Ctx(ctx).Data(do.KnowledgeBase{
14 | Name: req.Name,
15 | Status: v1.StatusOK,
16 | Description: req.Description,
17 | Category: req.Category,
18 | }).InsertAndGetId()
19 | if err != nil {
20 | return nil, err
21 | }
22 | res = &v1.KBCreateRes{
23 | Id: insertId,
24 | }
25 | return
26 | }
27 |
28 | func (c *ControllerV1) KBDelete(ctx context.Context, req *v1.KBDeleteReq) (res *v1.KBDeleteRes, err error) {
29 | _, err = dao.KnowledgeBase.Ctx(ctx).WherePri(req.Id).Delete()
30 | return
31 | }
32 |
33 | func (c *ControllerV1) KBGetList(ctx context.Context, req *v1.KBGetListReq) (res *v1.KBGetListRes, err error) {
34 | res = &v1.KBGetListRes{}
35 | err = dao.KnowledgeBase.Ctx(ctx).Where(do.KnowledgeBase{
36 | Status: req.Status,
37 | Name: req.Name,
38 | Category: req.Category,
39 | }).Scan(&res.List)
40 | return
41 | }
42 |
43 | func (c *ControllerV1) KBGetOne(ctx context.Context, req *v1.KBGetOneReq) (res *v1.KBGetOneRes, err error) {
44 | res = &v1.KBGetOneRes{}
45 | err = dao.KnowledgeBase.Ctx(ctx).WherePri(req.Id).Scan(&res.KnowledgeBase)
46 | return
47 | }
48 |
49 | func (c *ControllerV1) KBUpdate(ctx context.Context, req *v1.KBUpdateReq) (res *v1.KBUpdateRes, err error) {
50 | _, err = dao.KnowledgeBase.Ctx(ctx).Data(do.KnowledgeBase{
51 | Name: req.Name,
52 | Status: req.Status,
53 | Description: req.Description,
54 | Category: req.Category,
55 | }).WherePri(req.Id).Update()
56 | return
57 | }
58 |
--------------------------------------------------------------------------------
/server/internal/controller/rag/rag_v1_retriever.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "sort"
7 |
8 | "github.com/gogf/gf/v2/frame/g"
9 | gorag "github.com/wangle201210/go-rag/server/core"
10 | "github.com/wangle201210/go-rag/server/internal/logic/rag"
11 |
12 | "github.com/wangle201210/go-rag/server/api/rag/v1"
13 | )
14 |
15 | func (c *ControllerV1) Retriever(ctx context.Context, req *v1.RetrieverReq) (res *v1.RetrieverRes, err error) {
16 | ragSvr := rag.GetRagSvr()
17 | if req.TopK == 0 {
18 | req.TopK = 5
19 | }
20 | if req.Score == 0 {
21 | req.Score = 0.2
22 | }
23 | if req.Score < 1.0 {
24 | req.Score += 1
25 | }
26 | ragReq := &gorag.RetrieveReq{
27 | Query: req.Question,
28 | TopK: req.TopK,
29 | Score: req.Score,
30 | KnowledgeName: req.KnowledgeName,
31 | }
32 | g.Log().Infof(ctx, "ragReq: %v", ragReq)
33 | msg, err := ragSvr.Retrieve(ctx, ragReq)
34 | if err != nil {
35 | return
36 | }
37 | for _, document := range msg {
38 | if document.MetaData != nil {
39 | delete(document.MetaData, "_dense_vector")
40 | if v, e := document.MetaData["_score"]; e {
41 | vf := v.(float64)
42 | document.MetaData["_score"] = vf - 1
43 | }
44 | m := make(map[string]interface{})
45 | if err = json.Unmarshal([]byte(document.MetaData["ext"].(string)), &m); err != nil {
46 | return
47 | }
48 | document.MetaData["ext"] = m
49 | }
50 | }
51 | // eino 默认是把分高的排在两边,这里我xiu gai
52 | sort.Slice(msg, func(i, j int) bool {
53 | return msg[i].Score() > msg[j].Score()
54 | })
55 | res = &v1.RetrieverRes{
56 | Document: msg,
57 | }
58 | return
59 | }
60 |
--------------------------------------------------------------------------------
/server/internal/dao/dao.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/gogf/gf/v2/frame/g"
9 | "github.com/wangle201210/go-rag/server/internal/model"
10 | "gorm.io/driver/mysql"
11 | "gorm.io/gorm"
12 | "gorm.io/gorm/logger"
13 | )
14 |
15 | var db *gorm.DB
16 |
17 | func init() {
18 | err := InitDB()
19 | if err != nil {
20 | g.Log().Fatal(context.Background(), "database connection not initialized")
21 | }
22 | }
23 |
24 | // InitDB 初始化数据库连接
25 | func InitDB() error {
26 | config := &gorm.Config{
27 | Logger: logger.Default.LogMode(logger.Info),
28 | }
29 | dsn := GetDsn()
30 |
31 | println(dsn)
32 | var err error
33 | db, err = gorm.Open(mysql.Open(dsn), config)
34 | if err != nil {
35 | return fmt.Errorf("failed to connect database: %v", err)
36 | }
37 |
38 | sqlDB, err := db.DB()
39 | if err != nil {
40 | return fmt.Errorf("failed to get database instance: %v", err)
41 | }
42 |
43 | // 设置连接池
44 | sqlDB.SetMaxIdleConns(10)
45 | sqlDB.SetMaxOpenConns(100)
46 | sqlDB.SetConnMaxLifetime(time.Hour)
47 |
48 | // 自动迁移数据库表结构
49 | if err = autoMigrateTables(); err != nil {
50 | return fmt.Errorf("failed to migrate database tables: %v", err)
51 | }
52 |
53 | return nil
54 | }
55 |
56 | func GetDsn() string {
57 | cfg := g.DB().GetConfig()
58 | return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", cfg.User, cfg.Pass, cfg.Host, cfg.Port, cfg.Name)
59 | }
60 |
61 | // GetDB 获取数据库实例
62 | func GetDB() *gorm.DB {
63 | if db == nil {
64 | g.Log().Fatal(context.Background(), "database connection not initialized")
65 | }
66 | return db
67 | }
68 |
69 | // autoMigrateTables 自动迁移数据库表结构
70 | func autoMigrateTables() error {
71 | // 自动迁移会创建表、缺失的外键、约束、列和索引
72 | return db.AutoMigrate(&model.KnowledgeBase{})
73 | }
74 |
--------------------------------------------------------------------------------
/server/internal/dao/internal/knowledge_base.go:
--------------------------------------------------------------------------------
1 | // ==========================================================================
2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
3 | // ==========================================================================
4 |
5 | package internal
6 |
7 | import (
8 | "context"
9 |
10 | "github.com/gogf/gf/v2/database/gdb"
11 | "github.com/gogf/gf/v2/frame/g"
12 | )
13 |
14 | // KnowledgeBaseDao is the data access object for the table knowledge_base.
15 | type KnowledgeBaseDao struct {
16 | table string // table is the underlying table name of the DAO.
17 | group string // group is the database configuration group name of the current DAO.
18 | columns KnowledgeBaseColumns // columns contains all the column names of Table for convenient usage.
19 | }
20 |
21 | // KnowledgeBaseColumns defines and stores column names for the table knowledge_base.
22 | type KnowledgeBaseColumns struct {
23 | Id string // 主键ID
24 | Name string // 知识库名称
25 | Description string // 知识库描述
26 | Category string // 知识库分类
27 | Status string // 状态:0-禁用,1-启用
28 | CreateTime string // 创建时间
29 | UpdateTime string // 更新时间
30 | }
31 |
32 | // knowledgeBaseColumns holds the columns for the table knowledge_base.
33 | var knowledgeBaseColumns = KnowledgeBaseColumns{
34 | Id: "id",
35 | Name: "name",
36 | Description: "description",
37 | Category: "category",
38 | Status: "status",
39 | CreateTime: "create_time",
40 | UpdateTime: "update_time",
41 | }
42 |
43 | // NewKnowledgeBaseDao creates and returns a new DAO object for table data access.
44 | func NewKnowledgeBaseDao() *KnowledgeBaseDao {
45 | return &KnowledgeBaseDao{
46 | group: "default",
47 | table: "knowledge_base",
48 | columns: knowledgeBaseColumns,
49 | }
50 | }
51 |
52 | // DB retrieves and returns the underlying raw database management object of the current DAO.
53 | func (dao *KnowledgeBaseDao) DB() gdb.DB {
54 | return g.DB(dao.group)
55 | }
56 |
57 | // Table returns the table name of the current DAO.
58 | func (dao *KnowledgeBaseDao) Table() string {
59 | return dao.table
60 | }
61 |
62 | // Columns returns all column names of the current DAO.
63 | func (dao *KnowledgeBaseDao) Columns() KnowledgeBaseColumns {
64 | return dao.columns
65 | }
66 |
67 | // Group returns the database configuration group name of the current DAO.
68 | func (dao *KnowledgeBaseDao) Group() string {
69 | return dao.group
70 | }
71 |
72 | // Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
73 | func (dao *KnowledgeBaseDao) Ctx(ctx context.Context) *gdb.Model {
74 | return dao.DB().Model(dao.table).Safe().Ctx(ctx)
75 | }
76 |
77 | // Transaction wraps the transaction logic using function f.
78 | // It rolls back the transaction and returns the error if function f returns a non-nil error.
79 | // It commits the transaction and returns nil if function f returns nil.
80 | //
81 | // Note: Do not commit or roll back the transaction in function f,
82 | // as it is automatically handled by this function.
83 | func (dao *KnowledgeBaseDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
84 | return dao.Ctx(ctx).Transaction(ctx, f)
85 | }
86 |
--------------------------------------------------------------------------------
/server/internal/dao/knowledge_base.go:
--------------------------------------------------------------------------------
1 | // =================================================================================
2 | // This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
3 | // =================================================================================
4 |
5 | package dao
6 |
7 | import (
8 | "github.com/wangle201210/go-rag/server/internal/dao/internal"
9 | )
10 |
11 | // internalKnowledgeBaseDao is an internal type for wrapping the internal DAO implementation.
12 | type internalKnowledgeBaseDao = *internal.KnowledgeBaseDao
13 |
14 | // knowledgeBaseDao is the data access object for the table knowledge_base.
15 | // You can define custom methods on it to extend its functionality as needed.
16 | type knowledgeBaseDao struct {
17 | internalKnowledgeBaseDao
18 | }
19 |
20 | var (
21 | // KnowledgeBase is a globally accessible object for table knowledge_base operations.
22 | KnowledgeBase = knowledgeBaseDao{
23 | internal.NewKnowledgeBaseDao(),
24 | }
25 | )
26 |
27 | // Add your custom methods and functionality below.
28 |
--------------------------------------------------------------------------------
/server/internal/logic/chat/chat.go:
--------------------------------------------------------------------------------
1 | package chat
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "io"
8 |
9 | "github.com/cloudwego/eino-ext/components/model/openai"
10 | "github.com/cloudwego/eino/components/model"
11 | "github.com/cloudwego/eino/schema"
12 | "github.com/gogf/gf/v2/frame/g"
13 | "github.com/gogf/gf/v2/os/gctx"
14 | "github.com/wangle201210/chat-history/eino"
15 | "github.com/wangle201210/go-rag/server/internal/dao"
16 | )
17 |
18 | var chat *Chat
19 |
20 | type Chat struct {
21 | cm model.BaseChatModel
22 | eh *eino.History
23 | }
24 |
25 | func GetChat() *Chat {
26 | return chat
27 | }
28 |
29 | // 暂时用不上chat功能,先不init
30 | func init() {
31 | ctx := gctx.New()
32 | c, err := newChat(&openai.ChatModelConfig{
33 | APIKey: g.Cfg().MustGet(ctx, "chat.apiKey").String(),
34 | BaseURL: g.Cfg().MustGet(ctx, "chat.baseURL").String(),
35 | Model: g.Cfg().MustGet(ctx, "chat.model").String(),
36 | })
37 | if err != nil {
38 | g.Log().Fatalf(ctx, "newChat failed, err=%v", err)
39 | return
40 | }
41 | c.eh = eino.NewEinoHistory(dao.GetDsn())
42 | chat = c
43 | }
44 |
45 | func newChat(cfg *openai.ChatModelConfig) (res *Chat, err error) {
46 | chatModel, err := openai.NewChatModel(context.Background(), cfg)
47 | if err != nil {
48 | return nil, err
49 | }
50 | return &Chat{cm: chatModel}, nil
51 | }
52 |
53 | func (x *Chat) GetAnswer(ctx context.Context, convID string, docs []*schema.Document, question string) (answer string, err error) {
54 | messages, err := x.docsMessages(ctx, convID, docs, question)
55 | if err != nil {
56 | return "", err
57 | }
58 | result, err := generate(ctx, x.cm, messages)
59 | if err != nil {
60 | return "", fmt.Errorf("生成答案失败: %w", err)
61 | }
62 | err = x.eh.SaveMessage(result, convID)
63 | if err != nil {
64 | g.Log().Error(ctx, "save assistant message err: %v", err)
65 | return
66 | }
67 | return result.Content, nil
68 | }
69 |
70 | // GetAnswerStream 流式生成答案
71 | func (x *Chat) GetAnswerStream(ctx context.Context, convID string, docs []*schema.Document, question string) (answer *schema.StreamReader[*schema.Message], err error) {
72 | messages, err := x.docsMessages(ctx, convID, docs, question)
73 | if err != nil {
74 | return
75 | }
76 | ctx = context.Background()
77 | streamData, err := stream(ctx, x.cm, messages)
78 | if err != nil {
79 | err = fmt.Errorf("生成答案失败: %w", err)
80 | }
81 | srs := streamData.Copy(2)
82 | go func() {
83 | // for save
84 | fullMsgs := make([]*schema.Message, 0)
85 | defer func() {
86 | srs[1].Close()
87 | fullMsg, err := schema.ConcatMessages(fullMsgs)
88 | if err != nil {
89 | g.Log().Error(ctx, "error concatenating messages: %v", err)
90 | return
91 | }
92 | err = x.eh.SaveMessage(fullMsg, convID)
93 | if err != nil {
94 | g.Log().Error(ctx, "save assistant message err: %v", err)
95 | return
96 | }
97 | }()
98 | outer:
99 | for {
100 | select {
101 | case <-ctx.Done():
102 | fmt.Println("context done", ctx.Err())
103 | return
104 | default:
105 | chunk, err := srs[1].Recv()
106 | if err != nil {
107 | if errors.Is(err, io.EOF) {
108 | break outer
109 | }
110 | }
111 | fullMsgs = append(fullMsgs, chunk)
112 | }
113 | }
114 | }()
115 |
116 | return srs[0], nil
117 |
118 | }
119 |
120 | func generate(ctx context.Context, llm model.BaseChatModel, in []*schema.Message) (message *schema.Message, err error) {
121 | message, err = llm.Generate(ctx, in)
122 | if err != nil {
123 | err = fmt.Errorf("llm generate failed: %v", err)
124 | return
125 | }
126 | return
127 | }
128 |
129 | func stream(ctx context.Context, llm model.BaseChatModel, in []*schema.Message) (res *schema.StreamReader[*schema.Message], err error) {
130 | res, err = llm.Stream(ctx, in)
131 | if err != nil {
132 | err = fmt.Errorf("llm generate failed: %v", err)
133 | return
134 | }
135 | return
136 | }
137 |
--------------------------------------------------------------------------------
/server/internal/logic/chat/message.go:
--------------------------------------------------------------------------------
1 | package chat
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/cloudwego/eino/components/prompt"
8 | "github.com/cloudwego/eino/schema"
9 | "github.com/gogf/gf/v2/frame/g"
10 | )
11 |
12 | const (
13 | role = "你是一个专业的AI助手,能够根据提供的参考信息准确回答用户问题。"
14 | )
15 |
16 | // createTemplate 创建并返回一个配置好的聊天模板
17 | func createTemplate() prompt.ChatTemplate {
18 | // 创建模板,使用 FString 格式
19 | return prompt.FromMessages(schema.FString,
20 | // 系统消息模板
21 | schema.SystemMessage("{role}"+
22 | "请严格遵守以下规则:\n"+
23 | "1. 回答必须基于提供的参考内容,不要依赖外部知识\n"+
24 | "2. 如果参考内容中有明确答案,直接使用参考内容回答\n"+
25 | "3. 如果参考内容不完整或模糊,可以合理推断但需说明\n"+
26 | "4. 如果参考内容完全不相关或不存在,如实告知用户'根据现有资料无法回答'\n"+
27 | "5. 保持回答专业、简洁、准确\n"+
28 | "6. 必要时可引用参考内容中的具体数据或原文\n\n"+
29 | "当前提供的参考内容:\n"+
30 | "{docs}\n\n"+
31 | ""),
32 | schema.MessagesPlaceholder("chat_history", true),
33 | // 用户消息模板
34 | schema.UserMessage("Question: {question}"),
35 | )
36 | }
37 |
38 | // formatMessages 格式化消息并处理错误
39 | func formatMessages(template prompt.ChatTemplate, data map[string]any) ([]*schema.Message, error) {
40 | messages, err := template.Format(context.Background(), data)
41 | if err != nil {
42 | return nil, fmt.Errorf("格式化模板失败: %w", err)
43 | }
44 | return messages, nil
45 | }
46 |
47 | // docsMessages 将检索到的上下文和问题转换为消息列表
48 | func (x *Chat) docsMessages(ctx context.Context, convID string, docs []*schema.Document, question string) (messages []*schema.Message, err error) {
49 | chatHistory, err := x.eh.GetHistory(convID, 100)
50 | if err != nil {
51 | return
52 | }
53 | // 插入一条用户数据
54 | err = x.eh.SaveMessage(&schema.Message{
55 | Role: schema.User,
56 | Content: question,
57 | }, convID)
58 | if err != nil {
59 | return
60 | }
61 | template := createTemplate()
62 | for i, doc := range docs {
63 | g.Log().Debugf(context.Background(), "docs[%d]: %s", i, doc.Content)
64 | }
65 | data := map[string]any{
66 | "role": role,
67 | "question": question,
68 | "docs": docs,
69 | "chat_history": chatHistory,
70 | }
71 | messages, err = formatMessages(template, data)
72 | if err != nil {
73 | return
74 | }
75 | return
76 | }
77 |
--------------------------------------------------------------------------------
/server/internal/logic/rag/retriever.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | "github.com/elastic/go-elasticsearch/v8"
8 | "github.com/gogf/gf/v2/frame/g"
9 | "github.com/wangle201210/go-rag/server/core"
10 | "github.com/wangle201210/go-rag/server/core/config"
11 | )
12 |
13 | var ragSvr = &core.Rag{}
14 |
15 | func init() {
16 | ctx := context.Background()
17 | client, err := elasticsearch.NewClient(elasticsearch.Config{
18 | Addresses: []string{g.Cfg().MustGet(ctx, "es.address").String()},
19 | Username: g.Cfg().MustGet(ctx, "es.username").String(),
20 | Password: g.Cfg().MustGet(ctx, "es.password").String(),
21 | })
22 | if err != nil {
23 | log.Printf("NewClient of es8 failed, err=%v", err)
24 | return
25 | }
26 | ragSvr, err = core.New(ctx, &config.Config{
27 | Client: client,
28 | IndexName: g.Cfg().MustGet(ctx, "es.indexName").String(),
29 | APIKey: g.Cfg().MustGet(ctx, "embedding.apiKey").String(),
30 | BaseURL: g.Cfg().MustGet(ctx, "embedding.baseURL").String(),
31 | EmbeddingModel: g.Cfg().MustGet(ctx, "embedding.model").String(),
32 | ChatModel: g.Cfg().MustGet(ctx, "chat.model").String(),
33 | })
34 | if err != nil {
35 | log.Printf("New of rag failed, err=%v", err)
36 | return
37 | }
38 | }
39 |
40 | func GetRagSvr() *core.Rag {
41 | return ragSvr
42 | }
43 |
--------------------------------------------------------------------------------
/server/internal/mcp/indexer.go:
--------------------------------------------------------------------------------
1 | package mcp
2 |
3 | import (
4 | "context"
5 | "encoding/base64"
6 | "fmt"
7 |
8 | "github.com/ThinkInAIXYZ/go-mcp/protocol"
9 | "github.com/gogf/gf/v2/frame/g"
10 | "github.com/gogf/gf/v2/os/gctx"
11 | gorag "github.com/wangle201210/go-rag/server/core"
12 | "github.com/wangle201210/go-rag/server/internal/logic/rag"
13 | )
14 |
15 | type IndexParam struct {
16 | URI string `json:"uri" description:"文件路径" required:"true"` // 可以是文件路径(pdf,html,md等),也可以是网址
17 | KnowledgeName string `json:"knowledge_name" description:"知识库名字,请先通过getKnowledgeBaseList获取列表后判断是否有符合的知识库,如果没有则根据用户提示词自己生成" required:"true"`
18 | }
19 |
20 | func GetIndexerByFilePathTool() *protocol.Tool {
21 | tool, err := protocol.NewTool("Indexer_by_filepath", "通过文件路径进行文本嵌入", IndexParam{})
22 | if err != nil {
23 | g.Log().Errorf(gctx.New(), "Failed to create tool: %v", err)
24 | return nil
25 | }
26 | return tool
27 | }
28 |
29 | func HandleIndexerByFilePath(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
30 | var reqData IndexParam
31 | if err := protocol.VerifyAndUnmarshal(req.RawArguments, &reqData); err != nil {
32 | return nil, err
33 | }
34 | svr := rag.GetRagSvr()
35 | uri := reqData.URI
36 | indexReq := &gorag.IndexReq{
37 | URI: uri,
38 | KnowledgeName: reqData.KnowledgeName,
39 | }
40 | ids, err := svr.Index(ctx, indexReq)
41 | if err != nil {
42 | return nil, err
43 | }
44 | msg := fmt.Sprintf("index file %s successfully, knowledge_name: %s, doc_ids: %v", uri, reqData.KnowledgeName, ids)
45 | return &protocol.CallToolResult{
46 | Content: []protocol.Content{
47 | &protocol.TextContent{
48 | Type: "text",
49 | Text: msg,
50 | },
51 | },
52 | }, nil
53 | }
54 |
55 | type IndexFileParam struct {
56 | Filename string `json:"filename" description:"文件名字" required:"true"`
57 | Content string `json:"content" description:"被base64编码后的文件内容,先调用工具获取base64信息" required:"true"` // 可以是文件路径(pdf,html,md等),也可以是网址文件" required:"true"` // 可以是文件路径(pdf,html,md等),也可以是网址
58 | KnowledgeName string `json:"knowledge_name" description:"知识库名字,请先通过getKnowledgeBaseList获取列表后判断是否有符合的知识库,如果没有则根据用户提示词自己生成" required:"true"`
59 | }
60 |
61 | func GetIndexerByFileBase64ContentTool() *protocol.Tool {
62 | tool, err := protocol.NewTool("Indexer_by_base64_file_content", "获取文件base64信息后上传,然后对内容进行文本嵌入", IndexFileParam{})
63 | if err != nil {
64 | g.Log().Errorf(gctx.New(), "Failed to create tool: %v", err)
65 | return nil
66 | }
67 | return tool
68 | }
69 |
70 | func HandleIndexerByFileBase64Content(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
71 | var reqData IndexFileParam
72 | if err := protocol.VerifyAndUnmarshal(req.RawArguments, &reqData); err != nil {
73 | return nil, err
74 | }
75 | // svr := rag.GetRagSvr()
76 | decoded, err := base64.StdEncoding.DecodeString(reqData.Content)
77 | if err != nil {
78 | return nil, err
79 | }
80 | fmt.Println(decoded)
81 | // indexReq := &gorag.IndexReq{
82 | // URI: uri,
83 | // KnowledgeName: reqData.KnowledgeName,
84 | // }
85 | // ids, err := svr.Index(ctx, indexReq)
86 | // if err != nil {
87 | // return nil, err
88 | // }
89 | // msg := fmt.Sprintf("index file %s successfully, knowledge_name: %s, doc_ids: %v", uri, reqData.KnowledgeName, ids)
90 | return &protocol.CallToolResult{
91 | Content: []protocol.Content{
92 | &protocol.TextContent{
93 | Type: "text",
94 | Text: string(decoded),
95 | },
96 | },
97 | }, nil
98 | }
99 |
--------------------------------------------------------------------------------
/server/internal/mcp/knowledgebase.go:
--------------------------------------------------------------------------------
1 | package mcp
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/ThinkInAIXYZ/go-mcp/protocol"
8 | "github.com/gogf/gf/v2/frame/g"
9 | "github.com/gogf/gf/v2/os/gctx"
10 | v1 "github.com/wangle201210/go-rag/server/api/rag/v1"
11 | )
12 |
13 | type KnowledgeBaseParam struct {
14 | }
15 |
16 | func GetKnowledgeBaseTool() *protocol.Tool {
17 | tool, err := protocol.NewTool("getKnowledgeBaseList", "获取知识库列表", KnowledgeBaseParam{})
18 | if err != nil {
19 | g.Log().Errorf(gctx.New(), "Failed to create tool: %v", err)
20 | return nil
21 | }
22 | return tool
23 | }
24 |
25 | func HandleKnowledgeBase(ctx context.Context, toolReq *protocol.CallToolRequest) (res *protocol.CallToolResult, err error) {
26 | statusOK := v1.StatusOK
27 | getList, err := c.KBGetList(ctx, &v1.KBGetListReq{
28 | Status: &statusOK,
29 | })
30 | if err != nil {
31 | return nil, err
32 | }
33 | list := getList.List
34 | msg := fmt.Sprintf("get %d knowledgeBase", len(list))
35 | for _, l := range list {
36 | msg += fmt.Sprintf("\n - name: %s, description: %s", l.Name, l.Description)
37 | }
38 | return &protocol.CallToolResult{
39 | Content: []protocol.Content{
40 | &protocol.TextContent{
41 | Type: "text",
42 | Text: msg,
43 | },
44 | },
45 | }, nil
46 | }
47 |
--------------------------------------------------------------------------------
/server/internal/mcp/mcp.go:
--------------------------------------------------------------------------------
1 | package mcp
2 |
3 | import "github.com/wangle201210/go-rag/server/internal/controller/rag"
4 |
5 | var c = rag.NewV1()
6 |
--------------------------------------------------------------------------------
/server/internal/mcp/retriever.go:
--------------------------------------------------------------------------------
1 | package mcp
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/ThinkInAIXYZ/go-mcp/protocol"
8 | "github.com/gogf/gf/v2/frame/g"
9 | "github.com/gogf/gf/v2/os/gctx"
10 | v1 "github.com/wangle201210/go-rag/server/api/rag/v1"
11 | )
12 |
13 | type RetrieverParam struct {
14 | Question string `json:"question" description:"用户提问的问题" required:"true"`
15 | KnowledgeName string `json:"knowledge_name" description:"知识库名称,请先通过getKnowledgeBaseList获取列表后判断是否有符合用户提示词的知识库" required:"true"`
16 | TopK int `json:"top_k" description:"检索结果的数量,默认为5" required:"false"` // 默认为5
17 | Score float64 `json:"score" description:"检索结果的分数阀值,默认为0.2" required:"false"` // 默认为0.2
18 | }
19 |
20 | func GetRetrieverTool() *protocol.Tool {
21 | tool, err := protocol.NewTool("retriever", "检索知识库文档", RetrieverParam{})
22 | if err != nil {
23 | g.Log().Errorf(gctx.New(), "Failed to create tool: %v", err)
24 | return nil
25 | }
26 | return tool
27 | }
28 |
29 | func HandleRetriever(ctx context.Context, toolReq *protocol.CallToolRequest) (res *protocol.CallToolResult, err error) {
30 | var req RetrieverParam
31 | if err := protocol.VerifyAndUnmarshal(toolReq.RawArguments, &req); err != nil {
32 | return nil, err
33 | }
34 | retriever, err := c.Retriever(ctx, &v1.RetrieverReq{
35 | Question: req.Question,
36 | TopK: req.TopK,
37 | Score: req.Score,
38 | KnowledgeName: req.KnowledgeName,
39 | })
40 | if err != nil {
41 | return nil, err
42 | }
43 | docs := retriever.Document
44 | msg := fmt.Sprintf("retrieve %d documents", len(docs))
45 | for i, doc := range docs {
46 | msg += fmt.Sprintf("\n%d. score: %.2f, content: %s", i+1, doc.Score(), doc.Content)
47 | }
48 | return &protocol.CallToolResult{
49 | Content: []protocol.Content{
50 | &protocol.TextContent{
51 | Type: "text",
52 | Text: msg,
53 | },
54 | },
55 | }, nil
56 | }
57 |
--------------------------------------------------------------------------------
/server/internal/model/do/knowledge_base.go:
--------------------------------------------------------------------------------
1 | // =================================================================================
2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
3 | // =================================================================================
4 |
5 | package do
6 |
7 | import (
8 | "github.com/gogf/gf/v2/frame/g"
9 | "github.com/gogf/gf/v2/os/gtime"
10 | )
11 |
12 | // KnowledgeBase is the golang structure of table knowledge_base for DAO operations like Where/Data.
13 | type KnowledgeBase struct {
14 | g.Meta `orm:"table:knowledge_base, do:true"`
15 | Id interface{} // 主键ID
16 | Name interface{} // 知识库名称
17 | Description interface{} // 知识库描述
18 | Category interface{} // 知识库分类
19 | Status interface{} // 状态:0-禁用,1-启用
20 | CreateTime *gtime.Time // 创建时间
21 | UpdateTime *gtime.Time // 更新时间
22 | }
23 |
--------------------------------------------------------------------------------
/server/internal/model/entity/knowledge_base.go:
--------------------------------------------------------------------------------
1 | // =================================================================================
2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
3 | // =================================================================================
4 |
5 | package entity
6 |
7 | import (
8 | "github.com/gogf/gf/v2/os/gtime"
9 | )
10 |
11 | // KnowledgeBase is the golang structure for table knowledge_base.
12 | type KnowledgeBase struct {
13 | Id int64 `json:"id" orm:"id" description:"主键ID"` // 主键ID
14 | Name string `json:"name" orm:"name" description:"知识库名称"` // 知识库名称
15 | Description string `json:"description" orm:"description" description:"知识库描述"` // 知识库描述
16 | Category string `json:"category" orm:"category" description:"知识库分类"` // 知识库分类
17 | Status int `json:"status" orm:"status" description:"状态:0-禁用,1-启用"` // 状态:0-禁用,1-启用
18 | CreateTime *gtime.Time `json:"createTime" orm:"create_time" description:"创建时间"` // 创建时间
19 | UpdateTime *gtime.Time `json:"updateTime" orm:"update_time" description:"更新时间"` // 更新时间
20 | }
21 |
--------------------------------------------------------------------------------
/server/internal/model/knowledge_base.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/wangle201210/chat-history/models"
7 | "gorm.io/gorm"
8 | )
9 |
10 | // KnowledgeBase
11 | // 仅供gorm自动创建表使用
12 | type KnowledgeBase struct {
13 | Id int64 `gorm:"primaryKey;column:id"`
14 | Name string `gorm:"column:name;type:varchar(255)"`
15 | Description string `gorm:"column:description;type:varchar(255)"`
16 | Category string `gorm:"column:category;type:varchar(255)"`
17 | Status int `gorm:"column:status;default:1"`
18 | CreateTime time.Time `gorm:"column:created_at"`
19 | UpdateTime time.Time `gorm:"column:updated_at"`
20 | }
21 |
22 | // TableName 设置表名
23 | func (KnowledgeBase) TableName() string {
24 | return "knowledge_base"
25 | }
26 |
27 | func autoMigrateTables(db *gorm.DB) error {
28 | // 自动迁移会创建表、缺失的外键、约束、列和索引
29 | return db.AutoMigrate(
30 | &models.Conversation{},
31 | &models.Message{},
32 | &models.Attachment{},
33 | &models.MessageAttachment{},
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gogf/gf/v2/os/gctx"
5 |
6 | _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
7 | "github.com/wangle201210/go-rag/server/internal/cmd"
8 | )
9 |
10 | func main() {
11 | cmd.Main.Run(gctx.GetInitCtx())
12 | }
13 |
--------------------------------------------------------------------------------
/server/manifest/config/config_demo.yaml:
--------------------------------------------------------------------------------
1 | server:
2 | address: ":8000"
3 | openapiPath: "/api.json"
4 | swaggerPath: "/swagger"
5 |
6 | logger:
7 | level : "all"
8 | stdout: true
9 |
10 | database:
11 | default:
12 | host: "mysql" # 地址
13 | port: "3306" # 端口
14 | user: "root" # 账号
15 | pass: "123456" # 密码
16 | name: "go-rag" # 数据库名称
17 | type: "mysql" # 数据库类型
18 |
19 | es:
20 | address: "http://elasticsearch:9200"
21 | indexName: "rag-test"
22 | # username: "elastic"
23 | # password: "123456"
24 |
25 | embedding:
26 | model: "text-embedding-3-large"
27 | apiKey: "sk-***"
28 | baseURL: "https://api.openai.com/v1"
29 |
30 | chat:
31 | apiKey: "sk-***"
32 | baseURL: "https://api.openai.com/v1"
33 | model: "gpt-4o"
34 |
--------------------------------------------------------------------------------
/server/static/chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangle201210/go-rag/29d54bcfff76f50e5f5f8532653a2091d16cbca5/server/static/chat.png
--------------------------------------------------------------------------------
/server/static/indexer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangle201210/go-rag/29d54bcfff76f50e5f5f8532653a2091d16cbca5/server/static/indexer.png
--------------------------------------------------------------------------------
/server/static/kb-select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangle201210/go-rag/29d54bcfff76f50e5f5f8532653a2091d16cbca5/server/static/kb-select.png
--------------------------------------------------------------------------------
/server/static/kb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangle201210/go-rag/29d54bcfff76f50e5f5f8532653a2091d16cbca5/server/static/kb.png
--------------------------------------------------------------------------------
/server/static/mcp-cfg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangle201210/go-rag/29d54bcfff76f50e5f5f8532653a2091d16cbca5/server/static/mcp-cfg.png
--------------------------------------------------------------------------------
/server/static/mcp-use.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangle201210/go-rag/29d54bcfff76f50e5f5f8532653a2091d16cbca5/server/static/mcp-use.png
--------------------------------------------------------------------------------
/server/static/retriever.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangle201210/go-rag/29d54bcfff76f50e5f5f8532653a2091d16cbca5/server/static/retriever.png
--------------------------------------------------------------------------------