警告
引用文献は試験的です。 オプション名、イベント ペイロード、プロバイダー カバレッジは、将来のリリースで変更される可能性があります。
引用文献のしくみ
引用は、SDK ではなくモデル プロバイダーによって生成されます。 フローには、次の 3 つの部分があります。
- アプリケーションは、ドキュメントの添付ファイルやソース コンテンツを含むツールの結果など、引用可能な資料を提供します。
- ランタイムは、
enableCitationsがオンのとき、そのマテリアルをワイヤ上で引用可能としてマークします。 Anthropicモデルの場合、添付ファイルは引用文献が有効になっているdocumentブロックとして送信されます。 - モデルは引用メタデータを返し、ランタイムは最終的な
assistant.messageイベントでプロバイダーに依存しないcitationsオブジェクトに正規化します。
プロバイダーのサポートは制限されています。 各ソースレコードの provider フィールドには、引用の出典元が記録されます:
| プロバイダーの値 | 意味 |
|---|---|
anthropic | Anthropic (クロード) モデル応答によって生成された引用 |
openai | OpenAI モデル応答によって生成された引用 |
client | ツール出力からランタイムによって合成された引用 |
メモ
enableCitationsを有効にしても、応答に引用文献が含まれるとは限りません。 モデルは、応答が引用可能なソース マテリアルに接地されている場合にのみ、それらを出力します。 citations フィールドは常に省略可能として扱います。
セッションで引用を有効にする
セッション作成のオプションを設定し、再起動後に引用を行う場合は、再開時にもう一度設定します。
const session = await client.createSession({
onPermissionRequest: approveAll,
enableCitations: true,
});
const resumed = await client.resumeSession(session.sessionId, {
onPermissionRequest: approveAll,
enableCitations: true,
});
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
enable_citations=True,
)
resumed = await client.resume_session(
session.session_id,
on_permission_request=PermissionHandler.approve_all,
enable_citations=True,
)
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
EnableCitations: copilot.Bool(true),
})
resumed, err := client.ResumeSession(ctx, session.SessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
EnableCitations: copilot.Bool(true),
})
var session = await client.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
EnableCitations = true,
});
var resumed = await client.ResumeSessionAsync(session.SessionId, new ResumeSessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
EnableCitations = true,
});
CopilotSession session = client
.createSession(new SessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
.setEnableCitations(true))
.get();
CopilotSession resumed = client
.resumeSession(session.getSessionId(), new ResumeSessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
.setEnableCitations(true))
.get();
let session = client
.create_session(
SessionConfig::new()
.approve_all_permissions()
.with_enable_citations(true),
)
.await?;
let resumed = client
.resume_session(
ResumeSessionConfig::new(session.id().clone())
.approve_all_permissions()
.with_enable_citations(true),
)
.await?;
アシスタント メッセージから引用文献を読む
引用文献は、assistant.message_deltaイベントではなく、最終的なassistant.messageイベントに到着します。 ソース マーカーをレンダリングする前に、最後のメッセージを待ちます。
session.on((event) => {
if (event.type !== "assistant.message" || !event.data.citations) {
return;
}
const { sources, spans } = event.data.citations;
const sourceById = new Map(sources.map((source) => [source.id, source]));
for (const span of spans) {
const quoted = event.data.content.slice(span.startIndex, span.endIndex);
for (const reference of span.references) {
const source = sourceById.get(reference.sourceId);
const label = source?.title ?? source?.url ?? source?.path ?? source?.id;
console.log(`"${quoted}" — ${label}`);
}
}
});
from copilot.session_events import SessionEventType
def utf16_slice(text: str, start: int, end: int) -> str:
"""Slice by UTF-16 code units, which is how span offsets are measured."""
units = text.encode("utf-16-le")
return units[start * 2 : end * 2].decode("utf-16-le")
def handle(event):
if event.type != SessionEventType.ASSISTANT_MESSAGE or not event.data.citations:
return
sources = {source.id: source for source in event.data.citations.sources}
for span in event.data.citations.spans:
quoted = utf16_slice(event.data.content, span.start_index, span.end_index)
for reference in span.references:
source = sources[reference.source_id]
label = source.title or source.url or source.path or source.id
print(f'"{quoted}" — {label}')
session.on(handle)
// import "unicode/utf16"
session.On(func(event copilot.SessionEvent) {
d, ok := event.Data.(*copilot.AssistantMessageData)
if !ok || d.Citations == nil {
return
}
sources := map[string]copilot.CitationSource{}
for _, source := range d.Citations.Sources {
sources[source.ID] = source
}
// Span offsets are UTF-16 code units, so index the UTF-16 view of the content.
units := utf16.Encode([]rune(d.Content))
for _, span := range d.Citations.Spans {
quoted := string(utf16.Decode(units[span.StartIndex:span.EndIndex]))
for _, reference := range span.References {
source := sources[reference.SourceID]
label := source.ID
switch {
case source.Title != nil:
label = *source.Title
case source.URL != nil:
label = *source.URL
case source.Path != nil:
label = *source.Path
}
fmt.Printf("%q — %s\n", quoted, label)
}
}
})
session.On<SessionEvent>(evt =>
{
if (evt is not AssistantMessageEvent message || message.Data.Citations is null)
{
return;
}
var sources = message.Data.Citations.Sources.ToDictionary(source => source.Id);
foreach (var span in message.Data.Citations.Spans)
{
var quoted = message.Data.Content[(int)span.StartIndex..(int)span.EndIndex];
foreach (var reference in span.References)
{
var source = sources[reference.SourceId];
var label = source.Title ?? source.Url ?? source.Path ?? source.Id;
Console.WriteLine($"\"{quoted}\" — {label}");
}
}
});
session.on(AssistantMessageEvent.class, event -> {
Citations citations = event.getData().citations();
if (citations == null) {
return;
}
Map<String, CitationSource> sources = citations.sources().stream()
.collect(Collectors.toMap(CitationSource::id, source -> source));
for (CitationSpan span : citations.spans()) {
String quoted = event.getData().content()
.substring(span.startIndex().intValue(), span.endIndex().intValue());
for (CitationReference reference : span.references()) {
CitationSource source = sources.get(reference.sourceId());
String label = source.title() != null ? source.title()
: source.url() != null ? source.url()
: source.path() != null ? source.path()
: source.id();
System.out.printf("\"%s\" — %s%n", quoted, label);
}
}
});
use github_copilot_sdk::session_events::AssistantMessageData;
use std::collections::HashMap;
let mut events = session.subscribe();
while let Ok(event) = events.recv().await {
if event.event_type != "assistant.message" {
continue;
}
let Some(data) = event.typed_data::<AssistantMessageData>() else {
continue;
};
let Some(citations) = data.citations.as_ref() else {
continue;
};
let sources: HashMap<&str, _> = citations
.sources
.iter()
.map(|source| (source.id.as_str(), source))
.collect();
// Span offsets are UTF-16 code units, so index the UTF-16 view of the content.
let units: Vec<u16> = data.content.encode_utf16().collect();
for span in &citations.spans {
let quoted = String::from_utf16_lossy(
&units[span.start_index as usize..span.end_index as usize],
);
for reference in &span.references {
let Some(source) = sources.get(reference.source_id.as_str()) else {
continue;
};
let label = source
.title
.as_deref()
.or(source.url.as_deref())
.or(source.path.as_deref())
.unwrap_or(source.id.as_str());
println!("\"{quoted}\" — {label}");
}
}
}
引用ペイロードリファレンス
citations オブジェクトは重複除去されたソースを参照するスパンから分離するため、5 回引用されたソースがsourcesに 1 回表示されます。
| タイプ | フィールド | Description |
|---|---|---|
Citations | sources | 引用スパンで参照される、重複が除去されたソースのセット |
Citations | spans | 根拠ソースで注釈された生成テキストの範囲 |
CitationSource | id | |
Citation によって参照される安定したターンスコープ識別子 | ||
CitationSource | provider | 引用文献を作成したシステム: anthropic、 openai、または client |
CitationSource | title? | ソースの人間が判読できるタイトル |
CitationSource | url? | ソースの URL (Web リソースの場合) |
CitationSource | path? | ソースがファイルの場合、エージェント ワークスペース のルートを基準としたファイル パス |
CitationSpan | startIndex | 最終メッセージ コンテンツ内の開始オフセット (UTF-16 コード単位、ゼロベース、含む) |
CitationSpan | endIndex | 最終的なメッセージ コンテンツの終了オフセット (UTF-16 コード単位、0 から始まる、排他的) |
CitationSpan | references | このスパンをサポートするソース |
CitationReference | sourceId | この参照が指す CitationSource の識別子 |
CitationReference | citedText? | スパンをサポートするソースからの正確なテキスト (モデルが提供する場合) |
CitationReference | location? | スパンをサポートするソース内の場所 |
CitationReference | providerMetadata? | プロバイダーネイティブの関連付けデータ(不透明に渡される) |
ヒント
スパン オフセットは、最終的な content 文字列に対して UTF-16 コード単位で測定されます。 TypeScript、Java、および.NET文字列は既に UTF-16 であるため、直接スライスできます。 Python文字列は Unicode コード ポイントによってインデックスが作成され、Go 文字列と Rust 文字列は UTF-8 であるため、上記の例のように、スライスする前にコンテンツを UTF-16 コード 単位に変換します。
引用場所
CitationReference.location は、 typeでキー指定された判別共用体です。
| 場所のタイプ | フィールド | 使用 |
|---|---|---|
char | ||
startIndex、endIndex | ソース テキスト内の文字範囲 | |
page | ||
startPage、endPage | ページ分割されたドキュメント内のページ範囲 | |
block | ||
startBlock、endBlock | 構造化ドキュメント内のコンテンツ ブロック範囲 |
引用可能なソースを提供する
引用文献には、モデルが属性付けできるソース マテリアルが必要です。 これを提供するには、2 つの方法があります。
メッセージにドキュメントを添付する
引用が有効になっていて、セッションでAnthropicプロバイダーが使用されている場合、添付ファイルは引用がオンになっているdocument ブロックとして送信されるため、モデルはそれらの一節を引用できます。
await session.sendAndWait({
prompt: "Summarize the attached PDF and cite the passages you used.",
attachments: [
{
type: "blob",
data: pdfBase64,
displayName: "quarterly-report.pdf",
mimeType: "application/pdf",
},
],
});
file では、attachment API と blobおよび attachment shapes について説明しています。
ツールから引用可能なソースを返す
ツールの結果には、実験的な citableSources 配列が含まれます。 各エントリは、モデルが引用できる content と、 id とオプションの title、 url、および pathを提供します。 これらのソースはツールの結果と共に保持されるため、セッションの再開後も存続し、そこから構築された引用文献には client プロバイダーでタグ付けされます。
Limitations
- 引用文献はすべての SDK で試験的であり、互換性の保証の対象ではありません。
- カバレッジはモデル プロバイダーによって異なります。 引用サポートなしでプロバイダー用に構成されたセッションは、
citationsペイロードを出力しません。 - 引用は最終的な
assistant.messageイベントにのみ存在するため、ストリーミング コンシューマーは中間応答をレンダリングできません。 - パブリックコードとIP重複に関する引用は、この領域の一部ではありません。
詳細については、次を参照してください。
- ストリーミング セッション イベント: セッション イベントを購読し、イベントの種類を絞り込む
- 画像入力: ファイルとメモリ内 BLOB をメッセージにアタッチする
- セッションの再開と永続化: セッションを再開し、セッション オプションを再適用する
- SDK と CLI の互換性: SDK と CLI の機能マトリックス