Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 12 additions & 191 deletions examples/Search_Wikipedia_using_ReAct.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,7 @@
},
"outputs": [],
"source": [
"%pip install -q \"google-generativeai>=0.7.2\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "7oZwkgQpfrLl"
},
"outputs": [],
"source": [
"%pip install -q wikipedia"
"%pip install -U -q wikipedia 'google-genai>=1.0.0'"
]
},
{
Expand All @@ -151,13 +140,7 @@
},
"outputs": [],
"source": [
"import re\n",
"import os\n",
"\n",
"import wikipedia\n",
"from wikipedia.exceptions import DisambiguationError, PageError\n",
"\n",
"import google.generativeai as genai"
"import re\nimport os\n\nimport wikipedia\nfrom wikipedia.exceptions import DisambiguationError, PageError\n\nimport google.genai as genai"
]
},
{
Expand All @@ -178,8 +161,9 @@
"outputs": [],
"source": [
"from google.colab import userdata\n",
"GOOGLE_API_KEY=userdata.get('GOOGLE_API_KEY')\n",
"genai.configure(api_key=GOOGLE_API_KEY)"
"GOOGLE_API_KEY = userdata.get('GOOGLE_API_KEY')\n",
"\n",
"client = genai.Client(api_key=GOOGLE_API_KEY)"
]
},
{
Expand Down Expand Up @@ -496,43 +480,7 @@
},
"outputs": [],
"source": [
"class ReAct:\n",
" def __init__(self, model: str, ReAct_prompt: str | os.PathLike):\n",
" \"\"\"Prepares Gemini to follow a `Few-shot ReAct prompt` by imitating\n",
" `function calling` technique to generate both reasoning traces and\n",
" task-specific actions in an interleaved manner.\n",
"\n",
" Args:\n",
" model: name to the model.\n",
" ReAct_prompt: ReAct prompt OR path to the ReAct prompt.\n",
" \"\"\"\n",
" self.model = genai.GenerativeModel(model)\n",
" self.chat = self.model.start_chat(history=[])\n",
" self.should_continue_prompting = True\n",
" self._search_history: list[str] = []\n",
" self._search_urls: list[str] = []\n",
"\n",
" try:\n",
" # try to read the file\n",
" with open(ReAct_prompt, 'r') as f:\n",
" self._prompt = f.read()\n",
" except FileNotFoundError:\n",
" # assume that the parameter represents prompt itself rather than path to the prompt file.\n",
" self._prompt = ReAct_prompt\n",
"\n",
" @property\n",
" def prompt(self):\n",
" return self._prompt\n",
"\n",
" @classmethod\n",
" def add_method(cls, func):\n",
" setattr(cls, func.__name__, func)\n",
"\n",
" @staticmethod\n",
" def clean(text: str):\n",
" \"\"\"Helper function for responses.\"\"\"\n",
" text = text.replace(\"\\n\", \" \")\n",
" return text"
"class ReAct:\n def __init__(self, model: str, ReAct_prompt: str | os.PathLike):\n \"\"\"Prepares Gemini to follow a `Few-shot ReAct prompt` by imitating\n `function calling` technique to generate both reasoning traces and\n task-specific actions in an interleaved manner.\n\n Args:\n model: name to the model.\n ReAct_prompt: ReAct prompt OR path to the ReAct prompt.\n \"\"\"\n self.model_name = model\n self.client = client\n self.chat_history = []\n self.should_continue_prompting = True\n self._search_history: list[str] = []\n self._search_urls: list[str] = []\n\n try:\n # try to read the file\n with open(ReAct_prompt, 'r') as f:\n self._prompt = f.read()\n except FileNotFoundError:\n # assume that the parameter represents prompt itself rather than path to the prompt file.\n self._prompt = ReAct_prompt\n\n @property\n def prompt(self):\n return self._prompt\n\n @classmethod\n def add_method(cls, func):\n setattr(cls, func.__name__, func)\n\n @staticmethod\n def clean(text: str):\n \"\"\"Helper function for responses.\"\"\"\n text = text.replace(\"\\n\", \" \")\n return text"
]
},
{
Expand Down Expand Up @@ -583,43 +531,7 @@
},
"outputs": [],
"source": [
"@ReAct.add_method\n",
"def search(self, query: str):\n",
" \"\"\"Perfoms search on `query` via Wikipedia api and returns its summary.\n",
"\n",
" Args:\n",
" query: Search parameter to query the Wikipedia API with.\n",
"\n",
" Returns:\n",
" observation: Summary of Wikipedia search for `query` if found else\n",
" similar search results.\n",
" \"\"\"\n",
" observation = None\n",
" query = query.strip()\n",
" try:\n",
" # try to get the summary for requested `query` from the Wikipedia\n",
" observation = wikipedia.summary(query, sentences=4, auto_suggest=False)\n",
" wiki_url = wikipedia.page(query, auto_suggest=False).url\n",
" observation = self.clean(observation)\n",
"\n",
" # if successful, return the first 2-3 sentences from the summary as model's context\n",
" observation = self.model.generate_content(f'Retun the first 2 or 3 \\\n",
" sentences from the following text: {observation}')\n",
" observation = observation.text\n",
"\n",
" # keep track of the model's search history\n",
" self._search_history.append(query)\n",
" self._search_urls.append(wiki_url)\n",
" print(f\"Information Source: {wiki_url}\")\n",
"\n",
" # if the page is ambiguous/does not exist, return similar search phrases for model's context\n",
" except (DisambiguationError, PageError) as e:\n",
" observation = f'Could not find [\"{query}\"].'\n",
" # get a list of similar search topics\n",
" search_results = wikipedia.search(query)\n",
" observation += f' Similar: {search_results}. You should search for one of those instead.'\n",
"\n",
" return observation"
"@ReAct.add_method\ndef search(self, query: str):\n \"\"\"Perfoms search on `query` via Wikipedia api and returns its summary.\n\n Args:\n query: Search parameter to query the Wikipedia API with.\n\n Returns:\n observation: Summary of Wikipedia search for `query` if found else\n similar search results.\n \"\"\"\n observation = None\n query = query.strip()\n try:\n # Fetch the page once to get both summary and URL, avoiding duplicate requests\n self._current_page = wikipedia.page(query, auto_suggest=False)\n wiki_url = self._current_page.url\n observation = self.clean(self._current_page.summary)\n\n # if successful, return the first 2-3 sentences from the summary as model's context\n observation = self.client.models.generate_content(\n model=self.model_name,\n contents=f'Return the first 2 or 3 sentences from the following text: {observation}'\n ).text\n\n # keep track of the model's search history\n self._search_history.append(query)\n self._search_urls.append(wiki_url)\n print(f\"Information Source: {wiki_url}\")\n\n # if the page is ambiguous/does not exist, return similar search phrases for model's context\n except (DisambiguationError, PageError) as e:\n observation = f'Could not find [\"{query}\"].'\n # get a list of similar search topics\n search_results = wikipedia.search(query)\n observation += f' Similar: {search_results}. You should search for one of those instead.'\n\n return observation\n"
]
},
{
Expand All @@ -640,32 +552,7 @@
},
"outputs": [],
"source": [
"@ReAct.add_method\n",
"def lookup(self, phrase: str, context_length=200):\n",
" \"\"\"Searches for the `phrase` in the lastest Wikipedia search page\n",
" and returns number of sentences which is controlled by the\n",
" `context_length` parameter.\n",
"\n",
" Args:\n",
" phrase: Lookup phrase to search for within a page. Generally\n",
" attributes to some specification of any topic.\n",
"\n",
" context_length: Number of words to consider\n",
" while looking for the answer.\n",
"\n",
" Returns:\n",
" result: Context related to the `phrase` within the page.\n",
" \"\"\"\n",
" # get the last searched Wikipedia page and find `phrase` in it.\n",
" page = wikipedia.page(self._search_history[-1], auto_suggest=False)\n",
" page = page.content\n",
" page = self.clean(page)\n",
" start_index = page.find(phrase)\n",
"\n",
" # extract sentences considering the context length defined\n",
" result = page[max(0, start_index - context_length):start_index+len(phrase)+context_length]\n",
" print(f\"Information Source: {self._search_urls[-1]}\")\n",
" return result"
"@ReAct.add_method\ndef lookup(self, phrase: str, context_length=200):\n \"\"\"Searches for the `phrase` in the lastest Wikipedia search page\n and returns number of sentences which is controlled by the\n `context_length` parameter.\n\n Args:\n phrase: Lookup phrase to search for within a page. Generally\n attributes to some specification of any topic.\n\n context_length: Number of words to consider\n while looking for the answer.\n\n Returns:\n result: Context related to the `phrase` within the page.\n \"\"\"\n # Reuse the cached page from the last search() call to avoid redundant network requests\n page = self.clean(self._current_page.content)\n start_index = page.find(phrase)\n\n # extract sentences considering the context length defined\n result = page[max(0, start_index - context_length):start_index+len(phrase)+context_length]\n print(f\"Information Source: {self._search_urls[-1]}\")\n return result\n"
]
},
{
Expand Down Expand Up @@ -710,9 +597,7 @@
"id": "0VnX9zpBcdA0"
},
"source": [
"Now that you are all set with function definitions, the next step is to instruct the model to interrupt its execution upon encountering any of the action tokens. You will make use of the `stop_sequences` parameter from [`genai.GenerativeModel.GenerationConfig`](https://ai.google.dev/api/python/google/generativeai/GenerationConfig) class to instruct the model when to stop. Upon encountering an action token, the pipeline will simply extract what specific token from the `stop_sequences` argument terminated the model's execution, and then call the appropriate **tool** (function).\n",
"\n",
"The function's response will be added to model's chat history for continuing the context link."
"Now that you are all set with function definitions, the next step is to instruct the model to interrupt its execution upon encountering any of the action tokens. You will make use of the `stop_sequences` parameter from [`genai.types.GenerateContentConfig`](https://ai.google.dev/api/python/google/genai/types#GenerateContentConfig) class to instruct the model when to stop. Upon encountering an action token, the pipeline will simply extract what specific token from the `stop_sequences` argument terminated the model's execution, and then call the appropriate **tool** (function).\n\nThe function's response will be added to model's chat history for continuing the context link."
]
},
{
Expand All @@ -723,69 +608,7 @@
},
"outputs": [],
"source": [
"@ReAct.add_method\n",
"def __call__(self, user_question, max_calls: int=8, **generation_kwargs):\n",
" \"\"\"Starts multi-turn conversation with the chat models with function calling\n",
"\n",
" Args:\n",
" max_calls: max calls made to the model to get the final answer.\n",
"\n",
" generation_kwargs: Same as genai.GenerativeModel.GenerationConfig\n",
" candidate_count: (int | None) = None,\n",
" stop_sequences: (Iterable[str] | None) = None,\n",
" max_output_tokens: (int | None) = None,\n",
" temperature: (float | None) = None,\n",
" top_p: (float | None) = None,\n",
" top_k: (int | None) = None\n",
"\n",
" Raises:\n",
" AssertionError: if max_calls is not between 1 and 8\n",
" \"\"\"\n",
"\n",
" # hyperparameter fine-tuned according to the paper\n",
" assert 0 < max_calls <= 8, \"max_calls must be between 1 and 8\"\n",
"\n",
" if len(self.chat.history) == 0:\n",
" model_prompt = self.prompt.format(question=user_question)\n",
" else:\n",
" model_prompt = user_question\n",
"\n",
" # stop_sequences for the model to immitate function calling\n",
" callable_entities = ['</search>', '</lookup>', '</finish>']\n",
"\n",
" generation_kwargs.update({'stop_sequences': callable_entities})\n",
"\n",
" self.should_continue_prompting = True\n",
" for idx in range(max_calls):\n",
"\n",
" self.response = self.chat.send_message(content=[model_prompt],\n",
" generation_config=generation_kwargs, stream=False)\n",
"\n",
" for chunk in self.response:\n",
" print(chunk.text, end=' ')\n",
"\n",
" response_cmd = self.chat.history[-1].parts[-1].text\n",
"\n",
" try:\n",
" # regex to extract <function name writen in between angular brackets>\n",
" cmd = re.findall(r'<(.*)>', response_cmd)[-1]\n",
" print(f'</{cmd}>')\n",
" # regex to extract param\n",
" query = response_cmd.split(f'<{cmd}>')[-1].strip()\n",
" # call to appropriate function\n",
" observation = self.__getattribute__(cmd)(query)\n",
"\n",
" if not self.should_continue_prompting:\n",
" break\n",
"\n",
" stream_message = f\"\\nObservation {idx + 1}\\n{observation}\"\n",
" print(stream_message)\n",
" # send function's output as user's response\n",
" model_prompt = f\"<{cmd}>{query}</{cmd}>'s Output: {stream_message}\"\n",
"\n",
" except (IndexError, AttributeError) as e:\n",
" model_prompt = \"Please try to generate thought-action-observation traces \\\n",
" as instructed by the prompt.\""
"@ReAct.add_method\ndef __call__(self, user_question, max_calls: int=8, **generation_kwargs):\n \"\"\"Starts multi-turn conversation with the chat models with function calling\n\n Args:\n max_calls: max calls made to the model to get the final answer.\n\n generation_kwargs: Same as genai.types.GenerateContentConfig fields\n candidate_count: (int | None) = None,\n stop_sequences: (Iterable[str] | None) = None,\n max_output_tokens: (int | None) = None,\n temperature: (float | None) = None,\n top_p: (float | None) = None,\n top_k: (int | None) = None\n\n Raises:\n AssertionError: if max_calls is not between 1 and 8\n \"\"\"\n\n # hyperparameter fine-tuned according to the paper\n assert 0 < max_calls <= 8, \"max_calls must be between 1 and 8\"\n\n if len(self.chat_history) == 0:\n model_prompt = self.prompt.format(question=user_question)\n else:\n model_prompt = user_question\n\n # stop_sequences for the model to immitate function calling\n callable_entities = ['</search>', '</lookup>', '</finish>']\n\n generation_kwargs.update({'stop_sequences': callable_entities})\n\n self.should_continue_prompting = True\n for idx in range(max_calls):\n\n # Append user message to history and call the API\n self.chat_history.append({'role': 'user', 'parts': [{'text': model_prompt}]})\n self.response = self.client.models.generate_content(\n model=self.model_name,\n contents=self.chat_history,\n config=genai.types.GenerateContentConfig(**generation_kwargs)\n )\n response_cmd = self.response.text\n print(response_cmd, end=' ')\n # Append model reply to history\n self.chat_history.append({'role': 'model', 'parts': [{'text': response_cmd}]})\n\n try:\n # regex to extract <function name writen in between angular brackets>\n cmd = re.findall(r'<(.*)>', response_cmd)[-1]\n print(f'</{cmd}>')\n # regex to extract param\n query = response_cmd.split(f'<{cmd}>')[-1].strip()\n # call to appropriate function\n observation = self.__getattribute__(cmd)(query)\n\n if not self.should_continue_prompting:\n break\n\n stream_message = f\"\\nObservation {idx + 1}\\n{observation}\"\n print(stream_message)\n # send function's output as user's response\n model_prompt = f\"<{cmd}>{query}</{cmd}>'s Output: {stream_message}\"\n\n except (IndexError, AttributeError) as e:\n model_prompt = \"Please try to generate thought-action-observation traces \\\n as instructed by the prompt.\""
]
},
{
Expand Down Expand Up @@ -854,9 +677,7 @@
}
],
"source": [
"gemini_ReAct_chat = ReAct(model='gemini-3.5-flash', ReAct_prompt='model_instructions.txt')\n",
"# Note: try different combinations of generational_config parameters for variational results\n",
"gemini_ReAct_chat(\"What are the total of ages of the main trio from the new Percy Jackson and the Olympians TV series in real life?\", temperature=0.2)"
"MODEL_ID = \"gemini-2.5-flash\" # @param [\"gemini-2.0-flash\", \"gemini-2.5-flash\", \"gemini-2.5-pro\"] {\"allow-input\": true, isTemplate: true}\n\ngemini_ReAct_chat = ReAct(model=MODEL_ID, ReAct_prompt='model_instructions.txt')\n# Note: try different combinations of generation_config parameters for variational results\ngemini_ReAct_chat(\"What are the total of ages of the main trio from the new Percy Jackson and the Olympians TV series in real life?\", temperature=0.2)\n"
]
},
{
Expand Down Expand Up @@ -887,7 +708,7 @@
}
],
"source": [
"gemini_ReAct_chat.model.generate_content(\"What is the total of ages of the main trio from the new Percy Jackson and the Olympians TV series in real life?\").text"
"gemini_ReAct_chat.client.models.generate_content(\n model=MODEL_ID,\n contents=\"What is the total of ages of the main trio from the new Percy Jackson and the Olympians TV series in real life?\"\n).text\n"
]
},
{
Expand Down
Loading
Loading