diff --git a/agent/component/iterationitem.py b/agent/component/iterationitem.py
index 71d032b51..cb1a27049 100644
--- a/agent/component/iterationitem.py
+++ b/agent/component/iterationitem.py
@@ -38,6 +38,10 @@ class IterationItem(ComponentBase, ABC):
ans = parent.get_input()
ans = parent._param.delimiter.join(ans["content"]) if "content" in ans else ""
ans = [a.strip() for a in ans.split(parent._param.delimiter)]
+ if not ans:
+ self._idx = -1
+ return pd.DataFrame()
+
df = pd.DataFrame([{"content": ans[self._idx]}])
self._idx += 1
if self._idx >= len(ans):
diff --git a/agentic_reasoning/prompts.py b/agentic_reasoning/prompts.py
index 610409af1..715896b86 100644
--- a/agentic_reasoning/prompts.py
+++ b/agentic_reasoning/prompts.py
@@ -68,6 +68,7 @@ REASON_PROMPT = (
f"- You have a dataset to search, so you just provide a proper search query.\n"
f"- Use {BEGIN_SEARCH_QUERY} to request a dataset search and end with {END_SEARCH_QUERY}.\n"
"- The language of query MUST be as the same as 'Question' or 'search result'.\n"
+ "- If no helpful information can be found, rewrite the search query to be less and precise keywords.\n"
"- When done searching, continue your reasoning.\n\n"
'Please answer the following question. You should think step by step to solve it.\n\n'
)
diff --git a/api/db/services/dialog_service.py b/api/db/services/dialog_service.py
index f6e2d8f2b..e7d71186c 100644
--- a/api/db/services/dialog_service.py
+++ b/api/db/services/dialog_service.py
@@ -30,7 +30,8 @@ from api import settings
from rag.app.resume import forbidden_select_fields4resume
from rag.app.tag import label_question
from rag.nlp.search import index_name
-from rag.prompts import kb_prompt, message_fit_in, llm_id2llm_type, keyword_extraction, full_question, chunks_format
+from rag.prompts import kb_prompt, message_fit_in, llm_id2llm_type, keyword_extraction, full_question, chunks_format, \
+ citation_prompt
from rag.utils import rmSpace, num_tokens_from_string
from rag.utils.tavily_conn import Tavily
@@ -235,9 +236,12 @@ def chat(dialog, messages, stream=True, **kwargs):
gen_conf = dialog.llm_setting
msg = [{"role": "system", "content": prompt_config["system"].format(**kwargs)}]
+ prompt4citation = ""
+ if knowledges and (prompt_config.get("quote", True) and kwargs.get("quote", True)):
+ prompt4citation = citation_prompt()
msg.extend([{"role": m["role"], "content": re.sub(r"##\d+\$\$", "", m["content"])}
for m in messages if m["role"] != "system"])
- used_token_count, msg = message_fit_in(msg, int(max_tokens * 0.97))
+ used_token_count, msg = message_fit_in(msg, int(max_tokens * 0.95))
assert len(msg) >= 2, f"message_fit_in has bug: {msg}"
prompt = msg[0]["content"]
@@ -256,14 +260,23 @@ def chat(dialog, messages, stream=True, **kwargs):
think = ans[0] + ""
answer = ans[1]
if knowledges and (prompt_config.get("quote", True) and kwargs.get("quote", True)):
- answer, idx = retriever.insert_citations(answer,
- [ck["content_ltks"]
- for ck in kbinfos["chunks"]],
- [ck["vector"]
- for ck in kbinfos["chunks"]],
- embd_mdl,
- tkweight=1 - dialog.vector_similarity_weight,
- vtweight=dialog.vector_similarity_weight)
+ answer = re.sub(r"##[ij]\$\$", "", answer, flags=re.DOTALL)
+ if not re.search(r"##[0-9]+\$\$", answer):
+ answer, idx = retriever.insert_citations(answer,
+ [ck["content_ltks"]
+ for ck in kbinfos["chunks"]],
+ [ck["vector"]
+ for ck in kbinfos["chunks"]],
+ embd_mdl,
+ tkweight=1 - dialog.vector_similarity_weight,
+ vtweight=dialog.vector_similarity_weight)
+ else:
+ idx = set([])
+ for r in re.finditer(r"##([0-9]+)\$\$", answer):
+ i = int(r.group(1))
+ if i < len(kbinfos["chunks"]):
+ idx.add(i)
+
idx = set([kbinfos["chunks"][int(i)]["doc_id"] for i in idx])
recall_docs = [
d for d in kbinfos["doc_aggs"] if d["doc_id"] in idx]
@@ -298,7 +311,7 @@ def chat(dialog, messages, stream=True, **kwargs):
if stream:
last_ans = ""
answer = ""
- for ans in chat_mdl.chat_streamly(prompt, msg[1:], gen_conf):
+ for ans in chat_mdl.chat_streamly(prompt+prompt4citation, msg[1:], gen_conf):
if thought:
ans = re.sub(r".*", "", ans, flags=re.DOTALL)
answer = ans
@@ -312,7 +325,7 @@ def chat(dialog, messages, stream=True, **kwargs):
yield {"answer": thought+answer, "reference": {}, "audio_binary": tts(tts_mdl, delta_ans)}
yield decorate_answer(thought+answer)
else:
- answer = chat_mdl.chat(prompt, msg[1:], gen_conf)
+ answer = chat_mdl.chat(prompt+prompt4citation, msg[1:], gen_conf)
user_content = msg[-1].get("content", "[content not available]")
logging.debug("User: {}|Assistant: {}".format(user_content, answer))
res = decorate_answer(answer)
diff --git a/rag/prompts.py b/rag/prompts.py
index 32ef5325e..af6df1623 100644
--- a/rag/prompts.py
+++ b/rag/prompts.py
@@ -108,22 +108,63 @@ def kb_prompt(kbinfos, max_tokens):
docs = {d.id: d.meta_fields for d in docs}
doc2chunks = defaultdict(lambda: {"chunks": [], "meta": []})
- for ck in kbinfos["chunks"][:chunks_num]:
- doc2chunks[ck["docnm_kwd"]]["chunks"].append((f"URL: {ck['url']}\n" if "url" in ck else "") + ck["content_with_weight"])
+ for i, ck in enumerate(kbinfos["chunks"][:chunks_num]):
+ doc2chunks[ck["docnm_kwd"]]["chunks"].append((f"URL: {ck['url']}\n" if "url" in ck else "") + f"ID: {i}\n" + ck["content_with_weight"])
doc2chunks[ck["docnm_kwd"]]["meta"] = docs.get(ck["doc_id"], {})
knowledges = []
for nm, cks_meta in doc2chunks.items():
- txt = f"Document: {nm} \n"
+ txt = f"\nDocument: {nm} \n"
for k, v in cks_meta["meta"].items():
txt += f"{k}: {v}\n"
txt += "Relevant fragments as following:\n"
for i, chunk in enumerate(cks_meta["chunks"], 1):
- txt += f"{i}. {chunk}\n"
+ txt += f"{chunk}\n"
knowledges.append(txt)
return knowledges
+def citation_prompt():
+ return """
+
+# Citation requirements:
+- Inserts CITATIONS in format '##i$$ ##j$$' where i,j are the ID of the content you are citing and encapsulated with '##' and '$$'.
+- Inserts the CITATION symbols at the end of a sentence, AND NO MORE than 4 citations.
+- DO NOT insert CITATION in the answer if the content is not from retrieved chunks.
+
+--- Example START ---
+: Here is the knowledge base:
+
+Document: Elon Musk Breaks Silence on Crypto, Warns Against Dogecoin ...
+URL: https://blockworks.co/news/elon-musk-crypto-dogecoin
+ID: 0
+The Tesla co-founder advised against going all-in on dogecoin, but Elon Musk said it’s still his favorite crypto...
+
+Document: Elon Musk's Dogecoin tweet sparks social media frenzy
+ID: 1
+Musk said he is 'willing to serve' D.O.G.E. – shorthand for Dogecoin.
+
+Document: Causal effect of Elon Musk tweets on Dogecoin price
+ID: 2
+If you think of Dogecoin — the cryptocurrency based on a meme — you can’t help but also think of Elon Musk...
+
+Document: Elon Musk's Tweet Ignites Dogecoin's Future In Public Services
+ID: 3
+The market is heating up after Elon Musk's announcement about Dogecoin. Is this a new era for crypto?...
+
+ The above is the knowledge base.
+
+: What's the Elon's view on dogecoin?
+
+: Musk has consistently expressed his fondness for Dogecoin, often citing its humor and the inclusion of dogs in its branding. He has referred to it as his favorite cryptocurrency ##0$$ ##1$$.
+Recently, Musk has hinted at potential future roles for Dogecoin. His tweets have sparked speculation about Dogecoin's potential integration into public services ##3$$.
+Overall, while Musk enjoys Dogecoin and often promotes it, he also warns against over-investing in it, reflecting both his personal amusement and caution regarding its speculative nature.
+
+--- Example END ---
+
+"""
+
+
def keyword_extraction(chat_mdl, content, topn=3):
prompt = f"""
Role: You're a text analyzer.
diff --git a/rag/utils/tavily_conn.py b/rag/utils/tavily_conn.py
index 7d78636e0..c8eaf4ae9 100644
--- a/rag/utils/tavily_conn.py
+++ b/rag/utils/tavily_conn.py
@@ -27,7 +27,8 @@ class Tavily:
try:
response = self.tavily_client.search(
query=query,
- search_depth="advanced"
+ search_depth="advanced",
+ max_results=6
)
return [{"url": res["url"], "title": res["title"], "content": res["content"], "score": res["score"]} for res in response["results"]]
except Exception as e: