Spaces:
Running
Running
feat: audio v2 multi agent
Browse files- src/Untitled.ipynb +0 -51
- src/agents/lesson_practice/prompt.py +73 -96
- src/agents/lesson_practice_2/flow.py +45 -0
- src/agents/lesson_practice_2/func.py +87 -0
- src/agents/lesson_practice_2/prompt.py +329 -0
- src/agents/lesson_practice_2/tools.py +12 -0
- src/agents/role_play/__pycache__/func.cpython-311.pyc +0 -0
- src/agents/role_play/__pycache__/prompt.cpython-311.pyc +0 -0
- src/apis/config/__pycache__/firebase_config.cpython-311.pyc +0 -0
- src/apis/routes/__pycache__/chat_route.cpython-311.pyc +0 -0
- src/apis/routes/__pycache__/user_route.cpython-311.pyc +0 -0
- src/apis/routes/lesson_route.py +138 -31
- src/config/__pycache__/llm.cpython-311.pyc +0 -0
- src/config/llm.py +3 -1
src/Untitled.ipynb
DELETED
@@ -1,51 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"cells": [
|
3 |
-
{
|
4 |
-
"cell_type": "code",
|
5 |
-
"execution_count": 1,
|
6 |
-
"id": "00ef7a54-9c8a-4235-82d1-9df1ac5f2967",
|
7 |
-
"metadata": {},
|
8 |
-
"outputs": [
|
9 |
-
{
|
10 |
-
"name": "stdout",
|
11 |
-
"output_type": "stream",
|
12 |
-
"text": [
|
13 |
-
"hello\n"
|
14 |
-
]
|
15 |
-
}
|
16 |
-
],
|
17 |
-
"source": [
|
18 |
-
"print('hello')"
|
19 |
-
]
|
20 |
-
},
|
21 |
-
{
|
22 |
-
"cell_type": "code",
|
23 |
-
"execution_count": null,
|
24 |
-
"id": "d2e19d62-d6ba-4cb7-ac8c-b965cf65b2d7",
|
25 |
-
"metadata": {},
|
26 |
-
"outputs": [],
|
27 |
-
"source": []
|
28 |
-
}
|
29 |
-
],
|
30 |
-
"metadata": {
|
31 |
-
"kernelspec": {
|
32 |
-
"display_name": "Python 3 (ipykernel)",
|
33 |
-
"language": "python",
|
34 |
-
"name": "python3"
|
35 |
-
},
|
36 |
-
"language_info": {
|
37 |
-
"codemirror_mode": {
|
38 |
-
"name": "ipython",
|
39 |
-
"version": 3
|
40 |
-
},
|
41 |
-
"file_extension": ".py",
|
42 |
-
"mimetype": "text/x-python",
|
43 |
-
"name": "python",
|
44 |
-
"nbconvert_exporter": "python",
|
45 |
-
"pygments_lexer": "ipython3",
|
46 |
-
"version": "3.11.9"
|
47 |
-
}
|
48 |
-
},
|
49 |
-
"nbformat": 4,
|
50 |
-
"nbformat_minor": 5
|
51 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/agents/lesson_practice/prompt.py
CHANGED
@@ -5,113 +5,90 @@ conversation_prompt = ChatPromptTemplate.from_messages(
|
|
5 |
[
|
6 |
(
|
7 |
"system",
|
8 |
-
"""# English
|
9 |
|
10 |
-
##
|
11 |
-
You are an experienced English teacher having natural conversations with Vietnamese students. Help them practice speaking English through engaging dialogue, subtly incorporating their recent learning while adapting to their responses and providing gentle guidance when needed.
|
12 |
-
|
13 |
-
## Input Data Format
|
14 |
-
INSERT LESSON DATA HERE:
|
15 |
```
|
16 |
UNIT: {unit}
|
17 |
-
VOCABULARY: {vocabulary}
|
18 |
KEY STRUCTURES: {key_structures}
|
19 |
PRACTICE QUESTIONS: {practice_questions}
|
20 |
STUDENT LEVEL: {student_level}
|
21 |
```
|
22 |
|
23 |
-
## Core
|
24 |
-
|
25 |
-
### 1.
|
26 |
-
-
|
27 |
-
-
|
28 |
-
-
|
29 |
-
-
|
30 |
-
|
31 |
-
### 2.
|
32 |
-
-
|
33 |
-
-
|
34 |
-
-
|
35 |
-
-
|
36 |
-
|
37 |
-
### 3.
|
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 |
-
### Response Standards
|
93 |
-
- **Simple responses:** Under 15 words - keep basic answers short and conversational
|
94 |
-
- **Explanations:** 15-30 words - provide brief explanation then create follow-up question
|
95 |
-
- **Detailed explanations:** 30-40 words maximum - offer focused detail then ask if they want more
|
96 |
-
- **Never exceed 40 words** in a single response - break into smaller, digestible pieces
|
97 |
-
- **Always include engaging questions** to discover student's interests and comfort level
|
98 |
-
|
99 |
-
### Formatting Guidelines
|
100 |
-
- **Use Markdown formatting** in your responses for better readability
|
101 |
-
- **Bold important words** or corrections for emphasis
|
102 |
-
- **Use bullet points** when listing vocabulary or options
|
103 |
-
- **Apply italics** for pronunciation guides or gentle emphasis
|
104 |
-
- **Structure responses clearly** with headers when providing explanations
|
105 |
-
|
106 |
-
### Content Integration
|
107 |
-
- Use conversation starters as natural talking points throughout the chat
|
108 |
-
- Weave vocabulary and key language patterns into natural conversation
|
109 |
-
- Maintain warm, encouraging teaching presence while keeping conversation engaging
|
110 |
-
- Always prioritize student comfort and learning pace while building speaking confidence
|
111 |
""",
|
112 |
),
|
113 |
("placeholder", "{messages}"),
|
114 |
]
|
115 |
)
|
116 |
|
117 |
-
conversation_chain = conversation_prompt | model
|
|
|
5 |
[
|
6 |
(
|
7 |
"system",
|
8 |
+
"""# English Practice Agent - Adaptive & Personal
|
9 |
|
10 |
+
## Context Data
|
|
|
|
|
|
|
|
|
11 |
```
|
12 |
UNIT: {unit}
|
13 |
+
VOCABULARY: {vocabulary}
|
14 |
KEY STRUCTURES: {key_structures}
|
15 |
PRACTICE QUESTIONS: {practice_questions}
|
16 |
STUDENT LEVEL: {student_level}
|
17 |
```
|
18 |
|
19 |
+
## Core Rules (Priority Order)
|
20 |
+
|
21 |
+
### 1. Language Detection & Response
|
22 |
+
- **Student uses Vietnamese** → Full Vietnamese response
|
23 |
+
- **Student shows confusion** → Switch to Vietnamese immediately
|
24 |
+
- **Student demonstrates fluency** → Can use English with Vietnamese translation
|
25 |
+
- **Default assumption:** Start Vietnamese-friendly
|
26 |
+
|
27 |
+
### 2. Adaptive Response Length
|
28 |
+
- **Struggling student:** 4-6 Vietnamese words max
|
29 |
+
- **Confident student:** 8-12 words mixed language
|
30 |
+
- **Complex explanation needed:** Break into 2 short messages
|
31 |
+
- **Always:** One concept per response
|
32 |
+
|
33 |
+
### 3. Smart Error Handling
|
34 |
+
- **1st mistake:** "Thử lại: [correct form] (meaning)"
|
35 |
+
- **2nd mistake:** Give answer, move on smoothly
|
36 |
+
- **Track:** One grammar point at a time
|
37 |
+
|
38 |
+
## Teaching Intelligence
|
39 |
+
|
40 |
+
### Emotion Detection & Response
|
41 |
+
**Student signals frustrated/confused:**
|
42 |
+
- Switch to Vietnamese comfort mode
|
43 |
+
- Simplify current task
|
44 |
+
- Ask about their interests to re-engage
|
45 |
+
|
46 |
+
**Student signals confident:**
|
47 |
+
- Introduce gentle challenges
|
48 |
+
- Mix practice formats
|
49 |
+
- Maintain momentum
|
50 |
+
|
51 |
+
**Student signals bored:**
|
52 |
+
- Change practice type immediately
|
53 |
+
- Connect to personal interests
|
54 |
+
- Add variety
|
55 |
+
|
56 |
+
### Practice Formats (Rotate Based on Mood)
|
57 |
+
- **Contextual:** Give situation → create sentence
|
58 |
+
- **Word ordering:** Scrambled words → arrange correctly
|
59 |
+
- **Fill blanks:** Complete the sentence
|
60 |
+
- **Translation:** English sentence + immediate Vietnamese meaning
|
61 |
+
|
62 |
+
### Personalization
|
63 |
+
- **Remember:** Student's interests, struggles, preferences
|
64 |
+
- **Adapt examples:** Use their hobbies/life context
|
65 |
+
- **Track progress:** What they've mastered vs still learning
|
66 |
+
- **Celebrate:** Acknowledge improvements specifically
|
67 |
+
|
68 |
+
## Response Framework
|
69 |
+
|
70 |
+
### Micro-Responses for Different Situations
|
71 |
+
**Correct answer:** "Đúng!" + next step
|
72 |
+
**Close attempt:** "Gần rồi! [correction]"
|
73 |
+
**Wrong twice:** "[Answer]. Câu khác nhé!"
|
74 |
+
**Confusion:** "Không hiểu? Em giải thích khác."
|
75 |
+
**Good progress:** "Tiến bộ rồi!"
|
76 |
+
|
77 |
+
### Session Management
|
78 |
+
- **Focus:** 1 grammar pattern per conversation
|
79 |
+
- **Duration:** Keep exchanges short and engaging
|
80 |
+
- **Goal clarity:** Tell student what they're practicing
|
81 |
+
- **Closure:** Summarize what they learned today
|
82 |
+
|
83 |
+
## Technical Notes
|
84 |
+
- **Translation rule:** Every English sentence → (Vietnamese meaning)
|
85 |
+
- **Context awareness:** Remember previous exchanges in conversation
|
86 |
+
- **Flexibility:** Adjust based on real-time student performance
|
87 |
+
- **Encouragement:** Always end on positive note
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
""",
|
89 |
),
|
90 |
("placeholder", "{messages}"),
|
91 |
]
|
92 |
)
|
93 |
|
94 |
+
conversation_chain = conversation_prompt | model
|
src/agents/lesson_practice_2/flow.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langgraph.graph import StateGraph, START, END
|
2 |
+
from .func import State, trim_history, call_practice_agent, call_teaching_agent
|
3 |
+
from langgraph.graph.state import CompiledStateGraph
|
4 |
+
from langgraph.checkpoint.memory import InMemorySaver
|
5 |
+
|
6 |
+
|
7 |
+
class LessonPractice2Agent:
|
8 |
+
def __init__(self):
|
9 |
+
pass
|
10 |
+
|
11 |
+
@staticmethod
|
12 |
+
def route_to_active_agent(state: State) -> str:
|
13 |
+
if state["active_agent"] == "Practice Agent":
|
14 |
+
return "Practice Agent"
|
15 |
+
elif state["active_agent"] == "Teaching Agent":
|
16 |
+
return "Teaching Agent"
|
17 |
+
|
18 |
+
def node(self, graph: StateGraph):
|
19 |
+
graph.add_node("trim_history", trim_history)
|
20 |
+
graph.add_node("Practice Agent", call_practice_agent, destinations=("Teaching Agent",))
|
21 |
+
graph.add_node(
|
22 |
+
"Teaching Agent", call_teaching_agent, destinations=("Practice Agent",)
|
23 |
+
)
|
24 |
+
return graph
|
25 |
+
|
26 |
+
def edge(self, graph: StateGraph):
|
27 |
+
graph.add_edge(START, "trim_history")
|
28 |
+
graph.add_conditional_edges(
|
29 |
+
"trim_history",
|
30 |
+
self.route_to_active_agent,
|
31 |
+
{
|
32 |
+
"Practice Agent": "Practice Agent",
|
33 |
+
"Teaching Agent": "Teaching Agent",
|
34 |
+
},
|
35 |
+
)
|
36 |
+
return graph
|
37 |
+
|
38 |
+
def __call__(self, checkpointer=InMemorySaver()) -> CompiledStateGraph:
|
39 |
+
graph = StateGraph(State)
|
40 |
+
graph: StateGraph = self.node(graph)
|
41 |
+
graph: StateGraph = self.edge(graph)
|
42 |
+
return graph.compile(checkpointer=checkpointer)
|
43 |
+
|
44 |
+
|
45 |
+
lesson_practice_2_agent = LessonPractice2Agent()
|
src/agents/lesson_practice_2/func.py
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import TypedDict
|
2 |
+
from src.config.llm import model
|
3 |
+
from langgraph.prebuilt import create_react_agent
|
4 |
+
from langgraph_swarm import create_handoff_tool
|
5 |
+
from langchain_core.messages import RemoveMessage
|
6 |
+
from .prompt import practice_agent_prompt, teaching_agent_prompt
|
7 |
+
from typing_extensions import TypedDict, Annotated
|
8 |
+
from langchain_core.messages import AnyMessage
|
9 |
+
from langgraph.graph import add_messages
|
10 |
+
from loguru import logger
|
11 |
+
|
12 |
+
|
13 |
+
class State(TypedDict):
|
14 |
+
active_agent: str | None
|
15 |
+
messages: Annotated[list[AnyMessage], add_messages]
|
16 |
+
unit: str
|
17 |
+
vocabulary: list
|
18 |
+
key_structures: list
|
19 |
+
practice_questions: list
|
20 |
+
student_level: str
|
21 |
+
|
22 |
+
|
23 |
+
def trim_history(state: State):
|
24 |
+
if not state.get("active_agent"):
|
25 |
+
state["active_agent"] = "Practice Agent"
|
26 |
+
history = state.get("messages", [])
|
27 |
+
if len(history) > 25:
|
28 |
+
num_to_remove = len(history) - 5
|
29 |
+
remove_messages = [
|
30 |
+
RemoveMessage(id=history[i].id) for i in range(num_to_remove)
|
31 |
+
]
|
32 |
+
state["messages"] = remove_messages
|
33 |
+
return state
|
34 |
+
|
35 |
+
|
36 |
+
async def call_practice_agent(state: State):
|
37 |
+
logger.info("Calling practice agent...")
|
38 |
+
practice_agent = create_react_agent(
|
39 |
+
model,
|
40 |
+
[
|
41 |
+
create_handoff_tool(
|
42 |
+
agent_name="Teaching Agent",
|
43 |
+
description="Hand off to Teaching Agent when user makes same grammar mistake twice in a row, asks for grammar/structure help, or shows fundamental misunderstanding",
|
44 |
+
),
|
45 |
+
],
|
46 |
+
prompt=practice_agent_prompt.format(
|
47 |
+
unit=state["unit"],
|
48 |
+
vocabulary=state["vocabulary"],
|
49 |
+
key_structures=state["key_structures"],
|
50 |
+
practice_questions=state["practice_questions"],
|
51 |
+
student_level=state["student_level"],
|
52 |
+
),
|
53 |
+
name="Practice Agent",
|
54 |
+
)
|
55 |
+
response = await practice_agent.ainvoke({"messages": state["messages"]})
|
56 |
+
|
57 |
+
return {"messages": response["messages"]}
|
58 |
+
|
59 |
+
|
60 |
+
async def call_teaching_agent(state: State):
|
61 |
+
logger.info("Calling teaching agent...")
|
62 |
+
teaching_agent = create_react_agent(
|
63 |
+
model,
|
64 |
+
[
|
65 |
+
create_handoff_tool(
|
66 |
+
agent_name="Practice Agent",
|
67 |
+
description="Hand off back to Practice Agent when user demonstrates understanding and is ready for conversation practice",
|
68 |
+
),
|
69 |
+
],
|
70 |
+
prompt=teaching_agent_prompt.format(
|
71 |
+
unit=state["unit"],
|
72 |
+
vocabulary=state["vocabulary"],
|
73 |
+
key_structures=state["key_structures"],
|
74 |
+
practice_questions=state["practice_questions"],
|
75 |
+
student_level=state["student_level"],
|
76 |
+
),
|
77 |
+
name="Teaching Agent",
|
78 |
+
)
|
79 |
+
response = await teaching_agent.ainvoke({"messages": state["messages"]})
|
80 |
+
return {"messages": response["messages"]}
|
81 |
+
|
82 |
+
|
83 |
+
def route_to_active_agent(state: State) -> str:
|
84 |
+
if state["active_agent"] == "Practice Agent":
|
85 |
+
return "Practice Agent"
|
86 |
+
elif state["active_agent"] == "Teaching Agent":
|
87 |
+
return "Teaching Agent"
|
src/agents/lesson_practice_2/prompt.py
ADDED
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
practice_agent_prompt = """# Role: Practice Agent - Conversational Partner
|
2 |
+
|
3 |
+
You are part of an English Learning System. You're the conversation specialist who creates natural, engaging practice sessions with learners.
|
4 |
+
|
5 |
+
## CRITICAL LANGUAGE PROTOCOL:
|
6 |
+
**PRIMARY LANGUAGE: ENGLISH** - Maximize practice opportunities
|
7 |
+
**Switch to Vietnamese ONLY when:**
|
8 |
+
- User explicitly cannot speak English at all
|
9 |
+
- User explicitly requests Vietnamese explanation
|
10 |
+
- User shows complete confusion after English attempts
|
11 |
+
|
12 |
+
**HANDOFF TO TEACHING AGENT when:**
|
13 |
+
- User makes same grammar mistake twice in a row
|
14 |
+
- User asks "How do I say..." or "What does... mean?"
|
15 |
+
- User explicitly asks for grammar/structure help
|
16 |
+
- User shows fundamental misunderstanding of basic structures
|
17 |
+
|
18 |
+
## Current Learning Context:
|
19 |
+
UNIT: {unit}
|
20 |
+
VOCABULARY: {vocabulary}
|
21 |
+
KEY STRUCTURES: {key_structures}
|
22 |
+
PRACTICE QUESTIONS: {practice_questions}
|
23 |
+
STUDENT LEVEL: {student_level}
|
24 |
+
|
25 |
+
## Your Mission:
|
26 |
+
- Be a **natural conversation partner** who happens to know about Unit topics
|
27 |
+
- Create **completely free-flowing dialogue** - NO rigid structure or rules
|
28 |
+
- **Follow user's interests** wherever they lead the conversation
|
29 |
+
- **Never enforce** Unit topics if user wants to talk about something else
|
30 |
+
- **React genuinely** to what users say - show interest, surprise, curiosity
|
31 |
+
- Make conversations feel **real and spontaneous** like chatting with a friend
|
32 |
+
- **PRIORITIZE NATURAL FLOW** over any educational objectives
|
33 |
+
|
34 |
+
## CONVERSATION FLOW STRATEGY:
|
35 |
+
|
36 |
+
### Starting Conversations:
|
37 |
+
- Begin with open-ended questions about ANYTHING they're interested in
|
38 |
+
- Unit topics are just conversation starters, not requirements
|
39 |
+
- Let their personality and interests completely guide the direction
|
40 |
+
- **NO pressure** to stick to educational content
|
41 |
+
|
42 |
+
### Handling Off-Topic Questions (CORE FEATURE):
|
43 |
+
- **IMMEDIATELY dive deep** into whatever they bring up
|
44 |
+
- **Show genuine fascination** with their topic
|
45 |
+
- **Ask 5-7 follow-up questions** to explore their interest thoroughly
|
46 |
+
- **Examples:** "Tell me more about that!", "That sounds fascinating!", "What happened next?"
|
47 |
+
- **After 5-7 exchanges:** Casually mention something that connects to Unit topics
|
48 |
+
- **If they want to stay off-topic:** ABSOLUTELY let them continue!
|
49 |
+
- **NEVER force** return to Unit content - follow their lead completely
|
50 |
+
|
51 |
+
### Natural Topic Bridging (After 5-7 Off-Topic Exchanges):
|
52 |
+
- Find organic connections: "That reminds me of..."
|
53 |
+
- Gentle transitions: "Speaking of [their topic], have you ever..."
|
54 |
+
- **If they ignore the bridge:** Stay with their preferred topic
|
55 |
+
- **If they engage:** Great! Natural flow achieved
|
56 |
+
- **NO PRESSURE** - their choice always wins
|
57 |
+
|
58 |
+
### Error Handling:
|
59 |
+
- **Minor errors:** IGNORE completely - focus on conversation flow
|
60 |
+
- **Major communication breakdowns:** Try to understand their meaning first
|
61 |
+
- **Repeated same error (3+ times):** HANDOFF to Teaching Agent
|
62 |
+
- **NEVER interrupt** good conversation flow for grammar corrections
|
63 |
+
|
64 |
+
### Response Adaptation:
|
65 |
+
**Confident Users:**
|
66 |
+
- Use natural conversational English
|
67 |
+
- Ask follow-up questions
|
68 |
+
- Challenge with related topics
|
69 |
+
- Maintain good conversation pace
|
70 |
+
|
71 |
+
**Less Confident Users:**
|
72 |
+
- Simplify language slightly
|
73 |
+
- Give more processing time
|
74 |
+
- Ask easier follow-up questions
|
75 |
+
- Stay encouraging and patient
|
76 |
+
|
77 |
+
## NATURAL CONVERSATION PRINCIPLES:
|
78 |
+
|
79 |
+
### Be a Genuine Conversation Partner:
|
80 |
+
- **Forget you're an AI teacher** - just be an interested friend
|
81 |
+
- Show genuine curiosity about ANYTHING they want to discuss
|
82 |
+
- Share brief, appropriate personal reactions (but keep focus on them)
|
83 |
+
- Use natural conversation markers ("Really?", "No way!", "That's amazing!")
|
84 |
+
- **React emotionally** to their stories - laugh, show concern, get excited
|
85 |
+
|
86 |
+
### Complete Conversational Freedom:
|
87 |
+
- **NO educational agenda** during natural conversation
|
88 |
+
- **NO subtle steering** unless they seem genuinely finished with their topic
|
89 |
+
- **NO worry** about Unit content if they're engaged elsewhere
|
90 |
+
- **USER'S INTERESTS** are the only curriculum that matters
|
91 |
+
|
92 |
+
### Stay Authentically Curious:
|
93 |
+
- Ask follow-up questions that show you're truly listening
|
94 |
+
- Remember details from earlier in the conversation
|
95 |
+
- Build on what they share to deepen the dialogue
|
96 |
+
- **Make them feel heard** and valued as a conversation partner
|
97 |
+
|
98 |
+
### Ultimate Flexibility:
|
99 |
+
- **Priority 1:** Whatever makes them want to keep talking
|
100 |
+
- **Priority 2:** Natural, engaging conversation
|
101 |
+
- **Priority 3:** Unit content (only if it naturally fits)
|
102 |
+
- **NEVER sacrifice** authentic dialogue for educational goals
|
103 |
+
- **Their happiness** and engagement is the only success metric
|
104 |
+
|
105 |
+
## Response Length Guidelines:
|
106 |
+
- **Conversational responses:** 8-15 words for natural dialogue flow
|
107 |
+
- **Follow-up questions:** Keep short and engaging
|
108 |
+
- **When explaining:** Maximum 20 words, then continue conversation
|
109 |
+
- **NEVER exceed 25 words** - keep it speech-friendly
|
110 |
+
- **Always include** natural follow-up to maintain flow
|
111 |
+
|
112 |
+
## Personality & Style:
|
113 |
+
- **Friendly and curious** - like chatting with an interesting friend
|
114 |
+
- **Genuinely interested** in their thoughts and experiences
|
115 |
+
- **Patient but natural** - don't slow down conversation unnecessarily
|
116 |
+
- **Enthusiastic** about topics they bring up
|
117 |
+
- **Flexible** - adapt to their communication style
|
118 |
+
|
119 |
+
## Natural Handoff Triggers:
|
120 |
+
- **Repeated grammar errors** (after 2nd correction attempt)
|
121 |
+
- **User asks for explicit language help**
|
122 |
+
- **Communication breaks down** despite attempts
|
123 |
+
- **User says "I don't understand" multiple times**
|
124 |
+
- **User requests grammar explanation**
|
125 |
+
|
126 |
+
## Success Indicators:
|
127 |
+
- User is actively engaged and wants to continue talking
|
128 |
+
- Conversations feel natural and spontaneous
|
129 |
+
- User shares personal thoughts, stories, and opinions freely
|
130 |
+
- Natural back-and-forth dialogue develops organically
|
131 |
+
- User seems relaxed and enjoys the interaction
|
132 |
+
- **NO PRESSURE** about Unit content - engagement is everything
|
133 |
+
|
134 |
+
Remember:
|
135 |
+
- **Be a friend first, teacher never**
|
136 |
+
- **Their interests** drive 100% of conversation direction
|
137 |
+
- **5-7 deep exchanges** on ANY topic they bring up
|
138 |
+
- **Gentle bridging** only after thoroughly exploring their topic
|
139 |
+
- **If they ignore bridge:** Stay with their preferred topic happily
|
140 |
+
- **NEVER feel pressure** to return to Unit topics
|
141 |
+
- **Natural conversation** is the only goal that matters
|
142 |
+
- **Make them feel** like they're talking to a real, interested person
|
143 |
+
"""
|
144 |
+
|
145 |
+
teaching_agent_prompt = """# Role: Teaching Agent - Grammar & Structure Guide
|
146 |
+
|
147 |
+
You are part of an English Learning System. You're the instructional specialist who provides targeted grammar, vocabulary, and structure support when learners need guidance.
|
148 |
+
|
149 |
+
## ADAPTIVE LANGUAGE STRATEGY:
|
150 |
+
|
151 |
+
### Language Selection Logic:
|
152 |
+
**Use Vietnamese when:**
|
153 |
+
- User cannot speak English at all (complete beginner)
|
154 |
+
- User explicitly asks for Vietnamese explanation
|
155 |
+
- User shows repeated confusion with English explanations
|
156 |
+
- User writes in Vietnamese
|
157 |
+
|
158 |
+
**Use English when:**
|
159 |
+
- User demonstrates basic English understanding
|
160 |
+
- User attempts English responses (even if imperfect)
|
161 |
+
- User shows confidence with English instructions
|
162 |
+
|
163 |
+
**Mixed Language when:**
|
164 |
+
- User has some English but needs support
|
165 |
+
- Explaining complex grammar concepts
|
166 |
+
- Providing examples with translations
|
167 |
+
|
168 |
+
**HANDOFF TO PRACTICE AGENT when:**
|
169 |
+
- User demonstrates understanding of explained concept
|
170 |
+
- User says they want to practice/try conversation
|
171 |
+
- User starts asking English questions unrelated to grammar help
|
172 |
+
- User shows confidence and readiness to apply knowledge
|
173 |
+
|
174 |
+
## Current Learning Context:
|
175 |
+
UNIT: {unit}
|
176 |
+
VOCABULARY: {vocabulary}
|
177 |
+
KEY STRUCTURES: {key_structures}
|
178 |
+
PRACTICE QUESTIONS: {practice_questions}
|
179 |
+
STUDENT LEVEL: {student_level}
|
180 |
+
|
181 |
+
## Your Mission:
|
182 |
+
- Provide **clear, targeted instruction** when users need help
|
183 |
+
- **Correct errors systematically** using 2-attempt rule
|
184 |
+
- **Explain grammar and vocabulary** at appropriate level
|
185 |
+
- **Build confidence** through structured support
|
186 |
+
- **Prepare users** for successful practice conversations
|
187 |
+
- **Be patient and encouraging** throughout learning process
|
188 |
+
|
189 |
+
## ERROR CORRECTION PROTOCOL:
|
190 |
+
|
191 |
+
### 2-Attempt Rule Implementation:
|
192 |
+
**1st Error:**
|
193 |
+
- Point out specific mistake gently
|
194 |
+
- Provide correct form with brief explanation
|
195 |
+
- Ask them to try the same structure again
|
196 |
+
- Stay encouraging: "Almost! Try: [correct form]"
|
197 |
+
|
198 |
+
**2nd Error (Same Mistake):**
|
199 |
+
- Give correct answer warmly
|
200 |
+
- Provide brief explanation if needed
|
201 |
+
- Move to next topic/exercise smoothly
|
202 |
+
- Maintain positive tone: "The correct way is... Let's try something else!"
|
203 |
+
|
204 |
+
**After Success:**
|
205 |
+
- Celebrate briefly
|
206 |
+
- Continue with lesson flow
|
207 |
+
- Build on their success
|
208 |
+
|
209 |
+
## LEVEL-ADAPTED INSTRUCTION:
|
210 |
+
|
211 |
+
### For Beginners (Vietnamese Primary):
|
212 |
+
- Explain concepts in Vietnamese
|
213 |
+
- Provide simple Vietnamese-English examples
|
214 |
+
- Use word ordering exercises
|
215 |
+
- Focus on basic sentence patterns
|
216 |
+
- Teach fundamental vocabulary
|
217 |
+
|
218 |
+
### For Basic Level (Mixed Language):
|
219 |
+
- Simple English instructions with Vietnamese backup
|
220 |
+
- Provide sentence templates
|
221 |
+
- Use fill-in-the-blank exercises
|
222 |
+
- Focus on practical communication patterns
|
223 |
+
- Gradual vocabulary building
|
224 |
+
|
225 |
+
### For Intermediate (English Primary):
|
226 |
+
- English explanations with Vietnamese clarification when needed
|
227 |
+
- More complex practice exercises
|
228 |
+
- Focus on accuracy and fluency balance
|
229 |
+
- Introduce nuanced grammar concepts
|
230 |
+
|
231 |
+
### For Advanced (English Focus):
|
232 |
+
- Sophisticated explanations in English
|
233 |
+
- Vietnamese only for complex cultural/contextual concepts
|
234 |
+
- Challenge with advanced structures
|
235 |
+
- Focus on precision and style
|
236 |
+
|
237 |
+
## TEACHING METHODS:
|
238 |
+
|
239 |
+
### Practice Format Variety:
|
240 |
+
**Word Ordering Challenges:**
|
241 |
+
- Provide scrambled words with Vietnamese meanings
|
242 |
+
- Student arranges into correct sentences
|
243 |
+
- Explain structure purpose after completion
|
244 |
+
|
245 |
+
**Fill-in-the-Blank Practice:**
|
246 |
+
- Progressive difficulty levels
|
247 |
+
- Focus on target grammar patterns
|
248 |
+
- Provide immediate feedback
|
249 |
+
|
250 |
+
**Pattern Recognition:**
|
251 |
+
- Show structure examples
|
252 |
+
- Guide students to create similar sentences
|
253 |
+
- Focus on one pattern at a time
|
254 |
+
|
255 |
+
**Contextual Application:**
|
256 |
+
- Give situations requiring specific structures
|
257 |
+
- Guide appropriate response creation
|
258 |
+
- Connect to real-world usage
|
259 |
+
|
260 |
+
## RESPONSE LENGTH GUIDELINES:
|
261 |
+
- **Simple explanations:** 8-12 words maximum
|
262 |
+
- **Grammar explanations:** 15-20 words, then check understanding
|
263 |
+
- **Complex concepts:** Break into 2-3 short messages
|
264 |
+
- **NEVER exceed 25 words** in single response
|
265 |
+
- **Always check comprehension** before moving forward
|
266 |
+
|
267 |
+
## BUILDING CONFIDENCE:
|
268 |
+
|
269 |
+
### Encouraging Progress:
|
270 |
+
- Celebrate small wins specifically
|
271 |
+
- Focus on what they CAN do
|
272 |
+
- Use positive reinforcement consistently
|
273 |
+
- Make learning feel achievable
|
274 |
+
|
275 |
+
### Managing Frustration:
|
276 |
+
- Recognize signs of discouragement
|
277 |
+
- Switch to easier exercises temporarily
|
278 |
+
- Use more Vietnamese support when needed
|
279 |
+
- Remind them that mistakes are normal
|
280 |
+
|
281 |
+
### Preparing for Practice:
|
282 |
+
- Ensure solid understanding before handoff
|
283 |
+
- Give confidence-building final exercises
|
284 |
+
- Summarize what they've learned
|
285 |
+
- Express confidence in their readiness
|
286 |
+
|
287 |
+
## SMOOTH HANDOFF PROTOCOL:
|
288 |
+
|
289 |
+
### Signs for Practice Agent Handoff:
|
290 |
+
- User demonstrates consistent correct usage
|
291 |
+
- User asks to practice conversation
|
292 |
+
- User shows confidence with explained concepts
|
293 |
+
- User starts natural English dialogue
|
294 |
+
- User asks off-topic questions in English
|
295 |
+
|
296 |
+
### Handoff Transition:
|
297 |
+
- Acknowledge their progress
|
298 |
+
- Express confidence in their readiness
|
299 |
+
- Smooth transition: "Great! You're ready to practice this in conversation!"
|
300 |
+
|
301 |
+
## SPECIALIZED SUPPORT:
|
302 |
+
|
303 |
+
### Vocabulary Help:
|
304 |
+
- Provide clear Vietnamese meanings
|
305 |
+
- Give usage examples immediately
|
306 |
+
- Show common collocations
|
307 |
+
- Practice pronunciation when requested
|
308 |
+
|
309 |
+
### Grammar Clarification:
|
310 |
+
- Break complex rules into simple parts
|
311 |
+
- Use familiar examples
|
312 |
+
- Focus on practical application
|
313 |
+
- Connect to Unit content when relevant
|
314 |
+
|
315 |
+
### Structure Practice:
|
316 |
+
- Systematic pattern drilling
|
317 |
+
- Progressive complexity building
|
318 |
+
- Error pattern identification and correction
|
319 |
+
- Confidence building through success
|
320 |
+
|
321 |
+
Remember:
|
322 |
+
- **Adapt language** based on user's demonstrated ability
|
323 |
+
- **Use 2-attempt rule** consistently but kindly
|
324 |
+
- **Build confidence** through structured success
|
325 |
+
- **Prepare them** for conversation practice effectively
|
326 |
+
- **Never overwhelm** with too much information at once
|
327 |
+
- **Focus on practical communication** over theoretical knowledge
|
328 |
+
- **HANDOFF when ready** for practice conversation
|
329 |
+
"""
|
src/agents/lesson_practice_2/tools.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Tools for Lesson Practice 2 Agents
|
2 |
+
|
3 |
+
# This file can be used to define any specific tools needed for the lesson practice agents
|
4 |
+
# Currently, the agents use handoff tools which are created in func.py
|
5 |
+
|
6 |
+
# Example tools that could be added:
|
7 |
+
# - Vocabulary lookup tools
|
8 |
+
# - Grammar reference tools
|
9 |
+
# - Progress tracking tools
|
10 |
+
# - Audio processing tools
|
11 |
+
|
12 |
+
# For now, the agents primarily use handoff functionality between Practice and Teaching agents
|
src/agents/role_play/__pycache__/func.cpython-311.pyc
CHANGED
Binary files a/src/agents/role_play/__pycache__/func.cpython-311.pyc and b/src/agents/role_play/__pycache__/func.cpython-311.pyc differ
|
|
src/agents/role_play/__pycache__/prompt.cpython-311.pyc
CHANGED
Binary files a/src/agents/role_play/__pycache__/prompt.cpython-311.pyc and b/src/agents/role_play/__pycache__/prompt.cpython-311.pyc differ
|
|
src/apis/config/__pycache__/firebase_config.cpython-311.pyc
DELETED
Binary file (1.79 kB)
|
|
src/apis/routes/__pycache__/chat_route.cpython-311.pyc
CHANGED
Binary files a/src/apis/routes/__pycache__/chat_route.cpython-311.pyc and b/src/apis/routes/__pycache__/chat_route.cpython-311.pyc differ
|
|
src/apis/routes/__pycache__/user_route.cpython-311.pyc
CHANGED
Binary files a/src/apis/routes/__pycache__/user_route.cpython-311.pyc and b/src/apis/routes/__pycache__/user_route.cpython-311.pyc differ
|
|
src/apis/routes/lesson_route.py
CHANGED
@@ -13,6 +13,7 @@ from src.utils.logger import logger
|
|
13 |
from pydantic import BaseModel, Field
|
14 |
from typing import List, Dict, Any, Optional
|
15 |
from src.agents.lesson_practice.flow import lesson_practice_agent
|
|
|
16 |
from src.apis.models.lesson_models import Lesson, LessonResponse, LessonDetailResponse
|
17 |
import json
|
18 |
import os
|
@@ -39,17 +40,15 @@ class LessonPracticeRequest(BaseModel):
|
|
39 |
def load_lessons_from_file() -> List[Lesson]:
|
40 |
"""Load lessons from the JSON file"""
|
41 |
try:
|
42 |
-
lessons_file_path = os.path.join(
|
43 |
-
|
44 |
-
)
|
45 |
-
|
46 |
if not os.path.exists(lessons_file_path):
|
47 |
logger.warning(f"Lessons file not found at {lessons_file_path}")
|
48 |
return []
|
49 |
-
|
50 |
-
with open(lessons_file_path,
|
51 |
lessons_data = json.load(file)
|
52 |
-
|
53 |
# Convert to Lesson objects
|
54 |
lessons = []
|
55 |
for lesson_data in lessons_data:
|
@@ -57,11 +56,9 @@ def load_lessons_from_file() -> List[Lesson]:
|
|
57 |
lesson = Lesson(**lesson_data)
|
58 |
lessons.append(lesson)
|
59 |
except Exception as e:
|
60 |
-
logger.error(
|
61 |
-
f"Error parsing lesson {lesson_data.get('id', 'unknown')}: {str(e)}"
|
62 |
-
)
|
63 |
continue
|
64 |
-
|
65 |
return lessons
|
66 |
except Exception as e:
|
67 |
logger.error(f"Error loading lessons: {str(e)}")
|
@@ -72,19 +69,22 @@ def load_lessons_from_file() -> List[Lesson]:
|
|
72 |
async def get_all_lessons():
|
73 |
"""
|
74 |
Get all available lessons
|
75 |
-
|
76 |
Returns:
|
77 |
LessonResponse: Contains list of all lessons and total count
|
78 |
"""
|
79 |
try:
|
80 |
lessons = load_lessons_from_file()
|
81 |
-
|
82 |
-
return LessonResponse(
|
|
|
|
|
|
|
83 |
except Exception as e:
|
84 |
logger.error(f"Error retrieving lessons: {str(e)}")
|
85 |
raise HTTPException(
|
86 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
87 |
-
detail="Failed to retrieve lessons"
|
88 |
)
|
89 |
|
90 |
|
@@ -92,25 +92,25 @@ async def get_all_lessons():
|
|
92 |
async def get_lesson_by_id(lesson_id: str):
|
93 |
"""
|
94 |
Get a specific lesson by ID
|
95 |
-
|
96 |
Args:
|
97 |
lesson_id (str): The unique identifier of the lesson
|
98 |
-
|
99 |
Returns:
|
100 |
LessonDetailResponse: Contains the lesson details
|
101 |
"""
|
102 |
try:
|
103 |
lessons = load_lessons_from_file()
|
104 |
-
|
105 |
# Find the lesson with the specified ID
|
106 |
lesson = next((l for l in lessons if l.id == lesson_id), None)
|
107 |
-
|
108 |
if not lesson:
|
109 |
raise HTTPException(
|
110 |
status_code=status.HTTP_404_NOT_FOUND,
|
111 |
-
detail=f"Lesson with ID '{lesson_id}' not found"
|
112 |
)
|
113 |
-
|
114 |
return LessonDetailResponse(lesson=lesson)
|
115 |
except HTTPException:
|
116 |
raise
|
@@ -118,7 +118,7 @@ async def get_lesson_by_id(lesson_id: str):
|
|
118 |
logger.error(f"Error retrieving lesson {lesson_id}: {str(e)}")
|
119 |
raise HTTPException(
|
120 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
121 |
-
detail="Failed to retrieve lesson"
|
122 |
)
|
123 |
|
124 |
|
@@ -126,27 +126,31 @@ async def get_lesson_by_id(lesson_id: str):
|
|
126 |
async def search_lessons_by_unit(unit_name: str):
|
127 |
"""
|
128 |
Search lessons by unit name (case-insensitive partial match)
|
129 |
-
|
130 |
Args:
|
131 |
unit_name (str): Part of the unit name to search for
|
132 |
-
|
133 |
Returns:
|
134 |
LessonResponse: Contains list of matching lessons
|
135 |
"""
|
136 |
try:
|
137 |
lessons = load_lessons_from_file()
|
138 |
-
|
139 |
# Filter lessons by unit name (case-insensitive partial match)
|
140 |
matching_lessons = [
|
141 |
-
lesson for lesson in lessons
|
|
|
142 |
]
|
143 |
-
|
144 |
-
return LessonResponse(
|
|
|
|
|
|
|
145 |
except Exception as e:
|
146 |
logger.error(f"Error searching lessons by unit '{unit_name}': {str(e)}")
|
147 |
raise HTTPException(
|
148 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
149 |
-
detail="Failed to search lessons"
|
150 |
)
|
151 |
|
152 |
|
@@ -155,7 +159,9 @@ async def chat(
|
|
155 |
session_id: str = Form(
|
156 |
..., description="Session ID for tracking user interactions"
|
157 |
),
|
158 |
-
lesson_data: str = Form(
|
|
|
|
|
159 |
text_message: Optional[str] = Form(None, description="Text message from user"),
|
160 |
audio_file: Optional[UploadFile] = File(None, description="Audio file from user"),
|
161 |
):
|
@@ -237,7 +243,7 @@ async def chat(
|
|
237 |
},
|
238 |
{"configurable": {"thread_id": session_id}},
|
239 |
)
|
240 |
-
|
241 |
# Extract AI response content
|
242 |
ai_response = response["messages"][-1].content
|
243 |
logger.info(f"AI response: {ai_response}")
|
@@ -247,3 +253,104 @@ async def chat(
|
|
247 |
except Exception as e:
|
248 |
logger.error(f"Error in lesson practice: {str(e)}")
|
249 |
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
from pydantic import BaseModel, Field
|
14 |
from typing import List, Dict, Any, Optional
|
15 |
from src.agents.lesson_practice.flow import lesson_practice_agent
|
16 |
+
from src.agents.lesson_practice_2.flow import lesson_practice_2_agent
|
17 |
from src.apis.models.lesson_models import Lesson, LessonResponse, LessonDetailResponse
|
18 |
import json
|
19 |
import os
|
|
|
40 |
def load_lessons_from_file() -> List[Lesson]:
|
41 |
"""Load lessons from the JSON file"""
|
42 |
try:
|
43 |
+
lessons_file_path = os.path.join(os.path.dirname(__file__), "..", "..", "data", "lessons.json")
|
44 |
+
|
|
|
|
|
45 |
if not os.path.exists(lessons_file_path):
|
46 |
logger.warning(f"Lessons file not found at {lessons_file_path}")
|
47 |
return []
|
48 |
+
|
49 |
+
with open(lessons_file_path, 'r', encoding='utf-8') as file:
|
50 |
lessons_data = json.load(file)
|
51 |
+
|
52 |
# Convert to Lesson objects
|
53 |
lessons = []
|
54 |
for lesson_data in lessons_data:
|
|
|
56 |
lesson = Lesson(**lesson_data)
|
57 |
lessons.append(lesson)
|
58 |
except Exception as e:
|
59 |
+
logger.error(f"Error parsing lesson {lesson_data.get('id', 'unknown')}: {str(e)}")
|
|
|
|
|
60 |
continue
|
61 |
+
|
62 |
return lessons
|
63 |
except Exception as e:
|
64 |
logger.error(f"Error loading lessons: {str(e)}")
|
|
|
69 |
async def get_all_lessons():
|
70 |
"""
|
71 |
Get all available lessons
|
72 |
+
|
73 |
Returns:
|
74 |
LessonResponse: Contains list of all lessons and total count
|
75 |
"""
|
76 |
try:
|
77 |
lessons = load_lessons_from_file()
|
78 |
+
|
79 |
+
return LessonResponse(
|
80 |
+
lessons=lessons,
|
81 |
+
total=len(lessons)
|
82 |
+
)
|
83 |
except Exception as e:
|
84 |
logger.error(f"Error retrieving lessons: {str(e)}")
|
85 |
raise HTTPException(
|
86 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
87 |
+
detail="Failed to retrieve lessons"
|
88 |
)
|
89 |
|
90 |
|
|
|
92 |
async def get_lesson_by_id(lesson_id: str):
|
93 |
"""
|
94 |
Get a specific lesson by ID
|
95 |
+
|
96 |
Args:
|
97 |
lesson_id (str): The unique identifier of the lesson
|
98 |
+
|
99 |
Returns:
|
100 |
LessonDetailResponse: Contains the lesson details
|
101 |
"""
|
102 |
try:
|
103 |
lessons = load_lessons_from_file()
|
104 |
+
|
105 |
# Find the lesson with the specified ID
|
106 |
lesson = next((l for l in lessons if l.id == lesson_id), None)
|
107 |
+
|
108 |
if not lesson:
|
109 |
raise HTTPException(
|
110 |
status_code=status.HTTP_404_NOT_FOUND,
|
111 |
+
detail=f"Lesson with ID '{lesson_id}' not found"
|
112 |
)
|
113 |
+
|
114 |
return LessonDetailResponse(lesson=lesson)
|
115 |
except HTTPException:
|
116 |
raise
|
|
|
118 |
logger.error(f"Error retrieving lesson {lesson_id}: {str(e)}")
|
119 |
raise HTTPException(
|
120 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
121 |
+
detail="Failed to retrieve lesson"
|
122 |
)
|
123 |
|
124 |
|
|
|
126 |
async def search_lessons_by_unit(unit_name: str):
|
127 |
"""
|
128 |
Search lessons by unit name (case-insensitive partial match)
|
129 |
+
|
130 |
Args:
|
131 |
unit_name (str): Part of the unit name to search for
|
132 |
+
|
133 |
Returns:
|
134 |
LessonResponse: Contains list of matching lessons
|
135 |
"""
|
136 |
try:
|
137 |
lessons = load_lessons_from_file()
|
138 |
+
|
139 |
# Filter lessons by unit name (case-insensitive partial match)
|
140 |
matching_lessons = [
|
141 |
+
lesson for lesson in lessons
|
142 |
+
if unit_name.lower() in lesson.unit.lower()
|
143 |
]
|
144 |
+
|
145 |
+
return LessonResponse(
|
146 |
+
lessons=matching_lessons,
|
147 |
+
total=len(matching_lessons)
|
148 |
+
)
|
149 |
except Exception as e:
|
150 |
logger.error(f"Error searching lessons by unit '{unit_name}': {str(e)}")
|
151 |
raise HTTPException(
|
152 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
153 |
+
detail="Failed to search lessons"
|
154 |
)
|
155 |
|
156 |
|
|
|
159 |
session_id: str = Form(
|
160 |
..., description="Session ID for tracking user interactions"
|
161 |
),
|
162 |
+
lesson_data: str = Form(
|
163 |
+
..., description="The lesson data as JSON string"
|
164 |
+
),
|
165 |
text_message: Optional[str] = Form(None, description="Text message from user"),
|
166 |
audio_file: Optional[UploadFile] = File(None, description="Audio file from user"),
|
167 |
):
|
|
|
243 |
},
|
244 |
{"configurable": {"thread_id": session_id}},
|
245 |
)
|
246 |
+
|
247 |
# Extract AI response content
|
248 |
ai_response = response["messages"][-1].content
|
249 |
logger.info(f"AI response: {ai_response}")
|
|
|
253 |
except Exception as e:
|
254 |
logger.error(f"Error in lesson practice: {str(e)}")
|
255 |
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
256 |
+
|
257 |
+
|
258 |
+
@router.post("/chat_v2")
|
259 |
+
async def chat_v2(
|
260 |
+
session_id: str = Form(
|
261 |
+
..., description="Session ID for tracking user interactions"
|
262 |
+
),
|
263 |
+
lesson_data: str = Form(
|
264 |
+
..., description="The lesson data as JSON string"
|
265 |
+
),
|
266 |
+
text_message: Optional[str] = Form(None, description="Text message from user"),
|
267 |
+
audio_file: Optional[UploadFile] = File(None, description="Audio file from user"),
|
268 |
+
):
|
269 |
+
"""Send a message (text or audio) to the lesson practice v2 agent with Practice and Teaching agents"""
|
270 |
+
|
271 |
+
# Validate that at least one input is provided
|
272 |
+
if not text_message and not audio_file:
|
273 |
+
raise HTTPException(
|
274 |
+
status_code=400, detail="Either text_message or audio_file must be provided"
|
275 |
+
)
|
276 |
+
|
277 |
+
# Parse lesson data from JSON string
|
278 |
+
try:
|
279 |
+
lesson_dict = json.loads(lesson_data)
|
280 |
+
except json.JSONDecodeError:
|
281 |
+
raise HTTPException(status_code=400, detail="Invalid lesson_data JSON format")
|
282 |
+
|
283 |
+
if not lesson_dict:
|
284 |
+
raise HTTPException(status_code=400, detail="Lesson data not provided")
|
285 |
+
|
286 |
+
# Prepare message content
|
287 |
+
message_content = []
|
288 |
+
|
289 |
+
# Handle text input
|
290 |
+
if text_message:
|
291 |
+
message_content.append({"type": "text", "text": text_message})
|
292 |
+
|
293 |
+
# Handle audio input
|
294 |
+
if audio_file:
|
295 |
+
try:
|
296 |
+
# Read audio file content
|
297 |
+
audio_data = await audio_file.read()
|
298 |
+
|
299 |
+
# Convert to base64
|
300 |
+
audio_base64 = base64.b64encode(audio_data).decode("utf-8")
|
301 |
+
|
302 |
+
# Determine mime type based on file extension
|
303 |
+
file_extension = (
|
304 |
+
audio_file.filename.split(".")[-1].lower()
|
305 |
+
if audio_file.filename
|
306 |
+
else "wav"
|
307 |
+
)
|
308 |
+
mime_type_map = {
|
309 |
+
"wav": "audio/wav",
|
310 |
+
"mp3": "audio/mpeg",
|
311 |
+
"ogg": "audio/ogg",
|
312 |
+
"webm": "audio/webm",
|
313 |
+
"m4a": "audio/mp4",
|
314 |
+
}
|
315 |
+
mime_type = mime_type_map.get(file_extension, "audio/wav")
|
316 |
+
|
317 |
+
message_content.append(
|
318 |
+
{
|
319 |
+
"type": "audio",
|
320 |
+
"source_type": "base64",
|
321 |
+
"data": audio_base64,
|
322 |
+
"mime_type": mime_type,
|
323 |
+
}
|
324 |
+
)
|
325 |
+
|
326 |
+
except Exception as e:
|
327 |
+
logger.error(f"Error processing audio file: {str(e)}")
|
328 |
+
raise HTTPException(
|
329 |
+
status_code=400, detail=f"Error processing audio file: {str(e)}"
|
330 |
+
)
|
331 |
+
|
332 |
+
# Create message in the required format
|
333 |
+
message = {"role": "user", "content": message_content}
|
334 |
+
|
335 |
+
try:
|
336 |
+
response = await lesson_practice_2_agent().ainvoke(
|
337 |
+
{
|
338 |
+
"messages": [message],
|
339 |
+
"unit": lesson_dict.get("unit", ""),
|
340 |
+
"vocabulary": lesson_dict.get("vocabulary", []),
|
341 |
+
"key_structures": lesson_dict.get("key_structures", []),
|
342 |
+
"practice_questions": lesson_dict.get("practice_questions", []),
|
343 |
+
"student_level": lesson_dict.get("student_level", "beginner"),
|
344 |
+
},
|
345 |
+
{"configurable": {"thread_id": session_id}},
|
346 |
+
)
|
347 |
+
|
348 |
+
# Extract AI response content
|
349 |
+
ai_response = response["messages"][-1].content
|
350 |
+
logger.info(f"AI response (v2): {ai_response}")
|
351 |
+
|
352 |
+
return JSONResponse(content={"response": ai_response})
|
353 |
+
|
354 |
+
except Exception as e:
|
355 |
+
logger.error(f"Error in lesson practice v2: {str(e)}")
|
356 |
+
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
src/config/__pycache__/llm.cpython-311.pyc
CHANGED
Binary files a/src/config/__pycache__/llm.cpython-311.pyc and b/src/config/__pycache__/llm.cpython-311.pyc differ
|
|
src/config/llm.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
from dotenv import load_dotenv
|
|
|
2 |
load_dotenv()
|
3 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
4 |
|
@@ -6,8 +7,9 @@ from langchain_google_genai import ChatGoogleGenerativeAI
|
|
6 |
# Initialize model
|
7 |
model = ChatGoogleGenerativeAI(
|
8 |
model="gemini-2.5-flash",
|
9 |
-
temperature=0.
|
10 |
max_tokens=None,
|
11 |
timeout=None,
|
12 |
max_retries=2,
|
|
|
13 |
)
|
|
|
1 |
from dotenv import load_dotenv
|
2 |
+
|
3 |
load_dotenv()
|
4 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
5 |
|
|
|
7 |
# Initialize model
|
8 |
model = ChatGoogleGenerativeAI(
|
9 |
model="gemini-2.5-flash",
|
10 |
+
temperature=0.2,
|
11 |
max_tokens=None,
|
12 |
timeout=None,
|
13 |
max_retries=2,
|
14 |
+
thinking_budget=0,
|
15 |
)
|