langchain-llama-cpp 0.1.0__tar.gz → 0.2.0__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.
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/PKG-INFO +41 -10
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/README.md +40 -9
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/pyproject.toml +1 -1
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/pyproject.toml.orig +1 -1
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/_wire.py +8 -5
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/chat_models.py +136 -6
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/LICENSE +0 -0
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/__init__.py +0 -0
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/content_blocks.py +0 -0
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/exceptions.py +0 -0
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/py.typed +0 -0
- {langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-llama-cpp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: An integration package connecting llama.cpp's llama-server and LangChain, with reasoning, timings, structured output and tool calling
|
|
5
5
|
Keywords: langchain,llama.cpp,llama-server,llama-swap,llm,local-llm,chat-model
|
|
6
6
|
Author: Bharathan Mudaliar
|
|
@@ -54,8 +54,12 @@ or `uv add langchain-llama-cpp`. Python 3.10+.
|
|
|
54
54
|
|
|
55
55
|
- **llama-swap** in front of llama-server. `model` is the llama-swap entry to
|
|
56
56
|
use. A bare llama-server (no llama-swap) is not tested yet.
|
|
57
|
-
-
|
|
58
|
-
langchain-core 1.
|
|
57
|
+
- Live integration tests: gemma 4, thinking off and on. Last full run on
|
|
58
|
+
llama.cpp v0.9.0 (build b12110) with langchain-core 1.7.7 (2026-11-10).
|
|
59
|
+
Qwen3.6 and gpt-oss were tested on llama-server b11429 with langchain-core
|
|
60
|
+
1.6, and are no longer in the suite.
|
|
61
|
+
- Request fields and reply shapes follow the llama.cpp source of the latest
|
|
62
|
+
release, **v0.9.0** (build b12110).
|
|
59
63
|
|
|
60
64
|
## Quick start
|
|
61
65
|
|
|
@@ -164,9 +168,35 @@ sees the schema's field descriptions: asked for an age described as "in months",
|
|
|
164
168
|
it fills in years. Pass `include_format_instructions=True` with `json_schema` or
|
|
165
169
|
`json_mode` to add the schema to the system message.
|
|
166
170
|
|
|
171
|
+
### Model profile
|
|
172
|
+
|
|
173
|
+
At construction, `profile` is filled from the server's `/props`
|
|
174
|
+
(`/upstream/<model>/props` behind llama-swap, which starts the entry; `/props`
|
|
175
|
+
on a plain llama-server):
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
ChatLlama(model="gemma").profile
|
|
179
|
+
# {'text_inputs': True, 'max_input_tokens': 262144, 'image_inputs': False,
|
|
180
|
+
# 'audio_inputs': False, 'video_inputs': False, 'tool_calling': True}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`max_input_tokens` is the per-slot context window (`n_ctx`), which LangChain and
|
|
184
|
+
Deep Agents use to size things such as summarization. It's fetched once per
|
|
185
|
+
`(base_url, model)` per process. If the server can't be reached, `profile` stays
|
|
186
|
+
`None`. Pass `profile=` to set your own, or `fetch_profile=False` to skip the
|
|
187
|
+
request. The request uses a separate client, not your `http_client`, which is
|
|
188
|
+
used only for chat requests.
|
|
189
|
+
|
|
190
|
+
`structured_output` is deliberately not set, so `create_agent` uses
|
|
191
|
+
`ToolStrategy` for a `response_format`. llama-server can't combine `tools` with
|
|
192
|
+
a `json_schema` `response_format`. The response-format grammar replaces the
|
|
193
|
+
tool-call grammar, so the model answers in JSON straight away and never calls a
|
|
194
|
+
tool. With Qwen-style templates in v0.9.0 the request fails instead, with
|
|
195
|
+
"failed to parse grammar".
|
|
196
|
+
|
|
167
197
|
### Request parameters
|
|
168
198
|
|
|
169
|
-
Every field `/v1/chat/completions` accepts in
|
|
199
|
+
Every field `/v1/chat/completions` accepts in v0.9.0 is a typed parameter:
|
|
170
200
|
`temperature`, `top_k`, `top_p`, `min_p`, `samplers`, `repeat_penalty`, the DRY
|
|
171
201
|
and XTC samplers, `grammar`, `json_schema`, `reasoning_budget_tokens`,
|
|
172
202
|
`cache_prompt`, `id_slot`, `lora` and more. Set them on the model or per call:
|
|
@@ -213,13 +243,14 @@ seconds and defaults to `None` (no timeout), since local generation can be slow.
|
|
|
213
243
|
models lets the model write text before the call and won't let it stop
|
|
214
244
|
without one. A model that doesn't want a tool keeps writing until
|
|
215
245
|
`max_tokens`, and there's no tool call. This is
|
|
216
|
-
[ggml-org/llama.cpp#27217](https://github.com/ggml-org/llama.cpp/issues/27217)
|
|
217
|
-
keep `max_tokens` set.
|
|
246
|
+
[ggml-org/llama.cpp#27217](https://github.com/ggml-org/llama.cpp/issues/27217)
|
|
247
|
+
(closed as stale without a fix); keep `max_tokens` set.
|
|
218
248
|
- **A named `tool_choice`** is sent as `"required"` (see Tool calling).
|
|
219
249
|
- **JSON schema bounds on numbers** (`minimum`, `maximum`, `multipleOf` on
|
|
220
250
|
`number`) aren't enforced by llama-server's grammar. Integer bounds are.
|
|
221
251
|
- **Bare llama-server** (without llama-swap) isn't tested; `get_server_defaults`
|
|
222
|
-
needs llama-swap's `/upstream/<model>/props
|
|
252
|
+
needs llama-swap's `/upstream/<model>/props` (the profile falls back to `/props`).
|
|
253
|
+
- **Tools and `response_format` together** give no tool calls (see Model profile).
|
|
223
254
|
|
|
224
255
|
## Development
|
|
225
256
|
|
|
@@ -232,9 +263,9 @@ uv run pytest tests/unit_tests # offline
|
|
|
232
263
|
uv run ruff check && uv run ruff format --check && uv run pyright
|
|
233
264
|
```
|
|
234
265
|
|
|
235
|
-
The integration tests (LangChain's standard `langchain-tests` suite, on
|
|
236
|
-
|
|
237
|
-
set `LLAMA_BASE_URL`, then:
|
|
266
|
+
The integration tests (LangChain's standard `langchain-tests` suite, on Gemma 4
|
|
267
|
+
with thinking off and on) need a llama-swap server with a `gemma` entry. Copy
|
|
268
|
+
`.env.example` to `.env` and set `LLAMA_BASE_URL`, then:
|
|
238
269
|
|
|
239
270
|
```bash
|
|
240
271
|
uv run --env-file .env pytest tests/integration_tests
|
|
@@ -28,8 +28,12 @@ or `uv add langchain-llama-cpp`. Python 3.10+.
|
|
|
28
28
|
|
|
29
29
|
- **llama-swap** in front of llama-server. `model` is the llama-swap entry to
|
|
30
30
|
use. A bare llama-server (no llama-swap) is not tested yet.
|
|
31
|
-
-
|
|
32
|
-
langchain-core 1.
|
|
31
|
+
- Live integration tests: gemma 4, thinking off and on. Last full run on
|
|
32
|
+
llama.cpp v0.9.0 (build b12110) with langchain-core 1.7.7 (2026-11-10).
|
|
33
|
+
Qwen3.6 and gpt-oss were tested on llama-server b11429 with langchain-core
|
|
34
|
+
1.6, and are no longer in the suite.
|
|
35
|
+
- Request fields and reply shapes follow the llama.cpp source of the latest
|
|
36
|
+
release, **v0.9.0** (build b12110).
|
|
33
37
|
|
|
34
38
|
## Quick start
|
|
35
39
|
|
|
@@ -138,9 +142,35 @@ sees the schema's field descriptions: asked for an age described as "in months",
|
|
|
138
142
|
it fills in years. Pass `include_format_instructions=True` with `json_schema` or
|
|
139
143
|
`json_mode` to add the schema to the system message.
|
|
140
144
|
|
|
145
|
+
### Model profile
|
|
146
|
+
|
|
147
|
+
At construction, `profile` is filled from the server's `/props`
|
|
148
|
+
(`/upstream/<model>/props` behind llama-swap, which starts the entry; `/props`
|
|
149
|
+
on a plain llama-server):
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
ChatLlama(model="gemma").profile
|
|
153
|
+
# {'text_inputs': True, 'max_input_tokens': 262144, 'image_inputs': False,
|
|
154
|
+
# 'audio_inputs': False, 'video_inputs': False, 'tool_calling': True}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`max_input_tokens` is the per-slot context window (`n_ctx`), which LangChain and
|
|
158
|
+
Deep Agents use to size things such as summarization. It's fetched once per
|
|
159
|
+
`(base_url, model)` per process. If the server can't be reached, `profile` stays
|
|
160
|
+
`None`. Pass `profile=` to set your own, or `fetch_profile=False` to skip the
|
|
161
|
+
request. The request uses a separate client, not your `http_client`, which is
|
|
162
|
+
used only for chat requests.
|
|
163
|
+
|
|
164
|
+
`structured_output` is deliberately not set, so `create_agent` uses
|
|
165
|
+
`ToolStrategy` for a `response_format`. llama-server can't combine `tools` with
|
|
166
|
+
a `json_schema` `response_format`. The response-format grammar replaces the
|
|
167
|
+
tool-call grammar, so the model answers in JSON straight away and never calls a
|
|
168
|
+
tool. With Qwen-style templates in v0.9.0 the request fails instead, with
|
|
169
|
+
"failed to parse grammar".
|
|
170
|
+
|
|
141
171
|
### Request parameters
|
|
142
172
|
|
|
143
|
-
Every field `/v1/chat/completions` accepts in
|
|
173
|
+
Every field `/v1/chat/completions` accepts in v0.9.0 is a typed parameter:
|
|
144
174
|
`temperature`, `top_k`, `top_p`, `min_p`, `samplers`, `repeat_penalty`, the DRY
|
|
145
175
|
and XTC samplers, `grammar`, `json_schema`, `reasoning_budget_tokens`,
|
|
146
176
|
`cache_prompt`, `id_slot`, `lora` and more. Set them on the model or per call:
|
|
@@ -187,13 +217,14 @@ seconds and defaults to `None` (no timeout), since local generation can be slow.
|
|
|
187
217
|
models lets the model write text before the call and won't let it stop
|
|
188
218
|
without one. A model that doesn't want a tool keeps writing until
|
|
189
219
|
`max_tokens`, and there's no tool call. This is
|
|
190
|
-
[ggml-org/llama.cpp#27217](https://github.com/ggml-org/llama.cpp/issues/27217)
|
|
191
|
-
keep `max_tokens` set.
|
|
220
|
+
[ggml-org/llama.cpp#27217](https://github.com/ggml-org/llama.cpp/issues/27217)
|
|
221
|
+
(closed as stale without a fix); keep `max_tokens` set.
|
|
192
222
|
- **A named `tool_choice`** is sent as `"required"` (see Tool calling).
|
|
193
223
|
- **JSON schema bounds on numbers** (`minimum`, `maximum`, `multipleOf` on
|
|
194
224
|
`number`) aren't enforced by llama-server's grammar. Integer bounds are.
|
|
195
225
|
- **Bare llama-server** (without llama-swap) isn't tested; `get_server_defaults`
|
|
196
|
-
needs llama-swap's `/upstream/<model>/props
|
|
226
|
+
needs llama-swap's `/upstream/<model>/props` (the profile falls back to `/props`).
|
|
227
|
+
- **Tools and `response_format` together** give no tool calls (see Model profile).
|
|
197
228
|
|
|
198
229
|
## Development
|
|
199
230
|
|
|
@@ -206,9 +237,9 @@ uv run pytest tests/unit_tests # offline
|
|
|
206
237
|
uv run ruff check && uv run ruff format --check && uv run pyright
|
|
207
238
|
```
|
|
208
239
|
|
|
209
|
-
The integration tests (LangChain's standard `langchain-tests` suite, on
|
|
210
|
-
|
|
211
|
-
set `LLAMA_BASE_URL`, then:
|
|
240
|
+
The integration tests (LangChain's standard `langchain-tests` suite, on Gemma 4
|
|
241
|
+
with thinking off and on) need a llama-swap server with a `gemma` entry. Copy
|
|
242
|
+
`.env.example` to `.env` and set `LLAMA_BASE_URL`, then:
|
|
212
243
|
|
|
213
244
|
```bash
|
|
214
245
|
uv run --env-file .env pytest tests/integration_tests
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "langchain-llama-cpp"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "An integration package connecting llama.cpp's llama-server and LangChain, with reasoning, timings, structured output and tool calling"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "langchain-llama-cpp"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "An integration package connecting llama.cpp's llama-server and LangChain, with reasoning, timings, structured output and tool calling"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The JSON llama-server sends back from `/v1/chat/completions` (
|
|
1
|
+
"""The JSON llama-server sends back from `/v1/chat/completions` (v0.9.0).
|
|
2
2
|
|
|
3
3
|
Types for the type checker only: replies are plain dicts from `json.loads`,
|
|
4
4
|
never validated, so a server that sends something else isn't rejected here.
|
|
@@ -6,7 +6,7 @@ Every class is `total=False` because the code reads each key with `.get()`
|
|
|
6
6
|
and copes when it's missing; the docstrings say which keys the server always
|
|
7
7
|
sends.
|
|
8
8
|
|
|
9
|
-
Sources (llama.cpp
|
|
9
|
+
Sources (llama.cpp v0.9.0, build b12110): `tools/server/server-task.cpp`
|
|
10
10
|
(`to_json_oaicompat_chat`, `to_json_oaicompat_chat_stream`,
|
|
11
11
|
`server_task_result_cmpl_partial::to_json_oaicompat_chat`,
|
|
12
12
|
`probs_vector_to_json`, `result_prompt_progress::to_json`),
|
|
@@ -55,7 +55,7 @@ the stream ends without `[DONE]`.
|
|
|
55
55
|
from typing import Literal, TypedDict
|
|
56
56
|
|
|
57
57
|
FinishReason = Literal["stop", "length", "tool_calls"]
|
|
58
|
-
"""`length` when `max_tokens` or the context window ran out (
|
|
58
|
+
"""`length` when `max_tokens` or the context window ran out (v0.9.0 sends
|
|
59
59
|
`length` for every stop that isn't an end-of-sequence token or a stop string)."""
|
|
60
60
|
|
|
61
61
|
|
|
@@ -101,6 +101,9 @@ class LlamaContentPart(TypedDict, total=False):
|
|
|
101
101
|
type: str
|
|
102
102
|
text: str
|
|
103
103
|
|
|
104
|
+
name: str
|
|
105
|
+
"""In place of `text` on a `tool_reference` part."""
|
|
106
|
+
|
|
104
107
|
|
|
105
108
|
class LlamaMessage(TypedDict, total=False):
|
|
106
109
|
"""`choices[].message` in a reply. Always `role` and `content`."""
|
|
@@ -190,7 +193,7 @@ class LlamaPromptTokensDetails(TypedDict, total=False):
|
|
|
190
193
|
class LlamaCompletionTokensDetails(TypedDict, total=False):
|
|
191
194
|
"""`usage.completion_tokens_details`, as OpenAI sends it.
|
|
192
195
|
|
|
193
|
-
|
|
196
|
+
v0.9.0 never sends it (reasoning tokens are counted in
|
|
194
197
|
`completion_tokens` but not reported apart); read in case a build does.
|
|
195
198
|
"""
|
|
196
199
|
|
|
@@ -198,7 +201,7 @@ class LlamaCompletionTokensDetails(TypedDict, total=False):
|
|
|
198
201
|
|
|
199
202
|
|
|
200
203
|
class LlamaUsage(TypedDict, total=False):
|
|
201
|
-
"""`usage`.
|
|
204
|
+
"""`usage`. v0.9.0 always sends the four keys below except the last."""
|
|
202
205
|
|
|
203
206
|
prompt_tokens: int
|
|
204
207
|
|
{langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/chat_models.py
RENAMED
|
@@ -20,6 +20,7 @@ from langchain_core.language_models import (
|
|
|
20
20
|
LangSmithParams,
|
|
21
21
|
LanguageModelInput,
|
|
22
22
|
)
|
|
23
|
+
from langchain_core.language_models.model_profile import ModelProfile
|
|
23
24
|
from langchain_core.messages import (
|
|
24
25
|
AIMessage,
|
|
25
26
|
AIMessageChunk,
|
|
@@ -94,6 +95,10 @@ INITIAL_RETRY_DELAY = 0.5
|
|
|
94
95
|
MAX_RETRY_DELAY = 8.0
|
|
95
96
|
MAX_RETRY_AFTER = 60.0
|
|
96
97
|
|
|
98
|
+
# The `/props` fetch behind `profile`. Behind llama-swap it starts the entry,
|
|
99
|
+
# which can take tens of seconds for a large model; connecting must not.
|
|
100
|
+
PROFILE_TIMEOUT = httpx.Timeout(120.0, connect=10.0)
|
|
101
|
+
|
|
97
102
|
# ChatLlama fields that configure the client rather than the request.
|
|
98
103
|
_CLIENT_FIELDS = frozenset(
|
|
99
104
|
{
|
|
@@ -103,11 +108,16 @@ _CLIENT_FIELDS = frozenset(
|
|
|
103
108
|
"max_retries",
|
|
104
109
|
"streaming",
|
|
105
110
|
"stream_usage",
|
|
111
|
+
"fetch_profile",
|
|
106
112
|
"http_client",
|
|
107
113
|
"http_async_client",
|
|
108
114
|
}
|
|
109
115
|
)
|
|
110
116
|
|
|
117
|
+
# Profiles fetched from `/props`, per (base_url, model), for the process.
|
|
118
|
+
# Only successes are kept, so a server that was down is asked again.
|
|
119
|
+
_PROFILE_CACHE: dict[tuple[str, str], ModelProfile] = {}
|
|
120
|
+
|
|
111
121
|
_AsyncClients = weakref.WeakKeyDictionary[asyncio.AbstractEventLoop, httpx.AsyncClient]
|
|
112
122
|
|
|
113
123
|
# What `_chunk_from_line` returns for `data: [DONE]`, the end of a finished stream.
|
|
@@ -182,7 +192,7 @@ class ChatLlama(BaseChatModel):
|
|
|
182
192
|
"""Server root, e.g. `http://localhost:8080`. Defaults to `LLAMA_BASE_URL`."""
|
|
183
193
|
|
|
184
194
|
# --- Request fields -----------------------------------------------------
|
|
185
|
-
# Every field accepted by /v1/chat/completions in llama.cpp
|
|
195
|
+
# Every field accepted by /v1/chat/completions in llama.cpp v0.9.0 (b12110)
|
|
186
196
|
# (tools/server/server-common.cpp oaicompat_chat_params_parse,
|
|
187
197
|
# server-schema.cpp make_llama_cmpl_schema, server-context.cpp id_slot).
|
|
188
198
|
# Each is sent under its own name; None (the default) leaves it out, so the
|
|
@@ -338,7 +348,11 @@ class ChatLlama(BaseChatModel):
|
|
|
338
348
|
"""Continue the last assistant message instead of starting a new turn."""
|
|
339
349
|
|
|
340
350
|
echo: bool | None = None
|
|
341
|
-
"""
|
|
351
|
+
"""With `continue_final_message`, include the continued text in the reply.
|
|
352
|
+
|
|
353
|
+
By default the reply holds only the newly generated text. (In b11429 and
|
|
354
|
+
older builds it meant echoing the input tokens.)
|
|
355
|
+
"""
|
|
342
356
|
|
|
343
357
|
# Probabilities and debugging output
|
|
344
358
|
logprobs: bool | None = None
|
|
@@ -406,8 +420,23 @@ class ChatLlama(BaseChatModel):
|
|
|
406
420
|
`timings`. Can be overridden per call with `stream_usage=`.
|
|
407
421
|
"""
|
|
408
422
|
|
|
423
|
+
fetch_profile: bool = True
|
|
424
|
+
"""Fill `profile` from the server's `/props` at construction (default on).
|
|
425
|
+
|
|
426
|
+
Reads `GET /upstream/<model>/props` (llama-swap; this starts the entry),
|
|
427
|
+
falling back to `GET /props` (a plain llama-server) on a 404. Gives
|
|
428
|
+
`max_input_tokens` (the per-slot `n_ctx`), the input modalities and
|
|
429
|
+
`tool_calling`. Fetched once per `(base_url, model)` per process. Any failure
|
|
430
|
+
leaves `profile` at `None`. A `profile=` you pass is used as it is, with no
|
|
431
|
+
fetch. Turn it off for offline use. `/props` is fetched with a separate
|
|
432
|
+
client, not `http_client`.
|
|
433
|
+
"""
|
|
434
|
+
|
|
409
435
|
http_client: httpx.Client | None = Field(default=None, exclude=True)
|
|
410
|
-
"""Your own `httpx.Client
|
|
436
|
+
"""Your own `httpx.Client` for chat requests (not the `/props` profile fetch).
|
|
437
|
+
|
|
438
|
+
If given, `timeout` is not applied to it.
|
|
439
|
+
"""
|
|
411
440
|
|
|
412
441
|
http_async_client: httpx.AsyncClient | None = Field(default=None, exclude=True)
|
|
413
442
|
"""Your own `httpx.AsyncClient` for async calls. `timeout` isn't applied to it."""
|
|
@@ -495,8 +524,9 @@ class ChatLlama(BaseChatModel):
|
|
|
495
524
|
(the handler llama.cpp names "qwen3-coder") the grammar lets
|
|
496
525
|
the model write text before the call and forbids ending without
|
|
497
526
|
one, so a model that doesn't want a tool keeps writing until
|
|
498
|
-
`max_tokens`, with no tool call (llama.cpp issue #27217,
|
|
499
|
-
as
|
|
527
|
+
`max_tokens`, with no tool call (llama.cpp issue #27217, closed
|
|
528
|
+
as stale without a fix; unchanged as of v0.9.0). Keep
|
|
529
|
+
`max_tokens` set when forcing tools.
|
|
500
530
|
strict: Sent as `strict` on each tool when set. llama-server
|
|
501
531
|
ignores it: its grammar always enforces the schema.
|
|
502
532
|
parallel_tool_calls: Sent when set.
|
|
@@ -521,7 +551,7 @@ class ChatLlama(BaseChatModel):
|
|
|
521
551
|
]
|
|
522
552
|
|
|
523
553
|
# llama-server takes only these strings. It turns the object form
|
|
524
|
-
# into "auto" (
|
|
554
|
+
# into "auto" (v0.9.0), so a named tool becomes "required".
|
|
525
555
|
if tool_choice is True or tool_choice == "any":
|
|
526
556
|
tool_choice = "required"
|
|
527
557
|
elif not (
|
|
@@ -718,6 +748,52 @@ class ChatLlama(BaseChatModel):
|
|
|
718
748
|
|
|
719
749
|
# --- LangChain core hooks ---------------------------------------------
|
|
720
750
|
|
|
751
|
+
def _resolve_model_profile(self) -> ModelProfile | None:
|
|
752
|
+
"""The profile from the server's `/props`, or None (see `fetch_profile`).
|
|
753
|
+
|
|
754
|
+
Core calls this at construction when no `profile` was passed, and turns
|
|
755
|
+
any exception into None; failures return None here already.
|
|
756
|
+
"""
|
|
757
|
+
if not self.fetch_profile:
|
|
758
|
+
return None
|
|
759
|
+
|
|
760
|
+
base_url = self.base_url.rstrip("/")
|
|
761
|
+
key = (base_url, self.model)
|
|
762
|
+
if (cached := _PROFILE_CACHE.get(key)) is None:
|
|
763
|
+
props = self._fetch_props(base_url)
|
|
764
|
+
profile = _profile_from_props(props) if props is not None else None
|
|
765
|
+
if profile is None:
|
|
766
|
+
return None
|
|
767
|
+
_PROFILE_CACHE[key] = cached = profile
|
|
768
|
+
return ModelProfile(**cached) # a copy: the caller may change it
|
|
769
|
+
|
|
770
|
+
def _fetch_props(self, base_url: str) -> dict[str, Any] | None:
|
|
771
|
+
"""`/props` of this model's server: llama-swap's path, then llama-server's.
|
|
772
|
+
|
|
773
|
+
One attempt each, no retries, with `PROFILE_TIMEOUT`. Always on a
|
|
774
|
+
short-lived client of its own, never `http_client`: that client's event
|
|
775
|
+
hooks (e.g. a recorder of chat requests) shouldn't see this GET.
|
|
776
|
+
ChatLlama sends no auth headers, so nothing from it is needed.
|
|
777
|
+
"""
|
|
778
|
+
paths = (f"/upstream/{self.model}/props", "/props")
|
|
779
|
+
client = _props_client()
|
|
780
|
+
try:
|
|
781
|
+
for path in paths:
|
|
782
|
+
response = client.get(base_url + path, timeout=PROFILE_TIMEOUT)
|
|
783
|
+
if response.status_code == 404: # noqa: PLR2004
|
|
784
|
+
continue # not llama-swap: try a plain llama-server
|
|
785
|
+
if not response.is_success:
|
|
786
|
+
return None
|
|
787
|
+
props: object = response.json()
|
|
788
|
+
return (
|
|
789
|
+
cast("dict[str, Any]", props) if isinstance(props, dict) else None
|
|
790
|
+
)
|
|
791
|
+
except (httpx.HTTPError, ValueError): # unreachable, timed out, not JSON
|
|
792
|
+
return None
|
|
793
|
+
finally:
|
|
794
|
+
client.close()
|
|
795
|
+
return None
|
|
796
|
+
|
|
721
797
|
def _get_ls_params(
|
|
722
798
|
self, stop: list[str] | None = None, **kwargs: Any
|
|
723
799
|
) -> LangSmithParams:
|
|
@@ -1040,6 +1116,60 @@ def _without_none(**items: Any) -> dict[str, Any]:
|
|
|
1040
1116
|
return {key: value for key, value in items.items() if value is not None}
|
|
1041
1117
|
|
|
1042
1118
|
|
|
1119
|
+
# --- Profile --------------------------------------------------------------
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def _props_client() -> httpx.Client:
|
|
1123
|
+
"""A new client for the `/props` fetch (a function so tests can swap it)."""
|
|
1124
|
+
return httpx.Client()
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
def _profile_from_props(props: Mapping[str, Any]) -> ModelProfile | None:
|
|
1128
|
+
"""A `ModelProfile` from llama-server's `/props`, or None if it isn't one.
|
|
1129
|
+
|
|
1130
|
+
Fields as of llama.cpp v0.9.0 (`server-context.cpp` `get_res_props`):
|
|
1131
|
+
|
|
1132
|
+
- `default_generation_settings.n_ctx`: the per-slot context window, which
|
|
1133
|
+
holds the prompt and the reply → `max_input_tokens`.
|
|
1134
|
+
- `modalities.{vision, audio, video}` → `image_inputs`, `audio_inputs`,
|
|
1135
|
+
`video_inputs`.
|
|
1136
|
+
- `chat_template_caps.supports_tool_calls` → `tool_calling`. Without it the
|
|
1137
|
+
server's generic parser doesn't parse tool calls at all
|
|
1138
|
+
(`chat-auto-parser-generator.cpp`).
|
|
1139
|
+
|
|
1140
|
+
`structured_output` is left out on purpose: with `tools` in the request, a
|
|
1141
|
+
`response_format` grammar replaces the tool-call grammar in every chat
|
|
1142
|
+
handler, so an agent's model could no longer call tools.
|
|
1143
|
+
"""
|
|
1144
|
+
settings: object = props.get("default_generation_settings")
|
|
1145
|
+
if not isinstance(settings, dict):
|
|
1146
|
+
return None # a 200 from something that isn't llama-server
|
|
1147
|
+
|
|
1148
|
+
profile = ModelProfile(text_inputs=True)
|
|
1149
|
+
|
|
1150
|
+
n_ctx: object = cast("dict[str, Any]", settings).get("n_ctx")
|
|
1151
|
+
if isinstance(n_ctx, int) and not isinstance(n_ctx, bool) and n_ctx > 0:
|
|
1152
|
+
profile["max_input_tokens"] = n_ctx
|
|
1153
|
+
|
|
1154
|
+
modalities: object = props.get("modalities")
|
|
1155
|
+
if isinstance(modalities, dict):
|
|
1156
|
+
modality = cast("dict[str, Any]", modalities)
|
|
1157
|
+
if isinstance(vision := modality.get("vision"), bool):
|
|
1158
|
+
profile["image_inputs"] = vision
|
|
1159
|
+
if isinstance(audio := modality.get("audio"), bool):
|
|
1160
|
+
profile["audio_inputs"] = audio
|
|
1161
|
+
if isinstance(video := modality.get("video"), bool):
|
|
1162
|
+
profile["video_inputs"] = video
|
|
1163
|
+
|
|
1164
|
+
caps: object = props.get("chat_template_caps")
|
|
1165
|
+
if isinstance(caps, dict):
|
|
1166
|
+
tool_calls = cast("dict[str, Any]", caps).get("supports_tool_calls")
|
|
1167
|
+
if isinstance(tool_calls, bool):
|
|
1168
|
+
profile["tool_calling"] = tool_calls
|
|
1169
|
+
|
|
1170
|
+
return profile
|
|
1171
|
+
|
|
1172
|
+
|
|
1043
1173
|
# --- Tools and structured output ------------------------------------------
|
|
1044
1174
|
|
|
1045
1175
|
|
|
File without changes
|
|
File without changes
|
{langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/content_blocks.py
RENAMED
|
File without changes
|
{langchain_llama_cpp-0.1.0 → langchain_llama_cpp-0.2.0}/src/langchain_llama_cpp/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|