Skip to content

Commit

Permalink
Merge branch 'main' into feature/add-linting
Browse files Browse the repository at this point in the history
  • Loading branch information
mnicstruwig committed Dec 13, 2023
2 parents 1318456 + 40ec4c2 commit 44f36e8
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 124 deletions.
3 changes: 3 additions & 0 deletions openbb_agents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from openbb_agents.utils import get_verbosity

VERBOSE = get_verbosity()
92 changes: 2 additions & 90 deletions openbb_agents/agent.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,6 @@
import logging
from typing import Optional

from langchain import hub
from langchain.agents import AgentExecutor
from langchain.agents.format_scratchpad import (
format_log_to_str,
format_to_openai_function_messages,
)
from langchain.agents.output_parsers import (
JSONAgentOutputParser,
OpenAIFunctionsAgentOutputParser,
)
from langchain.chat_models import ChatOpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.schema import Document
from langchain.tools import StructuredTool
from langchain.tools.render import (
format_tool_to_openai_function,
render_text_description_and_args,
)
from langchain.vectorstores import FAISS, VectorStore

from openbb_agents.chains import (
generate_final_response,
generate_subquestion_answer,
Expand All @@ -29,6 +9,7 @@
)
from openbb_agents.models import SubQuestionAgentConfig
from openbb_agents.tools import (
create_tool_index,
get_all_openbb_tools,
map_openbb_routes_to_langchain_tools,
)
Expand Down Expand Up @@ -69,7 +50,7 @@ def openbb_agent(query: str, openbb_tools: Optional[list[str]] = None) -> str:
tools = map_openbb_routes_to_langchain_tools(openbb_tools)
else:
tools = get_all_openbb_tools()
vector_index = _create_tool_index(tools=tools)
vector_index = create_tool_index(tools=tools)

answered_subquestions = []
for subquestion in subquestion_list.subquestions: # TODO: Do in parallel
Expand Down Expand Up @@ -105,72 +86,3 @@ def openbb_agent(query: str, openbb_tools: Optional[list[str]] = None) -> str:
return generate_final_response(
query=query, answered_subquestions=answered_subquestions
)


def make_openai_agent(prompt, tools, model="gpt-4-1106-preview", verbose=False):
"""Create a new OpenAI agent from a list of tools."""
llm = ChatOpenAI(model=model)
llm_with_tools = llm.bind(
functions=[format_tool_to_openai_function(t) for t in tools]
)
chain = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_function_messages(
x["intermediate_steps"]
),
}
| prompt
| llm_with_tools
| OpenAIFunctionsAgentOutputParser()
)

return AgentExecutor(agent=chain, tools=tools, verbose=verbose)


def make_react_agent(tools, model="gpt-4-1106-preview", temperature=0.2, verbose=True):
"""Create a new ReAct agent from a list of tools."""

# This retrieves the ReAct agent chat prompt template available in Langchain Hub
# https://smith.langchain.com/hub/hwchase17/react-json?organizationId=10beea65-e722-5aa1-9f93-034c22e3cd6e
prompt = hub.pull("hwchase17/react-multi-input-json")

# Replace the 'tools' and 'tool_names' content of the prompt with
# information given to the agent Note that tool_names is a field available
# in each tool, so it can be inferred from same argument
prompt = prompt.partial(
tools=render_text_description_and_args(tools),
tool_names=", ".join([t.name for t in tools]),
)

llm = ChatOpenAI(model=model, temperature=temperature).bind(stop=["\nObservation"])

chain = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]),
}
| prompt
| llm
| JSONAgentOutputParser()
)

agent_executor = AgentExecutor(
agent=chain,
tools=tools,
verbose=verbose,
return_intermediate_steps=False,
handle_parsing_errors=True,
)
return agent_executor


def _create_tool_index(tools: list[StructuredTool]) -> VectorStore:
"""Create a tool index of LangChain StructuredTools."""
docs = [
Document(page_content=t.description, metadata={"index": i})
for i, t in enumerate(tools)
]

vector_store = FAISS.from_documents(docs, OpenAIEmbeddings())
return vector_store
58 changes: 31 additions & 27 deletions openbb_agents/chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@
)
from openbb_agents.utils import get_dependencies

from . import VERBOSE

logger = logging.getLogger(__name__)


def generate_final_response(
query: str, answered_subquestions: list[AnsweredSubQuestion]
query: str,
answered_subquestions: list[AnsweredSubQuestion],
verbose=VERBOSE,
) -> str:
"""Generate the final response to a query given answer to a list of subquestions."""

Expand All @@ -60,9 +64,7 @@ def generate_final_response(
system_message = FINAL_RESPONSE_PROMPT_TEMPLATE
prompt = ChatPromptTemplate.from_messages([("system", system_message)])

llm = ChatOpenAI(
model="gpt-4", temperature=0.1
) # Let's use the big model for the final answer.
llm = ChatOpenAI(model="gpt-4", temperature=0.1, verbose=verbose)

chain = (
{
Expand All @@ -76,13 +78,13 @@ def generate_final_response(
)

result = chain.invoke(
{"input": query, "answered_subquestions": answered_subquestions}
{"input": query, "answered_subquestions": answered_subquestions},
)
return str(result.content)


def generate_subquestion_answer(
subquestion_agent_config: SubQuestionAgentConfig,
subquestion_agent_config: SubQuestionAgentConfig, verbose=VERBOSE
) -> AnsweredSubQuestion:
"""Generate an answer to a subquestion using tools and dependencies."""

Expand Down Expand Up @@ -116,9 +118,9 @@ def generate_subquestion_answer(
)

try:
result = make_react_agent(tools=subquestion_agent_config.tools).invoke(
{"input": prompt}
)
result = make_react_agent(
tools=subquestion_agent_config.tools, verbose=verbose
).invoke({"input": prompt})
output = str(result["output"])
except Exception as err: # Terrible practice, but it'll do for now.
print(err)
Expand All @@ -145,6 +147,7 @@ def select_tools(
tools: list[StructuredTool],
subquestion: SubQuestion,
answered_subquestions: list[AnsweredSubQuestion],
verbose: bool = VERBOSE,
) -> SelectedToolsList:
"""Use an agent to select tools given a subquestion and its dependencies."""

Expand Down Expand Up @@ -179,15 +182,15 @@ def search_tools(query: str) -> list[tuple[str, str]]:
)

search_tool = StructuredTool.from_function(search_tools)
agent = make_openai_agent(prompt=prompt, tools=[search_tool])
agent = make_openai_agent(prompt=prompt, tools=[search_tool], verbose=verbose)
result = agent.invoke({"input": subquestion.question})

# Parse the output into a pydantic model and return
selected_tools = selected_tools_list_parser.parse(result["output"])
return selected_tools


def generate_subquestions(query: str) -> SubQuestionList:
def generate_subquestions(query: str, verbose=VERBOSE) -> SubQuestionList:
logger.info("Request to generate subquestions for query: %s", query)
subquestion_parser = PydanticOutputParser(pydantic_object=SubQuestionList)

Expand All @@ -207,7 +210,7 @@ def generate_subquestions(query: str) -> SubQuestionList:
format_instructions=subquestion_parser.get_format_instructions()
)

llm = ChatOpenAI(model="gpt-4", temperature=0.0)
llm = ChatOpenAI(model="gpt-4", temperature=0.0, verbose=verbose)
subquestion_chain = (
{"input": lambda x: x["input"]} | prompt | llm | subquestion_parser
)
Expand Down Expand Up @@ -237,7 +240,19 @@ def _get_tools(
return tools


def make_openai_agent(prompt, tools, model="gpt-4-1106-preview", verbose=False):
def _render_subquestions_and_answers(
answered_subquestions: list[AnsweredSubQuestion],
) -> str:
"Combines all subquestions and their answers"
output = ""
for answered_subq in answered_subquestions:
output += "Subquestion: " + answered_subq.subquestion.question + "\n"
output += "Observations: \n" + answered_subq.answer + "\n\n"

return output


def make_openai_agent(prompt, tools, model="gpt-4-1106-preview", verbose=VERBOSE):
"""Create a new OpenAI agent from a list of tools."""
llm = ChatOpenAI(model=model)
llm_with_tools = llm.bind(
Expand All @@ -258,7 +273,9 @@ def make_openai_agent(prompt, tools, model="gpt-4-1106-preview", verbose=False):
return AgentExecutor(agent=chain, tools=tools, verbose=verbose)


def make_react_agent(tools, model="gpt-4-1106-preview", temperature=0.2, verbose=False):
def make_react_agent(
tools, model="gpt-4-1106-preview", temperature=0.2, verbose=VERBOSE
):
"""Create a new ReAct agent from a list of tools."""

# This retrieves the ReAct agent chat prompt template available in Langchain Hub
Expand All @@ -284,7 +301,6 @@ def make_react_agent(tools, model="gpt-4-1106-preview", temperature=0.2, verbose
| llm
| JSONAgentOutputParser()
)

agent_executor = AgentExecutor(
agent=chain,
tools=tools,
Expand All @@ -293,15 +309,3 @@ def make_react_agent(tools, model="gpt-4-1106-preview", temperature=0.2, verbose
handle_parsing_errors=True,
)
return agent_executor


def _render_subquestions_and_answers(
answered_subquestions: list[AnsweredSubQuestion],
) -> str:
"Combines all subquestions and their answers"
output = ""
for answered_subq in answered_subquestions:
output += "Subquestion: " + answered_subq.subquestion.question + "\n"
output += "Observations: \n" + answered_subq.answer + "\n\n"

return output
7 changes: 7 additions & 0 deletions openbb_agents/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@
... REPEAT AS MANY TIMES AS NECESSARY TO ANSWER THE SUBQUESTION.
```
Make multiple queries with different inputs (perhaps by fetching more or less
data) if your initial attempt at calling the tool doesn't return the information
you require.
Important: when calling the function again, it is important to use different
input arguments.
If the tools responds with an error or empty response, attempt calling the tool again using
different inputs. Don't give up after the first error.
Expand Down
14 changes: 14 additions & 0 deletions openbb_agents/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,28 @@
from typing import List, Union

import tiktoken
from langchain.embeddings import OpenAIEmbeddings
from langchain.schema import Document
from langchain.tools import StructuredTool
from langchain.tools.base import ToolException
from langchain.vectorstores import FAISS, VectorStore
from openbb import obb
from pydantic.v1 import ValidationError, create_model
from pydantic.v1.fields import FieldInfo
from pydantic_core import PydanticUndefinedType


def create_tool_index(tools: list[StructuredTool]) -> VectorStore:
"""Create a tool index of LangChain StructuredTools."""
docs = [
Document(page_content=t.description, metadata={"index": i})
for i, t in enumerate(tools)
]

vector_store = FAISS.from_documents(docs, OpenAIEmbeddings())
return vector_store


def _fetch_obb_module(openbb_command_root: str) -> ModuleType:
module_path_split = openbb_command_root.split("/")[1:]
module_path = ".".join(module_path_split)
Expand Down
6 changes: 6 additions & 0 deletions openbb_agents/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import os

from .models import AnsweredSubQuestion, SubQuestion


def get_verbosity() -> bool:
return os.environ.get("VERBOSE", "False") == "True"


def get_dependencies(
answered_subquestions: list[AnsweredSubQuestion], subquestion: SubQuestion
) -> list[AnsweredSubQuestion]:
Expand Down
17 changes: 10 additions & 7 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import logging.config

import openbb_agents
from openbb_agents import agent

logging_config = {
Expand Down Expand Up @@ -50,16 +51,18 @@
"query", metavar="query", type=str, help="The query to send to the agent."
)
parser.add_argument(
"-v",
"--verbose",
type=bool,
help="Include verbose output.",
default=False,
"-v", "--verbose", action="store_true", help="Enable verbose logging."
)

args = parser.parse_args()
query = args.query
if args.verbose:
openbb_agents.VERBOSE = True
else:
openbb_agents.VERBOSE = False


# We only import after passing in command line args to have verbosity propagate.

query = args.query
result = agent.openbb_agent(query)

print("============")
Expand Down

0 comments on commit 44f36e8

Please sign in to comment.