AIDvise is a Next.js based web application that uses the LangGraph framework to orchestrate AI agents designed around providing academic support/advise to students and advisors. Users ask questions through a chat interface, invoking the backend agent which gathers data from a locally-hosted SQLite database and web tools in order to inform and deliver its response.
- Two account types, students and advisors
-
- Integrated chat agent that can give academic advice based on academic data and personal details
- Ability to remember user interests and generate a list of events relevant to that user based on their interests
- Ability to track courses user is interested in to notify them of sections opening, closing, and reopening
- Alert view displaying events and course changes relevant to the user
-
- Chat agent that can report the status of students under their guidance
- List view of students managed by their advisor
- Academic data overview for each student
The following need to be installed for this quickstart guide:
- pip (Comes with Python)
- npm (Comes with Node.js)
- Node.js 20.9+ (Developed with 24.14.0) (https://nodejs.org/en/download)
- Python 3.12.x (https://www.python.org/downloads/release/python-31210/)
To verify installation, run:
node --version
npm --version
python --version
pip --versionExpected output:
v20.9+
(npm version number)
Python 3.12.x
(pip version number)In addition, the user must gather these API keys:
-
- Go to https://platform.claude.com and create an account
- Navigate to
Profile(Bottom Left) ->Organization Settings - Navigate to
Billingvia the sidebar - Click the pencil icon next to
Buy Creditsto update your billing info - Click
Buy Creditsand select an amount to purchase - Navigate to
API Keysvia the sidebar - Click
Create Keyto generate an API Key - Copy the generated key to use in your
.envfile
-
- Go to https://platform.openai.com/home and create an account
- Navigate to
Billingvia the sidebar - Click
Add Payment Detailsand fill out the form with billing information - Select an ammount for your initial credit purchase
- Navigate to
API Keysvia the sidebar - Click
Create New Secret Keyto generate an API key - Copy the generated key to use in your
.envfile Use this only if you switch one or more model slots inconfig.json(model_select) to an OpenAI-backed model.
-
- Go to https://openrouter.ai/ and create an account
- Click
Get API Key - Click
New Keyto generate your API Key - Copy the generated key to use in your
.envfile - If you plan to use a paid model:
- Navigate to
Creditsvia the sidebar - Click
Add Credits - Fill out the form with a billing method
- Select an amount to purchase
Use this only if you switch one or more model slots in
config.json(model_select) to an OpenRouter-backed model.
- Navigate to
-
- Go to https://smith.langchain.com/ and create an account
- Navigate to settings via the sidebar
- Click
API Keyto generate your key - Copy the generated key to use in your
.envfile Needed only if you want LangSmith tracing/observability.
git clone https://github.com/LSilver17/AIDvise
cd AIDviseWindows (PowerShell):
py -3.12 -m venv .venv
.venv\Scripts\activatemacOS / Linux:
python3.12 -m venv .venv
source .venv/bin/activatepip install -r requirements.txtcd frontend
npm installAIDvise depends on values in the files .env and .env.local to run
Windows (PowerShell):
cd ..
Copy-Item .env.example .envmacOS / Linux:
cd ..
cp .env.example .envNext, open .env. The following keys are required:
# AI API keys
ANTHROPIC_API_KEY=your_key- Note: Make sure
model_select:modeinconfig.jsonis set to "all-claude" if using Anthropic chat model (assumed for this guide)
Windows (PowerShell):
cd frontend
Copy-Item .env.local.example .env.local
npx auth secretmacOS / Linux:
cd frontend
cp .env.local.example .env.local
npx auth secretOpen .env.local. The following keys are required (NEXTAUTH_SECRET is automatically generated after running npx auth secret):
NEXTAUTH_SECRET=YOUR_SECRET
NEXTAUTH_URL=http://localhost:3000/
Finally, switch back to root:
cd ..- python data_pipeline/database/database_dev_tools.py --setup --populate_courses courses_data.json --populate_programs programs_data.json --add_students students_data.json --add_advisors advisors_data.json --add_term term_data.json --add_events event_data.jsonThis command creates a database with the name configured in config.json under database_config:db_name. Each flag populates their respective table with the contents of the json file provided in the flag argument.
To adapt this project for your institution, the json files in data_pipeline/jsons can be referenced as an example of how to create your own of each type (whether manually or using a data scraping program).
Open two new terminals and run the following in each terminal:
Windows (PowerShell):
.venv/Scripts/activate
npx @langchain/langgraph-cli dev --port 8123 --no-browser macOS / Linux:
source .venv/bin/activate
npx @langchain/langgraph-cli dev --port 8123 --no-browser Windows (PowerShell):
.venv/Scripts/activate
cd frontend
npm run devmacOS / Linux:
source .venv/bin/activate
cd frontend
npm run devThe application can be accessed at http://localhost:3000
Navigate to the registration section to test with a new account. AIDvise handles account registration by connecting new users to pre-existing database entries. If using the pre-initialized database, you can register an account with IDs 1,2,3 or 1,2,3,4 for students and advisors, respectively. Otherwise, refer to the IDs listed in data_pipeline/jsons/students_data.json and data_pipeline/jsons/advisors_data.json to see what IDs are available for account creation. After logging in, test asking a question in the Chat section of the dashboard to confirm that the backend agent is connected.
The application supports the creation of two types of user accounts, students and advisors. Users interact with the software via a dashboard interface.
Students have access to a chat agent that is capable of listening to a students goals and interests and providing relevant advice based on their personal goals and preferences. Based on conversations, the AI can record information in the database, such as a student's interests, to supplement its responses. Based on a student's interests, the AI is capable of generating alerts for events and courses relevant to the student, which is accessible from the alerts page. Alerts are organized as seen and unseen and listed in order of recency. Updates to a section status (Opening, Closing, Reopening) for courses a student has expressed interest in creates an alert. The "Generate new alerts" button invokes a seperate agent for generating alerts based on the interests of the student.
Advisors have access to a list view of all the students under their guidance as well as their own chat agent. Asking the chat agent about a student will have them summarize details about a student like academic information as well as their interests expressed within conversations.
AIDvise uses a local SQLite database to store user and academic information, including college catalogs, term offerings, user accounts, student/advisor profiles, events, and alert tracking metadata.
The database is organized into several key catalog tables:
- Courses: Master list of college courses with department, code, title, description, credits, prerequisite notes, and semesters typically offered.
- Programs of Study: Degree and certificate programs with their required courses and alternative requirement options.
- Terms: Semester/year schedule data, including which courses are offered, specific class sections (instructors, dates, capacity, delivery method, status), and meeting times.
- Events: Campus events (workshops, orientations, deadlines) with their scheduled dates and times.
- Students & Advisors: User profiles linked to login accounts, including academic history, interests, and advising relationships.
Student and advisor accounts are created from preexisting entries in the Student and Advisor tables, the data of which persist even when an account is deleted. Registering an account involves creation of an entry in the User database which is then connected to a student/advisor entry depending on account type and ID selected in the registration form.
A command-line helper script is provided to manage the local SQLite database used by the project. The script is data_pipeline/database/database_dev_tools.py and supports creating the schema, populating tables from JSON files, and destructive reset operations. Run it from the project root inside your Python virtual environment.
Basic usage examples:
# Create the schema and populate core tables
python data_pipeline/database/database_dev_tools.py --setup \
--populate_courses jsons/courses_data.json \
--populate_programs jsons/programs_data.json \
--add_students jsons/students_data.json \
--add_advisors jsons/advisors_data.json \
--add_term jsons/term_data.json \
--add_events jsons/event_data.json
# Add only events from a JSON file
python data_pipeline/database/database_dev_tools.py --add_events jsons/event_data.json
# Reset and re-create only the course catalog
python data_pipeline/database/database_dev_tools.py --reset course_catalog --populate_courses jsons/courses_data.jsonAvailable flags:
--setup: create the full database schema and triggers.--populate_courses <file>: load courses from a JSON file indata_pipeline/jsons.--populate_programs <file>: load programs of study from a JSON file.--add_students <file>: add student records and histories from a JSON file.--add_advisors <file>: add advisor records from a JSON file.--add_term <file>: add a single term and its offerings from a JSON file.--add_events <file>: add events and their dates from a JSON file.--reset <group>: drop tables for a group. Valid groups:course_catalog,programs_catalog,terms_and_courses,events,users,all.
Important notes:
- The script reads the database filename from
config.json(database_config.db_name). Ensure that value is correct before running destructive--resetoperations. - Reset options are destructive and permanently delete data. Use with caution and backups if necessary.
The data pipeline uses QCC(Quinsigamond Community College) as a proof of concept but can be adapted for other institutions. The scraper URLs and field parsing logic can be updated to target any college or university, while the JSON output schema and backend integration remain unchanged. See data_pipeline/README.md for full adaptation instructions.
To adapt it for a different institution:
If the institution uses Jenzabar, the scraper may work with minimal changes.
Update the search URL in refresh_registration.py:
SEARCH_URL = "https://[institution-portal-url]/..."
TERM = "Fall 2026" # Update each semester accordinglyIf the institution has a different website structure, update the URLs and adjust the field parsing logic in scrape_classes_page.py and scrape_programs.py:
CLASSES_URL = "https://[institution].edu/classes"
PROGRAMS_URL = "https://[institution].edu/programs"The line-scanning extraction approach is flexible — update the label patterns (e.g. "Credits", "Prerequisites") to match whatever labels the target site uses.
The JSON output schema and backend integration do not need to change. Only the scraper URLs and parsing logic need to be updated per institution.
AIDvise comes with two agent graphs, chat_graph and alert_graph, defined in constr.py and alert_constr.py, respectively. Agents are specified within the "graphs" property in langgraph.json, located in the root directory. Graphs are defined with the following syntax: "graph_identifier": "./graph_directory:imported_graph_name". These agents can then be used on the frontend by defining them within the runtime constant in frontend\app\api\copilotkit\route.ts. The agents are given a name and connected by using the graph ID specified in langgraph.json. An agent with the name "default" is the one called by CopilotKit's frontend components. Other agents can be programatically controlled using React hooks, detailed in this CopilotKit documentation: https://docs.copilotkit.ai/langgraph/programmatic-control
Graphs are compiled and exported within constr.py files, located in ./lg_agent. Nodes and other graph utilities can be defined in the utilities subfolder.
The Chat Graph powers the chat assistant you use in the dashboard. When you ask a question, the system decides how to find the answer by consulting a small set of helpers:
- Database helper: looks up official campus data (courses, programs, student records) stored locally.
- Web helper: searches public web pages when the database doesn't contain the needed information.
- Insertion helper (students only): saves things you tell the assistant (for example, interests or course sections you want to track) into your profile.
For students, the assistant can only read or write the student's own data. Advisors can access information for the students they advise. The system includes safety limits so helpers only run a few times per question. This prevents agents from getting stuck in infinite loops. You can change high-level behavior (which AI model is used, how many attempts helpers get, etc.) by editing config.json.
The Alert Graph is the background process that creates personalized event-based alerts for students. In plain terms it:
- Checks when event alerts were last generated for the student.
- Collects recent campus events since the last check as well as the student's saved interests.
- Uses an AI assistant to decide which events are relevant to that student based on their interests.
- Saves the matching events so they appear on the Alerts page.
Alert generation runs when you press Check for new alerts in the UI. The module is designed to surface useful items without flooding the student with irrelevant results. Like the chat assistant, the alert process can be configured from config.json to use different AI profiles.
In addition to the event-based alerts enabled by the Alert Graph, course-based alerts are also generated. These are enabled through a seperate process that runs similtaniously with the Alert Graph in response to the Check for new alerts button. This process:
- Checks when course alerts were last generated for the student.
- Collects a list of all course section status changes (such as a course being closed when all seats are filled or being reopened when a student drops the class) since the last check.
- Filters these status changes to only those for course sections the student is actively tracking.
- Saves the remaining status changes so they appear on the Alerts page.
The config.json file controls important runtime settings for the application. Editing it lets you change behavior without touching source code.
- database_config.db_name: the filename of the local SQLite database the app uses. Change this if you want to switch between different database instances.
- loop_limits: how many times helper components (planning, database, web search, insertion) may run during a single request. Lower values make responses faster; higher values allow more thorough searches but may be slower.
- model_select: groups named AI model profiles. The
modefield picks which profile to use. Each profile specifies which AI model powers planning, database queries, web searches, insertions, and alerts. Use this to switch between different AI providers or cost-optimization strategies without restarting.
After editing config.json, restart the backend service (see Quickstart step 7) so changes take effect.
To add a new profile (for example, a budget-friendly or experimental setup), edit the model_select object in config.json:
"model_select": {
"mode": "my_new_profile",
"all_claude": { /* existing profile */ },
"my_new_profile": {
"planning": "gpt-4",
"db": "gpt-4-turbo",
"web": "gpt-3.5-turbo",
"insertion": "gpt-3.5-turbo",
"alerts": "gpt-3.5-turbo"
}
}Then change the mode field to point to your new profile name. Each field must reference a model that is defined in lg_agent/utilities/model_inits.py.
These options control internal behavior and are intended for testing and debugging, not production use.
- context_config: controls what information and helper tools the chat assistant has available. Options like
full(all helpers: database, web, insertion),some-db(limited database access),no-db(only web and insertion), andno-tools(planning only, no external helpers). Important: changing this doesn't just adjust the AI prompt, it actually enables or disables which helper nodes are available. Most modes work standalone, butsome-dbrequires manual tool restrictions viatool_selectto actually enforce limited access. This is useful for testing specific components but not recommended for end users. - tool_select: turn specific tools on or off. Used for isolated testing of individual features.
To add support for a new AI model provider or configuration, edit lg_agent/utilities/model_inits.py and add a case to the _create_model function:
def _create_model(model_name: str, node_type: str):
normalized = _normalize_model_name(model_name)
match normalized:
case "sonnet_4_6":
if env := os.getenv("ANTHROPIC_API_KEY"):
return ChatAnthropic(model="claude-sonnet-4-6", temperature=0.2)
else:
raise ValueError("ANTHROPIC_API_KEY not found in environment variables.")
# ...
case "claude_3_5_sonnet":
if env := os.getenv("ANTHROPIC_API_KEY"):
return ChatAnthropic(model="claude-3-5-sonnet-20241022", temperature=0)
else:
raise ValueError("ANTHROPIC_API_KEY not found in environment variables.")
case "gpt_4":
if env := os.getenv("OPENAI_API_KEY"):
return ChatOpenAI(model="gpt-4", temperature=0.2)
else:
raise ValueError("OPENAI_API_KEY not found in environment variables.")
case "my_custom_model":
if env := os.getenv("OPENROUTER_API_KEY"):
return ChatOpenRouter(model="custom-model-id", temperature=0)
else:
raise ValueError("OPENROUTER_API_KEY not found in environment variables.")
# ...
case _:
raise ValueError(f"Unknown model: {model_name}")Once added, you can reference the new model name in your model_select profiles in config.json (e.g., "planning": "my_custom_model"). Make sure the corresponding API key is available in your .env file.
If you stay with the default Anthropic setup, you only need ANTHROPIC_API_KEY in .env.
If you switch any model slots in config.json to a different provider, also add the matching key in .env:
- OpenAI models use
OPENAI_API_KEY - OpenRouter models use
OPENROUTER_API_KEY
If you want to utylize LangSmith features like ein-depth graph tracing or testing datasets, add these values to .env:
LANGCHAIN_API_KEY: your LangSmith API keyLANGCHAIN_TRACING_V2=true: turns tracing onLANGCHAIN_PROJECT: the project name you want to group traces under
This setup is optional and mainly intended for developers who want to debug or inspect agent behavior. If you do not need tracing, you can leave these values out.
Use lg_agent/utilities/model_inits.py to add new test profiles and FakeChatModel variants for the chat and alert graphs.
- Add a new profile under
model_selectinconfig.json. - Add a matching case in
_create_model()insidemodel_inits.py. - Create one or more helper functions that return
FakeChatModel(...)instances with the message sequence you want to test. - Set
model_select.modeto your new profile before importing the graphs.
Adding a new test profile:
"model_select": {
"mode": "student_tool_test",
"student_tool_test": {
"planning": "student_planning_test_v2",
"db": "student_db_test_v2",
"web": "tool_test",
"insertion": "tool_test",
"alerts": "test"
}
}Then add the matching cases in model_inits.py:
case "student_planning_test_v2":
return _my_new_student_planner_test_model()
case "student_db_test_v2":
return _my_new_student_db_test_model()Creating a new FakeChatModel instance:
from langchain_core.messages import AIMessage, ToolCall
from utilities.TestModel import FakeChatModel
def _my_new_student_planner_test_model() -> FakeChatModel:
return FakeChatModel(
messages=iter([
AIMessage(
content='{"requires_database": true, "info_needed_db": "Test the new profile."}',
tool_calls=[
ToolCall(
name="get_current_time",
args={},
id="time-1"
)
]
),
AIMessage(content='{"answer": "Done."}'),
])
)Use these fake models to drive specific graph behavior, such as forcing tool calls, testing loop limits, or checking fallback paths.
Simple test example:
import asyncio
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
import lg_agent.utilities.model_inits as model_inits
@tool
def fixed_current_time() -> str:
return "2026-05-18 12:00 PM"
model_inits.planning_llm = model_inits.planning_llm.bind_tools(
tools=[fixed_current_time],
runtime_state={},
)
from lg_agent.s_chat_graph import s_chat_graph
async def main():
state = {
"messages": [HumanMessage(content="Run tool tests")],
"plan": {},
"loop_count": 0,
"user_id": 1,
}
result = await s_chat_graph.ainvoke(state)
assert "messages" in result
assert result["messages"]
final_message = result["messages"][-1]
executed_calls = final_message.additional_kwargs.get("executed_tool_calls", [])
assert executed_calls
assert executed_calls[0]["name"] == "fixed_current_time"
assert executed_calls[0]["output"] == "2026-05-18 12:00 PM"
asyncio.run(main())Advise uses React components to build the frontend. App components are defined in ./frontend/app/components. The dashboard is rendered using the Aside component in ./navigation/aside.tsx. Page links are made with the DefaultButton component, taking the page's HREF as a prop. Dashboard pages are defined in ./app/dashboard. If defining a page that is meant to be accessed by only an advisor/student, it is important to enforce redirection of unauthorized users in a page's layout.tsx file.
Developers are provided the authSession() hook, defined in @/app/lib/account/authSession. This hook returns a session object which can be used for user authorization.
Upon loading the dashboard, a user context is created by pulling user information by the database based on the user's ID. This user context allows for easy access of user-specific data. This includes user data & metadata as well as account-type specific data like alerts and students for students & advisors, respectively.
The get_curr_context function and its related helper functions in @/app/lib/account/account_db_utils are used for context creation & retrieval, while the context itself is defined in @/app/lib/account/user_context. These two files must be modified for any modifications to user context.
Context data can be accessed from components within the UserContextProvider wrapper by using the useUserData() hook defined in @/app/lib/account/user_context. This hook is typically used to define component state. For example, const { userData } : {userData: StudentData} = useUserData(); allows for userData to be accessed within a component.
Luca Silver | Agent & Database Design | Profile
Sean Collins | Frontend Design & User Management| Profile
Noel Mensah | Data Management | Profile