-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
196 lines (157 loc) · 6.52 KB
/
Copy pathmain.py
File metadata and controls
196 lines (157 loc) · 6.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/usr/bin/env python3
"""EduCode Agent - CLI 入口
交互式命令行界面,学生可以直接提交代码获取辅导。
用法:
python main.py # 交互模式
python main.py --code "..." # 单次诊断
python main.py --file main.cpp # 从文件读取代码
python main.py --mode cloud # 指定 LLM 模式
"""
import argparse
import logging
import sys
from pathlib import Path
from src.config import load_config
from src.models.schemas import Language, StudentCodeInput
from src.orchestrator import AgentOrchestrator
from src.utils.llm_router import LLMRouter
from src.utils.rag_knowledge import RAGKnowledgeBase
def setup_logging(verbose: bool = False) -> None:
level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(
level=level,
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
datefmt="%H:%M:%S",
)
def detect_language(filename: str) -> Language:
"""根据文件扩展名判断语言"""
ext = Path(filename).suffix.lower()
if ext in (".cpp", ".cc", ".cxx", ".c", ".h", ".hpp"):
return Language.CPP
if ext in (".py", ".python"):
return Language.PYTHON
return Language.PYTHON # 默认 Python
def build_orchestrator(config_path: str | None, mode: str | None, mock: bool = False) -> AgentOrchestrator:
"""构建完整的 Agent 编排器"""
config = load_config(config_path)
if mode:
config.llm_router.current_mode = mode
router = LLMRouter.from_config(config, mock=mock)
rag_kb = None
if config.llm_router.current_mode != "local_mock" or mock:
try:
rag_kb = RAGKnowledgeBase()
knowledge_file = Path(__file__).parent / "data" / "mock_student_errors.json"
if knowledge_file.exists():
count = rag_kb.load_from_json(knowledge_file)
logging.getLogger(__name__).info("RAG 知识库已加载 %d 条", count)
except Exception as e:
logging.getLogger(__name__).warning("RAG 知识库加载失败: %s", e)
return AgentOrchestrator(router, rag_kb=rag_kb)
def single_diagnosis(orchestrator: AgentOrchestrator, code: str, language: Language, student_id: str | None = None) -> None:
"""执行单次代码诊断"""
student_input = StudentCodeInput(
code=code,
language=language,
student_id=student_id or "cli_user",
)
result = orchestrator.process(student_input)
print("\n" + "=" * 60)
print(result.formatted_output)
print("=" * 60)
if result.error:
print(f"\n❌ 错误: {result.error}", file=sys.stderr)
sys.exit(1)
def interactive_mode(orchestrator: AgentOrchestrator, student_id: str) -> None:
"""交互式多轮对话模式"""
print("\n🎓 EduCode AI 编程辅导助手")
print("=" * 40)
print("输入代码获取诊断和引导提问")
print("命令: /lang cpp|python 切换语言")
print(" /quit 退出")
print(" /history 查看对话历史")
print("=" * 40)
current_language = Language.PYTHON
while True:
try:
print(f"\n📝 当前语言: {current_language.value}")
user_input = input("请输入代码 (多行输入以空行结束):\n>>> ").strip()
except (EOFError, KeyboardInterrupt):
print("\n👋 再见!")
break
if not user_input:
continue
# 处理命令
if user_input.startswith("/"):
cmd = user_input.lower().split()
if cmd[0] == "/quit":
print("👋 再见!")
break
elif cmd[0] == "/lang" and len(cmd) > 1:
if cmd[1] in ("cpp", "c++"):
current_language = Language.CPP
elif cmd[1] == "python":
current_language = Language.PYTHON
else:
print(f"❌ 不支持的语言: {cmd[1]}")
continue
elif cmd[0] == "/history":
session = orchestrator.conversation_manager.get_or_create_session(student_id)
print(session.get_history_summary() or "暂无对话历史")
continue
else:
print(f"❌ 未知命令: {cmd[0]}")
continue
# 多行代码输入:如果第一行不是完整代码,继续读取
code_lines = [user_input]
while True:
try:
line = input("... ")
except (EOFError, KeyboardInterrupt):
break
if line == "":
break
code_lines.append(line)
code = "\n".join(code_lines)
# 执行诊断
student_input = StudentCodeInput(
code=code,
language=current_language,
student_id=student_id,
)
result = orchestrator.process_with_context(student_input)
print("\n" + "-" * 50)
print(result.formatted_output)
print("-" * 50)
def main() -> None:
parser = argparse.ArgumentParser(description="EduCode AI 编程辅导助手")
parser.add_argument("--code", help="直接传入代码字符串")
parser.add_argument("--file", help="从文件读取代码")
parser.add_argument("--lang", choices=["cpp", "python"], default="python", help="编程语言")
parser.add_argument("--mode", choices=["local_mock", "cloud_mimo"], help="LLM 模式")
parser.add_argument("--config", help="配置文件路径")
parser.add_argument("--student-id", default="cli_user", help="学生 ID")
parser.add_argument("--mock", action="store_true", help="Mock 模式(不调用 LLM)")
parser.add_argument("--verbose", "-v", action="store_true", help="详细日志")
args = parser.parse_args()
setup_logging(args.verbose)
# 构建编排器
orchestrator = build_orchestrator(args.config, args.mode, mock=args.mock)
language = Language.CPP if args.lang == "cpp" else Language.PYTHON
# 单次诊断模式
if args.code:
single_diagnosis(orchestrator, args.code, language, args.student_id)
return
if args.file:
file_path = Path(args.file)
if not file_path.exists():
print(f"❌ 文件不存在: {args.file}", file=sys.stderr)
sys.exit(1)
code = file_path.read_text(encoding="utf-8")
lang = detect_language(args.file)
single_diagnosis(orchestrator, code, lang, args.student_id)
return
# 交互模式
interactive_mode(orchestrator, args.student_id)
if __name__ == "__main__":
main()