From 42e54a2cb29943b793bf9a47dd7e0121e1c0c87d Mon Sep 17 00:00:00 2001 From: Navan Chauhan Date: Sat, 14 Oct 2023 14:47:49 -0600 Subject: final --- README.md | 7 ++- env.sample | 5 ++- info.txt | 6 +++ lang_prompt_demo.py | 60 +++++++++++++++++++++---- main.py | 114 ++++++++++++++++++++++++++++++----------------- outbound_call.py | 8 ++-- poetry.lock | 16 ++++++- pyproject.toml | 1 + speller_agent.py | 10 ++--- tools/contacts.py | 7 ++- tools/email_tool.py | 6 ++- tools/get_user_inputs.py | 2 +- tools/search.py | 1 + tools/summarize.py | 20 ++++----- tools/vocode.py | 19 +++++--- 15 files changed, 199 insertions(+), 83 deletions(-) create mode 100644 info.txt create mode 100644 tools/search.py diff --git a/README.md b/README.md index f7eeae5..87306a4 100644 --- a/README.md +++ b/README.md @@ -1 +1,6 @@ -uvicorn main:app --host 0.0.0.0 --port 6789 \ No newline at end of file + +``` +uvicorn main:app --host 0.0.0.0 --port 6789 +``` + +Look at `sample.env` for environment variables. \ No newline at end of file diff --git a/env.sample b/env.sample index 2709a6d..a92d8aa 100644 --- a/env.sample +++ b/env.sample @@ -5,5 +5,8 @@ AZURE_SPEECH_KEY= AZURE_SPEECH_REGION= TWILIO_ACCOUNT_SID= TWILIO_AUTH_TOKEN= +TWILIO_PHONE= ELEVENLABS_API_KEY= -ELEVENLABS_VOICE_ID= \ No newline at end of file +ELEVENLABS_VOICE_ID= +TEST_PHONE_NUMBER= +SERPAPI_API_KEY= \ No newline at end of file diff --git a/info.txt b/info.txt new file mode 100644 index 0000000..8056043 --- /dev/null +++ b/info.txt @@ -0,0 +1,6 @@ +Name: Hunter McSpeed +Credit/ Debit Card for payments: 4535 6789 0123 4567 +CVV: 321 +Expiration: 12/2020 +Address: 805 29th St, Boulder, CO 80303 +Phone Number: +1 (303) 492-1411 \ No newline at end of file diff --git a/lang_prompt_demo.py b/lang_prompt_demo.py index 3d8f1cd..cde0a59 100644 --- a/lang_prompt_demo.py +++ b/lang_prompt_demo.py @@ -5,6 +5,7 @@ from dotenv import load_dotenv from tools.contacts import get_all_contacts from tools.vocode import call_phone_number +from tools.summarize import summarize from tools.get_user_inputs import get_desired_inputs from tools.email_tool import email_tasks from langchain.memory import ConversationBufferMemory @@ -15,30 +16,71 @@ from stdout_filterer import RedactPhoneNumbers load_dotenv() from langchain.chat_models import ChatOpenAI -from langchain.chat_models import BedrockChat +# from langchain.chat_models import BedrockChat from langchain.agents import initialize_agent from langchain.agents import AgentType +from langchain.tools import WikipediaQueryRun + +import argparse + +memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) +tools=load_tools(["human", "wikipedia"]) + [get_all_contacts, call_phone_number, email_tasks, summarize] + +tools_desc = "" +for tool in tools: + tools_desc += tool.name + " : " + tool.description + "\n" + +def rephrase_prompt(objective: str) -> str: + # llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo") # type: ignore + # pred = llm.predict(f"Based on these tools {tools_desc} with the {objective} should be done in the following manner (outputting a single sentence), allowing for failure: ") + # print(pred) + # return pred + return f"{objective}" + +with open("info.txt") as f: + my_info = f.read() + memory.chat_memory.add_user_message("User information to us " + my_info + " end of user information.") + if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Command line argument parser example") + + parser.add_argument("--objective", type=str, help="Objective for the program") + parser.add_argument("--verbose", type=bool, help="Verbosity of the program", default=False) + + # Parse the arguments + args = parser.parse_args() + + # Get the value of --objective + objective_value = args.objective + + # Get the value of --verbose + verbose_value = args.verbose + # Redirect stdout to our custom class sys.stdout = typing.cast(typing.TextIO, RedactPhoneNumbers(sys.stdout)) + if objective_value is None: + objective_value = input("What is your objective? ") + OBJECTIVE = ( - input("Objective: ") - + "make sure you use the proper tool before calling final action to meet objective, feel free to say you need more information or cannot do something." + objective_value or "Find a random person in my contacts and tell them a joke" ) - #llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo") # type: ignore - llm = BedrockChat(model_id="anthropic.claude-instant-v1", model_kwargs={"temperature":0}) # type: ignore - memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) + llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo") # type: ignore + #llm = BedrockChat(model_id="anthropic.claude-instant-v1", model_kwargs={"temperature":0}) # type: ignore + #memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) # Logging of LLMChains verbose = True agent = initialize_agent( - tools=[get_all_contacts, call_phone_number, email_tasks] + load_tools(["serpapi", "human"]), + tools=tools, llm=llm, agent=AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION, - verbose=verbose, + verbose=verbose_value, memory=memory, ) - agent.run(OBJECTIVE) + out = agent.run(OBJECTIVE) + print(out) + + diff --git a/main.py b/main.py index 54b6c9c..e7058e8 100644 --- a/main.py +++ b/main.py @@ -75,16 +75,17 @@ telephony_server = TelephonyServer( inbound_call_configs=[ TwilioInboundCallConfig( url="/inbound_call", - # agent_config=ChatGPTAgentConfig( - # initial_message=BaseMessage(text="What up."), - # prompt_preamble="Act as a customer talking to 'Cosmos', a pizza establisment ordering a large pepperoni pizza for pickup. If asked for a name, your name is 'Hunter McRobie', and your credit card number is 4743 2401 5792 0539 CVV: 123 and expiratoin is 10/25. If asked for numbers, say them one by one",#"Have a polite conversation about life while talking like a pirate.", - # generate_responses=True, - # model_name="gpt-3.5-turbo" - # ), - agent_config=SpellerAgentConfig(generate_responses=False, initial_message=BaseMessage(text="What up.")), + agent_config=ChatGPTAgentConfig( + initial_message=BaseMessage(text="Ahoy Matey! Pizza Ahoy here! How may I help you."), + prompt_preamble="You are receiving calls on behald of 'Pizza Ahoy!', a pizza establisment taking orders only for pickup. YOu will be provided the transcript from a speech to text model, say what you would say in that siutation. Talk like a pirate. Apologise to customer if they ask for delivery.", + generate_responses=True, + model_name="gpt-3.5-turbo" + ), + # agent_config=SpellerAgentConfig(generate_responses=False, initial_message=BaseMessage(text="What up.")), twilio_config=TwilioConfig( account_sid=os.environ["TWILIO_ACCOUNT_SID"], auth_token=os.environ["TWILIO_AUTH_TOKEN"], + record=True ), synthesizer_config=ElevenLabsSynthesizerConfig.from_telephone_output_device( api_key=os.getenv("ELEVENLABS_API_KEY"), @@ -96,45 +97,76 @@ telephony_server = TelephonyServer( logger=logger, ) -async def send_message(message: str) -> AsyncIterable[str]: - callback = AsyncIteratorCallbackHandler() - model = ChatOpenAI( - streaming=True, - verbose=True, - callbacks=[callback], - ) - - async def wrap_done(fn: Awaitable, event: asyncio.Event): - """Wrap an awaitable with a event to signal when it's done or an exception is raised.""" - try: - await fn - except Exception as e: - # TODO: handle exception - print(f"Caught exception: {e}") - finally: - # Signal the aiter to stop. - event.set() - - # Begin a task that runs in the background. - task = asyncio.create_task(wrap_done( - model.agenerate(messages=[[HumanMessage(content=message)]]), - callback.done), - ) +import os +import sys +import typing +from dotenv import load_dotenv - async for token in callback.aiter(): - # Use server-sent-events to stream the response - yield f"data: {token}\n\n" +from tools.contacts import get_all_contacts +from tools.vocode import call_phone_number +from tools.summarize import summarize +from tools.get_user_inputs import get_desired_inputs +from tools.email_tool import email_tasks +from langchain.memory import ConversationBufferMemory +from langchain.agents import load_tools - await task +from stdout_filterer import RedactPhoneNumbers +load_dotenv() -class StreamRequest(BaseModel): - """Request body for streaming.""" - message: str +from langchain.chat_models import ChatOpenAI +# from langchain.chat_models import BedrockChat +from langchain.agents import initialize_agent +from langchain.agents import AgentType + +from langchain.tools import WikipediaQueryRun + +memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) +tools=load_tools(["human", "wikipedia"]) + [get_all_contacts, call_phone_number, email_tasks, summarize] + +tools_desc = "" +for tool in tools: + tools_desc += tool.name + " : " + tool.description + "\n" + +def rephrase_prompt(objective: str) -> str: + # llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo") # type: ignore + # pred = llm.predict(f"Based on these tools {tools_desc} with the {objective} should be done in the following manner (outputting a single sentence), allowing for failure: ") + # print(pred) + # return pred + return f"{objective}" + +with open("info.txt") as f: + my_info = f.read() + memory.chat_memory.add_user_message("User information to us " + my_info + " end of user information.") + + +class QueryItem(BaseModel): + query: str + +@app.post("/senpai") +def exec_and_return(item: QueryItem): + query = item.query + verbose_value = False + print(query) + OBJECTIVE = ( + query + or "Find a random person in my contacts and tell them a joke" + ) + llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo") # type: ignore + #llm = BedrockChat(model_id="anthropic.claude-instant-v1", model_kwargs={"temperature":0}) # type: ignore + #memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) + # Logging of LLMChains + verbose = True + agent = initialize_agent( + tools=tools, + llm=llm, + agent=AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION, + verbose=verbose_value, + memory=memory, + ) + out = agent.run(OBJECTIVE) -@app.post("/stream") -def stream(body: StreamRequest): - return StreamingResponse(send_message(body.message), media_type="text/event-stream") + return out app.include_router(telephony_server.get_router()) \ No newline at end of file diff --git a/outbound_call.py b/outbound_call.py index a37771f..f5e4bf1 100644 --- a/outbound_call.py +++ b/outbound_call.py @@ -17,7 +17,7 @@ from vocode.streaming.agent.chat_gpt_agent import ChatGPTAgent from vocode.streaming.models.agent import ChatGPTAgentConfig from vocode.streaming.models.message import BaseMessage -from vocode.streaming.models.synthesizer import ElevenLabsSynthesizerConfig +from vocode.streaming.models.synthesizer import ElevenLabsSynthesizerConfig, AzureSynthesizerConfig BASE_URL = os.environ["BASE_URL"] @@ -30,12 +30,12 @@ async def main(): outbound_call = OutboundCall( base_url=BASE_URL, - to_phone="+12243882079", + to_phone="+17208828227", from_phone="+18445610144", config_manager=config_manager, agent_config=ChatGPTAgentConfig( - initial_message=BaseMessage(text="Hello. Hello"), - prompt_preamble="Act as a customer talking to 'Cosmos', a pizza establisment ordering a large pepperoni pizza for pickup. If asked for a name, your name is 'Hunter McRobie', and your credit card number is 4-7-4-3 2-4-0-1 5-7-9-2 0-5-39 CVV: 123 and expiratoin is 10/25. If asked for numbers, say them one by one",#"Have a polite conversation about life while talking like a pirate.", + initial_message=BaseMessage(text="Hello. Can I order a pizza?"), + prompt_preamble="Act as a customer talking to 'Cosmos', a pizza establisment ordering a large pepperoni pizza for pickup. If asked for a name, your name is 'Hunter McRobie', and your credit card number is 4-7-4-3 2-4-0-1 5-7-9-2 0-5-3-9 CVV: 123 and expiration is 10/25. If asked for numbers, say them one by one. NEVER ACT AS COSMOS, ONLY AS THE CUSTOMER. Continue speaking if there are too many interuptions. Make sure you soundl like a pirate.",#"Have a polite conversation about life while talking like a pirate.", generate_responses=True, ), twilio_config=TwilioConfig( diff --git a/poetry.lock b/poetry.lock index e96f2d0..8ad3944 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3048,6 +3048,20 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] +[[package]] +name = "wikipedia" +version = "1.4.0" +description = "Wikipedia API for Python" +optional = false +python-versions = "*" +files = [ + {file = "wikipedia-1.4.0.tar.gz", hash = "sha256:db0fad1829fdd441b1852306e9856398204dc0786d2996dd2e0c8bb8e26133b2"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +requests = ">=2.0.0,<3.0.0" + [[package]] name = "wrapt" version = "1.15.0" @@ -3251,4 +3265,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11,<3.12" -content-hash = "0f5b568a4317c9cda4af20640c3bbdac76b4756ded9604bf57d75f44c36c679a" +content-hash = "5ea79826f19325084c0b9ba8b06ffc3dc0a09c52ec832cae87c6fd17aab465e9" diff --git a/pyproject.toml b/pyproject.toml index 1e66585..eaff1ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ google-auth-httplib2 = "^0.1.1" beautifulsoup4 = "^4.12.2" google-search-results = "^2.4.2" boto3 = "^1.28.63" +wikipedia = "^1.4.0" [build-system] diff --git a/speller_agent.py b/speller_agent.py index ce7c197..9cc7ca4 100644 --- a/speller_agent.py +++ b/speller_agent.py @@ -11,10 +11,8 @@ import sys import typing from dotenv import load_dotenv -from tools.contacts import get_all_contacts -from tools.vocode import call_phone_number -from tools.email_tool import email_tasks -from tools.summarize import summarize +from lang_prompt_demo import tools + from langchain.memory import ConversationBufferMemory from langchain.utilities import SerpAPIWrapper @@ -25,7 +23,7 @@ from stdout_filterer import RedactPhoneNumbers load_dotenv() from langchain.chat_models import ChatOpenAI -from langchain.chat_models import BedrockChat +# from langchain.chat_models import BedrockChat from langchain.agents import initialize_agent from langchain.agents import AgentType as LangAgentType @@ -36,7 +34,7 @@ memory = ConversationBufferMemory(memory_key="chat_history", return_messages=Tru # Logging of LLMChains verbose = True agent = initialize_agent( - tools=[get_all_contacts, call_phone_number, email_tasks, summarize] + load_tools(["serpapi", "human"]), + tools=tools, llm=llm, agent=LangAgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION, verbose=verbose, diff --git a/tools/contacts.py b/tools/contacts.py index afdc8a4..a37455f 100644 --- a/tools/contacts.py +++ b/tools/contacts.py @@ -11,10 +11,15 @@ CONTACTS = [ "name": "Greg", "phone" : os.getenv("TEST_PHONE_NUMBER"), "email": "grsi2038@colorado.edu" + }, + { + "name": "Hunter", + "phone": "+19178737978", + "email": "hunter.mcrobie@gmail.com" } ] @tool("get_all_contacts") -def get_all_contacts(placeholder: str) -> List[dict]: +def get_all_contacts(contact_name: str) -> List[dict]: """Returns all contacts in the user's phone book which includes email and phone numbers.""" return CONTACTS \ No newline at end of file diff --git a/tools/email_tool.py b/tools/email_tool.py index 932a7e5..da2f072 100644 --- a/tools/email_tool.py +++ b/tools/email_tool.py @@ -14,6 +14,9 @@ toolkit = GmailToolkit() tools = toolkit.get_tools() +my_information = "Use this information whenever needed User information " + open("info.txt").read() + " . Your task " + + @tool("email tasks") def email_tasks(input: str) -> bool: """draft/send/search/get email and return whatever you get. @@ -27,7 +30,8 @@ def email_tasks(input: str) -> bool: for example, `send an email to grsi2038@colorado.edu asking him if he is still looking for a job and that he should continue doing whatever he his doing because he will eventually find it` will email grsi2038@colorado.edu """ - prompt = input + prompt = my_information + input + #print(input) llm = OpenAI(temperature=0) agent = initialize_agent( diff --git a/tools/get_user_inputs.py b/tools/get_user_inputs.py index 3b59a3a..e219bab 100644 --- a/tools/get_user_inputs.py +++ b/tools/get_user_inputs.py @@ -16,7 +16,7 @@ INPUTS = {} @tool("get_desired_inputs") def get_desired_inputs(input: str) -> dict: - """ + """Use this between tools to get the desired inputs for the next tool. You will be given a task that will be performed by an autonomous agent on behalf of a user. You will gather any necessary data from the user to complete the specified task before executing the task. """ diff --git a/tools/search.py b/tools/search.py new file mode 100644 index 0000000..5355513 --- /dev/null +++ b/tools/search.py @@ -0,0 +1 @@ +from langchain.utilities import SerpAPIWrapper \ No newline at end of file diff --git a/tools/summarize.py b/tools/summarize.py index d90c49d..fa0bf44 100644 --- a/tools/summarize.py +++ b/tools/summarize.py @@ -4,29 +4,25 @@ import os from langchain.agents import tool from dotenv import load_dotenv -from langchain.agents.agent_toolkits import GmailToolkit - from langchain.llms import OpenAI from langchain.agents import initialize_agent, AgentType +from langchain.prompts.prompt import PromptTemplate -load_dotenv() -toolkit = GmailToolkit() -tools = toolkit.get_tools() +load_dotenv() @tool("summarize") def summarize(input: str) -> bool: """ Summarize the response to the input prompt. """ - prompt = input + data = input llm = OpenAI(temperature=0) - agent = initialize_agent( - prompt=prompt, - llm=llm, - agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, - ) - return agent.run(prompt) + template = "Human: Can you summarize this in a couple of sentences: {data}" + prompt = PromptTemplate(input_variables=["data"], template=template) + pred = llm.predict(prompt.format(data=data)) + return pred + #preferred_forums[make] = [make_url] diff --git a/tools/vocode.py b/tools/vocode.py index 975cb4c..496bc54 100644 --- a/tools/vocode.py +++ b/tools/vocode.py @@ -11,6 +11,8 @@ from vocode.streaming.models.transcriber import ( PunctuationEndpointingConfig, ) +from vocode.streaming.models.telephony import TwilioConfig + load_dotenv() @@ -26,18 +28,19 @@ import time LOOP = asyncio.new_event_loop() asyncio.set_event_loop(LOOP) +my_information = "Use this information whenever needed User information " + open("info.txt").read() + " . Your task " @tool("call phone number") def call_phone_number(input: str) -> str: - """calls a phone number as a bot and returns a transcript of the conversation. + """calls a phone number as a bot and returns a transcript of the conversation. Verifies the phone number from the user before calling. make sure you call `get all contacts` first to get a list of phone numbers to call. - the input to this tool is a pipe separated list of a phone number, a prompt, and the first thing the bot should say. + the input to this tool is a pipe separated list of a phone number, a prompt (including history), and the first thing the bot should say The prompt should instruct the bot with what to do on the call and be in the 3rd person, like 'the assistant is performing this task' instead of 'perform this task'. - should only use this tool once it has found an adequate phone number to call. + e.g. phone_number|prompt|initial_message - for example, `+15555555555|the assistant is explaining the meaning of life|i'm going to tell you the meaning of life` will call +15555555555, say 'i'm going to tell you the meaning of life', and instruct the assistant to tell the human what the meaning of life is. + should only use this tool once it has found and verified adequate phone number to call. """ phone_number, prompt, initial_message = input.split("|",2) print(phone_number, prompt, initial_message) @@ -48,9 +51,15 @@ def call_phone_number(input: str) -> str: config_manager=RedisConfigManager(), agent_config=ChatGPTAgentConfig( initial_message=BaseMessage(text=initial_message), - prompt_preamble=prompt, + prompt_preamble=my_information + prompt, generate_responses=True, + allow_agent_to_be_cut_off=False ), + twilio_config=TwilioConfig( + account_sid=os.environ["TWILIO_ACCOUNT_SID"], + auth_token=os.environ["TWILIO_AUTH_TOKEN"], + record=True + ), synthesizer_config=ElevenLabsSynthesizerConfig.from_telephone_output_device( api_key=os.getenv("ELEVENLABS_API_KEY"), voice_id=os.getenv("ELEVENLABS_VOICE_ID"), -- cgit v1.2.3