langchain-llamay 0.1.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.
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ dist/
3
+ __pycache__/
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ .benchmarks/
7
+ uv.lock
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kosa8 Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.5
2
+ Name: langchain-llamay
3
+ Version: 0.1.0
4
+ Summary: LangChain integration for llamay, a local LLM runtime
5
+ Project-URL: Homepage, https://llamay.com
6
+ Project-URL: Documentation, https://github.com/kosa8ai/langchain-llamay#readme
7
+ Project-URL: Source, https://github.com/kosa8ai/langchain-llamay
8
+ Author: Kosa8 Labs
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: langchain-core<2.0.0,>=1.0.0
17
+ Requires-Dist: langchain-ollama<2.0.0,>=1.0.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # langchain-llamay
21
+
22
+ LangChain integration for [llamay](https://llamay.com), a local LLM runtime for
23
+ macOS, Windows and Linux.
24
+
25
+ ```bash
26
+ pip install -U langchain-llamay
27
+ ```
28
+
29
+ ## Start a server
30
+
31
+ Download llamay from [llamay.com/download](https://llamay.com/download/), then:
32
+
33
+ ```bash
34
+ llamay serve -m qwen2.5:7b
35
+ ```
36
+
37
+ llamay downloads a model it does not have yet, including GGUF repositories on
38
+ Hugging Face (`hf.co/<owner>/<repo>:<quant>`), and listens on
39
+ `http://localhost:11435`. Set `LLAMAY_HOST` to reach a server elsewhere.
40
+
41
+ ## Chat
42
+
43
+ ```python
44
+ from langchain_llamay import ChatLlamay
45
+
46
+ llm = ChatLlamay(model="qwen2.5:7b")
47
+ print(llm.invoke("Why is the sky blue?").content)
48
+ ```
49
+
50
+ Tool calling, structured output and streaming work as they do in
51
+ `langchain-ollama`, which this package builds on: llamay serves Ollama's API.
52
+
53
+ One thing Ollama lacks does work here: `tool_choice`. llamay constrains the
54
+ answer to a call when one is required, so even a small model calls the tool it
55
+ is told to.
56
+
57
+ ```python
58
+ def get_weather(city: str) -> str:
59
+ """Get the weather for a city."""
60
+ return "sunny"
61
+
62
+
63
+ llm.bind_tools([get_weather], tool_choice="get_weather").invoke("Hello!").tool_calls
64
+ # [{'name': 'get_weather', 'args': {'city': ...}, ...}]
65
+ ```
66
+
67
+ `tool_choice` takes `"auto"`, `"none"`, `"any"` or `True` (call one of the
68
+ tools), a tool's name, or an OpenAI or Anthropic tool_choice object.
69
+
70
+ ```python
71
+ from pydantic import BaseModel
72
+
73
+
74
+ class City(BaseModel):
75
+ name: str
76
+ country: str
77
+
78
+
79
+ llm.with_structured_output(City).invoke("Name a city in Japan.")
80
+ ```
81
+
82
+ ## Embeddings
83
+
84
+ Start the server with an encoder beside the chat model:
85
+
86
+ ```bash
87
+ llamay serve -m qwen2.5:7b -embed all-minilm:latest
88
+ ```
89
+
90
+ ```python
91
+ from langchain_llamay import LlamayEmbeddings
92
+
93
+ embeddings = LlamayEmbeddings(model="all-minilm:latest")
94
+ vector = embeddings.embed_query("hello")
95
+ ```
96
+
97
+ ## Tests
98
+
99
+ ```bash
100
+ uv run --group test pytest tests/unit_tests
101
+ # with `llamay serve -m qwen2.5:0.5b -embed all-minilm:latest` running:
102
+ uv run --group test pytest tests/integration_tests
103
+ ```
@@ -0,0 +1,84 @@
1
+ # langchain-llamay
2
+
3
+ LangChain integration for [llamay](https://llamay.com), a local LLM runtime for
4
+ macOS, Windows and Linux.
5
+
6
+ ```bash
7
+ pip install -U langchain-llamay
8
+ ```
9
+
10
+ ## Start a server
11
+
12
+ Download llamay from [llamay.com/download](https://llamay.com/download/), then:
13
+
14
+ ```bash
15
+ llamay serve -m qwen2.5:7b
16
+ ```
17
+
18
+ llamay downloads a model it does not have yet, including GGUF repositories on
19
+ Hugging Face (`hf.co/<owner>/<repo>:<quant>`), and listens on
20
+ `http://localhost:11435`. Set `LLAMAY_HOST` to reach a server elsewhere.
21
+
22
+ ## Chat
23
+
24
+ ```python
25
+ from langchain_llamay import ChatLlamay
26
+
27
+ llm = ChatLlamay(model="qwen2.5:7b")
28
+ print(llm.invoke("Why is the sky blue?").content)
29
+ ```
30
+
31
+ Tool calling, structured output and streaming work as they do in
32
+ `langchain-ollama`, which this package builds on: llamay serves Ollama's API.
33
+
34
+ One thing Ollama lacks does work here: `tool_choice`. llamay constrains the
35
+ answer to a call when one is required, so even a small model calls the tool it
36
+ is told to.
37
+
38
+ ```python
39
+ def get_weather(city: str) -> str:
40
+ """Get the weather for a city."""
41
+ return "sunny"
42
+
43
+
44
+ llm.bind_tools([get_weather], tool_choice="get_weather").invoke("Hello!").tool_calls
45
+ # [{'name': 'get_weather', 'args': {'city': ...}, ...}]
46
+ ```
47
+
48
+ `tool_choice` takes `"auto"`, `"none"`, `"any"` or `True` (call one of the
49
+ tools), a tool's name, or an OpenAI or Anthropic tool_choice object.
50
+
51
+ ```python
52
+ from pydantic import BaseModel
53
+
54
+
55
+ class City(BaseModel):
56
+ name: str
57
+ country: str
58
+
59
+
60
+ llm.with_structured_output(City).invoke("Name a city in Japan.")
61
+ ```
62
+
63
+ ## Embeddings
64
+
65
+ Start the server with an encoder beside the chat model:
66
+
67
+ ```bash
68
+ llamay serve -m qwen2.5:7b -embed all-minilm:latest
69
+ ```
70
+
71
+ ```python
72
+ from langchain_llamay import LlamayEmbeddings
73
+
74
+ embeddings = LlamayEmbeddings(model="all-minilm:latest")
75
+ vector = embeddings.embed_query("hello")
76
+ ```
77
+
78
+ ## Tests
79
+
80
+ ```bash
81
+ uv run --group test pytest tests/unit_tests
82
+ # with `llamay serve -m qwen2.5:0.5b -embed all-minilm:latest` running:
83
+ uv run --group test pytest tests/integration_tests
84
+ ```
@@ -0,0 +1,13 @@
1
+ """LangChain integration for llamay, a local LLM runtime."""
2
+
3
+ from importlib import metadata
4
+
5
+ from langchain_llamay.chat_models import ChatLlamay
6
+ from langchain_llamay.embeddings import LlamayEmbeddings
7
+
8
+ try:
9
+ __version__ = metadata.version(__package__)
10
+ except metadata.PackageNotFoundError:
11
+ __version__ = ""
12
+
13
+ __all__ = ["ChatLlamay", "LlamayEmbeddings", "__version__"]
@@ -0,0 +1,14 @@
1
+ """Where a llamay server is, when nobody says."""
2
+
3
+ import os
4
+
5
+ DEFAULT_BASE_URL = "http://localhost:11435"
6
+
7
+
8
+ def default_base_url() -> str:
9
+ """LLAMAY_HOST, or llamay's own port on this machine.
10
+
11
+ llamay listens on 11435, beside Ollama's 11434, so the two can run at once;
12
+ pointing at 11434 by default would quietly talk to Ollama instead.
13
+ """
14
+ return os.environ.get("LLAMAY_HOST") or DEFAULT_BASE_URL
@@ -0,0 +1,52 @@
1
+ """The ollama client, able to send llamay's tool_choice.
2
+
3
+ Ollama's /api/chat has no tool_choice, so the ollama client has no argument for
4
+ it. llamay's does: it reads the field in OpenAI's and Anthropic's shapes and
5
+ constrains generation to a call, which is how a small local model can be made
6
+ to call a tool at all. These subclasses add the field to the request body.
7
+
8
+ The choice travels in a context variable rather than on the client, because one
9
+ client serves every call a chat model makes, concurrent ones included. The body
10
+ is built inside chat() before it returns -- for a stream too, whose generator
11
+ only reads the response -- so the variable is still set when _request runs.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from contextvars import ContextVar
17
+ from typing import Any
18
+
19
+ from ollama import AsyncClient, Client
20
+
21
+ _tool_choice: ContextVar[Any] = ContextVar("llamay_tool_choice", default=None)
22
+
23
+
24
+ def _with_choice(kwargs: dict[str, Any], args: tuple[Any, ...]) -> dict[str, Any]:
25
+ choice = _tool_choice.get()
26
+ if choice is not None and len(args) >= 2 and args[1] == "/api/chat" and "json" in kwargs:
27
+ kwargs = {**kwargs, "json": {**kwargs["json"], "tool_choice": choice}}
28
+ return kwargs
29
+
30
+
31
+ class LlamayClient(Client):
32
+ def chat(self, *args: Any, tool_choice: Any = None, **kwargs: Any) -> Any:
33
+ token = _tool_choice.set(tool_choice)
34
+ try:
35
+ return super().chat(*args, **kwargs)
36
+ finally:
37
+ _tool_choice.reset(token)
38
+
39
+ def _request(self, cls: Any, *args: Any, **kwargs: Any) -> Any:
40
+ return super()._request(cls, *args, **_with_choice(kwargs, args))
41
+
42
+
43
+ class LlamayAsyncClient(AsyncClient):
44
+ async def chat(self, *args: Any, tool_choice: Any = None, **kwargs: Any) -> Any:
45
+ token = _tool_choice.set(tool_choice)
46
+ try:
47
+ return await super().chat(*args, **kwargs)
48
+ finally:
49
+ _tool_choice.reset(token)
50
+
51
+ async def _request(self, cls: Any, *args: Any, **kwargs: Any) -> Any:
52
+ return await super()._request(cls, *args, **_with_choice(kwargs, args))
@@ -0,0 +1,131 @@
1
+ """llamay chat models."""
2
+
3
+ from collections.abc import Callable, Sequence
4
+ from typing import Any, Literal
5
+
6
+ from langchain_core.language_models import LangSmithParams, LanguageModelInput
7
+ from langchain_core.messages import AIMessage, BaseMessage
8
+ from langchain_core.runnables import Runnable
9
+ from langchain_core.tools import BaseTool
10
+ from langchain_core.utils.function_calling import convert_to_openai_tool
11
+ from langchain_ollama import ChatOllama
12
+ from pydantic import Field, model_validator
13
+ from typing_extensions import Self
14
+
15
+ from langchain_llamay._base import default_base_url
16
+ from langchain_llamay._client import LlamayAsyncClient, LlamayClient
17
+
18
+
19
+ def _llamay_tool_choice(choice: dict | str | bool | None) -> dict | str | None:
20
+ """LangChain's tool_choice in a form llamay reads.
21
+
22
+ llamay takes OpenAI's strings and object and Anthropic's object. LangChain
23
+ adds True for "call something" and a bare tool name for "call this one".
24
+ """
25
+ if choice is None or choice is False:
26
+ return None
27
+ if choice is True:
28
+ return "required"
29
+ if isinstance(choice, dict):
30
+ return choice
31
+ if choice in ("auto", "none", "any", "required"):
32
+ return choice
33
+ return {"type": "function", "function": {"name": choice}}
34
+
35
+
36
+ class ChatLlamay(ChatOllama):
37
+ """Chat with a model served by `llamay serve`.
38
+
39
+ llamay serves Ollama's API, so this is `ChatOllama` pointed at llamay, with
40
+ one addition Ollama does not have: `tool_choice` is honoured. llamay
41
+ constrains generation to a call when one is required, so even a small
42
+ local model calls the tool it is told to.
43
+
44
+ Setup:
45
+ Install llamay from https://llamay.com/download/ and start a server:
46
+
47
+ .. code-block:: bash
48
+
49
+ llamay serve -m qwen2.5:7b
50
+ pip install -U langchain-llamay
51
+
52
+ Instantiate:
53
+ .. code-block:: python
54
+
55
+ from langchain_llamay import ChatLlamay
56
+
57
+ llm = ChatLlamay(model="qwen2.5:7b")
58
+ llm.invoke("Why is the sky blue?")
59
+
60
+ Tool calling:
61
+ .. code-block:: python
62
+
63
+ llm.bind_tools([get_weather], tool_choice="get_weather").invoke("Hi")
64
+
65
+ Key init args:
66
+ model: str
67
+ Any name `llamay models` lists, or a Hugging Face reference such as
68
+ ``hf.co/bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M``.
69
+ base_url: str | None
70
+ The server. Defaults to ``LLAMAY_HOST``, else
71
+ ``http://localhost:11435``.
72
+ """
73
+
74
+ base_url: str | None = Field(default_factory=default_base_url)
75
+
76
+ @model_validator(mode="after")
77
+ def _llamay_clients(self) -> Self:
78
+ # ChatOllama has built its clients by now, with every setting it was
79
+ # given; giving them the subclass that can send tool_choice keeps all
80
+ # of that rather than rebuilding it here.
81
+ if self._client is not None:
82
+ self._client.__class__ = LlamayClient
83
+ if self._async_client is not None:
84
+ self._async_client.__class__ = LlamayAsyncClient
85
+ return self
86
+
87
+ @property
88
+ def _llm_type(self) -> str:
89
+ return "chat-llamay"
90
+
91
+ def _get_ls_params(self, stop: list[str] | None = None, **kwargs: Any) -> LangSmithParams:
92
+ params = super()._get_ls_params(stop=stop, **kwargs)
93
+ params["ls_provider"] = "llamay"
94
+ # A per-call model= is the model that answered; ChatOllama reports the
95
+ # constructor's, which files the trace under the wrong model.
96
+ params["ls_model_name"] = kwargs.get("model", self.model)
97
+ return params
98
+
99
+ def bind_tools(
100
+ self,
101
+ tools: Sequence[dict[str, Any] | type | Callable | BaseTool],
102
+ *,
103
+ tool_choice: dict | str | Literal["auto", "any"] | bool | None = None, # noqa: PYI051
104
+ **kwargs: Any,
105
+ ) -> Runnable[LanguageModelInput, AIMessage]:
106
+ """Bind tools, and optionally which of them the model must call.
107
+
108
+ Args:
109
+ tools: Tool definitions, in any form ``convert_to_openai_tool`` reads.
110
+ tool_choice: ``"auto"``, ``"none"``, ``"any"`` or ``True`` (call one
111
+ of them), a tool's name (call that one), or an OpenAI or
112
+ Anthropic tool_choice object.
113
+ kwargs: Passed to ``self.bind``.
114
+ """
115
+ choice = _llamay_tool_choice(tool_choice)
116
+ if choice is not None:
117
+ kwargs["tool_choice"] = choice
118
+ # Not super().bind_tools: its own tool_choice parameter would take the
119
+ # choice and drop it, which is what this method exists to stop.
120
+ return self.bind(tools=[convert_to_openai_tool(t) for t in tools], **kwargs)
121
+
122
+ def _chat_params(
123
+ self, messages: list[BaseMessage], stop: list[str] | None = None, **kwargs: Any
124
+ ) -> dict[str, Any]:
125
+ # Kept out of ChatOllama's params, which go to the ollama client as
126
+ # they are, and put back for LlamayClient.chat to send.
127
+ choice = kwargs.pop("tool_choice", None)
128
+ params = super()._chat_params(messages, stop, **kwargs)
129
+ if choice is not None:
130
+ params["tool_choice"] = choice
131
+ return params
@@ -0,0 +1,26 @@
1
+ """llamay embeddings."""
2
+
3
+ from langchain_ollama import OllamaEmbeddings
4
+ from pydantic import Field
5
+
6
+ from langchain_llamay._base import default_base_url
7
+
8
+
9
+ class LlamayEmbeddings(OllamaEmbeddings):
10
+ """Embeddings from the encoder a llamay server was started with.
11
+
12
+ llamay serves embeddings from an encoder loaded beside the chat model:
13
+
14
+ .. code-block:: bash
15
+
16
+ llamay serve -m qwen2.5:7b -embed all-minilm:latest
17
+
18
+ .. code-block:: python
19
+
20
+ from langchain_llamay import LlamayEmbeddings
21
+
22
+ embeddings = LlamayEmbeddings(model="all-minilm:latest")
23
+ embeddings.embed_query("hello")
24
+ """
25
+
26
+ base_url: str | None = Field(default_factory=default_base_url)
File without changes
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "langchain-llamay"
7
+ version = "0.1.0"
8
+ description = "LangChain integration for llamay, a local LLM runtime"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Kosa8 Labs" }]
13
+ requires-python = ">=3.10"
14
+ dependencies = [
15
+ "langchain-core>=1.0.0,<2.0.0",
16
+ "langchain-ollama>=1.0.0,<2.0.0",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 4 - Beta",
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://llamay.com"
27
+ Documentation = "https://github.com/kosa8ai/langchain-llamay#readme"
28
+ Source = "https://github.com/kosa8ai/langchain-llamay"
29
+
30
+ [dependency-groups]
31
+ test = ["pytest>=8,<9", "pytest-asyncio>=0.23", "langchain-tests>=1.0.0,<2.0.0"]
32
+
33
+ [tool.pytest.ini_options]
34
+ asyncio_mode = "auto"
35
+ asyncio_default_fixture_loop_scope = "function"
36
+ markers = ["compile: placeholder test that only checks the package imports"]
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["langchain_llamay"]
File without changes
@@ -0,0 +1,31 @@
1
+ """Standard tests against a live `llamay serve -m qwen2.5:0.5b -embed all-minilm:latest`."""
2
+
3
+ import os
4
+
5
+ from langchain_tests.integration_tests import ChatModelIntegrationTests
6
+
7
+ from langchain_llamay import ChatLlamay
8
+
9
+ MODEL = os.environ.get("LLAMAY_TEST_MODEL", "qwen2.5:0.5b")
10
+
11
+
12
+ class TestChatLlamayIntegration(ChatModelIntegrationTests):
13
+ @property
14
+ def chat_model_class(self) -> type[ChatLlamay]:
15
+ return ChatLlamay
16
+
17
+ @property
18
+ def chat_model_params(self) -> dict:
19
+ return {"model": MODEL, "temperature": 0}
20
+
21
+ @property
22
+ def has_tool_calling(self) -> bool:
23
+ return True
24
+
25
+ @property
26
+ def has_tool_choice(self) -> bool:
27
+ return True
28
+
29
+ @property
30
+ def supports_image_inputs(self) -> bool:
31
+ return False
@@ -0,0 +1,13 @@
1
+ from langchain_tests.integration_tests import EmbeddingsIntegrationTests
2
+
3
+ from langchain_llamay import LlamayEmbeddings
4
+
5
+
6
+ class TestLlamayEmbeddingsIntegration(EmbeddingsIntegrationTests):
7
+ @property
8
+ def embeddings_class(self) -> type[LlamayEmbeddings]:
9
+ return LlamayEmbeddings
10
+
11
+ @property
12
+ def embedding_model_params(self) -> dict:
13
+ return {"model": "all-minilm:latest"}
File without changes
@@ -0,0 +1,69 @@
1
+ from langchain_tests.unit_tests import ChatModelUnitTests
2
+
3
+ from langchain_llamay import ChatLlamay
4
+ from langchain_llamay._base import DEFAULT_BASE_URL
5
+
6
+
7
+ class TestChatLlamayUnit(ChatModelUnitTests):
8
+ @property
9
+ def chat_model_class(self) -> type[ChatLlamay]:
10
+ return ChatLlamay
11
+
12
+ @property
13
+ def chat_model_params(self) -> dict:
14
+ return {"model": "qwen2.5:0.5b"}
15
+
16
+
17
+ def test_defaults_to_llamays_port(monkeypatch) -> None:
18
+ monkeypatch.delenv("LLAMAY_HOST", raising=False)
19
+ assert ChatLlamay(model="qwen2.5:0.5b").base_url == DEFAULT_BASE_URL == "http://localhost:11435"
20
+
21
+
22
+ def test_reads_llamay_host(monkeypatch) -> None:
23
+ monkeypatch.setenv("LLAMAY_HOST", "http://gpu-box:11435")
24
+ assert ChatLlamay(model="qwen2.5:0.5b").base_url == "http://gpu-box:11435"
25
+
26
+
27
+ def test_an_explicit_base_url_wins(monkeypatch) -> None:
28
+ monkeypatch.setenv("LLAMAY_HOST", "http://gpu-box:11435")
29
+ assert ChatLlamay(model="m", base_url="http://other:1").base_url == "http://other:1"
30
+
31
+
32
+ def test_traces_as_llamay() -> None:
33
+ llm = ChatLlamay(model="qwen2.5:0.5b")
34
+ assert llm._llm_type == "chat-llamay"
35
+ assert llm._get_ls_params()["ls_provider"] == "llamay"
36
+
37
+
38
+ def test_tool_choice_reaches_the_request(monkeypatch) -> None:
39
+ # ChatOllama drops tool_choice; ChatLlamay sends it, in the shape llamay
40
+ # reads, on the same /api/chat request as the tools.
41
+ sent: list[dict] = []
42
+
43
+ def fake_request(self, cls, *args, **kwargs):
44
+ sent.append(kwargs.get("json", {}))
45
+ return cls(model="m", message={"role": "assistant", "content": ""}, done=True)
46
+
47
+ import ollama
48
+
49
+ monkeypatch.setattr(ollama.Client, "_request", fake_request)
50
+
51
+ def get_weather(city: str) -> str:
52
+ """Get the weather for a city."""
53
+ return "sunny"
54
+
55
+ llm = ChatLlamay(model="qwen2.5:0.5b")
56
+ for choice, want in [
57
+ ("get_weather", {"type": "function", "function": {"name": "get_weather"}}),
58
+ ("any", "any"),
59
+ (True, "required"),
60
+ ("auto", "auto"),
61
+ ]:
62
+ sent.clear()
63
+ llm.bind_tools([get_weather], tool_choice=choice).invoke("hi", stream=False)
64
+ assert sent and sent[-1].get("tool_choice") == want, (choice, sent)
65
+ assert sent[-1]["tools"][0]["function"]["name"] == "get_weather"
66
+
67
+ sent.clear()
68
+ llm.bind_tools([get_weather]).invoke("hi", stream=False)
69
+ assert "tool_choice" not in sent[-1]
@@ -0,0 +1,18 @@
1
+ from langchain_tests.unit_tests import EmbeddingsUnitTests
2
+
3
+ from langchain_llamay import LlamayEmbeddings
4
+
5
+
6
+ class TestLlamayEmbeddingsUnit(EmbeddingsUnitTests):
7
+ @property
8
+ def embeddings_class(self) -> type[LlamayEmbeddings]:
9
+ return LlamayEmbeddings
10
+
11
+ @property
12
+ def embedding_model_params(self) -> dict:
13
+ return {"model": "all-minilm:latest"}
14
+
15
+
16
+ def test_embeddings_default_to_llamays_port(monkeypatch) -> None:
17
+ monkeypatch.delenv("LLAMAY_HOST", raising=False)
18
+ assert LlamayEmbeddings(model="all-minilm:latest").base_url == "http://localhost:11435"