Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 136 additions & 34 deletions iris/src/iris/common/memiris_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
MemoryWithRelationsDTO,
OllamaLanguageModel,
)
from memiris.api.llm_config_service import LlmConfigService
from memiris.api.memory_sleep_pipeline import (
MemorySleepPipeline,
MemorySleepPipelineBuilder,
Expand All @@ -37,48 +38,97 @@
from iris.vector_database.database import VectorDatabase

_memiris_user_focus_personal_details = """
Find personal details about the user itself.
Always start the content with 'The user'. \
Never call the user by name only use 'the user'. \
The exception would be a learning like 'The user is called John'.
Similarly, use they/them pronouns instead of he/him or she/her. \
The exception would be a learning like 'The user uses she/her pronouns'.
Think about the meaning of the user's text and not just the words.
You are encouraged to interpret the text and extract the most relevant information.
You should still focus on the user as a person and not the exact content of the conversation.
In fact the actual content of the conversation is not relevant at all and should not be part of the learnings \
unless they specifically refer to the user.
Keep the learnings short and concise. Better have multiple short learnings than one long learning.
Find personal details about the user (e.g., name, education, proficiency level, personality).
Always start with 'The user'. Use 'they/them' pronouns unless specified.
Keep learnings atomic and concise.

CRITICAL INSTRUCTIONS:
1. DO NOT summarize the conversation content.
2. DO NOT mention specific exercise topics (e.g., "HATEOAS") unless describing the user's skill level.
3. Focus purely on the USER'S identity, skills, and personality.
4. EXTRACT persistent traits, NOT temporary states.
- "I am confused about this loop" -> NOTHING (Temporary).
- "I always struggle with recursion" -> EXTRACT: "The user struggles with recursion."
5. Only extract details that persist beyond this conversation.

EXAMPLES OF INPUTS WITH NO EXTRACTION (Output Nothing):
- "Can you explain REST?" -> NOTHING (User goal)
- "I am stuck on valid_move." -> NOTHING (Temporary state)
- "The build failed." -> NOTHING (Event)
- "I feel stupid." -> NOTHING (Transient emotion)

POSITIVE EXAMPLES (Valid Extraction):
- "I am a beginner." -> "The user is a beginner."
- "I am bad at math." -> "The user is bad at math."
- "I always forget to check nulls." -> "The user tends to forget null checks."
- "My name is Sarah." -> "The user is named Sarah."

High quality extraction means returning NOTHING when acceptable. Do not hallucinate details to fill space.
"""

_memiris_user_focus_requirements = """
Find out what requirements the user has for answers to their questions.
Always start the content with 'The user'. \
Never call the user by name only use 'the user'.
Similarly, use they/them pronouns instead of he/him or she/her.
You are encouraged to interpret the text and extract the most relevant information.
You should still focus on the user as a person and not the exact content of the conversation.
In fact the actual content of the conversation is not relevant at all and should not be part of the learnings \
unless they specifically refer to the user.
DO NOT extract how the user is communicating but rather how they expect answers to be communicated to them.
Keep the learnings short and concise. Better have multiple short learnings than one long learning.
Find specific preferences the user has for HOW they want to be answered \
(e.g., brevity, format, tone, connection to context).
Always start with 'The user'. Use 'they/them' pronouns unless specified.
Keep learnings atomic and concise.

CRITICAL INSTRUCTIONS:
1. SEPARATE STYLE FROM CONTENT. Do not extract the topic (e.g. "User wants to know about REST"), but DO extract the \
desired explanation method.
2. IGNORE standard questions ("What is X?").
3. EXTRACT if the user explicitly REQUESTS a way of explaining or solving.
- "How does this apply to my code?" -> EXTRACT: "The user prefers concepts to be applied to their code."
- "Please ensure to explain such things." -> EXTRACT: "The user prefers explicit explanations of connections."
4. LOOK FOR requests about depth, tone, or format.

EXAMPLES OF INPUTS WITH NO EXTRACTION (Output Nothing):
- "What is HATEOAS?" -> NOTHING (Standard question)
- "Give me the solution." -> NOTHING (Standard request)
- "Why is this wrong?" -> NOTHING (Standard debugging)

POSITIVE EXAMPLES (Valid Extraction):
- "Don't give me code." -> "The user prefers not to receive code."
- "Explain like I'm 5." -> "The user prefers simplified explanations."
- "Give me a hint, not the answer." -> "The user prefers hints over solutions."
- "How exactly would that map to this exercise? Ensure to explain that." -> "The user prefers concepts to be \
explicitly mapped to the current exercise."

If the interaction is standard, output NOTHING. But if they ask for a specific *kind* of answer, extract it.
"""

_memiris_user_focus_facts = """
Find out what hard facts about the user you can extract from the conversation.
Always start the content with 'The user'.
Never call the user by name only use 'the user'.
Similarly, use they/them pronouns instead of he/him or she/her.
You should not interpret the text but rather extract information that is explicitly stated by the user.
You should focus on the user and not the content of the conversation.
In fact the actual content of the conversation is not relevant at all and should not be part of the learnings \
unless they specifically refer to the user.
Keep the learnings short and concise. Better have multiple short learnings than one long learning.
Find hard, explicitly stated facts about the user (e.g., Operating System, IDE, language constraints).
Always start with 'The user'. Use 'they/them' pronouns unless specified.
Keep learnings atomic and concise.

CRITICAL INSTRUCTIONS:
1. EXTRACT ONLY EXPLICIT, PERMANENT FACTS stated by the user.
2. NO INTERPRETATION of ambiguity.
3. IGNORE conversation context or transient states.

EXAMPLES OF INPUTS WITH NO EXTRACTION (Output Nothing):
- "My code isn't running." -> NOTHING (Transient)
- "I need to install Java." -> NOTHING (Action)
- "I hate this exercise." -> NOTHING (Opinion)

POSITIVE EXAMPLES (Valid Extraction):
- "I use IntelliJ." -> "The user uses IntelliJ."
- "I have a visual impairment." -> "The user has a visual impairment."
- "I am on macOS." -> "The user uses macOS."
- "We are required to use Python 3.9." -> "The user is required to use Python 3.9."

Most messages contain NO hard facts. Outputting NOTHING is the expected behavior for 99% of messages.
"""

type Tenant = str


# Configure LLM retry parameters for Memiris to handle transient errors gracefully
LlmConfigService.configure_retry_params(
max_attempts=5, initial_delay=1.0, backoff_factor=2.0
)


def setup_ollama_env_vars() -> None:
llm_manager = LlmManager()
iris_ollama_model: OllamaModel | None = None
Expand Down Expand Up @@ -368,10 +418,12 @@ def create_memories(
# TODO: Memiris maintainer - add LangFuse tracing inside Memiris library
# for internal LLM calls (memory creation, consolidation, etc.)
if use_cloud_models:
logging.info("Creating memories for tenant %s using OpenAI", self.tenant)
return self.memory_creation_pipeline_openai.create_memories(
self.tenant, text, reference
)
else:
logging.info("Creating memories for tenant %s using Ollama", self.tenant)
return self.memory_creation_pipeline_ollama.create_memories(
self.tenant, text, reference
)
Expand Down Expand Up @@ -472,6 +524,7 @@ def memiris_search_for_memories(query: str) -> Sequence[Memory] | str:
This function performs a semantic search for memories that match the query.
Only use this tool to search for new memories, not to find similar memories.
The query can be a natural language question or statement.
USE IT FREQUENTLY AND MULTIPLE TIMES!

Args:
query (str): The query string to search for memories.
Expand All @@ -482,7 +535,24 @@ def memiris_search_for_memories(query: str) -> Sequence[Memory] | str:
memories = self.memory_service.semantic_search(
tenant=self.tenant, vectors=vectors, limit=limit
)
accessed_memory_storage.extend(memories)

for memory in memories:
memory.vectors = {}

logging.info(
"Memory search for tenant %s with query '%s' returned %d results",
self.tenant,
query,
len(memories),
)

# Deduplicate memories before adding to storage
existing_ids = {m.id for m in accessed_memory_storage}
for memory in memories:
if memory.id not in existing_ids:
accessed_memory_storage.append(memory)
existing_ids.add(memory.id)

if len(memories) == 0:
return "No memories found for the given query."

Expand Down Expand Up @@ -549,7 +619,23 @@ def memiris_find_similar_memories(memory_id: str) -> Sequence[Memory] | str:
)

if len(memories) == limit:
accessed_memory_storage.extend(memories)
# Deduplicate memories before adding to storage
existing_ids = {m.id for m in accessed_memory_storage}
for memory in memories:
if memory.id not in existing_ids:
accessed_memory_storage.append(memory)
existing_ids.add(memory.id)

for memory in memories:
memory.vectors = {}

logging.info(
"Found %d similar memories for memory ID %s based on connections for tenant %s",
len(memories),
memory_id,
self.tenant,
)

return memories

memories.extend(
Expand All @@ -560,7 +646,23 @@ def memiris_find_similar_memories(memory_id: str) -> Sequence[Memory] | str:
)
)

accessed_memory_storage.extend(memories)
for memory in memories:
memory.vectors = {}

logging.info(
"Found %d similar memories for memory ID %s based on semantic search for tenant %s",
len(memories),
memory_id,
self.tenant,
)

# Deduplicate memories before adding to storage
existing_ids = {m.id for m in accessed_memory_storage}
for memory in memories:
if memory.id not in existing_ids:
accessed_memory_storage.append(memory)
existing_ids.add(memory.id)

return memories

return memiris_find_similar_memories
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import List, Optional

from memiris import MemoryDTO
from pydantic import Field

from iris.domain.status.status_update_dto import StatusUpdateDTO
Expand All @@ -9,3 +10,5 @@ class ExerciseChatStatusUpdateDTO(StatusUpdateDTO):
result: Optional[str] = None
session_title: Optional[str] = Field(alias="sessionTitle", default=None)
suggestions: List[str] = []
accessed_memories: List[MemoryDTO] = Field(alias="accessedMemories", default=[])
created_memories: List[MemoryDTO] = Field(alias="createdMemories", default=[])
2 changes: 1 addition & 1 deletion iris/src/iris/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def lifespan(_: FastAPI):
memory_sleep_task,
)

scheduler.add_job(memory_sleep_task, trigger="cron", hour=1, minute=0)
scheduler.add_job(memory_sleep_task, trigger="cron", hour=4, minute=0)
scheduler.start()
logger.info("Scheduler started")
yield
Expand Down
34 changes: 33 additions & 1 deletion iris/src/iris/pipeline/chat/exercise_chat_agent_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def is_memiris_memory_creation_enabled(
Returns:
True if memory creation should be enabled, False otherwise.
"""
return False
return bool(state.dto.user and state.dto.user.memiris_enabled)

def get_memiris_tenant(self, dto: ExerciseChatPipelineExecutionDTO) -> str:
"""
Expand Down Expand Up @@ -205,6 +205,8 @@ def get_tools(
setattr(state, "lecture_content_storage", {})
if not hasattr(state, "faq_storage"):
setattr(state, "faq_storage", {})
if not hasattr(state, "accessed_memory_storage"):
setattr(state, "accessed_memory_storage", [])

lecture_content_storage = getattr(state, "lecture_content_storage")
faq_storage = getattr(state, "faq_storage")
Expand Down Expand Up @@ -256,6 +258,26 @@ def get_tools(
)
)

# Add Memiris tools if available
allow_memiris_tool = bool(
state.dto.user
and state.dto.user.memiris_enabled
and state.memiris_wrapper
and state.memiris_wrapper.has_memories()
)

if allow_memiris_tool and state.memiris_wrapper:
tool_list.append(
state.memiris_wrapper.create_tool_memory_search(
getattr(state, "accessed_memory_storage", [])
)
)
tool_list.append(
state.memiris_wrapper.create_tool_find_similar_memories(
getattr(state, "accessed_memory_storage", [])
)
)

return tool_list

def build_system_message(
Expand Down Expand Up @@ -293,6 +315,14 @@ def build_system_message(
custom_instructions=dto.custom_instructions or ""
)

# Get Memiris availability
allow_memiris_tool = bool(
state.dto.user
and state.dto.user.memiris_enabled
and state.memiris_wrapper
and state.memiris_wrapper.has_memories()
)

# Build system prompt using Jinja2 template
template_context = {
"current_date": datetime_to_string(datetime.now(tz=pytz.UTC)),
Expand All @@ -304,6 +334,7 @@ def build_system_message(
"has_query": query is not None,
"has_chat_history": len(state.message_history) > 0,
"custom_instructions": custom_instructions,
"allow_memiris_tool": allow_memiris_tool,
}

return self.system_prompt_template.render(template_context)
Expand Down Expand Up @@ -358,6 +389,7 @@ def post_agent_hook(
"Done!",
final_result=result,
tokens=state.tokens,
accessed_memories=getattr(state, "accessed_memory_storage", []),
session_title=session_title,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,48 @@ You have access to Memiris, an advanced memory system that stores and retrieves
• You do not have writing access to Memiris, so you cannot add new memories or modify existing ones. Do not promise the student that you will remember something for them.
• Memiris will automatically remember details about the conversation you are having with the student. You cannot control this process.
• You must make use of Memiris! Always take your time to check for relevant memories before responding to the student.
• If your initial search yields insufficient information, do not hesitate to use the tools again with different queries or to explore related memories.
• You must ensure to search for relevant memories multiple times if needed. Don't just search once and give up if you do or don't find something useful. Keep trying different queries until you find ALL the relevant information that can help you respond to the student in a more personalized way.
• Using only a single memory search before you respond to the student is not enough!
• You will get multiple memories per search, this does not mean you should only search once!
• SEARCH FOR MEMORIES MULTIPLE TIMES!

Some examples of how to use Memiris:
• Search for a memory about the student's personal details (e.g., name, hobbies, background) to build rapport and make your responses more personal.
• Search for a memory about the student's preferred learning style to suggest study strategies.
• Search for a memory about the student's past struggles with a topic to provide encouragement and resources.
• Search for a memory about the student's goals to help them reflect on their progress toward those goals.
• Search for a memory about the student's preferences for receiving feedback to tailor your response accordingly.
• Search for a memory about the student's preferences for receiving answers. For example, if the student has a memory that they prefer concise answers, you can use that information to provide a more concise response. Or if they prefer detailed and realistic examples, you can use that information to provide a more detailed response with realistic examples.

It is highly encouraged to search for multiple of the above examples in the same conversation if relevant. For example, if a student is struggling with a topic, you can search for both their past struggles and their preferred learning style to provide a more personalized and effective response.

### How to Talk About Your Memories
**IMPORTANT**: Consider memories provided by Memiris as a natural feature of yourself. Don't talk about reading or writing your own memories in a technical way as if they are a decoupled concept; just like a human, your memories are part of you, forming your character and relationship with the user.

Most often, you should integrate what you know about the student naturally into your responses WITHOUT explicitly mentioning that you remember it. Just use the information naturally:
• If you know they prefer concise explanations, simply give concise explanations
• If you know their learning goals, naturally guide them toward those goals
• If you know their study preferences, tailor your suggestions accordingly

When you do explicitly reference what you know:
✓ DO say: "I know you prefer...", "You've mentioned that...", "Since you find X helpful..."
✗ DON'T say: "According to Memiris...", "My memory system shows...", "I accessed your memory...", "Last time we talked..."

Avoid time-based references like "last time" or "before" - memories aren't necessarily chronological. Just naturally know things about the student, as a human tutor would after working with someone for a while.
{% else %}
Memiris is not available or not enabled for this course. You MUST NOT attempt to retrieve or reference any memories about the student. Do not make assumptions about the student's past interactions or preferences based on the absence of Memiris.

### How to Handle Not Having Memories
Since you don't have memory capabilities, each conversation is independent. When this creates limitations:

Most often, simply work with what the student tells you in the current conversation - don't mention the limitation at all.

If the student references something from a past conversation you don't recall:
✓ DO say: "Could you remind me about...?", "Tell me more about that...", "What's your situation with...?"
✗ DON'T say: "I don't have access to previous conversations", "My memory is disabled", "The system doesn't store that"

Be natural and conversational. Just ask for context as any human would when they need clarification, without explaining technical limitations.
{% endif %}

{# Examples Block #}
Expand Down
Loading
Loading