Anindhya commited on
Commit
279c61b
·
verified ·
1 Parent(s): 2e7971c

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: AIAssistant LangGraph
3
- emoji: 🌖
4
- colorFrom: yellow
5
- colorTo: gray
6
- sdk: gradio
7
- sdk_version: 5.42.0
8
  app_file: app.py
9
- pinned: false
 
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: AIAssistant_LangGraph
 
 
 
 
 
3
  app_file: app.py
4
+ sdk: gradio
5
+ sdk_version: 5.34.2
6
  ---
 
 
__pycache__/sidekick.cpython-312.pyc ADDED
Binary file (12.1 kB). View file
 
__pycache__/sidekick_tools.cpython-312.pyc ADDED
Binary file (2.98 kB). View file
 
app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from sidekick import Sidekick
3
+
4
+
5
+ async def setup():
6
+ sidekick = Sidekick()
7
+ await sidekick.setup()
8
+ return sidekick
9
+
10
+
11
+ async def process_message(sidekick, message, success_criteria, history):
12
+ results = await sidekick.run_superstep(message, success_criteria, history)
13
+ return results, sidekick
14
+
15
+
16
+ async def reset():
17
+ new_sidekick = Sidekick()
18
+ await new_sidekick.setup()
19
+ return "", "", None, new_sidekick
20
+
21
+
22
+ def free_resources(sidekick):
23
+ print("Cleaning up")
24
+ try:
25
+ if sidekick:
26
+ sidekick.cleanup()
27
+ except Exception as e:
28
+ print(f"Exception during cleanup: {e}")
29
+
30
+
31
+ with gr.Blocks(title="Personal Assistant", theme=gr.themes.Default(primary_hue="emerald")) as ui:
32
+ gr.Markdown("## Personal Assistant")
33
+ sidekick = gr.State(delete_callback=free_resources)
34
+
35
+ with gr.Row():
36
+ chatbot = gr.Chatbot(label="AI Assistant", height=300, type="messages")
37
+ with gr.Group():
38
+ with gr.Row():
39
+ message = gr.Textbox(show_label=False, placeholder="Your request to the AI Assistant")
40
+ with gr.Row():
41
+ success_criteria = gr.Textbox(
42
+ show_label=False, placeholder="What are your success critiera?"
43
+ )
44
+ with gr.Row():
45
+ reset_button = gr.Button("Reset", variant="stop")
46
+ go_button = gr.Button("Go!", variant="primary")
47
+
48
+ ui.load(setup, [], [sidekick])
49
+ message.submit(
50
+ process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick]
51
+ )
52
+ success_criteria.submit(
53
+ process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick]
54
+ )
55
+ go_button.click(
56
+ process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick]
57
+ )
58
+ reset_button.click(reset, [], [message, success_criteria, chatbot, sidekick])
59
+
60
+
61
+ ui.launch(inbrowser=True)
sandbox/dinner.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dinner Report: Le Bernardin
2
+
3
+ ## Restaurant Overview
4
+ - **Name**: Le Bernardin
5
+ - **Address**: 155 W 51st St, New York, NY 10019
6
+ - **Phone**: (212) 554-1515
7
+ - **Website**: [le-bernardin.com](http://le-bernardin.com)
8
+
9
+ ## Cuisine
10
+ Le Bernardin specializes in refined seafood dishes, crafted with the utmost respect for the ingredients. The menu includes a variety of seafood preparations, with an emphasis on freshness and simplicity.
11
+
12
+ ## Menu Highlights
13
+ - **Tuna Tartare**: Diced raw tuna, served with toasted sesame and avocado.
14
+ - **Wild Salmon**: Lightly cooked, served with a warm ginger-soy emulsion and bok choy.
15
+ - **Poached Lobster**: Accompanied by truffle butter and a delicate sauce.
16
+ - **Chocolate Soufflé**: A classic dessert, rich and airy.
17
+
18
+ ## Ambiance
19
+ The atmosphere at Le Bernardin is elegant and serene, characterized by minimalist decor and a focus on the dining experience.
20
+
21
+ ## Summary of Reviews
22
+ Le Bernardin has consistently received rave reviews for its exceptional cuisine and impeccable service. It is a three-Michelin star restaurant, recognized as one of the best seafood restaurants not only in New York but also in the world. Diners emphasize the harmonious flavors and the artistry of the dishes, making it a top choice for special occasions.
23
+
24
+ **In conclusion**, Le Bernardin represents the pinnacle of fine dining, offering diners an unforgettable experience centered around seafood excellence.
sidekick.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated
2
+ from typing_extensions import TypedDict
3
+ from langgraph.graph import StateGraph, START, END
4
+ from langgraph.graph.message import add_messages
5
+ from dotenv import load_dotenv
6
+ from langgraph.prebuilt import ToolNode
7
+ from langchain_openai import ChatOpenAI
8
+ from langgraph.checkpoint.memory import MemorySaver
9
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
10
+ from typing import List, Any, Optional, Dict
11
+ from pydantic import BaseModel, Field
12
+ from sidekick_tools import playwright_tools, other_tools
13
+ import uuid
14
+ import asyncio
15
+ from datetime import datetime
16
+
17
+ load_dotenv(override=True)
18
+
19
+
20
+ class State(TypedDict):
21
+ messages: Annotated[List[Any], add_messages]
22
+ success_criteria: str
23
+ feedback_on_work: Optional[str]
24
+ success_criteria_met: bool
25
+ user_input_needed: bool
26
+
27
+
28
+ class EvaluatorOutput(BaseModel):
29
+ feedback: str = Field(description="Feedback on the assistant's response")
30
+ success_criteria_met: bool = Field(description="Whether the success criteria have been met")
31
+ user_input_needed: bool = Field(
32
+ description="True if more input is needed from the user, or clarifications, or the assistant is stuck"
33
+ )
34
+
35
+
36
+ class Sidekick:
37
+ def __init__(self):
38
+ self.worker_llm_with_tools = None
39
+ self.evaluator_llm_with_output = None
40
+ self.tools = None
41
+ self.llm_with_tools = None
42
+ self.graph = None
43
+ self.sidekick_id = str(uuid.uuid4())
44
+ self.memory = MemorySaver()
45
+ self.browser = None
46
+ self.playwright = None
47
+
48
+ async def setup(self):
49
+ self.tools, self.browser, self.playwright = await playwright_tools()
50
+ self.tools += await other_tools()
51
+ worker_llm = ChatOpenAI(model="gpt-4o-mini")
52
+ self.worker_llm_with_tools = worker_llm.bind_tools(self.tools)
53
+ evaluator_llm = ChatOpenAI(model="gpt-4o-mini")
54
+ self.evaluator_llm_with_output = evaluator_llm.with_structured_output(EvaluatorOutput)
55
+ await self.build_graph()
56
+
57
+ def worker(self, state: State) -> Dict[str, Any]:
58
+ system_message = f"""You are a helpful assistant that can use tools to complete tasks.
59
+ You keep working on a task until either you have a question or clarification for the user, or the success criteria is met.
60
+ You have many tools to help you, including tools to browse the internet, navigating and retrieving web pages.
61
+ You have a tool to run python code, but note that you would need to include a print() statement if you wanted to receive output.
62
+ The current date and time is {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
63
+
64
+ This is the success criteria:
65
+ {state["success_criteria"]}
66
+ You should reply either with a question for the user about this assignment, or with your final response.
67
+ If you have a question for the user, you need to reply by clearly stating your question. An example might be:
68
+
69
+ Question: please clarify whether you want a summary or a detailed answer
70
+
71
+ If you've finished, reply with the final answer, and don't ask a question; simply reply with the answer.
72
+ """
73
+
74
+ if state.get("feedback_on_work"):
75
+ system_message += f"""
76
+ Previously you thought you completed the assignment, but your reply was rejected because the success criteria was not met.
77
+ Here is the feedback on why this was rejected:
78
+ {state["feedback_on_work"]}
79
+ With this feedback, please continue the assignment, ensuring that you meet the success criteria or have a question for the user."""
80
+
81
+ # Add in the system message
82
+
83
+ found_system_message = False
84
+ messages = state["messages"]
85
+ for message in messages:
86
+ if isinstance(message, SystemMessage):
87
+ message.content = system_message
88
+ found_system_message = True
89
+
90
+ if not found_system_message:
91
+ messages = [SystemMessage(content=system_message)] + messages
92
+
93
+ # Invoke the LLM with tools
94
+ response = self.worker_llm_with_tools.invoke(messages)
95
+
96
+ # Return updated state
97
+ return {
98
+ "messages": [response],
99
+ }
100
+
101
+ def worker_router(self, state: State) -> str:
102
+ last_message = state["messages"][-1]
103
+
104
+ if hasattr(last_message, "tool_calls") and last_message.tool_calls:
105
+ return "tools"
106
+ else:
107
+ return "evaluator"
108
+
109
+ def format_conversation(self, messages: List[Any]) -> str:
110
+ conversation = "Conversation history:\n\n"
111
+ for message in messages:
112
+ if isinstance(message, HumanMessage):
113
+ conversation += f"User: {message.content}\n"
114
+ elif isinstance(message, AIMessage):
115
+ text = message.content or "[Tools use]"
116
+ conversation += f"Assistant: {text}\n"
117
+ return conversation
118
+
119
+ def evaluator(self, state: State) -> State:
120
+ last_response = state["messages"][-1].content
121
+
122
+ system_message = """You are an evaluator that determines if a task has been completed successfully by an Assistant.
123
+ Assess the Assistant's last response based on the given criteria. Respond with your feedback, and with your decision on whether the success criteria has been met,
124
+ and whether more input is needed from the user."""
125
+
126
+ user_message = f"""You are evaluating a conversation between the User and Assistant. You decide what action to take based on the last response from the Assistant.
127
+
128
+ The entire conversation with the assistant, with the user's original request and all replies, is:
129
+ {self.format_conversation(state["messages"])}
130
+
131
+ The success criteria for this assignment is:
132
+ {state["success_criteria"]}
133
+
134
+ And the final response from the Assistant that you are evaluating is:
135
+ {last_response}
136
+
137
+ Respond with your feedback, and decide if the success criteria is met by this response.
138
+ Also, decide if more user input is required, either because the assistant has a question, needs clarification, or seems to be stuck and unable to answer without help.
139
+
140
+ The Assistant has access to a tool to write files. If the Assistant says they have written a file, then you can assume they have done so.
141
+ Overall you should give the Assistant the benefit of the doubt if they say they've done something. But you should reject if you feel that more work should go into this.
142
+
143
+ """
144
+ if state["feedback_on_work"]:
145
+ user_message += f"Also, note that in a prior attempt from the Assistant, you provided this feedback: {state['feedback_on_work']}\n"
146
+ user_message += "If you're seeing the Assistant repeating the same mistakes, then consider responding that user input is required."
147
+
148
+ evaluator_messages = [
149
+ SystemMessage(content=system_message),
150
+ HumanMessage(content=user_message),
151
+ ]
152
+
153
+ eval_result = self.evaluator_llm_with_output.invoke(evaluator_messages)
154
+ new_state = {
155
+ "messages": [
156
+ {
157
+ "role": "assistant",
158
+ "content": f"Evaluator Feedback on this answer: {eval_result.feedback}",
159
+ }
160
+ ],
161
+ "feedback_on_work": eval_result.feedback,
162
+ "success_criteria_met": eval_result.success_criteria_met,
163
+ "user_input_needed": eval_result.user_input_needed,
164
+ }
165
+ return new_state
166
+
167
+ def route_based_on_evaluation(self, state: State) -> str:
168
+ if state["success_criteria_met"] or state["user_input_needed"]:
169
+ return "END"
170
+ else:
171
+ return "worker"
172
+
173
+ async def build_graph(self):
174
+ # Set up Graph Builder with State
175
+ graph_builder = StateGraph(State)
176
+
177
+ # Add nodes
178
+ graph_builder.add_node("worker", self.worker)
179
+ graph_builder.add_node("tools", ToolNode(tools=self.tools))
180
+ graph_builder.add_node("evaluator", self.evaluator)
181
+
182
+ # Add edges
183
+ graph_builder.add_conditional_edges(
184
+ "worker", self.worker_router, {"tools": "tools", "evaluator": "evaluator"}
185
+ )
186
+ graph_builder.add_edge("tools", "worker")
187
+ graph_builder.add_conditional_edges(
188
+ "evaluator", self.route_based_on_evaluation, {"worker": "worker", "END": END}
189
+ )
190
+ graph_builder.add_edge(START, "worker")
191
+
192
+ # Compile the graph
193
+ self.graph = graph_builder.compile(checkpointer=self.memory)
194
+
195
+ async def run_superstep(self, message, success_criteria, history):
196
+ config = {"configurable": {"thread_id": self.sidekick_id}}
197
+
198
+ state = {
199
+ "messages": message,
200
+ "success_criteria": success_criteria or "The answer should be clear and accurate",
201
+ "feedback_on_work": None,
202
+ "success_criteria_met": False,
203
+ "user_input_needed": False,
204
+ }
205
+ result = await self.graph.ainvoke(state, config=config)
206
+ user = {"role": "user", "content": message}
207
+ reply = {"role": "assistant", "content": result["messages"][-2].content}
208
+ feedback = {"role": "assistant", "content": result["messages"][-1].content}
209
+ return history + [user, reply, feedback]
210
+
211
+ def cleanup(self):
212
+ if self.browser:
213
+ try:
214
+ loop = asyncio.get_running_loop()
215
+ loop.create_task(self.browser.close())
216
+ if self.playwright:
217
+ loop.create_task(self.playwright.stop())
218
+ except RuntimeError:
219
+ # If no loop is running, do a direct run
220
+ asyncio.run(self.browser.close())
221
+ if self.playwright:
222
+ asyncio.run(self.playwright.stop())
sidekick_tools.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from playwright.async_api import async_playwright
2
+ from langchain_community.agent_toolkits import PlayWrightBrowserToolkit
3
+ from dotenv import load_dotenv
4
+ import os
5
+ import requests
6
+ from langchain.agents import Tool
7
+ from langchain_community.agent_toolkits import FileManagementToolkit
8
+ from langchain_community.tools.wikipedia.tool import WikipediaQueryRun
9
+ from langchain_experimental.tools import PythonREPLTool
10
+ from langchain_community.utilities import GoogleSerperAPIWrapper
11
+ from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
12
+
13
+
14
+
15
+ load_dotenv(override=True)
16
+ pushover_token = os.getenv("PUSHOVER_TOKEN")
17
+ pushover_user = os.getenv("PUSHOVER_USER")
18
+ pushover_url = "https://api.pushover.net/1/messages.json"
19
+ serper = GoogleSerperAPIWrapper()
20
+
21
+ async def playwright_tools():
22
+ playwright = await async_playwright().start()
23
+ browser = await playwright.chromium.launch(headless=False)
24
+ toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=browser)
25
+ return toolkit.get_tools(), browser, playwright
26
+
27
+
28
+ def push(text: str):
29
+ """Send a push notification to the user"""
30
+ requests.post(pushover_url, data = {"token": pushover_token, "user": pushover_user, "message": text})
31
+ return "success"
32
+
33
+
34
+ def get_file_tools():
35
+ toolkit = FileManagementToolkit(root_dir="sandbox")
36
+ return toolkit.get_tools()
37
+
38
+
39
+ async def other_tools():
40
+ push_tool = Tool(name="send_push_notification", func=push, description="Use this tool when you want to send a push notification")
41
+ file_tools = get_file_tools()
42
+
43
+ tool_search =Tool(
44
+ name="search",
45
+ func=serper.run,
46
+ description="Use this tool when you want to get the results of an online web search"
47
+ )
48
+
49
+ wikipedia = WikipediaAPIWrapper()
50
+ wiki_tool = WikipediaQueryRun(api_wrapper=wikipedia)
51
+
52
+ python_repl = PythonREPLTool()
53
+
54
+ return file_tools + [push_tool, tool_search, python_repl, wiki_tool]
55
+