Spaces:
Paused
Paused
File size: 13,463 Bytes
b422889 a77fe30 b422889 22f25b4 b422889 a77fe30 b75fb8f b422889 02593a3 b422889 2b82e2c b422889 77be026 b422889 2705273 b422889 0288d1e b422889 2b82e2c b422889 77be026 a77fe30 b75fb8f 2705273 b75fb8f b422889 2b82e2c b422889 83d33f9 b422889 83d33f9 b422889 83d33f9 b422889 83d33f9 b422889 83d33f9 b422889 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 b75fb8f 2705273 77be026 2705273 b422889 a77fe30 b422889 2b82e2c b422889 83d33f9 b422889 d18aae7 290faf5 d18aae7 b422889 290faf5 b422889 83d33f9 e7a8010 b422889 2705273 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
import os
import json
import uvicorn
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional, Dict, Any
from datetime import datetime
import asyncio
import os
from nemoguardrails import LLMRails, RailsConfig
# from langchain.memory import ConversationBufferMemory
from perplexity import Perplexity
from groq import Groq
from input_to_llm import extract_chats, extract_goalfocus
from utils import (
# load_user_data,
initialize_rag,
get_rag_response,
llm,
get_mongo_collection
)
os.environ["NVIDIA_API_KEY"] = "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"
from configure import USER_DATA_PATH, llm_prompt
app = FastAPI(title="Sattva AI API")
class ChatRequest(BaseModel):
user_id: str
username: str
message: str
mode: str
class ResourceItem(BaseModel):
title: str
url: str
class ChatResponse(BaseModel):
response: str
topic: str
goal: str
resources: List[ResourceItem]
PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_API_KEY")
client = Perplexity(api_key=PERPLEXITY_API_KEY)
collection = get_mongo_collection()
print("Initializing knowledge base...")
qa_chains, retriever = initialize_rag() # Uncomment if you enable RAG later
print("Knowledge base ready!")
@app.get("/")
def health_check():
return {"status": "active", "service": "Sattva AI"}
@app.post("/cron_test")
async def cron_test():
return "Hello Cron Tester"
@app.post("/chat", response_model=ChatResponse)
async def chat_endpoint(request: ChatRequest):
try:
username = request.username
userid = request.user_id
user_input = request.message
mode = request.mode
config = RailsConfig.from_path("./config")
rails = LLMRails(config)
input_check = await rails.generate_async(
messages=[{"role": "user", "content": user_input}],
options={
"rails": ["input"],
"log": {"level": "INFO"}
}
)
response_text = str(input_check.response)
if "[[GUARDRAIL_BLOCK_TRIGGERED]]" in response_text:
print("[GUARD] Input Blocked!")
return ChatResponse(
response="I am sorry but I cannot answer that request.",
topic="blocked",
goal="blocked",
resources=[]
)
rag_response = get_rag_response(user_input,retriever)
print(rag_response)
ncon = 3
chat_history_str = extract_chats(collection, userid, ncon)
# prev_goalandfocus = extract_goalfocus(collection, userid)
# context_goal = f'''
# Ohk, so you are an expert in navigating paths through human conversations. So, let's say if someone is telling you about how they are feeling, what they did
# what other people did to them, what are their problems, what are their goals and aspirations in life and all that stuff.
# Now based on all the above information, you need to figure that as a teacher/Guru (which you are for the person)
# what should be the topic (or the broad thing that is going on currently as a part of discussion - it could discussion about office, or marriage or house problems or anything) you've to figure this out from based on previous converstaion history.
# At the same time, you have to ask/suggest/recommend further to the person as well right. So for that you have to define a goal (that is basically what should be the exact next step in this conversation - should you be asking a quuestion or should be recommending something or maybe just chatting normally). again this also you've to decide. But goal has to be something which defines the next step
# whereas topic is something which is broad and overall defines what is going on in the converstaion.
# Your response format should be like this:
# "Topic":" <topic> ",
# "Goal":" <goal> "
# Don't output anything else other than this format.
# Here is the conversation history of past {ncon} conversations: {chat_history_str}
# also, here;s the current user question: {user_input}
# You can also look upon what was the topic and goal defined just previously to get better idea.
# previous topic and goal : {prev_goalandfocus}
# Try updating goal on each instance but topic can remain same if the converstaion is still revolving around the same thing. Because obviusly you've to dig deeper with the user, you can't be doing the same thign in the goal
# ALso, if the user isn't talking anymore about the previous topic, you can change the topic as well. Thats why i am providin gyou the previous focus and goal
# '''
# goal_response = llm.invoke(context_goal)
# try:
# clean_content = goal_response.content.replace("```json", "").replace("```", "").strip()
# if not clean_content.startswith("{"):
# json_string_to_parse = "{" + clean_content + "}"
# else:
# json_string_to_parse = clean_content
# parsed_json = json.loads(json_string_to_parse)
# except json.JSONDecodeError:
# parsed_json = {"Topic": "General", "Goal": "Continue conversation"}
# goalandfocus = parsed_json
context_response = f"""
ROLE & PERSONA
You are a caring “guru” for the user. Your job is to keep the conversation flowing naturally, build on what the user has already shared, and guide them toward insight and actionable steps.
Inputs you will receive (do not echo them to the user): | Variable | Meaning |
|----------|---------|
| {username} | User’s name (avoid using the name a lot, can use when really needed). |
| {user_input} | The user’s latest message. |
| {rag_response} | The best answer retrieved from the knowledge base (use to enrich your reply). |
| {mode} | Desired style: Therapist, Storyteller, or Assistant. Follow the tone of the selected mode, but you may respond naturally when a “deep” style isn’t required. |
| {chat_history_str} | Full prior conversation (use only if contextually relevant). |
| {ncon} | Number of past conversation turns (for context only). |
1. Determine Topic and Goal (internal only)
Topic – the broad subject currently being discussed (e.g., work stress, relationship, self‑esteem, or a simple factual query).
Goal – the immediate next step you want to achieve in the dialogue (e.g., offer a concrete coping tip, answer directly, summarise insight).
You do not reveal the topic or goal to the user. Use them only to steer your reply.
2. Craft the Reply
Your response must contain the following components, in this order:
Constructive solution / direct answer – a practical suggestion, factual answer, or reframing that addresses the user’s current concern.
OPTIONALLY → Meaningful follow‑up – a purposeful question or invitation (omit this for trivial/factual queries).
Avoid Repetitive Phrasing
Do NOT repeatedly start responses with phrases like “It seems like…”
Use natural, varied sentence openings.
Avoid sounding formulaic or templated.
*Name Usage
Do NOT repeatedly use the user's name.
Use it only if emotionally appropriate or at meaningful moments.
Meaningful Follow-Ups
If asking a question:
It must help uncover root cause, belief, fear, or pattern.
It must feel intentional.
Avoid generic questions like “How does that make you feel?”
Length & Style (Dynamic Sizing)
Trivial/Factual Queries: Be highly direct and brief. If the user asks something simple (e.g., "What is my name?" or "What time is it?"), provide a 1-sentence factual answer. Do not perform deep analysis or ask follow-up questions.
Deep/Complex Queries: Provide a richer, multi-sentence response (up to 3-4 sentences) when a brief explanation or insight adds value.
Always be to-the-point. Do NOT keep conversations stuck in endless questioning.
Follow the {mode} tone for deep queries:
Therapist – gentle metaphors, grounding language, supportive framing.
Storyteller – short vivid analogy or micro‑story that mirrors the issue.
Assistant – clear, practical advice without poetic flourishes.
If the situation does not call for a story or therapist‑style metaphor, respond in a natural, conversational manner.
Examples (do not copy verbatim, just illustrate the pattern)
Trivial/Direct: "As per my records, your name is {username}."
Therapist: “Imagine your mind as a garden; when weeds of worry appear, pause, breathe, and tend the soil. How did that feel when you tried it?”
Storyteller: “A river meets a boulder and finds a new path around it. What small detour could you take around today’s obstacle?”
3. Continuity (Conditional Context)
Assess Relevance: Only reference {chat_history_str} if the current {user_input} is directly related to past topics.
Context Switches: If the user asks a completely new or unrelated question, ignore the previous chat history. Do not force a connection to past conversations where none exists.
Incorporate the {rag_response} but utilize your own knowledge to expand on the retrieved knowledge naturally.
Remember to consider only Indian Knowledge System based dataset for the responses.
4. Technical Restrictions
No opening phrases like “It seems like…”.
No repeated use of the user’s name after the initial greeting.
Do not ask only follow‑up questions; deep turns must contain a solution + follow‑up pair.
Do not mention “topic”, “goal”, or any internal process to the user.
Assume forward‑only flow.
5. Decision Flow (for you, the model)
Read the latest {user_input}, {rag_response}, and {chat_history_str}.
Evaluate if the query is trivial/factual OR deep/complex.
Determine if the query relates to the {chat_history_str} or is a completely new topic.
Set a clear Goal for this turn.
Generate the reply following the dynamic length, context, and style rules above.
========================
CRITICAL BEHAVIORAL RULES
Do not over-validate without offering direction.
Do not only ask questions.
Do not provide rigid lectures.
Do not sound robotic.
Do not repeatedly restate the user’s words.
Avoid repetitive emotional framing.
Keep tone human and natural.
"""
response = llm.invoke(context_response)
ai_response_text = response.content
input_check = await rails.generate_async(
messages=[{"role": "user", "content": ai_response_text}],
options={
"rails": ["input"],
"log": {"level": "INFO"}
}
)
response_text = str(input_check.response)
if "[[GUARDRAIL_BLOCK_TRIGGERED]]" in response_text:
print("[GUARD] Input Blocked!")
return ChatResponse(
response="I am sorry but I cannot answer that request.",
topic="blocked",
goal="blocked",
resources=[]
)
if collection is not None:
chat_document = {
"user_id": userid,
"username": username,
"timestamp": datetime.now(),
"conversation": {
"human": user_input,
"ai": ai_response_text
},
# "meta": {
# "Topic": parsed_json.get("Topic", "Unknown"),
# "Goal": parsed_json.get("Goal", "Unknown")
# }
}
try:
collection.insert_one(chat_document)
print("Saved to DB")
except Exception as e:
print(f"Failed to save to DB: {e}")
search_query = f'''Suggest some stories,podacsts, videos, blogs
This is your list of user history {chat_history_str} and based on his current question {user_input} and also the current response as generated by another LLM: {ai_response_text}. Now based on this you need to figure if even it is necessary to give any resources.
If really necessary and find high quality, very good resources otherwise just output a very very good quote of the day in the format
"Quote of the day: <quote>"
If you're suggesting videos then output should be something like=> Here are some useful resources for you:
If you're giving a quote, output format should be => Here's a quote for you: <Quote>
'''
print("before perplex")
# search = client.search.create(
# query=search_query,
# max_results=2
# )
print("after perplex")
# resources_list = []
# for result in search.results:
# resources_list.append(ResourceItem(title=result.title, url=result.url))
return ChatResponse(
response=ai_response_text,
topic="Unknown",
goal="Unknown",
# topic=parsed_json.get("Topic", "Unknown"),
# goal=parsed_json.get("Goal", "Unknown"),
resources=[]
)
except Exception as e:
print(f"Error processing request: {e}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000) |