vikramvasudevan commited on
Commit
bc05cd4
·
verified ·
1 Parent(s): 7290ba6

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. app.py +15 -12
  2. graph_helper.py +14 -8
  3. tools.py +35 -23
app.py CHANGED
@@ -177,10 +177,10 @@ async def chat_streaming(message, history, thread_id):
177
  def generate_processing_message():
178
  return (
179
  f"<div class='thinking-bubble'><em>🤔{random.choice(thinking_verbs)} ...</em></div>"
180
- f"<div style='opacity: 0.1' title='{full}'>"
181
- f"<span>{node}:{name or ''}:</span>"
182
- f"<strong>Looking for : [{message}]</strong> {truncated or '...'}"
183
- f"</div>"
184
  )
185
 
186
  if (
@@ -194,11 +194,11 @@ async def chat_streaming(message, history, thread_id):
194
 
195
  html = (
196
  f"<div class='thinking-bubble'><em>🤔 {msg.name} tool: {random.choice(thinking_verbs)} ...</em></div>"
197
- f"<div style='opacity: 0.5'>"
198
- f"<strong>Looking for : [{message}]</strong><br>"
199
- f"<strong>Tool Args:</strong> {tooltip or '(no args)'}<br>"
200
- f"{truncated or '...'}"
201
- f"</div>"
202
  )
203
  yield f"### { ' → '.join(node_tree)}\n{html}"
204
  elif isinstance(msg, AIMessageChunk):
@@ -206,7 +206,7 @@ async def chat_streaming(message, history, thread_id):
206
  def truncate_middle(text, front=50, back=50):
207
  if len(text) <= front + back:
208
  return text
209
- return f"{text[:front]}…{text[-back:]}"
210
 
211
  if not msg.content:
212
  # logger.warning("*** No Message Chunk!")
@@ -214,7 +214,7 @@ async def chat_streaming(message, history, thread_id):
214
  else:
215
  # Stream intermediate messages with transparent style
216
  if node != final_node:
217
- streamed_response += (msg.content)
218
  yield f"### { ' → '.join(node_tree) }\n<div class='intermediate-output'>{escape(truncate_middle(streamed_response))}</div>"
219
  else:
220
  # Buffer the final validated response instead of yielding
@@ -387,8 +387,11 @@ chatInterface = gr.ChatInterface(
387
  .intermediate-output {
388
  opacity: 0.4;
389
  font-style: italic;
390
- }
391
 
 
 
 
 
392
  """,
393
  )
394
 
 
177
  def generate_processing_message():
178
  return (
179
  f"<div class='thinking-bubble'><em>🤔{random.choice(thinking_verbs)} ...</em></div>"
180
+ # f"<div style='opacity: 0.1' title='{full}'>"
181
+ # f"<span>{node}:{name or ''}:</span>"
182
+ # f"<strong>Looking for : [{message}]</strong> {truncated or '...'}"
183
+ # f"</div>"
184
  )
185
 
186
  if (
 
194
 
195
  html = (
196
  f"<div class='thinking-bubble'><em>🤔 {msg.name} tool: {random.choice(thinking_verbs)} ...</em></div>"
197
+ # f"<div style='opacity: 0.5'>"
198
+ # f"<strong>Looking for : [{message}]</strong><br>"
199
+ # f"<strong>Tool Args:</strong> {tooltip or '(no args)'}<br>"
200
+ # f"{truncated or '...'}"
201
+ # f"</div>"
202
  )
203
  yield f"### { ' → '.join(node_tree)}\n{html}"
204
  elif isinstance(msg, AIMessageChunk):
 
206
  def truncate_middle(text, front=50, back=50):
207
  if len(text) <= front + back:
208
  return text
209
+ return f"{text[:front]}…{text[-back:]}".replace("\n", "") #remove new lines.
210
 
211
  if not msg.content:
212
  # logger.warning("*** No Message Chunk!")
 
214
  else:
215
  # Stream intermediate messages with transparent style
216
  if node != final_node:
217
+ streamed_response += msg.content
218
  yield f"### { ' → '.join(node_tree) }\n<div class='intermediate-output'>{escape(truncate_middle(streamed_response))}</div>"
219
  else:
220
  # Buffer the final validated response instead of yielding
 
387
  .intermediate-output {
388
  opacity: 0.4;
389
  font-style: italic;
 
390
 
391
+ white-space: nowrap;
392
+ overflow: hidden;
393
+ text-overflow: ellipsis;
394
+ }
395
  """,
396
  )
397
 
graph_helper.py CHANGED
@@ -27,6 +27,7 @@ logger.setLevel(logging.INFO)
27
  class ChatState(TypedDict):
28
  messages: Annotated[list[str], add_messages]
29
 
 
30
  def branching_condition(state: ChatState) -> str:
31
  last_message = state["messages"][-1]
32
  if hasattr(last_message, "tool_calls") and last_message.tool_calls:
@@ -58,7 +59,7 @@ def generate_graph() -> CompiledStateGraph:
58
 
59
  def validatorNode(state: ChatState) -> ChatState:
60
  messages = state["messages"] or []
61
-
62
  # Step 1: Separate out last message
63
  last_message = messages[-1]
64
  trimmed_messages = messages[:-1]
@@ -76,7 +77,9 @@ def generate_graph() -> CompiledStateGraph:
76
  "Return the fixed version of the assistant's message."
77
  )
78
  ),
79
- HumanMessage(content=last_message.content) # 🟢 convert AI output to Human input
 
 
80
  ]
81
 
82
  # Step 4: Invoke LLM
@@ -85,7 +88,6 @@ def generate_graph() -> CompiledStateGraph:
85
  # Step 5: Replace old AI message with validated one
86
  return {"messages": trimmed_messages + [response]}
87
 
88
-
89
  def init_system_prompt_node(state: ChatState) -> ChatState:
90
  messages = state["messages"] or []
91
 
@@ -104,7 +106,7 @@ def generate_graph() -> CompiledStateGraph:
104
  content="You MUST call the `format_scripture_answer` tool if the user question is about scripture content and the `query` tool has returned a result."
105
  ),
106
  SystemMessage(
107
- content="If the user's question is about any scripture content (even if multiple scriptures), you must use the `tool_search_db`. Only use `tool_search_web` for general non-scriptural questions."
108
  ),
109
  ]
110
 
@@ -118,10 +120,14 @@ def generate_graph() -> CompiledStateGraph:
118
  graph.add_edge(START, "init")
119
  graph.add_edge("init", "llm")
120
  # graph.add_conditional_edges("llm", tools_condition, "tools")
121
- graph.add_conditional_edges("llm", branching_condition, {
122
- "tools": "tools",
123
- "validator": "validator",
124
- })
 
 
 
 
125
  graph.add_edge("tools", "llm")
126
  graph.add_edge("validator", END)
127
  return graph.compile(checkpointer=memory)
 
27
  class ChatState(TypedDict):
28
  messages: Annotated[list[str], add_messages]
29
 
30
+
31
  def branching_condition(state: ChatState) -> str:
32
  last_message = state["messages"][-1]
33
  if hasattr(last_message, "tool_calls") and last_message.tool_calls:
 
59
 
60
  def validatorNode(state: ChatState) -> ChatState:
61
  messages = state["messages"] or []
62
+
63
  # Step 1: Separate out last message
64
  last_message = messages[-1]
65
  trimmed_messages = messages[:-1]
 
77
  "Return the fixed version of the assistant's message."
78
  )
79
  ),
80
+ HumanMessage(
81
+ content=last_message.content
82
+ ), # 🟢 convert AI output to Human input
83
  ]
84
 
85
  # Step 4: Invoke LLM
 
88
  # Step 5: Replace old AI message with validated one
89
  return {"messages": trimmed_messages + [response]}
90
 
 
91
  def init_system_prompt_node(state: ChatState) -> ChatState:
92
  messages = state["messages"] or []
93
 
 
106
  content="You MUST call the `format_scripture_answer` tool if the user question is about scripture content and the `query` tool has returned a result."
107
  ),
108
  SystemMessage(
109
+ content="For general scripture queries, always prefer semantic search (tool_search_db). Use metadata or literal search only if the user specifies an exact verse, azhwar, or phrase."
110
  ),
111
  ]
112
 
 
120
  graph.add_edge(START, "init")
121
  graph.add_edge("init", "llm")
122
  # graph.add_conditional_edges("llm", tools_condition, "tools")
123
+ graph.add_conditional_edges(
124
+ "llm",
125
+ branching_condition,
126
+ {
127
+ "tools": "tools",
128
+ "validator": "validator",
129
+ },
130
+ )
131
  graph.add_edge("tools", "llm")
132
  graph.add_edge("validator", END)
133
  return graph.compile(checkpointer=memory)
tools.py CHANGED
@@ -3,10 +3,18 @@ from langchain.agents import Tool
3
  from langchain_core.tools import StructuredTool
4
 
5
  from config import SanatanConfig
6
- from nalayiram_helper import get_standardized_azhwar_names, get_standardized_divya_desam_names
 
 
 
7
  from push_notifications_helper import push
8
  from serperdev_helper import search as search_web
9
- from sanatan_assistant import format_scripture_answer, query, query_by_metadata_field, query_by_literal_text
 
 
 
 
 
10
 
11
  tool_push = Tool(
12
  name="push", description="Send a push notification to the user", func=push
@@ -17,41 +25,45 @@ allowed_collections = [s["collection_name"] for s in SanatanConfig.scriptures]
17
  tool_search_db = StructuredTool.from_function(
18
  query,
19
  description=(
20
- "Do a semantic vector search within a specific scripture collection. "
21
- f"The collection_name must be one of: {', '.join(allowed_collections)}."
22
- "Use this to narrow down relevant scripture verses or explanations based on the given query."
 
 
23
  ),
24
  )
25
 
 
26
  tool_search_db_for_literal = StructuredTool.from_function(
27
  query_by_literal_text,
28
  description=(
29
- "Do a literal search within a specific scripture collection (only if user specifically asks for a literal search or if semantic search does not yield relevant results)."
30
- f"The collection_name must be one of: {', '.join(allowed_collections)}."
31
- "Use this to find relevant scripture verses or explanations based on the given query."
32
- # "If the query doesn't yield any relevant results, then call `tool_search_db_by_metadata` tool to search specifically by a given metadata field (only if specific field from metadata has been mentioned)."
33
- # f"use this configuration for reference :\n{json.dumps(SanatanConfig.scriptures, indent=1)}\n"
34
  ),
35
  )
36
 
 
37
  tool_search_db_by_metadata = StructuredTool.from_function(
38
  query_by_metadata_field,
39
  description=(
40
- "Search within a specific scripture collection using a metadata field. use this only when the user provides a specific search criteria for verse number, pasuram number, azhwar name etc"
41
- f"The collection_name must be one of: {', '.join(allowed_collections)}."
42
- " Use this to find relevant scripture verses or explanations."
43
- "if the user asks for a specific azhwar, use the `tool_get_standardized_azhwar_names` tool to get the standard name first and then pass to this tool to filter pasurams based on azhwar_name."
44
- "if the user asks for a specific prabandham name, use the `tool_get_standardized_azhwar_names` tool to get the standard prabandham name first and then pass to this tool to filter pasurams based on prabandham_name."
45
- "if the user asks for a specific divya desam name, use the `tool_get_standardized_divya_desam_names` tool to get the standard divya desam name first and then pass to this tool to filter pasurams based on `divya_desams`."
46
- f"use this configuration for reference :\n{json.dumps(SanatanConfig.scriptures, indent=1)}\n"
47
- # "be aware that verse numbers are sometimes stored as strings and sometimes as mumbers, so if str search does not yield results, try passing in the metadata_value as a number instead"
48
- # "in the context of divya_prabandham, the verse/pasuram number is stored in metadata as the field `verse` and it is stored as an int."
49
- # "in the context of sahasranamam, the verse/pasuram number is stored in metadata as the field `verse` and it is stored as an int."
50
- # "in the context of kamba_ramayanam, the verse number is stored in metadata as the field `verse_number` and it is stored as a string datatype."
51
- # "for other scriptures, the verse number is stored either as `verse` or `verse_number` fields and it can be either str or int so check for both whichever yields results."
 
52
  ),
53
  )
54
 
 
55
  tool_search_web = Tool(
56
  name="search_web", description="Search the web for information", func=search_web
57
  )
@@ -87,4 +99,4 @@ tool_get_standardized_divya_desam_names = StructuredTool.from_function(
87
  "Use this tool to standardize the names of the divya desams when the user asks for pasurams written on a specific divya desam."
88
  "Usually this is followed by passing that standardized divya desam name for a metadata search using the `tool_search_db_by_metadata` tool by using the fiels `divya_desams`."
89
  ),
90
- )
 
3
  from langchain_core.tools import StructuredTool
4
 
5
  from config import SanatanConfig
6
+ from nalayiram_helper import (
7
+ get_standardized_azhwar_names,
8
+ get_standardized_divya_desam_names,
9
+ )
10
  from push_notifications_helper import push
11
  from serperdev_helper import search as search_web
12
+ from sanatan_assistant import (
13
+ format_scripture_answer,
14
+ query,
15
+ query_by_metadata_field,
16
+ query_by_literal_text,
17
+ )
18
 
19
  tool_push = Tool(
20
  name="push", description="Send a push notification to the user", func=push
 
25
  tool_search_db = StructuredTool.from_function(
26
  query,
27
  description=(
28
+ "This is the **PRIMARY** tool to use for most user queries about scripture."
29
+ " Use this when the user asks **about themes, stories, ideas, emotions, or meanings** in the scriptures."
30
+ " This tool uses semantic vector search and can understand context and meaning beyond keywords."
31
+ f" Only use other tools like metadata or literal search if the user explicitly asks for them."
32
+ f" The collection_name must be one of: {', '.join(allowed_collections)}."
33
  ),
34
  )
35
 
36
+
37
  tool_search_db_for_literal = StructuredTool.from_function(
38
  query_by_literal_text,
39
  description=(
40
+ "Use this only if the user explicitly says they want a 'literal match' or exact phrase search."
41
+ " This is not the default. Try semantic search first using `tool_search_db`."
42
+ f" The collection_name must be one of: {', '.join(allowed_collections)}."
 
 
43
  ),
44
  )
45
 
46
+
47
  tool_search_db_by_metadata = StructuredTool.from_function(
48
  query_by_metadata_field,
49
  description=(
50
+ "Use this tool **only when the user provides explicit metadata criteria**, such as: azhwar name, pasuram number, verse number, prabandham name, or divya desam name."
51
+ " This is not meant for general queries."
52
+ f" The collection_name must be one of: {', '.join(allowed_collections)}."
53
+ "If the user asks for a specific azhwar, use `tool_get_standardized_azhwar_names` first."
54
+ "If the user asks for a specific prabandham, use `tool_get_standardized_prabandham_names` first."
55
+ "If the user mentions a divya desam, use `tool_get_standardized_divya_desam_names` first."
56
+ "If you set metadata_search_operator to $in, then metadata_value must always be a list — even if it contains only a single item."
57
+ """🔒 Important:
58
+ When using the tool_get_standardized_azhwar_names, tool_get_standardized_divya_desam_names, or any similar standardization tool, you must use the standardized name exactly as returned by the tool without modifying, reformatting, translating, or simplifying it in any way.
59
+ For example, if the tool returns Thirumālirum Solai, you must pass that exact string to tool_search_db_by_metadata. Do not change it to Thirumalirum Solai, Tirumalirumsolai, or anything else.
60
+ 🔍 This is critical for the search to return results correctly.
61
+ 🚫 Any deviation will cause the search to fail or miss results."""
62
+ f" Reference config:\n{json.dumps(SanatanConfig.scriptures, indent=1)}\n"
63
  ),
64
  )
65
 
66
+
67
  tool_search_web = Tool(
68
  name="search_web", description="Search the web for information", func=search_web
69
  )
 
99
  "Use this tool to standardize the names of the divya desams when the user asks for pasurams written on a specific divya desam."
100
  "Usually this is followed by passing that standardized divya desam name for a metadata search using the `tool_search_db_by_metadata` tool by using the fiels `divya_desams`."
101
  ),
102
+ )