|
import gradio as gr |
|
from langchain.tools import Tool |
|
from langchain_community.utilities import GoogleSearchAPIWrapper |
|
import os |
|
|
|
from langchain.tools import Tool |
|
from langchain_community.utilities import GoogleSearchAPIWrapper |
|
|
|
|
|
def get_search(query:str="", k:int=1): |
|
search = GoogleSearchAPIWrapper(k=k) |
|
def search_results(query): |
|
return search.results(query, k) |
|
tool = Tool( |
|
name="Google Search Snippets", |
|
description="Search Google for recent results.", |
|
func=search_results, |
|
) |
|
ref_text = tool.run(query) |
|
if 'Result' not in ref_text[0].keys(): |
|
return ref_text |
|
else: |
|
return None |
|
|
|
from langchain_community.document_transformers import Html2TextTransformer |
|
from langchain_community.document_loaders import AsyncHtmlLoader |
|
def get_page_content(link:str): |
|
loader = AsyncHtmlLoader([link]) |
|
docs = loader.load() |
|
html2text = Html2TextTransformer() |
|
docs_transformed = html2text.transform_documents(docs) |
|
if len(docs_transformed) > 0: |
|
return docs_transformed[0].page_content |
|
else: |
|
return None |
|
|
|
import tiktoken |
|
def num_tokens_from_string(string: str, encoding_name: str = "cl100k_base") -> int: |
|
"""Returns the number of tokens in a text string.""" |
|
encoding = tiktoken.get_encoding(encoding_name) |
|
num_tokens = len(encoding.encode(string)) |
|
return num_tokens |
|
|
|
def chunk_text_by_sentence(text, chunk_size=2048): |
|
"""Chunk the $text into sentences with less than 2k tokens.""" |
|
sentences = text.split('. ') |
|
chunked_text = [] |
|
curr_chunk = [] |
|
|
|
for sentence in sentences: |
|
if num_tokens_from_string(". ".join(curr_chunk)) + num_tokens_from_string(sentence) + 2 <= chunk_size: |
|
curr_chunk.append(sentence) |
|
else: |
|
chunked_text.append(". ".join(curr_chunk)) |
|
curr_chunk = [sentence] |
|
|
|
if curr_chunk: |
|
chunked_text.append(". ".join(curr_chunk)) |
|
return chunked_text[0] |
|
|
|
def chunk_text_front(text, chunk_size = 2048): |
|
''' |
|
get the first `trunk_size` token of text |
|
''' |
|
chunked_text = "" |
|
tokens = num_tokens_from_string(text) |
|
if tokens < chunk_size: |
|
return text |
|
else: |
|
ratio = float(chunk_size) / tokens |
|
char_num = int(len(text) * ratio) |
|
return text[:char_num] |
|
|
|
def chunk_texts(text, chunk_size = 2048): |
|
''' |
|
trunk the text into n parts, return a list of text |
|
[text, text, text] |
|
''' |
|
tokens = num_tokens_from_string(text) |
|
if tokens < chunk_size: |
|
return [text] |
|
else: |
|
texts = [] |
|
n = int(tokens/chunk_size) + 1 |
|
|
|
part_length = len(text) // n |
|
|
|
extra = len(text) % n |
|
parts = [] |
|
start = 0 |
|
|
|
for i in range(n): |
|
|
|
end = start + part_length + (1 if i < extra else 0) |
|
parts.append(text[start:end]) |
|
start = end |
|
return parts |
|
|
|
from datetime import datetime |
|
|
|
from openai import OpenAI |
|
import openai |
|
import os |
|
|
|
chatgpt_system_prompt = f''' |
|
You are ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture. |
|
Knowledge cutoff: 2023-04 |
|
Current date: {datetime.now().strftime('%Y-%m-%d')} |
|
''' |
|
|
|
def get_draft(question): |
|
|
|
draft_prompt = ''' |
|
IMPORTANT: |
|
Try to answer this question/instruction with step-by-step thoughts and make the answer more structural. |
|
Use `\n\n` to split the answer into several paragraphs. |
|
Just respond to the instruction directly. DO NOT add additional explanations or introducement in the answer unless you are asked to. |
|
''' |
|
|
|
openai_client = OpenAI(api_key = os.getenv('OPENAI_API_KEY')) |
|
draft = openai_client.chat.completions.create( |
|
model="gpt-3.5-turbo", |
|
messages=[ |
|
{ |
|
"role": "system", |
|
"content": chatgpt_system_prompt |
|
}, |
|
{ |
|
"role": "user", |
|
"content": f"{question}" + draft_prompt |
|
} |
|
], |
|
temperature = 1.0 |
|
).choices[0].message.content |
|
return draft |
|
|
|
def split_draft(draft, split_char = '\n\n'): |
|
|
|
|
|
paragraphs = draft.split(split_char) |
|
draft_paragraphs = [para for para in paragraphs if len(para)>5] |
|
|
|
return draft_paragraphs |
|
|
|
def split_draft_openai(question, answer, NUM_PARAGRAPHS = 4): |
|
split_prompt = f''' |
|
Split the answer of the question into multiple paragraphs with each paragraph containing a complete thought. |
|
The answer should be splited into less than {NUM_PARAGRAPHS} paragraphs. |
|
Use ## as splitting char to seperate the paragraphs. |
|
So you should output the answer with ## to split the paragraphs. |
|
**IMPORTANT** |
|
Just output the query directly. DO NOT add additional explanations or introducement in the answer unless you are asked to. |
|
''' |
|
openai_client = OpenAI(api_key = os.getenv('OPENAI_API_KEY')) |
|
splited_answer = openai_client.chat.completions.create( |
|
model="gpt-3.5-turbo", |
|
messages=[ |
|
{ |
|
"role": "system", |
|
"content": chatgpt_system_prompt |
|
}, |
|
{ |
|
"role": "user", |
|
"content": f"##Question: {question}\n\n##Response: {answer}\n\n##Instruction: {split_prompt}" |
|
} |
|
], |
|
temperature = 1.0 |
|
).choices[0].message.content |
|
split_draft_paragraphs = split_draft(splited_answer, split_char = '##') |
|
return split_draft_paragraphs |
|
|
|
def get_query(question, answer): |
|
query_prompt = ''' |
|
I want to verify the content correctness of the given question, especially the last sentences. |
|
Please summarize the content with the corresponding question. |
|
This summarization will be used as a query to search with Bing search engine. |
|
The query should be short but need to be specific to promise Bing can find related knowledge or pages. |
|
You can also use search syntax to make the query short and clear enough for the search engine to find relevant language data. |
|
Try to make the query as relevant as possible to the last few sentences in the content. |
|
**IMPORTANT** |
|
Just output the query directly. DO NOT add additional explanations or introducement in the answer unless you are asked to. |
|
''' |
|
|
|
openai_client = OpenAI(api_key = os.getenv('OPENAI_API_KEY')) |
|
query = openai_client.chat.completions.create( |
|
model="gpt-3.5-turbo", |
|
messages=[ |
|
{ |
|
"role": "system", |
|
"content": chatgpt_system_prompt |
|
}, |
|
{ |
|
"role": "user", |
|
"content": f"##Question: {question}\n\n##Content: {answer}\n\n##Instruction: {query_prompt}" |
|
} |
|
], |
|
temperature = 1.0 |
|
).choices[0].message.content |
|
return query |
|
|
|
def get_content(query): |
|
res = get_search(query, 1) |
|
if not res: |
|
print(">>> No good Google Search Result was found") |
|
return None |
|
search_results = res[0] |
|
link = search_results['link'] |
|
res = get_page_content(link) |
|
if not res: |
|
print(f">>> No content was found in {link}") |
|
return None |
|
retrieved_text = res |
|
trunked_texts = chunk_texts(retrieved_text, 1500) |
|
trunked_texts = [trunked_text.replace('\n', " ") for trunked_text in trunked_texts] |
|
return trunked_texts |
|
|
|
def get_revise_answer(question, answer, content): |
|
revise_prompt = ''' |
|
I want to revise the answer according to retrieved related text of the question in WIKI pages. |
|
You need to check whether the answer is correct. |
|
If you find some errors in the answer, revise the answer to make it better. |
|
If you find some necessary details are ignored, add it to make the answer more plausible according to the related text. |
|
If you find the answer is right and do not need to add more details, just output the original answer directly. |
|
**IMPORTANT** |
|
Try to keep the structure (multiple paragraphs with its subtitles) in the revised answer and make it more structual for understanding. |
|
Add more details from retrieved text to the answer. |
|
Split the paragraphs with \n\n characters. |
|
Just output the revised answer directly. DO NOT add additional explanations or annoucement in the revised answer unless you are asked to. |
|
''' |
|
|
|
openai_client = OpenAI(api_key = os.getenv('OPENAI_API_KEY')) |
|
revised_answer = openai_client.chat.completions.create( |
|
model="gpt-3.5-turbo", |
|
messages=[ |
|
{ |
|
"role": "system", |
|
"content": chatgpt_system_prompt |
|
}, |
|
{ |
|
"role": "user", |
|
"content": f"##Existing Text in Wiki Web: {content}\n\n##Question: {question}\n\n##Answer: {answer}\n\n##Instruction: {revise_prompt}" |
|
} |
|
], |
|
temperature = 1.0 |
|
).choices[0].message.content |
|
return revised_answer |
|
|
|
def get_reflect_answer(question, answer): |
|
reflect_prompt = ''' |
|
Give a title for the answer of the question. |
|
And add a subtitle to each paragraph in the answer and output the final answer using markdown format. |
|
This will make the answer to this question look more structured for better understanding. |
|
**IMPORTANT** |
|
Try to keep the structure (multiple paragraphs with its subtitles) in the response and make it more structual for understanding. |
|
Split the paragraphs with \n\n characters. |
|
Just output the revised answer directly. DO NOT add additional explanations or annoucement in the revised answer unless you are asked to. |
|
''' |
|
openai_client = OpenAI(api_key = os.getenv('OPENAI_API_KEY')) |
|
reflected_answer = openai_client.chat.completions.create( |
|
model="gpt-3.5-turbo", |
|
messages=[ |
|
{ |
|
"role": "system", |
|
"content": chatgpt_system_prompt |
|
}, |
|
{ |
|
"role": "user", |
|
"content": f"##Question:\n{question}\n\n##Answer:\n{answer}\n\n##Instruction:\n{reflect_prompt}" |
|
} |
|
], |
|
temperature = 1.0 |
|
).choices[0].message.content |
|
return reflected_answer |
|
|
|
def get_query_wrapper(q, question, answer): |
|
result = get_query(question, answer) |
|
q.put(result) |
|
|
|
def get_content_wrapper(q, query): |
|
result = get_content(query) |
|
q.put(result) |
|
|
|
def get_revise_answer_wrapper(q, question, answer, content): |
|
result = get_revise_answer(question, answer, content) |
|
q.put(result) |
|
|
|
def get_reflect_answer_wrapper(q, question, answer): |
|
result = get_reflect_answer(question, answer) |
|
q.put(result) |
|
|
|
from multiprocessing import Process, Queue |
|
def run_with_timeout(func, timeout, *args, **kwargs): |
|
q = Queue() |
|
|
|
p = Process(target=func, args=(q, *args), kwargs=kwargs) |
|
p.start() |
|
|
|
p.join(timeout) |
|
if p.is_alive(): |
|
print(f"{datetime.now()} [INFO] Function {str(func)} running timeout ({timeout}s), terminating...") |
|
p.terminate() |
|
p.join() |
|
result = None |
|
else: |
|
print(f"{datetime.now()} [INFO] Function {str(func)} executed successfully.") |
|
result = q.get() |
|
return result |
|
|
|
from difflib import unified_diff |
|
from IPython.display import display, HTML |
|
|
|
def generate_diff_html(text1, text2): |
|
diff = unified_diff(text1.splitlines(keepends=True), |
|
text2.splitlines(keepends=True), |
|
fromfile='text1', tofile='text2') |
|
|
|
diff_html = "" |
|
for line in diff: |
|
if line.startswith('+'): |
|
diff_html += f"<div style='color:green;'>{line.rstrip()}</div>" |
|
elif line.startswith('-'): |
|
diff_html += f"<div style='color:red;'>{line.rstrip()}</div>" |
|
elif line.startswith('@'): |
|
diff_html += f"<div style='color:blue;'>{line.rstrip()}</div>" |
|
else: |
|
diff_html += f"{line.rstrip()}<br>" |
|
return diff_html |
|
|
|
newline_char = '\n' |
|
|
|
def rat(question): |
|
print(f"{datetime.now()} [INFO] Generating draft...") |
|
draft = get_draft(question) |
|
print(f"{datetime.now()} [INFO] Return draft.") |
|
|
|
|
|
|
|
|
|
print(f"{datetime.now()} [INFO] Processing draft ...") |
|
|
|
draft_paragraphs = split_draft_openai(question, draft) |
|
print(f"{datetime.now()} [INFO] Draft is splitted into {len(draft_paragraphs)} sections.") |
|
answer = "" |
|
for i, p in enumerate(draft_paragraphs): |
|
|
|
print(f"{datetime.now()} [INFO] Revising {i+1}/{len(draft_paragraphs)} sections ...") |
|
answer = answer + '\n\n' + p |
|
|
|
|
|
|
|
print(f"{datetime.now()} [INFO] Generating query ...") |
|
res = run_with_timeout(get_query_wrapper, 30, question, answer) |
|
if not res: |
|
print(f"{datetime.now()} [INFO] Generating query timeout, skipping...") |
|
continue |
|
else: |
|
query = res |
|
print(f">>> {i}/{len(draft_paragraphs)} Query: {query.replace(newline_char, ' ')}") |
|
|
|
print(f"{datetime.now()} [INFO] Crawling network pages ...") |
|
|
|
res = run_with_timeout(get_content_wrapper, 30, query) |
|
if not res: |
|
print(f"{datetime.now()} [INFO] Parsing network pages timeout, skipping ...") |
|
continue |
|
else: |
|
content = res |
|
|
|
LIMIT = 2 |
|
for j, c in enumerate(content): |
|
if j >= LIMIT: |
|
break |
|
print(f"{datetime.now()} [INFO] Revising answers with retrieved network pages...[{j}/{min(len(content),LIMIT)}]") |
|
|
|
res = run_with_timeout(get_revise_answer_wrapper, 30, question, answer, c) |
|
if not res: |
|
print(f"{datetime.now()} [INFO] Revising answers timeout, skipping ...") |
|
continue |
|
else: |
|
diff_html = generate_diff_html(answer, res) |
|
display(HTML(diff_html)) |
|
answer = res |
|
print(f"{datetime.now()} [INFO] Answer revised [{j}/{min(len(content),3)}]") |
|
|
|
|
|
res = run_with_timeout(get_reflect_answer_wrapper, 30, question, answer) |
|
if not res: |
|
print(f"{datetime.now()} [INFO] Reflecting answers timeout, skipping next steps...") |
|
else: |
|
answer = res |
|
return draft, answer |
|
|
|
page_title = "RAT: Retrieval Augmented Thoughts Elicit Context-Aware Reasoning in Long-Horizon Generation" |
|
page_md = """ |
|
# RAT: Retrieval Augmented Thoughts Elicit Context-Aware Reasoning in Long-Horizon Generation |
|
|
|
We explore how iterative revising a chain of thoughts with the help of information retrieval significantly improves large language models' reasoning and generation ability in long-horizon generation tasks, while hugely mitigating hallucination. In particular, the proposed method — retrieval-augmented thoughts (RAT) — revises each thought step one by one with retrieved information relevant to the task query, the current and the past thought steps, after the initial zero-shot CoT is generated. |
|
|
|
Applying RAT to various base models substantially improves their performances on various long-horizon generation tasks; on average of relatively increasing rating scores by 13.63% on code generation, 16.96% on mathematical reasoning, 19.2% on creative writing, and 42.78% on embodied task planning. |
|
|
|
Feel free to try our demo! |
|
|
|
""" |
|
|
|
def clear_func(): |
|
return "", "", "" |
|
|
|
def set_openai_api_key(api_key): |
|
if api_key and api_key.startswith("sk-") and len(api_key) > 50: |
|
os.environ["OPENAI_API_KEY"] = api_key |
|
|
|
with gr.Blocks(title = page_title) as demo: |
|
|
|
gr.Markdown(page_md) |
|
|
|
with gr.Row(): |
|
chatgpt_box = gr.Textbox( |
|
label = "ChatGPT", |
|
placeholder = "Response from ChatGPT with zero-shot chain-of-thought.", |
|
elem_id = "chatgpt" |
|
) |
|
|
|
with gr.Row(): |
|
stream_box = gr.Textbox( |
|
label = "Streaming", |
|
placeholder = "Interactive response with RAT...", |
|
elem_id = "stream", |
|
lines = 10, |
|
visible = False |
|
) |
|
|
|
with gr.Row(): |
|
rat_box = gr.Textbox( |
|
label = "RAT", |
|
placeholder = "Final response with RAT ...", |
|
elem_id = "rat", |
|
lines = 6 |
|
) |
|
|
|
with gr.Column(elem_id="instruction_row"): |
|
with gr.Row(): |
|
instruction_box = gr.Textbox( |
|
label = "instruction", |
|
placeholder = "Enter your instruction here", |
|
lines = 2, |
|
elem_id="instruction", |
|
interactive=True, |
|
visible=True |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Row(): |
|
submit_btn = gr.Button( |
|
value="submit", visible=True, interactive=True |
|
) |
|
clear_btn = gr.Button( |
|
value="clear", visible=True, interactive=True |
|
) |
|
regenerate_btn = gr.Button( |
|
value="regenerate", visible=True, interactive=True |
|
) |
|
|
|
submit_btn.click( |
|
fn = rat, |
|
inputs = [instruction_box], |
|
outputs = [chatgpt_box, rat_box] |
|
) |
|
|
|
clear_btn.click( |
|
fn = clear_func, |
|
inputs = [], |
|
outputs = [instruction_box, chatgpt_box, rat_box] |
|
) |
|
|
|
regenerate_btn.click( |
|
fn = rat, |
|
inputs = [instruction_box], |
|
outputs = [chatgpt_box, rat_box] |
|
) |
|
|
|
examples = gr.Examples( |
|
examples=[ |
|
|
|
|
|
"Write a survey of retrieval-augmented generation in Large Language Models.", |
|
"Introduce Jin-Yong's life and his works.", |
|
"Summarize the American Civil War according to the timeline.", |
|
"Describe the life and achievements of Marie Curie" |
|
], |
|
inputs=[instruction_box] |
|
) |
|
|
|
demo.launch(server_name="0.0.0.0", debug=True) |