diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0942d993b686701e3a4d538abe3fb4c659f53b1a..ceabd9053dd899de3d7ede9cb3c41f361c984670 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -333,7 +333,7 @@ make test For changes that involve entirely new features, it may be worth adding an example Jupyter notebook to showcase this feature. -Example notebooks can be found in this folder: <https://github.com/jerryjliu/llama_index/tree/main/examples>. +Example notebooks can be found in this folder: <https://github.com/run-llama/llama_index/tree/main/docs/examples>. ### Creating a pull request diff --git a/docs/examples/agent/react_agent_with_initial_context.ipynb b/docs/examples/agent/react_agent_with_initial_context.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..b5ff7e2bc97a94a8dd7128691258db920c1ca80a --- /dev/null +++ b/docs/examples/agent/react_agent_with_initial_context.ipynb @@ -0,0 +1,394 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "6b0186a4", + "metadata": {}, + "source": [ + "<a href=\"https://colab.research.google.com/github/run-llama/llama_index/blob/main/docs/examples/agent/react_agent_with_query_engine.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" + ] + }, + { + "cell_type": "markdown", + "id": "b50c4af8-fec3-4396-860a-1322089d76cb", + "metadata": {}, + "source": [ + "# ReAct Agent with initial context\n", + "\n", + "In this section, we show how to setup an agent powered by the ReAct loop with initial context to aid in tool selection, persona(s), and constraints.\n", + "\n", + "The agent has access to two \"tools\": one to query the 2021 Lyft 10-K and the other to query the 2021 Uber 10-K.\n", + "\n", + "We try this out with gpt-3.5-turbo.\n", + "\n", + "Note that you can plug in any LLM that exposes a text completion endpoint." + ] + }, + { + "cell_type": "markdown", + "id": "db402a8b-90d6-4e1d-8df6-347c54624f26", + "metadata": {}, + "source": [ + "## Build Query Engine Tools" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02160804-64a2-4ef3-8a0d-8c16b06fd205", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index import (\n", + " SimpleDirectoryReader,\n", + " VectorStoreIndex,\n", + " StorageContext,\n", + " load_index_from_storage,\n", + ")\n", + "\n", + "from llama_index.tools import QueryEngineTool, ToolMetadata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91618236-54d3-4783-86b7-7b7554efeed1", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " storage_context = StorageContext.from_defaults(\n", + " persist_dir=\"./storage/lyft\"\n", + " )\n", + " lyft_index = load_index_from_storage(storage_context)\n", + "\n", + " storage_context = StorageContext.from_defaults(\n", + " persist_dir=\"./storage/uber\"\n", + " )\n", + " uber_index = load_index_from_storage(storage_context)\n", + "\n", + " index_loaded = True\n", + "except:\n", + " index_loaded = False" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "6a79cbc9", + "metadata": {}, + "source": [ + "Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36d80144", + "metadata": {}, + "outputs": [], + "source": [ + "!mkdir -p 'data/10k/'\n", + "!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/10k/uber_2021.pdf' -O 'data/10k/uber_2021.pdf'\n", + "!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/10k/lyft_2021.pdf' -O 'data/10k/lyft_2021.pdf'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3d0bb8c-16c8-4946-a9d8-59528cf3952a", + "metadata": {}, + "outputs": [], + "source": [ + "if not index_loaded:\n", + " # load data\n", + " lyft_docs = SimpleDirectoryReader(\n", + " input_files=[\"./data/10k/lyft_2021.pdf\"]\n", + " ).load_data()\n", + " uber_docs = SimpleDirectoryReader(\n", + " input_files=[\"./data/10k/uber_2021.pdf\"]\n", + " ).load_data()\n", + "\n", + " # build index\n", + " lyft_index = VectorStoreIndex.from_documents(lyft_docs)\n", + " uber_index = VectorStoreIndex.from_documents(uber_docs)\n", + "\n", + " # persist index\n", + " lyft_index.storage_context.persist(persist_dir=\"./storage/lyft\")\n", + " uber_index.storage_context.persist(persist_dir=\"./storage/uber\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31892898-a2dc-43c8-812a-3442feb2108d", + "metadata": {}, + "outputs": [], + "source": [ + "lyft_engine = lyft_index.as_query_engine(similarity_top_k=3)\n", + "uber_engine = uber_index.as_query_engine(similarity_top_k=3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9f3158a-7647-4442-8de1-4db80723b4d2", + "metadata": {}, + "outputs": [], + "source": [ + "query_engine_tools = [\n", + " QueryEngineTool(\n", + " query_engine=lyft_engine,\n", + " metadata=ToolMetadata(\n", + " name=\"lyft_10k\",\n", + " description=(\n", + " \"Provides information about Lyft financials for year 2021. \"\n", + " \"Use a detailed plain text question as input to the tool.\"\n", + " ),\n", + " ),\n", + " ),\n", + " QueryEngineTool(\n", + " query_engine=uber_engine,\n", + " metadata=ToolMetadata(\n", + " name=\"uber_10k\",\n", + " description=(\n", + " \"Provides information about Uber financials for year 2021. \"\n", + " \"Use a detailed plain text question as input to the tool.\"\n", + " ),\n", + " ),\n", + " ),\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "275c01b1-8dce-4216-9203-1e961b7fc313", + "metadata": {}, + "source": [ + "## Setup ReAct Agent w/ Context\n", + "\n", + "Here we setup two ReAct agents: one powered by standard gpt-3.5-turbo, and the other powered by gpt-3.5-turbo-instruct." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32f71a46-bdf6-4365-b1f1-e23a0d913a3d", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.agent import ReActAgent\n", + "from llama_index.llms import OpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9846b70b", + "metadata": {}, + "outputs": [], + "source": [ + "context = \"\"\"\\\n", + " You are a stock market sorcerer who is an expert on the companies Lyft and Uber.\\\n", + " You will answer questions about Uber and Lyft as in the persona of a sorcerer \\\n", + " and veteran stock market investor.\n", + " \"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ded93297-fee8-4329-bf37-cf77e87621ae", + "metadata": {}, + "outputs": [], + "source": [ + "llm = OpenAI(model=\"gpt-3.5-turbo-0613\")\n", + "agent = ReActAgent.from_tools(\n", + " query_engine_tools,\n", + " llm=llm,\n", + " verbose=True,\n", + " context=context, # the only difference from the previous example (react_agent_with_query_engine.ipynb)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70a82471-9226-42ad-bd8a-aebde3530d95", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1;3;38;5;200mThought: To find Lyft's revenue growth in 2021, I can use the Lyft 10-K tool.\n", + "Action: lyft_10k\n", + "Action Input: {'input': \"What was Lyft's revenue growth in 2021?\"}\n", + "\u001b[0m\u001b[1;3;34mObservation: Lyft's revenue growth in 2021 was 36%.\n", + "\u001b[0m\u001b[1;3;38;5;200mThought: I have the information about Lyft's revenue growth in 2021.\n", + "Answer: Lyft's revenue growth in 2021 was 36%.\n", + "\u001b[0mLyft's revenue growth in 2021 was 36%.\n" + ] + } + ], + "source": [ + "response = agent.chat(\"What was Lyft's revenue growth in 2021?\")\n", + "print(str(response))" + ] + }, + { + "cell_type": "markdown", + "id": "6fa830f5-11a5-4369-91b2-29695537debd", + "metadata": {}, + "source": [ + "## Run Some Example Queries\n", + "\n", + "We run some example queries using the agent, showcasing some of the agent's abilities to do chain-of-thought-reasoning and tool use to synthesize the right answer.\n", + "\n", + "We also show queries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0873463d-4790-4c17-bfe9-2ece610fe4b3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1;3;38;5;200mThought: I need to compare the revenue growth of Uber and Lyft in 2021 to provide an analysis.\n", + "Action: lyft_10k\n", + "Action Input: {'input': \"What was Lyft's revenue growth in 2021?\"}\n", + "\u001b[0m\u001b[1;3;34mObservation: Lyft's revenue growth in 2021 was 36%.\n", + "\u001b[0m\u001b[1;3;38;5;200mThought: I have obtained the information about Lyft's revenue growth in 2021. Now I need to gather information about Uber's revenue growth in 2021.\n", + "Action: uber_10k\n", + "Action Input: {'input': \"What was Uber's revenue growth in 2021?\"}\n", + "\u001b[0m\u001b[1;3;34mObservation: Uber's revenue growth in 2021 was 57%.\n", + "\u001b[0m\u001b[1;3;38;5;200mThought: I have obtained the information about Uber's revenue growth in 2021 as well. Now I can compare and contrast the revenue growth of Uber and Lyft in 2021 and provide an analysis.\n", + "Answer: In 2021, Lyft's revenue growth was 36% while Uber's revenue growth was 57%. This indicates that Uber experienced a higher revenue growth compared to Lyft. The higher revenue growth of Uber could be attributed to various factors such as its larger market presence, expansion into new markets, and diversification of services. However, it's important to note that revenue growth alone does not provide a complete picture of a company's financial performance. Other factors such as profitability, market share, and operating expenses should also be considered for a comprehensive analysis.\n", + "\u001b[0mIn 2021, Lyft's revenue growth was 36% while Uber's revenue growth was 57%. This indicates that Uber experienced a higher revenue growth compared to Lyft. The higher revenue growth of Uber could be attributed to various factors such as its larger market presence, expansion into new markets, and diversification of services. However, it's important to note that revenue growth alone does not provide a complete picture of a company's financial performance. Other factors such as profitability, market share, and operating expenses should also be considered for a comprehensive analysis.\n" + ] + } + ], + "source": [ + "response = agent.chat(\n", + " \"Compare and contrast the revenue growth of Uber and Lyft in 2021, then\"\n", + " \" give an analysis\"\n", + ")\n", + "print(str(response))" + ] + }, + { + "cell_type": "markdown", + "id": "9bf0cb61-22c6-486c-8970-5d5c1767f3fb", + "metadata": {}, + "source": [ + "**Async execution**: Here we try another query with async execution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb63492a-836c-42da-94a4-0b22cccbc3e0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1;3;38;5;200mThought: I need to use a tool to help me answer the question.\n", + "Action: uber_10k\n", + "Action Input: {'input': 'Please provide the risks of Uber in 2021.'}\n", + "\u001b[0m\u001b[1;3;34mObservation: The risks faced by Uber in 2021 include the adverse impact of the COVID-19 pandemic on its financial results and operations, including reduced demand for its Mobility offering and potential supply constraints. The pandemic has also led to market and economic conditions globally that have affected drivers, merchants, consumers, and business partners, as well as Uber's business, results of operations, financial position, and cash flows. Additionally, there are uncertainties related to the severity and duration of the disease, future waves or resurgences of the virus, the administration and efficacy of vaccines, and the impact of governmental orders and regulations. Uber is also involved in legal proceedings regarding the classification of drivers as independent contractors, which could have significant implications for its business. Furthermore, Uber's ability to penetrate suburban and rural areas and operate in key metropolitan areas, as well as its operations at airports, are subject to regulatory changes and restrictions that could limit its total addressable market. Finally, the successful implementation and performance of autonomous vehicle technologies on Uber's platform, as well as its ability to compete with other companies in this space, are also potential risks.\n", + "\u001b[0m\u001b[1;3;38;5;200mThought: I need to use a tool to help me answer the question.\n", + "Action: lyft_10k\n", + "Action Input: {'input': 'Please provide the risks of Lyft in 2021.'}\n", + "\u001b[0m\u001b[1;3;34mObservation: Lyft faces several risks in 2021. These risks include the difficulty in evaluating future prospects and the challenges the company may encounter due to its limited operating history and evolving business. Other risks include the ability to forecast revenue and manage expenses, attract and retain qualified drivers and riders, comply with laws and regulations, manage the impact of the COVID-19 pandemic on the business, plan and manage capital expenditures, develop and maintain assets, anticipate and respond to macroeconomic changes, maintain and enhance the company's reputation and brand, effectively manage growth and operations, expand geographic reach, hire and retain talented employees, develop new platform features and offerings, and right-size the real estate portfolio. Additionally, Lyft faces risks related to general economic factors, operational factors, competition, fluctuations in financial performance, and the growth and development of the ridesharing and other markets.\n", + "\u001b[0m\u001b[1;3;38;5;200mThought: (Implicit) I can answer without any more tools!\n", + "Answer: Analysis: Both Uber and Lyft face a range of risks in 2021. The COVID-19 pandemic has had a significant impact on both companies, leading to reduced demand and potential supply constraints. Both companies also face uncertainties related to the severity and duration of the pandemic, as well as the impact of governmental regulations and orders. \n", + "\n", + "Legal proceedings regarding the classification of drivers as independent contractors pose a risk for both Uber and Lyft, as the outcome could have significant implications for their business models. Additionally, regulatory changes and restrictions could limit the total addressable market for both companies, particularly in suburban and rural areas and at airports.\n", + "\n", + "Lyft specifically faces risks related to its limited operating history and evolving business, as well as challenges in forecasting revenue, managing expenses, and attracting and retaining drivers and riders. On the other hand, Uber faces risks related to its ability to effectively implement autonomous vehicle technologies and compete in that space.\n", + "\n", + "Overall, both Uber and Lyft operate in a dynamic and competitive market, and their financial performance and growth are subject to various risks and uncertainties. It is important for investors and stakeholders to carefully consider these risks when evaluating the prospects of each company.\n", + "\u001b[0mAnalysis: Both Uber and Lyft face a range of risks in 2021. The COVID-19 pandemic has had a significant impact on both companies, leading to reduced demand and potential supply constraints. Both companies also face uncertainties related to the severity and duration of the pandemic, as well as the impact of governmental regulations and orders. \n", + "\n", + "Legal proceedings regarding the classification of drivers as independent contractors pose a risk for both Uber and Lyft, as the outcome could have significant implications for their business models. Additionally, regulatory changes and restrictions could limit the total addressable market for both companies, particularly in suburban and rural areas and at airports.\n", + "\n", + "Lyft specifically faces risks related to its limited operating history and evolving business, as well as challenges in forecasting revenue, managing expenses, and attracting and retaining drivers and riders. On the other hand, Uber faces risks related to its ability to effectively implement autonomous vehicle technologies and compete in that space.\n", + "\n", + "Overall, both Uber and Lyft operate in a dynamic and competitive market, and their financial performance and growth are subject to various risks and uncertainties. It is important for investors and stakeholders to carefully consider these risks when evaluating the prospects of each company.\n" + ] + } + ], + "source": [ + "# Try another query with async execution\n", + "\n", + "import nest_asyncio\n", + "\n", + "nest_asyncio.apply()\n", + "\n", + "response = await agent.achat(\n", + " \"Compare and contrast the risks of Uber and Lyft in 2021, then give an\"\n", + " \" analysis\"\n", + ")\n", + "print(str(response))" + ] + }, + { + "cell_type": "markdown", + "id": "cc041786", + "metadata": {}, + "source": [ + "**Lets validate that the agent is using our context at all**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6430c2f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1;3;38;5;200mThought: (Implicit) I can answer without any more tools!\n", + "Answer: As an AI assistant, my persona is that of a stock market sorcerer and veteran investor. I am here to provide information, insights, and analysis related to companies like Lyft and Uber, as well as assist with various tasks and answer questions regarding the stock market and financials.\n", + "\u001b[0mAs an AI assistant, my persona is that of a stock market sorcerer and veteran investor. I am here to provide information, insights, and analysis related to companies like Lyft and Uber, as well as assist with various tasks and answer questions regarding the stock market and financials.\n" + ] + } + ], + "source": [ + "response = agent.chat(\n", + " \"What is your persona supposed to be?\"\n", + ") # This could be used for constraints, personas, or additional/initial background context for the agent\n", + "print(str(response))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "li-venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/module_guides/deploying/agents/modules.md b/docs/module_guides/deploying/agents/modules.md index 547033f1af7b7ff4597d10df471c207423f570dc..a29e8cbdd65a6ab94482890bb1107c422cd9b1f7 100644 --- a/docs/module_guides/deploying/agents/modules.md +++ b/docs/module_guides/deploying/agents/modules.md @@ -40,6 +40,7 @@ maxdepth: 1 maxdepth: 1 --- /examples/agent/react_agent_with_query_engine.ipynb +/examples/agent/react_agent_with_initial_context.ipynb ``` ## Additional Agents (available on LlamaHub) diff --git a/llama_index/agent/react/base.py b/llama_index/agent/react/base.py index 731b1e2f23b44632e7916a6b247519ee5b13b272..584b89da147ab39962d106ab310f26af52fd9bdd 100644 --- a/llama_index/agent/react/base.py +++ b/llama_index/agent/react/base.py @@ -57,9 +57,15 @@ class ReActAgent(AgentRunner): callback_manager: Optional[CallbackManager] = None, verbose: bool = False, tool_retriever: Optional[ObjectRetriever[BaseTool]] = None, + context: Optional[str] = None, ) -> None: """Init params.""" callback_manager = callback_manager or llm.callback_manager + if context and react_chat_formatter: + raise ValueError("Cannot provide both context and react_chat_formatter") + if context: + react_chat_formatter = ReActChatFormatter.from_context(context) + step_engine = ReActAgentWorker.from_tools( tools=tools, tool_retriever=tool_retriever, @@ -91,6 +97,7 @@ class ReActAgent(AgentRunner): output_parser: Optional[ReActOutputParser] = None, callback_manager: Optional[CallbackManager] = None, verbose: bool = False, + context: Optional[str] = None, **kwargs: Any, ) -> "ReActAgent": """Convenience constructor method from set of of BaseTools (Optional). @@ -119,4 +126,5 @@ class ReActAgent(AgentRunner): output_parser=output_parser, callback_manager=callback_manager, verbose=verbose, + context=context, ) diff --git a/llama_index/agent/react/formatter.py b/llama_index/agent/react/formatter.py index f00c21426ddfc7806dffbffd5191160574ce2381..a599bda064fcc3ebd3911f5f2d40971e4c734aa9 100644 --- a/llama_index/agent/react/formatter.py +++ b/llama_index/agent/react/formatter.py @@ -3,7 +3,10 @@ from abc import abstractmethod from typing import List, Optional, Sequence -from llama_index.agent.react.prompts import REACT_CHAT_SYSTEM_HEADER +from llama_index.agent.react.prompts import ( + CONTEXT_REACT_CHAT_SYSTEM_HEADER, + REACT_CHAT_SYSTEM_HEADER, +) from llama_index.agent.react.types import BaseReasoningStep, ObservationReasoningStep from llama_index.bridge.pydantic import BaseModel from llama_index.core.llms.types import ChatMessage, MessageRole @@ -43,7 +46,8 @@ class BaseAgentChatFormatter(BaseModel): class ReActChatFormatter(BaseAgentChatFormatter): """ReAct chat formatter.""" - system_header: str = REACT_CHAT_SYSTEM_HEADER + system_header: str = REACT_CHAT_SYSTEM_HEADER # default + context: str = "" # not needed w/ default def format( self, @@ -54,12 +58,14 @@ class ReActChatFormatter(BaseAgentChatFormatter): """Format chat history into list of ChatMessage.""" current_reasoning = current_reasoning or [] - tool_descs_str = "\n".join(get_react_tool_descriptions(tools)) + format_args = { + "tool_desc": "\n".join(get_react_tool_descriptions(tools)), + "tool_names": ", ".join([tool.metadata.get_name() for tool in tools]), + } + if self.context: + format_args["context"] = self.context - fmt_sys_header = self.system_header.format( - tool_desc=tool_descs_str, - tool_names=", ".join([tool.metadata.get_name() for tool in tools]), - ) + fmt_sys_header = self.system_header.format(**format_args) # format reasoning history as alternating user and assistant messages # where the assistant messages are thoughts and actions and the user @@ -83,3 +89,11 @@ class ReActChatFormatter(BaseAgentChatFormatter): *chat_history, *reasoning_history, ] + + @classmethod + def from_context(self, context: str) -> "ReActChatFormatter": + """Create ReActChatFormatter from context.""" + return ReActChatFormatter( + context=context, + system_header=CONTEXT_REACT_CHAT_SYSTEM_HEADER, + ) diff --git a/llama_index/agent/react/prompts.py b/llama_index/agent/react/prompts.py index 717317fddf132f7c2f51a2b0668a09a3c188a5ab..7cb258a36ced82ca2922a04527d0caa2df599919 100644 --- a/llama_index/agent/react/prompts.py +++ b/llama_index/agent/react/prompts.py @@ -55,3 +55,58 @@ Answer: Sorry, I cannot answer your query. Below is the current conversation consisting of interleaving human and assistant messages. """ + +CONTEXT_REACT_CHAT_SYSTEM_HEADER = """\ + +You are designed to help with a variety of tasks, from answering questions \ + to providing summaries to other types of analyses. + +## Tools +You have access to a wide variety of tools. You are responsible for using +the tools in any sequence you deem appropriate to complete the task at hand. +This may require breaking the task into subtasks and using different tools +to complete each subtask. + +Here is some context to help you answer the question and plan: +{context} + +You have access to the following tools: +{tool_desc} + +## Output Format +To answer the question, please use the following format. + +``` +Thought: I need to use a tool to help me answer the question. +Action: tool name (one of {tool_names}) if using a tool. +Action Input: the input to the tool, in a JSON format representing the kwargs (e.g. {{"input": "hello world", "num_beams": 5}}) +``` + +Please ALWAYS start with a Thought. + +Please use a valid JSON format for the Action Input. Do NOT do this {{'input': 'hello world', 'num_beams': 5}}. + +If this format is used, the user will respond in the following format: + +``` +Observation: tool response +``` + +You should keep repeating the above format until you have enough information +to answer the question without using any more tools. At that point, you MUST respond +in the one of the following two formats: + +``` +Thought: I can answer without using any more tools. +Answer: [your answer here] +``` + +``` +Thought: I cannot answer the question with the provided tools. +Answer: Sorry, I cannot answer your query. +``` + +## Current Conversation +Below is the current conversation consisting of interleaving human and assistant messages. + +"""