langchain-exaone 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,18 @@
1
+ .DS_Store
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+ .venv/
6
+ __pycache__/
7
+ *.py[cod]
8
+ *.egg-info/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .coverage
13
+ .coverage.*
14
+ node_modules/
15
+ dist/
16
+ build/
17
+ coverage/
18
+ /docs/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EXAONE Integrations contributors
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,133 @@
1
+ Metadata-Version: 2.5
2
+ Name: langchain-exaone
3
+ Version: 0.1.0
4
+ Summary: LangChain chat model integration for the EXAONE API
5
+ Project-URL: Repository, https://github.com/ykoh42/exaone-integrations
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: httpx<1,>=0.27
10
+ Requires-Dist: langchain-core<2,>=1.7.5
11
+ Requires-Dist: pydantic<3,>=2
12
+ Description-Content-Type: text/markdown
13
+
14
+ # `langchain-exaone`
15
+
16
+ Community-maintained LangChain chat model integration for the EXAONE API.
17
+
18
+ This package connects LangChain applications to the K-EXAONE 2.5 Chat Completions API. It implements LangChain's `BaseChatModel` interface and supports synchronous and asynchronous calls, streaming, tool calls, structured-output binding, usage metadata, and EXAONE reasoning history.
19
+
20
+ This project is not affiliated with or endorsed by LG AI Research or LangChain.
21
+
22
+ ## Install
23
+
24
+ This package is not published to PyPI yet. From a checkout of this repository, install it with:
25
+
26
+ ```bash
27
+ pip install ./packages/langchain-exaone-py
28
+ ```
29
+
30
+ After the first release, install it from PyPI with:
31
+
32
+ ```bash
33
+ pip install -U langchain-exaone
34
+ ```
35
+
36
+ Set `EXAONE_API_KEY` in the environment, then create the model:
37
+
38
+ ```python
39
+ from langchain_exaone import ChatEXAONE
40
+
41
+ model = ChatEXAONE()
42
+ message = model.invoke("안녕하세요. K-EXAONE을 소개해 주세요.")
43
+ print(message.text)
44
+ ```
45
+
46
+ The package does not load `.env` files. Use your application's secret-management setup to place the API key in the process environment, or pass `api_key` directly.
47
+
48
+ ## Serialization
49
+
50
+ `ChatEXAONE` does not opt into LangChain object serialization. LangChain's deserializer only allows registered integration classes by default, and this standalone package is not registered in `langchain-core`. The provider does not modify LangChain's global serialization registry. Create the model from application configuration when starting your process; its API key is held in a `SecretStr` and omitted from the model representation.
51
+
52
+ ## Reasoning history
53
+
54
+ EXAONE returns reasoning separately from final content. The provider exposes it on `AIMessage.additional_kwargs["reasoning_content"]` and includes it when an assistant message is sent back in later turns. Keep the original `AIMessage` in conversation history so reasoning and tool calls remain attached to their assistant turn.
55
+
56
+ ```python
57
+ from langchain_core.messages import HumanMessage
58
+ from langchain_exaone import ChatEXAONE
59
+
60
+ model = ChatEXAONE(reasoning_effort="low")
61
+ history = [HumanMessage("2 + 2는 얼마야?")]
62
+ answer = model.invoke(history)
63
+ history.append(answer)
64
+ history.append(HumanMessage("방금 답을 확인해줘."))
65
+ follow_up = model.invoke(history)
66
+ ```
67
+
68
+ Use `model.astream(...)` for asynchronous streaming. Streaming chunks retain reasoning in `additional_kwargs`, while text remains in the normal message content field.
69
+
70
+ LangChain's streaming interface represents one completion per run. If the API returns multiple choices, such as when `n` is greater than `1` in `model_kwargs`, `stream()` and `astream()` raise `ValueError` rather than merging separate candidates. Use `generate()` when you need multiple candidates.
71
+
72
+ ## Tools and structured output
73
+
74
+ ```python
75
+ from langchain_core.tools import tool
76
+ from langchain_exaone import ChatEXAONE
77
+
78
+
79
+ @tool
80
+ def get_weather(city: str) -> str:
81
+ """Get the current weather for a city."""
82
+ return f"Sunny in {city}."
83
+
84
+
85
+ model = ChatEXAONE().bind_tools([get_weather], tool_choice="auto")
86
+ response = model.invoke("서울 날씨를 알려줘.")
87
+ ```
88
+
89
+ `with_structured_output()` uses LangChain's standard tool-based strategy. You can pass additional Chat Completions fields with `model_kwargs` or per-call keyword arguments. Their acceptance depends on the EXAONE endpoint.
90
+
91
+ ## Configuration
92
+
93
+ | Option | Default | Description |
94
+ | --- | --- | --- |
95
+ | `model` | `k-exaone_v2.5` | EXAONE model ID |
96
+ | `api_key` | `EXAONE_API_KEY` | API credential |
97
+ | `base_url` | `https://api.exaone.ai/v1` | API base URL. Remote endpoints must use HTTPS; HTTP is allowed for loopback test servers. |
98
+ | `temperature` | `1.0` | Recommended EXAONE sampling value |
99
+ | `top_p` | `0.95` | Recommended EXAONE sampling value |
100
+ | `top_k` | `-1` | Disable top-k filtering, as in the API default |
101
+ | `presence_penalty` | `0.0` | Recommended EXAONE sampling value |
102
+ | `max_tokens` | API default | Maximum generated tokens, including reasoning |
103
+ | `reasoning_effort` | API default | `low`, `medium`, `high`, or `max` |
104
+ | `preserve_thinking` | `true` | Keep earlier assistant reasoning in multi-turn agent conversations |
105
+ | `chat_template_kwargs` | `{}` | Additional chat-template options; `preserve_thinking` is set from its dedicated option |
106
+ | `max_retries` | `2` | Retries for transient API errors before output is returned; set to `0` to disable |
107
+ | `timeout` | `None` | No provider-imposed HTTP timeout; pass a positive number to set one |
108
+
109
+ The API always enables reasoning. The provider preserves the returned `reasoning_content` on assistant messages and sends it with later turns; `preserve_thinking=True` also enables EXAONE's recommended `chat_template_kwargs.preserve_thinking` behavior. Keep returned assistant messages intact when continuing a conversation.
110
+
111
+ `max_tokens` is capped at the documented 524,288-token context limit. The provider cannot count an arbitrary prompt locally, so the API still validates the combined input and output token budget.
112
+
113
+ The provider retries HTTP 408, 429, 500, 502, 503, and 504 errors up to two times by default; set `max_retries=0` to disable retries. It honors the longer valid `Retry-After` or `Retry-After-Ms` delay. If neither header is present, the first retry waits at least one second to respect the API's per-second rate limit; a `max_retry_delay` below one second disables that retry. It does not retry after streaming output has started.
114
+
115
+ The EXAONE API is text-only. Image, audio, and other non-text input blocks are rejected explicitly.
116
+
117
+ See the [K-EXAONE 2.5 API reference](https://docs.exaone.ai/ko/docs/k-exaone-2.5) for the model's request parameters and limits.
118
+
119
+ ## Examples
120
+
121
+ See [`examples/basic.py`](examples/basic.py) for a runnable example. The package does not make API requests during import or construction.
122
+
123
+ ## Development
124
+
125
+ ```bash
126
+ uv sync --group test --group lint
127
+ uv run pytest
128
+ uv run ruff check .
129
+ uv run ruff format --check .
130
+ uv build
131
+ ```
132
+
133
+ Unit tests use synthetic responses and do not require API credentials.
@@ -0,0 +1,120 @@
1
+ # `langchain-exaone`
2
+
3
+ Community-maintained LangChain chat model integration for the EXAONE API.
4
+
5
+ This package connects LangChain applications to the K-EXAONE 2.5 Chat Completions API. It implements LangChain's `BaseChatModel` interface and supports synchronous and asynchronous calls, streaming, tool calls, structured-output binding, usage metadata, and EXAONE reasoning history.
6
+
7
+ This project is not affiliated with or endorsed by LG AI Research or LangChain.
8
+
9
+ ## Install
10
+
11
+ This package is not published to PyPI yet. From a checkout of this repository, install it with:
12
+
13
+ ```bash
14
+ pip install ./packages/langchain-exaone-py
15
+ ```
16
+
17
+ After the first release, install it from PyPI with:
18
+
19
+ ```bash
20
+ pip install -U langchain-exaone
21
+ ```
22
+
23
+ Set `EXAONE_API_KEY` in the environment, then create the model:
24
+
25
+ ```python
26
+ from langchain_exaone import ChatEXAONE
27
+
28
+ model = ChatEXAONE()
29
+ message = model.invoke("안녕하세요. K-EXAONE을 소개해 주세요.")
30
+ print(message.text)
31
+ ```
32
+
33
+ The package does not load `.env` files. Use your application's secret-management setup to place the API key in the process environment, or pass `api_key` directly.
34
+
35
+ ## Serialization
36
+
37
+ `ChatEXAONE` does not opt into LangChain object serialization. LangChain's deserializer only allows registered integration classes by default, and this standalone package is not registered in `langchain-core`. The provider does not modify LangChain's global serialization registry. Create the model from application configuration when starting your process; its API key is held in a `SecretStr` and omitted from the model representation.
38
+
39
+ ## Reasoning history
40
+
41
+ EXAONE returns reasoning separately from final content. The provider exposes it on `AIMessage.additional_kwargs["reasoning_content"]` and includes it when an assistant message is sent back in later turns. Keep the original `AIMessage` in conversation history so reasoning and tool calls remain attached to their assistant turn.
42
+
43
+ ```python
44
+ from langchain_core.messages import HumanMessage
45
+ from langchain_exaone import ChatEXAONE
46
+
47
+ model = ChatEXAONE(reasoning_effort="low")
48
+ history = [HumanMessage("2 + 2는 얼마야?")]
49
+ answer = model.invoke(history)
50
+ history.append(answer)
51
+ history.append(HumanMessage("방금 답을 확인해줘."))
52
+ follow_up = model.invoke(history)
53
+ ```
54
+
55
+ Use `model.astream(...)` for asynchronous streaming. Streaming chunks retain reasoning in `additional_kwargs`, while text remains in the normal message content field.
56
+
57
+ LangChain's streaming interface represents one completion per run. If the API returns multiple choices, such as when `n` is greater than `1` in `model_kwargs`, `stream()` and `astream()` raise `ValueError` rather than merging separate candidates. Use `generate()` when you need multiple candidates.
58
+
59
+ ## Tools and structured output
60
+
61
+ ```python
62
+ from langchain_core.tools import tool
63
+ from langchain_exaone import ChatEXAONE
64
+
65
+
66
+ @tool
67
+ def get_weather(city: str) -> str:
68
+ """Get the current weather for a city."""
69
+ return f"Sunny in {city}."
70
+
71
+
72
+ model = ChatEXAONE().bind_tools([get_weather], tool_choice="auto")
73
+ response = model.invoke("서울 날씨를 알려줘.")
74
+ ```
75
+
76
+ `with_structured_output()` uses LangChain's standard tool-based strategy. You can pass additional Chat Completions fields with `model_kwargs` or per-call keyword arguments. Their acceptance depends on the EXAONE endpoint.
77
+
78
+ ## Configuration
79
+
80
+ | Option | Default | Description |
81
+ | --- | --- | --- |
82
+ | `model` | `k-exaone_v2.5` | EXAONE model ID |
83
+ | `api_key` | `EXAONE_API_KEY` | API credential |
84
+ | `base_url` | `https://api.exaone.ai/v1` | API base URL. Remote endpoints must use HTTPS; HTTP is allowed for loopback test servers. |
85
+ | `temperature` | `1.0` | Recommended EXAONE sampling value |
86
+ | `top_p` | `0.95` | Recommended EXAONE sampling value |
87
+ | `top_k` | `-1` | Disable top-k filtering, as in the API default |
88
+ | `presence_penalty` | `0.0` | Recommended EXAONE sampling value |
89
+ | `max_tokens` | API default | Maximum generated tokens, including reasoning |
90
+ | `reasoning_effort` | API default | `low`, `medium`, `high`, or `max` |
91
+ | `preserve_thinking` | `true` | Keep earlier assistant reasoning in multi-turn agent conversations |
92
+ | `chat_template_kwargs` | `{}` | Additional chat-template options; `preserve_thinking` is set from its dedicated option |
93
+ | `max_retries` | `2` | Retries for transient API errors before output is returned; set to `0` to disable |
94
+ | `timeout` | `None` | No provider-imposed HTTP timeout; pass a positive number to set one |
95
+
96
+ The API always enables reasoning. The provider preserves the returned `reasoning_content` on assistant messages and sends it with later turns; `preserve_thinking=True` also enables EXAONE's recommended `chat_template_kwargs.preserve_thinking` behavior. Keep returned assistant messages intact when continuing a conversation.
97
+
98
+ `max_tokens` is capped at the documented 524,288-token context limit. The provider cannot count an arbitrary prompt locally, so the API still validates the combined input and output token budget.
99
+
100
+ The provider retries HTTP 408, 429, 500, 502, 503, and 504 errors up to two times by default; set `max_retries=0` to disable retries. It honors the longer valid `Retry-After` or `Retry-After-Ms` delay. If neither header is present, the first retry waits at least one second to respect the API's per-second rate limit; a `max_retry_delay` below one second disables that retry. It does not retry after streaming output has started.
101
+
102
+ The EXAONE API is text-only. Image, audio, and other non-text input blocks are rejected explicitly.
103
+
104
+ See the [K-EXAONE 2.5 API reference](https://docs.exaone.ai/ko/docs/k-exaone-2.5) for the model's request parameters and limits.
105
+
106
+ ## Examples
107
+
108
+ See [`examples/basic.py`](examples/basic.py) for a runnable example. The package does not make API requests during import or construction.
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ uv sync --group test --group lint
114
+ uv run pytest
115
+ uv run ruff check .
116
+ uv run ruff format --check .
117
+ uv build
118
+ ```
119
+
120
+ Unit tests use synthetic responses and do not require API credentials.
@@ -0,0 +1,10 @@
1
+ """Call EXAONE with the LangChain chat model integration."""
2
+
3
+ from langchain_exaone import ChatEXAONE
4
+
5
+ model = ChatEXAONE(reasoning_effort="low")
6
+ response = model.invoke("K-EXAONE을 한 문장으로 소개해 주세요.")
7
+
8
+ print(response.text)
9
+ print("Finish reason:", response.response_metadata.get("finish_reason"))
10
+ print("Usage:", response.usage_metadata)
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.26"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "langchain-exaone"
7
+ version = "0.1.0"
8
+ description = "LangChain chat model integration for the EXAONE API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ dependencies = [
14
+ "httpx>=0.27,<1",
15
+ "langchain-core>=1.7.5,<2",
16
+ "pydantic>=2,<3",
17
+ ]
18
+
19
+ [project.urls]
20
+ Repository = "https://github.com/ykoh42/exaone-integrations"
21
+
22
+ [dependency-groups]
23
+ test = [
24
+ "langchain-tests==1.1.9",
25
+ "pytest>=9,<10",
26
+ "pytest-asyncio>=1,<2",
27
+ ]
28
+ lint = ["ruff>=0.16,<0.18"]
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ packages = ["src/langchain_exaone"]
32
+
33
+ [tool.pytest.ini_options]
34
+ asyncio_mode = "auto"
35
+ asyncio_default_fixture_loop_scope = "function"
36
+ testpaths = ["tests"]
37
+
38
+ [tool.ruff]
39
+ line-length = 120
40
+
41
+ [tool.ruff.lint]
42
+ select = ["E", "F", "I", "B"]
@@ -0,0 +1,5 @@
1
+ """LangChain chat model integration for the EXAONE API."""
2
+
3
+ from langchain_exaone.chat_models import ChatEXAONE, ExaoneAPIError
4
+
5
+ __all__ = ["ChatEXAONE", "ExaoneAPIError"]