Skip to content

Commit 373fe3d

Browse files
nebrassglaforge
authored andcommitted
feat: Add ReasoningBank for reusable reasoning strategies
Add a contrib/reasoning-bank module implementing the ReasoningBank pattern (arXiv:2509.25140) for storing and retrieving proven reasoning strategies. Includes data models, in-memory service, and a FunctionTool for agent integration.
1 parent 551c31f commit 373fe3d

File tree

12 files changed

+1240
-0
lines changed

12 files changed

+1240
-0
lines changed

contrib/reasoning-bank/pom.xml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright 2025 Google LLC
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<project xmlns="http://maven.apache.org/POM/4.0.0"
18+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
20+
<modelVersion>4.0.0</modelVersion>
21+
22+
<parent>
23+
<groupId>com.google.adk</groupId>
24+
<artifactId>google-adk-parent</artifactId>
25+
<version>0.5.1-SNAPSHOT</version><!-- {x-version-update:google-adk:current} -->
26+
<relativePath>../../pom.xml</relativePath>
27+
</parent>
28+
29+
<artifactId>google-adk-reasoning-bank</artifactId>
30+
<name>Agent Development Kit - Reasoning Bank</name>
31+
<description>Reasoning Bank integration with Agent Development Kit for reusable reasoning strategies</description>
32+
33+
<dependencies>
34+
35+
<dependency>
36+
<groupId>com.google.adk</groupId>
37+
<artifactId>google-adk</artifactId>
38+
<version>${project.version}</version>
39+
</dependency>
40+
<dependency>
41+
<groupId>com.google.auto.value</groupId>
42+
<artifactId>auto-value-annotations</artifactId>
43+
<scope>provided</scope>
44+
</dependency>
45+
<dependency>
46+
<groupId>com.fasterxml.jackson.core</groupId>
47+
<artifactId>jackson-databind</artifactId>
48+
</dependency>
49+
<dependency>
50+
<groupId>io.reactivex.rxjava3</groupId>
51+
<artifactId>rxjava</artifactId>
52+
</dependency>
53+
<dependency>
54+
<groupId>com.google.guava</groupId>
55+
<artifactId>guava</artifactId>
56+
<version>33.0.0-jre</version>
57+
</dependency>
58+
<dependency>
59+
<groupId>com.google.truth</groupId>
60+
<artifactId>truth</artifactId>
61+
<scope>test</scope>
62+
</dependency>
63+
<dependency>
64+
<groupId>org.junit.jupiter</groupId>
65+
<artifactId>junit-jupiter-api</artifactId>
66+
<scope>test</scope>
67+
</dependency>
68+
<dependency>
69+
<groupId>org.junit.jupiter</groupId>
70+
<artifactId>junit-jupiter-engine</artifactId>
71+
<scope>test</scope>
72+
</dependency>
73+
<dependency>
74+
<groupId>org.junit.vintage</groupId>
75+
<artifactId>junit-vintage-engine</artifactId>
76+
<scope>test</scope>
77+
</dependency>
78+
79+
</dependencies>
80+
81+
<build>
82+
<plugins>
83+
<plugin>
84+
<artifactId>maven-compiler-plugin</artifactId>
85+
</plugin>
86+
<plugin>
87+
<groupId>org.jacoco</groupId>
88+
<artifactId>jacoco-maven-plugin</artifactId>
89+
</plugin>
90+
</plugins>
91+
</build>
92+
</project>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.reasoning;
18+
19+
import io.reactivex.rxjava3.core.Completable;
20+
import io.reactivex.rxjava3.core.Single;
21+
22+
/**
23+
* Base contract for reasoning bank services.
24+
*
25+
* <p>The service provides functionalities to store and retrieve reasoning strategies that can be
26+
* used to augment LLM prompts with relevant problem-solving approaches.
27+
*
28+
* <p>Based on the ReasoningBank paper (arXiv:2509.25140).
29+
*/
30+
public interface BaseReasoningBankService {
31+
32+
/**
33+
* Stores a reasoning strategy in the bank.
34+
*
35+
* @param appName The name of the application.
36+
* @param strategy The strategy to store.
37+
* @return A Completable that completes when the strategy is stored.
38+
*/
39+
Completable storeStrategy(String appName, ReasoningStrategy strategy);
40+
41+
/**
42+
* Stores a reasoning trace for later distillation into strategies.
43+
*
44+
* @param appName The name of the application.
45+
* @param trace The trace to store.
46+
* @return A Completable that completes when the trace is stored.
47+
*/
48+
Completable storeTrace(String appName, ReasoningTrace trace);
49+
50+
/**
51+
* Searches for reasoning strategies that match the given query.
52+
*
53+
* @param appName The name of the application.
54+
* @param query The query to search for (typically a task description).
55+
* @return A {@link SearchReasoningResponse} containing matching strategies.
56+
*/
57+
Single<SearchReasoningResponse> searchStrategies(String appName, String query);
58+
59+
/**
60+
* Searches for reasoning strategies that match the given query with a limit.
61+
*
62+
* @param appName The name of the application.
63+
* @param query The query to search for.
64+
* @param maxResults Maximum number of strategies to return.
65+
* @return A {@link SearchReasoningResponse} containing matching strategies.
66+
*/
67+
Single<SearchReasoningResponse> searchStrategies(String appName, String query, int maxResults);
68+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.reasoning;
18+
19+
import com.google.common.collect.ImmutableSet;
20+
import io.reactivex.rxjava3.core.Completable;
21+
import io.reactivex.rxjava3.core.Single;
22+
import java.util.ArrayList;
23+
import java.util.Collections;
24+
import java.util.HashSet;
25+
import java.util.List;
26+
import java.util.Locale;
27+
import java.util.Map;
28+
import java.util.Set;
29+
import java.util.concurrent.ConcurrentHashMap;
30+
import java.util.regex.Matcher;
31+
import java.util.regex.Pattern;
32+
import java.util.stream.Collectors;
33+
34+
/**
35+
* An in-memory reasoning bank service for prototyping purposes only.
36+
*
37+
* <p>Uses keyword matching instead of semantic search. For production use, consider implementing a
38+
* service backed by vector embeddings for semantic similarity matching.
39+
*/
40+
public final class InMemoryReasoningBankService implements BaseReasoningBankService {
41+
42+
private static final int DEFAULT_MAX_RESULTS = 5;
43+
44+
// Pattern to extract words for keyword matching.
45+
private static final Pattern WORD_PATTERN = Pattern.compile("[A-Za-z]+");
46+
47+
/** Keys are app names, values are lists of strategies. */
48+
private final Map<String, List<ReasoningStrategy>> strategies;
49+
50+
/** Keys are app names, values are lists of traces. */
51+
private final Map<String, List<ReasoningTrace>> traces;
52+
53+
public InMemoryReasoningBankService() {
54+
this.strategies = new ConcurrentHashMap<>();
55+
this.traces = new ConcurrentHashMap<>();
56+
}
57+
58+
@Override
59+
public Completable storeStrategy(String appName, ReasoningStrategy strategy) {
60+
return Completable.fromAction(
61+
() -> {
62+
List<ReasoningStrategy> appStrategies =
63+
strategies.computeIfAbsent(
64+
appName, k -> Collections.synchronizedList(new ArrayList<>()));
65+
appStrategies.add(strategy);
66+
});
67+
}
68+
69+
@Override
70+
public Completable storeTrace(String appName, ReasoningTrace trace) {
71+
return Completable.fromAction(
72+
() -> {
73+
List<ReasoningTrace> appTraces =
74+
traces.computeIfAbsent(appName, k -> Collections.synchronizedList(new ArrayList<>()));
75+
appTraces.add(trace);
76+
});
77+
}
78+
79+
@Override
80+
public Single<SearchReasoningResponse> searchStrategies(String appName, String query) {
81+
return searchStrategies(appName, query, DEFAULT_MAX_RESULTS);
82+
}
83+
84+
@Override
85+
public Single<SearchReasoningResponse> searchStrategies(
86+
String appName, String query, int maxResults) {
87+
return Single.fromCallable(
88+
() -> {
89+
if (!strategies.containsKey(appName)) {
90+
return SearchReasoningResponse.builder().build();
91+
}
92+
93+
List<ReasoningStrategy> appStrategies = strategies.get(appName);
94+
ImmutableSet<String> queryWords = extractWords(query);
95+
96+
if (queryWords.isEmpty()) {
97+
return SearchReasoningResponse.builder().build();
98+
}
99+
100+
List<ScoredStrategy> scoredStrategies = new ArrayList<>();
101+
102+
for (ReasoningStrategy strategy : appStrategies) {
103+
int score = calculateMatchScore(strategy, queryWords);
104+
if (score > 0) {
105+
scoredStrategies.add(new ScoredStrategy(strategy, score));
106+
}
107+
}
108+
109+
// Sort by score descending
110+
scoredStrategies.sort((a, b) -> Integer.compare(b.score, a.score));
111+
112+
// Take top results
113+
List<ReasoningStrategy> matchingStrategies =
114+
scoredStrategies.stream()
115+
.map(scoredStrategy -> scoredStrategy.strategy)
116+
.limit(maxResults)
117+
.collect(Collectors.toList());
118+
119+
return SearchReasoningResponse.builder().setStrategies(matchingStrategies).build();
120+
});
121+
}
122+
123+
private int calculateMatchScore(ReasoningStrategy strategy, Set<String> queryWords) {
124+
int score = 0;
125+
126+
// Check problem pattern
127+
Set<String> patternWords = extractWords(strategy.problemPattern());
128+
score += countOverlap(queryWords, patternWords) * 3; // Weight pattern matches higher
129+
130+
// Check name
131+
Set<String> nameWords = extractWords(strategy.name());
132+
score += countOverlap(queryWords, nameWords) * 2;
133+
134+
// Check tags
135+
for (String tag : strategy.tags()) {
136+
Set<String> tagWords = extractWords(tag);
137+
score += countOverlap(queryWords, tagWords);
138+
}
139+
140+
// Check steps (lower weight)
141+
for (String step : strategy.steps()) {
142+
Set<String> stepWords = extractWords(step);
143+
if (!Collections.disjoint(queryWords, stepWords)) {
144+
score += 1;
145+
}
146+
}
147+
148+
return score;
149+
}
150+
151+
private int countOverlap(Set<String> set1, Set<String> set2) {
152+
Set<String> intersection = new HashSet<>(set1);
153+
intersection.retainAll(set2);
154+
return intersection.size();
155+
}
156+
157+
private ImmutableSet<String> extractWords(String text) {
158+
if (text == null || text.isEmpty()) {
159+
return ImmutableSet.of();
160+
}
161+
162+
Set<String> words = new HashSet<>();
163+
Matcher matcher = WORD_PATTERN.matcher(text);
164+
while (matcher.find()) {
165+
words.add(matcher.group().toLowerCase(Locale.ROOT));
166+
}
167+
return ImmutableSet.copyOf(words);
168+
}
169+
170+
/** Helper class for scoring strategies during search. */
171+
private static class ScoredStrategy {
172+
final ReasoningStrategy strategy;
173+
final int score;
174+
175+
ScoredStrategy(ReasoningStrategy strategy, int score) {
176+
this.strategy = strategy;
177+
this.score = score;
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)