"""pipeline_llm.gateway — 模型治理引擎:门禁链 + 主备容错 + 账号端点轮转 + 双维度记账。 调用链(与设计规范 §2 一致): ① 个人限流窗口 → ② 组织限流窗口 → ③ 个人额度余额 → ④ 组织池余额(预授权冻结) → ⑤ 组织容错策略选模型(主→备) → ⑥ 账号×端点候选池:区域偏好过滤 → 余额加权轮转 + 限流冷却避让 → ⑦ 调用(调用方执行)→ ⑧ govern_settle 结算(实际用量,双维度记账) 集成方式(复用不改建): - llm_bridge._get_model_config 前置调 govern_resolve:机构配了策略/新模型表才走治理, 否则返回 None 由旧 llm 表逻辑兜底(向后兼容)。 - proxy_chat_completion(运行环境 token 路径)拿到真实 usage 后调 govern_settle 精算。 - llm_bridge 内部路径按 prompt/response 长度估算 token 调 govern_settle(note='est')。 限流:Redis 分钟窗口原子计数(db4,与会话 db3 隔离)。Redis 不可用时限流放行(fail-open, 记 warning)——限流失效不应阻断业务,余额检查仍在 DB 侧守住。 余额:读-校验-条件 UPDATE(WHERE balance>=amount),并发竞态由 Redis 预授权计数托底。 错误必须真实可行动(用户铁律):每一级失败返回明确的中文原因,前端原样展示。 """ import json import logging import time logger = logging.getLogger("pipeline_llm.gateway") DBNAME = "pipeline" GOVERN_REDIS_DB = 4 # 治理计数器命名空间(会话用 db3,互不干扰) _COOLDOWN_SEC = 60 # 429 冷却窗口 _EST_TOKENS_PER_CALL = 800 # 无法估算时的单次调用预估(预授权用) # 治理开关缓存:机构是否启用治理(有策略记录或有 llm_model 记录) _govern_cache: dict = {} _cfg_cache: dict = {} # (account_id) -> {api_key 解密, base_url...} # ────────────────────────── 基础设施 ────────────────────────── def _get_db(): from sqlor.dbpools import DBPools db = DBPools() if not db.databases: from appPublic.jsonConfig import getConfig config = getConfig() if config.databases: db.databases = config.databases return db, DBNAME def _password_key(): from appPublic.jsonConfig import getConfig try: return getConfig().password_key or 'QRIVSRHrthhwyjy176556332' except Exception: return 'QRIVSRHrthhwyjy176556332' def encrypt_api_key(plain: str) -> str: """AES 单层加密(对称可解密)。⚠️ 不用 RC4:password/unpassword 盐不对称,加密后不可解。""" if not plain: return '' from appPublic.aes import aes_encode_b64 return aes_encode_b64(_password_key(), plain) def decrypt_api_key(enc: str) -> str: """解密 api_key。非密文(历史明文/环境差异)原样返回。""" if not enc: return '' try: from appPublic.aes import aes_decode_b64 return aes_decode_b64(_password_key(), enc) except Exception: return enc def _redis(): """治理用 Redis 连接(db4)。从 session_redis 配置推导地址,不可用返回 None。""" try: import redis as _r from appPublic.jsonConfig import getConfig url = '' try: url = (getConfig().website or {}).get('session_redis', {}).get('url', '') except Exception: url = '' if not url: url = 'redis://127.0.0.1:6379/3' # 替换库号:会话用配置的库,治理固定 GOVERN_REDIS_DB base = url.rsplit('/', 1)[0] return _r.Redis.from_url(base + '/' + str(GOVERN_REDIS_DB), socket_timeout=2) except Exception as e: logger.warning("pipeline_llm: redis 不可用(限流降级放行): %s", e) return None def _row_to_dict(r): try: return {k: v for k, v in vars(r).items() if not callable(v)} except TypeError: return dict(r) def _parse_json(s, default): if not s: return default try: v = json.loads(s) return v if v is not None else default except Exception: return default def _fnum(v): try: return float(v or 0) except (TypeError, ValueError): return 0.0 # ────────────────────────── 门禁 ①②:限流 ────────────────────────── async def _rate_check(scope: str, scope_id: str, limit: int): """分钟窗口限流。返回 (ok, msg)。limit<=0 不限;Redis 故障放行。""" if not limit or limit <= 0 or not scope_id: return True, '' r = _redis() if r is None: return True, '' minute = int(time.time() // 60) key = 'llm_rl:%s:%s:%d' % (scope, scope_id, minute) try: cnt = r.incr(key) r.expire(key, 70) except Exception as e: logger.warning("pipeline_llm: 限流计数失败(放行): %s", e) return True, '' if cnt > limit: label = '个人' if scope == 'user' else '组织' return False, '%s限流:本分钟已调用 %d 次(上限 %d),请下一分钟再试' % (label, cnt, limit) return True, '' # ────────────────────────── 门禁 ③④:额度检查 + 预授权 ────────────────────────── async def _reserve_quota(sor, org_id: str, user_id: str, amount: float): """预授权:组织池 + 个人额度扣减。返回 (ok, msg, 实际冻结金额)。 条件 UPDATE 防超扣;个人不足先回滚组织侧。0 元调用(未定价模型)不冻结。 """ if amount <= 0: return True, '', 0.0 # 组织池 recs = await sor.sqlExe( "SELECT id, balance FROM llm_org_quota WHERE org_id=${o}$ AND status='active' LIMIT 1", {"o": org_id}) await sor.sqlExe("COMMIT", {}) org_quota_id = '' if recs: if _fnum(getattr(recs[0], 'balance', 0)) < amount: return False, '组织余额不足:当前余额 %.4f,本次预扣 %.4f。请充值组织池(模型治理→组织限额限流)' % ( _fnum(getattr(recs[0], 'balance', 0)), amount), 0.0 org_quota_id = getattr(recs[0], 'id', '') await sor.sqlExe( "UPDATE llm_org_quota SET balance=balance-${a}$ WHERE id=${i}$ AND balance>=${a}$", {"a": amount, "i": org_quota_id}) await sor.sqlExe("COMMIT", {}) # 个人额度(有记录才校验;共享池+个人上限模式) if user_id: recs = await sor.sqlExe( "SELECT id, quota_limit, quota_used FROM llm_user_quota " "WHERE user_id=${u}$ AND org_id=${o}$ AND status='active' LIMIT 1", {"u": user_id, "o": org_id}) await sor.sqlExe("COMMIT", {}) if recs: lim = _fnum(getattr(recs[0], 'quota_limit', 0)) used = _fnum(getattr(recs[0], 'quota_used', 0)) if lim > 0 and used + amount > lim: if org_quota_id: # 回滚组织侧 await sor.sqlExe( "UPDATE llm_org_quota SET balance=balance+${a}$ WHERE id=${i}$", {"a": amount, "i": org_quota_id}) await sor.sqlExe("COMMIT", {}) return False, '个人额度不足:已用 %.4f / 上限 %.4f,本次需 %.4f。请联系管理员调高个人额度' % ( used, lim, amount), 0.0 await sor.sqlExe( "UPDATE llm_user_quota SET quota_used=quota_used+${a}$ WHERE id=${i}$", {"a": amount, "i": getattr(recs[0], 'id', '')}) await sor.sqlExe("COMMIT", {}) return True, '', amount async def _release_quota(sor, org_id: str, user_id: str, amount: float): """释放预授权(调用失败时)。""" if amount <= 0: return await sor.sqlExe( "UPDATE llm_org_quota SET balance=balance+${a}$ WHERE org_id=${o}$ AND status='active'", {"a": amount, "o": org_id}) if user_id: await sor.sqlExe( "UPDATE llm_user_quota SET quota_used=GREATEST(quota_used-${a}$,0) " "WHERE user_id=${u}$ AND org_id=${o}$", {"a": amount, "u": user_id, "o": org_id}) await sor.sqlExe("COMMIT", {}) # ────────────────────────── 门禁 ⑤⑥:策略选模型 + 候选轮转 ────────────────────────── async def _org_policy(sor, org_id: str): """组织容错策略。无记录返回 (主模型空, 备链空, 端点偏好 any)。""" if not org_id: return '', [], 'any' recs = await sor.sqlExe( "SELECT primary_model_id, backup_model_ids, endpoint_pref FROM llm_org_policy " "WHERE org_id=${o}$ AND status='active' LIMIT 1", {"o": org_id}) await sor.sqlExe("COMMIT", {}) if not recs: return '', [], 'any' r = recs[0] return (getattr(r, 'primary_model_id', '') or '', _parse_json(getattr(r, 'backup_model_ids', ''), []), getattr(r, 'endpoint_pref', '') or 'any') async def _model_chain(sor, org_id: str, model_name: str): """解析模型链 [主, 备...],返回 llm_model 记录 dict 列表。 - 调用方指定 model_name:在 llm_model 找到 → [它] + 策略备链补充;找不到 → 空(走旧表兜底) - 未指定:策略主模型 + 备链;无策略 → 空 """ chain_ids = [] primary, backups, pref = await _org_policy(sor, org_id) if model_name: recs = await sor.sqlExe( "SELECT id FROM llm_model WHERE status='active' AND (name=${n}$ OR vendor_model_id=${n}$) LIMIT 1", {"n": model_name}) await sor.sqlExe("COMMIT", {}) if not recs: return [], pref chain_ids.append(getattr(recs[0], 'id', '')) chain_ids.extend([b for b in backups if b not in chain_ids]) else: if primary: chain_ids.append(primary) chain_ids.extend([b for b in backups if b not in chain_ids]) if not chain_ids: return [], pref models = [] for mid in chain_ids: recs = await sor.sqlExe( "SELECT id, name, vendor_id, vendor_model_id, capability, default_params, " "price_input, price_output, cost_input, cost_output, ppid, org_id " "FROM llm_model WHERE id=${i}$ AND status='active' LIMIT 1", {"i": mid}) await sor.sqlExe("COMMIT", {}) if recs: models.append(_row_to_dict(recs[0])) return models, pref async def _vendor_endpoints(sor, vendor_id: str): """供应商端点目录。返回 (protocol, [endpoint dict])。""" recs = await sor.sqlExe( "SELECT protocol, endpoints FROM llm_vendor WHERE id=${i}$ AND status='active' LIMIT 1", {"i": vendor_id}) await sor.sqlExe("COMMIT", {}) if not recs: return '', [] r = recs[0] return getattr(r, 'protocol', '') or '', _parse_json(getattr(r, 'endpoints', ''), []) async def _account_candidates(sor, model: dict): """模型的候选账号池:同供应商、启用中。""" recs = await sor.sqlExe( "SELECT id, name, api_key, endpoint_ids, balance, status FROM llm_account " "WHERE vendor_id=${v}$ AND status='active'", {"v": model.get('vendor_id', '')}) await sor.sqlExe("COMMIT", {}) return [_row_to_dict(r) for r in (recs or [])] def _filter_by_pref(candidates_with_ep, pref: str): """按端点偏好过滤/排序候选 (account, endpoint, idx) 列表。 must_*:只保留匹配区域,空则返回空(宁可冒泡不跨端点)。 prefer_*:匹配区域排前。any:原序。 """ if pref in ('must_domestic', 'prefer_domestic'): want = 'domestic' elif pref in ('must_intl', 'prefer_intl'): want = 'international' else: return candidates_with_ep matched = [c for c in candidates_with_ep if (c[1].get('region') or '') == want] if pref.startswith('must'): return matched rest = [c for c in candidates_with_ep if (c[1].get('region') or '') != want] return matched + rest async def _pick_candidate(sor, model: dict, pref: str): """构建候选并选一个:区域偏好过滤 → 冷却避让 → 余额加权。 返回 (ok, result):ok 时 result=(account_dict, endpoint_dict);否则 result=错误信息。 """ vendor_id = model.get('vendor_id', '') if not vendor_id: return False, '模型「%s」未绑定供应商,无法选择账号' % model.get('name', '') protocol, endpoints = await _vendor_endpoints(sor, vendor_id) if not endpoints: return False, '供应商端点目录为空(模型治理→供应商→端点目录),请配置端点' accounts = await _account_candidates(sor, model) if not accounts: return False, '模型「%s」的供应商下无启用账号(或账号余额状态异常)' % model.get('name', '') # 展开 (账号, 端点) 对:账号选用的端点下标 pairs = [] for acc in accounts: idxs = _parse_json(acc.get('endpoint_ids'), []) if not idxs: continue for i in idxs: try: i = int(i) except (TypeError, ValueError): continue if 0 <= i < len(endpoints): pairs.append((acc, endpoints[i], i)) if not pairs: return False, '所有账号都未选用端点(模型治理→供应商账号→选用端点),请配置' pairs = _filter_by_pref(pairs, pref) if not pairs: return False, ('端点偏好为「仅 %s」,但没有可用的该区域端点。按合规要求不跨端点降级——' '请配置该区域端点或调整组织策略的端点偏好' % ('国内' if 'domestic' in pref else '国际')) # 冷却避让 + 余额加权选择 r = _redis() best, best_score = None, -1.0 for acc, ep, i in pairs: cool_key = 'llm_cool:%s:%d' % (acc.get('id', ''), i) acct_key = 'llm_cool:%s:*' % (acc.get('id', ''),) try: if r is not None and (r.get(cool_key) or r.get(acct_key)): continue # 限流冷却中(端点级或账号级) except Exception: pass score = _fnum(acc.get('balance')) if score > best_score: best, best_score = (acc, ep, i), score if best is None: return False, '模型「%s」的全部候选(账号×端点)都在限流冷却中,请稍后重试' % model.get('name', '') acc, ep, i = best if _fnum(acc.get('balance')) <= 0: return False, '模型「%s」的候选账号余额均不足,请为账号充值' % model.get('name', '') return True, (acc, ep) def _mark_cooldown(account_id: str, ep_index): """429 后把该账号(或账号×端点对)放进冷却。ep_index='*' 表示账号级。""" r = _redis() if r is None: return try: r.set('llm_cool:%s:%s' % (account_id, ep_index), '1', ex=_COOLDOWN_SEC) except Exception: pass def mark_cooldown_by_pair(account_id: str, base_url: str): """供代理层 429 回调:按 base_url 找到端点下标进冷却(找不到则全部下标冷却)。""" try: # 代理层只拿得到 base_url;冷却键按下标,这里无法反查下标—— # 退化为账号级冷却(该账号所有端点),宁可保守 _mark_cooldown(account_id, '*') except Exception: pass # ────────────────────────── 对外主入口 ────────────────────────── class GovernError(ValueError): """治理失败:消息真实可行动,前端原样展示。""" async def governance_enabled(sor, org_id: str) -> bool: """机构是否启用治理:**策略即开关**——该机构有 active 的容错策略才启用。 ⚠️ 不能用"新模型表有记录"判断(2026-09-01 修正):系统级(org_id=0)模型 注册后会让所有未配策略的机构被误判为启用,调用直接报错,断掉旧 llm 表链路。 机构显式建策略才是接入治理的动作;无策略 → 一切照旧(__LEGACY__)。 带缓存(改配置需重启,与 key 缓存一致)。 """ if not org_id: return False if org_id in _govern_cache: return _govern_cache[org_id] enabled = False try: recs = await sor.sqlExe( "SELECT COUNT(*) AS c FROM llm_org_policy WHERE org_id=${o}$ AND status='active'", {"o": org_id}) await sor.sqlExe("COMMIT", {}) enabled = (int(getattr(recs[0], 'c', 0)) if recs else 0) > 0 except Exception as e: logger.warning("pipeline_llm: governance_enabled 检查失败(按未启用): %s", e) enabled = False _govern_cache[org_id] = enabled return enabled async def govern_resolve(org_id: str, user_id: str = '', model_name: str = '', est_tokens: int = 0, task_ref: str = ''): """门禁链 ①-⑥ + 预授权。 返回 (ok, result): ok=True → result = {api_base, api_key(明文,仅进程内), model_id, name, account_id, endpoint_region, reserved(冻结金额), model_row(定价信息), user_id, org_id, task_ref} ok=False → result = 错误信息(真实可行动) 机构未启用治理 → (False, '__LEGACY__') 特殊标记:调用方走旧 llm 表逻辑 """ db, dbname = _get_db() async with db.sqlorContext(dbname) as sor: if not await governance_enabled(sor, org_id or ''): return False, '__LEGACY__' # ① 个人限流 if user_id: recs = await sor.sqlExe( "SELECT rate_limit FROM llm_user_quota WHERE user_id=${u}$ AND org_id=${o}$ " "AND status='active' LIMIT 1", {"u": user_id, "o": org_id}) await sor.sqlExe("COMMIT", {}) ulimit = int(_fnum(getattr(recs[0], 'rate_limit', 0))) if recs else 0 ok, msg = await _rate_check('user', user_id, ulimit) if not ok: return False, msg # ② 组织限流 recs = await sor.sqlExe( "SELECT rate_limit FROM llm_org_quota WHERE org_id=${o}$ AND status='active' LIMIT 1", {"o": org_id}) await sor.sqlExe("COMMIT", {}) olimit = int(_fnum(getattr(recs[0], 'rate_limit', 0))) if recs else 0 ok, msg = await _rate_check('org', org_id, olimit) if not ok: return False, msg # ⑤ 模型链(含 ⑥ 偏好) models, pref = await _model_chain(sor, org_id or '', model_name or '') if not models: if model_name: return False, '__LEGACY__' # 指定模型不在新表 → 旧表兜底 return False, ('机构未配置模型容错策略且未指定模型:请在模型治理→组织容错策略配置主/备模型,' '或在调用时指定模型名') # ⑥ 逐模型尝试(主 → 备容错) last_err = '' chosen = None for m in models: ok, cand = await _pick_candidate(sor, m, pref) if ok and isinstance(cand, tuple): chosen = (m, cand) break last_err = cand if isinstance(cand, str) else last_err logger.info("pipeline_llm: 模型 %s 候选不可用(%s),尝试备模型", m.get('name'), cand) if chosen is None: return False, '主/备模型全部不可用。最后原因:%s' % last_err model, chosen_pair = chosen acc, ep = chosen_pair # ③④ 预授权(按预估用量冻结组织池+个人额度) price_in = _fnum(model.get('price_input')) price_out = _fnum(model.get('price_output')) est = int(est_tokens or 0) or _EST_TOKENS_PER_CALL est_charge = est / 1000.0 * (price_in + price_out) / 2.0 ok, msg, reserved = await _reserve_quota(sor, org_id or '', user_id or '', est_charge) if not ok: return False, msg # 组装结果(api_key 解密,只在本进程内存) return True, { 'api_base': (ep.get('base_url') or '').rstrip('/'), 'api_key': decrypt_api_key(acc.get('api_key') or ''), 'model_id': model.get('vendor_model_id') or model.get('name', ''), 'name': model.get('name', ''), 'account_id': acc.get('id', ''), 'endpoint_region': ep.get('region') or '', 'endpoint_proxy': ep.get('proxy') or '', 'timeout': int(_fnum(ep.get('timeout')) or 60), 'reserved': reserved, 'model_row': model, 'user_id': user_id or '', 'org_id': org_id or '', 'task_ref': task_ref or '', } async def govern_settle(ctx: dict, ok_call: bool, req_tokens: int = 0, resp_tokens: int = 0, note: str = ''): """结算 ⑧:按实际用量修正预授权 + 双维度记账。 ctx govern_resolve 成功时返回的 dict ok_call 上游调用是否成功(失败也记账:释放预授权,写 failed 流水) """ if not ctx or not isinstance(ctx, dict): return db, dbname = _get_db() try: model = ctx.get('model_row') or {} price_in = _fnum(model.get('price_input')) price_out = _fnum(model.get('price_output')) cost_in = _fnum(model.get('cost_input')) cost_out = _fnum(model.get('cost_output')) charge = req_tokens / 1000.0 * price_in + resp_tokens / 1000.0 * price_out cost = req_tokens / 1000.0 * cost_in + resp_tokens / 1000.0 * cost_out reserved = _fnum(ctx.get('reserved')) async with db.sqlorContext(dbname) as sor: if not ok_call: # 上游限流(429)失败 → 账号级冷却,轮转避让下一轮不再选它 if '429' in str(note or ''): _mark_cooldown(ctx.get('account_id', ''), '*') await _release_quota(sor, ctx.get('org_id', ''), ctx.get('user_id', ''), reserved) else: # 多退少补:实际消费 vs 预授权 diff = charge - reserved if diff > 0: await sor.sqlExe( "UPDATE llm_org_quota SET balance=balance-${d}$ " "WHERE org_id=${o}$ AND status='active'", {"d": diff, "o": ctx.get('org_id', '')}) elif diff < 0: await sor.sqlExe( "UPDATE llm_org_quota SET balance=balance+${d}$ " "WHERE org_id=${o}$ AND status='active'", {"d": -diff, "o": ctx.get('org_id', '')}) # 个人额度按实际修正(预授权按估算扣了,退还差额) if ctx.get('user_id') and diff < 0: await sor.sqlExe( "UPDATE llm_user_quota SET quota_used=GREATEST(quota_used-${d}$,0) " "WHERE user_id=${u}$ AND org_id=${o}$", {"d": -diff, "u": ctx.get('user_id', ''), "o": ctx.get('org_id', '')}) await sor.sqlExe("COMMIT", {}) # 成本侧:账号余额扣减 if ctx.get('account_id') and cost > 0: await sor.sqlExe( "UPDATE llm_account SET balance=balance-${c}$ WHERE id=${i}$", {"c": cost, "i": ctx.get('account_id', '')}) await sor.sqlExe("COMMIT", {}) # 双维度流水 from appPublic.uniqueID import getID await sor.C('llm_usage', { 'id': getID(), 'org_id': ctx.get('org_id', ''), 'user_id': ctx.get('user_id', ''), 'model_id': model.get('id', ''), 'account_id': ctx.get('account_id', ''), 'endpoint_region': ctx.get('endpoint_region', ''), 'req_tokens': int(req_tokens or 0), 'resp_tokens': int(resp_tokens or 0), 'cost': round(cost, 6), 'charge': round(charge, 6), 'ppid': model.get('ppid', '') or '', 'task_ref': ctx.get('task_ref', ''), 'status': 'ok' if ok_call else 'failed', 'note': (note or '')[:200], }) await sor.sqlExe("COMMIT", {}) except Exception as e: logger.warning("pipeline_llm: 结算失败(不阻断调用方): %s", e) # ────────────────────────── 充值 ────────────────────────── async def recharge_account(account_id: str, amount, note: str = '', operator: str = ''): """供应商账号充值(成本侧钱包)。返回 (ok, msg)。""" try: amount = float(amount) except (TypeError, ValueError): return False, '充值金额必须是数字' if amount <= 0: return False, '充值金额必须大于 0' db, dbname = _get_db() async with db.sqlorContext(dbname) as sor: recs = await sor.sqlExe("SELECT id FROM llm_account WHERE id=${i}$", {"i": account_id}) await sor.sqlExe("COMMIT", {}) if not recs: return False, '账号不存在' await sor.sqlExe( "UPDATE llm_account SET balance=balance+${a}$, total_recharge=total_recharge+${a}$ " "WHERE id=${i}$", {"a": amount, "i": account_id}) await sor.sqlExe("COMMIT", {}) await _write_recharge_usage(sor, '', '', account_id, amount, note, operator) _govern_cache.clear() return True, '账号充值 %.4f 成功' % amount async def recharge_org(org_id: str, amount, note: str = '', operator: str = ''): """组织池充值(消费侧钱包)。无记录自动创建。返回 (ok, msg)。""" try: amount = float(amount) except (TypeError, ValueError): return False, '充值金额必须是数字' if amount <= 0: return False, '充值金额必须大于 0' if not org_id: return False, '缺少机构 ID' db, dbname = _get_db() async with db.sqlorContext(dbname) as sor: recs = await sor.sqlExe("SELECT id FROM llm_org_quota WHERE org_id=${o}$", {"o": org_id}) await sor.sqlExe("COMMIT", {}) if recs: await sor.sqlExe( "UPDATE llm_org_quota SET balance=balance+${a}$, total_recharge=total_recharge+${a}$ " "WHERE org_id=${o}$", {"a": amount, "o": org_id}) else: from appPublic.uniqueID import getID await sor.C('llm_org_quota', { 'id': getID(), 'org_id': org_id, 'balance': amount, 'total_recharge': amount, 'rate_limit': 0, 'status': 'active'}) await sor.sqlExe("COMMIT", {}) await _write_recharge_usage(sor, org_id, '', '', amount, note, operator) _govern_cache.clear() return True, '组织池充值 %.4f 成功' % amount async def _write_recharge_usage(sor, org_id, user_id, account_id, amount, note, operator): """充值流水(llm_usage 里 status=recharge 的行,对账用)。""" from appPublic.uniqueID import getID await sor.C('llm_usage', { 'id': getID(), 'org_id': org_id or '', 'user_id': user_id or '', 'model_id': '', 'account_id': account_id or '', 'endpoint_region': '', 'req_tokens': 0, 'resp_tokens': 0, 'cost': round(float(amount), 6) if account_id else 0, 'charge': round(float(amount), 6) if org_id else 0, 'ppid': '', 'task_ref': 'recharge', 'status': 'recharge', 'note': ((note or '') + (' | 操作人:%s' % operator if operator else ''))[:200], }) await sor.sqlExe("COMMIT", {}) def load_gateway(): """注册治理函数到 ServerEnv(供 dspy/其他模块调用)。""" from ahserver.serverenv import ServerEnv env = ServerEnv() env.llm_govern_resolve = govern_resolve env.llm_govern_settle = govern_settle env.llm_recharge_account = recharge_account env.llm_recharge_org = recharge_org env.llm_encrypt_api_key = encrypt_api_key logger.info("[pipeline_llm] gateway loaded")