langchain-ollama 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.
@@ -1,3 +1,9 @@
1
+ """This is the langchain_ollama package.
2
+
3
+ It provides infrastructure for interacting with the Ollama service.
4
+ """
5
+
6
+
1
7
  from importlib import metadata
2
8
 
3
9
  from langchain_ollama.chat_models import ChatOllama
@@ -1,5 +1,6 @@
1
1
  """Ollama chat models."""
2
2
 
3
+ import json
3
4
  from typing import (
4
5
  Any,
5
6
  AsyncIterator,
@@ -21,6 +22,7 @@ from langchain_core.callbacks import (
21
22
  CallbackManagerForLLMRun,
22
23
  )
23
24
  from langchain_core.callbacks.manager import AsyncCallbackManagerForLLMRun
25
+ from langchain_core.exceptions import OutputParserException
24
26
  from langchain_core.language_models import LanguageModelInput
25
27
  from langchain_core.language_models.chat_models import BaseChatModel, LangSmithParams
26
28
  from langchain_core.messages import (
@@ -60,19 +62,85 @@ def _get_usage_metadata_from_generation_info(
60
62
  return None
61
63
 
62
64
 
65
+ def _parse_json_string(
66
+ json_string: str, raw_tool_call: dict[str, Any], skip: bool
67
+ ) -> Any:
68
+ """Attempt to parse a JSON string for tool calling.
69
+
70
+ Args:
71
+ json_string: JSON string to parse.
72
+ skip: Whether to ignore parsing errors and return the value anyways.
73
+ raw_tool_call: Raw tool call to include in error message.
74
+
75
+ Returns:
76
+ The parsed JSON string.
77
+
78
+ Raises:
79
+ OutputParserException: If the JSON string wrong invalid and skip=False.
80
+ """
81
+ try:
82
+ return json.loads(json_string)
83
+ except json.JSONDecodeError as e:
84
+ if skip:
85
+ return json_string
86
+ msg = (
87
+ f"Function {raw_tool_call['function']['name']} arguments:\n\n"
88
+ f"{raw_tool_call['function']['arguments']}\n\nare not valid JSON. "
89
+ f"Received JSONDecodeError {e}"
90
+ )
91
+ raise OutputParserException(msg) from e
92
+ except TypeError as e:
93
+ if skip:
94
+ return json_string
95
+ msg = (
96
+ f"Function {raw_tool_call['function']['name']} arguments:\n\n"
97
+ f"{raw_tool_call['function']['arguments']}\n\nare not a string or a "
98
+ f"dictionary. Received TypeError {e}"
99
+ )
100
+ raise OutputParserException(msg) from e
101
+
102
+
103
+ def _parse_arguments_from_tool_call(
104
+ raw_tool_call: dict[str, Any],
105
+ ) -> Optional[dict[str, Any]]:
106
+ """Parse arguments by trying to parse any shallowly nested string-encoded JSON.
107
+
108
+ Band-aid fix for issue in Ollama with inconsistent tool call argument structure.
109
+ Should be removed/changed if fixed upstream.
110
+ See https://github.com/ollama/ollama/issues/6155
111
+ """
112
+ if "function" not in raw_tool_call:
113
+ return None
114
+ arguments = raw_tool_call["function"]["arguments"]
115
+ parsed_arguments = {}
116
+ if isinstance(arguments, dict):
117
+ for key, value in arguments.items():
118
+ if isinstance(value, str):
119
+ parsed_arguments[key] = _parse_json_string(
120
+ value, skip=True, raw_tool_call=raw_tool_call
121
+ )
122
+ else:
123
+ parsed_arguments[key] = value
124
+ else:
125
+ parsed_arguments = _parse_json_string(
126
+ arguments, skip=False, raw_tool_call=raw_tool_call
127
+ )
128
+ return parsed_arguments
129
+
130
+
63
131
  def _get_tool_calls_from_response(
64
132
  response: Mapping[str, Any],
65
133
  ) -> List[ToolCall]:
66
134
  """Get tool calls from ollama response."""
67
135
  tool_calls = []
68
136
  if "message" in response:
69
- if "tool_calls" in response["message"]:
70
- for tc in response["message"]["tool_calls"]:
137
+ if raw_tool_calls := response["message"].get("tool_calls"):
138
+ for tc in raw_tool_calls:
71
139
  tool_calls.append(
72
140
  tool_call(
73
141
  id=str(uuid4()),
74
142
  name=tc["function"]["name"],
75
- args=tc["function"]["arguments"],
143
+ args=_parse_arguments_from_tool_call(tc) or {},
76
144
  )
77
145
  )
78
146
  return tool_calls
@@ -90,7 +158,7 @@ def _lc_tool_call_to_openai_tool_call(tool_call: ToolCall) -> dict:
90
158
 
91
159
 
92
160
  class ChatOllama(BaseChatModel):
93
- """Ollama chat model integration.
161
+ r"""Ollama chat model integration.
94
162
 
95
163
  .. dropdown:: Setup
96
164
  :open:
@@ -327,27 +395,36 @@ class ChatOllama(BaseChatModel):
327
395
  """Base url the model is hosted under."""
328
396
 
329
397
  client_kwargs: Optional[dict] = {}
330
- """Additional kwargs to pass to the httpx Client.
398
+ """Additional kwargs to pass to the httpx Client.
331
399
  For a full list of the params, see [this link](https://pydoc.dev/httpx/latest/httpx.Client.html)
332
400
  """
333
401
 
334
- _client: Client = PrivateAttr(default=None)
402
+ _client: Client = PrivateAttr(default=None) # type: ignore
335
403
  """
336
404
  The client to use for making requests.
337
405
  """
338
406
 
339
- _async_client: AsyncClient = PrivateAttr(default=None)
407
+ _async_client: AsyncClient = PrivateAttr(default=None) # type: ignore
340
408
  """
341
409
  The async client to use for making requests.
342
410
  """
343
411
 
344
- @property
345
- def _default_params(self) -> Dict[str, Any]:
346
- """Get the default parameters for calling Ollama."""
347
- return {
348
- "model": self.model,
349
- "format": self.format,
350
- "options": {
412
+ def _chat_params(
413
+ self,
414
+ messages: List[BaseMessage],
415
+ stop: Optional[List[str]] = None,
416
+ **kwargs: Any,
417
+ ) -> Dict[str, Any]:
418
+ ollama_messages = self._convert_messages_to_ollama_messages(messages)
419
+
420
+ if self.stop is not None and stop is not None:
421
+ raise ValueError("`stop` found in both the input and default params.")
422
+ elif self.stop is not None:
423
+ stop = self.stop
424
+
425
+ options_dict = kwargs.pop(
426
+ "options",
427
+ {
351
428
  "mirostat": self.mirostat,
352
429
  "mirostat_eta": self.mirostat_eta,
353
430
  "mirostat_tau": self.mirostat_tau,
@@ -359,14 +436,31 @@ class ChatOllama(BaseChatModel):
359
436
  "repeat_penalty": self.repeat_penalty,
360
437
  "temperature": self.temperature,
361
438
  "seed": self.seed,
362
- "stop": self.stop,
439
+ "stop": self.stop if stop is None else stop,
363
440
  "tfs_z": self.tfs_z,
364
441
  "top_k": self.top_k,
365
442
  "top_p": self.top_p,
366
443
  },
367
- "keep_alive": self.keep_alive,
444
+ )
445
+
446
+ tools = kwargs.get("tools")
447
+ default_stream = not bool(tools)
448
+
449
+ params = {
450
+ "messages": ollama_messages,
451
+ "stream": kwargs.pop("stream", default_stream),
452
+ "model": kwargs.pop("model", self.model),
453
+ "format": kwargs.pop("format", self.format),
454
+ "options": Options(**options_dict),
455
+ "keep_alive": kwargs.pop("keep_alive", self.keep_alive),
456
+ **kwargs,
368
457
  }
369
458
 
459
+ if tools:
460
+ params["tools"] = tools
461
+
462
+ return params
463
+
370
464
  @model_validator(mode="after")
371
465
  def _set_clients(self) -> Self:
372
466
  """Set clients to use for ollama."""
@@ -464,37 +558,13 @@ class ChatOllama(BaseChatModel):
464
558
  stop: Optional[List[str]] = None,
465
559
  **kwargs: Any,
466
560
  ) -> AsyncIterator[Union[Mapping[str, Any], str]]:
467
- ollama_messages = self._convert_messages_to_ollama_messages(messages)
561
+ chat_params = self._chat_params(messages, stop, **kwargs)
468
562
 
469
- stop = stop if stop is not None else self.stop
470
-
471
- params = self._default_params
472
-
473
- for key in self._default_params:
474
- if key in kwargs:
475
- params[key] = kwargs[key]
476
-
477
- params["options"]["stop"] = stop
478
- if "tools" in kwargs:
479
- yield await self._async_client.chat(
480
- model=params["model"],
481
- messages=ollama_messages,
482
- stream=False,
483
- options=Options(**params["options"]),
484
- keep_alive=params["keep_alive"],
485
- format=params["format"],
486
- tools=kwargs["tools"],
487
- ) # type:ignore
488
- else:
489
- async for part in await self._async_client.chat(
490
- model=params["model"],
491
- messages=ollama_messages,
492
- stream=True,
493
- options=Options(**params["options"]),
494
- keep_alive=params["keep_alive"],
495
- format=params["format"],
496
- ): # type:ignore
563
+ if chat_params["stream"]:
564
+ async for part in await self._async_client.chat(**chat_params):
497
565
  yield part
566
+ else:
567
+ yield await self._async_client.chat(**chat_params)
498
568
 
499
569
  def _create_chat_stream(
500
570
  self,
@@ -502,36 +572,12 @@ class ChatOllama(BaseChatModel):
502
572
  stop: Optional[List[str]] = None,
503
573
  **kwargs: Any,
504
574
  ) -> Iterator[Union[Mapping[str, Any], str]]:
505
- ollama_messages = self._convert_messages_to_ollama_messages(messages)
506
-
507
- stop = stop if stop is not None else self.stop
575
+ chat_params = self._chat_params(messages, stop, **kwargs)
508
576
 
509
- params = self._default_params
510
-
511
- for key in self._default_params:
512
- if key in kwargs:
513
- params[key] = kwargs[key]
514
-
515
- params["options"]["stop"] = stop
516
- if "tools" in kwargs:
517
- yield self._client.chat(
518
- model=params["model"],
519
- messages=ollama_messages,
520
- stream=False,
521
- options=Options(**params["options"]),
522
- keep_alive=params["keep_alive"],
523
- format=params["format"],
524
- tools=kwargs["tools"],
525
- )
577
+ if chat_params["stream"]:
578
+ yield from self._client.chat(**chat_params)
526
579
  else:
527
- yield from self._client.chat(
528
- model=params["model"],
529
- messages=ollama_messages,
530
- stream=True,
531
- options=Options(**params["options"]),
532
- keep_alive=params["keep_alive"],
533
- format=params["format"],
534
- )
580
+ yield self._client.chat(**chat_params)
535
581
 
536
582
  def _chat_stream_with_aggregation(
537
583
  self,
@@ -750,6 +796,8 @@ class ChatOllama(BaseChatModel):
750
796
  def bind_tools(
751
797
  self,
752
798
  tools: Sequence[Union[Dict[str, Any], Type, Callable, BaseTool]],
799
+ *,
800
+ tool_choice: Optional[Union[dict, str, Literal["auto", "any"], bool]] = None,
753
801
  **kwargs: Any,
754
802
  ) -> Runnable[LanguageModelInput, BaseMessage]:
755
803
  """Bind tool-like objects to this chat model.
@@ -760,6 +808,8 @@ class ChatOllama(BaseChatModel):
760
808
  tools: A list of tool definitions to bind to this chat model.
761
809
  Supports any tool definition handled by
762
810
  :meth:`langchain_core.utils.function_calling.convert_to_openai_tool`.
811
+ tool_choice: If provided, which tool for model to call. **This parameter
812
+ is currently ignored as it is not supported by Ollama.**
763
813
  kwargs: Any additional parameters are passed directly to
764
814
  ``self.bind(**kwargs)``.
765
815
  """ # noqa: E501
@@ -1,3 +1,5 @@
1
+ """Ollama embeddings models."""
2
+
1
3
  from typing import (
2
4
  List,
3
5
  Optional,
@@ -132,12 +134,12 @@ class OllamaEmbeddings(BaseModel, Embeddings):
132
134
  For a full list of the params, see [this link](https://pydoc.dev/httpx/latest/httpx.Client.html)
133
135
  """
134
136
 
135
- _client: Client = PrivateAttr(default=None)
137
+ _client: Client = PrivateAttr(default=None) # type: ignore
136
138
  """
137
139
  The client to use for making requests.
138
140
  """
139
141
 
140
- _async_client: AsyncClient = PrivateAttr(default=None)
142
+ _async_client: AsyncClient = PrivateAttr(default=None) # type: ignore
141
143
  """
142
144
  The async client to use for making requests.
143
145
  """
langchain_ollama/llms.py CHANGED
@@ -116,23 +116,30 @@ class OllamaLLM(BaseLLM):
116
116
  For a full list of the params, see [this link](https://pydoc.dev/httpx/latest/httpx.Client.html)
117
117
  """
118
118
 
119
- _client: Client = PrivateAttr(default=None)
119
+ _client: Client = PrivateAttr(default=None) # type: ignore
120
120
  """
121
121
  The client to use for making requests.
122
122
  """
123
123
 
124
- _async_client: AsyncClient = PrivateAttr(default=None)
124
+ _async_client: AsyncClient = PrivateAttr(default=None) # type: ignore
125
125
  """
126
126
  The async client to use for making requests.
127
127
  """
128
128
 
129
- @property
130
- def _default_params(self) -> Dict[str, Any]:
131
- """Get the default parameters for calling Ollama."""
132
- return {
133
- "model": self.model,
134
- "format": self.format,
135
- "options": {
129
+ def _generate_params(
130
+ self,
131
+ prompt: str,
132
+ stop: Optional[List[str]] = None,
133
+ **kwargs: Any,
134
+ ) -> Dict[str, Any]:
135
+ if self.stop is not None and stop is not None:
136
+ raise ValueError("`stop` found in both the input and default params.")
137
+ elif self.stop is not None:
138
+ stop = self.stop
139
+
140
+ options_dict = kwargs.pop(
141
+ "options",
142
+ {
136
143
  "mirostat": self.mirostat,
137
144
  "mirostat_eta": self.mirostat_eta,
138
145
  "mirostat_tau": self.mirostat_tau,
@@ -143,14 +150,25 @@ class OllamaLLM(BaseLLM):
143
150
  "repeat_last_n": self.repeat_last_n,
144
151
  "repeat_penalty": self.repeat_penalty,
145
152
  "temperature": self.temperature,
146
- "stop": self.stop,
153
+ "stop": self.stop if stop is None else stop,
147
154
  "tfs_z": self.tfs_z,
148
155
  "top_k": self.top_k,
149
156
  "top_p": self.top_p,
150
157
  },
151
- "keep_alive": self.keep_alive,
158
+ )
159
+
160
+ params = {
161
+ "prompt": prompt,
162
+ "stream": kwargs.pop("stream", True),
163
+ "model": kwargs.pop("model", self.model),
164
+ "format": kwargs.pop("format", self.format),
165
+ "options": Options(**options_dict),
166
+ "keep_alive": kwargs.pop("keep_alive", self.keep_alive),
167
+ **kwargs,
152
168
  }
153
169
 
170
+ return params
171
+
154
172
  @property
155
173
  def _llm_type(self) -> str:
156
174
  """Return type of LLM."""
@@ -179,27 +197,10 @@ class OllamaLLM(BaseLLM):
179
197
  stop: Optional[List[str]] = None,
180
198
  **kwargs: Any,
181
199
  ) -> AsyncIterator[Union[Mapping[str, Any], str]]:
182
- if self.stop is not None and stop is not None:
183
- raise ValueError("`stop` found in both the input and default params.")
184
- elif self.stop is not None:
185
- stop = self.stop
186
-
187
- params = self._default_params
188
-
189
- for key in self._default_params:
190
- if key in kwargs:
191
- params[key] = kwargs[key]
192
-
193
- params["options"]["stop"] = stop
194
200
  async for part in await self._async_client.generate(
195
- model=params["model"],
196
- prompt=prompt,
197
- stream=True,
198
- options=Options(**params["options"]),
199
- keep_alive=params["keep_alive"],
200
- format=params["format"],
201
+ **self._generate_params(prompt, stop=stop, **kwargs)
201
202
  ): # type: ignore
202
- yield part
203
+ yield part # type: ignore
203
204
 
204
205
  def _create_generate_stream(
205
206
  self,
@@ -207,26 +208,9 @@ class OllamaLLM(BaseLLM):
207
208
  stop: Optional[List[str]] = None,
208
209
  **kwargs: Any,
209
210
  ) -> Iterator[Union[Mapping[str, Any], str]]:
210
- if self.stop is not None and stop is not None:
211
- raise ValueError("`stop` found in both the input and default params.")
212
- elif self.stop is not None:
213
- stop = self.stop
214
-
215
- params = self._default_params
216
-
217
- for key in self._default_params:
218
- if key in kwargs:
219
- params[key] = kwargs[key]
220
-
221
- params["options"]["stop"] = stop
222
211
  yield from self._client.generate(
223
- model=params["model"],
224
- prompt=prompt,
225
- stream=True,
226
- options=Options(**params["options"]),
227
- keep_alive=params["keep_alive"],
228
- format=params["format"],
229
- )
212
+ **self._generate_params(prompt, stop=stop, **kwargs)
213
+ ) # type: ignore
230
214
 
231
215
  async def _astream_with_aggregation(
232
216
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-ollama
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: An integration package connecting Ollama and LangChain
5
5
  Home-page: https://github.com/langchain-ai/langchain
6
6
  License: MIT
@@ -11,7 +11,8 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: langchain-core (>=0.3.0,<0.4.0)
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Dist: langchain-core (>=0.3.20,<0.4.0)
15
16
  Requires-Dist: ollama (>=0.3.0,<1)
16
17
  Project-URL: Repository, https://github.com/langchain-ai/langchain
17
18
  Project-URL: Release Notes, https://github.com/langchain-ai/langchain/releases?q=tag%3A%22langchain-ollama%3D%3D0%22&expanded=true
@@ -0,0 +1,9 @@
1
+ langchain_ollama/__init__.py,sha256=SxPRrWcPayJpbwhheTtlqCaPp9ffiAAgZMM5Wc1yYpM,634
2
+ langchain_ollama/chat_models.py,sha256=YRPNR6ZKpQ6sF5045KwYbiGRA0YyHYXB8_7hPkJhAQE,32263
3
+ langchain_ollama/embeddings.py,sha256=svqdPF44qX5qbFpZmLiXrzTC-AldmMlZRS5wBfY-EmA,5056
4
+ langchain_ollama/llms.py,sha256=ojnYU0efhN10xhUINu1dCR2Erw79J_mYS6_l45J7Vls,12778
5
+ langchain_ollama/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ langchain_ollama-0.2.1.dist-info/LICENSE,sha256=2btS8uNUDWD_UNjw9ba6ZJt_00aUjEw9CGyK-xIHY8c,1072
7
+ langchain_ollama-0.2.1.dist-info/METADATA,sha256=SWUTgk7s9ouJwEBXiCPJj4YFLzVKZcM-YSHgWj8KDLQ,1876
8
+ langchain_ollama-0.2.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
9
+ langchain_ollama-0.2.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.8.1
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,9 +0,0 @@
1
- langchain_ollama/__init__.py,sha256=HhQZqbCjhrbr2dC_9Dkw12pg4HPjnDXUoInROMNJKqA,518
2
- langchain_ollama/chat_models.py,sha256=lfpm1D4YM_VjGegHq6JJws9nIzIp-QtX57VZvT8GC4I,30452
3
- langchain_ollama/embeddings.py,sha256=46gmGxzK5Cm0GYesTSSgWupJYmJ2ywN7FQUAl0fzpxE,4991
4
- langchain_ollama/llms.py,sha256=uwQfKwDHXhWWVSAFzHpuv8SirBwKp0H4irnA8lqU0M4,13259
5
- langchain_ollama/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- langchain_ollama-0.2.0.dist-info/LICENSE,sha256=2btS8uNUDWD_UNjw9ba6ZJt_00aUjEw9CGyK-xIHY8c,1072
7
- langchain_ollama-0.2.0.dist-info/METADATA,sha256=slqxbRBWofN8p4ewKoKh7hljZqey_qhjr3zrYwDgD0g,1824
8
- langchain_ollama-0.2.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
9
- langchain_ollama-0.2.0.dist-info/RECORD,,