- Implement Markdown parsing for ghost text using Milkdown parser - Add support for both block and inline content in ghost text - Refactor prompt system with comprehensive rules and examples - Adjust LLM parameters: increase temperature to 0.7, add repeat_penalty - Add CSS styles for formatted ghost text (bold, italic, code, links) - Add documentation for Copilot prompt system and ghost text rendering
474 lines
13 KiB
Markdown
474 lines
13 KiB
Markdown
# GitHub Copilot 提示词系统分析
|
|
|
|
## 概述
|
|
|
|
GitHub Copilot 的提示词系统是一个复杂的代码补全引擎,采用声明式组件架构来构建发送给 LLM 的提示词。本文档基于 `completions-sample-code/` 目录的源代码分析。
|
|
|
|
## 核心架构
|
|
|
|
```mermaid
|
|
flowchart TB
|
|
subgraph Input
|
|
A[用户光标位置] --> B[CompletionState]
|
|
C[当前文档] --> B
|
|
D[相似文件] --> B
|
|
end
|
|
|
|
subgraph PromptFactory
|
|
B --> E[VirtualPrompt]
|
|
E --> F[组件树构建]
|
|
end
|
|
|
|
subgraph Components
|
|
F --> G[CompletionsContext]
|
|
G --> H[DocumentMarker]
|
|
G --> I[Traits]
|
|
G --> J[Diagnostics]
|
|
G --> K[CodeSnippets]
|
|
G --> L[SimilarFiles]
|
|
G --> M[RecentEdits]
|
|
F --> N[CurrentFile]
|
|
end
|
|
|
|
subgraph Rendering
|
|
H --> O[CompletionsPromptRenderer]
|
|
I --> O
|
|
J --> O
|
|
K --> O
|
|
L --> O
|
|
M --> O
|
|
N --> O
|
|
O --> P[Prompt对象]
|
|
end
|
|
|
|
subgraph Output
|
|
P --> Q[API请求]
|
|
Q --> R[LLM补全]
|
|
end
|
|
```
|
|
|
|
## 1. 提示词基础配置
|
|
|
|
### 1.1 Token 限制
|
|
|
|
来源: [`prompt/src/prompt.ts`](../completions-sample-code/prompt/src/prompt.ts)
|
|
|
|
```typescript
|
|
// 最大补全长度
|
|
export const DEFAULT_MAX_COMPLETION_LENGTH = 500;
|
|
|
|
// 最大提示词长度 (模型上下文窗口 - 补全长度)
|
|
export const DEFAULT_MAX_PROMPT_LENGTH = 8192 - DEFAULT_MAX_COMPLETION_LENGTH;
|
|
|
|
// 默认代码片段数量
|
|
export const DEFAULT_NUM_SNIPPETS = 4;
|
|
|
|
// 后缀匹配阈值
|
|
export const DEFAULT_SUFFIX_MATCH_THRESHOLD = 10;
|
|
```
|
|
|
|
### 1.2 提示词分配比例
|
|
|
|
```typescript
|
|
export const DEFAULT_PROMPT_ALLOCATION_PERCENT = {
|
|
prefix: 35, // 光标前代码
|
|
suffix: 15, // 光标后代码
|
|
stableContext: 35, // 稳定上下文
|
|
volatileContext: 15 // 动态上下文
|
|
};
|
|
```
|
|
|
|
## 2. 语言标记系统
|
|
|
|
### 2.1 支持的语言
|
|
|
|
来源: [`prompt/src/languageMarker.ts`](../completions-sample-code/prompt/src/languageMarker.ts)
|
|
|
|
支持 60+ 种编程语言,每种语言定义了:
|
|
- `lineComment`: 单行注释标记 (start, end)
|
|
- `markdownLanguageIds`: Markdown 代码块语言标识符
|
|
|
|
示例:
|
|
```typescript
|
|
python: {
|
|
lineComment: { start: '#', end: '' },
|
|
markdownLanguageIds: ['python', 'py', 'gyp'],
|
|
},
|
|
javascript: {
|
|
lineComment: { start: '//', end: '' },
|
|
markdownLanguageIds: ['javascript', 'js'],
|
|
},
|
|
```
|
|
|
|
### 2.2 语言标记生成
|
|
|
|
```typescript
|
|
// 获取语言标记
|
|
export function getLanguageMarker(doc: DocumentInfo): string {
|
|
if (dontAddLanguageMarker.indexOf(languageId) === -1 && !hasLanguageMarker(doc)) {
|
|
if (languageId in shebangLines) {
|
|
return shebangLines[languageId]; // 如 #!/usr/bin/env python3
|
|
} else {
|
|
return `Language: ${languageId}`;
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
// 获取路径标记
|
|
export function getPathMarker(doc: DocumentInfo): string {
|
|
if (doc.relativePath) {
|
|
return `Path: ${doc.relativePath}`;
|
|
}
|
|
return '';
|
|
}
|
|
```
|
|
|
|
## 3. 组件系统架构
|
|
|
|
### 3.1 声明式组件
|
|
|
|
来源: [`prompt/src/components/components.ts`](../completions-sample-code/prompt/src/components/components.ts)
|
|
|
|
Copilot 使用类似 React 的 JSX 语法来声明提示词组件:
|
|
|
|
```typescript
|
|
// 基础组件类型
|
|
export type PromptElementProps<P = object> = P & Readonly<PromptAttributes & { children?: PromptComponentChildren }>;
|
|
|
|
// 组件上下文,提供状态管理
|
|
export interface ComponentContext {
|
|
useState<S>(initialState: S): [S, Dispatch<StateUpdater<S>>];
|
|
useData<T>(typePredicate: TypePredicate<T>, consumer: DataConsumer<T>): void;
|
|
}
|
|
```
|
|
|
|
### 3.2 默认提示词组件结构
|
|
|
|
来源: [`lib/src/prompt/completionsPromptFactory/componentsCompletionsPromptFactory.tsx`](../completions-sample-code/lib/src/prompt/completionsPromptFactory/componentsCompletionsPromptFactory.tsx)
|
|
|
|
```tsx
|
|
function defaultCompletionsPrompt(accessor: ServicesAccessor) {
|
|
return (
|
|
<>
|
|
<CompletionsContext>
|
|
<DocumentMarker tdms={tdms} weight={0.7} />
|
|
<Traits weight={0.6} />
|
|
<Diagnostics tdms={tdms} weight={0.65} />
|
|
<CodeSnippets tdms={tdms} weight={0.9} />
|
|
<SimilarFiles tdms={tdms} instantiationService={instantiationService} weight={0.8} />
|
|
<RecentEdits tdms={tdms} recentEditsProvider={recentEditsProvider} weight={0.99} />
|
|
</CompletionsContext>
|
|
<CurrentFile weight={1} />
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 3.3 组件权重说明
|
|
|
|
| 组件 | 权重 | 说明 |
|
|
|------|------|------|
|
|
| RecentEdits | 0.99 | 最近编辑内容,最高优先级 |
|
|
| CodeSnippets | 0.9 | 代码片段 |
|
|
| SimilarFiles | 0.8 | 相似文件内容 |
|
|
| DocumentMarker | 0.7 | 文档标记(语言/路径) |
|
|
| Diagnostics | 0.65 | 诊断信息(错误/警告) |
|
|
| Traits | 0.6 | 代码特征 |
|
|
| CurrentFile | 1.0 | 当前文件内容(必须包含) |
|
|
|
|
## 4. 当前文件组件
|
|
|
|
来源: [`lib/src/prompt/components/currentFile.tsx`](../completions-sample-code/lib/src/prompt/components/currentFile.tsx)
|
|
|
|
### 4.1 光标前代码 (BeforeCursor)
|
|
|
|
```tsx
|
|
export function BeforeCursor(props: {
|
|
document: CompletionRequestDocument | undefined;
|
|
position: Position | undefined;
|
|
maxCharacters: number;
|
|
}) {
|
|
let text = props.document.getText({ start: { line: 0, character: 0 }, end: props.position });
|
|
if (text.length > props.maxCharacters) {
|
|
text = text.slice(-props.maxCharacters); // 截取最后 maxCharacters 字符
|
|
}
|
|
return <Text>{text}</Text>;
|
|
}
|
|
```
|
|
|
|
### 4.2 光标后代码 (AfterCursor)
|
|
|
|
```tsx
|
|
export function AfterCursor(props: {...}, context: ComponentContext) {
|
|
// 获取光标后所有文本
|
|
let suffix = props.document.getText({
|
|
start: props.position,
|
|
end: { line: Number.MAX_VALUE, character: Number.MAX_VALUE },
|
|
});
|
|
|
|
// 后缀缓存机制:使用编辑距离判断是否复用缓存
|
|
const dist = findEditDistanceScore(firstSuffixTokens.tokens, cachedSuffixTokens.tokens);
|
|
if (100 * dist < suffixMatchThreshold * tokens.length) {
|
|
suffixToUse = cachedSuffix; // 使用缓存的后缀
|
|
}
|
|
|
|
return <Text>{suffixToUse}</Text>;
|
|
}
|
|
```
|
|
|
|
## 5. 相似文件与代码片段
|
|
|
|
### 5.1 相似文件选择
|
|
|
|
来源: [`prompt/src/snippetInclusion/similarFiles.ts`](../completions-sample-code/prompt/src/snippetInclusion/similarFiles.ts)
|
|
|
|
```typescript
|
|
export interface SimilarFilesOptions {
|
|
snippetLength: number; // 代码片段长度(行数)
|
|
threshold: number; // 相似度阈值
|
|
maxTopSnippets: number; // 最大返回片段数
|
|
maxCharPerFile: number; // 每文件最大字符数
|
|
maxNumberOfFiles: number; // 最大文件数
|
|
maxSnippetsPerFile: number; // 每文件最大片段数
|
|
}
|
|
|
|
// 默认配置
|
|
export const defaultSimilarFilesOptions: SimilarFilesOptions = {
|
|
snippetLength: 60,
|
|
threshold: 0.0,
|
|
maxTopSnippets: 4,
|
|
maxCharPerFile: 10000,
|
|
maxNumberOfFiles: 20,
|
|
maxSnippetsPerFile: 1,
|
|
};
|
|
```
|
|
|
|
### 5.2 Jaccard 相似度匹配
|
|
|
|
来源: [`prompt/src/snippetInclusion/selectRelevance.ts`](../completions-sample-code/prompt/src/snippetInclusion/selectRelevance.ts)
|
|
|
|
```typescript
|
|
// 使用 Jaccard 相似度计算代码片段相关性
|
|
abstract class WindowedMatcher {
|
|
protected abstract similarityScore(a: Set<string>, b: Set<string>): number;
|
|
|
|
// 分词器:将代码转换为 token 集合
|
|
class Tokenizer {
|
|
tokenize(a: string): Set<string> {
|
|
return new Set(splitIntoWords(a).filter(x => !this.stopsForLanguage.has(x)));
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5.3 代码片段格式化
|
|
|
|
来源: [`prompt/src/snippetInclusion/snippets.ts`](../completions-sample-code/prompt/src/snippetInclusion/snippets.ts)
|
|
|
|
```typescript
|
|
export function announceSnippet(snippet: SnippetToAnnounce) {
|
|
const headline = snippet.relativePath
|
|
? `Compare ${pluralizedSemantics} ${semantics} from ${snippet.relativePath}:`
|
|
: `Compare ${pluralizedSemantics} ${semantics}:`;
|
|
return { headline, snippet: snippet.snippet };
|
|
}
|
|
```
|
|
|
|
## 6. API 请求格式
|
|
|
|
### 6.1 请求结构
|
|
|
|
来源: [`lib/src/openai/fetch.ts`](../completions-sample-code/lib/src/openai/fetch.ts)
|
|
|
|
```typescript
|
|
type CompletionRequest = {
|
|
prompt: string; // 前缀代码
|
|
suffix: string; // 后缀代码
|
|
stream: true; // 始终使用流式响应
|
|
max_tokens: number; // 最大生成 token 数
|
|
n: number; // 并行补全数量
|
|
temperature: number; // 温度参数
|
|
top_p: number; // nucleus 采样参数
|
|
stop: string[]; // 停止标记
|
|
logprobs?: number; // logprob 数量
|
|
extra: {
|
|
language: string; // 语言 ID
|
|
trim_by_indentation?: boolean;
|
|
force_indent?: number;
|
|
next_indent?: number;
|
|
prompt_tokens: number;
|
|
suffix_tokens: number;
|
|
context?: string[]; // 额外上下文
|
|
};
|
|
};
|
|
```
|
|
|
|
### 6.2 停止标记
|
|
|
|
来源: [`lib/src/openai/openai.ts`](../completions-sample-code/lib/src/openai/openai.ts)
|
|
|
|
```typescript
|
|
const stopsForLanguage: { [key: string]: string[] } = {
|
|
markdown: ['\n\n\n'],
|
|
python: ['\ndef ', '\nclass ', '\nif ', '\n\n#'],
|
|
};
|
|
|
|
export function getStops(languageId?: string) {
|
|
return stopsForLanguage[languageId ?? ''] ?? ['\n\n\n', '\n```'];
|
|
}
|
|
```
|
|
|
|
### 6.3 温度参数
|
|
|
|
```typescript
|
|
export function getTemperatureForSamples(numShots: number): number {
|
|
if (numShots <= 1) return 0.0;
|
|
else if (numShots < 10) return 0.2;
|
|
else if (numShots < 20) return 0.4;
|
|
else return 0.8;
|
|
}
|
|
```
|
|
|
|
## 7. Tokenization
|
|
|
|
来源: [`prompt/src/tokenization/tokenizer.ts`](../completions-sample-code/prompt/src/tokenization/tokenizer.ts)
|
|
|
|
### 7.1 支持的 Tokenizer
|
|
|
|
```typescript
|
|
export enum TokenizerName {
|
|
cl100k = 'cl100k_base', // GPT-3.5/GPT-4
|
|
o200k = 'o200k_base', // GPT-4o
|
|
mock = 'mock', // 测试用
|
|
}
|
|
```
|
|
|
|
### 7.2 Tokenizer 接口
|
|
|
|
```typescript
|
|
export interface Tokenizer {
|
|
tokenLength(text: string): number;
|
|
tokenize(text: string): number[];
|
|
detokenize(tokens: number[]): string;
|
|
tokenizeStrings(text: string): string[];
|
|
takeLastTokens(text: string, n: number): { text: string; tokens: number[] };
|
|
takeFirstTokens(text: string, n: number): { text: string; tokens: number[] };
|
|
takeLastLinesTokens(text: string, n: number): string;
|
|
}
|
|
```
|
|
|
|
## 8. Tree-sitter 代码解析
|
|
|
|
来源: [`prompt/src/parse.ts`](../completions-sample-code/prompt/src/parse.ts)
|
|
|
|
### 8.1 支持的语言
|
|
|
|
```typescript
|
|
export enum WASMLanguage {
|
|
Python = 'python',
|
|
JavaScript = 'javascript',
|
|
TypeScript = 'typescript',
|
|
TSX = 'tsx',
|
|
Go = 'go',
|
|
Ruby = 'ruby',
|
|
CSharp = 'c-sharp',
|
|
Java = 'java',
|
|
Php = 'php',
|
|
Cpp = 'cpp',
|
|
}
|
|
```
|
|
|
|
### 8.2 用途
|
|
|
|
- 判断代码块是否为空块开始 (`isEmptyBlockStart`)
|
|
- 判断代码块是否完成 (`isBlockBodyFinished`)
|
|
- 获取语法节点起始位置 (`getNodeStart`)
|
|
|
|
## 9. 提示词构建流程
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User as 用户
|
|
participant VSCode as VS Code
|
|
participant GT as GhostText
|
|
participant PF as PromptFactory
|
|
participant Components as 组件系统
|
|
participant Tokenizer as Tokenizer
|
|
participant API as OpenAI API
|
|
|
|
User->>VSCode: 输入代码
|
|
VSCode->>GT: 请求补全
|
|
GT->>PF: extractPrompt
|
|
PF->>Components: 构建组件树
|
|
|
|
Components->>Components: DocumentMarker
|
|
Components->>Components: Traits
|
|
Components->>Components: Diagnostics
|
|
Components->>Components: CodeSnippets
|
|
Components->>Components: SimilarFiles
|
|
Components->>Components: RecentEdits
|
|
Components->>Components: CurrentFile
|
|
|
|
Components->>Tokenizer: 计算 token 数量
|
|
Tokenizer-->>Components: 返回 token 数
|
|
|
|
Components->>Components: Elision 省略处理
|
|
Components-->>PF: Prompt 对象
|
|
PF-->>GT: PromptResponse
|
|
GT->>API: 发送请求
|
|
API-->>GT: 流式返回补全
|
|
GT-->>VSCode: 显示 Ghost Text
|
|
VSCode-->>User: 展示建议
|
|
```
|
|
|
|
## 10. 关键设计模式
|
|
|
|
### 10.1 声明式组件
|
|
|
|
使用 JSX 语法声明提示词结构,支持:
|
|
- 组件组合
|
|
- 权重分配
|
|
- 状态管理
|
|
- 数据订阅
|
|
|
|
### 10.2 虚拟提示词树
|
|
|
|
在渲染前构建虚拟树结构,支持:
|
|
- 增量更新
|
|
- 高效 diff
|
|
- 条件渲染
|
|
|
|
### 10.3 Token 预算管理
|
|
|
|
- 每个组件有权重属性
|
|
- 根据 token 预算动态省略内容
|
|
- 优先保留高权重组件
|
|
|
|
### 10.4 后缀缓存
|
|
|
|
- 使用编辑距离判断后缀相似度
|
|
- 相似时复用缓存的后缀
|
|
- 减少 token 波动,提高缓存命中率
|
|
|
|
## 11. 实现参考
|
|
|
|
如果要在自己的项目中实现类似的提示词系统,需要关注以下核心模块:
|
|
|
|
1. **Tokenizer**: 使用 tiktoken 进行准确的 token 计数
|
|
2. **语言标记**: 为不同语言生成适当的标记
|
|
3. **上下文收集**: 收集相似文件、最近编辑等上下文
|
|
4. **Token 预算**: 动态分配 token 给不同组件
|
|
5. **FIM 格式**: 使用 Fill-In-the-Middle 格式发送请求
|
|
|
|
## 总结
|
|
|
|
GitHub Copilot 的提示词系统是一个精心设计的工程系统,核心特点包括:
|
|
|
|
1. **模块化组件架构**: 使用声明式组件构建提示词
|
|
2. **智能上下文选择**: 通过 Jaccard 相似度选择相关代码片段
|
|
3. **Token 预算管理**: 动态分配 token 给不同优先级的内容
|
|
4. **多语言支持**: 支持 60+ 种编程语言
|
|
5. **Tree-sitter 解析**: 精确理解代码结构
|
|
6. **流式响应**: 实时返回补全结果
|