From 56eb2bce33d7c3ce3c705c78330d919a5cf15eed Mon Sep 17 00:00:00 2001
From: wizardchen
Date: Tue, 5 Aug 2025 15:08:07 +0800
Subject: [PATCH] init commit
---
.dockerignore | 0
.env.example | 172 +
.gitignore | 34 +
LICENSE | 2692 ++++++++++++
Makefile | 94 +
README.md | 239 ++
README_EN.md | 239 ++
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 | 339 ++
client/knowledgebase.go | 211 +
client/message.go | 82 +
client/model.go | 155 +
client/session.go | 403 ++
client/tenant.go | 136 +
cmd/server/main.go | 113 +
config/config.yaml | 535 +++
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 | 172 +
docker/Dockerfile.app | 76 +
docker/Dockerfile.docreader | 132 +
docker/config/supervisord.conf | 31 +
docker/docker-compose.minio.yml | 29 +
docs/API.md | 1389 ++++++
docs/images/answer.png | Bin 0 -> 853609 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/qa.png | Bin 0 -> 87783 bytes
docs/使用其他向量数据库.md | 190 +
frontend/.gitignore | 30 +
frontend/Dockerfile | 35 +
frontend/env.d.ts | 6 +
frontend/index.html | 22 +
frontend/nginx.conf | 36 +
frontend/package-lock.json | 3781 +++++++++++++++++
frontend/package.json | 47 +
frontend/public/favicon.ico | Bin 0 -> 5558 bytes
frontend/src/App.vue | 23 +
frontend/src/api/chat/index.ts | 62 +
frontend/src/api/chat/streame.ts | 147 +
frontend/src/api/knowledge-base/index.ts | 62 +
frontend/src/api/test-data/index.ts | 55 +
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/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/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 | 242 ++
frontend/src/components/empty-knowledge.vue | 42 +
frontend/src/components/menu.vue | 453 ++
frontend/src/components/picture-preview.vue | 18 +
frontend/src/components/upload-mask.vue | 40 +
frontend/src/hooks/useKnowledgeBase.ts | 145 +
frontend/src/main.ts | 16 +
frontend/src/router/index.ts | 46 +
frontend/src/stores/knowledge.ts | 11 +
frontend/src/stores/menu.ts | 46 +
frontend/src/stores/settings.ts | 51 +
frontend/src/utils/index.ts | 44 +
frontend/src/utils/request.ts | 146 +
frontend/src/views/chat/components/botmsg.vue | 246 ++
.../src/views/chat/components/deepThink.vue | 138 +
.../src/views/chat/components/docInfo.vue | 129 +
.../src/views/chat/components/sendMsg.vue | 20 +
.../src/views/chat/components/usermsg.vue | 38 +
frontend/src/views/chat/index.vue | 303 ++
frontend/src/views/creatChat/creatChat.vue | 188 +
.../src/views/knowledge/KnowledgeBase.vue | 574 +++
frontend/src/views/platform/index.vue | 76 +
frontend/src/views/settings/Settings.vue | 100 +
frontend/tsconfig.app.json | 12 +
frontend/tsconfig.json | 11 +
frontend/tsconfig.node.json | 19 +
frontend/vite.config.ts | 16 +
go.mod | 108 +
go.sum | 292 ++
internal/application/repository/chunk.go | 127 +
internal/application/repository/knowledge.go | 187 +
.../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 | 1025 +++++
.../retriever/elasticsearch/v8/repository.go | 541 +++
.../retriever/postgres/repository.go | 390 ++
.../repository/retriever/postgres/structs.go | 87 +
internal/application/repository/session.go | 89 +
internal/application/repository/tenant.go | 82 +
.../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/filter_top_k.go | 53 +
.../service/chat_pipline/into_chat_message.go | 183 +
.../application/service/chat_pipline/merge.go | 175 +
.../service/chat_pipline/preprocess.go | 180 +
.../service/chat_pipline/rerank.go | 171 +
.../service/chat_pipline/rewrite.go | 174 +
.../service/chat_pipline/search.go | 124 +
.../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 | 407 ++
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 | 1561 +++++++
internal/application/service/knowledgebase.go | 574 +++
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 | 289 ++
internal/application/service/test_data.go | 443 ++
internal/common/asyncq.go | 72 +
internal/common/tools.go | 75 +
internal/config/config.go | 186 +
internal/container/cleanup.go | 86 +
internal/container/container.go | 366 ++
internal/errors/errors.go | 152 +
internal/errors/session.go | 16 +
internal/handler/chunk.go | 233 +
internal/handler/evaluation.go | 105 +
internal/handler/knowledge.go | 458 ++
internal/handler/knowledgebase.go | 298 ++
internal/handler/message.go | 135 +
internal/handler/model.go | 265 ++
internal/handler/session.go | 809 ++++
internal/handler/tenant.go | 226 +
internal/handler/test_data.go | 108 +
internal/logger/logger.go | 252 ++
internal/middleware/auth.go | 104 +
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 | 189 +
internal/models/chat/remote_api.go | 166 +
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/remote_api.go | 122 +
internal/models/rerank/reranker.go | 49 +
internal/models/utils/ollama/ollama.go | 311 ++
internal/models/utils/slices.go | 36 +
internal/router/router.go | 229 +
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 | 114 +
internal/types/chunk.go | 90 +
internal/types/cleanup.go | 4 +
internal/types/const.go | 20 +
internal/types/dataset.go | 11 +
internal/types/embedding.go | 33 +
internal/types/errors.go | 46 +
internal/types/evaluation.go | 99 +
internal/types/graph.go | 53 +
internal/types/interfaces/chunk.go | 62 +
internal/types/interfaces/evaluation.go | 35 +
internal/types/interfaces/file.go | 18 +
internal/types/interfaces/knowledge.go | 72 +
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/session.go | 49 +
internal/types/interfaces/stream_manager.go | 34 +
internal/types/interfaces/tenant.go | 41 +
internal/types/json.go | 68 +
internal/types/knowledge.go | 92 +
internal/types/knowledgebase.go | 95 +
internal/types/message.go | 60 +
internal/types/model.go | 91 +
internal/types/retriever.go | 82 +
internal/types/search.go | 129 +
internal/types/session.go | 120 +
internal/types/tenant.go | 65 +
migrations/mysql/00-init-db.sql | 148 +
migrations/paradedb/00-init-db.sql | 197 +
.../paradedb/01-migrate-to-paradedb.sql | 69 +
scripts/migrate.sh | 54 +
scripts/start_all.sh | 561 +++
services/docreader/Makefile | 23 +
services/docreader/poetry.lock | 2228 ++++++++++
services/docreader/pyproject.toml | 19 +
services/docreader/requirements.txt | 26 +
services/docreader/scripts/generate_proto.sh | 31 +
services/docreader/src/.DS_Store | Bin 0 -> 6148 bytes
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 | 27 +
services/docreader/src/parser/__init__.py | 41 +
services/docreader/src/parser/base_parser.py | 1302 ++++++
services/docreader/src/parser/caption.py | 271 ++
services/docreader/src/parser/doc_parser.py | 314 ++
services/docreader/src/parser/docx_parser.py | 1585 +++++++
services/docreader/src/parser/image_parser.py | 58 +
.../docreader/src/parser/markdown_parser.py | 33 +
services/docreader/src/parser/ocr_engine.py | 268 ++
services/docreader/src/parser/parser.py | 222 +
services/docreader/src/parser/pdf_parser.py | 62 +
services/docreader/src/parser/text_parser.py | 57 +
services/docreader/src/parser/web_parser.py | 133 +
services/docreader/src/proto/docreader.pb.go | 560 +++
services/docreader/src/proto/docreader.proto | 61 +
.../docreader/src/proto/docreader_grpc.pb.go | 167 +
.../docreader/src/proto/docreader_pb2_grpc.py | 104 +
services/docreader/src/server/server.py | 312 ++
.../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 +
282 files changed, 49077 insertions(+)
create mode 100644 .dockerignore
create mode 100644 .env.example
create mode 100644 .gitignore
create mode 100644 LICENSE
create mode 100644 Makefile
create mode 100644 README.md
create mode 100644 README_EN.md
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/images/answer.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/qa.png
create mode 100644 docs/使用其他向量数据库.md
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/chat/index.ts
create mode 100644 frontend/src/api/chat/streame.ts
create mode 100644 frontend/src/api/knowledge-base/index.ts
create mode 100644 frontend/src/api/test-data/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/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/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/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/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/knowledge/KnowledgeBase.vue
create mode 100644 frontend/src/views/platform/index.vue
create mode 100644 frontend/src/views/settings/Settings.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/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/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/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/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/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/test_data.go
create mode 100644 internal/common/asyncq.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/chunk.go
create mode 100644 internal/handler/evaluation.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/tenant.go
create mode 100644 internal/handler/test_data.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/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/remote_api.go
create mode 100644 internal/models/rerank/reranker.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/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/graph.go
create mode 100644 internal/types/interfaces/chunk.go
create mode 100644 internal/types/interfaces/evaluation.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/session.go
create mode 100644 internal/types/interfaces/stream_manager.go
create mode 100644 internal/types/interfaces/tenant.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 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 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 100644 services/docreader/scripts/generate_proto.sh
create mode 100644 services/docreader/src/.DS_Store
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/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/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/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
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..e69de29b
diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..d498e7c4
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,172 @@
+# 使用说明
+# 1. 复制此文件为 .env
+# 2. 替换所有占位符为实际值
+# 3. 确保 .env 文件不会被提交到版本控制系统
+
+# gin mod
+# 可选值: debug(开发模式,有详细日志), release(生产模式)
+GIN_MODE=debug
+
+# ollama
+# Ollama 服务的基准 URL,用于连接本地运行的 Ollama LLM 服务
+OLLAMA_BASE_URL=http://host.docker.internal:11435
+
+# 存储配置
+# 主数据库类型(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
+
+# 主数据库配置
+# 数据库地址,可以是主机名或IP地址
+DB_HOST=postgres
+
+# 数据库端口,默认为5432
+DB_PORT=5432
+
+# 数据库用户名
+DB_USER=postgres
+
+# 数据库密码
+DB_PASSWORD=postgres123!@#
+
+# 数据库名称
+DB_NAME=WeKnora
+
+# 如果使用 redis 作为流处理后端,需要配置以下参数
+# Redis地址,可以是主机名或IP地址
+REDIS_HOST=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
+
+# 如果使用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:9000
+# MINIO_ENDPOINT=minio:9000
+
+# 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
+
+# 初始化默认租户与知识库
+# 租户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
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..3aafc978
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+# 忽略.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/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..75b8af53
--- /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 00000000..3ddc7246
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,94 @@
+.PHONY: build run test clean docker-build docker-run migrate-up migrate-down docker-restart docker-stop start-all stop-all start-ollama stop-ollama
+
+# Go related variables
+BINARY_NAME=WeKnora
+MAIN_PATH=./cmd/server
+
+# Docker related variables
+DOCKER_IMAGE=WeKnora
+DOCKER_TAG=latest
+
+# 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:
+ docker build -t $(DOCKER_IMAGE):$(DOCKER_TAG) .
+
+# 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
+
+# Restart Docker container (stop, rebuild, start)
+docker-restart:
+ docker-compose stop -t 60
+ docker-compose up --build
+
+# 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:
+ GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -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
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..7473f91d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+| English | 简体中文 |
+
+
+
+
+
+ [项目介绍](#-项目介绍) • [架构设计](#-架构设计) • [核心特性](#-核心特性) • [快速开始](#-快速开始) • [文档](#-文档) • [开发指南](#-开发指南)
+
+
+
+
+# 💡 WeKnora - 基于大模型的文档理解检索框架
+
+## 📌 项目介绍
+
+[**WeKnora(维娜拉)**](https://weknora.weixin.qq.com) 是一款基于大语言模型(LLM)的文档理解与语义检索框架,专为结构复杂、内容异构的文档场景而打造。
+
+框架采用模块化架构,融合多模态预处理、语义向量索引、智能召回与大模型生成推理,构建起高效、可控的文档问答流程。核心检索流程基于 **RAG(Retrieval-Augmented Generation)** 机制,将上下文相关片段与语言模型结合,实现更高质量的语义回答。
+
+**官网:** https://weknora.weixin.qq.com
+
+## 🏗️ 架构设计
+
+
+
+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
+./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 的智能问答能力可无缝集成到公众号、小程序等微信场景中,提升用户交互体验
+
+## 📱 功能展示
+
+### Web UI 界面
+
+
+
+ 知识上传
 |
+ 知识问答入口
 |
+
+
+ 图文结果回答
 |
+
+
+
+**知识库管理:** 支持拖拽上传各类文档,自动识别文档结构并提取核心知识,建立索引。系统清晰展示处理进度和文档状态,实现高效的知识库管理。
+
+### 文档知识图谱
+
+
+
+  |
+  |
+
+
+
+WeKnora 支持将文档转化为知识图谱,展示文档中不同段落之间的关联关系。开启知识图谱功能后,系统会分析并构建文档内部的语义关联网络,不仅帮助用户理解文档内容,还为索引和检索提供结构化支撑,提升检索结果的相关性和广度。
+
+## 📘 文档
+
+详细接口说明请参考:[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) 协议发布。
+你可以自由使用、修改和分发本项目代码,但需保留原始版权声明。
\ No newline at end of file
diff --git a/README_EN.md b/README_EN.md
new file mode 100644
index 00000000..d9e28ad9
--- /dev/null
+++ b/README_EN.md
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+| English | 简体中文 |
+
+
+
+
+
+ [Overview](#-overview) • [Architecture](#-architecture) • [Key Features](#-key-features) • [Getting Started](#-getting-started) • [API Reference](#-api-reference) • [Developer Guide](#-developer-guide)
+
+
+
+
+# 💡 WeKnora - LLM-Powered Document Understanding & Retrieval Framework
+
+## 📌 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
+
+## 🏗️ 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
+# Start all services (Ollama + backend containers)
+./scripts/start_all.sh
+# Or
+make start-all
+```
+
+#### ④ Stop the services
+
+```bash
+./scripts/start_all.sh --stop
+# Or
+make stop-all
+```
+
+### 🌐 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:
+
+- **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
+
+## 📱 Interface Showcase
+
+### Web UI Interface
+
+
+
+ Knowledge Upload
 |
+ Q&A Entry
 |
+
+
+ Rich Text & Image Responses
 |
+
+
+
+**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.
+
+## 📘 API Reference
+
+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
+```
+
+### 🔧 Common Commands
+
+```bash
+# Wipe all data from DB (use with caution)
+make clean-db
+```
+
+## 🤝 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.
+
+### 🎯 How to Contribute
+
+- 🐛 **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
+
+### 📋 Contribution Process
+
+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
+
+### 🎨 Code Standards
+
+- Follow [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
+- Format code using `gofmt`
+- Add necessary unit tests
+- Update relevant documentation
+
+### 📝 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
+
+This project is licensed under the [MIT License](./LICENSE).
+You are free to use, modify, and distribute the code with proper attribution.
\ No newline at end of file
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 00000000..b236055a
--- /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 00000000..99ff39dc
--- /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 00000000..6ed51481
--- /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 00000000..c92a2761
--- /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 00000000..bb312f16
--- /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 00000000..cda4fa50
--- /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)
+ 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 00000000..1a20c4bb
--- /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 00000000..e69de29b
diff --git a/client/knowledge.go b/client/knowledge.go
new file mode 100644
index 00000000..04e14fbf
--- /dev/null
+++ b/client/knowledge.go
@@ -0,0 +1,339 @@
+// 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")
+
+// 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,
+) (*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 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) (*Knowledge, error) {
+ path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge/url", knowledgeBaseID)
+
+ reqBody := struct {
+ URL string `json:"url"`
+ }{
+ URL: url,
+ }
+
+ resp, err := c.doRequest(ctx, http.MethodPost, path, reqBody, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response KnowledgeResponse
+ 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 00000000..63aa921a
--- /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 00000000..28ae55ac
--- /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 00000000..ecde9f65
--- /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 00000000..86b380d8
--- /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 00000000..e2f80692
--- /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 00000000..403f8895
--- /dev/null
+++ b/cmd/server/main.go
@@ -0,0 +1,113 @@
+// 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/application/service"
+ "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,
+ testDataService *service.TestDataService,
+ 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)
+ })
+
+ // Initialize test data
+ if testDataService != nil {
+ if err := testDataService.InitializeTestData(context.Background()); err != nil {
+ log.Printf("Failed to initialize test data: %v", err)
+ }
+ }
+
+ // 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 00000000..f88b3f06
--- /dev/null
+++ b/config/config.yaml
@@ -0,0 +1,535 @@
+# 服务器配置
+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.7
+ 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: |
+ 你是一个总结助手,你的任务是总结文章或者片段内容。
+
+ ## 准则要求
+ - 总结结果长度不能超过100个字
+ - 保持客观,不添加个人观点或评价
+ - 使用第三人称陈述语气
+ - 不要基于任何先验知识回答用户的问题,只基于文章内容生成摘要
+ - 直接输出总结结果,不要有任何前缀或解释
+ - 使用中文输出总结结果
+ - 不能输出“无法生成”、“无法总结”等字眼
+
+ ## Few-shot示例
+
+ 用户给出的文章内容:
+ 随着5G技术的快速发展,各行各业正经历数字化转型。5G网络凭借高速率、低延迟和大连接的特性,正在推动智慧城市、工业互联网和远程医疗等领域的创新。专家预测,到2025年,5G将为全球经济贡献约2.2万亿美元。然而,5G建设也面临基础设施投入大、覆盖不均等挑战。
+
+ 文章总结:
+ 5G技术凭借高速率、低延迟和大连接特性推动各行业数字化转型,促进智慧城市、工业互联网和远程医疗创新。预计2025年将贡献约2.2万亿美元经济价值,但仍面临基础设施投入大和覆盖不均等挑战。
+
+ ## 用户给出的文章内容是:
+
+ 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
diff --git a/dataset/README b/dataset/README
new file mode 100644
index 00000000..6c78bcaa
--- /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 00000000..2c4e5b0d
--- /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 00000000..45f8265b
--- /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 00000000..84e8e71f
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,172 @@
+version: "3.8"
+
+services:
+ app:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile.app
+ container_name: WeKnora-app
+ ports:
+ - "8080:8080"
+ volumes:
+ - data-files:/data/files
+ - ./config:/app/config
+ environment:
+ - GIN_MODE=${GIN_MODE}
+ - DB_DRIVER=${DB_DRIVER}
+ - DB_HOST=${DB_HOST}
+ - DB_PORT=${DB_PORT}
+ - 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_ENDPOINT}
+ - MINIO_ACCESS_KEY_ID=${MINIO_ACCESS_KEY_ID}
+ - MINIO_SECRET_ACCESS_KEY=${MINIO_SECRET_ACCESS_KEY}
+ - MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME}
+ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
+ - STREAM_MANAGER_TYPE=${STREAM_MANAGER_TYPE}
+ - REDIS_ADDR=${REDIS_HOST}:${REDIS_PORT}
+ - REDIS_PASSWORD=${REDIS_PASSWORD}
+ - REDIS_DB=${REDIS_DB}
+ - REDIS_PREFIX=${REDIS_PREFIX}
+ - ENABLE_GRAPH_RAG=${ENABLE_GRAPH_RAG}
+ - TENANT_AES_KEY=${TENANT_AES_KEY}
+ - 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}
+ - INIT_TEST_TENANT_ID=${INIT_TEST_TENANT_ID}
+ - INIT_TEST_KNOWLEDGE_BASE_ID=${INIT_TEST_KNOWLEDGE_BASE_ID}
+ depends_on:
+ redis:
+ condition: service_started
+ postgres:
+ condition: service_healthy
+ networks:
+ - WeKnora-network
+ restart: unless-stopped
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+
+ frontend:
+ build:
+ context: ./frontend
+ dockerfile: Dockerfile
+ container_name: WeKnora-frontend
+ ports:
+ - "80:80"
+ depends_on:
+ - app
+ networks:
+ - WeKnora-network
+ restart: unless-stopped
+
+ docreader:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile.docreader
+ container_name: WeKnora-docreader
+ ports:
+ - "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}
+ networks:
+ - WeKnora-network
+ restart: unless-stopped
+
+ 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:latest
+ container_name: WeKnora-postgres
+ ports:
+ - "${DB_PORT}:5432"
+ environment:
+ - POSTGRES_USER=${DB_USER}
+ - POSTGRES_PASSWORD=${DB_PASSWORD}
+ - POSTGRES_DB=${DB_NAME}
+ volumes:
+ - postgres-data:/var/lib/postgresql/data
+ - ./migrations/paradedb:/docker-entrypoint-initdb.d
+ 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
+
+networks:
+ WeKnora-network:
+ driver: bridge
+
+volumes:
+ postgres-data:
+ data-files:
+ jaeger_data:
+ redis_data:
diff --git a/docker/Dockerfile.app b/docker/Dockerfile.app
new file mode 100644
index 00000000..197e865b
--- /dev/null
+++ b/docker/Dockerfile.app
@@ -0,0 +1,76 @@
+# Build stage
+FROM golang:1.24-alpine AS builder
+
+WORKDIR /app
+
+# Install dependencies
+RUN apk add --no-cache git build-base
+
+# 通过构建参数接收敏感信息
+ARG GOPRIVATE_ARG
+ARG GOPROXY_ARG
+ARG GOSUMDB_ARG=off
+
+# 设置Go环境变量
+ENV GOPRIVATE=${GOPRIVATE_ARG}
+ENV GOPROXY=${GOPROXY_ARG}
+ENV GOSUMDB=${GOSUMDB_ARG}
+
+# Copy go mod and sum files
+COPY go.mod go.sum ./
+RUN go mod download
+
+ENV CGO_ENABLED=1
+# Install migrate tool
+RUN go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
+
+# Copy source code
+COPY . .
+
+# Build the application
+RUN make build-prod
+
+# Final stage
+FROM alpine:3.17
+
+WORKDIR /app
+
+# Install runtime dependencies
+RUN echo 'http://dl-cdn.alpinelinux.org/alpine/edge/main' > /etc/apk/repositories
+RUN apk update --allow-untrusted
+RUN apk upgrade --allow-untrusted
+RUN apk add --no-cache build-base postgresql17-client mysql-client ca-certificates tzdata sed curl bash supervisor vim wget curl --allow-untrusted
+
+# Copy the binary from the builder stage
+COPY --from=builder /app/WeKnora .
+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 migrate tool from builder stage
+COPY --from=builder /go/bin/migrate /usr/local/bin/
+COPY --from=builder /go/pkg/mod/github.com/yanyiwu /go/pkg/mod/github.com/yanyiwu/
+
+# Make scripts executable
+RUN chmod +x ./scripts/*.sh
+
+# Setup supervisor configuration
+RUN mkdir -p /etc/supervisor.d/
+COPY docker/config/supervisord.conf /etc/supervisor.d/supervisord.conf
+
+# Expose ports
+EXPOSE 8080
+
+# Set environment variables
+ENV CGO_ENABLED=1
+
+# 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
+
+# Run supervisor instead of direct application start
+CMD ["supervisord", "-c", "/etc/supervisor.d/supervisord.conf"]
+# CMD ["sh", "/app/scripts/start_app.sh"]
+# CMD ["sleep", "infinity"]
\ No newline at end of file
diff --git a/docker/Dockerfile.docreader b/docker/Dockerfile.docreader
new file mode 100644
index 00000000..3cec8a2b
--- /dev/null
+++ b/docker/Dockerfile.docreader
@@ -0,0 +1,132 @@
+ARG PLATFORM=linux/amd64
+
+# 构建阶段
+FROM --platform=${PLATFORM} python:3.9-slim AS builder
+
+WORKDIR /app
+
+# 安装构建依赖
+RUN apt-get update && apt-get install -y \
+ gcc \
+ python3-dev \
+ libjpeg-dev \
+ zlib1g-dev \
+ libpq-dev \
+ libffi-dev \
+ libgl1-mesa-glx \
+ libglib2.0-0 \
+ wget \
+ antiword \
+ curl \
+ unzip \
+ && rm -rf /var/lib/apt/lists/*
+
+# 安装 protoc
+RUN curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip && \
+ unzip protoc-3.19.4-linux-x86_64.zip -d /usr/local && \
+ chmod +x /usr/local/bin/protoc && \
+ rm protoc-3.19.4-linux-x86_64.zip
+
+# 复制依赖文件
+COPY services/docreader/requirements.txt .
+
+# 安装依赖
+RUN pip install --no-cache-dir -r requirements.txt
+
+# 预下载 PP-OCRv5 模型
+RUN mkdir -p /root/.paddlex/official_models
+
+RUN wget https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_server_det_infer.tar \
+ -O /root/.paddlex/official_models/PP-OCRv5_server_det_infer.tar && \
+ wget https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_server_rec_infer.tar \
+ -O /root/.paddlex/official_models/PP-OCRv5_server_rec_infer.tar
+
+# 解压模型文件
+RUN tar -xf /root/.paddlex/official_models/PP-OCRv5_server_det_infer.tar -C /root/.paddlex/official_models/ && \
+ tar -xf /root/.paddlex/official_models/PP-OCRv5_server_rec_infer.tar -C /root/.paddlex/official_models/
+
+# 复制源代码和生成脚本
+COPY services/docreader/src/ /app/src/
+COPY services/docreader/scripts/ /app/scripts/
+
+# 使用正确的Python模块路径运行ocr.py
+# RUN cd /app && PYTHONPATH=/app python -m src.parser.ocr
+
+# 确保模型目录存在
+RUN ls -la /root/.paddlex/official_models
+
+# 生成 protobuf 代码
+RUN chmod +x /app/scripts/generate_proto.sh && /app/scripts/generate_proto.sh
+
+# 运行阶段
+FROM --platform=${PLATFORM} python:3.9-slim AS runner
+
+WORKDIR /app
+
+# 安装运行时依赖
+RUN apt-get update && apt-get install -y \
+ libjpeg62-turbo \
+ libpq5 \
+ wget \
+ gnupg \
+ libgl1-mesa-glx \
+ libglib2.0-0 \
+ antiword \
+ supervisor \
+ && rm -rf /var/lib/apt/lists/*
+
+# 安装所需依赖
+RUN apt-get update && apt-get install -y \
+ wget \
+ vim \
+ tar \
+ dpkg \
+ libxinerama1 \
+ libfontconfig1 \
+ libdbus-glib-1-2 \
+ libcairo2 \
+ libcups2 \
+ libglu1-mesa \
+ libsm6 \
+ && rm -rf /var/lib/apt/lists/*
+
+# 下载并安装最新版本的 LibreOffice 25.2.4
+RUN mkdir -p /tmp/libreoffice && \
+ cd /tmp/libreoffice && \
+ wget -q https://mirrors.tuna.tsinghua.edu.cn/libreoffice/libreoffice/stable/25.2.4/deb/x86_64/LibreOffice_25.2.4_Linux_x86-64_deb.tar.gz && \
+ tar -xzf LibreOffice_25.2.4_Linux_x86-64_deb.tar.gz && \
+ cd LibreOffice_25.2.4*_Linux_x86-64_deb/DEBS && \
+ dpkg -i *.deb && \
+ cd / && \
+ rm -rf /tmp/libreoffice
+
+# 设置 LibreOffice 环境变量
+ENV LIBREOFFICE_PATH=/opt/libreoffice25.2/program/soffice
+
+# 从构建阶段复制已安装的依赖和生成的代码
+COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
+COPY --from=builder /usr/local/bin /usr/local/bin
+
+# 复制 PaddleOCR 模型到运行阶段
+COPY --from=builder /root/.paddlex/official_models /root/.paddlex/official_models
+
+# 安装 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
+
+# 创建supervisor配置
+RUN mkdir -p /etc/supervisor/conf.d
+COPY services/docreader/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
+
+# 暴露 gRPC 端口
+EXPOSE 50051
+
+# 使用supervisor启动服务
+CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
\ No newline at end of file
diff --git a/docker/config/supervisord.conf b/docker/config/supervisord.conf
new file mode 100644
index 00000000..44039e29
--- /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 00000000..a78b0abb
--- /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 00000000..63b1dc65
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,1389 @@
+# 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 有以下方式:
+
+1. **创建租户时获取**:通过 `POST /api/v1/tenants` 接口创建新租户时,响应中会自动返回生成的 API Key。
+ ```json
+ {
+ "success": true,
+ "data": {
+ "id": 1,
+ "name": "租户名称",
+ "description": "租户描述",
+ "api_key": "生成的API密钥",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+ }
+ ```
+
+2. **查看现有租户信息**:通过 `GET /api/v1/tenants/:id` 接口查看已有租户的详细信息,响应中包含 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` - 创建新租户
+
+**请求体**:
+
+```json
+{
+ "name": "租户名称",
+ "description": "租户描述"
+}
+```
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 1,
+ "name": "租户名称",
+ "description": "租户描述",
+ "api_key": "生成的API密钥",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### GET `/tenants/:id` - 获取指定租户信息
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 1,
+ "name": "租户名称",
+ "description": "租户描述",
+ "api_key": "API密钥",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### PUT `/tenants/:id` - 更新租户信息
+
+**请求体**:
+
+```json
+{
+ "name": "新租户名称",
+ "description": "新租户描述"
+}
+```
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 1,
+ "name": "新租户名称",
+ "description": "新租户描述",
+ "api_key": "API密钥",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### DELETE `/tenants/:id` - 删除租户
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "message": "租户删除成功"
+}
+```
+
+#### GET `/tenants` - 获取租户列表
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": {
+ "items": [
+ {
+ "id": 1,
+ "name": "租户1",
+ "description": "租户1描述",
+ "api_key": "API密钥1",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ },
+ {
+ "id": 2,
+ "name": "租户2",
+ "description": "租户2描述",
+ "api_key": "API密钥2",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+ ]
+ }
+}
+```
+
+
+
+### 知识库管理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` - 创建知识库
+
+**请求体**:
+
+```json
+{
+ "name": "知识库名称",
+ "description": "知识库描述",
+ "config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200
+ },
+ "embedding_model_id": "模型ID"
+}
+```
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "知识库ID",
+ "tenant_id": 1,
+ "name": "知识库名称",
+ "description": "知识库描述",
+ "config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200
+ },
+ "embedding_model_id": "模型ID",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### GET `/knowledge-bases` - 获取知识库列表
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": "知识库ID1",
+ "tenant_id": 1,
+ "name": "知识库1",
+ "description": "知识库1描述",
+ "config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200
+ },
+ "embedding_model_id": "模型ID",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ },
+ {
+ "id": "知识库ID2",
+ "tenant_id": 1,
+ "name": "知识库2",
+ "description": "知识库2描述",
+ "config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200
+ },
+ "embedding_model_id": "模型ID",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+ ]
+}
+```
+
+#### GET `/knowledge-bases/:id` - 获取知识库详情
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "知识库ID",
+ "tenant_id": 1,
+ "name": "知识库名称",
+ "description": "知识库描述",
+ "config": {
+ "chunk_size": 1000,
+ "chunk_overlap": 200
+ },
+ "embedding_model_id": "模型ID",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### PUT `/knowledge-bases/:id` - 更新知识库
+
+**请求体**:
+
+```json
+{
+ "name": "新知识库名称",
+ "description": "新知识库描述",
+ "config": {
+ "chunk_size": 800,
+ "chunk_overlap": 150
+ }
+}
+```
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "知识库ID",
+ "tenant_id": 1,
+ "name": "新知识库名称",
+ "description": "新知识库描述",
+ "config": {
+ "chunk_size": 800,
+ "chunk_overlap": 150
+ },
+ "embedding_model_id": "模型ID",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### DELETE `/knowledge-bases/:id` - 删除知识库
+
+**响应**:
+
+```json
+{
+ "success": true
+}
+```
+
+#### GET `/knowledge-bases/:id/hybrid-search` - 混合搜索知识库内容
+
+**查询参数**:
+
+- `query`: 搜索关键词(必填)
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": "分块ID",
+ "content": "匹配到的内容片段",
+ "metadata": {
+ "source": "来源文件",
+ "page": 1
+ },
+ "knowledge_title": "人工智能导论.pdf",
+ "score": 0.92
+ },
+ {
+ "id": "分块ID",
+ "content": "匹配到的内容片段",
+ "metadata": {
+ "source": "来源文件",
+ "page": 2
+ },
+ "knowledge_title": "人工智能导论.pdf",
+ "score": 0.88
+ }
+ ]
+}
+```
+
+
+
+### 知识管理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` - 从文件创建知识
+
+**请求格式**:
+
+```
+Content-Type: multipart/form-data
+
+file: [上传的文件]
+```
+
+**响应**:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "4f0845e3-8f6e-4d5a-816b-196e34070617",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000003",
+ "type": "file",
+ "title": "彗星.txt",
+ "description": "",
+ "source": "",
+ "parse_status": "failed",
+ "enable_status": "disabled",
+ "embedding_model_id": "model-embedding-00000001",
+ "file_name": "彗星.txt",
+ "file_type": "txt",
+ "file_size": 7710,
+ "file_path": "",
+ "created_at": "2025-04-16T04:15:01.633797Z",
+ "updated_at": "2025-04-16T04:15:03.24784Z",
+ "processed_at": null,
+ "error_message": "EmbedBatch API error: Http Status 403 Forbidden"
+ }
+}
+```
+
+#### POST `/knowledge-bases/:id/knowledge/url` - 从 URL 创建知识
+
+请求体:
+
+```json
+{
+ "url": "https://example.com/document.pdf"
+}
+```
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "4f0845e3-8f6e-4d5a-816b-196e34070617",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000003",
+ "type": "file",
+ "title": "彗星.txt",
+ "description": "",
+ "source": "",
+ "parse_status": "failed",
+ "enable_status": "disabled",
+ "embedding_model_id": "model-embedding-00000001",
+ "file_name": "彗星.txt",
+ "file_type": "txt",
+ "file_size": 7710,
+ "file_path": "",
+ "created_at": "2025-04-16T04:15:01.633797Z",
+ "updated_at": "2025-04-16T04:15:03.24784Z",
+ "processed_at": null,
+ "error_message": "EmbedBatch API error: Http Status 403 Forbidden"
+ }
+}
+```
+
+#### GET `/knowledge-bases/:id/knowledge?page=&page_size` - 获取知识库下的知识列表
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": "4f0845e3-8f6e-4d5a-816b-196e34070617",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000003",
+ "type": "file",
+ "title": "彗星.txt",
+ "description": "",
+ "source": "",
+ "parse_status": "failed",
+ "enable_status": "disabled",
+ "embedding_model_id": "model-embedding-00000001",
+ "file_name": "彗星.txt",
+ "file_type": "txt",
+ "file_size": 7710,
+ "file_path": "",
+ "created_at": "2025-04-16T04:15:01.633797Z",
+ "updated_at": "2025-04-16T04:15:03.24784Z",
+ "processed_at": null,
+ "error_message": "EmbedBatch API error: Http Status 403 Forbidden"
+ }
+ ],
+ "total": 98,
+ "page": 1,
+ "page_size": 20
+}
+```
+
+注:parse_status 包含 `pending/processing/failed/completed` 四种状态
+
+#### GET `/knowledge/:id` - 获取知识详情
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "4f0845e3-8f6e-4d5a-816b-196e34070617",
+ "tenant_id": 1,
+ "knowledge_base_id": "kb-00000003",
+ "type": "file",
+ "title": "彗星.txt",
+ "description": "",
+ "source": "",
+ "parse_status": "failed",
+ "enable_status": "disabled",
+ "embedding_model_id": "model-embedding-00000001",
+ "file_name": "彗星.txt",
+ "file_type": "txt",
+ "file_size": 7710,
+ "file_path": "",
+ "created_at": "2025-04-16T04:15:01.633797Z",
+ "updated_at": "2025-04-16T04:15:03.24784Z",
+ "processed_at": null,
+ "error_message": "EmbedBatch API error: Http Status 403 Forbidden"
+ }
+}
+```
+
+#### GET `/knowledge/batch` - 批量获取知识
+
+查询参数:
+
+- `ids`: 知识 ID 列表,例如:`?ids=knowledge_id_1&ids=knowledge_id_2`
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": "知识ID1",
+ "tenant_id": 1,
+ "knowledge_base_id": "知识库ID",
+ "type": "file",
+ "title": "文档1.pdf",
+ "description": "",
+ "source": "",
+ "parse_status": "completed",
+ "enable_status": "enabled",
+ "embedding_model_id": "model-embedding-00000001",
+ "file_name": "文档1.pdf",
+ "file_type": "pdf",
+ "file_size": 1024,
+ "file_path": "",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z",
+ "processed_at": "2023-01-01T00:00:01Z",
+ "error_message": ""
+ },
+ {
+ "id": "知识ID2",
+ "tenant_id": 1,
+ "knowledge_base_id": "知识库ID",
+ "type": "url",
+ "title": "网页文档",
+ "description": "",
+ "source": "https://example.com/doc",
+ "parse_status": "completed",
+ "enable_status": "enabled",
+ "embedding_model_id": "model-embedding-00000001",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z",
+ "processed_at": "2023-01-01T00:00:01Z",
+ "error_message": ""
+ }
+ ]
+}
+```
+
+#### DELETE `/knowledge/:id` - 删除知识
+
+响应:
+
+```json
+{
+ "success": true,
+ "message": "删除成功"
+}
+```
+
+#### GET `/knowledge/:id/download` - 下载知识文件
+
+响应
+
+```
+attachment
+```
+
+
+
+### 模型管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | --------------------- | --------------------- |
+| POST | `/models` | 创建模型 |
+| GET | `/models` | 获取模型列表 |
+| GET | `/models/:id` | 获取模型详情 |
+| PUT | `/models/:id` | 更新模型 |
+| DELETE | `/models/:id` | 删除模型 |
+
+#### POST `/models` - 创建模型
+
+请求体:
+
+```json
+{
+ "name": "模型名称",
+ "type": "LLM",
+ "source": "OPENAI",
+ "description": "模型描述",
+ "parameters": {
+ "model": "gpt-4",
+ "api_key": "sk-xxxxx",
+ "base_url": "https://api.openai.com"
+ }
+}
+```
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "模型ID",
+ "tenant_id": 1,
+ "name": "模型名称",
+ "type": "LLM",
+ "source": "OPENAI",
+ "description": "模型描述",
+ "parameters": {
+ "model": "gpt-4",
+ "api_key": "sk-xxxxx",
+ "base_url": "https://api.openai.com"
+ },
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### GET `/models` - 获取模型列表
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": "模型ID1",
+ "tenant_id": 1,
+ "name": "模型1",
+ "type": "LLM",
+ "source": "OPENAI",
+ "description": "模型1描述",
+ "parameters": {
+ "model": "gpt-4",
+ "api_key": "sk-xxxxx",
+ "base_url": "https://api.openai.com"
+ },
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ },
+ {
+ "id": "模型ID2",
+ "tenant_id": 1,
+ "name": "模型2",
+ "type": "EMBEDDING",
+ "source": "OPENAI",
+ "description": "模型2描述",
+ "parameters": {
+ "model": "text-embedding-ada-002",
+ "api_key": "sk-xxxxx",
+ "base_url": "https://api.openai.com"
+ },
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+ ]
+}
+```
+
+#### GET `/models/:id` - 获取模型详情
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "模型ID",
+ "tenant_id": 1,
+ "name": "模型名称",
+ "type": "LLM",
+ "source": "OPENAI",
+ "description": "模型描述",
+ "parameters": {
+ "model": "gpt-4",
+ "api_key": "sk-xxxxx",
+ "base_url": "https://api.openai.com"
+ },
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### PUT `/models/:id` - 更新模型
+
+请求体:
+
+```json
+{
+ "name": "新模型名称",
+ "description": "新模型描述",
+ "parameters": {
+ "model": "gpt-4-turbo",
+ "api_key": "sk-xxxxx",
+ "base_url": "https://api.openai.com"
+ },
+ "is_default": false
+}
+```
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "模型ID",
+ "tenant_id": 1,
+ "name": "新模型名称",
+ "type": "LLM",
+ "source": "OPENAI",
+ "description": "新模型描述",
+ "parameters": {
+ "model": "gpt-4-turbo",
+ "api_key": "sk-xxxxx",
+ "base_url": "https://api.openai.com"
+ },
+ "is_default": false,
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### DELETE `/models/:id` - 删除模型
+
+响应:
+
+```json
+{
+ "success": true,
+ "message": "模型删除成功"
+}
+```
+
+
+
+### 分块管理API
+
+| 方法 | 路径 | 描述 |
+| ------ | --------------------------- | ------------------------ |
+| GET | `/chunks/:knowledge_id` | 获取知识的分块列表 |
+| PUT | `/chunks/:knowledge_id/:id` | 更新分块 |
+| DELETE | `/chunks/:knowledge_id/:id` | 删除分块 |
+| DELETE | `/chunks/:knowledge_id` | 删除知识下的所有分块 |
+
+#### GET `/chunks/:knowledge_id?page=&page_size=` - 获取知识的分块列表
+
+查询参数:
+
+- `page`: 页码(默认 1)
+- `page_size`: 每页条数(默认 20)
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": "分块ID1",
+ "knowledge_id": "知识ID",
+ "content": "分块1内容...",
+ "is_enabled": true,
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ },
+ {
+ "id": "分块ID2",
+ "knowledge_id": "知识ID",
+ "content": "分块2内容...",
+ "is_enable": true,
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+ ],
+ "total": 50,
+ "page": 1,
+ "page_size": 20
+}
+```
+
+#### PUT `/chunks/:knowledge_id/:id` - 更新分块
+
+请求体:
+
+```json
+{
+ "content": "更新后的分块内容",
+ "metadata": {
+ "page": 1,
+ "position": "top",
+ "custom_field": "自定义字段"
+ }
+}
+```
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "分块ID",
+ "knowledge_id": "知识ID",
+ "content": "更新后的分块内容",
+ "metadata": {
+ "page": 1,
+ "position": "top",
+ "custom_field": "自定义字段"
+ },
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### DELETE `/chunks/:knowledge_id/:id` - 删除分块
+
+响应:
+
+```json
+{
+ "success": true,
+ "message": "分块删除成功"
+}
+```
+
+#### DELETE `/chunks/:knowledge_id` - 删除知识下的所有分块
+
+响应:
+
+```json
+{
+ "success": true,
+ "message": "所有分块删除成功"
+}
+```
+
+
+
+### 会话管理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` - 创建会话
+
+请求体:
+
+```json
+{
+ "knowledge_base_id": "知识库ID",
+ "session_strategy": {
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "FIXED_RESPONSE",
+ "fallback_response": "对不起,我无法回答这个问题",
+ "keyword_threshold": 0.5,
+ "vector_threshold": 0.7,
+ "rerank_model_id": "排序模型ID",
+ "rerank_top_k": 3,
+ "summary_model_id": "总结模型ID",
+ "summary_parameters": {
+ "max_tokens": 100,
+ "top_p": 0.9,
+ "top_k": 40,
+ "frequency_penalty": 0.0,
+ "presence_penalty": 0.0,
+ "repeat_penalty": 1.1,
+ "prompt": "总结对话内容",
+ "context_template": "上下文模板"
+ }
+ }
+}
+```
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "会话ID",
+ "tenant_id": 1,
+ "knowledge_base_id": "知识库ID",
+ "title": "未命名会话",
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "FIXED_RESPONSE",
+ "fallback_response": "对不起,我无法回答这个问题",
+ "keyword_threshold": 0.5,
+ "vector_threshold": 0.7,
+ "rerank_model_id": "排序模型ID",
+ "rerank_top_k": 3,
+ "summary_model_id": "总结模型ID",
+ "summary_parameters": {
+ "max_tokens": 100,
+ "top_p": 0.9,
+ "top_k": 40,
+ "frequency_penalty": 0.0,
+ "presence_penalty": 0.0,
+ "repeat_penalty": 1.1,
+ "prompt": "总结对话内容",
+ "context_template": "上下文模板"
+ },
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### GET `/sessions/:id` - 获取会话详情
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "会话ID",
+ "tenant_id": 1,
+ "knowledge_base_id": "知识库ID",
+ "title": "会话标题",
+ "max_rounds": 5,
+ "enable_rewrite": true,
+ "fallback_strategy": "FIXED_RESPONSE",
+ "fallback_response": "对不起,我无法回答这个问题",
+ "keyword_threshold": 0.5,
+ "vector_threshold": 0.7,
+ "rerank_model_id": "排序模型ID",
+ "rerank_top_k": 3,
+ "summary_model_id": "总结模型ID",
+ "summary_parameters": {
+ "max_tokens": 100,
+ "top_p": 0.9,
+ "top_k": 40,
+ "frequency_penalty": 0.0,
+ "presence_penalty": 0.0,
+ "repeat_penalty": 1.1,
+ "prompt": "总结对话内容",
+ "context_template": "上下文模板"
+ },
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### GET `/sessions?page=&page_size=` - 获取租户的会话列表
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": "会话ID1",
+ "tenant_id": 1,
+ "knowledge_base_id": "知识库ID",
+ "title": "会话1",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ },
+ {
+ "id": "会话ID2",
+ "tenant_id": 1,
+ "knowledge_base_id": "知识库ID",
+ "title": "会话2",
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+ ],
+ "total": 98,
+ "page": 1,
+ "page_size": 20
+}
+```
+
+#### PUT `/sessions/:id` - 更新会话
+
+请求体:
+
+```json
+{
+ "title": "新会话标题",
+ "max_rounds": 10,
+ "enable_rewrite": false,
+ "fallback_strategy": "NO_FALLBACK",
+ "fallback_response": "",
+ "keyword_threshold": 0.6,
+ "vector_threshold": 0.8,
+ "rerank_model_id": "新排序模型ID",
+ "rerank_top_k": 5,
+ "summary_model_id": "新总结模型ID",
+ "summary_parameters": {
+ "max_tokens": 150,
+ "top_p": 0.8,
+ "top_k": 50,
+ "frequency_penalty": 0.1,
+ "presence_penalty": 0.1,
+ "repeat_penalty": 1.2,
+ "prompt": "总结这段对话",
+ "context_template": "新上下文模板"
+ }
+}
+```
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": "会话ID",
+ "tenant_id": 1,
+ "knowledge_base_id": "知识库ID",
+ "title": "新会话标题",
+ "max_rounds": 10,
+ "enable_rewrite": false,
+ "fallback_strategy": "NO_FALLBACK",
+ "fallback_response": "",
+ "keyword_threshold": 0.6,
+ "vector_threshold": 0.8,
+ "rerank_model_id": "新排序模型ID",
+ "rerank_top_k": 5,
+ "summary_model_id": "新总结模型ID",
+ "summary_parameters": {
+ "max_tokens": 150,
+ "top_p": 0.8,
+ "top_k": 50,
+ "frequency_penalty": 0.1,
+ "presence_penalty": 0.1,
+ "repeat_penalty": 1.2,
+ "prompt": "总结这段对话",
+ "context_template": "新上下文模板"
+ },
+ "created_at": "2023-01-01T00:00:00Z",
+ "updated_at": "2023-01-01T00:00:00Z"
+ }
+}
+```
+
+#### DELETE `/sessions/:id` - 删除会话
+
+响应:
+
+```json
+{
+ "success": true,
+ "message": "会话删除成功"
+}
+```
+
+#### POST `/sessions/:session_id/generate_title` - 生成会话标题
+
+请求体:
+
+```json
+{
+ "messages": [
+ {
+ "role": "user",
+ "content": "你好,我想了解关于人工智能的知识"
+ },
+ {
+ "role": "assistant",
+ "content": "人工智能是计算机科学的一个分支..."
+ }
+ ]
+}
+```
+
+响应:
+
+```json
+{
+ "success": true,
+ "data": "关于人工智能的对话"
+}
+```
+
+#### GET `/sessions/continue-stream/:session_id` - 继续未完成的会话
+
+**查询参数**:
+- `message_id`: 从 `/messages/:session_id/load` 接口中获取的 `is_completed` 为 `false` 的消息 ID
+
+**响应格式**:
+服务器端事件流(Server-Sent Events),与 `/knowledge-chat/:session_id` 返回结果一致
+
+
+
+### 聊天功能API
+
+| 方法 | 路径 | 描述 |
+| ---- | ----------------------------- | ------------------------ |
+| POST | `/knowledge-chat/:session_id` | 基于知识库的问答 |
+| POST | `/knowledge-search` | 基于知识库的搜索知识 |
+
+#### POST `/knowledge-chat/:session_id` - 基于知识库的问答
+
+**请求体**:
+
+```json
+{
+ "query": "人工智能的定义是什么?"
+}
+```
+
+**响应格式**:
+服务器端事件流(Server-Sent Events,Content-Type: text/event-stream)
+
+**响应示例**:
+
+```
+event: message
+data: {"id":"消息ID","response_type":"references","done":false,"knowledge_references":[{"id":"分块ID1","content":"人工智能的定义...","knowledge_title":"人工智能导论.pdf"},{"id":"分块ID2","content":"关于AI的更多信息...","knowledge_title":"人工智能导论.pdf"}]}
+
+event: message
+data: {"id":"消息ID","content":"人工","created_at":"2023-01-01T00:00:00Z","done":false}
+
+event: message
+data: {"id":"消息ID","content":"人工智能是","created_at":"2023-01-01T00:00:00Z","done":false}
+
+event: message
+data: {"id":"消息ID","content":"人工智能是研究...","created_at":"2023-01-01T00:00:00Z","done":false}
+
+event: message
+data: {"id":"消息ID","content":"人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学...","created_at":"2023-01-01T00:00:00Z","done":true}
+```
+
+
+
+### 消息管理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)
+
+响应:
+
+```json
+{
+ "data": [
+ {
+ "id": "22bafcce-a9c0-4dbd-8ce7-a881a2943e5f",
+ "session_id": "76218775-0af1-4933-9f76-40d0a6622e76",
+ "request_id": "6965bdcc-8264-43d5-b9f4-6bd844aa3aa7",
+ "content": "印度的国土面积",
+ "role": "user",
+ "knowledge_references": null,
+ "created_at": "2025-04-18T11:57:31.310671+08:00",
+ "updated_at": "2025-04-18T11:57:31.320384+08:00",
+ "is_completed": true
+ },
+ {
+ "id": "6c9564c8-4801-47d6-86ee-2ed58c1e331e",
+ "session_id": "76218775-0af1-4933-9f76-40d0a6622e76",
+ "request_id": "6965bdcc-8264-43d5-b9f4-6bd844aa3aa7",
+ "content": "",
+ "role": "assistant",
+ "knowledge_references": [
+ {
+ "id": "b3f489f8-278c-4f67-83ca-97b7bc39f35b",
+ "content": "",
+ "knowledge_id": "6c595abb-3086-408c-9e2e-40c543af23b6",
+ "knowledge_tite": "openaiassets_cfa8f2941d6b41cc79b91b4553ff8a4f_110591700536724035.txt"
+ },
+ {
+ "id": "71008e5c-12bd-4da3-bd8b-799b6af0c112",
+ "content": "",
+ "knowledge_id": "6c595abb-3086-408c-9e2e-40c543af23b6",
+ "knowledge_tite": "openaiassets_cfa8f2941d6b41cc79b91b4553ff8a4f_110591700536724035.txt"
+ }
+ ],
+ "created_at": "2025-04-18T11:57:45.499279+08:00",
+ "updated_at": "2025-04-18T11:57:45.513845+08:00",
+ "is_completed": false
+ }
+ ],
+ "success": true
+}
+```
+
+#### DELETE `/messages/:session_id/:id` - 删除消息
+
+响应:
+
+```json
+{
+ "success": true,
+ "message": "消息删除成功"
+}
+```
+
+
+
+### 评估功能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=:task_id' \
+--header 'X-API-Key: sk-00000001abcdefg123456'
+```
+
+**响应示例**:
+
+```json
+{
+ "data": {
+ "id": ":task_id",
+ "tenant_id": 1,
+ "embedding_id": "default",
+ "rerank_id": "default",
+ "chat_id": "default",
+ "start_time": "2025-04-27T17:59:35.662145114+08:00",
+ "status": 1,
+ "total": 100,
+ "finished": 27,
+ "metric": {
+ "retrieval_metrics": {
+ "precision": 0.26419753086419756,
+ "recall": 1,
+ "ndcg3": 0.9716533097411622,
+ "ndcg10": 0.9914200384804508,
+ "mrr": 1,
+ "map": 0.9808641975308641
+ },
+ "generation_metrics": {
+ "bleu1": 0.07886284400848455,
+ "bleu2": 0.06475994660439699,
+ "bleu4": 0.046991784754461315,
+ "rouge1": 0.1922821051422303,
+ "rouge2": 0.0941559283518759,
+ "rougel": 0.1837134727619397
+ }
+ }
+ },
+ "success": true
+}
+```
+
+#### POST `/evaluation` - 创建评估任务
+
+**请求参数**:
+- `embedding_id`(可选): 评估使用的嵌入模型,默认使用 model-embedding-00000001 模型
+- `chat_id`(可选): 评估使用的对话模型,默认使用 model-knowledgeqa-00000003 模型
+- `rerank_id`(可选): 评估使用的重排序模型,默认使用 model-rerank-00000002 模型
+- `dataset_id`(可选): 评估使用的数据集,暂时只支持官方测试数据集
+
+**请求示例**:
+
+```bash
+curl --location 'http://localhost:8080/api/v1/evaluation' \
+--header 'X-API-Key: sk-00000001abcdefg123456' \
+--header 'Content-Type: application/json' \
+--data '{}'
+```
+
+**响应示例**:
+
+```json
+{
+ "data": {
+ "id": "6f43d272-d65f-4005-96ad-104b7761ea65",
+ "tenant_id": 1,
+ "embedding_id": "default",
+ "rerank_id": "default",
+ "chat_id": "default",
+ "start_time": "2025-04-27T17:59:35.662145114+08:00",
+ "status": 1
+ },
+ "success": true
+}
+```
+
+
\ No newline at end of file
diff --git a/docs/images/answer.png b/docs/images/answer.png
new file mode 100644
index 0000000000000000000000000000000000000000..f601a8aa2b3b5fe354c9652f72a7a128b00398b3
GIT binary patch
literal 853609
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
<1HUG
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