Skip to content

Commit 7820a17

Browse files
qicesunMartin7-1
andauthored
refactor: move prompt repetition into a dedicated module (#592)
* Add prompt repetition primitives for AI Services and RAG * Add AUTO prompt repetition gates and configurability * Add prompt repetition rollout guide and README link * docs: add validation evidence for prompt repetition PR * docs: trim eval details and keep stable validation summary * docs: move prompt repetition guide to community-core README * Fix idempotence when input contains separator * test: cover prompt repetition decision branches * test: add exhaustive robustness coverage for prompt repetition * test: add exhaustive AUTO-mode robustness coverage * test: add real-model smoke IT for prompt repetition * refactor: move prompt repetition into dedicated module * chore: rerun community CI * update to the latest development version * test: cover ai services multimodal guardrail skip * chore: mark prompt repetition types as experimental --------- Co-authored-by: Martin7-1 <yi.zheng.se@gmail.com>
1 parent d694a3f commit 7820a17

21 files changed

+2394
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Please use [Discord](https://discord.gg/JzTFvyjG6R) or [StackOverFlow](https://s
1313
## Request Features
1414
Please let us know what features you need by [opening an issue](https://github.com/langchain4j/langchain4j-community/issues/new/choose).
1515

16+
## Guides
17+
- [Prompt repetition (non-RAG + RAG)](langchain4j-community-prompt-repetition/README.md)
1618

1719
## Contribute
1820
Contribution guidelines can be found [here](https://github.com/langchain4j/langchain4j-community/blob/main/CONTRIBUTING.md).

langchain4j-community-bom/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
<version>${project.version}</version>
2424
</dependency>
2525

26+
<dependency>
27+
<groupId>dev.langchain4j</groupId>
28+
<artifactId>langchain4j-community-prompt-repetition</artifactId>
29+
<version>${project.version}</version>
30+
</dependency>
31+
2632
<!-- models -->
2733
<dependency>
2834
<groupId>dev.langchain4j</groupId>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# LangChain4j Community Core
2+
3+
Prompt repetition has moved to the dedicated
4+
[`langchain4j-community-prompt-repetition`](../langchain4j-community-prompt-repetition/README.md)
5+
module.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# LangChain4j Community Prompt Repetition
2+
3+
## Prompt repetition
4+
5+
This module includes optional prompt repetition components.
6+
7+
Prompt repetition rewrites:
8+
9+
```text
10+
Q -> Q\nQ
11+
```
12+
13+
Use it only where it helps your workload.
14+
15+
### Components
16+
17+
- `PromptRepeatingInputGuardrail` for non-RAG user input.
18+
- `RepeatingQueryTransformer` for RAG retrieval queries.
19+
- `PromptRepetitionPolicy` shared by both.
20+
21+
### Modes
22+
23+
- `NEVER`: disable.
24+
- `ALWAYS`: repeat when eligible.
25+
- `AUTO`: conservative mode:
26+
- skip already repeated input
27+
- skip very long input (`maxChars`)
28+
- skip reasoning-intent prompts (`reasoningKeywords`)
29+
30+
### Non-RAG usage
31+
32+
```java
33+
PromptRepetitionPolicy policy = PromptRepetitionPolicy.builder()
34+
.mode(PromptRepetitionMode.AUTO)
35+
.maxChars(8_000)
36+
.build();
37+
38+
Assistant assistant = AiServices.builder(Assistant.class)
39+
.chatModel(chatModel)
40+
.inputGuardrails(new PromptRepeatingInputGuardrail(policy))
41+
.build();
42+
```
43+
44+
### RAG usage
45+
46+
Repeat the retrieval query only. Do not repeat the full augmented prompt.
47+
48+
```java
49+
PromptRepetitionPolicy policy = PromptRepetitionPolicy.builder()
50+
.mode(PromptRepetitionMode.AUTO)
51+
.maxChars(8_000)
52+
.build();
53+
54+
RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder()
55+
.queryTransformer(new RepeatingQueryTransformer(policy))
56+
.build();
57+
```
58+
59+
### Safety behavior
60+
61+
- Input guardrail rewrites only single-text user messages.
62+
- Input guardrail skips by default when RAG augmentation is detected.
63+
- Idempotence is built in:
64+
- first pass: `Q -> Q\nQ`
65+
- repeated pass: `SKIPPED_ALREADY_REPEATED`
66+
67+
### Rollout checklist
68+
69+
1. Start with `AUTO`.
70+
2. Keep `maxChars` conservative.
71+
3. Tune `reasoningKeywords` for your workload.
72+
4. Track apply/skip reasons during A/B evaluation.
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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>dev.langchain4j</groupId>
6+
<artifactId>langchain4j-community</artifactId>
7+
<version>1.13.0-beta23-SNAPSHOT</version>
8+
</parent>
9+
10+
<artifactId>langchain4j-community-prompt-repetition</artifactId>
11+
<name>LangChain4j :: Community :: Prompt Repetition</name>
12+
<description>Prompt repetition components for LangChain4j Community</description>
13+
14+
<licenses>
15+
<license>
16+
<name>Apache License, Version 2.0</name>
17+
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
18+
<distribution>repo</distribution>
19+
</license>
20+
</licenses>
21+
22+
<dependencies>
23+
24+
<dependency>
25+
<groupId>dev.langchain4j</groupId>
26+
<artifactId>langchain4j-core</artifactId>
27+
<version>${langchain4j.core.version}</version>
28+
</dependency>
29+
30+
<dependency>
31+
<groupId>dev.langchain4j</groupId>
32+
<artifactId>langchain4j</artifactId>
33+
<version>${langchain4j.version}</version>
34+
<scope>test</scope>
35+
</dependency>
36+
37+
<dependency>
38+
<groupId>org.junit.jupiter</groupId>
39+
<artifactId>junit-jupiter-api</artifactId>
40+
<scope>test</scope>
41+
</dependency>
42+
43+
<dependency>
44+
<groupId>org.tinylog</groupId>
45+
<artifactId>slf4j-tinylog</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
49+
<dependency>
50+
<groupId>org.tinylog</groupId>
51+
<artifactId>tinylog-impl</artifactId>
52+
<scope>test</scope>
53+
</dependency>
54+
55+
<dependency>
56+
<groupId>dev.langchain4j</groupId>
57+
<artifactId>langchain4j-open-ai</artifactId>
58+
<version>${langchain4j.open-ai.version}</version>
59+
<scope>test</scope>
60+
</dependency>
61+
62+
</dependencies>
63+
64+
<build>
65+
<plugins>
66+
<plugin>
67+
<groupId>org.apache.maven.plugins</groupId>
68+
<artifactId>maven-jar-plugin</artifactId>
69+
<executions>
70+
<execution>
71+
<goals>
72+
<goal>test-jar</goal>
73+
</goals>
74+
</execution>
75+
</executions>
76+
</plugin>
77+
78+
<plugin>
79+
<groupId>org.apache.maven.plugins</groupId>
80+
<artifactId>maven-source-plugin</artifactId>
81+
<executions>
82+
<execution>
83+
<id>attach-sources</id>
84+
<goals>
85+
<goal>jar-no-fork</goal>
86+
<goal>test-jar-no-fork</goal>
87+
</goals>
88+
</execution>
89+
</executions>
90+
</plugin>
91+
92+
<plugin>
93+
<groupId>org.jacoco</groupId>
94+
<artifactId>jacoco-maven-plugin</artifactId>
95+
<executions>
96+
<execution>
97+
<id>prepare-agent</id>
98+
<goals>
99+
<goal>prepare-agent</goal>
100+
</goals>
101+
</execution>
102+
<execution>
103+
<id>report</id>
104+
<goals>
105+
<goal>report</goal>
106+
</goals>
107+
<phase>prepare-package</phase>
108+
</execution>
109+
<execution>
110+
<id>jacoco-check</id>
111+
<goals>
112+
<goal>check</goal>
113+
</goals>
114+
<configuration>
115+
<rules>
116+
<rule>
117+
<element>BUNDLE</element>
118+
<limits>
119+
<limit>
120+
<counter>BRANCH</counter>
121+
<value>COVEREDRATIO</value>
122+
<minimum>0.80</minimum>
123+
</limit>
124+
<limit>
125+
<counter>INSTRUCTION</counter>
126+
<value>COVEREDRATIO</value>
127+
<minimum>0.80</minimum>
128+
</limit>
129+
</limits>
130+
</rule>
131+
</rules>
132+
</configuration>
133+
</execution>
134+
</executions>
135+
</plugin>
136+
137+
</plugins>
138+
</build>
139+
140+
<reporting>
141+
<plugins>
142+
<plugin>
143+
<groupId>org.jacoco</groupId>
144+
<artifactId>jacoco-maven-plugin</artifactId>
145+
<reportSets>
146+
<reportSet>
147+
<reports>
148+
<report>report</report>
149+
</reports>
150+
</reportSet>
151+
</reportSets>
152+
</plugin>
153+
</plugins>
154+
</reporting>
155+
156+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package dev.langchain4j.community.prompt.repetition;
2+
3+
import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
4+
5+
import dev.langchain4j.Experimental;
6+
import dev.langchain4j.data.message.TextContent;
7+
import dev.langchain4j.data.message.UserMessage;
8+
import dev.langchain4j.guardrail.InputGuardrail;
9+
import dev.langchain4j.guardrail.InputGuardrailRequest;
10+
import dev.langchain4j.guardrail.InputGuardrailResult;
11+
12+
/**
13+
* Repeats single-text user input before sending it to the model.
14+
* <p>
15+
* This guardrail is intentionally conservative:
16+
* - It skips non-single-text messages (multimodal or multi-part text)
17+
* - It skips inputs that are already augmented via RAG by default
18+
*/
19+
@Experimental
20+
public class PromptRepeatingInputGuardrail implements InputGuardrail {
21+
22+
public static final boolean DEFAULT_ALLOW_RAG_INPUT = false;
23+
24+
private final PromptRepetitionPolicy policy;
25+
private final boolean allowRagInput;
26+
27+
public PromptRepeatingInputGuardrail() {
28+
this(new PromptRepetitionPolicy(), DEFAULT_ALLOW_RAG_INPUT);
29+
}
30+
31+
public PromptRepeatingInputGuardrail(PromptRepetitionPolicy policy) {
32+
this(policy, DEFAULT_ALLOW_RAG_INPUT);
33+
}
34+
35+
public PromptRepeatingInputGuardrail(PromptRepetitionPolicy policy, boolean allowRagInput) {
36+
this.policy = ensureNotNull(policy, "policy");
37+
this.allowRagInput = allowRagInput;
38+
}
39+
40+
/**
41+
* Computes a repetition decision for the given request.
42+
*/
43+
public PromptRepetitionDecision decide(InputGuardrailRequest request) {
44+
ensureNotNull(request, "request");
45+
46+
UserMessage userMessage = request.userMessage();
47+
if (!userMessage.hasSingleText()) {
48+
return PromptRepetitionDecision.skipped(
49+
PromptRepetitionReason.SKIPPED_NON_TEXT, firstTextOrEmpty(userMessage));
50+
}
51+
52+
String text = userMessage.singleText();
53+
if (!allowRagInput && request.requestParams().augmentationResult() != null) {
54+
return PromptRepetitionDecision.skipped(PromptRepetitionReason.SKIPPED_RAG_DETECTED, text);
55+
}
56+
57+
return policy.decide(text);
58+
}
59+
60+
@Override
61+
public InputGuardrailResult validate(InputGuardrailRequest request) {
62+
PromptRepetitionDecision decision = decide(request);
63+
return decision.applied() ? successWith(decision.text()) : success();
64+
}
65+
66+
public PromptRepetitionPolicy policy() {
67+
return policy;
68+
}
69+
70+
public boolean allowRagInput() {
71+
return allowRagInput;
72+
}
73+
74+
private String firstTextOrEmpty(UserMessage userMessage) {
75+
return userMessage.contents().stream()
76+
.filter(TextContent.class::isInstance)
77+
.map(TextContent.class::cast)
78+
.map(TextContent::text)
79+
.findFirst()
80+
.orElse("");
81+
}
82+
}

0 commit comments

Comments
 (0)