import asyncio import logging import os import aiofiles from openai import AsyncOpenAI from Config.Config import ALY_LLM_API_KEY, ALY_LLM_BASE_URL, ALY_LLM_MODEL_NAME # 配置日志 logger = logging.getLogger(__name__) class LLMClient: """ 大语言模型客户端封装类,提供与LLM的交互功能 """ def __init__(self, api_key=None, base_url=None, model_name=None, system_prompt=""): self.api_key = api_key or ALY_LLM_API_KEY self.base_url = base_url or ALY_LLM_BASE_URL self.model_name = model_name or ALY_LLM_MODEL_NAME self.system_prompt = system_prompt async def get_response(self, query_text: str, knowledge_content: str = "", stream: bool = True): """异步获取大模型响应""" try: client = AsyncOpenAI(api_key=self.api_key, base_url=self.base_url) full_query = query_text if knowledge_content: full_query = f"选择作答的相应知识内容:{knowledge_content}\n下面是用户提的问题:{query_text}" completion = await client.chat.completions.create( model=self.model_name, messages=[ {'role': 'system', 'content': self.system_prompt}, {'role': 'user', 'content': full_query} ], stream=stream ) if stream: async for chunk in completion: if chunk and chunk.choices and len(chunk.choices) > 0: delta = chunk.choices[0].delta if delta and delta.content and delta.content.strip(): # 添加控制台打印输出 print(f"[LLM Stream] 接收到内容片段: {delta.content.strip()}") yield delta.content else: if completion and completion.choices and len(completion.choices) > 0: message = completion.choices[0].message if message and message.content and message.content.strip(): # 添加控制台打印输出 print(f"[LLM Response] 接收到完整内容: {message.content.strip()[:100]}...") # 仅打印前100字符 yield message.content except Exception as e: logger.error(f"大模型请求异常: {str(e)}") yield f"处理请求时发生异常: {str(e)}" async def save_lesson_plan(content_chunks, output_file, stream_print=False): """异步保存教案内容到文件,支持实时打印""" try: output_dir = os.path.dirname(output_file) if output_dir and not os.path.exists(output_dir): os.makedirs(output_dir, exist_ok=True) logger.info(f"创建输出目录: {output_dir}") async with aiofiles.open(output_file, 'w', encoding='utf-8') as f: full_content = [] async for chunk in content_chunks: full_content.append(chunk) await f.write(chunk) await f.flush() # 实时打印输出内容 if stream_print: print(chunk, end='', flush=True) logger.info(f"教案已保存到: {os.path.abspath(output_file)}") return ''.join(full_content), True except Exception as e: logger.error(f"保存教案失败: {str(e)}") return None, False async def generate_lesson_plan(system_prompt=None, user_prompt=None): """生成教案的异步函数,支持动态传入提示词""" # 默认系统提示词 default_system_prompt = """你是市级骨干教师,撰写教案时严格对标"核心素养四维度(物理观念、科学思维、探究能力、态度责任)",并采用"目标—评价—活动"逆向设计模板(UbD)。活动设计需包含"教师行为+期望的学生行为+评价要点"三列表格。""" # 使用传入的系统提示词或默认值 system_prompt = system_prompt or default_system_prompt # 默认用户提示词 default_user_prompt = """请输出"万有引力"第1课时的详案,要求: 1. 教学重难点用"行为动词+知识内容"表述; 2. 至少1个"演示实验"+1个"小组探究"+1个"即时反馈"技术(如Padlet); 3. 时间轴精确到分钟; 4. 课后"教学反思提示"留3条空白,供教师手写。 【格式要求】 ### 1. 教材与学情分析 ### 2. 四维目标(对应核心素养) ### 3. 重难点 ### 4. 教学准备(器材+数字资源) ### 5. 教学过程(时间轴表格) ### 6. 板书设计(ASCII示意图) ### 7. 作业布置(与导学案分层衔接) ### 8. 教学反思提示(空白) 【变量】 教材版本:{人教版八年级下第十章第2节} 学生基础:{已学过重力、匀速圆周运动} 课时长度:{45分钟}""" # 使用传入的用户提示词或默认值 user_prompt = user_prompt or default_user_prompt llm_client = LLMClient(system_prompt=system_prompt) async def get_lesson_plan(): async for chunk in llm_client.get_response(user_prompt, stream=True): yield chunk return get_lesson_plan() async def generate_courseware(system_prompt=None, user_prompt=None): """生成课件大纲的异步函数,支持动态传入提示词""" # 默认系统提示词 default_system_prompt = """你是PPT视觉设计教练,遵循"6×6原则"(每页≤6行,每行≤6词),字体≥28pt,主色调#005BAC(教育蓝),强调色#FFB703(暖黄)。所有动画≤0.5s,禁止花哨。需要给出演示者备注栏(<备注>)。""" # 使用传入的系统提示词或默认值 system_prompt = system_prompt or default_system_prompt # 默认用户提示词 default_user_prompt = """为"万有引力"生成可直接导入PowerPoint的Markdown大纲,共12页,含: 1. 封面(课程名+章节+教师姓名留白) 2. 情境导入(1个30s短视频建议+2张图片提示) 3. 概念建构(苹果落地+月亮绕地对比图) 4. 规律探究(卡文迪许实验GIF占位) 5. 公式推导(F=G·m₁m₂/r²分三步行) 6. 例题精讲(2道,step-by-step动画) 7. 当堂检测(Padlet二维码占位) 8. 小结(思维导图,可一键转SmartArt) 9. 作业二维码(链接到在线表单) 10. 结束页("思考:如果没有万有引力?"留白) 【格式要求】 每页用三级标题###表示,下方用