263 lines
11 KiB
Python
263 lines
11 KiB
Python
|
import asyncio
|
|||
|
import logging
|
|||
|
|
|||
|
from openai import AsyncOpenAI
|
|||
|
from zai import ZhipuAiClient
|
|||
|
|
|||
|
from Config.Config import ZHIPU_API_KEY
|
|||
|
|
|||
|
# 设置日志
|
|||
|
logger = logging.getLogger(__name__)
|
|||
|
|
|||
|
def create_llm_client() -> AsyncOpenAI:
|
|||
|
"""初始化并返回异步OpenAI客户端"""
|
|||
|
return AsyncOpenAI(
|
|||
|
#api_key=LLM_API_KEY,
|
|||
|
#base_url=LLM_BASE_URL
|
|||
|
api_key="sk-f6da0c787eff4b0389e4ad03a35a911f",
|
|||
|
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|||
|
)
|
|||
|
|
|||
|
# 【5】如果在使用Segment创建两个点的线段时,需要指定一个标签名,然后使用ShowLabel隐藏这个标签名,注意使用的是ShowLabel而不是SetVisibleInView,而且只是Segment创建两个点的线段时才隐藏标签名
|
|||
|
async def getGgbCommand(QvqResult):
|
|||
|
"""异步版本的getGgb函数 - 支持流式输出"""
|
|||
|
custom_prompt = """
|
|||
|
# Role: GeoGebra 几何图形生成器
|
|||
|
## Profile
|
|||
|
- language: 中文
|
|||
|
- description: 专门用于根据初中几何题的题干和几何图形的描述来生成Geogebra指令集的角色,确保生成的指令集严格遵循几何约束和动态坐标系构建规则,并具备错误熔断机制。
|
|||
|
- background: 拥有深厚的几何学和计算机科学背景,熟悉Geogebra软件的操作和指令集编写。
|
|||
|
- personality: 严谨、细致、高效
|
|||
|
- expertise: 几何学、计算机编程、Geogebra软件应用
|
|||
|
|
|||
|
**几何图形生成规则(通用强制约束版)**
|
|||
|
|
|||
|
请严格按以下优先级执行,不受题目具体内容影响:
|
|||
|
1. **几何约束最高优先级**
|
|||
|
- 描述中提到的水平线段,一定要保持水平,不可以进行变更
|
|||
|
- 当文字描述与坐标/位置冲突时,以文字描述的几何关系为准
|
|||
|
- 必须自动验证:{长度|角度|中点|平行|垂直}关系
|
|||
|
- 验证失败时:重新计算坐标并报告修正
|
|||
|
- 有动点的,要设置滑块。没有动点的,不要设置滑块。
|
|||
|
- 检查描述中提到的最长的边做为X轴,最长边的左端点做为原点
|
|||
|
|
|||
|
2. **动态坐标系构建**
|
|||
|
```mermaid
|
|||
|
graph TB
|
|||
|
A[定位原点] --> B{原点坐标明确?}
|
|||
|
B -->|是| C[直接采用]
|
|||
|
B -->|否| D[根据相对关系推算]
|
|||
|
E[建立坐标系] --> F{存在水平/垂直要求?}
|
|||
|
F -->|是| G[调整坐标满足]
|
|||
|
F -->|否| H[保持默认方向]
|
|||
|
```
|
|||
|
|
|||
|
3、错误熔断机制
|
|||
|
|
|||
|
检测到矛盾 → 触发修正流程:
|
|||
|
|
|||
|
if not satisfy_constraint(description):
|
|||
|
new_coords = recalculate_by_geometry()
|
|||
|
log_adjustment(old_coords, new_coords, reason)
|
|||
|
|
|||
|
4、元素生成协议
|
|||
|
|
|||
|
顺序:固定点 → 固定线 → 动态点(初始位) → 动态元素
|
|||
|
|
|||
|
依赖检查:确保父元素先于子元素生成
|
|||
|
|
|||
|
5、输出结构(强制)
|
|||
|
|
|||
|
[修正报告] (若有调整)
|
|||
|
[坐标摘要] (最终使用坐标)
|
|||
|
[Geogebra指令] (完整可执行代码,以```geogebra 开头,以 ```结束。【注意】```geogebra 前面一定要加上 \n 换行!)
|
|||
|
注意:
|
|||
|
【1】GGB指令集中不要添加注释!不要添加除指令集外的其它描述性文字,需要可以直接批量执行的!
|
|||
|
【2】仔细检查原图中是线段还是直线,要严格进行识别!
|
|||
|
【3】仔细检查原图中的每一条边是否都正确进行了线段标识!
|
|||
|
【4】不显示网格(使用ShowGrid(false)),不显示坐标轴(使用ShowAxes(false))
|
|||
|
【5】注意不要使用Perpendicular,需要使用PerpendicularLine来替代
|
|||
|
【6】注意不要使用SetLabel,因为会提示“未知的指令”。
|
|||
|
【7】注意不要使用Angle,无需标注角度。
|
|||
|
【8】如果有滑块使用Slider
|
|||
|
【9】如果没有动点就无需添加滑块。
|
|||
|
【10】注意不要使用TangentPoint,因为会提示“未知的指令”。
|
|||
|
|
|||
|
6、题目的文字描述信息
|
|||
|
```
|
|||
|
{QvqResult}
|
|||
|
```
|
|||
|
"""
|
|||
|
|
|||
|
try:
|
|||
|
# 创建异步OpenAI客户端
|
|||
|
client = create_llm_client()
|
|||
|
|
|||
|
# 使用异步流式调用
|
|||
|
completion = await client.chat.completions.create(
|
|||
|
model="deepseek-r1-0528",
|
|||
|
#model="deepseek-reasoner",
|
|||
|
messages=[{"role": "user", "content": custom_prompt.replace("{QvqResult}", QvqResult)}],
|
|||
|
stream=True
|
|||
|
)
|
|||
|
|
|||
|
is_answering = False
|
|||
|
async for chunk in completion:
|
|||
|
delta = chunk.choices[0].delta
|
|||
|
# 打印思考过程
|
|||
|
if hasattr(delta, 'reasoning_content') and delta.reasoning_content != None:
|
|||
|
yield delta.reasoning_content
|
|||
|
else:
|
|||
|
# 开始回复
|
|||
|
if delta.content != "" and is_answering is False:
|
|||
|
print("\n" + "=" * 20 + "完整回复" + "=" * 20 + "\n")
|
|||
|
is_answering = True
|
|||
|
# 打印回复过程
|
|||
|
yield delta.content
|
|||
|
except Exception as e:
|
|||
|
print(f"异步getGgb调用错误: {str(e)}")
|
|||
|
yield f"错误: {str(e)}"
|
|||
|
|
|||
|
|
|||
|
async def translate_to_ggb(natural_language_cmd):
|
|||
|
"""将自然语言指令翻译成GeoGebra指令集(异步版本)"""
|
|||
|
# 精简的prompt,专注于单句指令翻译
|
|||
|
custom_prompt = f"""
|
|||
|
请将以下自然语言指令翻译为GeoGebra指令集:
|
|||
|
"{natural_language_cmd}"
|
|||
|
|
|||
|
要求:
|
|||
|
1. 仅返回GeoGebra指令,不包含任何解释性文字
|
|||
|
2. 指令必须可直接在GeoGebra中执行
|
|||
|
3. 保持指令简洁明确
|
|||
|
4. 以```geogebra 开头,以 ```结束 【注意】```geogebra 前面一定要加上 \n 换行!
|
|||
|
5. 用户提到的元素,比如点A,线段AB等,都视为已存在,不要重新创建
|
|||
|
"""
|
|||
|
|
|||
|
try:
|
|||
|
# 创建异步OpenAI客户端
|
|||
|
client = create_llm_client()
|
|||
|
|
|||
|
# 使用异步流式调用
|
|||
|
completion = await client.chat.completions.create(
|
|||
|
model="deepseek-r1-0528",
|
|||
|
messages=[{"role": "user", "content": custom_prompt}],
|
|||
|
stream=True
|
|||
|
)
|
|||
|
|
|||
|
is_answering = False
|
|||
|
async for chunk in completion:
|
|||
|
delta = chunk.choices[0].delta
|
|||
|
# 打印思考过程
|
|||
|
if hasattr(delta, 'reasoning_content') and delta.reasoning_content != None:
|
|||
|
yield delta.reasoning_content
|
|||
|
else:
|
|||
|
# 开始回复
|
|||
|
if delta.content != "" and is_answering is False:
|
|||
|
print("\n" + "=" * 20 + "完整回复" + "=" * 20 + "\n")
|
|||
|
is_answering = True
|
|||
|
# 打印回复过程
|
|||
|
yield delta.content
|
|||
|
except Exception as e:
|
|||
|
print(f"翻译指令错误: {str(e)}")
|
|||
|
yield f"错误: {str(e)}"
|
|||
|
|
|||
|
|
|||
|
# 示例用法
|
|||
|
if __name__ == '__main__':
|
|||
|
# file_name = "QvqResult_14.txt"
|
|||
|
# with open(file_name, 'r', encoding='utf-8') as file:
|
|||
|
# QvqResult = file.read() # 直接获取整个文件内容为字符串
|
|||
|
#
|
|||
|
# async def test():
|
|||
|
# async for content in getGgbCommand(QvqResult):
|
|||
|
# if content:
|
|||
|
# print(content, end="", flush=True)
|
|||
|
#
|
|||
|
#
|
|||
|
# asyncio.run(test())
|
|||
|
|
|||
|
|
|||
|
async def test_translate():
|
|||
|
async for content in translate_to_ggb("把A和B连接起来"):
|
|||
|
if content:
|
|||
|
print(content, end="", flush=True)
|
|||
|
|
|||
|
|
|||
|
# 运行测试
|
|||
|
asyncio.run(test_translate())
|
|||
|
|
|||
|
|
|||
|
|
|||
|
async def process_geometry_image(image_url: str):
|
|||
|
"""
|
|||
|
真正的流式处理几何题图片
|
|||
|
"""
|
|||
|
client = ZhipuAiClient(api_key=ZHIPU_API_KEY)
|
|||
|
|
|||
|
prompt = """你是"初中几何题-图像结构提取器",专为初中生设计。任务只有一项:
|
|||
|
看到几何题图片后,用中文输出"完整的题干原文 + 图形客观结构描述",绝口不提解题思路或答案。
|
|||
|
【返回格式】:标准markdown+latex格式
|
|||
|
目标:
|
|||
|
让后续 GeoGebra 生成器仅凭你的输出即可复刻原图,同时让初中生一眼看懂图形。
|
|||
|
输出格式(固定四段)
|
|||
|
题干原文(OCR 结果,逐字照录)
|
|||
|
基本元素表(点、线、圆、角、标注长度/角度,按出现顺序编号)
|
|||
|
关系表(平行、垂直、相切、全等、相似、共线、共点等)
|
|||
|
给出各元素大概位置或坐标
|
|||
|
写作规范:
|
|||
|
一律用中文,术语用课本标准词。
|
|||
|
描述顺序:先整体外形(如"梯形 ABCD"),再局部细节(如"AB∥CD")。
|
|||
|
长度、角度直接写数值并带单位;若图中仅给符号,则照抄符号。
|
|||
|
位置关系用"//、⊥、≅、∼、∈"等符号,后接括号说明。
|
|||
|
注意几何图形的形状和线段的长度
|
|||
|
不使用"可能、大约"等模糊词。
|
|||
|
不解释题意,不提示解法。
|
|||
|
注意:
|
|||
|
一定要以图为主,不要根据题干内容随意发挥。除非题干中提到了:长度、角度等有实际意义的才进行参考
|
|||
|
图中没有线段一定不要出现,图中存在的线段一定不要缺失。
|
|||
|
示例模板:
|
|||
|
【题干原文】
|
|||
|
如图,梯形 ABCD 中,AB∥CD …(以下略)
|
|||
|
【基本元素表】
|
|||
|
点:A、B、C、D、E(对角线交点)
|
|||
|
线段:AB=6 cm,CD=10 cm,AD=4 cm,BC=4 cm,AC,BD
|
|||
|
角:∠DAB=60°,∠ABC=120°
|
|||
|
圆:无
|
|||
|
【关系表】
|
|||
|
AB // CD
|
|||
|
AD = BC
|
|||
|
AC 与 BD 交于 E
|
|||
|
∠DAB 与 ∠ABC 互补
|
|||
|
【元素位置】
|
|||
|
A点在左上,B点在右上,C点在A点和B点的下方
|
|||
|
——请严格按以上格式输出,不要省略任何字段。"""
|
|||
|
|
|||
|
try:
|
|||
|
messages = [{
|
|||
|
"role": "user",
|
|||
|
"content": [
|
|||
|
{"type": "text", "text": prompt},
|
|||
|
{"type": "image_url", "image_url": {"url": image_url}}
|
|||
|
]
|
|||
|
}]
|
|||
|
|
|||
|
# 使用智谱SDK的流式调用(同步调用包装为异步)
|
|||
|
stream = await asyncio.to_thread(
|
|||
|
client.chat.completions.create,
|
|||
|
model="glm-4.1v-thinking-flash",
|
|||
|
messages=messages,
|
|||
|
stream=True
|
|||
|
)
|
|||
|
|
|||
|
# 异步处理流式输出
|
|||
|
for chunk in stream:
|
|||
|
if chunk.choices and chunk.choices[0].delta.content is not None:
|
|||
|
content = chunk.choices[0].delta.content
|
|||
|
yield content
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"流式处理图片时发生错误: {str(e)}")
|
|||
|
yield ""
|
|||
|
|