Skip to content

Commit 0d99edf

Browse files
committed
feat(bubble-ai-starter-lightrag): 添加 LightRAG API 实现
1 parent d13e789 commit 0d99edf

File tree

60 files changed

+8892
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+8892
-3
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>cn.fxbin.bubble</groupId>
6+
<artifactId>bubble-ai-starters</artifactId>
7+
<version>2.0.0.BUILD-SNAPSHOT</version>
8+
</parent>
9+
10+
<artifactId>bubble-ai-starter-lightrag</artifactId>
11+
<name>Bubble AI Starter LightRAG</name>
12+
<description>Bubble AI Starter LightRAG</description>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>cn.fxbin.bubble</groupId>
17+
<artifactId>bubble-ai-starter</artifactId>
18+
</dependency>
19+
20+
<dependency>
21+
<groupId>com.dtflys.forest</groupId>
22+
<artifactId>forest-spring-boot3-starter</artifactId>
23+
</dependency>
24+
25+
<!-- Spring Boot Actuator (for health checks) -->
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-starter-actuator</artifactId>
29+
<optional>true</optional>
30+
</dependency>
31+
32+
<!-- Spring Boot WebFlux (for reactive streams) -->
33+
<dependency>
34+
<groupId>org.springframework.boot</groupId>
35+
<artifactId>spring-boot-starter-webflux</artifactId>
36+
</dependency>
37+
38+
<!-- Auto Configuration Annotation Processor -->
39+
<dependency>
40+
<groupId>net.dreamlu</groupId>
41+
<artifactId>mica-auto</artifactId>
42+
<scope>provided</scope>
43+
</dependency>
44+
45+
<!-- Testing dependencies -->
46+
<dependency>
47+
<groupId>org.springframework.boot</groupId>
48+
<artifactId>spring-boot-starter-test</artifactId>
49+
<scope>test</scope>
50+
</dependency>
51+
<dependency>
52+
<groupId>org.junit.jupiter</groupId>
53+
<artifactId>junit-jupiter-api</artifactId>
54+
<scope>test</scope>
55+
</dependency>
56+
<dependency>
57+
<groupId>org.junit.jupiter</groupId>
58+
<artifactId>junit-jupiter-engine</artifactId>
59+
<scope>test</scope>
60+
</dependency>
61+
<dependency>
62+
<groupId>org.mockito</groupId>
63+
<artifactId>mockito-core</artifactId>
64+
<scope>test</scope>
65+
</dependency>
66+
<dependency>
67+
<groupId>org.mockito</groupId>
68+
<artifactId>mockito-junit-jupiter</artifactId>
69+
<scope>test</scope>
70+
</dependency>
71+
<dependency>
72+
<groupId>org.springframework</groupId>
73+
<artifactId>spring-test</artifactId>
74+
<scope>test</scope>
75+
</dependency>
76+
<dependency>
77+
<groupId>org.assertj</groupId>
78+
<artifactId>assertj-core</artifactId>
79+
<scope>test</scope>
80+
</dependency>
81+
</dependencies>
82+
83+
</project>
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package cn.fxbin.bubble.ai.lightrag.autoconfigure;
2+
3+
import cn.fxbin.bubble.core.support.YamlConfigFactory;
4+
import cn.hutool.core.date.SystemClock;
5+
import com.dtflys.forest.http.ForestRequest;
6+
import com.dtflys.forest.http.ForestResponse;
7+
import com.dtflys.forest.interceptor.ForestInterceptor;
8+
import com.dtflys.forest.springboot.annotation.ForestScan;
9+
import jakarta.annotation.PostConstruct;
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
13+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
14+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
15+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
16+
import org.springframework.context.annotation.Bean;
17+
import org.springframework.context.annotation.Configuration;
18+
import org.springframework.context.annotation.PropertySource;
19+
import org.springframework.util.StringUtils;
20+
21+
import java.time.Duration;
22+
23+
/**
24+
* LightRAG 自动配置类
25+
*
26+
* <p>该类负责自动配置 LightRAG 相关的 Bean,包括 Forest HTTP 客户端的配置、
27+
* 认证拦截器、超时设置、重试策略等。当满足特定条件时,会自动创建和配置相关组件。</p>
28+
*
29+
* <h3>自动配置条件:</h3>
30+
* <ul>
31+
* <li>类路径中存在 Forest 相关类</li>
32+
* <li>配置属性 dm.ai.lightrag.enabled=true(默认为 true)</li>
33+
* <li>配置了有效的 base-url</li>
34+
* </ul>
35+
*
36+
* <h3>主要功能:</h3>
37+
* <ul>
38+
* <li>配置 Forest HTTP 客户端的全局设置</li>
39+
* <li>创建认证拦截器,支持API Key认证</li>
40+
* <li>配置超时和重试参数</li>
41+
* <li>提供 LightRAG 客户端 Bean 的自动装配</li>
42+
* </ul>
43+
*
44+
* @author fxbin
45+
* @version v1.0
46+
* @since 2025-08-21 15:22:03
47+
*/
48+
@Slf4j
49+
@Configuration(
50+
proxyBeanMethods = false
51+
)
52+
@PropertySource(value = "classpath:lightrag-forest.yaml", factory = YamlConfigFactory.class)
53+
@RequiredArgsConstructor
54+
@ForestScan(basePackages = "cn.fxbin.bubble.ai.lightrag.client")
55+
@ConditionalOnClass(com.dtflys.forest.Forest.class)
56+
@EnableConfigurationProperties(LightRagProperties.class)
57+
@ConditionalOnProperty(prefix = "bubble.ai.lightrag", name = "enabled", havingValue = "true", matchIfMissing = true)
58+
public class LightRagAutoConfiguration {
59+
60+
private final LightRagProperties properties;
61+
62+
/**
63+
* 配置 Forest 全局属性
64+
*
65+
* <p>通过 Spring Boot 配置属性的方式配置 Forest 全局参数。
66+
* 使用 @ForestScan 注解后,Forest 会自动读取这些配置。</p>
67+
*/
68+
@PostConstruct
69+
public void configureForest() {
70+
log.info("正在配置 LightRAG Forest 客户端,基础 URL: {}", properties.getBaseUrl());
71+
log.info("LightRAG Forest 客户端配置完成,使用 @ForestScan 自动扫描客户端接口");
72+
}
73+
74+
/**
75+
* 创建认证拦截器
76+
*
77+
* <p>创建API Key认证拦截器,支持通过API Key进行认证。</p>
78+
*
79+
* @return 认证拦截器
80+
*/
81+
@Bean
82+
@ConditionalOnMissingBean(name = "lightRagAuthInterceptor")
83+
public ForestInterceptor lightRagAuthInterceptor() {
84+
return new LightRagAuthInterceptor(properties);
85+
}
86+
87+
/**
88+
* 创建日志拦截器
89+
*
90+
* <p>用于记录 HTTP 请求和响应的详细信息,便于调试和监控。</p>
91+
*
92+
* @return 日志拦截器
93+
*/
94+
@Bean
95+
@ConditionalOnMissingBean(name = "lightRagLoggingInterceptor")
96+
public ForestInterceptor lightRagLoggingInterceptor() {
97+
return new LightRagLoggingInterceptor();
98+
}
99+
100+
/**
101+
* LightRAG 认证拦截器
102+
*
103+
* <p>实现API Key认证的 HTTP 请求拦截和认证信息添加。</p>
104+
*/
105+
@RequiredArgsConstructor
106+
public static class LightRagAuthInterceptor implements ForestInterceptor {
107+
108+
private final LightRagProperties properties;
109+
110+
@Override
111+
public boolean beforeExecute(ForestRequest request) {
112+
String apiKey = properties.getApiKey();
113+
if (StringUtils.hasText(apiKey)) {
114+
// Add API key as query parameter
115+
request.addQuery("api_key_header_value", apiKey);
116+
}
117+
return true;
118+
}
119+
}
120+
121+
/**
122+
* LightRAG 日志拦截器
123+
*
124+
* <p>记录 HTTP 请求和响应的详细信息,包括请求 URL、方法、参数、响应状态码、耗时等。</p>
125+
*/
126+
public static class LightRagLoggingInterceptor implements ForestInterceptor {
127+
128+
@Override
129+
public boolean beforeExecute(ForestRequest request) {
130+
if (log.isDebugEnabled()) {
131+
log.debug("LightRAG 请求开始: {} {}", request.getType().getName(), request.getUrl());
132+
if (request.getQuery() != null && !request.getQuery().isEmpty()) {
133+
log.debug("请求参数: {}", request.getQuery());
134+
}
135+
}
136+
137+
// 记录请求开始时间
138+
request.addAttachment("startTime", SystemClock.now());
139+
return true;
140+
}
141+
142+
@Override
143+
public void afterExecute(ForestRequest request, ForestResponse response) {
144+
if (log.isDebugEnabled()) {
145+
log.debug("LightRAG 请求结束: {} {} - 状态码: {}",
146+
request.getType().getName(), request.getUrl(), response.getStatusCode());
147+
if (response.getContent() != null) {
148+
log.debug("响应内容: {}", response.getContent());
149+
}
150+
}
151+
log.debug("请求耗时: {}ms", SystemClock.now() - (Long) request.getAttachment("startTime"));
152+
}
153+
}
154+
155+
/**
156+
* 内部配置类
157+
*
158+
* <p>包含需要在特定条件下才创建的 Bean 配置。</p>
159+
*/
160+
@Configuration
161+
@ConditionalOnProperty(prefix = "dm.ai.lightrag", name = "base-url")
162+
static class LightRagClientConfiguration {
163+
164+
/**
165+
* 验证配置的有效性
166+
*
167+
* <p>在应用启动时验证 LightRAG 配置是否正确,如果配置无效则记录警告信息。</p>
168+
*
169+
* @param properties LightRAG 配置属性
170+
*/
171+
@Bean
172+
@ConditionalOnMissingBean
173+
public LightRagConfigurationValidator lightRagConfigurationValidator(LightRagProperties properties) {
174+
return new LightRagConfigurationValidator(properties);
175+
}
176+
}
177+
178+
/**
179+
* LightRAG 配置验证器
180+
*
181+
* <p>用于验证 LightRAG 配置的有效性,确保必要的配置项都已正确设置。</p>
182+
*/
183+
@RequiredArgsConstructor
184+
public static class LightRagConfigurationValidator {
185+
186+
private final LightRagProperties properties;
187+
188+
@PostConstruct
189+
public void validate() {
190+
log.info("开始验证 LightRAG 配置...");
191+
192+
// 验证基础 URL
193+
if (!StringUtils.hasText(properties.getBaseUrl())) {
194+
log.warn("LightRAG 基础 URL 未配置,服务可能无法正常工作");
195+
return;
196+
}
197+
198+
String apiKey = properties.getApiKey();
199+
if (!StringUtils.hasText(apiKey)) {
200+
log.warn("LightRAG 配置了 API Key 认证但未提供 apiKey");
201+
}
202+
203+
int maxRetries = properties.getMaxRetries();
204+
if (maxRetries < 0) {
205+
log.warn("LightRAG 配置了无效的重试次数,将使用默认值 3");
206+
}
207+
if (maxRetries > 5) {
208+
log.warn("LightRAG 配置了超过 5 次的重试次数,这可能会导致性能问题");
209+
}
210+
211+
Duration timeout = properties.getTimeout();
212+
if (timeout.isNegative()) {
213+
log.warn("LightRAG 配置了无效的超时时间,将使用默认值 10 秒");
214+
}
215+
216+
log.info("LightRAG 配置验证完成");
217+
}
218+
}
219+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package cn.fxbin.bubble.ai.lightrag.autoconfigure;
2+
3+
import jakarta.validation.constraints.Max;
4+
import jakarta.validation.constraints.Min;
5+
import jakarta.validation.constraints.NotBlank;
6+
import jakarta.validation.constraints.NotNull;
7+
import lombok.Data;
8+
import org.springframework.boot.context.properties.ConfigurationProperties;
9+
import org.springframework.validation.annotation.Validated;
10+
11+
import java.time.Duration;
12+
13+
/**
14+
* LightRAG 服务配置属性类
15+
*
16+
* <p>该类定义了 LightRAG 服务的核心配置参数,专注于必需的基础配置。</p>
17+
*
18+
* <h3>配置示例:</h3>
19+
* <pre>{@code
20+
* bubble:
21+
* ai:
22+
* lightrag:
23+
* enabled: true
24+
* base-url: "http://localhost:8020"
25+
* timeout: 30s
26+
* max-retries: 3
27+
* api-key: "your-api-key"
28+
* }</pre>
29+
*
30+
* @author fxbin
31+
* @version v1.0
32+
* @since 2025-08-21 15:22:03
33+
*/
34+
@Data
35+
@Validated
36+
@ConfigurationProperties(prefix = "dm.ai.lightrag")
37+
public class LightRagProperties {
38+
39+
/**
40+
* 是否启用 LightRAG 服务
41+
* 默认值:true
42+
*/
43+
private boolean enabled = true;
44+
45+
/**
46+
* LightRAG 服务的基础 URL
47+
* 必须配置,用于指定 LightRAG 服务的访问地址
48+
*
49+
* 示例:http://localhost:8020 或 https://lightrag.example.com
50+
*/
51+
@NotBlank(message = "LightRAG 服务基础 URL 不能为空")
52+
private String baseUrl;
53+
54+
/**
55+
* 请求超时时间
56+
* 默认值:30秒
57+
*/
58+
@NotNull(message = "超时时间不能为空")
59+
private Duration timeout = Duration.ofSeconds(30);
60+
61+
/**
62+
* 最大重试次数
63+
* 默认值:3次
64+
* 取值范围:0-5
65+
*/
66+
@Min(value = 0, message = "重试次数不能小于0")
67+
@Max(value = 5, message = "重试次数不能大于5")
68+
private int maxRetries = 3;
69+
70+
/**
71+
* API密钥
72+
* 用于API认证
73+
*/
74+
private String apiKey;
75+
76+
77+
}

0 commit comments

Comments
 (0)