unique_toolkit 1.36.0__py3-none-any.whl → 1.37.0__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,8 +1,11 @@
1
1
  from unique_toolkit.agentic.loop_runner.base import LoopIterationRunner
2
2
  from unique_toolkit.agentic.loop_runner.middleware import (
3
+ QWEN_FORCED_TOOL_CALL_PROMPT_INSTRUCTION,
3
4
  PlanningConfig,
4
5
  PlanningMiddleware,
5
6
  PlanningSchemaConfig,
7
+ QwenForcedToolCallMiddleware,
8
+ is_qwen_model,
6
9
  )
7
10
  from unique_toolkit.agentic.loop_runner.runners import (
8
11
  BasicLoopIterationRunner,
@@ -11,9 +14,12 @@ from unique_toolkit.agentic.loop_runner.runners import (
11
14
 
12
15
  __all__ = [
13
16
  "LoopIterationRunner",
17
+ "QWEN_FORCED_TOOL_CALL_PROMPT_INSTRUCTION",
14
18
  "PlanningConfig",
15
19
  "PlanningMiddleware",
16
20
  "PlanningSchemaConfig",
21
+ "QwenForcedToolCallMiddleware",
22
+ "is_qwen_model",
17
23
  "BasicLoopIterationRunnerConfig",
18
24
  "BasicLoopIterationRunner",
19
25
  ]
@@ -3,5 +3,17 @@ from unique_toolkit.agentic.loop_runner.middleware.planning import (
3
3
  PlanningMiddleware,
4
4
  PlanningSchemaConfig,
5
5
  )
6
+ from unique_toolkit.agentic.loop_runner.middleware.qwen_forced_tool_call import (
7
+ QWEN_FORCED_TOOL_CALL_PROMPT_INSTRUCTION,
8
+ QwenForcedToolCallMiddleware,
9
+ is_qwen_model,
10
+ )
6
11
 
7
- __all__ = ["PlanningConfig", "PlanningMiddleware", "PlanningSchemaConfig"]
12
+ __all__ = [
13
+ "PlanningConfig",
14
+ "PlanningMiddleware",
15
+ "PlanningSchemaConfig",
16
+ "QWEN_FORCED_TOOL_CALL_PROMPT_INSTRUCTION",
17
+ "QwenForcedToolCallMiddleware",
18
+ "is_qwen_model",
19
+ ]
@@ -0,0 +1,13 @@
1
+ from unique_toolkit.agentic.loop_runner.middleware.qwen_forced_tool_call.helpers import (
2
+ is_qwen_model,
3
+ )
4
+ from unique_toolkit.agentic.loop_runner.middleware.qwen_forced_tool_call.qwen_forced_tool_call import (
5
+ QWEN_FORCED_TOOL_CALL_PROMPT_INSTRUCTION,
6
+ QwenForcedToolCallMiddleware,
7
+ )
8
+
9
+ __all__ = [
10
+ "QwenForcedToolCallMiddleware",
11
+ "QWEN_FORCED_TOOL_CALL_PROMPT_INSTRUCTION",
12
+ "is_qwen_model",
13
+ ]
@@ -0,0 +1,33 @@
1
+ from unique_toolkit.language_model.infos import LanguageModelInfo
2
+ from unique_toolkit.language_model.schemas import (
3
+ LanguageModelMessageRole,
4
+ LanguageModelMessages,
5
+ )
6
+
7
+
8
+ def is_qwen_model(*, model: str | LanguageModelInfo | None) -> bool:
9
+ """Check if the model is a Qwen model."""
10
+ if isinstance(model, LanguageModelInfo):
11
+ name = model.name
12
+ # name is an Enum with a .value attribute
13
+ return "qwen" in str(getattr(name, "value", name)).lower()
14
+ elif isinstance(model, str):
15
+ return "qwen" in model.lower()
16
+ return False
17
+
18
+
19
+ def append_qwen_forced_tool_call_instruction(
20
+ *,
21
+ messages: LanguageModelMessages,
22
+ forced_tool_call_instruction: str,
23
+ ) -> LanguageModelMessages:
24
+ """Append tool call instruction to the last user message for Qwen models."""
25
+ messages_list = list(messages)
26
+ for i in range(len(messages_list) - 1, -1, -1):
27
+ msg = messages_list[i]
28
+ if msg.role == LanguageModelMessageRole.USER and isinstance(msg.content, str):
29
+ messages_list[i] = msg.model_copy(
30
+ update={"content": msg.content + "\n" + forced_tool_call_instruction}
31
+ )
32
+ break
33
+ return LanguageModelMessages(root=messages_list)
@@ -0,0 +1,50 @@
1
+ import logging
2
+ from typing import Unpack
3
+
4
+ from unique_toolkit.agentic.loop_runner.base import (
5
+ LoopIterationRunner,
6
+ _LoopIterationRunnerKwargs,
7
+ )
8
+ from unique_toolkit.agentic.loop_runner.middleware.qwen_forced_tool_call.helpers import (
9
+ append_qwen_forced_tool_call_instruction,
10
+ )
11
+ from unique_toolkit.chat.service import LanguageModelStreamResponse
12
+
13
+ _LOGGER = logging.getLogger(__name__)
14
+
15
+ QWEN_FORCED_TOOL_CALL_PROMPT_INSTRUCTION = (
16
+ "Tool Call Instruction: \nYou always have to return a tool call. "
17
+ "You must start the response with <tool_call> and end with </tool_call>. "
18
+ "Do NOT provide natural language explanations, summaries, or any text outside the <tool_call> block."
19
+ )
20
+
21
+
22
+ class QwenForcedToolCallMiddleware(LoopIterationRunner):
23
+ def __init__(
24
+ self,
25
+ *,
26
+ loop_runner: LoopIterationRunner,
27
+ qwen_forced_tool_call_prompt_instruction: str,
28
+ ) -> None:
29
+ self._qwen_forced_tool_call_prompt_instruction = (
30
+ qwen_forced_tool_call_prompt_instruction
31
+ )
32
+ self._loop_runner = loop_runner
33
+
34
+ async def __call__(
35
+ self, **kwargs: Unpack[_LoopIterationRunnerKwargs]
36
+ ) -> LanguageModelStreamResponse:
37
+ tool_choices = kwargs.get("tool_choices") or []
38
+ iteration_index = kwargs["iteration_index"]
39
+
40
+ # For Qwen models, append tool call instruction to the last user message. These models ignore the parameter tool_choice.
41
+ if len(tool_choices) > 0 and iteration_index == 0 and kwargs.get("messages"):
42
+ _LOGGER.info(
43
+ "Appending tool call instruction to the last user message for Qwen models to force tool calls."
44
+ )
45
+ kwargs["messages"] = append_qwen_forced_tool_call_instruction(
46
+ messages=kwargs["messages"],
47
+ forced_tool_call_instruction=self._qwen_forced_tool_call_prompt_instruction,
48
+ )
49
+
50
+ return await self._loop_runner(**kwargs)
@@ -56,13 +56,15 @@ class BasicLoopIterationRunner(LoopIterationRunner):
56
56
 
57
57
  responses: list[LanguageModelStreamResponse] = []
58
58
 
59
+ available_tools = {t.name: t for t in kwargs.get("tools") or []}
60
+
59
61
  for opt in tool_choices:
60
- responses.append(
61
- await stream_response(
62
- loop_runner_kwargs=kwargs,
63
- tool_choice=opt,
64
- )
65
- )
62
+ func_name = opt.get("function", {}).get("name")
63
+ limited_tool = available_tools.get(func_name) if func_name else None
64
+ stream_kwargs = {"loop_runner_kwargs": kwargs, "tool_choice": opt}
65
+ if limited_tool:
66
+ stream_kwargs["tools"] = [limited_tool]
67
+ responses.append(await stream_response(**stream_kwargs))
66
68
 
67
69
  # Merge responses and refs:
68
70
  tool_calls = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 1.36.0
3
+ Version: 1.37.0
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Cedric Klinkert
@@ -121,6 +121,9 @@ All notable changes to this project will be documented in this file.
121
121
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
122
122
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
123
123
 
124
+ ## [1.37.0] - 2025-12-15
125
+ - Adding a prompt appendix to enforce forced tool calls when using Qwen models
126
+
124
127
  ## [1.36.0] - 2025-12-11
125
128
  - Add support for a sub agent tool system reminder when no references are present in the sub agent response.
126
129
 
@@ -62,15 +62,18 @@ unique_toolkit/agentic/history_manager/history_construction_with_contents.py,sha
62
62
  unique_toolkit/agentic/history_manager/history_manager.py,sha256=7V7_173XkAjc8otBACF0G3dbqRs34FSlURbBPrE95Wk,9537
63
63
  unique_toolkit/agentic/history_manager/loop_token_reducer.py,sha256=3c-uonDovtanEJUpAO4zlA4-n9MS_Ws_V0Yb6G7hPM0,20172
64
64
  unique_toolkit/agentic/history_manager/utils.py,sha256=VIn_UmcR3jHtpux0qp5lQQzczgAm8XYSeQiPo87jC3A,3143
65
- unique_toolkit/agentic/loop_runner/__init__.py,sha256=QLCYmIyfcKQEbuv1Xm0VuR_xC8JyD2_aMIvt1TRFzvw,517
65
+ unique_toolkit/agentic/loop_runner/__init__.py,sha256=Kg-6Zgt--iIYk-biAkvnuwbButxPRPoVlrpppUblm0s,721
66
66
  unique_toolkit/agentic/loop_runner/_stream_handler_utils.py,sha256=FTGc5y8wkDnwnRVSYEdandgKz-FiySOsrTFFMadwP6E,1706
67
67
  unique_toolkit/agentic/loop_runner/base.py,sha256=3g4PalzV00o8kcRwHds2c2rtxW4idD7_7vS2Z7GkMvQ,1370
68
- unique_toolkit/agentic/loop_runner/middleware/__init__.py,sha256=_yeRH8xYigfJZyQ5-5lZUo2RXDJkGfftCQrKFm2rWb4,217
68
+ unique_toolkit/agentic/loop_runner/middleware/__init__.py,sha256=r9c_Ml2g7obglnEC7gDihSTUrZ6s1sNGCMuMXR0Yl90,520
69
69
  unique_toolkit/agentic/loop_runner/middleware/planning/__init__.py,sha256=Y9MlihNA8suNREixW98RF45bj0EMtD_tQuDrO2MEML4,304
70
70
  unique_toolkit/agentic/loop_runner/middleware/planning/planning.py,sha256=5d8kyipuFyI_1SQG49f165eOwSHeSG1qjbJQ7laeTsk,3218
71
71
  unique_toolkit/agentic/loop_runner/middleware/planning/schema.py,sha256=76C36CWCLfDAYYqtaQlhXsmkWM1fCqf8j-l5afQREKA,2869
72
+ unique_toolkit/agentic/loop_runner/middleware/qwen_forced_tool_call/__init__.py,sha256=lP8N8XLvV1irvGC6Q0FedAlBx-T2UPKotDRwkdx7neA,417
73
+ unique_toolkit/agentic/loop_runner/middleware/qwen_forced_tool_call/helpers.py,sha256=DGKW9i7mCXNTejO2fotCmqSzI2b5k89ybkJA0QQ75qU,1234
74
+ unique_toolkit/agentic/loop_runner/middleware/qwen_forced_tool_call/qwen_forced_tool_call.py,sha256=dp08YgL4UwVDTsJB-z7eiJn7zWEbqG8eiWfMOcvKZdI,1955
72
75
  unique_toolkit/agentic/loop_runner/runners/__init__.py,sha256=raaNpHcTfXkYURy0ysyacispSdQzYPDoG17PyR57uK4,205
73
- unique_toolkit/agentic/loop_runner/runners/basic.py,sha256=3swSPsefV1X-ltUC8iNAOrn9PL0abUUfWXJjhM4sShA,3116
76
+ unique_toolkit/agentic/loop_runner/runners/basic.py,sha256=SQzwkLEiraU8neXvPEc_uOBzC17PkCpOEsFrZ79YGCY,3379
74
77
  unique_toolkit/agentic/message_log_manager/__init__.py,sha256=3-KY_sGkPbNoSnrzwPY0FQIJNnsz4NHXvocXgGRUeuE,169
75
78
  unique_toolkit/agentic/message_log_manager/service.py,sha256=AiuIq2dKQg9Y8bEYgGcve1X8-WRRdqPZXaZXXLJxfFM,3057
76
79
  unique_toolkit/agentic/postprocessor/postprocessor_manager.py,sha256=s6HFhA61TE05aAay15NFTWI1JvdSlxmGpEVfpBbGFyM,7684
@@ -209,7 +212,7 @@ unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBu
209
212
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
210
213
  unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
211
214
  unique_toolkit/test_utilities/events.py,sha256=_mwV2bs5iLjxS1ynDCjaIq-gjjKhXYCK-iy3dRfvO3g,6410
212
- unique_toolkit-1.36.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
213
- unique_toolkit-1.36.0.dist-info/METADATA,sha256=b-BMlh-JACksT29xq0D-VY8p3OZo-wXrrwJgn6Ronf0,46179
214
- unique_toolkit-1.36.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
215
- unique_toolkit-1.36.0.dist-info/RECORD,,
215
+ unique_toolkit-1.37.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
216
+ unique_toolkit-1.37.0.dist-info/METADATA,sha256=P8N25tqQqezFmyaOYAXtauCGKHdeGaxMa9kyfd-x1_Q,46284
217
+ unique_toolkit-1.37.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
218
+ unique_toolkit-1.37.0.dist-info/RECORD,,