From be02dd5b302400b38ec206007441c34592c26909 Mon Sep 17 00:00:00 2001 From: klungg Date: Tue, 29 Oct 2024 18:20:37 +0100 Subject: [PATCH 01/62] feat: added keys to docker-compose.yml --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index d3a6956..ae45aeb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,9 @@ services: FLASK_ENV: ${FLASK_ENV} # Autorestarts flask when code changes are detected OPENAI_API_KEY: ${OPENAI_API_KEY} LANGSMITH_API_KEY: ${LANGSMITH_API_KEY} + PERPLEXITY_API_KEY: ${PERPLEXITY_API_KEY} + GOOGLE_AUTH_KEY: ${GOOGLE_AUTH_KEY} + GOOGLE_CALENDAR_ID: ${GOOGLE_CALENDAR_ID} PORT: ${PORT} volumes: - ./core:/app # Mount the application code to detect live changes From 2ba8f85d86f2c7bf0c5a3b5998bee616ef9b1829 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 29 Oct 2024 18:36:11 +0100 Subject: [PATCH 02/62] feat: add file for different nodes in the graf --- core/noder.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 core/noder.py diff --git a/core/noder.py b/core/noder.py new file mode 100644 index 0000000..d98e212 --- /dev/null +++ b/core/noder.py @@ -0,0 +1,23 @@ +from graphstate import GraphState +from Agents.simpleagent import SimpleAgent +from langchain.prompts import PromptTemplate + + +def jarvis_agent(state: GraphState): + """Agent to determine how to answer user question""" + promt = PromptTemplate( + template= """ + Lorem ipsum.... + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + TODO: Create options for the llm to answer + + Answer with the option name and nothing else + """ + ) \ No newline at end of file From 415a4ef1e09ae52eed449aad57bbb7807472eff6 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 29 Oct 2024 18:36:35 +0100 Subject: [PATCH 03/62] feat : add class for Agents with tools in Simpleagent --- core/Agents/simpleagent.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/Agents/simpleagent.py b/core/Agents/simpleagent.py index 4976777..4bbe43a 100644 --- a/core/Agents/simpleagent.py +++ b/core/Agents/simpleagent.py @@ -1,6 +1,7 @@ from langchain_openai import ChatOpenAI from models import Model from config import OPENAI_API_KEY +from tools.tools import get_tools class SimpleAgent: @@ -9,3 +10,7 @@ class SimpleAgent: temperature=0, max_tokens=512, ) + +class ToolsAgent: + def __init__(self): + self.agent = SimpleAgent.llm.bind_tools(get_tools()) From 09869cf78f458a55aa79645d6de379ab6fb5e4b6 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 29 Oct 2024 18:37:08 +0100 Subject: [PATCH 04/62] feat: add a variable for data in GraphState --- core/graphstate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/graphstate.py b/core/graphstate.py index 4bef10a..3bab490 100644 --- a/core/graphstate.py +++ b/core/graphstate.py @@ -8,3 +8,4 @@ class GraphState(TypedDict): # in the annotation defines how this state key should be updated # (in this case, it appends messages to the list, rather than overwriting them) messages: Annotated[list, add_messages] + data: dict From 24eaa8c17c609c685e485e5fff45c898012e6dee Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 29 Oct 2024 18:54:53 +0100 Subject: [PATCH 05/62] feat: add nodes for deciding tool and generating response --- core/noder.py | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/core/noder.py b/core/noder.py index d98e212..1df341f 100644 --- a/core/noder.py +++ b/core/noder.py @@ -1,11 +1,12 @@ from graphstate import GraphState from Agents.simpleagent import SimpleAgent from langchain.prompts import PromptTemplate +from langchain_core.output_parsers import StrOutputParser def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" - promt = PromptTemplate( + prompt = PromptTemplate( template= """ Lorem ipsum.... Here are previous messages: @@ -19,5 +20,37 @@ def jarvis_agent(state: GraphState): TODO: Create options for the llm to answer Answer with the option name and nothing else - """ - ) \ No newline at end of file + """, + ) + chain = prompt | SimpleAgent.llm | StrOutputParser() + + return chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + +def tool_decider(state: GraphState): + """Agent to determine what tool to use""" + prompt = PromptTemplate( + template= """ + Lorem ipsum.... + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + TODO: Create options for the llm to answer + + Answer with the option name and nothing else + """, + ) + + chain = prompt | SimpleAgent.llm | StrOutputParser() + + return chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + +def response_generator(state: GraphState): + """Agent that generates a response to user based on user request + and possible data from tool calls""" + #TODO Implement + return "" \ No newline at end of file From 17b5e73ad09605236817aae0248c0c36e12bb0fe Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 29 Oct 2024 19:11:36 +0100 Subject: [PATCH 06/62] feat: add router for tool use --- core/graphstate.py | 4 ++-- core/noder.py | 30 +++++++++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/core/graphstate.py b/core/graphstate.py index 3bab490..8d67608 100644 --- a/core/graphstate.py +++ b/core/graphstate.py @@ -1,5 +1,4 @@ -from typing import Annotated -from typing import TypedDict +from typing import Annotated, TypedDict, Literal from langgraph.graph.message import add_messages @@ -9,3 +8,4 @@ class GraphState(TypedDict): # (in this case, it appends messages to the list, rather than overwriting them) messages: Annotated[list, add_messages] data: dict + tool_decision: Literal["use_tools", "generate"] diff --git a/core/noder.py b/core/noder.py index 1df341f..e28cc69 100644 --- a/core/noder.py +++ b/core/noder.py @@ -1,14 +1,17 @@ from graphstate import GraphState -from Agents.simpleagent import SimpleAgent +from Agents.simpleagent import SimpleAgent, ToolsAgent from langchain.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser +from typing import Literal def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( template= """ - Lorem ipsum.... + Your job is to determine if you need tools to answer the + users question. + Here are previous messages: Message: {messages} @@ -17,7 +20,7 @@ def jarvis_agent(state: GraphState): Data: {data} - TODO: Create options for the llm to answer + Your options are the following Answer with the option name and nothing else """, @@ -30,24 +33,29 @@ def tool_decider(state: GraphState): """Agent to determine what tool to use""" prompt = PromptTemplate( template= """ - Lorem ipsum.... - Here are previous messages: + Your job is to create tool_calls. The tool or tools you decide + to call should help answer the users question. + + Here are the previous messages: Message: {messages} - Data currently accumulated: + Here is the data currently accumulated: Data: {data} - TODO: Create options for the llm to answer - - Answer with the option name and nothing else + Please decide what tools to use to help the user and + add them to the additional_kwargs in the AI_message """, ) - chain = prompt | SimpleAgent.llm | StrOutputParser() + chain = prompt | ToolsAgent.agent | StrOutputParser() + response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + return {"messages": [response]} - return chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) +def router(state: GraphState) -> Literal["use_tools", "generate"]: + """Router to determine what to do""" + return state["decision"] def response_generator(state: GraphState): """Agent that generates a response to user based on user request From b1c53e176ae237053b8571c9c2dc692059748c97 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 29 Oct 2024 19:27:58 +0100 Subject: [PATCH 07/62] feat: promptengineer different nodes --- core/noder.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/core/noder.py b/core/noder.py index e28cc69..f9afc04 100644 --- a/core/noder.py +++ b/core/noder.py @@ -20,14 +20,16 @@ def jarvis_agent(state: GraphState): Data: {data} - Your options are the following + Your options are the following: + 1. 'use_tools': Call on tools to help solve the users problem + 2. 'generate': Generate a response if you have what you need to answer Answer with the option name and nothing else """, ) chain = prompt | SimpleAgent.llm | StrOutputParser() - - return chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + return {"tool_decision": response} def tool_decider(state: GraphState): """Agent to determine what tool to use""" @@ -55,10 +57,26 @@ def tool_decider(state: GraphState): def router(state: GraphState) -> Literal["use_tools", "generate"]: """Router to determine what to do""" - return state["decision"] + return state["tool_decision"] def response_generator(state: GraphState): """Agent that generates a response to user based on user request and possible data from tool calls""" - #TODO Implement - return "" \ No newline at end of file + prompt = PromptTemplate( + template= """ + You are a personal assistant and your job is to generate a response to the user. + + Here are the previous messages: + + Message: {messages} + + Here is the data currently accumulated: + + Data: {data} + + Formulate a response that answer the users question + """, + ) + chain = prompt | SimpleAgent + response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + return {"message": [response]} \ No newline at end of file From 1b6befec2f858bda9f65a007cc16eb304700da3d Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 29 Oct 2024 19:31:36 +0100 Subject: [PATCH 08/62] fix: add forgotten llm call --- core/noder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/noder.py b/core/noder.py index f9afc04..1f767aa 100644 --- a/core/noder.py +++ b/core/noder.py @@ -77,6 +77,6 @@ def response_generator(state: GraphState): Formulate a response that answer the users question """, ) - chain = prompt | SimpleAgent + chain = prompt | SimpleAgent.llm response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) return {"message": [response]} \ No newline at end of file From a77417ff909f4665367c06d6a1521baa9029293f Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 29 Oct 2024 19:39:29 +0100 Subject: [PATCH 09/62] style: remove unnecessary StrOutputParser --- core/noder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/noder.py b/core/noder.py index 1f767aa..c5e4493 100644 --- a/core/noder.py +++ b/core/noder.py @@ -51,7 +51,7 @@ def tool_decider(state: GraphState): """, ) - chain = prompt | ToolsAgent.agent | StrOutputParser() + chain = prompt | ToolsAgent.agent response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) return {"messages": [response]} From d86b22feb67785b6c92b1bb3217ddd38437e89f4 Mon Sep 17 00:00:00 2001 From: klungg Date: Tue, 29 Oct 2024 19:42:10 +0100 Subject: [PATCH 10/62] feat: fixes local time for calendar event creation --- core/tools/create_time_to_iso.py | 2 +- core/tools/current_time_iso.py | 2 +- core/tools/google_calender_create.py | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/tools/create_time_to_iso.py b/core/tools/create_time_to_iso.py index a1225c9..97fcb85 100644 --- a/core/tools/create_time_to_iso.py +++ b/core/tools/create_time_to_iso.py @@ -21,7 +21,7 @@ def create_time_iso_format(year: int, month: int, day: int, hour: int, minute: i Returns: str: The your inputed time in ISO format. """ - time = datetime(year, month, day, hour, minute, second).replace(microsecond=0).strftime('%Y-%m-%dT%H:%M:%SZ') + time = datetime(year, month, day, hour, minute, second).replace(microsecond=0).strftime('%Y-%m-%dT%H:%M:%S') return time def get_tool() -> StructuredTool: diff --git a/core/tools/current_time_iso.py b/core/tools/current_time_iso.py index 5f1b796..371f666 100644 --- a/core/tools/current_time_iso.py +++ b/core/tools/current_time_iso.py @@ -17,7 +17,7 @@ def current_time_iso_format(basetool): Returns: str: The current time in ISO format. """ - time = datetime.now().replace(microsecond=0).strftime('%Y-%m-%dT%H:%M:%SZ') + time = datetime.now().replace(microsecond=0).strftime('%Y-%m-%dT%H:%M:%S') return time def get_tool() -> StructuredTool: diff --git a/core/tools/google_calender_create.py b/core/tools/google_calender_create.py index 5bb9bd0..a1ec247 100644 --- a/core/tools/google_calender_create.py +++ b/core/tools/google_calender_create.py @@ -25,8 +25,8 @@ class create_calendar_event_parameters(BaseModel): summary: str = Field(description="Event title", example="Test Event") location: str = Field(description="Event location", example="Online") description: str = Field(description="Event description", example="This is a test event created by the Google Calendar tool") - start_time: str = Field(description="Event start time in ISO format(YYYY-MM-DDTHH:MM:SSZ), the current time can be collected from current_time_iso_format tool", example="2024-10-16T12:00:00Z") - end_time: str = Field(description="Event end time in ISO format(YYYY-MM-DDTHH:MM:SSZ), can use add_time tool to add for example an hour", example="2024-10-16T15:00:00Z") + start_time: str = Field(description="Event start time in ISO format(YYYY-MM-DDTHH:MM:SS), the current time can be collected from current_time_iso_format tool", example="2024-10-16T12:00:00Z") + end_time: str = Field(description="Event end time in ISO format(YYYY-MM-DDTHH:MM:SS), can use add_time tool to add for example an hour", example="2024-10-16T15:00:00Z") @tool("create_calendar_event",args_schema=create_calendar_event_parameters) # # Remove for testing def create_calendar_event(summary: str, location: str, description: str, start_time: str, end_time: str) -> str: @@ -37,8 +37,8 @@ def create_calendar_event(summary: str, location: str, description: str, start_t summary(string): Event title. location(string): Event location. Default to "Anywhere but not nowhere" if user does not provide a location. description(string): Event description. - start_time(string): Event start time in ISO format(YYYY-MM-DDTHH:MM:SSZ) - end_time(string): Event end time in ISO format(YYYY-MM-DDTHH:MM:SSZ) + start_time(string): Event start time in ISO format(YYYY-MM-DDTHH:MM:SS) + end_time(string): Event end time in ISO format(YYYY-MM-DDTHH:MM:SS) Returns: Confirmation message with event details. @@ -50,11 +50,11 @@ def create_calendar_event(summary: str, location: str, description: str, start_t "description": description, "start": { "dateTime": start_time, - "timeZone": "UTC", + "timeZone": "Europe/Oslo", }, "end": { "dateTime": end_time, - "timeZone": "UTC", + "timeZone": "Europe/Oslo", }, } @@ -69,8 +69,8 @@ def get_tool() -> StructuredTool: summary = "Test Event" location = "Online" description = "This is a test event created by the Google Calendar tool" - start_time = "2024-10-16T12:00:00Z" # Format: YYYY-MM-DDTHH:MM:SSZ - end_time = "2024-10-16T15:00:00Z" # Format: YYYY-MM-DDTHH:MM:SSZ + start_time = "2024-10-25T12:00:00" # Format: YYYY-MM-DDTHH:MM:SS + end_time = "2024-10-25T15:00:00" # Format: YYYY-MM-DDTHH:MM:SS result = create_calendar_event(summary, location, description, start_time, end_time) print(result) \ No newline at end of file From c6a7555f86d6a88ae53af83dd2e014da81563709 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 29 Oct 2024 20:01:17 +0100 Subject: [PATCH 11/62] Feat: added graph edges and test --- core/graphAgent.py | 24 ++++++++++++++---------- core/noder.py | 3 ++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/core/graphAgent.py b/core/graphAgent.py index 8be02ac..ab1201f 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -13,30 +13,34 @@ import asyncio from time import sleep import functools +from noder import jarvis_agent, tool_decider, router, response_generator class Graph: - MAIN_AGENT = "chatbot" def __init__(self): LANGCHAIN_TRACING_V2: str = "true" - self.llm = SimpleAgent.llm - self.llm_with_tools = self.llm.bind_tools(get_tools()) - self.workflow = StateGraph(GraphState) - self.workflow.add_node(self.MAIN_AGENT, self.chatbot) + self.workflow.add_node("jarvis_agent", jarvis_agent) + self.workflow.add_node("use_tool", tool_decider) + self.workflow.add_node("generate", response_generator) self.workflow.add_node("tools", ToolNode(get_tools())) - self.workflow.add_edge(START, self.MAIN_AGENT) - self.workflow.add_edge("tools", self.MAIN_AGENT) + self.workflow.add_edge(START, "jarvis_agent") + self.workflow.add_edge("use_tool", "tools") + self.workflow.add_edge("tools", "jarvis_agent") + self.workflow.add_edge("generate", END) # Defining conditional edges self.workflow.add_conditional_edges( - self.MAIN_AGENT, - tools_condition, - {"tools": "tools", "__end__": END} + "jarvis_agent", + router, + {"generate": "generate", "use_tool": "use_tool"} ) + self.graph = self.workflow.compile() + + with open("graph_node_network.png", 'wb') as f: f.write(self.graph.get_graph().draw_mermaid_png()) diff --git a/core/noder.py b/core/noder.py index c5e4493..5c21006 100644 --- a/core/noder.py +++ b/core/noder.py @@ -1,10 +1,11 @@ from graphstate import GraphState from Agents.simpleagent import SimpleAgent, ToolsAgent -from langchain.prompts import PromptTemplate +from langchain_core.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser from typing import Literal + def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( From f94b1098483b3cbc2e2c3c641c548d14ede4d413 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 31 Oct 2024 19:22:23 +0100 Subject: [PATCH 12/62] fix: typo in noder --- core/noder.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/noder.py b/core/noder.py index 5c21006..d389c29 100644 --- a/core/noder.py +++ b/core/noder.py @@ -4,8 +4,6 @@ from langchain_core.output_parsers import StrOutputParser from typing import Literal - - def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( @@ -80,4 +78,4 @@ def response_generator(state: GraphState): ) chain = prompt | SimpleAgent.llm response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) - return {"message": [response]} \ No newline at end of file + return {"messages": [response]} \ No newline at end of file From 0636869658741743bbc8f4a04a2b7d2f7a7ee735 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 31 Oct 2024 19:23:34 +0100 Subject: [PATCH 13/62] fix: filter stream so response is sent to frontend --- core/graphAgent.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/core/graphAgent.py b/core/graphAgent.py index ab1201f..ea51300 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -77,23 +77,19 @@ async def run(self, user_prompt: str, socketio): # Focuses only on the 'on_chain_stream'-events. # There may be better events to base the response on - if event_type == 'on_chain_stream' and event['name'] == 'LangGraph': - chunk = event['data']['chunk'] + if event_type == 'on_chain_end' and event['name'] == 'LangGraph': + ai_message = event['data']['output']['messages'][-1] - # Filters the stream to only get events by main agent - if self.MAIN_AGENT in chunk: - ai_message = event['data']['chunk'][self.MAIN_AGENT]['messages'][-1] - - if isinstance(ai_message, AIMessage): - if 'tool_calls' in ai_message.additional_kwargs: - tool_call = ai_message.additional_kwargs['tool_calls'][0]['function'] - #tool_call_id = ai_message.additional_kwargs['call_tool'][0]['tool_call_id'] - socketio.emit("tool_call", tool_call) - continue - - socketio.emit("chunk", ai_message.content) - socketio.emit("tokens", ai_message.usage_metadata['total_tokens']) + if isinstance(ai_message, AIMessage): + if 'tool_calls' in ai_message.additional_kwargs: + tool_call = ai_message.additional_kwargs['tool_calls'][0]['function'] + #tool_call_id = ai_message.additional_kwargs['call_tool'][0]['tool_call_id'] + socketio.emit("tool_call", tool_call) continue + + socketio.emit("chunk", ai_message.content) + socketio.emit("tokens", ai_message.usage_metadata['total_tokens']) + continue if event_type == 'on_chain_stream' and event['name'] == 'tools': tool_response = event['data']['chunk']['messages'][-1] From 93d77b942559906f1b4e9c6cd4e3100f12be9f79 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 31 Oct 2024 19:52:02 +0100 Subject: [PATCH 14/62] fix: typo --- core/graphstate.py | 2 +- core/noder.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/graphstate.py b/core/graphstate.py index 8d67608..9238b43 100644 --- a/core/graphstate.py +++ b/core/graphstate.py @@ -8,4 +8,4 @@ class GraphState(TypedDict): # (in this case, it appends messages to the list, rather than overwriting them) messages: Annotated[list, add_messages] data: dict - tool_decision: Literal["use_tools", "generate"] + tool_decision: Literal["use_tool", "generate"] diff --git a/core/noder.py b/core/noder.py index d389c29..6e34f79 100644 --- a/core/noder.py +++ b/core/noder.py @@ -20,14 +20,15 @@ def jarvis_agent(state: GraphState): Data: {data} Your options are the following: - 1. 'use_tools': Call on tools to help solve the users problem + 1. 'use_tool': Call on tools to help solve the users problem 2. 'generate': Generate a response if you have what you need to answer Answer with the option name and nothing else """, ) - chain = prompt | SimpleAgent.llm | StrOutputParser() - response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + chain = prompt | ToolsAgent.agent | StrOutputParser() + response = chain.invoke({ + "messages": state["messages"], "data": state.get("data", {})}) return {"tool_decision": response} def tool_decider(state: GraphState): @@ -54,7 +55,7 @@ def tool_decider(state: GraphState): response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) return {"messages": [response]} -def router(state: GraphState) -> Literal["use_tools", "generate"]: +def router(state: GraphState) -> Literal["use_tool", "generate"]: """Router to determine what to do""" return state["tool_decision"] From 546ac8747bd9122b0c4387d96c1b137cee12ac0a Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 31 Oct 2024 19:52:21 +0100 Subject: [PATCH 15/62] fix: remove init in ToolsAgent --- core/Agents/simpleagent.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/Agents/simpleagent.py b/core/Agents/simpleagent.py index 4bbe43a..96c7451 100644 --- a/core/Agents/simpleagent.py +++ b/core/Agents/simpleagent.py @@ -12,5 +12,4 @@ class SimpleAgent: ) class ToolsAgent: - def __init__(self): - self.agent = SimpleAgent.llm.bind_tools(get_tools()) + agent = SimpleAgent.llm.bind_tools(get_tools()) From 1728d42985268626149af0ba76a92d890e5290b7 Mon Sep 17 00:00:00 2001 From: klungg Date: Sun, 3 Nov 2024 23:57:26 +0100 Subject: [PATCH 16/62] feat: added google calender read tool --- .gitignore | 2 + core/tools/google_calender_create.py | 4 +- core/tools/google_calender_read.py | 91 ++++++++++++++++++++++++++++ core/tools/tools.py | 2 + 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 core/tools/google_calender_read.py diff --git a/.gitignore b/.gitignore index f8b6f1d..82840fa 100644 --- a/.gitignore +++ b/.gitignore @@ -113,6 +113,8 @@ venv/ ENV/ env.bak/ venv.bak/ +#google auth key +audify-433915-326da4ccd68f.json # Spyder project settings .spyderproject diff --git a/core/tools/google_calender_create.py b/core/tools/google_calender_create.py index a1ec247..c6811bc 100644 --- a/core/tools/google_calender_create.py +++ b/core/tools/google_calender_create.py @@ -69,8 +69,8 @@ def get_tool() -> StructuredTool: summary = "Test Event" location = "Online" description = "This is a test event created by the Google Calendar tool" - start_time = "2024-10-25T12:00:00" # Format: YYYY-MM-DDTHH:MM:SS - end_time = "2024-10-25T15:00:00" # Format: YYYY-MM-DDTHH:MM:SS + start_time = "2024-11-04T12:00:00" # Format: YYYY-MM-DDTHH:MM:SS + end_time = "2024-11-04T15:00:00" # Format: YYYY-MM-DDTHH:MM:SS result = create_calendar_event(summary, location, description, start_time, end_time) print(result) \ No newline at end of file diff --git a/core/tools/google_calender_read.py b/core/tools/google_calender_read.py new file mode 100644 index 0000000..5bbd95b --- /dev/null +++ b/core/tools/google_calender_read.py @@ -0,0 +1,91 @@ +import os +from datetime import datetime, timedelta +from dotenv import load_dotenv +from google.oauth2.service_account import Credentials +from googleapiclient.discovery import build +from langchain_core.tools.structured import StructuredTool +from langchain_core.tools import tool +from pydantic import BaseModel, Field + +load_dotenv() + +SCOPES = [ + "https://www.googleapis.com/auth/calendar", + "https://www.googleapis.com/auth/calendar.events", +] + +def get_calendar_service(): + creds = Credentials.from_service_account_file( + os.getenv("GOOGLE_AUTH_KEY"), scopes=SCOPES + ) + service = build("calendar", "v3", credentials=creds) + return service + +class read_calendar_events_parameters(BaseModel): + time_min: str = Field( + description="Start time to fetch events from in ISO format(YYYY-MM-DDTHH:MM:SS), the current time can be collected from current_time_iso_format tool", + example="2024-10-16T00:00:00" + ) + time_max: str = Field( + description="End time to fetch events until in ISO format(YYYY-MM-DDTHH:MM:SS)", + example="2024-10-16T23:59:59" + ) + + +@tool("read_calendar_events", args_schema=read_calendar_events_parameters) +def read_calendar_events(time_min: str, time_max: str) -> str: + """ + Use this tool to read events from the calendar within a specified time range. + + Args: + time_min(string): Start time to fetch events from in ISO format(YYYY-MM-DDTHH:MM:SS) + time_max(string): End time to fetch events until in ISO format(YYYY-MM-DDTHH:MM:SS) + max_results(int): Maximum number of events to return (default: 10) + + Returns: + String containing formatted list of events with their details + """ + service = get_calendar_service() + + events_result = service.events().list( + calendarId=os.getenv("GOOGLE_CALENDAR_ID"), + timeMin=time_min, + timeMax=time_max, + maxResults=10, + singleEvents=True, + orderBy='startTime' + ).execute() + + events = events_result.get('items', []) + + if not events: + return "No events found in the specified time range." + + formatted_events = [] + for event in events: + start = event['start'].get('dateTime', event['start'].get('date')) + end = event['end'].get('dateTime', event['end'].get('date')) + + event_details = ( + f"Event: {event['summary']}\n" + f"Start: {start}\n" + f"End: {end}\n" + f"Location: {event.get('location', 'No location specified')}\n" + f"Description: {event.get('description', 'No description provided')}\n" + f"Link: {event.get('htmlLink', 'No link available')}\n" + ) + formatted_events.append(event_details) + + return "\n".join(formatted_events) + +def get_tool() -> StructuredTool: + return read_calendar_events + +if __name__ == "__main__": + # Example usage + now = datetime.utcnow() + time_min = now.isoformat() + time_max = (now + timedelta(days=7)).isoformat() + + result = read_calendar_events(time_min, time_max) + print(result) \ No newline at end of file diff --git a/core/tools/tools.py b/core/tools/tools.py index 9393e43..c95aba2 100644 --- a/core/tools/tools.py +++ b/core/tools/tools.py @@ -6,6 +6,7 @@ import tools.read_pdf as read_pdf import tools.weather as weather import tools.google_calender_create as create_calender_event +import tools.google_calender_read as read_calender_event import tools.create_time_to_iso as create_time_to_iso_format import tools.current_time_iso as current_time_iso_format import tools.add_time as add_time @@ -20,6 +21,7 @@ def get_tools() -> list[StructuredTool]: tools.append(read_pdf.get_tool()) tools.append(weather.get_tool()) tools.append(create_calender_event.get_tool()) + tools.append(read_calender_event.get_tool()) tools.append(create_time_to_iso_format.get_tool()) tools.append(current_time_iso_format.get_tool()) From 687d93ac7ffadf94110bffd007632bf2657a09a6 Mon Sep 17 00:00:00 2001 From: klungg Date: Tue, 5 Nov 2024 17:46:30 +0100 Subject: [PATCH 17/62] feat: add auto-scroll for agent messages --- core/static/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/static/index.js b/core/static/index.js index c3196e5..3186bd4 100644 --- a/core/static/index.js +++ b/core/static/index.js @@ -75,6 +75,10 @@ async function addStreamedMessage(uuid, messagePart) { // Concat ChatPart on message with uuid element.innerHTML += messagePart; } + + // Add auto-scroll + let chat_history = document.getElementById("chat_history") + chat_history.scrollTop = chat_history.scrollHeight; } addUserMessage = (message) => { From b4b54e422bc3c70fd4cf5b898f5a5fdb00ea9da7 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 18:08:46 +0100 Subject: [PATCH 18/62] fix: add a couple of words of instructions to LLM --- core/noder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/noder.py b/core/noder.py index 6e34f79..4568167 100644 --- a/core/noder.py +++ b/core/noder.py @@ -9,7 +9,8 @@ def jarvis_agent(state: GraphState): prompt = PromptTemplate( template= """ Your job is to determine if you need tools to answer the - users question. + users question and answer with only the name of the option + choose. Here are previous messages: From 75729c4e8a2bbb88865d45ccd0d88f190f06896a Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 5 Nov 2024 18:37:32 +0100 Subject: [PATCH 19/62] Feat: added calender agent --- core/noder.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/core/noder.py b/core/noder.py index 4568167..66eaa4e 100644 --- a/core/noder.py +++ b/core/noder.py @@ -3,7 +3,7 @@ from langchain_core.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser from typing import Literal - +from tools.tools import get_tools def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( @@ -80,4 +80,35 @@ def response_generator(state: GraphState): ) chain = prompt | SimpleAgent.llm response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) - return {"messages": [response]} \ No newline at end of file + return {"messages": [response]} + + + +def calender_agent(state: GraphState): + """Agent that handles all actions in the calender""" + prompt = PromptTemplate( + template= """ + Your job is to handle any calender events that the user requests. + you have acces to the google calender api and can create, delete + + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + See calender events are in: + + Calendere evnts:{calender_events} + + Your options are the following: + to generate a calender event + Answer with the option name and nothing else + """, + ) + chain = prompt | ToolsAgent.agent | StrOutputParser() + response = chain.invoke({ + "messages": state["messages"], "data": state.get("data", {}), "calender_events": get_tools()[]}) + return {"tool_decision": response} From 3b29196ea7af236ee90c2e70a4b3558bd4aa03b7 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 18:37:53 +0100 Subject: [PATCH 20/62] feat: add get-method for perplexity-based tools --- core/tools/tools.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/tools/tools.py b/core/tools/tools.py index 9393e43..41ebc2a 100644 --- a/core/tools/tools.py +++ b/core/tools/tools.py @@ -24,3 +24,10 @@ def get_tools() -> list[StructuredTool]: tools.append(current_time_iso_format.get_tool()) return tools + +def get_perplexity_based_tools() -> list[StructuredTool]: + tools = [] + tools.append(weather.get_tool()) + tools.append(web_search.get_tool()) + + return tools From 56af55958327166711e0adf194c7852d991de8b6 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 29 Oct 2024 20:01:17 +0100 Subject: [PATCH 21/62] Feat: added graph edges and test --- core/graphAgent.py | 24 ++++++++++++++---------- core/noder.py | 3 ++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/core/graphAgent.py b/core/graphAgent.py index 8be02ac..ab1201f 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -13,30 +13,34 @@ import asyncio from time import sleep import functools +from noder import jarvis_agent, tool_decider, router, response_generator class Graph: - MAIN_AGENT = "chatbot" def __init__(self): LANGCHAIN_TRACING_V2: str = "true" - self.llm = SimpleAgent.llm - self.llm_with_tools = self.llm.bind_tools(get_tools()) - self.workflow = StateGraph(GraphState) - self.workflow.add_node(self.MAIN_AGENT, self.chatbot) + self.workflow.add_node("jarvis_agent", jarvis_agent) + self.workflow.add_node("use_tool", tool_decider) + self.workflow.add_node("generate", response_generator) self.workflow.add_node("tools", ToolNode(get_tools())) - self.workflow.add_edge(START, self.MAIN_AGENT) - self.workflow.add_edge("tools", self.MAIN_AGENT) + self.workflow.add_edge(START, "jarvis_agent") + self.workflow.add_edge("use_tool", "tools") + self.workflow.add_edge("tools", "jarvis_agent") + self.workflow.add_edge("generate", END) # Defining conditional edges self.workflow.add_conditional_edges( - self.MAIN_AGENT, - tools_condition, - {"tools": "tools", "__end__": END} + "jarvis_agent", + router, + {"generate": "generate", "use_tool": "use_tool"} ) + self.graph = self.workflow.compile() + + with open("graph_node_network.png", 'wb') as f: f.write(self.graph.get_graph().draw_mermaid_png()) diff --git a/core/noder.py b/core/noder.py index c5e4493..5c21006 100644 --- a/core/noder.py +++ b/core/noder.py @@ -1,10 +1,11 @@ from graphstate import GraphState from Agents.simpleagent import SimpleAgent, ToolsAgent -from langchain.prompts import PromptTemplate +from langchain_core.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser from typing import Literal + def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( From 2d8aa18cbd627b39743ba283e447cad25233619a Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 31 Oct 2024 19:22:23 +0100 Subject: [PATCH 22/62] fix: typo in noder --- core/noder.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/noder.py b/core/noder.py index 5c21006..d389c29 100644 --- a/core/noder.py +++ b/core/noder.py @@ -4,8 +4,6 @@ from langchain_core.output_parsers import StrOutputParser from typing import Literal - - def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( @@ -80,4 +78,4 @@ def response_generator(state: GraphState): ) chain = prompt | SimpleAgent.llm response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) - return {"message": [response]} \ No newline at end of file + return {"messages": [response]} \ No newline at end of file From a01ad30efc76bd3eeceb4f9a2f974c71c3c425e3 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 31 Oct 2024 19:23:34 +0100 Subject: [PATCH 23/62] fix: filter stream so response is sent to frontend --- core/graphAgent.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/core/graphAgent.py b/core/graphAgent.py index ab1201f..ea51300 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -77,23 +77,19 @@ async def run(self, user_prompt: str, socketio): # Focuses only on the 'on_chain_stream'-events. # There may be better events to base the response on - if event_type == 'on_chain_stream' and event['name'] == 'LangGraph': - chunk = event['data']['chunk'] + if event_type == 'on_chain_end' and event['name'] == 'LangGraph': + ai_message = event['data']['output']['messages'][-1] - # Filters the stream to only get events by main agent - if self.MAIN_AGENT in chunk: - ai_message = event['data']['chunk'][self.MAIN_AGENT]['messages'][-1] - - if isinstance(ai_message, AIMessage): - if 'tool_calls' in ai_message.additional_kwargs: - tool_call = ai_message.additional_kwargs['tool_calls'][0]['function'] - #tool_call_id = ai_message.additional_kwargs['call_tool'][0]['tool_call_id'] - socketio.emit("tool_call", tool_call) - continue - - socketio.emit("chunk", ai_message.content) - socketio.emit("tokens", ai_message.usage_metadata['total_tokens']) + if isinstance(ai_message, AIMessage): + if 'tool_calls' in ai_message.additional_kwargs: + tool_call = ai_message.additional_kwargs['tool_calls'][0]['function'] + #tool_call_id = ai_message.additional_kwargs['call_tool'][0]['tool_call_id'] + socketio.emit("tool_call", tool_call) continue + + socketio.emit("chunk", ai_message.content) + socketio.emit("tokens", ai_message.usage_metadata['total_tokens']) + continue if event_type == 'on_chain_stream' and event['name'] == 'tools': tool_response = event['data']['chunk']['messages'][-1] From 9bc73edf916bc9aeaf7bfcee10b078cf7b4fb898 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 31 Oct 2024 19:52:02 +0100 Subject: [PATCH 24/62] fix: typo --- core/graphstate.py | 2 +- core/noder.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/graphstate.py b/core/graphstate.py index 8d67608..9238b43 100644 --- a/core/graphstate.py +++ b/core/graphstate.py @@ -8,4 +8,4 @@ class GraphState(TypedDict): # (in this case, it appends messages to the list, rather than overwriting them) messages: Annotated[list, add_messages] data: dict - tool_decision: Literal["use_tools", "generate"] + tool_decision: Literal["use_tool", "generate"] diff --git a/core/noder.py b/core/noder.py index d389c29..6e34f79 100644 --- a/core/noder.py +++ b/core/noder.py @@ -20,14 +20,15 @@ def jarvis_agent(state: GraphState): Data: {data} Your options are the following: - 1. 'use_tools': Call on tools to help solve the users problem + 1. 'use_tool': Call on tools to help solve the users problem 2. 'generate': Generate a response if you have what you need to answer Answer with the option name and nothing else """, ) - chain = prompt | SimpleAgent.llm | StrOutputParser() - response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + chain = prompt | ToolsAgent.agent | StrOutputParser() + response = chain.invoke({ + "messages": state["messages"], "data": state.get("data", {})}) return {"tool_decision": response} def tool_decider(state: GraphState): @@ -54,7 +55,7 @@ def tool_decider(state: GraphState): response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) return {"messages": [response]} -def router(state: GraphState) -> Literal["use_tools", "generate"]: +def router(state: GraphState) -> Literal["use_tool", "generate"]: """Router to determine what to do""" return state["tool_decision"] From 934a2e5eb97f4a859779d671daabd6315cae30aa Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 31 Oct 2024 19:52:21 +0100 Subject: [PATCH 25/62] fix: remove init in ToolsAgent --- core/Agents/simpleagent.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/Agents/simpleagent.py b/core/Agents/simpleagent.py index 4bbe43a..96c7451 100644 --- a/core/Agents/simpleagent.py +++ b/core/Agents/simpleagent.py @@ -12,5 +12,4 @@ class SimpleAgent: ) class ToolsAgent: - def __init__(self): - self.agent = SimpleAgent.llm.bind_tools(get_tools()) + agent = SimpleAgent.llm.bind_tools(get_tools()) From 67bac29ee2fee2b116ad6de17f36e8d723b5269d Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 18:08:46 +0100 Subject: [PATCH 26/62] fix: add a couple of words of instructions to LLM --- core/noder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/noder.py b/core/noder.py index 6e34f79..4568167 100644 --- a/core/noder.py +++ b/core/noder.py @@ -9,7 +9,8 @@ def jarvis_agent(state: GraphState): prompt = PromptTemplate( template= """ Your job is to determine if you need tools to answer the - users question. + users question and answer with only the name of the option + choose. Here are previous messages: From feaa1acf2277a80c8d0a2645e9ef69848e057ee6 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 18:37:53 +0100 Subject: [PATCH 27/62] feat: add get-method for perplexity-based tools --- core/tools/tools.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/tools/tools.py b/core/tools/tools.py index c95aba2..00699d5 100644 --- a/core/tools/tools.py +++ b/core/tools/tools.py @@ -26,3 +26,10 @@ def get_tools() -> list[StructuredTool]: tools.append(current_time_iso_format.get_tool()) return tools + +def get_perplexity_based_tools() -> list[StructuredTool]: + tools = [] + tools.append(weather.get_tool()) + tools.append(web_search.get_tool()) + + return tools From 4ba54886f4e6781f469917d21004d3c2088c0805 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 5 Nov 2024 18:37:32 +0100 Subject: [PATCH 28/62] Feat: added calender agent --- core/noder.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/core/noder.py b/core/noder.py index 4568167..66eaa4e 100644 --- a/core/noder.py +++ b/core/noder.py @@ -3,7 +3,7 @@ from langchain_core.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser from typing import Literal - +from tools.tools import get_tools def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( @@ -80,4 +80,35 @@ def response_generator(state: GraphState): ) chain = prompt | SimpleAgent.llm response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) - return {"messages": [response]} \ No newline at end of file + return {"messages": [response]} + + + +def calender_agent(state: GraphState): + """Agent that handles all actions in the calender""" + prompt = PromptTemplate( + template= """ + Your job is to handle any calender events that the user requests. + you have acces to the google calender api and can create, delete + + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + See calender events are in: + + Calendere evnts:{calender_events} + + Your options are the following: + to generate a calender event + Answer with the option name and nothing else + """, + ) + chain = prompt | ToolsAgent.agent | StrOutputParser() + response = chain.invoke({ + "messages": state["messages"], "data": state.get("data", {}), "calender_events": get_tools()[]}) + return {"tool_decision": response} From f8246416d92f7fe870d08b6e8572c15ad9a934e5 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 5 Nov 2024 19:11:14 +0100 Subject: [PATCH 29/62] Refactro: Moved calender_agent --- core/noder.py | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/core/noder.py b/core/noder.py index 66eaa4e..b28ee9f 100644 --- a/core/noder.py +++ b/core/noder.py @@ -4,6 +4,7 @@ from langchain_core.output_parsers import StrOutputParser from typing import Literal from tools.tools import get_tools + def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( @@ -83,32 +84,3 @@ def response_generator(state: GraphState): return {"messages": [response]} - -def calender_agent(state: GraphState): - """Agent that handles all actions in the calender""" - prompt = PromptTemplate( - template= """ - Your job is to handle any calender events that the user requests. - you have acces to the google calender api and can create, delete - - Here are previous messages: - - Message: {messages} - - Data currently accumulated: - - Data: {data} - - See calender events are in: - - Calendere evnts:{calender_events} - - Your options are the following: - to generate a calender event - Answer with the option name and nothing else - """, - ) - chain = prompt | ToolsAgent.agent | StrOutputParser() - response = chain.invoke({ - "messages": state["messages"], "data": state.get("data", {}), "calender_events": get_tools()[]}) - return {"tool_decision": response} From 88932f5e73f72dcf794177e1db4be6ad5e566106 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 19:11:14 +0100 Subject: [PATCH 30/62] feat: add agent_decision to GraphState --- core/graphstate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/graphstate.py b/core/graphstate.py index 9238b43..8f018c4 100644 --- a/core/graphstate.py +++ b/core/graphstate.py @@ -9,3 +9,4 @@ class GraphState(TypedDict): messages: Annotated[list, add_messages] data: dict tool_decision: Literal["use_tool", "generate"] + agent_decision: Literal["perplexity", "calendar", "other"] From b6cbe4e24dee4af1cacdb93d8ed0697d4dd1cf3c Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 5 Nov 2024 19:11:40 +0100 Subject: [PATCH 31/62] Feat: created calender agent node and calnder based tools. --- core/calendar.py | 41 +++++++++++++++++++++++++++++++++++++++++ core/tools/tools.py | 7 +++++++ 2 files changed, 48 insertions(+) create mode 100644 core/calendar.py diff --git a/core/calendar.py b/core/calendar.py new file mode 100644 index 0000000..d7bf6f0 --- /dev/null +++ b/core/calendar.py @@ -0,0 +1,41 @@ +from openai import OpenAI +import dotenv +import config +from graphstate import GraphState +from Agents.simpleagent import SimpleAgent, ToolsAgent +from langchain_core.prompts import PromptTemplate +from langchain_core.output_parsers import StrOutputParser +from typing import Literal +from tools.tools import calender_based_tools +dotenv.load_dotenv() + + +def calender_agent(state: GraphState): + """Agent that handles all actions in the calender""" + prompt = PromptTemplate( + template= """ + Your job is to handle any calender tasks that the user requests. + you have acces to the google calender api and can create, delete + + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + See calender events are in: + + Your options are the following: + 1. "read_calendar_continue": Read calender events and continue + 2. "read_calendar_loop"Read: calender event and loop back to this norde + 3. "create_calender_event: create calender event and continue + + Answer with the option name and nothing else! + """, + ) + chain = prompt | SimpleAgent.llm.bind_tools(calender_based_tools()) | StrOutputParser() + response = chain.invoke({ + "messages": state["messages"], "data": state.get("data", {})}) + return {"tool_decision": response} diff --git a/core/tools/tools.py b/core/tools/tools.py index 00699d5..32218c1 100644 --- a/core/tools/tools.py +++ b/core/tools/tools.py @@ -33,3 +33,10 @@ def get_perplexity_based_tools() -> list[StructuredTool]: tools.append(web_search.get_tool()) return tools + +def calender_based_tools() -> list[StructuredTool]: + tools = [] + tools.append(create_calender_event.get_tool()) + tools.append(read_calender_event.get_tool()) + tools.append(current_time_iso_format.get_tool()) + tools.append(create_time_to_iso_format.get_tool()) From e945a449fdcb5cfc7ea866a312e498db16b159d6 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 19:12:10 +0100 Subject: [PATCH 32/62] feat: add perplexity_agent in noder.py --- core/noder.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/core/noder.py b/core/noder.py index 66eaa4e..a62a50c 100644 --- a/core/noder.py +++ b/core/noder.py @@ -3,7 +3,8 @@ from langchain_core.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser from typing import Literal -from tools.tools import get_tools +from tools.tools import get_tools, get_perplexity_based_tools + def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" prompt = PromptTemplate( @@ -112,3 +113,28 @@ def calender_agent(state: GraphState): response = chain.invoke({ "messages": state["messages"], "data": state.get("data", {}), "calender_events": get_tools()[]}) return {"tool_decision": response} + +def perplexity_agent(state: GraphState): + """Agent that handles tools using the perplexity api""" + prompt = PromptTemplate( + template= """ + Your job is to create tool_calls to tools using the perplexity API. + The tool or tools you decide + to call should help answer the users question. + + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + Please decide what tools to use to help the user and + add them to the additional_kwargs in the AI_message + """, + ) + chain = prompt | SimpleAgent.llm.bind_tools(get_perplexity_based_tools()) + response = chain.invoke({ + "messages": state["messages"], "data": state.get("data", {})}) + return {"messages": [response]} From a6727cb2cdff26aab497e5802850c02bf4fb0b90 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 19:12:43 +0100 Subject: [PATCH 33/62] feat: add a router for agent_decision --- core/noder.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/core/noder.py b/core/noder.py index a62a50c..ec425ea 100644 --- a/core/noder.py +++ b/core/noder.py @@ -33,12 +33,12 @@ def jarvis_agent(state: GraphState): "messages": state["messages"], "data": state.get("data", {})}) return {"tool_decision": response} -def tool_decider(state: GraphState): +def tool_agent_decider(state: GraphState): """Agent to determine what tool to use""" prompt = PromptTemplate( template= """ - Your job is to create tool_calls. The tool or tools you decide - to call should help answer the users question. + Your job is to decide which Agent that should answer the users question. + Answer only with the name of the agent you want to use. Here are the previous messages: @@ -48,12 +48,14 @@ def tool_decider(state: GraphState): Data: {data} - Please decide what tools to use to help the user and - add them to the additional_kwargs in the AI_message + Your options for agents are the following: + 1. 'perplexity': This agent has access to tools that use the perplexity API. + These tools are the following: + 2. 'calendar': """, ) - chain = prompt | ToolsAgent.agent + chain = prompt | SimpleAgent.llm response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) return {"messages": [response]} @@ -61,6 +63,10 @@ def router(state: GraphState) -> Literal["use_tool", "generate"]: """Router to determine what to do""" return state["tool_decision"] +def tool_agent_router(state: GraphState) -> Literal["placeholder"]: + """Router to determine which agent to use""" + return state["agent_decision"] + def response_generator(state: GraphState): """Agent that generates a response to user based on user request and possible data from tool calls""" @@ -111,7 +117,7 @@ def calender_agent(state: GraphState): ) chain = prompt | ToolsAgent.agent | StrOutputParser() response = chain.invoke({ - "messages": state["messages"], "data": state.get("data", {}), "calender_events": get_tools()[]}) + "messages": state["messages"], "data": state.get("data", {}), "calender_events": get_tools()}) return {"tool_decision": response} def perplexity_agent(state: GraphState): From f8db811433dff8fda1c51e1a8c1f6c957bed9b3f Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 19:35:34 +0100 Subject: [PATCH 34/62] feat: add get_other_tools in tools.py --- core/tools/tools.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/tools/tools.py b/core/tools/tools.py index 32218c1..14b3a75 100644 --- a/core/tools/tools.py +++ b/core/tools/tools.py @@ -40,3 +40,14 @@ def calender_based_tools() -> list[StructuredTool]: tools.append(read_calender_event.get_tool()) tools.append(current_time_iso_format.get_tool()) tools.append(create_time_to_iso_format.get_tool()) + + return tools + +def get_other_tools() -> list[StructuredTool]: + tools = [] + tools.append(add_tool.get_tool()) + tools.append(find_files.get_tool()) + tools.append(read_file.get_tool()) + tools.append(read_pdf.get_tool()) + + return tools \ No newline at end of file From 2e15b64810ce72a7bb463ddffbe62036e03e1cb0 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 19:38:27 +0100 Subject: [PATCH 35/62] feat: finish tool_agent_decider options --- core/noder.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/core/noder.py b/core/noder.py index 69dfc9c..41d1a18 100644 --- a/core/noder.py +++ b/core/noder.py @@ -3,7 +3,7 @@ from langchain_core.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser from typing import Literal -from tools.tools import get_tools, get_perplexity_based_tools +from tools.tools import get_tools, get_perplexity_based_tools, calender_based_tools, get_other_tools from tools.tools import get_tools @@ -52,20 +52,28 @@ def tool_agent_decider(state: GraphState): Your options for agents are the following: 1. 'perplexity': This agent has access to tools that use the perplexity API. - These tools are the following: - 2. 'calendar': + These tools are the following: {perplexity_tools} + 2. 'calender': This agent has access to calender tools + These tools are the following: {calender_tools} + 3. 'other': Other tools available: {other_tools} """, ) chain = prompt | SimpleAgent.llm - response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + response = chain.invoke({ + "messages": state["messages"], + "data": state.get("data", {}), + "perplexity_tools": get_perplexity_based_tools(), + "calender_tools": calender_based_tools(), + "other_tools": get_other_tools() + }) return {"messages": [response]} def router(state: GraphState) -> Literal["use_tool", "generate"]: """Router to determine what to do""" return state["tool_decision"] -def tool_agent_router(state: GraphState) -> Literal["placeholder"]: +def tool_agent_router(state: GraphState) -> Literal["perplexity", "calendar", "other"]: """Router to determine which agent to use""" return state["agent_decision"] From 32f49fc777f2eff5327156dbc5de993879dd15d4 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 5 Nov 2024 19:45:57 +0100 Subject: [PATCH 36/62] Recomp: moved calender agent and calender tool decider to noder.py, decider too --- core/graphAgent.py | 5 ++-- core/noder.py | 57 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/core/graphAgent.py b/core/graphAgent.py index ea51300..de0eb49 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -13,7 +13,7 @@ import asyncio from time import sleep import functools -from noder import jarvis_agent, tool_decider, router, response_generator +from noder import jarvis_agent, tool_agent_decider, router, response_generator class Graph: def __init__(self): @@ -22,9 +22,10 @@ def __init__(self): self.workflow = StateGraph(GraphState) self.workflow.add_node("jarvis_agent", jarvis_agent) - self.workflow.add_node("use_tool", tool_decider) + self.workflow.add_node("use_tool", tool_agent_decider) self.workflow.add_node("generate", response_generator) self.workflow.add_node("tools", ToolNode(get_tools())) + self.workflow.add_node("calendar_tool", ToolNode(get_tools())) self.workflow.add_edge(START, "jarvis_agent") self.workflow.add_edge("use_tool", "tools") diff --git a/core/noder.py b/core/noder.py index 41d1a18..8e9f5ca 100644 --- a/core/noder.py +++ b/core/noder.py @@ -5,7 +5,6 @@ from typing import Literal from tools.tools import get_tools, get_perplexity_based_tools, calender_based_tools, get_other_tools -from tools.tools import get_tools def jarvis_agent(state: GraphState): """Agent to determine how to answer user question""" @@ -123,3 +122,59 @@ def perplexity_agent(state: GraphState): response = chain.invoke({ "messages": state["messages"], "data": state.get("data", {})}) return {"messages": [response]} + + +def calender_desicion_agent(state: GraphState): + """Agent that decides what to do with the calender""" + prompt = PromptTemplate( + template= """ + Your job is to determine if you wich calender related tools you need to answer the + jarvis agents question and answer with only the name of the option + choose. + + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + Your options are the following: + 1. 'use_calendar_tool': Call on calendar_tools to help solve the users problem + 2. 'return_to_jarvis': go back to the jarvis agent + + Answer with the option name and nothing else. + """, + ) + chain = prompt | ToolsAgent.agent | StrOutputParser() + response = chain.invoke({ + "messages": state["messages"], "data": state.get("data", {})}) + return {"tool_decision": response} + +def calender_tool_decider(state: GraphState): + """Agent that handles all actions in the calender""" + prompt = PromptTemplate( + template= """ + Your job is to create and handle the tool calls needed to create and read calender events. + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + + Please decide what tools to use to help the user and + add them to the additional_kwargs in the AI_message + """, + ) + chain = prompt | SimpleAgent.llm.bind_tools(calender_based_tools()) | StrOutputParser() + response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) + return {"messages": response} + + +def calendar_router(state: GraphState) -> Literal["use_calendar_tool", "return_to_jarvis"]: + """Router to determine what to do""" + return state["tool_decision"] \ No newline at end of file From d5a439865cfa0eb4d2cd851b9ff3c463dce82785 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 5 Nov 2024 19:46:44 +0100 Subject: [PATCH 37/62] Delete: calendar.py --- core/calendar.py | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 core/calendar.py diff --git a/core/calendar.py b/core/calendar.py deleted file mode 100644 index d7bf6f0..0000000 --- a/core/calendar.py +++ /dev/null @@ -1,41 +0,0 @@ -from openai import OpenAI -import dotenv -import config -from graphstate import GraphState -from Agents.simpleagent import SimpleAgent, ToolsAgent -from langchain_core.prompts import PromptTemplate -from langchain_core.output_parsers import StrOutputParser -from typing import Literal -from tools.tools import calender_based_tools -dotenv.load_dotenv() - - -def calender_agent(state: GraphState): - """Agent that handles all actions in the calender""" - prompt = PromptTemplate( - template= """ - Your job is to handle any calender tasks that the user requests. - you have acces to the google calender api and can create, delete - - Here are previous messages: - - Message: {messages} - - Data currently accumulated: - - Data: {data} - - See calender events are in: - - Your options are the following: - 1. "read_calendar_continue": Read calender events and continue - 2. "read_calendar_loop"Read: calender event and loop back to this norde - 3. "create_calender_event: create calender event and continue - - Answer with the option name and nothing else! - """, - ) - chain = prompt | SimpleAgent.llm.bind_tools(calender_based_tools()) | StrOutputParser() - response = chain.invoke({ - "messages": state["messages"], "data": state.get("data", {})}) - return {"tool_decision": response} From 2f18d2428280c13358c0538e8bfb42a3aa89d69a Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Tue, 5 Nov 2024 19:55:07 +0100 Subject: [PATCH 38/62] feat: add other_agent to noder.py --- core/noder.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/core/noder.py b/core/noder.py index 8e9f5ca..1a3dbb5 100644 --- a/core/noder.py +++ b/core/noder.py @@ -177,4 +177,29 @@ def calender_tool_decider(state: GraphState): def calendar_router(state: GraphState) -> Literal["use_calendar_tool", "return_to_jarvis"]: """Router to determine what to do""" - return state["tool_decision"] \ No newline at end of file + return state["tool_decision"] + +def other_agent(state: GraphState): + """Agent that handles other tools available in the system""" + prompt = PromptTemplate( + template= """ + Your job is to create tool_calls to tools. + The tool or tools you decide + to call should help answer the users question. + + Here are previous messages: + + Message: {messages} + + Data currently accumulated: + + Data: {data} + + Please decide what tools to use to help the user and + add them to the additional_kwargs in the AI_message + """, + ) + chain = prompt | SimpleAgent.llm.bind_tools(get_other_tools()) + response = chain.invoke({ + "messages": state["messages"], "data": state.get("data", {})}) + return {"messages": [response]} \ No newline at end of file From cd4810ac91956a975bb12a43ae2d03a1d8063e37 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Tue, 5 Nov 2024 20:49:38 +0100 Subject: [PATCH 39/62] Recomp: new agent architecture and fixes --- core/graphAgent.py | 30 +++++++++++++++++++++++++----- core/noder.py | 17 +++++++++++------ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/core/graphAgent.py b/core/graphAgent.py index de0eb49..34a27a5 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -13,7 +13,7 @@ import asyncio from time import sleep import functools -from noder import jarvis_agent, tool_agent_decider, router, response_generator +from noder import * class Graph: def __init__(self): @@ -22,13 +22,21 @@ def __init__(self): self.workflow = StateGraph(GraphState) self.workflow.add_node("jarvis_agent", jarvis_agent) - self.workflow.add_node("use_tool", tool_agent_decider) + self.workflow.add_node("agent_decider", tool_agent_decider) self.workflow.add_node("generate", response_generator) self.workflow.add_node("tools", ToolNode(get_tools())) - self.workflow.add_node("calendar_tool", ToolNode(get_tools())) + + self.workflow.add_node("perplexity_agent", perplexity_agent) + self.workflow.add_node("calender_tool", ToolNode(get_tools())) + self.workflow.add_node("use_calender_tool", calender_tool_decider) + self.workflow.add_node("calender_decider", calender_desicion_agent) + self.workflow.add_node("other_agent", other_agent) self.workflow.add_edge(START, "jarvis_agent") - self.workflow.add_edge("use_tool", "tools") + self.workflow.add_edge("perplexity_agent", "tools") + self.workflow.add_edge("use_calender_tool", "calender_tool") + self.workflow.add_edge("calender_tool", "calender_decider") + self.workflow.add_edge("other_agent", "tools") self.workflow.add_edge("tools", "jarvis_agent") self.workflow.add_edge("generate", END) @@ -36,7 +44,19 @@ def __init__(self): self.workflow.add_conditional_edges( "jarvis_agent", router, - {"generate": "generate", "use_tool": "use_tool"} + {"generate": "generate", "use_tool": "agent_decider"} + ) + + self.workflow.add_conditional_edges( + "agent_decider", + agent_router, + {"perplexity": "perplexity_agent", "calender": "calender_decider", "other": "other_agent"} + ) + + self.workflow.add_conditional_edges( + "calender_decider", + calender_router, + {"use_calender_tool": "use_calender_tool", "return_to_jarvis": "jarvis_agent"} ) self.graph = self.workflow.compile() diff --git a/core/noder.py b/core/noder.py index 1a3dbb5..2b26b96 100644 --- a/core/noder.py +++ b/core/noder.py @@ -66,13 +66,13 @@ def tool_agent_decider(state: GraphState): "calender_tools": calender_based_tools(), "other_tools": get_other_tools() }) - return {"messages": [response]} + return {"agent_decision": [response]} -def router(state: GraphState) -> Literal["use_tool", "generate"]: +def router(state: GraphState) -> Literal["generate", "use_tool"]: """Router to determine what to do""" return state["tool_decision"] -def tool_agent_router(state: GraphState) -> Literal["perplexity", "calendar", "other"]: +def tool_agent_router(state: GraphState) -> Literal["perplexity", "calender", "other"]: """Router to determine which agent to use""" return state["agent_decision"] @@ -98,6 +98,10 @@ def response_generator(state: GraphState): response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) return {"messages": [response]} +def agent_router(state: GraphState) -> Literal["perplexity", "calender", "other"]: + """Router to determine what agent to use""" + return state["agent_decision"] + def perplexity_agent(state: GraphState): """Agent that handles tools using the perplexity api""" prompt = PromptTemplate( @@ -141,7 +145,7 @@ def calender_desicion_agent(state: GraphState): Data: {data} Your options are the following: - 1. 'use_calendar_tool': Call on calendar_tools to help solve the users problem + 1. 'use_calender_tool': Call on calender_tools to help solve the users problem 2. 'return_to_jarvis': go back to the jarvis agent Answer with the option name and nothing else. @@ -156,7 +160,8 @@ def calender_tool_decider(state: GraphState): """Agent that handles all actions in the calender""" prompt = PromptTemplate( template= """ - Your job is to create and handle the tool calls needed to create and read calender events. + Your job is to create and handle the tool calls needed to create and read calender events.¨ + You will based on previous messages and data decide what tools to use. and create the tool calls. Here are previous messages: Message: {messages} @@ -175,7 +180,7 @@ def calender_tool_decider(state: GraphState): return {"messages": response} -def calendar_router(state: GraphState) -> Literal["use_calendar_tool", "return_to_jarvis"]: +def calender_router(state: GraphState) -> Literal["use_calender_tool", "return_to_jarvis"]: """Router to determine what to do""" return state["tool_decision"] From 6be9ee4cef843c3ba91251e07d9ca295bb997431 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 7 Nov 2024 18:27:27 +0100 Subject: [PATCH 40/62] feat: add function to get all chat messages currently on the website --- core/static/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/static/index.js b/core/static/index.js index 3186bd4..4b78eb5 100644 --- a/core/static/index.js +++ b/core/static/index.js @@ -63,6 +63,17 @@ let html = /*html*/`
${message}
`; document.getElementById('chat_history').innerHTML += html; + const messages = getAllChatMessages(); + console.log(messages); +} + +async function getAllChatMessages() { + const chatMessages = document.querySelectorAll('.chat_message'); + const messagesList = []; + chatMessages.forEach((element) => { + messagesList.push(element.textContent.trim()); + }); + return messagesList; } async function addStreamedMessage(uuid, messagePart) { From f8548d021b03f10014537aef51e815a26da86aaf Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Thu, 7 Nov 2024 18:52:36 +0100 Subject: [PATCH 41/62] Refactor: agent works - calendar --- core/graphAgent.py | 22 ++++---- core/graphstate.py | 2 + core/models.py | 1 + core/noder.py | 50 +++++++++---------- ...er_create.py => google_calendar_create.py} | 0 ...lender_read.py => google_calendar_read.py} | 18 +++---- core/tools/tools.py | 14 +++--- 7 files changed, 53 insertions(+), 54 deletions(-) rename core/tools/{google_calender_create.py => google_calendar_create.py} (100%) rename core/tools/{google_calender_read.py => google_calendar_read.py} (85%) diff --git a/core/graphAgent.py b/core/graphAgent.py index 34a27a5..f5f62ff 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -27,15 +27,15 @@ def __init__(self): self.workflow.add_node("tools", ToolNode(get_tools())) self.workflow.add_node("perplexity_agent", perplexity_agent) - self.workflow.add_node("calender_tool", ToolNode(get_tools())) - self.workflow.add_node("use_calender_tool", calender_tool_decider) - self.workflow.add_node("calender_decider", calender_desicion_agent) + self.workflow.add_node("calendar_tool", ToolNode(get_tools())) + self.workflow.add_node("use_calendar_tool", calendar_tool_decider) + self.workflow.add_node("calendar_decider", calendar_desicion_agent) self.workflow.add_node("other_agent", other_agent) self.workflow.add_edge(START, "jarvis_agent") self.workflow.add_edge("perplexity_agent", "tools") - self.workflow.add_edge("use_calender_tool", "calender_tool") - self.workflow.add_edge("calender_tool", "calender_decider") + self.workflow.add_edge("use_calendar_tool", "calendar_tool") + self.workflow.add_edge("calendar_tool", "calendar_decider") self.workflow.add_edge("other_agent", "tools") self.workflow.add_edge("tools", "jarvis_agent") self.workflow.add_edge("generate", END) @@ -50,19 +50,18 @@ def __init__(self): self.workflow.add_conditional_edges( "agent_decider", agent_router, - {"perplexity": "perplexity_agent", "calender": "calender_decider", "other": "other_agent"} + {"perplexity": "perplexity_agent", "calendar": "calendar_decider", "other": "other_agent"} ) self.workflow.add_conditional_edges( - "calender_decider", - calender_router, - {"use_calender_tool": "use_calender_tool", "return_to_jarvis": "jarvis_agent"} + "calendar_decider", + calendar_router, + {"use_calendar_tool": "use_calendar_tool", "return_to_jarvis": "jarvis_agent"} ) self.graph = self.workflow.compile() - with open("graph_node_network.png", 'wb') as f: f.write(self.graph.get_graph().draw_mermaid_png()) @@ -122,4 +121,5 @@ async def run(self, user_prompt: str, socketio): return "success" except Exception as e: print(e) - return "error" \ No newline at end of file + return "error" + diff --git a/core/graphstate.py b/core/graphstate.py index 8f018c4..dd1ea16 100644 --- a/core/graphstate.py +++ b/core/graphstate.py @@ -10,3 +10,5 @@ class GraphState(TypedDict): data: dict tool_decision: Literal["use_tool", "generate"] agent_decision: Literal["perplexity", "calendar", "other"] + calendar_decision: Literal["use_calendar_tool", "return_to_jarvis"] + diff --git a/core/models.py b/core/models.py index 8e3f151..c97fa11 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,4 @@ class Model: gpt_4o = "gpt-4o-mini" + gpt_4oo = "gpt-4o" gpt_35 = "gpt-3.5-turbo" \ No newline at end of file diff --git a/core/noder.py b/core/noder.py index 2b26b96..a81db43 100644 --- a/core/noder.py +++ b/core/noder.py @@ -3,7 +3,7 @@ from langchain_core.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser from typing import Literal -from tools.tools import get_tools, get_perplexity_based_tools, calender_based_tools, get_other_tools +from tools.tools import get_tools, get_perplexity_based_tools, calendar_based_tools, get_other_tools def jarvis_agent(state: GraphState): @@ -23,8 +23,8 @@ def jarvis_agent(state: GraphState): Data: {data} Your options are the following: - 1. 'use_tool': Call on tools to help solve the users problem - 2. 'generate': Generate a response if you have what you need to answer + - 'use_tool': Call on tools to help solve the users problem + - 'generate': Generate a response if you have what you need to answer Answer with the option name and nothing else """, @@ -50,20 +50,23 @@ def tool_agent_decider(state: GraphState): Data: {data} Your options for agents are the following: - 1. 'perplexity': This agent has access to tools that use the perplexity API. + - 'perplexity': This agent has access to tools that use the perplexity API. These tools are the following: {perplexity_tools} - 2. 'calender': This agent has access to calender tools - These tools are the following: {calender_tools} - 3. 'other': Other tools available: {other_tools} + - 'calendar': This agent has access to calendar tools + These tools are the following: {calendar_tools} + - 'other': Other tools available: {other_tools} + + + Answer with the option name and nothing else """, ) - chain = prompt | SimpleAgent.llm + chain = prompt | SimpleAgent.llm | StrOutputParser() response = chain.invoke({ "messages": state["messages"], "data": state.get("data", {}), "perplexity_tools": get_perplexity_based_tools(), - "calender_tools": calender_based_tools(), + "calendar_tools": calendar_based_tools(), "other_tools": get_other_tools() }) return {"agent_decision": [response]} @@ -72,7 +75,7 @@ def router(state: GraphState) -> Literal["generate", "use_tool"]: """Router to determine what to do""" return state["tool_decision"] -def tool_agent_router(state: GraphState) -> Literal["perplexity", "calender", "other"]: +def agent_router(state: GraphState) -> Literal["perplexity", "calendar", "other"]: """Router to determine which agent to use""" return state["agent_decision"] @@ -98,9 +101,6 @@ def response_generator(state: GraphState): response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) return {"messages": [response]} -def agent_router(state: GraphState) -> Literal["perplexity", "calender", "other"]: - """Router to determine what agent to use""" - return state["agent_decision"] def perplexity_agent(state: GraphState): """Agent that handles tools using the perplexity api""" @@ -128,11 +128,11 @@ def perplexity_agent(state: GraphState): return {"messages": [response]} -def calender_desicion_agent(state: GraphState): - """Agent that decides what to do with the calender""" +def calendar_desicion_agent(state: GraphState): + """Agent that decides what to do with the calendar""" prompt = PromptTemplate( template= """ - Your job is to determine if you wich calender related tools you need to answer the + Your job is to determine if you wich calendar related tools you need to answer the jarvis agents question and answer with only the name of the option choose. @@ -145,8 +145,8 @@ def calender_desicion_agent(state: GraphState): Data: {data} Your options are the following: - 1. 'use_calender_tool': Call on calender_tools to help solve the users problem - 2. 'return_to_jarvis': go back to the jarvis agent + - 'use_calendar_tool': Call on calendar_tools to help solve the users problem + - 'return_to_jarvis': go back to the jarvis agent Answer with the option name and nothing else. """, @@ -154,13 +154,13 @@ def calender_desicion_agent(state: GraphState): chain = prompt | ToolsAgent.agent | StrOutputParser() response = chain.invoke({ "messages": state["messages"], "data": state.get("data", {})}) - return {"tool_decision": response} + return {"calendar_decision": response} -def calender_tool_decider(state: GraphState): - """Agent that handles all actions in the calender""" +def calendar_tool_decider(state: GraphState): + """Agent that handles all actions in the calendar""" prompt = PromptTemplate( template= """ - Your job is to create and handle the tool calls needed to create and read calender events.¨ + Your job is to create and handle the tool calls needed to create and read calendar events.¨ You will based on previous messages and data decide what tools to use. and create the tool calls. Here are previous messages: @@ -175,14 +175,14 @@ def calender_tool_decider(state: GraphState): add them to the additional_kwargs in the AI_message """, ) - chain = prompt | SimpleAgent.llm.bind_tools(calender_based_tools()) | StrOutputParser() + chain = prompt | SimpleAgent.llm.bind_tools(calendar_based_tools()) response = chain.invoke({"messages": state["messages"], "data": state.get("data", {})}) return {"messages": response} -def calender_router(state: GraphState) -> Literal["use_calender_tool", "return_to_jarvis"]: +def calendar_router(state: GraphState) -> Literal["use_calendar_tool", "return_to_jarvis"]: """Router to determine what to do""" - return state["tool_decision"] + return state["calendar_decision"] def other_agent(state: GraphState): """Agent that handles other tools available in the system""" diff --git a/core/tools/google_calender_create.py b/core/tools/google_calendar_create.py similarity index 100% rename from core/tools/google_calender_create.py rename to core/tools/google_calendar_create.py diff --git a/core/tools/google_calender_read.py b/core/tools/google_calendar_read.py similarity index 85% rename from core/tools/google_calender_read.py rename to core/tools/google_calendar_read.py index 5bbd95b..5fc0e18 100644 --- a/core/tools/google_calender_read.py +++ b/core/tools/google_calendar_read.py @@ -29,21 +29,17 @@ class read_calendar_events_parameters(BaseModel): time_max: str = Field( description="End time to fetch events until in ISO format(YYYY-MM-DDTHH:MM:SS)", example="2024-10-16T23:59:59" + , + ) + maxResults: int = Field( + description="Maximum number of events to fetch", + example=10 ) - @tool("read_calendar_events", args_schema=read_calendar_events_parameters) -def read_calendar_events(time_min: str, time_max: str) -> str: +def read_calendar_events(time_min: str, time_max: str, maxResults: int = 10) -> str: """ Use this tool to read events from the calendar within a specified time range. - - Args: - time_min(string): Start time to fetch events from in ISO format(YYYY-MM-DDTHH:MM:SS) - time_max(string): End time to fetch events until in ISO format(YYYY-MM-DDTHH:MM:SS) - max_results(int): Maximum number of events to return (default: 10) - - Returns: - String containing formatted list of events with their details """ service = get_calendar_service() @@ -51,7 +47,7 @@ def read_calendar_events(time_min: str, time_max: str) -> str: calendarId=os.getenv("GOOGLE_CALENDAR_ID"), timeMin=time_min, timeMax=time_max, - maxResults=10, + maxResults=maxResults, singleEvents=True, orderBy='startTime' ).execute() diff --git a/core/tools/tools.py b/core/tools/tools.py index 14b3a75..84d35bb 100644 --- a/core/tools/tools.py +++ b/core/tools/tools.py @@ -5,8 +5,8 @@ import tools.read_file as read_file import tools.read_pdf as read_pdf import tools.weather as weather -import tools.google_calender_create as create_calender_event -import tools.google_calender_read as read_calender_event +import tools.google_calendar_create as create_calendar_event +import tools.google_calendar_read as read_calendar_event import tools.create_time_to_iso as create_time_to_iso_format import tools.current_time_iso as current_time_iso_format import tools.add_time as add_time @@ -20,8 +20,8 @@ def get_tools() -> list[StructuredTool]: tools.append(read_file.get_tool()) tools.append(read_pdf.get_tool()) tools.append(weather.get_tool()) - tools.append(create_calender_event.get_tool()) - tools.append(read_calender_event.get_tool()) + tools.append(create_calendar_event.get_tool()) + tools.append(read_calendar_event.get_tool()) tools.append(create_time_to_iso_format.get_tool()) tools.append(current_time_iso_format.get_tool()) @@ -34,10 +34,10 @@ def get_perplexity_based_tools() -> list[StructuredTool]: return tools -def calender_based_tools() -> list[StructuredTool]: +def calendar_based_tools() -> list[StructuredTool]: tools = [] - tools.append(create_calender_event.get_tool()) - tools.append(read_calender_event.get_tool()) + tools.append(create_calendar_event.get_tool()) + tools.append(read_calendar_event.get_tool()) tools.append(current_time_iso_format.get_tool()) tools.append(create_time_to_iso_format.get_tool()) From 238b2ae6ca455200a23873e7bfde3ab25d9e761d Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Thu, 7 Nov 2024 19:20:17 +0100 Subject: [PATCH 42/62] Fix: added there should not be any ' or " in answer to routers --- core/noder.py | 12 ++++++------ core/tools/google_calendar_read.py | 24 +++++++++++++++++++++++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/core/noder.py b/core/noder.py index a81db43..455f51a 100644 --- a/core/noder.py +++ b/core/noder.py @@ -26,7 +26,7 @@ def jarvis_agent(state: GraphState): - 'use_tool': Call on tools to help solve the users problem - 'generate': Generate a response if you have what you need to answer - Answer with the option name and nothing else + Answer with the option name and nothing else there should not be any ' or " in the answer. """, ) chain = prompt | ToolsAgent.agent | StrOutputParser() @@ -50,14 +50,14 @@ def tool_agent_decider(state: GraphState): Data: {data} Your options for agents are the following: - - 'perplexity': This agent has access to tools that use the perplexity API. + - "perplexity": This agent has access to tools that use the perplexity API. These tools are the following: {perplexity_tools} - - 'calendar': This agent has access to calendar tools + - "calendar": This agent has access to calendar tools These tools are the following: {calendar_tools} - - 'other': Other tools available: {other_tools} + - "other": Other tools available: {other_tools} - Answer with the option name and nothing else + Answer with the option name and nothing else there should not be any ' or " in the answer. """, ) @@ -148,7 +148,7 @@ def calendar_desicion_agent(state: GraphState): - 'use_calendar_tool': Call on calendar_tools to help solve the users problem - 'return_to_jarvis': go back to the jarvis agent - Answer with the option name and nothing else. + Answer with the option name and nothing else there should not be any ' or " in the answer. """, ) chain = prompt | ToolsAgent.agent | StrOutputParser() diff --git a/core/tools/google_calendar_read.py b/core/tools/google_calendar_read.py index 5fc0e18..49d56b7 100644 --- a/core/tools/google_calendar_read.py +++ b/core/tools/google_calendar_read.py @@ -1,5 +1,5 @@ import os -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from dotenv import load_dotenv from google.oauth2.service_account import Credentials from googleapiclient.discovery import build @@ -42,6 +42,28 @@ def read_calendar_events(time_min: str, time_max: str, maxResults: int = 10) -> Use this tool to read events from the calendar within a specified time range. """ service = get_calendar_service() + + try: + # Parse input strings into datetime objects + time_min_dt = datetime.fromisoformat(time_min) + time_max_dt = datetime.fromisoformat(time_max) + + # If timezone info is missing, add UTC timezone + if time_min_dt.tzinfo is None: + time_min_dt = time_min_dt.replace(tzinfo=timezone.utc) + if time_max_dt.tzinfo is None: + time_max_dt = time_max_dt.replace(tzinfo=timezone.utc) + + # Validate that time_min is before time_max + if time_min_dt >= time_max_dt: + return "Error: time_min must be earlier than time_max." + + # Convert back to RFC3339 strings + time_min_formatted = time_min_dt.isoformat() + time_max_formatted = time_max_dt.isoformat() + + except ValueError as e: + return f"Invalid time format: {e}" events_result = service.events().list( calendarId=os.getenv("GOOGLE_CALENDAR_ID"), From ef1d3632b6a4dcab5a90af757ae4a2d132270408 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Thu, 7 Nov 2024 19:25:29 +0100 Subject: [PATCH 43/62] Feat: added response.replaces --- core/noder.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/noder.py b/core/noder.py index 455f51a..35c83b7 100644 --- a/core/noder.py +++ b/core/noder.py @@ -32,6 +32,8 @@ def jarvis_agent(state: GraphState): chain = prompt | ToolsAgent.agent | StrOutputParser() response = chain.invoke({ "messages": state["messages"], "data": state.get("data", {})}) + response.replace("'", "") + response.replace('"', '') return {"tool_decision": response} def tool_agent_decider(state: GraphState): @@ -69,6 +71,8 @@ def tool_agent_decider(state: GraphState): "calendar_tools": calendar_based_tools(), "other_tools": get_other_tools() }) + response.replace("'", "") + response.replace('"', '') return {"agent_decision": [response]} def router(state: GraphState) -> Literal["generate", "use_tool"]: @@ -154,6 +158,8 @@ def calendar_desicion_agent(state: GraphState): chain = prompt | ToolsAgent.agent | StrOutputParser() response = chain.invoke({ "messages": state["messages"], "data": state.get("data", {})}) + response.replace("'", "") + response.replace('"', '') return {"calendar_decision": response} def calendar_tool_decider(state: GraphState): From b878b14fa3f455f4ace765a559e1e6a01b4476b6 Mon Sep 17 00:00:00 2001 From: Sverre Nystad Date: Thu, 7 Nov 2024 20:06:56 +0100 Subject: [PATCH 44/62] feat: add tool_responses to Debug & running Jarvis processes --- core/static/index.js | 143 ++++++++++++++++++++---------------- core/static/socketEvents.js | 54 +++++++------- 2 files changed, 106 insertions(+), 91 deletions(-) diff --git a/core/static/index.js b/core/static/index.js index 4b78eb5..e2fe57a 100644 --- a/core/static/index.js +++ b/core/static/index.js @@ -5,105 +5,120 @@ Main js file for loading the dynamic UI elements. */ // Runs on inital startup, after window (html) has finished loading - init = () => { - document.getElementById('send_button').addEventListener('click', sendMessage) - document.getElementById('clear_log').addEventListener('click', clear_log) - - document.querySelector(".chatHistory").innerHTML += chatHistoryList() - - // To hide settings page when clicking somewhere else after it's opened. - document.addEventListener('click', function(event){ - const settings = document.getElementById("settingsPage"); - const settingsButton = document.getElementById("settingsButton"); - if(!settings.contains(event.target) && !settingsButton.contains(event.target) && settings.style.display=="block") { - settingsPage() - } - }); -} +init = () => { + document.getElementById("send_button").addEventListener("click", sendMessage); + document.getElementById("clear_log").addEventListener("click", clear_log); + + document.querySelector(".chatHistory").innerHTML += chatHistoryList(); + + // To hide settings page when clicking somewhere else after it's opened. + document.addEventListener("click", function (event) { + const settings = document.getElementById("settingsPage"); + const settingsButton = document.getElementById("settingsButton"); + if ( + !settings.contains(event.target) && + !settingsButton.contains(event.target) && + settings.style.display == "block" + ) { + settingsPage(); + } + }); +}; window.onload = init; // global state of the UI state = { - activeConversationId: 0, // The active conversation ID, so the UI knows what conversation to display and load. - userId: 0, // The user ID using the interface. probably not going to be used for a while. - totalTokensUsed: 0, // Track of total tokens and $$$ used - aiMessageId: 0 // The message id. Set when a response is received from the AI in chat.js -} + activeConversationId: 0, // The active conversation ID, so the UI knows what conversation to display and load. + userId: 0, // The user ID using the interface. probably not going to be used for a while. + totalTokensUsed: 0, // Track of total tokens and $$$ used + aiMessageId: 0, // The message id. Set when a response is received from the AI in chat.js +}; // Changes the loading icon -let loading = false +let loading = false; let setLoading = (newLoadingVal) => { - if(newLoadingVal){ - document.getElementById("chat_history").innerHTML += /* html */` + if (newLoadingVal) { + document.getElementById("chat_history").innerHTML += /* html */ `
-
` - }else{ - try{ - document.getElementById("spinner").remove() - }catch(e){ + `; + } else { + try { + document.getElementById("spinner").remove(); + } catch (e) {} + } - } - } - - loading = newLoadingVal -} + loading = newLoadingVal; +}; // For seeing formatted HTML in javascript files, this extension for VSCode is recommended: // https://marketplace.visualstudio.com/items?itemName=pushqrdx.inline-html async function addMessage(message, uuid) { -let html = /*html*/` + let html = /*html*/ `
  • ${message}
  • `; - document.getElementById('chat_history').innerHTML += html; - const messages = getAllChatMessages(); - console.log(messages); + document.getElementById("chat_history").innerHTML += html; + const messages = getAllChatMessages(); + console.log(messages); } async function getAllChatMessages() { - const chatMessages = document.querySelectorAll('.chat_message'); - const messagesList = []; - chatMessages.forEach((element) => { - messagesList.push(element.textContent.trim()); - }); - return messagesList; + const chatMessages = document.querySelectorAll(".chat_message"); + const messagesList = []; + chatMessages.forEach((element) => { + messagesList.push(element.textContent.trim()); + }); + return messagesList; } async function addStreamedMessage(uuid, messagePart) { - let element = document.getElementById(uuid); - - if (element == null) { - await addMessage(messagePart, uuid); - element = document.getElementById(uuid); - } else { - // Concat ChatPart on message with uuid - element.innerHTML += messagePart; - } - - // Add auto-scroll - let chat_history = document.getElementById("chat_history") - chat_history.scrollTop = chat_history.scrollHeight; + let element = document.getElementById(uuid); + + if (element == null) { + await addMessage(messagePart, uuid); + element = document.getElementById(uuid); + } else { + // Concat ChatPart on message with uuid + element.innerHTML += messagePart; + } + + // Add auto-scroll + let chat_history = document.getElementById("chat_history"); + chat_history.scrollTop = chat_history.scrollHeight; +} + +async function addToolResponseToProcessContainer(toolResponse) { + let html = /*html*/ ` +
    + Tool Icon +
    ${toolResponse}
    +
    `; + document.querySelector(".processesContainer").innerHTML += html; + + // Auto-scroll to the latest process + let processesContainer = document.querySelector(".processesContainer"); + processesContainer.scrollTop = processesContainer.scrollHeight; } addUserMessage = (message) => { - let html = /*html*/` + let html = /*html*/ `
  • ${message}
    -
  • ` - document.getElementById('chat_history').innerHTML += html; -} + `; + document.getElementById("chat_history").innerHTML += html; +}; initialLoadMessages = () => { - const chat_box = document.getElementById('chat_history') - allChats = [] - chat_box.value = messageLog.join('\n') -} \ No newline at end of file + const chat_box = document.getElementById("chat_history"); + allChats = []; + chat_box.value = messageLog.join("\n"); +}; diff --git a/core/static/socketEvents.js b/core/static/socketEvents.js index d7522c1..d0d9da2 100644 --- a/core/static/socketEvents.js +++ b/core/static/socketEvents.js @@ -3,43 +3,43 @@ // TODO: add this port to .env later var socket = io("ws://localhost:3000"); // websocket querying port 3001, where Flask is running. -socket.on('connect', () => { - socket.emit('message', {data: 'I\'m connected!'}); +socket.on("connect", () => { + socket.emit("message", { data: "I'm connected!" }); }); -let tokenCounter = 0 -let uuid = 0 +let tokenCounter = 0; +let uuid = 0; // prints chunks that are streamed to the console and adds them to the chat -socket.on("chunk", async (chunk)=>{ - if(!state.activeAIMessage){ - console.log("STARTED MESSAGE") - uuid = generateUUID(); - await addStreamedMessage(uuid, ""); - ai_message = document.getElementById(uuid) - state.activeAIMessage = ai_message - } - await addStreamedMessage(uuid, chunk); -}) +socket.on("chunk", async (chunk) => { + if (!state.activeAIMessage) { + console.log("STARTED MESSAGE"); + uuid = generateUUID(); + await addStreamedMessage(uuid, ""); + ai_message = document.getElementById(uuid); + state.activeAIMessage = ai_message; + } + await addStreamedMessage(uuid, chunk); +}); socket.on("tokens", async (tokens) => { - state.totalTokensUsed += tokens - console.log("Total tokens so far:", state.totalTokensUsed) - endStreamedAIMessage() -}) - -socket.on("start_message", async () => { + state.totalTokensUsed += tokens; + console.log("Total tokens so far:", state.totalTokensUsed); + endStreamedAIMessage(); +}); -}) +socket.on("start_message", async () => {}); socket.on("tool_call", async (tool_call) => { - console.log("Tool called: ", tool_call); -}) + console.log("Tool called: ", tool_call); +}); socket.on("tool_response", async (tool_response) => { - console.log("Response from tool: ", tool_response); - -}) + console.log("Response from tool: ", tool_response); + + // Add the tool response to the chat + await addToolResponseToProcessContainer(tool_response); +}); // Remember to parse the streamed response @@ -54,4 +54,4 @@ socket.on("tool_response", async (tool_response) => { chat_history.scrollTop = chat_history.scrollHeight; */ -console.log('socketEvents.js loaded...') \ No newline at end of file +console.log("socketEvents.js loaded..."); From 66060e6b8ab86b78f8570a03ced592006428936e Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 7 Nov 2024 20:13:56 +0100 Subject: [PATCH 45/62] refactor: change promt in response_generator to format response better --- core/noder.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/noder.py b/core/noder.py index 35c83b7..83719d9 100644 --- a/core/noder.py +++ b/core/noder.py @@ -12,7 +12,7 @@ def jarvis_agent(state: GraphState): template= """ Your job is to determine if you need tools to answer the users question and answer with only the name of the option - choose. + chosen. Here are previous messages: @@ -89,6 +89,8 @@ def response_generator(state: GraphState): prompt = PromptTemplate( template= """ You are a personal assistant and your job is to generate a response to the user. + You should format the response so that it is easy for an text-to-speech model + to communicate to the user Here are the previous messages: @@ -98,7 +100,7 @@ def response_generator(state: GraphState): Data: {data} - Formulate a response that answer the users question + Formulate a response that answer the users question and is formatted correctly """, ) chain = prompt | SimpleAgent.llm From 6d262fb297a317105427d4540458c4858bcd4217 Mon Sep 17 00:00:00 2001 From: Jon Bergland Date: Thu, 7 Nov 2024 20:28:31 +0100 Subject: [PATCH 46/62] feat: add to prompt that RAG-functionality is available to the LLM --- core/graphAgent.py | 2 +- core/noder.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/graphAgent.py b/core/graphAgent.py index f5f62ff..c6605e5 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -29,7 +29,7 @@ def __init__(self): self.workflow.add_node("perplexity_agent", perplexity_agent) self.workflow.add_node("calendar_tool", ToolNode(get_tools())) self.workflow.add_node("use_calendar_tool", calendar_tool_decider) - self.workflow.add_node("calendar_decider", calendar_desicion_agent) + self.workflow.add_node("calendar_decider", calendar_decision_agent) self.workflow.add_node("other_agent", other_agent) self.workflow.add_edge(START, "jarvis_agent") diff --git a/core/noder.py b/core/noder.py index 83719d9..d3d8d5b 100644 --- a/core/noder.py +++ b/core/noder.py @@ -52,8 +52,8 @@ def tool_agent_decider(state: GraphState): Data: {data} Your options for agents are the following: - - "perplexity": This agent has access to tools that use the perplexity API. - These tools are the following: {perplexity_tools} + - "perplexity": This agent has access to tools that use the perplexity API and tools + for doing a RAG-search. These tools are the following: {perplexity_tools} - "calendar": This agent has access to calendar tools These tools are the following: {calendar_tools} - "other": Other tools available: {other_tools} @@ -112,8 +112,8 @@ def perplexity_agent(state: GraphState): """Agent that handles tools using the perplexity api""" prompt = PromptTemplate( template= """ - Your job is to create tool_calls to tools using the perplexity API. - The tool or tools you decide + Your job is to create tool_calls to tools using the perplexity API or + to tools that do a RAG-search. The tool or tools you decide to call should help answer the users question. Here are previous messages: @@ -134,11 +134,11 @@ def perplexity_agent(state: GraphState): return {"messages": [response]} -def calendar_desicion_agent(state: GraphState): +def calendar_decision_agent(state: GraphState): """Agent that decides what to do with the calendar""" prompt = PromptTemplate( template= """ - Your job is to determine if you wich calendar related tools you need to answer the + Your job is to determine if you which calendar related tools you need to answer the jarvis agents question and answer with only the name of the option choose. From 2869c7608b623a6d74550e2ad260ba0a26521486 Mon Sep 17 00:00:00 2001 From: klungg Date: Thu, 7 Nov 2024 20:40:04 +0100 Subject: [PATCH 47/62] feat: temp memory backend, rag embedding and search --- core/faiss_index.bin | Bin 0 -> 24682 bytes core/graphAgent.py | 1 + core/graphstate.py | 3 +- core/id_to_metadata.json | 1 + core/main.py | 52 +++++++++++++++++++++++++++++++++-- core/static/socketEvents.js | 8 ++++++ core/tools/rag_search.py | 24 ++++++++++++++++ core/tools/tools.py | 3 ++ core/tools/weather.py | 2 +- core/user_to_vector_ids.json | 1 + 10 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 core/faiss_index.bin create mode 100644 core/id_to_metadata.json create mode 100644 core/tools/rag_search.py create mode 100644 core/user_to_vector_ids.json diff --git a/core/faiss_index.bin b/core/faiss_index.bin new file mode 100644 index 0000000000000000000000000000000000000000..d3f0f6b38fb1c0c503185aee902acfb79ce92632 GIT binary patch literal 24682 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⁡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 ZQ-)bc`%>*J{NE&yN= { socket.emit('message', {data: 'I\'m connected!'}); + + socket.emit('get_chat_history', (history) => { + // Restore chat history to UI + history.chat_history.forEach(entry => { + addUserMessage(entry.human_message); + addMessage(entry.ai_message); + }); + }); }); let tokenCounter = 0 diff --git a/core/tools/rag_search.py b/core/tools/rag_search.py new file mode 100644 index 0000000..1825fb6 --- /dev/null +++ b/core/tools/rag_search.py @@ -0,0 +1,24 @@ +from rag import similarity_search +from langchain_core.tools import tool +from langchain_core.tools.structured import StructuredTool +from typing import List, Tuple + + +@tool +def rag_search(query: str)-> List[Tuple[str, float]]: + """ + Use this tool to get relevant conversation history + + Args: + query (str): The query to search for + + Returns: + List[Tuple[str, float]]: Relevant chat history + """ + result = similarity_search(query) + + return result + + +def get_tool() -> StructuredTool: + return rag_search \ No newline at end of file diff --git a/core/tools/tools.py b/core/tools/tools.py index 84d35bb..a3d434c 100644 --- a/core/tools/tools.py +++ b/core/tools/tools.py @@ -10,6 +10,7 @@ import tools.create_time_to_iso as create_time_to_iso_format import tools.current_time_iso as current_time_iso_format import tools.add_time as add_time +import tools.rag_search as rag_search def get_tools() -> list[StructuredTool]: @@ -24,6 +25,7 @@ def get_tools() -> list[StructuredTool]: tools.append(read_calendar_event.get_tool()) tools.append(create_time_to_iso_format.get_tool()) tools.append(current_time_iso_format.get_tool()) + tools.append(rag_search.get_tool()) return tools @@ -31,6 +33,7 @@ def get_perplexity_based_tools() -> list[StructuredTool]: tools = [] tools.append(weather.get_tool()) tools.append(web_search.get_tool()) + tools.append(rag_search.get_tool()) return tools diff --git a/core/tools/weather.py b/core/tools/weather.py index bea4d8c..8c3651a 100644 --- a/core/tools/weather.py +++ b/core/tools/weather.py @@ -34,7 +34,7 @@ def weather_forecast(user_query: str) -> str: # chat completion without streaming response = client.chat.completions.create( - model="llama-3.1-sonar-large-128k-online", + model="llama-3.1-sonar-huge-128k-online", messages=messages, temperature=0.0, ) diff --git a/core/user_to_vector_ids.json b/core/user_to_vector_ids.json new file mode 100644 index 0000000..b64e184 --- /dev/null +++ b/core/user_to_vector_ids.json @@ -0,0 +1 @@ +{"1": [210453397504, 210453397505]} \ No newline at end of file From 9d4a8020a2862f32dea20ed92430e94a10cabd75 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Thu, 7 Nov 2024 21:15:08 +0100 Subject: [PATCH 48/62] Fix: makes date to utc in reader function --- core/tools/google_calendar_read.py | 36 +++++++++--------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/core/tools/google_calendar_read.py b/core/tools/google_calendar_read.py index 49d56b7..a6bfb80 100644 --- a/core/tools/google_calendar_read.py +++ b/core/tools/google_calendar_read.py @@ -13,15 +13,15 @@ "https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events", ] - def get_calendar_service(): + creds = Credentials.from_service_account_file( os.getenv("GOOGLE_AUTH_KEY"), scopes=SCOPES ) service = build("calendar", "v3", credentials=creds) return service -class read_calendar_events_parameters(BaseModel): +class read_calendar_events_parameters(BaseModel): time_min: str = Field( description="Start time to fetch events from in ISO format(YYYY-MM-DDTHH:MM:SS), the current time can be collected from current_time_iso_format tool", example="2024-10-16T00:00:00" @@ -43,28 +43,13 @@ def read_calendar_events(time_min: str, time_max: str, maxResults: int = 10) -> """ service = get_calendar_service() - try: - # Parse input strings into datetime objects - time_min_dt = datetime.fromisoformat(time_min) - time_max_dt = datetime.fromisoformat(time_max) - - # If timezone info is missing, add UTC timezone - if time_min_dt.tzinfo is None: - time_min_dt = time_min_dt.replace(tzinfo=timezone.utc) - if time_max_dt.tzinfo is None: - time_max_dt = time_max_dt.replace(tzinfo=timezone.utc) - - # Validate that time_min is before time_max - if time_min_dt >= time_max_dt: - return "Error: time_min must be earlier than time_max." - - # Convert back to RFC3339 strings - time_min_formatted = time_min_dt.isoformat() - time_max_formatted = time_max_dt.isoformat() + dt_min =datetime.strptime(time_min, '%Y-%m-%dT%H:%M:%S') + dt_max =datetime.strptime(time_max, '%Y-%m-%dT%H:%M:%S') + dt_utc_min = dt_min.replace(tzinfo=timezone.utc) + dt_utc_max = dt_max.replace(tzinfo=timezone.utc) + time_min = dt_utc_min.isoformat() + time_max = dt_utc_max.isoformat() - except ValueError as e: - return f"Invalid time format: {e}" - events_result = service.events().list( calendarId=os.getenv("GOOGLE_CALENDAR_ID"), timeMin=time_min, @@ -101,9 +86,10 @@ def get_tool() -> StructuredTool: if __name__ == "__main__": # Example usage - now = datetime.utcnow() + now = datetime.now(timezone.utc) + time_min = now.isoformat() time_max = (now + timedelta(days=7)).isoformat() - + print(time_min, time_max) result = read_calendar_events(time_min, time_max) print(result) \ No newline at end of file From 6e810248221d01c5e9dfd05aa413e77d9ea23bb5 Mon Sep 17 00:00:00 2001 From: klungg Date: Thu, 7 Nov 2024 21:31:35 +0100 Subject: [PATCH 49/62] bug: bugfix rag search --- core/noder.py | 7 ++++--- core/tools/rag_search.py | 2 +- core/tools/tools.py | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/noder.py b/core/noder.py index d3d8d5b..b8df456 100644 --- a/core/noder.py +++ b/core/noder.py @@ -12,7 +12,7 @@ def jarvis_agent(state: GraphState): template= """ Your job is to determine if you need tools to answer the users question and answer with only the name of the option - chosen. + chosen. You have access to chat history using tools, thus also some personal data can be retrieved. Here are previous messages: @@ -53,7 +53,7 @@ def tool_agent_decider(state: GraphState): Your options for agents are the following: - "perplexity": This agent has access to tools that use the perplexity API and tools - for doing a RAG-search. These tools are the following: {perplexity_tools} + for doing a chat history search. These tools are the following: {perplexity_tools} - "calendar": This agent has access to calendar tools These tools are the following: {calendar_tools} - "other": Other tools available: {other_tools} @@ -113,7 +113,7 @@ def perplexity_agent(state: GraphState): prompt = PromptTemplate( template= """ Your job is to create tool_calls to tools using the perplexity API or - to tools that do a RAG-search. The tool or tools you decide + to tools that do a RAG-search on the chat history. The tool or tools you decide to call should help answer the users question. Here are previous messages: @@ -199,6 +199,7 @@ def other_agent(state: GraphState): Your job is to create tool_calls to tools. The tool or tools you decide to call should help answer the users question. + You can also use the chat history search tool to help answer the users question. Here are previous messages: diff --git a/core/tools/rag_search.py b/core/tools/rag_search.py index 1825fb6..7e6dfc4 100644 --- a/core/tools/rag_search.py +++ b/core/tools/rag_search.py @@ -15,7 +15,7 @@ def rag_search(query: str)-> List[Tuple[str, float]]: Returns: List[Tuple[str, float]]: Relevant chat history """ - result = similarity_search(query) + result = similarity_search(query, "1") return result diff --git a/core/tools/tools.py b/core/tools/tools.py index a3d434c..0d78d62 100644 --- a/core/tools/tools.py +++ b/core/tools/tools.py @@ -52,5 +52,6 @@ def get_other_tools() -> list[StructuredTool]: tools.append(find_files.get_tool()) tools.append(read_file.get_tool()) tools.append(read_pdf.get_tool()) + tools.append(rag_search.get_tool()) return tools \ No newline at end of file From 611ac15cbbfd8b76700cf04ca1b99823b8511517 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Thu, 7 Nov 2024 21:38:48 +0100 Subject: [PATCH 50/62] prompt: fix --- core/noder.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/noder.py b/core/noder.py index d3d8d5b..5cd04ef 100644 --- a/core/noder.py +++ b/core/noder.py @@ -142,6 +142,9 @@ def calendar_decision_agent(state: GraphState): jarvis agents question and answer with only the name of the option choose. + if you have searched for calendeer events once you should return to jarvis. + the same is for creatting a event, you only need to create that event once. and return to jarvis. + Here are previous messages: Message: {messages} From 79162666b5be846da1034d8decb69166132e3620 Mon Sep 17 00:00:00 2001 From: klungg Date: Thu, 7 Nov 2024 21:42:13 +0100 Subject: [PATCH 51/62] bug: removed personal rag data --- core/faiss_index.bin | Bin 24682 -> 0 bytes core/id_to_metadata.json | 1 - core/user_to_vector_ids.json | 1 - 3 files changed, 2 deletions(-) delete mode 100644 core/faiss_index.bin delete mode 100644 core/id_to_metadata.json delete mode 100644 core/user_to_vector_ids.json diff --git a/core/faiss_index.bin b/core/faiss_index.bin deleted file mode 100644 index d3f0f6b38fb1c0c503185aee902acfb79ce92632..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24682 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⁡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 ZQ-)bc`%>*J{NE&yN= Date: Thu, 7 Nov 2024 21:48:28 +0100 Subject: [PATCH 52/62] feat: updated gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 82840fa..adf61a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # Custom Ignores user_data +faiss_index.bin +id_to_metadata.json +user_to_vector_ids.json # Byte-compiled / optimized / DLL files From 6836cd86a7c9d430d0ba8ea0b7add7e68cb4ac74 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Thu, 7 Nov 2024 21:53:24 +0100 Subject: [PATCH 53/62] Prompt: fixed prompt --- core/noder.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/noder.py b/core/noder.py index 207edf7..b8b2a9d 100644 --- a/core/noder.py +++ b/core/noder.py @@ -13,6 +13,7 @@ def jarvis_agent(state: GraphState): Your job is to determine if you need tools to answer the users question and answer with only the name of the option chosen. You have access to chat history using tools, thus also some personal data can be retrieved. + Here are previous messages: @@ -27,6 +28,7 @@ def jarvis_agent(state: GraphState): - 'generate': Generate a response if you have what you need to answer Answer with the option name and nothing else there should not be any ' or " in the answer. + Answer only one of the options! """, ) chain = prompt | ToolsAgent.agent | StrOutputParser() @@ -141,8 +143,8 @@ def calendar_decision_agent(state: GraphState): Your job is to determine if you which calendar related tools you need to answer the jarvis agents question and answer with only the name of the option choose. - - if you have searched for calendeer events once you should return to jarvis. + you should create a claender event or read calendar events. if the user has asked for it. + if you have searched for calender events atleast once you should probably return to jarvis. the same is for creatting a event, you only need to create that event once. and return to jarvis. Here are previous messages: From b0f2b49f6d73738e7b234d3118af64c31136635a Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Thu, 7 Nov 2024 22:08:15 +0100 Subject: [PATCH 54/62] Feat: added timezone to current time --- core/requirements.txt | Bin 2008 -> 2032 bytes core/tools/current_time_iso.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/requirements.txt b/core/requirements.txt index 6885a17e64b46932f055fd39a4ed83b758bcbcd8..984f3176d4d73ca2b9909534cf90abcc4e535abb 100644 GIT binary patch delta 32 jcmcb?|ABwQ4R(nFhDwGKhAIYIAT(ky0AdpcJs=4Hou&r| delta 7 Ocmeyse}jL+4R!zzECY4` diff --git a/core/tools/current_time_iso.py b/core/tools/current_time_iso.py index 371f666..657bf58 100644 --- a/core/tools/current_time_iso.py +++ b/core/tools/current_time_iso.py @@ -2,9 +2,8 @@ from langchain_core.tools import tool from pydantic import BaseModel, Field from langchain_core.tools.structured import StructuredTool -from datetime import datetime - - +from datetime import datetime, timezone +import pytz #Dormamu I've come to bargain @@ -17,7 +16,8 @@ def current_time_iso_format(basetool): Returns: str: The current time in ISO format. """ - time = datetime.now().replace(microsecond=0).strftime('%Y-%m-%dT%H:%M:%S') + timezone = pytz.timezone('Europe/Stockholm') + time = datetime.now(timezone).replace(microsecond=0).strftime('%Y-%m-%dT%H:%M:%S') return time def get_tool() -> StructuredTool: From 924cf42f2dc469fc5c48c5d0d7801d5325a07e15 Mon Sep 17 00:00:00 2001 From: EldarAlvik Date: Fri, 8 Nov 2024 00:03:27 +0100 Subject: [PATCH 55/62] Promptengineered: much smaller chance for jarvis agent to send a empty string --- core/Agents/simpleagent.py | 2 +- core/noder.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/Agents/simpleagent.py b/core/Agents/simpleagent.py index 96c7451..f6e5242 100644 --- a/core/Agents/simpleagent.py +++ b/core/Agents/simpleagent.py @@ -8,7 +8,7 @@ class SimpleAgent: llm = ChatOpenAI( model = Model.gpt_4o, temperature=0, - max_tokens=512, + max_tokens=1024, ) class ToolsAgent: diff --git a/core/noder.py b/core/noder.py index b8b2a9d..bbaf790 100644 --- a/core/noder.py +++ b/core/noder.py @@ -13,8 +13,9 @@ def jarvis_agent(state: GraphState): Your job is to determine if you need tools to answer the users question and answer with only the name of the option chosen. You have access to chat history using tools, thus also some personal data can be retrieved. - - + som times you have to use multiple tools from multiple diffrent tools that has been called to complte the users requests. + if you calender is sent to you for a second or third time you should generate instead of using tools. + if you get a complex task you should call a tool to help you solve the task. Here are previous messages: Message: {messages} @@ -28,7 +29,9 @@ def jarvis_agent(state: GraphState): - 'generate': Generate a response if you have what you need to answer Answer with the option name and nothing else there should not be any ' or " in the answer. - Answer only one of the options! + You can never answer with an empty string. + please never answer with an empty string. + if you answer with an ampty string you will not do anything and stop working. """, ) chain = prompt | ToolsAgent.agent | StrOutputParser() @@ -44,7 +47,6 @@ def tool_agent_decider(state: GraphState): template= """ Your job is to decide which Agent that should answer the users question. Answer only with the name of the agent you want to use. - Here are the previous messages: Message: {messages} @@ -143,8 +145,9 @@ def calendar_decision_agent(state: GraphState): Your job is to determine if you which calendar related tools you need to answer the jarvis agents question and answer with only the name of the option choose. + if you cant find a calendar event you should create a calendar event. you should create a claender event or read calendar events. if the user has asked for it. - if you have searched for calender events atleast once you should probably return to jarvis. + if you have searched for calendar events atleast once you should probably return to jarvis. the same is for creatting a event, you only need to create that event once. and return to jarvis. Here are previous messages: From cef84cdf0f00f22fa717181ac00a636b018d3c58 Mon Sep 17 00:00:00 2001 From: William M Schmidt Date: Sun, 10 Nov 2024 16:28:49 +0100 Subject: [PATCH 56/62] ADD LANGCHAIN_PROJECT to .env after this commit Tweaked UI and changed a few configs. --- core/main.py | 2 +- core/static/index.css | 20 +++++++++++++++++--- core/static/index.js | 7 ++++--- core/static/socketEvents.js | 7 ++++--- core/static/tool_icon.png | Bin 0 -> 8437 bytes docker-compose.yml | 3 +++ 6 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 core/static/tool_icon.png diff --git a/core/main.py b/core/main.py index 80254ac..6a96832 100644 --- a/core/main.py +++ b/core/main.py @@ -104,7 +104,7 @@ def disconnect(): def handle_prompt(data): try: session_id = request.sid - conversation_id = data['conversation_id'] + conversation_id = data['conversation_id'] # unused for now # Create new chat entry with human message chat_entry = { diff --git a/core/static/index.css b/core/static/index.css index 03a8065..0e72545 100644 --- a/core/static/index.css +++ b/core/static/index.css @@ -86,7 +86,7 @@ body { #chat_input_text { width: 90%; - height: 9vh; + height: 90%; border: none; background-color: #2d2d2d; color: var(--onSurface); @@ -94,11 +94,11 @@ body { #voice_button { width: 10%; - height: 9vh; + height: 90%; background-color: #4d4d4d; border-radius: 10px; font-size: 24px; - + margin-left: 12px; } .chat_input_container{ @@ -317,4 +317,18 @@ a{ width: 80%; height: 400px; } + } + + .processElement{ + margin: 4px; + display: inline-flex; + flex-direction: row; + align-items: center; + } + .processElement>div{ + margin-left: 8px; + } + .processElement>img{ + width: 20px; + margin-left: 6px; } \ No newline at end of file diff --git a/core/static/index.js b/core/static/index.js index e2fe57a..6d2e8df 100644 --- a/core/static/index.js +++ b/core/static/index.js @@ -96,10 +96,11 @@ async function addStreamedMessage(uuid, messagePart) { async function addToolResponseToProcessContainer(toolResponse) { let html = /*html*/ ` -

    +
    Tool Icon
    ${toolResponse}
    -
    `; +
    +
    `; document.querySelector(".processesContainer").innerHTML += html; // Auto-scroll to the latest process @@ -112,7 +113,7 @@ addUserMessage = (message) => {
  • -
    ${message}
    +
    ${message}
  • `; document.getElementById("chat_history").innerHTML += html; }; diff --git a/core/static/socketEvents.js b/core/static/socketEvents.js index aa9c4e5..1a30438 100644 --- a/core/static/socketEvents.js +++ b/core/static/socketEvents.js @@ -44,9 +44,10 @@ socket.on("tool_call", async (tool_call) => { socket.on("tool_response", async (tool_response) => { console.log("Response from tool: ", tool_response); - - // Add the tool response to the chat - await addToolResponseToProcessContainer(tool_response); + if(tool_response.length > 0){ + // Add the tool response to the chat + await addToolResponseToProcessContainer(tool_response); + } }); // Remember to parse the streamed response diff --git a/core/static/tool_icon.png b/core/static/tool_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c2dcfa6e361a5bd31a49d7502f6ff0d64b27d0fd GIT binary patch literal 8437 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 Date: Sun, 10 Nov 2024 21:37:55 +0100 Subject: [PATCH 57/62] feat: Added neoagent to begin pulling everything together --- core/Agents/neoagent.py | 51 +++++++++++++++++++++++++++++++++ core/Agents/simpleagent.py | 1 + core/graphAgent.py | 58 ++++++++++++++++++++++++++++++-------- core/main.py | 2 +- 4 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 core/Agents/neoagent.py diff --git a/core/Agents/neoagent.py b/core/Agents/neoagent.py new file mode 100644 index 0000000..fc0d679 --- /dev/null +++ b/core/Agents/neoagent.py @@ -0,0 +1,51 @@ +from typing import Annotated + +from typing_extensions import TypedDict + +from langgraph.graph import StateGraph, START, END +from langgraph.graph.message import add_messages + + +class State(TypedDict): + # Messages have the type "list". The `add_messages` function + # in the annotation defines how this state key should be updated + # (in this case, it appends messages to the list, rather than overwriting them) + messages: Annotated[list, add_messages] + + +graph_builder = StateGraph(State) + +""" +# Updating the state requires creating a new state (following state immutability for history and checkpoints) + +# Example function to increment a value +def increment_count(state: GraphState) -> GraphState: + return GraphState(count=state["count"] + 1) + +# To add a message to the state. +def add_message(state: GraphState, message: str, is_human: bool = True) -> GraphState: + new_message = HumanMessage(content=message) if is_human else AIMessage(content=message) + return GraphState( + count=state["count"], + messages=state["messages"] + [new_message] + ) + +from langgraph.graph import StateGraph, END + +def create_complex_graph(): + workflow = StateGraph(GraphState) + + def process_message(state: GraphState): + last_message = state["messages"][-1].content if state["messages"] else "No messages yet" + response = f"Received: {last_message}. Count is now {state['count'] + 1}" + return { + "count": state["count"] + 1, + "messages": state["messages"] + [AIMessage(content=response)] + } + + workflow.add_node("process", process_message) + workflow.set_entry_point("process") + workflow.add_edge("process", END) + + return workflow.compile() +""" \ No newline at end of file diff --git a/core/Agents/simpleagent.py b/core/Agents/simpleagent.py index f6e5242..60b2167 100644 --- a/core/Agents/simpleagent.py +++ b/core/Agents/simpleagent.py @@ -3,6 +3,7 @@ from config import OPENAI_API_KEY from tools.tools import get_tools + # SimpleAgent defines what model is used. class SimpleAgent: llm = ChatOpenAI( diff --git a/core/graphAgent.py b/core/graphAgent.py index 09278c7..3e065f8 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -1,19 +1,22 @@ -from typing import Literal -from langchain_openai import ChatOpenAI +#from typing import Literal +# from langchain_openai import ChatOpenAI from graphstate import GraphState from tools.tools import get_tools from langgraph.graph import StateGraph, START, END from langgraph.prebuilt import ToolNode, tools_condition from langchain_core.messages import BaseMessage, AIMessageChunk, HumanMessage, AIMessage, ToolMessage -from models import Model -import json -from config import OPENAI_API_KEY -from Agents.simpleagent import SimpleAgent -from graphtools import graphtool -import asyncio -from time import sleep -import functools from noder import * +from time import sleep +import tiktoken # To count tokens +from models import Model +#from config import OPENAI_API_KEY +#from models import Model +#import json +#from Agents.simpleagent import SimpleAgent +#from graphtools import graphtool +#import asyncio +#import functools + class Graph: def __init__(self): @@ -62,7 +65,6 @@ def __init__(self): self.graph = self.workflow.compile() - with open("graph_node_network.png", 'wb') as f: f.write(self.graph.get_graph().draw_mermaid_png()) @@ -73,6 +75,23 @@ def chatbot(self, state: GraphState): """ return {"messages": [self.llm_with_tools.invoke(state["messages"])]} + # Counts tokens using the tiktoken native library from langchain. By default counts gpt4o tokens. + def count_tokens(messages, model=Model.gpt_4o): + encoding = tiktoken.encoding_for_model(model) + # Tokenize each message in the history and sum up the token count + total_tokens = 0 + for role, content in messages: + total_tokens += len(encoding.encode(role)) # Count tokens for the role + total_tokens += len(encoding.encode(content)) # Count tokens for the content + return total_tokens + + # Can trim history by removing a message at a time until within the specified token limit. + def trim_history(chat_history, max_tokens): + current_tokens = Graph.count_tokens(chat_history) + while current_tokens > max_tokens: + chat_history.pop(0) # Remove the oldest message + current_tokens = Graph.count_tokens(chat_history) + return chat_history # UNFINISHED def run_stream_only(self, user_prompt: str): @@ -91,7 +110,22 @@ async def run(self, user_prompt: str, socketio): Run the agent with a user prompt and emit the response and total tokens via socket """ try: - input = {"messages": [("human", user_prompt)]} + if 'system:' in user_prompt: + user_prompt = user_prompt.replace('system:', 'suggested prompt for my ai:') + + # TODO: Link to the current chatID + # Graph.trim_history to remove excess tokens above the limit. + chat_history = [("human", "How many planets are there in the solar system?"), + ("ai", "There are eight planets in our solar system. They are Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune.")] + + input = {"messages": [ + ("system", """ + You are Jarvis, an AI assistant here to help the human accomplish tasks. + Respond in a conversational, natural style that sounds good when spoken aloud. + Keep responses short and to the point, using clear, engaging language. + When explaining your thought process, be concise and only describe essential steps to maintain a conversational flow. + """) + ] + chat_history + [("human", user_prompt)]} socketio.emit("start_message", " ") async for event in self.graph.astream_events(input, version='v2'): event_type = event.get('event') diff --git a/core/main.py b/core/main.py index 6a96832..75d63d2 100644 --- a/core/main.py +++ b/core/main.py @@ -111,7 +111,7 @@ def handle_prompt(data): "human_message": data['prompt'], "ai_message": "" # Will be filled when AI responds } - + socketio.emit("start_message") # Run the AI response From 836d68c276bab5d10feb04324a7c1ec6deaaf9af Mon Sep 17 00:00:00 2001 From: William M Schmidt Date: Mon, 11 Nov 2024 16:49:54 +0100 Subject: [PATCH 58/62] feat: added in-memory checkpointing. (J remembers things in-chat) --- .env.example | 9 ++++++--- core/Agents/neoagent.py | 40 +++++++++++++++++++++++++++++++++++----- core/config.py | 8 +------- core/graphAgent.py | 27 ++++++++++++++++----------- core/main.py | 3 ++- core/requirements.txt | Bin 2032 -> 2576 bytes docker-compose.yml | 2 +- 7 files changed, 61 insertions(+), 28 deletions(-) diff --git a/.env.example b/.env.example index f735c81..f41e926 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ -OPENAI_API_KEY="your_api_key" -LANGSMITH_API_KEY="your_langsmith_api_key" #Find it here: https://smith.langchain.com -PORT="3000" \ No newline at end of file +OPENAI_API_KEY=your_api_key +LANGSMITH_API_KEY=your_langsmith_api_key #Find it here: https://smith.langchain.com +PORT=3000 +#FLASK_ENV=development #Optional if you want docker to reload flask when you save your code. +#LANGSMITH_API_KEY=your_api_key #optional. Let's you debug using langsmith +#LANGCHAIN_PROJECT=your_project_name #pops up in langsmith dashboard \ No newline at end of file diff --git a/core/Agents/neoagent.py b/core/Agents/neoagent.py index fc0d679..02d7fa4 100644 --- a/core/Agents/neoagent.py +++ b/core/Agents/neoagent.py @@ -1,15 +1,45 @@ from typing import Annotated - from typing_extensions import TypedDict -from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages +from langchain_openai import ChatOpenAI +from langchain_core.tools import tool +from langgraph.checkpoint.memory import MemorySaver +from langgraph.graph import MessagesState, StateGraph, START, END +from langgraph.prebuilt import ToolNode + +from models import Model #Models for chatGPT + + +""" +Neoagent uses the ReAct agent framework. +Simply put in steps: +1. 'Re' The agent reasons about the problem, and plans out steps to solve it. +2. 'Act' The agent acts upon the information gathered. Calling tools or interacting with systems based on the earlier reasoning. +3. 'Loop' If the problem is not adequately solved, the agent can reason and act recursively until a satisfying solution is reached. +ReAct is a simple multi-step agent architecture. +Smaller graphs are often better understood by the LLMs. +""" + +memory = MemorySaver() + +@tool +def search(query: str): + """Call to surf the web.""" + # This is a placeholder for the actual implementation + # Don't let the LLM know this though 😊 + return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈." +tools = [search] +tool_node = ToolNode(tools) +model = ChatOpenAI( + model = Model.gpt_4o, + temperature=0, + max_tokens=16384, # Max tokens for mini. For gpt4o it's 128k +) # Using ChatGPT hardcoded (TODO: Make this dynamic) +bound_model = model.bind_tools(tools) class State(TypedDict): - # Messages have the type "list". The `add_messages` function - # in the annotation defines how this state key should be updated - # (in this case, it appends messages to the list, rather than overwriting them) messages: Annotated[list, add_messages] diff --git a/core/config.py b/core/config.py index b9cd51a..08ffe25 100644 --- a/core/config.py +++ b/core/config.py @@ -8,13 +8,7 @@ #add langsmith api to env as LANGSMITH_API_KEY = "your_api_key" on EU server LANGSMITH_API_KEY = os.getenv("LANGSMITH_API_KEY", "no_key") - -os.environ["LANGCHAIN_TRACING_V2"] = "true" -os.environ["LANGCHAIN_ENDPOINT"] = "https://eu.api.smith.langchain.com" -try: - os.environ["LANGCHAIN_API_KEY"] = LANGSMITH_API_KEY -except: - print("No langsmith key found") +print(LANGSMITH_API_KEY) if __name__ == "__main__": print(f"[INFO] OPENAI_API_KEY: {OPENAI_API_KEY}") diff --git a/core/graphAgent.py b/core/graphAgent.py index 3e065f8..4966c8b 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -16,12 +16,14 @@ #from graphtools import graphtool #import asyncio #import functools +from langgraph.checkpoint.memory import MemorySaver +memory = MemorySaver() # Used to save state using checkpointing. See 'config' and astream execution furhter down. +from dotenv import load_dotenv +load_dotenv(dotenv_path='../.env', override=True) class Graph: def __init__(self): - LANGCHAIN_TRACING_V2: str = "true" - self.workflow = StateGraph(GraphState) self.workflow.add_node("jarvis_agent", jarvis_agent) @@ -63,7 +65,7 @@ def __init__(self): {"use_calendar_tool": "use_calendar_tool", "return_to_jarvis": "jarvis_agent"} ) - self.graph = self.workflow.compile() + self.graph = self.workflow.compile(checkpointer=memory) #Compiles the graph using memory checkpointer with open("graph_node_network.png", 'wb') as f: f.write(self.graph.get_graph().draw_mermaid_png()) @@ -127,20 +129,24 @@ async def run(self, user_prompt: str, socketio): """) ] + chat_history + [("human", user_prompt)]} socketio.emit("start_message", " ") - async for event in self.graph.astream_events(input, version='v2'): + config = {"configurable": {"thread_id": "1"}} # Thread here is hardcoded for now. + async for event in self.graph.astream_events(input, config, version='v2'): # The config uses the memory checkpoint to save chat state. Only in-memory, not persistent yet. event_type = event.get('event') - # Focuses only on the 'on_chain_stream'-events. # There may be better events to base the response on if event_type == 'on_chain_end' and event['name'] == 'LangGraph': ai_message = event['data']['output']['messages'][-1] if isinstance(ai_message, AIMessage): + print(ai_message) if 'tool_calls' in ai_message.additional_kwargs: - tool_call = ai_message.additional_kwargs['tool_calls'][0]['function'] - #tool_call_id = ai_message.additional_kwargs['call_tool'][0]['tool_call_id'] - socketio.emit("tool_call", tool_call) - continue + try: + tool_call = ai_message.additional_kwargs['tool_calls'][0]['function'] + #tool_call_id = ai_message.additional_kwargs['call_tool'][0]['tool_call_id'] + socketio.emit("tool_call", tool_call) + continue + except Exception as e: + return e socketio.emit("chunk", ai_message.content) socketio.emit("tokens", ai_message.usage_metadata['total_tokens']) @@ -148,7 +154,6 @@ async def run(self, user_prompt: str, socketio): if event_type == 'on_chain_stream' and event['name'] == 'tools': tool_response = event['data']['chunk']['messages'][-1] - if isinstance(tool_response, ToolMessage): socketio.emit("tool_response", tool_response.content) continue @@ -156,5 +161,5 @@ async def run(self, user_prompt: str, socketio): return "success" except Exception as e: print(e) - return "error" + return e diff --git a/core/main.py b/core/main.py index 75d63d2..0dcffe8 100644 --- a/core/main.py +++ b/core/main.py @@ -11,7 +11,7 @@ from modules.chat import read_chat import logging log = logging.getLogger('werkzeug') -log.setLevel(logging.ERROR) +log.setLevel(logging.ERROR) #INFO, DEBUG, WARNING, ERROR, or CRITICAL - config as needed during development. from collections import defaultdict # @@ -117,6 +117,7 @@ def handle_prompt(data): # Run the AI response async def run_and_store(): response = await jarvis.run(data['prompt'], socketio) + ### TODO: Replace this with GraphState for chat history. # Update the AI response in the chat entry chat_entry["ai_message"] = response # Add completed chat entry to history diff --git a/core/requirements.txt b/core/requirements.txt index 984f3176d4d73ca2b9909534cf90abcc4e535abb..7385c06b6f85b135dacab6f8b72bb77201f62711 100644 GIT binary patch delta 531 zcmZuty-LGS7(E7SK$KDtJ7#f{NOGe^2PdZv4$cm8TVonbn_%KlmyY5f1n=lm6dy-? z2Ymz2ccbmla>?&G-}%lt-|c#@u04$*Mu<@%=gCmwoaY2b_6p`NuN&sg-R}&EK9DD< zBC5=@do@e!RKmF!J~SSwGg@#W^vDdbi*2lD`eau;1r<^Zs4V({=&zmBWTy1!({+yV z0mghx_*u|gZbLKyLXP*E2&UuoyQ}hc!N;ZZ?#%v2MJ8j0rG#oEY=Y&A0XcWpsr009 z%6s(3nX-kFmcm66=^$J|Z>;rODrGw?uUX09=5zDfD=?)M%}gcZPFSAQ64_-e)P${h vS2z`(ETUMM!m;4yt99R&Ic*X7cV;fOe!P_Im`f$1w(p`TxO5eoLpS*a*_vLT delta 76 zcmbOr@_~QCgpJ$U7$?UtyKFwf+{ZNe6`SAW81^-jRXEZn&*OMAc@t;cWC>=T$p@HN dCf6~UNfaG1vm35rY8`n=t4BNdQgv7f1jA diff --git a/docker-compose.yml b/docker-compose.yml index 62512fc..39a4a20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: FLASK_ENV: ${FLASK_ENV} # Autorestarts flask when code changes are detected OPENAI_API_KEY: ${OPENAI_API_KEY} LANGSMITH_API_KEY: ${LANGSMITH_API_KEY} - LANGCHAIN_TRACING_V2: true + LANGCHAIN_TRACING_V2: "true" LANGCHAIN_ENDPOINT: "https://api.smith.langchain.com" LANGCHAIN_PROJECT: ${LANGCHAIN_PROJECT} PERPLEXITY_API_KEY: ${PERPLEXITY_API_KEY} From 13c60233d69b9db23b68a763cc4db87a96f46fb7 Mon Sep 17 00:00:00 2001 From: William M Schmidt Date: Mon, 11 Nov 2024 23:24:38 +0100 Subject: [PATCH 59/62] feat: added tool and memory to neoagent Will branch now, and implement similar functionality to neo --- .env.example | 3 ++- core/Agents/neoagent.py | 27 ++++++++++++++------------- core/Agents/proofreader.py | 0 core/graphAgent.py | 23 ----------------------- core/main.py | 3 +-- core/modules/chat.py | 4 ++++ core/requirements.txt | Bin 2576 -> 3418 bytes core/util/trim_message_history.py | 25 +++++++++++++++++++++++++ docker-compose.yml | 1 + 9 files changed, 47 insertions(+), 39 deletions(-) delete mode 100644 core/Agents/proofreader.py create mode 100644 core/util/trim_message_history.py diff --git a/.env.example b/.env.example index f41e926..48e659f 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,5 @@ LANGSMITH_API_KEY=your_langsmith_api_key #Find it here: https://smith.langchain. PORT=3000 #FLASK_ENV=development #Optional if you want docker to reload flask when you save your code. #LANGSMITH_API_KEY=your_api_key #optional. Let's you debug using langsmith -#LANGCHAIN_PROJECT=your_project_name #pops up in langsmith dashboard \ No newline at end of file +#LANGCHAIN_PROJECT=your_project_name #pops up in langsmith dashboard +#TAVILY_API_KEY=your_api_key #Needed for the AI to search the web. The tool will be disabled if there's no api key \ No newline at end of file diff --git a/core/Agents/neoagent.py b/core/Agents/neoagent.py index 02d7fa4..9de9874 100644 --- a/core/Agents/neoagent.py +++ b/core/Agents/neoagent.py @@ -10,6 +10,10 @@ from models import Model #Models for chatGPT +# Premade tool imports +from langchain_community.tools.tavily_search import TavilySearchResults +# Custom tool imports +from tools.add_tool import add # Adds 2 numbers together """ Neoagent uses the ReAct agent framework. @@ -20,29 +24,26 @@ ReAct is a simple multi-step agent architecture. Smaller graphs are often better understood by the LLMs. """ - -memory = MemorySaver() - -@tool -def search(query: str): - """Call to surf the web.""" - # This is a placeholder for the actual implementation - # Don't let the LLM know this though 😊 - return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈." - -tools = [search] -tool_node = ToolNode(tools) +# Defining the model TODO: Make this configurable with Llama, Grok, Gemini, Claude model = ChatOpenAI( model = Model.gpt_4o, temperature=0, max_tokens=16384, # Max tokens for mini. For gpt4o it's 128k ) # Using ChatGPT hardcoded (TODO: Make this dynamic) +# Defining the checkpoint memory saver. +memory = MemorySaver() + +# Defining the tavily web-search tool +tavily = TavilySearchResults(max_results=2) + +tools = [add, tavily] +tool_node = ToolNode(tools) + bound_model = model.bind_tools(tools) class State(TypedDict): messages: Annotated[list, add_messages] - graph_builder = StateGraph(State) """ diff --git a/core/Agents/proofreader.py b/core/Agents/proofreader.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/graphAgent.py b/core/graphAgent.py index 4966c8b..81be601 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -7,8 +7,6 @@ from langchain_core.messages import BaseMessage, AIMessageChunk, HumanMessage, AIMessage, ToolMessage from noder import * from time import sleep -import tiktoken # To count tokens -from models import Model #from config import OPENAI_API_KEY #from models import Model #import json @@ -19,9 +17,6 @@ from langgraph.checkpoint.memory import MemorySaver memory = MemorySaver() # Used to save state using checkpointing. See 'config' and astream execution furhter down. -from dotenv import load_dotenv -load_dotenv(dotenv_path='../.env', override=True) - class Graph: def __init__(self): self.workflow = StateGraph(GraphState) @@ -76,24 +71,6 @@ def chatbot(self, state: GraphState): and returns the result which will be added to the list of messages. """ return {"messages": [self.llm_with_tools.invoke(state["messages"])]} - - # Counts tokens using the tiktoken native library from langchain. By default counts gpt4o tokens. - def count_tokens(messages, model=Model.gpt_4o): - encoding = tiktoken.encoding_for_model(model) - # Tokenize each message in the history and sum up the token count - total_tokens = 0 - for role, content in messages: - total_tokens += len(encoding.encode(role)) # Count tokens for the role - total_tokens += len(encoding.encode(content)) # Count tokens for the content - return total_tokens - - # Can trim history by removing a message at a time until within the specified token limit. - def trim_history(chat_history, max_tokens): - current_tokens = Graph.count_tokens(chat_history) - while current_tokens > max_tokens: - chat_history.pop(0) # Remove the oldest message - current_tokens = Graph.count_tokens(chat_history) - return chat_history # UNFINISHED def run_stream_only(self, user_prompt: str): diff --git a/core/main.py b/core/main.py index 0dcffe8..cc81baa 100644 --- a/core/main.py +++ b/core/main.py @@ -17,9 +17,8 @@ # # Setup # -print("J is booting up....") +print("Jarvis is booting up....") check_folders() # Check directories are made for user data -read_chat("1") # # Server config diff --git a/core/modules/chat.py b/core/modules/chat.py index 5292ec3..6c52bf0 100644 --- a/core/modules/chat.py +++ b/core/modules/chat.py @@ -1,6 +1,10 @@ import os import json +""" + CURRENTLY NOT IN USE +""" + def read_chat(id: str) -> dict: ''' Uses chat_id to get the chat JSON file and returns a python dict object. diff --git a/core/requirements.txt b/core/requirements.txt index 7385c06b6f85b135dacab6f8b72bb77201f62711..119c51168eba4c6a6c487f6a24066fec4b19dd36 100644 GIT binary patch delta 746 zcmYjPJ#Q0H5M0}L4z{o&AzawTqKKf0&hkfWD+L8b3gk#pprA@|n2T-XFYHTVr+^|q z0MM3cQbwXeF4NGUl&O*e2_ey@${$FWdFPMl>AkyqJ3BMGdtV>_PJh0;b)_XeX^Ja- ztg(1zzr%MMYbYTNKzXW1c5R+~L2D3r~3IUUD1b&kc1=y2B1{MJ_ olhFkilXM0slimiZld=aMlOza+lg max_tokens: + chat_history.pop(0) # Remove the oldest message + current_tokens = count_tokens(chat_history) + return chat_history \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 39a4a20..8587781 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: GOOGLE_AUTH_KEY: ${GOOGLE_AUTH_KEY} GOOGLE_CALENDAR_ID: ${GOOGLE_CALENDAR_ID} PORT: ${PORT} + TAVILY_API_KEY: ${TAVILY_API_KEY} volumes: - ./core:/app # Mount the application code to detect live changes networks: From 00128357495647517ed94b1ac2302848841a1e13 Mon Sep 17 00:00:00 2001 From: William M Schmidt Date: Mon, 11 Nov 2024 23:55:11 +0100 Subject: [PATCH 60/62] feat: Added neoagent for testing and as a base-agent Alternative, simple agent for chatting using the ReAct template --- core/Agents/neoagent.py | 118 ++++++++++++++++++++++++++++++++++------ core/graphAgent.py | 18 +----- core/main.py | 4 ++ core/neoagent.png | Bin 0 -> 8804 bytes 4 files changed, 106 insertions(+), 34 deletions(-) create mode 100644 core/neoagent.png diff --git a/core/Agents/neoagent.py b/core/Agents/neoagent.py index 9de9874..7d72e11 100644 --- a/core/Agents/neoagent.py +++ b/core/Agents/neoagent.py @@ -6,7 +6,8 @@ from langchain_core.tools import tool from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import MessagesState, StateGraph, START, END -from langgraph.prebuilt import ToolNode +from langchain_core.messages import BaseMessage, AIMessageChunk, HumanMessage, AIMessage, ToolMessage +from langgraph.prebuilt import ToolNode, tools_condition from models import Model #Models for chatGPT @@ -24,28 +25,111 @@ ReAct is a simple multi-step agent architecture. Smaller graphs are often better understood by the LLMs. """ -# Defining the model TODO: Make this configurable with Llama, Grok, Gemini, Claude -model = ChatOpenAI( - model = Model.gpt_4o, - temperature=0, - max_tokens=16384, # Max tokens for mini. For gpt4o it's 128k -) # Using ChatGPT hardcoded (TODO: Make this dynamic) -# Defining the checkpoint memory saver. -memory = MemorySaver() +class Neoagent: + def __init__(self): + print(""" +------------------------------ +Instantiated NeoAgent.... +------------------------------ + """) + system_prompt = "You are Jarvis, an AI assistant here to help the human accomplish tasks. Respond in a conversational, natural style that sounds good when spoken aloud. Keep responses short and to the point, using clear, engaging language. When explaining your thought process, be concise and only describe essential steps to maintain a conversational flow." + # Defining the model TODO: Make this configurable with Llama, Grok, Gemini, Claude + model = ChatOpenAI( + model = Model.gpt_4o, + temperature=0, + max_tokens=16384, # Max tokens for mini. For gpt4o it's 128k + ) # Using ChatGPT hardcoded (TODO: Make this dynamic) + # Defining the checkpoint memory saver. + memory = MemorySaver() -# Defining the tavily web-search tool -tavily = TavilySearchResults(max_results=2) + # Defining the tavily web-search tool + tavily = TavilySearchResults(max_results=2) -tools = [add, tavily] -tool_node = ToolNode(tools) + # Adding tools and creating the tool node. + tools = [add, tavily] + tool_node = ToolNode(tools) + llm_with_tools = model.bind_tools(tools) -bound_model = model.bind_tools(tools) + class State(TypedDict): + messages: Annotated[list, add_messages] -class State(TypedDict): - messages: Annotated[list, add_messages] + graph_builder = StateGraph(State) -graph_builder = StateGraph(State) + #Executive node that thinks about the problem or query at hand. + def executive_node(state: State): + if not state["messages"]: + state["messages"] = [("system", system_prompt)] + return {"messages": [llm_with_tools.invoke(state["messages"])]} + + graph_builder.add_node("executive_node", executive_node) + graph_builder.add_node("tools", tool_node) # The prebuilt tool node added as "tools" + graph_builder.add_conditional_edges( + "executive_node", + tools_condition, + ) + + # add conditionals, entry point and compile the graph. Exit is defined in the tools node if required. + graph_builder.add_edge("tools", "executive_node") + graph_builder.set_entry_point("executive_node") + self.graph = graph_builder.compile(checkpointer=memory) + + # Draws the graph visually + with open("neoagent.png", 'wb') as f: + f.write(self.graph.get_graph().draw_mermaid_png()) + + # Streams graph updates using websockets. + def stream_graph_updates(self, user_input: str): + config = {"configurable": {"thread_id": "1"}} # TODO: Remove. This is just a placeholder + for event in self.graph.stream({"messages": [("user", user_input)]}, config): + for value in event.values(): + print("Assistant:", value["messages"][-1].content) + + async def run(self, user_prompt: str, socketio): + """ + Run the agent with a user prompt and emit the response and total tokens via socket + """ + + # TODO: Make the chats saved and restored, using this ID as the guiding values. + # Sets the thread_id for the conversation + config = {"configurable": {"thread_id": "1"}} + + try: + input = {"messages": [("human", user_prompt)]} + socketio.emit("start_message", " ") + config = {"configurable": {"thread_id": "1"}} # Thread here is hardcoded for now. + async for event in self.graph.astream_events(input, config, version='v2'): # The config uses the memory checkpoint to save chat state. Only in-memory, not persistent yet. + event_type = event.get('event') + # Focuses only on the 'on_chain_stream'-events. + # There may be better events to base the response on + if event_type == 'on_chain_end' and event['name'] == 'LangGraph': + ai_message = event['data']['output']['messages'][-1] + + if isinstance(ai_message, AIMessage): + print(ai_message) + if 'tool_calls' in ai_message.additional_kwargs: + try: + tool_call = ai_message.additional_kwargs['tool_calls'][0]['function'] + #tool_call_id = ai_message.additional_kwargs['call_tool'][0]['tool_call_id'] + socketio.emit("tool_call", tool_call) + continue + except Exception as e: + return e + + socketio.emit("chunk", ai_message.content) + socketio.emit("tokens", ai_message.usage_metadata['total_tokens']) + continue + + if event_type == 'on_chain_stream' and event['name'] == 'tools': + tool_response = event['data']['chunk']['messages'][-1] + if isinstance(tool_response, ToolMessage): + socketio.emit("tool_response", tool_response.content) + continue + + return "success" + except Exception as e: + print(e) + return e """ # Updating the state requires creating a new state (following state immutability for history and checkpoints) diff --git a/core/graphAgent.py b/core/graphAgent.py index 81be601..0c74ca4 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -83,28 +83,12 @@ def run_stream_only(self, user_prompt: str): for chunk in self.llm.stream(user_prompt): yield chunk.content - #for running the agent comment out for testing in terminal async def run(self, user_prompt: str, socketio): """ Run the agent with a user prompt and emit the response and total tokens via socket """ try: - if 'system:' in user_prompt: - user_prompt = user_prompt.replace('system:', 'suggested prompt for my ai:') - - # TODO: Link to the current chatID - # Graph.trim_history to remove excess tokens above the limit. - chat_history = [("human", "How many planets are there in the solar system?"), - ("ai", "There are eight planets in our solar system. They are Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune.")] - - input = {"messages": [ - ("system", """ - You are Jarvis, an AI assistant here to help the human accomplish tasks. - Respond in a conversational, natural style that sounds good when spoken aloud. - Keep responses short and to the point, using clear, engaging language. - When explaining your thought process, be concise and only describe essential steps to maintain a conversational flow. - """) - ] + chat_history + [("human", user_prompt)]} + input = {"messages": [("human", user_prompt)]} socketio.emit("start_message", " ") config = {"configurable": {"thread_id": "1"}} # Thread here is hardcoded for now. async for event in self.graph.astream_events(input, config, version='v2'): # The config uses the memory checkpoint to save chat state. Only in-memory, not persistent yet. diff --git a/core/main.py b/core/main.py index cc81baa..0bb6ae0 100644 --- a/core/main.py +++ b/core/main.py @@ -1,5 +1,6 @@ from flask import Flask, request, url_for, jsonify from graphAgent import Graph +from Agents.neoagent import Neoagent from models import Model from summarize_chat import summarize_chat from rag import embed_and_store @@ -29,7 +30,10 @@ socketio = SocketIO(app, cors_allowed_origins="*") # Enable CORS for WebSocket # Agent instantiation +# Graph() contains all complex tools +# Neoagent() is a simple ReAct agent that only has websearch and the add tool. For testing purposes. jarvis = Graph() # API key is configured in agent.py +#jarvis = Neoagent() # Initialize active_chats with the correct format active_chats = defaultdict(lambda: {"chat_history": []}) diff --git a/core/neoagent.png b/core/neoagent.png new file mode 100644 index 0000000000000000000000000000000000000000..115bf25904bca9e0e7e511b564b0219ac5ee68fd GIT binary patch literal 8804 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 Zp9?>)94QQIg+~Ja{EzFe&DfiX{{nJ4MS}nU literal 0 HcmV?d00001 From 8efbee7010c1e1d9206641d4a6695b873e9bda1b Mon Sep 17 00:00:00 2001 From: William M Schmidt Date: Mon, 11 Nov 2024 23:56:56 +0100 Subject: [PATCH 61/62] refactor: file renaming etc. Default is graph agent. --- core/Agents/{neoagent.py => neo_agent.py} | 2 +- core/graphAgent.py | 5 +++++ core/main.py | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) rename core/Agents/{neoagent.py => neo_agent.py} (99%) diff --git a/core/Agents/neoagent.py b/core/Agents/neo_agent.py similarity index 99% rename from core/Agents/neoagent.py rename to core/Agents/neo_agent.py index 7d72e11..39e42c8 100644 --- a/core/Agents/neoagent.py +++ b/core/Agents/neo_agent.py @@ -25,7 +25,7 @@ ReAct is a simple multi-step agent architecture. Smaller graphs are often better understood by the LLMs. """ -class Neoagent: +class NeoAgent: def __init__(self): print(""" ------------------------------ diff --git a/core/graphAgent.py b/core/graphAgent.py index 0c74ca4..1accf4c 100644 --- a/core/graphAgent.py +++ b/core/graphAgent.py @@ -19,6 +19,11 @@ class Graph: def __init__(self): + print(""" +------------------------------ +Instantiated Graph Agent.... +------------------------------ + """) self.workflow = StateGraph(GraphState) self.workflow.add_node("jarvis_agent", jarvis_agent) diff --git a/core/main.py b/core/main.py index 0bb6ae0..674226f 100644 --- a/core/main.py +++ b/core/main.py @@ -1,6 +1,6 @@ from flask import Flask, request, url_for, jsonify from graphAgent import Graph -from Agents.neoagent import Neoagent +from core.Agents.neo_agent import NeoAgent from models import Model from summarize_chat import summarize_chat from rag import embed_and_store @@ -31,9 +31,9 @@ # Agent instantiation # Graph() contains all complex tools -# Neoagent() is a simple ReAct agent that only has websearch and the add tool. For testing purposes. +# NeoAgent() is a simple ReAct agent that only has websearch and the add tool. For testing purposes. jarvis = Graph() # API key is configured in agent.py -#jarvis = Neoagent() +#jarvis = NeoAgent() # Initialize active_chats with the correct format active_chats = defaultdict(lambda: {"chat_history": []}) From 63a2ea6f1b775178ce04cab232877a441570a60e Mon Sep 17 00:00:00 2001 From: William M Schmidt Date: Tue, 12 Nov 2024 00:00:32 +0100 Subject: [PATCH 62/62] bugfix: somehow 'core' was added to import --- core/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/main.py b/core/main.py index 674226f..aec49c6 100644 --- a/core/main.py +++ b/core/main.py @@ -1,6 +1,6 @@ from flask import Flask, request, url_for, jsonify from graphAgent import Graph -from core.Agents.neo_agent import NeoAgent +from Agents.neo_agent import NeoAgent from models import Model from summarize_chat import summarize_chat from rag import embed_and_store