strands-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.
- strands_exaone-0.1.0/.gitignore +18 -0
- strands_exaone-0.1.0/LICENSE +21 -0
- strands_exaone-0.1.0/PKG-INFO +271 -0
- strands_exaone-0.1.0/README.md +246 -0
- strands_exaone-0.1.0/examples/harness/README.md +41 -0
- strands_exaone-0.1.0/examples/harness/agent.py +58 -0
- strands_exaone-0.1.0/examples/strands-cli/README.md +33 -0
- strands_exaone-0.1.0/examples/strands-cli/agent.py +29 -0
- strands_exaone-0.1.0/examples/strands-cli/package.json +7 -0
- strands_exaone-0.1.0/pyproject.toml +57 -0
- strands_exaone-0.1.0/src/strands_exaone/__init__.py +7 -0
- strands_exaone-0.1.0/src/strands_exaone/errors.py +104 -0
- strands_exaone-0.1.0/src/strands_exaone/formatting.py +131 -0
- strands_exaone-0.1.0/src/strands_exaone/metadata.py +95 -0
- strands_exaone-0.1.0/src/strands_exaone/model.py +823 -0
- strands_exaone-0.1.0/src/strands_exaone/py.typed +0 -0
- strands_exaone-0.1.0/src/strands_exaone/retry.py +51 -0
- strands_exaone-0.1.0/src/strands_exaone/retry_strategy.py +78 -0
- strands_exaone-0.1.0/src/strands_exaone/streaming.py +351 -0
- strands_exaone-0.1.0/tests/harness_support.py +61 -0
- strands_exaone-0.1.0/tests/test_falsy_metadata_callback.py +131 -0
- strands_exaone-0.1.0/tests/test_harness.py +1102 -0
- strands_exaone-0.1.0/tests/test_harness_background.py +202 -0
- strands_exaone-0.1.0/tests/test_harness_builtins.py +660 -0
- strands_exaone-0.1.0/tests/test_harness_context.py +285 -0
- strands_exaone-0.1.0/tests/test_harness_example.py +165 -0
- strands_exaone-0.1.0/tests/test_harness_interventions.py +295 -0
- strands_exaone-0.1.0/tests/test_harness_session.py +218 -0
- strands_exaone-0.1.0/tests/test_metadata_cancellation.py +564 -0
- strands_exaone-0.1.0/tests/test_metadata_retry.py +830 -0
- strands_exaone-0.1.0/tests/test_provider.py +1478 -0
- strands_exaone-0.1.0/tests/test_reasoning_signature.py +73 -0
- strands_exaone-0.1.0/tests/test_structured_output.py +195 -0
- strands_exaone-0.1.0/tests/test_tool_result_status.py +136 -0
- strands_exaone-0.1.0/uv.lock +3114 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ykoh42
|
|
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,271 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: strands-exaone
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: EXAONE model provider for Strands Agents
|
|
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: pydantic<3,>=2
|
|
11
|
+
Requires-Dist: strands-agents<1.65.0,>=1.64.0
|
|
12
|
+
Requires-Dist: truststore<1,>=0.10
|
|
13
|
+
Requires-Dist: typing-extensions<5,>=4.13.2
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: complexipy<9,>=8.3; extra == 'dev'
|
|
16
|
+
Requires-Dist: hatch<2.0.0,>=1.0.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-asyncio<1.5.0,>=1.0.0; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest-cov<8.0.0,>=7.0.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-timeout<3.0.0,>=2.0.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-xdist<4.0.0,>=3.0.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest<10.0.0,>=9.0.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff<0.18.0,>=0.16.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: strands-harness~=0.3.4; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# strands-exaone for Python
|
|
27
|
+
|
|
28
|
+
Use the EXAONE Chat Completions API as a model provider for [Strands Agents](https://strandsagents.com/). This community-maintained package is not affiliated with LG AI Research or the Strands Agents project.
|
|
29
|
+
|
|
30
|
+
Users are responsible for reviewing and complying with the terms that apply to their EXAONE API use.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
The Python distribution requires Python 3.10 or newer and `strands-agents` 1.64.x. It is awaiting its PyPI release. From the root of a checkout, install it in your virtual environment:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python -m pip install -e ./packages/strands-exaone-py
|
|
38
|
+
export EXAONE_API_KEY='your-api-key'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from strands import Agent
|
|
43
|
+
from strands_exaone import ExaoneModel, ExaoneRetryStrategy
|
|
44
|
+
|
|
45
|
+
model = ExaoneModel(preserve_thinking=True)
|
|
46
|
+
agent = Agent(model=model, retry_strategy=ExaoneRetryStrategy(), callback_handler=None)
|
|
47
|
+
response = agent("Explain what an AI agent is in one sentence.")
|
|
48
|
+
print(response)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The provider reads `EXAONE_API_KEY` when the model is constructed. You can instead pass `api_key=...`. The package does not load `.env` files automatically.
|
|
52
|
+
|
|
53
|
+
The configured endpoint and any custom HTTP client receive your API key and conversation. Use a trusted endpoint and transport, and use HTTPS except for controlled local testing.
|
|
54
|
+
|
|
55
|
+
The callback handler determines whether intermediate reasoning and response text are printed. This example disables the default callback and prints only the final response; reasoning remains in conversation history for later turns.
|
|
56
|
+
|
|
57
|
+
For file reading and optional web search with Strands Harness, see the [Harness example](https://github.com/ykoh42/exaone-integrations/tree/main/packages/strands-exaone-py/examples/harness).
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
| Option | Description | Default |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| `model_id` | EXAONE model ID. | `k-exaone_v2.5` |
|
|
64
|
+
| `base_url` | API base URL, including `/v1`. Remote endpoints must use HTTPS; HTTP is allowed for loopback test servers. | `https://api.exaone.ai/v1` |
|
|
65
|
+
| `stream` | Request a streaming response; `False` adapts a non-streaming response to Strands events. | `True` |
|
|
66
|
+
| `temperature`, `top_p` | Sampling parameters. | `1.0`, `0.95` |
|
|
67
|
+
| `max_tokens`, `top_k`, `presence_penalty` | Generation parameters. | Service defaults when omitted |
|
|
68
|
+
| `reasoning_effort` | EXAONE reasoning setting. | Service default |
|
|
69
|
+
| `preserve_thinking` | Ask EXAONE to preserve previous reasoning in model input. | `True` |
|
|
70
|
+
| `enable_thinking` | EXAONE option for reasoning generation. | Service default |
|
|
71
|
+
| `include_usage` | Set `stream_options.include_usage` on streaming requests. | `True` |
|
|
72
|
+
| `response_format` | Response format for ordinary agent calls. | Omitted |
|
|
73
|
+
| `timeout` | Deadline for sending the request, retry waits, reading the response, asynchronous metadata callbacks, and time the caller pauses between yielded stream events. A custom transport that stalls during cleanup can finish after the deadline. | No provider-imposed timeout |
|
|
74
|
+
| `context_window_limit` | Context window reported to Strands. Set a positive token count. | `524288` |
|
|
75
|
+
| `max_retries`, `base_retry_delay`, `max_retry_delay` | Optional retry policy. | `0` retries; `1` and `30` seconds |
|
|
76
|
+
| `metadata_callback` | Callback for successful request metadata. | None |
|
|
77
|
+
| `http_client` | Caller-owned `httpx.AsyncClient` for custom transport or headers. | Provider-managed client |
|
|
78
|
+
| `params` | Additional Chat Completions parameters. | None |
|
|
79
|
+
|
|
80
|
+
Explicit options take precedence over matching `params` values, which take precedence over provider defaults. The provider manages `model`, `messages`, `stream`, `tools`, and `tool_choice`; do not set them in `params`. For streaming requests, `params["stream_options"]` can set `include_usage` and other service-supported options; the named `include_usage` option takes precedence. `stream_options` is omitted from non-streaming requests. `params["response_format"]` can set a format for ordinary model calls; the provider's `response_format` option takes precedence. Strands represents one completion, so `n` must be 1. Supported generation settings can vary by service version; see the [EXAONE API documentation](https://docs.exaone.ai/en/docs/k-exaone-2.5).
|
|
81
|
+
|
|
82
|
+
`model_info.limits.context_window`, when set, takes precedence over `context_window_limit`, following the Strands SDK's model info merge contract.
|
|
83
|
+
|
|
84
|
+
First-class generation options are validated before storage or a request. Values supplied through `params` remain an extension point and are passed through unless the provider owns the field; EXAONE may reject unsupported values.
|
|
85
|
+
|
|
86
|
+
`update_config(...)` applies to requests that have not started. Each in-flight request keeps the configuration it started with, including its retry policy.
|
|
87
|
+
|
|
88
|
+
## Reasoning and tools
|
|
89
|
+
|
|
90
|
+
Reasoning is stored separately from the final answer and sent back as reasoning_content when the Strands conversation includes it. The provider requests EXAONE's agent-use settings by default: temperature=1.0, top_p=0.95, and preserve_thinking=True. enable_thinking is a separate request option; check the returned fields and content when your application needs specific reasoning-handling behavior.
|
|
91
|
+
|
|
92
|
+
Strands can summarize older conversation history during automatic context management. The provider forwards the reasoning that remains in the history it receives; it cannot restore content that was summarized or removed. Token counts used for proactive context management are estimates, not exact EXAONE token counts.
|
|
93
|
+
|
|
94
|
+
Long-running agents should monitor memory and trace growth; behavior depends on the SDK version and workload.
|
|
95
|
+
|
|
96
|
+
The provider requests streaming usage by default (include_usage=True) so Strands can use token counts for context management. EXAONE may omit usage, and downstream context management is controlled by the framework.
|
|
97
|
+
|
|
98
|
+
The returned response fields are authoritative. Do not assume the request setting alone guarantees that reasoning is absent from visible content.
|
|
99
|
+
|
|
100
|
+
When EXAONE returns a refusal without non-whitespace ordinary response content, the provider exposes that refusal as a Strands text block and preserves any whitespace content. Strands has no separate refusal event; the refusal remains available in response metadata as well. System prompt blocks follow the Strands OpenAI adapter contract: text blocks are sent as separate messages and blocks without text, such as cache points, are omitted.
|
|
101
|
+
|
|
102
|
+
For direct `structured_output()` calls, a refusal without JSON raises `ExaoneError`; its sanitized `body` includes the refusal text. Strands tool execution uses the Chat Completions `tool_calls` field. Legacy `function_call` response fields are retained in metadata but do not trigger a Strands tool.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from strands import Agent, tool
|
|
106
|
+
from strands_exaone import ExaoneModel, ExaoneRetryStrategy
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@tool
|
|
110
|
+
def get_number() -> int:
|
|
111
|
+
"""Return the example number."""
|
|
112
|
+
return 7
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
agent = Agent(
|
|
116
|
+
model=ExaoneModel(preserve_thinking=True),
|
|
117
|
+
tools=[get_number],
|
|
118
|
+
retry_strategy=ExaoneRetryStrategy(),
|
|
119
|
+
callback_handler=None,
|
|
120
|
+
)
|
|
121
|
+
print(agent("Use the tool to get the number, then add 3."))
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The provider assembles streamed tool-call arguments before exposing calls to Strands. If a completed tool call contains a JSON object with only closing braces or brackets missing, or a trailing comma, the provider completes that syntax without changing argument values. It rejects unterminated strings, missing values, and other malformed arguments. The Harness still validates arguments against each tool's schema. The provider accepts text, reasoning, tool calls, and text/JSON tool results. Unsupported content types and reasoning signatures present in the history received by the provider are rejected.
|
|
125
|
+
|
|
126
|
+
Because Chat Completions tool messages have no separate status field, tool results marked as errors are sent with an `[ERROR] ` prefix. Successful results are sent without a prefix.
|
|
127
|
+
|
|
128
|
+
When Strands supplies multiple system text blocks, the provider sends them as separate system messages.
|
|
129
|
+
|
|
130
|
+
A user message containing both text and tool results is split into tool messages followed by a user text message, matching Strands' OpenAI Chat Completions adapter so tool responses remain adjacent to their assistant tool calls.
|
|
131
|
+
|
|
132
|
+
When using Strands Harness, configure its `read` tool to return a text description for image and document files:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from strands_harness import create_harness
|
|
136
|
+
from strands_exaone import ExaoneRetryStrategy
|
|
137
|
+
|
|
138
|
+
agent = create_harness(
|
|
139
|
+
model=ExaoneModel(),
|
|
140
|
+
retry_strategy=ExaoneRetryStrategy(),
|
|
141
|
+
builtin_tools={"*": False, "read": {"media": False}},
|
|
142
|
+
builtin_plugins=[],
|
|
143
|
+
session=False,
|
|
144
|
+
memory=False,
|
|
145
|
+
skills=False,
|
|
146
|
+
caching=False,
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
This setting avoids sending unsupported media blocks; it does not give the model image or document understanding. The `read` tool can access files permitted by the agent's sandbox, and its results are sent to the model endpoint. Use an isolated sandbox or an approval mechanism to restrict access to files you intend to share.
|
|
151
|
+
|
|
152
|
+
Tools that use their own network transport may have TLS trust settings separate from the EXAONE provider's client.
|
|
153
|
+
|
|
154
|
+
After an interrupted tool call, inspect external state before repeating a task that could have side effects.
|
|
155
|
+
|
|
156
|
+
Use separate session IDs for concurrently active agents or serialize access to shared file sessions.
|
|
157
|
+
|
|
158
|
+
Cancellation may not stop work already in progress. Use suitable tool timeouts and check external effects before retrying.
|
|
159
|
+
|
|
160
|
+
For side-effecting tools, ensure that cancelling an invocation also invalidates any pending approval.
|
|
161
|
+
|
|
162
|
+
Configure callbacks for child agents as well as the parent when generated reasoning or tool output must not be printed.
|
|
163
|
+
|
|
164
|
+
Text is forwarded without trimming, including leading and trailing whitespace. Strands may remove a text block containing only whitespace during a tool turn, so an exact round trip is not guaranteed for such blocks.
|
|
165
|
+
|
|
166
|
+
## Structured output
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from pydantic import BaseModel
|
|
170
|
+
from strands import Agent
|
|
171
|
+
from strands_exaone import ExaoneModel
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class Answer(BaseModel):
|
|
175
|
+
city: str
|
|
176
|
+
temperature_c: int
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
agent = Agent(model=ExaoneModel(), callback_handler=None)
|
|
180
|
+
result = agent("Return Seoul and a temperature of 20 degrees Celsius.", structured_output_model=Answer)
|
|
181
|
+
answer = result.structured_output
|
|
182
|
+
print(answer.city, answer.temperature_c)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
In Strands SDK 1.64, `structured_output_model` uses the SDK's structured-output tool and may require additional model calls. A configured `response_format` is also sent on those tool-based calls; EXAONE's support for combining them has not been verified. The provider's direct async `model.structured_output(...)` method instead requests its own JSON Schema without streaming and validates the result against the Pydantic model. For ordinary model calls, `response_format={"type": "json_object"}` requests JSON object mode.
|
|
186
|
+
|
|
187
|
+
When using recursive schemas, verify that the selected framework path preserves schema references and that EXAONE supports the schema.
|
|
188
|
+
|
|
189
|
+
Structured-output calls reject aborted responses rather than treating them as valid schema results.
|
|
190
|
+
|
|
191
|
+
## Retries
|
|
192
|
+
|
|
193
|
+
`ExaoneModel`'s own HTTP retry loop is disabled by default. For Agent and Harness use, configure `ExaoneRetryStrategy` as the Strands retry strategy; it replaces the SDK default and honors EXAONE's `Retry-After` and `Retry-After-Ms` headers. It defaults to three total attempts, a one-second initial backoff, and a 30-second delay cap. If a server hint exceeds the cap, it surfaces the error instead of retrying early. Without a usable hint, it uses bounded exponential backoff with jitter.
|
|
194
|
+
|
|
195
|
+
Strands owns Agent-level retry selection, so a model provider cannot silently replace the SDK's strategy. `ExaoneRetryStrategy` is opt-in: pass an instance to `Agent` or `create_harness` to replace the generic policy with EXAONE-aware waits. The quick start and Harness example show this explicit configuration.
|
|
196
|
+
|
|
197
|
+
Strands' default retry strategy does not inspect EXAONE headers. Keeping it alongside the provider's own retry loop can multiply attempts, so leave `max_retries` at its default of zero when using `ExaoneRetryStrategy`. The strategy retries EXAONE transient HTTP errors, safe connection failures, and Strands throttling failures before the provider exposes output. It does not retry tool arguments that remain invalid after syntax-only completion or other model response errors.
|
|
198
|
+
|
|
199
|
+
`ExaoneModel`'s optional `max_retries` setting remains available for direct model calls that do not use an Agent retry strategy. It retries connection failures before a response and retryable HTTP responses. The provider never replays a partially delivered stream. `timeout` bounds request work, provider retry waits, response reads, asynchronous metadata callbacks, and time the consumer pauses between yielded stream events. Agent-level `ExaoneRetryStrategy` waits are outside the model timeout. It cannot interrupt a synchronous callback while that function is running; an uncooperative custom transport can also delay response cleanup beyond the deadline. A caller-owned `http_client` retains its own transport timeouts.
|
|
200
|
+
|
|
201
|
+
When stopping iteration early, explicitly close the async generator so its response can be released promptly. This applies to both `model.stream(...)` and `agent.stream_async(...)`:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from contextlib import aclosing
|
|
205
|
+
|
|
206
|
+
from strands_exaone import ExaoneModel
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
async def first_event():
|
|
210
|
+
model = ExaoneModel()
|
|
211
|
+
messages = [{"role": "user", "content": [{"text": "Hello"}]}]
|
|
212
|
+
async with aclosing(model.stream(messages)) as stream:
|
|
213
|
+
return await anext(stream)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Alternatively, signal cancellation and wait for the stream to finish closing.
|
|
217
|
+
|
|
218
|
+
Use `ExaoneRetryStrategy` to replace the SDK's generic throttling backoff with EXAONE-aware handling. This keeps the Harness retry boundary intact while preventing retries from ignoring the server's delay hint.
|
|
219
|
+
|
|
220
|
+
Model routers can behave differently from provider retries when they switch after streaming begins. Test the framework's fallback behavior when partial output matters to your application.
|
|
221
|
+
|
|
222
|
+
For direct model calls, this configuration disables the Agent retry strategy and permits one provider retry with a maximum delay of 10 seconds:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from strands_harness import create_harness
|
|
226
|
+
from strands_exaone import ExaoneModel
|
|
227
|
+
|
|
228
|
+
agent = create_harness(
|
|
229
|
+
model=ExaoneModel(max_retries=1, max_retry_delay=10),
|
|
230
|
+
retry_strategy=None,
|
|
231
|
+
)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
This option is intended for direct model calls or when choosing provider-level retries explicitly. For Harness use, `ExaoneRetryStrategy` is the recommended way to honor each EXAONE response's retry headers.
|
|
235
|
+
|
|
236
|
+
The provider preserves invalid model output as an error rather than repairing or silently dropping it. Malformed tool arguments are rejected before Strands can execute the tool. Structured-output validation errors should be handled through the Strands structured-output contract; automatic prompt rewriting or repeating a model-generated mistake is not enabled.
|
|
237
|
+
|
|
238
|
+
## Metadata and errors
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from strands_exaone import ExaoneModel
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def on_metadata(metadata: dict) -> None:
|
|
245
|
+
print(metadata["request_id"], metadata["usage"])
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
model = ExaoneModel(include_usage=True, metadata_callback=on_metadata)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
After a successful response, the callback receives `status_code`, `request_id`, selected response `headers`, `usage`, and `response`. Raw usage can include `reasoning_tokens`. Available response metadata includes top-level IDs, model, service and weight metadata, `result_code`, and `description`; choice-level `finish_reason`, `matched_stop`, and `logprobs`; and selected message fields. Streaming logprob and annotation arrays are accumulated in order; refusal fragments are joined, while audio and legacy function-call fragments remain in `message_deltas`. Fields are present only when supplied by the service. The callback is not called if the response fails or is interrupted before completion.
|
|
252
|
+
|
|
253
|
+
Asynchronous callbacks are awaited and receive task cancellation when the invocation's cancellation signal is set. When a signal is supplied, an asynchronous callback runs in a separate task; changes it makes to `ContextVar` values do not propagate back to the caller. A callback that suppresses cancellation may delay termination; a synchronous callback cannot be interrupted while it is running. Callback errors propagate to the caller. A callback's Strands throttling or context-overflow exception is wrapped in `ExaoneError`, with the original as its cause, so it is not mistaken for a model error after the response.
|
|
254
|
+
|
|
255
|
+
Strands usage metrics are emitted only when all three token counts are non-negative safe integers; raw usage remains available to the metadata callback even when those metrics cannot be emitted.
|
|
256
|
+
|
|
257
|
+
`metrics.latencyMs` is captured before the metadata callback runs. A streaming call still includes time the consumer spends paused between events, because the provider stream follows the caller's async iteration pace.
|
|
258
|
+
|
|
259
|
+
**Metadata can contain user or generated content**, including logprob tokens, refusal text, audio, and function arguments. It is not size-limited and can grow with the response. API keys are redacted, but metadata should only be logged or shared under an appropriate data policy. The provider does not copy ordinary assistant content, reasoning, or tool calls into response metadata.
|
|
260
|
+
|
|
261
|
+
`ExaoneError` exposes `status_code`, `request_id`, selected `headers`, sanitized `body`, and normalized `error_type`, `code`, and `param` fields. `error_type` and `param` come from the nested `error` object. `code` comes from `error.code` when that key is present, including when its value is `null`; otherwise it comes from the documented top-level `result_code`. Error messages use the sanitized `error.message`, then top-level `description`; HTTP failures fall back to `HTTP {status}`, while an error envelope in a successful response uses a generic provider message. `ExaoneThrottledError` subclasses Strands' `ModelThrottledException`, and `ExaoneContextWindowError` subclasses its `ContextWindowOverflowException`. Neither is a subclass of `ExaoneError`; each provides an `ExaoneError` in `.details` with the same diagnostics. Other API errors use `ExaoneError`. An injected `http_client` remains caller-owned.
|
|
262
|
+
|
|
263
|
+
HTTP 400 or 413 is classified as a context-window error only when `error.code` is exactly `context_length_exceeded`. Other errors, including messages that merely mention the context window, remain `ExaoneError` and do not trigger Strands' overflow recovery. Whether EXAONE returns this specific code for an actual overflow has not been verified.
|
|
264
|
+
|
|
265
|
+
Standard HTTPX transport and stream errors retain their exception type, with the API key redacted from the message. Custom HTTPX subclasses are forwarded as their nearest standard HTTPX base type. An `HTTPStatusError` raised by a custom client is forwarded as `HTTPError` with its `status_code` preserved, but without its original response. Forwarded HTTPX errors do not include the original request or chained transport errors.
|
|
266
|
+
|
|
267
|
+
Other exceptions raised by a custom HTTPX transport are passed through unchanged. Their messages and chained exceptions may contain sensitive data; configure custom transports and logging accordingly.
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
[MIT](https://github.com/ykoh42/exaone-integrations/blob/main/packages/strands-exaone-py/LICENSE)
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# strands-exaone for Python
|
|
2
|
+
|
|
3
|
+
Use the EXAONE Chat Completions API as a model provider for [Strands Agents](https://strandsagents.com/). This community-maintained package is not affiliated with LG AI Research or the Strands Agents project.
|
|
4
|
+
|
|
5
|
+
Users are responsible for reviewing and complying with the terms that apply to their EXAONE API use.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
The Python distribution requires Python 3.10 or newer and `strands-agents` 1.64.x. It is awaiting its PyPI release. From the root of a checkout, install it in your virtual environment:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python -m pip install -e ./packages/strands-exaone-py
|
|
13
|
+
export EXAONE_API_KEY='your-api-key'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from strands import Agent
|
|
18
|
+
from strands_exaone import ExaoneModel, ExaoneRetryStrategy
|
|
19
|
+
|
|
20
|
+
model = ExaoneModel(preserve_thinking=True)
|
|
21
|
+
agent = Agent(model=model, retry_strategy=ExaoneRetryStrategy(), callback_handler=None)
|
|
22
|
+
response = agent("Explain what an AI agent is in one sentence.")
|
|
23
|
+
print(response)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The provider reads `EXAONE_API_KEY` when the model is constructed. You can instead pass `api_key=...`. The package does not load `.env` files automatically.
|
|
27
|
+
|
|
28
|
+
The configured endpoint and any custom HTTP client receive your API key and conversation. Use a trusted endpoint and transport, and use HTTPS except for controlled local testing.
|
|
29
|
+
|
|
30
|
+
The callback handler determines whether intermediate reasoning and response text are printed. This example disables the default callback and prints only the final response; reasoning remains in conversation history for later turns.
|
|
31
|
+
|
|
32
|
+
For file reading and optional web search with Strands Harness, see the [Harness example](https://github.com/ykoh42/exaone-integrations/tree/main/packages/strands-exaone-py/examples/harness).
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
| Option | Description | Default |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `model_id` | EXAONE model ID. | `k-exaone_v2.5` |
|
|
39
|
+
| `base_url` | API base URL, including `/v1`. Remote endpoints must use HTTPS; HTTP is allowed for loopback test servers. | `https://api.exaone.ai/v1` |
|
|
40
|
+
| `stream` | Request a streaming response; `False` adapts a non-streaming response to Strands events. | `True` |
|
|
41
|
+
| `temperature`, `top_p` | Sampling parameters. | `1.0`, `0.95` |
|
|
42
|
+
| `max_tokens`, `top_k`, `presence_penalty` | Generation parameters. | Service defaults when omitted |
|
|
43
|
+
| `reasoning_effort` | EXAONE reasoning setting. | Service default |
|
|
44
|
+
| `preserve_thinking` | Ask EXAONE to preserve previous reasoning in model input. | `True` |
|
|
45
|
+
| `enable_thinking` | EXAONE option for reasoning generation. | Service default |
|
|
46
|
+
| `include_usage` | Set `stream_options.include_usage` on streaming requests. | `True` |
|
|
47
|
+
| `response_format` | Response format for ordinary agent calls. | Omitted |
|
|
48
|
+
| `timeout` | Deadline for sending the request, retry waits, reading the response, asynchronous metadata callbacks, and time the caller pauses between yielded stream events. A custom transport that stalls during cleanup can finish after the deadline. | No provider-imposed timeout |
|
|
49
|
+
| `context_window_limit` | Context window reported to Strands. Set a positive token count. | `524288` |
|
|
50
|
+
| `max_retries`, `base_retry_delay`, `max_retry_delay` | Optional retry policy. | `0` retries; `1` and `30` seconds |
|
|
51
|
+
| `metadata_callback` | Callback for successful request metadata. | None |
|
|
52
|
+
| `http_client` | Caller-owned `httpx.AsyncClient` for custom transport or headers. | Provider-managed client |
|
|
53
|
+
| `params` | Additional Chat Completions parameters. | None |
|
|
54
|
+
|
|
55
|
+
Explicit options take precedence over matching `params` values, which take precedence over provider defaults. The provider manages `model`, `messages`, `stream`, `tools`, and `tool_choice`; do not set them in `params`. For streaming requests, `params["stream_options"]` can set `include_usage` and other service-supported options; the named `include_usage` option takes precedence. `stream_options` is omitted from non-streaming requests. `params["response_format"]` can set a format for ordinary model calls; the provider's `response_format` option takes precedence. Strands represents one completion, so `n` must be 1. Supported generation settings can vary by service version; see the [EXAONE API documentation](https://docs.exaone.ai/en/docs/k-exaone-2.5).
|
|
56
|
+
|
|
57
|
+
`model_info.limits.context_window`, when set, takes precedence over `context_window_limit`, following the Strands SDK's model info merge contract.
|
|
58
|
+
|
|
59
|
+
First-class generation options are validated before storage or a request. Values supplied through `params` remain an extension point and are passed through unless the provider owns the field; EXAONE may reject unsupported values.
|
|
60
|
+
|
|
61
|
+
`update_config(...)` applies to requests that have not started. Each in-flight request keeps the configuration it started with, including its retry policy.
|
|
62
|
+
|
|
63
|
+
## Reasoning and tools
|
|
64
|
+
|
|
65
|
+
Reasoning is stored separately from the final answer and sent back as reasoning_content when the Strands conversation includes it. The provider requests EXAONE's agent-use settings by default: temperature=1.0, top_p=0.95, and preserve_thinking=True. enable_thinking is a separate request option; check the returned fields and content when your application needs specific reasoning-handling behavior.
|
|
66
|
+
|
|
67
|
+
Strands can summarize older conversation history during automatic context management. The provider forwards the reasoning that remains in the history it receives; it cannot restore content that was summarized or removed. Token counts used for proactive context management are estimates, not exact EXAONE token counts.
|
|
68
|
+
|
|
69
|
+
Long-running agents should monitor memory and trace growth; behavior depends on the SDK version and workload.
|
|
70
|
+
|
|
71
|
+
The provider requests streaming usage by default (include_usage=True) so Strands can use token counts for context management. EXAONE may omit usage, and downstream context management is controlled by the framework.
|
|
72
|
+
|
|
73
|
+
The returned response fields are authoritative. Do not assume the request setting alone guarantees that reasoning is absent from visible content.
|
|
74
|
+
|
|
75
|
+
When EXAONE returns a refusal without non-whitespace ordinary response content, the provider exposes that refusal as a Strands text block and preserves any whitespace content. Strands has no separate refusal event; the refusal remains available in response metadata as well. System prompt blocks follow the Strands OpenAI adapter contract: text blocks are sent as separate messages and blocks without text, such as cache points, are omitted.
|
|
76
|
+
|
|
77
|
+
For direct `structured_output()` calls, a refusal without JSON raises `ExaoneError`; its sanitized `body` includes the refusal text. Strands tool execution uses the Chat Completions `tool_calls` field. Legacy `function_call` response fields are retained in metadata but do not trigger a Strands tool.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from strands import Agent, tool
|
|
81
|
+
from strands_exaone import ExaoneModel, ExaoneRetryStrategy
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@tool
|
|
85
|
+
def get_number() -> int:
|
|
86
|
+
"""Return the example number."""
|
|
87
|
+
return 7
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
agent = Agent(
|
|
91
|
+
model=ExaoneModel(preserve_thinking=True),
|
|
92
|
+
tools=[get_number],
|
|
93
|
+
retry_strategy=ExaoneRetryStrategy(),
|
|
94
|
+
callback_handler=None,
|
|
95
|
+
)
|
|
96
|
+
print(agent("Use the tool to get the number, then add 3."))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The provider assembles streamed tool-call arguments before exposing calls to Strands. If a completed tool call contains a JSON object with only closing braces or brackets missing, or a trailing comma, the provider completes that syntax without changing argument values. It rejects unterminated strings, missing values, and other malformed arguments. The Harness still validates arguments against each tool's schema. The provider accepts text, reasoning, tool calls, and text/JSON tool results. Unsupported content types and reasoning signatures present in the history received by the provider are rejected.
|
|
100
|
+
|
|
101
|
+
Because Chat Completions tool messages have no separate status field, tool results marked as errors are sent with an `[ERROR] ` prefix. Successful results are sent without a prefix.
|
|
102
|
+
|
|
103
|
+
When Strands supplies multiple system text blocks, the provider sends them as separate system messages.
|
|
104
|
+
|
|
105
|
+
A user message containing both text and tool results is split into tool messages followed by a user text message, matching Strands' OpenAI Chat Completions adapter so tool responses remain adjacent to their assistant tool calls.
|
|
106
|
+
|
|
107
|
+
When using Strands Harness, configure its `read` tool to return a text description for image and document files:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from strands_harness import create_harness
|
|
111
|
+
from strands_exaone import ExaoneRetryStrategy
|
|
112
|
+
|
|
113
|
+
agent = create_harness(
|
|
114
|
+
model=ExaoneModel(),
|
|
115
|
+
retry_strategy=ExaoneRetryStrategy(),
|
|
116
|
+
builtin_tools={"*": False, "read": {"media": False}},
|
|
117
|
+
builtin_plugins=[],
|
|
118
|
+
session=False,
|
|
119
|
+
memory=False,
|
|
120
|
+
skills=False,
|
|
121
|
+
caching=False,
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This setting avoids sending unsupported media blocks; it does not give the model image or document understanding. The `read` tool can access files permitted by the agent's sandbox, and its results are sent to the model endpoint. Use an isolated sandbox or an approval mechanism to restrict access to files you intend to share.
|
|
126
|
+
|
|
127
|
+
Tools that use their own network transport may have TLS trust settings separate from the EXAONE provider's client.
|
|
128
|
+
|
|
129
|
+
After an interrupted tool call, inspect external state before repeating a task that could have side effects.
|
|
130
|
+
|
|
131
|
+
Use separate session IDs for concurrently active agents or serialize access to shared file sessions.
|
|
132
|
+
|
|
133
|
+
Cancellation may not stop work already in progress. Use suitable tool timeouts and check external effects before retrying.
|
|
134
|
+
|
|
135
|
+
For side-effecting tools, ensure that cancelling an invocation also invalidates any pending approval.
|
|
136
|
+
|
|
137
|
+
Configure callbacks for child agents as well as the parent when generated reasoning or tool output must not be printed.
|
|
138
|
+
|
|
139
|
+
Text is forwarded without trimming, including leading and trailing whitespace. Strands may remove a text block containing only whitespace during a tool turn, so an exact round trip is not guaranteed for such blocks.
|
|
140
|
+
|
|
141
|
+
## Structured output
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from pydantic import BaseModel
|
|
145
|
+
from strands import Agent
|
|
146
|
+
from strands_exaone import ExaoneModel
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class Answer(BaseModel):
|
|
150
|
+
city: str
|
|
151
|
+
temperature_c: int
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
agent = Agent(model=ExaoneModel(), callback_handler=None)
|
|
155
|
+
result = agent("Return Seoul and a temperature of 20 degrees Celsius.", structured_output_model=Answer)
|
|
156
|
+
answer = result.structured_output
|
|
157
|
+
print(answer.city, answer.temperature_c)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
In Strands SDK 1.64, `structured_output_model` uses the SDK's structured-output tool and may require additional model calls. A configured `response_format` is also sent on those tool-based calls; EXAONE's support for combining them has not been verified. The provider's direct async `model.structured_output(...)` method instead requests its own JSON Schema without streaming and validates the result against the Pydantic model. For ordinary model calls, `response_format={"type": "json_object"}` requests JSON object mode.
|
|
161
|
+
|
|
162
|
+
When using recursive schemas, verify that the selected framework path preserves schema references and that EXAONE supports the schema.
|
|
163
|
+
|
|
164
|
+
Structured-output calls reject aborted responses rather than treating them as valid schema results.
|
|
165
|
+
|
|
166
|
+
## Retries
|
|
167
|
+
|
|
168
|
+
`ExaoneModel`'s own HTTP retry loop is disabled by default. For Agent and Harness use, configure `ExaoneRetryStrategy` as the Strands retry strategy; it replaces the SDK default and honors EXAONE's `Retry-After` and `Retry-After-Ms` headers. It defaults to three total attempts, a one-second initial backoff, and a 30-second delay cap. If a server hint exceeds the cap, it surfaces the error instead of retrying early. Without a usable hint, it uses bounded exponential backoff with jitter.
|
|
169
|
+
|
|
170
|
+
Strands owns Agent-level retry selection, so a model provider cannot silently replace the SDK's strategy. `ExaoneRetryStrategy` is opt-in: pass an instance to `Agent` or `create_harness` to replace the generic policy with EXAONE-aware waits. The quick start and Harness example show this explicit configuration.
|
|
171
|
+
|
|
172
|
+
Strands' default retry strategy does not inspect EXAONE headers. Keeping it alongside the provider's own retry loop can multiply attempts, so leave `max_retries` at its default of zero when using `ExaoneRetryStrategy`. The strategy retries EXAONE transient HTTP errors, safe connection failures, and Strands throttling failures before the provider exposes output. It does not retry tool arguments that remain invalid after syntax-only completion or other model response errors.
|
|
173
|
+
|
|
174
|
+
`ExaoneModel`'s optional `max_retries` setting remains available for direct model calls that do not use an Agent retry strategy. It retries connection failures before a response and retryable HTTP responses. The provider never replays a partially delivered stream. `timeout` bounds request work, provider retry waits, response reads, asynchronous metadata callbacks, and time the consumer pauses between yielded stream events. Agent-level `ExaoneRetryStrategy` waits are outside the model timeout. It cannot interrupt a synchronous callback while that function is running; an uncooperative custom transport can also delay response cleanup beyond the deadline. A caller-owned `http_client` retains its own transport timeouts.
|
|
175
|
+
|
|
176
|
+
When stopping iteration early, explicitly close the async generator so its response can be released promptly. This applies to both `model.stream(...)` and `agent.stream_async(...)`:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from contextlib import aclosing
|
|
180
|
+
|
|
181
|
+
from strands_exaone import ExaoneModel
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def first_event():
|
|
185
|
+
model = ExaoneModel()
|
|
186
|
+
messages = [{"role": "user", "content": [{"text": "Hello"}]}]
|
|
187
|
+
async with aclosing(model.stream(messages)) as stream:
|
|
188
|
+
return await anext(stream)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Alternatively, signal cancellation and wait for the stream to finish closing.
|
|
192
|
+
|
|
193
|
+
Use `ExaoneRetryStrategy` to replace the SDK's generic throttling backoff with EXAONE-aware handling. This keeps the Harness retry boundary intact while preventing retries from ignoring the server's delay hint.
|
|
194
|
+
|
|
195
|
+
Model routers can behave differently from provider retries when they switch after streaming begins. Test the framework's fallback behavior when partial output matters to your application.
|
|
196
|
+
|
|
197
|
+
For direct model calls, this configuration disables the Agent retry strategy and permits one provider retry with a maximum delay of 10 seconds:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from strands_harness import create_harness
|
|
201
|
+
from strands_exaone import ExaoneModel
|
|
202
|
+
|
|
203
|
+
agent = create_harness(
|
|
204
|
+
model=ExaoneModel(max_retries=1, max_retry_delay=10),
|
|
205
|
+
retry_strategy=None,
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This option is intended for direct model calls or when choosing provider-level retries explicitly. For Harness use, `ExaoneRetryStrategy` is the recommended way to honor each EXAONE response's retry headers.
|
|
210
|
+
|
|
211
|
+
The provider preserves invalid model output as an error rather than repairing or silently dropping it. Malformed tool arguments are rejected before Strands can execute the tool. Structured-output validation errors should be handled through the Strands structured-output contract; automatic prompt rewriting or repeating a model-generated mistake is not enabled.
|
|
212
|
+
|
|
213
|
+
## Metadata and errors
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from strands_exaone import ExaoneModel
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def on_metadata(metadata: dict) -> None:
|
|
220
|
+
print(metadata["request_id"], metadata["usage"])
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
model = ExaoneModel(include_usage=True, metadata_callback=on_metadata)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
After a successful response, the callback receives `status_code`, `request_id`, selected response `headers`, `usage`, and `response`. Raw usage can include `reasoning_tokens`. Available response metadata includes top-level IDs, model, service and weight metadata, `result_code`, and `description`; choice-level `finish_reason`, `matched_stop`, and `logprobs`; and selected message fields. Streaming logprob and annotation arrays are accumulated in order; refusal fragments are joined, while audio and legacy function-call fragments remain in `message_deltas`. Fields are present only when supplied by the service. The callback is not called if the response fails or is interrupted before completion.
|
|
227
|
+
|
|
228
|
+
Asynchronous callbacks are awaited and receive task cancellation when the invocation's cancellation signal is set. When a signal is supplied, an asynchronous callback runs in a separate task; changes it makes to `ContextVar` values do not propagate back to the caller. A callback that suppresses cancellation may delay termination; a synchronous callback cannot be interrupted while it is running. Callback errors propagate to the caller. A callback's Strands throttling or context-overflow exception is wrapped in `ExaoneError`, with the original as its cause, so it is not mistaken for a model error after the response.
|
|
229
|
+
|
|
230
|
+
Strands usage metrics are emitted only when all three token counts are non-negative safe integers; raw usage remains available to the metadata callback even when those metrics cannot be emitted.
|
|
231
|
+
|
|
232
|
+
`metrics.latencyMs` is captured before the metadata callback runs. A streaming call still includes time the consumer spends paused between events, because the provider stream follows the caller's async iteration pace.
|
|
233
|
+
|
|
234
|
+
**Metadata can contain user or generated content**, including logprob tokens, refusal text, audio, and function arguments. It is not size-limited and can grow with the response. API keys are redacted, but metadata should only be logged or shared under an appropriate data policy. The provider does not copy ordinary assistant content, reasoning, or tool calls into response metadata.
|
|
235
|
+
|
|
236
|
+
`ExaoneError` exposes `status_code`, `request_id`, selected `headers`, sanitized `body`, and normalized `error_type`, `code`, and `param` fields. `error_type` and `param` come from the nested `error` object. `code` comes from `error.code` when that key is present, including when its value is `null`; otherwise it comes from the documented top-level `result_code`. Error messages use the sanitized `error.message`, then top-level `description`; HTTP failures fall back to `HTTP {status}`, while an error envelope in a successful response uses a generic provider message. `ExaoneThrottledError` subclasses Strands' `ModelThrottledException`, and `ExaoneContextWindowError` subclasses its `ContextWindowOverflowException`. Neither is a subclass of `ExaoneError`; each provides an `ExaoneError` in `.details` with the same diagnostics. Other API errors use `ExaoneError`. An injected `http_client` remains caller-owned.
|
|
237
|
+
|
|
238
|
+
HTTP 400 or 413 is classified as a context-window error only when `error.code` is exactly `context_length_exceeded`. Other errors, including messages that merely mention the context window, remain `ExaoneError` and do not trigger Strands' overflow recovery. Whether EXAONE returns this specific code for an actual overflow has not been verified.
|
|
239
|
+
|
|
240
|
+
Standard HTTPX transport and stream errors retain their exception type, with the API key redacted from the message. Custom HTTPX subclasses are forwarded as their nearest standard HTTPX base type. An `HTTPStatusError` raised by a custom client is forwarded as `HTTPError` with its `status_code` preserved, but without its original response. Forwarded HTTPX errors do not include the original request or chained transport errors.
|
|
241
|
+
|
|
242
|
+
Other exceptions raised by a custom HTTPX transport are passed through unchanged. Their messages and chained exceptions may contain sensitive data; configure custom transports and logging accordingly.
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
[MIT](https://github.com/ykoh42/exaone-integrations/blob/main/packages/strands-exaone-py/LICENSE)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# EXAONE with Strands Harness
|
|
2
|
+
|
|
3
|
+
Use the Python model provider with Strands Harness. File reading is disabled by default and requires an explicit flag. This community-maintained integration is not affiliated with LG AI Research, Strands Agents, or Exa.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Python 3.10 or newer is required. From this example directory, create an environment and install the local provider with the Harness:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python3 -m venv .venv
|
|
11
|
+
.venv/bin/python -m pip install 'strands-harness~=0.3.4' ../..
|
|
12
|
+
export EXAONE_API_KEY='your-api-key'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The provider reads `EXAONE_API_KEY` from the environment; it does not load `.env` files automatically. By default, model requests are sent to the EXAONE API.
|
|
16
|
+
|
|
17
|
+
To use another compatible endpoint, set `EXAONE_BASE_URL` to its base URL, including `/v1`. If the variable is unset, the provider's default EXAONE endpoint is used.
|
|
18
|
+
|
|
19
|
+
## Run
|
|
20
|
+
|
|
21
|
+
To let the agent read a file you choose to share with the model, enable file reading explicitly:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
.venv/bin/python agent.py --allow-file-read 'Read /absolute/path/to/a/text-file.txt and summarize it.'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The example configures `read` with `media=False`: image and document files are described as text instead of being sent as unsupported media blocks. This does not give the model image or document understanding. The tool is disabled unless you pass `--allow-file-read`.
|
|
28
|
+
|
|
29
|
+
When enabled, the model can request any file the Harness sandbox permits it to read, and tool results are sent to the configured model endpoint. Run the example in an isolated sandbox containing only files you intend to share.
|
|
30
|
+
|
|
31
|
+
To allow web search, pass `--web-search`:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
.venv/bin/python agent.py --web-search 'Find the official Python documentation.'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This enables the third-party Exa search tool. **Search queries are sent to Exa**, subject to its [privacy policy](https://exa.ai/privacy-policy). Search is disabled by default. Set `EXA_API_KEY` in the environment if you have an Exa key; the Harness also supports keyless search with a lower rate limit. The model chooses whether to call the enabled tools.
|
|
38
|
+
|
|
39
|
+
The example uses `ExaoneRetryStrategy` so Strands waits for EXAONE's `Retry-After` hints instead of applying its generic throttling backoff. It disables the default printing callback, session persistence, memory, and skills. It uses the Harness default automatic context manager, which may summarize or remove older history. See the [Python package README](../../README.md) for reasoning preservation, configuration, and context-management details.
|
|
40
|
+
|
|
41
|
+
The default context manager and background-task support also register management tools for retrieving context and managing background work.
|