Skip to content

Add optional Copilot SDK completion provider - #2616

Open
Chris Trevino (darthtrevino) wants to merge 2 commits into
microsoft:mainfrom
darthtrevino:chtrevin-microsoft-copilot-sdk-llm-provider
Open

Chris Trevino (darthtrevino) wants to merge 2 commits into
microsoft:mainfrom
darthtrevino:chtrevin-microsoft-copilot-sdk-llm-provider

Conversation

@darthtrevino

@darthtrevino Chris Trevino (darthtrevino) commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Description

Add a built-in copilot completion provider to the standalone graphrag-llm
package. Consumers can use models available to their GitHub Copilot account
through the official Python SDK without configuring direct OpenAI/Azure
endpoints.

This adapts the provider idea from BenchmarkQED to GraphRAG's current completion
abstraction and registration mechanism. There are no provider-specific branches
in indexing, querying, or downstream applications. The SDK is an optional
graphrag-llm[copilot] dependency for Python 3.11+; it is also a development
dependency so CI can exercise real SDK types and signatures.

Related Issues

Prior contribution and motivation:
microsoft/benchmark-qed#141.
This PR does not modify BenchmarkQED or close that contribution.

Proposed Changes

  • Register copilot through the existing completion factory, retaining custom
    registration overrides and CLI/library configuration via completion_models.
  • Implement synchronous and asynchronous text completions, real SDK delta
    streaming, typed output-schema requests through the SDK with strict-schema
    normalization, and local Pydantic validation of generated responses.
  • Integrate caching, retry/rate-limit middleware and non-streaming usage metrics.
    Isolate cache entries by provider/model and restore concrete structured
    response types on cache hits.
  • Give each uncached request its own client and tool-free session; validate model
    availability; disable repository discovery, instructions, hooks, skills, memory
    and cross-session storage. Abort incomplete work, unsubscribe, disconnect,
    delete the request's session and stop the client on failure/cancellation/explicit
    stream closure.
  • Translate supplied system/developer messages and user/assistant history
    without retaining conversations between calls. Reject unsupported arguments
    instead of silently ignoring them.
  • Add focused contract tests, configuration and standalone usage documentation,
    an optional dependency/lock entry, and a minor release note. Existing
    dependency versions and lockfile formatting are preserved.

Checklist

  • I have tested these changes locally.
  • I have reviewed the code changes.
  • I have updated the documentation (if necessary).
  • I have added appropriate unit tests (if applicable).

Additional Notes

Validation

  • uv run --no-sync pytest tests\unit\language_model tests\unit\config tests\unit\graphrag_factory -q
    -- passed, 64 tests.
  • uv run --no-sync poe check -- passed: formatting, Ruff and whole-repository
    Pyright (0 errors).
  • uv build --package graphrag-llm -- passed for the wheel and source
    distribution; wheel metadata confirms SDK installation is conditional on the
    copilot extra and Python >=3.11.
  • uv lock --check -- passed.
  • git diff --check -- passed.
  • Live SDK smoke scripts (uv run --no-sync python -) with SDK 1.0.26, model
    gpt-6.1-sol, and a compatible installed CLI supplied via cli_path --
    passed for synchronous typed structured output and asynchronous streaming
    with supplied conversation history; the final streaming check also asserted no
    event-loop callback errors. The live typed-output test used a simple
    Answer model containing one integer field (answer=42), on one model/CLI
    combination. It was not advanced-schema or cross-model compatibility coverage;
    nested/nullable/default-field normalization was exercised in unit tests, not
    independently verified against the live backend.
  • These were real authenticated Copilot SDK/CLI requests, with no BYOK endpoint
    configured. CAPI routing is inferred from that default Copilot path; the
    underlying HTTP endpoint/backend was not independently traced, and no direct
    CAPI HTTP integration test was run.
  • The default SDK runtime 1.0.103 returned the expected JSON object, but a
    stricter smoke test failed because the SDK logs an upstream event-decoding
    AssertionError for session.indexed_search /
    disabledReason=server_limit. This is documented, not suppressed or patched.
    Selecting the compatible installed CLI through cli_path avoided that error.
  • Full test suite and a complete GraphRAG indexing/query evaluation were not
    run
    .

JSON output behavior and caveats

There are two distinct output paths; they should not both be described as
native JSON mode:

Typed output: response_format=SomePydanticModel. The adapter derives the
model's JSON Schema, normalizes it using OpenAI's public
pydantic_function_tool helper, and sends the resulting output schema through
the Copilot SDK's send_and_wait(response_schema=...). This is a constraint
requested for the model's generated response, not validation of the user's input
prompt. Using this schema-conversion helper does not enable tool execution.

The adapter also parses and validates the returned text against the original
Pydantic model before caching a successful response, and restores the
concrete Pydantic response type after cache lookup. Malformed JSON or Pydantic
validation failures raise errors rather than being repaired or cached as
successful structured output.

OpenAI-compatible strict output schemas expect closed objects
(additionalProperties: false) and all declared object properties to be listed
as required. Normalization can therefore make a field required in the generated
output even if the Pydantic model allows it to be omitted because it has a
default. A nullable field (T | None) can contain JSON null; nullable does
not mean omittable
in the strict output schema. Pydantic defaults are not a
promise that the backend will fill missing output properties.

The backend/model/runtime supports only its own schema subset. Normalization is
not a guarantee that arbitrary Pydantic models or arbitrary JSON Schema
constructs will be accepted: for example, open-ended object/map shapes or
incompatible schema keywords may be rejected. Unsupported schemas can fail at
the endpoint, and returned content can still fail local validation. Schema
conformance validates output structure/types, not factual correctness.
The live smoke test establishes only the simple-schema case described above.

This adapter rejects stream=True together with typed response_format,
consistent with its existing completion interface. This is an adapter
restriction
, not a claim that the Copilot SDK universally cannot stream
structured responses.

Unconstrained object output: response_format_json_object=True. When no
typed response_format is supplied, the adapter adds a JSON-only instruction to
the system prompt. It does not send a native JSON-object mode or a strict
output schema to the SDK for this path. Non-streaming output is parsed with
json.loads and must be a JSON object; malformed JSON or a non-object value
raises an error. There is no JSON repair or Markdown-code-fence stripping.
If used with streaming, the JSON instruction still applies, but chunks are
returned without final assembled-JSON parsing or object validation.

Capabilities and limitations

  • This is a new feature proposal in a repository whose contribution guide says
    it is in maintenance mode; maintainer acceptance is not assumed.
  • Each cache miss starts a client/runtime. This keeps lifecycle ownership and
    event-loop isolation straightforward, but adds startup overhead for large
    workloads.
  • JSON behavior differs between typed SDK output-schema requests and
    prompt-only JSON-object requests, as detailed above. Neither path establishes
    factual correctness or universal schema compatibility.
  • History is a role-labelled JSON transcript in a fresh prompt, not native
    role-separated OpenAI history or resumed SDK sessions
    .
  • No embeddings, tools, multimodal inputs, multiple choices, sampling controls or
    generation token-limit controls. GraphRAG DRIFT search is currently
    incompatible
    because it injects unsupported sampling parameters internally;
    use another provider for DRIFT.
  • Streams bypass caching. Standard middleware metrics describe stream creation,
    not successful consumption; streaming token usage is available on the final
    chunk when requested, and iteration failures are not automatically retried.
    Close streams explicitly if stopping early.
  • SDK token usage is not an estimate of Copilot subscription billing.
    Availability, quotas and runtime compatibility depend on the user's Copilot
    environment.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@darthtrevino
Chris Trevino (darthtrevino) marked this pull request as ready for review October 30, 2026 21:06
@darthtrevino
Chris Trevino (darthtrevino) requested a review from a team as a code owner October 30, 2026 21:06
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant