1 Commits

Author SHA1 Message Date
86bf82b3e6 feat: 流式应答ui 2025-09-07 11:42:01 +08:00
9 changed files with 17 additions and 186 deletions

View File

@@ -29,16 +29,16 @@
<!-- <artifactId>spring-ai-openai-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.ai</groupId>-->
<!-- <artifactId>spring-ai-tika-document-reader</artifactId>-->
<!-- </dependency>-->
<!-- 处理知识库:向量库 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
<!-- &lt;!&ndash; 处理知识库:向量库 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.ai</groupId>-->
<!-- <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<!-- 使用ollama的api -->
<dependency>

View File

@@ -1,16 +1,10 @@
package com.storm.dev.config;
import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.ai.ollama.OllamaEmbeddingClient;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.PgVectorStore;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 注入OllamaApi、OllamaChatClient对象
@@ -30,24 +24,4 @@ public class OllamaConfig {
return new OllamaChatClient(ollamaApi);
}
@Bean
public TokenTextSplitter tokenTextSplitter() {
return new TokenTextSplitter();
}
@Bean
public SimpleVectorStore simpleVectorStore(OllamaApi ollamaApi) {
OllamaEmbeddingClient embeddingClient = new OllamaEmbeddingClient(ollamaApi);
embeddingClient.withDefaultOptions(OllamaOptions.create().withModel("nomic-embed-text"));
return new SimpleVectorStore(embeddingClient);
}
@Bean
public PgVectorStore pgVectorStore(OllamaApi ollamaApi, JdbcTemplate jdbcTemplate) {
OllamaEmbeddingClient embeddingClient = new OllamaEmbeddingClient(ollamaApi);
embeddingClient.withDefaultOptions(OllamaOptions.create().withModel("nomic-embed-text"));
return new PgVectorStore(jdbcTemplate, embeddingClient);
}
}

View File

@@ -2,25 +2,16 @@ server:
port: 8090
spring:
datasource:
driver-class-name: org.postgresql.Driver
username: postgres
password: postgres
url: jdbc:postgresql://192.168.109.134:15432/ai-rag-knowledge
type: com.zaxxer.hikari.HikariDataSource
ai:
ollama:
base-url: http://192.168.109.134:11434
embedding:
options:
num-batch: 512
model: nomic-embed-text
base-url: http://117.72.202.142:11434
# Redis
redis:
sdk:
config:
host: 127.0.0.1
port: 6379
host: 117.72.202.142
port: 16379
pool-size: 10
min-idle-size: 5
idle-timeout: 30000

View File

@@ -1,90 +0,0 @@
package com.storm.dev.text;
import com.alibaba.fastjson.JSON;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.ai.chat.ChatResponse;
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.ollama.OllamaChatClient;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.PgVectorStore;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author: lyd
* @date: 2026/1/14 21:47
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class RAGApiTest {
@Resource
private OllamaChatClient ollamaChatClient;
@Resource
private TokenTextSplitter tokenTextSplitter;
@Resource
private SimpleVectorStore simpleVectorStore;
@Resource
private PgVectorStore pgVectorStore;
@Test
public void upload() {
// 上传
TikaDocumentReader reader = new TikaDocumentReader("./data/file.text");
List<Document> documents = reader.get();
List<Document> documentSplitterList = tokenTextSplitter.apply(documents);
// 打标
documents.forEach(document -> document.getMetadata().put("knowledge", "德德"));
documentSplitterList.forEach(document -> document.getMetadata().put("knowledge", "德德"));
pgVectorStore.accept(documentSplitterList);
log.info("上传完成!");
}
@Test
public void chat() {
// 构建提问
String message = "李永德,哪年出生的";
// 构建推理模板
String SYSTEM_PROMPT = """
Use the information from the DOCUMENTS section to provide accurate answers but act as if you knew this information innately.
If unsure, simply state that you don't know.
Another thing you need to note is that your reply must be in Chinese!
DOCUMENTS:
{documents}
""";
// 读取向量库信息
SearchRequest request = SearchRequest.query(message).withTopK(5).withFilterExpression("knowledge == '德德'");
// 相似性搜索
List<Document> documents = pgVectorStore.similaritySearch(request);
String documentsCollectors = documents.stream().map(Document::getContent).collect(Collectors.joining());
// 推理RAG
Message ragMessage = new SystemPromptTemplate(SYSTEM_PROMPT).createMessage(Map.of("documents", documentsCollectors));
ArrayList<Message> messages = new ArrayList<>();
messages.add(new UserMessage(message));
messages.add(ragMessage);
// 提问
ChatResponse chatResponse = ollamaChatClient.call(new Prompt(messages, OllamaOptions.create().withModel("deepseek-r1:7b")));
log.info("测试结果:{}", JSON.toJSONString(chatResponse));
}
}

View File

@@ -1 +0,0 @@
李永德1999年12月31日出生福建泉州人。

View File

@@ -22,7 +22,7 @@ public class OllamaController implements IAiService {
private OllamaChatClient chatClient;
/**
* http://localhost:8090/api/v1/ollama/generate?model=deepseek-r1:7b&message=1+1
* http://localhost:8090/api/v1/ollama/generate?model=deepseek-r1:1.5b&message=1+1
*/
@GetMapping("generate")
@Override
@@ -31,7 +31,7 @@ public class OllamaController implements IAiService {
}
/**
* http://localhost:8090/api/v1/ollama/generate_stream?model=deepseek-r1:7b&message=hi
* http://localhost:8090/api/v1/ollama/generate_stream?model=deepseek-r1:1.5b&message=hi
*/
@GetMapping("generate_stream")
@Override

View File

@@ -1,43 +0,0 @@
# docker-compose -f docker-compose-environment-aliyun.yml up -d
version: '3'
services:
# 对话模型
# ollama pull deepseek-r1:1.5b
# 运行模型
# ollama run deepseek-r1:1.5b
# 联网模型
# ollama pull nomic-embed-text
ollama:
image: registry.cn-hangzhou.aliyuncs.com/xfg-studio/ollama:0.5.10
container_name: ollama
restart: unless-stopped
ports:
- "11434:11434"
vector_db:
image: registry.cn-hangzhou.aliyuncs.com/xfg-studio/pgvector:v0.5.0
container_name: vector_db
restart: always
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=ai-rag-knowledge
- PGPASSWORD=postgres
volumes:
- ./pgvector/sql/init.sql:/docker-entrypoint-initdb.d/init.sql
logging:
options:
max-size: 10m
max-file: "3"
ports:
- '15432:5432'
healthcheck:
test: "pg_isready -U postgres -d ai-rag-knowledge"
interval: 2s
timeout: 20s
retries: 10
networks:
- my-network
networks:
my-network:
driver: bridge

View File

@@ -69,7 +69,7 @@
addMessage('<span class="animate-pulse">▍</span>');
// 构建API URL
const apiUrl = `http://localhost:8090/api/v1/ollama/generate_stream?model=deepseek-r1:7b&message=${encodeURIComponent(message)}`;
const apiUrl = `http://localhost:8090/api/v1/ollama/generate_stream?model=deepseek-r1:1.5b&message=${encodeURIComponent(message)}`;
// 使用EventSource接收流式响应
const eventSource = new EventSource(apiUrl);

View File

@@ -56,7 +56,7 @@
<script>
const API_BASE = 'http://localhost:8090/api/v1/ollama/generate_stream';
const MODEL = 'deepseek-r1:7b';
const MODEL = 'deepseek-r1:1.5b';
const chatContainer = document.getElementById('chatContainer');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');