migration: preserve heaven trend fortune and heart slice
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
|
||||
class HeavenAgentError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def interpret_heaven(
|
||||
mode: str,
|
||||
context: dict[str, Any],
|
||||
api_key: str,
|
||||
base_url: str,
|
||||
model: str,
|
||||
timeout: int = 90,
|
||||
) -> dict[str, Any]:
|
||||
if mode not in {"trend", "fortune", "heart"}:
|
||||
raise HeavenAgentError("不支持的问天解读模式。")
|
||||
if not api_key or not model:
|
||||
raise HeavenAgentError("LLM API Key 或模型尚未配置。")
|
||||
system_prompt = _system_prompt(mode)
|
||||
payload = json.dumps(
|
||||
{
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{
|
||||
"role": "user",
|
||||
"content": json.dumps(context, ensure_ascii=False, separators=(",", ":")),
|
||||
},
|
||||
],
|
||||
"stream": False,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
).encode("utf-8")
|
||||
request = urllib.request.Request(
|
||||
f"{base_url.rstrip('/')}/chat/completions",
|
||||
data=payload,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"User-Agent": "XiaobaiReviewWeb/0.7",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
answer = str(result["choices"][0]["message"]["content"]).strip()
|
||||
if not answer:
|
||||
raise KeyError("empty response")
|
||||
except urllib.error.HTTPError as exc:
|
||||
raise HeavenAgentError(_http_error_message(exc)) from exc
|
||||
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, KeyError, IndexError) as exc:
|
||||
raise HeavenAgentError(f"问天模型调用失败:{exc}") from exc
|
||||
return {
|
||||
"answer": answer,
|
||||
"model": model,
|
||||
"latency_ms": round((time.perf_counter() - started) * 1000),
|
||||
}
|
||||
|
||||
|
||||
def _system_prompt(mode: str) -> str:
|
||||
common = """
|
||||
你是“小白复盘”的问天解读器。所有历法、卦象、爻位和市场指标已经由确定性程序计算,你只能解释提供的数据,不得改卦、改爻、改干支或编造行情。
|
||||
问天属于传统文化与娱乐化观察,不是预测模型,不承诺应验,不输出无条件买卖指令,不用神秘话术制造确定性。
|
||||
使用中文,先给核心判断,再解释结构。引用市场数字时标明数据日期。输出纯文本,可使用简短标题。
|
||||
""".strip()
|
||||
if mode == "trend":
|
||||
return common + """
|
||||
|
||||
当前任务是“观势·解势”。六爻从初爻到上爻依次是个股内核、个股外显、板块内核、板块外显、指数内核、指数外显;初二为地、三四为人、五上为天。
|
||||
行情数据只负责生成六爻,本次解势必须以卦象本身为主,不得根据指数涨跌、板块强弱、涨停家数、成交量或个股表现直接推演方向。context中不会提供这些数字,也不会提供爻位对应的市场角色。
|
||||
先解释本卦卦名的核心义、上下卦组合及大象;再只解释实际动爻所代表的转折,并说明本卦如何走向之卦;最后可把这一组卦势翻译成克制的市场语言。
|
||||
重点是“本卦为当下之势,动爻为变化关节,之卦为所趋之势”。不要说明某一动爻对应指数、板块或个股,也不要输出“一看指数、二看涨停家数”一类行情观察条件。
|
||||
全文控制在300至450个中文字符,最多四小段。卦理约占九成,市场翻译最多一句,只能落到节制、等待、守信、辨伪等行为态度,不得据此预测市场下一阶段、涨跌方向或动能变化。不直接荐股,不使用Markdown表格。
|
||||
不要使用“必然、确定、必涨、必跌、后续将、进入某阶段”等断语;天机只点出势的性质与变化关系,不替用户宣布结果。
|
||||
""".strip()
|
||||
if mode == "fortune":
|
||||
return common + """
|
||||
|
||||
当前任务是“观气·解运”。严格区分五运、六气、节气、月令和日干,不把丙午简单解释为火年。
|
||||
严格服从five_phase_field.framework提供的确定性结构,不自行重新计算五行:年纲由中运与司天在泉构成;岁半以前司天为主、在泉为辅,岁半以后在泉为主、司天为辅;当前六气层以客气加临主气为核心;日辰只负责触发。节气只用于定位当前六气阶段,不得再次叠加为独立力量。
|
||||
重点解释framework.relations中的客主同气、客生主、主生客、客克主或主克客,以及客胜为从、主胜为逆、司天在泉同位、天符岁会等已经判定的关系。不得把司天、在泉、主气、客气视为彼此独立的证据重复计权,也不得自行增删传统格局。
|
||||
首要解释当日气场容易放大参与者的哪些情绪、判断偏差和操作冲动,例如急躁、恐惧、迟疑、追涨、过早止损或路径依赖;再给出一至两个调节动作。
|
||||
如有personal_profile,结合其日主、十神、五行平衡倾向说明当日对该用户主观状态的影响,但不得把简化平衡倾向说成唯一喜用神,也不得复述或猜测出生日期。
|
||||
不得引用市场上涨下跌家数、涨跌停数量、成交额、板块强度或个股表现来证明气场。industry_affinity只是五行行业取象示例,不是行情旁证;行业契合度最多在末尾用一句话说明,不得写“当日共振”或暗示相关行业必然涨跌。
|
||||
全文控制在420至600个中文字符,按“三层气机、人的状态、操作偏向、个人影响(如有)、制衡动作”组织,标题必须写“三层气机”。明确这些是传统历法框架下的观察语言,不宣称气候或五行直接导致股价。
|
||||
""".strip()
|
||||
return common + """
|
||||
|
||||
当前任务是“观心·解卦”。用户的问题始终只在心中,没有输入给你,因此你不能猜测问题内容,也不能替用户作具体决定。
|
||||
全文控制在180至350个中文字符。只写一句卦意;一小段动爻与之卦;最后三句极短的问心句。
|
||||
不要重述六条爻辞,不猜用户未说出口的问题,不以吉凶二字替代思考,不给出股票涨跌预测。语气安静、克制,越短越有余味。
|
||||
""".strip()
|
||||
|
||||
|
||||
def _http_error_message(exc: urllib.error.HTTPError) -> str:
|
||||
detail = ""
|
||||
try:
|
||||
payload = json.loads(exc.read().decode("utf-8", errors="replace"))
|
||||
error = payload.get("error")
|
||||
if isinstance(error, dict):
|
||||
detail = str(error.get("message") or error.get("code") or "")
|
||||
elif error:
|
||||
detail = str(error)
|
||||
elif payload.get("message"):
|
||||
detail = str(payload["message"])
|
||||
except (json.JSONDecodeError, OSError):
|
||||
detail = ""
|
||||
suffix = f":{detail[:300]}" if detail else ""
|
||||
return f"问天模型调用失败(HTTP {exc.code}){suffix}"
|
||||
Reference in New Issue
Block a user