{"meta":{"title":"인용","intro":"인용은 도우미 응답의 텍스트 구간을 이를 뒷받침하는 출처에 연결합니다. 세션을 만들거나 다시 시작할 때 enableCitations을(를) 켠 다음, citations 이벤트에서 assistant.message 페이로드를 읽어 각주, 출처 목록 또는 인라인 링크를 렌더링합니다.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ko/copilot","title":"GitHub Copilot"},{"href":"/ko/copilot/how-tos","title":"방법"},{"href":"/ko/copilot/how-tos/copilot-sdk","title":"코필로트 SDK"},{"href":"/ko/copilot/how-tos/copilot-sdk/features","title":"기능"},{"href":"/ko/copilot/how-tos/copilot-sdk/features/citations","title":"인용"}],"documentType":"article"},"body":"# 인용\n\n인용은 도우미 응답의 텍스트 구간을 이를 뒷받침하는 출처에 연결합니다. 세션을 만들거나 다시 시작할 때 enableCitations을(를) 켠 다음, citations 이벤트에서 assistant.message 페이로드를 읽어 각주, 출처 목록 또는 인라인 링크를 렌더링합니다.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n> \\[!WARNING]\n> 인용은 실험적입니다. 옵션 이름, 이벤트 페이로드 및 공급자 적용 범위는 향후 릴리스에서 변경될 수 있습니다.\n\n## 인용 작동 방식\n\n인용은 SDK가 아닌 모델 공급자에 의해 생성됩니다. 흐름에는 다음 세 부분이 있습니다.\n\n1. 애플리케이션은 원본 콘텐츠를 전달하는 문서 첨부 파일 또는 도구 결과와 같은 인용 가능한 자료를 제공합니다.\n2. 런타임은 `enableCitations`가 활성화되면 해당 자료를 전송 시 인용 가능한 상태로 표시합니다. Anthropic 모델의 경우 파일 첨부는 인용이 활성화된 `document` 블록으로 전송됩니다.\n3. 모델은 인용 메타데이터를 반환하고 런타임은 최종 `assistant.message` 이벤트에서 공급자 중립적 `citations` 개체로 정규화합니다.\n\n공급자 지원은 제한됩니다.\n`provider` 각 원본 레코드의 필드는 인용의 출처를 기록합니다.\n\n| 공급자 값       | 의미                                 |\n| ----------- | ---------------------------------- |\n| `anthropic` | Anthropic(Claude) 모델 응답에 의해 생성된 인용 |\n| `openai`    | OpenAI 모델 응답에 의해 생성된 인용            |\n| `client`    | 런타임이 도구 출력에서 생성한 인용 정보             |\n\n> \\[!NOTE]\n> `enableCitations`을(를) 켜도 응답에 인용이 포함된다고 보장하지 않습니다. 모델은 응답이 인용 가능한 출처 자료에 근거한 경우에만 이를 출력합니다. 항상 `citations` 필드를 선택 사항으로 처리하세요.\n\n## 세션에서 인용 활성화\n\n세션 생성 시 옵션을 설정하고, 다시 시작한 후에도 인용을 표시하려면 재개할 때 해당 옵션을 다시 설정하세요.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```typescript\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n    enableCitations: true,\n});\n\nconst resumed = await client.resumeSession(session.sessionId, {\n    onPermissionRequest: approveAll,\n    enableCitations: true,\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nsession = await client.create_session(\n    on_permission_request=PermissionHandler.approve_all,\n    enable_citations=True,\n)\n\nresumed = await client.resume_session(\n    session.session_id,\n    on_permission_request=PermissionHandler.approve_all,\n    enable_citations=True,\n)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\nsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n    OnPermissionRequest: copilot.PermissionHandler.ApproveAll,\n    EnableCitations:     copilot.Bool(true),\n})\n\nresumed, err := client.ResumeSession(ctx, session.SessionID, &copilot.ResumeSessionConfig{\n    OnPermissionRequest: copilot.PermissionHandler.ApproveAll,\n    EnableCitations:     copilot.Bool(true),\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    OnPermissionRequest = PermissionHandler.ApproveAll,\n    EnableCitations = true,\n});\n\nvar resumed = await client.ResumeSessionAsync(session.SessionId, new ResumeSessionConfig\n{\n    OnPermissionRequest = PermissionHandler.ApproveAll,\n    EnableCitations = true,\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nCopilotSession session = client\n        .createSession(new SessionConfig()\n                .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n                .setEnableCitations(true))\n        .get();\n\nCopilotSession resumed = client\n        .resumeSession(session.getSessionId(), new ResumeSessionConfig()\n                .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n                .setEnableCitations(true))\n        .get();\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n<!-- docs-validate: skip -->\n\n```rust\nlet session = client\n    .create_session(\n        SessionConfig::new()\n            .approve_all_permissions()\n            .with_enable_citations(true),\n    )\n    .await?;\n\nlet resumed = client\n    .resume_session(\n        ResumeSessionConfig::new(session.id().clone())\n            .approve_all_permissions()\n            .with_enable_citations(true),\n    )\n    .await?;\n```\n\n</div>\n\n</div>\n\n## 도우미 메시지에서 인용 읽기\n\n인용은 `assistant.message_delta`최종 이벤트에서 도착하며, `assistant.message`이벤트에서는 도착하지 않습니다. 원본 마커를 렌더링하기 전에 마지막 메시지를 기다립니다.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```typescript\nsession.on((event) => {\n    if (event.type !== \"assistant.message\" || !event.data.citations) {\n        return;\n    }\n\n    const { sources, spans } = event.data.citations;\n    const sourceById = new Map(sources.map((source) => [source.id, source]));\n\n    for (const span of spans) {\n        const quoted = event.data.content.slice(span.startIndex, span.endIndex);\n        for (const reference of span.references) {\n            const source = sourceById.get(reference.sourceId);\n            const label = source?.title ?? source?.url ?? source?.path ?? source?.id;\n            console.log(`\"${quoted}\" — ${label}`);\n        }\n    }\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nfrom copilot.session_events import SessionEventType\n\ndef utf16_slice(text: str, start: int, end: int) -> str:\n    \"\"\"Slice by UTF-16 code units, which is how span offsets are measured.\"\"\"\n    units = text.encode(\"utf-16-le\")\n    return units[start * 2 : end * 2].decode(\"utf-16-le\")\n\ndef handle(event):\n    if event.type != SessionEventType.ASSISTANT_MESSAGE or not event.data.citations:\n        return\n\n    sources = {source.id: source for source in event.data.citations.sources}\n\n    for span in event.data.citations.spans:\n        quoted = utf16_slice(event.data.content, span.start_index, span.end_index)\n        for reference in span.references:\n            source = sources[reference.source_id]\n            label = source.title or source.url or source.path or source.id\n            print(f'\"{quoted}\" — {label}')\n\nsession.on(handle)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\n// import \"unicode/utf16\"\n\nsession.On(func(event copilot.SessionEvent) {\n    d, ok := event.Data.(*copilot.AssistantMessageData)\n    if !ok || d.Citations == nil {\n        return\n    }\n\n    sources := map[string]copilot.CitationSource{}\n    for _, source := range d.Citations.Sources {\n        sources[source.ID] = source\n    }\n\n    // Span offsets are UTF-16 code units, so index the UTF-16 view of the content.\n    units := utf16.Encode([]rune(d.Content))\n\n    for _, span := range d.Citations.Spans {\n        quoted := string(utf16.Decode(units[span.StartIndex:span.EndIndex]))\n        for _, reference := range span.References {\n            source := sources[reference.SourceID]\n            label := source.ID\n            switch {\n            case source.Title != nil:\n                label = *source.Title\n            case source.URL != nil:\n                label = *source.URL\n            case source.Path != nil:\n                label = *source.Path\n            }\n            fmt.Printf(\"%q — %s\\n\", quoted, label)\n        }\n    }\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nsession.On<SessionEvent>(evt =>\n{\n    if (evt is not AssistantMessageEvent message || message.Data.Citations is null)\n    {\n        return;\n    }\n\n    var sources = message.Data.Citations.Sources.ToDictionary(source => source.Id);\n\n    foreach (var span in message.Data.Citations.Spans)\n    {\n        var quoted = message.Data.Content[(int)span.StartIndex..(int)span.EndIndex];\n        foreach (var reference in span.References)\n        {\n            var source = sources[reference.SourceId];\n            var label = source.Title ?? source.Url ?? source.Path ?? source.Id;\n            Console.WriteLine($\"\\\"{quoted}\\\" — {label}\");\n        }\n    }\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nsession.on(AssistantMessageEvent.class, event -> {\n    Citations citations = event.getData().citations();\n    if (citations == null) {\n        return;\n    }\n\n    Map<String, CitationSource> sources = citations.sources().stream()\n            .collect(Collectors.toMap(CitationSource::id, source -> source));\n\n    for (CitationSpan span : citations.spans()) {\n        String quoted = event.getData().content()\n                .substring(span.startIndex().intValue(), span.endIndex().intValue());\n        for (CitationReference reference : span.references()) {\n            CitationSource source = sources.get(reference.sourceId());\n            String label = source.title() != null ? source.title()\n                    : source.url() != null ? source.url()\n                    : source.path() != null ? source.path()\n                    : source.id();\n            System.out.printf(\"\\\"%s\\\" — %s%n\", quoted, label);\n        }\n    }\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n<!-- docs-validate: skip -->\n\n```rust\nuse github_copilot_sdk::session_events::AssistantMessageData;\nuse std::collections::HashMap;\n\nlet mut events = session.subscribe();\n\nwhile let Ok(event) = events.recv().await {\n    if event.event_type != \"assistant.message\" {\n        continue;\n    }\n\n    let Some(data) = event.typed_data::<AssistantMessageData>() else {\n        continue;\n    };\n    let Some(citations) = data.citations.as_ref() else {\n        continue;\n    };\n\n    let sources: HashMap<&str, _> = citations\n        .sources\n        .iter()\n        .map(|source| (source.id.as_str(), source))\n        .collect();\n\n    // Span offsets are UTF-16 code units, so index the UTF-16 view of the content.\n    let units: Vec<u16> = data.content.encode_utf16().collect();\n\n    for span in &citations.spans {\n        let quoted = String::from_utf16_lossy(\n            &units[span.start_index as usize..span.end_index as usize],\n        );\n        for reference in &span.references {\n            let Some(source) = sources.get(reference.source_id.as_str()) else {\n                continue;\n            };\n            let label = source\n                .title\n                .as_deref()\n                .or(source.url.as_deref())\n                .or(source.path.as_deref())\n                .unwrap_or(source.id.as_str());\n            println!(\"\\\"{quoted}\\\" — {label}\");\n        }\n    }\n}\n```\n\n</div>\n\n</div>\n\n## 인용 페이로드 참조\n\n`citations` 객체는 중복 제거된 소스와 이를 참조하는 span을 분리하므로, 다섯 번 인용된 소스도 `sources`에는 한 번만 나타납니다.\n\n| Type                                              | Field               | Description                                   |\n| ------------------------------------------------- | ------------------- | --------------------------------------------- |\n| `Citations`                                       | `sources`           | 인용 구간에서 참조된 중복 제거 출처 집합                       |\n| `Citations`                                       | `spans`             | 이를 뒷받침하는 출처로 주석 처리된 생성된 텍스트 범위                |\n| `CitationSource`                                  | `id`                |                                               |\n| `CitationReference.sourceId`에서 참조되는 안정적인 턴 범위 식별자 |                     |                                               |\n| `CitationSource`                                  | `provider`          | 인용을 생성한 시스템: `anthropic`, `openai`또는 `client` |\n| `CitationSource`                                  | `title?`            | 사람이 읽을 수 있는 원본 제목                             |\n| `CitationSource`                                  | `url?`              | 원본의 URL(웹 리소스인 경우)                            |\n| `CitationSource`                                  | `path?`             | 원본이 파일인 경우 에이전트 작업 영역 루트를 기준으로 하는 파일 경로       |\n| `CitationSpan`                                    | `startIndex`        | 최종 메시지 콘텐츠의 시작 오프셋(UTF-16 코드 단위, 0부터 시작, 포함)  |\n| `CitationSpan`                                    | `endIndex`          | 최종 메시지 콘텐츠의 끝 오프셋(UTF-16 코드 단위, 0 기반, 미포함)    |\n| `CitationSpan`                                    | `references`        | 이 구간을 뒷받침하는 출처들                               |\n| `CitationReference`                               | `sourceId`          | 이 참조가 가리키는 `CitationSource`의 식별자              |\n| `CitationReference`                               | `citedText?`        | 모델에서 해당 범위를 제공한 경우, 그 범위를 뒷받침하는 원본의 정확한 텍스트   |\n| `CitationReference`                               | `location?`         | 해당 범위를 뒷받침하는 원본 내 위치                          |\n| `CitationReference`                               | `providerMetadata?` | 불투명하게 전달된 공급자 네이티브 상관 관계 데이터                  |\n\n> \\[!TIP]\n> 범위 오프셋은 최종 `content` 문자열에 대해 UTF-16 코드 단위로 측정됩니다. TypeScript, Java 및 .NET 문자열은 이미 UTF-16이므로 직접 조각화할 수 있습니다. Python 문자열은 유니코드 코드 포인트에 의해 인덱싱되고 Go 및 Rust 문자열은 UTF-8이므로 위의 예제와 같이 조각화하기 전에 콘텐츠를 UTF-16 코드 단위로 변환합니다.\n\n### 인용 위치\n\n`CitationReference.location`는 `type`를 판별 키로 사용하는 식별된 유니온입니다:\n\n| 위치 유형                    | Fields               | 사용하세요 |\n| ------------------------ | -------------------- | ----- |\n| `char`                   |                      |       |\n| `startIndex`, `endIndex` | 원본 텍스트 내의 문자 범위      |       |\n| `page`                   |                      |       |\n| `startPage`, `endPage`   | 페이지를 매긴 문서 내의 페이지 범위 |       |\n| `block`                  |                      |       |\n| `startBlock`, `endBlock` | 구조화된 문서 내의 콘텐츠 블록 범위 |       |\n\n## 인용 가능한 원본 제공\n\n인용에는 모델이 특성화할 수 있는 원본 자료가 필요합니다. 이를 제공하는 방법에는 두 가지가 있습니다.\n\n### 메시지에 문서 첨부\n\n인용을 사용하도록 설정하고 세션에서 Anthropic 공급자를 사용하는 경우 파일 첨부 파일은 인용이 설정된 블록으로 `document` 전송되므로 모델은 해당 항목의 구절을 인용할 수 있습니다.\n\n<!-- docs-validate: skip -->\n\n```typescript\nawait session.sendAndWait({\n    prompt: \"Summarize the attached PDF and cite the passages you used.\",\n    attachments: [\n        {\n            type: \"blob\",\n            data: pdfBase64,\n            displayName: \"quarterly-report.pdf\",\n            mimeType: \"application/pdf\",\n        },\n    ],\n});\n```\n\n첨부 파일 API와 `blob` 및 [](/ko/copilot/how-tos/copilot-sdk/features/image-input) 첨부 파일 셰이프에 대해서는 `file`을 참조하세요.\n\n### 도구에서 인용 가능한 원본 반환\n\n도구 결과에는 실험적인 `citableSources` 배열이 포함됩니다. 각 항목은 모델이 인용할 수 있는 `content`와 `id`, 그리고 선택 사항인 `path`, `title`, `url`를 함께 제공합니다. 이러한 소스는 도구 결과와 함께 저장되므로 세션을 다시 시작해도 유지되며, 이를 기반으로 생성된 인용은 `client` 공급자로 태그됩니다.\n\n## Limitations\n\n* 인용은 모든 SDK에서 실험적이며 호환성 보장에 포함되지 않습니다.\n* 적용 범위는 모델 공급자에 따라 달라집니다. 인용을 지원하지 않는 공급자에 대해 구성된 세션은 `citations` 페이로드를 내보내지 않습니다.\n* 인용은 최종 `assistant.message` 이벤트에만 존재하므로 스트리밍 소비자는 중간 응답을 렌더링할 수 없습니다.\n* 공용 코드 및 IP 중복 인용은 이 화면의 일부가 아닙니다.\n\n## 추가 읽기\n\n* [스트리밍 세션 이벤트](/ko/copilot/how-tos/copilot-sdk/features/streaming-events): 세션 이벤트 구독 및 이벤트 유형 좁히기\n* [이미지 입력](/ko/copilot/how-tos/copilot-sdk/features/image-input): 메시지에 파일 및 메모리 내 Blob 연결\n* [세션 다시 시작 및 지속성](/ko/copilot/how-tos/copilot-sdk/features/session-persistence): 세션 다시 시작 및 세션 옵션 다시 적용\n* [SDK 및 CLI 호환성](/ko/copilot/how-tos/copilot-sdk/troubleshooting/compatibility): SDK 및 CLI 기능 매트릭스"}