跳至內容

嵌入 Copilot SDK

本導覽說明 FastAPI 應用程式如何嵌入 GitHub Copilot SDK,並將 Copilot 工作階段轉換為應用程式服務。您將了解應用程式如何啟動 SDK 用戶端、建立串流工作階段、接聽工作階段事件、將回呼銜接至非同步產生器,以及避免使用過時的模型目錄。

SDK 的使用位置

主要整合點是 app/services/copilot_chat.py。它使用下列方式匯入 SDK:

from copilot import CopilotClient, SessionEvent, SessionEventType

PyPI 套件為 github-copilot-sdk,版本固定為 1.0.9,設定於 pyproject.toml,但匯入根命名空間是 copilot

requires-python = ">=3.11"
dependencies = [
    "github-copilot-sdk==1.0.9",

需要 Python 3.11 或更新版本。

單一長效服務

app/main.py 會建立一個在 FastAPI 應用程式生命週期內持續存在的聊天服務:

app.state.chat_service = CopilotChatService()

此服務採延遲連線。 ensure_started()asyncio.Lock保護,因此並行 HTTP 要求不會競相啟動兩個傳輸連線:

self._lock = asyncio.Lock()
async with self._lock:
    if self._is_started and self._client is not None:
        return

啟動與關閉程序皆明確執行:

self._client = CopilotClient()
await self._client.start()
self._is_started = True
if self._client is not None:
    await self._client.stop()

SDK 也支援 async with CopilotClient(),適合短期執行的指令碼;實作範例會在用戶端僅存續一個命令時採用這種形式。

建立串流工作階段

CopilotChatService.chat_stream() 會為每個提示建立新的 SDK 工作階段:

session = await self._client.create_session(
    model=model,
    streaming=True,
    system_message=(
        {"mode": "append", "content": system_message} if system_message else None
    ),
)

create_session(...) 僅接受關鍵字引數。重要參數包括 modelstreamingsystem_messagetoolsmcp_serverssession_id,以及 on_permission_request

system_message 是 TypedDict 聯集型別:

{"mode": "append", "content": "..."}
{"mode": "replace", "content": "..."}

此示範使用附加模式,以便加入應用程式內容,而不取代 Copilot 的基礎行為。

⚠️ Python 自訂工具需要 on_permission_request,否則呼叫會遭拒絕。對於相同的簡易工具流程,.NET 範例不需要處理常式。Python 也不需要 GHCP001 實驗性 API 抑制設定,即可使用來自 copilot.rpc 的權限決策。

工作階段事件

核心架構重點:Python SDK 事件是 僅限推送的回呼session.on(handler) 會註冊處理常式並傳回可呼叫的取消訂閱函式;它不提供非同步迭代器。

與 C# SDK 不同,Python 僅公開 一個 SessionEvent 資料類別。您要依據 evt.type(一個 SessionEventType 列舉)進行分支,而不是為每種事件比對一個子類別。以下是實際的處理常式:

def on_event(evt: SessionEvent) -> None:
    # Unlike .NET, every event arrives as one SessionEvent
    # carrying a `type` enum and a `data` payload, so this
    # dispatches on `evt.type` rather than on subclasses.
    if evt.type is SessionEventType.ASSISTANT_MESSAGE_DELTA:
        queue.put_nowait(evt.data.delta_content or "")
    elif evt.type is SessionEventType.ASSISTANT_MESSAGE:
        logger.info(
            "Assistant response complete: %d chars",
            len(evt.data.content or ""),
        )
    elif evt.type is SessionEventType.SESSION_IDLE:
        if not done.done():
            done.set_result(None)
    elif evt.type is SessionEventType.SESSION_ERROR:
        logger.error("Session error: %s", evt.data.message)
        if not done.done():
            done.set_exception(RuntimeError(evt.data.message))

ASSISTANT_MESSAGE_DELTA 會攜帶串流文字, ASSISTANT_MESSAGE 代表完整回答, SESSION_IDLE 會完成本輪等候,而 SESSION_ERROR 則會回報失敗。

將回呼銜接至非同步產生器

FastAPI 串流需要非同步迭代器,但 SDK 呼叫的是處理常式。此服務會將回呼銜接至 asyncio.Queue,再由 chat_stream() 取出內容:

queue: asyncio.Queue[object] = asyncio.Queue()
loop = asyncio.get_running_loop()
item = await queue.get()
if item is _DONE:
    break
if isinstance(item, BaseException):
    raise item
yield item  # type: ignore[misc]

_DONE 是哨兵物件,而不是內容值:

# Sentinel pushed onto the queue when the session goes idle.
_DONE = object()
queue.put_nowait(_DONE)

之所以需要佇列,是因為事件回呼無法對 HTTP 回應 yield。這直接對應到 C# 服務寫入 System.Threading.Channels 通道的做法。

重新連線行為

如果 SDK 傳輸連線中斷,服務會將用戶端標示為狀態異常,並透過佇列傳遞例外狀況:

except (ConnectionError, OSError) as ex:
    logger.warning("Copilot connection lost: %s", ex)
    self._is_started = False
    queue.put_nowait(ex)

目前的要求會失敗,而下一個要求會再次呼叫 ensure_started()。由於 _is_started 為 false,因此會停止舊用戶端並建立新的傳輸連線。

列出目前可用的模型

CopilotChatService.list_models() 會向已連線的 Copilot CLI 查詢登入帳戶可使用哪些模型:

models = await self._client.list_models()
return [(m.id, m.name or m.id) for m in models or [] if m.id]

SDK 會傳回 ModelInfo 物件,其中包含 .id.name。將模型 ID 寫死會造成問題,因為可用性會因帳戶和推出階段而異。

目前有一個已知的上游錯誤: list_models() 可能引發 ValueError: Missing required field 'multiplier' in ModelBillinggithub/copilot-sdk#1302)。 app/routers/chat.py 會廣泛攔截例外狀況,並改用包含六個模型的靜態目錄:

except Exception:
    # Includes the known SDK issue where ModelBilling is missing the
    # required 'multiplier' field: github/copilot-sdk#1302.
    logger.warning(
        "Could not list models from Copilot CLI; using static catalog", exc_info=True
    )

return list(AVAILABLE_MODELS.values())

如此一來,即使即時探索功能暫時失效,模型選擇器仍可使用。