diff --git a/README.md b/README.md index 9ab3963..0061d6b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Spring AI Demo -> ๐Ÿค– ไธ€ไธช็ฎ€ๆดไผ˜้›…็š„ Spring AI ๅฏน่ฏๆผ”็คบ้กน็›ฎ๏ผŒๅŸบไบŽ Spring Boot 3.2.0 ไธŽ Spring AI 1.0.0-M3 ๆž„ๅปบ๏ผŒๆ”ฏๆŒๆตๅผๅ“ๅบ”ใ€Markdown ๆธฒๆŸ“ไธŽ RAG ๆ–‡ๆกฃ้—ฎ็ญ”ใ€‚ +> ๐Ÿค– ไธ€ไธช็ฎ€ๆดไผ˜้›…็š„ Spring AI ๅฏน่ฏๆผ”็คบ้กน็›ฎ๏ผŒๅŸบไบŽ Spring Boot 3.2.0 ไธŽ Spring AI 1.0.0-M3 ๆž„ๅปบ๏ผŒๆ”ฏๆŒๆตๅผๅ“ๅบ”ใ€Markdown ๆธฒๆŸ“ไธŽ RAG ๆ™บ่ƒฝ้—ฎ็ญ”ใ€‚ [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2.0-brightgreen)](https://spring.io/projects/spring-boot) [![Spring AI](https://img.shields.io/badge/Spring%20AI-1.0.0--M3-blue)](https://spring.io/projects/spring-ai) @@ -9,14 +9,15 @@ ## โœจ ็‰นๆ€ง -- ๐ŸŒ **ๆ™บ่ƒฝๅฏน่ฏ** - ๅŸบไบŽ OpenAI/Ollama ็š„่‡ช็„ถ่ฏญ่จ€ไบคไบ’ +- ๐ŸŒ **AI ๆ™บ่ƒฝๅฏน่ฏ** - ๅŸบไบŽ Ollama ็š„่‡ช็„ถ่ฏญ่จ€ไบคไบ’ - ๐Ÿ“ก **ๆตๅผๅ“ๅบ”** - ๅฎžๆ—ถ้€ๅญ—่พ“ๅ‡บ๏ผŒไฝ“้ชŒๆต็•… - ๐Ÿ“ **Markdown ๆ”ฏๆŒ** - ๅฎŒๆ•ดๆธฒๆŸ“ไปฃ็ ๅ—ใ€่กจๆ ผใ€ๅˆ—่กจ็ญ‰ๆ ผๅผ - ๐Ÿ‘จโ€๐Ÿ’ป **ไปฃ็ ้ซ˜ไบฎ** - Highlight.js ่‡ชๅŠจ่ฏญ่จ€ๆฃ€ๆต‹ไธŽ่ฏญๆณ•็€่‰ฒ -- ๐Ÿ“š **RAG ๆ–‡ๆกฃ้—ฎ็ญ”** - ๆ–‡ๆกฃๅ‘้‡ๅŒ–ๅญ˜ๅ‚จ๏ผŒๆ™บ่ƒฝ่ฏญไน‰ๆฃ€็ดข -- ๐Ÿ—ƒ๏ธ **ๅ‘้‡ๆ•ฐๆฎๅบ“** - Milvus ๅˆ†ๅธƒๅผๅ‘้‡ๆ•ฐๆฎๅบ“้›†ๆˆ +- ๐Ÿ“š **RAG ๆ™บ่ƒฝ้—ฎ็ญ”** - ๅŸบไบŽๅ‘้‡ๆฃ€็ดข็š„ไธŠไธ‹ๆ–‡ๆ„Ÿ็Ÿฅๅ›ž็ญ” +- ๐Ÿ” **่ฏญไน‰ๆฃ€็ดข** - Milvus ๅ‘้‡ๆ•ฐๆฎๅบ“็›ธไผผๅบฆๆœ็ดข +- ๐Ÿ”— **ๅผ•็”จๆบฏๆบ** - ๅ›ž็ญ”้™„ๅธฆๆ–‡ๆกฃๆฅๆบๅผ•็”จ +- ๐Ÿ—ƒ๏ธ **ๅ‘้‡ๆ•ฐๆฎๅบ“** - Milvus ๅˆ†ๅธƒๅผๅ‘้‡ๆ•ฐๆฎๅบ“ - ๐Ÿ” **Embedding ๆœๅŠก** - SiliconFlow BAAI/bge-large-zh-v1.5 -- ๐Ÿ“” **PDF ่งฃๆž** - Apache PDFBox ๆ–‡ๆกฃๅค„็†ๆ”ฏๆŒ - ๐ŸŽจ **็ฒพ็พŽ็•Œ้ข** - ๆทฑ่‰ฒไธป้ข˜ๅ“ๅบ”ๅผ่ฎพ่ฎก ## ๐Ÿš€ ๅฟซ้€Ÿๅผ€ๅง‹ @@ -26,7 +27,7 @@ - JDK 17+ - Maven 3.8+ - [Ollama](https://ollama.ai/) ๆœฌๅœฐๆœๅŠก -- Milvus ๅ‘้‡ๆ•ฐๆฎๅบ“๏ผˆๅฏ้€‰๏ผŒ็”จไบŽ RAG๏ผ‰ +- Milvus ๅ‘้‡ๆ•ฐๆฎๅบ“ ### 1. ๅฎ‰่ฃ… Ollama @@ -64,7 +65,6 @@ mvn spring-boot:run | Embedding | SiliconFlow BAAI/bge-large-zh-v1.5 | - | | ๅ‘้‡ๆ•ฐๆฎๅบ“ | Milvus | 2.3.4 | | ๅ“ๅบ”ๅผ็ผ–็จ‹ | Spring WebFlux | 3.2.0 | -| ๆ–‡ๆกฃๅค„็† | Apache PDFBox | 2.0.29 | ### ๅ‰็ซฏ @@ -78,27 +78,25 @@ mvn spring-boot:run ``` springAiDemo/ -โ”œโ”€โ”€ src/ -โ”‚ โ””โ”€โ”€ main/ -โ”‚ โ”œโ”€โ”€ java/com/demo/ -โ”‚ โ”‚ โ”œโ”€โ”€ MyApplication.java # Spring Boot ๅฏๅŠจๅ…ฅๅฃ -โ”‚ โ”‚ โ”œโ”€โ”€ config/ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ RagConfig.java # RAG ๆ–‡ๆœฌๅˆ†ๅ‰ฒ้…็ฝฎ -โ”‚ โ”‚ โ”œโ”€โ”€ controller/ -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ChatController.java # AI ่Šๅคฉ REST API -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ DocumentController.java # ๆ–‡ๆกฃๅฏผๅ…ฅ REST API -โ”‚ โ”‚ โ””โ”€โ”€ service/ -โ”‚ โ”‚ โ””โ”€โ”€ DocumentService.java # ๆ–‡ๆกฃๅค„็†ๆœๅŠก -โ”‚ โ””โ”€โ”€ resources/ -โ”‚ โ”œโ”€โ”€ application.yaml # ๅบ”็”จ้…็ฝฎ -โ”‚ โ””โ”€โ”€ static/ # ๅ‰็ซฏ้™ๆ€่ต„ๆบ -โ”‚ โ”œโ”€โ”€ index.html # ไธป้กต้ข -โ”‚ โ”œโ”€โ”€ css/style.css # ๆทฑ่‰ฒไธป้ข˜ๆ ทๅผ -โ”‚ โ””โ”€โ”€ js/app.js # ๆตๅผๅ“ๅบ”้€ป่พ‘ +โ”œโ”€โ”€ src/main/java/com/demo/ +โ”‚ โ”œโ”€โ”€ MyApplication.java # Spring Boot ๅฏๅŠจๅ…ฅๅฃ +โ”‚ โ”œโ”€โ”€ config/ +โ”‚ โ”‚ โ””โ”€โ”€ RagConfig.java # RAG ้…็ฝฎ็ฑป +โ”‚ โ”œโ”€โ”€ controller/ +โ”‚ โ”‚ โ”œโ”€โ”€ ChatController.java # AI ่Šๅคฉ API +โ”‚ โ”‚ โ””โ”€โ”€ DocumentController.java # ๆ–‡ๆกฃๅฏผๅ…ฅ API +โ”‚ โ”œโ”€โ”€ dto/ +โ”‚ โ”‚ โ”œโ”€โ”€ ApiResponse.java # ้€š็”จๅ“ๅบ”ๅฐ่ฃ… +โ”‚ โ”‚ โ”œโ”€โ”€ ChatRequest.java # ่Šๅคฉ่ฏทๆฑ‚ DTO +โ”‚ โ”‚ โ””โ”€โ”€ ChatResponse.java # ่Šๅคฉๅ“ๅบ” DTO +โ”‚ โ””โ”€โ”€ service/ +โ”‚ โ”œโ”€โ”€ ChatService.java # RAG ่ŠๅคฉๆœๅŠก +โ”‚ โ””โ”€โ”€ DocumentService.java # ๆ–‡ๆกฃๅค„็†ๆœๅŠก โ”œโ”€โ”€ data/ -โ”‚ โ””โ”€โ”€ doris_intro.md # RAG ็คบไพ‹ๆ–‡ๆกฃ -โ”œโ”€โ”€ pom.xml -โ””โ”€โ”€ README.md +โ”‚ โ””โ”€โ”€ doris_intro.md # RAG ็คบไพ‹ๆ–‡ๆกฃ +โ””โ”€โ”€ src/main/resources/ + โ”œโ”€โ”€ application.yaml # ๅบ”็”จ้…็ฝฎ + โ””โ”€โ”€ static/ # ๅ‰็ซฏ่ต„ๆบ ``` ## ๐Ÿ’ฌ API ๆ–‡ๆกฃ @@ -108,20 +106,22 @@ springAiDemo/ | ๆ–นๆณ• | ็ซฏ็‚น | ๆ่ฟฐ | ๅ‚ๆ•ฐ | |:---|:---|:---|:---| | GET | `/api/chat/test` | ๅฅๅบทๆฃ€ๆŸฅ | - | -| GET | `/api/chat/ai` | ๅŒๆญฅ AI ๅฏน่ฏ | `msg` | -| GET | `/api/chat/ai/stream` | ๆตๅผ AI ๅฏน่ฏ | `msg` | +| GET | `/api/chat/ai` | ๆตๅผ AI ๅฏน่ฏ | `msg` | +| POST | `/api/chat` | RAG ๆ™บ่ƒฝ้—ฎ็ญ” | `{ "question": "..." }` | ### ๆ–‡ๆกฃๆŽฅๅฃ -| ๆ–นๆณ• | ็ซฏ็‚น | ๆ่ฟฐ | ๅ‚ๆ•ฐ | -|:---|:---|:---|:---| -| GET | `/api/documents/import` | ๅฏผๅ…ฅๆ–‡ๆกฃๅˆฐๅ‘้‡ๅบ“ | - | +| ๆ–นๆณ• | ็ซฏ็‚น | ๆ่ฟฐ | +|:---|:---|:---| +| GET | `/api/documents/import` | ๅฏผๅ…ฅๆ–‡ๆกฃๅˆฐๅ‘้‡ๅบ“ | ### ่ฏทๆฑ‚็คบไพ‹ -**ๅŒๆญฅๅฏน่ฏ๏ผš** +**RAG ๆ™บ่ƒฝ้—ฎ็ญ”๏ผš** ```bash -curl "http://localhost:8080/api/chat/ai?msg=ไป€ไนˆๆ˜ฏSpring AI" +curl -X POST http://localhost:8080/api/chat \ + -H "Content-Type: application/json" \ + -d '{"question": "Apache Doris ๆ˜ฏไป€ไนˆ๏ผŸ"}' ``` **ๆตๅผๅฏน่ฏ๏ผš** @@ -134,21 +134,39 @@ curl "http://localhost:8080/api/chat/ai/stream?msg=่ฎฒไธ€ไธชๆ•…ไบ‹" curl http://localhost:8080/api/documents/import ``` +### ๅ“ๅบ”ๆ ผๅผ + +**POST /api/chat ๅ“ๅบ”๏ผš** +```json +{ + "code": 200, + "message": "success", + "data": { + "answer": "Apache Doris ๆ˜ฏไธ€ไธช...", + "references": ["data/doris_intro.md"], + "timestamp": 1713000000000 + } +} +``` + ## ๐Ÿ“š RAG ๆ–‡ๆกฃ้—ฎ็ญ” -### ๅŠŸ่ƒฝ่ฏดๆ˜Ž +### ๅทฅไฝœๅŽŸ็† -ๅฐ†ๆœฌๅœฐๆ–‡ๆกฃๅฏผๅ…ฅ Milvus ๅ‘้‡ๆ•ฐๆฎๅบ“๏ผŒๅฎž็ŽฐๅŸบไบŽ่ฏญไน‰็†่งฃ็š„ๆ™บ่ƒฝ้—ฎ็ญ”ใ€‚ +``` +็”จๆˆท้—ฎ้ข˜ โ†’ Embedding โ†’ ๅ‘้‡ๆฃ€็ดข โ†’ ๆž„ๅปบไธŠไธ‹ๆ–‡ โ†’ LLM ็”Ÿๆˆๅ›ž็ญ” โ†’ ่ฟ”ๅ›ž็ญ”ๆกˆ+ๅผ•็”จ +``` + +1. **ๆ–‡ๆกฃๅฏผๅ…ฅ** - ๅฐ† `.md` / `.txt` ๆ–‡ๆกฃ่ฏปๅ–ๅนถๅˆ‡ๅ‰ฒๆˆ chunks +2. **ๅ‘้‡ๅŒ–** - ไฝฟ็”จ BAAI/bge-large-zh-v1.5 ็”Ÿๆˆ 1024 ็ปดๅ‘้‡ +3. **ๅญ˜ๅ‚จๆฃ€็ดข** -ๅญ˜ๅ…ฅ Milvus๏ผŒๅ‘้‡็›ธไผผๅบฆๆœ็ดข top-K ็ป“ๆžœ +4. **็”Ÿๆˆๅ›ž็ญ”** - ๅฐ†ๆฃ€็ดข็ป“ๆžœไฝœไธบไธŠไธ‹ๆ–‡๏ผŒLLM ็”Ÿๆˆ็ญ”ๆกˆ ### ไฝฟ็”จๆญฅ้ชค -1. ๅฐ† `.md` ๆˆ– `.txt` ๆ–‡ๆกฃๆ”พๅ…ฅ `data/` ็›ฎๅฝ• -2. ๅฏๅŠจๅบ”็”จ -3. ่ฐƒ็”จๅฏผๅ…ฅๆŽฅๅฃ๏ผš - ```bash - curl http://localhost:8080/api/documents/import - ``` -4. ้€š่ฟ‡่Šๅคฉ็•Œ้ขๆ้—ฎ็›ธๅ…ณ้—ฎ้ข˜ +1. ๅฐ†ๆ–‡ๆกฃๆ”พๅ…ฅ `data/` ็›ฎๅฝ• +2. ่ฐƒ็”จๅฏผๅ…ฅๆŽฅๅฃ๏ผš`curl http://localhost:8080/api/documents/import` +3. ้€š่ฟ‡ POST `/api/chat` ๆ้—ฎ ### ๆ–‡ๆกฃๅค„็†้…็ฝฎ @@ -160,20 +178,6 @@ document: max-num-chunk: 10000 # ๆœ€ๅคงๅ—ๆ•ฐ้‡ ``` -## ๐ŸŽจ ็•Œ้ข้ข„่งˆ - -๐Ÿงฉ **็ŽฐไปฃๅŒ–ๆทฑ่‰ฒไธป้ข˜** -- ๆธๅ˜ๆ ‡้ข˜ไธŽๆฏ›็Žป็’ƒๆ•ˆๆžœ -- ๆต็•…็š„ๆถˆๆฏๆฐ”ๆณกๅŠจ็”ป -- Markdown ไปฃ็ ๅ—้ซ˜ไบฎ -- ๆตๅผๅ“ๅบ”ๅŠจ็”ปๆŒ‡็คบๅ™จ - -๐Ÿ“ **Markdown ๆ”ฏๆŒ** -- ไปฃ็ ๅ—ไธŽ่กŒๅ†…ไปฃ็  -- ๅŠ ็ฒ—ใ€ๆ–œไฝ“ใ€ๅˆ ้™ค็บฟ -- ๆœ‰ๅบ/ๆ— ๅบๅˆ—่กจ -- ๅผ•็”จๅ—ใ€่กจๆ ผใ€้“พๆŽฅ - ## ๐Ÿ› ๏ธ ้…็ฝฎ่ฏดๆ˜Ž ### AI ๅฏน่ฏ้…็ฝฎ @@ -182,10 +186,10 @@ document: spring: ai: openai: - base-url: http://localhost:11434 # Ollama ๆœๅŠกๅœฐๅ€ + base-url: http://localhost:11434 chat: options: - model: gpt-oss:120b-cloud # ๅฏน่ฏๆจกๅž‹ + model: gpt-oss:120b-cloud temperature: 0.7 ``` @@ -200,7 +204,6 @@ spring: base-url: https://api.siliconflow.cn model: BAAI/bge-large-zh-v1.5 dimensions: 1024 - enabled: true ``` ### ๅ‘้‡ๆ•ฐๆฎๅบ“้…็ฝฎ @@ -210,47 +213,44 @@ spring: ai: vectorstore: milvus: - host: 192.168.50.103 - port: 19530 - database-name: doris_docs - collection-name: vector_store - embedding-dimension: 1024 - index-type: IVF_FLAT - metric-type: COSINE + client: + host: 192.168.50.103 + port: 19530 + databaseName: doris_docs + collectionName: vector_store ``` ## ๐ŸŽฌ ๆžถๆž„ๅ›พ ``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Client โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ ChatController โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ ChatClient โ”‚ -โ”‚ (HTTP) โ”‚ โ”‚ (Spring MVC) โ”‚ โ”‚ (Spring AI) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - โ”‚ DocumentService โ”‚ โ”‚ - โ”‚ (ๆ–‡ๆกฃๅค„็†) โ”‚ โ–ผ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ Ollama/OpenAI โ”‚ - โ–ผ โ”‚ (LLM Provider) โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ Milvus Store โ”‚ - โ”‚ (ๅ‘้‡ๅญ˜ๅ‚จ) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Client โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ +โ”‚ โ”‚ Web UI โ”‚ โ”‚ POST /api/chat โ”‚โ”‚ +โ”‚ โ”‚ (ๆตๅผ/้žๆตๅผ) โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ { question: "..." } โ”‚โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ChatController โ”‚ +โ”‚ POST /api/chat โ†’ ChatService โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ VectorStore โ”‚ โ”‚ ChatClient โ”‚ +โ”‚ (Milvus ่ฏญไน‰ๆฃ€็ดข) โ”‚ โ”‚ (Ollama LLM) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ DocumentService โ”‚ โ”‚ SiliconFlow API โ”‚ +โ”‚ (ๆ–‡ๆกฃๅˆ‡ๅ‰ฒ/ๅ‘้‡ๅŒ–) โ”‚ โ”‚ (Embedding) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` -## ๐Ÿ“„ ้ข„็•™ๅŠŸ่ƒฝ - -ไปฅไธ‹ไพ่ต–ๅทฒ้›†ๆˆ๏ผŒๅฏๆŒ‰้œ€ๅฏ็”จ๏ผš - -- ๐Ÿ“” **PDF ๆ–‡ๆกฃ่งฃๆž** - Apache PDFBox ๆ–‡ๆกฃๅค„็† - -## ๐Ÿ‘ท ๅทฒ็Ÿฅ้™ๅˆถ - -1. ๆš‚ๆ— ๅฏน่ฏๅކๅฒๆŒไน…ๅŒ– -2. ๆš‚ๆœชๅฎž็Žฐ่ฏทๆฑ‚้ข‘็އ้™ๅˆถ -3. Milvus ่ฟžๆŽฅไฟกๆฏ็กฌ็ผ–็ ๅœจ้…็ฝฎไธญ - ## ๐Ÿ“– ๆ›ดๆ–ฐๆ—ฅๅฟ— ### v1.0.0 (2026-04-19) @@ -262,10 +262,15 @@ spring: ### v1.1.0 (2026-04-19) - โœจ ๆ–ฐๅขž RAG ๆ–‡ๆกฃ้—ฎ็ญ”ๅŠŸ่ƒฝ - โœจ ๆ–ฐๅขž DocumentController ๆ–‡ๆกฃๅฏผๅ…ฅ API -- โœจ ๆ–ฐๅขž DocumentService ๆ–‡ๆกฃๅค„็†ๆœๅŠก - โœจ ้…็ฝฎ SiliconFlow Embedding ๆœๅŠก - โœจ ้›†ๆˆ Milvus ๅ‘้‡ๆ•ฐๆฎๅบ“ +### v1.2.0 (2026-04-19) +- โœจ ๆ–ฐๅขž POST /api/chat RAG ๆ™บ่ƒฝ้—ฎ็ญ”ๆŽฅๅฃ +- โœจ ๆ–ฐๅขž ChatService RAG ๆ ธๅฟƒๆœๅŠก +- โœจ ๆ–ฐๅขž ChatRequest/ChatResponse/ApiResponse DTO +- โœจ ๆ–ฐๅขžๅผ•็”จๆบฏๆบๅŠŸ่ƒฝ๏ผŒ่ฟ”ๅ›žๆ–‡ๆกฃๆฅๆบ + ## ๐Ÿ“— License MIT ยฉ 2026 Spring AI Demo diff --git a/src/main/java/com/demo/config/RagConfig.java b/src/main/java/com/demo/config/RagConfig.java index c3a4228..98a98b5 100644 --- a/src/main/java/com/demo/config/RagConfig.java +++ b/src/main/java/com/demo/config/RagConfig.java @@ -1,5 +1,7 @@ package com.demo.config; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.transformer.splitter.TokenTextSplitter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -14,6 +16,11 @@ public class RagConfig { @Value("${document.max-num-chunk}") private int maxNumChunk; + + @Bean + public ChatClient chatClient(ChatModel chatModel){ + return ChatClient.builder(chatModel).build(); + } @Bean public TokenTextSplitter tokenTextSplitter() { return new TokenTextSplitter( diff --git a/src/main/java/com/demo/controller/ChatController.java b/src/main/java/com/demo/controller/ChatController.java index 45b2e31..72c49b3 100644 --- a/src/main/java/com/demo/controller/ChatController.java +++ b/src/main/java/com/demo/controller/ChatController.java @@ -1,20 +1,25 @@ package com.demo.controller; +import com.demo.dto.ApiResponse; +import com.demo.dto.ChatRequest; +import com.demo.dto.ChatResponse; +import com.demo.service.ChatService; +import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; +@Slf4j @RestController @RequestMapping("/api/chat") public class ChatController { private final ChatClient chatClient; + private final ChatService chatService; - public ChatController(ChatClient.Builder chatClientBuilder) { + public ChatController(ChatClient.Builder chatClientBuilder, ChatService chatService) { this.chatClient = chatClientBuilder.build(); + this.chatService = chatService; } @GetMapping("/test") @@ -31,6 +36,13 @@ public class ChatController { .content(); } + @PostMapping + public ApiResponse chat(@RequestBody ChatRequest request) { + log.info("ๆŽฅๆ”ถๅˆฐ่ฏทๆฑ‚๏ผŒ็”จๆˆท็š„้—ฎ้ข˜ๆ˜ฏ๏ผš{}",request.getQuestion()); + ChatResponse response = chatService.chat(request); + return ApiResponse.success(response); + } + /** * ๆตๅผ็š„่ŠๅคฉๆŽฅๅฃ๏ผŒ่ฆๆณจๆ„ๅฆ‚ๆžœไธญๆ–‡ๆœ‰ไนฑ็ ๏ผŒๅฐฑๆ˜ฏ็ผ–็ ๅพ—้—ฎ้ข˜๏ผŒ้œ€่ฆๆทปๅŠ produces = "text/html;charset=UTF-8 * @param msg diff --git a/src/main/java/com/demo/dto/ApiResponse.java b/src/main/java/com/demo/dto/ApiResponse.java new file mode 100644 index 0000000..669d294 --- /dev/null +++ b/src/main/java/com/demo/dto/ApiResponse.java @@ -0,0 +1,46 @@ +package com.demo.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ApiResponse { + + private int code; + + private String message; + + private T data; + + public static ApiResponse success(T data) { + return ApiResponse.builder() + .code(200) + .message("success") + .data(data) + .build(); + } + + public static ApiResponse success(String message, T data) { + return ApiResponse.builder() + .code(200) + .message(message) + .data(data) + .build(); + } + + public static ApiResponse error(int code, String message) { + return ApiResponse.builder() + .code(code) + .message(message) + .build(); + } + + public static ApiResponse error(String message) { + return error(500, message); + } +} diff --git a/src/main/java/com/demo/dto/ChatRequest.java b/src/main/java/com/demo/dto/ChatRequest.java new file mode 100644 index 0000000..d4a94ea --- /dev/null +++ b/src/main/java/com/demo/dto/ChatRequest.java @@ -0,0 +1,13 @@ +package com.demo.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class ChatRequest { + + @NotBlank(message = "่ฏท่พ“ๅ…ฅไฝ ็š„้—ฎ้ข˜") + private String question; + private Integer topK = 3; + +} diff --git a/src/main/java/com/demo/dto/ChatResponse.java b/src/main/java/com/demo/dto/ChatResponse.java new file mode 100644 index 0000000..3191453 --- /dev/null +++ b/src/main/java/com/demo/dto/ChatResponse.java @@ -0,0 +1,21 @@ +package com.demo.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ChatResponse { + + private String answer; + + private List references; + + private Long timestamp; +} diff --git a/src/main/java/com/demo/service/ChatService.java b/src/main/java/com/demo/service/ChatService.java new file mode 100644 index 0000000..7ff0c62 --- /dev/null +++ b/src/main/java/com/demo/service/ChatService.java @@ -0,0 +1,101 @@ +package com.demo.service; + +import com.demo.dto.ChatRequest; +import com.demo.dto.ChatResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.SystemPromptTemplate; +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class ChatService { + @Autowired + private final VectorStore vectorStore; + @Autowired + private final ChatClient chatClient; + + private static final String SYSTEM_PROMPT = """ + ไฝ ๆ˜ฏไธ€ไธชไธ“ไธš็š„ Apache Doris ๆŠ€ๆœฏๆ”ฏๆŒๅŠฉๆ‰‹ใ€‚ + ่ฏทๅŸบไบŽไปฅไธ‹ๆไพ›็š„ๆ–‡ๆกฃๅ†…ๅฎนๅ›ž็ญ”็”จๆˆท็š„้—ฎ้ข˜ใ€‚ + \s + ๆ–‡ๆกฃๅ†…ๅฎน๏ผš + {context} + \s + ๅ›ž็ญ”่ฆๆฑ‚๏ผš + 1. ๅฆ‚ๆžœๆ–‡ๆกฃไธญๆœ‰็›ธๅ…ณไฟกๆฏ๏ผŒ่ฏทๅ‡†็กฎๅผ•็”จๅนถ่ฏฆ็ป†ๅ›ž็ญ” + 2. ๅฆ‚ๆžœๆ–‡ๆกฃไธญๆฒกๆœ‰็›ธๅ…ณไฟกๆฏ๏ผŒ่ฏทๆ˜Ž็กฎๅ‘Š็Ÿฅ็”จๆˆท + 3. ไฟๆŒไธ“ไธšใ€ๅ‡†็กฎใ€ๅ‹ๅฅฝ็š„่ฏญๆฐ” + 4. ไฝฟ็”จไธญๆ–‡ๅ›ž็ญ” + """; + + public ChatService(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) { + this.vectorStore = vectorStore; + this.chatClient = chatClientBuilder.build(); + } + + /** + * ๆ นๆฎ็”จๆˆท็š„่ฏทๆฑ‚๏ผŒๆŸฅ่ฏข็Ÿฅ่ฏ†ๅบ“๏ผŒๅนถ้€š่ฟ‡ๅคงๆจกๅž‹ๅ›ž็ญ”้—ฎ้ข˜ใ€‚ + * @param request + * @return + */ + public ChatResponse chat(ChatRequest request) { + + // 1. ๆ‹ฟๅˆฐ็”จๆˆท้—ฎ้ข˜๏ผŒ้ฆ–ๅ…ˆ้œ€่ฆๆฃ€้”™ๅ‘้‡ๆ•ฐๆฎๅบ“๏ผŒๆŸฅ็œ‹ๆ˜ฏๅฆๆœ‰็›ธๅ…ณ็š„ๆ–‡ๆกฃ + // ่ฟ™้‡Œๆ˜ฏๅ‘้‡ๆฃ€็ดข๏ผŒๆˆ‘ไปฌๆ‹ฟๅˆฐ็š„็”จๆˆท็š„้—ฎ้ข˜ๆ˜ฏๆ–‡ๅญ—๏ผŒ้€š่ฟ‡embeddingๆจกๅž‹่ฟ›่กŒๅ‘้‡ๅŒ–ๅค„็†ๅŽ๏ผŒๅ†่ฟ›่ฟ›่กŒๆฃ€็ดข + List relevantDocs = vectorStore.similaritySearch( + SearchRequest.query(request.getQuestion()) + .withTopK(request.getTopK()) + .withSimilarityThreshold(0.5) // ใ€ๅ…ณ้”ฎๆ ธๅฟƒใ€‘็›ธไผผๅบฆๅฟ…้กปๅคงไบŽ 0.75๏ผˆๆปกๅˆ†้€šๅธธๆ˜ฏ1.0๏ผ‰ + ); + log.info("ๆฃ€็ดขๅˆฐ {} ไธช็›ธๅ…ณๆ–‡ๆกฃ", relevantDocs.size()); + +// // debug:้ๅކๆ‰“ๅฐๅฎƒไปฌ็š„็œŸๅฎžๅพ—ๅˆ† +// for (int i = 0; i < relevantDocs.size(); i++) { +// Document doc = relevantDocs.get(i); +// // ๆ‰“ๅฐๆฏไธ€ๆกๆ–‡ๆกฃ็š„ metadata๏ผŒ้‡Œ้ข้€šๅธธไผšๆœ‰ไธ€ไธชๅซ "distance" ๆˆ– "similarity" ็š„ๅญ—ๆฎต +// log.info("็ฌฌ {} ไธชๆ–‡ๆกฃ็š„ Metadata: {}", i + 1, doc.getMetadata()); +// // log.info("ๆ–‡ๆกฃ็‰‡ๆฎตๅ†…ๅฎน: {}", doc.getContent().substring(0, Math.min(50, doc.getContent().length())) + "..."); +// } + + // 2. ๆ นๆฎๆฃ€็ดข็ป“ๆžœ๏ผŒๆž„ๅปบไธŠไธ‹ๆ–‡(่ฏทๆฑ‚llm็š„ไธŠไธ‹ๆ–‡) + String content = relevantDocs.stream() + .map(Document::getContent) + .collect(Collectors.joining("\n\n --- \n\n")); + + // 3. ๅˆ›ๅปบๆ็คบ่ฏ + SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(SYSTEM_PROMPT); + Message systemPrompt = systemPromptTemplate.createMessage(Map.of("context", content)); + UserMessage userMessage = new UserMessage(request.getQuestion()); + Prompt prompt = new Prompt(List.of(systemPrompt, userMessage)); + + // 4. ่ฐƒ็”จ LLM ็”Ÿๆˆ็ญ”ๆกˆ + String answer = chatClient.prompt(prompt) + .call() + .content(); + + // 5. ๅฐ่ฃ…่ฟ”ๅ›ž็š„ๅ†…ๅฎน + List references = relevantDocs.stream() + .map(doc -> (String) doc.getMetadata().get("source")) + .distinct() + .collect(Collectors.toList()); + + return ChatResponse.builder() + .answer(answer) + .references(references) + .timestamp(System.currentTimeMillis()) + .build(); + } +}