test_AI_Agent / app.py
SarahXia0405's picture
Update app.py
89f9fdb verified
raw
history blame
10.6 kB
# app.py
from typing import List, Dict, Tuple, Optional
import gradio as gr
from config import (
DEFAULT_MODEL,
DEFAULT_COURSE_TOPICS,
LEARNING_MODES,
DOC_TYPES,
)
from clare_core import (
parse_syllabus_docx,
update_weaknesses_from_message,
update_cognitive_state_from_message,
render_session_status,
find_similar_past_question,
detect_language,
chat_with_clare,
export_conversation,
generate_quiz_from_history,
get_empty_input_prompt,
summarize_conversation,
)
from rag_engine import (
build_rag_chunks_from_file,
retrieve_relevant_chunks,
)
with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant") as demo:
gr.Markdown(
"""
# 🎓 Clare – AI Teaching Assistant
**Hanbridge University** · English UI · Supports both English & Chinese questions.
- Ask in English → Clare answers in English.
- Ask in Chinese → Clare can answer in Chinese.
- Use different **learning modes** to change Clare's teaching style.
- Optionally upload your **course syllabus / slides / literature (.docx)** so Clare stays aligned with your course.
"""
)
# 顶部:模型、语言偏好、学习模式
with gr.Row():
model_name = gr.Textbox(
label="Model name",
value=DEFAULT_MODEL,
info="For example: gpt-4.1-mini, gpt-4.1, gpt-4o, etc.",
)
language_preference = gr.Radio(
choices=["Auto", "English", "中文"],
value="Auto",
label="Preferred answer language",
)
learning_mode = gr.Radio(
choices=LEARNING_MODES,
value="Concept Explainer",
label="Learning mode",
)
# 课程文件上传
with gr.Row():
syllabus_file = gr.File(
label="Upload course file (.docx)",
file_types=[".docx"],
)
doc_type = gr.Dropdown(
choices=DOC_TYPES,
value="Syllabus",
label="File type",
)
# 状态:课程大纲 + 学生弱项 + 认知状态 + RAG chunks
course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
weakness_state = gr.State([])
cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
rag_chunks_state = gr.State([]) # Session 级 RAG 向量库
# 上传 syllabus 时更新课程大纲 + RAG chunks
def update_course_and_rag(file, doc_type_val):
# 更新课程大纲(保持原有逻辑)
if file is None:
topics = DEFAULT_COURSE_TOPICS
else:
if doc_type_val == "Syllabus":
try:
file_path = file.name
if file_path.lower().endswith(".docx"):
topics = parse_syllabus_docx(file_path)
else:
topics = DEFAULT_COURSE_TOPICS
except Exception:
topics = DEFAULT_COURSE_TOPICS
else:
topics = DEFAULT_COURSE_TOPICS
# 构建 RAG chunks
rag_chunks = build_rag_chunks_from_file(file, doc_type_val)
return topics, rag_chunks
syllabus_file.change(
fn=update_course_and_rag,
inputs=[syllabus_file, doc_type],
outputs=[course_outline_state, rag_chunks_state],
)
# 左侧聊天,右侧 Session 状态栏
with gr.Row():
chatbot = gr.Chatbot(
label="Clare Chat",
height=450,
)
session_status = gr.Markdown(
value=render_session_status("Concept Explainer", [], {"confusion": 0, "mastery": 0}),
label="Session status",
)
user_input = gr.Textbox(
label="Your question",
placeholder="Ask Clare anything about your course, assignment, or study plan...",
)
with gr.Row():
clear_btn = gr.Button("Reset conversation")
export_btn = gr.Button("Export conversation")
quiz_btn = gr.Button("Generate 3 quiz questions")
summary_btn = gr.Button("Summarize concepts")
export_box = gr.Textbox(
label="Conversation export (Markdown)",
lines=8,
)
quiz_box = gr.Textbox(
label="Generated quiz (with answer key)",
lines=8,
)
summary_box = gr.Textbox(
label="Concept summary (for study notes)",
lines=8,
)
# 主对话逻辑
def respond(
message,
chat_history,
course_outline,
weaknesses,
cognitive_state,
rag_chunks,
model_name_val,
language_pref_val,
learning_mode_val,
doc_type_val,
):
# 1) 决定本轮语言(Auto / English / 中文)
resolved_lang = detect_language(message or "", language_pref_val)
# 2) 空输入防护
if not message or not message.strip():
empty_msg = get_empty_input_prompt(resolved_lang)
new_history = chat_history + [("", empty_msg)]
status_text = render_session_status(learning_mode_val, weaknesses or [], cognitive_state)
return "", new_history, weaknesses, cognitive_state, status_text
# 3) 更新弱项 & 认知状态
weaknesses = update_weaknesses_from_message(message, weaknesses or [])
cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
# 4) Same Question Check(session 内复用答案)
dup = find_similar_past_question(message, chat_history)
if dup is not None:
past_q, past_a, sim = dup
prefix_en = (
"I noticed this question is very similar to one you asked earlier, "
"so I'm showing the previous explanation again. "
"If there's a specific part that's still unclear, tell me and I can "
"re-explain it in a different way.\n\n"
"**Earlier answer:**\n"
)
prefix_zh = (
"我注意到你现在的问题和之前问过的非常相似,"
"所以我先把当时的回答再展示一次。"
"如果还有具体不清楚的地方,可以告诉我,我会换一种方式解释。\n\n"
"**之前的回答:**\n"
)
if resolved_lang == "中文":
answer = prefix_zh + past_a
else:
answer = prefix_en + past_a
new_history = chat_history + [(message, answer)]
status_text = render_session_status(learning_mode_val, weaknesses, cognitive_state)
return "", new_history, weaknesses, cognitive_state, status_text
# 5) RAG:基于上传文档做检索
rag_context = retrieve_relevant_chunks(message, rag_chunks or [])
# 6) 正常调用 Clare(带上 RAG context)
answer, new_history = chat_with_clare(
message=message,
history=chat_history,
model_name=model_name_val,
language_preference=resolved_lang,
learning_mode=learning_mode_val,
doc_type=doc_type_val,
course_outline=course_outline,
weaknesses=weaknesses,
cognitive_state=cognitive_state,
rag_context=rag_context,
)
status_text = render_session_status(learning_mode_val, weaknesses, cognitive_state)
return "", new_history, weaknesses, cognitive_state, status_text
user_input.submit(
respond,
[
user_input,
chatbot,
course_outline_state,
weakness_state,
cognitive_state_state,
rag_chunks_state,
model_name,
language_preference,
learning_mode,
doc_type,
],
[user_input, chatbot, weakness_state, cognitive_state_state, session_status],
)
# 清空对话 & 状态
def clear_all():
empty_state = {"confusion": 0, "mastery": 0}
status_text = render_session_status("Concept Explainer", [], empty_state)
return (
[], # chatbot
[], # weaknesses
empty_state, # cognitive_state
[], # rag_chunks
"", # export_box
"", # quiz_box
"", # summary_box
status_text, # session_status
)
clear_btn.click(
clear_all,
None,
[
chatbot,
weakness_state,
cognitive_state_state,
rag_chunks_state,
export_box,
quiz_box,
summary_box,
session_status,
],
queue=False,
)
# 导出对话
def on_export(chat_history, course_outline, learning_mode_val, weaknesses, cognitive_state):
return export_conversation(
chat_history,
course_outline,
learning_mode_val,
weaknesses or [],
cognitive_state,
)
export_btn.click(
on_export,
[chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state],
[export_box],
)
# 生成 quiz
def on_quiz(
chat_history,
course_outline,
weaknesses,
cognitive_state,
model_name_val,
language_pref_val,
):
return generate_quiz_from_history(
chat_history,
course_outline,
weaknesses or [],
cognitive_state,
model_name_val,
language_pref_val,
)
quiz_btn.click(
on_quiz,
[
chatbot,
course_outline_state,
weakness_state,
cognitive_state_state,
model_name,
language_preference,
],
[quiz_box],
)
# 概念总结
def on_summary(
chat_history,
course_outline,
weaknesses,
cognitive_state,
model_name_val,
language_pref_val,
):
return summarize_conversation(
chat_history,
course_outline,
weaknesses or [],
cognitive_state,
model_name_val,
language_pref_val,
)
summary_btn.click(
on_summary,
[
chatbot,
course_outline_state,
weakness_state,
cognitive_state_state,
model_name,
language_preference,
],
[summary_box],
)
if __name__ == "__main__":
demo.launch()