이 출시되었습니다! 11월의 새로운 기능 및 수정 사항을 읽어보세요.

언어 모델 프롬프트 만들기

문자열 연결을 사용하여 언어 모델 프롬프트를 빌드할 수 있지만, 기능을 구성하고 프롬프트가 언어 모델의 컨텍스트 창 내에 유지되도록 하는 것은 어렵습니다. 이러한 제한 사항을 극복하기 위해 @vscode/prompt-tsx 라이브러리를 사용할 수 있습니다.

@vscode/prompt-tsx 라이브러리는 다음과 같은 기능을 제공합니다.

  • TSX 기반 프롬프트 렌더링: TSX 컴포넌트를 사용하여 프롬프트를 구성하여 가독성과 유지보수성을 높입니다.
  • 우선순위 기반 트리밍: 프롬프트의 덜 중요한 부분을 자동으로 트리밍하여 모델의 컨텍스트 창에 맞춥니다.
  • 유연한 토큰 관리: flexGrow, flexReserve, flexBasis와 같은 속성을 사용하여 토큰 예산을 협력적으로 사용합니다.
  • 도구 통합: 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 {
	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단계: History 컴포넌트 정의

사용을 더 쉽게 만들기 위해 기록 메시지를 래핑하고 passPriority 속성을 사용하여 전달 컨테이너 역할을 하는 History 컴포넌트를 정의합니다. 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단계: 기본 지침 및 사용자 쿼리 정의

먼저 기본 지침을 포함하는 UserMessage 컴포넌트를 정의합니다.

<UserMessage priority={100}>Here are your base instructions.</UserMessage>

그런 다음 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 객체에서 *사용되지 않은* 토큰 예산을 가져옵니다. flex 요소의 동작에 대해 prompt-tsx 문서에서 자세히 읽을 수 있습니다.

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 {
	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 컴포넌트 정의

마지막으로, 사용자가 현재 보고 있는 파일의 내용을 포함하는 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를 참조하세요.

© . This site is unofficial and not affiliated with Microsoft.