From 498fbaa75f9e9d9acf0e66b23cf5fc37beeb28cd Mon Sep 17 00:00:00 2001
From: chennan
Date: Sat, 25 Oct 2025 13:48:50 +0800
Subject: [PATCH 1/3] init
---
.dockerignore | 0
.env.example | 210 +
.gitattributes | 1 +
.gitignore | 38 +
CHANGELOG.md | 103 +
LICENSE | 2692 ++++++++++++
Makefile | 215 +
README.md | 395 +-
README_CN.md | 326 ++
README_JA.md | 328 ++
VERSION | 1 +
client/README.md | 183 +
client/README_EN.md | 184 +
client/chunk.go | 171 +
client/client.go | 104 +
client/evaluation.go | 113 +
client/example.go | 271 ++
client/go.mod | 3 +
client/go.sum | 0
client/knowledge.go | 356 ++
client/knowledgebase.go | 211 +
client/message.go | 82 +
client/model.go | 155 +
client/session.go | 403 ++
client/tenant.go | 136 +
cmd/server/main.go | 104 +
config/config.yaml | 602 +++
dataset/README | 273 ++
dataset/README_zh.md | 284 ++
dataset/qa_dataset.py | 381 ++
dataset/samples/answers.parquet | Bin 0 -> 1949 bytes
dataset/samples/corpus.parquet | Bin 0 -> 3902 bytes
dataset/samples/qas.parquet | Bin 0 -> 1585 bytes
dataset/samples/qrels.parquet | Bin 0 -> 2067 bytes
dataset/samples/queries.parquet | Bin 0 -> 1677 bytes
docker-compose.yml | 246 ++
docker/Dockerfile.app | 79 +
docker/Dockerfile.docreader | 160 +
docker/config/supervisord.conf | 31 +
docker/docker-compose.minio.yml | 29 +
docs/API.md | 2105 +++++++++
docs/QA.md | 100 +
docs/WeKnora.md | 319 ++
docs/WeKnora.pdf | Bin 0 -> 2020058 bytes
docs/images/answer.png | Bin 0 -> 853609 bytes
docs/images/arc.png | Bin 0 -> 35764 bytes
docs/images/config.png | Bin 0 -> 701060 bytes
docs/images/graph1.png | Bin 0 -> 835658 bytes
docs/images/graph2.png | Bin 0 -> 406651 bytes
docs/images/knowledges.png | Bin 0 -> 179689 bytes
docs/images/logo.png | Bin 0 -> 115448 bytes
docs/images/pipeline.jpg | Bin 0 -> 66763 bytes
docs/images/pipeline.png | Bin 0 -> 516509 bytes
docs/images/pipeline2.jpeg | Bin 0 -> 106731 bytes
docs/images/qa.png | Bin 0 -> 87783 bytes
...17\346\225\260\346\215\256\345\272\223.md" | 190 +
frontend/.dockerignore | 8 +
frontend/.gitignore | 30 +
frontend/Dockerfile | 36 +
frontend/env.d.ts | 6 +
frontend/index.html | 22 +
frontend/nginx.conf | 52 +
frontend/package-lock.json | 3821 +++++++++++++++++
frontend/package.json | 50 +
frontend/public/favicon.ico | Bin 0 -> 5558 bytes
frontend/src/App.vue | 23 +
frontend/src/api/auth/index.ts | 239 ++
frontend/src/api/chat/index.ts | 31 +
frontend/src/api/chat/streame.ts | 122 +
frontend/src/api/initialization/index.ts | 421 ++
frontend/src/api/knowledge-base/index.ts | 55 +
frontend/src/api/system/index.ts | 12 +
frontend/src/assets/fonts.css | 6 +
frontend/src/assets/fonts/TencentSans.ttf | Bin 0 -> 8537656 bytes
frontend/src/assets/img/Frame3718.svg | 6 +
frontend/src/assets/img/botanswer_loading.gif | Bin 0 -> 555932 bytes
frontend/src/assets/img/circle.png | Bin 0 -> 824 bytes
frontend/src/assets/img/delete.svg | 5 +
frontend/src/assets/img/download.svg | 4 +
frontend/src/assets/img/file-add-green.svg | 4 +
frontend/src/assets/img/file-add-icon.svg | 4 +
frontend/src/assets/img/file-add.svg | 4 +
frontend/src/assets/img/logout.svg | 6 +
frontend/src/assets/img/more.png | Bin 0 -> 128 bytes
frontend/src/assets/img/prefixIcon-green.svg | 4 +
frontend/src/assets/img/prefixIcon-grey.svg | 4 +
frontend/src/assets/img/prefixIcon.svg | 4 +
frontend/src/assets/img/sending-aircraft.svg | 3 +
frontend/src/assets/img/setting-green.svg | 5 +
frontend/src/assets/img/setting.svg | 5 +
frontend/src/assets/img/think.gif | Bin 0 -> 342330 bytes
frontend/src/assets/img/upload-mask.svg | 64 +
frontend/src/assets/img/upload.svg | 100 +
frontend/src/assets/img/user-green.svg | 4 +
frontend/src/assets/img/user.svg | 4 +
frontend/src/assets/img/weknora.png | Bin 0 -> 7295 bytes
frontend/src/assets/img/zhishiku-green.svg | 3 +
frontend/src/assets/img/zhishiku.svg | 3 +
frontend/src/assets/img/ziliao.svg | 3 +
frontend/src/assets/theme/theme.css | 95 +
frontend/src/components/Input-field.vue | 135 +
frontend/src/components/css/markdown.less | 426 ++
frontend/src/components/doc-content.vue | 263 ++
frontend/src/components/empty-knowledge.vue | 42 +
frontend/src/components/menu.vue | 987 +++++
frontend/src/components/picture-preview.vue | 18 +
frontend/src/components/upload-mask.vue | 40 +
frontend/src/hooks/useKnowledgeBase.ts | 166 +
frontend/src/main.ts | 16 +
frontend/src/router/index.ts | 117 +
frontend/src/stores/auth.ts | 169 +
frontend/src/stores/knowledge.ts | 11 +
frontend/src/stores/menu.ts | 50 +
frontend/src/stores/settings.ts | 51 +
frontend/src/utils/index.ts | 44 +
frontend/src/utils/request.ts | 198 +
frontend/src/utils/security.ts | 207 +
frontend/src/views/auth/Login.vue | 553 +++
frontend/src/views/chat/components/botmsg.vue | 248 ++
.../src/views/chat/components/deepThink.vue | 151 +
.../src/views/chat/components/docInfo.vue | 138 +
.../src/views/chat/components/sendMsg.vue | 20 +
.../src/views/chat/components/usermsg.vue | 38 +
frontend/src/views/chat/index.vue | 312 ++
frontend/src/views/creatChat/creatChat.vue | 216 +
.../initialization/InitializationContent.vue | 3567 +++++++++++++++
.../src/views/knowledge/KnowledgeBase.vue | 627 +++
.../src/views/knowledge/KnowledgeBaseList.vue | 234 +
frontend/src/views/platform/index.vue | 138 +
frontend/src/views/settings/Settings.vue | 100 +
.../src/views/settings/SystemSettings.vue | 211 +
frontend/src/views/tenant/TenantInfo.vue | 535 +++
frontend/tsconfig.app.json | 12 +
frontend/tsconfig.json | 11 +
frontend/tsconfig.node.json | 19 +
frontend/vite.config.ts | 20 +
go.mod | 113 +
go.sum | 297 ++
internal/application/repository/chunk.go | 131 +
internal/application/repository/knowledge.go | 204 +
.../application/repository/knowledgebase.go | 70 +
internal/application/repository/message.go | 153 +
internal/application/repository/model.go | 77 +
.../retriever/elasticsearch/structs.go | 62 +
.../retriever/elasticsearch/v7/repository.go | 1030 +++++
.../retriever/elasticsearch/v8/repository.go | 541 +++
.../repository/retriever/neo4j/repository.go | 231 +
.../retriever/postgres/repository.go | 390 ++
.../repository/retriever/postgres/structs.go | 88 +
internal/application/repository/session.go | 89 +
internal/application/repository/tenant.go | 82 +
internal/application/repository/user.go | 154 +
.../service/chat_pipline/chat_completion.go | 60 +
.../chat_pipline/chat_completion_stream.go | 62 +
.../service/chat_pipline/chat_pipline.go | 161 +
.../service/chat_pipline/chat_pipline_test.go | 125 +
.../service/chat_pipline/common.go | 67 +
.../service/chat_pipline/extract_entity.go | 499 +++
.../service/chat_pipline/filter_top_k.go | 53 +
.../service/chat_pipline/into_chat_message.go | 191 +
.../application/service/chat_pipline/merge.go | 175 +
.../service/chat_pipline/preprocess.go | 180 +
.../service/chat_pipline/rerank.go | 171 +
.../service/chat_pipline/rewrite.go | 176 +
.../service/chat_pipline/search.go | 125 +
.../service/chat_pipline/search_entity.go | 136 +
.../service/chat_pipline/stream_filter.go | 84 +
.../service/chat_pipline/tracing.go | 270 ++
internal/application/service/chunk.go | 278 ++
internal/application/service/dataset.go | 243 ++
internal/application/service/evaluation.go | 454 ++
internal/application/service/extract.go | 137 +
internal/application/service/file/cos.go | 83 +
internal/application/service/file/dummy.go | 38 +
internal/application/service/file/local.go | 111 +
internal/application/service/file/minio.go | 126 +
internal/application/service/graph.go | 995 +++++
internal/application/service/knowledge.go | 1804 ++++++++
internal/application/service/knowledgebase.go | 578 +++
internal/application/service/message.go | 287 ++
internal/application/service/metric/bleu.go | 165 +
internal/application/service/metric/common.go | 133 +
internal/application/service/metric/map.go | 70 +
.../application/service/metric/map_test.go | 85 +
internal/application/service/metric/mrr.go | 48 +
.../application/service/metric/mrr_test.go | 72 +
internal/application/service/metric/ndcg.go | 78 +
.../application/service/metric/precision.go | 33 +
.../service/metric/precision_test.go | 67 +
internal/application/service/metric/recall.go | 33 +
.../application/service/metric/recall_test.go | 70 +
internal/application/service/metric/rouge.go | 72 +
.../application/service/metric/rouge_score.go | 252 ++
internal/application/service/metric_hook.go | 167 +
internal/application/service/model.go | 327 ++
.../service/retriever/composite.go | 289 ++
.../keywords_vector_hybrid_indexer.go | 163 +
.../application/service/retriever/registry.go | 64 +
internal/application/service/session.go | 517 +++
internal/application/service/tenant.go | 291 ++
internal/application/service/user.go | 449 ++
internal/common/tools.go | 100 +
internal/config/config.go | 190 +
internal/container/cleanup.go | 86 +
internal/container/container.go | 418 ++
internal/errors/errors.go | 152 +
internal/errors/session.go | 16 +
internal/handler/auth.go | 343 ++
internal/handler/chunk.go | 241 ++
internal/handler/evaluation.go | 105 +
internal/handler/initialization.go | 1775 ++++++++
internal/handler/knowledge.go | 476 ++
internal/handler/knowledgebase.go | 297 ++
internal/handler/message.go | 135 +
internal/handler/model.go | 265 ++
internal/handler/session.go | 805 ++++
internal/handler/system.go | 49 +
internal/handler/tenant.go | 226 +
internal/logger/logger.go | 252 ++
internal/middleware/auth.go | 149 +
internal/middleware/error_handler.go | 46 +
internal/middleware/logger.go | 83 +
internal/middleware/recovery.go | 34 +
internal/middleware/trace.go | 124 +
internal/models/chat/chat.go | 72 +
internal/models/chat/ollama.go | 191 +
internal/models/chat/remote_api.go | 271 ++
internal/models/chat/remote_api_test.go | 127 +
internal/models/embedding/batch.go | 83 +
internal/models/embedding/embedder.go | 76 +
internal/models/embedding/ollama.go | 125 +
internal/models/embedding/openai.go | 198 +
internal/models/rerank/aliyun_reranker.go | 164 +
internal/models/rerank/remote_api.go | 122 +
internal/models/rerank/reranker.go | 97 +
internal/models/rerank/reranker_test.go | 156 +
internal/models/utils/ollama/ollama.go | 316 ++
internal/models/utils/slices.go | 36 +
internal/router/router.go | 282 ++
internal/router/task.go | 66 +
internal/runtime/container.go | 23 +
internal/stream/factory.go | 36 +
internal/stream/memory_manager.go | 123 +
internal/stream/redis_manager.go | 192 +
internal/tracing/init.go | 105 +
internal/types/chat.go | 64 +
internal/types/chat_manage.go | 118 +
internal/types/chunk.go | 90 +
internal/types/cleanup.go | 4 +
internal/types/const.go | 20 +
internal/types/dataset.go | 11 +
internal/types/embedding.go | 34 +
internal/types/errors.go | 46 +
internal/types/evaluation.go | 99 +
internal/types/extract_graph.go | 51 +
internal/types/graph.go | 53 +
internal/types/interfaces/chunk.go | 62 +
internal/types/interfaces/evaluation.go | 35 +
internal/types/interfaces/extracter.go | 11 +
internal/types/interfaces/file.go | 18 +
internal/types/interfaces/knowledge.go | 74 +
internal/types/interfaces/knowledgebase.go | 138 +
internal/types/interfaces/message.go | 41 +
internal/types/interfaces/model.go | 49 +
internal/types/interfaces/resource.go | 19 +
internal/types/interfaces/retriever.go | 109 +
internal/types/interfaces/retriever_graph.go | 13 +
internal/types/interfaces/session.go | 49 +
internal/types/interfaces/stream_manager.go | 34 +
internal/types/interfaces/tenant.go | 41 +
internal/types/interfaces/user.go | 75 +
internal/types/json.go | 68 +
internal/types/knowledge.go | 92 +
internal/types/knowledgebase.go | 195 +
internal/types/message.go | 60 +
internal/types/model.go | 105 +
internal/types/retriever.go | 82 +
internal/types/search.go | 129 +
internal/types/session.go | 120 +
internal/types/tenant.go | 69 +
internal/types/user.go | 114 +
internal/utils/security.go | 171 +
mcp-server/.gitignore | 2 +
mcp-server/CHANGELOG.md | 98 +
mcp-server/EXAMPLES.md | 411 ++
mcp-server/INSTALL.md | 208 +
mcp-server/LICENSE | 21 +
mcp-server/MANIFEST.in | 14 +
mcp-server/PROJECT_SUMMARY.md | 279 ++
mcp-server/README.md | 137 +
mcp-server/__init__.py | 14 +
mcp-server/main.py | 141 +
mcp-server/pyproject.toml | 108 +
mcp-server/requirements.txt | 2 +
mcp-server/run.py | 44 +
mcp-server/run_server.py | 43 +
mcp-server/setup.py | 63 +
mcp-server/test_imports.py | 38 +
mcp-server/test_module.py | 231 +
mcp-server/weknora_mcp_server.py | 672 +++
migrations/mysql/00-init-db.sql | 153 +
migrations/paradedb/00-init-db.sql | 205 +
.../paradedb/01-migrate-to-paradedb.sql | 69 +
packages/.gitkeep | 1 +
rerank_server_demo.py | 102 +
scripts/build_images.sh | 350 ++
scripts/get_version.sh | 86 +
scripts/migrate.sh | 54 +
scripts/start_all.sh | 729 ++++
services/docreader/Makefile | 23 +
services/docreader/poetry.lock | 2228 ++++++++++
services/docreader/pyproject.toml | 19 +
services/docreader/requirements.txt | 32 +
services/docreader/scripts/generate_proto.sh | 31 +
services/docreader/src/__init__.py | 0
services/docreader/src/client/client.go | 115 +
services/docreader/src/client/client_test.go | 154 +
services/docreader/src/download_deps.py | 70 +
services/docreader/src/parser/__init__.py | 42 +
services/docreader/src/parser/base_parser.py | 1244 ++++++
services/docreader/src/parser/caption.py | 360 ++
services/docreader/src/parser/config.py | 21 +
services/docreader/src/parser/doc_parser.py | 315 ++
services/docreader/src/parser/docx_parser.py | 1489 +++++++
services/docreader/src/parser/image_parser.py | 68 +
services/docreader/src/parser/image_utils.py | 43 +
.../docreader/src/parser/markdown_parser.py | 33 +
services/docreader/src/parser/ocr_engine.py | 278 ++
services/docreader/src/parser/parser.py | 206 +
services/docreader/src/parser/pdf_parser.py | 113 +
services/docreader/src/parser/storage.py | 360 ++
services/docreader/src/parser/text_parser.py | 58 +
services/docreader/src/parser/web_parser.py | 130 +
services/docreader/src/proto/docreader.pb.go | 819 ++++
services/docreader/src/proto/docreader.proto | 89 +
.../docreader/src/proto/docreader_grpc.pb.go | 167 +
.../docreader/src/proto/docreader_pb2_grpc.py | 145 +
services/docreader/src/server/server.py | 377 ++
.../src/testdata/images/test_text.png | Bin 0 -> 1823 bytes
services/docreader/src/testdata/test.html | 56 +
services/docreader/src/testdata/test.md | 37 +
services/docreader/src/testdata/test.txt | 16 +
.../docreader/src/testdata/test_download.txt | 19 +
services/docreader/src/utils/__init__.py | 83 +
services/docreader/src/utils/request.py | 149 +
services/docreader/supervisord.conf | 32 +
...47\345\223\201\346\226\207\346\241\243.md" | 664 ---
347 files changed, 67140 insertions(+), 779 deletions(-)
create mode 100644 .dockerignore
create mode 100644 .env.example
create mode 100644 .gitattributes
create mode 100644 .gitignore
create mode 100644 CHANGELOG.md
create mode 100644 LICENSE
create mode 100644 Makefile
create mode 100644 README_CN.md
create mode 100644 README_JA.md
create mode 100644 VERSION
create mode 100644 client/README.md
create mode 100644 client/README_EN.md
create mode 100644 client/chunk.go
create mode 100644 client/client.go
create mode 100644 client/evaluation.go
create mode 100644 client/example.go
create mode 100644 client/go.mod
create mode 100644 client/go.sum
create mode 100644 client/knowledge.go
create mode 100644 client/knowledgebase.go
create mode 100644 client/message.go
create mode 100644 client/model.go
create mode 100644 client/session.go
create mode 100644 client/tenant.go
create mode 100644 cmd/server/main.go
create mode 100644 config/config.yaml
create mode 100644 dataset/README
create mode 100644 dataset/README_zh.md
create mode 100644 dataset/qa_dataset.py
create mode 100644 dataset/samples/answers.parquet
create mode 100644 dataset/samples/corpus.parquet
create mode 100644 dataset/samples/qas.parquet
create mode 100644 dataset/samples/qrels.parquet
create mode 100644 dataset/samples/queries.parquet
create mode 100644 docker-compose.yml
create mode 100644 docker/Dockerfile.app
create mode 100644 docker/Dockerfile.docreader
create mode 100644 docker/config/supervisord.conf
create mode 100644 docker/docker-compose.minio.yml
create mode 100644 docs/API.md
create mode 100644 docs/QA.md
create mode 100644 docs/WeKnora.md
create mode 100644 docs/WeKnora.pdf
create mode 100644 docs/images/answer.png
create mode 100644 docs/images/arc.png
create mode 100644 docs/images/config.png
create mode 100644 docs/images/graph1.png
create mode 100644 docs/images/graph2.png
create mode 100644 docs/images/knowledges.png
create mode 100644 docs/images/logo.png
create mode 100644 docs/images/pipeline.jpg
create mode 100644 docs/images/pipeline.png
create mode 100644 docs/images/pipeline2.jpeg
create mode 100644 docs/images/qa.png
create mode 100644 "docs/\344\275\277\347\224\250\345\205\266\344\273\226\345\220\221\351\207\217\346\225\260\346\215\256\345\272\223.md"
create mode 100644 frontend/.dockerignore
create mode 100644 frontend/.gitignore
create mode 100644 frontend/Dockerfile
create mode 100644 frontend/env.d.ts
create mode 100644 frontend/index.html
create mode 100644 frontend/nginx.conf
create mode 100644 frontend/package-lock.json
create mode 100644 frontend/package.json
create mode 100644 frontend/public/favicon.ico
create mode 100644 frontend/src/App.vue
create mode 100644 frontend/src/api/auth/index.ts
create mode 100644 frontend/src/api/chat/index.ts
create mode 100644 frontend/src/api/chat/streame.ts
create mode 100644 frontend/src/api/initialization/index.ts
create mode 100644 frontend/src/api/knowledge-base/index.ts
create mode 100644 frontend/src/api/system/index.ts
create mode 100644 frontend/src/assets/fonts.css
create mode 100644 frontend/src/assets/fonts/TencentSans.ttf
create mode 100644 frontend/src/assets/img/Frame3718.svg
create mode 100644 frontend/src/assets/img/botanswer_loading.gif
create mode 100644 frontend/src/assets/img/circle.png
create mode 100644 frontend/src/assets/img/delete.svg
create mode 100644 frontend/src/assets/img/download.svg
create mode 100644 frontend/src/assets/img/file-add-green.svg
create mode 100644 frontend/src/assets/img/file-add-icon.svg
create mode 100644 frontend/src/assets/img/file-add.svg
create mode 100644 frontend/src/assets/img/logout.svg
create mode 100644 frontend/src/assets/img/more.png
create mode 100644 frontend/src/assets/img/prefixIcon-green.svg
create mode 100644 frontend/src/assets/img/prefixIcon-grey.svg
create mode 100644 frontend/src/assets/img/prefixIcon.svg
create mode 100644 frontend/src/assets/img/sending-aircraft.svg
create mode 100644 frontend/src/assets/img/setting-green.svg
create mode 100644 frontend/src/assets/img/setting.svg
create mode 100644 frontend/src/assets/img/think.gif
create mode 100644 frontend/src/assets/img/upload-mask.svg
create mode 100644 frontend/src/assets/img/upload.svg
create mode 100644 frontend/src/assets/img/user-green.svg
create mode 100644 frontend/src/assets/img/user.svg
create mode 100644 frontend/src/assets/img/weknora.png
create mode 100644 frontend/src/assets/img/zhishiku-green.svg
create mode 100644 frontend/src/assets/img/zhishiku.svg
create mode 100644 frontend/src/assets/img/ziliao.svg
create mode 100644 frontend/src/assets/theme/theme.css
create mode 100644 frontend/src/components/Input-field.vue
create mode 100644 frontend/src/components/css/markdown.less
create mode 100644 frontend/src/components/doc-content.vue
create mode 100644 frontend/src/components/empty-knowledge.vue
create mode 100644 frontend/src/components/menu.vue
create mode 100644 frontend/src/components/picture-preview.vue
create mode 100644 frontend/src/components/upload-mask.vue
create mode 100644 frontend/src/hooks/useKnowledgeBase.ts
create mode 100644 frontend/src/main.ts
create mode 100644 frontend/src/router/index.ts
create mode 100644 frontend/src/stores/auth.ts
create mode 100644 frontend/src/stores/knowledge.ts
create mode 100644 frontend/src/stores/menu.ts
create mode 100644 frontend/src/stores/settings.ts
create mode 100644 frontend/src/utils/index.ts
create mode 100644 frontend/src/utils/request.ts
create mode 100644 frontend/src/utils/security.ts
create mode 100644 frontend/src/views/auth/Login.vue
create mode 100644 frontend/src/views/chat/components/botmsg.vue
create mode 100644 frontend/src/views/chat/components/deepThink.vue
create mode 100644 frontend/src/views/chat/components/docInfo.vue
create mode 100644 frontend/src/views/chat/components/sendMsg.vue
create mode 100644 frontend/src/views/chat/components/usermsg.vue
create mode 100644 frontend/src/views/chat/index.vue
create mode 100644 frontend/src/views/creatChat/creatChat.vue
create mode 100644 frontend/src/views/initialization/InitializationContent.vue
create mode 100644 frontend/src/views/knowledge/KnowledgeBase.vue
create mode 100644 frontend/src/views/knowledge/KnowledgeBaseList.vue
create mode 100644 frontend/src/views/platform/index.vue
create mode 100644 frontend/src/views/settings/Settings.vue
create mode 100644 frontend/src/views/settings/SystemSettings.vue
create mode 100644 frontend/src/views/tenant/TenantInfo.vue
create mode 100644 frontend/tsconfig.app.json
create mode 100644 frontend/tsconfig.json
create mode 100644 frontend/tsconfig.node.json
create mode 100644 frontend/vite.config.ts
create mode 100644 go.mod
create mode 100644 go.sum
create mode 100644 internal/application/repository/chunk.go
create mode 100644 internal/application/repository/knowledge.go
create mode 100644 internal/application/repository/knowledgebase.go
create mode 100644 internal/application/repository/message.go
create mode 100644 internal/application/repository/model.go
create mode 100644 internal/application/repository/retriever/elasticsearch/structs.go
create mode 100644 internal/application/repository/retriever/elasticsearch/v7/repository.go
create mode 100644 internal/application/repository/retriever/elasticsearch/v8/repository.go
create mode 100644 internal/application/repository/retriever/neo4j/repository.go
create mode 100644 internal/application/repository/retriever/postgres/repository.go
create mode 100644 internal/application/repository/retriever/postgres/structs.go
create mode 100644 internal/application/repository/session.go
create mode 100644 internal/application/repository/tenant.go
create mode 100644 internal/application/repository/user.go
create mode 100644 internal/application/service/chat_pipline/chat_completion.go
create mode 100644 internal/application/service/chat_pipline/chat_completion_stream.go
create mode 100644 internal/application/service/chat_pipline/chat_pipline.go
create mode 100644 internal/application/service/chat_pipline/chat_pipline_test.go
create mode 100644 internal/application/service/chat_pipline/common.go
create mode 100644 internal/application/service/chat_pipline/extract_entity.go
create mode 100644 internal/application/service/chat_pipline/filter_top_k.go
create mode 100644 internal/application/service/chat_pipline/into_chat_message.go
create mode 100644 internal/application/service/chat_pipline/merge.go
create mode 100644 internal/application/service/chat_pipline/preprocess.go
create mode 100644 internal/application/service/chat_pipline/rerank.go
create mode 100644 internal/application/service/chat_pipline/rewrite.go
create mode 100644 internal/application/service/chat_pipline/search.go
create mode 100644 internal/application/service/chat_pipline/search_entity.go
create mode 100644 internal/application/service/chat_pipline/stream_filter.go
create mode 100644 internal/application/service/chat_pipline/tracing.go
create mode 100644 internal/application/service/chunk.go
create mode 100644 internal/application/service/dataset.go
create mode 100644 internal/application/service/evaluation.go
create mode 100644 internal/application/service/extract.go
create mode 100644 internal/application/service/file/cos.go
create mode 100644 internal/application/service/file/dummy.go
create mode 100644 internal/application/service/file/local.go
create mode 100644 internal/application/service/file/minio.go
create mode 100644 internal/application/service/graph.go
create mode 100644 internal/application/service/knowledge.go
create mode 100644 internal/application/service/knowledgebase.go
create mode 100644 internal/application/service/message.go
create mode 100644 internal/application/service/metric/bleu.go
create mode 100644 internal/application/service/metric/common.go
create mode 100644 internal/application/service/metric/map.go
create mode 100644 internal/application/service/metric/map_test.go
create mode 100644 internal/application/service/metric/mrr.go
create mode 100644 internal/application/service/metric/mrr_test.go
create mode 100644 internal/application/service/metric/ndcg.go
create mode 100644 internal/application/service/metric/precision.go
create mode 100644 internal/application/service/metric/precision_test.go
create mode 100644 internal/application/service/metric/recall.go
create mode 100644 internal/application/service/metric/recall_test.go
create mode 100644 internal/application/service/metric/rouge.go
create mode 100644 internal/application/service/metric/rouge_score.go
create mode 100644 internal/application/service/metric_hook.go
create mode 100644 internal/application/service/model.go
create mode 100644 internal/application/service/retriever/composite.go
create mode 100644 internal/application/service/retriever/keywords_vector_hybrid_indexer.go
create mode 100644 internal/application/service/retriever/registry.go
create mode 100644 internal/application/service/session.go
create mode 100644 internal/application/service/tenant.go
create mode 100644 internal/application/service/user.go
create mode 100644 internal/common/tools.go
create mode 100644 internal/config/config.go
create mode 100644 internal/container/cleanup.go
create mode 100644 internal/container/container.go
create mode 100644 internal/errors/errors.go
create mode 100644 internal/errors/session.go
create mode 100644 internal/handler/auth.go
create mode 100644 internal/handler/chunk.go
create mode 100644 internal/handler/evaluation.go
create mode 100644 internal/handler/initialization.go
create mode 100644 internal/handler/knowledge.go
create mode 100644 internal/handler/knowledgebase.go
create mode 100644 internal/handler/message.go
create mode 100644 internal/handler/model.go
create mode 100644 internal/handler/session.go
create mode 100644 internal/handler/system.go
create mode 100644 internal/handler/tenant.go
create mode 100644 internal/logger/logger.go
create mode 100644 internal/middleware/auth.go
create mode 100644 internal/middleware/error_handler.go
create mode 100644 internal/middleware/logger.go
create mode 100644 internal/middleware/recovery.go
create mode 100644 internal/middleware/trace.go
create mode 100644 internal/models/chat/chat.go
create mode 100644 internal/models/chat/ollama.go
create mode 100644 internal/models/chat/remote_api.go
create mode 100644 internal/models/chat/remote_api_test.go
create mode 100644 internal/models/embedding/batch.go
create mode 100644 internal/models/embedding/embedder.go
create mode 100644 internal/models/embedding/ollama.go
create mode 100644 internal/models/embedding/openai.go
create mode 100644 internal/models/rerank/aliyun_reranker.go
create mode 100644 internal/models/rerank/remote_api.go
create mode 100644 internal/models/rerank/reranker.go
create mode 100644 internal/models/rerank/reranker_test.go
create mode 100644 internal/models/utils/ollama/ollama.go
create mode 100644 internal/models/utils/slices.go
create mode 100644 internal/router/router.go
create mode 100644 internal/router/task.go
create mode 100644 internal/runtime/container.go
create mode 100644 internal/stream/factory.go
create mode 100644 internal/stream/memory_manager.go
create mode 100644 internal/stream/redis_manager.go
create mode 100644 internal/tracing/init.go
create mode 100644 internal/types/chat.go
create mode 100644 internal/types/chat_manage.go
create mode 100644 internal/types/chunk.go
create mode 100644 internal/types/cleanup.go
create mode 100644 internal/types/const.go
create mode 100644 internal/types/dataset.go
create mode 100644 internal/types/embedding.go
create mode 100644 internal/types/errors.go
create mode 100644 internal/types/evaluation.go
create mode 100644 internal/types/extract_graph.go
create mode 100644 internal/types/graph.go
create mode 100644 internal/types/interfaces/chunk.go
create mode 100644 internal/types/interfaces/evaluation.go
create mode 100644 internal/types/interfaces/extracter.go
create mode 100644 internal/types/interfaces/file.go
create mode 100644 internal/types/interfaces/knowledge.go
create mode 100644 internal/types/interfaces/knowledgebase.go
create mode 100644 internal/types/interfaces/message.go
create mode 100644 internal/types/interfaces/model.go
create mode 100644 internal/types/interfaces/resource.go
create mode 100644 internal/types/interfaces/retriever.go
create mode 100644 internal/types/interfaces/retriever_graph.go
create mode 100644 internal/types/interfaces/session.go
create mode 100644 internal/types/interfaces/stream_manager.go
create mode 100644 internal/types/interfaces/tenant.go
create mode 100644 internal/types/interfaces/user.go
create mode 100644 internal/types/json.go
create mode 100644 internal/types/knowledge.go
create mode 100644 internal/types/knowledgebase.go
create mode 100644 internal/types/message.go
create mode 100644 internal/types/model.go
create mode 100644 internal/types/retriever.go
create mode 100644 internal/types/search.go
create mode 100644 internal/types/session.go
create mode 100644 internal/types/tenant.go
create mode 100644 internal/types/user.go
create mode 100644 internal/utils/security.go
create mode 100644 mcp-server/.gitignore
create mode 100644 mcp-server/CHANGELOG.md
create mode 100644 mcp-server/EXAMPLES.md
create mode 100644 mcp-server/INSTALL.md
create mode 100644 mcp-server/LICENSE
create mode 100644 mcp-server/MANIFEST.in
create mode 100644 mcp-server/PROJECT_SUMMARY.md
create mode 100644 mcp-server/README.md
create mode 100644 mcp-server/__init__.py
create mode 100644 mcp-server/main.py
create mode 100644 mcp-server/pyproject.toml
create mode 100644 mcp-server/requirements.txt
create mode 100644 mcp-server/run.py
create mode 100644 mcp-server/run_server.py
create mode 100644 mcp-server/setup.py
create mode 100644 mcp-server/test_imports.py
create mode 100644 mcp-server/test_module.py
create mode 100644 mcp-server/weknora_mcp_server.py
create mode 100644 migrations/mysql/00-init-db.sql
create mode 100644 migrations/paradedb/00-init-db.sql
create mode 100644 migrations/paradedb/01-migrate-to-paradedb.sql
create mode 100644 packages/.gitkeep
create mode 100644 rerank_server_demo.py
create mode 100755 scripts/build_images.sh
create mode 100755 scripts/get_version.sh
create mode 100644 scripts/migrate.sh
create mode 100755 scripts/start_all.sh
create mode 100644 services/docreader/Makefile
create mode 100644 services/docreader/poetry.lock
create mode 100644 services/docreader/pyproject.toml
create mode 100644 services/docreader/requirements.txt
create mode 100755 services/docreader/scripts/generate_proto.sh
create mode 100644 services/docreader/src/__init__.py
create mode 100644 services/docreader/src/client/client.go
create mode 100644 services/docreader/src/client/client_test.go
create mode 100644 services/docreader/src/download_deps.py
create mode 100644 services/docreader/src/parser/__init__.py
create mode 100644 services/docreader/src/parser/base_parser.py
create mode 100644 services/docreader/src/parser/caption.py
create mode 100644 services/docreader/src/parser/config.py
create mode 100644 services/docreader/src/parser/doc_parser.py
create mode 100644 services/docreader/src/parser/docx_parser.py
create mode 100644 services/docreader/src/parser/image_parser.py
create mode 100644 services/docreader/src/parser/image_utils.py
create mode 100644 services/docreader/src/parser/markdown_parser.py
create mode 100644 services/docreader/src/parser/ocr_engine.py
create mode 100644 services/docreader/src/parser/parser.py
create mode 100644 services/docreader/src/parser/pdf_parser.py
create mode 100644 services/docreader/src/parser/storage.py
create mode 100644 services/docreader/src/parser/text_parser.py
create mode 100644 services/docreader/src/parser/web_parser.py
create mode 100644 services/docreader/src/proto/docreader.pb.go
create mode 100644 services/docreader/src/proto/docreader.proto
create mode 100644 services/docreader/src/proto/docreader_grpc.pb.go
create mode 100644 services/docreader/src/proto/docreader_pb2_grpc.py
create mode 100644 services/docreader/src/server/server.py
create mode 100644 services/docreader/src/testdata/images/test_text.png
create mode 100644 services/docreader/src/testdata/test.html
create mode 100644 services/docreader/src/testdata/test.md
create mode 100644 services/docreader/src/testdata/test.txt
create mode 100644 services/docreader/src/testdata/test_download.txt
create mode 100644 services/docreader/src/utils/__init__.py
create mode 100644 services/docreader/src/utils/request.py
create mode 100644 services/docreader/supervisord.conf
delete mode 100644 "\344\272\247\345\223\201\346\226\207\346\241\243.md"
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..e69de29
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..6b035ee
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,210 @@
+# 使用说明
+# 1. 复制此文件为 .env
+# 2. 替换所有占位符为实际值
+# 3. 确保 .env 文件不会被提交到版本控制系统
+
+# gin mod
+# 可选值: debug(开发模式,有详细日志), release(生产模式)
+GIN_MODE=debug
+
+# Ollama 服务的基准 URL,用于连接本地/其他服务器上运行的 Ollama 服务
+OLLAMA_BASE_URL=http://host.docker.internal:11434
+
+# 存储配置
+# 主数据库类型(postgres/mysql)
+DB_DRIVER=postgres
+
+# 向量存储类型(postgres/elasticsearch_v7/elasticsearch_v8)
+RETRIEVE_DRIVER=postgres
+
+# 文件存储类型(local/minio/cos)
+STORAGE_TYPE=local
+
+# 流处理后端(memory/redis)
+STREAM_MANAGER_TYPE=redis
+
+# 主数据库配置
+# 数据库端口,默认为5432
+DB_PORT=5432
+
+# 应用服务端口,默认为8080
+APP_PORT=8080
+
+# 前端服务端口,默认为80
+FRONTEND_PORT=80
+
+# 文档解析模块端口,默认为50051
+DOCREADER_PORT=50051
+
+# 数据库用户名
+DB_USER=postgres
+
+# 数据库密码
+DB_PASSWORD=postgres123!@#
+
+# 数据库名称
+DB_NAME=WeKnora
+
+# 如果使用 redis 作为流处理后端,需要配置以下参数
+# Redis端口,默认为6379
+REDIS_PORT=6379
+
+# Redis密码,如果没有设置密码,可以留空
+REDIS_PASSWORD=redis123!@#
+
+# Redis数据库索引,默认为0
+REDIS_DB=0
+
+# Redis key的前缀,用于命名空间隔离
+REDIS_PREFIX=stream:
+
+# 当使用本地存储时,文件保存的基础目录路径
+LOCAL_STORAGE_BASE_DIR=./data/files
+
+TENANT_AES_KEY=weknorarag-api-key-secret-secret
+
+# 是否开启知识图谱构建和检索(构建阶段需调用大模型,耗时较长)
+ENABLE_GRAPH_RAG=false
+
+MINIO_PORT=9000
+
+MINIO_CONSOLE_PORT=9001
+
+# Embedding并发数,出现429错误时,可调小此参数
+CONCURRENCY_POOL_SIZE=5
+
+# 如果使用ElasticSearch作为向量存储,需要配置以下参数
+# ElasticSearch地址,例如 http://localhost:9200
+# ELASTICSEARCH_ADDR=your_elasticsearch_addr
+
+# ElasticSearch用户名,如果需要身份验证
+# ELASTICSEARCH_USERNAME=your_elasticsearch_username
+
+# ElasticSearch密码,如果需要身份验证
+# ELASTICSEARCH_PASSWORD=your_elasticsearch_password
+
+# ElasticSearch索引名称,用于存储向量数据
+# ELASTICSEARCH_INDEX=WeKnora
+
+# 如果使用MinIO作为文件存储,需要配置以下参数
+# MinIO访问密钥
+# MINIO_ACCESS_KEY_ID=your_minio_access_key
+
+# MinIO密钥
+# MINIO_SECRET_ACCESS_KEY=your_minio_secret_key
+
+# MinIO桶名称,用于存储文件
+# MINIO_BUCKET_NAME=your_minio_bucket_name
+
+# 如果使用腾讯云COS作为文件存储,需要配置以下参数
+# 腾讯云COS的访问密钥ID
+# COS_SECRET_ID=your_cos_secret_id
+
+# 腾讯云COS的密钥
+# COS_SECRET_KEY=your_cos_secret_key
+
+# 腾讯云COS的区域,例如 ap-guangzhou
+# COS_REGION=your_cos_region
+
+# 腾讯云COS的桶名称
+# COS_BUCKET_NAME=your_cos_bucket_name
+
+# 腾讯云COS的应用ID
+# COS_APP_ID=your_cos_app_id
+
+# 腾讯云COS的路径前缀,用于存储文件
+# COS_PATH_PREFIX=your_cos_path_prefix
+
+# COS_ENABLE_OLD_DOMAIN=true 表示启用旧的域名格式,默认为 true
+COS_ENABLE_OLD_DOMAIN=true
+
+# 如果解析网络连接使用Web代理,需要配置以下参数
+# WEB_PROXY=your_web_proxy
+
+# Neo4j 开关
+# NEO4J_ENABLE=false
+
+# Neo4j的访问地址
+# NEO4J_URI=neo4j://neo4j:7687
+
+# Neo4j的用户名和密码
+# NEO4J_USERNAME=neo4j
+
+# Neo4j的密码
+# NEO4J_PASSWORD=password
+
+##############################################################
+
+###### 注意: 以下配置不再生效,已在Web“配置初始化”阶段完成 #########
+
+
+# # 初始化默认租户与知识库
+# # 租户ID,通常是一个字符串
+# INIT_TEST_TENANT_ID=1
+
+# # 知识库ID,通常是一个字符串
+# INIT_TEST_KNOWLEDGE_BASE_ID=kb-00000001
+
+# # LLM Model
+# # 使用的LLM模型名称
+# # 默认使用 Ollama 的 Qwen3 8B 模型,ollama 会自动处理模型下载和加载
+# # 如果需要使用其他模型,请替换为实际的模型名称
+# INIT_LLM_MODEL_NAME=qwen3:8b
+
+# # LLM模型的访问地址
+# # 支持第三方模型服务的URL
+# # 如果使用 Ollama 的本地服务,可以留空,ollama 会自动处理
+# # INIT_LLM_MODEL_BASE_URL=your_llm_model_base_url
+
+# # LLM模型的API密钥,如果需要身份验证,可以设置
+# # 支持第三方模型服务的API密钥
+# # 如果使用 Ollama 的本地服务,可以留空,ollama 会自动处理
+# # INIT_LLM_MODEL_API_KEY=your_llm_model_api_key
+
+# # Embedding Model
+# # 使用的Embedding模型名称
+# # 默认使用 nomic-embed-text 模型,支持文本嵌入
+# # 如果需要使用其他模型,请替换为实际的模型名称
+# INIT_EMBEDDING_MODEL_NAME=nomic-embed-text
+
+# # Embedding模型向量维度
+# INIT_EMBEDDING_MODEL_DIMENSION=768
+
+# # Embedding模型的ID,通常是一个字符串
+# INIT_EMBEDDING_MODEL_ID=builtin:nomic-embed-text:768
+
+# # Embedding模型的访问地址
+# # 支持第三方模型服务的URL
+# # 如果使用 Ollama 的本地服务,可以留空,ollama 会自动处理
+# # INIT_EMBEDDING_MODEL_BASE_URL=your_embedding_model_base_url
+
+# # Embedding模型的API密钥,如果需要身份验证,可以设置
+# # 支持第三方模型服务的API密钥
+# # 如果使用 Ollama 的本地服务,可以留空,ollama 会自动处理
+# # INIT_EMBEDDING_MODEL_API_KEY=your_embedding_model_api_key
+
+# # Rerank Model(可选)
+# # 对于rag来说,使用Rerank模型对提升文档搜索的准确度有着重要作用
+# # 目前 ollama 暂不支持运行 Rerank 模型
+# # 使用的Rerank模型名称
+# # INIT_RERANK_MODEL_NAME=your_rerank_model_name
+
+# # Rerank模型的访问地址
+# # 支持第三方模型服务的URL
+# # INIT_RERANK_MODEL_BASE_URL=your_rerank_model_base_url
+
+# # Rerank模型的API密钥,如果需要身份验证,可以设置
+# # 支持第三方模型服务的API密钥
+# # INIT_RERANK_MODEL_API_KEY=your_rerank_model_api_key
+
+# # VLM_MODEL_NAME 使用的多模态模型名称
+# # 用于解析图片数据
+# # VLM_MODEL_NAME=your_vlm_model_name
+
+# # VLM_MODEL_BASE_URL 使用的多模态模型访问地址
+# # 支持第三方模型服务的URL
+# # VLM_MODEL_BASE_URL=your_vlm_model_base_url
+
+# # VLM_MODEL_API_KEY 使用的多模态模型API密钥
+# # 支持第三方模型服务的API密钥
+# # VLM_MODEL_API_KEY=your_vlm_model_api_key
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..526c8a3
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.sh text eol=lf
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3fb44ce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+# 忽略.env文件和其他包含敏感信息的配置文件
+.env
+# 但不忽略示例文件
+!.env.example
+*.pem
+*_key
+*_secret
+*.key
+*.crt
+
+# IDE和编辑器文件
+.idea/
+.vscode/
+*.swp
+*.swo
+
+# 构建和依赖文件
+node_modules/
+/dist/
+/build/
+*.log
+
+# 临时文件
+tmp/
+temp/
+
+WeKnora
+/models/
+services/docreader/src/proto/__pycache__
+test/data/mswag.txt
+data/files/
+
+.python-version
+.venv/
+
+### macOS
+# General
+.DS_Store
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..0fe4684
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,103 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.4] - 2025-09-17
+
+### 🚀 Major Features
+- **NEW**: Multi-knowledgebases operation support
+ - Added comprehensive multi-knowledgebase management functionality
+ - Implemented multi-data source search engine configuration and optimization logic
+ - Enhanced knowledge base switching and management in UI
+- **NEW**: Enhanced tenant information management
+ - Added dedicated tenant information page
+ - Improved user and tenant management capabilities
+
+### 🎨 UI/UX Improvements
+- **REDESIGNED**: Settings page with improved layout and functionality
+- **ENHANCED**: Menu component with multi-knowledgebase support
+- **IMPROVED**: Initialization configuration page structure
+- **OPTIMIZED**: Login page and authentication flow
+
+### 🔒 Security Fixes
+- **FIXED**: XSS attack vulnerabilities in thinking component
+- **FIXED**: Content Security Policy (CSP) errors
+- **ENHANCED**: Frontend security measures and input sanitization
+
+### 🐛 Bug Fixes
+- **FIXED**: Login direct page navigation issues
+- **FIXED**: App LLM model check logic
+- **FIXED**: Version script functionality
+- **FIXED**: File download content errors
+- **IMPROVED**: Document content component display
+
+### 🧹 Code Cleanup
+- **REMOVED**: Test data functionality and related APIs
+- **SIMPLIFIED**: Initialization configuration components
+- **CLEANED**: Redundant UI components and unused code
+
+
+## [0.1.3] - 2025-09-16
+
+### 🔒 Security Features
+- **NEW**: Added login authentication functionality to enhance system security
+- Implemented user authentication and authorization mechanisms
+- Added session management and access control
+- Fixed XSS attack vulnerabilities in frontend components
+
+### 📚 Documentation Updates
+- Added security notices in all README files (English, Chinese, Japanese)
+- Updated deployment recommendations emphasizing internal/private network deployment
+- Enhanced security guidelines to prevent information leakage risks
+- Fixed documentation spelling issues
+
+### 🛡️ Security Improvements
+- Hide API keys in UI for security purposes
+- Enhanced input sanitization and XSS protection
+- Added comprehensive security utilities
+
+### 🐛 Bug Fixes
+- Fixed OCR AVX support issues
+- Improved frontend health check dependencies
+- Enhanced Docker binary downloads for target architecture
+- Fixed COS file service initialization parameters and URL processing logic
+
+### 🚀 Features & Enhancements
+- Improved application and docreader log output
+- Enhanced frontend routing and authentication flow
+- Added comprehensive user management system
+- Improved initialization configuration handling
+
+### 🛡️ Security Recommendations
+- Deploy WeKnora services in internal/private network environments
+- Avoid direct exposure to public internet
+- Configure proper firewall rules and access controls
+- Regular updates for security patches and improvements
+
+## [0.1.2] - 2025-09-10
+
+- Fixed health check implementation for docreader service
+- Improved query handling for empty queries
+- Enhanced knowledge base column value update methods
+- Optimized logging throughout the application
+- Added process parsing documentation for markdown files
+- Fixed OCR model pre-fetching in Docker containers
+- Resolved image parser concurrency errors
+- Added support for modifying listening port configuration
+
+## [0.1.0] - 2025-09-08
+
+- Initial public release of WeKnora.
+- Web UI for knowledge upload, chat, configuration, and settings.
+- RAG pipeline with chunking, embedding, retrieval, reranking, and generation.
+- Initialization wizard for configuring models (LLM, embedding, rerank, retriever).
+- Support for local Ollama and remote API models.
+- Vector backends: PostgreSQL (pgvector), Elasticsearch; GraphRAG support.
+- End-to-end evaluation utilities and metrics.
+- Docker Compose for quick startup and service orchestration.
+- MCP server support for integrating with MCP-compatible clients.
+
+[0.1.4]: https://github.com/Tencent/WeKnora/tree/v0.1.4
+[0.1.3]: https://github.com/Tencent/WeKnora/tree/v0.1.3
+[0.1.2]: https://github.com/Tencent/WeKnora/tree/v0.1.2
+[0.1.0]: https://github.com/Tencent/WeKnora/tree/v0.1.0
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..75b8af5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,2692 @@
+Tencent is pleased to support the open source community by making this project available.
+
+Copyright (C) 2025 Tencent. All rights reserved.
+
+This project is licensed under the MIT License except for the third-party components listed below, which is licensed under different terms. Tencent does not impose any additional limitations beyond what is outlined in the respective licenses of these third-party components. Users must comply with all terms and conditions of original licenses of these third-party components and must ensure that the usage of the third party components adheres to all relevant laws and regulations.
+
+
+Terms of the MIT License:
+--------------------------------------------------------------------
+Copyright (C) 2025 Tencent. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the " Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+--------------------------------------------------------------------
+Other third-party components:
+
+Open Source Software Licensed under the Apache-2.0:
+--------------------------------------------------------------------
+1. otel-1.37.0
+Copyright (c) 2025 opentelemetry-go original author and authors
+
+2. stdr-1.2.2
+Copyright (c) 2021 stdr original author and authors
+
+3. parquet-go-0.25.0
+Copyright 2023 Twilio, Inc.
+
+4. rpc-0.0.0-20250603155806-513f23925822
+Copyright (c) 2025 go-genproto original author and authors
+
+5. api-0.0.0-20250603155806-513f23925822
+Copyright (c) 2025 go-genproto original author and authors
+
+6. grpc-1.73.0
+Copyright (c) 2025 grpc-go original author and authors
+
+7. base64x-0.1.5
+Copyright (c) 2025 base64x original author and authors
+
+8. trace-1.37.0
+Copyright (c) 2025 opentelemetry-go original author and authors
+
+9. loader-0.2.4
+Copyright 2025 ByteDance Inc.
+
+10. paddlepaddle-gpu-3.0.0
+Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
+
+11. otlp-1.7.0
+Copyright (c) 2025 opentelemetry-proto original author and authors
+
+12. logr-1.4.3
+Copyright (c) 2025 logr original author and authors
+
+13. otlptrace-1.37.0
+Copyright (c) 2025 opentelemetry-go original author and authors
+
+14. metric-1.37.0
+Copyright (c) 2025 opentelemetry-go original author and authors
+
+15. sonic-1.13.2
+Copyright (c) 2025 sonic original author and authors
+
+16. go-openai-1.40.5
+Copyright (c) 2025 go-openai original author and authors
+
+17. crc64nvme-1.0.1
+Copyright (c) 2025 crc64nvme original author and authors
+
+18. concurrent-0.0.0-20180306012644-bacd9c7ef1dd
+Copyright (c) 2018 concurrent original author and authors
+
+19. stdouttrace-1.35.0
+Copyright (c) 2025 opentelemetry-go original author and authors
+
+20. otlptracegrpc-1.37.0
+Copyright (c) 2025 opentelemetry-go original author and authors
+
+21. paddleocr-3.0.0
+Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
+
+
+
+Terms of the Apache-2.0:
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Open Source Software Licensed under the BSD-2-Clause:
+--------------------------------------------------------------------
+1. esrecurse-4.3.0
+Copyright (C) 2014 [Yusuke Suzuki](https://github.com/Constellation) (twitter: [@Constellation](https://twitter.com/Constellation)) and other contributors.
+
+2. glob-to-regexp-0.4.1
+Copyright (c) 2013, Nick Fitzgerald
+
+3. eslint-scope-5.1.1
+Copyright(c) 2025 eslint-scope original author and authors
+
+
+
+Terms of the BSD-2-Clause:
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Open Source Software Licensed under the BSD-3-Clause:
+--------------------------------------------------------------------
+1. text-0.26.0
+Copyright 2009 The Go Authors.
+
+2. compress-1.18.0
+Copyright (c) 2012 The Go Authors. All rights reserved.
+Copyright (c) 2019 Klaus Post. All rights reserved.
+Copyright 2016-2017 The New York Times Company
+Copyright (c) 2015 Klaus Post
+Copyright 2016 The filepath Authors
+
+3. net-0.41.0
+Copyright 2009 The Go Authors.
+
+4. fsnotify-1.8.0
+Copyright © 2012 The Go Authors. All rights reserved.
+Copyright © fsnotify Authors. All rights reserved.
+
+5. sys-0.33.0
+Copyright 2009 The Go Authors.
+
+6. protobuf-1.36.6
+Copyright (c) 2018 The Go Authors. All rights reserved.
+
+7. arch-0.15.0
+Copyright 2015 The Go Authors.
+
+
+
+Terms of the BSD-3-Clause:
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the ORGANIZATION nor the names of its contributors may be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Open Source Software Licensed under the CC-BY-4.0:
+--------------------------------------------------------------------
+1. caniuse-lite-1.0.30001727
+Copyright(c) 2025 Ben Briggs
+
+
+
+Terms of the CC-BY-4.0:
+Attribution 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution 4.0 International Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution 4.0 International Public License ("Public License"). To the
+extent this Public License may be interpreted as a contract, You are
+granted the Licensed Rights in consideration of Your acceptance of
+these terms and conditions, and the Licensor grants You such rights in
+consideration of benefits the Licensor receives from making the
+Licensed Material available under these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ d. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ e. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ f. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ g. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ h. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ i. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ j. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ k. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ 4. If You Share Adapted Material You produce, the Adapter's
+ License You apply must not prevent recipients of the Adapted
+ Material from complying with this Public License.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material; and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+
+
+Open Source Software Licensed under the ISC:
+--------------------------------------------------------------------
+1. electron-to-chromium-1.5.183
+Copyright 2018 Kilian Valkhof
+
+
+
+Terms of the ISC:
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+
+Open Source Software Licensed under the MIT:
+--------------------------------------------------------------------
+1. birpc-2.5.0
+Copyright (c) 2021 Anthony Fu
+
+2. @babel/generator-7.28.0
+Copyright (c) 2014-present Sebastian Mckenzie and other contributors
+
+3. neo-async-2.6.2
+Copyright (c) 2014-2018 Suguru Motegi
+
+4. parse-node-version-1.0.1
+Copyright (c) 2018 Blaine Bublitz and Eric Schoffstall
+
+5. cors-1.7.5
+Copyright (c) 2016 Gin-Gonic
+
+6. vue-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+7. copy-anything-2.0.6
+Copyright (c) 2018 Luca Ban - Mesqueeb
+
+8. yaml.v3-3.0.1
+Copyright (c) 2006-2010 Kirill Simonov
+Copyright (c) 2006-2011 Kirill Simonov
+Copyright (c) 2011-2019 Canonical Ltd
+
+9. gojieba-1.4.5
+Copyright (c) The project creators and maintainers
+
+10. codec-1.2.12
+Copyright (c) 2012-2020 Ugorji Nwoke.
+
+11. vue-tsc-2.2.12
+Copyright(c) 2025 vue-tsc original author and authors
+
+12. follow-redirects-1.15.9
+Copyright 2017-present Oliviver Lalonde , James Talmage , Ruben Verborgh
+
+13. @vue/server-renderer-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+14. pgvector-go-0.3.0
+Copyright (c) 2021-2025 Andrew Kane
+
+15. @babel/traverse-7.28.0
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+16. @webassemblyjs/helper-api-error-1.13.2
+Copyright (c) 2018 Sven Sauleau
+
+17. estree-walker-2.0.2
+Copyright (c) 2015-20 [these people](https://github.com/Rich-Harris/estree-walker/graphs/contributors)
+
+18. @types/node-22.16.3
+Copyright(c) 2025 @types/node original author and authors
+
+19. magic-string-0.30.17
+Copyright 2018 Rich Harris
+
+20. pinia-3.0.3
+Copyright (c) 2019-present Eduardo San Martin Morote
+
+21. @types/lodash-4.17.20
+Copyright(c) 2025 @types/lodash original author and authors
+
+22. es-set-tostringtag-2.1.0
+Copyright (c) 2022 ECMAScript Shims
+
+23. @vue/runtime-dom-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+24. @vue/reactivity-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+25. de-indent-1.0.2
+Copyright(c) 2025 Evan You
+
+26. @babel/template-7.27.2
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+27. tdesign-icons-vue-next-0.3.6
+Copyright (c) 2021-present TDesign
+
+28. webpack-5.100.1
+Copyright JS Foundation and other contributors
+
+29. asynq-0.25.1
+Copyright (c) 2019 Kentaro Hibino
+
+30. shell-quote-1.8.3
+Copyright (c) 2013 James Halliday (mail@substack.net)
+
+31. acorn-8.15.0
+Copyright(c) 2025 acorn original author and authors
+
+32. esbuild-0.25.6
+Copyright (c) 2020 Evan Wallace
+
+33. tdesign-vue-next-1.14.2
+Copyright (c) 2021-present TDesign
+
+34. @vue/runtime-core-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+35. validator-13.15.15
+Q29weXJpZ2h0IChjKSAyMDE4IENocmlzIE8nSGFyYSA8Y29oYXJhODdAZ21haWwuY29tPg==
+
+36. postcss-8.5.6
+Copyright 2013 Andrey Sitnik
+
+37. mimetype-1.4.8
+Copyright (c) 2018 Gabriel Vasile
+
+38. @babel/helper-globals-7.28.0
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+39. @babel/helper-create-class-features-plugin-7.27.1
+Copyright (c) 2014-present Sebastian McKenzieg and other contributors
+
+40. @babel/helper-replace-supers-7.27.1
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+41. is-what-3.14.1
+Copyright (c) 2018 Luca Ban - Mesqueeb
+
+42. @rolldown/pluginutils-1.0.0-beta.27
+Copyright (c) 2024-present VoidZero Inc. & Contributors
+
+43. ollama-0.9.6
+Copyright (c) Ollama
+
+44. locafero-0.7.0
+Copyright (c) 2023 Márk Sági-Kazar
+
+45. stylus->=0.54.8
+Copyright (c) Automatttic
+
+46. @vue/language-core-2.2.12
+Copyright (c) 2021-present Johnson Chu
+
+47. @babel/parser-7.28.0
+Copyright (c) 2014-present Sebastian MacKenzie and other contributors
+
+48. cos-go-sdk-v5-0.7.65
+Copyright (c) 2017 mozillazg
+
+49. @rolldown/pluginutils-1.0.0-beta.19
+Copyright (c) 2024-present VoidZero Inc. & Contributors
+
+50. @vue/compiler-core-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+51. @vue/shared-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+52. @vitejs/plugin-vue-jsx-5.0.1
+Copyright (c) 2019-present, Yuxi
+(Evan) You and Vite contributors
+
+53. fdir-6.4.6
+Copyright 2023 AbdullAh AttA
+
+54. go-rendezvous-0.0.0-20200823014737-9f7001d12a5f
+Copyright (c) 2017-2020 Damian Gryski
+
+55. @babel/plugin-syntax-jsx-7.27.1
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+56. @vue/compiler-ssr-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+57. @babel/helper-member-expression-to-functions-7.27.1
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+58. es-module-lexer-1.7.0
+Copyright (C) 2018-2022 Guy Bedford
+
+59. vue-router-4.5.1
+Copyright (c) 2019-present Eduardo San Martin Morote
+
+60. @babel/helper-validator-option-7.27.1
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+61. @vue/compiler-sfc-3.5.17
+Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
+
+62. @vue/devtools-shared-7.7.7
+Copyright (c) 2023 webfansplz
+
+63. gensync-1.0.0-beta.2
+Copyright 2018 Logan Smyth
+
+64. @rspack/core-0.x || 1.x
+Copyright (c) 2022-present Bytsedance Inc. and its affiliates.
+
+65. jiti->=1.21.0
+Copyright (c) Pooya Parsa
+
+66. pgservicefile-0.0.0-20240606120523-5a60cdf6a761
+Copyright (c) 2020 Jack Christensens
+
+67. @types/validator-13.15.2
+Copyright(c) 2025 @types/validator original author and authors
+
+68. @vue/devtools-api-7.7.7
+Copyright (c) 2023 webfansplz
+
+69. tapable-2.2.2
+Copyright JS Foundation and
+other contributors
+
+70. @vitejs/plugin-vue-6.0.0
+Copyright (c) 2019-present, Yuxhi (Evan) You and Vite contributors
+
+71. @babel/core-7.28.0
+Copyright (c) 2014-present Sebastian McInerney and other contributors
+
+72. @babel/types-7.28.1
+Copyright (c) 2014-present Sebastian McPkenzie and other contributors
+
+73. @babel/compat-data-7.28.0
+Copyright (c) 2014-present Sebastian MacKenzie and other contributors
+
+74. @volar/typescript-2.4.15
+Copyright (c) 2021-present Johnson Chu
+
+75. @babel/helper-annotate-as-pure-7.27.3
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+76. vite-7.0.4
+Copyright (c) 2019-present, VoidZero Inc. and Vite contributors
+
+77. @volar/source-map-2.4.15
+Copyright (c) 2021-present Johnson Chu
+
+78. json-parse-even-better-errors-2.3.1
+Copyright 2017 Kat Marchán
+Copyright npm, Inc.
+
+79. @vue/compiler-dom-3.5.17
+Copyright (c) 2018-present, Yuxia (Evan) You and Vue contributors
+
+80. @volar/language-core-2.4.15
+Copyright (c) 2021-present Johnson Chu
+
+81. @vue/devtools-kit-7.7.7
+Copyright (c) 2023 webfansplz
+
+82. @babel/helper-skip-transparent-expression-wrappers-7.27.1
+Copyright (c) 2014-present Sebastian MacKenzie and other contributors
+
+83. mitt-3.0.1
+Copyright (c) 2021 Jason Miller
+
+84. acorn-import-phases-1.0.4
+Copyright (c) 2025 Nicolò Ribaudo
+
+85. @babel/helper-string-parser-7.27.1
+Copyright (c) 2014-present Sebastian McKeenzied and other contributors
+
+86. watchpack-2.4.4
+Copyright JS Foundation and other contributors
+
+87. @babel/runtime-7.27.6
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+88. @babel/plugin-transform-typescript-7.28.0
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+89. jest-worker-27.5.1
+Copyright (c) jest-worker authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/jest-worker.
+
+
+
+Terms of the MIT:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Open Source Software Licensed under the MIT AND ISC AND 0BSD:
+--------------------------------------------------------------------
+1. rollup-4.45.0
+Copyright(c) 2025 Rich Harris
+
+
+
+Terms of the MIT AND ISC AND 0BSD:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Open Source Software Licensed under the MIT-CMU:
+--------------------------------------------------------------------
+1. Pillow-default
+Copyright © 1997-2011 by Secret Labs AB
+Copyright © 1995-2011 by Fredrik Lundh and contributors
+Copyright © 2010 by Jeffrey A. Clark and contributors
+
+
+
+Terms of the MIT-CMU:
+Permission to use, copy, modify and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided that
+the above copyright notice appears in all copies and that both that copyright
+notice and this permission notice appear in supporting documentation, and that
+the name of CMU and The Regents of the University of California not be used in
+advertising or publicity pertaining to distribution of the software without
+specific written permission.
+
+CMU AND THE REGENTS OF THE UNIVERSITY OF CALIFORNIA DISCLAIM ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL CMU OR THE REGENTS OF THE UNIVERSITY OF CALIFORNIA BE
+LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM THE LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+Open Source Software Licensed under the MPL-2.0:
+--------------------------------------------------------------------
+1. lightningcss-1.21.0
+Copyright(c) 2025 lightningcss original author and authors
+
+
+
+Terms of the MPL-2.0:
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
+
+Open Source Software Licensed under the apache-2.0:
+--------------------------------------------------------------------
+1. @ampproject/remapping-2.3.0
+Copyright (c) @ampproject/remapping authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@ampproject/remapping.
+
+2. sync-0.15.0
+Copyright (c) sync authors.
+You may obtain the source code and detailed information about this component at https://mvnrepository.com/artifact/com.aerospike/sync.
+
+3. @webassemblyjs/leb128-1.13.2
+Copyright (c) @webassemblyjs/leb128 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/leb128.
+
+4. sdk-1.37.0
+Copyright (c) sdk authors.
+You may obtain the source code and detailed information about this component at https://mvnrepository.com/artifact/io.lakefs/sdk.
+
+5. crypto-0.39.0
+Copyright (c) crypto authors.
+You may obtain the source code and detailed information about this component at https://github.com/smallstep/crypto.
+
+6. grpcio-default
+Copyright (c) grpcio authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/grpcio.
+
+7. sdk-1.1.0
+Copyright (c) sdk authors.
+You may obtain the source code and detailed information about this component at https://mvnrepository.com/artifact/com.jedlix/sdk.
+
+8. afero-1.12.0
+Copyright (c) afero authors.
+You may obtain the source code and detailed information about this component at https://github.com/spf13/afero.
+
+9. @xtuc/long-4.2.2
+Copyright (c) @xtuc/long authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@xtuc/long.
+
+10. chrome-trace-event-1.0.4
+Copyright (c) chrome-trace-event authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/chrome-trace-event.
+
+11. reflect2-1.0.2
+Copyright (c) reflect2 authors.
+You may obtain the source code and detailed information about this component at https://github.com/modern-go/reflect2.
+
+12. grpcio-tools-default
+Copyright (c) grpcio-tools authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/grpcio-tools.
+
+13. playwright-default
+Copyright (c) playwright authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/playwright.
+
+14. requests-default
+Copyright (c) requests authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/requests.
+
+15. ini-1.67.0
+Copyright (c) ini authors.
+You may obtain the source code and detailed information about this component at https://github.com/go-ini/ini.
+
+16. asyncio-default
+Copyright (c) asyncio authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/asyncio.
+
+17. time-0.11.0
+Copyright (c) time authors.
+You may obtain the source code and detailed information about this component at https://mvnrepository.com/artifact/org.occurrent/time.
+
+18. less-4.3.0
+Copyright (c) less authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/less.
+
+19. typescript-5.8.3
+Copyright (c) typescript authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/typescript.
+
+
+
+Terms of the apache-2.0:
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Open Source Software Licensed under the bsd-new:
+--------------------------------------------------------------------
+1. lxml-default
+Copyright (c) lxml authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/lxml.
+
+2. pypdf-default
+Copyright (c) pypdf authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/pypdf.
+
+3. go-querystring-1.1.0
+Copyright (c) go-querystring authors.
+You may obtain the source code and detailed information about this component at https://github.com/google/go-querystring.
+
+4. speakingurl-14.0.1
+Copyright (c) speakingurl authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/speakingurl.
+
+5. markdown-default
+Copyright (c) markdown authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/markdown.
+
+6. source-map-0.6.1
+Copyright (c) source-map authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/source-map.
+
+7. serialize-javascript-6.0.2
+Copyright (c) serialize-javascript authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/serialize-javascript.
+
+8. golang-asm-0.15.1
+Copyright (c) golang-asm authors.
+You may obtain the source code and detailed information about this component at https://github.com/twitchyliquid64/golang-asm.
+
+9. source-map-js-1.2.1
+Copyright (c) source-map-js authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/source-map-js.
+
+10. PyPDF2-default
+Copyright (c) PyPDF2 authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/pypdf2.
+
+11. fast-uri-3.0.6
+Copyright (c) fast-uri authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/fast-uri.
+
+12. mxj-1.8.4
+Copyright (c) mxj authors.
+You may obtain the source code and detailed information about this component at https://github.com/clbanning/mxj.
+
+13. protobuf-default
+Copyright (c) protobuf authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/protobuf.
+
+14. @xtuc/ieee754-1.2.0
+Copyright (c) @xtuc/ieee754 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@xtuc/ieee754.
+
+
+
+Terms of the bsd-new:
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the ORGANIZATION nor the names of its contributors may be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Open Source Software Licensed under the bsd-simplified:
+--------------------------------------------------------------------
+1. terser-5.43.1
+Copyright (c) terser authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/terser.
+
+2. estraverse-4.3.0
+Copyright (c) estraverse authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/estraverse.
+
+3. entities-4.5.0
+Copyright (c) entities authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/entities.
+
+
+
+Terms of the bsd-simplified:
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Open Source Software Licensed under the bsd-zero:
+--------------------------------------------------------------------
+1. tslib-2.8.1
+Copyright (c) tslib authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/tslib.
+
+
+
+Terms of the bsd-zero:
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Open Source Software Licensed under the isc:
+--------------------------------------------------------------------
+1. graceful-fs-4.2.11
+Copyright (c) graceful-fs authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/graceful-fs.
+
+2. which-5.0.0
+Copyright (c) which authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/which.
+
+3. isexe-3.1.1
+Copyright (c) isexe authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/isexe.
+
+4. yaml-2.4.2
+Copyright (c) yaml authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/yaml.
+
+5. lru-cache-5.1.1
+Copyright (c) lru-cache authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/lru-cache.
+
+6. read-package-json-fast-4.0.0
+Copyright (c) read-package-json-fast authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/read-package-json-fast.
+
+7. minimatch-9.0.5
+Copyright (c) minimatch authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/minimatch.
+
+8. semver-6.3.1
+Copyright (c) semver authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/semver.
+
+9. npm-normalize-package-bin-4.0.0
+Copyright (c) npm-normalize-package-bin authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/npm-normalize-package-bin.
+
+10. picocolors-1.1.1
+Copyright (c) picocolors authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/picocolors.
+
+
+
+Terms of the isc:
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+
+Open Source Software Licensed under the mit:
+--------------------------------------------------------------------
+1. @tsconfig/node22-22.0.2
+Copyright (c) @tsconfig/node22 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@tsconfig/node22.
+
+2. tinyglobby-0.2.14
+Copyright (c) tinyglobby authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/tinyglobby.
+
+3. jsesc-3.1.0
+Copyright (c) jsesc authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/jsesc.
+
+4. es-errors-1.3.0
+Copyright (c) es-errors authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/es-errors.
+
+5. brotli-1.1.0
+Copyright (c) brotli authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/brotli.
+
+6. undici-types-6.21.0
+Copyright (c) undici-types authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/undici-types.
+
+7. dayjs-1.11.10
+Copyright (c) dayjs authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/dayjs.
+
+8. @jridgewell/gen-mapping-0.3.12
+Copyright (c) @jridgewell/gen-mapping authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@jridgewell/gen-mapping.
+
+9. events-3.3.0
+Copyright (c) events authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/events.
+
+10. @types/sortablejs-1.15.8
+Copyright (c) @types/sortablejs authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@types/sortablejs.
+
+11. js-tokens-4.0.0
+Copyright (c) js-tokens authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/js-tokens.
+
+12. @vue/babel-helper-vue-transform-on-1.4.0
+Copyright (c) @vue/babel-helper-vue-transform-on authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@vue/babel-helper-vue-transform-on.
+
+13. @types/marked-5.0.2
+Copyright (c) @types/marked authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@types/marked.
+
+14. @types/tinycolor2-1.4.6
+Copyright (c) @types/tinycolor2 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@types/tinycolor2.
+
+15. xid-1.6.0
+Copyright (c) xid authors.
+You may obtain the source code and detailed information about this component at https://github.com/rs/xid.
+
+16. lodash-es-4.17.21
+Copyright (c) lodash-es authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/lodash-es.
+
+17. go-runewidth-0.0.15
+Copyright (c) go-runewidth authors.
+You may obtain the source code and detailed information about this component at https://gitee.com/mirrors/go-runewidth.
+
+18. marked-5.1.2
+Copyright (c) marked authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/marked.
+
+19. cast-1.7.1
+Copyright (c) cast authors.
+You may obtain the source code and detailed information about this component at https://github.com/spf13/cast.
+
+20. antiword-default
+Copyright (c) antiword authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/antiword.
+
+21. cos-python-sdk-v5-default
+Copyright (c) cos-python-sdk-v5 authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/cos-python-sdk-v5.
+
+22. loader-runner-4.3.0
+Copyright (c) loader-runner authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/loader-runner.
+
+23. alien-signals-1.0.13
+Copyright (c) alien-signals authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/alien-signals.
+
+24. @webassemblyjs/wasm-parser-1.14.1
+Copyright (c) @webassemblyjs/wasm-parser authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/wasm-parser.
+
+25. textract-default
+Copyright (c) textract authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/textract.
+
+26. hasown-2.0.2
+Copyright (c) hasown authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/hasown.
+
+27. shebang-regex-3.0.0
+Copyright (c) shebang-regex authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/shebang-regex.
+
+28. @types/eslint-scope-3.7.7
+Copyright (c) @types/eslint-scope authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@types/eslint-scope.
+
+29. node-releases-2.0.19
+Copyright (c) node-releases authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/node-releases.
+
+30. combined-stream-1.0.8
+Copyright (c) combined-stream authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/combined-stream.
+
+31. ajv-formats-2.1.1
+Copyright (c) ajv-formats authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/ajv-formats.
+
+32. has-tostringtag-1.0.2
+Copyright (c) has-tostringtag authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/has-tostringtag.
+
+33. vscode-uri-3.1.0
+Copyright (c) vscode-uri authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/vscode-uri.
+
+34. npm-run-all2-7.0.2
+Copyright (c) npm-run-all2 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/npm-run-all2.
+
+35. tablewriter-0.0.5
+Copyright (c) tablewriter authors.
+You may obtain the source code and detailed information about this component at https://github.com/olekukonko/tablewriter.
+
+36. cross-spawn-7.0.6
+Copyright (c) cross-spawn authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/cross-spawn.
+
+37. gin-1.10.0
+Copyright (c) gin authors.
+You may obtain the source code and detailed information about this component at https://github.com/donetkit/gin.
+
+38. he-1.2.0
+Copyright (c) he authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/he.
+
+39. @babel/helper-optimise-call-expression-7.27.1
+Copyright (c) @babel/helper-optimise-call-expression authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/helper-optimise-call-expression.
+
+40. debug-4.4.1
+Copyright (c) debug authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/debug.
+
+41. commander-2.20.3
+Copyright (c) commander authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/commander.
+
+42. mapstructure-1.4.3
+Copyright (c) mapstructure authors.
+You may obtain the source code and detailed information about this component at https://github.com/go-viper/mapstructure.
+
+43. @vue/compiler-vue2-2.7.16
+Copyright (c) @vue/compiler-vue2 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@vue/compiler-vue2.
+
+44. escalade-3.2.0
+Copyright (c) escalade authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/escalade.
+
+45. conc-0.3.0
+Copyright (c) conc authors.
+You may obtain the source code and detailed information about this component at https://github.com/sourcegraph/conc.
+
+46. @types/eslint-9.6.1
+Copyright (c) @types/eslint authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@types/eslint.
+
+47. go-httpheader-0.2.1
+Copyright (c) go-httpheader authors.
+You may obtain the source code and detailed information about this component at https://github.com/mozillazg/go-httpheader.
+
+48. @webassemblyjs/wasm-opt-1.14.1
+Copyright (c) @webassemblyjs/wasm-opt authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/wasm-opt.
+
+49. @popperjs/core-2.11.8
+Copyright (c) @popperjs/core authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@popperjs/core.
+
+50. @webassemblyjs/wast-printer-1.14.1
+Copyright (c) @webassemblyjs/wast-printer authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/wast-printer.
+
+51. webpack-sources-3.3.3
+Copyright (c) webpack-sources authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/webpack-sources.
+
+52. @webassemblyjs/wasm-gen-1.14.1
+Copyright (c) @webassemblyjs/wasm-gen authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/wasm-gen.
+
+53. @babel/helper-validator-identifier-7.27.1
+Copyright (c) @babel/helper-validator-identifier authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/helper-validator-identifier.
+
+54. merge-stream-2.0.0
+Copyright (c) merge-stream authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/merge-stream.
+
+55. logrus-1.9.3
+Copyright (c) logrus authors.
+You may obtain the source code and detailed information about this component at https://github.com/sirupsen/logrus.
+
+56. path-key-3.1.1
+Copyright (c) path-key authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/path-key.
+
+57. gorm-1.25.12
+Copyright (c) gorm authors.
+You may obtain the source code and detailed information about this component at https://github.com/go-gorm/gorm.
+
+58. fast-deep-equal-3.1.3
+Copyright (c) fast-deep-equal authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/fast-deep-equal.
+
+59. @webassemblyjs/utf8-1.13.2
+Copyright (c) @webassemblyjs/utf8 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/utf8.
+
+60. @babel/plugin-syntax-typescript-7.27.1
+Copyright (c) @babel/plugin-syntax-typescript authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/plugin-syntax-typescript.
+
+61. mime-db-1.52.0
+Copyright (c) mime-db authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/mime-db.
+
+62. @webassemblyjs/floating-point-hex-parser-1.13.2
+Copyright (c) @webassemblyjs/floating-point-hex-parser authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/floating-point-hex-parser.
+
+63. hookable-5.5.3
+Copyright (c) hookable authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/hookable.
+
+64. pidtree-0.6.0
+Copyright (c) pidtree authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/pidtree.
+
+65. @types/lodash-es-4.17.12
+Copyright (c) @types/lodash-es authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@types/lodash-es.
+
+66. shebang-command-2.0.0
+Copyright (c) shebang-command authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/shebang-command.
+
+67. @webassemblyjs/wasm-edit-1.14.1
+Copyright (c) @webassemblyjs/wasm-edit authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/wasm-edit.
+
+68. convert-source-map-2.0.0
+Copyright (c) convert-source-map authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/convert-source-map.
+
+69. json-schema-traverse-1.0.0
+Copyright (c) json-schema-traverse authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/json-schema-traverse.
+
+70. @webassemblyjs/helper-buffer-1.14.1
+Copyright (c) @webassemblyjs/helper-buffer authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/helper-buffer.
+
+71. @babel/helper-module-imports-7.27.1
+Copyright (c) @babel/helper-module-imports authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/helper-module-imports.
+
+72. update-browserslist-db-1.1.3
+Copyright (c) update-browserslist-db authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/update-browserslist-db.
+
+73. sugarss-5.0.0
+Copyright (c) sugarss authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/sugarss.
+
+74. uniseg-0.4.7
+Copyright (c) uniseg authors.
+You may obtain the source code and detailed information about this component at https://github.com/rivo/uniseg.
+
+75. buffer-from-1.1.2
+Copyright (c) buffer-from authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/buffer-from.
+
+76. @vue/babel-plugin-jsx-1.4.0
+Copyright (c) @vue/babel-plugin-jsx authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@vue/babel-plugin-jsx.
+
+77. @webassemblyjs/helper-wasm-section-1.14.1
+Copyright (c) @webassemblyjs/helper-wasm-section authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/helper-wasm-section.
+
+78. dig-1.18.1
+Copyright (c) dig authors.
+You may obtain the source code and detailed information about this component at https://github.com/uber-go/dig.
+
+79. go-isatty-0.0.20
+Copyright (c) go-isatty authors.
+You may obtain the source code and detailed information about this component at https://github.com/mattn/go-isatty.
+
+80. randombytes-2.1.0
+Copyright (c) randombytes authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/randombytes.
+
+81. mistletoe-default
+Copyright (c) mistletoe authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/mistletoe.
+
+82. @jridgewell/trace-mapping-0.3.29
+Copyright (c) @jridgewell/trace-mapping authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@jridgewell/trace-mapping.
+
+83. @jridgewell/source-map-0.3.10
+Copyright (c) @jridgewell/source-map authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@jridgewell/source-map.
+
+84. ajv-keywords-5.1.0
+Copyright (c) ajv-keywords authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/ajv-keywords.
+
+85. memorystream-0.3.1
+Copyright (c) memorystream authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/memorystream.
+
+86. @vue/devtools-api-6.6.4
+Copyright (c) @vue/devtools-api authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@vue/devtools-api.
+
+87. go-1.1.12
+Copyright (c) go authors.
+You may obtain the source code and detailed information about this component at https://github.com/json-iterator/go.
+
+88. supports-color-8.1.1
+Copyright (c) supports-color authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/supports-color.
+
+89. mime-types-2.1.35
+Copyright (c) mime-types authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/mime-types.
+
+90. enhanced-resolve-5.18.2
+Copyright (c) enhanced-resolve authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/enhanced-resolve.
+
+91. now-1.1.5
+Copyright (c) now authors.
+You may obtain the source code and detailed information about this component at https://github.com/jinzhu/now.
+
+92. ajv-8.17.1
+Copyright (c) ajv authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/ajv.
+
+93. proxy-from-env-1.1.0
+Copyright (c) proxy-from-env authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/proxy-from-env.
+
+94. chrome-trace-event-1.0.4
+Copyright (c) chrome-trace-event authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/chrome-trace-event.
+
+95. axios-1.10.0
+Copyright (c) axios authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/axios.
+
+96. gotenv-1.6.0
+Copyright (c) gotenv authors.
+You may obtain the source code and detailed information about this component at https://github.com/subosito/gotenv.
+
+97. brace-expansion-2.0.2
+Copyright (c) brace-expansion authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/brace-expansion.
+
+98. function-bind-1.1.2
+Copyright (c) function-bind authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/function-bind.
+
+99. @microsoft/fetch-event-source-2.0.1
+Copyright (c) @microsoft/fetch-event-source authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@microsoft/fetch-event-source.
+
+100. mxj-1.8.4
+Copyright (c) mxj authors.
+You may obtain the source code and detailed information about this component at https://github.com/clbanning/mxj.
+
+101. schema-utils-4.3.2
+Copyright (c) schema-utils authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/schema-utils.
+
+102. muggle-string-0.4.1
+Copyright (c) muggle-string authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/muggle-string.
+
+103. go-urn-1.4.0
+Copyright (c) go-urn authors.
+You may obtain the source code and detailed information about this component at https://github.com/leodido/go-urn.
+
+104. form-data-4.0.3
+Copyright (c) form-data authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/form-data.
+
+105. locales-0.14.1
+Copyright (c) locales authors.
+You may obtain the source code and detailed information about this component at https://gitee.com/mirrors/locales.
+
+106. python-docx-default
+Copyright (c) python-docx authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/python-docx.
+
+107. less-loader-12.3.0
+Copyright (c) less-loader authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/less-loader.
+
+108. @jridgewell/sourcemap-codec-1.5.4
+Copyright (c) @jridgewell/sourcemap-codec authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@jridgewell/sourcemap-codec.
+
+109. universal-translator-0.18.1
+Copyright (c) universal-translator authors.
+You may obtain the source code and detailed information about this component at https://github.com/go-playground/universal-translator.
+
+110. inflection-1.0.0
+Copyright (c) inflection authors.
+You may obtain the source code and detailed information about this component at https://github.com/jinzhu/inflection.
+
+111. sass-embedded-1.70.0
+Copyright (c) sass-embedded authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/sass-embedded.
+
+112. @jridgewell/resolve-uri-3.1.2
+Copyright (c) @jridgewell/resolve-uri authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@jridgewell/resolve-uri.
+
+113. tinycolor2-1.6.0
+Copyright (c) tinycolor2 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/tinycolor2.
+
+114. pagefind-1.3.0
+Copyright (c) pagefind authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/pagefind.
+
+115. viper-1.20.1
+Copyright (c) viper authors.
+You may obtain the source code and detailed information about this component at https://github.com/spf13/viper.
+
+116. delayed-stream-1.0.0
+Copyright (c) delayed-stream authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/delayed-stream.
+
+117. @webassemblyjs/helper-numbers-1.13.2
+Copyright (c) @webassemblyjs/helper-numbers authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/helper-numbers.
+
+118. nanoid-3.3.11
+Copyright (c) nanoid authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/nanoid.
+
+119. @babel/helper-compilation-targets-7.27.2
+Copyright (c) @babel/helper-compilation-targets authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/helper-compilation-targets.
+
+120. @webassemblyjs/ast-1.14.1
+Copyright (c) @webassemblyjs/ast authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/ast.
+
+121. @types/estree-1.0.8
+Copyright (c) @types/estree authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@types/estree.
+
+122. uuid-1.6.0
+Copyright (c) uuid authors.
+You may obtain the source code and detailed information about this component at https://kojipkgs.fedoraproject.org//vol/fedora_koji_archive00/packages/uuid/1.6.0/2.fc8/src/uuid-1.6.0-2.fc8.src.rpm.
+
+123. postgres-1.5.11
+Copyright (c) postgres authors.
+You may obtain the source code and detailed information about this component at https://github.com/go-gorm/postgres.
+
+124. perfect-debounce-1.0.0
+Copyright (c) perfect-debounce authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/perfect-debounce.
+
+125. md5-simd-1.1.2
+Copyright (c) md5-simd authors.
+You may obtain the source code and detailed information about this component at https://github.com/minio/md5-simd.
+
+126. ms-2.1.3
+Copyright (c) ms authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/ms.
+
+127. @babel/helper-module-transforms-7.27.3
+Copyright (c) @babel/helper-module-transforms authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/helper-module-transforms.
+
+128. @types/json-schema-7.0.15
+Copyright (c) @types/json-schema authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@types/json-schema.
+
+129. multierr-1.11.0
+Copyright (c) multierr authors.
+You may obtain the source code and detailed information about this component at https://github.com/uber-go/multierr.
+
+130. pflag-1.0.6
+Copyright (c) pflag authors.
+You may obtain the source code and detailed information about this component at https://github.com/php-toolkit/pflag.
+
+131. @webassemblyjs/ieee754-1.13.2
+Copyright (c) @webassemblyjs/ieee754 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/ieee754.
+
+132. browserslist-4.25.1
+Copyright (c) browserslist authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/browserslist.
+
+133. asynckit-0.4.0
+Copyright (c) asynckit authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/asynckit.
+
+134. sortablejs-1.15.6
+Copyright (c) sortablejs authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/sortablejs.
+
+135. get-intrinsic-1.3.0
+Copyright (c) get-intrinsic authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/get-intrinsic.
+
+136. markdownify-default
+Copyright (c) markdownify authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/markdownify.
+
+137. go-json-0.10.5
+Copyright (c) go-json authors.
+You may obtain the source code and detailed information about this component at https://github.com/goccy/go-json.
+
+138. path-browserify-1.0.1
+Copyright (c) path-browserify authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/path-browserify.
+
+139. terser-webpack-plugin-5.3.14
+Copyright (c) terser-webpack-plugin authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/terser-webpack-plugin.
+
+140. @babel/helpers-7.27.6
+Copyright (c) @babel/helpers authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/helpers.
+
+141. @vue/babel-plugin-resolve-type-1.4.0
+Copyright (c) @vue/babel-plugin-resolve-type authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@vue/babel-plugin-resolve-type.
+
+142. json-parse-even-better-errors-4.0.0
+Copyright (c) json-parse-even-better-errors authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/json-parse-even-better-errors.
+
+143. csstype-3.1.3
+Copyright (c) csstype authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/csstype.
+
+144. @babel/helper-plugin-utils-7.27.1
+Copyright (c) @babel/helper-plugin-utils authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/helper-plugin-utils.
+
+145. balanced-match-1.0.2
+Copyright (c) balanced-match authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/balanced-match.
+
+146. ansi-styles-6.2.1
+Copyright (c) ansi-styles authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/ansi-styles.
+
+147. go-humanize-1.0.1
+Copyright (c) go-humanize authors.
+You may obtain the source code and detailed information about this component at https://gitee.com/mirrors/go-humanize.
+
+148. picomatch-4.0.2
+Copyright (c) picomatch authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/picomatch.
+
+149. @webassemblyjs/helper-wasm-bytecode-1.13.2
+Copyright (c) @webassemblyjs/helper-wasm-bytecode authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@webassemblyjs/helper-wasm-bytecode.
+
+150. @babel/code-frame-7.27.1
+Copyright (c) @babel/code-frame authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@babel/code-frame.
+
+151. tsx-4.8.1
+Copyright (c) tsx authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/tsx.
+
+152. superjson-2.2.2
+Copyright (c) superjson authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/superjson.
+
+153. require-from-string-2.0.2
+Copyright (c) require-from-string authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/require-from-string.
+
+154. json5-2.2.3
+Copyright (c) json5 authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/json5.
+
+155. sass-1.70.0
+Copyright (c) sass authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/sass.
+
+156. @vue/tsconfig-0.7.0
+Copyright (c) @vue/tsconfig authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/@vue/tsconfig.
+
+157. openai-default
+Copyright (c) openai authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/openai.
+
+158. urllib3-default
+Copyright (c) urllib3 authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/urllib3.
+
+159. beautifulsoup4-default
+Copyright (c) beautifulsoup4 authors.
+You may obtain the source code and detailed information about this component at https://pypi.org/project/beautifulsoup4.
+
+160. pgpassfile-1.0.0
+Copyright (c) pgpassfile authors.
+You may obtain the source code and detailed information about this component at https://github.com/jackc/pgpassfile.
+
+161. source-map-support-0.5.21
+Copyright (c) source-map-support authors.
+You may obtain the source code and detailed information about this component at https://www.npmjs.com/package/source-map-support.
+
+
+
+Terms of the mit:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1ac93ab
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,215 @@
+.PHONY: help build run test clean docker-build-app docker-build-docreader docker-build-frontend docker-build-all docker-run migrate-up migrate-down docker-restart docker-stop start-all stop-all start-ollama stop-ollama build-images build-images-app build-images-docreader build-images-frontend clean-images check-env list-containers pull-images show-platform
+
+# Show help
+help:
+ @echo "WeKnora Makefile 帮助"
+ @echo ""
+ @echo "基础命令:"
+ @echo " build 构建应用"
+ @echo " run 运行应用"
+ @echo " test 运行测试"
+ @echo " clean 清理构建文件"
+ @echo ""
+ @echo "Docker 命令:"
+ @echo " docker-build-app 构建应用 Docker 镜像 (wechatopenai/weknora-app)"
+ @echo " docker-build-docreader 构建文档读取器镜像 (wechatopenai/weknora-docreader)"
+ @echo " docker-build-frontend 构建前端镜像 (wechatopenai/weknora-ui)"
+ @echo " docker-build-all 构建所有 Docker 镜像"
+ @echo " docker-run 运行 Docker 容器"
+ @echo " docker-stop 停止 Docker 容器"
+ @echo " docker-restart 重启 Docker 容器"
+ @echo ""
+ @echo "服务管理:"
+ @echo " start-all 启动所有服务"
+ @echo " stop-all 停止所有服务"
+ @echo " start-ollama 仅启动 Ollama 服务"
+ @echo ""
+ @echo "镜像构建:"
+ @echo " build-images 从源码构建所有镜像"
+ @echo " build-images-app 从源码构建应用镜像"
+ @echo " build-images-docreader 从源码构建文档读取器镜像"
+ @echo " build-images-frontend 从源码构建前端镜像"
+ @echo " clean-images 清理本地镜像"
+ @echo ""
+ @echo "数据库:"
+ @echo " migrate-up 执行数据库迁移"
+ @echo " migrate-down 回滚数据库迁移"
+ @echo ""
+ @echo "开发工具:"
+ @echo " fmt 格式化代码"
+ @echo " lint 代码检查"
+ @echo " deps 安装依赖"
+ @echo " docs 生成 API 文档"
+ @echo ""
+ @echo "环境检查:"
+ @echo " check-env 检查环境配置"
+ @echo " list-containers 列出运行中的容器"
+ @echo " pull-images 拉取最新镜像"
+ @echo " show-platform 显示当前构建平台"
+
+# Go related variables
+BINARY_NAME=WeKnora
+MAIN_PATH=./cmd/server
+
+# Docker related variables
+DOCKER_IMAGE=wechatopenai/weknora-app
+DOCKER_TAG=latest
+
+# Platform detection
+ifeq ($(shell uname -m),x86_64)
+ PLATFORM=linux/amd64
+else ifeq ($(shell uname -m),aarch64)
+ PLATFORM=linux/arm64
+else ifeq ($(shell uname -m),arm64)
+ PLATFORM=linux/arm64
+else
+ PLATFORM=linux/amd64
+endif
+
+# Build the application
+build:
+ go build -o $(BINARY_NAME) $(MAIN_PATH)
+
+# Run the application
+run: build
+ ./$(BINARY_NAME)
+
+# Run tests
+test:
+ go test -v ./...
+
+# Clean build artifacts
+clean:
+ go clean
+ rm -f $(BINARY_NAME)
+
+# Build Docker image
+docker-build-app:
+ @echo "获取版本信息..."
+ @eval $$(./scripts/get_version.sh env); \
+ ./scripts/get_version.sh info; \
+ docker build --platform $(PLATFORM) \
+ --build-arg VERSION_ARG="$$VERSION" \
+ --build-arg COMMIT_ID_ARG="$$COMMIT_ID" \
+ --build-arg BUILD_TIME_ARG="$$BUILD_TIME" \
+ --build-arg GO_VERSION_ARG="$$GO_VERSION" \
+ -f docker/Dockerfile.app -t $(DOCKER_IMAGE):$(DOCKER_TAG) .
+
+# Build docreader Docker image
+docker-build-docreader:
+ docker build --platform $(PLATFORM) -f docker/Dockerfile.docreader -t wechatopenai/weknora-docreader:latest .
+
+# Build frontend Docker image
+docker-build-frontend:
+ docker build --platform $(PLATFORM) -f frontend/Dockerfile -t wechatopenai/weknora-ui:latest frontend/
+
+# Build all Docker images
+docker-build-all: docker-build-app docker-build-docreader docker-build-frontend
+
+# Run Docker container (传统方式)
+docker-run:
+ docker-compose up
+
+# 使用新脚本启动所有服务
+start-all:
+ ./scripts/start_all.sh
+
+# 使用新脚本仅启动Ollama服务
+start-ollama:
+ ./scripts/start_all.sh --ollama
+
+# 使用新脚本仅启动Docker容器
+start-docker:
+ ./scripts/start_all.sh --docker
+
+# 使用新脚本停止所有服务
+stop-all:
+ ./scripts/start_all.sh --stop
+
+# Stop Docker container (传统方式)
+docker-stop:
+ docker-compose down
+
+# 从源码构建镜像相关命令
+build-images:
+ ./scripts/build_images.sh
+
+build-images-app:
+ ./scripts/build_images.sh --app
+
+build-images-docreader:
+ ./scripts/build_images.sh --docreader
+
+build-images-frontend:
+ ./scripts/build_images.sh --frontend
+
+clean-images:
+ ./scripts/build_images.sh --clean
+
+# Restart Docker container (stop, start)
+docker-restart:
+ docker-compose stop -t 60
+ docker-compose up
+
+# Database migrations
+migrate-up:
+ ./scripts/migrate.sh up
+
+migrate-down:
+ ./scripts/migrate.sh down
+
+# Generate API documentation
+docs:
+ swag init -g $(MAIN_PATH)/main.go -o ./docs
+
+# Format code
+fmt:
+ go fmt ./...
+
+# Lint code
+lint:
+ golangci-lint run
+
+# Install dependencies
+deps:
+ go mod download
+
+# Build for production
+build-prod:
+ VERSION=$${VERSION:-unknown}; \
+ COMMIT_ID=$${COMMIT_ID:-unknown}; \
+ BUILD_TIME=$${BUILD_TIME:-unknown}; \
+ GO_VERSION=$${GO_VERSION:-unknown}; \
+ LDFLAGS="-X 'github.com/Tencent/WeKnora/internal/handler.Version=$$VERSION' -X 'github.com/Tencent/WeKnora/internal/handler.CommitID=$$COMMIT_ID' -X 'github.com/Tencent/WeKnora/internal/handler.BuildTime=$$BUILD_TIME' -X 'github.com/Tencent/WeKnora/internal/handler.GoVersion=$$GO_VERSION'"; \
+ go build -ldflags="-w -s $$LDFLAGS" -o $(BINARY_NAME) $(MAIN_PATH)
+
+clean-db:
+ @echo "Cleaning database..."
+ @if [ $$(docker volume ls -q -f name=weknora_postgres-data) ]; then \
+ docker volume rm weknora_postgres-data; \
+ fi
+ @if [ $$(docker volume ls -q -f name=weknora_minio_data) ]; then \
+ docker volume rm weknora_minio_data; \
+ fi
+ @if [ $$(docker volume ls -q -f name=weknora_redis_data) ]; then \
+ docker volume rm weknora_redis_data; \
+ fi
+
+# Environment check
+check-env:
+ ./scripts/start_all.sh --check
+
+# List containers
+list-containers:
+ ./scripts/start_all.sh --list
+
+# Pull latest images
+pull-images:
+ ./scripts/start_all.sh --pull
+
+# Show current platform
+show-platform:
+ @echo "当前系统架构: $(shell uname -m)"
+ @echo "Docker构建平台: $(PLATFORM)"
+
+
diff --git a/README.md b/README.md
index 8e94dba..2b0929f 100644
--- a/README.md
+++ b/README.md
@@ -1,164 +1,329 @@
-# C-Cube Intelligent Customer Service System
+
+
+
+
+
-## Overview
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-C-Cube is an enterprise-level AI-powered customer service solution designed to help businesses improve customer service efficiency, reduce operational costs, and provide 24/7 intelligent customer support.
+
+| English | 简体中文 | 日本語 |
+
-## Key Features
+
+
-- **Intelligent Dialogue Engine**: Natural language understanding, intent recognition, and context management
-- **Knowledge Base Management**: Smart Q&A system with knowledge graph integration
-- **Human-AI Collaboration**: Seamless handoff between AI and human agents
-- **Multi-Channel Support**: Web, mobile, WeChat, Enterprise WeChat, DingTalk, phone, and email
-- **Analytics & Reporting**: Real-time monitoring, intelligent analysis, and customizable reports
-- **Continuous Learning**: Machine learning powered by human agent conversations
+ [Overview](#-overview) • [Architecture](#-architecture) • [Key Features](#-key-features) • [Getting Started](#-getting-started) • [API Reference](#-api-reference) • [Developer Guide](#-developer-guide)
+
+
+
-## Quick Start
+# 💡 WeKnora - LLM-Powered Document Understanding & Retrieval Framework
-### Installation
+## 📌 Overview
+
+[**WeKnora**](https://weknora.weixin.qq.com) is an LLM-powered framework designed for deep document understanding and semantic retrieval, especially for handling complex, heterogeneous documents.
+
+It adopts a modular architecture that combines multimodal preprocessing, semantic vector indexing, intelligent retrieval, and large language model inference. At its core, WeKnora follows the **RAG (Retrieval-Augmented Generation)** paradigm, enabling high-quality, context-aware answers by combining relevant document chunks with model reasoning.
+
+**Website:** https://weknora.weixin.qq.com
+
+## 🔒 Security Notice
+
+**Important:** Starting from v0.1.3, WeKnora includes login authentication functionality to enhance system security. For production deployments, we strongly recommend:
+
+- Deploy WeKnora services in internal/private network environments rather than public internet
+- Avoid exposing the service directly to public networks to prevent potential information leakage
+- Configure proper firewall rules and access controls for your deployment environment
+- Regularly update to the latest version for security patches and improvements
+
+## 🏗️ Architecture
+
+
+
+WeKnora employs a modern modular design to build a complete document understanding and retrieval pipeline. The system primarily includes document parsing, vector processing, retrieval engine, and large model inference as core modules, with each component being flexibly configurable and extendable.
+
+## 🎯 Key Features
+
+- **🔍 Precise Understanding**: Structured content extraction from PDFs, Word documents, images and more into unified semantic views
+- **🧠 Intelligent Reasoning**: Leverages LLMs to understand document context and user intent for accurate Q&A and multi-turn conversations
+- **🔧 Flexible Extension**: All components from parsing and embedding to retrieval and generation are decoupled for easy customization
+- **⚡ Efficient Retrieval**: Hybrid retrieval strategies combining keywords, vectors, and knowledge graphs
+- **🎯 User-Friendly**: Intuitive web interface and standardized APIs for zero technical barriers
+- **🔒 Secure & Controlled**: Support for local deployment and private cloud, ensuring complete data sovereignty
+
+## 📊 Application Scenarios
+
+| Scenario | Applications | Core Value |
+|---------|----------|----------|
+| **Enterprise Knowledge Management** | Internal document retrieval, policy Q&A, operation manual search | Improve knowledge discovery efficiency, reduce training costs |
+| **Academic Research Analysis** | Paper retrieval, research report analysis, scholarly material organization | Accelerate literature review, assist research decisions |
+| **Product Technical Support** | Product manual Q&A, technical documentation search, troubleshooting | Enhance customer service quality, reduce support burden |
+| **Legal & Compliance Review** | Contract clause retrieval, regulatory policy search, case analysis | Improve compliance efficiency, reduce legal risks |
+| **Medical Knowledge Assistance** | Medical literature retrieval, treatment guideline search, case analysis | Support clinical decisions, improve diagnosis quality |
+
+## 🧩 Feature Matrix
+
+| Module | Support | Description |
+|---------|---------|------|
+| Document Formats | ✅ PDF / Word / Txt / Markdown / Images (with OCR / Caption) | Support for structured and unstructured documents with text extraction from images |
+| Embedding Models | ✅ Local models, BGE / GTE APIs, etc. | Customizable embedding models, compatible with local deployment and cloud vector generation APIs |
+| Vector DB Integration | ✅ PostgreSQL (pgvector), Elasticsearch | Support for mainstream vector index backends, flexible switching for different retrieval scenarios |
+| Retrieval Strategies | ✅ BM25 / Dense Retrieval / GraphRAG | Support for sparse/dense recall and knowledge graph-enhanced retrieval with customizable retrieve-rerank-generate pipelines |
+| LLM Integration | ✅ Support for Qwen, DeepSeek, etc., with thinking/non-thinking mode switching | Compatible with local models (e.g., via Ollama) or external API services with flexible inference configuration |
+| QA Capabilities | ✅ Context-aware, multi-turn dialogue, prompt templates | Support for complex semantic modeling, instruction control and chain-of-thought Q&A with configurable prompts and context windows |
+| E2E Testing | ✅ Retrieval+generation process visualization and metric evaluation | End-to-end testing tools for evaluating recall hit rates, answer coverage, BLEU/ROUGE and other metrics |
+| Deployment Modes | ✅ Support for local deployment / Docker images | Meets private, offline deployment and flexible operation requirements |
+| User Interfaces | ✅ Web UI + RESTful API | Interactive interface and standard API endpoints, suitable for both developers and business users |
+
+## 🚀 Getting Started
+
+### 🛠 Prerequisites
+
+Make sure the following tools are installed on your system:
+
+* [Docker](https://www.docker.com/)
+* [Docker Compose](https://docs.docker.com/compose/)
+* [Git](https://git-scm.com/)
+
+### 📦 Installation
+
+#### ① Clone the repository
+
+```bash
+# Clone the main repository
+git clone https://github.com/Tencent/WeKnora.git
+cd WeKnora
+```
+
+#### ② Configure environment variables
+
+```bash
+# Copy example env file
+cp .env.example .env
+
+# Edit .env and set required values
+# All variables are documented in the .env.example comments
+```
+
+#### ③ Start the services
```bash
-# Clone the repository
-git clone https://github.com/Wintercom/c-cube.git
+# Start all services (Ollama + backend containers)
+./scripts/start_all.sh
+# Or
+make start-all
+```
-# Install dependencies
-cd c-cube
-npm install
+#### ③ Start the services (backup)
+
+```bash
+# Start ollama services (Optional)
+ollama serve > /dev/null 2>&1 &
# Start the service
-npm start
+docker compose up -d
+```
+
+#### ④ Stop the services
+
+```bash
+./scripts/start_all.sh --stop
+# Or
+make stop-all
```
-### Configuration
+### 🌐 Access Services
+
+Once started, services will be available at:
+
+* Web UI: `http://localhost`
+* Backend API: `http://localhost:8080`
+* Jaeger Tracing: `http://localhost:16686`
+
+### 🔌 Using WeChat Dialog Open Platform
+
+WeKnora serves as the core technology framework for the [WeChat Dialog Open Platform](https://chatbot.weixin.qq.com), providing a more convenient usage approach:
-Create a `.env` file in the root directory:
+- **Zero-code Deployment**: Simply upload knowledge to quickly deploy intelligent Q&A services within the WeChat ecosystem, achieving an "ask and answer" experience
+- **Efficient Question Management**: Support for categorized management of high-frequency questions, with rich data tools to ensure accurate, reliable, and easily maintainable answers
+- **WeChat Ecosystem Integration**: Through the WeChat Dialog Open Platform, WeKnora's intelligent Q&A capabilities can be seamlessly integrated into WeChat Official Accounts, Mini Programs, and other WeChat scenarios, enhancing user interaction experiences
-```env
-BOT_ID=your_bot_id
-API_KEY=your_api_key
-DATABASE_URL=your_database_url
+### 🔗 Access WeKnora via MCP Server
+
+#### 1️⃣ Clone the repository
+```
+git clone https://github.com/Tencent/WeKnora
```
-### Basic Usage
+#### 2️⃣ Configure MCP Server
+Configure the MCP client to connect to the server:
+```json
+{
+ "mcpServers": {
+ "weknora": {
+ "args": [
+ "path/to/WeKnora/mcp-server/run_server.py"
+ ],
+ "command": "python",
+ "env":{
+ "WEKNORA_API_KEY":"Enter your WeKnora instance, open developer tools, check the request header x-api-key starting with sk",
+ "WEKNORA_BASE_URL":"http(s)://your-weknora-address/api/v1"
+ }
+ }
+ }
+}
+```
-```javascript
-// Initialize the chatbot
-CCube.init({
- botId: 'your-bot-id',
- apiKey: 'your-api-key',
- position: 'right',
- theme: 'light'
-});
+Run directly using stdio command:
+```
+pip install weknora-mcp-server
+python -m weknora-mcp-server
```
-## Documentation
+## 🔧 Initialization Configuration Guide
-For complete product documentation in Chinese, please refer to [产品文档.md](./产品文档.md)
+To help users quickly configure various models and reduce trial-and-error costs, we've improved the original configuration file initialization method by adding a Web UI interface for model configuration. Before using, please ensure the code is updated to the latest version. The specific steps are as follows:
+If this is your first time using this project, you can skip steps ①② and go directly to steps ③④.
-### Documentation Contents
+### ① Stop the services
-1. Product Overview
-2. Core Features
-3. Technical Architecture
-4. Deployment Options
-5. User Guide
-6. API Documentation
-7. Security & Compliance
-8. FAQ
+```bash
+./scripts/start_all.sh --stop
+```
-## Architecture
+### ② Clear existing data tables (recommended when no important data exists)
+```bash
+make clean-db
```
-┌─────────────────────────────────────────────────────────────┐
-│ Access Layer │
-│ Web │ APP │ WeChat │ Enterprise WeChat │ Phone │ Email │
-└────────────────────┬────────────────────────────────────────┘
- │
-┌────────────────────▼────────────────────────────────────────┐
-│ API Gateway │
-│ Load Balancing │ Auth │ Rate Limiting │ Logging │
-└────────────────────┬────────────────────────────────────────┘
- │
-┌────────────────────▼────────────────────────────────────────┐
-│ Business Service Layer │
-│ Dialogue │ Session │ User │ Permission Management │
-└────────────────────┬────────────────────────────────────────┘
- │
-┌────────────────────▼────────────────────────────────────────┐
-│ AI Service Layer │
-│ NLU │ Dialogue Management │ Knowledge Graph │ Recommendation│
-└────────────────────┬────────────────────────────────────────┘
- │
-┌────────────────────▼────────────────────────────────────────┐
-│ Data Layer │
-│ MySQL │ Redis │ MongoDB │ Elasticsearch │ HDFS │
-└─────────────────────────────────────────────────────────────┘
+
+### ③ Compile and start services
+
+```bash
+./scripts/start_all.sh
```
-## Technology Stack
+### ④ Access Web UI
-### Backend
-- Go, Python
-- Gin, FastAPI
-- Kafka, RabbitMQ
-- Redis, MySQL, MongoDB
+http://localhost
-### Frontend
-- React, Vue.js
-- Ant Design, Element UI
-- WebSocket, Socket.io
+On first access, it will automatically redirect to the initialization configuration page. After configuration is complete, it will automatically redirect to the knowledge base page. Please follow the page instructions to complete model configuration.
-### AI
-- PyTorch, TensorFlow
-- BERT, GPT, T5
-- Faiss, Milvus
+
-## API Reference
+## 📱 Interface Showcase
-### Send Message
+### Web UI Interface
-```http
-POST /api/v1/chat/send
-Authorization: Bearer YOUR_API_KEY
-Content-Type: application/json
+
+
+ Knowledge Upload
 |
+ Q&A Entry
 |
+
+
+ Rich Text & Image Responses
 |
+
+
-{
- "bot_id": "bot_123456",
- "user_id": "user_789",
- "message": "I want to check my order status",
- "session_id": "session_abc"
-}
+**Knowledge Base Management:** Support for dragging and dropping various documents, automatically identifying document structures and extracting core knowledge to establish indexes. The system clearly displays processing progress and document status, achieving efficient knowledge base management.
+
+### Document Knowledge Graph
+
+
+
+  |
+  |
+
+
+
+WeKnora supports transforming documents into knowledge graphs, displaying the relationships between different sections of the documents. Once the knowledge graph feature is enabled, the system analyzes and constructs an internal semantic association network that not only helps users understand document content but also provides structured support for indexing and retrieval, enhancing the relevance and breadth of search results.
+
+### MCP Server Integration Effects
+
+
+## 📘 API Reference
+
+Troubleshooting FAQ: [Troubleshooting FAQ](./docs/QA.md)
+
+Detailed API documentation is available at: [API Docs](./docs/API.md)
+
+## 🧭 Developer Guide
+
+### 📁 Directory Structure
+
+```
+WeKnora/
+├── cmd/ # Main entry point
+├── internal/ # Core business logic
+├── config/ # Configuration files
+├── migrations/ # DB migration scripts
+├── scripts/ # Shell scripts
+├── services/ # Microservice logic
+├── frontend/ # Frontend app
+└── docs/ # Project documentation
```
-### Query Chat History
+### 🔧 Common Commands
-```http
-GET /api/v1/chat/history?session_id=session_abc&page=1&page_size=20
-Authorization: Bearer YOUR_API_KEY
+```bash
+# Wipe all data from DB (use with caution)
+make clean-db
```
-## Security
+## 🤝 Contributing
+
+We welcome community contributions! For suggestions, bugs, or feature requests, please submit an [Issue](https://github.com/Tencent/WeKnora/issues) or directly create a Pull Request.
-- Data encryption (TLS 1.3, AES-256)
-- Multi-tenant data isolation
-- Role-based access control
-- ISO 27001, SOC 2 Type II certified
-- GDPR compliant
+### 🎯 How to Contribute
-## Contributing
+- 🐛 **Bug Fixes**: Discover and fix system defects
+- ✨ **New Features**: Propose and implement new capabilities
+- 📚 **Documentation**: Improve project documentation
+- 🧪 **Test Cases**: Write unit and integration tests
+- 🎨 **UI/UX Enhancements**: Improve user interface and experience
-We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md) for details.
+### 📋 Contribution Process
-## License
+1. **Fork the project** to your GitHub account
+2. **Create a feature branch** `git checkout -b feature/amazing-feature`
+3. **Commit changes** `git commit -m 'Add amazing feature'`
+4. **Push branch** `git push origin feature/amazing-feature`
+5. **Create a Pull Request** with detailed description of changes
-Copyright © 2024 C-Cube. All rights reserved.
+### 🎨 Code Standards
-## Contact
+- Follow [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
+- Format code using `gofmt`
+- Add necessary unit tests
+- Update relevant documentation
-- Website: https://www.c-cube.ai
-- Email: support@c-cube.ai
-- Sales: sales@c-cube.ai
+### 📝 Commit Guidelines
+
+Use [Conventional Commits](https://www.conventionalcommits.org/) standard:
+
+```
+feat: Add document batch upload functionality
+fix: Resolve vector retrieval precision issue
+docs: Update API documentation
+test: Add retrieval engine test cases
+refactor: Restructure document parsing module
+```
----
+## 📄 License
-For detailed Chinese documentation, please refer to [产品文档.md](./产品文档.md)
+This project is licensed under the [MIT License](./LICENSE).
+You are free to use, modify, and distribute the code with proper attribution.
diff --git a/README_CN.md b/README_CN.md
new file mode 100644
index 0000000..5903a72
--- /dev/null
+++ b/README_CN.md
@@ -0,0 +1,326 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+| English | 简体中文 | 日本語 |
+
+
+
+
+
+ [项目介绍](#-项目介绍) • [架构设计](#-架构设计) • [核心特性](#-核心特性) • [快速开始](#-快速开始) • [文档](#-文档) • [开发指南](#-开发指南)
+
+
+
+
+# 💡 WeKnora - 基于大模型的文档理解检索框架
+
+## 📌 项目介绍
+
+[**WeKnora(维娜拉)**](https://weknora.weixin.qq.com) 是一款基于大语言模型(LLM)的文档理解与语义检索框架,专为结构复杂、内容异构的文档场景而打造。
+
+框架采用模块化架构,融合多模态预处理、语义向量索引、智能召回与大模型生成推理,构建起高效、可控的文档问答流程。核心检索流程基于 **RAG(Retrieval-Augmented Generation)** 机制,将上下文相关片段与语言模型结合,实现更高质量的语义回答。
+
+**官网:** https://weknora.weixin.qq.com
+
+## 🔒 安全声明
+
+**重要提示:** 从 v0.1.3 版本开始,WeKnora 提供了登录鉴权功能,以增强系统安全性。在生产环境部署时,我们强烈建议:
+
+- 将 WeKnora 服务部署在内网/私有网络环境中,而非公网环境
+- 避免将服务直接暴露在公网上,以防止重要信息泄露风险
+- 为部署环境配置适当的防火墙规则和访问控制
+- 定期更新到最新版本以获取安全补丁和改进
+
+## 🏗️ 架构设计
+
+
+
+WeKnora 采用现代化模块化设计,构建了一条完整的文档理解与检索流水线。系统主要包括文档解析、向量化处理、检索引擎和大模型推理等核心模块,每个组件均可灵活配置与扩展。
+
+## 🎯 核心特性
+
+- **🔍 精准理解**:支持 PDF、Word、图片等文档的结构化内容提取,统一构建语义视图
+- **🧠 智能推理**:借助大语言模型理解文档上下文与用户意图,支持精准问答与多轮对话
+- **🔧 灵活扩展**:从解析、嵌入、召回到生成全流程解耦,便于灵活集成与定制扩展
+- **⚡ 高效检索**:混合多种检索策略:关键词、向量、知识图谱
+- **🎯 简单易用**:直观的Web界面与标准API,零技术门槛快速上手
+- **🔒 安全可控**:支持本地化与私有云部署,数据完全自主可控
+
+## 📊 适用场景
+
+| 应用场景 | 具体应用 | 核心价值 |
+|---------|----------|----------|
+| **企业知识管理** | 内部文档检索、规章制度问答、操作手册查询 | 提升知识查找效率,降低培训成本 |
+| **科研文献分析** | 论文检索、研究报告分析、学术资料整理 | 加速文献调研,辅助研究决策 |
+| **产品技术支持** | 产品手册问答、技术文档检索、故障排查 | 提升客户服务质量,减少技术支持负担 |
+| **法律合规审查** | 合同条款检索、法规政策查询、案例分析 | 提高合规效率,降低法律风险 |
+| **医疗知识辅助** | 医学文献检索、诊疗指南查询、病例分析 | 辅助临床决策,提升诊疗质量 |
+
+## 🧩 功能模块能力
+
+| 功能模块 | 支持情况 | 说明 |
+|---------|---------|------|
+| 文档格式支持 | ✅ PDF / Word / Txt / Markdown / 图片(含 OCR / Caption) | 支持多种结构化与非结构化文档内容解析,支持图文混排与图像文字提取 |
+| 嵌入模型支持 | ✅ 本地模型、BGE / GTE API 等 | 支持自定义 embedding 模型,兼容本地部署与云端向量生成接口 |
+| 向量数据库接入 | ✅ PostgreSQL(pgvector)、Elasticsearch | 支持主流向量索引后端,可灵活切换与扩展,适配不同检索场景 |
+| 检索机制 | ✅ BM25 / Dense Retrieve / GraphRAG | 支持稠密/稀疏召回、知识图谱增强检索等多种策略,可自由组合召回-重排-生成流程 |
+| 大模型集成 | ✅ 支持 Qwen、DeepSeek 等,思考/非思考模式切换 | 可接入本地大模型(如 Ollama 启动)或调用外部 API 服务,支持推理模式灵活配置 |
+| 问答能力 | ✅ 上下文感知、多轮对话、提示词模板 | 支持复杂语义建模、指令控制与链式问答,可配置提示词与上下文窗口 |
+| 端到端测试支持 | ✅ 检索+生成过程可视化与指标评估 | 提供一体化链路测试工具,支持评估召回命中率、回答覆盖度、BLEU / ROUGE 等主流指标 |
+| 部署模式 | ✅ 支持本地部署 / Docker 镜像 | 满足私有化、离线部署与灵活运维的需求 |
+| 用户界面 | ✅ Web UI + RESTful API | 提供交互式界面与标准 API 接口,适配开发者与业务用户使用习惯 |
+
+## 🚀 快速开始
+
+### 🛠 环境要求
+
+确保本地已安装以下工具:
+
+* [Docker](https://www.docker.com/)
+* [Docker Compose](https://docs.docker.com/compose/)
+* [Git](https://git-scm.com/)
+
+### 📦 安装步骤
+
+#### ① 克隆代码仓库
+
+```bash
+# 克隆主仓库
+git clone https://github.com/Tencent/WeKnora.git
+cd WeKnora
+```
+
+#### ② 配置环境变量
+
+```bash
+# 复制示例配置文件
+cp .env.example .env
+
+# 编辑 .env,填入对应配置信息
+# 所有变量说明详见 .env.example 注释
+```
+
+#### ③ 启动服务
+
+```bash
+# 启动全部服务(含 Ollama 与后端容器)
+./scripts/start_all.sh
+# 或
+make start-all
+```
+
+#### ③ 启动服务备选
+
+```bash
+# 启动 ollama 服务 (可选)
+ollama serve > /dev/null 2>&1 &
+
+# 启动服务
+docker compose up -d
+```
+
+#### ④ 停止服务
+
+```bash
+./scripts/start_all.sh --stop
+# 或
+make stop-all
+```
+
+### 🌐 服务访问地址
+
+启动成功后,可访问以下地址:
+
+* Web UI:`http://localhost`
+* 后端 API:`http://localhost:8080`
+* 链路追踪(Jaeger):`http://localhost:16686`
+
+### 🔌 使用微信对话开放平台
+
+WeKnora 作为[微信对话开放平台](https://chatbot.weixin.qq.com)的核心技术框架,提供更简便的使用方式:
+
+- **零代码部署**:只需上传知识,即可在微信生态中快速部署智能问答服务,实现"即问即答"的体验
+- **高效问题管理**:支持高频问题的独立分类管理,提供丰富的数据工具,确保回答精准可靠且易于维护
+- **微信生态覆盖**:通过微信对话开放平台,WeKnora 的智能问答能力可无缝集成到公众号、小程序等微信场景中,提升用户交互体验
+### 🔗MCP服务器访问已经部署好的WEKnora
+#### 1️⃣克隆储存库
+```
+git clone https://github.com/Tencent/WeKnora
+```
+#### 2️⃣配置MCP服务器
+mcp客户端配置服务器
+```json
+{
+ "mcpServers": {
+ "weknora": {
+ "args": [
+ "path/to/WeKnora/mcp-server/run_server.py"
+ ],
+ "command": "python",
+ "env":{
+ "WEKNORA_API_KEY":"进入你的weknora实例,打开开发者工具,查看请求头x-api-key,以sk开头",
+ "WEKNORA_BASE_URL":"http(s)://你的weknora地址/api/v1"
+ }
+ }
+ }
+}
+```
+使用stdio命令直接运行
+```
+pip install weknora-mcp-server
+python -m weknora-mcp-server
+```
+
+## 🔧 初始化配置引导
+
+为了方便用户快速配置各类模型,降低试错成本,我们改进了原来的配置文件初始化方式,增加了Web UI界面进行各种模型的配置。在使用之前,请确保代码更新到最新版本。具体使用步骤如下:
+如果是第一次使用本项目,可跳过①②步骤,直接进入③④步骤。
+
+### ① 关闭服务
+
+```bash
+./scripts/start_all.sh --stop
+```
+
+### ② 清空原有数据表(建议在没有重要数据的情况下使用)
+
+```bash
+make clean-db
+```
+
+### ③ 编译并启动服务
+
+```bash
+./scripts/start_all.sh
+```
+
+### ④ 访问Web UI
+
+http://localhost
+
+首次访问会自动跳转到初始化配置页面,配置完成后会自动跳转到知识库页面。请按照页面提示信息完成模型的配置。
+
+
+
+
+## 📱 功能展示
+
+### Web UI 界面
+
+
+
+ 知识上传
 |
+ 知识问答入口
 |
+
+
+ 图文结果回答
 |
+
+
+
+**知识库管理:** 支持拖拽上传各类文档,自动识别文档结构并提取核心知识,建立索引。系统清晰展示处理进度和文档状态,实现高效的知识库管理。
+
+### 文档知识图谱
+
+
+
+  |
+  |
+
+
+
+WeKnora 支持将文档转化为知识图谱,展示文档中不同段落之间的关联关系。开启知识图谱功能后,系统会分析并构建文档内部的语义关联网络,不仅帮助用户理解文档内容,还为索引和检索提供结构化支撑,提升检索结果的相关性和广度。
+### 配套MCP服务器调用效果
+
+
+
+## 📘 文档
+
+常见问题排查:[常见问题排查](./docs/QA.md)
+
+详细接口说明请参考:[API 文档](./docs/API.md)
+
+## 🧭 开发指南
+
+### 📁 项目目录结构
+
+```
+WeKnora/
+├── cmd/ # 应用入口
+├── internal/ # 核心业务逻辑
+├── config/ # 配置文件
+├── migrations/ # 数据库迁移脚本
+├── scripts/ # 启动与工具脚本
+├── services/ # 各子服务实现
+├── frontend/ # 前端项目
+└── docs/ # 项目文档
+```
+
+### 🔧 常用命令
+
+```bash
+# 清空数据库(慎用!)
+make clean-db
+```
+
+## 🤝 贡献指南
+
+我们欢迎社区用户参与贡献!如有建议、Bug 或新功能需求,请通过 [Issue](https://github.com/Tencent/WeKnora/issues) 提出,或直接提交 Pull Request。
+
+### 🎯 贡献方式
+
+- 🐛 **Bug修复**: 发现并修复系统缺陷
+- ✨ **新功能**: 提出并实现新特性
+- 📚 **文档改进**: 完善项目文档
+- 🧪 **测试用例**: 编写单元测试和集成测试
+- 🎨 **UI/UX优化**: 改进用户界面和体验
+
+### 📋 贡献流程
+
+1. **Fork项目** 到你的GitHub账户
+2. **创建特性分支** `git checkout -b feature/amazing-feature`
+3. **提交更改** `git commit -m 'Add amazing feature'`
+4. **推送分支** `git push origin feature/amazing-feature`
+5. **创建Pull Request** 并详细描述变更内容
+
+### 🎨 代码规范
+
+- 遵循 [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
+- 使用 `gofmt` 格式化代码
+- 添加必要的单元测试
+- 更新相关文档
+
+### 📝 提交规范
+
+使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范:
+
+```
+feat: 添加文档批量上传功能
+fix: 修复向量检索精度问题
+docs: 更新API文档
+test: 添加检索引擎测试用例
+refactor: 重构文档解析模块
+```
+
+## 📄 许可证
+
+本项目基于 [MIT](./LICENSE) 协议发布。
+你可以自由使用、修改和分发本项目代码,但需保留原始版权声明。
diff --git a/README_JA.md b/README_JA.md
new file mode 100644
index 0000000..b7cdfc4
--- /dev/null
+++ b/README_JA.md
@@ -0,0 +1,328 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+| English | 简体中文 | 日本語 |
+
+
+
+
+
+ [プロジェクト紹介](#-プロジェクト紹介) • [アーキテクチャ設計](#️-アーキテクチャ設計) • [コア機能](#-コア機能) • [クイックスタート](#-クイックスタート) • [ドキュメント](#-ドキュメント) • [開発ガイド](#-開発ガイド)
+
+
+
+
+# 💡 WeKnora - 大規模言語モデルベースの文書理解検索フレームワーク
+
+## 📌 プロジェクト紹介
+
+[**WeKnora(ウィーノラ)**](https://weknora.weixin.qq.com) は、大規模言語モデル(LLM)をベースとした文書理解と意味検索フレームワークで、構造が複雑で内容が異質な文書シナリオ向けに特別に設計されています。
+
+フレームワークはモジュラーアーキテクチャを採用し、マルチモーダル前処理、意味ベクトルインデックス、インテリジェント検索、大規模モデル生成推論を統合して、効率的で制御可能な文書Q&Aワークフローを構築します。コア検索プロセスは **RAG(Retrieval-Augmented Generation)** メカニズムに基づいており、文脈関連フラグメントと言語モデルを組み合わせて、より高品質な意味的回答を実現します。
+
+**公式サイト:** https://weknora.weixin.qq.com
+
+## 🔒 セキュリティ通知
+
+**重要:** v0.1.3バージョンより、WeKnoraにはシステムセキュリティを強化するためのログイン認証機能が含まれています。本番環境でのデプロイメントにおいて、以下を強く推奨します:
+
+- WeKnoraサービスはパブリックインターネットではなく、内部/プライベートネットワーク環境にデプロイしてください
+- 重要な情報漏洩を防ぐため、サービスを直接パブリックネットワークに公開することは避けてください
+- デプロイメント環境に適切なファイアウォールルールとアクセス制御を設定してください
+- セキュリティパッチと改善のため、定期的に最新バージョンに更新してください
+
+## 🏗️ アーキテクチャ設計
+
+
+
+WeKnoraは現代的なモジュラー設計を採用し、完全な文書理解と検索パイプラインを構築しています。システムには主に文書解析、ベクトル化処理、検索エンジン、大規模モデル推論などのコアモジュールが含まれ、各コンポーネントは柔軟に設定および拡張できます。
+
+## 🎯 コア機能
+
+- **🔍 正確な理解**:PDF、Word、画像などの文書の構造化コンテンツ抽出をサポートし、統一された意味ビューを構築
+- **🧠 インテリジェント推論**:大規模言語モデルを活用して文書コンテキストとユーザーの意図を理解し、正確なQ&Aとマルチターン対話をサポート
+- **🔧 柔軟な拡張**:解析、埋め込み、検索から生成までの全プロセスを分離し、柔軟な統合とカスタマイズ拡張を容易に
+- **⚡ 効率的な検索**:複数の検索戦略のハイブリッド:キーワード、ベクトル、ナレッジグラフ
+- **🎯 使いやすさ**:直感的なWebインターフェースと標準API、技術的な障壁なしで素早く開始可能
+- **🔒 セキュアで制御可能**:ローカルおよびプライベートクラウドデプロイメントをサポート、データは完全に自己管理可能
+
+## 📊 適用シナリオ
+
+| 応用シナリオ | 具体的な応用 | コア価値 |
+|---------|----------|----------|
+| **企業ナレッジ管理** | 内部文書検索、規則Q&A、操作マニュアル照会 | ナレッジ検索効率の向上、トレーニングコストの削減 |
+| **科学研究文献分析** | 論文検索、研究レポート分析、学術資料整理 | 文献調査の加速、研究意思決定の支援 |
+| **製品技術サポート** | 製品マニュアルQ&A、技術文書検索、トラブルシューティング | カスタマーサービス品質の向上、技術サポート負担の軽減 |
+| **法的コンプライアンス審査** | 契約条項検索、法規政策照会、ケース分析 | コンプライアンス効率の向上、法的リスクの削減 |
+| **医療知識支援** | 医学文献検索、診療ガイドライン照会、症例分析 | 臨床意思決定の支援、診療品質の向上 |
+
+## 🧩 機能モジュール能力
+
+| 機能モジュール | サポート状況 | 説明 |
+|---------|---------|------|
+| 文書フォーマットサポート | ✅ PDF / Word / Txt / Markdown / 画像(OCR / Caption含む) | 様々な構造化・非構造化文書コンテンツの解析をサポート、図文混在と画像文字抽出をサポート |
+| 埋め込みモデルサポート | ✅ ローカルモデル、BGE / GTE API等 | カスタムembeddingモデルをサポート、ローカルデプロイとクラウドベクトル生成インターフェースに対応 |
+| ベクトルデータベース接続 | ✅ PostgreSQL(pgvector)、Elasticsearch | 主流のベクトルインデックスバックエンドをサポート、柔軟な切り替えと拡張が可能、異なる検索シナリオに適応 |
+| 検索メカニズム | ✅ BM25 / Dense Retrieve / GraphRAG | 密・疎検索、ナレッジグラフ強化検索など複数の戦略をサポート、検索-再ランキング-生成プロセスを自由に組み合わせ可能 |
+| 大規模モデル統合 | ✅ Qwen、DeepSeek等をサポート、思考/非思考モード切り替え | ローカル大規模モデル(Ollama起動など)に接続可能、または外部APIサービスを呼び出し、推論モードの柔軟な設定をサポート |
+| Q&A能力 | ✅ コンテキスト認識、マルチターン対話、プロンプトテンプレート | 複雑な意味モデリング、指示制御、チェーンQ&Aをサポート、プロンプトとコンテキストウィンドウを設定可能 |
+| エンドツーエンドテストサポート | ✅ 検索+生成プロセスの可視化と指標評価 | 一体化されたリンクテストツールを提供、リコール的中率、回答カバレッジ、BLEU / ROUGE等の主流指標の評価をサポート |
+| デプロイメントモード | ✅ ローカルデプロイメント / Dockerイメージをサポート | プライベート化、オフラインデプロイメント、柔軟な運用保守のニーズに対応 |
+| ユーザーインターフェース | ✅ Web UI + RESTful API | インタラクティブインターフェースと標準APIインターフェースを提供、開発者とビジネスユーザーの使用習慣に適応 |
+
+## 🚀 クイックスタート
+
+### 🛠 環境要件
+
+以下のツールがローカルにインストールされていることを確認してください:
+
+* [Docker](https://www.docker.com/)
+* [Docker Compose](https://docs.docker.com/compose/)
+* [Git](https://git-scm.com/)
+
+### 📦 インストール手順
+
+#### ① コードリポジトリのクローン
+
+```bash
+# メインリポジトリをクローン
+git clone https://github.com/Tencent/WeKnora.git
+cd WeKnora
+```
+
+#### ② 環境変数の設定
+
+```bash
+# サンプル設定ファイルをコピー
+cp .env.example .env
+
+# .envを編集し、対応する設定情報を入力
+# すべての変数の説明は.env.exampleのコメントを参照
+```
+
+#### ③ サービスの起動
+
+```bash
+# すべてのサービスを起動(Ollamaとバックエンドコンテナを含む)
+./scripts/start_all.sh
+# または
+make start-all
+```
+
+#### ③ サービス起動の代替方法
+
+```bash
+# ollamaサービスを起動(オプション)
+ollama serve > /dev/null 2>&1 &
+
+# サービスを起動
+docker compose up -d
+```
+
+#### ④ サービスの停止
+
+```bash
+./scripts/start_all.sh --stop
+# または
+make stop-all
+```
+
+### 🌐 サービスアクセスアドレス
+
+起動成功後、以下のアドレスにアクセスできます:
+
+* Web UI:`http://localhost`
+* バックエンドAPI:`http://localhost:8080`
+* リンクトレース(Jaeger):`http://localhost:16686`
+
+### 🔌 WeChat対話オープンプラットフォームの使用
+
+WeKnoraは[WeChat対話オープンプラットフォーム](https://chatbot.weixin.qq.com)のコア技術フレームワークとして、より簡単な使用方法を提供します:
+
+- **ノーコードデプロイメント**:知識をアップロードするだけで、WeChatエコシステムで迅速にインテリジェントQ&Aサービスをデプロイし、「即座に質問して即座に回答」の体験を実現
+- **効率的な問題管理**:高頻度の問題の独立した分類管理をサポートし、豊富なデータツールを提供して、正確で信頼性が高く、メンテナンスが容易な回答を保証
+- **WeChatエコシステムカバレッジ**:WeChat対話オープンプラットフォームを通じて、WeKnoraのインテリジェントQ&A能力を公式アカウント、ミニプログラムなどのWeChatシナリオにシームレスに統合し、ユーザーインタラクション体験を向上
+
+### 🔗MCPサーバーを使用してデプロイ済みのWeKnoraにアクセス
+#### 1️⃣リポジトリのクローン
+```
+git clone https://github.com/Tencent/WeKnora
+```
+#### 2️⃣MCPサーバーの設定
+MCPクライアントでサーバーを設定
+```json
+{
+ "mcpServers": {
+ "weknora": {
+ "args": [
+ "path/to/WeKnora/mcp-server/run_server.py"
+ ],
+ "command": "python",
+ "env":{
+ "WEKNORA_API_KEY":"WeKnoraインスタンスに入り、開発者ツールを開いて、リクエストヘッダーx-api-keyを確認、skで始まる",
+ "WEKNORA_BASE_URL":"http(s)://あなたのWeKnoraアドレス/api/v1"
+ }
+ }
+ }
+}
+```
+stdioコマンドで直接実行
+```
+pip install weknora-mcp-server
+python -m weknora-mcp-server
+```
+
+## 🔧 初期設定ガイド
+
+ユーザーが各種モデルを素早く設定し、試行錯誤のコストを削減するために、元の設定ファイル初期化方法を改善し、Web UIインターフェースを追加して各種モデルの設定を行えるようにしました。使用前に、コードが最新バージョンに更新されていることを確認してください。具体的な使用手順は以下の通りです:
+本プロジェクトを初めて使用する場合は、①②の手順をスキップして、直接③④の手順に進んでください。
+
+### ① サービスの停止
+
+```bash
+./scripts/start_all.sh --stop
+```
+
+### ② 既存のデータテーブルをクリア(重要なデータがない場合の推奨)
+
+```bash
+make clean-db
+```
+
+### ③ コンパイルしてサービスを起動
+
+```bash
+./scripts/start_all.sh
+```
+
+### ④ Web UIにアクセス
+
+http://localhost
+
+初回アクセス時は自動的に初期設定ページにリダイレクトされ、設定完了後は自動的にナレッジベースページにリダイレクトされます。ページの指示に従ってモデルの設定を完了してください。
+
+
+
+
+## 📱 機能デモ
+
+### Web UIインターフェース
+
+
+
+ ナレッジアップロード
 |
+ ナレッジQ&Aエントリー
 |
+
+
+ 図文結果回答
 |
+
+
+
+**ナレッジベース管理:** 各種文書のドラッグ&ドロップアップロードをサポートし、文書構造を自動認識してコア知識を抽出し、インデックスを構築します。システムは処理の進行状況と文書のステータスを明確に表示し、効率的なナレッジベース管理を実現します。
+
+### 文書ナレッジグラフ
+
+
+
+  |
+  |
+
+
+
+WeKnoraは文書をナレッジグラフに変換し、文書内の異なる段落間の関連関係を表示することをサポートします。ナレッジグラフ機能を有効にすると、システムは文書内部の意味関連ネットワークを分析・構築し、ユーザーが文書内容を理解するのを助けるだけでなく、インデックスと検索に構造化サポートを提供し、検索結果の関連性と幅を向上させます。
+
+### 対応MCPサーバー呼び出し効果
+
+
+
+## 📘 ドキュメント
+
+よくある問題の解決:[よくある問題](./docs/QA.md)
+
+詳細なAPIドキュメントは:[APIドキュメント](./docs/API.md)を参照してください
+
+## 🧭 開発ガイド
+
+### 📁 プロジェクトディレクトリ構造
+
+```
+WeKnora/
+├── cmd/ # アプリケーションエントリー
+├── internal/ # コアビジネスロジック
+├── config/ # 設定ファイル
+├── migrations/ # データベースマイグレーションスクリプト
+├── scripts/ # 起動とツールスクリプト
+├── services/ # 各サブサービスの実装
+├── frontend/ # フロントエンドプロジェクト
+└── docs/ # プロジェクトドキュメント
+```
+
+### 🔧 よく使うコマンド
+
+```bash
+# データベースをクリア(注意して使用!)
+make clean-db
+```
+
+## 🤝 貢献ガイド
+
+コミュニティユーザーの貢献を歓迎します!提案、バグ、新機能のリクエストがある場合は、[Issue](https://github.com/Tencent/WeKnora/issues)を通じて提出するか、直接Pull Requestを提出してください。
+
+### 🎯 貢献方法
+
+- 🐛 **バグ修正**: システムの欠陥を発見して修正
+- ✨ **新機能**: 新しい機能を提案して実装
+- 📚 **ドキュメント改善**: プロジェクトドキュメントを改善
+- 🧪 **テストケース**: ユニットテストと統合テストを作成
+- 🎨 **UI/UX最適化**: ユーザーインターフェースと体験を改善
+
+### 📋 貢献フロー
+
+1. **プロジェクトをFork** してあなたのGitHubアカウントへ
+2. **機能ブランチを作成** `git checkout -b feature/amazing-feature`
+3. **変更をコミット** `git commit -m 'Add amazing feature'`
+4. **ブランチをプッシュ** `git push origin feature/amazing-feature`
+5. **Pull Requestを作成** して変更内容を詳しく説明
+
+### 🎨 コード規約
+
+- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)に従う
+- `gofmt`を使用してコードをフォーマット
+- 必要なユニットテストを追加
+- 関連ドキュメントを更新
+
+### 📝 コミット規約
+
+[Conventional Commits](https://www.conventionalcommits.org/)規約を使用:
+
+```
+feat: 文書バッチアップロード機能を追加
+fix: ベクトル検索精度の問題を修正
+docs: APIドキュメントを更新
+test: 検索エンジンテストケースを追加
+refactor: 文書解析モジュールをリファクタリング
+```
+
+## 📄 ライセンス
+
+このプロジェクトは[MIT](./LICENSE)ライセンスの下で公開されています。
+このプロジェクトのコードを自由に使用、変更、配布できますが、元の著作権表示を保持する必要があります。
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..446ba66
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.4
\ No newline at end of file
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..b236055
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,183 @@
+# WeKnora HTTP 客户端
+
+这个包提供了与WeKnora服务进行交互的客户端库,支持所有基于HTTP的接口调用,使其他模块更方便地集成WeKnora服务,无需直接编写HTTP请求代码。
+
+## 主要功能
+
+该客户端包含以下主要功能模块:
+
+1. **会话管理**:创建、获取、更新和删除会话
+2. **知识库管理**:创建、获取、更新和删除知识库
+3. **知识管理**:添加、获取和删除知识内容
+4. **租户管理**:租户的CRUD操作
+5. **知识问答**:支持普通问答和流式问答
+6. **分块管理**:查询、更新和删除知识分块
+7. **消息管理**:获取和删除会话消息
+8. **模型管理**:创建、获取、更新和删除模型
+
+## 使用方法
+
+### 创建客户端实例
+
+```go
+import (
+ "context"
+ "github.com/Tencent/WeKnora/internal/client"
+ "time"
+)
+
+// 创建客户端实例
+apiClient := client.NewClient(
+ "http://api.example.com",
+ client.WithToken("your-auth-token"),
+ client.WithTimeout(30*time.Second),
+)
+```
+
+### 示例:创建知识库并上传文件
+
+```go
+// 创建知识库
+kb := &client.KnowledgeBase{
+ Name: "测试知识库",
+ Description: "这是一个测试知识库",
+ ChunkingConfig: client.ChunkingConfig{
+ ChunkSize: 500,
+ ChunkOverlap: 50,
+ Separators: []string{"\n\n", "\n", ". ", "? ", "! "},
+ },
+ ImageProcessingConfig: client.ImageProcessingConfig{
+ ModelID: "image_model_id",
+ },
+ EmbeddingModelID: "embedding_model_id",
+ SummaryModelID: "summary_model_id",
+}
+
+kb, err := apiClient.CreateKnowledgeBase(context.Background(), kb)
+if err != nil {
+ // 处理错误
+}
+
+// 上传知识文件并添加元数据
+metadata := map[string]string{
+ "source": "local",
+ "type": "document",
+}
+knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), kb.ID, "path/to/file.pdf", metadata)
+if err != nil {
+ // 处理错误
+}
+```
+
+### 示例:创建会话并进行问答
+
+```go
+// 创建会话
+sessionRequest := &client.CreateSessionRequest{
+ KnowledgeBaseID: knowledgeBaseID,
+ SessionStrategy: &client.SessionStrategy{
+ MaxRounds: 10,
+ EnableRewrite: true,
+ FallbackStrategy: "fixed_answer",
+ FallbackResponse: "抱歉,我无法回答这个问题",
+ EmbeddingTopK: 5,
+ KeywordThreshold: 0.5,
+ VectorThreshold: 0.7,
+ RerankModelID: "rerank_model_id",
+ RerankTopK: 3,
+ RerankThreshold: 0.8,
+ SummaryModelID: "summary_model_id",
+ },
+}
+
+session, err := apiClient.CreateSession(context.Background(), sessionRequest)
+if err != nil {
+ // 处理错误
+}
+
+// 普通问答
+answer, err := apiClient.KnowledgeQA(context.Background(), session.ID, &client.KnowledgeQARequest{
+ Query: "什么是人工智能?",
+})
+if err != nil {
+ // 处理错误
+}
+
+// 流式问答
+err = apiClient.KnowledgeQAStream(context.Background(), session.ID, "什么是机器学习?", func(response *client.StreamResponse) error {
+ // 处理每个响应片段
+ fmt.Print(response.Content)
+ return nil
+})
+if err != nil {
+ // 处理错误
+}
+```
+
+### 示例:管理模型
+
+```go
+// 创建模型
+modelRequest := &client.CreateModelRequest{
+ Name: "测试模型",
+ Type: client.ModelTypeChat,
+ Source: client.ModelSourceInternal,
+ Description: "这是一个测试模型",
+ Parameters: client.ModelParameters{
+ "temperature": 0.7,
+ "top_p": 0.9,
+ },
+ IsDefault: true,
+}
+model, err := apiClient.CreateModel(context.Background(), modelRequest)
+if err != nil {
+ // 处理错误
+}
+
+// 列出所有模型
+models, err := apiClient.ListModels(context.Background())
+if err != nil {
+ // 处理错误
+}
+```
+
+### 示例:管理知识分块
+
+```go
+// 列出知识分块
+chunks, total, err := apiClient.ListKnowledgeChunks(context.Background(), knowledgeID, 1, 10)
+if err != nil {
+ // 处理错误
+}
+
+// 更新分块
+updateRequest := &client.UpdateChunkRequest{
+ Content: "更新后的分块内容",
+ IsEnabled: true,
+}
+updatedChunk, err := apiClient.UpdateChunk(context.Background(), knowledgeID, chunkID, updateRequest)
+if err != nil {
+ // 处理错误
+}
+```
+
+### 示例:获取会话消息
+
+```go
+// 获取最近消息
+messages, err := apiClient.GetRecentMessages(context.Background(), sessionID, 10)
+if err != nil {
+ // 处理错误
+}
+
+// 获取指定时间之前的消息
+beforeTime := time.Now().Add(-24 * time.Hour)
+olderMessages, err := apiClient.GetMessagesBefore(context.Background(), sessionID, beforeTime, 10)
+if err != nil {
+ // 处理错误
+}
+```
+
+## 完整示例
+
+请参考 `example.go` 文件中的 `ExampleUsage` 函数,其中展示了客户端的完整使用流程。
\ No newline at end of file
diff --git a/client/README_EN.md b/client/README_EN.md
new file mode 100644
index 0000000..99ff39d
--- /dev/null
+++ b/client/README_EN.md
@@ -0,0 +1,184 @@
+# WeKnora HTTP Client
+
+This package provides a client library for interacting with WeKnora services, supporting all HTTP-based interface calls, making it easier for other modules to integrate with WeKnora services without having to write HTTP request code directly.
+
+## Main Features
+
+The client includes the following main functional modules:
+
+1. **Session Management**: Create, retrieve, update, and delete sessions
+2. **Knowledge Base Management**: Create, retrieve, update, and delete knowledge bases
+3. **Knowledge Management**: Add, retrieve, and delete knowledge content
+4. **Tenant Management**: CRUD operations for tenants
+5. **Knowledge Q&A**: Supports regular Q&A and streaming Q&A
+6. **Chunk Management**: Query, update, and delete knowledge chunks
+7. **Message Management**: Retrieve and delete session messages
+8. **Model Management**: Create, retrieve, update, and delete models
+9. **Evaluation Function**: Start evaluation tasks and get evaluation results
+
+## Usage
+
+### Creating Client Instance
+
+```go
+import (
+ "context"
+ "github.com/Tencent/WeKnora/internal/client"
+ "time"
+)
+
+// Create client instance
+apiClient := client.NewClient(
+ "http://api.example.com",
+ client.WithToken("your-auth-token"),
+ client.WithTimeout(30*time.Second),
+)
+```
+
+### Example: Create Knowledge Base and Upload File
+
+```go
+// Create knowledge base
+kb := &client.KnowledgeBase{
+ Name: "Test Knowledge Base",
+ Description: "This is a test knowledge base",
+ ChunkingConfig: client.ChunkingConfig{
+ ChunkSize: 500,
+ ChunkOverlap: 50,
+ Separators: []string{"\n\n", "\n", ". ", "? ", "! "},
+ },
+ ImageProcessingConfig: client.ImageProcessingConfig{
+ ModelID: "image_model_id",
+ },
+ EmbeddingModelID: "embedding_model_id",
+ SummaryModelID: "summary_model_id",
+}
+
+kb, err := apiClient.CreateKnowledgeBase(context.Background(), kb)
+if err != nil {
+ // Handle error
+}
+
+// Upload knowledge file with metadata
+metadata := map[string]string{
+ "source": "local",
+ "type": "document",
+}
+knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), kb.ID, "path/to/file.pdf", metadata)
+if err != nil {
+ // Handle error
+}
+```
+
+### Example: Create Session and Chat
+
+```go
+// Create session
+sessionRequest := &client.CreateSessionRequest{
+ KnowledgeBaseID: knowledgeBaseID,
+ SessionStrategy: &client.SessionStrategy{
+ MaxRounds: 10,
+ EnableRewrite: true,
+ FallbackStrategy: "fixed_answer",
+ FallbackResponse: "Sorry, I cannot answer this question",
+ EmbeddingTopK: 5,
+ KeywordThreshold: 0.5,
+ VectorThreshold: 0.7,
+ RerankModelID: "rerank_model_id",
+ RerankTopK: 3,
+ RerankThreshold: 0.8,
+ SummaryModelID: "summary_model_id",
+ },
+}
+
+session, err := apiClient.CreateSession(context.Background(), sessionRequest)
+if err != nil {
+ // Handle error
+}
+
+// Regular Q&A
+answer, err := apiClient.KnowledgeQA(context.Background(), session.ID, &client.KnowledgeQARequest{
+ Query: "What is artificial intelligence?",
+})
+if err != nil {
+ // Handle error
+}
+
+// Streaming Q&A
+err = apiClient.KnowledgeQAStream(context.Background(), session.ID, "What is machine learning?", func(response *client.StreamResponse) error {
+ // Handle each response chunk
+ fmt.Print(response.Content)
+ return nil
+})
+if err != nil {
+ // Handle error
+}
+```
+
+### Example: Managing Models
+
+```go
+// Create model
+modelRequest := &client.CreateModelRequest{
+ Name: "Test Model",
+ Type: client.ModelTypeChat,
+ Source: client.ModelSourceInternal,
+ Description: "This is a test model",
+ Parameters: client.ModelParameters{
+ "temperature": 0.7,
+ "top_p": 0.9,
+ },
+ IsDefault: true,
+}
+model, err := apiClient.CreateModel(context.Background(), modelRequest)
+if err != nil {
+ // Handle error
+}
+
+// List all models
+models, err := apiClient.ListModels(context.Background())
+if err != nil {
+ // Handle error
+}
+```
+
+### Example: Managing Knowledge Chunks
+
+```go
+// List knowledge chunks
+chunks, total, err := apiClient.ListKnowledgeChunks(context.Background(), knowledgeID, 1, 10)
+if err != nil {
+ // Handle error
+}
+
+// Update chunk
+updateRequest := &client.UpdateChunkRequest{
+ Content: "Updated chunk content",
+ IsEnabled: true,
+}
+updatedChunk, err := apiClient.UpdateChunk(context.Background(), knowledgeID, chunkID, updateRequest)
+if err != nil {
+ // Handle error
+}
+```
+
+### Example: Getting Session Messages
+
+```go
+// Get recent messages
+messages, err := apiClient.GetRecentMessages(context.Background(), sessionID, 10)
+if err != nil {
+ // Handle error
+}
+
+// Get messages before a specific time
+beforeTime := time.Now().Add(-24 * time.Hour)
+olderMessages, err := apiClient.GetMessagesBefore(context.Background(), sessionID, beforeTime, 10)
+if err != nil {
+ // Handle error
+}
+```
+
+## Complete Example
+
+Please refer to the `ExampleUsage` function in the `example.go` file, which demonstrates the complete usage flow of the client.
diff --git a/client/chunk.go b/client/chunk.go
new file mode 100644
index 0000000..6ed5148
--- /dev/null
+++ b/client/chunk.go
@@ -0,0 +1,171 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// This package encapsulates CRUD operations for server resources and provides a friendly interface for callers
+// The Chunk related interfaces are used to manage document chunks in the knowledge base
+package client
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+)
+
+// Chunk represents the information about a document chunk
+// Chunks are the basic units of storage and indexing in the knowledge base
+type Chunk struct {
+ ID string `json:"id"` // Unique identifier of the chunk
+ KnowledgeID string `json:"knowledge_id"` // Identifier of the parent knowledge
+ TenantID uint `json:"tenant_id"` // Tenant ID
+ Content string `json:"content"` // Text content of the chunk
+ Embedding []float32 `json:"embedding"` // Vector embedding representation
+ ChunkIndex int `json:"chunk_index"` // Index position of chunk in the document
+ TotalChunks int `json:"total_chunks"` // Total number of chunks in the document
+ IsEnabled bool `json:"is_enabled"` // Whether this chunk is enabled
+ StartAt int `json:"start_at"` // Starting position in original text
+ EndAt int `json:"end_at"` // Ending position in original text
+ VectorStoreID string `json:"vector_store_id"` // Vector storage ID
+ KeywordStoreID string `json:"keyword_store_id"` // Keyword storage ID
+ EmbeddingStatus int `json:"embedding_status"` // Embedding status: 0-unprocessed, 1-processing, 2-completed
+ ChunkType string `json:"chunk_type"`
+ ImageInfo string `json:"image_info"`
+ CreatedAt string `json:"created_at"` // Creation time
+ UpdatedAt string `json:"updated_at"` // Last update time
+}
+
+// ChunkResponse represents the response for a single chunk
+// API response structure containing a single chunk information
+type ChunkResponse struct {
+ Success bool `json:"success"` // Whether operation was successful
+ Data Chunk `json:"data"` // Chunk data
+}
+
+// ChunkListResponse represents the response for a list of chunks
+// API response structure for returning a list of chunks
+type ChunkListResponse struct {
+ Success bool `json:"success"` // Whether operation was successful
+ Data []Chunk `json:"data"` // List of chunks
+ Total int64 `json:"total"` // Total count
+ Page int `json:"page"` // Current page
+ PageSize int `json:"page_size"` // Items per page
+}
+
+// UpdateChunkRequest represents the request structure for updating a chunk
+// Used for requesting chunk information updates
+type UpdateChunkRequest struct {
+ Content string `json:"content"` // Chunk content
+ Embedding []float32 `json:"embedding"` // Vector embedding
+ ChunkIndex int `json:"chunk_index"` // Chunk index
+ IsEnabled bool `json:"is_enabled"` // Whether enabled
+ StartAt int `json:"start_at"` // Start position
+ EndAt int `json:"end_at"` // End position
+}
+
+// ListKnowledgeChunks lists all chunks under a knowledge document
+// Queries all chunks by knowledge ID with pagination support
+// Parameters:
+// - ctx: Context
+// - knowledgeID: Knowledge ID
+// - page: Page number, starts from 1
+// - pageSize: Number of items per page
+//
+// Returns:
+// - []Chunk: List of chunks
+// - int64: Total count
+// - error: Error information
+func (c *Client) ListKnowledgeChunks(ctx context.Context,
+ knowledgeID string, page int, pageSize int,
+) ([]Chunk, int64, error) {
+ path := fmt.Sprintf("/api/v1/chunks/%s", knowledgeID)
+
+ queryParams := url.Values{}
+ queryParams.Add("page", strconv.Itoa(page))
+ queryParams.Add("page_size", strconv.Itoa(pageSize))
+
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ var response ChunkListResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, 0, err
+ }
+
+ return response.Data, response.Total, nil
+}
+
+// UpdateChunk updates a chunk's information
+// Updates information for a specific chunk under a knowledge document
+// Parameters:
+// - ctx: Context
+// - knowledgeID: Knowledge ID
+// - chunkID: Chunk ID
+// - request: Update request
+//
+// Returns:
+// - *Chunk: Updated chunk
+// - error: Error information
+func (c *Client) UpdateChunk(ctx context.Context,
+ knowledgeID string, chunkID string, request *UpdateChunkRequest,
+) (*Chunk, error) {
+ path := fmt.Sprintf("/api/v1/chunks/%s/%s", knowledgeID, chunkID)
+ resp, err := c.doRequest(ctx, http.MethodPut, path, request, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response ChunkResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// DeleteChunk deletes a specific chunk
+// Deletes a specific chunk under a knowledge document
+// Parameters:
+// - ctx: Context
+// - knowledgeID: Knowledge ID
+// - chunkID: Chunk ID
+//
+// Returns:
+// - error: Error information
+func (c *Client) DeleteChunk(ctx context.Context, knowledgeID string, chunkID string) error {
+ path := fmt.Sprintf("/api/v1/chunks/%s/%s", knowledgeID, chunkID)
+ resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
+
+// DeleteChunksByKnowledgeID deletes all chunks under a knowledge document
+// Batch deletes all chunks under the specified knowledge document
+// Parameters:
+// - ctx: Context
+// - knowledgeID: Knowledge ID
+//
+// Returns:
+// - error: Error information
+func (c *Client) DeleteChunksByKnowledgeID(ctx context.Context, knowledgeID string) error {
+ path := fmt.Sprintf("/api/v1/chunks/%s", knowledgeID)
+ resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
diff --git a/client/client.go b/client/client.go
new file mode 100644
index 0000000..c92a276
--- /dev/null
+++ b/client/client.go
@@ -0,0 +1,104 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// This package encapsulates CRUD operations for server resources and provides a friendly interface for callers
+package client
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "time"
+)
+
+// Client is the client for interacting with the WeKnora service
+type Client struct {
+ baseURL string
+ httpClient *http.Client
+ token string
+}
+
+// ClientOption defines client configuration options
+type ClientOption func(*Client)
+
+// WithTimeout sets the HTTP client timeout
+func WithTimeout(timeout time.Duration) ClientOption {
+ return func(c *Client) {
+ c.httpClient.Timeout = timeout
+ }
+}
+
+// WithToken sets the authentication token
+func WithToken(token string) ClientOption {
+ return func(c *Client) {
+ c.token = token
+ }
+}
+
+// NewClient creates a new client instance
+func NewClient(baseURL string, options ...ClientOption) *Client {
+ client := &Client{
+ baseURL: baseURL,
+ httpClient: &http.Client{
+ Timeout: 30 * time.Second,
+ },
+ }
+
+ for _, option := range options {
+ option(client)
+ }
+
+ return client
+}
+
+// doRequest executes an HTTP request
+func (c *Client) doRequest(ctx context.Context,
+ method, path string, body interface{}, query url.Values,
+) (*http.Response, error) {
+ var reqBody io.Reader
+ if body != nil {
+ jsonData, err := json.Marshal(body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to serialize request body: %w", err)
+ }
+ reqBody = bytes.NewBuffer(jsonData)
+ }
+
+ url := fmt.Sprintf("%s%s", c.baseURL, path)
+ if len(query) > 0 {
+ url = fmt.Sprintf("%s?%s", url, query.Encode())
+ }
+
+ req, err := http.NewRequestWithContext(ctx, method, url, reqBody)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+ if c.token != "" {
+ req.Header.Set("X-API-Key", c.token)
+ }
+ if requestID := ctx.Value("RequestID"); requestID != nil {
+ req.Header.Set("X-Request-ID", requestID.(string))
+ }
+
+ return c.httpClient.Do(req)
+}
+
+// parseResponse parses an HTTP response
+func parseResponse(resp *http.Response, target interface{}) error {
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+ body, _ := io.ReadAll(resp.Body)
+ return fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ if target == nil {
+ return nil
+ }
+
+ return json.NewDecoder(resp.Body).Decode(target)
+}
diff --git a/client/evaluation.go b/client/evaluation.go
new file mode 100644
index 0000000..bb312f1
--- /dev/null
+++ b/client/evaluation.go
@@ -0,0 +1,113 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// The Evaluation related interfaces are used for starting and retrieving model evaluation task results
+// Evaluation tasks can be used to measure model performance and
+// compare different embedding models, chat models, and reranking models
+package client
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+)
+
+// EvaluationTask represents an evaluation task
+// Contains basic information about a model evaluation task
+type EvaluationTask struct {
+ ID string `json:"id"` // Task unique identifier
+ Status string `json:"status"` // Task status: pending, running, completed, failed
+ Progress int `json:"progress"` // Task progress, integer value 0-100
+ DatasetID string `json:"dataset_id"` // Evaluation dataset ID
+ EmbeddingID string `json:"embedding_id"` // Embedding model ID
+ ChatID string `json:"chat_id"` // Chat model ID
+ RerankID string `json:"rerank_id"` // Reranking model ID
+ CreatedAt string `json:"created_at"` // Task creation time
+ CompleteAt string `json:"complete_at"` // Task completion time
+ ErrorMsg string `json:"error_msg"` // Error message, has value when task fails
+}
+
+// EvaluationResult represents the evaluation results
+// Contains detailed evaluation result information
+type EvaluationResult struct {
+ TaskID string `json:"task_id"` // Associated task ID
+ Status string `json:"status"` // Task status
+ Progress int `json:"progress"` // Task progress
+ TotalQueries int `json:"total_queries"` // Total number of queries
+ TotalSamples int `json:"total_samples"` // Total number of samples
+ Metrics map[string]float64 `json:"metrics"` // Evaluation metrics collection
+ QueriesStat []map[string]interface{} `json:"queries_stat"` // Statistics for each query
+ CreatedAt string `json:"created_at"` // Creation time
+ CompleteAt string `json:"complete_at"` // Completion time
+ ErrorMsg string `json:"error_msg"` // Error message
+}
+
+// EvaluationRequest represents an evaluation request
+// Parameters used to start a new evaluation task
+type EvaluationRequest struct {
+ DatasetID string `json:"dataset_id"` // Dataset ID to evaluate
+ EmbeddingModelID string `json:"embedding_id"` // Embedding model ID
+ ChatModelID string `json:"chat_id"` // Chat model ID
+ RerankModelID string `json:"rerank_id"` // Reranking model ID
+}
+
+// EvaluationTaskResponse represents an evaluation task response
+// API response structure for evaluation tasks
+type EvaluationTaskResponse struct {
+ Success bool `json:"success"` // Whether operation was successful
+ Data EvaluationTask `json:"data"` // Evaluation task data
+}
+
+// EvaluationResultResponse represents an evaluation result response
+// API response structure for evaluation results
+type EvaluationResultResponse struct {
+ Success bool `json:"success"` // Whether operation was successful
+ Data EvaluationResult `json:"data"` // Evaluation result data
+}
+
+// StartEvaluation starts an evaluation task
+// Creates and starts a new evaluation task based on provided parameters
+// Parameters:
+// - ctx: Context, used for passing request context information such as deadline, cancellation signals, etc.
+// - request: Evaluation request parameters, including dataset ID and model IDs
+//
+// Returns:
+// - *EvaluationTask: Created evaluation task information
+// - error: Error information if the request fails
+func (c *Client) StartEvaluation(ctx context.Context, request *EvaluationRequest) (*EvaluationTask, error) {
+ resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/evaluation", request, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response EvaluationTaskResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// GetEvaluationResult retrieves evaluation results
+// Retrieves detailed results for an evaluation task by task ID
+// Parameters:
+// - ctx: Context, used for passing request context information
+// - taskID: Evaluation task ID, used to identify the specific evaluation task to query
+//
+// Returns:
+// - *EvaluationResult: Detailed evaluation task results
+// - error: Error information if the request fails
+func (c *Client) GetEvaluationResult(ctx context.Context, taskID string) (*EvaluationResult, error) {
+ queryParams := url.Values{}
+ queryParams.Add("task_id", taskID)
+
+ resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/evaluation", nil, queryParams)
+ if err != nil {
+ return nil, err
+ }
+
+ var response EvaluationResultResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
diff --git a/client/example.go b/client/example.go
new file mode 100644
index 0000000..62833ae
--- /dev/null
+++ b/client/example.go
@@ -0,0 +1,271 @@
+package client
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+)
+
+// ExampleUsage demonstrates the complete usage flow of the WeKnora client, including:
+// - Creating a client instance
+// - Creating a knowledge base
+// - Uploading knowledge files
+// - Creating a session
+// - Performing question-answering
+// - Using streaming question-answering
+// - Managing models
+// - Managing knowledge chunks
+// - Getting session messages
+// - Cleaning up resources
+func ExampleUsage() {
+ // Create a client instance
+ apiClient := NewClient(
+ "http://localhost:8080",
+ WithToken("your-auth-token"),
+ WithTimeout(30*time.Second),
+ )
+
+ // 1. Create a knowledge base
+ fmt.Println("1. Creating knowledge base...")
+ kb := &KnowledgeBase{
+ Name: "Test Knowledge Base",
+ Description: "This is a test knowledge base",
+ ChunkingConfig: ChunkingConfig{
+ ChunkSize: 500,
+ ChunkOverlap: 50,
+ Separators: []string{"\n\n", "\n", ". ", "? ", "! "},
+ },
+ ImageProcessingConfig: ImageProcessingConfig{
+ ModelID: "image_model_id",
+ },
+ EmbeddingModelID: "embedding_model_id",
+ SummaryModelID: "summary_model_id",
+ }
+
+ createdKB, err := apiClient.CreateKnowledgeBase(context.Background(), kb)
+ if err != nil {
+ fmt.Printf("Failed to create knowledge base: %v\n", err)
+ return
+ }
+ fmt.Printf("Knowledge base created successfully: ID=%s, Name=%s\n", createdKB.ID, createdKB.Name)
+
+ // 2. Upload knowledge file
+ fmt.Println("\n2. Uploading knowledge file...")
+ filePath := "path/to/sample.pdf" // Sample file path
+
+ // Check if file exists before uploading
+ if _, err := os.Stat(filePath); os.IsNotExist(err) {
+ fmt.Printf("File does not exist: %s, skipping upload step\n", filePath)
+ } else {
+ // Add metadata
+ metadata := map[string]string{
+ "source": "local",
+ "type": "document",
+ }
+ knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), createdKB.ID, filePath, metadata, nil)
+ if err != nil {
+ fmt.Printf("Failed to upload knowledge file: %v\n", err)
+ } else {
+ fmt.Printf("File uploaded successfully: Knowledge ID=%s, Title=%s\n", knowledge.ID, knowledge.Title)
+ }
+ }
+
+ // Create text knowledge (alternative to file upload)
+ // Note: This is just an example, the client package may not support creating text knowledge directly
+ // In actual use, refer to the methods provided in client.knowledge.go
+ fmt.Println("\nCreating text knowledge (example)")
+ fmt.Println("Title: Test Text Knowledge")
+ fmt.Println("Description: Test knowledge created from text")
+
+ // 3. Create a model
+ fmt.Println("\n3. Creating model...")
+ modelRequest := &CreateModelRequest{
+ Name: "Test Model",
+ Type: ModelTypeChat,
+ Source: ModelSourceInternal,
+ Description: "This is a test model",
+ Parameters: ModelParameters{
+ "temperature": 0.7,
+ "top_p": 0.9,
+ },
+ IsDefault: true,
+ }
+
+ model, err := apiClient.CreateModel(context.Background(), modelRequest)
+ if err != nil {
+ fmt.Printf("Failed to create model: %v\n", err)
+ } else {
+ fmt.Printf("Model created successfully: ID=%s, Name=%s\n", model.ID, model.Name)
+ }
+
+ // List all models
+ models, err := apiClient.ListModels(context.Background())
+ if err != nil {
+ fmt.Printf("Failed to get model list: %v\n", err)
+ } else {
+ fmt.Printf("System has %d models\n", len(models))
+ }
+
+ // 4. Create a session
+ fmt.Println("\n4. Creating session...")
+ sessionRequest := &CreateSessionRequest{
+ KnowledgeBaseID: createdKB.ID,
+ SessionStrategy: &SessionStrategy{
+ MaxRounds: 10,
+ EnableRewrite: true,
+ FallbackStrategy: "fixed_answer",
+ FallbackResponse: "Sorry, I cannot answer this question",
+ EmbeddingTopK: 5,
+ KeywordThreshold: 0.5,
+ VectorThreshold: 0.7,
+ RerankModelID: "rerank_model_id",
+ RerankTopK: 3,
+ RerankThreshold: 0.8,
+ SummaryModelID: "summary_model_id",
+ SummaryParameters: &SummaryConfig{
+ Temperature: 0.7,
+ TopP: 0.9,
+ MaxTokens: 100,
+ },
+ },
+ }
+
+ session, err := apiClient.CreateSession(context.Background(), sessionRequest)
+ if err != nil {
+ fmt.Printf("Failed to create session: %v\n", err)
+ return
+ }
+ fmt.Printf("Session created successfully: ID=%s\n", session.ID)
+
+ // 5. Perform knowledge Q&A (using streaming API)
+ fmt.Println("\n5. Performing knowledge Q&A...")
+ question := "What is artificial intelligence?"
+ fmt.Printf("Question: %s\nAnswer: ", question)
+
+ // Use streaming API for Q&A (Note: Client may only provide streaming Q&A API)
+ var answer strings.Builder
+ var references []*SearchResult
+
+ err = apiClient.KnowledgeQAStream(context.Background(),
+ session.ID,
+ question,
+ func(response *StreamResponse) error {
+ if response.ResponseType == ResponseTypeAnswer {
+ answer.WriteString(response.Content)
+ }
+
+ if response.Done && len(response.KnowledgeReferences) > 0 {
+ references = response.KnowledgeReferences
+ }
+ return nil
+ })
+
+ if err != nil {
+ fmt.Printf("Q&A failed: %v\n", err)
+ } else {
+ fmt.Printf("%s\n", answer.String())
+ if len(references) > 0 {
+ fmt.Println("References:")
+ for i, ref := range references {
+ fmt.Printf("%d. %s\n", i+1, ref.Content[:min(50, len(ref.Content))]+"...")
+ }
+ }
+ }
+
+ // 6. Perform another streaming Q&A
+ fmt.Println("\n6. Performing streaming Q&A...")
+ streamQuestion := "What is machine learning?"
+ fmt.Printf("Question: %s\nAnswer: ", streamQuestion)
+
+ err = apiClient.KnowledgeQAStream(context.Background(),
+ session.ID,
+ streamQuestion,
+ func(response *StreamResponse) error {
+ fmt.Print(response.Content)
+ return nil
+ },
+ )
+ if err != nil {
+ fmt.Printf("\nStreaming Q&A failed: %v\n", err)
+ }
+ fmt.Println() // Line break
+
+ // 7. Get session messages
+ fmt.Println("\n7. Getting session messages...")
+ messages, err := apiClient.GetRecentMessages(context.Background(), session.ID, 10)
+ if err != nil {
+ fmt.Printf("Failed to get session messages: %v\n", err)
+ } else {
+ fmt.Printf("Retrieved %d recent messages:\n", len(messages))
+ for i, msg := range messages {
+ fmt.Printf("%d. Role: %s, Content: %s\n", i+1, msg.Role, msg.Content[:min(30, len(msg.Content))]+"...")
+ }
+ }
+
+ // 8. Manage knowledge chunks
+ // Assume we have uploaded knowledge and have a knowledge ID
+ knowledgeID := "knowledge_id_example" // In actual use, use a real knowledge ID
+
+ fmt.Println("\n8. Managing knowledge chunks...")
+ chunks, total, err := apiClient.ListKnowledgeChunks(context.Background(), knowledgeID, 1, 10)
+ if err != nil {
+ fmt.Printf("Failed to get knowledge chunks: %v\n", err)
+ } else {
+ fmt.Printf("Knowledge has %d chunks, retrieved %d chunks\n", total, len(chunks))
+
+ if len(chunks) > 0 {
+ // Update the first chunk
+ chunkID := chunks[0].ID
+ updateRequest := &UpdateChunkRequest{
+ Content: "Updated chunk content - " + chunks[0].Content,
+ IsEnabled: true,
+ }
+
+ updatedChunk, err := apiClient.UpdateChunk(context.Background(), knowledgeID, chunkID, updateRequest)
+ if err != nil {
+ fmt.Printf("Failed to update chunk: %v\n", err)
+ } else {
+ fmt.Printf("Chunk updated successfully: ID=%s\n", updatedChunk.ID)
+ }
+ }
+ }
+
+ // 10. Clean up resources (optional, in actual use, keep or delete as needed)
+ fmt.Println("\n10. Cleaning up resources...")
+ if session != nil {
+ if err := apiClient.DeleteSession(context.Background(), session.ID); err != nil {
+ fmt.Printf("Failed to delete session: %v\n", err)
+ } else {
+ fmt.Println("Session deleted")
+ }
+ }
+
+ // Delete knowledge (assuming we have a valid knowledge ID)
+ if knowledgeID != "" {
+ if err := apiClient.DeleteKnowledge(context.Background(), knowledgeID); err != nil {
+ fmt.Printf("Failed to delete knowledge: %v\n", err)
+ } else {
+ fmt.Println("Knowledge deleted")
+ }
+ }
+
+ if createdKB != nil {
+ if err := apiClient.DeleteKnowledgeBase(context.Background(), createdKB.ID); err != nil {
+ fmt.Printf("Failed to delete knowledge base: %v\n", err)
+ } else {
+ fmt.Println("Knowledge base deleted")
+ }
+ }
+
+ fmt.Println("\nExample completed")
+}
+
+// min returns the smaller of two integers
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
diff --git a/client/go.mod b/client/go.mod
new file mode 100644
index 0000000..1a20c4b
--- /dev/null
+++ b/client/go.mod
@@ -0,0 +1,3 @@
+module github.com/Tencent/WeKnora/client
+
+go 1.24.2
diff --git a/client/go.sum b/client/go.sum
new file mode 100644
index 0000000..e69de29
diff --git a/client/knowledge.go b/client/knowledge.go
new file mode 100644
index 0000000..3a3ea9d
--- /dev/null
+++ b/client/knowledge.go
@@ -0,0 +1,356 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// The Knowledge related interfaces are used to manage knowledge entries in the knowledge base
+// Knowledge entries can be created from local files, web URLs, or directly from text content
+// They can also be retrieved, deleted, and downloaded as files
+package client
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
+ "time"
+)
+
+// Knowledge represents knowledge information
+type Knowledge struct {
+ ID string `json:"id"`
+ TenantID uint `json:"tenant_id"`
+ KnowledgeBaseID string `json:"knowledge_base_id"`
+ Type string `json:"type"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ Source string `json:"source"`
+ ParseStatus string `json:"parse_status"`
+ EnableStatus string `json:"enable_status"`
+ EmbeddingModelID string `json:"embedding_model_id"`
+ FileName string `json:"file_name"`
+ FileType string `json:"file_type"`
+ FileSize int64 `json:"file_size"`
+ FilePath string `json:"file_path"`
+ Metadata map[string]string `json:"metadata"` // Extensible metadata for storing machine information, paths, etc.
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ ProcessedAt *time.Time `json:"processed_at"`
+ ErrorMessage string `json:"error_message"`
+}
+
+// KnowledgeResponse represents the API response containing a single knowledge entry
+type KnowledgeResponse struct {
+ Success bool `json:"success"`
+ Data Knowledge `json:"data"`
+ Code string `json:"code"`
+ Message string `json:"message"`
+}
+
+// KnowledgeListResponse represents the API response containing a list of knowledge entries with pagination
+type KnowledgeListResponse struct {
+ Success bool `json:"success"`
+ Data []Knowledge `json:"data"`
+ Total int64 `json:"total"`
+ Page int `json:"page"`
+ PageSize int `json:"page_size"`
+}
+
+// KnowledgeBatchResponse represents the API response for batch knowledge retrieval
+type KnowledgeBatchResponse struct {
+ Success bool `json:"success"`
+ Data []Knowledge `json:"data"`
+}
+
+// UpdateImageInfoRequest represents the request structure for updating a chunk
+// Used for requesting chunk information updates
+type UpdateImageInfoRequest struct {
+ ImageInfo string `json:"image_info"` // Image information in JSON format
+}
+
+// ErrDuplicateFile is returned when attempting to create a knowledge entry with a file that already exists
+var ErrDuplicateFile = errors.New("file already exists")
+
+// ErrDuplicateURL is returned when attempting to create a knowledge entry with a URL that already exists
+var ErrDuplicateURL = errors.New("URL already exists")
+
+// CreateKnowledgeFromFile creates a knowledge entry from a local file path
+func (c *Client) CreateKnowledgeFromFile(ctx context.Context,
+ knowledgeBaseID string, filePath string, metadata map[string]string, enableMultimodel *bool,
+) (*Knowledge, error) {
+ // Open the local file
+ file, err := os.Open(filePath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open file: %w", err)
+ }
+ defer file.Close()
+
+ // Get file information
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get file information: %w", err)
+ }
+
+ // Create the HTTP request
+ path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge/file", knowledgeBaseID)
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ // Create a multipart form writer
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+ part, err := writer.CreateFormFile("file", fileInfo.Name())
+ if err != nil {
+ return nil, fmt.Errorf("failed to create form file: %w", err)
+ }
+
+ // Copy file contents
+ _, err = io.Copy(part, file)
+ if err != nil {
+ return nil, fmt.Errorf("failed to copy file content: %w", err)
+ }
+
+ // Add enable_multimodel field
+ if enableMultimodel != nil {
+ if err := writer.WriteField("enable_multimodel", strconv.FormatBool(*enableMultimodel)); err != nil {
+ return nil, fmt.Errorf("failed to write enable_multimodel field: %w", err)
+ }
+ }
+
+ // Add metadata to the request if provided
+ if metadata != nil {
+ metadataBytes, err := json.Marshal(metadata)
+ if err != nil {
+ return nil, fmt.Errorf("failed to serialize metadata: %w", err)
+ }
+ if err := writer.WriteField("metadata", string(metadataBytes)); err != nil {
+ return nil, fmt.Errorf("failed to write metadata field: %w", err)
+ }
+ }
+
+ // Close the multipart writer
+ err = writer.Close()
+ if err != nil {
+ return nil, fmt.Errorf("failed to close writer: %w", err)
+ }
+
+ // Set request headers
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ if c.token != "" {
+ req.Header.Set("X-API-Key", c.token)
+ }
+ if requestID := ctx.Value("RequestID"); requestID != nil {
+ req.Header.Set("X-Request-ID", requestID.(string))
+ }
+
+ // Set the request body
+ req.Body = io.NopCloser(body)
+
+ // Send the request
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send request: %w", err)
+ }
+ defer resp.Body.Close()
+
+ // Parse the response
+ var response KnowledgeResponse
+ if resp.StatusCode == http.StatusConflict {
+ if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
+ return nil, fmt.Errorf("failed to parse response: %w", err)
+ }
+ return &response.Data, ErrDuplicateFile
+ } else if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+ return &response.Data, nil
+}
+
+// CreateKnowledgeFromURL creates a knowledge entry from a web URL
+func (c *Client) CreateKnowledgeFromURL(ctx context.Context, knowledgeBaseID string, url string, enableMultimodel *bool) (*Knowledge, error) {
+ path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge/url", knowledgeBaseID)
+
+ reqBody := struct {
+ URL string `json:"url"`
+ EnableMultimodel *bool `json:"enable_multimodel"`
+ }{
+ URL: url,
+ EnableMultimodel: enableMultimodel,
+ }
+
+ resp, err := c.doRequest(ctx, http.MethodPost, path, reqBody, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response KnowledgeResponse
+ if resp.StatusCode == http.StatusConflict {
+ if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
+ return nil, fmt.Errorf("failed to parse response: %w", err)
+ }
+ return &response.Data, ErrDuplicateURL
+ } else if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// GetKnowledge retrieves a knowledge entry by its ID
+func (c *Client) GetKnowledge(ctx context.Context, knowledgeID string) (*Knowledge, error) {
+ path := fmt.Sprintf("/api/v1/knowledge/%s", knowledgeID)
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response KnowledgeResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// GetKnowledgeBatch retrieves multiple knowledge entries by their IDs
+func (c *Client) GetKnowledgeBatch(ctx context.Context, knowledgeIDs []string) ([]Knowledge, error) {
+ path := "/api/v1/knowledge/batch"
+
+ queryParams := url.Values{}
+ for _, id := range knowledgeIDs {
+ queryParams.Add("ids", id)
+ }
+
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams)
+ if err != nil {
+ return nil, err
+ }
+
+ var response KnowledgeBatchResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return response.Data, nil
+}
+
+// ListKnowledge lists knowledge entries in a knowledge base with pagination
+func (c *Client) ListKnowledge(ctx context.Context,
+ knowledgeBaseID string,
+ page int,
+ pageSize int,
+) ([]Knowledge, int64, error) {
+ path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge", knowledgeBaseID)
+
+ queryParams := url.Values{}
+ queryParams.Add("page", strconv.Itoa(page))
+ queryParams.Add("page_size", strconv.Itoa(pageSize))
+
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ var response KnowledgeListResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, 0, err
+ }
+
+ return response.Data, response.Total, nil
+}
+
+// DeleteKnowledge deletes a knowledge entry by its ID
+func (c *Client) DeleteKnowledge(ctx context.Context, knowledgeID string) error {
+ path := fmt.Sprintf("/api/v1/knowledge/%s", knowledgeID)
+ resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
+
+// DownloadKnowledgeFile downloads a knowledge file to the specified local path
+func (c *Client) DownloadKnowledgeFile(ctx context.Context, knowledgeID string, destPath string) error {
+ path := fmt.Sprintf("/api/v1/knowledge/%s/download", knowledgeID)
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ // Check for HTTP errors
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+ body, _ := io.ReadAll(resp.Body)
+ return fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ // Create destination file
+ out, err := os.Create(destPath)
+ if err != nil {
+ return fmt.Errorf("failed to create file: %w", err)
+ }
+ defer out.Close()
+
+ // Copy response body to file
+ _, err = io.Copy(out, resp.Body)
+ if err != nil {
+ return fmt.Errorf("failed to write file: %w", err)
+ }
+
+ return nil
+}
+
+func (c *Client) UpdateKnowledge(ctx context.Context, knowledge *Knowledge) error {
+ path := fmt.Sprintf("/api/v1/knowledge/%s", knowledge.ID)
+
+ resp, err := c.doRequest(ctx, http.MethodPut, path, knowledge, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
+
+// UpdateChunk updates a chunk's information
+// Updates information for a specific chunk under a knowledge document
+// Parameters:
+// - ctx: Context
+// - knowledgeID: Knowledge ID
+// - chunkID: Chunk ID
+// - request: Update request
+//
+// Returns:
+// - *Chunk: Updated chunk
+// - error: Error information
+func (c *Client) UpdateImageInfo(ctx context.Context,
+ knowledgeID string, chunkID string, request *UpdateImageInfoRequest,
+) error {
+ path := fmt.Sprintf("/api/v1/knowledge/image/%s/%s", knowledgeID, chunkID)
+ resp, err := c.doRequest(ctx, http.MethodPut, path, request, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
diff --git a/client/knowledgebase.go b/client/knowledgebase.go
new file mode 100644
index 0000000..63aa921
--- /dev/null
+++ b/client/knowledgebase.go
@@ -0,0 +1,211 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// The KnowledgeBase related interfaces are used to manage knowledge bases
+// Knowledge bases are collections of knowledge entries that can be used for question-answering
+// They can also be searched and queried using hybrid search
+package client
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+ "time"
+)
+
+// KnowledgeBase represents a knowledge base
+type KnowledgeBase struct {
+ ID string `json:"id"`
+ Name string `json:"name"` // Name must be unique within the same tenant
+ Description string `json:"description"`
+ TenantID uint `json:"tenant_id"` // Changed to uint type
+ ChunkingConfig ChunkingConfig `json:"chunking_config"`
+ ImageProcessingConfig ImageProcessingConfig `json:"image_processing_config"`
+ EmbeddingModelID string `json:"embedding_model_id"`
+ SummaryModelID string `json:"summary_model_id"` // Summary model ID
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+// KnowledgeBaseConfig represents knowledge base configuration
+type KnowledgeBaseConfig struct {
+ ChunkingConfig ChunkingConfig `json:"chunking_config"`
+ ImageProcessingConfig ImageProcessingConfig `json:"image_processing_config"`
+}
+
+// ChunkingConfig represents document chunking configuration
+type ChunkingConfig struct {
+ ChunkSize int `json:"chunk_size"` // Chunk size
+ ChunkOverlap int `json:"chunk_overlap"` // Overlap size
+ Separators []string `json:"separators"` // Separators
+ EnableMultimodal bool `json:"enable_multimodal"` // Whether to enable multimodal processing
+}
+
+// ImageProcessingConfig represents image processing configuration
+type ImageProcessingConfig struct {
+ ModelID string `json:"model_id"` // Multimodal model ID
+}
+
+// KnowledgeBaseResponse knowledge base response
+type KnowledgeBaseResponse struct {
+ Success bool `json:"success"`
+ Data KnowledgeBase `json:"data"`
+}
+
+// KnowledgeBaseListResponse knowledge base list response
+type KnowledgeBaseListResponse struct {
+ Success bool `json:"success"`
+ Data []KnowledgeBase `json:"data"`
+}
+
+// SearchResult represents search result
+type SearchResult struct {
+ ID string `json:"id"`
+ Content string `json:"content"`
+ KnowledgeID string `json:"knowledge_id"`
+ ChunkIndex int `json:"chunk_index"`
+ KnowledgeTitle string `json:"knowledge_title"`
+ StartAt int `json:"start_at"`
+ EndAt int `json:"end_at"`
+ Seq int `json:"seq"`
+ Score float64 `json:"score"`
+ ChunkType string `json:"chunk_type"`
+ ImageInfo string `json:"image_info"`
+ Metadata map[string]string `json:"metadata"`
+ KnowledgeFilename string `json:"knowledge_filename"`
+ KnowledgeSource string `json:"knowledge_source"`
+}
+
+// HybridSearchResponse hybrid search response
+type HybridSearchResponse struct {
+ Success bool `json:"success"`
+ Data []*SearchResult `json:"data"`
+}
+
+type CopyKnowledgeBaseRequest struct {
+ SourceID string `json:"source_id"`
+ TargetID string `json:"target_id"`
+}
+
+// CreateKnowledgeBase creates a knowledge base
+func (c *Client) CreateKnowledgeBase(ctx context.Context, knowledgeBase *KnowledgeBase) (*KnowledgeBase, error) {
+ resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/knowledge-bases", knowledgeBase, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response KnowledgeBaseResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// GetKnowledgeBase gets a knowledge base
+func (c *Client) GetKnowledgeBase(ctx context.Context, knowledgeBaseID string) (*KnowledgeBase, error) {
+ path := fmt.Sprintf("/api/v1/knowledge-bases/%s", knowledgeBaseID)
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response KnowledgeBaseResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// ListKnowledgeBases lists knowledge bases
+func (c *Client) ListKnowledgeBases(ctx context.Context) ([]KnowledgeBase, error) {
+ resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/knowledge-bases", nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response KnowledgeBaseListResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return response.Data, nil
+}
+
+// UpdateKnowledgeBaseRequest update knowledge base request
+type UpdateKnowledgeBaseRequest struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Config *KnowledgeBaseConfig `json:"config"`
+}
+
+// UpdateKnowledgeBase updates a knowledge base
+func (c *Client) UpdateKnowledgeBase(ctx context.Context,
+ knowledgeBaseID string,
+ request *UpdateKnowledgeBaseRequest,
+) (*KnowledgeBase, error) {
+ path := fmt.Sprintf("/api/v1/knowledge-bases/%s", knowledgeBaseID)
+ resp, err := c.doRequest(ctx, http.MethodPut, path, request, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response KnowledgeBaseResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// DeleteKnowledgeBase deletes a knowledge base
+func (c *Client) DeleteKnowledgeBase(ctx context.Context, knowledgeBaseID string) error {
+ path := fmt.Sprintf("/api/v1/knowledge-bases/%s", knowledgeBaseID)
+ resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
+
+// HybridSearch performs hybrid search
+func (c *Client) HybridSearch(ctx context.Context, knowledgeBaseID string, query string) ([]*SearchResult, error) {
+ path := fmt.Sprintf("/api/v1/knowledge-bases/%s/hybrid-search", knowledgeBaseID)
+
+ queryParams := url.Values{}
+ queryParams.Add("query", query)
+
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams)
+ if err != nil {
+ return nil, err
+ }
+
+ var response HybridSearchResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return response.Data, nil
+}
+
+func (c *Client) CopyKnowledgeBase(ctx context.Context, request *CopyKnowledgeBaseRequest) error {
+ path := "/api/v1/knowledge-bases/copy"
+
+ resp, err := c.doRequest(ctx, http.MethodPost, path, request, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
diff --git a/client/message.go b/client/message.go
new file mode 100644
index 0000000..28ae55a
--- /dev/null
+++ b/client/message.go
@@ -0,0 +1,82 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// The Message related interfaces are used to manage messages in a session
+// Messages can be created, retrieved, deleted, and queried
+package client
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+ "time"
+)
+
+// Message message information
+type Message struct {
+ ID string `json:"id"`
+ SessionID string `json:"session_id"`
+ RequestID string `json:"request_id"`
+ Content string `json:"content"`
+ Role string `json:"role"`
+ KnowledgeReferences []*SearchResult `json:"knowledge_references" `
+ IsCompleted bool `json:"is_completed"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+// MessageListResponse message list response
+type MessageListResponse struct {
+ Success bool `json:"success"`
+ Data []Message `json:"data"`
+}
+
+// LoadMessages loads session messages, supports pagination and time filtering
+func (c *Client) LoadMessages(ctx context.Context, sessionID string, limit int, beforeTime *time.Time) ([]Message, error) {
+ path := fmt.Sprintf("/api/v1/messages/%s/load", sessionID)
+
+ queryParams := url.Values{}
+ queryParams.Add("limit", strconv.Itoa(limit))
+
+ if beforeTime != nil {
+ queryParams.Add("before_time", beforeTime.Format(time.RFC3339Nano))
+ }
+
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams)
+ if err != nil {
+ return nil, err
+ }
+
+ var response MessageListResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return response.Data, nil
+}
+
+// GetRecentMessages gets recent messages from a session
+func (c *Client) GetRecentMessages(ctx context.Context, sessionID string, limit int) ([]Message, error) {
+ return c.LoadMessages(ctx, sessionID, limit, nil)
+}
+
+// GetMessagesBefore gets messages before a specified time
+func (c *Client) GetMessagesBefore(ctx context.Context, sessionID string, beforeTime time.Time, limit int) ([]Message, error) {
+ return c.LoadMessages(ctx, sessionID, limit, &beforeTime)
+}
+
+// DeleteMessage deletes a message
+func (c *Client) DeleteMessage(ctx context.Context, sessionID string, messageID string) error {
+ path := fmt.Sprintf("/api/v1/messages/%s/%s", sessionID, messageID)
+ resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
diff --git a/client/model.go b/client/model.go
new file mode 100644
index 0000000..ecde9f6
--- /dev/null
+++ b/client/model.go
@@ -0,0 +1,155 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// The Model related interfaces are used to manage models for different tasks
+// Models can be created, retrieved, updated, deleted, and queried
+package client
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+)
+
+// ModelType model type
+type ModelType string
+
+// ModelSource model source
+type ModelSource string
+
+// ModelParameters model parameters
+type ModelParameters map[string]interface{}
+
+// Model model information
+type Model struct {
+ ID string `json:"id"`
+ TenantID uint `json:"tenant_id"`
+ Name string `json:"name"`
+ Type ModelType `json:"type"`
+ Source ModelSource `json:"source"`
+ Description string `json:"description"`
+ Parameters ModelParameters `json:"parameters"`
+ IsDefault bool `json:"is_default"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+}
+
+// CreateModelRequest model creation request
+type CreateModelRequest struct {
+ Name string `json:"name"`
+ Type ModelType `json:"type"`
+ Source ModelSource `json:"source"`
+ Description string `json:"description"`
+ Parameters ModelParameters `json:"parameters"`
+ IsDefault bool `json:"is_default"`
+}
+
+// UpdateModelRequest model update request
+type UpdateModelRequest struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Parameters ModelParameters `json:"parameters"`
+ IsDefault bool `json:"is_default"`
+}
+
+// ModelResponse model response
+type ModelResponse struct {
+ Success bool `json:"success"`
+ Data Model `json:"data"`
+}
+
+// ModelListResponse model list response
+type ModelListResponse struct {
+ Success bool `json:"success"`
+ Data []Model `json:"data"`
+}
+
+// Model type constants
+const (
+ ModelTypeEmbedding ModelType = "embedding"
+ ModelTypeChat ModelType = "chat"
+ ModelTypeRerank ModelType = "rerank"
+ ModelTypeSummary ModelType = "summary"
+)
+
+// Model source constants
+const (
+ ModelSourceInternal ModelSource = "internal"
+ ModelSourceExternal ModelSource = "external"
+)
+
+// CreateModel creates a model
+func (c *Client) CreateModel(ctx context.Context, request *CreateModelRequest) (*Model, error) {
+ resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/models", request, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response ModelResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// GetModel gets a model
+func (c *Client) GetModel(ctx context.Context, modelID string) (*Model, error) {
+ path := fmt.Sprintf("/api/v1/models/%s", modelID)
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response ModelResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// ListModels lists all models
+func (c *Client) ListModels(ctx context.Context) ([]Model, error) {
+ resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/models", nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response ModelListResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return response.Data, nil
+}
+
+// UpdateModel updates a model
+func (c *Client) UpdateModel(ctx context.Context, modelID string, request *UpdateModelRequest) (*Model, error) {
+ path := fmt.Sprintf("/api/v1/models/%s", modelID)
+ resp, err := c.doRequest(ctx, http.MethodPut, path, request, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response ModelResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// DeleteModel deletes a model
+func (c *Client) DeleteModel(ctx context.Context, modelID string) error {
+ path := fmt.Sprintf("/api/v1/models/%s", modelID)
+ resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
diff --git a/client/session.go b/client/session.go
new file mode 100644
index 0000000..86b380d
--- /dev/null
+++ b/client/session.go
@@ -0,0 +1,403 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// The Session related interfaces are used to manage sessions for question-answering
+// Sessions can be created, retrieved, updated, deleted, and queried
+// They can also be used to generate titles for sessions
+package client
+
+import (
+ "bufio"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+// SessionStrategy defines session strategy
+type SessionStrategy struct {
+ MaxRounds int `json:"max_rounds"` // Maximum number of rounds to maintain
+ EnableRewrite bool `json:"enable_rewrite"` // Enable query rewrite
+ FallbackStrategy string `json:"fallback_strategy"` // Fallback strategy
+ FallbackResponse string `json:"fallback_response"` // Fixed fallback response content
+ EmbeddingTopK int `json:"embedding_top_k"` // Top K for vector retrieval
+ KeywordThreshold float64 `json:"keyword_threshold"` // Keyword retrieval threshold
+ VectorThreshold float64 `json:"vector_threshold"` // Vector retrieval threshold
+ RerankModelID string `json:"rerank_model_id"` // Rerank model ID
+ RerankTopK int `json:"rerank_top_k"` // Top K for reranking
+ RerankThreshold float64 `json:"reranking_threshold"` // Reranking threshold
+ SummaryModelID string `json:"summary_model_id"` // Summary model ID
+ SummaryParameters *SummaryConfig `json:"summary_parameters"` // Summary model parameters
+ NoMatchPrefix string `json:"no_match_prefix"` // Fallback response prefix
+}
+
+// SummaryConfig defines summary configuration
+type SummaryConfig struct {
+ MaxTokens int `json:"max_tokens"`
+ TopP float64 `json:"top_p"`
+ TopK int `json:"top_k"`
+ FrequencyPenalty float64 `json:"frequency_penalty"`
+ PresencePenalty float64 `json:"presence_penalty"`
+ RepeatPenalty float64 `json:"repeat_penalty"`
+ Prompt string `json:"prompt"`
+ ContextTemplate string `json:"context_template"`
+ NoMatchPrefix string `json:"no_match_prefix"`
+ Temperature float64 `json:"temperature"`
+ Seed int `json:"seed"`
+ MaxCompletionTokens int `json:"max_completion_tokens"`
+}
+
+// CreateSessionRequest session creation request
+type CreateSessionRequest struct {
+ KnowledgeBaseID string `json:"knowledge_base_id"` // Associated knowledge base ID
+ SessionStrategy *SessionStrategy `json:"session_strategy"` // Session strategy
+}
+
+// Session session information
+type Session struct {
+ ID string `json:"id"`
+ TenantID uint `json:"tenant_id"`
+ KnowledgeBaseID string `json:"knowledge_base_id"`
+ Title string `json:"title"`
+ MaxRounds int `json:"max_rounds"`
+ EnableRewrite bool `json:"enable_rewrite"`
+ FallbackStrategy string `json:"fallback_strategy"`
+ FallbackResponse string `json:"fallback_response"`
+ EmbeddingTopK int `json:"embedding_top_k"`
+ KeywordThreshold float64 `json:"keyword_threshold"`
+ VectorThreshold float64 `json:"vector_threshold"`
+ RerankModelID string `json:"rerank_model_id"`
+ RerankTopK int `json:"rerank_top_k"`
+ RerankThreshold float64 `json:"reranking_threshold"` // Reranking threshold
+ SummaryModelID string `json:"summary_model_id"`
+ SummaryParameters *SummaryConfig `json:"summary_parameters"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+}
+
+// SessionResponse session response
+type SessionResponse struct {
+ Success bool `json:"success"`
+ Data Session `json:"data"`
+}
+
+// SessionListResponse session list response
+type SessionListResponse struct {
+ Success bool `json:"success"`
+ Data []Session `json:"data"`
+ Total int `json:"total"`
+ Page int `json:"page"`
+ PageSize int `json:"page_size"`
+}
+
+// CreateSession creates a session
+func (c *Client) CreateSession(ctx context.Context, request *CreateSessionRequest) (*Session, error) {
+ resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/sessions", request, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response SessionResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// GetSession gets a session
+func (c *Client) GetSession(ctx context.Context, sessionID string) (*Session, error) {
+ path := fmt.Sprintf("/api/v1/sessions/%s", sessionID)
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response SessionResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// GetSessionsByTenant gets all sessions for a tenant
+func (c *Client) GetSessionsByTenant(ctx context.Context, page int, pageSize int) ([]Session, int, error) {
+ queryParams := url.Values{}
+ queryParams.Add("page", strconv.Itoa(page))
+ queryParams.Add("page_size", strconv.Itoa(pageSize))
+ resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/sessions", nil, queryParams)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ var response SessionListResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, 0, err
+ }
+
+ return response.Data, response.Total, nil
+}
+
+// UpdateSession updates a session
+func (c *Client) UpdateSession(ctx context.Context, sessionID string, request *CreateSessionRequest) (*Session, error) {
+ path := fmt.Sprintf("/api/v1/sessions/%s", sessionID)
+ resp, err := c.doRequest(ctx, http.MethodPut, path, request, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response SessionResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// DeleteSession deletes a session
+func (c *Client) DeleteSession(ctx context.Context, sessionID string) error {
+ path := fmt.Sprintf("/api/v1/sessions/%s", sessionID)
+ resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
+
+// GenerateTitleRequest title generation request
+type GenerateTitleRequest struct {
+ Messages []Message `json:"messages"`
+}
+
+// GenerateTitleResponse title generation response
+type GenerateTitleResponse struct {
+ Success bool `json:"success"`
+ Data string `json:"data"`
+}
+
+// GenerateTitle generates a session title
+func (c *Client) GenerateTitle(ctx context.Context, sessionID string, request *GenerateTitleRequest) (string, error) {
+ path := fmt.Sprintf("/api/v1/sessions/%s/generate_title", sessionID)
+ resp, err := c.doRequest(ctx, http.MethodPost, path, request, nil)
+ if err != nil {
+ return "", err
+ }
+
+ var response GenerateTitleResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return "", err
+ }
+
+ return response.Data, nil
+}
+
+// KnowledgeQARequest knowledge Q&A request
+type KnowledgeQARequest struct {
+ Query string `json:"query"`
+}
+
+type ResponseType string
+
+const (
+ ResponseTypeAnswer ResponseType = "answer"
+ ResponseTypeReferences ResponseType = "references"
+)
+
+// StreamResponse streaming response
+type StreamResponse struct {
+ ID string `json:"id"` // Unique identifier
+ ResponseType ResponseType `json:"response_type"` // Response type
+ Content string `json:"content"` // Current content fragment
+ Done bool `json:"done"` // Whether completed
+ KnowledgeReferences []*SearchResult `json:"knowledge_references"` // Knowledge references
+}
+
+// KnowledgeQAStream knowledge Q&A streaming API
+func (c *Client) KnowledgeQAStream(ctx context.Context, sessionID string, query string, callback func(*StreamResponse) error) error {
+ path := fmt.Sprintf("/api/v1/knowledge-chat/%s", sessionID)
+ fmt.Printf("Starting KnowledgeQAStream request, session ID: %s, query: %s\n", sessionID, query)
+
+ request := &KnowledgeQARequest{
+ Query: query,
+ }
+
+ resp, err := c.doRequest(ctx, http.MethodPost, path, request, nil)
+ if err != nil {
+ fmt.Printf("Request failed: %v\n", err)
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+ body, _ := io.ReadAll(resp.Body)
+ err := fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ fmt.Printf("Request returned error status: %v\n", err)
+ return err
+ }
+
+ fmt.Println("Successfully established SSE connection, processing data stream")
+
+ // Use bufio to read SSE data line by line
+ scanner := bufio.NewScanner(resp.Body)
+ var dataBuffer string
+ var eventType string
+ messageCount := 0
+
+ for scanner.Scan() {
+ line := scanner.Text()
+ fmt.Printf("Received SSE line: %s\n", line)
+
+ // Empty line indicates the end of an event
+ if line == "" {
+ if dataBuffer != "" {
+ fmt.Printf("Processing data: %s, event type: %s\n", dataBuffer, eventType)
+ var streamResponse StreamResponse
+ if err := json.Unmarshal([]byte(dataBuffer), &streamResponse); err != nil {
+ fmt.Printf("Failed to parse SSE data: %v\n", err)
+ return fmt.Errorf("failed to parse SSE data: %w", err)
+ }
+
+ messageCount++
+ fmt.Printf("Parsed message #%d, done status: %v\n", messageCount, streamResponse.Done)
+
+ if err := callback(&streamResponse); err != nil {
+ fmt.Printf("Callback processing failed: %v\n", err)
+ return err
+ }
+ dataBuffer = ""
+ eventType = ""
+ }
+ continue
+ }
+
+ // Process lines with event: prefix
+ if strings.HasPrefix(line, "event:") {
+ eventType = line[6:] // Remove "event:" prefix
+ fmt.Printf("Set event type: %s\n", eventType)
+ }
+
+ // Process lines with data: prefix
+ if strings.HasPrefix(line, "data:") {
+ dataBuffer = line[5:] // Remove "data:" prefix
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ fmt.Printf("Failed to read SSE stream: %v\n", err)
+ return fmt.Errorf("failed to read SSE stream: %w", err)
+ }
+
+ fmt.Printf("KnowledgeQAStream completed, processed %d messages\n", messageCount)
+ return nil
+}
+
+// ContinueStream continues to receive an active stream for a session
+func (c *Client) ContinueStream(ctx context.Context, sessionID string, messageID string, callback func(*StreamResponse) error) error {
+ path := fmt.Sprintf("/api/v1/sessions/continue-stream/%s", sessionID)
+
+ queryParams := url.Values{}
+ queryParams.Add("message_id", messageID)
+
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+ body, _ := io.ReadAll(resp.Body)
+ return fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ // Use bufio to read SSE data line by line
+ scanner := bufio.NewScanner(resp.Body)
+ var dataBuffer string
+ var eventType string
+
+ for scanner.Scan() {
+ line := scanner.Text()
+
+ // Empty line indicates the end of an event
+ if line == "" {
+ if dataBuffer != "" && eventType == "message" {
+ var streamResponse StreamResponse
+ if err := json.Unmarshal([]byte(dataBuffer), &streamResponse); err != nil {
+ return fmt.Errorf("failed to parse SSE data: %w", err)
+ }
+
+ if err := callback(&streamResponse); err != nil {
+ return err
+ }
+ dataBuffer = ""
+ eventType = ""
+ }
+ continue
+ }
+
+ // Process lines with event: prefix
+ if strings.HasPrefix(line, "event:") {
+ eventType = line[6:] // Remove "event:" prefix
+ }
+
+ // Process lines with data: prefix
+ if strings.HasPrefix(line, "data:") {
+ dataBuffer = line[5:] // Remove "data:" prefix
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("failed to read SSE stream: %w", err)
+ }
+
+ return nil
+}
+
+// SearchKnowledgeRequest knowledge search request
+type SearchKnowledgeRequest struct {
+ Query string `json:"query"` // Query content
+ KnowledgeBaseID string `json:"knowledge_base_id"` // Knowledge base ID
+}
+
+// SearchKnowledgeResponse search results response
+type SearchKnowledgeResponse struct {
+ Success bool `json:"success"`
+ Data []*SearchResult `json:"data"`
+}
+
+// SearchKnowledge performs knowledge base search without LLM summarization
+func (c *Client) SearchKnowledge(ctx context.Context, request *SearchKnowledgeRequest) ([]*SearchResult, error) {
+ fmt.Printf("Starting SearchKnowledge request, knowledge base ID: %s, query: %s\n",
+ request.KnowledgeBaseID, request.Query)
+
+ resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/knowledge-search", request, nil)
+ if err != nil {
+ fmt.Printf("Request failed: %v\n", err)
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+ body, _ := io.ReadAll(resp.Body)
+ err := fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ fmt.Printf("Request returned error status: %v\n", err)
+ return nil, err
+ }
+
+ var response SearchKnowledgeResponse
+ if err := parseResponse(resp, &response); err != nil {
+ fmt.Printf("Failed to parse response: %v\n", err)
+ return nil, err
+ }
+
+ fmt.Printf("SearchKnowledge completed, found %d results\n", len(response.Data))
+ return response.Data, nil
+}
diff --git a/client/tenant.go b/client/tenant.go
new file mode 100644
index 0000000..e2f8069
--- /dev/null
+++ b/client/tenant.go
@@ -0,0 +1,136 @@
+// Package client provides the implementation for interacting with the WeKnora API
+// The Tenant related interfaces are used to manage tenants in the system
+// Tenants can be created, retrieved, updated, deleted, and queried
+// They can also be used to manage retriever engines for different tasks
+package client
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "time"
+)
+
+// RetrieverEngines defines a collection of retriever engine parameters
+type RetrieverEngines struct {
+ Engines []RetrieverEngineParams `json:"engines"`
+}
+
+// RetrieverEngineParams contains configuration for retriever engines
+type RetrieverEngineParams struct {
+ RetrieverType string `json:"retriever_type"` // Type of retriever (e.g., keywords, vector)
+ RetrieverEngineType string `json:"retriever_engine_type"` // Type of engine implementing the retriever
+}
+
+// Tenant represents tenant information in the system
+type Tenant struct {
+ ID uint `yaml:"id" json:"id" gorm:"primaryKey"`
+ // Tenant name
+ Name string `yaml:"name" json:"name"`
+ // Tenant description
+ Description string `yaml:"description" json:"description"`
+ // API key for authentication
+ APIKey string `yaml:"api_key" json:"api_key"`
+ // Tenant status (active, inactive)
+ Status string `yaml:"status" json:"status" gorm:"default:'active'"`
+ // Configured retrieval engines
+ RetrieverEngines RetrieverEngines `yaml:"retriever_engines" json:"retriever_engines" gorm:"type:json"`
+ // Business/department information
+ Business string `yaml:"business" json:"business"`
+ // Creation timestamp
+ CreatedAt time.Time `yaml:"created_at" json:"created_at"`
+ // Last update timestamp
+ UpdatedAt time.Time `yaml:"updated_at" json:"updated_at"`
+}
+
+// TenantResponse represents the API response structure for tenant operations
+type TenantResponse struct {
+ Success bool `json:"success"` // Whether the operation was successful
+ Data Tenant `json:"data"` // Tenant data
+}
+
+// TenantListResponse represents the API response structure for listing tenants
+type TenantListResponse struct {
+ Success bool `json:"success"` // Whether the operation was successful
+ Data struct {
+ Items []Tenant `json:"items"` // List of tenant items
+ } `json:"data"`
+}
+
+// CreateTenant creates a new tenant
+func (c *Client) CreateTenant(ctx context.Context, tenant *Tenant) (*Tenant, error) {
+ resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/tenants", tenant, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response TenantResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// GetTenant retrieves a tenant by ID
+func (c *Client) GetTenant(ctx context.Context, tenantID uint) (*Tenant, error) {
+ path := fmt.Sprintf("/api/v1/tenants/%d", tenantID)
+ resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response TenantResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// UpdateTenant updates an existing tenant
+func (c *Client) UpdateTenant(ctx context.Context, tenant *Tenant) (*Tenant, error) {
+ path := fmt.Sprintf("/api/v1/tenants/%d", tenant.ID)
+ resp, err := c.doRequest(ctx, http.MethodPut, path, tenant, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response TenantResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return &response.Data, nil
+}
+
+// DeleteTenant removes a tenant by ID
+func (c *Client) DeleteTenant(ctx context.Context, tenantID uint) error {
+ path := fmt.Sprintf("/api/v1/tenants/%d", tenantID)
+ resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ var response struct {
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+ }
+
+ return parseResponse(resp, &response)
+}
+
+// ListTenants retrieves all tenants
+func (c *Client) ListTenants(ctx context.Context) ([]Tenant, error) {
+ resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/tenants", nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response TenantListResponse
+ if err := parseResponse(resp, &response); err != nil {
+ return nil, err
+ }
+
+ return response.Data.Items, nil
+}
diff --git a/cmd/server/main.go b/cmd/server/main.go
new file mode 100644
index 0000000..b253f35
--- /dev/null
+++ b/cmd/server/main.go
@@ -0,0 +1,104 @@
+// Package main is the main package for the WeKnora server
+// It contains the main function and the entry point for the server
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "github.com/gin-gonic/gin"
+
+ "github.com/Tencent/WeKnora/internal/config"
+ "github.com/Tencent/WeKnora/internal/container"
+ "github.com/Tencent/WeKnora/internal/runtime"
+ "github.com/Tencent/WeKnora/internal/tracing"
+ "github.com/Tencent/WeKnora/internal/types/interfaces"
+)
+
+func main() {
+ // Set log format with request ID
+ log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile)
+ log.SetOutput(os.Stdout)
+
+ // Set Gin mode
+ if os.Getenv("GIN_MODE") == "release" {
+ gin.SetMode(gin.ReleaseMode)
+ } else {
+ gin.SetMode(gin.DebugMode)
+ }
+
+ // Build dependency injection container
+ c := container.BuildContainer(runtime.GetContainer())
+
+ // Run application
+ err := c.Invoke(func(
+ cfg *config.Config,
+ router *gin.Engine,
+ tracer *tracing.Tracer,
+ resourceCleaner interfaces.ResourceCleaner,
+ ) error {
+ // Create context for resource cleanup
+ shutdownTimeout := cfg.Server.ShutdownTimeout
+ if shutdownTimeout == 0 {
+ shutdownTimeout = 30 * time.Second
+ }
+ cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), shutdownTimeout)
+ defer cleanupCancel()
+
+ // Register tracer cleanup function to resource cleaner
+ resourceCleaner.RegisterWithName("Tracer", func() error {
+ return tracer.Cleanup(cleanupCtx)
+ })
+
+ // Create HTTP server
+ server := &http.Server{
+ Addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port),
+ Handler: router,
+ }
+
+ ctx, done := context.WithCancel(context.Background())
+ signals := make(chan os.Signal, 1)
+ signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
+ go func() {
+ sig := <-signals
+ log.Printf("Received signal: %v, starting server shutdown...", sig)
+
+ // Create a context with timeout for server shutdown
+ shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer shutdownCancel()
+
+ if err := server.Shutdown(shutdownCtx); err != nil {
+ log.Fatalf("Server forced to shutdown: %v", err)
+ }
+
+ // Clean up all registered resources
+ log.Println("Cleaning up resources...")
+ errs := resourceCleaner.Cleanup(cleanupCtx)
+ if len(errs) > 0 {
+ log.Printf("Errors occurred during resource cleanup: %v", errs)
+ }
+
+ log.Println("Server has exited")
+ done()
+ }()
+
+ // Start server
+ log.Printf("Server is running at %s:%d", cfg.Server.Host, cfg.Server.Port)
+ if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ return fmt.Errorf("failed to start server: %v", err)
+ }
+
+ // Wait for shutdown signal
+ <-ctx.Done()
+ return nil
+ })
+ if err != nil {
+ log.Fatalf("Failed to run application: %v", err)
+ }
+}
diff --git a/config/config.yaml b/config/config.yaml
new file mode 100644
index 0000000..cc8e6e6
--- /dev/null
+++ b/config/config.yaml
@@ -0,0 +1,602 @@
+# 服务器配置
+server:
+ port: 8080
+ host: "0.0.0.0"
+
+# 对话服务配置
+conversation:
+ max_rounds: 5
+ keyword_threshold: 0.3
+ embedding_top_k: 10
+ vector_threshold: 0.5
+ rerank_threshold: 0.5
+ rerank_top_k: 5
+ fallback_strategy: "fixed"
+ fallback_response: "抱歉,我无法回答这个问题。"
+ fallback_prompt: |
+ 你是一个专业、友好的AI助手。现在用户提出的问题超出了你的知识库范围,你需要生成一个礼貌且有帮助的回复。
+
+ ## 回复要求
+ - 诚实承认你无法提供准确答案
+ - 简洁友好,不要过度道歉
+ - 可以提供相关的建议或替代方案
+ - 回复控制在50字以内
+ - 使用礼貌、专业的语气
+
+ ## Few-shot示例
+
+ 用户问题: 今天杭州西湖的游客数量是多少?
+ 回复: 抱歉,我无法获取实时的杭州西湖游客数据。您可以通过杭州旅游官网或相关APP查询这一信息。
+
+ 用户问题: 张教授的新论文发表了吗?
+ 回复: 我没有张教授的最新论文信息。建议您查询学术数据库或直接联系张教授获取最新动态。
+
+ 用户问题: 我的银行卡号是多少?
+ 回复: 作为AI助手,我无法获取您的个人银行信息。请登录您的银行APP或联系银行客服获取相关信息。
+
+ ## 用户当前的问题是:
+ {{.Query}}
+ enable_rewrite: true
+ enable_rerank: true
+ rewrite_prompt_system: |
+ 你是一个专注于指代消解和省略补全的智能助手,你的任务是根据历史对话上下文,清晰识别用户问题中的代词并替换为明确的主语,同时补全省略的关键信息。
+
+ ## 改写目标
+ 请根据历史对话,对当前用户问题进行改写,目标是:
+ - 进行指代消解,将"它"、"这个"、"那个"、"他"、"她"、"它们"、"他们"、"她们"等代词替换为明确的主语
+ - 补全省略的关键信息,确保问题语义完整
+ - 保持问题的原始含义和表达方式不变
+ - 改写后必须也是一个问题
+ - 改写后的问题字数控制在30字以内
+ - 仅输出改写后的问题,不要输出任何解释,更不要尝试回答该问题,后面有其他助手回去解答此问题
+
+ ## Few-shot示例
+
+ 示例1:
+ 历史对话:
+ 用户: 微信支付有哪些功能?
+ 助手: 微信支付的主要功能包括转账、付款码、收款、信用卡还款等多种支付服务。
+
+ 用户问题: 它的安全性
+ 改写后: 微信支付的安全性
+
+ 示例2:
+ 历史对话:
+ 用户: 苹果手机电池不耐用怎么办?
+ 助手: 您可以通过降低屏幕亮度、关闭后台应用和定期更新系统来延长电池寿命。
+
+ 用户问题: 这样会影响使用体验吗?
+ 改写后: 降低屏幕亮度和关闭后台应用是否影响使用体验
+
+ 示例3:
+ 历史对话:
+ 用户: 如何制作红烧肉?
+ 助手: 红烧肉的制作需要先将肉块焯水,然后加入酱油、糖等调料慢炖。
+
+ 用户问题: 需要炖多久?
+ 改写后: 红烧肉需要炖多久
+
+ 示例4:
+ 历史对话:
+ 用户: 北京到上海的高铁票价是多少?
+ 助手: 北京到上海的高铁票价根据车次和座位类型不同,二等座约为553元,一等座约为933元。
+
+ 用户问题: 时间呢?
+ 改写后: 北京到上海的高铁时长
+
+ 示例5:
+ 历史对话:
+ 用户: 如何注册微信账号?
+ 助手: 注册微信账号需要下载微信APP,输入手机号,接收验证码,然后设置昵称和密码。
+
+ 用户问题: 国外手机号可以吗?
+ 改写后: 国外手机号是否可以注册微信账号
+ rewrite_prompt_user: |
+ ## 历史对话背景
+ {{range .Conversation}}
+ ------BEGIN------
+ 用户的问题是:{{.Query}}
+ 助手的回答是:{{.Answer}}
+ ------END------
+ {{end}}
+
+ ## 需要改写的用户问题
+ {{.Query}}
+
+ ## 改写后的问题
+ keywords_extraction_prompt: |
+ # 角色
+ 你是一个专业的关键词提取助手,你的任务是根据用户的问题,提取出最重要的关键词/短语。
+
+ # 要求
+ - 总结用户的问题,并给出最重要的关键词/短语,关键词/短语的数量不超过5个
+ - 使用逗号作为分隔符来分隔关键词/短语
+ - 关键词/短语必须来自于用户的问题,不得虚构
+ - 不要输出任何解释,直接输出关键词/短语,不要有任何前缀、解释或标点符号,不要尝试回答该问题,后面有其他助手会去搜索此问题
+
+ # 输出格式
+ keyword1, keyword2, keyword3, keyword4, keyword5
+
+ # Examples
+
+ ## Example 1
+ USER: 如何提高英语口语水平?
+ ###############
+ Output: 英语口语, 口语水平, 提高英语口语, 英语口语提升, 英语口语练习
+
+ ## Example 2
+ USER: 最近上海有什么好玩的展览活动?
+ ###############
+ Output: 上海展览, 展览活动, 上海展览推荐, 展览活动推荐, 上海展览活动
+
+ ## Example 3
+ USER: 苹果手机电池不耐用怎么解决?
+ ###############
+ Output: 苹果手机, 电池不耐用, 电池优化, 电池寿命, 电池保养
+
+ ## Example 4
+ USER: Python的Logo长啥样?
+ ###############
+ Output: Python Logo
+
+ ## Example 5
+ USER: 如何使用iPhone连接WiFi?
+ ###############
+ Output: iPhone, 连接WiFi, 使用iPhone连接WiFi
+
+ # Real Data
+ USER: {{.Query}}
+
+ keywords_extraction_prompt_user: |
+ Output:
+
+ generate_summary_prompt: |
+ 你是一个精准的文章总结专家。你的任务是提取并总结用户提供的文章或片段的核心内容。
+
+ ## 核心要求
+ - 总结结果长度为50-100个字,根据内容复杂度灵活调整
+ - 完全基于提供的文章内容生成总结,不添加任何未在文章中出现的信息
+ - 确保总结包含文章的关键信息点和主要结论
+ - 即使文章内容较复杂或专业,也必须尝试提取核心要点进行总结
+ - 直接输出总结结果,不包含任何引言、前缀或解释
+
+ ## 格式与风格
+ - 使用客观、中立的第三人称陈述语气
+ - 使用清晰简洁的中文表达
+ - 保持逻辑连贯性,确保句与句之间有合理过渡
+ - 避免重复使用相同的表达方式或句式结构
+
+ ## 注意事项
+ - 绝对不输出"无法生成"、"无法总结"、"内容不足"等拒绝回应的词语
+ - 不要照抄或参考示例中的任何内容,确保总结完全基于用户提供的新文章
+ - 对于任何文本都尽最大努力提取重点并总结,无论长度或复杂度
+
+ ## 以下是用户给出的文章相关信息:
+
+ generate_session_title_prompt: |
+ 你是一个专业的会话标题生成助手,你的任务是为用户提问创建简洁、精准且具描述性的标题。
+
+ ## 格式要求
+ - 标题长度必须在10个字以内
+ - 标题应准确反映用户问题的核心主题
+ - 使用名词短语结构,避免使用问句
+ - 保持简洁明了,删除非必要词语
+ - 不要使用"关于"、"如何"等冗余词语开头
+ - 直接输出标题文本,不要有任何前缀、解释或标点符号
+
+ ## Few-shot示例
+
+ 用户问题: 如何提高英语口语水平?
+ 标题: 英语口语提升
+
+ 用户问题: 最近上海有什么好玩的展览活动?
+ 标题: 上海展览推荐
+
+ 用户问题: 苹果手机电池不耐用怎么解决?
+ 标题: 苹果电池优化
+
+ ## 用户的问题是:
+ summary:
+ repeat_penalty: 1.0
+ temperature: 0.3
+ max_completion_tokens: 2048
+ no_match_prefix: |-
+
+
+ NO_MATCH
+ prompt: |
+ 这是用户和助手之间的对话。当用户提出问题时,助手会基于特定的信息进行解答。助手首先在心中思考推理过程,然后向用户提供答案。
+ 推理过程用 标签包围,答案直接输出在think标签后面,即:
+
+ 这里是推理过程
+
+ 这里是答案
+ context_template: |
+ 你是一个专业的智能信息检索助手,名为小微,犹如专业的高级秘书,依据检索到的信息回答用户问题。
+ 当用户提出问题时,助手只能基于给定的信息进行解答,不能利用任何先验知识。
+
+ ## 回答问题规则
+ - 仅根据检索到的信息中的事实进行回复,不得运用任何先验知识,保持回应的客观性和准确性。
+ - 复杂问题和答案的按Markdown分结构展示,总述部分不需要拆分
+ - 如果是比较简单的答案,不需要把最终答案拆分的过于细碎
+ - 结果中使用的图片地址必须来自于检索到的信息,不得虚构
+ - 检查结果中的文字和图片是否来自于检索到的信息,如果扩展了不在检索到的信息中的内容,必须进行修改,直到得到最终答案
+ - 如果用户问题无法回答,只输出NO_MATCH即可,即:
+
+
+ NO_MATCH
+
+ ## 输出限制
+ - 以Markdown图文格式输出你的最终结果
+ - 输出内容要保证简短且全面,条理清晰,信息明确,不重复。
+
+ ## 当前时间是:
+ {{.CurrentTime}} {{.CurrentWeek}}
+
+ ## 检索到的信息如下:
+ ------BEGIN------
+ {{range .Contexts}}
+ {{.}}
+ {{end}}
+ ------END------
+
+ ## 用户当前的问题是:
+ {{.Query}}
+ extract_entities_prompt: |
+ ## 任务
+ 用户提供的文本中,提取所有符合以下实体类型的实体:
+ EntityTypes: [Person, Organization, Location, Product, Event, Date, Work, Concept, Resource, Category, Operation]
+
+ ## 要求
+ 1. 提取结果必须以JSON数组格式输出
+ 2. 每个实体必须包含 title 和 type 字段,description 字段可选但强烈建议提供
+ 3. 确保 type 字段的值必须严格从 EntityTypes 列表中选择,不得创建新类型
+ 4. 如果无法确定实体类型,不要强行归类,宁可不提取该实体
+ 5. 不要输出任何解释或额外内容,只输出JSON数组
+ 6. 所有字段值不能包含HTML标签或其他代码
+ 7. 如果实体有歧义,需在description中说明具体指代
+ 8. 若没有找到任何实体,返回空数组 []
+
+ ## 实体提取规则
+ - Person: 真实或虚构的人物,包括历史人物、现代人物、文学角色等
+ - Organization: 公司、政府机构、团队、学校等组织实体
+ - Location: 地理位置、地标、国家、城市等
+ - Product: 商品、服务、品牌等商业产品
+ - Event: 事件、会议、节日、历史事件等
+ - Date: 日期、时间段、年代等时间相关信息
+ - Work: 书籍、电影、音乐、艺术作品等创作内容
+ - Concept: 抽象概念、思想、理论等
+ - Resource: 自然资源、信息资源、工具等
+ - Category: 分类、类别、领域等
+ - Operation: 操作、动作、方法、过程等
+
+ ## 提取步骤
+ 1. 仔细阅读文本,识别可能的实体
+ 2. 对每个识别到的实体,确定其最适合的实体类型(必须从EntityTypes中选择)
+ 3. 为每个实体创建包含以下字段的JSON对象:
+ - title: 实体的标准名称,不包含修饰词,如引号等
+ - type: 从EntityTypes中选择的实体类型
+ - description: 对该实体的简明中文描述,应基于文本内容
+ 4. 验证每个实体的所有字段是否正确且格式化恰当
+ 5. 将所有实体对象合并为一个JSON数组
+ 6. 检查最终JSON是否有效并符合要求
+
+ ## 示例
+ [输入]
+ 文本: 《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。该书前80回由曹雪芹所著,后40回一般认为是高鹗所续。小说以贾、史、王、薛四大家族的兴衰为背景,以贾宝玉、林黛玉和薛宝钗的爱情悲剧为主线,刻画了以贾宝玉和金陵十二钗为中心的正邪两赋、贤愚并出的高度复杂的人物群像。成书于乾隆年间(1743年前后),是中国文学史上现实主义的高峰,对后世影响深远。
+
+ [输出]
+ [
+ {
+ "title": "红楼梦",
+ "type": "Work",
+ "description": "红楼梦是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书"
+ },
+ {
+ "title": "石头记",
+ "type": "Work",
+ "description": "石头记是红楼梦的别名"
+ },
+ {
+ "title": "曹雪芹",
+ "type": "Person",
+ "description": "曹雪芹是清代作家,红楼梦的作者,创作了前80回"
+ },
+ {
+ "title": "高鹗",
+ "type": "Person",
+ "description": "高鹗是红楼梦后40回的续作者"
+ },
+ {
+ "title": "贾宝玉",
+ "type": "Person",
+ "description": "贾宝玉是红楼梦中的主要角色,爱情悲剧的主角之一"
+ },
+ {
+ "title": "林黛玉",
+ "type": "Person",
+ "description": "林黛玉是红楼梦中的主要角色,爱情悲剧的主角之一"
+ },
+ {
+ "title": "薛宝钗",
+ "type": "Person",
+ "description": "薛宝钗是红楼梦中的主要角色,爱情悲剧的主角之一"
+ },
+ {
+ "title": "金陵十二钗",
+ "type": "Concept",
+ "description": "金陵十二钗是红楼梦中以贾宝玉为中心的十二位主要女性角色"
+ },
+ {
+ "title": "乾隆年间",
+ "type": "Date",
+ "description": "乾隆年间指的是红楼梦成书的时间,约1743年前后"
+ },
+ {
+ "title": "四大家族",
+ "type": "Concept",
+ "description": "四大家族是红楼梦中的贾、史、王、薛四个家族,是小说的背景"
+ },
+ {
+ "title": "中国文学史",
+ "type": "Category",
+ "description": "红楼梦被视为中国文学史中现实主义的高峰之作"
+ }
+ ]
+
+ extract_relationships_prompt: |
+ ## 任务
+ 从用户提供的实体数组中,提取实体之间存在的明确关系,形成结构化的关系网络。
+
+ ## 要求
+ 1. 关系提取必须基于提供的文本内容,不得臆测不存在的关系
+ 2. 结果必须以JSON数组格式输出,每个关系为数组中的一个对象
+ 3. 每个关系对象必须包含 source, target, description 和 strength 字段
+ 4. 不要输出任何解释或额外内容,只输出JSON数组
+ 5. 若没有找到任何关系,返回空数组 []
+
+ ## 关系提取规则
+ - 只有在文本中明确体现的关系才应被提取
+ - 源实体(source)和目标实体(target)必须是实体数组中已有的实体
+ - 关系描述(description)应简明扼要地说明两个实体间的具体关系
+ - 关系强度(strength)应根据以下标准确定:
+ * 10分:直接创造/从属关系(如作者与作品、发明者与发明、母公司与子公司)
+ * 9分:同一实体的不同表现形式(如别名、曾用名)
+ * 8分:紧密相关且互相影响的关系(如密切合作伙伴、家庭成员)
+ * 7分:明确但非直接的关系(如作品中的角色、组织中的成员)
+ * 6分:间接关联且有明确联系(如同事关系、相似产品)
+ * 5分:存在关联但较为松散(如同一领域的不同概念)
+
+ ## 提取步骤
+ 1. 仔细分析文本内容,确定哪些实体之间存在明确关系
+ 2. 只考虑文本中明确提及的关系,不要臆测
+ 3. 对每个找到的关系,确定:
+ - source: 关系的源实体标题(必须是实体列表中已有的实体)
+ - target: 关系的目标实体标题(必须是实体列表中已有的实体)
+ - description: 简明准确的关系描述(用中文表述)
+ - strength: 基于上述标准的关系强度(5-10之间的整数)
+ 4. 检查每个关系是否双向:
+ - 如果关系是双向的(如"A是B的朋友"意味着"B也是A的朋友"),考虑是否需要创建反向关系
+ - 如果关系是单向的(如"A创作了B"),则只保留单向关系
+ 5. 验证所有关系的一致性和合理性:
+ - 确保没有矛盾的关系(如A同时是B的父亲和兄弟)
+ - 确保关系描述与关系强度匹配
+ 6. 将所有有效关系组织为JSON数组
+
+ ## 示例
+ [输入]
+ 实体: [
+ {
+ "title": "红楼梦",
+ "type": "Work",
+ "description": "红楼梦是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书"
+ },
+ {
+ "title": "石头记",
+ "type": "Work",
+ "description": "石头记是红楼梦的别名"
+ },
+ {
+ "title": "曹雪芹",
+ "type": "Person",
+ "description": "曹雪芹是清代作家,红楼梦的作者,创作了前80回"
+ },
+ {
+ "title": "高鹗",
+ "type": "Person",
+ "description": "高鹗是红楼梦后40回的续作者"
+ },
+ {
+ "title": "贾宝玉",
+ "type": "Person",
+ "description": "贾宝玉是红楼梦中的主要角色,爱情悲剧的主角之一"
+ },
+ {
+ "title": "林黛玉",
+ "type": "Person",
+ "description": "林黛玉是红楼梦中的主要角色,爱情悲剧的主角之一"
+ },
+ {
+ "title": "薛宝钗",
+ "type": "Person",
+ "description": "薛宝钗是红楼梦中的主要角色,爱情悲剧的主角之一"
+ },
+ {
+ "title": "四大家族",
+ "type": "Concept",
+ "description": "四大家族是红楼梦中的贾、史、王、薛四个家族,是小说的背景"
+ },
+ {
+ "title": "金陵十二钗",
+ "type": "Concept",
+ "description": "金陵十二钗是红楼梦中以贾宝玉为中心的十二位主要女性角色"
+ },
+ {
+ "title": "乾隆年间",
+ "type": "Date",
+ "description": "乾隆年间指的是红楼梦成书的时间,约1743年前后"
+ },
+ {
+ "title": "中国文学史",
+ "type": "Category",
+ "description": "红楼梦被视为中国文学史中现实主义的高峰之作"
+ }
+ ]
+
+ 文本: 《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。该书前80回由曹雪芹所著,后40回一般认为是高鹗所续。小说以贾、史、王、薛四大家族的兴衰为背景,以贾宝玉、林黛玉和薛宝钗的爱情悲剧为主线,刻画了以贾宝玉和金陵十二钗为中心的正邪两赋、贤愚并出的高度复杂的人物群像。成书于乾隆年间(1743年前后),是中国文学史上现实主义的高峰,对后世影响深远。
+
+ [输出]
+ [
+ {
+ "source": "曹雪芹",
+ "target": "红楼梦",
+ "description": "曹雪芹是红楼梦的主要作者,创作了前80回",
+ "strength": 10
+ },
+ {
+ "source": "高鹗",
+ "target": "红楼梦",
+ "description": "高鹗是红楼梦后40回的续作者",
+ "strength": 10
+ },
+ {
+ "source": "红楼梦",
+ "target": "石头记",
+ "description": "石头记是红楼梦的别名",
+ "strength": 9
+ },
+ {
+ "source": "红楼梦",
+ "target": "中国文学史",
+ "description": "红楼梦被视为中国文学史中现实主义的高峰之作",
+ "strength": 7
+ },
+ {
+ "source": "贾宝玉",
+ "target": "林黛玉",
+ "description": "贾宝玉与林黛玉有深厚的爱情关系,是小说主线之一",
+ "strength": 8
+ },
+ {
+ "source": "贾宝玉",
+ "target": "薛宝钗",
+ "description": "贾宝玉与薛宝钗的关系是小说爱情悲剧主线的一部分",
+ "strength": 8
+ },
+ {
+ "source": "贾宝玉",
+ "target": "金陵十二钗",
+ "description": "贾宝玉是金陵十二钗故事的中心人物",
+ "strength": 8
+ },
+ {
+ "source": "红楼梦",
+ "target": "贾宝玉",
+ "description": "贾宝玉是红楼梦中的主要角色",
+ "strength": 7
+ },
+ {
+ "source": "红楼梦",
+ "target": "林黛玉",
+ "description": "林黛玉是红楼梦中的主要角色",
+ "strength": 7
+ },
+ {
+ "source": "红楼梦",
+ "target": "薛宝钗",
+ "description": "薛宝钗是红楼梦中的主要角色",
+ "strength": 7
+ },
+ {
+ "source": "红楼梦",
+ "target": "四大家族",
+ "description": "四大家族是红楼梦的背景设定",
+ "strength": 7
+ },
+ {
+ "source": "红楼梦",
+ "target": "金陵十二钗",
+ "description": "金陵十二钗是红楼梦中的重要概念",
+ "strength": 7
+ },
+ {
+ "source": "红楼梦",
+ "target": "乾隆年间",
+ "description": "红楼梦成书于乾隆年间,约1743年前后",
+ "strength": 6
+ }
+ ]
+
+# 知识库配置
+knowledge_base:
+ chunk_size: 512
+ chunk_overlap: 50
+ split_markers: ["\n\n", "\n", "。"]
+ image_processing:
+ enable_multimodal: true
+
+extract:
+ extract_graph:
+ description: |
+ 请基于给定文本,按以下步骤完成信息提取任务,确保逻辑清晰、信息完整准确:
+
+ ## 一、实体提取与属性补充
+ 1. **提取核心实体**:通读文本,按逻辑顺序(如文本叙述顺序、实体关联紧密程度)提取所有与任务相关的核心实体。
+ 2. **补充实体详细属性**:针对每个提取的实体,全面补充其在文本中明确提及的详细属性,确保无关键属性遗漏。
+
+ ## 二、关系提取与验证
+ 1. **明确关系类型**:仅从指定关系列表中选择对应类型,限定关系类型为: %s。
+ 2. **提取有效关系**:基于已提取的实体及属性,识别文本中真实存在的关系,确保关系符合文本事实、无虚假关联。
+ 3. **明确关系主体**:对每一组提取的关系,清晰标注两个关联主体,避免主体混淆。
+ 4. **补充关联属性**:若文本中存在与该关系直接相关的补充信息,需将该信息作为关系的关联属性补充,进一步完善关系信息。
+ tags:
+ - "作者"
+ - "别名"
+ examples:
+ - text: |
+ 《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。该书前80回由曹雪芹所著,后40回一般认为是高鹗所续。
+ 小说以贾、史、王、薛四大家族的兴衰为背景,以贾宝玉、林黛玉和薛宝钗的爱情悲剧为主线,刻画了以贾宝玉和金陵十二钗为中心的正邪两赋、贤愚并出的高度复杂的人物群像。
+ 成书于乾隆年间(1743年前后),是中国文学史上现实主义的高峰,对后世影响深远。
+ node:
+ - name: "红楼梦"
+ attributes:
+ - "中国古典四大名著之一"
+ - "又名《石头记》"
+ - "被誉为中国封建社会的百科全书"
+ - name: "石头记"
+ attributes:
+ - "《红楼梦》的别名"
+ - name: "曹雪芹"
+ attributes:
+ - "清代作家"
+ - "《红楼梦》前 80 回的作者"
+ - name: "高鹗"
+ attributes:
+ - "一般认为是《红楼梦》后 40 回的续写者"
+ relation:
+ - node1: "红楼梦"
+ node2: "曹雪芹"
+ type: "作者"
+ - node1: "红楼梦"
+ node2: "高鹗"
+ type: "作者"
+ - node1: "红楼梦"
+ node2: "石头记"
+ type: "别名"
+ extract_entity:
+ description: |
+ 请基于用户给的问题,按以下步骤处理关键信息提取任务:
+ 1. 梳理逻辑关联:首先完整分析文本内容,明确其核心逻辑关系,并简要标注该核心逻辑类型;
+ 2. 提取关键实体:围绕梳理出的逻辑关系,精准提取文本中的关键信息并归类为明确实体,确保不遗漏核心信息、不添加冗余内容;
+ 3. 排序实体优先级:按实体与文本核心主题的关联紧密程度排序,优先呈现对理解文本主旨最重要的实体;
+ examples:
+ - text: "《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。"
+ node:
+ - name: "红楼梦"
+ - name: "曹雪芹"
+ - name: "中国古典四大名著"
+ fabri_text:
+ with_tag: |
+ 请随机生成一段文本,要求内容与 %s 等相关,字数在 [50-200] 之间,并且尽量包含一些与这些标签相关的专业术语或典型元素,使文本更具针对性和相关性。
+ with_no_tag: |
+ 请随机生成一段文本,内容请自由发挥,字数在 [50-200] 之间。
\ No newline at end of file
diff --git a/dataset/README b/dataset/README
new file mode 100644
index 0000000..6c78bca
--- /dev/null
+++ b/dataset/README
@@ -0,0 +1,273 @@
+# QA Dataset Sampling Tool
+
+A comprehensive tool for sampling QA datasets and generating answers using OpenAI's GPT models. This tool helps you create high-quality question-answering datasets from large-scale collections like MS MARCO.
+
+## Features
+
+- **Smart Sampling**: Intelligently sample queries, documents, and relevance judgments from large datasets
+- **Answer Generation**: Automatically generate high-quality answers using OpenAI's GPT models
+- **Resume Support**: Continue interrupted answer generation from where it left off
+- **Progress Tracking**: Real-time progress updates and statistics
+- **Result Visualization**: Easy-to-read display of generated QA pairs with context
+
+## Installation
+
+### Prerequisites
+
+- Python 3.7+
+- OpenAI API key
+
+### Install Dependencies
+
+```bash
+pip install pandas pyarrow openai
+```
+
+### Set Environment Variables
+
+```bash
+export OPENAI_API_KEY="your-openai-api-key"
+# Optional: Use custom OpenAI endpoint
+export OPENAI_BASE_URL="https://api.openai.com/v1"
+```
+
+### Parpare dataset
+
+We provide pre-processed samples from popular QA datasets:
+
+MarkrAI/msmarco_sample_autorag
+
+## Quick Start
+
+### 1. Sample Data from Large Dataset
+
+First, sample a subset of queries, documents, and relevance judgments from your full dataset:
+
+```bash
+python dataset/qa_dataset.py sample \
+ --queries ~/dataset/mmarco-queries.parquet \
+ --corpus ~/dataset/mmarco-corpus.parquet \
+ --qrels ~/dataset/mmarco-qrels.parquet \
+ --nq 100 \
+ --output_dir ./dataset/samples
+```
+
+### 2. Generate Answers
+
+Use OpenAI's GPT model to generate answers for the sampled questions:
+
+```bash
+python dataset/qa_dataset.py generate \
+ --input_dir ./dataset/samples \
+ --output_dir ./dataset/samples
+```
+
+### 3. View Results
+
+Display the generated QA pairs with their context:
+
+```bash
+python dataset/qa_dataset.py show \
+ --input_dir ./dataset/samples \
+ -n 5
+```
+
+## Detailed Usage
+
+### Sample Command
+
+Create a representative sample from your full dataset.
+
+```bash
+python dataset/qa_dataset.py sample [OPTIONS]
+```
+
+**Required Parameters:**
+- `--queries`: Path to queries parquet file (columns: `id`, `text`)
+- `--corpus`: Path to corpus parquet file (columns: `id`, `text`)
+- `--qrels`: Path to qrels parquet file (columns: `qid`, `pid`)
+
+**Optional Parameters:**
+- `--nq`: Number of queries to sample (default: 1000)
+- `--output_dir`: Output directory for sampled data (default: ./save)
+
+**Example:**
+```bash
+python dataset/qa_dataset.py sample \
+ --queries data/queries.parquet \
+ --corpus data/corpus.parquet \
+ --qrels data/qrels.parquet \
+ --nq 500 \
+ --output_dir ./my_sample
+```
+
+### Generate Command
+
+Generate answers for sampled questions using OpenAI API.
+
+```bash
+python dataset/qa_dataset.py generate [OPTIONS]
+```
+
+**Required Parameters:**
+- `--input_dir`: Directory containing sampled data (queries.parquet, corpus.parquet, qrels.parquet)
+
+**Optional Parameters:**
+- `--output_dir`: Output directory for generated answers (default: ./save)
+
+**Features:**
+- **Resume Support**: Automatically continues from where it left off if interrupted
+- **Error Handling**: Retries failed API calls up to 3 times
+- **Progress Saving**: Saves progress after each successful answer generation
+
+**Example:**
+```bash
+python dataset/qa_dataset.py generate \
+ --input_dir ./my_sample \
+ --output_dir ./my_sample
+```
+
+### Show Command
+
+Display generated QA pairs with full context.
+
+```bash
+python dataset/qa_dataset.py show [OPTIONS]
+```
+
+**Required Parameters:**
+- `--input_dir`: Directory containing QA data (queries.parquet, corpus.parquet, qrels.parquet, qas.parquet, answers.parquet)
+
+**Optional Parameters:**
+- `-n`: Number of results to display (default: 5)
+
+**Example:**
+```bash
+python dataset/qa_dataset.py show \
+ --input_dir ./my_sample \
+ -n 3
+```
+
+## Input Data Format
+
+### Queries File (queries.parquet)
+| Column | Type | Description |
+|--------|------|-------------|
+| id | string | Unique query identifier |
+| text | string | The actual question text |
+
+### Corpus File (corpus.parquet)
+| Column | Type | Description |
+|--------|------|-------------|
+| id | string | Unique passage/document identifier |
+| text | string | The passage/document content |
+
+### Qrels File (qrels.parquet)
+| Column | Type | Description |
+|--------|------|-------------|
+| qid | string | Query ID (matches queries.id) |
+| pid | string | Passage ID (matches corpus.id) |
+
+## Output Files
+
+After running all commands, your output directory will contain:
+
+### Sampled Data
+- `queries.parquet`: Sampled queries subset
+- `corpus.parquet`: Sampled documents subset
+- `qrels.parquet`: Sampled relevance judgments
+
+### Generated Answers
+- `answers.parquet`: Generated answers with unique IDs
+- `qas.parquet`: Question-answer mapping (qid → aid)
+
+## Advanced Usage
+
+### Custom OpenAI Configuration
+
+You can use different OpenAI models or endpoints:
+
+```bash
+# Use GPT-4 Turbo
+export OPENAI_API_KEY="your-key"
+python dataset/qa_dataset.py generate --input_dir ./samples
+
+# Use Azure OpenAI
+export OPENAI_API_KEY="azure-key"
+export OPENAI_BASE_URL="https://your-resource.openai.azure.com/openai/deployments/gpt-4"
+python dataset/qa_dataset.py generate --input_dir ./samples
+```
+
+### Large Dataset Sampling
+
+For very large datasets, consider sampling in batches:
+
+```bash
+# First batch
+python dataset/qa_dataset.py sample --nq 1000 --output_dir ./batch1
+python dataset/qa_dataset.py generate --input_dir ./batch1
+
+# Second batch
+python dataset/qa_dataset.py sample --nq 1000 --output_dir ./batch2
+python dataset/qa_dataset.py generate --input_dir ./batch2
+```
+
+## Troubleshooting
+
+### Common Issues
+
+**1. OpenAI API Errors**
+- Ensure your API key is set correctly: `echo $OPENAI_API_KEY`
+- Check your API quota and billing status
+- Verify network connectivity to OpenAI
+
+**2. Memory Issues with Large Datasets**
+- Reduce `--nq` parameter for smaller samples
+- Ensure sufficient RAM for pandas operations
+- Consider using smaller parquet files
+
+**3. File Not Found Errors**
+- Verify all input file paths are correct
+- Ensure parquet files have correct column names
+- Check file permissions
+
+### Debug Mode
+
+Enable verbose output by adding print statements or using Python debugger:
+
+```bash
+python -m pdb dataset/qa_dataset.py sample --queries ...
+```
+
+## Example Workflow
+
+```bash
+# 1. Setup environment
+export OPENAI_API_KEY="sk-..."
+
+# 2. Sample 200 queries from MS MARCO
+python dataset/qa_dataset.py sample \
+ --queries ~/mmarco/queries.parquet \
+ --corpus ~/mmarco/corpus.parquet \
+ --qrels ~/mmarco/qrels.parquet \
+ --nq 200 \
+ --output_dir ./marco_sample
+
+# 3. Generate answers (may take time depending on API rate limits)
+python dataset/qa_dataset.py generate \
+ --input_dir ./marco_sample \
+ --output_dir ./marco_sample
+
+# 4. Review results
+python dataset/qa_dataset.py show \
+ --input_dir ./marco_sample \
+ -n 10
+```
+
+## Contributing
+
+Feel free to submit issues and enhancement requests!
+
+## License
+
+MIT License - feel free to use this tool for your research and projects.
\ No newline at end of file
diff --git a/dataset/README_zh.md b/dataset/README_zh.md
new file mode 100644
index 0000000..2c4e5b0
--- /dev/null
+++ b/dataset/README_zh.md
@@ -0,0 +1,284 @@
+# QA数据集采样工具
+
+一个全面的QA数据集采样工具,使用OpenAI的GPT模型生成答案。该工具帮助您从大规模数据集(如MS MARCO)创建高质量的问答数据集。
+
+## 功能特性
+
+- **智能采样**:智能地从大型数据集中采样查询、文档和相关性判断
+- **答案生成**:使用OpenAI的GPT模型自动生成高质量答案
+- **断点续传**:支持中断后继续生成,从上次位置开始
+- **进度跟踪**:实时进度更新和统计信息
+- **结果可视化**:易于阅读的问答对展示,包含完整上下文
+
+## 安装指南
+
+### 系统要求
+
+- Python 3.7+
+- OpenAI API密钥
+
+### 安装依赖
+
+```bash
+pip install pandas pyarrow openai
+```
+
+### 设置环境变量
+
+```bash
+export OPENAI_API_KEY="你的openai-api-key"
+# 可选:使用自定义OpenAI端点
+export OPENAI_BASE_URL="https://api.openai.com/v1"
+```
+
+### 准备数据集
+
+您可以使用任何符合格式要求的QA数据集,或下载预处理好的样本:
+
+**使用HuggingFace/ModelScope样本**
+我们提供了来自流行QA数据集的预处理样本:
+- MarkrAI/eli5_sample_autorag
+- MarkrAI/msmarco_sample_autorag
+- MarkrAI/triviaqa_sample_autorag
+- gnekt/hotpotqa_small_sample_autorag
+
+**使用您自己的数据集**
+确保您的数据集包含以下文件:
+- `queries.parquet`(列:id, text)
+- `corpus.parquet`(列:id, text)
+- `qrels.parquet`(列:qid, pid)
+
+## 快速开始
+
+### 1. 从大型数据集采样
+
+首先,从完整数据集中采样查询、文档和相关性判断的子集:
+
+```bash
+python dataset/qa_dataset.py sample \
+ --queries ~/dataset/mmarco-queries.parquet \
+ --corpus ~/dataset/mmarco-corpus.parquet \
+ --qrels ~/dataset/mmarco-qrels.parquet \
+ --nq 100 \
+ --output_dir ./dataset/samples
+```
+
+### 2. 生成答案
+
+使用OpenAI的GPT模型为采样的问答生成答案:
+
+```bash
+python dataset/qa_dataset.py generate \
+ --input_dir ./dataset/samples \
+ --output_dir ./dataset/samples
+```
+
+### 3. 查看结果
+
+展示生成的问答对及其上下文:
+
+```bash
+python dataset/qa_dataset.py show \
+ --input_dir ./dataset/samples \
+ -n 5
+```
+
+## 详细使用说明
+
+### 采样命令
+
+从完整数据集中创建代表性样本。
+
+```bash
+python dataset/qa_dataset.py sample [选项]
+```
+
+**必需参数:**
+- `--queries`:查询parquet文件路径(列:`id`, `text`)
+- `--corpus`:语料库parquet文件路径(列:`id`, `text`)
+- `--qrels`:相关性判断parquet文件路径(列:`qid`, `pid`)
+
+**可选参数:**
+- `--nq`:要采样的查询数量(默认:1000)
+- `--output_dir`:采样数据输出目录(默认:./save)
+
+**示例:**
+```bash
+python dataset/qa_dataset.py sample \
+ --queries data/queries.parquet \
+ --corpus data/corpus.parquet \
+ --qrels data/qrels.parquet \
+ --nq 500 \
+ --output_dir ./my_sample
+```
+
+### 生成命令
+
+使用OpenAI API为采样问题生成答案。
+
+```bash
+python dataset/qa_dataset.py generate [选项]
+```
+
+**必需参数:**
+- `--input_dir`:包含采样数据的目录(queries.parquet, corpus.parquet, qrels.parquet)
+
+**可选参数:**
+- `--output_dir`:生成答案的输出目录(默认:./save)
+
+**特性:**
+- **断点续传**:中断后自动从上次位置继续
+- **错误处理**:API调用失败自动重试3次
+- **进度保存**:每成功生成一个答案就保存进度
+
+**示例:**
+```bash
+python dataset/qa_dataset.py generate \
+ --input_dir ./my_sample \
+ --output_dir ./my_sample
+```
+
+### 展示命令
+
+展示生成的问答对及完整上下文。
+
+```bash
+python dataset/qa_dataset.py show [选项]
+```
+
+**必需参数:**
+- `--input_dir`:包含QA数据的目录(queries.parquet, corpus.parquet, qrels.parquet, qas.parquet, answers.parquet)
+
+**可选参数:**
+- `-n`:要展示的结果数量(默认:5)
+
+**示例:**
+```bash
+python dataset/qa_dataset.py show \
+ --input_dir ./my_sample \
+ -n 3
+```
+
+## 输入数据格式
+
+### 查询文件 (queries.parquet)
+| 列名 | 类型 | 描述 |
+|------|------|------|
+| id | string | 唯一查询标识符 |
+| text | string | 实际的问题文本 |
+
+### 语料库文件 (corpus.parquet)
+| 列名 | 类型 | 描述 |
+|------|------|------|
+| id | string | 唯一段落/文档标识符 |
+| text | string | 段落/文档内容 |
+
+### 相关性判断文件 (qrels.parquet)
+| 列名 | 类型 | 描述 |
+|------|------|------|
+| qid | string | 查询ID(匹配queries.id) |
+| pid | string | 段落ID(匹配corpus.id) |
+
+## 输出文件
+
+运行所有命令后,输出目录将包含:
+
+### 采样数据
+- `queries.parquet`:采样的查询子集
+- `corpus.parquet`:采样的文档子集
+- `qrels.parquet`:采样的相关性判断
+
+### 生成的答案
+- `answers.parquet`:生成的答案(含唯一ID)
+- `qas.parquet`:问答映射(qid → aid)
+
+## 高级用法
+
+### 自定义OpenAI配置
+
+您可以使用不同的OpenAI模型或端点:
+
+```bash
+# 使用GPT-4 Turbo
+export OPENAI_API_KEY="你的密钥"
+python dataset/qa_dataset.py generate --input_dir ./samples
+
+# 使用Azure OpenAI
+export OPENAI_API_KEY="azure密钥"
+export OPENAI_BASE_URL="https://你的资源.openai.azure.com/openai/deployments/gpt-4"
+python dataset/qa_dataset.py generate --input_dir ./samples
+```
+
+### 大型数据集采样
+
+对于非常大的数据集,建议分批采样:
+
+```bash
+# 第一批
+python dataset/qa_dataset.py sample --nq 1000 --output_dir ./batch1
+python dataset/qa_dataset.py generate --input_dir ./batch1
+
+# 第二批
+python dataset/qa_dataset.py sample --nq 1000 --output_dir ./batch2
+python dataset/qa_dataset.py generate --input_dir ./batch2
+```
+
+## 故障排除
+
+### 常见问题
+
+**1. OpenAI API错误**
+- 确保API密钥设置正确:`echo $OPENAI_API_KEY`
+- 检查API配额和账单状态
+- 验证与OpenAI的网络连接
+
+**2. 大数据集内存问题**
+- 减小`--nq`参数以获得更小的样本
+- 确保pandas操作有足够的RAM
+- 考虑使用更小的parquet文件
+
+**3. 文件未找到错误**
+- 验证所有输入文件路径是否正确
+- 确保parquet文件有正确的列名
+- 检查文件权限
+
+### 调试模式
+
+通过添加打印语句或使用Python调试器启用详细输出:
+
+```bash
+python -m pdb dataset/qa_dataset.py sample --queries ...
+```
+
+## 示例工作流
+
+```bash
+# 1. 设置环境
+export OPENAI_API_KEY="sk-..."
+
+# 2. 从MS MARCO采样200个查询
+python dataset/qa_dataset.py sample \
+ --queries ~/mmarco/queries.parquet \
+ --corpus ~/mmarco/corpus.parquet \
+ --qrels ~/mmarco/qrels.parquet \
+ --nq 200 \
+ --output_dir ./marco_sample
+
+# 3. 生成答案(根据API速率限制可能需要一些时间)
+python dataset/qa_dataset.py generate \
+ --input_dir ./marco_sample \
+ --output_dir ./marco_sample
+
+# 4. 查看结果
+python dataset/qa_dataset.py show \
+ --input_dir ./marco_sample \
+ -n 10
+```
+
+## 贡献
+
+欢迎提交问题和功能增强请求!
+
+## 许可证
+
+MIT许可证 - 可自由用于研究和项目。
\ No newline at end of file
diff --git a/dataset/qa_dataset.py b/dataset/qa_dataset.py
new file mode 100644
index 0000000..45f8265
--- /dev/null
+++ b/dataset/qa_dataset.py
@@ -0,0 +1,381 @@
+"""
+QA Dataset Sampling Tool
+
+```
+pip install pandas pyarrow
+pip install openai
+```
+
+# 采样数据
+python dataset/qa_dataset.py sample \
+ --queries ~/dataset/mmarco-queries.parquet \
+ --corpus ~/dataset/mmarco-corpus.parquet \
+ --qrels ~/dataset/mmarco-qrels.parquet \
+ --nq 100 \
+ --output_dir ./dataset/samples
+
+# 生成答案(基于采样结果)
+python dataset/qa_dataset.py generate \
+ --input_dir ./dataset/samples \
+ --output_dir ./dataset/samples
+
+# 展示结果
+python dataset/qa_dataset.py show \
+ --input_dir ./dataset/samples \
+ -n 1
+"""
+
+import os
+from pathlib import Path
+import argparse
+
+import pandas as pd
+import openai
+
+
+def read_parquet(path):
+ return pd.read_parquet(path)
+
+
+def save_to_parquet(df: pd.DataFrame, path: str):
+ """Save DataFrame to parquet file"""
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
+ df.to_parquet(path)
+ print(f"Saved to {path}")
+
+
+def print_stats(df: pd.DataFrame, name: str):
+ """Print statistics of a DataFrame"""
+ print(f"\n{name} Statistics:")
+ print(f"- Total records: {len(df)}")
+ if "id" in df.columns:
+ print(f"- Unique ids: {df['id'].nunique()}")
+ if "qid" in df.columns:
+ print(f"- Unique qids: {df['qid'].nunique()}")
+ if "pid" in df.columns:
+ print(f"- Unique pids: {df['pid'].nunique()}")
+
+
+def sample_data(
+ queries: pd.DataFrame, corpus: pd.DataFrame, qrels: pd.DataFrame, nq=1000
+):
+ """
+ Sample data from the dataset with validation checks.
+
+ Args:
+ queries: DataFrame with qid and text columns (one-to-one)
+ corpus: DataFrame with pid and text columns (one-to-one)
+ qrels: DataFrame with qid and pid columns (many-to-many)
+ nq: Number of queries to sample (default: 1000)
+
+ Returns:
+ Tuple of (sampled_queries, sampled_corpus, sampled_qrels)
+ """
+ # 1. Filter qrels to only include qids that exist in queries
+ valid_qids = set(queries["id"])
+ qrels = qrels[qrels["qid"].isin(valid_qids)]
+
+ # 2. Filter qrels to only include pids that exist in corpus
+ valid_pids = set(corpus["id"])
+ qrels = qrels[qrels["pid"].isin(valid_pids)]
+
+ # 3. Sample queries (ensure we have enough qrels samples for each)
+ # Get qids with most associated pids to ensure diversity
+ qid_counts = qrels["qid"].value_counts()
+ sampled_qids = qid_counts.nlargest(min(nq, len(qid_counts))).index
+
+ # 4. Get all pids associated with sampled qids
+ sampled_qrels = qrels[qrels["qid"].isin(sampled_qids)]
+ sampled_pids = set(sampled_qrels["pid"])
+
+ # 5. Add extra pids from corpus for redundancy (20% of sampled pids)
+ extra_pids = set(corpus["id"].sample(int(0.2 * len(sampled_pids))))
+ all_pids = sampled_pids.union(extra_pids)
+
+ # 6. Create final sampled datasets
+ sampled_queries = queries[queries["id"].isin(sampled_qids)]
+ sampled_corpus = corpus[corpus["id"].isin(all_pids)]
+
+ return sampled_queries, sampled_corpus, sampled_qrels
+
+
+class QAAnsweringSystem:
+ def __init__(
+ self, queries: pd.DataFrame, corpus: pd.DataFrame, qrels: pd.DataFrame
+ ):
+ """
+ Initialize QA system with data
+
+ Args:
+ queries: DataFrame with qid and text columns
+ corpus: DataFrame with pid and text columns
+ qrels: DataFrame with qid and pid mapping
+ """
+ self.queries = queries
+ self.corpus = corpus
+ self.qrels = qrels
+ self.client = openai.Client(
+ api_key=os.getenv("OPENAI_API_KEY"),
+ base_url=os.getenv("OPENAI_BASE_URL"),
+ )
+
+ # Create lookup dictionaries
+ self.qid_to_text = dict(zip(queries["id"], queries["text"]))
+ self.pid_to_text = dict(zip(corpus["id"], corpus["text"]))
+ self.qid_to_pids = qrels.groupby("qid")["pid"].apply(list).to_dict()
+
+ def get_context_for_qid(self, qid: str) -> str:
+ """
+ Get all relevant text for a query ID
+
+ Args:
+ qid: Query ID to search for
+
+ Returns:
+ Combined context text from all related passages
+ """
+ if qid not in self.qid_to_pids:
+ raise ValueError("Question ID not found")
+
+ context_parts = []
+ print(f"Context for Question ID {qid}: {self.qid_to_pids[qid]}")
+ for pid in self.qid_to_pids[qid]:
+ if pid in self.pid_to_text:
+ context_parts.append(self.pid_to_text[pid])
+
+ return "\n\n".join(context_parts)
+
+ def answer_question(self, qid: str, model: str = "gpt-4o-2024-05-13") -> str:
+ """
+ Use OpenAI API to answer question based on qid context
+
+ Args:
+ qid: Query ID to answer
+ model: OpenAI model to use
+
+ Returns:
+ Generated answer from LLM
+ """
+ if qid not in self.qid_to_text:
+ raise ValueError("Question ID not found")
+
+ question = self.qid_to_text[qid]
+ context = self.get_context_for_qid(qid)
+
+ if not context:
+ raise ValueError("No context found for this question")
+
+ prompt = f"""Answer the question based on the context below. Keep the answer concise.
+
+Question: {question}
+
+Context: {context}
+
+Answer:"""
+ response = self.client.chat.completions.create(
+ model=model,
+ messages=[{"role": "user", "content": prompt}],
+ temperature=0.3,
+ )
+ return response.choices[0].message.content
+
+
+def sample_command(args):
+ """Handle sample command"""
+ # Load data
+ print("Loading data...")
+ queries = read_parquet(args.queries)
+ corpus = read_parquet(args.corpus)
+ qrels = read_parquet(args.qrels)
+
+ # Print original stats
+ print("\nOriginal Dataset Statistics:")
+ print_stats(queries, "Queries")
+ print_stats(corpus, "Corpus")
+ print_stats(qrels, "Qrels")
+
+ # Sample data
+ print(f"\nSampling {args.nq} queries...")
+ sampled_queries, sampled_corpus, sampled_qrels = sample_data(
+ queries, corpus, qrels, args.nq
+ )
+
+ # Print sampled stats
+ print("\nSampled Dataset Statistics:")
+ print_stats(sampled_queries, "Sampled Queries")
+ print_stats(sampled_corpus, "Sampled Corpus")
+ print_stats(sampled_qrels, "Sampled Qrels")
+
+ # Save sampled data
+ print("\nSaving sampled data...")
+ save_to_parquet(sampled_queries, f"{args.output_dir}/queries.parquet")
+ save_to_parquet(sampled_corpus, f"{args.output_dir}/corpus.parquet")
+ save_to_parquet(sampled_qrels, f"{args.output_dir}/qrels.parquet")
+ print("\nSampling completed successfully!")
+
+
+def generate_answers(input_dir: str, output_dir: str, max_retries: int = 3):
+ """
+ Generate answers for sampled queries with resume support
+
+ Args:
+ input_dir: Directory containing sampled queries/corpus/qrels
+ output_dir: Directory to save answer files
+ max_retries: Maximum retry attempts for failed queries
+ """
+ print("\nLoading sampled data...")
+ queries = read_parquet(f"{input_dir}/queries.parquet")
+ corpus = read_parquet(f"{input_dir}/corpus.parquet")
+ qrels = read_parquet(f"{input_dir}/qrels.parquet")
+
+ # Try to load existing answers if any
+ answers_path = f"{output_dir}/answers.parquet"
+ qa_pairs_path = f"{output_dir}/qas.parquet"
+
+ try:
+ existing_answers = read_parquet(answers_path)
+ existing_qas = read_parquet(qa_pairs_path)
+ processed_qids = set(existing_qas["qid"])
+ print(f"\nFound {len(processed_qids)} previously processed queries")
+ except (FileNotFoundError, KeyError):
+ print("No existing answers found, use empty state")
+ existing_answers = pd.DataFrame(columns=["id", "text"])
+ existing_qas = pd.DataFrame(columns=["qid", "aid"])
+ processed_qids = set()
+
+ qa_system = QAAnsweringSystem(queries, corpus, qrels)
+
+ answers = existing_answers.to_dict("records")
+ qa_pairs = existing_qas.to_dict("records")
+ answer_id_counter = len(answers) + 1
+
+ for qid in queries["id"]:
+ if qid in processed_qids:
+ continue
+
+ retry_count = 0
+ while retry_count <= max_retries:
+ try:
+ answer_text = qa_system.answer_question(qid)
+ aid = answer_id_counter
+ answers.append({"id": aid, "text": answer_text})
+ qa_pairs.append({"qid": qid, "aid": aid})
+ answer_id_counter += 1
+
+ # Save progress after each successful answer
+ save_to_parquet(pd.DataFrame(answers), answers_path)
+ save_to_parquet(pd.DataFrame(qa_pairs), qa_pairs_path)
+ print(f"Processed qid: {qid}")
+ break
+ except (openai.APIError, openai.APIConnectionError) as e:
+ retry_count += 1
+ if retry_count > max_retries:
+ print(
+ f"\nFailed to process qid {qid} after {max_retries} attempts: {str(e)}"
+ )
+ # Save failed state
+ save_to_parquet(pd.DataFrame(answers), answers_path)
+ save_to_parquet(pd.DataFrame(qa_pairs), qa_pairs_path)
+ else:
+ print(f"\nRetry {retry_count} for qid {qid}...")
+
+ print("\nAnswer generation completed!")
+ print(f"Total queries: {len(queries)}")
+ print(f"Successfully processed: {len(qa_pairs)}")
+ print(f"Failed queries: {len(queries) - len(qa_pairs)}")
+
+
+def show_results(input_dir: str, n: int = 5):
+ """
+ Show n random results with question, context and answer
+
+ Args:
+ input_dir: Directory containing the QA data
+ n: Number of results to show (default: 5)
+ """
+ print(f"\nShowing {n} random results:")
+
+ # Load data
+ queries = read_parquet(f"{input_dir}/queries.parquet")
+ corpus = read_parquet(f"{input_dir}/corpus.parquet")
+ qrels = read_parquet(f"{input_dir}/qrels.parquet")
+ qa_pairs = read_parquet(f"{input_dir}/qas.parquet")
+ answers = read_parquet(f"{input_dir}/answers.parquet")
+
+ # Create QA system for context lookup
+ qa_system = QAAnsweringSystem(queries, corpus, qrels)
+
+ # Get first n QA pairs
+ for _, row in qa_pairs.sample(n).iterrows():
+ qid = row["qid"]
+ aid = row["aid"]
+
+ # Get question
+ question = qa_system.qid_to_text[qid]
+
+ # Get context
+ context = qa_system.get_context_for_qid(qid)
+
+ # Get answer
+ answer = answers[answers["id"] == aid]["text"].values[0]
+
+ print("\n" + "=" * 50)
+ print(f"Question (qid={qid}):\n{question}")
+ print("\nContext:")
+ print(context)
+ print(f"\nAnswer (aid={aid}):\n{answer}")
+ print("=" * 50 + "\n")
+
+
+def main():
+ # Set up command line arguments
+ parser = argparse.ArgumentParser(description="QA Dataset Tool")
+ subparsers = parser.add_subparsers(dest="command", required=True)
+
+ # Sample command
+ sample_parser = subparsers.add_parser("sample", help="Sample dataset")
+ sample_parser.add_argument(
+ "--queries", type=str, required=True, help="Path to queries parquet file"
+ )
+ sample_parser.add_argument(
+ "--corpus", type=str, required=True, help="Path to corpus parquet file"
+ )
+ sample_parser.add_argument(
+ "--qrels", type=str, required=True, help="Path to qrels parquet file"
+ )
+ sample_parser.add_argument(
+ "--nq", type=int, default=1000, help="Number of queries to sample"
+ )
+ sample_parser.add_argument(
+ "--output_dir", type=str, default="./save", help="Output directory"
+ )
+ sample_parser.set_defaults(func=sample_command)
+
+ # Generate command
+ generate_parser = subparsers.add_parser("generate", help="Generate answers")
+ generate_parser.add_argument(
+ "--input_dir", type=str, required=True, help="Directory with sampled data"
+ )
+ generate_parser.add_argument(
+ "--output_dir", type=str, default="./save", help="Output directory"
+ )
+ generate_parser.set_defaults(
+ func=lambda args: generate_answers(args.input_dir, args.output_dir)
+ )
+
+ # Show command
+ show_parser = subparsers.add_parser("show", help="Show QA results")
+ show_parser.add_argument(
+ "--input_dir", type=str, required=True, help="Directory with QA data"
+ )
+ show_parser.add_argument(
+ "-n", type=int, default=5, help="Number of results to show (default: 5)"
+ )
+ show_parser.set_defaults(func=lambda args: show_results(args.input_dir, args.n))
+
+ args = parser.parse_args()
+ args.func(args)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/dataset/samples/answers.parquet b/dataset/samples/answers.parquet
new file mode 100644
index 0000000000000000000000000000000000000000..0dc3ce517d4de0b36187c1aa8b7f56c4d1d02f73
GIT binary patch
literal 1949
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
P0R1*N1+VcI_{;g9%u_z=
literal 0
HcmV?d00001
diff --git a/dataset/samples/qas.parquet b/dataset/samples/qas.parquet
new file mode 100644
index 0000000000000000000000000000000000000000..faa461ece965ab9a94a939dd22a1bb882c3be84e
GIT binary patch
literal 1585
zcmZ`)U2oz>6dl|wN~@@fRy8sb56IG@t;D84a4J^&GG+rYCA$f$VqjH8{t67SjTh_~
z6K&P!{*XTQvFc-gNPkT49Ye^HbP#6doVoYhJ3clSv{x1P#BaoJyP_bH=Y%{v%n(AV
zA`zd9xiWx`?@H=Rmfxn0-=ZEcbm}t0*2z)jh6N|%yyO|FP}gm
z_>27cHwuxm3v1$dhPX$>gBODI)hA}ZAl(8A?cv&BHpvP3LcUxg1@R!SknCZeZxe-_
zkn?l5rCMo!+e5w#g%c6#Cs4mY-av3RUn1BSX4uEMVOl7XZB;Z>kpII~8!R>zJ1<5@zGpCLfBJMe%VH=?V
znf=IhKbMPJB{-YMN^5XOiTUA4{jttHyeq?do;yZ3n}hP5>@st1%oFS{NR0V>7Gtto
zXTD%FAJJOn2tG2@C$FU+l1H_pWBD_Y4rs6UUVojMN%}4O1CG_WNh!UAFOHumty6VO
zTa=DzbIdVS{eq7XJiX^Tmq6nh2G7EIjnbF`0l%0;T_A@WX)A^w^+(NfqfxtGAJpF6
znrHpc^v~IQrkaBf@mTZLnA-?Uuje}L{z{w9#(K4~(3+j8SzA_3T}iRy1v9-?IQpQS
z0Jk*sOV({tHu7B8Z1=98;;eQHv|FpeaZj<@KZ7o1wyp%jTH^-tphjft)!6AbD$YQ4
zQCssG%<%eCyUF=*H0VtrM`eh*ZKZ0qmyK={`%)&Rp5`3t?v2KL|_S<14jfLC;Re4yep(~Yl)3i@h?^lL;Z(_Gk@%*6f<>I=Zo|EPE0dFK&
zzms$phTchrgO0-c@l^%piFs`C^Rm5uV%6MRli@jP$$7ILcc+wH`4zmg=o?DU*8QLjaDBS*3#F?I@Ea~KHSUx%m~O2FQ{>vMV8neBQ!plM
zNlgfh%7ef?U{3JWkg72~2OQ1EDcqy>5CIYf_ZvEVIK}jl%9r6Si!a*WpAm8ae-{4%S~!<&
literal 0
HcmV?d00001
diff --git a/dataset/samples/qrels.parquet b/dataset/samples/qrels.parquet
new file mode 100644
index 0000000000000000000000000000000000000000..1fde976c56d77bb24a6f6cf676a05a2c0a5063ae
GIT binary patch
literal 2067
zcmb_eTW{h<6dpsCbyraptzru#9;nqtsl=vWAT6u(VIbkQz$R>i!BrLc2AJ5!3pSTT
z+n>;s
zib-}|pjZl0L0pJLz6ZBsaE`>1hy{)Z2tY*1@P#bhNWL_2m_i+Z|PD~-!ZkZZV+?F
zx83EPVyun~%_DN#@DyF~6hL--%X*(mteK$3+e|Cr|I0)hWt;0xyuoHV-cfElt2^;#
zQnbPoL-EEF$n!?9Om(76mZX~QQkhJ~a{@LqCR20l5UH3xgpUB#5LVRpm51VCR(Ox1
zySUwcBfSa^MfAhdugNIj62|x(E;3$YoWsQdE@M2vr2&-{ix2d3B0RmPC;qU9K~bo_
zhVdMOAKVGZNss7Yk8yJhx8gT7+w$c~C)X8QGqu|BRHtdaF^g*V?R-$PS2C;Ms&?Bl
zDxK@vXgrWI>1nN0AF1LZqe}TemYtfaUH0U+wIk3i6zSY-l&~q=mZetO{l|1#r3~EV
z>u$E4*D4pli%nh1yFIaR1vrGm*QLzd=oHdMw`dXWnq4pzyED>DRPV{%_7E`AJ;Gbb
zXVl7~&?u3-@hf;Ivr1|
zT7A80%~yHOdbVrzvO%t@)JgZG_E4{!k{Uwni)kO~Xe{e~Ps#-GJFStLty>?B2cCmw
z2Yip!N0IXB)@a4I9gnLz=VI#L!c&J}$IkRz>wlt^Zs$z~}G|k+yF>0E9(r={os;l~vwj@`T
z2x%t<^Xu>75PS==(-LiB(#^O1OqUlh7q6i=mNM<{0Y0I9t%OEfiNxw9kO*E6v19n<3%=-!rwE6c9Dc1PrY)g_f`kM)xPq7++{||3ZZ7TNm1%5wNgBIUvRz8hS8TbGtXh(7
zIkcxsoRd?9j5mWtT4LpU`7}?v-$(
z&)0x>ckuhs!HdKF*GIoRJAD4r+rRgZ{(N)v=C{NBAOC&+$J^JxenI-$xtsX&Lq07{
zxK%=YIxu>ctFd#4U07y##x|GH#u9|me@!WqCZ!jh27q9t}gD53PCK!RC@V>pi+
zWsBL1r!x?8&^ThDe+1)U&@I@T7P5%U<*WderxFJY_ZO3Wm&!gRD(Cm|HSK<0g>+H{
zw|{|SvKP5m7m$$S)4t}JTEPFq?1{E#T9X}P;6$z$h&MA29_BJ)vfQDZ^$ALsd!nbg
z7KDqoDZ++b+j7jEkFg}lP8j<%F!F3;U|J;Fi(EfGAqINiGD3p8R;Za;sDZ8*InI+@
z<}?raWj@bzjQ;mrwC$m#g@Yla-xD2MA8NyxwCKm$a5xx~>?3O!*aMHC#rz6j{rtD&nBoe?xB*{s{DAQqmb!Qy<1VgrDJDrD@nZts
zIpOOKpvf8zszP}g<1q#Tu0%u%2uA|(`uA{=zLQ-i>Qt4rR&i^j*OXBAHtoB%q_^&k
zyRtjOoU*UGO~K)-);%ROYR;}%bH+2>QQP&M
z_G%;HRVCk6n?197htwRI>Ll2Ox~;f=n$Lr}Ye{^^bsW9g+&xogzSHXE|JQnyWLAc&
zK%M27%mC^OG_@YLTlEs1i6@odKGs(a+L!4nu~BqJx=m&&$D5V4@kSrpq_;D3D|TA-
zsiu}As?nFDO?z!BdwJp;jBB&`5*v_p;tYB&ZiC+l-ndVlQia=7Yt8&oqjRIol*CWK
zGSo+HERFFdAwcZduaeZHp>1-e{-R6jX{@OvtU>Uk3d-HSjrfeO4If8Vc-`>*>-2%j
Y%pRCb@+b*8cn_0T@ymIHw&9QH1J5iJcK`qY
literal 0
HcmV?d00001
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..9f29764
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,246 @@
+services:
+ app:
+ image: wechatopenai/weknora-app:latest
+ container_name: WeKnora-app
+ ports:
+ - "${APP_PORT:-8080}:8080"
+ volumes:
+ - data-files:/data/files
+ - ./config/config.yaml:/app/config/config.yaml
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 60s
+ environment:
+ - COS_SECRET_ID=${COS_SECRET_ID:-}
+ - COS_SECRET_KEY=${COS_SECRET_KEY:-}
+ - COS_REGION=${COS_REGION:-}
+ - COS_BUCKET_NAME=${COS_BUCKET_NAME:-}
+ - COS_APP_ID=${COS_APP_ID:-}
+ - COS_PATH_PREFIX=${COS_PATH_PREFIX:-}
+ - COS_ENABLE_OLD_DOMAIN=${COS_ENABLE_OLD_DOMAIN:-}
+ - GIN_MODE=${GIN_MODE:-}
+ - DB_DRIVER=postgres
+ - DB_HOST=postgres
+ - DB_PORT=5432
+ - DB_USER=${DB_USER:-}
+ - DB_PASSWORD=${DB_PASSWORD:-}
+ - DB_NAME=${DB_NAME:-}
+ - TZ=Asia/Shanghai
+ - OTEL_EXPORTER_OTLP_ENDPOINT=jaeger:4317
+ - OTEL_SERVICE_NAME=WeKnora
+ - OTEL_TRACES_EXPORTER=otlp
+ - OTEL_METRICS_EXPORTER=none
+ - OTEL_LOGS_EXPORTER=none
+ - OTEL_PROPAGATORS=tracecontext,baggage
+ - RETRIEVE_DRIVER=${RETRIEVE_DRIVER:-}
+ - ELASTICSEARCH_ADDR=${ELASTICSEARCH_ADDR:-}
+ - ELASTICSEARCH_USERNAME=${ELASTICSEARCH_USERNAME:-}
+ - ELASTICSEARCH_PASSWORD=${ELASTICSEARCH_PASSWORD:-}
+ - ELASTICSEARCH_INDEX=${ELASTICSEARCH_INDEX:-}
+ - DOCREADER_ADDR=docreader:50051
+ - STORAGE_TYPE=${STORAGE_TYPE:-}
+ - LOCAL_STORAGE_BASE_DIR=${LOCAL_STORAGE_BASE_DIR:-}
+ - MINIO_ENDPOINT=minio:9000
+ - MINIO_ACCESS_KEY_ID=${MINIO_ACCESS_KEY_ID:-minioadmin}
+ - MINIO_SECRET_ACCESS_KEY=${MINIO_SECRET_ACCESS_KEY:-minioadmin}
+ - MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME:-}
+ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
+ - STREAM_MANAGER_TYPE=${STREAM_MANAGER_TYPE:-}
+ - REDIS_ADDR=redis:6379
+ - REDIS_PASSWORD=${REDIS_PASSWORD:-}
+ - REDIS_DB=${REDIS_DB:-}
+ - REDIS_PREFIX=${REDIS_PREFIX:-}
+ - ENABLE_GRAPH_RAG=${ENABLE_GRAPH_RAG:-}
+ - NEO4J_ENABLE=${NEO4J_ENABLE:-}
+ - NEO4J_URI=bolt://neo4j:7687
+ - NEO4J_USERNAME=${NEO4J_USERNAME:-neo4j}
+ - NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
+ - TENANT_AES_KEY=${TENANT_AES_KEY:-}
+ - CONCURRENCY_POOL_SIZE=${CONCURRENCY_POOL_SIZE:-5}
+ - INIT_LLM_MODEL_NAME=${INIT_LLM_MODEL_NAME:-}
+ - INIT_LLM_MODEL_BASE_URL=${INIT_LLM_MODEL_BASE_URL:-}
+ - INIT_LLM_MODEL_API_KEY=${INIT_LLM_MODEL_API_KEY:-}
+ - INIT_EMBEDDING_MODEL_NAME=${INIT_EMBEDDING_MODEL_NAME:-}
+ - INIT_EMBEDDING_MODEL_BASE_URL=${INIT_EMBEDDING_MODEL_BASE_URL:-}
+ - INIT_EMBEDDING_MODEL_API_KEY=${INIT_EMBEDDING_MODEL_API_KEY:-}
+ - INIT_EMBEDDING_MODEL_DIMENSION=${INIT_EMBEDDING_MODEL_DIMENSION:-}
+ - INIT_EMBEDDING_MODEL_ID=${INIT_EMBEDDING_MODEL_ID:-}
+ - INIT_RERANK_MODEL_NAME=${INIT_RERANK_MODEL_NAME:-}
+ - INIT_RERANK_MODEL_BASE_URL=${INIT_RERANK_MODEL_BASE_URL:-}
+ - INIT_RERANK_MODEL_API_KEY=${INIT_RERANK_MODEL_API_KEY:-}
+ depends_on:
+ redis:
+ condition: service_started
+ postgres:
+ condition: service_healthy
+ minio:
+ condition: service_started
+ docreader:
+ condition: service_healthy
+ neo4j:
+ condition: service_started
+ networks:
+ - WeKnora-network
+ restart: unless-stopped
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+
+ minio:
+ image: minio/minio:latest
+ container_name: WeKnora-minio
+ ports:
+ - "${MINIO_PORT:-9000}:9000"
+ - "${MINIO_CONSOLE_PORT:-9001}:9001"
+ environment:
+ - MINIO_ROOT_USER=${MINIO_ACCESS_KEY_ID:-minioadmin}
+ - MINIO_ROOT_PASSWORD=${MINIO_SECRET_ACCESS_KEY:-minioadmin}
+ command: server --console-address ":9001" /data
+ volumes:
+ - minio_data:/data
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
+ interval: 30s
+ timeout: 20s
+ retries: 3
+ networks:
+ - WeKnora-network
+
+ frontend:
+ image: wechatopenai/weknora-ui:latest
+ container_name: WeKnora-frontend
+ ports:
+ - "${FRONTEND_PORT:-80}:80"
+ depends_on:
+ app:
+ condition: service_healthy
+ networks:
+ - WeKnora-network
+ restart: unless-stopped
+
+ docreader:
+ image: wechatopenai/weknora-docreader:latest
+ container_name: WeKnora-docreader
+ ports:
+ - "${DOCREADER_PORT:-50051}:50051"
+ environment:
+ - COS_SECRET_ID=${COS_SECRET_ID:-}
+ - COS_SECRET_KEY=${COS_SECRET_KEY:-}
+ - COS_REGION=${COS_REGION:-}
+ - COS_BUCKET_NAME=${COS_BUCKET_NAME:-}
+ - COS_APP_ID=${COS_APP_ID:-}
+ - COS_PATH_PREFIX=${COS_PATH_PREFIX:-}
+ - COS_ENABLE_OLD_DOMAIN=${COS_ENABLE_OLD_DOMAIN:-}
+ - VLM_MODEL_BASE_URL=${VLM_MODEL_BASE_URL:-}
+ - VLM_MODEL_NAME=${VLM_MODEL_NAME:-}
+ - VLM_MODEL_API_KEY=${VLM_MODEL_API_KEY:-}
+ - STORAGE_TYPE=${STORAGE_TYPE:-}
+ - MINIO_PUBLIC_ENDPOINT=http://localhost:${MINIO_PORT:-9000}
+ - MINIO_ENDPOINT=minio:9000
+ - MINIO_ACCESS_KEY_ID=${MINIO_ACCESS_KEY_ID:-minioadmin}
+ - MINIO_SECRET_ACCESS_KEY=${MINIO_SECRET_ACCESS_KEY:-minioadmin}
+ - MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME:-}
+ - MINIO_USE_SSL=${MINIO_USE_SSL:-}
+ - WEB_PROXY=${WEB_PROXY:-}
+ healthcheck:
+ test: ["CMD", "grpc_health_probe", "-addr=:50051"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 60s
+ networks:
+ - WeKnora-network
+ restart: unless-stopped
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+
+ jaeger:
+ image: jaegertracing/all-in-one:latest
+ ports:
+ - "6831:6831/udp" # Jaeger Thrift接收器
+ - "6832:6832/udp" # Jaeger Thrift接收器(Compact)
+ - "5778:5778" # 配置端口
+ - "16686:16686" # Web UI
+ - "4317:4317" # OTLP gRPC接收器
+ - "4318:4318" # OTLP HTTP接收器
+ - "14250:14250" # 接收模型端口
+ - "14268:14268" # Jaeger HTTP接收器
+ - "9411:9411" # Zipkin兼容性端口
+ environment:
+ - COLLECTOR_OTLP_ENABLED=true
+ - COLLECTOR_ZIPKIN_HOST_PORT=:9411
+ volumes:
+ - jaeger_data:/var/lib/jaeger # 持久化 Jaeger 数据
+ networks:
+ - WeKnora-network
+ restart: unless-stopped
+ # 修改的PostgreSQL配置
+ postgres:
+ image: paradedb/paradedb:v0.18.9-pg17
+ container_name: WeKnora-postgres
+ ports:
+ - "${DB_PORT}:5432"
+ environment:
+ - POSTGRES_USER=${DB_USER}
+ # NOCC:hardcode-password(工具误报)
+ - POSTGRES_PASSWORD=${DB_PASSWORD}
+ - POSTGRES_DB=${DB_NAME}
+ volumes:
+ - postgres-data:/var/lib/postgresql/data
+ - ./migrations/paradedb/00-init-db.sql:/docker-entrypoint-initdb.d/00-init-db.sql
+ - ./migrations/paradedb/01-migrate-to-paradedb.sql:/docker-entrypoint-initdb.d/01-migrate-to-paradedb.sql
+ networks:
+ - WeKnora-network
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
+ interval: 10s # 增加时间间隔
+ timeout: 10s # 增加超时时间
+ retries: 3 # 减少重试次数,让失败更快反馈
+ start_period: 30s # 给予初始启动更多时间
+ restart: unless-stopped
+ # 添加停机时的优雅退出时间
+ stop_grace_period: 1m
+
+ redis:
+ image: redis:7.0-alpine
+ container_name: WeKnora-redis
+ ports:
+ - "${REDIS_PORT}:6379"
+ volumes:
+ - redis_data:/data
+ command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
+ restart: always
+ networks:
+ - WeKnora-network
+
+ neo4j:
+ image: neo4j:latest
+ container_name: WeKnora-neo4j
+ volumes:
+ - neo4j-data:/data
+ environment:
+ - NEO4J_AUTH=${NEO4J_USERNAME:-neo4j}/${NEO4J_PASSWORD:-password}
+ - NEO4J_apoc_export_file_enabled=true
+ - NEO4J_apoc_import_file_enabled=true
+ - NEO4J_apoc_import_file_use__neo4j__config=true
+ - NEO4JLABS_PLUGINS=["apoc"]
+ ports:
+ - "7474:7474"
+ - "7687:7687"
+ restart: always
+ networks:
+ - WeKnora-network
+
+networks:
+ WeKnora-network:
+ driver: bridge
+
+volumes:
+ postgres-data:
+ data-files:
+ jaeger_data:
+ redis_data:
+ minio_data:
+ neo4j-data:
diff --git a/docker/Dockerfile.app b/docker/Dockerfile.app
new file mode 100644
index 0000000..777ee62
--- /dev/null
+++ b/docker/Dockerfile.app
@@ -0,0 +1,79 @@
+# Build stage
+FROM golang:1.24-alpine AS builder
+
+WORKDIR /app
+
+# 通过构建参数接收敏感信息
+ARG GOPRIVATE_ARG
+ARG GOPROXY_ARG
+ARG GOSUMDB_ARG=off
+
+# 设置Go环境变量
+ENV GOPRIVATE=${GOPRIVATE_ARG}
+ENV GOPROXY=${GOPROXY_ARG}
+ENV GOSUMDB=${GOSUMDB_ARG}
+
+# Install dependencies
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
+ apk add --no-cache git build-base
+
+# Install migrate tool
+RUN go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
+
+# Copy go mod and sum files
+COPY go.mod go.sum ./
+RUN --mount=type=cache,target=/go/pkg/mod go mod download
+COPY . .
+
+# Get version and commit info for build injection
+ARG VERSION_ARG
+ARG COMMIT_ID_ARG
+ARG BUILD_TIME_ARG
+ARG GO_VERSION_ARG
+
+# Set build-time variables
+ENV VERSION=${VERSION_ARG}
+ENV COMMIT_ID=${COMMIT_ID_ARG}
+ENV BUILD_TIME=${BUILD_TIME_ARG}
+ENV GO_VERSION=${GO_VERSION_ARG}
+
+# Build the application with version info
+RUN --mount=type=cache,target=/go/pkg/mod make build-prod
+RUN --mount=type=cache,target=/go/pkg/mod cp -r /go/pkg/mod/github.com/yanyiwu/ /app/yanyiwu/
+
+# Final stage
+FROM alpine:3.17
+
+WORKDIR /app
+
+# Install runtime dependencies
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
+ apk update && apk upgrade && \
+ apk add --no-cache build-base postgresql-client mysql-client ca-certificates tzdata sed curl bash vim wget
+
+# Create a non-root user and switch to it
+RUN mkdir -p /data/files && \
+ adduser -D -g '' appuser && \
+ chown -R appuser:appuser /app /data/files
+
+# Copy migrate tool from builder stage
+COPY --from=builder /go/bin/migrate /usr/local/bin/
+COPY --from=builder /app/yanyiwu/ /go/pkg/mod/github.com/yanyiwu/
+
+# Copy the binary from the builder stage
+COPY --from=builder /app/config ./config
+COPY --from=builder /app/scripts ./scripts
+COPY --from=builder /app/migrations ./migrations
+COPY --from=builder /app/dataset/samples ./dataset/samples
+COPY --from=builder /app/WeKnora .
+
+# Make scripts executable
+RUN chmod +x ./scripts/*.sh
+
+# Expose ports
+EXPOSE 8080
+
+# Switch to non-root user and run the application directly
+USER appuser
+
+CMD ["./WeKnora"]
\ No newline at end of file
diff --git a/docker/Dockerfile.docreader b/docker/Dockerfile.docreader
new file mode 100644
index 0000000..89a0d40
--- /dev/null
+++ b/docker/Dockerfile.docreader
@@ -0,0 +1,160 @@
+# =========================
+# 构建阶段
+# =========================
+FROM python:3.10.18-bookworm AS builder
+
+# 切换 apt 源到清华
+RUN sed -i 's@http://deb.debian.org@https://mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.list.d/debian.sources && \
+ sed -i 's@http://security.debian.org@https://mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.list.d/debian.sources
+
+WORKDIR /app
+
+# 安装构建依赖
+RUN apt-get update && apt-get install -y \
+ gcc \
+ python3-dev \
+ libjpeg-dev \
+ zlib1g-dev \
+ libpq-dev \
+ libffi-dev \
+ libgl1 \
+ libglib2.0-0 \
+ wget \
+ antiword \
+ curl \
+ unzip \
+ && rm -rf /var/lib/apt/lists/*
+
+# 检查是否存在本地protoc安装包,如果存在则离线安装,否则在线安装,其他安装包按需求添加
+ARG TARGETARCH
+COPY packages/ /app/packages/
+RUN echo "检查本地protoc安装包..." && \
+ # 根据目标架构选择正确的protoc包名
+ case ${TARGETARCH} in \
+ "amd64") PROTOC_ARCH="x86_64" ;; \
+ "arm64") PROTOC_ARCH="aarch_64" ;; \
+ "arm") PROTOC_ARCH="arm" ;; \
+ *) echo "Unsupported architecture for protoc: ${TARGETARCH}" && exit 1 ;; \
+ esac && \
+ PROTOC_PACKAGE="protoc-3.19.4-linux-${PROTOC_ARCH}.zip" && \
+ if [ -f "/app/packages/${PROTOC_PACKAGE}" ]; then \
+ echo "发现本地protoc安装包,将进行离线安装"; \
+ # 离线安装:使用本地包(精确路径避免歧义)
+ cp /app/packages/${PROTOC_PACKAGE} /app/ && \
+ unzip -o /app/${PROTOC_PACKAGE} -d /usr/local && \
+ chmod +x /usr/local/bin/protoc && \
+ rm -f /app/${PROTOC_PACKAGE}; \
+ else \
+ echo "未发现本地protoc安装包,将进行在线安装"; \
+ # 在线安装:从网络下载
+ curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/${PROTOC_PACKAGE} && \
+ unzip -o ${PROTOC_PACKAGE} -d /usr/local && \
+ chmod +x /usr/local/bin/protoc && \
+ rm -f ${PROTOC_PACKAGE}; \
+ fi
+
+# 复制依赖文件
+COPY services/docreader/requirements.txt .
+
+# 安装依赖
+RUN pip cache purge && pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
+
+# 预下载 PP-OCRv4 模型
+RUN mkdir -p /root/.paddleocr/whl/det/ch && \
+ mkdir -p /root/.paddleocr/whl/rec/ch && \
+ mkdir -p /root/.paddleocr/whl/cls/ch && \
+ # 下载检测模型
+ wget https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_infer.tar \
+ -O /root/.paddleocr/whl/det/ch/ch_PP-OCRv4_det_infer.tar && \
+ tar -xf /root/.paddleocr/whl/det/ch/ch_PP-OCRv4_det_infer.tar -C /root/.paddleocr/whl/det/ch/ && \
+ # 下载识别模型
+ wget https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_rec_infer.tar \
+ -O /root/.paddleocr/whl/rec/ch/ch_PP-OCRv4_rec_infer.tar && \
+ tar -xf /root/.paddleocr/whl/rec/ch/ch_PP-OCRv4_rec_infer.tar -C /root/.paddleocr/whl/rec/ch/ && \
+ # 下载文本方向分类模型(用于判断文本是否需要旋转)
+ wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar \
+ -O /root/.paddleocr/whl/cls/ch_ppocr_mobile_v2.0_cls_infer.tar && \
+ tar -xf /root/.paddleocr/whl/cls/ch_ppocr_mobile_v2.0_cls_infer.tar -C /root/.paddleocr/whl/cls/ && \
+ # 清理压缩包
+ rm -f /root/.paddleocr/whl/det/ch/ch_PP-OCRv4_det_infer.tar && \
+ rm -f /root/.paddleocr/whl/rec/ch/ch_PP-OCRv4_rec_infer.tar && \
+ rm -f /root/.paddleocr/whl/cls/ch_ppocr_mobile_v2.0_cls_infer.tar
+
+# 复制源代码和生成脚本
+COPY services/docreader/src/ /app/src/
+COPY services/docreader/scripts/ /app/scripts/
+
+# 确保模型目录存在
+RUN ls -la /root/.paddleocr/whl/
+
+# 生成 protobuf 代码
+RUN chmod +x /app/scripts/generate_proto.sh && bash /app/scripts/generate_proto.sh
+
+
+# =========================
+# 运行阶段
+# =========================
+FROM python:3.10.18-bookworm AS runner
+
+# 切换 apt 源到清华
+RUN sed -i 's@http://deb.debian.org@https://mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.list.d/debian.sources && \
+ sed -i 's@http://security.debian.org@https://mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.list.d/debian.sources
+
+WORKDIR /app
+
+# 安装运行时依赖
+RUN apt-get update && apt-get install -y \
+ libjpeg62-turbo \
+ libpq5 \
+ wget \
+ gnupg \
+ libgl1 \
+ libglib2.0-0 \
+ antiword \
+ vim \
+ tar \
+ dpkg \
+ libxinerama1 \
+ libfontconfig1 \
+ libdbus-glib-1-2 \
+ libcairo2 \
+ libcups2 \
+ libglu1-mesa \
+ libsm6 \
+ libreoffice \
+ curl \
+ && rm -rf /var/lib/apt/lists/*
+
+# 安装 grpc_health_probe
+ARG TARGETARCH
+RUN GRPC_HEALTH_PROBE_VERSION=v0.4.24 && \
+ # 根据目标架构选择正确的二进制文件
+ case ${TARGETARCH} in \
+ "amd64") ARCH="amd64" ;; \
+ "arm64") ARCH="arm64" ;; \
+ "arm") ARCH="arm" ;; \
+ *) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
+ esac && \
+ wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-${ARCH} && \
+ chmod +x /bin/grpc_health_probe
+
+# 从构建阶段复制已安装的依赖和生成的代码
+COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
+COPY --from=builder /usr/local/bin /usr/local/bin
+COPY --from=builder /root/.paddleocr /root/.paddleocr
+
+# 安装 Playwright 浏览器
+RUN python -m playwright install webkit
+RUN python -m playwright install-deps webkit
+
+COPY --from=builder /app/src /app/src
+
+# 设置 Python 路径
+ENV PYTHONPATH=/app/src
+RUN cd /app/src && python -m download_deps
+
+# 暴露 gRPC 端口
+EXPOSE 50051
+
+# 直接运行 Python 服务(日志输出到 stdout/stderr)
+CMD ["python", "/app/src/server/server.py"]
\ No newline at end of file
diff --git a/docker/config/supervisord.conf b/docker/config/supervisord.conf
new file mode 100644
index 0000000..44039e2
--- /dev/null
+++ b/docker/config/supervisord.conf
@@ -0,0 +1,31 @@
+[supervisord]
+nodaemon=true
+logfile=/var/log/supervisord.log
+logfile_maxbytes=50MB
+logfile_backups=10
+loglevel=info
+pidfile=/var/run/supervisord.pid
+user=root
+
+[program:WeKnora]
+command=/app/WeKnora
+directory=/app
+autostart=true
+autorestart=true
+startretries=5
+redirect_stderr=true
+stdout_logfile=/var/log/WeKnora.log
+stdout_logfile_maxbytes=50MB
+stdout_logfile_backups=10
+environment=CGO_ENABLED=1
+user=appuser
+
+[unix_http_server]
+file=/var/run/supervisor.sock
+chmod=0700
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+[supervisorctl]
+serverurl=unix:///var/run/supervisor.sock
\ No newline at end of file
diff --git a/docker/docker-compose.minio.yml b/docker/docker-compose.minio.yml
new file mode 100644
index 0000000..a78b0ab
--- /dev/null
+++ b/docker/docker-compose.minio.yml
@@ -0,0 +1,29 @@
+version: "3.8"
+
+services:
+ minio:
+ image: minio/minio:latest
+ container_name: WeKnora-minio
+ ports:
+ - "9000:9000"
+ - "9001:9001"
+ volumes:
+ - minio-data:/data
+ environment:
+ - MINIO_ROOT_USER=${MINIO_ACCESS_KEY_ID}
+ - MINIO_ROOT_PASSWORD=${MINIO_SECRET_ACCESS_KEY}
+ command: server --console-address ":9001" /data
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
+ interval: 30s
+ timeout: 20s
+ retries: 3
+ networks:
+ - WeKnora-network
+
+volumes:
+ minio-data:
+
+networks:
+ WeKnora-network:
+ external: true
\ No newline at end of file
diff --git a/docs/API.md b/docs/API.md
new file mode 100644
index 0000000..ce1e014
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,2105 @@
+# WeKnora API 文档
+
+## 目录
+
+- [概述](#概述)
+- [基础信息](#基础信息)
+- [认证机制](#认证机制)
+- [错误处理](#错误处理)
+- [API 概览](#api-概览)
+- [API 详细说明](#api-详细说明)
+ - [租户管理 API](#租户管理api)
+ - [知识库管理 API](#知识库管理api)
+ - [知识管理 API](#知识管理api)
+ - [模型管理 API](#模型管理api)
+ - [分块管理 API](#分块管理api)
+ - [会话管理 API](#会话管理api)
+ - [聊天功能 API](#聊天功能api)
+ - [消息管理 API](#消息管理api)
+ - [评估功能 API](#评估功能api)
+
+## 概述
+
+WeKnora 提供了一系列 RESTful API,用于创建和管理知识库、检索知识,以及进行基于知识的问答。本文档详细描述了这些 API 的使用方式。
+
+## 基础信息
+
+- **基础 URL**: `/api/v1`
+- **响应格式**: JSON
+- **认证方式**: API Key
+
+## 认证机制
+
+所有 API 请求需要在 HTTP 请求头中包含 `X-API-Key` 进行身份认证:
+
+```
+X-API-Key: your_api_key
+```
+
+为便于问题追踪和调试,建议每个请求的 HTTP 请求头中添加 `X-Request-ID`:
+
+```
+X-Request-ID: unique_request_id
+```
+
+### 获取 API Key
+
+获取 API Key 有以下方式:
+
+**创建租户时获取**:通过 `POST /api/v1/tenants` 接口创建新租户时,响应中会自动返回生成的 API Key。
+
+请妥善保管您的 API Key,避免泄露。API Key 代表您的账户身份,拥有完整的 API 访问权限。
+
+## 错误处理
+
+所有 API 使用标准的 HTTP 状态码表示请求状态,并返回统一的错误响应格式:
+
+```json
+{
+ "success": false,
+ "error": {
+ "code": "错误代码",
+ "message": "错误信息",
+ "details": "错误详情"
+ }
+}
+```
+
+## API 概览
+
+WeKnora API 按功能分为以下几类:
+
+1. **租户管理**:创建和管理租户账户
+2. **知识库管理**:创建、查询和管理知识库
+3. **知识管理**:上传、检索和管理知识内容
+4. **模型管理**:配置和管理各种AI模型
+5. **分块管理**:管理知识的分块内容
+6. **会话管理**:创建和管理对话会话
+7. **聊天功能**:基于知识库进行问答
+8. **消息管理**:获取和管理对话消息
+9. **评估功能**:评估模型性能
+
+## API 详细说明
+
+以下是每个API的详细说明和示例。
+
+### 租户管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | -------------- | --------------------- |
+| POST | `/tenants` | 创建新租户 |
+| GET | `/tenants/:id` | 获取指定租户信息 |
+| PUT | `/tenants/:id` | 更新租户信息 |
+| DELETE | `/tenants/:id` | 删除租户 |
+| GET | `/tenants` | 获取租户列表 |
+
+#### POST `/tenants` - 创建新租户
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/tenants' \
+--header 'Content-Type: application/json' \
+--data '{
+ "name": "weknora",
+ "description": "weknora tenants",
+ "business": "wechat",
+ "retriever_engines": {
+ "engines": [
+ {
+ "retriever_type": "keywords",
+ "retriever_engine_type": "postgres"
+ },
+ {
+ "retriever_type": "vector",
+ "retriever_engine_type": "postgres"
+ }
+ ]
+ }
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": 10000,
+ "name": "weknora",
+ "description": "weknora tenants",
+ "api_key": "sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG",
+ "status": "active",
+ "retriever_engines": {
+ "engines": [
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "keywords"
+ },
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "vector"
+ }
+ ]
+ },
+ "business": "wechat",
+ "storage_quota": 10737418240,
+ "storage_used": 0,
+ "created_at": "2025-08-11T20:37:28.396980093+08:00",
+ "updated_at": "2025-08-11T20:37:28.396980301+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### GET `/tenants/:id` - 获取指定租户信息
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/tenants/10000' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": 10000,
+ "name": "weknora",
+ "description": "weknora tenants",
+ "api_key": "sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG",
+ "status": "active",
+ "retriever_engines": {
+ "engines": [
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "keywords"
+ },
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "vector"
+ }
+ ]
+ },
+ "business": "wechat",
+ "storage_quota": 10737418240,
+ "storage_used": 0,
+ "created_at": "2025-08-11T20:37:28.39698+08:00",
+ "updated_at": "2025-08-11T20:37:28.405693+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### PUT `/tenants/:id` - 更新租户信息
+
+注意 API Key 会变更
+
+**请求**:
+
+```curl
+curl --location --request PUT 'http://localhost:8080/api/v1/tenants/10000' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-KREi84yPtahKxMtIMOW-Cxx2dxb9xROpUuDSpi3vbiC1QVDe' \
+--data '{
+ "name": "weknora new",
+ "description": "weknora tenants new",
+ "status": "active",
+ "retriever_engines": {
+ "engines": [
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "keywords"
+ },
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "vector"
+ }
+ ]
+ },
+ "business": "wechat",
+ "storage_quota": 10737418240
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": 10000,
+ "name": "weknora new",
+ "description": "weknora tenants new",
+ "api_key": "sk-IKtd9JGV4-aPGQ6RiL8YJu9Vzb3-ae4lgFkjFJZmhvUn2mLu",
+ "status": "active",
+ "retriever_engines": {
+ "engines": [
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "keywords"
+ },
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "vector"
+ }
+ ]
+ },
+ "business": "wechat",
+ "storage_quota": 10737418240,
+ "storage_used": 0,
+ "created_at": "0001-01-01T00:00:00Z",
+ "updated_at": "2025-08-11T20:49:02.13421034+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### DELETE `/tenants/:id` - 删除租户
+
+**请求**:
+
+```curl
+curl --location --request DELETE 'http://localhost:8080/api/v1/tenants/10000' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-IKtd9JGV4-aPGQ6RiL8YJu9Vzb3-ae4lgFkjFJZmhvUn2mLu'
+```
+
+**响应**:
+
+```json
+{
+ "message": "Tenant deleted successfully",
+ "success": true
+}
+```
+
+#### GET `/tenants` - 获取租户列表
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/tenants' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "items": [
+ {
+ "id": 10002,
+ "name": "weknora",
+ "description": "weknora tenants",
+ "api_key": "sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA",
+ "status": "active",
+ "retriever_engines": {
+ "engines": [
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "keywords"
+ },
+ {
+ "retriever_engine_type": "postgres",
+ "retriever_type": "vector"
+ }
+ ]
+ },
+ "business": "wechat",
+ "storage_quota": 10737418240,
+ "storage_used": 0,
+ "created_at": "2025-08-11T20:52:58.05679+08:00",
+ "updated_at": "2025-08-11T20:52:58.060495+08:00",
+ "deleted_at": null
+ }
+ ]
+ },
+ "success": true
+}
+```
+
+
+
+### 知识库管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | ------------------------------------ | ------------------------ |
+| POST | `/knowledge-bases` | 创建知识库 |
+| GET | `/knowledge-bases` | 获取知识库列表 |
+| GET | `/knowledge-bases/:id` | 获取知识库详情 |
+| PUT | `/knowledge-bases/:id` | 更新知识库 |
+| DELETE | `/knowledge-bases/:id` | 删除知识库 |
+| GET | `/knowledge-bases/:id/hybrid-search` | 混合搜索知识库内容 |
+| POST | `/knowledge-bases/copy` | 拷贝知识库 |
+
+#### POST `/knowledge-bases` - 创建知识库
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge-bases' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--data '{
+ "name": "weknora",
+ "description": "weknora description",
+ "chunking_config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200,
+ "separators": [
+ "."
+ ],
+ "enable_multimodal": true
+ },
+ "image_processing_config": {
+ "model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e"
+ },
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
+ "vlm_model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e",
+ "vlm_config": {
+ "model_name": "qwen2.5vl:3b",
+ "interface_type": "ollama",
+ "base_url": "",
+ "api_key": ""
+ },
+ "cos_config": {
+ "secret_id": "",
+ "secret_key": "",
+ "region": "",
+ "bucket_name": "",
+ "app_id": "",
+ "path_prefix": ""
+ }
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "b5829e4a-3845-4624-a7fb-ea3b35e843b0",
+ "name": "weknora",
+ "description": "weknora description",
+ "tenant_id": 1,
+ "chunking_config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200,
+ "separators": [
+ "."
+ ],
+ "enable_multimodal": true
+ },
+ "image_processing_config": {
+ "model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e"
+ },
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
+ "vlm_model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e",
+ "vlm_config": {
+ "model_name": "qwen2.5vl:3b",
+ "base_url": "",
+ "api_key": "",
+ "interface_type": "ollama"
+ },
+ "cos_config": {
+ "secret_id": "",
+ "secret_key": "",
+ "region": "",
+ "bucket_name": "",
+ "app_id": "",
+ "path_prefix": ""
+ },
+ "created_at": "2025-08-12T11:30:09.206238645+08:00",
+ "updated_at": "2025-08-12T11:30:09.206238854+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### GET `/knowledge-bases` - 获取知识库列表
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge-bases' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ'
+```
+
+**响应**:
+
+```json
+{
+ "data": [
+ {
+ "id": "kb-00000001",
+ "name": "Default Knowledge Base",
+ "description": "System Default Knowledge Base",
+ "tenant_id": 1,
+ "chunking_config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200,
+ "separators": [
+ "\n\n",
+ "\n",
+ "。",
+ "!",
+ "?",
+ ";",
+ ";"
+ ],
+ "enable_multimodal": true
+ },
+ "image_processing_config": {
+ "model_id": ""
+ },
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
+ "vlm_model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e",
+ "vlm_config": {
+ "model_name": "qwen2.5vl:3b",
+ "base_url": "http://host.docker.internal:11435/v1",
+ "api_key": "",
+ "interface_type": "ollama"
+ },
+ "cos_config": {
+ "secret_id": "",
+ "secret_key": "",
+ "region": "",
+ "bucket_name": "",
+ "app_id": "",
+ "path_prefix": ""
+ },
+ "created_at": "2025-08-11T20:10:41.817794+08:00",
+ "updated_at": "2025-08-12T11:23:00.593097+08:00",
+ "deleted_at": null
+ }
+ ],
+ "success": true
+}
+```
+
+#### GET `/knowledge-bases/:id` - 获取知识库详情
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "kb-00000001",
+ "name": "Default Knowledge Base",
+ "description": "System Default Knowledge Base",
+ "tenant_id": 1,
+ "chunking_config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200,
+ "separators": [
+ "\n\n",
+ "\n",
+ "。",
+ "!",
+ "?",
+ ";",
+ ";"
+ ],
+ "enable_multimodal": true
+ },
+ "image_processing_config": {
+ "model_id": ""
+ },
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
+ "vlm_model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e",
+ "vlm_config": {
+ "model_name": "qwen2.5vl:3b",
+ "base_url": "http://host.docker.internal:11435/v1",
+ "api_key": "",
+ "interface_type": "ollama"
+ },
+ "cos_config": {
+ "secret_id": "",
+ "secret_key": "",
+ "region": "",
+ "bucket_name": "",
+ "app_id": "",
+ "path_prefix": ""
+ },
+ "created_at": "2025-08-11T20:10:41.817794+08:00",
+ "updated_at": "2025-08-12T11:23:00.593097+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### PUT `/knowledge-bases/:id` - 更新知识库
+
+**请求**:
+
+```curl
+curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/b5829e4a-3845-4624-a7fb-ea3b35e843b0' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--data '{
+ "name": "weknora new",
+ "description": "weknora description new",
+ "config": {
+ "chunking_config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200,
+ "separators": [
+ "\n\n",
+ "\n",
+ "。",
+ "!",
+ "?",
+ ";",
+ ";"
+ ],
+ "enable_multimodal": true
+ },
+ "image_processing_config": {
+ "model_id": ""
+ }
+ }
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "b5829e4a-3845-4624-a7fb-ea3b35e843b0",
+ "name": "weknora new",
+ "description": "weknora description new",
+ "tenant_id": 1,
+ "chunking_config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200,
+ "separators": [
+ "\n\n",
+ "\n",
+ "。",
+ "!",
+ "?",
+ ";",
+ ";"
+ ],
+ "enable_multimodal": true
+ },
+ "image_processing_config": {
+ "model_id": ""
+ },
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
+ "vlm_model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e",
+ "vlm_config": {
+ "model_name": "qwen2.5vl:3b",
+ "base_url": "",
+ "api_key": "",
+ "interface_type": "ollama"
+ },
+ "cos_config": {
+ "secret_id": "",
+ "secret_key": "",
+ "region": "",
+ "bucket_name": "",
+ "app_id": "",
+ "path_prefix": ""
+ },
+ "created_at": "2025-08-12T11:30:09.206238+08:00",
+ "updated_at": "2025-08-12T11:36:09.083577609+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### DELETE `/knowledge-bases/:id` - 删除知识库
+
+**请求**:
+
+```curl
+curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/b5829e4a-3845-4624-a7fb-ea3b35e843b0' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ'
+```
+
+**响应**:
+
+```json
+{
+ "message": "Knowledge base deleted successfully",
+ "success": true
+}
+```
+
+#### GET `/knowledge-bases/:id/hybrid-search` - 混合搜索知识库内容
+
+**请求**:
+
+```curl
+curl --location --request GET 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/hybrid-search' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--data '{
+ "query_text": "彗星",
+ "vector_threshold": 0.1,
+ "keyword_threshold": 0.1,
+ "match_count": 1
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": [
+ {
+ "id": "7d955251-3f79-4fd5-a6aa-02f81e044091",
+ "content": "有几位后来xxxxx",
+ "knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
+ "chunk_index": 3,
+ "knowledge_title": "彗星.txt",
+ "start_at": 2287,
+ "end_at": 2760,
+ "seq": 3,
+ "score": 0.7402352891601821,
+ "match_type": 2,
+ "sub_chunk_id": null,
+ "metadata": {},
+ "chunk_type": "text",
+ "parent_chunk_id": "",
+ "image_info": "",
+ "knowledge_filename": "彗星.txt",
+ "knowledge_source": ""
+ }
+ ],
+ "success": true
+}
+```
+
+
+
+### 知识管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | ------------------------------------- | ------------------------ |
+| POST | `/knowledge-bases/:id/knowledge/file` | 从文件创建知识 |
+| POST | `/knowledge-bases/:id/knowledge/url` | 从 URL 创建知识 |
+| GET | `/knowledge-bases/:id/knowledge` | 获取知识库下的知识列表 |
+| GET | `/knowledge/:id` | 获取知识详情 |
+| DELETE | `/knowledge/:id` | 删除知识 |
+| GET | `/knowledge/:id/download` | 下载知识文件 |
+| PUT | `/knowledge/:id` | 更新知识 |
+| PUT | `/knowledge/image/:id/:chunk_id` | 更新图像分块信息 |
+| GET | `/knowledge/batch` | 批量获取知识 |
+
+#### POST `/knowledge-bases/:id/knowledge/file` - 从文件创建知识
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/knowledge/file' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--form 'file=@"/Users/xxxx/tests/彗星.txt"' \
+--form 'enable_multimodel="true"'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "type": "file",
+ "title": "彗星.txt",
+ "description": "",
+ "source": "",
+ "parse_status": "processing",
+ "enable_status": "disabled",
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "file_name": "彗星.txt",
+ "file_type": "txt",
+ "file_size": 7710,
+ "file_hash": "d69476ddbba45223a5e97e786539952c",
+ "file_path": "data/files/1/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/1754970756171067621.txt",
+ "storage_size": 0,
+ "metadata": null,
+ "created_at": "2025-08-12T11:52:36.168632288+08:00",
+ "updated_at": "2025-08-12T11:52:36.173612121+08:00",
+ "processed_at": null,
+ "error_message": "",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### POST `/knowledge-bases/:id/knowledge/url` - 从 URL 创建知识
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/knowledge/url' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json' \
+--data '{
+ "url":"https://github.com/Tencent/WeKnora",
+ "enable_multimodel":true
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "9c8af585-ae15-44ce-8f73-45ad18394651",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "type": "url",
+ "title": "",
+ "description": "",
+ "source": "https://github.com/Tencent/WeKnora",
+ "parse_status": "processing",
+ "enable_status": "disabled",
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "file_name": "",
+ "file_type": "",
+ "file_size": 0,
+ "file_hash": "",
+ "file_path": "",
+ "storage_size": 0,
+ "metadata": null,
+ "created_at": "2025-08-12T11:55:05.709266776+08:00",
+ "updated_at": "2025-08-12T11:55:05.712918234+08:00",
+ "processed_at": null,
+ "error_message": "",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### GET `/knowledge-bases/:id/knowledge?page=&page_size` - 获取知识库下的知识列表
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/knowledge?page_size=1&page=1' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "data": [
+ {
+ "id": "9c8af585-ae15-44ce-8f73-45ad18394651",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "type": "url",
+ "title": "",
+ "description": "",
+ "source": "https://github.com/Tencent/WeKnora",
+ "parse_status": "pending",
+ "enable_status": "disabled",
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "file_name": "",
+ "file_type": "",
+ "file_size": 0,
+ "file_hash": "",
+ "file_path": "",
+ "storage_size": 0,
+ "metadata": null,
+ "created_at": "2025-08-12T11:55:05.709266+08:00",
+ "updated_at": "2025-08-12T11:55:05.709266+08:00",
+ "processed_at": null,
+ "error_message": "",
+ "deleted_at": null
+ }
+ ],
+ "page": 1,
+ "page_size": 1,
+ "success": true,
+ "total": 2
+}
+```
+
+注:parse_status 包含 `pending/processing/failed/completed` 四种状态
+
+#### GET `/knowledge/:id` - 获取知识详情
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "type": "file",
+ "title": "彗星.txt",
+ "description": "彗星是由冰和尘埃构成的太阳系小天体,接近太阳时会形成彗发和彗尾。其轨道周期差异大,来源包括柯伊伯带和奥尔特云。彗星与小行星的区别逐渐模糊,部分彗星已失去挥发物质,类似小行星。截至2019年,已知彗星超6600颗,数量庞大。彗星在古代被视为凶兆,现代研究揭示其复杂结构与起源。",
+ "source": "",
+ "parse_status": "completed",
+ "enable_status": "enabled",
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "file_name": "彗星.txt",
+ "file_type": "txt",
+ "file_size": 7710,
+ "file_hash": "d69476ddbba45223a5e97e786539952c",
+ "file_path": "data/files/1/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/1754970756171067621.txt",
+ "storage_size": 33689,
+ "metadata": null,
+ "created_at": "2025-08-12T11:52:36.168632+08:00",
+ "updated_at": "2025-08-12T11:52:53.376871+08:00",
+ "processed_at": "2025-08-12T11:52:53.376573+08:00",
+ "error_message": "",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### GET `/knowledge/batch` - 批量获取知识
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge/batch?ids=9c8af585-ae15-44ce-8f73-45ad18394651&ids=4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "data": [
+ {
+ "id": "9c8af585-ae15-44ce-8f73-45ad18394651",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "type": "url",
+ "title": "",
+ "description": "",
+ "source": "https://github.com/Tencent/WeKnora",
+ "parse_status": "pending",
+ "enable_status": "disabled",
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "file_name": "",
+ "file_type": "",
+ "file_size": 0,
+ "file_hash": "",
+ "file_path": "",
+ "storage_size": 0,
+ "metadata": null,
+ "created_at": "2025-08-12T11:55:05.709266+08:00",
+ "updated_at": "2025-08-12T11:55:05.709266+08:00",
+ "processed_at": null,
+ "error_message": "",
+ "deleted_at": null
+ },
+ {
+ "id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "type": "file",
+ "title": "彗星.txt",
+ "description": "彗星是由冰和尘埃构成的太阳系小天体,接近太阳时会形成彗发和彗尾。其轨道周期差异大,来源包括柯伊伯带和奥尔特云。彗星与小行星的区别逐渐模糊,部分彗星已失去挥发物质,类似小行星。截至2019年,已知彗星超6600颗,数量庞大。彗星在古代被视为凶兆,现代研究揭示其复杂结构与起源。",
+ "source": "",
+ "parse_status": "completed",
+ "enable_status": "enabled",
+ "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "file_name": "彗星.txt",
+ "file_type": "txt",
+ "file_size": 7710,
+ "file_hash": "d69476ddbba45223a5e97e786539952c",
+ "file_path": "data/files/1/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/1754970756171067621.txt",
+ "storage_size": 33689,
+ "metadata": null,
+ "created_at": "2025-08-12T11:52:36.168632+08:00",
+ "updated_at": "2025-08-12T11:52:53.376871+08:00",
+ "processed_at": "2025-08-12T11:52:53.376573+08:00",
+ "error_message": "",
+ "deleted_at": null
+ }
+ ],
+ "success": true
+}
+```
+
+#### DELETE `/knowledge/:id` - 删除知识
+
+**请求**:
+
+```curl
+curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge/9c8af585-ae15-44ce-8f73-45ad18394651' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "message": "Deleted successfully",
+ "success": true
+}
+```
+
+#### GET `/knowledge/:id/download` - 下载知识文件
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/download' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```
+attachment
+```
+
+
+
+### 模型管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | --------------------- | --------------------- |
+| POST | `/models` | 创建模型 |
+| GET | `/models` | 获取模型列表 |
+| GET | `/models/:id` | 获取模型详情 |
+| PUT | `/models/:id` | 更新模型 |
+| DELETE | `/models/:id` | 删除模型 |
+
+#### POST `/models` - 创建模型
+
+创建对话模型(KnowledgeQA)请求体:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/models' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--data '{
+ "name": "qwen3:8b",
+ "type": "KnowledgeQA",
+ "source": "local",
+ "description": "LLM Model for Knowledge QA",
+ "parameters": {
+ "base_url": "",
+ "api_key": ""
+ },
+ "is_default": false
+}'
+```
+
+创建嵌入模型(Embedding)请求体:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/models' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--data '{
+ "name": "nomic-embed-text:latest",
+ "type": "Embedding",
+ "source": "local",
+ "description": "Embedding Model",
+ "parameters": {
+ "base_url": "",
+ "api_key": "",
+ "embedding_parameters": {
+ "dimension": 768,
+ "truncate_prompt_tokens": 0
+ }
+ },
+ "is_default": false
+}'
+```
+
+创建排序模型(Rerank)请求体:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/models' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--data '{
+ "name": "linux6200/bge-reranker-v2-m3:latest",
+ "type": "Rerank",
+ "source": "local",
+ "description": "Rerank Model for Knowledge QA",
+ "parameters": {
+ "base_url": "",
+ "api_key": ""
+ },
+ "is_default": false
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "09c5a1d6-ee8b-4657-9a17-d3dcbd5c70cb",
+ "tenant_id": 1,
+ "name": "nomic-embed-text:latest3",
+ "type": "Embedding",
+ "source": "local",
+ "description": "Embedding Model",
+ "parameters": {
+ "base_url": "",
+ "api_key": "",
+ "embedding_parameters": {
+ "dimension": 768,
+ "truncate_prompt_tokens": 0
+ }
+ },
+ "is_default": false,
+ "status": "downloading",
+ "created_at": "2025-08-12T10:39:01.454591766+08:00",
+ "updated_at": "2025-08-12T10:39:01.454591766+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### GET `/models` - 获取模型列表
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/models' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ'
+```
+
+**响应**:
+
+```json
+{
+ "data": [
+ {
+ "id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "tenant_id": 1,
+ "name": "nomic-embed-text:latest",
+ "type": "Embedding",
+ "source": "local",
+ "description": "Embedding Model",
+ "parameters": {
+ "base_url": "",
+ "api_key": "",
+ "embedding_parameters": {
+ "dimension": 768,
+ "truncate_prompt_tokens": 0
+ }
+ },
+ "is_default": true,
+ "status": "active",
+ "created_at": "2025-08-11T20:10:41.813832+08:00",
+ "updated_at": "2025-08-11T20:10:41.822354+08:00",
+ "deleted_at": null
+ },
+ {
+ "id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "tenant_id": 1,
+ "name": "qwen3:8b",
+ "type": "KnowledgeQA",
+ "source": "local",
+ "description": "LLM Model for Knowledge QA",
+ "parameters": {
+ "base_url": "",
+ "api_key": "",
+ "embedding_parameters": {
+ "dimension": 0,
+ "truncate_prompt_tokens": 0
+ }
+ },
+ "is_default": true,
+ "status": "active",
+ "created_at": "2025-08-11T20:10:41.811761+08:00",
+ "updated_at": "2025-08-11T20:10:41.825381+08:00",
+ "deleted_at": null
+ }
+ ],
+ "success": true
+}
+```
+
+#### GET `/models/:id` - 获取模型详情
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/models/dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
+ "tenant_id": 1,
+ "name": "nomic-embed-text:latest",
+ "type": "Embedding",
+ "source": "local",
+ "description": "Embedding Model",
+ "parameters": {
+ "base_url": "",
+ "api_key": "",
+ "embedding_parameters": {
+ "dimension": 768,
+ "truncate_prompt_tokens": 0
+ }
+ },
+ "is_default": true,
+ "status": "active",
+ "created_at": "2025-08-11T20:10:41.813832+08:00",
+ "updated_at": "2025-08-11T20:10:41.822354+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### PUT `/models/:id` - 更新模型
+
+**请求**:
+
+```curl
+curl --location --request PUT 'http://localhost:8080/api/v1/models/8fdc464d-8eaa-44d4-a85b-094b28af5330' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--data '{
+ "name": "linux6200/bge-reranker-v2-m3:latest",
+ "description": "Rerank Model for Knowledge QA new",
+ "parameters": {
+ "base_url": "",
+ "api_key": ""
+ },
+ "is_default": false
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "8fdc464d-8eaa-44d4-a85b-094b28af5330",
+ "tenant_id": 1,
+ "name": "linux6200/bge-reranker-v2-m3:latest",
+ "type": "Rerank",
+ "source": "local",
+ "description": "Rerank Model for Knowledge QA new",
+ "parameters": {
+ "base_url": "",
+ "api_key": "",
+ "embedding_parameters": {
+ "dimension": 0,
+ "truncate_prompt_tokens": 0
+ }
+ },
+ "is_default": false,
+ "status": "active",
+ "created_at": "2025-08-12T10:57:39.512681+08:00",
+ "updated_at": "2025-08-12T11:00:27.271678+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### DELETE `/models/:id` - 删除模型
+
+**请求**:
+
+```curl
+curl --location --request DELETE 'http://localhost:8080/api/v1/models/8fdc464d-8eaa-44d4-a85b-094b28af5330' \
+--header 'Content-Type: application/json' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ'
+```
+
+**响应**:
+
+```json
+{
+ "message": "Model deleted",
+ "success": true
+}
+```
+
+
+
+### 分块管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | --------------------------- | ------------------------ |
+| GET | `/chunks/:knowledge_id` | 获取知识的分块列表 |
+| DELETE | `/chunks/:knowledge_id/:id` | 删除分块 |
+| DELETE | `/chunks/:knowledge_id` | 删除知识下的所有分块 |
+
+#### GET `/chunks/:knowledge_id?page=&page_size=` - 获取知识的分块列表
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5?page=1&page_size=1' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "data": [
+ {
+ "id": "df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7",
+ "tenant_id": 0,
+ "knowledge_id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5",
+ "knowledge_base_id": "kb-00000001",
+ "content": "彗星xxxx",
+ "chunk_index": 0,
+ "is_enabled": true,
+ "start_at": 0,
+ "end_at": 964,
+ "pre_chunk_id": "",
+ "next_chunk_id": "",
+ "chunk_type": "text",
+ "parent_chunk_id": "",
+ "relation_chunks": null,
+ "indirect_relation_chunks": null,
+ "image_info": "",
+ "created_at": "0001-01-01T00:00:00Z",
+ "updated_at": "0001-01-01T00:00:00Z",
+ "deleted_at": null
+ }
+ ],
+ "page": 1,
+ "page_size": 1,
+ "success": true,
+ "total": 5
+}
+```
+
+#### DELETE `/chunks/:knowledge_id/:id` - 删除分块
+
+**请求**:
+
+```curl
+curl --location --request DELETE 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "message": "Chunk deleted",
+ "success": true
+}
+```
+
+#### DELETE `/chunks/:knowledge_id` - 删除知识下的所有分块
+
+**请求**:
+
+```curl
+curl --location --request DELETE 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "message": "All chunks under knowledge deleted",
+ "success": true
+}
+```
+
+
+
+### 会话管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | --------------------------------------- | --------------------- |
+| POST | `/sessions` | 创建会话 |
+| GET | `/sessions/:id` | 获取会话详情 |
+| GET | `/sessions` | 获取租户的会话列表 |
+| PUT | `/sessions/:id` | 更新会话 |
+| DELETE | `/sessions/:id` | 删除会话 |
+| POST | `/sessions/:session_id/generate_title` | 生成会话标题 |
+| GET | `/sessions/continue-stream/:session_id` | 继续未完成的会话 |
+
+#### POST `/sessions` - 创建会话
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/sessions' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json' \
+--data '{
+ "knowledge_base_id": "kb-00000001",
+ "session_strategy": {
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "FIXED_RESPONSE",
+ "fallback_response": "对不起,我无法回答这个问题",
+ "embedding_top_k": 10,
+ "keyword_threshold": 0.5,
+ "vector_threshold": 0.7,
+ "rerank_model_id": "排序模型ID",
+ "rerank_top_k": 3,
+ "rerank_threshold": 0.7,
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "summary_parameters": {
+ "max_tokens": 0,
+ "repeat_penalty": 1,
+ "top_k": 0,
+ "top_p": 0,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "prompt": "这是用户和助手之间的对话。xxx",
+ "context_template": "你是一个专业的智能信息检索助手xxx",
+ "no_match_prefix": "\n\nNO_MATCH",
+ "temperature": 0.3,
+ "seed": 0,
+ "max_completion_tokens": 2048
+ },
+ "no_match_prefix": "\n\nNO_MATCH"
+ }
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "411d6b70-9a85-4d03-bb74-aab0fd8bd12f",
+ "title": "",
+ "description": "",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "FIXED_RESPONSE",
+ "fallback_response": "对不起,我无法回答这个问题",
+ "embedding_top_k": 10,
+ "keyword_threshold": 0.5,
+ "vector_threshold": 0.7,
+ "rerank_model_id": "排序模型ID",
+ "rerank_top_k": 3,
+ "rerank_threshold": 0.7,
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "summary_parameters": {
+ "max_tokens": 0,
+ "repeat_penalty": 1,
+ "top_k": 0,
+ "top_p": 0,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "prompt": "这是用户和助手之间的对话。xxx",
+ "context_template": "你是一个专业的智能信息检索助手xxx",
+ "no_match_prefix": "\n\nNO_MATCH",
+ "temperature": 0.3,
+ "seed": 0,
+ "max_completion_tokens": 2048
+ },
+ "created_at": "2025-08-12T12:26:19.611616669+08:00",
+ "updated_at": "2025-08-12T12:26:19.611616919+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### GET `/sessions/:id` - 获取会话详情
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/sessions/ceb9babb-1e30-41d7-817d-fd584954304b' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "ceb9babb-1e30-41d7-817d-fd584954304b",
+ "title": "模型优化策略",
+ "description": "",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "fixed",
+ "fallback_response": "抱歉,我无法回答这个问题。",
+ "embedding_top_k": 10,
+ "keyword_threshold": 0.3,
+ "vector_threshold": 0.5,
+ "rerank_model_id": "",
+ "rerank_top_k": 5,
+ "rerank_threshold": 0.7,
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "summary_parameters": {
+ "max_tokens": 0,
+ "repeat_penalty": 1,
+ "top_k": 0,
+ "top_p": 0,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "prompt": "这是用户和助手之间的对话",
+ "context_template": "你是一个专业的智能信息检索助手",
+ "no_match_prefix": "\n\nNO_MATCH",
+ "temperature": 0.3,
+ "seed": 0,
+ "max_completion_tokens": 2048
+ },
+ "created_at": "2025-08-12T10:24:38.308596+08:00",
+ "updated_at": "2025-08-12T10:25:41.317761+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### GET `/sessions?page=&page_size=` - 获取租户的会话列表
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/sessions?page=1&page_size=1' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "data": [
+ {
+ "id": "411d6b70-9a85-4d03-bb74-aab0fd8bd12f",
+ "title": "",
+ "description": "",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "FIXED_RESPONSE",
+ "fallback_response": "对不起,我无法回答这个问题",
+ "embedding_top_k": 10,
+ "keyword_threshold": 0.5,
+ "vector_threshold": 0.7,
+ "rerank_model_id": "排序模型ID",
+ "rerank_top_k": 3,
+ "rerank_threshold": 0.7,
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "summary_parameters": {
+ "max_tokens": 0,
+ "repeat_penalty": 1,
+ "top_k": 0,
+ "top_p": 0,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "prompt": "这是用户和助手之间的对话。xxx",
+ "context_template": "你是一个专业的智能信息检索助手xxx",
+ "no_match_prefix": "\n\nNO_MATCH",
+ "temperature": 0.3,
+ "seed": 0,
+ "max_completion_tokens": 2048
+ },
+ "created_at": "2025-08-12T12:26:19.611616+08:00",
+ "updated_at": "2025-08-12T12:26:19.611616+08:00",
+ "deleted_at": null
+ }
+ ],
+ "page": 1,
+ "page_size": 1,
+ "success": true,
+ "total": 2
+}
+```
+
+#### PUT `/sessions/:id` - 更新会话
+
+**请求**:
+
+```curl
+curl --location --request PUT 'http://localhost:8080/api/v1/sessions/411d6b70-9a85-4d03-bb74-aab0fd8bd12f' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json' \
+--data '{
+ "title": "weknora",
+ "description": "weknora description",
+ "knowledge_base_id": "kb-00000001",
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "FIXED_RESPONSE",
+ "fallback_response": "对不起,我无法回答这个问题",
+ "embedding_top_k": 10,
+ "keyword_threshold": 0.5,
+ "vector_threshold": 0.7,
+ "rerank_model_id": "排序模型ID",
+ "rerank_top_k": 3,
+ "rerank_threshold": 0.7,
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "summary_parameters": {
+ "max_tokens": 0,
+ "repeat_penalty": 1,
+ "top_k": 0,
+ "top_p": 0,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "prompt": "这是用户和助手之间的对话。xxx",
+ "context_template": "你是一个专业的智能信息检索助手xxx",
+ "no_match_prefix": "\n\nNO_MATCH",
+ "temperature": 0.3,
+ "seed": 0,
+ "max_completion_tokens": 2048
+ }
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "id": "411d6b70-9a85-4d03-bb74-aab0fd8bd12f",
+ "title": "weknora",
+ "description": "weknora description",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000001",
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "FIXED_RESPONSE",
+ "fallback_response": "对不起,我无法回答这个问题",
+ "embedding_top_k": 10,
+ "keyword_threshold": 0.5,
+ "vector_threshold": 0.7,
+ "rerank_model_id": "排序模型ID",
+ "rerank_top_k": 3,
+ "rerank_threshold": 0.7,
+ "summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "summary_parameters": {
+ "max_tokens": 0,
+ "repeat_penalty": 1,
+ "top_k": 0,
+ "top_p": 0,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "prompt": "这是用户和助手之间的对话。xxx",
+ "context_template": "你是一个专业的智能信息检索助手xxx",
+ "no_match_prefix": "\n\nNO_MATCH",
+ "temperature": 0.3,
+ "seed": 0,
+ "max_completion_tokens": 2048
+ },
+ "created_at": "0001-01-01T00:00:00Z",
+ "updated_at": "2025-08-12T14:20:56.738424351+08:00",
+ "deleted_at": null
+ },
+ "success": true
+}
+```
+
+#### DELETE `/sessions/:id` - 删除会话
+
+**请求**:
+
+```curl
+curl --location --request DELETE 'http://localhost:8080/api/v1/sessions/411d6b70-9a85-4d03-bb74-aab0fd8bd12f' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "message": "Session deleted successfully",
+ "success": true
+}
+```
+
+#### POST `/sessions/:session_id/generate_title` - 生成会话标题
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/sessions/ceb9babb-1e30-41d7-817d-fd584954304b/generate_title' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json' \
+--data '{
+ "messages": [
+ {
+ "role": "user",
+ "content": "你好,我想了解关于人工智能的知识"
+ },
+ {
+ "role": "assistant",
+ "content": "人工智能是计算机科学的一个分支..."
+ }
+ ]
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": "模型优化策略",
+ "success": true
+}
+```
+
+#### GET `/sessions/continue-stream/:session_id` - 继续未完成的会话
+
+**查询参数**:
+- `message_id`: 从 `/messages/:session_id/load` 接口中获取的 `is_completed` 为 `false` 的消息 ID
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/sessions/continue-stream/ceb9babb-1e30-41d7-817d-fd584954304b?message_id=b8b90eeb-7dd5-4cf9-81c6-5ebcbd759451' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应格式**:
+服务器端事件流(Server-Sent Events),与 `/knowledge-chat/:session_id` 返回结果一致
+
+
+
+### 聊天功能API
+
+| 方法 | 路径 | 描述 |
+| ---- | ----------------------------- | ------------------------ |
+| POST | `/knowledge-chat/:session_id` | 基于知识库的问答 |
+| POST | `/knowledge-search` | 基于知识库的搜索知识 |
+
+#### POST `/knowledge-chat/:session_id` - 基于知识库的问答
+
+**请求**:
+
+```curl
+curl --location 'http://localhost:8080/api/v1/knowledge-chat/ceb9babb-1e30-41d7-817d-fd584954304b' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json' \
+--data '{
+ "query": "彗尾的形状"
+}'
+```
+
+**响应格式**:
+服务器端事件流(Server-Sent Events,Content-Type: text/event-stream)
+
+**响应**:
+
+```
+event: message
+data: {"id":"3475c004-0ada-4306-9d30-d7f5efce50d2","response_type":"references","content":"","done":false,"knowledge_references":[{"id":"c8347bef-127f-4a22-b962-edf5a75386ec","content":"彗星xxx。","knowledge_id":"a6790b93-4700-4676-bd48-0d4804e1456b","chunk_index":0,"knowledge_title":"彗星.txt","start_at":0,"end_at":2760,"seq":0,"score":4.038836479187012,"match_type":3,"sub_chunk_id":["688821f0-40bf-428e-8cb6-541531ebeb76","c1e9903e-2b4d-4281-be15-0149288d45c2","7d955251-3f79-4fd5-a6aa-02f81e044091"],"metadata":{},"chunk_type":"text","parent_chunk_id":"","image_info":"","knowledge_filename":"彗星.txt","knowledge_source":""},{"id":"fa3aadee-cadb-4a84-9941-c839edc3e626","content":"# 文档名称\n彗星.txt\n\n# 摘要\n彗星是由冰和尘埃构成的太阳系小天体,接近太阳时会释放气体形成彗发和彗尾。其轨道周期差异大,来源包括柯伊伯带和奥尔特云。彗星与小行星的区别逐渐模糊,部分彗星已失去挥发物质,类似小行星。目前已知彗星数量众多,且存在系外彗星。彗星在古代被视为凶兆,现代研究揭示其复杂结构与起源。","knowledge_id":"a6790b93-4700-4676-bd48-0d4804e1456b","chunk_index":6,"knowledge_title":"彗星.txt","start_at":0,"end_at":0,"seq":6,"score":0.6131043121858466,"match_type":3,"sub_chunk_id":null,"metadata":{},"chunk_type":"summary","parent_chunk_id":"c8347bef-127f-4a22-b962-edf5a75386ec","image_info":"","knowledge_filename":"彗星.txt","knowledge_source":""}]}
+
+event: message
+data: {"id":"3475c004-0ada-4306-9d30-d7f5efce50d2","response_type":"answer","content":"表现为","done":false,"knowledge_references":null}
+
+event: message
+data: {"id":"3475c004-0ada-4306-9d30-d7f5efce50d2","response_type":"answer","content":"结构","done":false,"knowledge_references":null}
+
+event: message
+data: {"id":"3475c004-0ada-4306-9d30-d7f5efce50d2","response_type":"answer","content":"。","done":false,"knowledge_references":null}
+
+event: message
+data: {"id":"3475c004-0ada-4306-9d30-d7f5efce50d2","response_type":"answer","content":"","done":true,"knowledge_references":null}
+```
+
+
+
+### 消息管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | ---------------------------- | ------------------------ |
+| GET | `/messages/:session_id/load` | 获取最近的会话消息列表 |
+| DELETE | `/messages/:session_id/:id` | 删除消息 |
+
+#### GET `/messages/:session_id/load?before_time=2025-04-18T11:57:31.310671+08:00&limit=20` - 获取最近的会话消息列表
+
+**查询参数**:
+
+- `before_time`: 上一次拉取的最早一条消息的 created_at 字段,为空拉取最近的消息
+- `limit`: 每页条数(默认 20)
+
+**请求**:
+
+```curl
+curl --location --request GET 'http://localhost:8080/api/v1/messages/ceb9babb-1e30-41d7-817d-fd584954304b/load?limit=3&before_time=2030-08-12T14%3A35%3A42.123456789Z' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json' \
+--data '{
+ "query": "彗尾的形状"
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": [
+ {
+ "id": "b8b90eeb-7dd5-4cf9-81c6-5ebcbd759451",
+ "session_id": "ceb9babb-1e30-41d7-817d-fd584954304b",
+ "request_id": "hCA8SDjxcAvv",
+ "content": "\n好的",
+ "role": "assistant",
+ "knowledge_references": [
+ {
+ "id": "c8347bef-127f-4a22-b962-edf5a75386ec",
+ "content": "彗星xxx",
+ "knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
+ "chunk_index": 0,
+ "knowledge_title": "彗星.txt",
+ "start_at": 0,
+ "end_at": 2760,
+ "seq": 0,
+ "score": 4.038836479187012,
+ "match_type": 4,
+ "sub_chunk_id": [
+ "688821f0-40bf-428e-8cb6-541531ebeb76",
+ "c1e9903e-2b4d-4281-be15-0149288d45c2",
+ "7d955251-3f79-4fd5-a6aa-02f81e044091"
+ ],
+ "metadata": {},
+ "chunk_type": "text",
+ "parent_chunk_id": "",
+ "image_info": "",
+ "knowledge_filename": "彗星.txt",
+ "knowledge_source": ""
+ },
+ {
+ "id": "fa3aadee-cadb-4a84-9941-c839edc3e626",
+ "content": "# 文档名称\n彗星.txt\n\n# 摘要\n彗星是由冰和尘埃构成的太阳系小天体,接近太阳时会释放气体形成彗发和彗尾。其轨道周期差异大,来源包括柯伊伯带和奥尔特云。彗星与小行星的区别逐渐模糊,部分彗星已失去挥发物质,类似小行星。目前已知彗星数量众多,且存在系外彗星。彗星在古代被视为凶兆,现代研究揭示其复杂结构与起源。",
+ "knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
+ "chunk_index": 6,
+ "knowledge_title": "彗星.txt",
+ "start_at": 0,
+ "end_at": 0,
+ "seq": 6,
+ "score": 0.6131043121858466,
+ "match_type": 0,
+ "sub_chunk_id": null,
+ "metadata": {},
+ "chunk_type": "summary",
+ "parent_chunk_id": "c8347bef-127f-4a22-b962-edf5a75386ec",
+ "image_info": "",
+ "knowledge_filename": "彗星.txt",
+ "knowledge_source": ""
+ }
+ ],
+ "is_completed": true,
+ "created_at": "2025-08-12T10:24:38.370548+08:00",
+ "updated_at": "2025-08-12T10:25:40.416382+08:00",
+ "deleted_at": null
+ },
+ {
+ "id": "7fa136ae-a045-424e-baac-52113d92ae94",
+ "session_id": "ceb9babb-1e30-41d7-817d-fd584954304b",
+ "request_id": "3475c004-0ada-4306-9d30-d7f5efce50d2",
+ "content": "彗尾的形状",
+ "role": "user",
+ "knowledge_references": [],
+ "is_completed": true,
+ "created_at": "2025-08-12T14:30:39.732246+08:00",
+ "updated_at": "2025-08-12T14:30:39.733277+08:00",
+ "deleted_at": null
+ },
+ {
+ "id": "9bcafbcf-a758-40af-a9a3-c4d8e0f49439",
+ "session_id": "ceb9babb-1e30-41d7-817d-fd584954304b",
+ "request_id": "3475c004-0ada-4306-9d30-d7f5efce50d2",
+ "content": "\n好的",
+ "role": "assistant",
+ "knowledge_references": [
+ {
+ "id": "c8347bef-127f-4a22-b962-edf5a75386ec",
+ "content": "彗星xxx",
+ "knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
+ "chunk_index": 0,
+ "knowledge_title": "彗星.txt",
+ "start_at": 0,
+ "end_at": 2760,
+ "seq": 0,
+ "score": 4.038836479187012,
+ "match_type": 3,
+ "sub_chunk_id": [
+ "688821f0-40bf-428e-8cb6-541531ebeb76",
+ "c1e9903e-2b4d-4281-be15-0149288d45c2",
+ "7d955251-3f79-4fd5-a6aa-02f81e044091"
+ ],
+ "metadata": {},
+ "chunk_type": "text",
+ "parent_chunk_id": "",
+ "image_info": "",
+ "knowledge_filename": "彗星.txt",
+ "knowledge_source": ""
+ },
+ {
+ "id": "fa3aadee-cadb-4a84-9941-c839edc3e626",
+ "content": "# 文档名称\n彗星.txt\n\n# 摘要\n彗星是由冰和尘埃构成的太阳系小天体,接近太阳时会释放气体形成彗发和彗尾。其轨道周期差异大,来源包括柯伊伯带和奥尔特云。彗星与小行星的区别逐渐模糊,部分彗星已失去挥发物质,类似小行星。目前已知彗星数量众多,且存在系外彗星。彗星在古代被视为凶兆,现代研究揭示其复杂结构与起源。",
+ "knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
+ "chunk_index": 6,
+ "knowledge_title": "彗星.txt",
+ "start_at": 0,
+ "end_at": 0,
+ "seq": 6,
+ "score": 0.6131043121858466,
+ "match_type": 3,
+ "sub_chunk_id": null,
+ "metadata": {},
+ "chunk_type": "summary",
+ "parent_chunk_id": "c8347bef-127f-4a22-b962-edf5a75386ec",
+ "image_info": "",
+ "knowledge_filename": "彗星.txt",
+ "knowledge_source": ""
+ }
+ ],
+ "is_completed": true,
+ "created_at": "2025-08-12T14:30:39.735108+08:00",
+ "updated_at": "2025-08-12T14:31:17.829926+08:00",
+ "deleted_at": null
+ }
+ ],
+ "success": true
+}
+```
+
+#### DELETE `/messages/:session_id/:id` - 删除消息
+
+**请求**:
+
+```curl
+curl --location --request DELETE 'http://localhost:8080/api/v1/messages/ceb9babb-1e30-41d7-817d-fd584954304b/9bcafbcf-a758-40af-a9a3-c4d8e0f49439' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "message": "Message deleted successfully",
+ "success": true
+}
+```
+
+
+
+### 评估功能API
+
+| 方法 | 路径 | 描述 |
+| ---- | ------------- | --------------------- |
+| GET | `/evaluation` | 获取评估任务 |
+| POST | `/evaluation` | 创建评估任务 |
+
+#### GET `/evaluation` - 获取评估任务
+
+**请求参数**:
+- `task_id`: 从 `POST /evaluation` 接口中获取到的任务 ID
+- `X-API-Key`: 用户 API Key
+
+**请求**:
+
+```bash
+curl --location 'http://localhost:8080/api/v1/evaluation?task_id=c34563ad-b09f-4858-b72e-e92beb80becb' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "task": {
+ "id": "c34563ad-b09f-4858-b72e-e92beb80becb",
+ "tenant_id": 1,
+ "dataset_id": "default",
+ "start_time": "2025-08-12T14:54:26.221804768+08:00",
+ "status": 2,
+ "total": 1,
+ "finished": 1
+ },
+ "params": {
+ "session_id": "",
+ "knowledge_base_id": "2ef57434-8c8d-4442-b967-2f7fc578a2fc",
+ "vector_threshold": 0.5,
+ "keyword_threshold": 0.3,
+ "embedding_top_k": 10,
+ "vector_database": "",
+ "rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
+ "rerank_top_k": 5,
+ "rerank_threshold": 0.7,
+ "chat_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "summary_config": {
+ "max_tokens": 0,
+ "repeat_penalty": 1,
+ "top_k": 0,
+ "top_p": 0,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "prompt": "这是用户和助手之间的对话。",
+ "context_template": "你是一个专业的智能信息检索助手",
+ "no_match_prefix": "\n\nNO_MATCH",
+ "temperature": 0.3,
+ "seed": 0,
+ "max_completion_tokens": 2048
+ },
+ "fallback_strategy": "",
+ "fallback_response": "抱歉,我无法回答这个问题。"
+ },
+ "metric": {
+ "retrieval_metrics": {
+ "precision": 0,
+ "recall": 0,
+ "ndcg3": 0,
+ "ndcg10": 0,
+ "mrr": 0,
+ "map": 0
+ },
+ "generation_metrics": {
+ "bleu1": 0.037656734016532384,
+ "bleu2": 0.04067392145167686,
+ "bleu4": 0.048963321289052536,
+ "rouge1": 0,
+ "rouge2": 0,
+ "rougel": 0
+ }
+ }
+ },
+ "success": true
+}
+```
+
+#### POST `/evaluation` - 创建评估任务
+
+**请求参数**:
+- `dataset_id`: 评估使用的数据集,暂时只支持官方测试数据集 `default`
+- `knowledge_base_id`: 评估使用的知识库
+- `chat_id`: 评估使用的对话模型
+- `rerank_id`: 评估使用的重排序模型
+
+**请求**:
+
+```bash
+curl --location 'http://localhost:8080/api/v1/evaluation' \
+--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \
+--header 'Content-Type: application/json' \
+--data '{
+ "dataset_id": "default",
+ "knowledge_base_id": "kb-00000001",
+ "chat_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "rerank_id": "b30171a1-787b-426e-a293-735cd5ac16c0"
+}'
+```
+
+**响应**:
+
+```json
+{
+ "data": {
+ "task": {
+ "id": "c34563ad-b09f-4858-b72e-e92beb80becb",
+ "tenant_id": 1,
+ "dataset_id": "default",
+ "start_time": "2025-08-12T14:54:26.221804768+08:00",
+ "status": 1
+ },
+ "params": {
+ "session_id": "",
+ "knowledge_base_id": "2ef57434-8c8d-4442-b967-2f7fc578a2fc",
+ "vector_threshold": 0.5,
+ "keyword_threshold": 0.3,
+ "embedding_top_k": 10,
+ "vector_database": "",
+ "rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
+ "rerank_top_k": 5,
+ "rerank_threshold": 0.7,
+ "chat_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
+ "summary_config": {
+ "max_tokens": 0,
+ "repeat_penalty": 1,
+ "top_k": 0,
+ "top_p": 0,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "prompt": "这是用户和助手之间的对话。",
+ "context_template": "你是一个专业的智能信息检索助手,xxx",
+ "no_match_prefix": "\n\nNO_MATCH",
+ "temperature": 0.3,
+ "seed": 0,
+ "max_completion_tokens": 2048
+ },
+ "fallback_strategy": "",
+ "fallback_response": "抱歉,我无法回答这个问题。"
+ }
+ },
+ "success": true
+}
+```
+
+
\ No newline at end of file
diff --git a/docs/QA.md b/docs/QA.md
new file mode 100644
index 0000000..fd24773
--- /dev/null
+++ b/docs/QA.md
@@ -0,0 +1,100 @@
+# 常见问题
+
+## 1. 如何查看日志?
+```bash
+docker compose logs -f app docreader postgres
+```
+
+## 2. 如何启动和停止服务?
+```bash
+# 启动服务
+./scripts/start_all.sh
+
+# 停止服务
+./scripts/start_all.sh --stop
+
+# 清空数据库
+./scripts/start_all.sh --stop && make clean-db
+```
+
+## 3. 服务启动后无法正常上传文档?
+
+通常是Embedding模型和对话模型没有正确被设置导致。按照以下步骤进行排查
+
+1. 查看`.env`配置中的模型信息是否配置完整,其中如果使用ollama访问本地模型,需要确保本地ollama服务正常运行,同时在`.env`中的如下环境变量需要正确设置:
+```bash
+# LLM Model
+INIT_LLM_MODEL_NAME=your_llm_model
+# Embedding Model
+INIT_EMBEDDING_MODEL_NAME=your_embedding_model
+# Embedding模型向量维度
+INIT_EMBEDDING_MODEL_DIMENSION=your_embedding_model_dimension
+# Embedding模型的ID,通常是一个字符串
+INIT_EMBEDDING_MODEL_ID=your_embedding_model_id
+```
+
+如果是通过remote api访问模型,则需要额外提供对应的`BASE_URL`和`API_KEY`:
+```bash
+# LLM模型的访问地址
+INIT_LLM_MODEL_BASE_URL=your_llm_model_base_url
+# LLM模型的API密钥,如果需要身份验证,可以设置
+INIT_LLM_MODEL_API_KEY=your_llm_model_api_key
+# Embedding模型的访问地址
+INIT_EMBEDDING_MODEL_BASE_URL=your_embedding_model_base_url
+# Embedding模型的API密钥,如果需要身份验证,可以设置
+INIT_EMBEDDING_MODEL_API_KEY=your_embedding_model_api_key
+```
+
+当需要重排序功能时,需要额外配置Rerank模型,具体配置如下:
+```bash
+# 使用的Rerank模型名称
+INIT_RERANK_MODEL_NAME=your_rerank_model_name
+# Rerank模型的访问地址
+INIT_RERANK_MODEL_BASE_URL=your_rerank_model_base_url
+# Rerank模型的API密钥,如果需要身份验证,可以设置
+INIT_RERANK_MODEL_API_KEY=your_rerank_model_api_key
+```
+
+2. 查看主服务日志,是否有`ERROR`日志输出
+
+## 4. 如何开启多模态功能?
+1. 确保 `.env` 如下配置被正确设置:
+```bash
+# VLM_MODEL_NAME 使用的多模态模型名称
+VLM_MODEL_NAME=your_vlm_model_name
+
+# VLM_MODEL_BASE_URL 使用的多模态模型访问地址
+VLM_MODEL_BASE_URL=your_vlm_model_base_url
+
+# VLM_MODEL_API_KEY 使用的多模态模型API密钥
+VLM_MODEL_API_KEY=your_vlm_model_api_key
+```
+注:多模态大模型当前仅支持remote api访问,固需要提供`VLM_MODEL_BASE_URL`和`VLM_MODEL_API_KEY`
+
+2. 解析后的文件需要上传到COS中,确保 `.env` 中 `COS` 信息正确设置:
+```bash
+# 腾讯云COS的访问密钥ID
+COS_SECRET_ID=your_cos_secret_id
+
+# 腾讯云COS的密钥
+COS_SECRET_KEY=your_cos_secret_key
+
+# 腾讯云COS的区域,例如 ap-guangzhou
+COS_REGION=your_cos_region
+
+# 腾讯云COS的桶名称
+COS_BUCKET_NAME=your_cos_bucket_name
+
+# 腾讯云COS的应用ID
+COS_APP_ID=your_cos_app_id
+
+# 腾讯云COS的路径前缀,用于存储文件
+COS_PATH_PREFIX=your_cos_path_prefix
+```
+重要:务必将COS中文件的权限设置为**公有读**,否则文档解析模块无法正常解析文件
+
+3. 查看文档解析模块日志,查看OCR和Caption是否正确解析和打印
+
+
+## P.S.
+如果以上方式未解决问题,请在issue中描述您的问题,并提供必要的日志信息辅助我们进行问题排查
\ No newline at end of file
diff --git a/docs/WeKnora.md b/docs/WeKnora.md
new file mode 100644
index 0000000..d5d2caf
--- /dev/null
+++ b/docs/WeKnora.md
@@ -0,0 +1,319 @@
+## 介绍
+WeKora 是一个可立即在生产环境投入的企业级RAG框架,实现智能文档理解和检索功能。该系统采用模块化设计,将文档理解、向量存储、推理文件等功能分离。
+
+
+
+---
+
+## PipeLine
+WeKnora 处理文档需要多个步骤:插入-》知识提取-》索引-》检索-》生成,整个流程支持多种检索方法,
+
+
+
+
+以用户上传的一张住宿流水单pdf文件为例,详细介绍下其数据流:
+
+### 1. 接收请求与初始化
++ **请求识别**: 系统收到一个请求,并为其分配了唯一的 `request_id=Lkq0OGLYu2fV`,用于追踪整个处理流程。
++ **租户与会话验证**:
+ - 系统首先验证了租户信息(ID: 1, Name: Default Tenant)。
+ - 接着开始处理一个知识库问答(Knowledge QA)请求,该请求属于会话 `1f241340-ae75-40a5-8731-9a3a82e34fdd`。
++ **用户问题**: 用户的原始问题是:“**入住的房型是什么**”。
++ **消息创建**: 系统为用户的提问和即将生成的回答分别创建了消息记录,ID 分别为 `703ddf09-...` 和 `6f057649-...`。
+
+### 2. 知识库问答流程启动
+系统正式调用知识库问答服务,并定义了将要按顺序执行的完整处理管道(Pipeline),包含以下9个事件:
+`[rewrite_query, preprocess_query, chunk_search, chunk_rerank, chunk_merge, filter_top_k, into_chat_message, chat_completion_stream, stream_filter]`
+
+---
+
+### 3. 事件执行详情
+#### 事件 1: `rewrite_query` - 问题改写
++ **目的**: 为了让检索更精确,系统需要结合上下文来理解用户的真实意图。
++ **操作**:
+ 1. 系统检索了当前会话最近的20条历史消息(实际检索到8条)作为上下文。
+ 2. 调用了一个名为 `deepseek-r1:7b` 的本地大语言模型。
+ 3. 模型根据聊天历史分析出提问者是“Liwx”,并将原问题“入住的房型是什么”改写得更具体。
++ **结果**: 问题被成功改写为:“**Liwx本次入住的房型是什么**”。
+
+#### 事件 2: `preprocess_query` - 问题预处理
++ **目的**: 将改写后的问题进行分词,转换为适合搜索引擎处理的关键词序列。
++ **操作**: 对改写后的问题进行了分词处理。
++ **结果**: 生成了一串关键词:“`需要 改写 用户 问题 入住 房型 根据 提供 信息 入住 人 Liwx 选择 房型 双床 房 因此 改写 后 完整 问题 为 Liwx 本次 入住 房型`”。
+
+#### 事件 3: `chunk_search` - 知识区块检索
+这是最核心的**检索(Retrieval)**步骤,系统执行了两次混合搜索(Hybrid Search)。
+
++ **第一次搜索 (使用改写后的完整问句)**:
+ - **向量检索**:
+ 1. 加载嵌入模型 `bge-m3:latest` 将问句转换为一个1024维的向量。
+ 2. 在PostgreSQL数据库中进行向量相似度搜索,找到了2个相关的知识区块(chunk),ID 分别为 `e3bf6599-...` 和 `3989c6ce-...`。
+ - **关键词检索**:
+ 1. 同时,系统也进行了关键词搜索。
+ 2. 同样找到了上述2个知识区块。
+ - **结果合并**: 两种方法找到的4个结果(实际是2个重复的)被去重,最终得到2个唯一的知识区块。
++ **第二次搜索 (使用预处理后的关键词序列)**:
+ - 系统使用分词后的关键词重复了上述的**向量检索**和**关键词检索**过程。
+ - 最终也得到了相同的2个知识区块。
++ **最终结果**: 经过两次搜索和结果合并,系统锁定了2个最相关的知识区块,并将它们的内容提取出来,准备用于生成答案。
+
+#### 事件 4: `chunk_rerank` - 结果重排序
++ **目的**: 使用一个更强大的模型对初步检索出的结果进行更精细的排序,以提高最终答案的质量。
++ **操作**: 日志显示 `Rerank model ID is empty, skipping reranking`。这意味着系统配置了重排序步骤,但没有指定具体的重排序模型,因此**跳过了此步骤**。
+
+#### 事件 5: `chunk_merge` - 区块合并
++ **目的**: 将内容上相邻或相关的知识区块进行合并,形成更完整的上下文。
++ **操作**: 系统分析了检索到的2个区块,并尝试进行合并。根据日志,最终处理后仍然是2个独立的区块,但已按相关性分数排好序。
+
+#### 事件 6: `filter_top_k` - Top-K 过滤
++ **目的**: 仅保留最相关的K个结果,防止过多无关信息干扰语言模型。
++ **操作**: 系统配置保留前5个(Top-K = 5)最相关的区块。由于当前只有2个区块,它们全部通过了此过滤器。
+
+#### 事件 7 & 8: `into_chat_message` & `chat_completion_stream` - 生成回答
+这是**生成(Generation)**步骤。
+
++ **目的**: 基于检索到的信息,生成自然流畅的回答。
++ **操作**:
+ 1. 系统将检索到的2个知识区块的内容、用户的原始问题以及聊天历史整合在一起,形成一个完整的提示(Prompt)。
+ 2. 再次调用 `deepseek-r1:7b` 大语言模型,并以**流式(Stream)**的方式请求生成答案。流式输出可以实现打字机效果,提升用户体验。
+
+#### 事件 9: `stream_filter` - 流式输出过滤
++ **目的**: 对模型生成的实时文本流进行后处理,过滤掉不需要的特殊标记或内容。
++ **操作**:
+ - 系统设置了一个过滤器,用于移除模型在思考过程中可能产生的内部标记,如 `` 和 ``。
+ - 日志显示,模型输出的第一个词块是 ` 根据`,过滤器成功拦截并移除了 `` 标记,只将“根据”及之后的内容传递下去。
+
+### 4. 完成与响应
++ **发送引用**: 在生成答案的同时,系统将作为依据的2个知识区块作为“参考内容”发送给前端,以便用户查证来源。
++ **更新消息**: 当模型生成完所有内容后,系统将完整的回答更新到之前创建的消息记录(ID: `6f057649-...`)中。
++ **请求结束**: 服务器返回 `200` 成功状态码,标志着本次从提问到回答的完整流程结束。
+
+### 总结
+这个日志完整地记录了一次典型的RAG流程:系统通过**问题改写**和**预处理**来精确理解用户意图,接着利用**向量与关键词混合检索**从知识库中找到相关信息,虽然跳过了**重排序**,但依然执行了**合并**与**过滤**,最后将检索到的知识作为上下文,交由大语言模型**生成**流畅、准确的回答,并通过**流式过滤**保证了输出的纯净性。
+
+## 文档解析切分
+代码实现了一个独立的、通过gRPC通信的微服务,专门负责文档内容的深度解析、分块和多模态信息提取。它正是“异步处理”阶段的核心执行者。
+
+### **整体架构**
+这是一个基于Python的gRPC服务,其核心职责是接收文件(或URL),并将其解析成结构化的、可供后续处理(如向量化)的文本块(Chunks)。
+
++ `server.py`: 服务的入口和网络层。它负责启动一个多进程、多线程的gRPC服务器,接收来自Go后端的请求,并将解析结果返回。
++ `parser.py`: 设计模式中的**外观(Facade)模式**。它提供了一个统一的`Parser`类,屏蔽了内部多种具体解析器(如PDF、DOCX、Markdown等)的复杂性。外部调用者(`server.py`)只需与这个`Parser`类交互。
++ `base_parser.py`: 解析器的基类,定义了所有具体解析器共享的核心逻辑和抽象方法。这是整个解析流程的“大脑”,包含了最复杂的文本分块、图片处理、OCR和图像描述生成等功能。
+
+---
+
+### **详细工作流程**
+当Go后端启动异步任务时,它会携带文件内容和配置信息,向这个Python服务发起一次gRPC调用。以下是完整的处理流程:
+
+#### **第一步:请求接收与分发 (**`server.py`** & **`parser.py`**)
+1. **gRPC服务入口 (**`server.py: serve`**)**:
+ - 服务通过`serve()`函数启动。它会根据环境变量(`GRPC_WORKER_PROCESSES`, `GRPC_MAX_WORKERS`)启动一个**多进程、多线程**的服务器,以充分利用CPU资源,提高并发处理能力。
+ - 每个工作进程都监听在指定的端口(如50051),准备接收请求。
+2. **请求处理 (**`server.py: ReadFromFile`**)**:
+ - 当Go后端发起`ReadFromFile`请求时,其中一个工作进程会接收到该请求。
+ - 该方法首先会解析请求中的参数,包括:
+ * `file_name`, `file_type`, `file_content`:文件的基本信息和二进制内容。
+ * `read_config`: 一个包含所有解析配置的复杂对象,如`chunk_size`(分块大小)、`chunk_overlap`(重叠大小)、`enable_multimodal`(是否启用多模态处理)、`storage_config`(对象存储配置)、`vlm_config`(视觉语言模型配置)等。
+ - 它将这些配置整合成一个`ChunkingConfig`数据对象。
+ - 最关键的一步是调用 `self.parser.parse_file(...)`,将解析任务交给`Parser`外观类处理。
+3. **解析器选择 (**`parser.py: Parser.parse_file`**)**:
+ - `Parser`类接收到任务后,首先调用`get_parser(file_type)`方法。
+ - 该方法会根据文件类型(例如 `'pdf'`)在一个字典 `self.parsers` 中查找对应的具体解析器类(例如 `PDFParser`)。
+ - 找到后,它会**实例化**这个`PDFParser`类,并将`ChunkingConfig`等所有配置信息传递给构造函数。
+
+#### **第二步:核心解析与分块 (**`base_parser.py`**)**
+它触及了整个流程的核心:**如何保证信息的上下文完整性和原始顺序**。
+
+根据 `base_parser.py` 代码,**最终切分出的 Chunk 中的文本、表格和图像是按照它们在原始文档中的出现顺序来保存的**。
+
+这个顺序得以保证,主要归功于 `BaseParser` 中几个设计精巧的方法相互协作。我们来详细追踪一下这个流程。
+
+整个顺序的保证可以分为三个阶段:
+
+1. **阶段一:统一的文本流创建 (**`pdf_parser.py`**)**:
+ - 在 `parse_into_text` 方法中,您的代码会**逐页**处理PDF。
+ - 在每一页内部,它会按照一定的逻辑(先提取非表格文本,再附加表格,最后附加图像占位符)将所有内容**拼接成一个长字符串** (`page_content_parts`)。
+ - **关键点**: 虽然在这个阶段,文本、表格和图像占位符的拼接顺序可能不是100%精确到字符级别,但它保证了**同一页的内容会在一起**,并且大致遵循了从上到下的阅读顺序。
+ - 最后,所有页面的内容被 `"\n\n--- Page Break ---\n\n"` 连接起来,形成一个**包含了所有信息(文本、Markdown表格、图像占位符)的、单一的、有序的文本流 (**`final_text`**)**。
+2. **阶段二:原子化与保护 (**`_split_into_units`**)**:
+ - 这个单一的 `final_text` 被传递给 `_split_into_units` 方法。
+ - 这个方法是**保证结构完整性的关键**。它使用正则表达式,将**整个Markdown表格**和**整个Markdown图像占位符**识别为**不可分割的原子单元 (atomic units)**。
+ - 它会将这些原子单元(表格、图片)和它们之间的普通文本块,按照它们在 `final_text` 中出现的**原始顺序**,切分成一个列表 (`units`)。
+ - **结果**: 我们现在有了一个列表,例如 `['一些文本', '', '另一些文本', '|...|...|\n|---|---|\n...', '更多文本']`。这个列表中的元素顺序**完全等同于它们在原始文档中的顺序**。
+3. **阶段三:顺序分块 (**`chunk_text`**)**:
+ - `chunk_text` 方法接收到这个**有序的 **`units`** 列表**。
+ - 它的工作机制非常简单直接:它会**按顺序**遍历这个列表中的每一个单元(`unit`)。
+ - 它将这些单元**依次添加**到一个临时的 `current_chunk` 列表中,直到这个块的长度接近 `chunk_size` 的上限。
+ - 当一个块满了之后,它就被保存下来,然后开始一个新的块(可能会带有上一个块的重叠部分)。
+ - **关键点**: 因为 `chunk_text` **严格按照 **`units`** 列表的顺序进行处理**,所以它永远不会打乱表格、文本和图像之间的相对顺序。一个在文档中先出现的表格,也必然会出现在一个序号更靠前的 Chunk 中。
+4. **阶段四:图像信息附加 (**`process_chunks_images`**)**:
+ - 在文本块被切分好之后,`process_chunks_images` 方法会被调用。
+ - 它会处理**每一个**已经生成好的 Chunk。
+ - 在每个 Chunk 内部,它会找到图像占位符,然后进行AI处理。
+ - 最后,它会将处理好的图像信息(包含永久URL、OCR文本、图像描述等)附加到**该 Chunk 自己**的 `.images` 属性中。
+ - **关键点**: 这个过程**不会改变 Chunk 的顺序或其 **`.content`** 的内容**。它只是为已经存在的、顺序正确的 Chunk 附加额外的信息。
+
+#### **第三步:多模态处理(如果启用) (**`base_parser.py`**)**
+如果 `enable_multimodal` 为 `True`,在文本分块完成后,会进入最复杂的多模态处理阶段。
+
+1. **并发任务启动 (**`BaseParser.process_chunks_images`**)**:
+ - 该方法使用`asyncio`(Python的异步I/O框架)来**并发处理所有文本块中的图片**,以极大地提升效率。
+ - 它为每个`Chunk`创建一个异步任务`process_chunk_images_async`。
+2. **处理单个块中的图片 (**`BaseParser.process_chunk_images_async`**)**:
+ - **提取图片引用**: 首先,使用正则表达式 `extract_images_from_chunk` 从当前块的文本中找到所有的图片引用(例如,``)。
+ - **图片持久化**: 对于找到的每个图片,并发地调用 `download_and_upload_image`。这个函数负责:
+ * 从其原始位置(可能是PDF内部、本地路径或远程URL)获取图片数据。
+ * 将图片**上传到配置好的对象存储(COS/MinIO)**。这一步至关重要,它将临时的、不稳定的图片引用转换成一个持久化、可通过URL公开访问的地址。
+ * 返回持久化的URL和图片对象(PIL Image)。
+ - **并发AI处理**: 将所有成功上传的图片收集起来,调用`process_multiple_images`。
+ * 该方法内部使用`asyncio.Semaphore`来限制并发数量(例如最多同时处理5张图片),防止瞬间消耗过多内存或触发模型API的速率限制。
+ * 对于每张图片,它会调用`process_image_async`。
+3. **处理单张图片 (**`BaseParser.process_image_async`**)**:
+ - **OCR**: 调用`perform_ocr`,它会使用一个OCR引擎(如`PaddleOCR`)来识别图片中的所有文字。
+ - **图像描述 (Caption)**: 调用`get_image_caption`,它会将图片数据(转为Base64)发送给配置的视觉语言模型(VLM),生成对图片内容的自然语言描述。
+ - 该方法返回 `(ocr_text, caption, 持久化URL)`。
+4. **结果聚合**:
+ - 所有图片处理完成后,包含持久化URL、OCR文本和图像描述的结构化信息,会被附加到对应`Chunk`对象的 `.images` 字段上。
+
+#### **第四步:返回结果 (**`server.py`**)**
+1. **数据转换 (**`server.py: _convert_chunk_to_proto`**)**:
+ - 当`parser.parse_file`执行完毕后,它返回一个包含所有处理过的`Chunk`对象的列表(`ParseResult`)。
+ - `ReadFromFile`方法接收到这个结果,并调用`_convert_chunk_to_proto`,将Python的`Chunk`对象(包括其内部的图片信息)转换成gRPC定义的Protobuf消息格式。
+2. **响应返回**:
+ - 最后,gRPC服务器将这个包含所有分块和多模态信息的`ReadResponse`消息发送回给调用方——Go后端服务。
+
+至此,Go后端就拿到了结构化、信息丰富的文档数据,可以进行下一步的向量化和索引存储了。
+
+
+## 部署
+支持Docker 镜像本地部署,并通过API端口提供接口服务
+
+## 性能和监控
+Weknora包含丰富的监控和测试组件:
+
++ 分布式跟踪:集成Jaeger用于跟踪请求在服务架构中的完整执行路。本质上,Jaeger是一种帮助用户“看见”请求在分布式系统中完整生命周期的技术。
++ 健康监控:监控服务处在健康状态
++ 可扩展性:通过容器化部署,可通过多个服务满足大规模并发请求
+
+## QA
+### 问题1: 在检索过程的执行了两次混合搜索的目的是什么?以及第一次和第二次搜索有什么不同?
+这是一个非常好的观察。系统执行两次混合搜索是为了**最大化检索的准确性和召回率**,本质上是一种**查询扩展(Query Expansion)和多策略检索**的组合方法。
+
+#### 目的
+通过两种不同形式的查询(原始改写句 vs. 分词后的关键词序列)去搜索,系统可以结合两种查询方式的优点:
+
++ **语义检索的深度**: 使用完整的句子进行搜索,能更好地利用向量模型(如`bge-m3`)对句子整体含义的理解能力,找到语义上最接近的知识区块。
++ **关键词检索的广度**: 使用分词后的关键词进行搜索,能确保即使知识区块的表述方式与原问题不同,但只要包含了核心关键词,就有机会被命中。这对于传统的关键词匹配算法(如BM25)尤其有效。
+
+简单来说,就是**用两种不同的“问法”去问同一个问题**,然后将两边的结果汇总起来,确保最相关的知识不会被遗漏。
+
+#### 两次搜索的不同点
+它们最核心的不同在于**输入的查询文本(Query Text)**:
+
+1. **第一次混合搜索**
+ - **输入**: 使用的是经过`rewrite_query`事件后生成的、**语法完整的自然语言问句**。
+ - **日志证据**:
+
+```plain
+INFO [2025-08-29 09:46:36.896] [request_id=Lkq0OGLYu2fV] knowledgebase.go:266[HybridSearch] | Hybrid search parameters, knowledge base ID: kb-00000001, query text: 需要改写的用户问题是:“入住的房型是什么”。根据提供的信息,入住人Liwx选择的房型是双床房。因此,改写后的完整问题为: “Liwx本次入住的房型是什么”
+```
+
+2. **第二次混合搜索**
+ - **输入**: 使用的是经过`preprocess_query`事件处理后生成的、**由空格隔开的关键词序列**。
+ - **日志证据**:
+
+```plain
+INFO [2025-08-29 09:46:37.257] [request_id=Lkq0OGLYu2fV] knowledgebase.go:266[HybridSearch] | Hybrid search parameters, knowledge base ID: kb-00000001, query text: 需要 改写 用户 问题 入住 房型 根据 提供 信息 入住 人 Liwx 选择 房型 双床 房 因此 改写 后 完整 问题 为 Liwx 本次 入住 房型
+```
+
+最终,系统将这两次搜索的结果进行去重和合并(日志中显示每次都找到2个结果,去重后总共还是2个),从而得到一个更可靠的知识集合,用于后续的答案生成。
+
+
+
+### 问题2:重排序模型分析
+Reranker(重排器)是目前RAG领域中非常先进的技术,它们在工作原理和适用场景上有着显著的区别。
+
+简单来说,它们代表了从“**专门的判别模型**”到“**利用大语言模型(LLM)进行判别**”再到“**深度挖掘LLM内部信息进行判别**”的演进。
+
+以下是它们的详细区别:
+
+
+
+#### 1. Normal Reranker (常规重排器 / 交叉编码器)
+这是最经典也是最主流的重排方法。
+
++ **模型类型**: **序列分类模型 (Sequence Classification Model)**。本质上是一个**交叉编码器 (Cross-Encoder)**,通常基于BERT、RoBERTa等双向编码器架构。`BAAI/bge-reranker-base/large/v2-m3` 都属于这一类。
++ **工作原理**:
+ 1. 它将**查询(Query)**和**待排序的文档(Passage)**拼接成一个单一的输入序列,例如:`[CLS] what is panda? [SEP] The giant panda is a bear species endemic to China. [SEP]`。
+ 2. 这个拼接后的序列被完整地送入模型中。模型内部的自注意力机制(Self-Attention)可以同时分析查询和文档中的每一个词,并计算它们之间**细粒度的交互关系**。
+ 3. 模型最终输出一个**单一的分数(Logit)**,这个分数直接代表了查询和文档的相关性。分数越高,相关性越强。
++ **关键特性**:
+ - **优点**: 由于查询和文档在模型内部进行了充分的、深度的交互,其**准确度通常非常高**,是衡量Reranker性能的黄金标准。
+ - **缺点**: **速度较慢**。因为它必须为**每一个“查询-文档”对**都独立执行一次完整的、代价高昂的计算。如果初步检索返回了100个文档,它就需要运行100次。
+
+
+
+#### 2. LLM-based Reranker (基于LLM的重排器)
+这种方法创造性地利用了通用大语言模型(LLM)的能力来进行重排。
+
++ **模型类型**: **因果语言模型 (Causal Language Model)**,即我们常说的GPT、Llama、Gemma这类用于生成文本的LLM。`BAAI/bge-reranker-v2-gemma` 就是一个典型的例子。
++ **工作原理**:
+ 1. 它**不是直接输出一个分数**,而是将重排任务**转化为一个问答或文本生成任务**。
+ 2. 它通过一个精心设计的**提示(Prompt)**来组织输入,例如:`"Given a query A and a passage B, determine whether the passage contains an answer to the query by providing a prediction of either 'Yes' or 'No'. A: {query} B: {passage}"`。
+ 3. 它将这个完整的Prompt喂给LLM,然后**观察LLM在最后生成“Yes”这个词的概率**。
+ 4. 这个**生成“Yes”的概率(或其Logit值)就被当作是相关性分数**。如果模型非常确信答案是“Yes”,说明它认为文档B包含了查询A的答案,即相关性高。
++ **关键特性**:
+ - **优点**: 能够利用LLM强大的**语义理解、推理和世界知识**,对于需要深度理解和推理才能判断相关性的复杂查询,效果可能更好。
+ - **缺点**: 计算开销可能非常大(取决于LLM的大小),并且性能**高度依赖于Prompt的设计**。
+
+
+
+#### 3. LLM-based Layerwise Reranker (基于LLM分层信息的重排器)
+这是第二种方法的“威力加强版”,是一种更前沿、更复杂的探究性技术。
+
++ **模型类型**: 同样是**因果语言模型 (Causal Language Model)**,例如`BAAI/bge-reranker-v2-minicpm-layerwise`。
++ **工作原理**:
+ 1. 输入部分与第二种方法完全相同,也是使用“Yes/No”的Prompt。
+ 2. 核心区别在于**分数的提取方式**。它不再仅仅依赖LLM**最后一层**的输出(即最终的预测结果)。
+ 3. 它认为LLM在逐层处理信息的过程中,不同深度的网络层(Layer)可能捕获了不同层次的语义相关性信息。因此,它会从**模型的多个中间层**提取出关于“Yes”这个词的预测Logit。
+ 4. 代码中的 `cutoff_layers=[28]` 参数就是告诉模型:“请把第28层的输出给我”。最终,你会得到一个或多个来自不同网络层的分数,这些分数可以被平均或以其他方式组合,形成一个更鲁棒的最终相关性判断。
++ **关键特性**:
+ - **优点**: 理论上可以获得**更丰富、更全面的相关性信号**,可能达到比只看最后一层更高的精度,是目前探索性能极限的一种方法。
+ - **缺点**: **复杂度最高**,需要对模型进行特定的修改才能提取中间层信息(代码中的`trust_remote_code=True`就是一个信号),计算开销也很大。
+
+#### 总结对比
+| 特性 | 1. Normal Reranker (常规) | 2. LLM-based Reranker (基于LLM) | 3. LLM-based Layerwise Reranker (基于LLM分层) |
+| :--- | :--- | :--- | :--- |
+| **底层模型** | 交叉编码器 (如BERT) | 因果语言模型 (如Gemma) | 因果语言模型 (如MiniCPM) |
+| **工作原理** | 计算Query和Passage的深度交互,直接输出相关分 | 将排序任务转为"Yes/No"预测,用"Yes"的概率作为分数 | 与2类似,但从LLM的多个中间层提取"Yes"的概率 |
+| **输出** | 单一的相关性分数 | 单一的相关性分数(来自最后一层) | 多个相关性分数(来自不同层) |
+| **优点** | **速度与精度的最佳平衡点**,成熟稳定 | 利用LLM的推理能力,处理复杂问题 | 理论上精度最高,信号更丰富 |
+| **缺点** | 相比向量检索慢 | 计算开销大,依赖Prompt设计 | **复杂度最高**,计算开销最大 |
+| **推荐场景** | **大多数生产环境的首选**,效果好,易于部署 | 对答案质量有极致要求,且计算资源充足的场景 | 学术研究或追求SOTA(State-of-the-art)性能的场景 |
+
+
+#### 使用建议
+1. **开始阶段**: 强烈建议您**从 **`Normal Reranker`** 开始**,例如 `BAAI/bge-reranker-v2-m3`。它是目前综合表现最好的模型之一,能显著提升您的RAG系统性能,并且相对容易集成和部署。
+2. **进阶探索**: 如果您发现常规Reranker在处理某些非常微妙或需要复杂推理的查询时表现不佳,并且您拥有充足的GPU资源,可以尝试 `LLM-based Reranker`。
+3. **前沿研究**: `Layerwise Reranker` 更适合研究人员或希望在特定任务上压榨出最后一点性能的专家。
+
+
+### 问题3:粗过滤或细过滤后的知识(带重排)如何组装发送给大模型的?
+这一块主要是设计提示词,典型的指令细节,其核心任务是根据上下文回答用户问题。组装上下文时需要指定
+关键约束:必须严格按照所提供文档回答,禁止使用你自己的知识回答
+未知情况处理: 如果文档中没有足够的信息来回答问题,请告知“根据所掌握的资料,无法回答这个问题”
+引用要求:在回答时,如果引用了某个文档内容,请在句子末尾加上文档编号
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/WeKnora.pdf b/docs/WeKnora.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..f98a9f2daccf9627050ddbf6b1b488f10430a07a
GIT binary patch
literal 2020058
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