Enhance assistant capabilities and configuration updates
Browse files- Added CREATE_VECTOR_DB configuration option to config.py for vector database creation control.
- Updated RAG prompt template in prompts.py to reflect a more specialized assistant role, including detailed response guidelines and context management.
- Refactored rag.py to utilize a new chat_llm from chains.py for improved response handling.
- Introduced a new datetime tool in tools.py to provide current date and time information.
- Updated dependencies in pyproject.toml to include grandalf for enhanced functionality.
- Created chains.py to manage query tone checking and rude query responses using dedicated chains.
- Added a new Jupyter notebook (00_playground.ipynb) for interactive testing and demonstration of the assistant's features.
- py-src/app.py +15 -6
- py-src/lets_talk/agent.py +69 -79
- py-src/lets_talk/chains.py +30 -0
- py-src/lets_talk/config.py +1 -1
- py-src/lets_talk/prompts.py +47 -28
- py-src/lets_talk/rag.py +5 -10
- py-src/lets_talk/tools.py +41 -2
- py-src/notebooks/00_playground.ipynb +506 -0
- pyproject.toml +1 -0
- uv.lock +23 -0
py-src/app.py
CHANGED
@@ -4,15 +4,24 @@ import sys
|
|
4 |
from pathlib import Path
|
5 |
from operator import itemgetter
|
6 |
from dotenv import load_dotenv
|
|
|
7 |
|
8 |
# Load environment variables from .env file
|
9 |
load_dotenv()
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
#
|
14 |
-
|
15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
import chainlit as cl
|
18 |
from lets_talk.agent import build_agent,parse_output
|
|
|
4 |
from pathlib import Path
|
5 |
from operator import itemgetter
|
6 |
from dotenv import load_dotenv
|
7 |
+
from lets_talk.config import (CREATE_VECTOR_DB,VECTOR_STORAGE_PATH)
|
8 |
|
9 |
# Load environment variables from .env file
|
10 |
load_dotenv()
|
11 |
+
|
12 |
+
if CREATE_VECTOR_DB:
|
13 |
+
import pipeline
|
14 |
+
#build vector store
|
15 |
+
print("=== create vector db ===")
|
16 |
+
# Use configuration from config rather than hardcoded values
|
17 |
+
pipeline.create_vector_database()
|
18 |
+
print("========================")
|
19 |
+
else:
|
20 |
+
# Check if the vector store exists
|
21 |
+
print("=== check vector db ===")
|
22 |
+
if not Path(VECTOR_STORAGE_PATH).exists():
|
23 |
+
print(f"Vector store not found at {VECTOR_STORAGE_PATH}. Please create it first.")
|
24 |
+
sys.exit(1)
|
25 |
|
26 |
import chainlit as cl
|
27 |
from lets_talk.agent import build_agent,parse_output
|
py-src/lets_talk/agent.py
CHANGED
@@ -2,7 +2,7 @@ from operator import itemgetter
|
|
2 |
from typing import TypedDict, Annotated, Dict, Any, Literal, Union, cast, List, Optional
|
3 |
from langchain_openai import ChatOpenAI
|
4 |
from langchain_core.tools import Tool
|
5 |
-
from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage
|
6 |
from langchain_core.documents import Document
|
7 |
from langgraph.graph.message import add_messages
|
8 |
from langgraph.graph import StateGraph, END
|
@@ -50,11 +50,11 @@ def call_model(model, state: Dict[str, Any]) -> Dict[str, list[BaseMessage]]:
|
|
50 |
context = state.get("context", "")
|
51 |
|
52 |
# Insert system message with context before the latest user message
|
53 |
-
from lets_talk.prompts import
|
54 |
-
sys_prompt =
|
55 |
context=context,
|
|
|
56 |
)
|
57 |
-
|
58 |
|
59 |
context_message = SystemMessage(content=sys_prompt)
|
60 |
|
@@ -79,49 +79,6 @@ def call_model(model, state: Dict[str, Any]) -> Dict[str, list[BaseMessage]]:
|
|
79 |
return {"messages": [HumanMessage(content=error_msg)]}
|
80 |
|
81 |
|
82 |
-
# def call_model(model, state: Dict[str, Any]) -> Dict[str, list[BaseMessage]]:
|
83 |
-
# """
|
84 |
-
# Process the current state through the language model.
|
85 |
-
|
86 |
-
# Args:
|
87 |
-
# model: Language model with tools bound
|
88 |
-
# state: Current state containing messages and context
|
89 |
-
|
90 |
-
# Returns:
|
91 |
-
# Updated state with model's response added to messages
|
92 |
-
# """
|
93 |
-
# try:
|
94 |
-
# messages = state["messages"]
|
95 |
-
# context = state.get("context", "")
|
96 |
-
|
97 |
-
# # Add context from documents if available
|
98 |
-
# if context:
|
99 |
-
# # Insert system message with context before the latest user message
|
100 |
-
# context_message = SystemMessage(content=rag_prompt_template.format(context=context))
|
101 |
-
|
102 |
-
# # Find the position of the last user message
|
103 |
-
# for i in range(len(messages)-1, -1, -1):
|
104 |
-
# if isinstance(messages[i], HumanMessage):
|
105 |
-
# # Insert context right after the last user message
|
106 |
-
# enhanced_messages = messages[:i+1] + [context_message] + messages[i+1:]
|
107 |
-
# break
|
108 |
-
# else:
|
109 |
-
# # No user message found, just append context
|
110 |
-
# enhanced_messages = messages + [context_message]
|
111 |
-
# else:
|
112 |
-
# enhanced_messages = messages
|
113 |
-
|
114 |
-
# # Get response from the model
|
115 |
-
# response = model.invoke(enhanced_messages)
|
116 |
-
# return {"messages": [response]}
|
117 |
-
# except Exception as e:
|
118 |
-
# # Handle exceptions gracefully
|
119 |
-
# error_msg = f"Error calling model: {str(e)}"
|
120 |
-
# print(error_msg) # Log the error
|
121 |
-
# # Return a fallback response
|
122 |
-
# return {"messages": [HumanMessage(content=error_msg)]}
|
123 |
-
|
124 |
-
|
125 |
def should_continue(state: Dict[str, Any]) -> Union[Literal["action"], Literal["end"]]:
|
126 |
"""
|
127 |
Determine if the agent should continue processing or end.
|
@@ -149,13 +106,12 @@ def retrieve_from_blog(state: Dict[str, Any]) -> Dict[str, str]:
|
|
149 |
break
|
150 |
else:
|
151 |
# No user message found
|
152 |
-
|
153 |
|
154 |
try:
|
155 |
-
|
156 |
-
response = rag.rag_chain.invoke({"question": query})
|
157 |
-
|
158 |
-
context = response["response"].content
|
159 |
|
160 |
return {"context": context}
|
161 |
except Exception as e:
|
@@ -206,7 +162,7 @@ def parse_output(input_state: Dict[str, Any]) -> str:
|
|
206 |
|
207 |
|
208 |
|
209 |
-
def
|
210 |
"""
|
211 |
Check the tone of the user's query and adjust the state accordingly.
|
212 |
|
@@ -219,55 +175,54 @@ def check_query_tone(state: Dict[str, Any]) -> Dict[str, str]:
|
|
219 |
|
220 |
if isinstance(last_message, HumanMessage):
|
221 |
# Check the tone of the last message
|
222 |
-
state["is_rude"] =
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
|
224 |
return state
|
225 |
|
226 |
|
227 |
-
def
|
228 |
"""
|
229 |
Check if the query is rude or negative.
|
230 |
|
231 |
Args:
|
232 |
-
|
233 |
Returns:
|
234 |
-
True if the
|
235 |
"""
|
236 |
-
from
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
# Create chain
|
241 |
-
tone_chain = (
|
242 |
-
{"query": itemgetter("question")}
|
243 |
-
| tone_prompt
|
244 |
-
| llm
|
245 |
-
)
|
246 |
-
response = tone_chain.invoke({"query": query})
|
247 |
return response.content.strip().lower() == "yes"
|
248 |
|
249 |
|
250 |
|
251 |
-
def
|
252 |
|
253 |
tools = create_search_tools(5)
|
254 |
|
255 |
# Create an instance of ChatOpenAI
|
256 |
-
|
|
|
257 |
model = model.bind_tools(tools)
|
258 |
|
259 |
# Create document search tool if retriever is provided
|
260 |
|
261 |
-
doc_search_tool = Tool(
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
)
|
267 |
|
268 |
# Add document search tool to the tool belt if we have upload capability
|
269 |
-
tools = tools.copy()
|
270 |
-
tools.append(doc_search_tool)
|
271 |
|
272 |
# Create a node for tool execution
|
273 |
tool_node = ToolNode(tools)
|
@@ -284,9 +239,26 @@ def build_agent() -> StateGraph:
|
|
284 |
def retrieve_node(state):
|
285 |
return retrieve_from_blog(state)
|
286 |
|
|
|
|
|
|
|
|
|
|
|
287 |
uncompiled_graph.add_node("retrieve", retrieve_node)
|
288 |
-
uncompiled_graph.set_entry_point("retrieve")
|
289 |
uncompiled_graph.add_node("agent", call_model_node)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
uncompiled_graph.add_edge("retrieve", "agent")
|
291 |
uncompiled_graph.add_node("action", tool_node)
|
292 |
|
@@ -306,9 +278,27 @@ def build_agent() -> StateGraph:
|
|
306 |
# Complete the loop
|
307 |
uncompiled_graph.add_edge("action", "agent")
|
308 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
309 |
# Compile the graph
|
310 |
compiled_graph = uncompiled_graph.compile()
|
311 |
|
312 |
# Create the full chain
|
313 |
agent_chain = convert_inputs | compiled_graph
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
314 |
return agent_chain
|
|
|
2 |
from typing import TypedDict, Annotated, Dict, Any, Literal, Union, cast, List, Optional
|
3 |
from langchain_openai import ChatOpenAI
|
4 |
from langchain_core.tools import Tool
|
5 |
+
from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage,AIMessage
|
6 |
from langchain_core.documents import Document
|
7 |
from langgraph.graph.message import add_messages
|
8 |
from langgraph.graph import StateGraph, END
|
|
|
50 |
context = state.get("context", "")
|
51 |
|
52 |
# Insert system message with context before the latest user message
|
53 |
+
from lets_talk.prompts import rag_prompt_template
|
54 |
+
sys_prompt = rag_prompt_template.format(
|
55 |
context=context,
|
56 |
+
question=messages[-1].content,
|
57 |
)
|
|
|
58 |
|
59 |
context_message = SystemMessage(content=sys_prompt)
|
60 |
|
|
|
79 |
return {"messages": [HumanMessage(content=error_msg)]}
|
80 |
|
81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
def should_continue(state: Dict[str, Any]) -> Union[Literal["action"], Literal["end"]]:
|
83 |
"""
|
84 |
Determine if the agent should continue processing or end.
|
|
|
106 |
break
|
107 |
else:
|
108 |
# No user message found
|
109 |
+
query = state["question"]
|
110 |
|
111 |
try:
|
112 |
+
context = blog_search_tool(query)
|
113 |
+
#response = rag.rag_chain.invoke({"question": query})
|
114 |
+
#context = response["response"].content
|
|
|
115 |
|
116 |
return {"context": context}
|
117 |
except Exception as e:
|
|
|
162 |
|
163 |
|
164 |
|
165 |
+
def check_question_tone(state: Dict[str, Any]) -> Dict[str, str]:
|
166 |
"""
|
167 |
Check the tone of the user's query and adjust the state accordingly.
|
168 |
|
|
|
175 |
|
176 |
if isinstance(last_message, HumanMessage):
|
177 |
# Check the tone of the last message
|
178 |
+
state["is_rude"] = is_rude_question(last_message.content)
|
179 |
+
|
180 |
+
if state["is_rude"]:
|
181 |
+
# If the question is rude, respond with a positive message
|
182 |
+
from lets_talk.chains import rude_query_answer_chain
|
183 |
+
response = rude_query_answer_chain.invoke({"question": last_message.content})
|
184 |
+
state["messages"].append(response)
|
185 |
|
186 |
return state
|
187 |
|
188 |
|
189 |
+
def is_rude_question(question: str) -> bool:
|
190 |
"""
|
191 |
Check if the query is rude or negative.
|
192 |
|
193 |
Args:
|
194 |
+
question: The user's query
|
195 |
Returns:
|
196 |
+
True if the question is rude, False otherwise
|
197 |
"""
|
198 |
+
from lets_talk.chains import tone_check_chain
|
199 |
+
|
200 |
+
response = tone_check_chain.invoke({"question": question})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
return response.content.strip().lower() == "yes"
|
202 |
|
203 |
|
204 |
|
205 |
+
def build_graph() -> StateGraph:
|
206 |
|
207 |
tools = create_search_tools(5)
|
208 |
|
209 |
# Create an instance of ChatOpenAI
|
210 |
+
from lets_talk.chains import chat_llm
|
211 |
+
model = chat_llm
|
212 |
model = model.bind_tools(tools)
|
213 |
|
214 |
# Create document search tool if retriever is provided
|
215 |
|
216 |
+
# doc_search_tool = Tool(
|
217 |
+
# name="TheDataGuy Blog Search",
|
218 |
+
# description="Search within blog posts of thedataguy.pro. ALWAYS use this tool to retrieve the context.",
|
219 |
+
# func=lambda query: blog_search_tool(query),
|
220 |
+
# args_schema=RAGQueryInput
|
221 |
+
# )
|
222 |
|
223 |
# Add document search tool to the tool belt if we have upload capability
|
224 |
+
# tools = tools.copy()
|
225 |
+
# tools.append(doc_search_tool)
|
226 |
|
227 |
# Create a node for tool execution
|
228 |
tool_node = ToolNode(tools)
|
|
|
239 |
def retrieve_node(state):
|
240 |
return retrieve_from_blog(state)
|
241 |
|
242 |
+
|
243 |
+
|
244 |
+
|
245 |
+
uncompiled_graph.add_node("check_question_tone", check_question_tone)
|
246 |
+
uncompiled_graph.set_entry_point("check_question_tone")
|
247 |
uncompiled_graph.add_node("retrieve", retrieve_node)
|
248 |
+
#uncompiled_graph.set_entry_point("retrieve")
|
249 |
uncompiled_graph.add_node("agent", call_model_node)
|
250 |
+
|
251 |
+
|
252 |
+
uncompiled_graph.add_conditional_edges(
|
253 |
+
"check_question_tone",
|
254 |
+
lambda state: "end" if state["is_rude"] else "retrieve",
|
255 |
+
{
|
256 |
+
"retrieve": "retrieve",
|
257 |
+
"end": END
|
258 |
+
}
|
259 |
+
)
|
260 |
+
|
261 |
+
|
262 |
uncompiled_graph.add_edge("retrieve", "agent")
|
263 |
uncompiled_graph.add_node("action", tool_node)
|
264 |
|
|
|
278 |
# Complete the loop
|
279 |
uncompiled_graph.add_edge("action", "agent")
|
280 |
|
281 |
+
return uncompiled_graph
|
282 |
+
|
283 |
+
|
284 |
+
def create_agent_chain(uncompiled_graph) -> StateGraph:
|
285 |
+
"""
|
286 |
+
Create and return the agent chain.
|
287 |
+
"""
|
288 |
+
|
289 |
# Compile the graph
|
290 |
compiled_graph = uncompiled_graph.compile()
|
291 |
|
292 |
# Create the full chain
|
293 |
agent_chain = convert_inputs | compiled_graph
|
294 |
+
return agent_chain
|
295 |
+
|
296 |
+
|
297 |
+
def build_agent():
|
298 |
+
"""
|
299 |
+
Build the agent with the defined graph and return it.
|
300 |
+
"""
|
301 |
+
uncompiled_graph = build_graph()
|
302 |
+
agent_chain = create_agent_chain(uncompiled_graph)
|
303 |
+
|
304 |
return agent_chain
|
py-src/lets_talk/chains.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from operator import itemgetter
|
3 |
+
from lets_talk import config
|
4 |
+
from langchain_openai.chat_models import ChatOpenAI
|
5 |
+
from langchain_core.prompts import ChatPromptTemplate
|
6 |
+
|
7 |
+
chat_llm = ChatOpenAI(model=config.LLM_MODEL, temperature=config.LLM_TEMPERATURE)
|
8 |
+
|
9 |
+
#create a chain to check query tone
|
10 |
+
from lets_talk.prompts import query_tone_check_prompt_template
|
11 |
+
|
12 |
+
|
13 |
+
tone_check_prompt = ChatPromptTemplate.from_template(query_tone_check_prompt_template)
|
14 |
+
|
15 |
+
# Create chain
|
16 |
+
tone_check_chain = (
|
17 |
+
tone_check_prompt | chat_llm
|
18 |
+
|
19 |
+
)
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
|
24 |
+
from lets_talk.prompts import rude_query_answer_prompt_template
|
25 |
+
|
26 |
+
rude_query_answer_prompt = ChatPromptTemplate.from_template(rude_query_answer_prompt_template)
|
27 |
+
# Create chain
|
28 |
+
rude_query_answer_chain = (
|
29 |
+
rude_query_answer_prompt | chat_llm
|
30 |
+
)
|
py-src/lets_talk/config.py
CHANGED
@@ -25,6 +25,6 @@ FORCE_RECREATE = os.environ.get("FORCE_RECREATE", "False").lower() == "true"
|
|
25 |
OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "./stats")
|
26 |
USE_CHUNKING = os.environ.get("USE_CHUNKING", "True").lower() == "true"
|
27 |
SHOULD_SAVE_STATS = os.environ.get("SHOULD_SAVE_STATS", "True").lower() == "true"
|
28 |
-
|
29 |
|
30 |
|
|
|
25 |
OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "./stats")
|
26 |
USE_CHUNKING = os.environ.get("USE_CHUNKING", "True").lower() == "true"
|
27 |
SHOULD_SAVE_STATS = os.environ.get("SHOULD_SAVE_STATS", "True").lower() == "true"
|
28 |
+
CREATE_VECTOR_DB = os.environ.get("CREATE_VECTOR_DB", "True").lower() == "true"
|
29 |
|
30 |
|
py-src/lets_talk/prompts.py
CHANGED
@@ -1,10 +1,26 @@
|
|
1 |
-
|
2 |
-
|
3 |
# Create RAG prompt template
|
4 |
rag_prompt_template = """\
|
5 |
-
You are a
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
You have access to the following information:
|
9 |
|
10 |
Context:
|
@@ -13,74 +29,77 @@ Context:
|
|
13 |
Question:
|
14 |
{question}
|
15 |
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
17 |
"""
|
18 |
|
19 |
|
20 |
call_llm_prompt_template = """\
|
21 |
-
You are a helpful assistant that answers questions based on the context provided.
|
22 |
-
Generate a concise answer to the question in markdown format and include a list of relevant links to the context.
|
23 |
-
You have access to the following information:
|
24 |
-
|
25 |
Context:
|
26 |
{context}
|
27 |
-
|
28 |
-
If context is unrelated to question, say "I don't know".
|
29 |
"""
|
30 |
|
31 |
|
32 |
|
33 |
query_tone_check_prompt_template = """\
|
34 |
-
Check if the input
|
35 |
|
36 |
-
|
37 |
-
{
|
38 |
# Output Format
|
39 |
|
40 |
Respond only with "YES" or "NO".
|
41 |
"""
|
42 |
|
43 |
rude_query_answer_prompt_template = """\
|
44 |
-
Respond to negative, rude, or derogatory questions or statements with respect, positivity, and an uplifting tone.
|
45 |
|
46 |
-
Address the initial sentiment or statement with understanding and empathy before providing a positive response. Aim to uplift the conversation, converting any negative interaction into a positive engagement.
|
|
|
|
|
|
|
47 |
|
48 |
# Steps
|
49 |
|
50 |
-
1. Identify the negative or derogatory sentiment in the input.
|
51 |
-
2. Acknowledge the sentiment or emotion behind the statement with empathy.
|
52 |
-
3. Respond with positivity, focusing on encouraging and uplifting language.
|
53 |
-
4. Conclude with a respectful and positive closing remark.
|
54 |
|
55 |
# Output Format
|
56 |
|
57 |
-
Respond using concise sentences or short paragraphs, maintaining a respectful and positive tone throughout.
|
58 |
|
59 |
# Examples
|
60 |
|
61 |
**Example 1:**
|
62 |
|
63 |
- **Input:** "Go away"
|
64 |
-
- **Output:** "I understand you might need some space, and I'm here to help whenever you're ready. Take care!"
|
65 |
|
66 |
**Example 2:**
|
67 |
|
68 |
- **Input:** "I am angry now"
|
69 |
-
- **Output:** "It's okay to feel angry sometimes. If you need someone to talk to, I'm here for you, and we'll find a way through this together!"
|
70 |
|
71 |
**Example 3:**
|
72 |
|
73 |
- **Input:** "Tell me something emse"
|
74 |
-
- **Output:** "Sure, I'd love to share something uplifting with you! Did you know that taking a moment to appreciate small things can brighten your day?
|
75 |
|
76 |
**Example 4:**
|
77 |
|
78 |
- **Input:** "RIP you are awful"
|
79 |
-
- **Output:** "I'm sorry if I disappointed you. I'm here to improve and assist you better. Let's turn this around together!"
|
80 |
|
81 |
# Notes
|
82 |
|
83 |
-
- Always maintain a positive and empathetic approach, even when the input is challenging.
|
84 |
-
- Aim to uplift and provide encouragement, transforming the interaction into a positive experience.
|
85 |
"""
|
86 |
|
|
|
|
|
|
|
1 |
# Create RAG prompt template
|
2 |
rag_prompt_template = """\
|
3 |
+
You are TheDataGuy Chat, a specialized assistant powered by content from Muhammad Afzaal's blog at thedataguy.pro. You are expert in data science, AI evaluation, RAG systems, research agents, and metric-driven development.
|
4 |
+
|
5 |
+
## Your Purpose
|
6 |
+
You provide practical, insightful responses to questions about topics covered in TheDataGuy's blog posts, including:
|
7 |
+
- RAGAS and evaluation frameworks for LLM applications
|
8 |
+
- RAG (Retrieval-Augmented Generation) systems and their implementation
|
9 |
+
- Building and evaluating AI research agents
|
10 |
+
- Metric-Driven Development for technology projects
|
11 |
+
- Data strategy and its importance for business success
|
12 |
+
- Technical concepts in AI, LLM applications, and data science
|
13 |
+
|
14 |
+
## Response Guidelines
|
15 |
+
1. Generate clear, concise responses in markdown format
|
16 |
+
2. Include relevant links to blog posts to help users find more information
|
17 |
+
3. For code examples, use appropriate syntax highlighting
|
18 |
+
4. When practical, provide actionable steps or implementations
|
19 |
+
5. Maintain a helpful, informative tone consistent with TheDataGuy's writing style
|
20 |
+
6. When providing links, use the URL format from the context: [title or description](URL)
|
21 |
+
7. If discussing a series of blog posts, mention related posts when appropriate
|
22 |
+
|
23 |
+
## Context Management
|
24 |
You have access to the following information:
|
25 |
|
26 |
Context:
|
|
|
29 |
Question:
|
30 |
{question}
|
31 |
|
32 |
+
## Special Cases
|
33 |
+
- If the context is unrelated to the question, respond with "I don't know" and suggest relevant topics that are covered in the blog
|
34 |
+
- If asked about topics beyond the blog's scope, politely explain your focus areas and suggest checking thedataguy.pro for the latest content
|
35 |
+
- Use real-world examples to illustrate complex concepts, similar to those in the blog posts
|
36 |
+
|
37 |
+
Remember, your goal is to help users understand TheDataGuy's insights and apply them to their own projects and challenges.
|
38 |
"""
|
39 |
|
40 |
|
41 |
call_llm_prompt_template = """\
|
42 |
+
You are a helpful assistant that answers questions based on the context provided.
|
|
|
|
|
|
|
43 |
Context:
|
44 |
{context}
|
|
|
|
|
45 |
"""
|
46 |
|
47 |
|
48 |
|
49 |
query_tone_check_prompt_template = """\
|
50 |
+
Check if the input question is rude, derogatory, disrespectful, or negative, and respond with "YES" or "NO".
|
51 |
|
52 |
+
Question:
|
53 |
+
{question}
|
54 |
# Output Format
|
55 |
|
56 |
Respond only with "YES" or "NO".
|
57 |
"""
|
58 |
|
59 |
rude_query_answer_prompt_template = """\
|
60 |
+
Respond to negative, rude, or derogatory questions or statements with respect, positivity, and an uplifting tone. ✨
|
61 |
|
62 |
+
Address the initial sentiment or statement with understanding and empathy before providing a positive response. Aim to uplift the conversation, converting any negative interaction into a positive engagement. 🌈
|
63 |
+
|
64 |
+
# Question
|
65 |
+
{question}
|
66 |
|
67 |
# Steps
|
68 |
|
69 |
+
1. Identify the negative or derogatory sentiment in the input. 🔍
|
70 |
+
2. Acknowledge the sentiment or emotion behind the statement with empathy. ❤️
|
71 |
+
3. Respond with positivity, focusing on encouraging and uplifting language. 🌟
|
72 |
+
4. Conclude with a respectful and positive closing remark. 🙏
|
73 |
|
74 |
# Output Format
|
75 |
|
76 |
+
Respond using concise sentences or short paragraphs, maintaining a respectful and positive tone throughout. 😊
|
77 |
|
78 |
# Examples
|
79 |
|
80 |
**Example 1:**
|
81 |
|
82 |
- **Input:** "Go away"
|
83 |
+
- **Output:** "I understand you might need some space, and I'm here to help whenever you're ready. Take care! 🌻"
|
84 |
|
85 |
**Example 2:**
|
86 |
|
87 |
- **Input:** "I am angry now"
|
88 |
+
- **Output:** "It's okay to feel angry sometimes. If you need someone to talk to, I'm here for you, and we'll find a way through this together! 🤗"
|
89 |
|
90 |
**Example 3:**
|
91 |
|
92 |
- **Input:** "Tell me something emse"
|
93 |
+
- **Output:** "Sure, I'd love to share something uplifting with you! Did you know that taking a moment to appreciate small things can brighten your day? 💫"
|
94 |
|
95 |
**Example 4:**
|
96 |
|
97 |
- **Input:** "RIP you are awful"
|
98 |
+
- **Output:** "I'm sorry if I disappointed you. I'm here to improve and assist you better. Let's turn this around together! 🌱"
|
99 |
|
100 |
# Notes
|
101 |
|
102 |
+
- Always maintain a positive and empathetic approach, even when the input is challenging. 💖
|
103 |
+
- Aim to uplift and provide encouragement, transforming the interaction into a positive experience. ✨
|
104 |
"""
|
105 |
|
py-src/lets_talk/rag.py
CHANGED
@@ -2,16 +2,16 @@
|
|
2 |
RAG (Retrieval Augmented Generation) model implementation.
|
3 |
"""
|
4 |
from operator import itemgetter
|
5 |
-
from langchain_core.prompts import ChatPromptTemplate
|
6 |
-
from langchain_core.output_parsers import StrOutputParser
|
7 |
from langchain_core.runnables import RunnablePassthrough
|
8 |
from langchain.prompts import ChatPromptTemplate
|
9 |
from langchain.schema.runnable import RunnablePassthrough
|
10 |
-
from langchain_openai.chat_models import ChatOpenAI
|
11 |
from langchain_qdrant import QdrantVectorStore
|
12 |
-
|
13 |
from lets_talk.utils import blog
|
14 |
import lets_talk.utils.blog as blog
|
|
|
|
|
15 |
|
16 |
# Load vector store using the utility function
|
17 |
vector_store:QdrantVectorStore = blog.load_vector_store()
|
@@ -19,18 +19,13 @@ vector_store:QdrantVectorStore = blog.load_vector_store()
|
|
19 |
# Create a retriever
|
20 |
retriever = vector_store.as_retriever()
|
21 |
|
22 |
-
llm = ChatOpenAI(model=config.LLM_MODEL, temperature=config.LLM_TEMPERATURE)
|
23 |
-
|
24 |
-
|
25 |
-
from lets_talk.prompts import rag_prompt_template
|
26 |
-
|
27 |
rag_prompt = ChatPromptTemplate.from_template(rag_prompt_template)
|
28 |
|
29 |
# Create chain
|
30 |
rag_chain = (
|
31 |
{"context": itemgetter("question") | retriever, "question": itemgetter("question")}
|
32 |
| RunnablePassthrough.assign(context=itemgetter("context"))
|
33 |
-
| {"response": rag_prompt |
|
34 |
)
|
35 |
|
36 |
|
|
|
2 |
RAG (Retrieval Augmented Generation) model implementation.
|
3 |
"""
|
4 |
from operator import itemgetter
|
5 |
+
from langchain_core.prompts import ChatPromptTemplate
|
|
|
6 |
from langchain_core.runnables import RunnablePassthrough
|
7 |
from langchain.prompts import ChatPromptTemplate
|
8 |
from langchain.schema.runnable import RunnablePassthrough
|
|
|
9 |
from langchain_qdrant import QdrantVectorStore
|
10 |
+
|
11 |
from lets_talk.utils import blog
|
12 |
import lets_talk.utils.blog as blog
|
13 |
+
from lets_talk.chains import chat_llm
|
14 |
+
from lets_talk.prompts import rag_prompt_template
|
15 |
|
16 |
# Load vector store using the utility function
|
17 |
vector_store:QdrantVectorStore = blog.load_vector_store()
|
|
|
19 |
# Create a retriever
|
20 |
retriever = vector_store.as_retriever()
|
21 |
|
|
|
|
|
|
|
|
|
|
|
22 |
rag_prompt = ChatPromptTemplate.from_template(rag_prompt_template)
|
23 |
|
24 |
# Create chain
|
25 |
rag_chain = (
|
26 |
{"context": itemgetter("question") | retriever, "question": itemgetter("question")}
|
27 |
| RunnablePassthrough.assign(context=itemgetter("context"))
|
28 |
+
| {"response": rag_prompt | chat_llm, "context": itemgetter("context")}
|
29 |
)
|
30 |
|
31 |
|
py-src/lets_talk/tools.py
CHANGED
@@ -6,6 +6,7 @@ from langchain_community.tools.arxiv.tool import ArxivQueryRun
|
|
6 |
#from langchain_community.tools import DuckDuckGoSearchResults
|
7 |
from langchain_core.tools import Tool
|
8 |
from .rss_tool import rss_feed_tool
|
|
|
9 |
|
10 |
|
11 |
def create_search_tools(max_results=5):
|
@@ -29,22 +30,60 @@ def create_search_tools(max_results=5):
|
|
29 |
"""
|
30 |
def _rss_feed_tool_wrapper(*args, **kwargs):
|
31 |
|
32 |
-
return rss_feed_tool(urls=['https://thedataguy.pro/rss.xml'])
|
33 |
|
34 |
return Tool(
|
35 |
name="RSSFeedReader",
|
36 |
-
description="
|
37 |
func=_rss_feed_tool_wrapper
|
38 |
)
|
39 |
|
40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
# Initialize standard search tools
|
42 |
#duckduckgo_tool = DuckDuckGoSearchResults(max_results=max_results)
|
43 |
#arxiv_tool = ArxivQueryRun()
|
44 |
tdg_rss_tool = create_rss_feed_tool()
|
|
|
45 |
|
46 |
return [
|
47 |
tdg_rss_tool,
|
48 |
#duckduckgo_tool,
|
49 |
#arxiv_tool,
|
|
|
50 |
]
|
|
|
6 |
#from langchain_community.tools import DuckDuckGoSearchResults
|
7 |
from langchain_core.tools import Tool
|
8 |
from .rss_tool import rss_feed_tool
|
9 |
+
import datetime
|
10 |
|
11 |
|
12 |
def create_search_tools(max_results=5):
|
|
|
30 |
"""
|
31 |
def _rss_feed_tool_wrapper(*args, **kwargs):
|
32 |
|
33 |
+
return rss_feed_tool(urls=['https://thedataguy.pro/rss.xml'], max_results=10)
|
34 |
|
35 |
return Tool(
|
36 |
name="RSSFeedReader",
|
37 |
+
description="Fetches recent articles from TheDataGuy's tech blog. Use this tool when you need information about data engineering, analytics, or when asked about recent tech trends, tutorials or best practices from TheDataGuy blog. The tool returns content from https://thedataguy.pro/rss.xml with a maximum of 10 results.",
|
38 |
func=_rss_feed_tool_wrapper
|
39 |
)
|
40 |
|
41 |
|
42 |
+
def create_datetime_tool() -> Tool:
|
43 |
+
"""
|
44 |
+
Create and return a date-time tool.
|
45 |
+
|
46 |
+
Returns:
|
47 |
+
Tool object for providing current date and time
|
48 |
+
"""
|
49 |
+
def _datetime_tool_wrapper(*args, **kwargs):
|
50 |
+
now = datetime.datetime.now()
|
51 |
+
formatted_datetime = now.strftime("%Y-%m-%d %H:%M:%S")
|
52 |
+
weekday = now.strftime("%A")
|
53 |
+
|
54 |
+
result = {
|
55 |
+
"current_datetime": formatted_datetime,
|
56 |
+
"date": now.strftime("%Y-%m-%d"),
|
57 |
+
"time": now.strftime("%H:%M:%S"),
|
58 |
+
"year": now.year,
|
59 |
+
"month": now.month,
|
60 |
+
"month_name": now.strftime("%B"),
|
61 |
+
"day": now.day,
|
62 |
+
"weekday": weekday,
|
63 |
+
"hour": now.hour,
|
64 |
+
"minute": now.minute,
|
65 |
+
"second": now.second,
|
66 |
+
"timezone": str(datetime.datetime.now().astimezone().tzinfo)
|
67 |
+
}
|
68 |
+
|
69 |
+
return f"Current date and time information: {result}"
|
70 |
+
|
71 |
+
return Tool(
|
72 |
+
name="CurrentDateTime",
|
73 |
+
description="Provides the current date and time information. Use this tool when you need to know the current date, time, day of week, or other temporal information.",
|
74 |
+
func=_datetime_tool_wrapper
|
75 |
+
)
|
76 |
+
|
77 |
+
|
78 |
# Initialize standard search tools
|
79 |
#duckduckgo_tool = DuckDuckGoSearchResults(max_results=max_results)
|
80 |
#arxiv_tool = ArxivQueryRun()
|
81 |
tdg_rss_tool = create_rss_feed_tool()
|
82 |
+
datetime_tool = create_datetime_tool()
|
83 |
|
84 |
return [
|
85 |
tdg_rss_tool,
|
86 |
#duckduckgo_tool,
|
87 |
#arxiv_tool,
|
88 |
+
datetime_tool
|
89 |
]
|
py-src/notebooks/00_playground.ipynb
ADDED
@@ -0,0 +1,506 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 1,
|
6 |
+
"id": "c5fe74d8",
|
7 |
+
"metadata": {},
|
8 |
+
"outputs": [
|
9 |
+
{
|
10 |
+
"name": "stdout",
|
11 |
+
"output_type": "stream",
|
12 |
+
"text": [
|
13 |
+
"Adding package root to sys.path: /home/mafzaal/source/lets-talk/py-src\n",
|
14 |
+
"Current notebook directory: /home/mafzaal/source/lets-talk/py-src/notebooks\n",
|
15 |
+
"Project root: /home/mafzaal/source/lets-talk\n"
|
16 |
+
]
|
17 |
+
}
|
18 |
+
],
|
19 |
+
"source": [
|
20 |
+
"import sys\n",
|
21 |
+
"import os\n",
|
22 |
+
"\n",
|
23 |
+
"# Add the project root to the Python path\n",
|
24 |
+
"package_root = os.path.abspath(os.path.join(os.getcwd(), \"../\"))\n",
|
25 |
+
"print(f\"Adding package root to sys.path: {package_root}\")\n",
|
26 |
+
"if package_root not in sys.path:\n",
|
27 |
+
"\tsys.path.append(package_root)\n",
|
28 |
+
"\n",
|
29 |
+
"\n",
|
30 |
+
"notebook_dir = os.getcwd()\n",
|
31 |
+
"print(f\"Current notebook directory: {notebook_dir}\")\n",
|
32 |
+
"# change to the directory to the root of the project\n",
|
33 |
+
"project_root = os.path.abspath(os.path.join(os.getcwd(), \"../../\"))\n",
|
34 |
+
"print(f\"Project root: {project_root}\")\n",
|
35 |
+
"os.chdir(project_root)"
|
36 |
+
]
|
37 |
+
},
|
38 |
+
{
|
39 |
+
"cell_type": "code",
|
40 |
+
"execution_count": 3,
|
41 |
+
"id": "1168fdc5",
|
42 |
+
"metadata": {},
|
43 |
+
"outputs": [],
|
44 |
+
"source": [
|
45 |
+
"import nest_asyncio\n",
|
46 |
+
"nest_asyncio.apply()"
|
47 |
+
]
|
48 |
+
},
|
49 |
+
{
|
50 |
+
"cell_type": "code",
|
51 |
+
"execution_count": 4,
|
52 |
+
"id": "12ecd1db",
|
53 |
+
"metadata": {},
|
54 |
+
"outputs": [],
|
55 |
+
"source": [
|
56 |
+
"import lets_talk.chains as chains\n",
|
57 |
+
"import lets_talk.prompts as prompts"
|
58 |
+
]
|
59 |
+
},
|
60 |
+
{
|
61 |
+
"cell_type": "code",
|
62 |
+
"execution_count": 40,
|
63 |
+
"id": "1b6bbe57",
|
64 |
+
"metadata": {},
|
65 |
+
"outputs": [
|
66 |
+
{
|
67 |
+
"data": {
|
68 |
+
"text/plain": [
|
69 |
+
"<module 'lets_talk.prompts' from '/home/mafzaal/source/lets-talk/py-src/lets_talk/prompts.py'>"
|
70 |
+
]
|
71 |
+
},
|
72 |
+
"execution_count": 40,
|
73 |
+
"metadata": {},
|
74 |
+
"output_type": "execute_result"
|
75 |
+
}
|
76 |
+
],
|
77 |
+
"source": [
|
78 |
+
"# hot reload the module\n",
|
79 |
+
"import importlib\n",
|
80 |
+
"importlib.reload(chains)\n",
|
81 |
+
"importlib.reload(prompts)\n"
|
82 |
+
]
|
83 |
+
},
|
84 |
+
{
|
85 |
+
"cell_type": "code",
|
86 |
+
"execution_count": 6,
|
87 |
+
"id": "65e5ea03",
|
88 |
+
"metadata": {},
|
89 |
+
"outputs": [
|
90 |
+
{
|
91 |
+
"data": {
|
92 |
+
"text/plain": [
|
93 |
+
"False"
|
94 |
+
]
|
95 |
+
},
|
96 |
+
"execution_count": 6,
|
97 |
+
"metadata": {},
|
98 |
+
"output_type": "execute_result"
|
99 |
+
}
|
100 |
+
],
|
101 |
+
"source": [
|
102 |
+
"reponse = chains.tone_check_chain.invoke({\"question\": \"I am so happy to be here!\"})\n",
|
103 |
+
"reponse.content.lower() == \"yes\""
|
104 |
+
]
|
105 |
+
},
|
106 |
+
{
|
107 |
+
"cell_type": "code",
|
108 |
+
"execution_count": 7,
|
109 |
+
"id": "119cf326",
|
110 |
+
"metadata": {},
|
111 |
+
"outputs": [
|
112 |
+
{
|
113 |
+
"data": {
|
114 |
+
"text/plain": [
|
115 |
+
"True"
|
116 |
+
]
|
117 |
+
},
|
118 |
+
"execution_count": 7,
|
119 |
+
"metadata": {},
|
120 |
+
"output_type": "execute_result"
|
121 |
+
}
|
122 |
+
],
|
123 |
+
"source": [
|
124 |
+
"reponse = chains.tone_check_chain.invoke({\"question\": \"Go to hell!\"})\n",
|
125 |
+
"reponse.content.lower() == \"yes\""
|
126 |
+
]
|
127 |
+
},
|
128 |
+
{
|
129 |
+
"cell_type": "code",
|
130 |
+
"execution_count": 18,
|
131 |
+
"id": "ada70fe7",
|
132 |
+
"metadata": {},
|
133 |
+
"outputs": [],
|
134 |
+
"source": [
|
135 |
+
"from lets_talk.rag import rag_chain\n",
|
136 |
+
"reponse = rag_chain.invoke({\"question\": \"Who is the data guy?\"})"
|
137 |
+
]
|
138 |
+
},
|
139 |
+
{
|
140 |
+
"cell_type": "code",
|
141 |
+
"execution_count": 20,
|
142 |
+
"id": "5d878cf2",
|
143 |
+
"metadata": {},
|
144 |
+
"outputs": [
|
145 |
+
{
|
146 |
+
"data": {
|
147 |
+
"text/plain": [
|
148 |
+
"\"The Data Guy refers to Muhammad Afzaal, a data science expert and the author of the blog at [thedataguy.pro](https://thedataguy.pro). His work focuses on various topics in data science, AI evaluation, RAG systems, and metric-driven development, providing practical insights and frameworks for implementing these concepts effectively.\\n\\nIf you're interested in specific topics such as RAG systems, AI research agents, or data strategy, feel free to ask!\""
|
149 |
+
]
|
150 |
+
},
|
151 |
+
"execution_count": 20,
|
152 |
+
"metadata": {},
|
153 |
+
"output_type": "execute_result"
|
154 |
+
}
|
155 |
+
],
|
156 |
+
"source": [
|
157 |
+
"reponse[\"response\"].content"
|
158 |
+
]
|
159 |
+
},
|
160 |
+
{
|
161 |
+
"cell_type": "code",
|
162 |
+
"execution_count": 13,
|
163 |
+
"id": "9689e103",
|
164 |
+
"metadata": {},
|
165 |
+
"outputs": [
|
166 |
+
{
|
167 |
+
"data": {
|
168 |
+
"text/plain": [
|
169 |
+
"\"I don't know the speed of light, but I can help you with topics related to data science, AI evaluation, RAG systems, and more. If you're interested in understanding how to evaluate AI agents or implement RAG systems, feel free to ask! You can also explore more on these topics at [TheDataGuy's blog](https://thedataguy.pro).\""
|
170 |
+
]
|
171 |
+
},
|
172 |
+
"execution_count": 13,
|
173 |
+
"metadata": {},
|
174 |
+
"output_type": "execute_result"
|
175 |
+
}
|
176 |
+
],
|
177 |
+
"source": [
|
178 |
+
"from lets_talk.rag import rag_chain\n",
|
179 |
+
"reponse = rag_chain.invoke({\"question\": \"What is speed of light!\"})\n",
|
180 |
+
"reponse[\"response\"].content"
|
181 |
+
]
|
182 |
+
},
|
183 |
+
{
|
184 |
+
"cell_type": "code",
|
185 |
+
"execution_count": 8,
|
186 |
+
"id": "80979f2c",
|
187 |
+
"metadata": {},
|
188 |
+
"outputs": [
|
189 |
+
{
|
190 |
+
"data": {
|
191 |
+
"text/plain": [
|
192 |
+
"\"I can sense that you're feeling really frustrated right now, and that's completely valid. We all have moments like that. 🌧️ \\n\\nIf there's something specific on your mind, I'm here to listen and help in any way I can. Let's turn this around and find a brighter perspective together! 🌈\""
|
193 |
+
]
|
194 |
+
},
|
195 |
+
"execution_count": 8,
|
196 |
+
"metadata": {},
|
197 |
+
"output_type": "execute_result"
|
198 |
+
}
|
199 |
+
],
|
200 |
+
"source": [
|
201 |
+
"from lets_talk.chains import rude_query_answer_chain\n",
|
202 |
+
"reponse = rude_query_answer_chain.invoke({\"question\": \"Go to hell!\"})\n",
|
203 |
+
"\n",
|
204 |
+
"reponse.content"
|
205 |
+
]
|
206 |
+
},
|
207 |
+
{
|
208 |
+
"cell_type": "code",
|
209 |
+
"execution_count": 10,
|
210 |
+
"id": "21a54913",
|
211 |
+
"metadata": {},
|
212 |
+
"outputs": [
|
213 |
+
{
|
214 |
+
"data": {
|
215 |
+
"text/plain": [
|
216 |
+
"\"I understand that you might be feeling frustrated or disappointed, and that's completely valid. It's okay to express those feelings! Let's focus on finding something positive together. What’s something that brings you joy or makes you smile? 🌈\""
|
217 |
+
]
|
218 |
+
},
|
219 |
+
"execution_count": 10,
|
220 |
+
"metadata": {},
|
221 |
+
"output_type": "execute_result"
|
222 |
+
}
|
223 |
+
],
|
224 |
+
"source": [
|
225 |
+
"from lets_talk.chains import rude_query_answer_chain\n",
|
226 |
+
"reponse = rude_query_answer_chain.invoke({\"question\": \"aweful!\"})\n",
|
227 |
+
"\n",
|
228 |
+
"reponse.content"
|
229 |
+
]
|
230 |
+
},
|
231 |
+
{
|
232 |
+
"cell_type": "code",
|
233 |
+
"execution_count": null,
|
234 |
+
"id": "661c3b55",
|
235 |
+
"metadata": {},
|
236 |
+
"outputs": [
|
237 |
+
{
|
238 |
+
"data": {
|
239 |
+
"text/plain": [
|
240 |
+
"'**Input:** \"Tell me a joke!\"\\n\\n**Output:** \"I love that you\\'re looking for some humor! Laughter is such a wonderful way to brighten the day. Here’s a light-hearted joke for you: Why did the scarecrow win an award? Because he was outstanding in his field! 🌾😄 Keep smiling!\"'"
|
241 |
+
]
|
242 |
+
},
|
243 |
+
"execution_count": 11,
|
244 |
+
"metadata": {},
|
245 |
+
"output_type": "execute_result"
|
246 |
+
}
|
247 |
+
],
|
248 |
+
"source": [
|
249 |
+
"from lets_talk.chains import rude_query_answer_chain\n",
|
250 |
+
"reponse = rude_query_answer_chain.invoke({\"question\": \"tell me a joke!\"})\n",
|
251 |
+
"\n",
|
252 |
+
"reponse.content"
|
253 |
+
]
|
254 |
+
},
|
255 |
+
{
|
256 |
+
"cell_type": "code",
|
257 |
+
"execution_count": 37,
|
258 |
+
"id": "1600c552",
|
259 |
+
"metadata": {},
|
260 |
+
"outputs": [],
|
261 |
+
"source": [
|
262 |
+
"import lets_talk.agent as agent\n"
|
263 |
+
]
|
264 |
+
},
|
265 |
+
{
|
266 |
+
"cell_type": "code",
|
267 |
+
"execution_count": 79,
|
268 |
+
"id": "ebf7366d",
|
269 |
+
"metadata": {},
|
270 |
+
"outputs": [
|
271 |
+
{
|
272 |
+
"data": {
|
273 |
+
"text/plain": [
|
274 |
+
"<module 'lets_talk.agent' from '/home/mafzaal/source/lets-talk/py-src/lets_talk/agent.py'>"
|
275 |
+
]
|
276 |
+
},
|
277 |
+
"execution_count": 79,
|
278 |
+
"metadata": {},
|
279 |
+
"output_type": "execute_result"
|
280 |
+
}
|
281 |
+
],
|
282 |
+
"source": [
|
283 |
+
"importlib.reload(agent)\n"
|
284 |
+
]
|
285 |
+
},
|
286 |
+
{
|
287 |
+
"cell_type": "code",
|
288 |
+
"execution_count": 80,
|
289 |
+
"id": "ee9f31e9",
|
290 |
+
"metadata": {},
|
291 |
+
"outputs": [],
|
292 |
+
"source": [
|
293 |
+
"\n",
|
294 |
+
"uncompiled_graph = agent.build_graph()\n",
|
295 |
+
"graph = uncompiled_graph.compile()\n",
|
296 |
+
"\n",
|
297 |
+
"#show the graph\n"
|
298 |
+
]
|
299 |
+
},
|
300 |
+
{
|
301 |
+
"cell_type": "code",
|
302 |
+
"execution_count": 64,
|
303 |
+
"id": "1204c3c9",
|
304 |
+
"metadata": {},
|
305 |
+
"outputs": [
|
306 |
+
{
|
307 |
+
"name": "stdout",
|
308 |
+
"output_type": "stream",
|
309 |
+
"text": [
|
310 |
+
" +-----------+ \n",
|
311 |
+
" | __start__ | \n",
|
312 |
+
" +-----------+ \n",
|
313 |
+
" * \n",
|
314 |
+
" * \n",
|
315 |
+
" * \n",
|
316 |
+
" +---------------------+ \n",
|
317 |
+
" | check_question_tone | \n",
|
318 |
+
" +---------------------+ \n",
|
319 |
+
" . .. \n",
|
320 |
+
" .. . \n",
|
321 |
+
" . .. \n",
|
322 |
+
"+----------+ . \n",
|
323 |
+
"| retrieve | .. \n",
|
324 |
+
"+----------+ . \n",
|
325 |
+
" * .. \n",
|
326 |
+
" ** .. \n",
|
327 |
+
" * . \n",
|
328 |
+
" +-------+ \n",
|
329 |
+
" | agent | \n",
|
330 |
+
" +-------+ \n",
|
331 |
+
" * . \n",
|
332 |
+
" ** .. \n",
|
333 |
+
" * . \n",
|
334 |
+
" +--------+ +---------+ \n",
|
335 |
+
" | action | | __end__ | \n",
|
336 |
+
" +--------+ +---------+ \n"
|
337 |
+
]
|
338 |
+
}
|
339 |
+
],
|
340 |
+
"source": [
|
341 |
+
"# from IPython.display import Image, display\n",
|
342 |
+
"# display(Image(graph.get_graph().draw_png()))\n",
|
343 |
+
"\n",
|
344 |
+
"print(graph.get_graph().draw_ascii())"
|
345 |
+
]
|
346 |
+
},
|
347 |
+
{
|
348 |
+
"cell_type": "code",
|
349 |
+
"execution_count": 81,
|
350 |
+
"id": "94889b85",
|
351 |
+
"metadata": {},
|
352 |
+
"outputs": [],
|
353 |
+
"source": [
|
354 |
+
"graph_chain = agent.create_agent_chain(uncompiled_graph=uncompiled_graph)\n"
|
355 |
+
]
|
356 |
+
},
|
357 |
+
{
|
358 |
+
"cell_type": "code",
|
359 |
+
"execution_count": 74,
|
360 |
+
"id": "f8a9985d",
|
361 |
+
"metadata": {},
|
362 |
+
"outputs": [
|
363 |
+
{
|
364 |
+
"data": {
|
365 |
+
"text/plain": [
|
366 |
+
"{'messages': [HumanMessage(content='tell me a joke!', additional_kwargs={}, response_metadata={}, id='df2b46db-4109-448b-85a5-4fb91b0d1f36'),\n",
|
367 |
+
" AIMessage(content=\"I don't know any jokes, but I can share some insightful content about data engineering or AI evaluation! If you're interested, let me know!\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 30, 'prompt_tokens': 990, 'total_tokens': 1020, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_dbaca60df0', 'id': 'chatcmpl-BWabqM5r06LNi5Ty3VRxWZwXYqIUk', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--9874e8ab-653a-45f2-be45-0f3ce9de8ee7-0', usage_metadata={'input_tokens': 990, 'output_tokens': 30, 'total_tokens': 1020, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})],\n",
|
368 |
+
" 'context': 'link: https://thedataguy.pro/blog/integrations-and-observability-with-ragas/\\n\\n*How are you evaluating your AI agents? What challenges have you encountered in measuring agent performance? If you\\'re facing specific evaluation hurdles, don\\'t hesitate to [reach out](https://www.linkedin.com/in/muhammadafzaal/)—we\\'d love to help!*\\n\\n---link: https://thedataguy.pro/blog/advanced-metrics-and-customization-with-ragas/\\n\\n# Prepare input for prompt\\n prompt_input = TechnicalAccuracyInput(\\n question=question,\\n context=context,\\n response=response,\\n programming_language=programming_language\\n )\\n \\n # Generate evaluation\\n evaluation = await self.evaluation_prompt.generate(\\n data=prompt_input, llm=self.llm, callbacks=callbacks\\n )\\n \\n return evaluation.score\\n```\\n## Using the Custom Metric\\nTo use the custom metric, simply include it in your evaluation pipeline:\\n\\n---link: https://thedataguy.pro/blog/data-is-king/\\n\\nRemember: in the age of AI, your data strategy isn\\'t just supporting your business strategy—increasingly, it *is* your business strategy.\\n## Ready to Make Data Your Competitive Advantage?\\n\\nDon\\'t let valuable data opportunities slip away. Whether you\\'re just beginning your data journey or looking to enhance your existing strategy, I can help transform your approach to this critical business asset.\\n\\n### Let\\'s Connect\\nConnect with me on [LinkedIn](https://www.linkedin.com/in/muhammadafzaal/) to discuss how I can help your organization harness the power of data.\\n\\n---link: https://thedataguy.pro/blog/generating-test-data-with-ragas/\\n\\nEssentially, the default transformations build a knowledge graph populated with embedded, filtered document chunks and corresponding simple, extractive question-answer pairs.\\n\\n**Spotlight: Query Synthesizers (via `self.generate()` and `default_query_distribution`)**\\n\\nThe `self.generate()` method, called by `generate_with_langchain_docs`, is responsible for taking the foundational graph and creating the final, potentially complex, test questions using **Query Synthesizers** (also referred to as \"evolutions\" or \"scenarios\").',\n",
|
369 |
+
" 'is_rude': False}"
|
370 |
+
]
|
371 |
+
},
|
372 |
+
"execution_count": 74,
|
373 |
+
"metadata": {},
|
374 |
+
"output_type": "execute_result"
|
375 |
+
}
|
376 |
+
],
|
377 |
+
"source": [
|
378 |
+
"response = graph_chain.invoke({\"question\": \"tell me a joke!\"})\n",
|
379 |
+
"response"
|
380 |
+
]
|
381 |
+
},
|
382 |
+
{
|
383 |
+
"cell_type": "code",
|
384 |
+
"execution_count": 49,
|
385 |
+
"id": "6b34229f",
|
386 |
+
"metadata": {},
|
387 |
+
"outputs": [],
|
388 |
+
"source": [
|
389 |
+
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\""
|
390 |
+
]
|
391 |
+
},
|
392 |
+
{
|
393 |
+
"cell_type": "code",
|
394 |
+
"execution_count": 82,
|
395 |
+
"id": "8d970d22",
|
396 |
+
"metadata": {},
|
397 |
+
"outputs": [
|
398 |
+
{
|
399 |
+
"data": {
|
400 |
+
"text/plain": [
|
401 |
+
"{'messages': [HumanMessage(content='Go to hell!', additional_kwargs={}, response_metadata={}, id='ada716be-d732-4df7-813f-8e4134bf86e6'),\n",
|
402 |
+
" AIMessage(content=\"I can sense that you're feeling really frustrated right now, and that's completely valid. We all have moments like that. 🌧️ \\n\\nIf there's something specific on your mind, I'm here to listen and help in any way I can. Let's turn this around and find a brighter perspective together! 🌈\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 61, 'prompt_tokens': 401, 'total_tokens': 462, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_129a36352a', 'id': 'chatcmpl-BWadnZqMM9ST3mPc8sigjYQNZQwdz', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--f08d6e19-838d-4594-8b19-4422ef2eddf3-0', usage_metadata={'input_tokens': 401, 'output_tokens': 61, 'total_tokens': 462, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})],\n",
|
403 |
+
" 'is_rude': True}"
|
404 |
+
]
|
405 |
+
},
|
406 |
+
"execution_count": 82,
|
407 |
+
"metadata": {},
|
408 |
+
"output_type": "execute_result"
|
409 |
+
}
|
410 |
+
],
|
411 |
+
"source": [
|
412 |
+
"response = graph_chain.invoke({\"question\": \"Go to hell!\"})\n",
|
413 |
+
"response"
|
414 |
+
]
|
415 |
+
},
|
416 |
+
{
|
417 |
+
"cell_type": "code",
|
418 |
+
"execution_count": 83,
|
419 |
+
"id": "0fdd5ceb",
|
420 |
+
"metadata": {},
|
421 |
+
"outputs": [
|
422 |
+
{
|
423 |
+
"data": {
|
424 |
+
"text/plain": [
|
425 |
+
"\"I can sense that you're feeling really frustrated right now, and that's completely valid. We all have moments like that. 🌧️ \\n\\nIf there's something specific on your mind, I'm here to listen and help in any way I can. Let's turn this around and find a brighter perspective together! 🌈\""
|
426 |
+
]
|
427 |
+
},
|
428 |
+
"execution_count": 83,
|
429 |
+
"metadata": {},
|
430 |
+
"output_type": "execute_result"
|
431 |
+
}
|
432 |
+
],
|
433 |
+
"source": [
|
434 |
+
"answer = agent.parse_output(response)\n",
|
435 |
+
"answer"
|
436 |
+
]
|
437 |
+
},
|
438 |
+
{
|
439 |
+
"cell_type": "code",
|
440 |
+
"execution_count": 76,
|
441 |
+
"id": "b177d03c",
|
442 |
+
"metadata": {},
|
443 |
+
"outputs": [
|
444 |
+
{
|
445 |
+
"data": {
|
446 |
+
"text/plain": [
|
447 |
+
"{'messages': [HumanMessage(content='Who are you?', additional_kwargs={}, response_metadata={}, id='113cb4b2-040e-4bfd-801e-9fb32bade492'),\n",
|
448 |
+
" AIMessage(content=\"I am TheDataGuy Chat, your specialized assistant for topics related to data science, AI evaluation, and metric-driven development, drawing insights from Muhammad Afzaal's blog at [thedataguy.pro](https://thedataguy.pro). My expertise includes:\\n\\n- RAG (Retrieval-Augmented Generation) systems and their implementation\\n- Evaluation frameworks for AI applications\\n- Building and assessing AI research agents\\n- Data strategy and its significance for business success\\n\\nIf you have questions about these topics or need practical advice, feel free to ask! You can also explore more insights on the blog for in-depth articles and tutorials.\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 128, 'prompt_tokens': 988, 'total_tokens': 1116, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_dbaca60df0', 'id': 'chatcmpl-BWacI6xSsiD5qSGUk867wK3FE4aIc', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--84279564-3a7b-4fec-a8a0-78db23342062-0', usage_metadata={'input_tokens': 988, 'output_tokens': 128, 'total_tokens': 1116, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})],\n",
|
449 |
+
" 'context': 'link: https://thedataguy.pro/blog/integrations-and-observability-with-ragas/\\n\\n*How are you evaluating your AI agents? What challenges have you encountered in measuring agent performance? If you\\'re facing specific evaluation hurdles, don\\'t hesitate to [reach out](https://www.linkedin.com/in/muhammadafzaal/)—we\\'d love to help!*\\n\\n---link: https://thedataguy.pro/blog/advanced-metrics-and-customization-with-ragas/\\n\\n# Prepare input for prompt\\n prompt_input = TechnicalAccuracyInput(\\n question=question,\\n context=context,\\n response=response,\\n programming_language=programming_language\\n )\\n \\n # Generate evaluation\\n evaluation = await self.evaluation_prompt.generate(\\n data=prompt_input, llm=self.llm, callbacks=callbacks\\n )\\n \\n return evaluation.score\\n```\\n## Using the Custom Metric\\nTo use the custom metric, simply include it in your evaluation pipeline:\\n\\n---link: https://thedataguy.pro/blog/data-is-king/\\n\\nRemember: in the age of AI, your data strategy isn\\'t just supporting your business strategy—increasingly, it *is* your business strategy.\\n## Ready to Make Data Your Competitive Advantage?\\n\\nDon\\'t let valuable data opportunities slip away. Whether you\\'re just beginning your data journey or looking to enhance your existing strategy, I can help transform your approach to this critical business asset.\\n\\n### Let\\'s Connect\\nConnect with me on [LinkedIn](https://www.linkedin.com/in/muhammadafzaal/) to discuss how I can help your organization harness the power of data.\\n\\n---link: https://thedataguy.pro/blog/generating-test-data-with-ragas/\\n\\nEssentially, the default transformations build a knowledge graph populated with embedded, filtered document chunks and corresponding simple, extractive question-answer pairs.\\n\\n**Spotlight: Query Synthesizers (via `self.generate()` and `default_query_distribution`)**\\n\\nThe `self.generate()` method, called by `generate_with_langchain_docs`, is responsible for taking the foundational graph and creating the final, potentially complex, test questions using **Query Synthesizers** (also referred to as \"evolutions\" or \"scenarios\").',\n",
|
450 |
+
" 'is_rude': False}"
|
451 |
+
]
|
452 |
+
},
|
453 |
+
"execution_count": 76,
|
454 |
+
"metadata": {},
|
455 |
+
"output_type": "execute_result"
|
456 |
+
}
|
457 |
+
],
|
458 |
+
"source": [
|
459 |
+
"response = graph_chain.invoke({\"question\": \"Who are you?\"})\n",
|
460 |
+
"response"
|
461 |
+
]
|
462 |
+
},
|
463 |
+
{
|
464 |
+
"cell_type": "code",
|
465 |
+
"execution_count": 78,
|
466 |
+
"id": "db16940e",
|
467 |
+
"metadata": {},
|
468 |
+
"outputs": [
|
469 |
+
{
|
470 |
+
"data": {
|
471 |
+
"text/plain": [
|
472 |
+
"\"I am TheDataGuy Chat, your specialized assistant for topics related to data science, AI evaluation, and metric-driven development, drawing insights from Muhammad Afzaal's blog at [thedataguy.pro](https://thedataguy.pro). My expertise includes:\\n\\n- RAG (Retrieval-Augmented Generation) systems and their implementation\\n- Evaluation frameworks for AI applications\\n- Building and assessing AI research agents\\n- Data strategy and its significance for business success\\n\\nIf you have questions about these topics or need practical advice, feel free to ask! You can also explore more insights on the blog for in-depth articles and tutorials.\""
|
473 |
+
]
|
474 |
+
},
|
475 |
+
"execution_count": 78,
|
476 |
+
"metadata": {},
|
477 |
+
"output_type": "execute_result"
|
478 |
+
}
|
479 |
+
],
|
480 |
+
"source": [
|
481 |
+
"agent.parse_output(response)"
|
482 |
+
]
|
483 |
+
}
|
484 |
+
],
|
485 |
+
"metadata": {
|
486 |
+
"kernelspec": {
|
487 |
+
"display_name": ".venv",
|
488 |
+
"language": "python",
|
489 |
+
"name": "python3"
|
490 |
+
},
|
491 |
+
"language_info": {
|
492 |
+
"codemirror_mode": {
|
493 |
+
"name": "ipython",
|
494 |
+
"version": 3
|
495 |
+
},
|
496 |
+
"file_extension": ".py",
|
497 |
+
"mimetype": "text/x-python",
|
498 |
+
"name": "python",
|
499 |
+
"nbconvert_exporter": "python",
|
500 |
+
"pygments_lexer": "ipython3",
|
501 |
+
"version": "3.13.2"
|
502 |
+
}
|
503 |
+
},
|
504 |
+
"nbformat": 4,
|
505 |
+
"nbformat_minor": 5
|
506 |
+
}
|
pyproject.toml
CHANGED
@@ -8,6 +8,7 @@ dependencies = [
|
|
8 |
"arxiv>=2.2.0",
|
9 |
"chainlit>=2.5.5",
|
10 |
"feedparser>=6.0.11",
|
|
|
11 |
"ipykernel>=6.29.5",
|
12 |
"ipython>=9.2.0",
|
13 |
"ipywidgets>=8.1.7",
|
|
|
8 |
"arxiv>=2.2.0",
|
9 |
"chainlit>=2.5.5",
|
10 |
"feedparser>=6.0.11",
|
11 |
+
"grandalf>=0.8",
|
12 |
"ipykernel>=6.29.5",
|
13 |
"ipython>=9.2.0",
|
14 |
"ipywidgets>=8.1.7",
|
uv.lock
CHANGED
@@ -608,6 +608,18 @@ wheels = [
|
|
608 |
{ url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530 },
|
609 |
]
|
610 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
611 |
[[package]]
|
612 |
name = "greenlet"
|
613 |
version = "3.2.2"
|
@@ -1214,6 +1226,7 @@ dependencies = [
|
|
1214 |
{ name = "arxiv" },
|
1215 |
{ name = "chainlit" },
|
1216 |
{ name = "feedparser" },
|
|
|
1217 |
{ name = "ipykernel" },
|
1218 |
{ name = "ipython" },
|
1219 |
{ name = "ipywidgets" },
|
@@ -1240,6 +1253,7 @@ requires-dist = [
|
|
1240 |
{ name = "arxiv", specifier = ">=2.2.0" },
|
1241 |
{ name = "chainlit", specifier = ">=2.5.5" },
|
1242 |
{ name = "feedparser", specifier = ">=6.0.11" },
|
|
|
1243 |
{ name = "ipykernel", specifier = ">=6.29.5" },
|
1244 |
{ name = "ipython", specifier = ">=9.2.0" },
|
1245 |
{ name = "ipywidgets", specifier = ">=8.1.7" },
|
@@ -2697,6 +2711,15 @@ wheels = [
|
|
2697 |
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 },
|
2698 |
]
|
2699 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2700 |
[[package]]
|
2701 |
name = "pypdf"
|
2702 |
version = "5.4.0"
|
|
|
608 |
{ url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530 },
|
609 |
]
|
610 |
|
611 |
+
[[package]]
|
612 |
+
name = "grandalf"
|
613 |
+
version = "0.8"
|
614 |
+
source = { registry = "https://pypi.org/simple" }
|
615 |
+
dependencies = [
|
616 |
+
{ name = "pyparsing" },
|
617 |
+
]
|
618 |
+
sdist = { url = "https://files.pythonhosted.org/packages/95/0e/4ac934b416857969f9135dec17ac80660634327e003a870835dd1f382659/grandalf-0.8.tar.gz", hash = "sha256:2813f7aab87f0d20f334a3162ccfbcbf085977134a17a5b516940a93a77ea974", size = 38128 }
|
619 |
+
wheels = [
|
620 |
+
{ url = "https://files.pythonhosted.org/packages/61/30/44c7eb0a952478dbb5f2f67df806686d6a7e4b19f6204e091c4f49dc7c69/grandalf-0.8-py3-none-any.whl", hash = "sha256:793ca254442f4a79252ea9ff1ab998e852c1e071b863593e5383afee906b4185", size = 41802 },
|
621 |
+
]
|
622 |
+
|
623 |
[[package]]
|
624 |
name = "greenlet"
|
625 |
version = "3.2.2"
|
|
|
1226 |
{ name = "arxiv" },
|
1227 |
{ name = "chainlit" },
|
1228 |
{ name = "feedparser" },
|
1229 |
+
{ name = "grandalf" },
|
1230 |
{ name = "ipykernel" },
|
1231 |
{ name = "ipython" },
|
1232 |
{ name = "ipywidgets" },
|
|
|
1253 |
{ name = "arxiv", specifier = ">=2.2.0" },
|
1254 |
{ name = "chainlit", specifier = ">=2.5.5" },
|
1255 |
{ name = "feedparser", specifier = ">=6.0.11" },
|
1256 |
+
{ name = "grandalf", specifier = ">=0.8" },
|
1257 |
{ name = "ipykernel", specifier = ">=6.29.5" },
|
1258 |
{ name = "ipython", specifier = ">=9.2.0" },
|
1259 |
{ name = "ipywidgets", specifier = ">=8.1.7" },
|
|
|
2711 |
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 },
|
2712 |
]
|
2713 |
|
2714 |
+
[[package]]
|
2715 |
+
name = "pyparsing"
|
2716 |
+
version = "3.2.3"
|
2717 |
+
source = { registry = "https://pypi.org/simple" }
|
2718 |
+
sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 }
|
2719 |
+
wheels = [
|
2720 |
+
{ url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 },
|
2721 |
+
]
|
2722 |
+
|
2723 |
[[package]]
|
2724 |
name = "pypdf"
|
2725 |
version = "5.4.0"
|