""" _bootstrap.py — cheap-worker 地基:跨包 import tier2 worker 框架层 + 代理旁路 + key 注入 + M3 model 装配。 对照源:tier2 worker/client.py(代理旁路 + key)、worker/config.py(build_model_openai)、 worker/agent_loop/studio.py:31-58(sys.path 兜底范式)。 两条内网阶段约束(AGENTS.md §9)落地: · key 进项目文档 docs/内网凭据与端点.md、不进 env var;client.get_api_key() 只读 env NEWAPI_KEY, 故本模块在装 model 前先把文档里的 key 解析注入 os.environ(对 Node m3.mjs getKey「优先 env 否则解析文档」同款单一事实源)。 · 代理旁路时序:install_proxy_bypass 必须在 import anthropic/openai 之前——已由 config.py 内部(import 时先 resolve_base_url 装旁路再 import agentscope/openai)保证;本模块不在 import config 前 import openai,故安全。 """ import os import re import sys from pathlib import Path # ── ① 跨包 import 兜底:把 tier2/gen-worker/ 加进 sys.path(worker / observability 两个顶层包据此可 import)── # 本文件在 cheap-worker/ 下 → parents[1] = repo 根 → /tier2/gen-worker。 # 目录名 gen-worker 含连字符不能直接 import;照 studio.py 范式把 gen-worker/ 本身加进 sys.path,再 import 其下合法包名 worker。 _REPO_ROOT = Path(__file__).resolve().parents[1] _GEN_WORKER = _REPO_ROOT / "tier2" / "gen-worker" if str(_GEN_WORKER) not in sys.path: sys.path.insert(0, str(_GEN_WORKER)) # 内网凭据单一事实源(内网阶段允许入仓)。 _CRED_DOC = _REPO_ROOT / "docs" / "内网凭据与端点.md" # spike 的便宜档模型固定 MiniMax-M3(KTD6:与 Node amodel-gen 同模型,保证对照公平;reframe 长期可换更便宜模型)。 SPIKE_MODEL = "MiniMax-M3" def ensure_api_key_env() -> str: """确保 os.environ['NEWAPI_KEY'] 可用:已设则用;否则从 docs/内网凭据与端点.md 解析注入(内网允许入仓)。 Returns: 解析到的 key(sk- 开头)。 Raises: RuntimeError: env 未设且文档无可解析的 key。 """ key = os.environ.get("NEWAPI_KEY") if key: return key try: doc = _CRED_DOC.read_text(encoding="utf-8") m = re.search(r"NEWAPI_KEY[^\n]*?(sk-[A-Za-z0-9]+)", doc) if m: os.environ["NEWAPI_KEY"] = m.group(1) return m.group(1) except OSError: pass raise RuntimeError( f"找不到 NEWAPI_KEY(请设 env,或确认 {_CRED_DOC} 含 sk- 开头的 key;" "内网阶段 key 进文档不进 env,见 AGENTS.md §9)" ) def load_framework() -> dict: """import 复用 tier2 worker 框架层,返回常用件句柄字典(config / client / middleware)。 在 import config(会触发 client 装代理旁路 + import agentscope/openai)前先 ensure_api_key_env, 保证后续 config.build_model_openai 内部 client.get_api_key() 拿得到 key。 """ ensure_api_key_env() # 跨包 import;sys.path 已在模块级兜底。代理旁路由 config import 时内部装好。 from worker import config, client, middleware # noqa: E402 return {"config": config, "client": client, "middleware": middleware} def build_cheap_model(max_tokens: int = 16000): """装一个便宜档 spike 用的 M3 模型(OpenAI 协议、base_url 自动补 /v1、record=True 记 token)。 复用 tier2 config.build_model_openai:它内部自动补 /v1、装代理旁路、取 key,与 Node m3.mjs 走同一 OpenAI 协议 /v1/chat/completions。 Args: max_tokens: 单轮最大输出 token(默认 16000,对 m3.mjs)。 Returns: RecordingOpenAIChatModel 实例(OpenAIChatModel 子类,带 .records 成本台账)。 """ fw = load_framework() return fw["config"].build_model_openai(SPIKE_MODEL, max_tokens=max_tokens, record=True)