精心设计语言模型提示
你可以通过字符串连接来构建语言模型提示,但是很难组合特征并确保你的提示保持在语言模型的上下文Windows内。为了克服这些限制,你可以使用@vscode/prompt-tsx图书馆。
该@vscode/prompt-tsx库提供了以下功能:
- 基于TSX的提示渲染:使用TSX组件编写提示,使它们更易读和维护
- 基于优先级的修剪:自动修剪提示中不太重要的部分,以适应模型的上下文Windows
- 灵活的代币管理:使用诸如
弹性增长,弹性储备,和弹性基准协作使用代币预算 - 工具集成:与 VS Code 的语言模型工具 API 集成
要查看所有功能的完整概述和详细使用说明,请参阅完整的README。
本文描述了使用该库进行提示设计的实际示例。这些示例的完整代码可以在prompt-tsx 仓库找到。
管理对话历史中的优先事项
在你的提示中包含对话历史记录是重要的,因为它使用户能够对以前的消息提出跟进问题。然而,你需要确保它的优先级得到适当处理,因为历史记录会随着时间的推移变得越来越大。我们发现最合理的模式通常是按以下顺序优先处理:
- 基本提示说明
- 当前用户查询
- 最近几轮聊天记录
- 任何支持数据
- 尽可能多的剩余历史
出于这个原因,在提示中将历史分为两部分,其中最近的提示轮次优先于一般上下文信息。
在这个库中,树中的每个TSX节点都有一个优先级,这个优先级在概念上类似于zIndex,其中数字越大表示优先级越高。
步骤 1: 定义 HistoryMessages 组件
要列出历史消息,请定义一个历史消息组件。这个示例是一个很好的起点,但如果你处理更复杂的数据类型,你可能需要扩展它。
这个例子使用了优先级列表助手组件,它会自动为每个子组件分配升序或降序优先级。
import {
UserMessage,
AssistantMessage,
PromptElement,
BasePromptElementProps,
PrioritizedList,
} from '@vscode/prompt-tsx';
import { ChatContext, ChatRequestTurn, ChatResponseTurn, ChatResponseMarkdownPart } from 'vscode';
interface IHistoryMessagesProps extends BasePromptElementProps {
history: ChatContext['history'];
}
export class HistoryMessages extends PromptElement<IHistoryMessagesProps> {
render(): PromptPiece {
const history: (UserMessage | AssistantMessage)[] = [];
for (const turn of this.props.history) {
if (turn instanceof ChatRequestTurn) {
history.push(<UserMessage>{turn.prompt}</UserMessage>);
} else if (turn instanceof ChatResponseTurn) {
history.push(
<AssistantMessage name={turn.participant}>
{chatResponseToMarkdown(turn)}
</AssistantMessage>
);
}
}
return (
<PrioritizedList priority={0} 降序={false}>
{历史记录}
</优先列表>
);
}
}
步骤 2:定义提示组件
接下来,定义一个我的提示组件包括基本指令、用户查询和历史消息及其适当的优先级。优先级值在兄弟组件中是局部的。请记住,在处理提示中的其他内容之前,您可能需要修剪历史记录中较旧的消息,因此需要将两个消息分开。<历史消息>元素:
import {
UserMessage,
PromptElement,
BasePromptElementProps,
} from '@vscode/prompt-tsx';
interface IMyPromptProps extends BasePromptElementProps {
history: ChatContext['history'];
userQuery: string;
}
export class MyPrompt extends PromptElement<IMyPromptProps> {
render() {
return (
<>
<UserMessage priority={100}>
Here are your base instructions. They have the highest priority because you want to make
sure they're always included!
</UserMessage>
{/* Older messages in the history have the lowest priority since they're less relevant */}
<HistoryMessages history={this.props.history.slice(0, -2)} priority={0} />
{/* The last 2 history messages are preferred over any workspace context you have below */}
<HistoryMessages history={this.props.history.slice(-2)} priority={80} />
{/* The user query is right behind the based instructions in priority */}
<UserMessage priority={90}>{this.props.userQuery}</UserMessage>
<UserMessage priority={70}>
With a slightly lower priority, you can include some contextual data about the workspace
or files here...
</UserMessage>
</>
);
}
}
现在,在库尝试修剪提示的其他元素之前,所有较旧的历史消息都会被修剪。
步骤 3: 定义历史组件
为了使消费变得更容易,定义一个历史将历史消息包装并使用组件通过优先级将属性用作一个透明容器。使用通过优先级其子元素在优先级排序时被视为包含元素的直接子元素。
导入 { PromptElement, BasePromptElementProps } 来自 '@vscode/prompt-tsx';
interface IHistoryProps extends BasePromptElementProps {
history: ChatContext['history'];
newer: number; // last 2 message priority values
older: number; // previous message priority values
passPriority: true; // require this prop be set!
}
export class History extends PromptElement<IHistoryProps> {
render(): PromptPiece {
return (
<>
<HistoryMessages history={this.props.history.slice(0, -2)} priority={this.props.older} />
<HistoryMessages history={this.属性.历史.切片(-2)} 优先级={this.属性.更新} />
</>
);
}
}
现在,您可以使用和重复使用这个单一元素来包含聊天历史记录:
<历史 历史={this.props.历史} 传递优先级 较旧={0} 较新={80}/>
扩展文件内容以适应
在这个例子中,你希望包括用户当前查看的所有文件的内容,并将其包含在他们的提示中。这些文件可能很大,以至于包含所有文件会导致其文本被修剪!这个例子展示了如何使用弹性增长属性以协作的方式调整文件内容以适应令牌预算。
步骤 1:定义基本指令和用户查询
首先,你定义一个用户消息包含基本说明的组件。
<用户消息 优先级={100}>这是你的基本说明。</用户消息>
然后,您通过使用用户消息组件。该组件具有高优先级,以确保它在基本说明之后立即包含。
<用户消息 优先级={90}>{this.props.用户查询}</用户消息>
步骤 2:包含文件内容
您现在可以使用以下内容包含文件内容文件上下文组件。您将其分配为弹性增长值1确保它在基本说明、用户查询和历史记录之后呈现。
<FileContext priority={70} flexGrow={1} files={this.props.files} />
随着一个弹性增长 值,元素在其 任何未使用 的代币预算中 提示词大小传递给它的对象渲染()和准备() 调用。您可以在 prompt-tsx 文档 中阅读有关 flex 元素的行为。
步骤 3:包含历史记录
接下来,使用以下内容包含历史消息历史你之前创建的组件。这有点棘手,因为你确实想显示一些历史记录,但同时也希望文件内容占据大部分提示。
因此,分配历史组件 a弹性增长值2确保它在所有其他元素之后渲染,包括<文件上下文 />但是,也设定一个弹性储备值"/5"为历史保留总预算的五分之一。
<历史
历史={this.props.历史}
优先级
更旧={0}
更新={80}
弹性增长={2}
弹性保留="/5"
/>
步骤 3: 将提示的所有元素结合起来
现在,将所有元素组合在一起我的提示组件。
import {
UserMessage,
PromptElement,
BasePromptElementProps,
} from '@vscode/prompt-tsx';
import { History } from './history';
interface IFilesToInclude {
document: TextDocument;
line: number;
}
interface IMyPromptProps extends BasePromptElementProps {
history: ChatContext['history'];
userQuery: string;
files: IFilesToInclude[];
}
export class MyPrompt extends PromptElement<IMyPromptProps> {
render() {
return (
<>
<UserMessage priority={100}>Here are your base instructions.</UserMessage>
<History
history={this.props.history}
passPriority
older={0}
newer={80}
flexGrow={2}
flexReserve="/5"
/>
<UserMessage priority={90}>{this.props.userQuery}</UserMessage>
<FileContext priority={70} flexGrow={1} files={this.props.files} />
</>
);
}
}
步骤 4:定义 FileContext 组件
最后,定义一个文件上下文包括用户当前正在查看的文件内容的组件。因为您使用了弹性增长,你可以通过使用该信息来实现获取每个文件中“有趣”行周围的尽可能多的行的逻辑提示词大小输入:.
为了简洁,省略了对获取展开的文件 被省略。您可以在 prompt-tsx 仓库 中查看。
导入 { PromptElement, BasePromptElementProps, PromptSizing, PromptPiece } 来自 '@vscode/prompt-tsx';
class FileContext extends PromptElement<{ files: IFilesToInclude[] } & BasePromptElementProps> {
async render(_state: void, sizing: PromptSizing): Promise<PromptPiece> {
const files = await this.getExpandedFiles(sizing);
return <>{files.map(f => f.toString())}</>;
}
私有 异步 获取展开文件(尺寸: 提示尺寸) {
// 实现细节在此总结。
// 请参考仓库以获取完整的实现。
}
}
总结
在这些示例中,您创建了一个我的提示组件包括基本指令、用户查询、历史消息和具有不同优先级的文件内容。你使用了弹性增长合作调整文件内容以适应令牌预算。
通过遵循此模式,您可以确保始终包含提示中最重要的部分,同时根据需要修剪不太重要的部分,以适应模型的上下文Windows。对于该完整实现的详细信息获取展开的文件方法和文件上下文跟踪器 类,参考 提示-tsx 仓库。