langchain-dev-utils 1.3.4__py3-none-any.whl → 1.3.5__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 +1 @@
1
- __version__ = "1.3.4"
1
+ __version__ = "1.3.5"
@@ -1,5 +1,5 @@
1
1
  from importlib import util
2
- from typing import Literal
2
+ from typing import Literal, Optional
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
@@ -41,3 +41,86 @@ def _get_base_url_field_name(model_cls: type[BaseModel]) -> str | None:
41
41
  return "api_base"
42
42
 
43
43
  return None
44
+
45
+
46
+ def _validate_base_url(base_url: Optional[str] = None) -> None:
47
+ """Validate base URL format.
48
+
49
+ Args:
50
+ base_url: Base URL to validate
51
+
52
+ Raises:
53
+ ValueError: If base URL is not a valid HTTP or HTTPS URL
54
+ """
55
+ if base_url is None:
56
+ return
57
+
58
+ from urllib.parse import urlparse
59
+
60
+ parsed = urlparse(base_url.strip())
61
+
62
+ if not parsed.scheme or not parsed.netloc:
63
+ raise ValueError(
64
+ f"base_url must be a valid HTTP or HTTPS URL. Received: {base_url}"
65
+ )
66
+
67
+ if parsed.scheme not in ("http", "https"):
68
+ raise ValueError(
69
+ f"base_url must use HTTP or HTTPS protocol. Received: {parsed.scheme}"
70
+ )
71
+
72
+
73
+ def _validate_model_cls_name(model_cls_name: str) -> None:
74
+ """Validate model class name follows Python naming conventions.
75
+
76
+ Args:
77
+ model_cls_name: Class name to validate
78
+
79
+ Raises:
80
+ ValueError: If class name is invalid
81
+ """
82
+ if not model_cls_name:
83
+ raise ValueError("model_cls_name cannot be empty")
84
+
85
+ if not model_cls_name[0].isalpha():
86
+ raise ValueError(
87
+ f"model_cls_name must start with a letter. Received: {model_cls_name}"
88
+ )
89
+
90
+ if not all(c.isalnum() or c == "_" for c in model_cls_name):
91
+ raise ValueError(
92
+ f"model_cls_name can only contain letters, numbers, and underscores. Received: {model_cls_name}"
93
+ )
94
+
95
+ if model_cls_name[0].islower():
96
+ raise ValueError(
97
+ f"model_cls_name should start with an uppercase letter (PEP 8). Received: {model_cls_name}"
98
+ )
99
+
100
+
101
+ def _validate_provider_name(provider_name: str) -> None:
102
+ """Validate provider name follows Python naming conventions.
103
+
104
+ Args:
105
+ provider_name: Provider name to validate
106
+
107
+ Raises:
108
+ ValueError: If provider name is invalid
109
+ """
110
+ if not provider_name:
111
+ raise ValueError("provider_name cannot be empty")
112
+
113
+ if not provider_name[0].isalnum():
114
+ raise ValueError(
115
+ f"provider_name must start with a letter. Received: {provider_name}"
116
+ )
117
+
118
+ if not all(c.isalnum() or c == "_" for c in provider_name):
119
+ raise ValueError(
120
+ f"provider_name can only contain letters, numbers, underscores. Received: {provider_name}"
121
+ )
122
+
123
+ if len(provider_name) > 20:
124
+ raise ValueError(
125
+ f"provider_name must be 20 characters or fewer. Received: {provider_name}"
126
+ )
@@ -11,7 +11,7 @@ def format_prompt(request: ModelRequest) -> str:
11
11
  Variables are first resolved from the state, then from the context if not found.
12
12
 
13
13
  Example:
14
- >>> from langchain_dev_utils.agents.middleware.format_prompt import format_prompt
14
+ >>> from langchain_dev_utils.agents.middleware import format_prompt
15
15
  >>> from langchain.agents import create_agent
16
16
  >>> from langchain_core.messages import HumanMessage
17
17
  >>> from dataclasses import dataclass
@@ -50,6 +50,11 @@ from pydantic import (
50
50
  )
51
51
  from typing_extensions import Self
52
52
 
53
+ from ..._utils import (
54
+ _validate_base_url,
55
+ _validate_model_cls_name,
56
+ _validate_provider_name,
57
+ )
53
58
  from ..types import (
54
59
  CompatibilityOptions,
55
60
  ReasoningKeepPolicy,
@@ -586,6 +591,51 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
586
591
  )
587
592
 
588
593
 
594
+ def _validate_compatibility_options(
595
+ compatibility_options: Optional[CompatibilityOptions] = None,
596
+ ) -> None:
597
+ """Validate provider configuration against supported features.
598
+
599
+ Args:
600
+ compatibility_options: Optional configuration for the provider
601
+
602
+ Raises:
603
+ ValueError: If provider configuration is invalid
604
+ """
605
+ if compatibility_options is None:
606
+ compatibility_options = {}
607
+
608
+ if "supported_tool_choice" in compatibility_options:
609
+ _supported_tool_choice = compatibility_options["supported_tool_choice"]
610
+ for tool_choice in _supported_tool_choice:
611
+ if tool_choice not in ["auto", "none", "required", "specific"]:
612
+ raise ValueError(
613
+ f"Unsupported tool_choice: {tool_choice}. Please choose from 'auto', 'none', 'required','specific'."
614
+ )
615
+
616
+ if "supported_response_format" in compatibility_options:
617
+ _supported_response_format = compatibility_options["supported_response_format"]
618
+ for response_format in _supported_response_format:
619
+ if response_format not in ["json_schema", "json_mode"]:
620
+ raise ValueError(
621
+ f"Unsupported response_format: {response_format}. Please choose from 'json_schema', 'json_mode'."
622
+ )
623
+
624
+ if "reasoning_keep_policy" in compatibility_options:
625
+ _reasoning_keep_policy = compatibility_options["reasoning_keep_policy"]
626
+ if _reasoning_keep_policy not in ["never", "current", "all"]:
627
+ raise ValueError(
628
+ f"Unsupported reasoning_keep_policy: {_reasoning_keep_policy}. Please choose from 'never', 'current', 'all'."
629
+ )
630
+
631
+ if "include_usage" in compatibility_options:
632
+ _include_usage = compatibility_options["include_usage"]
633
+ if not isinstance(_include_usage, bool):
634
+ raise ValueError(
635
+ f"include_usage must be a boolean value. Received: {_include_usage}"
636
+ )
637
+
638
+
589
639
  def _create_openai_compatible_model(
590
640
  provider: str,
591
641
  base_url: str,
@@ -615,6 +665,14 @@ def _create_openai_compatible_model(
615
665
  if profiles is not None:
616
666
  _register_profile_with_provider(provider, profiles)
617
667
 
668
+ _validate_compatibility_options(compatibility_options)
669
+
670
+ _validate_provider_name(provider)
671
+
672
+ _validate_model_cls_name(chat_model_cls_name)
673
+
674
+ _validate_base_url(base_url)
675
+
618
676
  return create_model(
619
677
  chat_model_cls_name,
620
678
  __base__=_BaseChatOpenAICompatible,
@@ -7,6 +7,7 @@ from langchain_core.utils import from_env
7
7
  from langchain_dev_utils._utils import (
8
8
  _check_pkg_install,
9
9
  _get_base_url_field_name,
10
+ _validate_provider_name,
10
11
  )
11
12
 
12
13
  from .types import ChatModelProvider, ChatModelType, CompatibilityOptions
@@ -126,6 +127,7 @@ def register_model_provider(
126
127
  >>> model = load_chat_model(model="vllm:qwen3-4b")
127
128
  >>> model.invoke("Hello")
128
129
  """
130
+ _validate_provider_name(provider_name)
129
131
  base_url = base_url or from_env(f"{provider_name.upper()}_API_BASE", default=None)()
130
132
  if isinstance(chat_model, str):
131
133
  _check_pkg_install("langchain_openai")
@@ -4,6 +4,12 @@ from langchain_core.utils import from_env, secret_from_env
4
4
  from langchain_openai.embeddings import OpenAIEmbeddings
5
5
  from pydantic import Field, SecretStr, create_model
6
6
 
7
+ from ..._utils import (
8
+ _validate_base_url,
9
+ _validate_model_cls_name,
10
+ _validate_provider_name,
11
+ )
12
+
7
13
 
8
14
  class _BaseEmbeddingOpenAICompatible(OpenAIEmbeddings):
9
15
  """Base class for OpenAI-Compatible embeddings.
@@ -53,6 +59,16 @@ def _create_openai_compatible_embedding(
53
59
  """
54
60
  embeddings_cls_name = embeddings_cls_name or f"{provider.title()}Embeddings"
55
61
 
62
+ if len(provider) >= 20:
63
+ raise ValueError(
64
+ f"provider must be less than 50 characters. Received: {provider}"
65
+ )
66
+
67
+ _validate_model_cls_name(embeddings_cls_name)
68
+ _validate_provider_name(provider)
69
+
70
+ _validate_base_url(base_url)
71
+
56
72
  return create_model(
57
73
  embeddings_cls_name,
58
74
  __base__=_BaseEmbeddingOpenAICompatible,
@@ -6,6 +6,7 @@ from langchain_core.utils import from_env
6
6
  from langchain_dev_utils._utils import (
7
7
  _check_pkg_install,
8
8
  _get_base_url_field_name,
9
+ _validate_provider_name,
9
10
  )
10
11
 
11
12
  _EMBEDDINGS_PROVIDERS_DICT = {}
@@ -87,7 +88,7 @@ def register_embeddings_provider(
87
88
  >>> embeddings = load_embeddings("vllm:qwen3-embedding-4b")
88
89
  >>> embeddings.embed_query("hello world")
89
90
  """
90
-
91
+ _validate_provider_name(provider_name)
91
92
  base_url = base_url or from_env(f"{provider_name.upper()}_API_BASE", default=None)()
92
93
  if isinstance(embeddings_model, str):
93
94
  if base_url is None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-dev-utils
3
- Version: 1.3.4
3
+ Version: 1.3.5
4
4
  Summary: A practical utility library for LangChain and LangGraph development
5
5
  Project-URL: Source Code, https://github.com/TBice123123/langchain-dev-utils
6
6
  Project-URL: repository, https://github.com/TBice123123/langchain-dev-utils
@@ -1,5 +1,5 @@
1
- langchain_dev_utils/__init__.py,sha256=9qxIaE8bFi9OKpc20hJx4mYCCa26qBsDb5ixbbRmD9c,23
2
- langchain_dev_utils/_utils.py,sha256=MFEzR1BjXMj6HEVwt2x2omttFuDJ_rYAEbNqe99r9pM,1338
1
+ langchain_dev_utils/__init__.py,sha256=HxBccdYuMvYSmkuVJi1a6zde7NfWgx8iuIRvVLBi-XA,23
2
+ langchain_dev_utils/_utils.py,sha256=rYsVM6ceU-VzZsJyf7ikfMh3OD84xCt0MLQIB3bwf_A,3858
3
3
  langchain_dev_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  langchain_dev_utils/agents/__init__.py,sha256=69_biZzyJvW9OBT1g8TX_77mp9-I_TvWo9QtlvHq83E,177
5
5
  langchain_dev_utils/agents/factory.py,sha256=8XB6y_ddf58vXlTLHBL6KCirFqkD2GjtzsuOt98sS7U,3732
@@ -7,7 +7,7 @@ langchain_dev_utils/agents/file_system.py,sha256=Yk3eetREE26WNrnTWLoiDUpOyCJ-rhj
7
7
  langchain_dev_utils/agents/plan.py,sha256=WwhoiJBmVYVI9bT8HfjCzTJ_SIp9WFil0gOeznv2omQ,6497
8
8
  langchain_dev_utils/agents/wrap.py,sha256=dJbCvljyw-ee43__2uws4H4ZhsyyX1AuavNtHrdWqN8,9485
9
9
  langchain_dev_utils/agents/middleware/__init__.py,sha256=QVQibaNHvHPyNTZ2UNFfYL153ZboaCHcoioTHK0FsiY,710
10
- langchain_dev_utils/agents/middleware/format_prompt.py,sha256=LzYiQXCRvkpfDhGPxhZwdepeU3j-HUWPJqcx3FWsfT4,2357
10
+ langchain_dev_utils/agents/middleware/format_prompt.py,sha256=yIkoSVPp0FemkjezvGsOmtgOkZDyEYQ8yh4YWYYGtVc,2343
11
11
  langchain_dev_utils/agents/middleware/handoffs.py,sha256=r196Xk0Jws1Tz6JQuvy5HEc3HAAQejCxFmJpB6KrvLU,7230
12
12
  langchain_dev_utils/agents/middleware/model_fallback.py,sha256=8xiNjTJ0yiRkPLCRfAGNnqY1TLstj1Anmiqyv5w2mA8,1633
13
13
  langchain_dev_utils/agents/middleware/model_router.py,sha256=qBspvj9ZoKfmC1pHWTO0EHHfxjgCUd-TuSbqvZl0kmg,7977
@@ -17,17 +17,17 @@ langchain_dev_utils/agents/middleware/tool_call_repair.py,sha256=oZF0Oejemqs9kSn
17
17
  langchain_dev_utils/agents/middleware/tool_emulator.py,sha256=OgtPhqturaWzF4fRSJ3f_IXvIrYrrAjlpOC5zmLtrkY,2031
18
18
  langchain_dev_utils/agents/middleware/tool_selection.py,sha256=dRH5ejR6N02Djwxt6Gd63MYkg6SV5pySlzaRt53OoZk,3113
19
19
  langchain_dev_utils/chat_models/__init__.py,sha256=YSLUyHrWEEj4y4DtGFCOnDW02VIYZdfAH800m4Klgeg,224
20
- langchain_dev_utils/chat_models/base.py,sha256=X76uaPoJDOLRphFyyeFH_RgEIvPcMoZEZWuRtjPCpl0,11486
20
+ langchain_dev_utils/chat_models/base.py,sha256=G_SNvd53ogho-LRgD7DCD65xj51J2JxmOkA4URNW6ZQ,11560
21
21
  langchain_dev_utils/chat_models/types.py,sha256=MD3cv_ZIe9fCdgwisNfuxAOhy-j4YSs1ZOQYyCjlNKs,927
22
22
  langchain_dev_utils/chat_models/adapters/__init__.py,sha256=4tTbhAAQdpX_gWyWeH97hqS5HnaoqQqW6QBh9Qd1SKs,106
23
23
  langchain_dev_utils/chat_models/adapters/create_utils.py,sha256=r8_XWLNF3Yc6sumlBhmgG1QcBa4Dsba7X3f_9YeMeGA,2479
24
- langchain_dev_utils/chat_models/adapters/openai_compatible.py,sha256=A3_NcmnsrCrHgm1-mGT-JqQVYUBsOzFH0KzSNX6edbY,24876
24
+ langchain_dev_utils/chat_models/adapters/openai_compatible.py,sha256=Xsd6HN1zGGDl87bZ5NMfwKfxWkgdP4DpszEqlb4Z-MY,27198
25
25
  langchain_dev_utils/chat_models/adapters/register_profiles.py,sha256=YS9ItCEq2ISoB_bp6QH5NVKOVR9-7la3r7B_xQNxZxE,366
26
26
  langchain_dev_utils/embeddings/__init__.py,sha256=zbEOaV86TUi9Zrg_dH9dpdgacWg31HMJTlTQknA9EKk,244
27
- langchain_dev_utils/embeddings/base.py,sha256=_no5sGEiBZENaHLWzwLB45tTsDj-bha7nt1GUhWBmP4,8731
27
+ langchain_dev_utils/embeddings/base.py,sha256=GXFKZSAExMtCFUpsd6mY4NxCWCrq7JAatBw3kS9LaKY,8803
28
28
  langchain_dev_utils/embeddings/adapters/__init__.py,sha256=yJEZZdzZ2fv1ExezLaNxo0VU9HJTHKYbS3T_XP8Ab9c,114
29
29
  langchain_dev_utils/embeddings/adapters/create_utils.py,sha256=K4JlbjG-O5xLY3wxaVt0UZ3QwI--cVb4qyxLATKVAWQ,2012
30
- langchain_dev_utils/embeddings/adapters/openai_compatible.py,sha256=fPX1GpJ6nwadbVu7mAMEVTIvGL2ZvtfRwkld2CJeHng,2767
30
+ langchain_dev_utils/embeddings/adapters/openai_compatible.py,sha256=fo7-m7dcWL4xrhSqdAHHVREsiXfVOvIrlaotaYTEiyE,3159
31
31
  langchain_dev_utils/message_convert/__init__.py,sha256=nnkDa_Im0dCb5u4aa2FRB9tqB8e6H6sEGYK6Vg81u2s,472
32
32
  langchain_dev_utils/message_convert/content.py,sha256=2V1g21byg3iLv5RjUW8zv3jwYwV7IH2hNim7jGRsIes,8096
33
33
  langchain_dev_utils/message_convert/format.py,sha256=NdrYX0cJn2-G1ArLSjJ7yO788KV1d83F4Kimpyft0IM,2446
@@ -38,7 +38,7 @@ langchain_dev_utils/pipeline/types.py,sha256=T3aROKKXeWvd0jcH5XkgMDQfEkLfPaiOhhV
38
38
  langchain_dev_utils/tool_calling/__init__.py,sha256=mu_WxKMcu6RoTf4vkTPbA1WSBSNc6YIqyBtOQ6iVQj4,322
39
39
  langchain_dev_utils/tool_calling/human_in_the_loop.py,sha256=7Z_QO5OZUR6K8nLoIcafc6osnvX2IYNorOJcbx6bVso,9672
40
40
  langchain_dev_utils/tool_calling/utils.py,sha256=S4-KXQ8jWmpGTXYZitovF8rxKpaSSUkFruM8LDwvcvE,2765
41
- langchain_dev_utils-1.3.4.dist-info/METADATA,sha256=K8AGEzWWmv-5HYzuNmmu3tboFyikx9zZjoj7H8Pald0,4552
42
- langchain_dev_utils-1.3.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
43
- langchain_dev_utils-1.3.4.dist-info/licenses/LICENSE,sha256=AWAOzNEcsvCEzHOF0qby5OKxviVH_eT9Yce1sgJTico,1084
44
- langchain_dev_utils-1.3.4.dist-info/RECORD,,
41
+ langchain_dev_utils-1.3.5.dist-info/METADATA,sha256=ftpdThaRWWEeTsrAYWNlf6WJPhP9xrEVTPez-JonkTk,4552
42
+ langchain_dev_utils-1.3.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
43
+ langchain_dev_utils-1.3.5.dist-info/licenses/LICENSE,sha256=AWAOzNEcsvCEzHOF0qby5OKxviVH_eT9Yce1sgJTico,1084
44
+ langchain_dev_utils-1.3.5.dist-info/RECORD,,