mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-20 23:21:06 +02:00
feat: Implement Alison AI Classroom IT Support Assistant
This commit introduces Alison, an AI-powered classroom IT support assistant, as a new module within the SurfSense application. Key features of this implementation include: - A new LangGraph-based agent for conversational troubleshooting. - A custom knowledge base for IT support issues, located in the `alison_docs/` directory. - An extension of the RAG pipeline to use Alison's knowledge base. - Role-aware responses for professors and proctors. - A configuration toggle to enable or disable the Alison module. - Documentation for setting up and using Alison. The implementation follows the existing patterns in the codebase and is designed to be a self-contained module. Note: The unit tests for the Alison agent are currently not passing due to issues with the test environment. Further work is needed to get the tests to run correctly.
This commit is contained in:
parent
8f1fba52b4
commit
f5ea337b75
17 changed files with 714 additions and 47 deletions
|
|
@ -0,0 +1,63 @@
|
|||
import os
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
from sqlalchemy.future import select
|
||||
from app.db import async_session_maker, User, SearchSpace
|
||||
from .markdown_processor import add_received_markdown_file_document
|
||||
|
||||
async def index_alison_docs():
|
||||
"""
|
||||
Indexes the documents in the alison_docs directory.
|
||||
"""
|
||||
async with async_session_maker() as session:
|
||||
try:
|
||||
# 1. Create or get the "alison" user
|
||||
result = await session.execute(select(User).where(User.email == "alison@surfsense.ai"))
|
||||
alison_user = result.scalars().first()
|
||||
|
||||
if not alison_user:
|
||||
alison_user = User(
|
||||
id=uuid4(),
|
||||
email="alison@surfsense.ai",
|
||||
hashed_password="dummy_password", # This should be handled more securely in a real application
|
||||
is_active=True,
|
||||
is_superuser=False,
|
||||
is_verified=True,
|
||||
)
|
||||
session.add(alison_user)
|
||||
await session.commit()
|
||||
await session.refresh(alison_user)
|
||||
|
||||
# 2. Create or get the "Alison's Knowledge Base" search space
|
||||
result = await session.execute(select(SearchSpace).where(SearchSpace.name == "Alison's Knowledge Base"))
|
||||
alison_search_space = result.scalars().first()
|
||||
|
||||
if not alison_search_space:
|
||||
alison_search_space = SearchSpace(
|
||||
name="Alison's Knowledge Base",
|
||||
description="Knowledge base for the Alison IT support assistant.",
|
||||
user_id=alison_user.id,
|
||||
)
|
||||
session.add(alison_search_space)
|
||||
await session.commit()
|
||||
await session.refresh(alison_search_space)
|
||||
|
||||
# 3. Index the documents
|
||||
alison_docs_dir = "app/alison_docs"
|
||||
for filename in os.listdir(alison_docs_dir):
|
||||
if filename.endswith(".md"):
|
||||
filepath = os.path.join(alison_docs_dir, filename)
|
||||
with open(filepath, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
await add_received_markdown_file_document(
|
||||
session=session,
|
||||
file_name=filename,
|
||||
file_in_markdown=content,
|
||||
search_space_id=alison_search_space.id,
|
||||
user_id=str(alison_user.id),
|
||||
)
|
||||
logging.info("Alison's knowledge base indexed successfully.")
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to index Alison's knowledge base: {e}")
|
||||
await session.rollback()
|
||||
|
|
@ -6,7 +6,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
||||
from app.agents.researcher.configuration import SearchMode
|
||||
from app.agents.researcher.graph import graph as researcher_graph
|
||||
from app.agents.researcher.state import State
|
||||
from app.agents.researcher.state import State as ResearcherState
|
||||
from app.agents.alison.graph import graph as alison_graph
|
||||
from app.agents.alison.state import AlisonState
|
||||
from app.services.streaming_service import StreamingService
|
||||
|
||||
|
||||
|
|
@ -20,6 +22,8 @@ async def stream_connector_search_results(
|
|||
langchain_chat_history: list[Any],
|
||||
search_mode_str: str,
|
||||
document_ids_to_add_in_context: list[int],
|
||||
alison_enabled: bool = False,
|
||||
user_role: str = "professor",
|
||||
) -> AsyncGenerator[str, None]:
|
||||
"""
|
||||
Stream connector search results to the client
|
||||
|
|
@ -31,60 +35,84 @@ async def stream_connector_search_results(
|
|||
session: The database session
|
||||
research_mode: The research mode
|
||||
selected_connectors: List of selected connectors
|
||||
alison_enabled: Whether the Alison agent is enabled
|
||||
user_role: The user's role
|
||||
|
||||
Yields:
|
||||
str: Formatted response strings
|
||||
"""
|
||||
streaming_service = StreamingService()
|
||||
|
||||
if research_mode == "REPORT_GENERAL":
|
||||
num_sections = 1
|
||||
elif research_mode == "REPORT_DEEP":
|
||||
num_sections = 3
|
||||
elif research_mode == "REPORT_DEEPER":
|
||||
num_sections = 6
|
||||
else:
|
||||
# Default fallback
|
||||
num_sections = 1
|
||||
|
||||
# Convert UUID to string if needed
|
||||
user_id_str = str(user_id) if isinstance(user_id, UUID) else user_id
|
||||
|
||||
if search_mode_str == "CHUNKS":
|
||||
search_mode = SearchMode.CHUNKS
|
||||
elif search_mode_str == "DOCUMENTS":
|
||||
search_mode = SearchMode.DOCUMENTS
|
||||
# Simple keyword check to see if the query is IT support-related
|
||||
it_support_keywords = ["projector", "mic", "microphone", "zoom", "display", "wifi", "internet"]
|
||||
is_it_support_query = any(keyword in user_query.lower() for keyword in it_support_keywords)
|
||||
|
||||
# Sample configuration
|
||||
config = {
|
||||
"configurable": {
|
||||
"user_query": user_query,
|
||||
"num_sections": num_sections,
|
||||
"connectors_to_search": selected_connectors,
|
||||
"user_id": user_id_str,
|
||||
"search_space_id": search_space_id,
|
||||
"search_mode": search_mode,
|
||||
"research_mode": research_mode,
|
||||
"document_ids_to_add_in_context": document_ids_to_add_in_context,
|
||||
if alison_enabled and is_it_support_query:
|
||||
# Use the Alison agent
|
||||
config = {
|
||||
"configurable": {
|
||||
"user_id": user_id_str,
|
||||
"user_role": user_role,
|
||||
}
|
||||
}
|
||||
}
|
||||
# Initialize state with database session and streaming service
|
||||
initial_state = State(
|
||||
db_session=session,
|
||||
streaming_service=streaming_service,
|
||||
chat_history=langchain_chat_history,
|
||||
)
|
||||
initial_state = AlisonState(
|
||||
user_query=user_query,
|
||||
db_session=session,
|
||||
streaming_service=streaming_service,
|
||||
chat_history=langchain_chat_history,
|
||||
identified_problem=None,
|
||||
troubleshooting_steps=None,
|
||||
visual_aids=None,
|
||||
escalation_required=False,
|
||||
final_response=None,
|
||||
)
|
||||
async for chunk in alison_graph.astream(
|
||||
initial_state,
|
||||
config=config,
|
||||
stream_mode="custom",
|
||||
):
|
||||
if isinstance(chunk, dict) and "yield_value" in chunk:
|
||||
yield chunk["yield_value"]
|
||||
else:
|
||||
# Use the Researcher agent
|
||||
if research_mode == "REPORT_GENERAL":
|
||||
num_sections = 1
|
||||
elif research_mode == "REPORT_DEEP":
|
||||
num_sections = 3
|
||||
elif research_mode == "REPORT_DEEPER":
|
||||
num_sections = 6
|
||||
else:
|
||||
num_sections = 1
|
||||
|
||||
# Run the graph directly
|
||||
print("\nRunning the complete researcher workflow...")
|
||||
if search_mode_str == "CHUNKS":
|
||||
search_mode = SearchMode.CHUNKS
|
||||
elif search_mode_str == "DOCUMENTS":
|
||||
search_mode = SearchMode.DOCUMENTS
|
||||
|
||||
# Use streaming with config parameter
|
||||
async for chunk in researcher_graph.astream(
|
||||
initial_state,
|
||||
config=config,
|
||||
stream_mode="custom",
|
||||
):
|
||||
if isinstance(chunk, dict) and "yield_value" in chunk:
|
||||
yield chunk["yield_value"]
|
||||
config = {
|
||||
"configurable": {
|
||||
"user_query": user_query,
|
||||
"num_sections": num_sections,
|
||||
"connectors_to_search": selected_connectors,
|
||||
"user_id": user_id_str,
|
||||
"search_space_id": search_space_id,
|
||||
"search_mode": search_mode,
|
||||
"research_mode": research_mode,
|
||||
"document_ids_to_add_in_context": document_ids_to_add_in_context,
|
||||
}
|
||||
}
|
||||
initial_state = ResearcherState(
|
||||
db_session=session,
|
||||
streaming_service=streaming_service,
|
||||
chat_history=langchain_chat_history,
|
||||
)
|
||||
async for chunk in researcher_graph.astream(
|
||||
initial_state,
|
||||
config=config,
|
||||
stream_mode="custom",
|
||||
):
|
||||
if isinstance(chunk, dict) and "yield_value" in chunk:
|
||||
yield chunk["yield_value"]
|
||||
|
||||
yield streaming_service.format_completion()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue