Spaces:
Running
Running
File size: 4,183 Bytes
74e8a7b | 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 | """
Conversation memory helpers (LangChain ConversationBufferWindowMemory with
a manually enforced window size) and post-response memory hygiene.
"""
from langchain_classic.memory import ConversationBufferWindowMemory
from src.utils.config import MEMORY_WINDOW_K, groq_client
# NOTE: matches notebook's current (possibly unintentional) mismatch โ the
# memory object itself is built with MEMORY_WINDOW_K (5), but this local
# trim constant is 3, making 3 the effective operative window. See the
# config.py note above โ flag for confirmation before treating as final.
_SAVE_TRIM_K = 3
def get_empty_memory() -> ConversationBufferWindowMemory:
return ConversationBufferWindowMemory(k=MEMORY_WINDOW_K, return_messages=True, memory_key="history")
def save_to_memory(memory: ConversationBufferWindowMemory, user_msg: str, assistant_msg: str) -> None:
"""Save a turn and manually enforce the (tighter) trim window."""
memory.save_context({"input": user_msg}, {"output": assistant_msg})
limit = _SAVE_TRIM_K * 2
if len(memory.chat_memory.messages) > limit:
memory.chat_memory.messages = memory.chat_memory.messages[-limit:]
def history_as_messages(memory: ConversationBufferWindowMemory) -> list[dict]:
"""Convert stored memory into OpenAI-style chat messages."""
chat_history = memory.load_memory_variables({})["history"]
return [
{"role": ("user" if m.type == "human" else "assistant"), "content": m.content}
for m in chat_history
]
# โโ NEW: memory hygiene โ strip personalization before saving โโโโโโโโโโโโโโ
_MEMORY_STRIP_SYSTEM = """You are given an academic advisor's answer to a student.
Your task: return ONLY the factual/informational part of the answer โ remove
any section that personalizes the answer to the specific student (GPA checks,
"ููุงุณุจู"/"ูุชูุงูู ู
ุน ุงูุชู
ุงู
ุงุชู" framing, eligibility verdicts, program
recommendations, "ุงูุงุฎุชูุงุฑ ุงูุฃู
ุซู" or similar closing recommendations).
Rules:
- If the answer has NO personalization content at all, return it unchanged.
- Do NOT summarize or paraphrase the factual content โ keep it word-for-word.
- Only remove personalization/recommendation content, nothing else.
- Return ONLY the trimmed answer text, no explanation, no markdown fences.
"""
def strip_personalization_tail(response: str) -> str:
"""
Before saving an assistant turn to memory, remove personalization/
recommendation content so a fit-related answer doesn't bias the tone
of unrelated future turns still inside the k-window. The full
personalized answer is still shown to the student this turn โ only
what gets remembered is trimmed.
"""
if not response or len(response) < 50:
return response
try:
resp = groq_client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[
{"role": "system", "content": _MEMORY_STRIP_SYSTEM},
{"role": "user", "content": response},
],
temperature=0.0,
max_tokens=len(response) // 2 + 200,
)
trimmed = resp.choices[0].message.content.strip()
if trimmed:
print(f"[Memory] stripped {len(response) - len(trimmed)} chars of personalization")
return trimmed
return response
except Exception as e:
print(f"[Memory strip error] {e}")
return response
def _save_to_memory_background(memory: ConversationBufferWindowMemory, user_msg: str, assistant_msg: str) -> None:
"""
Runs on a background thread, after the response has already been returned
to the caller โ memory hygiene (personalization stripping + save) doesn't
need to block the response, since it only affects what future turns see,
not the current one.
"""
try:
cleaned = strip_personalization_tail(assistant_msg)
save_to_memory(memory, user_msg, cleaned)
except Exception as e:
print(f"[Background memory save error] {e}")
save_to_memory(memory, user_msg, assistant_msg) # fail safe โ save unstripped rather than lose the turn
|