unique_toolkit 2026.36.0.dev6__py3-none-any.whl → 2026.36.0.dev7__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.
@@ -0,0 +1,133 @@
1
+ """Apply LanguageModelInfo constraints to experimental streaming requests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Mapping, Sequence
7
+ from typing import Any
8
+
9
+ from openai.types.shared_params import Reasoning
10
+
11
+ from unique_toolkit.chat.responses_api import _attempt_extract_reasoning_from_options
12
+ from unique_toolkit.language_model.infos import LanguageModelInfo, LanguageModelName
13
+ from unique_toolkit.language_model.schemas import reasoning_effort_to_openai
14
+
15
+ _LOGGER = logging.getLogger(__name__)
16
+
17
+ _REASONING_OPTION_KEYS = ("reasoning", "reasoning_effort", "reasoningEffort")
18
+
19
+
20
+ def prepare_chat_completions_model_params(
21
+ model_name: LanguageModelName | str,
22
+ temperature: float,
23
+ other_options: dict[str, Any] | None,
24
+ ) -> dict[str, Any]:
25
+ """Prepare Chat Completions create() options including clamped temperature.
26
+
27
+ Copies ``other_options`` so the caller dict is not mutated, applies
28
+ ``LanguageModelInfo`` temperature/reasoning rules, and writes the clamped
29
+ temperature onto the returned options dict.
30
+
31
+ Args:
32
+ model_name (LanguageModelName | str): Model identifier from the caller.
33
+ temperature (float): Requested sampling temperature.
34
+ other_options (dict[str, Any] | None): Extra OpenAI create() kwargs.
35
+
36
+ Returns:
37
+ dict[str, Any]: Sanitized create() options including ``temperature``.
38
+ """
39
+ options = dict(other_options) if other_options else {}
40
+ requested_effort = options.get("reasoning_effort")
41
+ temperature, resolved_effort = LanguageModelInfo.from_name(
42
+ model_name
43
+ ).resolve_temp_and_reasoning(temperature, requested_effort)
44
+ if resolved_effort is not None:
45
+ options["reasoning_effort"] = resolved_effort
46
+ else:
47
+ options.pop("reasoning_effort", None)
48
+ options["temperature"] = temperature
49
+ return options
50
+
51
+
52
+ def prepare_responses_model_params(
53
+ model_name: LanguageModelName | str,
54
+ temperature: float,
55
+ reasoning: Reasoning | None,
56
+ other_options: dict[str, Any] | None,
57
+ tools: Sequence[Mapping[str, Any]] | None = None,
58
+ ) -> dict[str, Any]:
59
+ """Prepare Responses API create() options including clamped temperature.
60
+
61
+ Extracts reasoning from ``other_options`` when the explicit argument is
62
+ missing (UniqueAI passes ``reasoning`` that way), applies
63
+ ``LanguageModelInfo`` bounds, and bumps ``minimal`` effort to ``low`` when
64
+ a code-interpreter tool is present. Clamped temperature and resolved
65
+ reasoning are written onto the returned options dict.
66
+
67
+ Args:
68
+ model_name (LanguageModelName | str): Model identifier from the caller.
69
+ temperature (float): Requested sampling temperature.
70
+ reasoning (Reasoning | None): Explicit Responses ``reasoning`` payload.
71
+ other_options (dict[str, Any] | None): Extra OpenAI create() kwargs.
72
+ tools (Sequence[Mapping[str, Any]] | None): Converted Responses tools.
73
+
74
+ Returns:
75
+ dict[str, Any]: Sanitized create() options including ``temperature``.
76
+ """
77
+ options = dict(other_options) if other_options else {}
78
+ if reasoning is None:
79
+ reasoning = _attempt_extract_reasoning_from_options(options)
80
+ for key in _REASONING_OPTION_KEYS:
81
+ options.pop(key, None)
82
+
83
+ requested_effort = reasoning.get("effort") if reasoning is not None else None
84
+ temperature, resolved_effort = LanguageModelInfo.from_name(
85
+ model_name
86
+ ).resolve_temp_and_reasoning(temperature, requested_effort)
87
+ reasoning = _bump_code_interpreter_effort(
88
+ _apply_resolved_reasoning(reasoning, resolved_effort),
89
+ tools,
90
+ )
91
+ options["temperature"] = temperature
92
+ if reasoning is not None:
93
+ options["reasoning"] = reasoning
94
+ return options
95
+
96
+
97
+ def _apply_resolved_reasoning(
98
+ reasoning: Reasoning | None,
99
+ resolved_effort: str | None,
100
+ ) -> Reasoning | None:
101
+ """Write the resolved effort into a new Responses ``reasoning`` payload."""
102
+ if resolved_effort is not None:
103
+ reasoning = Reasoning(**(reasoning or {}))
104
+ reasoning["effort"] = reasoning_effort_to_openai(resolved_effort)
105
+ return reasoning
106
+ if reasoning is None or "effort" not in reasoning:
107
+ return reasoning
108
+ reasoning = Reasoning(**reasoning)
109
+ del reasoning["effort"]
110
+ if not reasoning:
111
+ return None
112
+ return reasoning
113
+
114
+
115
+ def _bump_code_interpreter_effort(
116
+ reasoning: Reasoning | None,
117
+ tools: Sequence[Mapping[str, Any]] | None,
118
+ ) -> Reasoning | None:
119
+ """Code interpreter cannot run at ``minimal`` effort; bump to ``low``."""
120
+ if (
121
+ reasoning is None
122
+ or tools is None
123
+ or not any(tool["type"] == "code_interpreter" for tool in tools)
124
+ or reasoning.get("effort") != "minimal"
125
+ ):
126
+ return reasoning
127
+
128
+ _LOGGER.warning(
129
+ "Code interpreter cannot be used with `minimal` effort. Switching to `low`."
130
+ )
131
+ reasoning = Reasoning(**reasoning)
132
+ reasoning["effort"] = "low"
133
+ return reasoning
@@ -44,6 +44,7 @@ from unique_toolkit.language_model.schemas import (
44
44
  from unique_toolkit.protocols.support import SupportCompleteWithReferences
45
45
 
46
46
  from .._async_bridge import run_async_from_sync
47
+ from .._model_params import prepare_chat_completions_model_params
47
48
  from ..events import (
48
49
  StreamEnded,
49
50
  StreamEventBus,
@@ -378,6 +379,10 @@ class ChatCompletionsCompleteWithReferences(SupportCompleteWithReferences):
378
379
  message_id = chat.last_assistant_message_id
379
380
  chat_id = chat.chat_id
380
381
 
382
+ other_options = prepare_chat_completions_model_params(
383
+ model_name, temperature, other_options
384
+ )
385
+
381
386
  gpt_messages = (
382
387
  messages.to_openai(mode="completions")
383
388
  if isinstance(messages, LanguageModelMessages)
@@ -389,9 +394,8 @@ class ChatCompletionsCompleteWithReferences(SupportCompleteWithReferences):
389
394
  optional_create_kwargs["tools"] = converted_tools
390
395
  if tool_choice is not None:
391
396
  optional_create_kwargs["tool_choice"] = tool_choice
392
- if other_options:
393
- for k, v in other_options.items():
394
- optional_create_kwargs.setdefault(k, v)
397
+ for k, v in other_options.items():
398
+ optional_create_kwargs.setdefault(k, v)
395
399
 
396
400
  gpt_request: list[dict[str, Any]] = [
397
401
  dict(cast("Mapping[str, Any]", message)) for message in gpt_messages
@@ -444,7 +448,6 @@ class ChatCompletionsCompleteWithReferences(SupportCompleteWithReferences):
444
448
  messages=gpt_messages,
445
449
  stream=True,
446
450
  stream_options=stream_options,
447
- temperature=temperature,
448
451
  **optional_create_kwargs,
449
452
  )
450
453
 
@@ -39,6 +39,7 @@ from unique_toolkit.language_model.schemas import (
39
39
  from unique_toolkit.protocols.support import ResponsesSupportCompleteWithReferences
40
40
 
41
41
  from .._async_bridge import run_async_from_sync
42
+ from .._model_params import prepare_responses_model_params
42
43
  from ..events import (
43
44
  ActivityProgress,
44
45
  StreamEnded,
@@ -512,8 +513,16 @@ class ResponsesCompleteWithReferences(ResponsesSupportCompleteWithReferences):
512
513
  )
513
514
 
514
515
  converted_messages = input_messages(messages)
515
-
516
516
  converted_tools = _convert_tools(tools)
517
+
518
+ other_options = prepare_responses_model_params(
519
+ model_name,
520
+ temperature,
521
+ reasoning,
522
+ other_options,
523
+ converted_tools,
524
+ )
525
+
517
526
  create_kwargs: dict[str, Any] = {}
518
527
  if converted_tools:
519
528
  create_kwargs["tools"] = converted_tools
@@ -533,16 +542,12 @@ class ResponsesCompleteWithReferences(ResponsesSupportCompleteWithReferences):
533
542
  create_kwargs["tool_choice"] = tool_choice
534
543
  if top_p is not None:
535
544
  create_kwargs["top_p"] = top_p
536
- if reasoning is not None:
537
- create_kwargs["reasoning"] = reasoning
538
- if other_options:
539
- for k, v in other_options.items():
540
- create_kwargs.setdefault(k, v)
545
+ for k, v in other_options.items():
546
+ create_kwargs.setdefault(k, v)
541
547
  gpt_request: dict[str, Any] = {
542
548
  "model": model,
543
549
  "input": converted_messages,
544
550
  "stream": True,
545
- "temperature": temperature,
546
551
  **create_kwargs,
547
552
  }
548
553
 
@@ -580,7 +585,6 @@ class ResponsesCompleteWithReferences(ResponsesSupportCompleteWithReferences):
580
585
  model=model,
581
586
  input=converted_messages,
582
587
  stream=True,
583
- temperature=temperature,
584
588
  **create_kwargs,
585
589
  )
586
590
  async with stream:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-toolkit
3
- Version: 2026.36.0.dev6
3
+ Version: 2026.36.0.dev7
4
4
  Summary:
5
5
  Author: Cedric Klinkert, Martin Fadler, Sadique Sheik, Fabian Schläpfer, Pascal Hauri, Ejiroghene Oki-Peter, Rami Azouz, Ricardo Visini, Sanzio Monti
6
6
  Author-email: Cedric Klinkert <cedric.klinkert@unique.ch>, Martin Fadler <martin.fadler@unique.ch>, Sadique Sheik <sadique@unique.ch>, Fabian Schläpfer <fabian@unique.ch>, Pascal Hauri <pascal@unique.ch>, Ejiroghene Oki-Peter <oki-peter.ejiroghene@unique.ai>, Rami Azouz <rami.ext@unique.ch>, Ricardo Visini <ricardo.visini@unique.ch>, Sanzio Monti <sanzio.monti@unique.ai>
@@ -331,8 +331,9 @@ unique_toolkit/experimental/integrations/openai/__init__.py,sha256=1vh39WXdUMQXS
331
331
  unique_toolkit/experimental/integrations/openai/streaming/__init__.py,sha256=JhsXd_NZaCDY5XAT-qY0kumpmpYJ-Nws0ZWo6MGj2sI,47
332
332
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/__init__.py,sha256=-_SsMgmlySX1ae5XSKcoNjiJ1FRgepooJW49NaEhCHs,3217
333
333
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/_async_bridge.py,sha256=j7fK4Q555pXUpSPXdBllKk__b-susz_Jhmh5NqdM_EA,1377
334
+ unique_toolkit/experimental/integrations/openai/streaming/event_routing/_model_params.py,sha256=K-aBUzLbIVOJprafZl6UD-gsJZW2acbY0O8NRFQxocY,4981
334
335
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/chat_completions/__init__.py,sha256=sv9Ldr0GeAwc0I7tyM4NbOf_sYJl602S0T2Z0oHb1nE,578
335
- unique_toolkit/experimental/integrations/openai/streaming/event_routing/chat_completions/complete_with_references.py,sha256=XDQtVQ2vpX-_9bnBCAVP5nY_vZqQZA18f5yO_l2hlUI,21172
336
+ unique_toolkit/experimental/integrations/openai/streaming/event_routing/chat_completions/complete_with_references.py,sha256=YvYIGaHjqeUL-C6MJMKNhTKTinvbZ09mBHvLTRaq4sA,21284
336
337
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/chat_completions/stream_event_router.py,sha256=XNByWclJXeYOeeDBGkUBVY5r1aNckg4B5xf27UybPYc,6199
337
338
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/chat_completions/text_event_handler.py,sha256=1g_EVQ4IpWeorZ3cm9YuN4NMdg19Zv64Ts0ABUzWzR4,5664
338
339
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/chat_completions/tool_call_event_handler.py,sha256=Dxg8odtkSO8ErqLLhh0OEObsB-mSUVJ98eE2huTJUUg,2965
@@ -342,7 +343,7 @@ unique_toolkit/experimental/integrations/openai/streaming/event_routing/protocol
342
343
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/protocols/responses.py,sha256=bM5qSZjJOTWQyPDUPyMasMwZ-Wu7LQKR64OcKVpUY5E,3294
343
344
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/responses/__init__.py,sha256=-8Ey_srOpxX4B_HJhh27h4nZaNC6Y4mk5o8wSLelPwA,847
344
345
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/responses/code_interpreter_event_handler.py,sha256=3WFT-zuVCrTP01riJY9uuu5U8-tmFUD-XYTZYDrGx4w,6013
345
- unique_toolkit/experimental/integrations/openai/streaming/event_routing/responses/complete_with_references.py,sha256=rVcIY7CLPJUVWJrtZDZR5RLd3MnuXbsyL-FdpXoVcjQ,26126
346
+ unique_toolkit/experimental/integrations/openai/streaming/event_routing/responses/complete_with_references.py,sha256=LAIt3xqroByvTw2DQrFs38Tods3ZjBnpOwzliAg17eM,26176
346
347
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/responses/completed_event_handler.py,sha256=BqsJC14KOk_cwelXqyMWlBrFwj5fugkr2gzvwYb3aYM,1360
347
348
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/responses/stream_event_router.py,sha256=hvQHDPmfYD-ZDRR5W079TFYEgQ-vhIGMNRyagiG6RzM,8280
348
349
  unique_toolkit/experimental/integrations/openai/streaming/event_routing/responses/text_delta_event_handler.py,sha256=JB-7W89K4pT6K8A9Z_f_Pow5p-EIGwLoLUSb9BI3qW4,3634
@@ -421,6 +422,6 @@ unique_toolkit/short_term_memory/service.py,sha256=2JMBEBn6GoyK-a0xvdcWpPft-3C74
421
422
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
422
423
  unique_toolkit/smart_rules/compile.py,sha256=vRe0aMJWuVIK-VsqomgxQImzke7MDmwgSxxJfYCa7zw,1386
423
424
  unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
424
- unique_toolkit-2026.36.0.dev6.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
425
- unique_toolkit-2026.36.0.dev6.dist-info/METADATA,sha256=pZseZiZpalFBDEY17j10JOFZrZ51yy8Y9ByMQ5LEdAw,7821
426
- unique_toolkit-2026.36.0.dev6.dist-info/RECORD,,
425
+ unique_toolkit-2026.36.0.dev7.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
426
+ unique_toolkit-2026.36.0.dev7.dist-info/METADATA,sha256=Wqg6T1X6FZR7xev8-poWDfWwRIcdTFIH6hwdfRRB9XE,7821
427
+ unique_toolkit-2026.36.0.dev7.dist-info/RECORD,,