refactor: 静态变量提到对话外 + System Prompt 模板展开可视化
- 新建 SessionBar:会话变量独立于消息气泡,显示在对话顶部
- 重写 SystemPromptView:解析 {{var}} 占位符并内联展示模板→变量映射
- 重构 MessageList:提取 static_var 到 varMap,过滤后传入气泡
- 更新 SegmentRenderer + MessageBubble:传递 varMap 到 SystemPromptView
- 更新所有 Demo:static_var 从 user 消息迁移到 system 消息,使用真实会话配置(current_date、language、knowledge_cutoff)
- 更新导出逻辑:system 消息中收集 static_var 并在模板中展开 {{var}}
- 更新测试:新增模板展开用例,18 tests pass
This commit is contained in:
@@ -233,8 +233,62 @@ describe('exportToOpenAIFormat', () => {
|
||||
],
|
||||
}
|
||||
const { messages } = exportToOpenAIFormat(env)
|
||||
expect(messages[0].content).toContain('小明')
|
||||
expect(messages[0].content).toContain('says hello')
|
||||
// static_var 被提取到 system 消息中作为配置行
|
||||
expect(messages[0].role).toBe('system')
|
||||
expect(messages[0].content).toContain('user_name = 小明')
|
||||
// 用户消息中 static_var 展开为值
|
||||
expect(messages[1].role).toBe('user')
|
||||
expect(messages[1].content).toContain('小明')
|
||||
expect(messages[1].content).toContain('says hello')
|
||||
})
|
||||
|
||||
it('expands static_vars in system_prompt template via {{var}} substitution', () => {
|
||||
const env: PromptEnvelope = {
|
||||
version: '1.0',
|
||||
model: 'gpt-4-turbo',
|
||||
messages: [
|
||||
{
|
||||
id: '0',
|
||||
role: 'system',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'current_date',
|
||||
value: '2026年6月7日',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'language',
|
||||
value: '中文',
|
||||
},
|
||||
{
|
||||
kind: 'system_prompt',
|
||||
content: '今天是 {{current_date}}。请用 {{language}} 回复。',
|
||||
collapsed: true,
|
||||
},
|
||||
],
|
||||
timestamp: 0,
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
role: 'user',
|
||||
segments: [{ kind: 'text', content: '你好' }],
|
||||
timestamp: 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
const result = exportToOpenAIFormat(env)
|
||||
expect(result.messages).toHaveLength(2)
|
||||
const sysMsg = result.messages[0]
|
||||
expect(sysMsg.role).toBe('system')
|
||||
// {{var}} 模板应被展开为实际值
|
||||
expect(sysMsg.content).toContain('current_date = 2026年6月7日')
|
||||
expect(sysMsg.content).toContain('language = 中文')
|
||||
expect(sysMsg.content).toContain('今天是 2026年6月7日。')
|
||||
expect(sysMsg.content).toContain('请用 中文 回复。')
|
||||
// 不应残留原始模板占位符
|
||||
expect(sysMsg.content).not.toContain('{{current_date}}')
|
||||
expect(sysMsg.content).not.toContain('{{language}}')
|
||||
})
|
||||
|
||||
it('emits tool_call_request as assistant message with tool_calls', () => {
|
||||
|
||||
@@ -10,9 +10,11 @@ const roleConfig = {
|
||||
|
||||
interface MessageBubbleProps {
|
||||
message: Message
|
||||
/** 会话级变量映射表,用于 system_prompt 中的 {{var}} 模板展开展示 */
|
||||
varMap?: Record<string, string>
|
||||
}
|
||||
|
||||
export default function MessageBubble({ message }: MessageBubbleProps) {
|
||||
export default function MessageBubble({ message, varMap = {} }: MessageBubbleProps) {
|
||||
const cfg = roleConfig[message.role]
|
||||
const Icon = cfg.icon
|
||||
const isUser = message.role === 'user'
|
||||
@@ -33,7 +35,7 @@ export default function MessageBubble({ message }: MessageBubbleProps) {
|
||||
{/* Segments */}
|
||||
<div className="space-y-0.5">
|
||||
{message.segments.map((seg, i) => (
|
||||
<SegmentRenderer key={i} segment={seg} />
|
||||
<SegmentRenderer key={i} segment={seg} varMap={varMap} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,12 +1,53 @@
|
||||
import type { Message } from '../types/protocol'
|
||||
import { useMemo } from 'react'
|
||||
import type { Message, StaticVarSegment } from '../types/protocol'
|
||||
import SessionBar from './SessionBar'
|
||||
import MessageBubble from './MessageBubble'
|
||||
|
||||
interface MessageListProps {
|
||||
messages: Message[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 从所有 system 消息中提取 static_var 片段,
|
||||
* 构建会话变量映射表并从消息体中移除这些片段。
|
||||
*/
|
||||
function extractSessionVars(messages: Message[]): {
|
||||
variables: StaticVarSegment[]
|
||||
varMap: Record<string, string>
|
||||
cleanedMessages: Message[]
|
||||
} {
|
||||
const variables: StaticVarSegment[] = []
|
||||
const varMap: Record<string, string> = {}
|
||||
|
||||
const cleanedMessages = messages.map((msg) => {
|
||||
const staticVars: StaticVarSegment[] = []
|
||||
const remaining = msg.segments.filter((seg) => {
|
||||
if (seg.kind === 'static_var') {
|
||||
staticVars.push(seg)
|
||||
return false // 从消息体中移除
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// 收集变量
|
||||
for (const v of staticVars) {
|
||||
variables.push(v)
|
||||
varMap[v.name] = v.value
|
||||
}
|
||||
|
||||
return { ...msg, segments: remaining }
|
||||
})
|
||||
|
||||
return { variables, varMap, cleanedMessages }
|
||||
}
|
||||
|
||||
export default function MessageList({ messages }: MessageListProps) {
|
||||
if (messages.length === 0) {
|
||||
const { variables, varMap, cleanedMessages } = useMemo(
|
||||
() => extractSessionVars(messages),
|
||||
[messages]
|
||||
)
|
||||
|
||||
if (cleanedMessages.length === 0) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center text-gray-300 text-sm">
|
||||
选择一个 Demo 场景开始
|
||||
@@ -15,10 +56,16 @@ export default function MessageList({ messages }: MessageListProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
{/* 会话变量横栏 —— 在对话气泡之外 */}
|
||||
<SessionBar variables={variables} />
|
||||
|
||||
{/* 消息列表 */}
|
||||
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-1">
|
||||
{messages.map((msg) => (
|
||||
<MessageBubble key={msg.id} message={msg} />
|
||||
{cleanedMessages.map((msg) => (
|
||||
<MessageBubble key={msg.id} message={msg} varMap={varMap} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Segment } from '../types/protocol'
|
||||
import TextSegmentView from './segments/TextSegmentView'
|
||||
import StaticVarBadge from './segments/StaticVarBadge'
|
||||
import SystemPromptView from './segments/SystemPromptView'
|
||||
import MemoryView from './segments/MemoryView'
|
||||
import SkillsView from './segments/SkillsView'
|
||||
@@ -13,19 +12,24 @@ import MediaView from './segments/MediaView'
|
||||
|
||||
interface SegmentRendererProps {
|
||||
segment: Segment
|
||||
/** 会话级变量映射表,传递给 SystemPromptView 用于展示 {{var}} 模板渲染结果 */
|
||||
varMap?: Record<string, string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Route a Segment to its correct view component based on `kind`.
|
||||
* static_var 不在此处渲染 —— 已被 MessageList 提取到 SessionBar 中。
|
||||
*/
|
||||
export default function SegmentRenderer({ segment }: SegmentRendererProps) {
|
||||
export default function SegmentRenderer({ segment, varMap = {} }: SegmentRendererProps) {
|
||||
switch (segment.kind) {
|
||||
case 'text':
|
||||
return <TextSegmentView segment={segment} />
|
||||
case 'static_var':
|
||||
return <StaticVarBadge segment={segment} />
|
||||
// static_var 已由 MessageList 提取到对话外部的 SessionBar 中,
|
||||
// 不作为气泡内元素渲染。若此处仍有残留,静默跳过。
|
||||
return null
|
||||
case 'system_prompt':
|
||||
return <SystemPromptView segment={segment} />
|
||||
return <SystemPromptView segment={segment} varMap={varMap} />
|
||||
case 'memory':
|
||||
return <MemoryView segment={segment} />
|
||||
case 'skills':
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { StaticVarSegment } from '../types/protocol'
|
||||
import { Sliders } from 'lucide-react'
|
||||
|
||||
interface SessionBarProps {
|
||||
variables: StaticVarSegment[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话区顶部的会话变量横栏 —— 会话级别的配置变量,独立于任何消息。
|
||||
* 这些变量在对话开始时被注入到 System Prompt 模板中。
|
||||
*/
|
||||
export default function SessionBar({ variables }: SessionBarProps) {
|
||||
if (variables.length === 0) return null
|
||||
|
||||
return (
|
||||
<div className="shrink-0 border-b border-gray-200 bg-gradient-to-r from-blue-50/60 to-white px-4 py-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* 标题 */}
|
||||
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-blue-400 uppercase tracking-wider mr-1">
|
||||
<Sliders size={12} />
|
||||
会话变量
|
||||
</span>
|
||||
|
||||
{/* 变量列表 */}
|
||||
{variables.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-white border border-blue-200 text-[11px] font-mono shadow-sm"
|
||||
title={v.description}
|
||||
>
|
||||
<span className="text-blue-400">{'{{'}{v.name}{'}}'}</span>
|
||||
<span className="text-gray-300">→</span>
|
||||
<span className="text-blue-700 font-medium">{v.value}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,19 @@
|
||||
import type { StaticVarSegment } from '../../types/protocol'
|
||||
import { Variable } from 'lucide-react'
|
||||
import { Sliders } from 'lucide-react'
|
||||
|
||||
/**
|
||||
* 会话级静态变量 —— 这些变量在对话开始时被注入到 System Prompt 模板中展开。
|
||||
* 例如:{{current_date}} → 2026年6月7日,{{language}} → 中文
|
||||
*
|
||||
* 视觉上呈现为紧凑的配置项卡片,一行展示所有会话级变量。
|
||||
*/
|
||||
export default function StaticVarBadge({ segment }: { segment: StaticVarSegment }) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full bg-blue-100 text-blue-700 text-xs font-mono border border-blue-200">
|
||||
<Variable size={12} />
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-blue-50 border border-blue-200 text-[11px] font-mono">
|
||||
<Sliders size={11} className="text-blue-400 shrink-0" />
|
||||
<span className="text-blue-400">{'{{'}{segment.name}{'}}'}</span>
|
||||
<span className="text-blue-300">→</span>
|
||||
<span className="font-semibold">{segment.value}</span>
|
||||
<span className="text-blue-300 mx-0.5">=</span>
|
||||
<span className="text-blue-700 font-medium">{segment.value}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,55 @@
|
||||
import type { SystemPromptSegment } from '../../types/protocol'
|
||||
import type { SystemPromptSegment, StaticVarSegment } from '../../types/protocol'
|
||||
import CollapsiblePanel from '../CollapsiblePanel'
|
||||
import { Bot } from 'lucide-react'
|
||||
|
||||
export default function SystemPromptView({ segment }: { segment: SystemPromptSegment }) {
|
||||
interface SystemPromptViewProps {
|
||||
segment: SystemPromptSegment
|
||||
varMap?: Record<string, string> // 来自会话变量的 name→value 映射
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个带 {{var}} 模板占位符的字符串解析为混合内容片段。
|
||||
* 普通文本渲染为纯文本,{{var}} 渲染为带解析值的内联标签。
|
||||
*/
|
||||
function renderTemplate(content: string, varMap: Record<string, string>) {
|
||||
// 按 {{...}} 分割,捕获分隔符
|
||||
const parts = content.split(/(\{\{[^}]+\}\})/g)
|
||||
|
||||
return parts.map((part, i) => {
|
||||
const match = part.match(/^\{\{([^}]+)\}\}$/)
|
||||
if (!match) {
|
||||
// 普通文本
|
||||
return <span key={i}>{part}</span>
|
||||
}
|
||||
|
||||
const varName = match[1].trim()
|
||||
const resolved = varMap[varName]
|
||||
|
||||
return (
|
||||
<span
|
||||
key={i}
|
||||
className="inline-flex items-baseline gap-0.5 px-1 rounded bg-blue-100/70 border border-blue-200 text-[11px] font-mono align-baseline"
|
||||
title={resolved ? `模板变量 {{${varName}}} 已展开为 "${resolved}"` : `模板变量 {{${varName}}} —— 未找到对应值`}
|
||||
>
|
||||
<span className="text-blue-400">{'{{'}{varName}{'}}'}</span>
|
||||
{resolved && (
|
||||
<>
|
||||
<span className="text-blue-300 text-[9px]">→</span>
|
||||
<span className="text-blue-700 font-semibold">{resolved}</span>
|
||||
</>
|
||||
)}
|
||||
{!resolved && (
|
||||
<span className="text-red-400 text-[9px] italic">未解析</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default function SystemPromptView({ segment, varMap = {} }: SystemPromptViewProps) {
|
||||
const lineCount = segment.content.split('\n').length
|
||||
const hasVars = /\{\{[^}]+\}\}/.test(segment.content)
|
||||
|
||||
return (
|
||||
<CollapsiblePanel
|
||||
title="System Prompt"
|
||||
@@ -11,10 +57,10 @@ export default function SystemPromptView({ segment }: { segment: SystemPromptSeg
|
||||
color="border-gray-400 text-gray-600"
|
||||
bgColor="bg-gray-50"
|
||||
defaultCollapsed={segment.collapsed}
|
||||
badge={`${lineCount} 行`}
|
||||
badge={`${lineCount} 行${hasVars ? ' · 模板' : ''}`}
|
||||
>
|
||||
<pre className="text-xs text-gray-600 whitespace-pre-wrap font-mono leading-relaxed max-h-48 overflow-y-auto">
|
||||
{segment.content}
|
||||
{renderTemplate(segment.content, varMap)}
|
||||
</pre>
|
||||
</CollapsiblePanel>
|
||||
)
|
||||
|
||||
+106
-22
@@ -20,9 +20,31 @@ const demoA: PromptEnvelope = {
|
||||
id: 'a-1',
|
||||
role: 'system',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'current_date',
|
||||
value: '2026年6月7日',
|
||||
description: '当前对话日期,注入到 System Prompt 模板中',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'language',
|
||||
value: '中文(简体)',
|
||||
description: '模型回复的首选语言',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'user_name',
|
||||
value: '小明',
|
||||
description: '当前用户名称',
|
||||
},
|
||||
{
|
||||
kind: 'system_prompt',
|
||||
content: `你是 HCI 课程设计助手。你帮助学生对聊天界面的信息架构进行批判性思考。
|
||||
content: `当前日期:{{current_date}}
|
||||
用户名称:{{user_name}}
|
||||
回复语言:{{language}}
|
||||
|
||||
你是 HCI 课程设计助手。你帮助学生对聊天界面的信息架构进行批判性思考。
|
||||
回答应简洁、有结构,鼓励学生从用户体验角度分析问题。
|
||||
如果学生对某个概念不清楚,用通俗的例子解释,不要用术语堆砌。`,
|
||||
collapsed: true,
|
||||
@@ -86,11 +108,6 @@ const demoA: PromptEnvelope = {
|
||||
id: 'a-2',
|
||||
role: 'user',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'user_name',
|
||||
value: '小明',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
content: '你好,我想讨论一下我设计的聊天协议方案。你觉得 9 种 prompt 类型的分类合理吗?',
|
||||
@@ -124,10 +141,23 @@ const demoB: PromptEnvelope = {
|
||||
id: 'b-1',
|
||||
role: 'system',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'current_date',
|
||||
value: '2026年6月7日',
|
||||
description: '当前日期,SQL 查询中用于计算相对日期',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'knowledge_cutoff',
|
||||
value: '2026年1月',
|
||||
description: '模型训练数据的截止时间',
|
||||
},
|
||||
{
|
||||
kind: 'system_prompt',
|
||||
content:
|
||||
'你是一个数据分析助手,可以使用 Python 工具进行数据查询和可视化。',
|
||||
content: `当前日期:{{current_date}}。知识截止:{{knowledge_cutoff}}。
|
||||
|
||||
你是一个数据分析助手,可以使用 Python 工具进行数据查询和可视化。`,
|
||||
collapsed: true,
|
||||
},
|
||||
{
|
||||
@@ -298,10 +328,23 @@ const demoC: PromptEnvelope = {
|
||||
id: 'c-1',
|
||||
role: 'system',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'current_date',
|
||||
value: '2026年6月7日',
|
||||
description: '当前日期',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'language',
|
||||
value: '中文(简体)',
|
||||
description: '文档审阅的默认输出语言',
|
||||
},
|
||||
{
|
||||
kind: 'system_prompt',
|
||||
content:
|
||||
'你是文档审阅助手。帮助用户分析长文档、提取要点、回答关于文档内容的问题。',
|
||||
content: `当前日期:{{current_date}},回复语言:{{language}}。
|
||||
|
||||
你是文档审阅助手。帮助用户分析长文档、提取要点、回答关于文档内容的问题。`,
|
||||
collapsed: true,
|
||||
},
|
||||
{
|
||||
@@ -396,9 +439,38 @@ const demoD: PromptEnvelope = {
|
||||
id: 'd-1',
|
||||
role: 'system',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'current_date',
|
||||
value: '2026年6月7日',
|
||||
description: '当前对话日期',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'language',
|
||||
value: '中文(简体)',
|
||||
description: '模型回复的首选语言',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'knowledge_cutoff',
|
||||
value: '2026年1月',
|
||||
description: '模型训练数据截止日期',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'user_name',
|
||||
value: '小明',
|
||||
description: '当前用户名称',
|
||||
},
|
||||
{
|
||||
kind: 'system_prompt',
|
||||
content: `你是 Claude,一个 HCI 研究助手。你的角色是帮助学生批判性地思考聊天界面的设计问题。
|
||||
content: `当前日期:{{current_date}}
|
||||
用户:{{user_name}}
|
||||
回复语言:{{language}}
|
||||
知识截止:{{knowledge_cutoff}}
|
||||
|
||||
你是 Claude,一个 HCI 研究助手。你的角色是帮助学生批判性地思考聊天界面的设计问题。
|
||||
|
||||
核心原则:
|
||||
- 鼓励从用户体验角度分析,而非技术实现角度
|
||||
@@ -538,11 +610,6 @@ const demoD: PromptEnvelope = {
|
||||
id: 'd-2',
|
||||
role: 'user',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'user_name',
|
||||
value: '小明',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
content: '你好!我在准备课程设计的文献综述部分。我找到了一篇相关的研究报告,帮我分析一下它是否可以支持我的论点。',
|
||||
@@ -665,9 +732,31 @@ const demoE: PromptEnvelope = {
|
||||
id: 'e-1',
|
||||
role: 'system',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'current_date',
|
||||
value: '2026年6月7日',
|
||||
description: '当前对话日期',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'language',
|
||||
value: '中文(简体)',
|
||||
description: '模型回复的首选语言',
|
||||
},
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'knowledge_cutoff',
|
||||
value: '2026年1月',
|
||||
description: '模型训练数据截止日期',
|
||||
},
|
||||
{
|
||||
kind: 'system_prompt',
|
||||
content: `你是 HCI 课程设计助手,具备 Anthropic Skills 机制。
|
||||
content: `当前日期:{{current_date}}
|
||||
回复语言:{{language}}
|
||||
知识截止:{{knowledge_cutoff}}
|
||||
|
||||
你是 HCI 课程设计助手,具备 Anthropic Skills 机制。
|
||||
|
||||
你有以下 skills 可用。用户输入以 / 开头的命令时会直接触发对应 skill。你也可以在分析用户意图后,主动建议合适的 skill。
|
||||
|
||||
@@ -764,11 +853,6 @@ const demoE: PromptEnvelope = {
|
||||
id: 'e-2',
|
||||
role: 'user',
|
||||
segments: [
|
||||
{
|
||||
kind: 'static_var',
|
||||
name: 'user_name',
|
||||
value: '小明',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
content: '我想深入了解 Anthropic Skills 的渐进式披露机制(Progressive Disclosure),作为我的 HCI 课程论文的案例研究对象。请帮我调研一下这个机制的设计原理、交互模式和学术界相关讨论。',
|
||||
|
||||
@@ -38,10 +38,16 @@ export interface TextSegment {
|
||||
content: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话级静态变量 —— 在对话开始时注入到 System Prompt 模板中展开。
|
||||
* 例如 {{current_date}} 在模板中展开为 "2026年6月7日"。
|
||||
* 这些变量对用户可见,解释了模型"看到"的上下文配置。
|
||||
*/
|
||||
export interface StaticVarSegment {
|
||||
kind: 'static_var'
|
||||
name: string // e.g. "user_name"
|
||||
value: string // e.g. "张三"
|
||||
name: string // 模板变量名,e.g. "current_date"
|
||||
value: string // 展开后的值,e.g. "2026年6月7日"
|
||||
description?: string // 简短说明该变量的用途
|
||||
}
|
||||
|
||||
export interface SystemPromptSegment {
|
||||
|
||||
+24
-2
@@ -85,6 +85,8 @@ export function segmentToText(seg: Segment): string | null {
|
||||
/** Render a structural segment into text for the system message */
|
||||
function formatStructural(seg: Segment): string | null {
|
||||
switch (seg.kind) {
|
||||
case 'static_var':
|
||||
return `${seg.name} = ${seg.value}`
|
||||
case 'system_prompt':
|
||||
return `[System Prompt]\n${seg.content}`
|
||||
case 'memory':
|
||||
@@ -148,14 +150,34 @@ export function exportToOpenAIFormat(envelope: PromptEnvelope): OpenAIExport {
|
||||
for (const msg of envelope.messages) {
|
||||
// ── System ──
|
||||
if (msg.role === 'system') {
|
||||
// 第一遍:收集 static_var 用于模板展开
|
||||
const varMap: Record<string, string> = {}
|
||||
for (const seg of msg.segments) {
|
||||
if (seg.kind === 'static_var') {
|
||||
varMap[seg.name] = seg.value
|
||||
}
|
||||
}
|
||||
|
||||
const contentParts: string[] = []
|
||||
for (const seg of msg.segments) {
|
||||
if (seg.kind === 'tool_overview') {
|
||||
// tool_overview → top-level tools array
|
||||
seg.items.forEach(item => tools.push(toolItemToOpenAI(item)))
|
||||
// Also add a text summary to the system message
|
||||
const summary = formatStructural(seg)
|
||||
if (summary) contentParts.push(summary)
|
||||
} else if (seg.kind === 'static_var') {
|
||||
// 静态变量:导出为配置行
|
||||
const s = formatStructural(seg)
|
||||
if (s) contentParts.push(s)
|
||||
} else if (seg.kind === 'system_prompt') {
|
||||
// 展开模板中的 {{var}} 占位符
|
||||
let expanded = seg.content
|
||||
for (const [name, value] of Object.entries(varMap)) {
|
||||
expanded = expanded.replace(
|
||||
new RegExp(`\\{\\{${name}\\}\\}`, 'g'),
|
||||
value
|
||||
)
|
||||
}
|
||||
contentParts.push(expanded)
|
||||
} else {
|
||||
const t = segmentToText(seg)
|
||||
if (t !== null) contentParts.push(t)
|
||||
|
||||
Reference in New Issue
Block a user