이 가이드에서는 Chat API의 StreamMessage 리소스를 사용하여 Google Chat에서 메시지를 점진적으로 스트리밍하는 방법을 설명합니다. 스트리밍을 사용하면 Google Chat 앱에서 메시지를 완료하기 전에 실시간 진행률 표시기, 중간 추론 또는 도구 실행 단계, 증분 텍스트 응답을 표시할 수 있습니다.
스트리밍은 생성형 AI로 구동되는 Chat 앱 또는 완전한 응답을 생성하는 데 몇 초 또는 몇 분이 걸리는 장기 실행 백엔드 워크플로를 위해 설계되었습니다.
메시지 스트리밍 작동 방식
스트리밍 메시지는 스페이스에서 표준 Message 리소스가 되기 전에 활성 스트리밍 상태를 거칩니다.
- 스트림 시작(
spaces.streamMessages.create): Chat 앱은 대화목록 형식의 스페이스에서StreamMessage를 만들어 답장하는 사용자 상호작용 메시지를 참조합니다. API는 스레드에 실시간 진행률 표시기를 표시하고StreamMessage리소스 이름과 초기nextChunkToken를 반환합니다. - 스트림 업데이트(
appendStreamMessageContent또는patch): Chat 앱은 출력을 생성할 때 최신nextChunkToken를 사용하여 순차적 업데이트를 전송합니다.spaces.streamMessages.appendStreamMessageContent를 호출하여 새 텍스트 청크를 연결하거나, 중간 단계를 추가하거나, 인라인 인용을 추가하거나,progressText를 업데이트합니다.spaces.streamMessages.patch를 호출하여 스트리밍된 콘텐츠 청크를 바꾸거나progressText를 제자리에서 업데이트합니다.
- 스트림 완료 또는 삭제(
commit또는delete):spaces.streamMessages.commit를 호출하여 스트림을 완료하고 영구Message리소스로 변환합니다. 커밋할 때 카드, 액세서리 위젯, 하단 출처와 같은 커밋 전용 요소를 연결할 수도 있습니다.spaces.streamMessages.delete을 호출하여 스페이스에 메시지를 저장하지 않고 활성 스트림을 삭제합니다.
스트림이 활성 상태인 동안에는 스트리밍 중인 참조된 메시지(referencedMessage)의 발신자만 중지를 클릭하여 생성을 취소할 수 있습니다. Chat은 백엔드 작업을 중지하고 부분 출력을 커밋하거나 스트림을 삭제할 수 있도록 Chat 앱에 취소 시스템 명령어 이벤트를 전송합니다.
기본 요건
Chat API로 메시지를 스트리밍하려면 다음 기본 요건을 완료하세요.
- Google Chat에 액세스할 수 있는 Business 또는 Enterprise Google Workspace 계정
- 환경을 설정합니다.
- Google Cloud 프로젝트를 만듭니다.
- Chat 앱의 이름, 아이콘, 설명으로 Google Chat API를 사용 설정하고 구성합니다.
- 서비스 계정 사용자 인증 정보를 만들고
credentials.json로 저장합니다. 메시지 스트리밍에는https://www.googleapis.com/auth/chat.bot승인 범위가 있는 앱 인증이 필요합니다. 사용자 인증은 지원되지 않습니다.
- 대화목록 Chat 스페이스(또는 이미 하나 이상의 메시지가 포함된 1:1 채팅 메시지)에 Chat 앱을 추가하고 답장할 액세스 가능한 메시지의 리소스 이름(
spaces/SPACE_ID/messages/MESSAGE_ID)을 가져옵니다. Python 샘플의 경우 Python용 Google API 클라이언트 라이브러리와
google-auth를 설치하고 Chat API 서비스 클라이언트를 초기화합니다.pip install google-api-python-client google-authfrom google.oauth2 import service_account from googleapiclient.discovery import build SCOPES = ["https://www.googleapis.com/auth/chat.bot"] creds = service_account.Credentials.from_service_account_file( "credentials.json", scopes=SCOPES ) chat = build("chat", "v1", credentials=creds)
스트림 시작
대답 스트리밍을 시작하려면 spaces.streamMessages.create를 호출하고 요청 본문에 StreamMessage를 전달합니다.
referencedMessage(필수): 응답을 트리거한 메시지의 리소스 이름으로,spaces/{space}/messages/{message}형식입니다. 참조된 메시지는 동일한 스페이스에 속해야 하며 Chat 앱에서 액세스할 수 있어야 합니다. Chat은referencedMessage와 동일한 대화목록에 스트리밍된 답장을 게시합니다.progressText(필수): 진행률 표시기 옆에 표시되는 비어 있지 않은 일반 텍스트 상태 라벨(최대 250자, 영문 기준)입니다(예:"Thinking...").markupSyntax(선택사항): 메시지의 텍스트 형식 문법(예:MARKUP_SYNTAX_MARKDOWN또는MARKUP_SYNTAX_CHAT)(MARKUP_SYNTAX_UNSPECIFIED인 경우 Chat 앱의 구성된 문법으로 기본 설정됨). 인라인 인용을 포함하려면MARKUP_SYNTAX_MARKDOWN를 설정합니다.streamContentChunks(선택사항): 즉시 표시할 초기text,step또는citation청크입니다.
후속 호출을 위해 응답에서 name(spaces/{space}/streamMessages/{stream_message}) 및 nextChunkToken를 모두 저장합니다.
HTTP
POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"referencedMessage": "spaces/SPACE_ID/messages/MESSAGE_ID",
"progressText": "Thinking...",
"markupSyntax": "MARKUP_SYNTAX_MARKDOWN"
}
Python
stream = (
chat.spaces()
.streamMessages()
.create(
parent="spaces/SPACE_ID",
body={
"referencedMessage": "spaces/SPACE_ID/messages/MESSAGE_ID",
"progressText": "Thinking...",
"markupSyntax": "MARKUP_SYNTAX_MARKDOWN",
},
)
.execute()
)
stream_name = stream["name"]
chunk_token = stream["nextChunkToken"]
이 메서드는 StreamMessage 리소스를 반환합니다.
{
"name": "spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID",
"progressText": "Thinking...",
"markupSyntax": "MARKUP_SYNTAX_MARKDOWN",
"nextChunkToken": "INITIAL_CHUNK_TOKEN"
}
콘텐츠 청크 추가
Chat 앱에서 중간 단계 또는 텍스트 토큰을 생성할 때 spaces.streamMessages.appendStreamMessageContent를 호출합니다.
chunkToken에 최신nextChunkToken를 전달합니다. 호출이 성공할 때마다 다음 업데이트 또는 커밋 요청에 전달해야 하는 새nextChunkToken이 반환됩니다.text청크는 이전에 스트리밍된 텍스트에 직접 연결됩니다. 청크 사이에 필요한 경우 공백이나 줄바꿈을 포함합니다.step청크는 메시지에 표시된 중간 단계 목록에 추가됩니다.progressText는 선택사항입니다.progressText를 생략하거나""로 설정하면 Chat은 이전에 설정된progressText를 계속 표시합니다.
HTTP
POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID:appendStreamMessageContent HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"chunkToken": "INITIAL_CHUNK_TOKEN",
"progressText": "Searching documentation...",
"streamContentChunks": [
{
"step": {
"title": "Search knowledge base",
"description": "Found 3 matching articles."
}
},
{
"text": "See <chat-citation data-id=\"c1\">guide</chat-citation>:\n"
},
{
"citation": {
"id": "c1",
"citedSources": [
{
"title": "System Architecture Guide",
"uri": "https://example.com/docs/arch"
}
]
}
}
]
}
Python
stream = (
chat.spaces()
.streamMessages()
.appendStreamMessageContent(
name=stream_name,
body={
"chunkToken": chunk_token,
"progressText": "Searching documentation...",
"streamContentChunks": [
{
"step": {
"title": "Search knowledge base",
"description": "Found 3 matching articles.",
}
},
{
"text": (
'See <chat-citation data-id="c1">'
"guide</chat-citation>:\n"
)
},
{
"citation": {
"id": "c1",
"citedSources": [
{
"title": "System Architecture Guide",
"uri": "https://example.com/docs/arch",
}
],
}
},
],
},
)
.execute()
)
chunk_token = stream["nextChunkToken"]
스트리밍된 콘텐츠 또는 진행률 텍스트 바꾸기
후처리 또는 안전 필터를 실행한 후와 같이 이전에 스트리밍된 콘텐츠를 다시 작성해야 하거나 청크를 추가하지 않고 progressText만 업데이트해야 하는 경우 spaces.streamMessages.patch를 호출합니다.
updateMask를 업데이트할 필드의 쉼표로 구분된 목록(progressText,streamContentChunks또는progressText,streamContentChunks)으로 설정합니다.StreamMessage요청 본문 내에 최신nextChunkToken를 포함하고 응답에 반환된 새nextChunkToken를 기록합니다.streamContentChunks가updateMask에 있으면 제공된 청크가 이전에 스트리밍된 콘텐츠에 추가되는 대신 대체됩니다.progressText가updateMask에 있으면progressText는 최대 250자(영문 기준)의 비어 있지 않은 일반 텍스트여야 합니다.
HTTP
PATCH https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID?updateMask=progressText,streamContentChunks HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"nextChunkToken": "LATEST_CHUNK_TOKEN",
"progressText": "Formatting response...",
"streamContentChunks": [
{
"text": "Updated response text."
}
]
}
Python
stream = (
chat.spaces()
.streamMessages()
.patch(
name=stream_name,
updateMask="progressText,streamContentChunks",
body={
"nextChunkToken": chunk_token,
"progressText": "Formatting response...",
"streamContentChunks": [
{"text": "Updated response text."}
],
},
)
.execute()
)
chunk_token = stream["nextChunkToken"]
스트림 완료
생성이 완료되면 최신 chunkToken로 spaces.streamMessages.commit를 호출하여 스트림을 영구 Message 리소스로 마무리합니다.
- 커밋 요청에 커밋 전용 청크 유형(
card,accessoryWidget,citedSource)을 포함하여 최종streamContentChunks를 선택적으로 포함할 수 있습니다. 이전 호출에서 비어 있지 않은 콘텐츠를 이미 스트리밍한 경우 커밋할 때
streamContentChunks를 생략할 수 있습니다. 최종 커밋된 메시지는 비워 둘 수 없습니다.
HTTP
POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID:commit HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"chunkToken": "LATEST_CHUNK_TOKEN",
"streamContentChunks": [
{
"text": "All checks passed."
},
{
"citedSource": {
"title": "Deployment Runbook",
"uri": "https://example.com/docs/runbook"
}
},
{
"card": {
"cardId": "details-card",
"card": {
"header": {
"title": "Build Status"
},
"sections": [
{
"widgets": [
{
"textParagraph": {
"text": "All 42 checks succeeded."
}
}
]
}
]
}
}
}
]
}
Python
status_card = {
"cardId": "details-card",
"card": {
"header": {"title": "Build Status"},
"sections": [
{
"widgets": [
{"textParagraph": {"text": "All 42 checks succeeded."}}
]
}
],
},
}
commit_response = (
chat.spaces()
.streamMessages()
.commit(
name=stream_name,
body={
"chunkToken": chunk_token,
"streamContentChunks": [
{"text": "All checks passed."},
{
"citedSource": {
"title": "Deployment Runbook",
"uri": "https://example.com/docs/runbook",
}
},
{"card": status_card},
],
},
)
.execute()
)
final_message = commit_response["message"]
print(f"Committed message: {final_message['name']}")
활성 스트림 삭제
복구 불가 오류가 발생하거나 사용자가 생성을 취소하고 대화목록에 부분 답장을 남기지 않으려면 spaces.streamMessages.delete를 호출합니다.
HTTP
DELETE https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Python
chat.spaces().streamMessages().delete(name=stream_name).execute()
스트림이 Message에 커밋되면 완료된 메시지를 삭제해야 하는 경우 대신 spaces.messages.delete를 호출합니다.
지원되는 콘텐츠 청크 유형
streamContentChunks의 각 항목은 StreamContentChunk 공용체의 한 필드를 지정합니다. 일부 청크 유형은 스트림 전체에서 전송될 수 있지만 다른 청크 유형은 spaces.streamMessages.commit에서만 허용됩니다.
| 청크 필드 | 지원되는 메서드 | 최종 Message 필드 |
참고 |
|---|---|---|---|
text |
create, appendStreamMessageContent,
patch, commit |
text |
증분 텍스트 appendStreamMessageContent에 연결되고 patch에 대체되었습니다.
최종 커밋된 텍스트는 32,000바이트를 초과할 수 없습니다. |
step |
create, appendStreamMessageContent,
patch, commit |
elements.steps.steps[] |
일반 텍스트 title(필수) 및 선택사항 description를 사용하는 중간 처리 단계입니다. |
citation |
create, appendStreamMessageContent,
patch, commit |
elements.citations[] |
영숫자 id(63자(영문 기준) 미만, 밑줄 허용) 및 citedSources가 있는 인라인 Citation text에 markupSyntax: "MARKUP_SYNTAX_MARKDOWN" 및 일치하는 <chat-citation data-id="ID">...</chat-citation> 태그가 필요합니다. 기존 id가 있는 citation를 전송하면 이전 인용을 덮어씁니다. |
card |
commit만 지원 |
cardsV2[] |
대화형 또는 정보 제공용
CardWithId 카드 |
accessoryWidget |
commit만 지원 |
accessoryWidgets[] |
AccessoryWidget 메시지 아래에 표시되는 버튼(예: 의견 버튼) |
citedSource |
commit만 지원 |
elements.citedSources[] |
CitedSource 바닥글 링크는 인라인 인용 태그 없이 메시지 아래에 표시됩니다. |
사용자가 시작한 스트림 취소 처리
스트림이 활성 상태인 동안에는 스트리밍 중인 참조된 메시지(referencedMessage)의 발신자만 스트리밍 메시지에서 중지를 클릭할 수 있습니다. 사용자가 스트림을 중지하면 Chat은 cancelStream.streamMessage(중지된 스트림의 spaces/{space}/streamMessages/{stream_message} 리소스 이름)가 포함된 시스템 명령어 상호작용 이벤트를 Chat 앱 엔드포인트로 전송합니다.
수신 JSON 페이로드의 구조는 Chat 앱을 Google Workspace 부가기능으로 빌드하는지 아니면 Chat API 상호작용 이벤트를 사용하여 빌드하는지에 따라 달라집니다.
Google Workspace 부가기능
{
"commonEventObject": {
"hostApp": "CHAT"
},
"chat": {
"user": {
"name": "users/USER_ID"
},
"space": {
"name": "spaces/SPACE_ID"
},
"systemCommandPayload": {
"systemCommandMetadata": {
"cancelStream": {
"streamMessage": "spaces/SPACE_ID/streamMessages/STREAM_ID"
}
}
}
}
}
Google Chat API
{
"type": "SYSTEM_COMMAND",
"space": {
"name": "spaces/SPACE_ID"
},
"user": {
"name": "users/USER_ID"
},
"systemCommandMetadata": {
"cancelStream": {
"streamMessage": "spaces/SPACE_ID/streamMessages/STREAM_ID"
}
}
}
Chat 앱이 cancelStream 이벤트를 수신하는 경우:
- 백그라운드 작업자에게
streamMessage에 대한 추가 토큰 생성을 중지하거나 다운스트림 도구 실행을 중지하라는 신호를 보냅니다. - 다음 방법 중 하나를 사용하여 활성 스트림을 해결합니다.
- 부분 출력 유지: 최신
chunkToken로spaces.streamMessages.commit를 호출합니다(선택적으로"\n\n_Generation stopped._"와 같은 메모를 추가). - 답장 삭제: 대화목록에 아무것도 저장되지 않도록
spaces.streamMessages.delete를 호출합니다.
- 부분 출력 유지: 최신
- 상호작용 이벤트 핸들러에서 빈 HTTP
200 OKJSON 응답({})을 반환합니다.
제한사항
메시지 스트리밍에는 다음과 같은 제한사항이 있습니다.
- 플랫폼 가용성: 메시지 스트리밍은 웹의 Chat에서만 표시됩니다. Android 및 iOS 지원은 곧 출시될 예정입니다.
- 스트림 제한 시간: 활성
StreamMessage은 생성 후 10분이 지나면 타임아웃됩니다. Chat 앱은 스트림을 시작한 후 10분 이내에spaces.streamMessages.commit또는spaces.streamMessages.delete를 호출해야 합니다.