制作语言模型提示
你可以通过使用字符串连接来构建语言模型提示,但很难组合功能并确保你的提示保持在语言模型的上下文窗口内。为了克服这些限制,你可以使用@vscode/prompt-tsx
库。
@vscode/prompt-tsx
库提供以下功能:
- 基于TSX的提示渲染: 使用TSX组件编写提示,使其更具可读性和可维护性
- 基于优先级的剪枝: 自动剪枝提示中不太重要的部分,以适应模型的上下文窗口
- 灵活的令牌管理: 使用诸如
flexGrow
,flexReserve
, 和flexBasis
等属性来协作使用令牌预算 - 工具集成: 与VS Code的语言模型工具API集成
有关所有功能的完整概述和详细使用说明,请参阅完整README。
本文介绍了使用该库进行提示设计的实际示例。这些示例的完整代码可以在prompt-tsx 仓库中找到。
管理对话历史中的优先级
在提示中包含对话历史记录很重要,因为它使用户能够对先前的消息提出后续问题。然而,您需要确保其优先级得到适当处理,因为历史记录会随着时间的推移而变得庞大。我们发现,最有意义的模式通常是按以下顺序优先处理:
- 基础提示指令
- 当前用户查询
- 最近几次的聊天记录
- 任何支持数据
- 尽可能多的剩余历史记录
因此,在提示中将历史记录分为两部分,其中最近的提示轮次优先于一般的上下文信息。
在这个库中,树中的每个TSX节点都有一个优先级,这个概念类似于zIndex,其中数字越大表示优先级越高。
步骤 1: 定义 HistoryMessages 组件
要列出历史消息,定义一个HistoryMessages
组件。这个例子提供了一个很好的起点,但如果你处理更复杂的数据类型,可能需要扩展它。
此示例使用了PrioritizedList
辅助组件,它会自动为其每个子项分配升序或降序的优先级。
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} descending={false}>
{history}
</PrioritizedList>
);
}
}
步骤2:定义提示组件
接下来,定义一个MyPrompt
组件,该组件包括基本指令、用户查询和历史消息及其适当的优先级。优先级值在兄弟节点之间是局部的。请记住,在提示中处理其他内容之前,您可能希望修剪历史记录中的旧消息,因此您需要拆分两个
元素:
import {
SystemMessage,
UserMessage,
PromptElement,
BasePromptElementProps,
} from '@vscode/prompt-tsx';
interface IMyPromptProps extends BasePromptElementProps {
history: ChatContext['history'];
userQuery: string;
}
export class MyPrompt extends PromptElement<IMyPromptProps> {
render() {
return (
<>
<SystemMessage priority={100}>
Here are your base instructions. They have the highest priority because you want to make
sure they're always included!
</SystemMessage>
{/* 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 system message 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:定义历史组件
为了使消费稍微容易一些,定义一个History
组件,它包装历史消息并使用passPriority
属性作为传递容器。使用passPriority
时,其子元素被视为包含元素的直接子元素,以便进行优先级排序。
import { PromptElement, BasePromptElementProps } from '@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.props.history.slice(-2)} priority={this.props.newer} />
</>
);
}
}
现在,您可以使用并重复使用这个单一元素来包含聊天历史记录:
<History history={this.props.history} passPriority older={0} newer={80}/>
扩展文件内容以适应
在这个例子中,您希望将用户当前查看的所有文件的内容包含在他们的提示中。这些文件可能很大,以至于包含所有文件会导致它们的文本被修剪!这个例子展示了如何使用flexGrow
属性来协作调整文件内容的大小,以适应令牌预算。
步骤1:定义基本指令和用户查询
首先,你定义一个SystemMessage
组件,其中包含基本指令。该组件具有最高优先级,以确保它始终被包含。
<SystemMessage priority={100}>Here are your base instructions.</SystemMessage>
然后,您可以通过使用UserMessage
组件来包含用户查询。该组件具有高优先级,以确保它紧跟在基本指令之后被包含。
<UserMessage priority={90}>{this.props.userQuery}</UserMessage>
步骤2:包含文件内容
你现在可以通过使用FileContext
组件来包含文件内容。你给它分配一个flexGrow
值为1
,以确保它在基本指令、用户查询和历史之后渲染。
<FileContext priority={70} flexGrow={1} files={this.props.files} />
使用flexGrow
值,元素可以获取其PromptSizing
对象中任何未使用的令牌预算,该对象被传递到其render()
和prepare()
调用中。您可以在prompt-tsx文档中阅读更多关于flex元素行为的信息。
步骤3:包含历史记录
接下来,使用你之前创建的History
组件来包含历史消息。这有点棘手,因为你确实希望显示一些历史记录,但也希望文件内容占据提示的大部分。
因此,为History
组件分配一个flexGrow
值为2
,以确保它在所有其他元素之后渲染,包括
。但是,还要设置一个flexReserve
值为"/5"
,以保留总预算的五分之一用于历史记录。
<History
history={this.props.history}
passPriority
older={0}
newer={80}
flexGrow={2}
flexReserve="/5"
/>
步骤3:组合提示的所有元素
现在,将所有元素组合到MyPrompt
组件中。
import {
SystemMessage,
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 (
<>
<SystemMessage priority={100}>Here are your base instructions.</SystemMessage>
<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 组件
最后,定义一个FileContext
组件,该组件包含用户当前正在查看的文件内容。因为你使用了flexGrow
,你可以通过使用PromptSizing
中的信息来实现逻辑,以获取每个文件中围绕“有趣”行的尽可能多的行。
为简洁起见,省略了getExpandedFiles
的实现逻辑。您可以在prompt-tsx repo中查看。
import { PromptElement, BasePromptElementProps, PromptSizing, PromptPiece } from '@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())}</>;
}
private async getExpandedFiles(sizing: PromptSizing) {
// Implementation details are summarized here.
// Refer to the repo for the complete implementation.
}
}
摘要
在这些示例中,您创建了一个MyPrompt
组件,该组件包括基本指令、用户查询、历史消息和具有不同优先级的文件内容。您使用了flexGrow
来协作调整文件内容的大小,以适应令牌预算。
通过遵循这种模式,您可以确保提示中最重要的部分始终被包含,而不太重要的部分则根据需要被修剪以适应模型的上下文窗口。有关getExpandedFiles
方法和FileContextTracker
类的完整实现细节,请参考prompt-tsx repo。