227 lines
9.5 KiB
Python
227 lines
9.5 KiB
Python
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():
|
||
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():
|
||
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. 结束页("思考:如果没有万有引力?"留白)
|
||
|
||
【格式要求】
|
||
每页用三级标题###表示,下方用<ul>列要点;如需图片,用! `https://via.placeholder.com/800x450?text=Image` 占位并给出版权提示;在要点后另起一行写<备注>演示者话术。"""
|
||
|
||
# 使用传入的用户提示词或默认值
|
||
user_prompt = user_prompt or default_user_prompt
|
||
|
||
llm_client = LLMClient(system_prompt=system_prompt)
|
||
|
||
async def get_courseware():
|
||
async for chunk in llm_client.get_response(user_prompt, stream=True):
|
||
yield chunk
|
||
|
||
return get_courseware()
|
||
|
||
|
||
async def generate_homework(system_prompt=None, user_prompt=None, material_path=None):
|
||
"""生成课后作业的异步函数,支持动态传入提示词和材料路径"""
|
||
# 默认系统提示词
|
||
default_system_prompt = "你是命题专家,熟悉布鲁姆认知分类和'双减'政策。你需要根据用户提供的题目材料来生成作业,而不是自己创造题目。客观题采用'四选一'单选,难度比例易:中:难=6:3:1;主观题设置2小问,第1问'解释现象'对应'理解',第2问'方案设计'对应'创新'。题量控制为20分钟完成。"
|
||
|
||
# 使用传入的系统提示词或默认值
|
||
system_prompt = system_prompt or default_system_prompt
|
||
|
||
# 默认材料路径
|
||
default_material_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "static", "YunXiao.txt")
|
||
material_path = material_path or default_material_path
|
||
|
||
# 读取题目材料
|
||
try:
|
||
with open(material_path, "r", encoding="utf-8") as f:
|
||
question_material = f.read()
|
||
except Exception as e:
|
||
logger.error(f"读取题目材料失败: {str(e)}")
|
||
question_material = ""
|
||
|
||
# 默认用户提示词
|
||
default_user_prompt = f"""请根据以下'万有引力'相关题目材料,输出课后作业,满分100分,含:
|
||
A. 客观题(8题×5分=40分)
|
||
- 从提供的材料中选择题目,并按要求重新组织
|
||
- 前3题考'史实&概念'识记
|
||
- 中间3题考'公式变形&比例'理解
|
||
- 后2题考'情境估算'应用
|
||
B. 主观题(2题,30+30分)
|
||
- 题1:结合'天问一号'发射新闻,解释地球与火星之间的引力如何变化
|
||
- 题2:设计一个实验,用智能手机+免费APP估算地球质量,写出步骤与所需测量量
|
||
C. 评分标准(主观题分点给分,每点10分)
|
||
D. 参考答案与解析(客观题给出选项+一句话解析;主观题给出关键公式与评分关键词)
|
||
|
||
以下是题目材料:
|
||
{question_material}
|
||
|
||
【格式要求】
|
||
### A. 客观题
|
||
<ul>
|
||
<li>1. ……</li>
|
||
……
|
||
</ul>
|
||
|
||
### B. 主观题
|
||
#### 1. ……
|
||
#### 2. ………
|
||
|
||
### C. 评分标准
|
||
……
|
||
|
||
### D. 参考答案与解析
|
||
……"""
|
||
|
||
# 使用传入的用户提示词或默认值
|
||
user_prompt = user_prompt or default_user_prompt
|
||
|
||
llm_client = LLMClient(system_prompt=system_prompt)
|
||
|
||
async def get_homework():
|
||
async for chunk in llm_client.get_response(user_prompt, stream=True):
|
||
yield chunk
|
||
|
||
return get_homework()
|
||
|
||
|
||
# 保持向后兼容性的函数别名
|
||
async def generate_gravitation_lesson_plan():
|
||
"""生成万有引力教案的异步函数(向后兼容)"""
|
||
return await generate_lesson_plan() |