summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/net/yacy/ai/RAGAugmentor.java334
-rw-r--r--source/net/yacy/http/servlets/RAGProxyServlet.java24
-rw-r--r--source/net/yacy/search/query/QueryParams.java16
-rw-r--r--source/net/yacy/search/query/SearchEvent.java26
-rw-r--r--source/net/yacy/search/snippet/TextSnippet.java98
5 files changed, 167 insertions, 331 deletions
diff --git a/source/net/yacy/ai/RAGAugmentor.java b/source/net/yacy/ai/RAGAugmentor.java
index d28e33c2a..c9b7215a9 100644
--- a/source/net/yacy/ai/RAGAugmentor.java
+++ b/source/net/yacy/ai/RAGAugmentor.java
@@ -22,22 +22,12 @@ package net.yacy.ai;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Comparator;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
-import org.apache.solr.client.solrj.SolrQuery;
-import org.apache.solr.common.SolrDocument;
-import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.common.SolrException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -45,7 +35,6 @@ import org.json.JSONObject;
import net.yacy.cora.document.analysis.Classification;
import net.yacy.cora.document.id.DigestURL;
import net.yacy.cora.document.id.MultiProtocolURL;
-import net.yacy.cora.federate.solr.connector.EmbeddedSolrConnector;
import net.yacy.cora.federate.yacy.CacheStrategy;
import net.yacy.cora.lod.vocabulary.Tagging;
import net.yacy.cora.protocol.ClientIdentification;
@@ -83,7 +72,7 @@ public final class RAGAugmentor {
private RAGAugmentor() {}
/**
- * Executes local index search with default boost terms.
+ * Executes local index search.
*
* @param query query string
* @param count max number of results
@@ -91,50 +80,8 @@ public final class RAGAugmentor {
* @return JSON array with {@code url,title[,text]} entries
*/
public static JSONArray searchResults(String query, int count, final boolean includeSnippet) {
- return searchResults(query, count, includeSnippet, new LinkedHashSet<>());
- }
-
- /**
- * Executes local Solr search with optional dynamic boost terms.
- *
- * @param query query string
- * @param count max number of results
- * @param includeSnippet include text snippets from indexed text field
- * @param boostTerms optional overlap terms used to bias ranking
- * @return JSON array with normalized search result objects
- */
- public static JSONArray searchResults(String query, int count, final boolean includeSnippet, final Set<String> boostTerms) {
- final JSONArray results = new JSONArray();
- if (query == null || query.length() == 0 || count == 0) return results;
- final Switchboard sb = Switchboard.getSwitchboard();
- final EmbeddedSolrConnector connector = sb.index.fulltext().getDefaultEmbeddedConnector();
final QueryParams theQuery = buildTextQueryParams(query, count, QueryParams.Searchdom.LOCAL);
- final SolrQuery params = theQuery.solrQuery(Classification.ContentDomain.TEXT, false, false, false);
-
- try {
- final SolrDocumentList sdl = connector.getDocumentListByParams(params);
- Iterator<SolrDocument> i = sdl.iterator();
- while (i.hasNext()) {
- try {
- SolrDocument doc = i.next();
- final JSONObject result = new JSONObject(true);
- String url = (String) doc.getFieldValue(CollectionSchema.sku.getSolrFieldName());
- result.put("url", url == null ? "" : url.trim());
- String title = firstFieldString(doc.getFieldValue(CollectionSchema.title.getSolrFieldName()));
- result.put("title", title == null ? "" : title.trim());
- if (includeSnippet) {
- // Use indexed text body as quick snippet source.
- String text = firstFieldString(doc.getFieldValue(CollectionSchema.text_t.getSolrFieldName()));
- result.put("text", limitSnippet(text == null ? "" : text.trim(), 2000));
- }
- results.put(result);
- } catch (JSONException e) {
- }
- }
- return results;
- } catch (SolrException | IOException e) {
- return results;
- }
+ return searchResults(theQuery, count, includeSnippet);
}
/**
@@ -146,27 +93,11 @@ public final class RAGAugmentor {
* @return markdown context block
*/
public static String searchResultsAsMarkdown(String query, int count, boolean global) {
- return searchResultsAsMarkdown(query, count, global, new LinkedHashSet<>());
- }
-
- /**
- * Renders search results as compact markdown and applies snippet ranking to
- * reduce noise.
- *
- * @param query query string
- * @param count max number of search rows
- * @param global when true, use global YaCy search; otherwise local Solr
- * @param boostTerms optional local-search boost terms
- * @return markdown formatted context used in downstream prompt augmentation
- */
- public static String searchResultsAsMarkdown(String query, int count, boolean global, final Set<String> boostTerms) {
final long searchStart = System.currentTimeMillis();
- JSONArray searchResults = global ? searchResultsGlobal(query, count, true) : searchResults(query, count, true, boostTerms);
+ JSONArray searchResults = global ? searchResultsGlobal(query, count, true) : searchResults(query, count, true);
ConcurrentLog.info("RAGProxy", "searchResults=" + searchResults.length() + " global=" + global + " searchMs=" + (System.currentTimeMillis() - searchStart));
StringBuilder sb = new StringBuilder();
- // Convert raw rows into scoreable snippet candidates.
- List<Snippet> results = new ArrayList<>();
for (int i = 0; i < searchResults.length(); i++) {
try {
JSONObject r = searchResults.getJSONObject(i);
@@ -176,26 +107,15 @@ public final class RAGAugmentor {
if (title.isEmpty()) title = url;
if (text.isEmpty()) text = title;
if (title.length() > 0 && text.length() > 0) {
- Snippet snippet = new Snippet(query, text, url, title, 256);
- if (snippet.getText().length() > 0) results.add(snippet);
+ sb.append("## ").append(title).append("\n");
+ sb.append(text).append("\n");
+ if (url.length() > 0) sb.append("Source: ").append(url).append("\n");
+ sb.append("\n\n");
}
} catch (JSONException e) {}
}
- // Lower score is better with the current tf-idf based chunk scorer.
- results.sort(Comparator.comparingDouble(Snippet::getScore));
- // Keep top half to avoid overloading the model context window.
- int limit = results.size() / 2;
- if (results.size() > 0 && limit == 0) limit = 1;
- for (int i = 0; i < limit; i++) {
- Snippet snippet = results.get(i);
- sb.append("## ").append(snippet.getTitle()).append("\n");
- sb.append(snippet.text).append("\n");
- if (snippet.getURL().length() > 0) sb.append("Source: ").append(snippet.getURL()).append("\n");
- sb.append("\n\n");
- }
-
- ConcurrentLog.info("RAGProxy", "markdownChars=" + sb.length() + " snippetCount=" + results.size());
+ ConcurrentLog.info("RAGProxy", "markdownChars=" + sb.length() + " resultCount=" + searchResults.length());
return sb.toString();
}
@@ -209,10 +129,24 @@ public final class RAGAugmentor {
* @return JSON array with normalized result objects
*/
public static JSONArray searchResultsGlobal(String query, int count, final boolean includeSnippet) {
+ final QueryParams theQuery = buildTextQueryParams(query, count, QueryParams.Searchdom.GLOBAL);
+ return searchResults(theQuery, count, includeSnippet);
+ }
+
+ /**
+ * Execute a shared YaCy search event and extract results in a compact JSON
+ * representation. This uses the same execution pipeline as normal web search
+ * for both local and global RAG retrieval.
+ *
+ * @param theQuery fully-built YaCy query params
+ * @param count max number of results
+ * @param includeSnippet include snippet text when available
+ * @return JSON array with normalized result objects
+ */
+ private static JSONArray searchResults(final QueryParams theQuery, final int count, final boolean includeSnippet) {
final JSONArray results = new JSONArray();
- if (query == null || query.length() == 0 || count == 0) return results;
+ if (theQuery == null || count == 0) return results;
final Switchboard sb = Switchboard.getSwitchboard();
- final QueryParams theQuery = buildTextQueryParams(query, count, QueryParams.Searchdom.GLOBAL);
final SearchEvent theSearch = SearchEventCache.getEvent(
theQuery,
sb.peers,
@@ -229,27 +163,44 @@ public final class RAGAugmentor {
final long timeout = sb.getConfigLong(
SwitchboardConstants.REMOTESEARCH_MAXTIME_USER,
sb.getConfigLong(SwitchboardConstants.REMOTESEARCH_MAXTIME_DEFAULT, 3000));
- // Wait until remote feeds are done (or timeout), then stabilize ordering.
- waitForFeedingAndResort(theSearch, timeout);
- for (int i = 0; i < count; i++) {
- URIMetadataNode node = theSearch.oneResult(i, timeout);
- if (node == null) break;
+ final boolean globalSearch = !theQuery.isLocal();
+ if (globalSearch) {
+ theSearch.resortCachedResults();
+ } else {
+ // Local search can wait briefly for feeder completion to stabilize ordering.
+ waitForFeedingAndResort(theSearch, timeout);
+ }
+ final long deadline = System.currentTimeMillis() + timeout;
+ int resultIndex = 0;
+ while (resultIndex < count) {
+ final long remaining = deadline - System.currentTimeMillis();
+ if (remaining <= 0) break;
+ final long attemptTimeout = globalSearch ? Math.min(remaining, 500L) : remaining;
+ final URIMetadataNode node = theSearch.oneResult(resultIndex, attemptTimeout);
+ if (node == null) {
+ if (!globalSearch || theSearch.isFeedingFinished()) break;
+ theSearch.resortCachedResults();
+ continue;
+ }
+ if (globalSearch) theSearch.resortCachedResults();
try {
final JSONObject result = new JSONObject(true);
result.put("url", node.urlstring());
- result.put("title", node.title());
+ String title = node.title();
+ String text = null;
if (includeSnippet) {
- // Prefer direct snippet; fall back to textSnippet, description, then text field.
- String text = node.snippet();
+ if (text == null || text.isEmpty()) text = node.snippet();
if (text == null || text.isEmpty()) {
TextSnippet snippet = node.textSnippet();
if (snippet != null && snippet.exists() && !snippet.getErrorCode().fail()) text = snippet.getLineRaw();
}
if (text == null || text.isEmpty()) text = firstFieldString(node.getFieldValue(CollectionSchema.description_txt.getSolrFieldName()));
if (text == null || text.isEmpty()) text = firstFieldString(node.getFieldValue(CollectionSchema.text_t.getSolrFieldName()));
- result.put("text", limitSnippet(text == null ? "" : text.trim(), 2000));
+ result.put("text", text == null ? "" : text.trim());
}
+ result.put("title", title);
results.put(result);
+ resultIndex++;
} catch (JSONException e) {
}
}
@@ -274,16 +225,16 @@ public final class RAGAugmentor {
String querystring = modifier.parse(query);
if (querystring.length() == 0) querystring = query == null ? "" : query.trim();
final QueryGoal qg = new QueryGoal(querystring);
- return new QueryParams(
+ final QueryParams theQuery = new QueryParams(
qg,
modifier,
0,
"",
- Classification.ContentDomain.TEXT,
+ Classification.ContentDomain.ALL,
"",
- timezoneOffset,
+ 0,
new HashSet<Tagging.Metatag>(),
- CacheStrategy.IFFRESH,
+ CacheStrategy.CACHEONLY,
count,
0,
".*",
@@ -303,6 +254,18 @@ public final class RAGAugmentor {
0.0d,
0.0d,
sb.getConfigSet("search.navigation"));
+ theQuery.setStrictContentDom(!Boolean.FALSE.toString().equalsIgnoreCase(
+ sb.getConfig(SwitchboardConstants.SEARCH_STRICT_CONTENT_DOM,
+ String.valueOf(SwitchboardConstants.SEARCH_STRICT_CONTENT_DOM_DEFAULT))));
+ theQuery.setMaxSuggestions(0);
+ theQuery.setStandardFacetsMaxCount(sb.getConfigInt(
+ SwitchboardConstants.SEARCH_NAVIGATION_MAXCOUNT,
+ QueryParams.FACETS_STANDARD_MAXCOUNT_DEFAULT));
+ theQuery.setDateFacetMaxCount(sb.getConfigInt(
+ SwitchboardConstants.SEARCH_NAVIGATION_DATES_MAXCOUNT,
+ QueryParams.FACETS_DATE_MAXCOUNT_DEFAULT));
+ theQuery.getQueryGoal().filterOut(Switchboard.blueList);
+ return theQuery;
}
/**
@@ -341,28 +304,6 @@ public final class RAGAugmentor {
}
/**
- * Computes token overlap between original prompt and computed query terms.
- *
- * @param originalPrompt raw user prompt
- * @param computedQuery generated query terms
- * @param maxTerms max terms to keep; {@code <=0} means unlimited
- * @return cleaned ordered overlap set
- */
- public static Set<String> intersectTokens(String originalPrompt, String computedQuery, int maxTerms) {
- Set<String> promptTerms = querySet(originalPrompt == null ? "" : originalPrompt);
- Set<String> queryTerms = querySet(computedQuery == null ? "" : computedQuery);
- Set<String> intersection = new LinkedHashSet<>();
- for (String term : promptTerms) {
- if (!queryTerms.contains(term)) continue;
- final String cleaned = cleanToken(term);
- if (cleaned.isEmpty()) continue;
- intersection.add(cleaned);
- if (maxTerms > 0 && intersection.size() >= maxTerms) break;
- }
- return intersection;
- }
-
- /**
* Splits text into sentence-aware chunks around a target max length.
*
* @param text source text
@@ -389,45 +330,6 @@ public final class RAGAugmentor {
}
/**
- * Converts a query string into a normalized token set.
- *
- * @param query raw query text
- * @return lowercase token set
- */
- private static Set<String> querySet(String query) {
- return Arrays.stream(query.trim().toLowerCase().split("\\s+"))
- .map(String::toLowerCase)
- .filter(word -> !word.isEmpty())
- .collect(Collectors.toSet());
- }
-
- /**
- * Removes non-alphanumeric characters and enforces minimal token length.
- *
- * @param term source token
- * @return cleaned lowercase token or empty string
- */
- private static String cleanToken(String term) {
- if (term == null) return "";
- String cleaned = term.replaceAll("[^A-Za-z0-9]", "");
- if (cleaned.length() < 2) return "";
- return cleaned.toLowerCase();
- }
-
- /**
- * Truncates snippet text to a maximum character count.
- *
- * @param text input text
- * @param maxChars limit
- * @return truncated or original text
- */
- private static String limitSnippet(String text, int maxChars) {
- if (text == null) return "";
- if (maxChars <= 0 || text.length() <= maxChars) return text;
- return text.substring(0, maxChars);
- }
-
- /**
* Returns first non-null string from a field value that may be scalar or
* collection.
*
@@ -463,108 +365,4 @@ public final class RAGAugmentor {
search.resortCachedResults();
}
- /**
- * Represents one candidate snippet around a search result and stores its
- * relevance score relative to the query.
- */
- private static class Snippet {
- private String text, url, title;
- private double score;
-
- /**
- * Scores text chunks and keeps the best chunk plus direct neighbors to
- * preserve context continuity.
- *
- * @param query query text
- * @param text source document/snippet text
- * @param url source URL
- * @param title source title
- * @param maxChunkLength target chunk size
- */
- public Snippet(String query, String text, String url, String title, int maxChunkLength) {
- this.url = url;
- this.title = title;
- this.score = 0.0;
-
- if (text == null || text.isEmpty() || maxChunkLength <= 0 || query == null) {
- this.text = "";
- return;
- }
-
- List<String> chunks = slicer(text, maxChunkLength);
- if (chunks.isEmpty()) {
- this.text = "";
- return;
- }
- List<String> chunksLowerCase = new ArrayList<>(chunks.size());
- // Cache lowercase chunks so token comparisons are case-insensitive.
- for (String chunk: chunks) chunksLowerCase.add(chunk.toLowerCase());
-
- Set<String> queryWordSet = querySet(query);
- if (queryWordSet.isEmpty()) {
- this.text = "";
- return;
- }
-
- int totalChunks = chunksLowerCase.size();
- Map<String, Double> idf = new HashMap<>();
- for (String word: queryWordSet) {
- int docFreq = 0;
- for (String chunk: chunksLowerCase) {
- if (chunk.contains(word)) docFreq++;
- }
- // Smoothed IDF to avoid divide-by-zero and extreme values.
- idf.put(word, Math.log((double) totalChunks / (docFreq + 1)) + 1);
- }
-
- Map<Integer, Double> chunkScores = new HashMap<>();
- for (int i = 0; i < chunksLowerCase.size(); i++) {
- String chunk = chunksLowerCase.get(i);
- double score = 0.0;
- Map<String, Integer> tf = new HashMap<>();
-
- String[] wordsInChunk = chunk.split("\\s+");
- for (String w : wordsInChunk) {
- String cleanWord = w.replaceAll("[.,!?;:]", "");
- if (cleanWord.length() > 0 && queryWordSet.contains(cleanWord)) {
- tf.put(cleanWord, tf.getOrDefault(cleanWord, 0) + 1);
- }
- }
-
- for (String word: queryWordSet) {
- int tfValue = tf.getOrDefault(word, 0);
- double tfIdf = (double) tfValue * idf.getOrDefault(word, 1.0);
- score += tfIdf;
- }
- chunkScores.put(i, score);
- }
-
- int topChunkIndex = -1;
- for (Map.Entry<Integer, Double> entry: chunkScores.entrySet()) {
- // Keep best-scoring chunk index.
- if (entry.getValue() > this.score) {
- this.score = entry.getValue();
- topChunkIndex = entry.getKey();
- }
- }
-
- if (topChunkIndex < 0) {
- this.text = "";
- this.score = 0.0;
- return;
- }
-
- List<String> snippetChunks = new ArrayList<>();
- // Include neighboring chunks to reduce abrupt starts/ends.
- if (topChunkIndex > 0) snippetChunks.add(chunks.get(topChunkIndex - 1));
- snippetChunks.add(chunks.get(topChunkIndex));
- if (topChunkIndex < chunks.size() - 1) snippetChunks.add(chunks.get(topChunkIndex + 1));
- this.text = String.join(" ", snippetChunks);
- }
-
- public double getScore() { return this.score; }
- public String getText() { return this.text; }
- public String getURL() { return this.url; }
- public String getTitle() { return this.title; }
- }
}
diff --git a/source/net/yacy/http/servlets/RAGProxyServlet.java b/source/net/yacy/http/servlets/RAGProxyServlet.java
index c552b7387..4b79153ba 100644
--- a/source/net/yacy/http/servlets/RAGProxyServlet.java
+++ b/source/net/yacy/http/servlets/RAGProxyServlet.java
@@ -28,7 +28,6 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.Deque;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import javax.servlet.ServletException;
@@ -69,6 +68,7 @@ import net.yacy.search.Switchboard;
public class RAGProxyServlet extends HttpServlet {
private static final long serialVersionUID = 3411544789759643137L;
+ private static final int DIRECT_SEARCH_WORD_LIMIT = 40;
public static final String LLM_SYSTEM_PROMPT_DEFAULT = "You are a smart and helpful chatbot. If possible, use friendly emojies.";
private static final String LLM_USER_PREFIX_DEFAULT = "\n\nAdditional Information:\n\nbelow you find a collection of texts that might be useful to generate a response. Do not discuss these documents, just use them to answer the question above.\n\n";
@@ -169,7 +169,6 @@ public class RAGProxyServlet extends HttpServlet {
UserObject userObject = null;
String user = "";
String ragMode = "no";
- String userPrompt = "";
int lastUserIndex = -1;
for (int i = messages.length() - 1; i >= 0; i--) {
JSONObject message = messages.getJSONObject(i);
@@ -181,7 +180,6 @@ public class RAGProxyServlet extends HttpServlet {
if (lastUserIndex >= 0) {
userObject = new UserObject(messages.getJSONObject(lastUserIndex));
user = userObject.getContentText(); // this is the latest user prompt
- userPrompt = user;
ragMode = userObject.getSearchMode();
}
ConcurrentLog.info("RAGProxy", "ragMode=" + ragMode + " userChars=" + (user == null ? 0 : user.length()));
@@ -194,17 +192,20 @@ public class RAGProxyServlet extends HttpServlet {
// modify system and user prompt here in bodyObject to enable RAG
final String queryPrefix = sb.getConfig("ai.llm-query-generator-prefix", LLM_QUERY_GENERATOR_PREFIX_DEFAULT);
final long queryStart = System.currentTimeMillis();
- searchResultQuery = RAGAugmentor.searchWordsForPrompt(llm4tldr.llm, llm4tldr.model, queryPrefix + user); // might return null in case any error occurred
- if (searchResultQuery == null || searchResultQuery.length() == 0) searchResultQuery = user; // in case there is an error we simply search with the prompt
+ if (countWords(user) <= DIRECT_SEARCH_WORD_LIMIT) {
+ searchResultQuery = user;
+ } else {
+ searchResultQuery = RAGAugmentor.searchWordsForPrompt(llm4tldr.llm, llm4tldr.model, queryPrefix + user); // might return null in case any error occurred
+ if (searchResultQuery == null || searchResultQuery.length() == 0) searchResultQuery = user; // in case there is an error we simply search with the prompt
+ }
final long queryElapsed = System.currentTimeMillis() - queryStart;
- final Set<String> boostTerms = RAGAugmentor.intersectTokens(userPrompt, searchResultQuery, 8);
final long searchStart = System.currentTimeMillis();
- searchResultMarkdown = RAGAugmentor.searchResultsAsMarkdown(searchResultQuery, 10, "global".equals(ragMode), boostTerms);
+ searchResultMarkdown = RAGAugmentor.searchResultsAsMarkdown(searchResultQuery, 10, "global".equals(ragMode));
final long searchElapsed = System.currentTimeMillis() - searchStart;
ConcurrentLog.info(
"RAGProxy",
"searchQuery=\"" + searchResultQuery + "\" queryMs=" + queryElapsed + " searchMs=" + searchElapsed +
- " markdownChars=" + searchResultMarkdown.length() + " boostTerms=" + boostTerms.size());
+ " markdownChars=" + searchResultMarkdown.length());
user += userPrefix;
user += searchResultMarkdown;
userObject.setContentText(user);
@@ -226,6 +227,13 @@ public class RAGProxyServlet extends HttpServlet {
}
}
+ private static int countWords(final String text) {
+ if (text == null) return 0;
+ final String trimmed = text.trim();
+ if (trimmed.isEmpty()) return 0;
+ return trimmed.split("\\s+").length;
+ }
+
public final static class DataURL {
private String mimetype;
private byte[] data;
diff --git a/source/net/yacy/search/query/QueryParams.java b/source/net/yacy/search/query/QueryParams.java
index f0e079fca..e3bd008bf 100644
--- a/source/net/yacy/search/query/QueryParams.java
+++ b/source/net/yacy/search/query/QueryParams.java
@@ -75,10 +75,10 @@ import net.yacy.kelondro.data.word.WordReferenceRow;
import net.yacy.kelondro.index.RowHandleSet;
import net.yacy.kelondro.util.Bitfield;
import net.yacy.kelondro.util.SetTools;
-import net.yacy.peers.Seed;
-import net.yacy.search.index.Segment;
-import net.yacy.search.navigator.NavigatorPlugins;
-import net.yacy.search.navigator.NavigatorSort;
+import net.yacy.peers.Seed;
+import net.yacy.search.index.Segment;
+import net.yacy.search.navigator.NavigatorPlugins;
+import net.yacy.search.navigator.NavigatorSort;
import net.yacy.search.ranking.RankingProfile;
import net.yacy.search.schema.CollectionConfiguration;
import net.yacy.search.schema.CollectionSchema;
@@ -132,10 +132,10 @@ public final class QueryParams {
/** List of Solr fields used to extract text snippets when requesting the Solr index */
private final static CollectionSchema[] SOLR_SNIPPET_FIELDS = new CollectionSchema[]{CollectionSchema.description_txt, CollectionSchema.h4_txt, CollectionSchema.h3_txt, CollectionSchema.h2_txt, CollectionSchema.h1_txt, CollectionSchema.text_t};
- public static final Bitfield empty_constraint = new Bitfield(4, "AAAAAA");
- public static final Pattern catchall_pattern = Pattern.compile(".*");
-
- private final QueryGoal queryGoal;
+ public static final Bitfield empty_constraint = new Bitfield(4, "AAAAAA");
+ public static final Pattern catchall_pattern = Pattern.compile(".*");
+
+ private final QueryGoal queryGoal;
public int itemsPerPage;
public int offset;
diff --git a/source/net/yacy/search/query/SearchEvent.java b/source/net/yacy/search/query/SearchEvent.java
index 6f5e87f01..b7d7e94c2 100644
--- a/source/net/yacy/search/query/SearchEvent.java
+++ b/source/net/yacy/search/query/SearchEvent.java
@@ -1895,9 +1895,8 @@ public final class SearchEvent implements ScoreMapUpdatesListener {
false,
180,
false);
- final String solrsnippetline = solrsnippet.descriptionline(this.getQuery().getQueryGoal());
- final String yacysnippetline = yacysnippet.descriptionline(this.getQuery().getQueryGoal());
- final URIMetadataNode re = node.makeResultEntry(this.query.getSegment(), this.peers, solrsnippetline.length() > yacysnippetline.length() ? solrsnippet : yacysnippet);
+ final TextSnippet selectedSnippet = solrsnippet.getLineRaw().length() > yacysnippet.getLineRaw().length() ? solrsnippet : yacysnippet;
+ final URIMetadataNode re = node.makeResultEntry(this.query.getSegment(), this.peers, maximizeSnippet(node, selectedSnippet));
addResult(re, localEntryElement.getWeight());
success = true;
} else {
@@ -2022,7 +2021,7 @@ public final class SearchEvent implements ScoreMapUpdatesListener {
((this.query.constraint != null) && (this.query.constraint.get(Tokenizer.flag_cat_indexof))),
SearchEvent.SNIPPET_MAX_LENGTH,
!this.query.isLocal());
- return page.makeResultEntry(this.query.getSegment(), this.peers, snippet); // result without snippet
+ return page.makeResultEntry(this.query.getSegment(), this.peers, maximizeSnippet(page, snippet)); // result without snippet
}
// load snippet
@@ -2043,7 +2042,7 @@ public final class SearchEvent implements ScoreMapUpdatesListener {
if (!snippet.getErrorCode().fail()) {
// we loaded the file and found the snippet
- return page.makeResultEntry(this.query.getSegment(), this.peers, snippet); // result with snippet attached
+ return page.makeResultEntry(this.query.getSegment(), this.peers, maximizeSnippet(page, snippet)); // result with snippet attached
} else if (cacheStrategy.mustBeOffline()) {
// we did not demand online loading, therefore a failure does not mean that the missing snippet causes a rejection of this result
// this may happen during a remote search, because snippet loading is omitted to retrieve results faster
@@ -2083,6 +2082,23 @@ public final class SearchEvent implements ScoreMapUpdatesListener {
return page.makeResultEntry(this.query.getSegment(), this.peers, null); // result without snippet
}
+ private static TextSnippet maximizeSnippet(final URIMetadataNode page, final TextSnippet snippet) {
+ if (page == null || snippet == null || !snippet.exists()) {
+ return snippet;
+ }
+ String rawText = page.getText();
+ if (rawText == null || rawText.isEmpty()) {
+ final ArrayList<String> descriptions = page.getDescription();
+ if (descriptions != null && !descriptions.isEmpty()) {
+ rawText = descriptions.get(0);
+ }
+ }
+ if (rawText == null || rawText.isEmpty()) {
+ return snippet;
+ }
+ return new TextSnippet(page.url(), rawText, snippet.getLineRaw(), snippet.isMarked(), snippet.getErrorCode(), snippet.getError());
+ }
+
/**
* This is the access point for the search interface to retrive ranked results.
* for display.
diff --git a/source/net/yacy/search/snippet/TextSnippet.java b/source/net/yacy/search/snippet/TextSnippet.java
index 948e41c69..a9f7ed411 100644
--- a/source/net/yacy/search/snippet/TextSnippet.java
+++ b/source/net/yacy/search/snippet/TextSnippet.java
@@ -167,15 +167,26 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
private String error;
private ResultClass resultStatus;
- public TextSnippet(
- final DigestURL url,
- final String line,
- final boolean isMarked,
- final ResultClass errorCode,
- final String errortext) {
- long beginTime = System.currentTimeMillis();
- init(url, line, isMarked, errorCode, errortext, beginTime);
- }
+ public TextSnippet(
+ final DigestURL url,
+ final String line,
+ final boolean isMarked,
+ final ResultClass errorCode,
+ final String errortext) {
+ long beginTime = System.currentTimeMillis();
+ init(url, line, line, isMarked, errorCode, errortext, beginTime);
+ }
+
+ public TextSnippet(
+ final DigestURL url,
+ final String rawLine,
+ final String displayLine,
+ final boolean isMarked,
+ final ResultClass errorCode,
+ final String errortext) {
+ long beginTime = System.currentTimeMillis();
+ init(url, rawLine, displayLine, isMarked, errorCode, errortext, beginTime);
+ }
public TextSnippet(
final LoaderDispatcher loader,
@@ -191,7 +202,7 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
final DigestURL url = row.url();
if (queryTerms.isEmpty()) {
- init(url, null, false, ResultClass.ERROR_NO_TERM_GIVEN, "no query terms given", beginTime);
+ init(url, null, null, false, ResultClass.ERROR_NO_TERM_GIVEN, "no query terms given", beginTime);
return;
}
@@ -204,7 +215,7 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
final String snippetLine = snippetsCache.get(wordhashes, urlHash);
if (snippetLine != null) {
// found the snippet
- init(url, snippetLine, false, source, null, beginTime);
+ init(url, snippetLine, snippetLine, false, source, null, beginTime);
return;
}
} else {
@@ -261,7 +272,7 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
}
if (sentences == null) {
// not found the snippet
- init(url, null, false, ResultClass.SOURCE_METADATA, null, beginTime);
+ init(url, null, null, false, ResultClass.SOURCE_METADATA, null, beginTime);
return;
}
@@ -271,7 +282,7 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
textline = tsr.getSnippet();
remainingTerms = tsr.getRemainingTerms();
} catch (final UnsupportedOperationException e) {
- init(url, null, false, ResultClass.ERROR_NO_MATCH, "snippet extractor failed:" + e.getMessage(), beginTime);
+ init(url, null, null, false, ResultClass.ERROR_NO_MATCH, "snippet extractor failed:" + e.getMessage(), beginTime);
return;
}
}
@@ -323,9 +334,10 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
}
}
}
- init(url, textline.length() > 0 ? textline : this.line, false, ResultClass.SOURCE_METADATA, null, beginTime);
- return;
- }
+ final String fallbackLine = textline.length() > 0 ? textline : this.line;
+ init(url, fallbackLine, fallbackLine, false, ResultClass.SOURCE_METADATA, null, beginTime);
+ return;
+ }
sentences = null; // we don't need this here any more
// try to load the resource from the cache
@@ -339,14 +351,14 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
if (response == null) {
// in case that we did not get any result we can still return a success when we are not allowed to go online
if (cacheStrategy == null || cacheStrategy.mustBeOffline()) {
- init(url, null, false, ResultClass.ERROR_SOURCE_LOADING, "omitted network load (not allowed), no cache entry", beginTime);
- return;
- }
+ init(url, null, null, false, ResultClass.ERROR_SOURCE_LOADING, "omitted network load (not allowed), no cache entry", beginTime);
+ return;
+ }
// if it is still not available, report an error
- init(url, null, false, ResultClass.ERROR_RESOURCE_LOADING, "error loading resource from net, no cache entry", beginTime);
- return;
- }
+ init(url, null, null, false, ResultClass.ERROR_RESOURCE_LOADING, "error loading resource from net, no cache entry", beginTime);
+ return;
+ }
if (!response.fromCache()) {
// place entry on indexing queue
@@ -359,44 +371,44 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
try {
document = Document.mergeDocuments(response.url(), response.getMimeType(), response.parse());
} catch (final Parser.Failure e) {
- init(url, null, false, ResultClass.ERROR_PARSER_FAILED, e.getMessage(), beginTime); // cannot be parsed
- return;
- }
- if (document == null) {
- init(url, null, false, ResultClass.ERROR_PARSER_FAILED, "parser error/failed", beginTime); // cannot be parsed
- return;
- }
+ init(url, null, null, false, ResultClass.ERROR_PARSER_FAILED, e.getMessage(), beginTime); // cannot be parsed
+ return;
+ }
+ if (document == null) {
+ init(url, null, null, false, ResultClass.ERROR_PARSER_FAILED, "parser error/failed", beginTime); // cannot be parsed
+ return;
+ }
// compute sentences from parsed document
sentences = new SentenceReader(document.getTextString(), pre);
document.close();
if (!sentences.hasNext()) {
- init(url, null, false, ResultClass.ERROR_PARSER_NO_LINES, "parser returned no sentences", beginTime);
- return;
- }
+ init(url, null, null, false, ResultClass.ERROR_PARSER_NO_LINES, "parser returned no sentences", beginTime);
+ return;
+ }
try {
final SnippetExtractor tsr = new SnippetExtractor(sentences, remainingTerms, snippetMaxLength);
textline = tsr.getSnippet();
remainingTerms = tsr.getRemainingTerms();
} catch (final UnsupportedOperationException e) {
- init(url, null, false, ResultClass.ERROR_NO_MATCH, "snippet extractor failed:" + e.getMessage(), beginTime);
- return;
- }
+ init(url, null, null, false, ResultClass.ERROR_NO_MATCH, "snippet extractor failed:" + e.getMessage(), beginTime);
+ return;
+ }
sentences = null;
if (textline == null || !remainingTerms.isEmpty()) {
- init(url, null, false, ResultClass.ERROR_NO_MATCH, "no matching snippet found", beginTime);
- return;
- }
+ init(url, null, null, false, ResultClass.ERROR_NO_MATCH, "no matching snippet found", beginTime);
+ return;
+ }
if (textline.length() > snippetMaxLength) textline = textline.substring(0, snippetMaxLength);
// finally store this snippet in our own cache
if(wordhashes != null) {
snippetsCache.put(wordhashes, urlHash, textline);
}
- init(url, textline, false, source, null, beginTime);
+ init(url, textline, textline, false, source, null, beginTime);
}
/**
@@ -411,14 +423,16 @@ public class TextSnippet implements Comparable<TextSnippet>, Comparator<TextSnip
*/
private void init(
final DigestURL url,
- final String line,
+ final String rawLine,
+ final String displayLine,
final boolean isMarked,
final ResultClass errorCode,
final String errortext,
final long beginTime) {
this.urlhash = url.hash();
- this.rawline = sanitizeSnippetLine(line);
- this.line = stripMarkdownForDisplay(this.rawline);
+ this.rawline = sanitizeSnippetLine(rawLine);
+ final String sanitizedDisplay = sanitizeSnippetLine(displayLine);
+ this.line = stripMarkdownForDisplay(sanitizedDisplay == null ? this.rawline : sanitizedDisplay);
this.isMarked = isMarked;
this.resultStatus = errorCode;
this.error = errortext;