langchain-githubcopilot-chat 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (14) hide show
  1. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/PKG-INFO +4 -5
  2. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/langchain_githubcopilot_chat/chat_models.py +5 -0
  3. langchain_githubcopilot_chat-0.1.2/langchain_githubcopilot_chat/embeddings.py +321 -0
  4. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/pyproject.toml +5 -5
  5. langchain_githubcopilot_chat-0.1.0/langchain_githubcopilot_chat/embeddings.py +0 -96
  6. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/LICENSE +0 -0
  7. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/README.md +0 -0
  8. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/langchain_githubcopilot_chat/__init__.py +0 -0
  9. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/langchain_githubcopilot_chat/document_loaders.py +0 -0
  10. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/langchain_githubcopilot_chat/py.typed +0 -0
  11. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/langchain_githubcopilot_chat/retrievers.py +0 -0
  12. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/langchain_githubcopilot_chat/toolkits.py +0 -0
  13. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/langchain_githubcopilot_chat/tools.py +0 -0
  14. {langchain_githubcopilot_chat-0.1.0 → langchain_githubcopilot_chat-0.1.2}/langchain_githubcopilot_chat/vectorstores.py +0 -0
@@ -1,21 +1,20 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-githubcopilot-chat
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: An integration package connecting GithubcopilotChat and LangChain
5
5
  Home-page: https://github.com/langchain-ai/langchain
6
6
  License: MIT
7
7
  Author: YIhan Wu
8
8
  Author-email: iumm@ibat.ac.cn
9
- Requires-Python: >=3.9,<4.0
9
+ Requires-Python: >=3.10,<4.0
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
12
  Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
16
15
  Classifier: Programming Language :: Python :: 3.13
17
- Requires-Dist: httpx (>=0.24.0)
18
- Requires-Dist: langchain-core (>=0.3.15,<0.4.0)
16
+ Requires-Dist: httpx (>=0.28.1)
17
+ Requires-Dist: langchain-core (>=1.1.0,<2.0.0)
19
18
  Project-URL: Repository, https://github.com/langchain-ai/langchain
20
19
  Project-URL: Release Notes, https://github.com/langchain-ai/langchain/releases?q=tag%3A%22githubcopilot-chat%3D%3D0%22&expanded=true
21
20
  Project-URL: Source Code, https://github.com/langchain-ai/langchain/tree/master/libs/partners/githubcopilot-chat
@@ -444,6 +444,11 @@ class ChatGithubCopilot(BaseChatModel):
444
444
  max_retries: int = 2
445
445
  """Number of automatic retries on transient errors."""
446
446
 
447
+ # ------------------------------------------------------------------
448
+ # Pydantic v2 config — allow the ``model`` alias on construction
449
+ # ------------------------------------------------------------------
450
+ model_config = {"populate_by_name": True}
451
+
447
452
  # ------------------------------------------------------------------
448
453
  # Validators / setup
449
454
  # ------------------------------------------------------------------
@@ -0,0 +1,321 @@
1
+ """GitHub Copilot Chat embeddings integration via GitHub Models Embeddings API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any, Dict, List, Optional, Union
7
+
8
+ import httpx
9
+ from langchain_core.embeddings import Embeddings
10
+ from pydantic import BaseModel, Field, SecretStr, model_validator
11
+
12
+ _GITHUB_MODELS_BASE_URL = "https://models.github.ai"
13
+ _EMBEDDINGS_PATH = "/inference/embeddings"
14
+ _ORG_EMBEDDINGS_PATH = "/orgs/{org}/inference/embeddings"
15
+ _API_VERSION = "2026-03-10"
16
+
17
+
18
+ class GithubcopilotChatEmbeddings(BaseModel, Embeddings):
19
+ """GitHub Copilot Chat embedding model integration via the GitHub Models API.
20
+
21
+ GitHub Models provides access to embedding models (e.g. OpenAI
22
+ ``text-embedding-3-small``, ``text-embedding-3-large``) through a unified
23
+ OpenAI-compatible REST API. This class wraps the ``/inference/embeddings``
24
+ endpoint so that any embedding model available in the GitHub Models catalog
25
+ can be used as a drop-in LangChain ``Embeddings`` implementation.
26
+
27
+ Setup:
28
+ Install ``langchain-githubcopilot-chat`` and set the
29
+ ``GITHUB_TOKEN`` environment variable (a classic or fine-grained PAT
30
+ with the ``models: read`` scope, or a GitHub Copilot subscription token).
31
+
32
+ .. code-block:: bash
33
+
34
+ pip install -U langchain-githubcopilot-chat
35
+ export GITHUB_TOKEN="github_pat_..."
36
+
37
+ Key init args:
38
+ model: str
39
+ Model ID in the ``{publisher}/{model_name}`` format, e.g.
40
+ ``"openai/text-embedding-3-small"``.
41
+ github_token: Optional[SecretStr]
42
+ GitHub token. Falls back to ``GITHUB_TOKEN`` env var.
43
+ base_url: str
44
+ Base URL of the GitHub Models API.
45
+ Defaults to ``"https://models.github.ai"``.
46
+ org: Optional[str]
47
+ Organisation login. When set, requests are attributed to that org.
48
+ api_version: str
49
+ GitHub Models REST API version header value.
50
+ Defaults to ``"2026-03-10"``.
51
+ dimensions: Optional[int]
52
+ The number of dimensions for the output embeddings. Only supported
53
+ by ``text-embedding-3`` and later models.
54
+ encoding_format: str
55
+ The format to return embeddings in. Either ``"float"`` (default)
56
+ or ``"base64"``.
57
+ timeout: Optional[float]
58
+ HTTP request timeout in seconds.
59
+ max_retries: int
60
+ Number of automatic retries on transient errors (default ``2``).
61
+
62
+ Instantiate:
63
+ .. code-block:: python
64
+
65
+ from langchain_githubcopilot_chat import GithubcopilotChatEmbeddings
66
+
67
+ embed = GithubcopilotChatEmbeddings(
68
+ model="openai/text-embedding-3-small",
69
+ # github_token="github_pat_...", # or set GITHUB_TOKEN env var
70
+ )
71
+
72
+ Embed single text:
73
+ .. code-block:: python
74
+
75
+ vector = embed.embed_query("What is the meaning of life?")
76
+ print(len(vector)) # e.g. 1536
77
+
78
+ Embed multiple texts:
79
+ .. code-block:: python
80
+
81
+ vectors = embed.embed_documents(
82
+ ["Document one.", "Document two."]
83
+ )
84
+ print(len(vectors), len(vectors[0]))
85
+
86
+ Async:
87
+ .. code-block:: python
88
+
89
+ vector = await embed.aembed_query("What is the meaning of life?")
90
+
91
+ vectors = await embed.aembed_documents(
92
+ ["Document one.", "Document two."]
93
+ )
94
+ """
95
+
96
+ model_config = {"populate_by_name": True}
97
+
98
+ model_name: str = Field(alias="model")
99
+ """Embedding model ID in the ``{publisher}/{model_name}`` format.
100
+
101
+ Examples: ``"openai/text-embedding-3-small"``,
102
+ ``"openai/text-embedding-3-large"``.
103
+ """
104
+
105
+ github_token: Optional[SecretStr] = Field(default=None)
106
+ """GitHub token with ``models: read`` scope.
107
+
108
+ If not provided, the value of the ``GITHUB_TOKEN`` environment variable
109
+ is used.
110
+ """
111
+
112
+ base_url: str = _GITHUB_MODELS_BASE_URL
113
+ """Base URL for the GitHub Models REST API."""
114
+
115
+ org: Optional[str] = None
116
+ """Organisation login for attributed inference requests.
117
+
118
+ When set, requests are sent to
119
+ ``/orgs/{org}/inference/embeddings`` instead of ``/inference/embeddings``.
120
+ """
121
+
122
+ api_version: str = _API_VERSION
123
+ """GitHub Models API version sent as the ``X-GitHub-Api-Version`` header."""
124
+
125
+ dimensions: Optional[int] = None
126
+ """Number of output embedding dimensions.
127
+
128
+ Only supported by ``text-embedding-3`` and later models.
129
+ """
130
+
131
+ encoding_format: str = "float"
132
+ """Format of the returned embeddings. Either ``"float"`` or ``"base64"``."""
133
+
134
+ timeout: Optional[float] = None
135
+ """HTTP request timeout in seconds."""
136
+
137
+ max_retries: int = 2
138
+ """Number of automatic retries on transient errors."""
139
+
140
+ # ------------------------------------------------------------------
141
+ # Validators / setup
142
+ # ------------------------------------------------------------------
143
+
144
+ @model_validator(mode="before")
145
+ @classmethod
146
+ def _resolve_token(cls, values: Dict[str, Any]) -> Dict[str, Any]:
147
+ """Resolve the GitHub token from the environment if not supplied."""
148
+ token = values.get("github_token") or values.get("api_key")
149
+ if not token:
150
+ token = os.environ.get("GITHUB_TOKEN")
151
+ if token:
152
+ values["github_token"] = token
153
+ return values
154
+
155
+ # ------------------------------------------------------------------
156
+ # Internal helpers
157
+ # ------------------------------------------------------------------
158
+
159
+ @property
160
+ def _token(self) -> str:
161
+ """Return the raw GitHub token string."""
162
+ if self.github_token:
163
+ return self.github_token.get_secret_value()
164
+ env_token = os.environ.get("GITHUB_TOKEN", "")
165
+ if not env_token:
166
+ raise ValueError(
167
+ "A GitHub token is required. Set the GITHUB_TOKEN environment "
168
+ "variable or pass ``github_token`` when instantiating "
169
+ "GithubcopilotChatEmbeddings."
170
+ )
171
+ return env_token
172
+
173
+ @property
174
+ def _embeddings_url(self) -> str:
175
+ """Return the full embeddings endpoint URL."""
176
+ if self.org:
177
+ path = _ORG_EMBEDDINGS_PATH.format(org=self.org)
178
+ else:
179
+ path = _EMBEDDINGS_PATH
180
+ return self.base_url.rstrip("/") + path
181
+
182
+ def _build_headers(self) -> Dict[str, str]:
183
+ return {
184
+ "Authorization": f"Bearer {self._token}",
185
+ "Accept": "application/vnd.github+json",
186
+ "Content-Type": "application/json",
187
+ "X-GitHub-Api-Version": self.api_version,
188
+ }
189
+
190
+ def _build_payload(self, input: Union[str, List[str]]) -> Dict[str, Any]:
191
+ """Assemble the JSON body for the embeddings API."""
192
+ payload: Dict[str, Any] = {
193
+ "model": self.model_name,
194
+ "input": input,
195
+ "encoding_format": self.encoding_format,
196
+ }
197
+ if self.dimensions is not None:
198
+ payload["dimensions"] = self.dimensions
199
+ return payload
200
+
201
+ def _do_request(self, payload: Dict[str, Any]) -> Dict[str, Any]:
202
+ """Perform a synchronous HTTP POST with retries."""
203
+ headers = self._build_headers()
204
+ last_exc: Optional[Exception] = None
205
+ for attempt in range(self.max_retries + 1):
206
+ try:
207
+ response = httpx.post(
208
+ self._embeddings_url,
209
+ headers=headers,
210
+ json=payload,
211
+ timeout=self.timeout,
212
+ )
213
+ response.raise_for_status()
214
+ return response.json()
215
+ except (httpx.TimeoutException, httpx.TransportError) as exc:
216
+ last_exc = exc
217
+ if attempt == self.max_retries:
218
+ raise
219
+ except httpx.HTTPStatusError as exc:
220
+ if exc.response.status_code < 500:
221
+ raise
222
+ last_exc = exc
223
+ if attempt == self.max_retries:
224
+ raise
225
+ raise RuntimeError("Unexpected retry loop exit") from last_exc
226
+
227
+ async def _do_request_async(self, payload: Dict[str, Any]) -> Dict[str, Any]:
228
+ """Perform an asynchronous HTTP POST with retries."""
229
+ headers = self._build_headers()
230
+ last_exc: Optional[Exception] = None
231
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
232
+ for attempt in range(self.max_retries + 1):
233
+ try:
234
+ response = await client.post(
235
+ self._embeddings_url,
236
+ headers=headers,
237
+ json=payload,
238
+ )
239
+ response.raise_for_status()
240
+ return response.json()
241
+ except (httpx.TimeoutException, httpx.TransportError) as exc:
242
+ last_exc = exc
243
+ if attempt == self.max_retries:
244
+ raise
245
+ except httpx.HTTPStatusError as exc:
246
+ if exc.response.status_code < 500:
247
+ raise
248
+ last_exc = exc
249
+ if attempt == self.max_retries:
250
+ raise
251
+ raise RuntimeError("Unexpected retry loop exit") from last_exc
252
+
253
+ @staticmethod
254
+ def _extract_embeddings(response_data: Dict[str, Any]) -> List[List[float]]:
255
+ """Extract the list of embedding vectors from an API response."""
256
+ data = response_data.get("data", [])
257
+ if not data:
258
+ raise ValueError(
259
+ f"GitHub Models Embeddings API returned no data. "
260
+ f"Response: {response_data}"
261
+ )
262
+ # Sort by index to preserve input order (the API may reorder items)
263
+ sorted_data = sorted(data, key=lambda x: x.get("index", 0))
264
+ return [item["embedding"] for item in sorted_data]
265
+
266
+ # ------------------------------------------------------------------
267
+ # LangChain Embeddings interface
268
+ # ------------------------------------------------------------------
269
+
270
+ def embed_documents(self, texts: List[str]) -> List[List[float]]:
271
+ """Embed a list of documents using the GitHub Models Embeddings API.
272
+
273
+ Args:
274
+ texts: The list of texts to embed.
275
+
276
+ Returns:
277
+ A list of embedding vectors, one per input text.
278
+ """
279
+ if not texts:
280
+ return []
281
+ payload = self._build_payload(texts)
282
+ response_data = self._do_request(payload)
283
+ return self._extract_embeddings(response_data)
284
+
285
+ def embed_query(self, text: str) -> List[float]:
286
+ """Embed a single query text using the GitHub Models Embeddings API.
287
+
288
+ Args:
289
+ text: The text to embed.
290
+
291
+ Returns:
292
+ An embedding vector.
293
+ """
294
+ return self.embed_documents([text])[0]
295
+
296
+ async def aembed_documents(self, texts: List[str]) -> List[List[float]]:
297
+ """Asynchronously embed a list of documents.
298
+
299
+ Args:
300
+ texts: The list of texts to embed.
301
+
302
+ Returns:
303
+ A list of embedding vectors, one per input text.
304
+ """
305
+ if not texts:
306
+ return []
307
+ payload = self._build_payload(texts)
308
+ response_data = await self._do_request_async(payload)
309
+ return self._extract_embeddings(response_data)
310
+
311
+ async def aembed_query(self, text: str) -> List[float]:
312
+ """Asynchronously embed a single query text.
313
+
314
+ Args:
315
+ text: The text to embed.
316
+
317
+ Returns:
318
+ An embedding vector.
319
+ """
320
+ results = await self.aembed_documents([text])
321
+ return results[0]
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "langchain-githubcopilot-chat"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "An integration package connecting GithubcopilotChat and LangChain"
9
9
  authors = ["YIhan Wu <iumm@ibat.ac.cn>"]
10
10
  readme = "README.md"
@@ -19,9 +19,9 @@ disallow_untyped_defs = "True"
19
19
  "Release Notes" = "https://github.com/langchain-ai/langchain/releases?q=tag%3A%22githubcopilot-chat%3D%3D0%22&expanded=true"
20
20
 
21
21
  [tool.poetry.dependencies]
22
- python = ">=3.9,<4.0"
23
- langchain-core = "^0.3.15"
24
- httpx = ">=0.24.0"
22
+ python = ">=3.10,<4.0"
23
+ langchain-core = ">=1.1.0,<2.0.0"
24
+ httpx = ">=0.28.1"
25
25
 
26
26
  [tool.ruff.lint]
27
27
  select = ["E", "F", "I", "T201"]
@@ -59,7 +59,7 @@ pytest = "^7.4.3"
59
59
  pytest-asyncio = "^0.23.2"
60
60
  pytest-socket = "^0.7.0"
61
61
  pytest-watcher = "^0.3.4"
62
- langchain-tests = "^0.3.5"
62
+ langchain-tests = "^1.1.5"
63
63
 
64
64
  [tool.poetry.group.codespell.dependencies]
65
65
  codespell = "^2.2.6"
@@ -1,96 +0,0 @@
1
- from typing import List
2
-
3
- from langchain_core.embeddings import Embeddings
4
-
5
-
6
- class GithubcopilotChatEmbeddings(Embeddings):
7
- """GithubcopilotChat embedding model integration.
8
-
9
- # TODO: Replace with relevant packages, env vars.
10
- Setup:
11
- Install ``langchain-githubcopilot-chat`` and set environment variable
12
- ``GITHUBCOPILOTCHAT_API_KEY``.
13
-
14
- .. code-block:: bash
15
-
16
- pip install -U langchain-githubcopilot-chat
17
- export GITHUBCOPILOTCHAT_API_KEY="your-api-key"
18
-
19
- # TODO: Populate with relevant params.
20
- Key init args — completion params:
21
- model: str
22
- Name of GithubcopilotChat model to use.
23
-
24
- See full list of supported init args and their descriptions in the params section.
25
-
26
- # TODO: Replace with relevant init params.
27
- Instantiate:
28
- .. code-block:: python
29
-
30
- from langchain_githubcopilot_chat import GithubcopilotChatEmbeddings
31
-
32
- embed = GithubcopilotChatEmbeddings(
33
- model="...",
34
- # api_key="...",
35
- # other params...
36
- )
37
-
38
- Embed single text:
39
- .. code-block:: python
40
-
41
- input_text = "The meaning of life is 42"
42
- embed.embed_query(input_text)
43
-
44
- .. code-block:: python
45
-
46
- # TODO: Example output.
47
-
48
- # TODO: Delete if token-level streaming isn't supported.
49
- Embed multiple text:
50
- .. code-block:: python
51
-
52
- input_texts = ["Document 1...", "Document 2..."]
53
- embed.embed_documents(input_texts)
54
-
55
- .. code-block:: python
56
-
57
- # TODO: Example output.
58
-
59
- # TODO: Delete if native async isn't supported.
60
- Async:
61
- .. code-block:: python
62
-
63
- await embed.aembed_query(input_text)
64
-
65
- # multiple:
66
- # await embed.aembed_documents(input_texts)
67
-
68
- .. code-block:: python
69
-
70
- # TODO: Example output.
71
-
72
- """
73
-
74
- def __init__(self, model: str):
75
- self.model = model
76
-
77
- def embed_documents(self, texts: List[str]) -> List[List[float]]:
78
- """Embed search docs."""
79
- return [[0.5, 0.6, 0.7] for _ in texts]
80
-
81
- def embed_query(self, text: str) -> List[float]:
82
- """Embed query text."""
83
- return self.embed_documents([text])[0]
84
-
85
- # optional: add custom async implementations here
86
- # you can also delete these, and the base class will
87
- # use the default implementation, which calls the sync
88
- # version in an async executor:
89
-
90
- # async def aembed_documents(self, texts: List[str]) -> List[List[float]]:
91
- # """Asynchronous Embed search docs."""
92
- # ...
93
-
94
- # async def aembed_query(self, text: str) -> List[float]:
95
- # """Asynchronous Embed query text."""
96
- # ...