Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Upload folder using huggingface_hub
Browse files- app.py +15 -12
- graph_helper.py +14 -8
- 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 +=
|
| 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(
|
|
|
|
|
|
|
| 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="
|
| 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(
|
| 122 |
-
"
|
| 123 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
| 7 |
from push_notifications_helper import push
|
| 8 |
from serperdev_helper import search as search_web
|
| 9 |
-
from sanatan_assistant import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"
|
| 21 |
-
|
| 22 |
-
"
|
|
|
|
|
|
|
| 23 |
),
|
| 24 |
)
|
| 25 |
|
|
|
|
| 26 |
tool_search_db_for_literal = StructuredTool.from_function(
|
| 27 |
query_by_literal_text,
|
| 28 |
description=(
|
| 29 |
-
"
|
| 30 |
-
|
| 31 |
-
"
|
| 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 |
-
"
|
| 41 |
-
|
| 42 |
-
"
|
| 43 |
-
"
|
| 44 |
-
"
|
| 45 |
-
"
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
| 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 |
+
)
|