现已推出!阅读关于 11 月的新功能和修复的信息。

制作语言模型提示

您可以使用字符串连接来构建语言模型提示,但很难组合功能并确保您的提示保持在语言模型的上下文窗口内。为了克服这些限制,您可以使用 @vscode/prompt-tsx 库。

@vscode/prompt-tsx 库提供以下功能

  • 基于 TSX 的提示渲染:使用 TSX 组件组合提示,使其更具可读性和可维护性
  • 基于优先级的修剪:自动修剪提示中不重要的部分,以适应模型的上下文窗口
  • 灵活的令牌管理:使用诸如 flexGrowflexReserveflexBasis 之类的属性来协作使用令牌预算
  • 工具集成:与 VS Code 的语言模型工具 API 集成

有关所有功能的完整概述和详细用法说明,请参阅 完整 README

本文介绍了使用该库进行提示设计的实际示例。这些示例的完整代码可以在 prompt-tsx 存储库 中找到。

管理对话历史记录中的优先级

在提示中包含对话历史记录非常重要,因为它允许用户针对之前的消息提出后续问题。但是,您要确保其优先级得到适当处理,因为历史记录会随着时间的推移而变得庞大。我们发现最有意义的模式通常是按顺序优先考虑

  1. 基本提示说明
  2. 当前用户查询
  3. 最近的几次聊天历史记录
  4. 任何支持数据
  5. 尽可能多的剩余历史记录

因此,请在提示中将历史记录分为两个部分,其中最近的提示轮次优先于一般的上下文信息。

在此库中,树中的每个 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:定义 Prompt 组件

接下来,定义一个 MyPrompt 组件,该组件包括基本说明、用户查询以及具有相应优先级的历史记录消息。优先级值在同级之间是局部的。请记住,您可能希望在触及提示中的任何其他内容之前修剪历史记录中较旧的消息,因此您需要拆分两个 <HistoryMessages> 元素

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 组件

为了使使用更容易一些,请定义一个 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 值,元素会获得传递到其 render()prepare() 调用中的 PromptSizing 对象中任何未使用的令牌预算。您可以在 prompt-tsx 文档中阅读有关 flex 元素行为的更多信息。

步骤 3:包含历史记录

接下来,使用您之前创建的 History 组件包含历史记录消息。这有点棘手,因为您确实希望显示一些历史记录,但也希望文件内容占用提示的大部分。

因此,为 History 组件分配一个 flexGrow 值为 2,以确保它在包括 <FileContext /> 在内的所有其他元素之后呈现。但是,还设置一个 flexReserve 值为 "/5",为历史记录保留总预算的 1/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 存储库中查看。

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 存储库