From ad346a7e1f1c35b09a5550c1b60cebe68f0619bf Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 09 六月 2026 15:26:33 +0800
Subject: [PATCH] feat(ai): 集成 Pinecone 向量数据库并实现知识库 RAG 功能

---
 src/main/java/com/ruoyi/approve/dto/KnowledgeBaseVectorVO.java                   |   21 
 src/main/java/com/ruoyi/approve/pojo/KnowledgeBase.java                          |   15 
 src/main/java/com/ruoyi/approve/service/KnowledgeBaseVectorService.java          |   43 +
 src/main/java/com/ruoyi/ai/service/impl/KnowledgeRagServiceImpl.java             |  343 +++++++++
 src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java          |  140 +++
 src/main/java/com/ruoyi/approve/mapper/KnowledgeBaseVectorMapper.java            |   39 +
 src/main/java/com/ruoyi/approve/pojo/KnowledgeBaseVector.java                    |   80 ++
 src/main/java/com/ruoyi/ai/controller/KnowledgeChatController.java               |   85 ++
 doc/知识库RAG功能实现文档.md                                                              | 1034 ++++++++++++++++++++++++++++
 src/main/java/com/ruoyi/ai/service/KnowledgeRagService.java                      |   34 
 src/main/java/com/ruoyi/approve/service/impl/KnowledgeBaseVectorServiceImpl.java |  144 ++++
 src/main/java/com/ruoyi/ai/dto/KnowledgeChatRequest.java                         |   21 
 doc/sql/20260609_knowledge_base_vector.sql                                       |   30 
 src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java                      |   44 
 src/main/resources/application.yml                                               |    7 
 src/main/java/com/ruoyi/ai/assistant/KnowledgeChatAgent.java                     |   35 
 16 files changed, 2,085 insertions(+), 30 deletions(-)

diff --git a/doc/sql/20260609_knowledge_base_vector.sql b/doc/sql/20260609_knowledge_base_vector.sql
new file mode 100644
index 0000000..7b4323f
--- /dev/null
+++ b/doc/sql/20260609_knowledge_base_vector.sql
@@ -0,0 +1,30 @@
+-- 鐭ヨ瘑搴撳悜閲忔绱㈠姛鑳芥暟鎹簱鍙樻洿
+-- 鎵ц鍓嶈纭繚 knowledge_base 琛ㄥ凡瀛樺湪
+
+-- 1. knowledge_base 琛ㄥ鍔犲瓧娈�
+ALTER TABLE knowledge_base
+ADD COLUMN IF NOT EXISTS file_count INT DEFAULT 0 COMMENT '鏂囦欢鏁伴噺',
+ADD COLUMN IF NOT EXISTS total_chunk_count INT DEFAULT 0 COMMENT '鎬诲垏鐗囨暟閲�',
+ADD COLUMN IF NOT EXISTS description VARCHAR(500) COMMENT '鐭ヨ瘑搴撴弿杩�';
+
+-- 2. 鍒涘缓鐭ヨ瘑搴撴枃浠跺悜閲忚褰曡〃
+CREATE TABLE IF NOT EXISTS knowledge_base_vector (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭ID',
+    knowledge_base_id BIGINT NOT NULL COMMENT '鍏宠仈鐭ヨ瘑搴揑D',
+    storage_blob_id BIGINT NOT NULL COMMENT '鍏宠仈鏂囦欢blob ID',
+    file_name VARCHAR(255) NOT NULL COMMENT '鏂囦欢鍚嶇О',
+    file_type VARCHAR(50) NOT NULL COMMENT '鏂囦欢绫诲瀷(docx/pdf/xlsx/txt绛�)',
+    vector_status TINYINT DEFAULT 0 COMMENT '鍚戦噺鍖栫姸鎬�: 0-寰呭鐞�, 1-澶勭悊涓�, 2-宸插畬鎴�, 3-澶辫触',
+    vector_error VARCHAR(500) COMMENT '鍚戦噺鍖栧け璐ュ師鍥�',
+    chunk_count INT DEFAULT 0 COMMENT '鍒囩墖鏁伴噺',
+    namespace VARCHAR(100) COMMENT '鍚戦噺鍛藉悕绌洪棿',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    create_user INT COMMENT '鍒涘缓浜�',
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+    update_user INT COMMENT '鏇存柊浜�',
+    tenant_id BIGINT COMMENT '绉熸埛ID',
+    dept_id BIGINT COMMENT '閮ㄩ棬ID',
+    INDEX idx_knowledge_base_id (knowledge_base_id),
+    INDEX idx_storage_blob_id (storage_blob_id),
+    INDEX idx_vector_status (vector_status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鐭ヨ瘑搴撴枃浠跺悜閲忚褰曡〃';
\ No newline at end of file
diff --git "a/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md" "b/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..4a94cc5
--- /dev/null
+++ "b/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md"
@@ -0,0 +1,1034 @@
+# 鐭ヨ瘑搴揜AG鍚戦噺妫�绱㈠姛鑳藉疄鐜版枃妗�
+
+## 涓�銆佸姛鑳芥杩�
+
+鍩轰簬 RAG锛圧etrieval-Augmented Generation锛夋妧鏈疄鐜扮煡璇嗗簱闂瓟鍔熻兘锛屾敮鎸侊細
+- 鐭ヨ瘑搴撶鐞嗭紙CRUD锛�
+- 鏂囦欢涓婁紶涓庡悜閲忓寲澶勭悊
+- 鍩轰簬鍚戦噺妫�绱㈢殑鏅鸿兘闂瓟
+- 澶氱鏂囦欢鏍煎紡鏀寔锛坱xt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df锛�
+
+## 浜屻�佹妧鏈灦鏋�
+
+### 2.1 鎶�鏈爤
+| 缁勪欢 | 鎶�鏈� |
+|------|------|
+| 鍚戦噺鏁版嵁搴� | Pinecone |
+| Embedding妯″瀷 | 闃块噷浜� DashScope text-embedding-v3 |
+| LLM | 闃块噷浜戦�氫箟鍗冮棶 qwen-max |
+| 妗嗘灦 | langchain4j |
+| ORM | MyBatis-Plus |
+
+### 2.2 鏋舵瀯鍥�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                        鍓嶇搴旂敤                              鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                     Controller Layer                         鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹� KnowledgeBaseCtrl   鈹�  鈹� KnowledgeChatController     鈹�   鈹�
+鈹�  鈹� (鐭ヨ瘑搴撶鐞�)         鈹�  鈹� (鐭ヨ瘑搴撻棶绛�)                 鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                      Service Layer                           鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹侹nowledgeBaseService 鈹�  鈹� KnowledgeRagService         鈹�   鈹�
+鈹�  鈹� (鐭ヨ瘑搴揅RUD)         鈹�  鈹� (鍚戦噺鍖�/妫�绱�)                鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                      AI Layer                                鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹� KnowledgeChatAgent  鈹�  鈹� EmbeddingStore (Pinecone)   鈹�   鈹�
+鈹�  鈹� (闂瓟Agent)          鈹�  鈹� (鍚戦噺瀛樺偍)                   鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 涓夈�佸悗绔疄鐜�
+
+### 3.1 鏁版嵁搴撹璁�
+
+#### 3.1.1 鐭ヨ瘑搴撹〃锛坘nowledge_base锛�
+
+```sql
+CREATE TABLE knowledge_base (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    title VARCHAR(255) COMMENT '鐭ヨ瘑鏍囬',
+    type VARCHAR(50) COMMENT '鐭ヨ瘑绫诲瀷',
+    scenario VARCHAR(255) COMMENT '閫傜敤鍦烘櫙',
+    efficiency VARCHAR(20) COMMENT '瑙e喅鏁堢巼',
+    problem TEXT COMMENT '闂鎻忚堪',
+    solution TEXT COMMENT '瑙e喅鏂规',
+    key_points TEXT COMMENT '鍏抽敭瑕佺偣',
+    creator VARCHAR(100) COMMENT '鍒涘缓浜�',
+    usage_count INT DEFAULT 0 COMMENT '浣跨敤娆℃暟',
+    file_count INT DEFAULT 0 COMMENT '鏂囦欢鏁伴噺',
+    total_chunk_count INT DEFAULT 0 COMMENT '鎬诲垏鐗囨暟閲�',
+    description VARCHAR(500) COMMENT '鐭ヨ瘑搴撴弿杩�',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    create_user INT,
+    update_user INT,
+    tenant_id BIGINT,
+    dept_id BIGINT
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鐭ヨ瘑搴撹〃';
+```
+
+#### 3.1.2 鐭ヨ瘑搴撳悜閲忚褰曡〃锛坘nowledge_base_vector锛�
+
+```sql
+CREATE TABLE knowledge_base_vector (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭ID',
+    knowledge_base_id BIGINT NOT NULL COMMENT '鍏宠仈鐭ヨ瘑搴揑D',
+    storage_blob_id BIGINT NOT NULL COMMENT '鍏宠仈鏂囦欢blob ID',
+    file_name VARCHAR(255) NOT NULL COMMENT '鏂囦欢鍚嶇О',
+    file_type VARCHAR(50) NOT NULL COMMENT '鏂囦欢绫诲瀷',
+    vector_status TINYINT DEFAULT 0 COMMENT '鍚戦噺鍖栫姸鎬�: 0-寰呭鐞�, 1-澶勭悊涓�, 2-宸插畬鎴�, 3-澶辫触',
+    vector_error VARCHAR(500) COMMENT '鍚戦噺鍖栧け璐ュ師鍥�',
+    chunk_count INT DEFAULT 0 COMMENT '鍒囩墖鏁伴噺',
+    namespace VARCHAR(100) COMMENT '鍚戦噺鍛藉悕绌洪棿',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    create_user INT,
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    update_user INT,
+    tenant_id BIGINT,
+    dept_id BIGINT,
+    INDEX idx_knowledge_base_id (knowledge_base_id),
+    INDEX idx_storage_blob_id (storage_blob_id),
+    INDEX idx_vector_status (vector_status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鐭ヨ瘑搴撴枃浠跺悜閲忚褰曡〃';
+```
+
+### 3.2 Maven渚濊禆
+
+```xml
+<!-- langchain4j BOM -->
+<dependencyManagement>
+    <dependencies>
+        <dependency>
+            <groupId>dev.langchain4j</groupId>
+            <artifactId>langchain4j-bom</artifactId>
+            <version>1.0.0-beta3</version>
+            <type>pom</type>
+            <scope>import</scope>
+        </dependency>
+    </dependencies>
+</dependencyManagement>
+
+<dependencies>
+    <!-- langchain4j 鏍稿績 -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-spring-boot-starter</artifactId>
+    </dependency>
+
+    <!-- Pinecone 鍚戦噺鏁版嵁搴� -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-pinecone</artifactId>
+    </dependency>
+
+    <!-- 闃块噷浜� DashScope -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
+    </dependency>
+</dependencies>
+```
+
+### 3.3 閰嶇疆鏂囦欢锛坅pplication.yml锛�
+
+```yaml
+# Pinecone 鍚戦噺鏁版嵁搴撻厤缃�
+pinecone:
+  api-key: your-pinecone-api-key
+  index: your-index-name
+  namespace: knowledge-base
+
+# langchain4j 閰嶇疆
+langchain4j:
+  community:
+    dashscope:
+      streaming-chat-model:
+        api-key: your-dashscope-api-key
+        model-name: "qwen-max"
+      embedding-model:
+        api-key: your-dashscope-api-key
+        model-name: "text-embedding-v3"
+```
+
+### 3.4 鏍稿績浠g爜瀹炵幇
+
+#### 3.4.1 瀹炰綋绫�
+
+**KnowledgeBase.java**
+```java
+@Data
+@TableName("knowledge_base")
+public class KnowledgeBase implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String title;
+    private String type;
+    private String scenario;
+    private String efficiency;
+    private String problem;
+    private String solution;
+    private String keyPoints;
+    private String creator;
+    private Integer usageCount;
+    private Integer fileCount;
+    private Integer totalChunkCount;
+    private String description;
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
+```
+
+**KnowledgeBaseVector.java**
+```java
+@Data
+@TableName("knowledge_base_vector")
+public class KnowledgeBaseVector implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long knowledgeBaseId;
+    private Long storageBlobId;
+    private String fileName;
+    private String fileType;
+    private Integer vectorStatus;
+    private String vectorError;
+    private Integer chunkCount;
+    private String namespace;
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    // 鍚戦噺鍖栫姸鎬佸父閲�
+    public static final int STATUS_PENDING = 0;
+    public static final int STATUS_PROCESSING = 1;
+    public static final int STATUS_COMPLETED = 2;
+    public static final int STATUS_FAILED = 3;
+}
+```
+
+#### 3.4.2 EmbeddingStore閰嶇疆
+
+**EmbeddingStoreConfig.java**
+```java
+@Configuration
+public class EmbeddingStoreConfig {
+
+    @Value("${pinecone.api-key}")
+    private String pineconeApiKey;
+
+    @Value("${pinecone.index}")
+    private String indexName;
+
+    @Value("${pinecone.namespace}")
+    private String namespace;
+
+    @Bean
+    public Pinecone pinecone() {
+        return new Pinecone.Builder(pineconeApiKey).build();
+    }
+
+    @Bean
+    public Index pineconeIndex(Pinecone pinecone) {
+        return pinecone.getIndexConnection(indexName);
+    }
+
+    @Bean
+    public EmbeddingStore<TextSegment> embeddingStore(EmbeddingModel embeddingModel) {
+        return PineconeEmbeddingStore.builder()
+                .apiKey(pineconeApiKey)
+                .index(indexName)
+                .nameSpace(namespace)
+                .createIndex(PineconeServerlessIndexConfig.builder()
+                        .cloud("AWS")
+                        .region("us-east-1")
+                        .dimension(embeddingModel.dimension())
+                        .build())
+                .build();
+    }
+}
+```
+
+#### 3.4.3 RAG鏈嶅姟瀹炵幇
+
+**KnowledgeRagService.java**
+```java
+public interface KnowledgeRagService {
+    void processVectorAsync(Long vectorId);
+    void processVector(Long vectorId);
+    List<String> searchRelevantContent(String namespace, String query, int maxResults);
+    void deleteEmbeddings(String namespace, Long storageBlobId);
+}
+```
+
+**KnowledgeRagServiceImpl.java**锛堟牳蹇冨疄鐜帮級
+```java
+@Slf4j
+@Service
+public class KnowledgeRagServiceImpl implements KnowledgeRagService {
+
+    private final KnowledgeBaseVectorService knowledgeBaseVectorService;
+    private final StorageBlobService storageBlobService;
+    private final EmbeddingModel embeddingModel;
+    private final EmbeddingStore<TextSegment> embeddingStore;
+    private final FileProperties fileProperties;
+    private final Index pineconeIndex;
+
+    @Value("${pinecone.namespace}")
+    private String namespace;
+
+    private static final int CHUNK_SIZE = 500;
+    private static final int CHUNK_OVERLAP = 100;
+    private static final long CHUNK_THRESHOLD_BYTES = 80L * 1024 * 1024;
+    private static final int EMBEDDING_MAX_LENGTH = 8000;
+
+    @Override
+    @Async("threadPoolTaskExecutor")
+    public void processVectorAsync(Long vectorId) {
+        processVector(vectorId);
+    }
+
+    @Override
+    public void processVector(Long vectorId) {
+        KnowledgeBaseVector vector = knowledgeBaseVectorService.getById(vectorId);
+        if (vector == null) return;
+
+        try {
+            // 鏇存柊鐘舵�佷负澶勭悊涓�
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_PROCESSING, null, null);
+
+            // 鑾峰彇鏂囦欢鍐呭
+            StorageBlob blob = storageBlobService.getById(vector.getStorageBlobId());
+            File file = getFile(blob);
+            String content = extractFileContent(file, vector.getFileName());
+
+            if (content == null || content.trim().isEmpty()) {
+                throw new RuntimeException("鏂囦欢鍐呭涓虹┖");
+            }
+
+            // 鏂囨湰鍒囩墖
+            List<TextSegment> chunks;
+            boolean needChunk = file.length() > CHUNK_THRESHOLD_BYTES || content.length() > EMBEDDING_MAX_LENGTH;
+            if (needChunk) {
+                chunks = splitText(content, vector);
+            } else {
+                Map<String, Object> metadata = buildMetadata(vector);
+                chunks = List.of(TextSegment.from(content, new Metadata(metadata)));
+            }
+
+            // 鐢熸垚宓屽叆鍚戦噺骞跺瓨鍌�
+            int chunkCount = 0;
+            for (TextSegment chunk : chunks) {
+                Embedding embedding = embeddingModel.embed(chunk).content();
+                embeddingStore.add(embedding, chunk);
+                chunkCount++;
+            }
+
+            // 鏇存柊鐘舵�佷负瀹屾垚
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_COMPLETED, chunkCount, null);
+
+        } catch (Exception e) {
+            log.error("鍚戦噺鍖栧鐞嗗け璐�", e);
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_FAILED, null, e.getMessage());
+        }
+    }
+
+    @Override
+    public List<String> searchRelevantContent(String namespace, String query, int maxResults) {
+        Embedding queryEmbedding = embeddingModel.embed(query).content();
+        EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder()
+                .queryEmbedding(queryEmbedding)
+                .maxResults(maxResults)
+                .minScore(0.7)
+                .build();
+        EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest);
+        return searchResult.matches().stream()
+                .map(match -> match.embedded().text())
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void deleteEmbeddings(String namespace, Long storageBlobId) {
+        Struct filter = Struct.newBuilder()
+                .putFields("storageBlobId", Value.newBuilder()
+                        .setStructValue(Struct.newBuilder()
+                                .putFields("$eq", Value.newBuilder()
+                                        .setNumberValue(storageBlobId.doubleValue())
+                                        .build()))
+                        .build())
+                .build();
+        pineconeIndex.delete(new ArrayList<>(), false, this.namespace, filter);
+    }
+
+    private String extractFileContent(File file, String fileName) throws Exception {
+        String ext = getFileExtension(fileName);
+        if (isPlainText(ext)) {
+            return readFileWithEncoding(file);
+        }
+        if ("docx".equals(ext)) {
+            return extractDocx(file);
+        }
+        if ("xlsx".equals(ext) || "xls".equals(ext)) {
+            return extractExcel(file);
+        }
+        return readFileWithEncoding(file);
+    }
+
+    // ... 鍏朵粬杈呭姪鏂规硶
+}
+```
+
+#### 3.4.4 鐭ヨ瘑搴撻棶绛擜gent
+
+**KnowledgeChatAgent.java**
+```java
+@AiService(
+        wiringMode = EXPLICIT,
+        streamingChatModel = "qwenStreamingChatModel",
+        chatMemoryProvider = "chatMemoryProvider"
+)
+public interface KnowledgeChatAgent {
+
+    @SystemMessage("""
+            浣犳槸浼佷笟鐭ヨ瘑搴撻棶绛斿姪鎵嬨��
+            浣犻渶瑕佸熀浜庢彁渚涚殑鐭ヨ瘑搴撳唴瀹瑰洖绛旂敤鎴烽棶棰樸��
+            閬靛惊浠ヤ笅瑙勫垯锛�
+            1. 涓ユ牸鍩轰簬鐭ヨ瘑搴撳唴瀹瑰洖绛旓紝涓嶈缂栭�犱俊鎭�
+            2. 濡傛灉鐭ヨ瘑搴撲腑娌℃湁鐩稿叧淇℃伅锛屾槑纭憡鐭ョ敤鎴�
+            3. 鍥炵瓟瑕佸噯纭�佺畝娲併�佹湁鏉$悊
+            4. 寮曠敤鏉ユ簮鏃舵敞鏄�"鏍规嵁鐭ヨ瘑搴撳唴瀹�"
+            """)
+    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
+}
+```
+
+#### 3.4.5 Controller灞�
+
+**KnowledgeBaseController.java**
+```java
+@RestController
+@RequestMapping("/knowledgeBase")
+@Tag(name = "鐭ヨ瘑搴撶鐞�")
+public class KnowledgeBaseController {
+
+    @GetMapping("/getList")
+    public AjaxResult getList(@RequestParam(defaultValue = "1") long current,
+                              @RequestParam(defaultValue = "10") long size,
+                              KnowledgeBase knowledgeBase) {
+        Page page = new Page(current, size);
+        return AjaxResult.success(knowledgeBaseService.listpage(page, knowledgeBase));
+    }
+
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody KnowledgeBase knowledgeBase) {
+        return AjaxResult.success(knowledgeBaseService.save(knowledgeBase));
+    }
+
+    @PostMapping("/update")
+    public AjaxResult update(@RequestBody KnowledgeBase knowledgeBase) {
+        return AjaxResult.success(knowledgeBaseService.updateById(knowledgeBase));
+    }
+
+    @DeleteMapping("/delete")
+    public AjaxResult delete(@RequestBody List<Long> ids) {
+        return AjaxResult.success(knowledgeBaseService.removeByIds(ids));
+    }
+
+    @GetMapping("/vector/status/{knowledgeBaseId}")
+    @Operation(summary = "鏌ヨ鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��")
+    public AjaxResult getVectorStatus(@PathVariable Long knowledgeBaseId) {
+        return AjaxResult.success(knowledgeBaseVectorService.getVectorStatusByKnowledgeBaseId(knowledgeBaseId));
+    }
+
+    @PostMapping("/vector/reprocess/{vectorId}")
+    @Operation(summary = "閲嶆柊鍚戦噺鍖栨枃浠�")
+    public AjaxResult reprocessVector(@PathVariable Long vectorId) {
+        knowledgeBaseVectorService.reprocessVector(vectorId);
+        return AjaxResult.success("宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟");
+    }
+
+    @PostMapping("/file/save")
+    @Operation(summary = "淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�")
+    public AjaxResult saveKnowledgeBaseFiles(@RequestBody KnowledgeBaseFileDTO dto) {
+        // 淇濆瓨闄勪欢鍏宠仈骞惰Е鍙戝悜閲忓寲
+        // ...
+    }
+
+    @DeleteMapping("/file/delete")
+    @Operation(summary = "鍒犻櫎鐭ヨ瘑搴撴枃浠�")
+    public AjaxResult deleteKnowledgeBaseFiles(@RequestBody List<Long> vectorIds) {
+        knowledgeBaseVectorService.deleteVectors(vectorIds);
+        return AjaxResult.success();
+    }
+}
+```
+
+**KnowledgeChatController.java**
+```java
+@RestController
+@RequestMapping("/ai/knowledge")
+@Tag(name = "鐭ヨ瘑搴撻棶绛�")
+public class KnowledgeChatController {
+
+    private final KnowledgeChatAgent knowledgeChatAgent;
+    private final KnowledgeRagService knowledgeRagService;
+    private final KnowledgeBaseService knowledgeBaseService;
+
+    @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
+    @Operation(summary = "鐭ヨ瘑搴撻棶绛�")
+    public Flux<String> chat(@RequestBody KnowledgeChatRequest request) {
+        // 妫�绱㈢浉鍏冲唴瀹�
+        String namespace = "kb-" + request.getKnowledgeBaseId();
+        List<String> relevantContents = knowledgeRagService.searchRelevantContent(
+                namespace, request.getQuestion(), 5);
+
+        if (relevantContents.isEmpty()) {
+            return Flux.just("鐭ヨ瘑搴撲腑鏈壘鍒扮浉鍏冲唴瀹�");
+        }
+
+        // 鏋勫缓涓婁笅鏂�
+        StringBuilder context = new StringBuilder();
+        context.append("浠ヤ笅鏄粠鐭ヨ瘑搴撲腑妫�绱㈠埌鐨勭浉鍏冲唴瀹癸細\n\n");
+        for (int i = 0; i < relevantContents.size(); i++) {
+            context.append("銆愬唴瀹�").append(i + 1).append("銆慭n");
+            context.append(relevantContents.get(i)).append("\n\n");
+        }
+        context.append("---\n璇峰熀浜庝互涓婄煡璇嗗簱鍐呭鍥炵瓟锛歕n").append(request.getQuestion());
+
+        return knowledgeChatAgent.chat(request.getMemoryId(), context.toString());
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "鐭ヨ瘑搴撳垪琛�")
+    public AjaxResult listKnowledgeBases() {
+        return AjaxResult.success(knowledgeBaseService.list());
+    }
+}
+```
+
+---
+
+## 鍥涖�丄PI鎺ュ彛鏂囨。
+
+### 4.1 鐭ヨ瘑搴撶鐞嗘帴鍙�
+
+| 鎺ュ彛 | 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| 鑾峰彇鍒楄〃 | GET | /knowledgeBase/getList | 鍒嗛〉鏌ヨ鐭ヨ瘑搴撳垪琛� |
+| 鏂板鐭ヨ瘑搴� | POST | /knowledgeBase/add | 鍒涘缓鐭ヨ瘑搴� |
+| 鏇存柊鐭ヨ瘑搴� | POST | /knowledgeBase/update | 鏇存柊鐭ヨ瘑搴撲俊鎭� |
+| 鍒犻櫎鐭ヨ瘑搴� | DELETE | /knowledgeBase/delete | 鎵归噺鍒犻櫎鐭ヨ瘑搴� |
+| 鏌ヨ鍚戦噺鍖栫姸鎬� | GET | /knowledgeBase/vector/status/{id} | 鏌ヨ鏂囦欢鍚戦噺鍖栫姸鎬� |
+| 閲嶆柊鍚戦噺鍖� | POST | /knowledgeBase/vector/reprocess/{id} | 閲嶆柊澶勭悊澶辫触鐨勬枃浠� |
+| 淇濆瓨鏂囦欢鍏宠仈 | POST | /knowledgeBase/file/save | 涓婁紶鏂囦欢鍚庡叧鑱斿埌鐭ヨ瘑搴� |
+| 鍒犻櫎鏂囦欢 | DELETE | /knowledgeBase/file/delete | 鍒犻櫎鐭ヨ瘑搴撴枃浠� |
+
+### 4.2 鐭ヨ瘑搴撻棶绛旀帴鍙�
+
+| 鎺ュ彛 | 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| 鐭ヨ瘑搴撻棶绛� | POST | /ai/knowledge/chat | 娴佸紡杩斿洖闂瓟缁撴灉 |
+| 鐭ヨ瘑搴撳垪琛� | GET | /ai/knowledge/list | 鑾峰彇鍙�夌煡璇嗗簱鍒楄〃 |
+
+### 4.3 鎺ュ彛璇︾粏璇存槑
+
+#### 4.3.1 淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�
+
+**璇锋眰**
+```json
+POST /knowledgeBase/file/save
+{
+    "knowledgeBaseId": 1,
+    "storageBlobIds": [100, 101, 102]
+}
+```
+
+**鍝嶅簲**
+```json
+{
+    "code": 200,
+    "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 4.3.2 鐭ヨ瘑搴撻棶绛�
+
+**璇锋眰**
+```json
+POST /ai/knowledge/chat
+Content-Type: application/json
+
+{
+    "knowledgeBaseId": 1,
+    "memoryId": "session-uuid",
+    "question": "濡備綍澶勭悊搴撳瓨鐩樼偣宸紓锛�"
+}
+```
+
+**鍝嶅簲**锛圫SE娴佸紡锛�
+```
+鏍规嵁鐭ヨ瘑搴撳唴瀹癸紝搴撳瓨鐩樼偣宸紓鐨勫鐞嗘祦绋嬪涓嬶細
+
+1. 鍙戠幇宸紓鍚庯紝棣栧厛鏍稿鐩樼偣璁板綍...
+2. 妫�鏌ユ槸鍚︽湁婕忕洏鎴栭敊鐩�...
+3. ...
+```
+
+---
+
+## 浜斻�佸墠绔疄鐜�
+
+### 5.1 鐭ヨ瘑搴撶鐞嗛〉闈�
+
+```vue
+<template>
+  <div class="knowledge-base">
+    <!-- 鍒楄〃 -->
+    <el-table :data="tableData" border>
+      <el-table-column prop="title" label="鐭ヨ瘑鏍囬" />
+      <el-table-column prop="type" label="鐭ヨ瘑绫诲瀷" />
+      <el-table-column prop="fileCount" label="鏂囦欢鏁伴噺" />
+      <el-table-column prop="totalChunkCount" label="鍒囩墖鏁伴噺" />
+      <el-table-column label="鎿嶄綔">
+        <template #default="{ row }">
+          <el-button @click="handleEdit(row)">缂栬緫</el-button>
+          <el-button @click="handleFiles(row)">鏂囦欢绠$悊</el-button>
+          <el-button @click="handleChat(row)">闂瓟</el-button>
+          <el-button type="danger" @click="handleDelete(row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getKnowledgeBaseList, deleteKnowledgeBase } from '@/api/knowledge'
+
+const tableData = ref([])
+
+const loadData = async () => {
+  const res = await getKnowledgeBaseList({ current: 1, size: 10 })
+  tableData.value = res.data.records
+}
+
+onMounted(loadData)
+</script>
+```
+
+### 5.2 鏂囦欢涓婁紶涓庡悜閲忓寲鐘舵��
+
+```vue
+<template>
+  <div class="file-manager">
+    <!-- 鏂囦欢涓婁紶 -->
+    <el-upload
+      :action="uploadUrl"
+      :on-success="handleUploadSuccess"
+      multiple
+    >
+      <el-button type="primary">涓婁紶鏂囦欢</el-button>
+    </el-upload>
+
+    <!-- 鏂囦欢鍒楄〃涓庡悜閲忓寲鐘舵�� -->
+    <el-table :data="fileList">
+      <el-table-column prop="fileName" label="鏂囦欢鍚�" />
+      <el-table-column label="鍚戦噺鍖栫姸鎬�">
+        <template #default="{ row }">
+          <el-tag :type="getStatusType(row.vectorStatus)">
+            {{ getStatusText(row.vectorStatus) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="chunkCount" label="鍒囩墖鏁�" />
+      <el-table-column label="鎿嶄綔">
+        <template #default="{ row }">
+          <el-button v-if="row.vectorStatus === 3" @click="reprocess(row)">
+            閲嶆柊澶勭悊
+          </el-button>
+          <el-button type="danger" @click="deleteFile(row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script setup>
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload'
+
+// 涓婁紶鎴愬姛鍚庝繚瀛樺叧鑱�
+const uploadedBlobIds = ref([])
+
+const handleUploadSuccess = (response, file) => {
+  if (response.code === 200) {
+    uploadedBlobIds.value.push(response.data.id)
+  }
+}
+
+// 淇濆瓨鏂囦欢鍏宠仈
+const saveFiles = async () => {
+  await saveKnowledgeBaseFiles({
+    knowledgeBaseId: props.knowledgeBaseId,
+    storageBlobIds: uploadedBlobIds.value
+  })
+  // 鍒锋柊鏂囦欢鍒楄〃
+  loadFileList()
+}
+
+// 鐘舵�佹枃鏈槧灏�
+const getStatusText = (status) => {
+  const map = {
+    0: '寰呭鐞�',
+    1: '澶勭悊涓�',
+    2: '宸插畬鎴�',
+    3: '澶辫触'
+  }
+  return map[status] || '鏈煡'
+}
+
+const getStatusType = (status) => {
+  const map = {
+    0: 'info',
+    1: 'warning',
+    2: 'success',
+    3: 'danger'
+  }
+  return map[status] || 'info'
+}
+</script>
+```
+
+### 5.3 鐭ヨ瘑搴撻棶绛旂晫闈�
+
+```vue
+<template>
+  <div class="knowledge-chat">
+    <!-- 鐭ヨ瘑搴撻�夋嫨 -->
+    <el-select v-model="selectedKbId" placeholder="閫夋嫨鐭ヨ瘑搴�">
+      <el-option
+        v-for="kb in knowledgeBases"
+        :key="kb.id"
+        :label="kb.title"
+        :value="kb.id"
+      />
+    </el-select>
+
+    <!-- 瀵硅瘽鍖哄煙 -->
+    <div class="chat-messages">
+      <div
+        v-for="(msg, index) in messages"
+        :key="index"
+        :class="['message', msg.role]"
+      >
+        <div class="content">{{ msg.content }}</div>
+      </div>
+    </div>
+
+    <!-- 杈撳叆妗� -->
+    <el-input
+      v-model="inputQuestion"
+      placeholder="璇疯緭鍏ラ棶棰�"
+      @keyup.enter="sendMessage"
+    >
+      <template #append>
+        <el-button @click="sendMessage" :loading="loading">鍙戦��</el-button>
+      </template>
+    </el-input>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getKnowledgeBaseList, knowledgeChat } from '@/api/knowledge'
+
+const knowledgeBases = ref([])
+const selectedKbId = ref(null)
+const messages = ref([])
+const inputQuestion = ref('')
+const loading = ref(false)
+const memoryId = ref(crypto.randomUUID())
+
+const sendMessage = async () => {
+  if (!inputQuestion.value.trim()) return
+  if (!selectedKbId.value) {
+    ElMessage.warning('璇烽�夋嫨鐭ヨ瘑搴�')
+    return
+  }
+
+  // 娣诲姞鐢ㄦ埛娑堟伅
+  messages.value.push({
+    role: 'user',
+    content: inputQuestion.value
+  })
+
+  loading.value = true
+
+  try {
+    // 娴佸紡璇锋眰
+    const response = await fetch('/api/ai/knowledge/chat', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        knowledgeBaseId: selectedKbId.value,
+        memoryId: memoryId.value,
+        question: inputQuestion.value
+      })
+    })
+
+    // 澶勭悊SSE娴佸紡鍝嶅簲
+    const reader = response.body.getReader()
+    const decoder = new TextDecoder()
+    let aiContent = ''
+
+    messages.value.push({ role: 'assistant', content: '' })
+
+    while (true) {
+      const { done, value } = await reader.read()
+      if (done) break
+
+      const text = decoder.decode(value)
+      aiContent += text
+      messages.value[messages.value.length - 1].content = aiContent
+    }
+  } finally {
+    loading.value = false
+    inputQuestion.value = ''
+  }
+}
+
+onMounted(async () => {
+  const res = await getKnowledgeBaseList()
+  knowledgeBases.value = res.data
+})
+</script>
+```
+
+### 5.4 API灏佽
+
+```javascript
+// api/knowledge.js
+import request from '@/utils/request'
+
+// 鑾峰彇鐭ヨ瘑搴撳垪琛�
+export function getKnowledgeBaseList(params) {
+  return request({
+    url: '/knowledgeBase/getList',
+    method: 'get',
+    params
+  })
+}
+
+// 鏂板鐭ヨ瘑搴�
+export function addKnowledgeBase(data) {
+  return request({
+    url: '/knowledgeBase/add',
+    method: 'post',
+    data
+  })
+}
+
+// 鏇存柊鐭ヨ瘑搴�
+export function updateKnowledgeBase(data) {
+  return request({
+    url: '/knowledgeBase/update',
+    method: 'post',
+    data
+  })
+}
+
+// 鍒犻櫎鐭ヨ瘑搴�
+export function deleteKnowledgeBase(ids) {
+  return request({
+    url: '/knowledgeBase/delete',
+    method: 'delete',
+    data: ids
+  })
+}
+
+// 鑾峰彇鏂囦欢鍚戦噺鍖栫姸鎬�
+export function getVectorStatus(knowledgeBaseId) {
+  return request({
+    url: `/knowledgeBase/vector/status/${knowledgeBaseId}`,
+    method: 'get'
+  })
+}
+
+// 閲嶆柊鍚戦噺鍖�
+export function reprocessVector(vectorId) {
+  return request({
+    url: `/knowledgeBase/vector/reprocess/${vectorId}`,
+    method: 'post'
+  })
+}
+
+// 淇濆瓨鏂囦欢鍏宠仈
+export function saveKnowledgeBaseFiles(data) {
+  return request({
+    url: '/knowledgeBase/file/save',
+    method: 'post',
+    data
+  })
+}
+
+// 鍒犻櫎鏂囦欢
+export function deleteKnowledgeBaseFiles(vectorIds) {
+  return request({
+    url: '/knowledgeBase/file/delete',
+    method: 'delete',
+    data: vectorIds
+  })
+}
+
+// 鐭ヨ瘑搴撻棶绛旓紙娴佸紡锛�
+export async function knowledgeChat(data) {
+  const response = await fetch('/api/ai/knowledge/chat', {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(data)
+  })
+  return response.body
+}
+
+// 鑾峰彇鐭ヨ瘑搴撳垪琛紙闂瓟鐢級
+export function getKnowledgeBaseListForChat() {
+  return request({
+    url: '/ai/knowledge/list',
+    method: 'get'
+  })
+}
+```
+
+---
+
+## 鍏�佹牳蹇冩祦绋�
+
+### 6.1 鏂囦欢涓婁紶涓庡悜閲忓寲娴佺▼
+
+```
+1. 鍓嶇璋冪敤 /common/upload 涓婁紶鏂囦欢 鈫� 杩斿洖 storageBlobId
+2. 鍓嶇璋冪敤 /knowledgeBase/file/save 鍏宠仈鏂囦欢鍒扮煡璇嗗簱
+3. 鍚庣鍒涘缓 KnowledgeBaseVector 璁板綍锛堢姸鎬侊細寰呭鐞嗭級
+4. 鍚庣寮傛璋冪敤 KnowledgeRagService.processVectorAsync()
+   鈹溾攢鈹� 鏇存柊鐘舵�佷负"澶勭悊涓�"
+   鈹溾攢鈹� 鎻愬彇鏂囦欢鍐呭锛堟敮鎸佸绉嶆牸寮忥級
+   鈹溾攢鈹� 鑷姩妫�娴嬫枃浠剁紪鐮侊紙UTF-8/GBK锛�
+   鈹溾攢鈹� 鏂囨湰鍒囩墖锛堝ぇ鏂囦欢鎴栭暱鍐呭鎵嶅垏鐗囷級
+   鈹溾攢鈹� 鐢熸垚 Embedding 鍚戦噺
+   鈹溾攢鈹� 瀛樺偍鍒� Pinecone
+   鈹斺攢鈹� 鏇存柊鐘舵�佷负"瀹屾垚"鎴�"澶辫触"
+```
+
+### 6.2 鐭ヨ瘑搴撻棶绛旀祦绋�
+
+```
+1. 鐢ㄦ埛閫夋嫨鐭ヨ瘑搴擄紝杈撳叆闂
+2. 鍓嶇璋冪敤 /ai/knowledge/chat锛堟祦寮忔帴鍙o級
+3. 鍚庣澶勭悊锛�
+   鈹溾攢鈹� 鏋勫缓鍛藉悕绌洪棿锛歬b-{knowledgeBaseId}
+   鈹溾攢鈹� 璋冪敤 Embedding 妯″瀷鐢熸垚闂鍚戦噺
+   鈹溾攢鈹� 浠� Pinecone 妫�绱㈢浉鍏冲唴瀹癸紙minScore=0.7, maxResults=5锛�
+   鈹溾攢鈹� 鏋勫缓涓婁笅鏂� Prompt
+   鈹溾攢鈹� 璋冪敤 LLM 鐢熸垚鍥炵瓟
+   鈹斺攢鈹� 娴佸紡杩斿洖缁撴灉
+```
+
+---
+
+## 涓冦�佹敞鎰忎簨椤�
+
+1. **Pinecone 鍛藉悕绌洪棿**锛氫笉鑳戒娇鐢� `__default__`锛屽繀椤讳娇鐢ㄨ嚜瀹氫箟鍛藉悕绌洪棿
+2. **鏂囦欢缂栫爜**锛氳嚜鍔ㄦ娴� UTF-8/GBK锛岄伩鍏嶄贡鐮�
+3. **鍒囩墖绛栫暐**锛�
+   - 鏂囦欢 > 80MB 鎴栧唴瀹� > 8000 瀛楃鏃舵墠鍒囩墖
+   - 鍒囩墖澶у皬 500 瀛楃锛岄噸鍙� 100 瀛楃
+   - 浼樺厛鍦ㄥ彞瀛愯竟鐣屽垏鍒�
+4. **Embedding 闄愬埗**锛氶樋閲屼簯 DashScope 闄愬埗鍗曟杈撳叆鏈�澶� 8192 瀛楃
+5. **鍚戦噺鍒犻櫎**锛氫娇鐢� Pinecone 鍘熺敓瀹㈡埛绔紝閫氳繃 metadata filter 鍒犻櫎
+6. **寮傛澶勭悊**锛氬悜閲忓寲浣跨敤 `@Async` 寮傛鎵ц锛岄伩鍏嶉樆濉炴帴鍙�
+
+---
+
+## 鍏�佹枃浠舵竻鍗�
+
+### 鍚庣鏂囦欢
+```
+src/main/java/com/ruoyi/
+鈹溾攢鈹� approve/
+鈹�   鈹溾攢鈹� controller/
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseController.java
+鈹�   鈹溾攢鈹� pojo/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBase.java
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseVector.java
+鈹�   鈹溾攢鈹� service/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseService.java
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseVectorService.java
+鈹�   鈹�   鈹斺攢鈹� impl/
+鈹�   鈹�       鈹溾攢鈹� KnowledgeBaseServiceImpl.java
+鈹�   鈹�       鈹斺攢鈹� KnowledgeBaseVectorServiceImpl.java
+鈹�   鈹溾攢鈹� mapper/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseMapper.java
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseVectorMapper.java
+鈹�   鈹斺攢鈹� dto/
+鈹�       鈹斺攢鈹� KnowledgeBaseVectorVO.java
+鈹斺攢鈹� ai/
+    鈹溾攢鈹� config/
+    鈹�   鈹溾攢鈹� EmbeddingStoreConfig.java
+    鈹�   鈹斺攢鈹� XiaozhiAgentConfig.java
+    鈹溾攢鈹� controller/
+    鈹�   鈹斺攢鈹� KnowledgeChatController.java
+    鈹溾攢鈹� assistant/
+    鈹�   鈹斺攢鈹� KnowledgeChatAgent.java
+    鈹溾攢鈹� service/
+    鈹�   鈹溾攢鈹� KnowledgeRagService.java
+    鈹�   鈹斺攢鈹� impl/
+    鈹�       鈹斺攢鈹� KnowledgeRagServiceImpl.java
+    鈹斺攢鈹� dto/
+        鈹斺攢鈹� KnowledgeChatRequest.java
+```
+
+### 鍓嶇鏂囦欢
+```
+src/views/knowledge/
+鈹溾攢鈹� index.vue              # 鐭ヨ瘑搴撳垪琛�
+鈹溾攢鈹� form.vue               # 鏂板/缂栬緫琛ㄥ崟
+鈹溾攢鈹� files.vue              # 鏂囦欢绠$悊
+鈹斺攢鈹� chat.vue               # 鐭ヨ瘑搴撻棶绛�
+
+src/api/knowledge.js       # API灏佽
+```
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/ai/assistant/KnowledgeChatAgent.java b/src/main/java/com/ruoyi/ai/assistant/KnowledgeChatAgent.java
new file mode 100644
index 0000000..2d4072c
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/assistant/KnowledgeChatAgent.java
@@ -0,0 +1,35 @@
+package com.ruoyi.ai.assistant;
+
+import dev.langchain4j.service.MemoryId;
+import dev.langchain4j.service.SystemMessage;
+import dev.langchain4j.service.UserMessage;
+import dev.langchain4j.service.spring.AiService;
+import reactor.core.publisher.Flux;
+
+import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
+
+/**
+ * 鐭ヨ瘑搴撻棶绛擜gent
+ * 鍩轰簬RAG妫�绱㈠寮虹敓鎴�
+ */
+@AiService(
+        wiringMode = EXPLICIT,
+        streamingChatModel = "qwenStreamingChatModel",
+        chatMemoryProvider = "chatMemoryProviderXiaozhi"
+)
+public interface KnowledgeChatAgent {
+
+    @SystemMessage("""
+            浣犳槸浼佷笟鐭ヨ瘑搴撻棶绛斿姪鎵嬨��
+
+            浣犻渶瑕佸熀浜庢彁渚涚殑鐭ヨ瘑搴撳唴瀹瑰洖绛旂敤鎴烽棶棰樸��
+
+            閬靛惊浠ヤ笅瑙勫垯锛�
+            1. 涓ユ牸鍩轰簬鐭ヨ瘑搴撳唴瀹瑰洖绛旓紝涓嶈缂栭�犱俊鎭�
+            2. 濡傛灉鐭ヨ瘑搴撲腑娌℃湁鐩稿叧淇℃伅锛屾槑纭憡鐭ョ敤鎴�
+            3. 鍥炵瓟瑕佸噯纭�佺畝娲併�佹湁鏉$悊
+            4. 濡傛灉鍐呭杈冨锛屼娇鐢ㄥ垎鐐瑰垪琛ㄥ舰寮�
+            5. 寮曠敤鏉ユ簮鏃舵敞鏄�"鏍规嵁鐭ヨ瘑搴撳唴瀹�"
+            """)
+    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java b/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
index ca5156d..eaa487c 100644
--- a/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
+++ b/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
@@ -5,32 +5,48 @@
 import dev.langchain4j.store.embedding.EmbeddingStore;
 import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore;
 import dev.langchain4j.store.embedding.pinecone.PineconeServerlessIndexConfig;
-import org.springframework.beans.factory.annotation.Autowired;
+import io.pinecone.clients.Index;
+import io.pinecone.clients.Pinecone;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 /**
- * @author :yys
- * @date : 2025/5/2 21:07
+ * 鍚戦噺瀛樺偍閰嶇疆
  */
 @Configuration
 public class EmbeddingStoreConfig {
 
-    @Autowired
-    private EmbeddingModel embeddingModel;
+    @Value("${pinecone.api-key:pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9}")
+    private String pineconeApiKey;
+
+    @Value("${pinecone.index:xiaozhi-index}")
+    private String indexName;
+
+    @Value("${pinecone.namespace:knowledge-base}")
+    private String namespace;
 
     @Bean
-    public EmbeddingStore<TextSegment> embeddingStore() {
-        //鍒涘缓鍚戦噺瀛樺偍
+    public Pinecone pinecone() {
+        return new Pinecone.Builder(pineconeApiKey).build();
+    }
+
+    @Bean
+    public Index pineconeIndex(Pinecone pinecone) {
+        return pinecone.getIndexConnection(indexName);
+    }
+
+    @Bean
+    public EmbeddingStore<TextSegment> embeddingStore(EmbeddingModel embeddingModel) {
         return PineconeEmbeddingStore.builder()
-                .apiKey("pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9")
-                .index("xiaozhi-index")//濡傛灉鎸囧畾鐨勭储寮曚笉瀛樺湪锛屽皢鍒涘缓涓�涓柊鐨勭储寮�
-                .nameSpace("xiaozhi-namespace") //濡傛灉鎸囧畾鐨勫悕绉扮┖闂翠笉瀛樺湪锛屽皢鍒涘缓涓�涓柊鐨勫悕绉� 绌洪棿
+                .apiKey(pineconeApiKey)
+                .index(indexName)
+                .nameSpace(namespace)
                 .createIndex(PineconeServerlessIndexConfig.builder()
-                        .cloud("AWS") //鎸囧畾绱㈠紩閮ㄧ讲鍦� AWS 浜戞湇鍔′笂銆�
-                        .region("us-east-1") //鎸囧畾绱㈠紩鎵�鍦ㄧ殑 AWS 鍖哄煙涓� us-east-1銆�
-                        .dimension(embeddingModel.dimension()) //鎸囧畾绱㈠紩鐨勫悜閲忕淮搴︼紝璇ョ淮搴︿笌 embeddedModel 鐢熸垚鐨勫悜閲忕淮搴︾浉鍚屻��
+                        .cloud("AWS")
+                        .region("us-east-1")
+                        .dimension(embeddingModel.dimension())
                         .build())
                 .build();
     }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/ai/controller/KnowledgeChatController.java b/src/main/java/com/ruoyi/ai/controller/KnowledgeChatController.java
new file mode 100644
index 0000000..2738439
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/controller/KnowledgeChatController.java
@@ -0,0 +1,85 @@
+package com.ruoyi.ai.controller;
+
+import com.ruoyi.ai.assistant.KnowledgeChatAgent;
+import com.ruoyi.ai.dto.KnowledgeChatRequest;
+import com.ruoyi.ai.service.KnowledgeRagService;
+import com.ruoyi.approve.pojo.KnowledgeBase;
+import com.ruoyi.approve.service.KnowledgeBaseService;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+
+import java.util.List;
+
+/**
+ * 鐭ヨ瘑搴撻棶绛擟ontroller
+ */
+@Slf4j
+@RestController
+@RequestMapping("/ai/knowledge")
+@RequiredArgsConstructor
+@Tag(name = "鐭ヨ瘑搴撻棶绛�")
+public class KnowledgeChatController {
+
+    private final KnowledgeChatAgent knowledgeChatAgent;
+    private final KnowledgeRagService knowledgeRagService;
+    private final KnowledgeBaseService knowledgeBaseService;
+
+    /**
+     * 鐭ヨ瘑搴撻棶绛旓紙娴佸紡杩斿洖锛�
+     */
+    @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
+    @Operation(summary = "鐭ヨ瘑搴撻棶绛�")
+    public Flux<String> chat(@RequestBody KnowledgeChatRequest request) {
+        if (request.getKnowledgeBaseId() == null) {
+            return Flux.just("鐭ヨ瘑搴揑D涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(request.getMemoryId())) {
+            return Flux.just("浼氳瘽ID涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(request.getQuestion())) {
+            return Flux.just("闂涓嶈兘涓虹┖");
+        }
+
+        KnowledgeBase knowledgeBase = knowledgeBaseService.getById(request.getKnowledgeBaseId());
+        if (knowledgeBase == null) {
+            return Flux.just("鐭ヨ瘑搴撲笉瀛樺湪");
+        }
+
+        String namespace = "kb-" + request.getKnowledgeBaseId();
+
+        List<String> relevantContents = knowledgeRagService.searchRelevantContent(
+                namespace, request.getQuestion(), 5);
+
+        if (relevantContents.isEmpty()) {
+            return Flux.just("鐭ヨ瘑搴撲腑鏈壘鍒扮浉鍏冲唴瀹癸紝璇峰厛涓婁紶鐩稿叧鏂囨。銆�");
+        }
+
+        StringBuilder contextBuilder = new StringBuilder();
+        contextBuilder.append("浠ヤ笅鏄粠鐭ヨ瘑搴撲腑妫�绱㈠埌鐨勭浉鍏冲唴瀹癸細\n\n");
+        for (int i = 0; i < relevantContents.size(); i++) {
+            contextBuilder.append("銆愬唴瀹�").append(i + 1).append("銆慭n");
+            contextBuilder.append(relevantContents.get(i)).append("\n\n");
+        }
+        contextBuilder.append("---\n");
+        contextBuilder.append("璇峰熀浜庝互涓婄煡璇嗗簱鍐呭鍥炵瓟鐢ㄦ埛闂锛歕n");
+        contextBuilder.append(request.getQuestion());
+
+        return knowledgeChatAgent.chat(request.getMemoryId(), contextBuilder.toString());
+    }
+
+    /**
+     * 鐭ヨ瘑搴撳垪琛紙鐢ㄤ簬閫夋嫨鐭ヨ瘑搴擄級
+     */
+    @GetMapping("/list")
+    @Operation(summary = "鐭ヨ瘑搴撳垪琛�")
+    public AjaxResult listKnowledgeBases() {
+        List<KnowledgeBase> list = knowledgeBaseService.list();
+        return AjaxResult.success(list);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/ai/dto/KnowledgeChatRequest.java b/src/main/java/com/ruoyi/ai/dto/KnowledgeChatRequest.java
new file mode 100644
index 0000000..134c2c8
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/dto/KnowledgeChatRequest.java
@@ -0,0 +1,21 @@
+package com.ruoyi.ai.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 鐭ヨ瘑搴撻棶绛旇姹�
+ */
+@Data
+@Schema(description = "鐭ヨ瘑搴撻棶绛旇姹�")
+public class KnowledgeChatRequest {
+
+    @Schema(description = "鐭ヨ瘑搴揑D", required = true)
+    private Long knowledgeBaseId;
+
+    @Schema(description = "浼氳瘽ID锛岀敤浜庝繚鎸佷笂涓嬫枃", required = true)
+    private String memoryId;
+
+    @Schema(description = "鐢ㄦ埛鎻愰棶鍐呭", required = true)
+    private String question;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/ai/service/KnowledgeRagService.java b/src/main/java/com/ruoyi/ai/service/KnowledgeRagService.java
new file mode 100644
index 0000000..44d5f87
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/service/KnowledgeRagService.java
@@ -0,0 +1,34 @@
+package com.ruoyi.ai.service;
+
+import java.util.List;
+
+/**
+ * 鐭ヨ瘑搴揜AG鏈嶅姟
+ * 璐熻矗鏂囦欢鍚戦噺鍖栧鐞嗗拰妫�绱�
+ */
+public interface KnowledgeRagService {
+
+    /**
+     * 寮傛澶勭悊鍚戦噺鍖�
+     */
+    void processVectorAsync(Long vectorId);
+
+    /**
+     * 鍚屾澶勭悊鍚戦噺鍖�
+     */
+    void processVector(Long vectorId);
+
+    /**
+     * 妫�绱㈢浉鍏冲唴瀹�
+     * @param namespace 鍛藉悕绌洪棿
+     * @param query 鏌ヨ鏂囨湰
+     * @param maxResults 鏈�澶х粨鏋滄暟
+     * @return 鐩稿叧鍐呭鍒楄〃
+     */
+    List<String> searchRelevantContent(String namespace, String query, int maxResults);
+
+    /**
+     * 鍒犻櫎鎸囧畾鏂囦欢鐨勫悜閲忔暟鎹�
+     */
+    void deleteEmbeddings(String namespace, Long storageBlobId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/ai/service/impl/KnowledgeRagServiceImpl.java b/src/main/java/com/ruoyi/ai/service/impl/KnowledgeRagServiceImpl.java
new file mode 100644
index 0000000..44f9e45
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/service/impl/KnowledgeRagServiceImpl.java
@@ -0,0 +1,343 @@
+package com.ruoyi.ai.service.impl;
+
+import com.ruoyi.ai.service.KnowledgeRagService;
+import com.ruoyi.approve.pojo.KnowledgeBaseVector;
+import com.ruoyi.approve.service.KnowledgeBaseVectorService;
+import com.ruoyi.basic.pojo.StorageBlob;
+import com.ruoyi.basic.service.StorageBlobService;
+import com.ruoyi.common.config.FileProperties;
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+import dev.langchain4j.data.embedding.Embedding;
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
+import dev.langchain4j.store.embedding.EmbeddingSearchResult;
+import dev.langchain4j.store.embedding.EmbeddingStore;
+import io.pinecone.clients.Index;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 鐭ヨ瘑搴揜AG鏈嶅姟瀹炵幇
+ */
+@Slf4j
+@Service
+public class KnowledgeRagServiceImpl implements KnowledgeRagService {
+
+    private final KnowledgeBaseVectorService knowledgeBaseVectorService;
+    private final StorageBlobService storageBlobService;
+    private final EmbeddingModel embeddingModel;
+    private final EmbeddingStore<TextSegment> embeddingStore;
+    private final FileProperties fileProperties;
+    private final Index pineconeIndex;
+
+    @Value("${pinecone.namespace:knowledge-base}")
+    private String namespace;
+
+    public KnowledgeRagServiceImpl(
+            KnowledgeBaseVectorService knowledgeBaseVectorService,
+            StorageBlobService storageBlobService,
+            EmbeddingModel embeddingModel,
+            EmbeddingStore<TextSegment> embeddingStore,
+            FileProperties fileProperties,
+            Index pineconeIndex) {
+        this.knowledgeBaseVectorService = knowledgeBaseVectorService;
+        this.storageBlobService = storageBlobService;
+        this.embeddingModel = embeddingModel;
+        this.embeddingStore = embeddingStore;
+        this.fileProperties = fileProperties;
+        this.pineconeIndex = pineconeIndex;
+    }
+
+    private static final int CHUNK_SIZE = 500;
+    private static final int CHUNK_OVERLAP = 100;
+    private static final long CHUNK_THRESHOLD_BYTES = 80L * 1024 * 1024;
+    private static final int EMBEDDING_MAX_LENGTH = 8000;
+
+    @Override
+    @Async("threadPoolTaskExecutor")
+    public void processVectorAsync(Long vectorId) {
+        log.info("寮�濮嬪紓姝ュ悜閲忓寲澶勭悊: vectorId={}, thread={}", vectorId, Thread.currentThread().getName());
+        processVector(vectorId);
+    }
+
+    @Override
+    public void processVector(Long vectorId) {
+        log.info("寮�濮嬪鐞嗗悜閲忓寲: vectorId={}", vectorId);
+        KnowledgeBaseVector vector = knowledgeBaseVectorService.getById(vectorId);
+        if (vector == null) {
+            log.error("鍚戦噺璁板綍涓嶅瓨鍦�: {}", vectorId);
+            return;
+        }
+
+        try {
+            knowledgeBaseVectorService.updateVectorStatus(vectorId,
+                    KnowledgeBaseVector.STATUS_PROCESSING, null, null);
+
+            StorageBlob blob = storageBlobService.getById(vector.getStorageBlobId());
+            if (blob == null) {
+                throw new RuntimeException("鏂囦欢涓嶅瓨鍦�: " + vector.getStorageBlobId());
+            }
+
+            File file = getFile(blob);
+            log.info("鏂囦欢璺緞: {}, 鏄惁瀛樺湪: {}", file.getAbsolutePath(), file.exists());
+            long fileSize = file.length();
+
+            String content = extractFileContent(file, vector.getFileName());
+            log.info("鏂囦欢鍐呭闀垮害: {}", content != null ? content.length() : 0);
+
+            if (content == null || content.trim().isEmpty()) {
+                throw new RuntimeException("鏂囦欢鍐呭涓虹┖");
+            }
+
+            List<TextSegment> chunks;
+            boolean needChunk = fileSize > CHUNK_THRESHOLD_BYTES || content.length() > EMBEDDING_MAX_LENGTH;
+            if (needChunk) {
+                log.info("寮�濮嬪垏鐗�: fileSize={}, contentLength={}", fileSize, content.length());
+                chunks = splitText(content, vector);
+                log.info("鍒囩墖瀹屾垚锛屽叡 {} 涓潡", chunks.size());
+            } else {
+                log.info("鏂囦欢杈冨皬锛屼笉杩涜鍒囩墖");
+                Map<String, Object> metadata = buildMetadata(vector);
+                chunks = List.of(TextSegment.from(content, new dev.langchain4j.data.document.Metadata(metadata)));
+            }
+
+            int chunkCount = 0;
+            for (TextSegment chunk : chunks) {
+                Embedding embedding = embeddingModel.embed(chunk).content();
+                embeddingStore.add(embedding, chunk);
+                chunkCount++;
+            }
+
+            knowledgeBaseVectorService.updateVectorStatus(vectorId,
+                    KnowledgeBaseVector.STATUS_COMPLETED, chunkCount, null);
+
+            log.info("鍚戦噺鍖栧鐞嗗畬鎴�: vectorId={}, chunkCount={}", vectorId, chunkCount);
+
+        } catch (Exception e) {
+            log.error("鍚戦噺鍖栧鐞嗗け璐�: vectorId={}", vectorId, e);
+            knowledgeBaseVectorService.updateVectorStatus(vectorId,
+                    KnowledgeBaseVector.STATUS_FAILED, null, e.getMessage());
+        }
+    }
+
+    @Override
+    public List<String> searchRelevantContent(String namespace, String query, int maxResults) {
+        try {
+            Embedding queryEmbedding = embeddingModel.embed(query).content();
+
+            EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder()
+                    .queryEmbedding(queryEmbedding)
+                    .maxResults(maxResults)
+                    .minScore(0.7)
+                    .build();
+
+            EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest);
+
+            return searchResult.matches().stream()
+                    .map(match -> match.embedded().text())
+                    .collect(Collectors.toList());
+
+        } catch (Exception e) {
+            log.error("鍚戦噺妫�绱㈠け璐�: namespace={}", namespace, e);
+            return new ArrayList<>();
+        }
+    }
+
+    @Override
+    public void deleteEmbeddings(String namespace, Long storageBlobId) {
+        log.info("鍒犻櫎鍚戦噺鏁版嵁: namespace={}, storageBlobId={}", namespace, storageBlobId);
+        try {
+            Struct filter = Struct.newBuilder()
+                    .putFields("storageBlobId", Value.newBuilder()
+                            .setStructValue(Struct.newBuilder()
+                                    .putFields("$eq", Value.newBuilder()
+                                            .setNumberValue(storageBlobId.doubleValue())
+                                            .build()))
+                            .build())
+                    .build();
+
+            List<String> emptyIds = new ArrayList<>();
+            pineconeIndex.delete(emptyIds, false, this.namespace, filter);
+            log.info("鍚戦噺鍒犻櫎瀹屾垚: storageBlobId={}", storageBlobId);
+        } catch (Exception e) {
+            log.error("鍒犻櫎鍚戦噺鏁版嵁澶辫触: namespace={}, storageBlobId={}", namespace, storageBlobId, e);
+        }
+    }
+
+    private File getFile(StorageBlob blob) {
+        String path = blob.getPath();
+        if (path != null && !path.isEmpty()) {
+            return new File(new File(fileProperties.getPath(), path), blob.getUidFilename());
+        }
+        return new File(fileProperties.getPath(), blob.getUidFilename());
+    }
+
+    private String extractFileContent(File file, String fileName) throws Exception {
+        String ext = getFileExtension(fileName);
+
+        if (isPlainText(ext)) {
+            return readFileWithEncoding(file);
+        }
+
+        if ("docx".equals(ext)) {
+            return extractDocx(file);
+        }
+
+        if ("xlsx".equals(ext)) {
+            return extractXlsx(file);
+        }
+
+        if ("xls".equals(ext)) {
+            return extractXls(file);
+        }
+
+        return readFileWithEncoding(file);
+    }
+
+    private String readFileWithEncoding(File file) throws Exception {
+        byte[] bytes = Files.readAllBytes(file.toPath());
+
+        String utf8Content = new String(bytes, StandardCharsets.UTF_8);
+        if (isValidUtf8(utf8Content)) {
+            log.debug("鏂囦欢缂栫爜: UTF-8");
+            return utf8Content;
+        }
+
+        try {
+            Charset gbk = Charset.forName("GBK");
+            String gbkContent = new String(bytes, gbk);
+            log.debug("鏂囦欢缂栫爜: GBK");
+            return gbkContent;
+        } catch (Exception e) {
+            log.warn("缂栫爜妫�娴嬪け璐ワ紝浣跨敤 UTF-8");
+            return utf8Content;
+        }
+    }
+
+    private boolean isValidUtf8(String decoded) {
+        // 妫�鏌ユ浛鎹㈠瓧绗� U+FFFD (UTF-8 瑙g爜澶辫触鏃跺嚭鐜�)
+        if (decoded.contains("锟�")) {
+            return false;
+        }
+        int invalidCount = 0;
+        int checkLen = Math.min(decoded.length(), 1000);
+        for (int i = 0; i < checkLen; i++) {
+            char c = decoded.charAt(i);
+            // 妫�鏌ョ鏈変娇鐢ㄥ尯鍩� (U+E000-U+F8FF) 鎴栧紓甯告帶鍒跺瓧绗�
+            if ((c >= '顎�' && c <= '铮�') || (c < ' ' && c != '\n' && c != '\r' && c != '\t')) {
+                invalidCount++;
+            }
+        }
+        return invalidCount < checkLen * 0.05;
+    }
+
+    private String getFileExtension(String fileName) {
+        if (fileName == null || !fileName.contains(".")) {
+            return "";
+        }
+        return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
+    }
+
+    private boolean isPlainText(String ext) {
+        return "txt".equals(ext) || "md".equals(ext) || "json".equals(ext)
+                || "csv".equals(ext) || "xml".equals(ext) || "yaml".equals(ext)
+                || "yml".equals(ext);
+    }
+
+    private String extractDocx(File file) throws Exception {
+        try (var doc = new org.apache.poi.xwpf.usermodel.XWPFDocument(new java.io.FileInputStream(file));
+             var extractor = new org.apache.poi.xwpf.extractor.XWPFWordExtractor(doc)) {
+            return extractor.getText();
+        }
+    }
+
+    private String extractXlsx(File file) throws Exception {
+        try (var workbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook(file)) {
+            return extractWorkbook(workbook);
+        }
+    }
+
+    private String extractXls(File file) throws Exception {
+        try (var workbook = new org.apache.poi.hssf.usermodel.HSSFWorkbook(new java.io.FileInputStream(file))) {
+            return extractWorkbook(workbook);
+        }
+    }
+
+    private String extractWorkbook(org.apache.poi.ss.usermodel.Workbook workbook) {
+        StringBuilder text = new StringBuilder();
+        var formatter = new org.apache.poi.ss.usermodel.DataFormatter();
+        for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
+            var sheet = workbook.getSheetAt(i);
+            text.append("Sheet: ").append(sheet.getSheetName()).append("\n");
+            for (var row : sheet) {
+                for (var cell : row) {
+                    text.append(formatter.formatCellValue(cell)).append("\t");
+                }
+                text.append("\n");
+            }
+        }
+        return text.toString();
+    }
+
+    private List<TextSegment> splitText(String content, KnowledgeBaseVector vector) {
+        List<TextSegment> chunks = new ArrayList<>();
+
+        if (content.length() <= CHUNK_SIZE) {
+            Map<String, Object> metadata = buildMetadata(vector);
+            chunks.add(TextSegment.from(content, new dev.langchain4j.data.document.Metadata(metadata)));
+            return chunks;
+        }
+
+        int start = 0;
+        int chunkIndex = 0;
+        while (start < content.length()) {
+            int end = Math.min(start + CHUNK_SIZE, content.length());
+
+            if (end < content.length()) {
+                int lastPeriod = content.lastIndexOf('銆�', end);
+                int lastNewline = content.lastIndexOf('\n', end);
+                int boundary = Math.max(lastPeriod, lastNewline);
+                if (boundary > start + CHUNK_SIZE / 2) {
+                    end = boundary + 1;
+                }
+            }
+
+            String chunkText = content.substring(start, end).trim();
+            if (!chunkText.isEmpty()) {
+                Map<String, Object> metadata = buildMetadata(vector);
+                metadata.put("chunkIndex", chunkIndex);
+                chunks.add(TextSegment.from(chunkText, new dev.langchain4j.data.document.Metadata(metadata)));
+                chunkIndex++;
+            }
+
+            start = end - CHUNK_OVERLAP;
+            if (start < 0) start = 0;
+            if (start >= content.length() - CHUNK_OVERLAP) break;
+        }
+
+        return chunks;
+    }
+
+    private Map<String, Object> buildMetadata(KnowledgeBaseVector vector) {
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("knowledgeBaseId", vector.getKnowledgeBaseId());
+        metadata.put("storageBlobId", vector.getStorageBlobId());
+        metadata.put("fileName", vector.getFileName());
+        metadata.put("namespace", vector.getNamespace());
+        return metadata;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java b/src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java
index 3ebb782..ba93fb4 100644
--- a/src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java
+++ b/src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java
@@ -2,16 +2,25 @@
 
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.approve.dto.KnowledgeBaseVectorVO;
 import com.ruoyi.approve.pojo.KnowledgeBase;
+import com.ruoyi.approve.pojo.KnowledgeBaseVector;
 import com.ruoyi.approve.service.KnowledgeBaseService;
+import com.ruoyi.approve.service.KnowledgeBaseVectorService;
+import com.ruoyi.basic.dto.StorageAttachmentDTO;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.pojo.StorageBlob;
+import com.ruoyi.basic.service.StorageAttachmentService;
+import com.ruoyi.basic.service.StorageBlobService;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.framework.web.domain.AjaxResult;
-import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.AllArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @RestController
@@ -20,40 +29,42 @@
 @Tag(name = "鐭ヨ瘑搴撶鐞�")
 public class KnowledgeBaseController {
     private KnowledgeBaseService knowledgeBaseService;
+    private KnowledgeBaseVectorService knowledgeBaseVectorService;
+    private StorageAttachmentService storageAttachmentService;
+    private StorageBlobService storageBlobService;
 
-    /**銆�
+    /**
      * 鑾峰彇鍒楄〃
-     * @return
      */
     @GetMapping("/getList")
     public AjaxResult getList(@RequestParam(defaultValue = "1") long current,
                               @RequestParam(defaultValue = "10") long size, KnowledgeBase knowledgeBase) {
         Page page = new Page(current, size);
-        return AjaxResult.success(knowledgeBaseService.listpage(page,knowledgeBase));
+        return AjaxResult.success(knowledgeBaseService.listpage(page, knowledgeBase));
     }
-    /**銆�
-     * 澧炴坊
-     * @return
+
+    /**
+     * 鏂板鐭ヨ瘑搴�
      */
     @PostMapping("/add")
-    public AjaxResult add(@RequestBody KnowledgeBase knowledgeBase){
+    public AjaxResult add(@RequestBody KnowledgeBase knowledgeBase) {
         return AjaxResult.success(knowledgeBaseService.save(knowledgeBase));
     }
+
     /**
-     * 鏇存柊
-     * @return
+     * 鏇存柊鐭ヨ瘑搴�
      */
     @PostMapping("/update")
-    public AjaxResult update(@RequestBody KnowledgeBase knowledgeBase){
+    public AjaxResult update(@RequestBody KnowledgeBase knowledgeBase) {
         return AjaxResult.success(knowledgeBaseService.updateById(knowledgeBase));
     }
+
     /**
-     * 鍒犻櫎
-     * @return
+     * 鍒犻櫎鐭ヨ瘑搴�
      */
     @DeleteMapping("/delete")
-    public AjaxResult delete(@RequestBody List<Long> ids){
-        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("璇蜂紶鍏ヨ鍒犻櫎鐨処D");
+    public AjaxResult delete(@RequestBody List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) return AjaxResult.error("璇蜂紶鍏ヨ鍒犻櫎鐨処D");
         return AjaxResult.success(knowledgeBaseService.removeByIds(ids));
     }
 
@@ -65,4 +76,101 @@
         util.exportExcel(response, accountExpenses, "鐭ヨ瘑搴撶鐞嗗鍑�");
     }
 
-}
+    /**
+     * 鏌ヨ鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��
+     */
+    @GetMapping("/vector/status/{knowledgeBaseId}")
+    @Operation(summary = "鏌ヨ鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��")
+    public AjaxResult getVectorStatus(@PathVariable Long knowledgeBaseId) {
+        List<KnowledgeBaseVectorVO> list = knowledgeBaseVectorService.getVectorStatusByKnowledgeBaseId(knowledgeBaseId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 閲嶆柊鍚戦噺鍖栨枃浠�
+     */
+    @PostMapping("/vector/reprocess/{vectorId}")
+    @Operation(summary = "閲嶆柊鍚戦噺鍖栨枃浠�")
+    public AjaxResult reprocessVector(@PathVariable Long vectorId) {
+        knowledgeBaseVectorService.reprocessVector(vectorId);
+        return AjaxResult.success("宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟");
+    }
+
+    /**
+     * 淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱旓紙鏂囦欢涓婁紶鍚庤皟鐢級
+     * 涓婁紶娴佺▼锛�
+     * 1. 鍏堣皟鐢� /common/upload 涓婁紶鏂囦欢锛岃幏鍙� storageBlobDTOs
+     * 2. 鍐嶈皟鐢ㄦ鎺ュ彛鍏宠仈鏂囦欢鍒扮煡璇嗗簱骞惰Е鍙戝悜閲忓寲
+     */
+    @PostMapping("/file/save")
+    @Operation(summary = "淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�")
+    public AjaxResult saveKnowledgeBaseFiles(@RequestBody KnowledgeBaseFileDTO dto) {
+        if (dto.getKnowledgeBaseId() == null) {
+            return AjaxResult.error("鐭ヨ瘑搴揑D涓嶈兘涓虹┖");
+        }
+        if (CollectionUtils.isEmpty(dto.getStorageBlobIds())) {
+            return AjaxResult.error("鏂囦欢ID涓嶈兘涓虹┖");
+        }
+
+        // 淇濆瓨闄勪欢鍏宠仈
+        StorageAttachmentDTO attachmentDTO = new StorageAttachmentDTO();
+        attachmentDTO.setRecordType("knowledge_base");
+        attachmentDTO.setRecordId(dto.getKnowledgeBaseId());
+        attachmentDTO.setApplication("rag_file");
+        List<StorageBlobDTO> blobDTOs = new ArrayList<>();
+        for (Long blobId : dto.getStorageBlobIds()) {
+            StorageBlobDTO blobDTO = new StorageBlobDTO();
+            blobDTO.setId(blobId);
+            blobDTOs.add(blobDTO);
+        }
+        attachmentDTO.setStorageBlobDTOs(blobDTOs);
+        storageAttachmentService.saveStorageAttachment(attachmentDTO);
+
+        // 鍒涘缓鍚戦噺璁板綍骞惰Е鍙戝悜閲忓寲
+        for (Long blobId : dto.getStorageBlobIds()) {
+            StorageBlob blob = storageBlobService.getById(blobId);
+            if (blob != null) {
+                String fileName = blob.getOriginalFilename();
+                String fileType = getFileExtension(fileName);
+
+                knowledgeBaseVectorService.createVectorRecord(
+                        dto.getKnowledgeBaseId(),
+                        blobId,
+                        fileName,
+                        fileType
+                );
+            }
+        }
+
+        return AjaxResult.success();
+    }
+
+    private String getFileExtension(String fileName) {
+        if (fileName == null || !fileName.contains(".")) {
+            return "unknown";
+        }
+        return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
+    }
+
+    /**
+     * 鍒犻櫎鐭ヨ瘑搴撴枃浠�
+     */
+    @DeleteMapping("/file/delete")
+    @Operation(summary = "鍒犻櫎鐭ヨ瘑搴撴枃浠�")
+    public AjaxResult deleteKnowledgeBaseFiles(@RequestBody List<Long> vectorIds) {
+        if (CollectionUtils.isEmpty(vectorIds)) {
+            return AjaxResult.error("璇烽�夋嫨瑕佸垹闄ょ殑鏂囦欢");
+        }
+        knowledgeBaseVectorService.deleteVectors(vectorIds);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 鐭ヨ瘑搴撴枃浠禗TO
+     */
+    @lombok.Data
+    public static class KnowledgeBaseFileDTO {
+        private Long knowledgeBaseId;
+        private List<Long> storageBlobIds;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/approve/dto/KnowledgeBaseVectorVO.java b/src/main/java/com/ruoyi/approve/dto/KnowledgeBaseVectorVO.java
new file mode 100644
index 0000000..9cfb721
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/dto/KnowledgeBaseVectorVO.java
@@ -0,0 +1,21 @@
+package com.ruoyi.approve.dto;
+
+import com.ruoyi.approve.pojo.KnowledgeBaseVector;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 鐭ヨ瘑搴撴枃浠跺悜閲忕姸鎬乂O
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "鐭ヨ瘑搴撴枃浠跺悜閲忕姸鎬乂O")
+public class KnowledgeBaseVectorVO extends KnowledgeBaseVector {
+
+    @Schema(description = "鏂囦欢棰勮URL")
+    private String previewUrl;
+
+    @Schema(description = "鏂囦欢涓嬭浇URL")
+    private String downloadUrl;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/approve/mapper/KnowledgeBaseVectorMapper.java b/src/main/java/com/ruoyi/approve/mapper/KnowledgeBaseVectorMapper.java
new file mode 100644
index 0000000..5fb19fe
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/KnowledgeBaseVectorMapper.java
@@ -0,0 +1,39 @@
+package com.ruoyi.approve.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.approve.dto.KnowledgeBaseVectorVO;
+import com.ruoyi.approve.pojo.KnowledgeBaseVector;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 鐭ヨ瘑搴撴枃浠跺悜閲忚褰� Mapper
+ */
+@Mapper
+public interface KnowledgeBaseVectorMapper extends BaseMapper<KnowledgeBaseVector> {
+
+    /**
+     * 鏌ヨ鐭ヨ瘑搴撶殑鏂囦欢鍚戦噺鐘舵�佸垪琛�
+     */
+    @Select("SELECT v.*, b.path as previewUrl " +
+            "FROM knowledge_base_vector v " +
+            "LEFT JOIN storage_blob b ON v.storage_blob_id = b.id " +
+            "WHERE v.knowledge_base_id = #{knowledgeBaseId} " +
+            "ORDER BY v.create_time DESC")
+    List<KnowledgeBaseVectorVO> selectByKnowledgeBaseId(@Param("knowledgeBaseId") Long knowledgeBaseId);
+
+    /**
+     * 缁熻鐭ヨ瘑搴撶殑鏂囦欢鏁伴噺
+     */
+    @Select("SELECT COUNT(*) FROM knowledge_base_vector WHERE knowledge_base_id = #{knowledgeBaseId}")
+    int countByKnowledgeBaseId(@Param("knowledgeBaseId") Long knowledgeBaseId);
+
+    /**
+     * 缁熻鐭ヨ瘑搴撶殑鎬诲垏鐗囨暟閲�
+     */
+    @Select("SELECT COALESCE(SUM(chunk_count), 0) FROM knowledge_base_vector WHERE knowledge_base_id = #{knowledgeBaseId} AND vector_status = 2")
+    int sumChunkCountByKnowledgeBaseId(@Param("knowledgeBaseId") Long knowledgeBaseId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/approve/pojo/KnowledgeBase.java b/src/main/java/com/ruoyi/approve/pojo/KnowledgeBase.java
index 06dec8e..b15da7a 100644
--- a/src/main/java/com/ruoyi/approve/pojo/KnowledgeBase.java
+++ b/src/main/java/com/ruoyi/approve/pojo/KnowledgeBase.java
@@ -91,4 +91,19 @@
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
 
+    /**
+     * 鏂囦欢鏁伴噺
+     */
+    private Integer fileCount;
+
+    /**
+     * 鎬诲垏鐗囨暟閲�
+     */
+    private Integer totalChunkCount;
+
+    /**
+     * 鐭ヨ瘑搴撴弿杩�
+     */
+    private String description;
+
 }
diff --git a/src/main/java/com/ruoyi/approve/pojo/KnowledgeBaseVector.java b/src/main/java/com/ruoyi/approve/pojo/KnowledgeBaseVector.java
new file mode 100644
index 0000000..1db31fc
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/KnowledgeBaseVector.java
@@ -0,0 +1,80 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 鐭ヨ瘑搴撴枃浠跺悜閲忚褰曡〃
+ * knowledge_base_vector
+ */
+@Data
+@TableName("knowledge_base_vector")
+@Schema(description = "鐭ヨ瘑搴撴枃浠跺悜閲忚褰�")
+public class KnowledgeBaseVector implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    @Schema(description = "涓婚敭ID")
+    private Long id;
+
+    @Schema(description = "鍏宠仈鐭ヨ瘑搴揑D")
+    private Long knowledgeBaseId;
+
+    @Schema(description = "鍏宠仈鏂囦欢blob ID")
+    private Long storageBlobId;
+
+    @Schema(description = "鏂囦欢鍚嶇О")
+    private String fileName;
+
+    @Schema(description = "鏂囦欢绫诲瀷(docx/pdf/xlsx/txt绛�)")
+    private String fileType;
+
+    @Schema(description = "鍚戦噺鍖栫姸鎬�: 0-寰呭鐞�, 1-澶勭悊涓�, 2-宸插畬鎴�, 3-澶辫触")
+    private Integer vectorStatus;
+
+    @Schema(description = "鍚戦噺鍖栧け璐ュ師鍥�")
+    private String vectorError;
+
+    @Schema(description = "鍒囩墖鏁伴噺")
+    private Integer chunkCount;
+
+    @Schema(description = "鍚戦噺鍛藉悕绌洪棿")
+    private String namespace;
+
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "鍒涘缓浜�")
+    private Integer createUser;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "鏇存柊鏃堕棿")
+    private LocalDateTime updateTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @Schema(description = "鏇存柊浜�")
+    private Integer updateUser;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "绉熸埛ID")
+    private Long tenantId;
+
+    @TableField(fill = FieldFill.INSERT)
+    @Schema(description = "閮ㄩ棬ID")
+    private Long deptId;
+
+    // 鍚戦噺鍖栫姸鎬佸父閲�
+    public static final int STATUS_PENDING = 0;
+    public static final int STATUS_PROCESSING = 1;
+    public static final int STATUS_COMPLETED = 2;
+    public static final int STATUS_FAILED = 3;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/approve/service/KnowledgeBaseVectorService.java b/src/main/java/com/ruoyi/approve/service/KnowledgeBaseVectorService.java
new file mode 100644
index 0000000..10f2ca9
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/KnowledgeBaseVectorService.java
@@ -0,0 +1,43 @@
+package com.ruoyi.approve.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.approve.dto.KnowledgeBaseVectorVO;
+import com.ruoyi.approve.pojo.KnowledgeBaseVector;
+
+import java.util.List;
+
+/**
+ * 鐭ヨ瘑搴撴枃浠跺悜閲忚褰� Service
+ */
+public interface KnowledgeBaseVectorService extends IService<KnowledgeBaseVector> {
+
+    /**
+     * 鏌ヨ鐭ヨ瘑搴撶殑鏂囦欢鍚戦噺鐘舵�佸垪琛�
+     */
+    List<KnowledgeBaseVectorVO> getVectorStatusByKnowledgeBaseId(Long knowledgeBaseId);
+
+    /**
+     * 鍒涘缓鍚戦噺璁板綍骞惰Е鍙戝紓姝ュ悜閲忓寲
+     */
+    KnowledgeBaseVector createVectorRecord(Long knowledgeBaseId, Long storageBlobId, String fileName, String fileType);
+
+    /**
+     * 鏇存柊鍚戦噺鐘舵��
+     */
+    void updateVectorStatus(Long id, Integer status, Integer chunkCount, String error);
+
+    /**
+     * 閲嶆柊澶勭悊鍚戦噺鍖�
+     */
+    void reprocessVector(Long id);
+
+    /**
+     * 鍒犻櫎鍚戦噺璁板綍鍙婄浉鍏冲悜閲忔暟鎹�
+     */
+    void deleteVector(Long id);
+
+    /**
+     * 鎵归噺鍒犻櫎鍚戦噺璁板綍
+     */
+    void deleteVectors(List<Long> ids);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/approve/service/impl/KnowledgeBaseVectorServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/KnowledgeBaseVectorServiceImpl.java
new file mode 100644
index 0000000..52bc0b5
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/KnowledgeBaseVectorServiceImpl.java
@@ -0,0 +1,144 @@
+package com.ruoyi.approve.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.approve.dto.KnowledgeBaseVectorVO;
+import com.ruoyi.approve.mapper.KnowledgeBaseVectorMapper;
+import com.ruoyi.approve.pojo.KnowledgeBase;
+import com.ruoyi.approve.pojo.KnowledgeBaseVector;
+import com.ruoyi.approve.service.KnowledgeBaseService;
+import com.ruoyi.approve.service.KnowledgeBaseVectorService;
+import com.ruoyi.ai.service.KnowledgeRagService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * 鐭ヨ瘑搴撴枃浠跺悜閲忚褰� Service瀹炵幇
+ */
+@Slf4j
+@Service
+public class KnowledgeBaseVectorServiceImpl extends ServiceImpl<KnowledgeBaseVectorMapper, KnowledgeBaseVector>
+        implements KnowledgeBaseVectorService {
+
+    private final KnowledgeBaseService knowledgeBaseService;
+    private final KnowledgeRagService knowledgeRagService;
+
+    public KnowledgeBaseVectorServiceImpl(
+            KnowledgeBaseService knowledgeBaseService,
+            @Lazy KnowledgeRagService knowledgeRagService) {
+        this.knowledgeBaseService = knowledgeBaseService;
+        this.knowledgeRagService = knowledgeRagService;
+    }
+
+    @Override
+    public List<KnowledgeBaseVectorVO> getVectorStatusByKnowledgeBaseId(Long knowledgeBaseId) {
+        return baseMapper.selectByKnowledgeBaseId(knowledgeBaseId);
+    }
+
+    @Override
+    public KnowledgeBaseVector createVectorRecord(Long knowledgeBaseId, Long storageBlobId,
+                                                   String fileName, String fileType) {
+        KnowledgeBase knowledgeBase = knowledgeBaseService.getById(knowledgeBaseId);
+        if (knowledgeBase == null) {
+            throw new RuntimeException("鐭ヨ瘑搴撲笉瀛樺湪: " + knowledgeBaseId);
+        }
+
+        KnowledgeBaseVector vector = new KnowledgeBaseVector();
+        vector.setKnowledgeBaseId(knowledgeBaseId);
+        vector.setStorageBlobId(storageBlobId);
+        vector.setFileName(fileName);
+        vector.setFileType(fileType);
+        vector.setVectorStatus(KnowledgeBaseVector.STATUS_PENDING);
+        vector.setNamespace("kb-" + knowledgeBaseId);
+        vector.setChunkCount(0);
+        save(vector);
+
+        // 寮傛瑙﹀彂鍚戦噺鍖栧鐞�
+        knowledgeRagService.processVectorAsync(vector.getId());
+
+        return vector;
+    }
+
+    @Override
+    public void updateVectorStatus(Long id, Integer status, Integer chunkCount, String error) {
+        KnowledgeBaseVector vector = getById(id);
+        if (vector == null) {
+            return;
+        }
+        vector.setVectorStatus(status);
+        if (chunkCount != null) {
+            vector.setChunkCount(chunkCount);
+        }
+        if (error != null) {
+            vector.setVectorError(error);
+        }
+        updateById(vector);
+
+        // 濡傛灉瀹屾垚锛屾洿鏂扮煡璇嗗簱缁熻
+        if (status == KnowledgeBaseVector.STATUS_COMPLETED) {
+            updateKnowledgeBaseStats(vector.getKnowledgeBaseId());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void reprocessVector(Long id) {
+        KnowledgeBaseVector vector = getById(id);
+        if (vector == null) {
+            throw new RuntimeException("鍚戦噺璁板綍涓嶅瓨鍦�: " + id);
+        }
+        vector.setVectorStatus(KnowledgeBaseVector.STATUS_PENDING);
+        vector.setVectorError(null);
+        vector.setChunkCount(0);
+        updateById(vector);
+
+        // 寮傛閲嶆柊澶勭悊
+        knowledgeRagService.processVectorAsync(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteVector(Long id) {
+        KnowledgeBaseVector vector = getById(id);
+        if (vector == null) {
+            return;
+        }
+
+        // 鍒犻櫎鍚戦噺搴撲腑鐨勬暟鎹�
+        try {
+            knowledgeRagService.deleteEmbeddings(vector.getNamespace(), vector.getStorageBlobId());
+        } catch (Exception e) {
+            log.error("鍒犻櫎鍚戦噺搴撴暟鎹け璐�", e);
+        }
+
+        // 鍒犻櫎璁板綍
+        removeById(id);
+
+        // 鏇存柊鐭ヨ瘑搴撶粺璁�
+        updateKnowledgeBaseStats(vector.getKnowledgeBaseId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteVectors(List<Long> ids) {
+        for (Long id : ids) {
+            deleteVector(id);
+        }
+    }
+
+    private void updateKnowledgeBaseStats(Long knowledgeBaseId) {
+        KnowledgeBase knowledgeBase = knowledgeBaseService.getById(knowledgeBaseId);
+        if (knowledgeBase == null) {
+            return;
+        }
+        int fileCount = baseMapper.countByKnowledgeBaseId(knowledgeBaseId);
+        int totalChunkCount = baseMapper.sumChunkCountByKnowledgeBaseId(knowledgeBaseId);
+        knowledgeBase.setFileCount(fileCount);
+        knowledgeBase.setTotalChunkCount(totalChunkCount);
+        knowledgeBaseService.updateById(knowledgeBase);
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 63fe8c2..ad3d4bc 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -7,6 +7,13 @@
     allow-circular-references: true
   profiles:
     active: dev
+
+# Pinecone 鍚戦噺鏁版嵁搴撻厤缃�
+pinecone:
+  api-key: pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9
+  index: xiaozhi-index
+  namespace: knowledge-base
+
 langchain4j:
   mcp:
     # MCP 鏈嶅姟绔湴鍧�锛堟牴鎹疄闄呴儴缃茬殑 MCP 鏈嶅姟璋冩暣锛�

--
Gitblit v1.9.3