Skip to content

partyfly/st-prompt-engine

Repository files navigation

MVU Exec Engine

独立的后端 TypeScript 库,用于:

  • 动态提示词编排(预设/preset、人物卡/persona、世界书/worldbook、历史消息),构建可执行的 LLM messages。
  • 按 MVU 思路解析并应用 区块,程序化变量更新,输出 stat/display/delta 三视图并生成基础 Schema 进行约束。
  • 兼容 SillyTavern(ST)素材:支持 ST preset/persona/regex 的适配与 prompt order;支持 ST 风格的正则脚本门控(placement/markdownOnly/promptOnly/depth 等)。
  • 提供 Provider(OpenAI/Anthropic/Gemini/Echo)、持久化(示例内存)、事件回调等扩展点,易于集成到任意应用。

环境

  • Node >= 22.18.0(ESM)

安装

  • 在项目根目录:npm i,然后 npm run build
  • 作为库使用:发布到私有/公共仓库后,在业务项目中 import { Engine, ... } from 'mvu-exec-engine'

快速上手

import { Engine, InMemoryStore, EchoProvider } from 'mvu-exec-engine';

const engine = new Engine({ provider: new EchoProvider(), store: new InMemoryStore(), sessionId: 'demo' });

await engine.loadMaterials({
  preset: { system: '你是严谨可靠的助手。所有变量更新必须在 <UpdateVariable> 中按格式输出。' },
  persona: { name: '理', define: '圣女,温柔端庄。' },
  worldbook: [],
  initialVariables: {
    日期: ["03月15日", "mm月dd日"],
    : { 好感度: [0, "[-30,100]之间"] },
    $meta: { strictSet: true }
  }
});

const { messages } = await engine.compose({ history: [{ role: 'user', content: '打个招呼。' }] });
const reply = await engine.generate(messages);        // 内部会做 generate 阶段预处理
const rendered = await engine.postprocessReply(reply.content); // 渲染阶段后处理(模板/注入/正则)
const result = await engine.applyVariablesFromReply(rendered); // 解析并应用 <UpdateVariable>

核心能力

  • Prompt 组合:从 preset/persona/worldbook 生成会话 messages;支持 ST prompt order、按位/按目标/按正则的 @INJECT、按条目就近贴片 [GENERATE:{idx}:BEFORE|AFTER]
  • 模板运行时:使用 StaticTemplateRuntime(纯字符串模板,仅支持 mustache 风格占位符与简单宏,不执行任何脚本/EJS)。SafeFunctionTemplateRuntime/VMTemplateRuntime 只是对 StaticTemplateRuntime 的兼容别名。模板环境可访问 variables/display/delta/persona/preset/globals/getVar 等。
  • 正则规则:在 generate/render 两阶段对 messages 或最终 reply 进行按序替换;兼容 ST 脚本的门控字段(markdownOnly/promptOnly/stPlacement/minDepth/maxDepth/runOnEdit 等)。
  • 变量引擎:解析并执行 _.set/add/assign/insert/remove|unset|delete,维护 stat/display/delta;自动从初始变量生成 Schema,支持 $meta.extensible=false 拒绝越权写入。
  • Provider:内置 Echo/OpenAI/Anthropic/Gemini,支持代理(ALL_PROXY/HTTPS_PROXY/HTTP_PROXY/SOCKS 等)与简单用量估算。

示例与命令

  • 运行前先 npm run build
  • 快速演示(EchoProvider):npm run demo
  • 基于 ST 素材的演示:
    • 预览+生成:npm run demo:best
    • 交互式 CLI:npm run demo:best:chat
      • 环境变量:
        • PROVIDER=gemini|openai|anthropic(也可通过是否设置 GEMINI_API_KEY 自动选择)
        • USER_TITLE=豪哥(注入到 globals 中用于模板替换)
        • 代理:ALL_PROXY/HTTPS_PROXY/HTTP_PROXY/SOCKS_PROXYREQUEST_PROXY_URL+REQUEST_PROXY_ENABLED=1
      • 运行后可选择 persona 的 first_mes/alternate_greetings,输入消息对话,/exit 退出。
    • Web Demo:npm run demo:web 然后访问 http://localhost:3000
    • v0.2 API 演示:npm run demo:v02

v0.2 新增/常用 API

  • 流式辅助:
    • engine.generateStream(messages, { postprocess?, emitVariableState? }):真正流式(SSE/Chunk)。事件:
      • { type: 'content', delta, content? } 文本增量;当 postprocess: true 时,content 为累计渲染结果。
      • { type: 'usage', data } 用量快照(inputTokens/outputTokens/model)。
      • { type: 'variable_state', data } 可选,尾部根据最终文本解析变量并下发状态。
      • { type: 'final', text } 末尾补齐全文本。
    • engine.postprocessStream(asyncIterable):对任意文本增量应用渲染规则,输出 { type:'content', delta, content }
  • Prompt 序列选择(ST 对齐):
    • engine.listPromptOrders()engine.selectPromptOrder('last'|'global'|{ characterId })
  • 全局与覆盖:
    • engine.setUserGlobals({ user, user_title })engine.setUserOverrides({ ... })
  • 调试与导出:
    • engine.getUsage()engine.exportConversation({ format: 'json'|'object' })
  • 正则规则:
    • engine.listRegexRules()engine.validateRegex(rules)engine.updateRegexRules(rules)
  • 事件:engine.on('compose:start'|'generate:end'|'postprocess:end'|'promptOrder:changed', handler)

SillyTavern 适配

  • adaptStPresetToMaterials(stPreset[, { preferCharacterId }]):从 ST preset 生成 preset/worldbook/modelParams/meta(含 prompt order)。
  • adaptStPersonaToMaterials(stPersona):从 ST 人物卡生成 persona/worldbook/regex(含 ST world/entries)。
  • adaptStRegexToRules(scripts):将 ST regex 脚本转为 materials.regex 规则数组(保留门控字段)。

Provider 与代理配置

  • 选择 Provider:
    • PROVIDER=gemini|openai|anthropic|claude(best_chat_cli 中使用);若检测到 GEMINI_API_KEY 会优先选 Gemini。
  • OpenAI:OPENAI_API_KEYOPENAI_BASE_URL(可选)、OPENAI_MODEL(默认 gpt-4o-mini
  • Anthropic:ANTHROPIC_API_KEYANTHROPIC_BASE_URL(可选)、ANTHROPIC_MODEL
  • Gemini:GEMINI_API_KEYGEMINI_MODELGEMINI_RESPONSE_MIME(可选)、GEMINI_USE_HEADER=1(走 header 鉴权,否则 query key)
  • 代理:
    • 通用:ALL_PROXY/HTTPS_PROXY/HTTP_PROXY/SOCKS_PROXY(支持 socks5h/socks5),或设置 REQUEST_PROXY_URL+REQUEST_PROXY_ENABLED=1
    • 跳过域名:NO_PROXY/no_proxy
  • 调试:DEBUG_PROXY=1 打印代理相关信息

Cloudflare Worker 快速示例(流式)

// worker.ts
import { Engine, InMemoryStore, OpenAIProvider } from 'mvu-exec-engine';

export default {
  async fetch(req: Request, env: any) {
    const url = new URL(req.url);
    if (url.pathname === '/chat') {
      const provider = new OpenAIProvider({ apiKey: env.OPENAI_API_KEY, model: 'gpt-4o-mini' });
      const engine = new Engine({ provider, store: new InMemoryStore() }); // 默认 StaticTemplateRuntime
      await engine.loadMaterials({
        preset: { system: '你是严谨的助手。' },
        persona: { name: '理', define: '温柔端庄。' },
        worldbook: [],
      });
      const { messages } = await engine.compose({ history: [{ role: 'user', content: '打个招呼' }] });

      const { readable, writable } = new TransformStream();
      const writer = writable.getWriter();
      (async () => {
        const enc = new TextEncoder();
        for await (const ev of engine.generateStream(messages, { postprocess: true, emitVariableState: true })) {
          if (ev.type === 'content') await writer.write(enc.encode(ev.delta));
          if (ev.type === 'final') await writer.write(enc.encode('\n'));
        }
        await writer.close();
      })();
      return new Response(readable, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
    }
    return new Response('ok');
  }
}

更多文档

  • 使用说明(推荐阅读):docs/USAGE_CN.md
  • 模板与实施规范:docs/TEMPLATE_PROMPT_CONVERSION.md
  • UI 集成规范:docs/UIDEV_SPEC.md
  • Cloudflare 部署指南:docs/DEPLOY_CLOUDFLARE.md

注意

  • 本库不再支持 <% %> EJS 脚本;如模板中出现将抛出 E_TEMPLATE_SCRIPT_NOT_SUPPORTEDstripCode 仍用于兼容 ST 模板中的残留标记,但不会执行脚本。
  • Schema 来自初始变量的结构推断,$meta.extensible=false 会阻止对象/数组新增项;被拒绝的命令将写入 VariableData.errors
  • engines 要求 Node >= 22.18.0。

切换模板运行时(可选)

  • 默认:StaticTemplateRuntime(纯字符串模板,不执行脚本)
  • 兼容别名:SafeFunctionTemplateRuntime / VMTemplateRuntime 实际都复用 StaticTemplateRuntime,不再提供 node:vm/new Function 执行环境。
    import { Engine, StaticTemplateRuntime } from 'mvu-exec-engine';
    const engine = new Engine({ provider, store, runtime: new StaticTemplateRuntime() });

流式接口说明(摘要)

  • 事件模型:
    • { type: 'content', delta, content? } 文本增量;当 postprocess: true 时,content 为累计渲染结果(适合直接显示)。
    • { type: 'usage', data } 用量快照(inputTokens/outputTokens/model)。
    • { type: 'variable_state', data } 可选;尾部将最终文本解析后下发变量更新结果与最新变量树。
    • { type: 'final', text } 最终文本收束。
  • 两种使用方式:
    • 推荐:engine.generateStream(messages, { postprocess: true, emitVariableState: true }),获取“已渲染”的增量,末尾携带变量状态。
    • 自定义:若要对“原始增量”自行处理,可不传 postprocess,并用 engine.postprocessStream(rawChunks) 处理。
  • 终止:传入 { signal: AbortController.signal } 到 Provider 的 sendStream(内置 Provider 已支持)。

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors