Files
dsProject/dsLightRag/Util/GGBUtil.py

263 lines
11 KiB
Python
Raw Normal View History

2025-08-14 15:45:08 +08:00
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 换行
注意
1GGB指令集中不要添加注释不要添加除指令集外的其它描述性文字需要可以直接批量执行的
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 ABCD 以下略
基本元素表
ABCDE对角线交点
线段AB=6 cmCD=10 cmAD=4 cmBC=4 cmACBD
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 ""