prompture 0.0.40.dev1__tar.gz → 0.0.41__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.
Files changed (139) hide show
  1. {prompture-0.0.40.dev1 → prompture-0.0.41}/.env.copy +18 -0
  2. {prompture-0.0.40.dev1/prompture.egg-info → prompture-0.0.41}/PKG-INFO +1 -1
  3. prompture-0.0.41/VERSION +1 -0
  4. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/_version.py +2 -2
  5. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/__init__.py +39 -0
  6. prompture-0.0.41/prompture/drivers/async_modelscope_driver.py +286 -0
  7. prompture-0.0.41/prompture/drivers/async_moonshot_driver.py +311 -0
  8. prompture-0.0.41/prompture/drivers/async_openrouter_driver.py +290 -0
  9. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_registry.py +30 -0
  10. prompture-0.0.41/prompture/drivers/async_zai_driver.py +302 -0
  11. prompture-0.0.41/prompture/drivers/modelscope_driver.py +303 -0
  12. prompture-0.0.41/prompture/drivers/moonshot_driver.py +341 -0
  13. prompture-0.0.41/prompture/drivers/openrouter_driver.py +348 -0
  14. prompture-0.0.41/prompture/drivers/zai_driver.py +317 -0
  15. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/model_rates.py +2 -0
  16. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/settings.py +15 -0
  17. {prompture-0.0.40.dev1 → prompture-0.0.41/prompture.egg-info}/PKG-INFO +1 -1
  18. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture.egg-info/SOURCES.txt +7 -0
  19. prompture-0.0.40.dev1/prompture/drivers/async_openrouter_driver.py +0 -102
  20. prompture-0.0.40.dev1/prompture/drivers/openrouter_driver.py +0 -152
  21. {prompture-0.0.40.dev1 → prompture-0.0.41}/.claude/skills/add-driver/SKILL.md +0 -0
  22. {prompture-0.0.40.dev1 → prompture-0.0.41}/.claude/skills/add-driver/references/driver-template.md +0 -0
  23. {prompture-0.0.40.dev1 → prompture-0.0.41}/.claude/skills/add-example/SKILL.md +0 -0
  24. {prompture-0.0.40.dev1 → prompture-0.0.41}/.claude/skills/add-field/SKILL.md +0 -0
  25. {prompture-0.0.40.dev1 → prompture-0.0.41}/.claude/skills/add-test/SKILL.md +0 -0
  26. {prompture-0.0.40.dev1 → prompture-0.0.41}/.claude/skills/run-tests/SKILL.md +0 -0
  27. {prompture-0.0.40.dev1 → prompture-0.0.41}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
  28. {prompture-0.0.40.dev1 → prompture-0.0.41}/.claude/skills/update-pricing/SKILL.md +0 -0
  29. {prompture-0.0.40.dev1 → prompture-0.0.41}/.github/FUNDING.yml +0 -0
  30. {prompture-0.0.40.dev1 → prompture-0.0.41}/.github/scripts/update_docs_version.py +0 -0
  31. {prompture-0.0.40.dev1 → prompture-0.0.41}/.github/scripts/update_wrapper_version.py +0 -0
  32. {prompture-0.0.40.dev1 → prompture-0.0.41}/.github/workflows/dev.yml +0 -0
  33. {prompture-0.0.40.dev1 → prompture-0.0.41}/.github/workflows/documentation.yml +0 -0
  34. {prompture-0.0.40.dev1 → prompture-0.0.41}/.github/workflows/publish.yml +0 -0
  35. {prompture-0.0.40.dev1 → prompture-0.0.41}/CLAUDE.md +0 -0
  36. {prompture-0.0.40.dev1 → prompture-0.0.41}/LICENSE +0 -0
  37. {prompture-0.0.40.dev1 → prompture-0.0.41}/MANIFEST.in +0 -0
  38. {prompture-0.0.40.dev1 → prompture-0.0.41}/README.md +0 -0
  39. {prompture-0.0.40.dev1 → prompture-0.0.41}/ROADMAP.md +0 -0
  40. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/_static/custom.css +0 -0
  41. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/_templates/footer.html +0 -0
  42. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/api/core.rst +0 -0
  43. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/api/drivers.rst +0 -0
  44. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/api/field_definitions.rst +0 -0
  45. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/api/index.rst +0 -0
  46. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/api/runner.rst +0 -0
  47. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/api/tools.rst +0 -0
  48. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/api/validator.rst +0 -0
  49. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/conf.py +0 -0
  50. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/contributing.rst +0 -0
  51. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/examples.rst +0 -0
  52. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/field_definitions_reference.rst +0 -0
  53. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/index.rst +0 -0
  54. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/installation.rst +0 -0
  55. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/quickstart.rst +0 -0
  56. {prompture-0.0.40.dev1 → prompture-0.0.41}/docs/source/toon_input_guide.rst +0 -0
  57. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/README.md +0 -0
  58. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/llm_to_json/README.md +0 -0
  59. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
  60. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/llm_to_json/pyproject.toml +0 -0
  61. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/llm_to_json/test.py +0 -0
  62. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/llm_to_toon/README.md +0 -0
  63. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
  64. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/llm_to_toon/pyproject.toml +0 -0
  65. {prompture-0.0.40.dev1 → prompture-0.0.41}/packages/llm_to_toon/test.py +0 -0
  66. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/__init__.py +0 -0
  67. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/agent.py +0 -0
  68. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/agent_types.py +0 -0
  69. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/aio/__init__.py +0 -0
  70. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/async_agent.py +0 -0
  71. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/async_conversation.py +0 -0
  72. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/async_core.py +0 -0
  73. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/async_driver.py +0 -0
  74. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/async_groups.py +0 -0
  75. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/cache.py +0 -0
  76. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/callbacks.py +0 -0
  77. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/cli.py +0 -0
  78. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/conversation.py +0 -0
  79. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/core.py +0 -0
  80. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/cost_mixin.py +0 -0
  81. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/discovery.py +0 -0
  82. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/driver.py +0 -0
  83. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/airllm_driver.py +0 -0
  84. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_airllm_driver.py +0 -0
  85. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_azure_driver.py +0 -0
  86. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_claude_driver.py +0 -0
  87. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_google_driver.py +0 -0
  88. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_grok_driver.py +0 -0
  89. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_groq_driver.py +0 -0
  90. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_hugging_driver.py +0 -0
  91. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_lmstudio_driver.py +0 -0
  92. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_local_http_driver.py +0 -0
  93. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_ollama_driver.py +0 -0
  94. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/async_openai_driver.py +0 -0
  95. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/azure_driver.py +0 -0
  96. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/claude_driver.py +0 -0
  97. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/google_driver.py +0 -0
  98. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/grok_driver.py +0 -0
  99. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/groq_driver.py +0 -0
  100. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/hugging_driver.py +0 -0
  101. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/lmstudio_driver.py +0 -0
  102. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/local_http_driver.py +0 -0
  103. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/ollama_driver.py +0 -0
  104. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/openai_driver.py +0 -0
  105. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/registry.py +0 -0
  106. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/drivers/vision_helpers.py +0 -0
  107. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/field_definitions.py +0 -0
  108. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/group_types.py +0 -0
  109. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/groups.py +0 -0
  110. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/image.py +0 -0
  111. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/ledger.py +0 -0
  112. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/logging.py +0 -0
  113. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/persistence.py +0 -0
  114. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/persona.py +0 -0
  115. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/runner.py +0 -0
  116. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/__init__.py +0 -0
  117. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/generator.py +0 -0
  118. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
  119. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/templates/README.md.j2 +0 -0
  120. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/templates/config.py.j2 +0 -0
  121. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/templates/env.example.j2 +0 -0
  122. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/templates/main.py.j2 +0 -0
  123. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/templates/models.py.j2 +0 -0
  124. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
  125. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/serialization.py +0 -0
  126. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/server.py +0 -0
  127. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/session.py +0 -0
  128. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/tools.py +0 -0
  129. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/tools_schema.py +0 -0
  130. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture/validator.py +0 -0
  131. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture.egg-info/dependency_links.txt +0 -0
  132. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture.egg-info/entry_points.txt +0 -0
  133. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture.egg-info/requires.txt +0 -0
  134. {prompture-0.0.40.dev1 → prompture-0.0.41}/prompture.egg-info/top_level.txt +0 -0
  135. {prompture-0.0.40.dev1 → prompture-0.0.41}/pyproject.toml +0 -0
  136. {prompture-0.0.40.dev1 → prompture-0.0.41}/requirements.txt +0 -0
  137. {prompture-0.0.40.dev1 → prompture-0.0.41}/setup.cfg +0 -0
  138. {prompture-0.0.40.dev1 → prompture-0.0.41}/test.py +0 -0
  139. {prompture-0.0.40.dev1 → prompture-0.0.41}/test_version_diagnosis.py +0 -0
@@ -60,6 +60,24 @@ OPENROUTER_MODEL=openai/gpt-3.5-turbo
60
60
  GROK_API_KEY=your-api-key-here
61
61
  GROK_MODEL=grok-4-fast-reasoning
62
62
 
63
+ # Moonshot AI (Kimi) Configuration
64
+ # Required if AI_PROVIDER=moonshot
65
+ MOONSHOT_API_KEY=
66
+ MOONSHOT_MODEL=kimi-k2-0905-preview
67
+ MOONSHOT_ENDPOINT=https://api.moonshot.ai/v1
68
+
69
+ # Z.ai (Zhipu AI) Configuration
70
+ # Required if AI_PROVIDER=zai
71
+ ZHIPU_API_KEY=
72
+ ZHIPU_MODEL=glm-4.7
73
+ ZHIPU_ENDPOINT=https://api.z.ai/api/paas/v4
74
+
75
+ # ModelScope (Alibaba Cloud) Configuration
76
+ # Required if AI_PROVIDER=modelscope
77
+ MODELSCOPE_API_KEY=
78
+ MODELSCOPE_MODEL=Qwen/Qwen3-235B-A22B-Instruct-2507
79
+ MODELSCOPE_ENDPOINT=https://api-inference.modelscope.cn/v1
80
+
63
81
  # AirLLM Configuration
64
82
  AIRLLM_MODEL=meta-llama/Llama-2-7b-hf
65
83
  AIRLLM_COMPRESSION=
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prompture
3
- Version: 0.0.40.dev1
3
+ Version: 0.0.41
4
4
  Summary: Ask LLMs to return structured JSON and run cross-model tests. API-first.
5
5
  Author-email: Juan Denis <juan@vene.co>
6
6
  License-Expression: MIT
@@ -0,0 +1 @@
1
+ 0.0.41
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.40.dev1'
32
- __version_tuple__ = version_tuple = (0, 0, 40, 'dev1')
31
+ __version__ = version = '0.0.41'
32
+ __version_tuple__ = version_tuple = (0, 0, 41)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -37,10 +37,13 @@ from .async_groq_driver import AsyncGroqDriver
37
37
  from .async_hugging_driver import AsyncHuggingFaceDriver
38
38
  from .async_lmstudio_driver import AsyncLMStudioDriver
39
39
  from .async_local_http_driver import AsyncLocalHTTPDriver
40
+ from .async_modelscope_driver import AsyncModelScopeDriver
41
+ from .async_moonshot_driver import AsyncMoonshotDriver
40
42
  from .async_ollama_driver import AsyncOllamaDriver
41
43
  from .async_openai_driver import AsyncOpenAIDriver
42
44
  from .async_openrouter_driver import AsyncOpenRouterDriver
43
45
  from .async_registry import ASYNC_DRIVER_REGISTRY, get_async_driver, get_async_driver_for_model
46
+ from .async_zai_driver import AsyncZaiDriver
44
47
  from .azure_driver import AzureDriver
45
48
  from .claude_driver import ClaudeDriver
46
49
  from .google_driver import GoogleDriver
@@ -48,6 +51,8 @@ from .grok_driver import GrokDriver
48
51
  from .groq_driver import GroqDriver
49
52
  from .lmstudio_driver import LMStudioDriver
50
53
  from .local_http_driver import LocalHTTPDriver
54
+ from .modelscope_driver import ModelScopeDriver
55
+ from .moonshot_driver import MoonshotDriver
51
56
  from .ollama_driver import OllamaDriver
52
57
  from .openai_driver import OpenAIDriver
53
58
  from .openrouter_driver import OpenRouterDriver
@@ -65,6 +70,7 @@ from .registry import (
65
70
  unregister_async_driver,
66
71
  unregister_driver,
67
72
  )
73
+ from .zai_driver import ZaiDriver
68
74
 
69
75
  # Register built-in sync drivers
70
76
  register_driver(
@@ -123,6 +129,33 @@ register_driver(
123
129
  lambda model=None: GrokDriver(api_key=settings.grok_api_key, model=model or settings.grok_model),
124
130
  overwrite=True,
125
131
  )
132
+ register_driver(
133
+ "moonshot",
134
+ lambda model=None: MoonshotDriver(
135
+ api_key=settings.moonshot_api_key,
136
+ model=model or settings.moonshot_model,
137
+ endpoint=settings.moonshot_endpoint,
138
+ ),
139
+ overwrite=True,
140
+ )
141
+ register_driver(
142
+ "modelscope",
143
+ lambda model=None: ModelScopeDriver(
144
+ api_key=settings.modelscope_api_key,
145
+ model=model or settings.modelscope_model,
146
+ endpoint=settings.modelscope_endpoint,
147
+ ),
148
+ overwrite=True,
149
+ )
150
+ register_driver(
151
+ "zai",
152
+ lambda model=None: ZaiDriver(
153
+ api_key=settings.zhipu_api_key,
154
+ model=model or settings.zhipu_model,
155
+ endpoint=settings.zhipu_endpoint,
156
+ ),
157
+ overwrite=True,
158
+ )
126
159
  register_driver(
127
160
  "airllm",
128
161
  lambda model=None: AirLLMDriver(
@@ -197,9 +230,12 @@ __all__ = [
197
230
  "AsyncHuggingFaceDriver",
198
231
  "AsyncLMStudioDriver",
199
232
  "AsyncLocalHTTPDriver",
233
+ "AsyncModelScopeDriver",
234
+ "AsyncMoonshotDriver",
200
235
  "AsyncOllamaDriver",
201
236
  "AsyncOpenAIDriver",
202
237
  "AsyncOpenRouterDriver",
238
+ "AsyncZaiDriver",
203
239
  "AzureDriver",
204
240
  "ClaudeDriver",
205
241
  "GoogleDriver",
@@ -207,9 +243,12 @@ __all__ = [
207
243
  "GroqDriver",
208
244
  "LMStudioDriver",
209
245
  "LocalHTTPDriver",
246
+ "ModelScopeDriver",
247
+ "MoonshotDriver",
210
248
  "OllamaDriver",
211
249
  "OpenAIDriver",
212
250
  "OpenRouterDriver",
251
+ "ZaiDriver",
213
252
  "get_async_driver",
214
253
  "get_async_driver_for_model",
215
254
  # Factory functions
@@ -0,0 +1,286 @@
1
+ """Async ModelScope (Alibaba Cloud) driver using httpx.
2
+
3
+ No hardcoded pricing — ModelScope's free tier has no per-token cost.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import os
10
+ from collections.abc import AsyncIterator
11
+ from typing import Any
12
+
13
+ import httpx
14
+
15
+ from ..async_driver import AsyncDriver
16
+ from ..cost_mixin import CostMixin
17
+ from .modelscope_driver import ModelScopeDriver
18
+
19
+
20
+ class AsyncModelScopeDriver(CostMixin, AsyncDriver):
21
+ supports_json_mode = True
22
+ supports_json_schema = False
23
+ supports_tool_use = True
24
+ supports_streaming = True
25
+ supports_vision = False
26
+
27
+ MODEL_PRICING = ModelScopeDriver.MODEL_PRICING
28
+
29
+ def __init__(
30
+ self,
31
+ api_key: str | None = None,
32
+ model: str = "Qwen/Qwen3-235B-A22B-Instruct-2507",
33
+ endpoint: str = "https://api-inference.modelscope.cn/v1",
34
+ ):
35
+ self.api_key = api_key or os.getenv("MODELSCOPE_API_KEY")
36
+ if not self.api_key:
37
+ raise ValueError("ModelScope API key not found. Set MODELSCOPE_API_KEY env var.")
38
+ self.model = model
39
+ self.base_url = endpoint.rstrip("/")
40
+ self.headers = {
41
+ "Authorization": f"Bearer {self.api_key}",
42
+ "Content-Type": "application/json",
43
+ }
44
+
45
+ supports_messages = True
46
+
47
+ async def generate(self, prompt: str, options: dict[str, Any]) -> dict[str, Any]:
48
+ messages = [{"role": "user", "content": prompt}]
49
+ return await self._do_generate(messages, options)
50
+
51
+ async def generate_messages(self, messages: list[dict[str, str]], options: dict[str, Any]) -> dict[str, Any]:
52
+ return await self._do_generate(messages, options)
53
+
54
+ async def _do_generate(self, messages: list[dict[str, str]], options: dict[str, Any]) -> dict[str, Any]:
55
+ model = options.get("model", self.model)
56
+
57
+ model_config = self._get_model_config("modelscope", model)
58
+ tokens_param = model_config["tokens_param"]
59
+ supports_temperature = model_config["supports_temperature"]
60
+
61
+ self._validate_model_capabilities(
62
+ "modelscope",
63
+ model,
64
+ using_json_schema=bool(options.get("json_schema")),
65
+ )
66
+
67
+ opts = {"temperature": 1.0, "max_tokens": 512, **options}
68
+
69
+ data: dict[str, Any] = {
70
+ "model": model,
71
+ "messages": messages,
72
+ }
73
+ data[tokens_param] = opts.get("max_tokens", 512)
74
+
75
+ if supports_temperature and "temperature" in opts:
76
+ data["temperature"] = opts["temperature"]
77
+
78
+ if options.get("json_mode"):
79
+ data["response_format"] = {"type": "json_object"}
80
+
81
+ async with httpx.AsyncClient() as client:
82
+ try:
83
+ response = await client.post(
84
+ f"{self.base_url}/chat/completions",
85
+ headers=self.headers,
86
+ json=data,
87
+ timeout=120,
88
+ )
89
+ response.raise_for_status()
90
+ resp = response.json()
91
+ except httpx.HTTPStatusError as e:
92
+ error_msg = f"ModelScope API request failed: {e!s}"
93
+ raise RuntimeError(error_msg) from e
94
+ except Exception as e:
95
+ raise RuntimeError(f"ModelScope API request failed: {e!s}") from e
96
+
97
+ usage = resp.get("usage", {})
98
+ prompt_tokens = usage.get("prompt_tokens", 0)
99
+ completion_tokens = usage.get("completion_tokens", 0)
100
+ total_tokens = usage.get("total_tokens", 0)
101
+
102
+ total_cost = self._calculate_cost("modelscope", model, prompt_tokens, completion_tokens)
103
+
104
+ meta = {
105
+ "prompt_tokens": prompt_tokens,
106
+ "completion_tokens": completion_tokens,
107
+ "total_tokens": total_tokens,
108
+ "cost": round(total_cost, 6),
109
+ "raw_response": resp,
110
+ "model_name": model,
111
+ }
112
+
113
+ text = resp["choices"][0]["message"]["content"]
114
+ return {"text": text, "meta": meta}
115
+
116
+ # ------------------------------------------------------------------
117
+ # Tool use
118
+ # ------------------------------------------------------------------
119
+
120
+ async def generate_messages_with_tools(
121
+ self,
122
+ messages: list[dict[str, Any]],
123
+ tools: list[dict[str, Any]],
124
+ options: dict[str, Any],
125
+ ) -> dict[str, Any]:
126
+ """Generate a response that may include tool calls."""
127
+ model = options.get("model", self.model)
128
+ model_config = self._get_model_config("modelscope", model)
129
+ tokens_param = model_config["tokens_param"]
130
+ supports_temperature = model_config["supports_temperature"]
131
+
132
+ self._validate_model_capabilities("modelscope", model, using_tool_use=True)
133
+
134
+ opts = {"temperature": 1.0, "max_tokens": 512, **options}
135
+
136
+ data: dict[str, Any] = {
137
+ "model": model,
138
+ "messages": messages,
139
+ "tools": tools,
140
+ }
141
+ data[tokens_param] = opts.get("max_tokens", 512)
142
+
143
+ if supports_temperature and "temperature" in opts:
144
+ data["temperature"] = opts["temperature"]
145
+
146
+ if "tool_choice" in options:
147
+ data["tool_choice"] = options["tool_choice"]
148
+
149
+ async with httpx.AsyncClient() as client:
150
+ try:
151
+ response = await client.post(
152
+ f"{self.base_url}/chat/completions",
153
+ headers=self.headers,
154
+ json=data,
155
+ timeout=120,
156
+ )
157
+ response.raise_for_status()
158
+ resp = response.json()
159
+ except httpx.HTTPStatusError as e:
160
+ error_msg = f"ModelScope API request failed: {e!s}"
161
+ raise RuntimeError(error_msg) from e
162
+ except Exception as e:
163
+ raise RuntimeError(f"ModelScope API request failed: {e!s}") from e
164
+
165
+ usage = resp.get("usage", {})
166
+ prompt_tokens = usage.get("prompt_tokens", 0)
167
+ completion_tokens = usage.get("completion_tokens", 0)
168
+ total_tokens = usage.get("total_tokens", 0)
169
+ total_cost = self._calculate_cost("modelscope", model, prompt_tokens, completion_tokens)
170
+
171
+ meta = {
172
+ "prompt_tokens": prompt_tokens,
173
+ "completion_tokens": completion_tokens,
174
+ "total_tokens": total_tokens,
175
+ "cost": round(total_cost, 6),
176
+ "raw_response": resp,
177
+ "model_name": model,
178
+ }
179
+
180
+ choice = resp["choices"][0]
181
+ text = choice["message"].get("content") or ""
182
+ stop_reason = choice.get("finish_reason")
183
+
184
+ tool_calls_out: list[dict[str, Any]] = []
185
+ for tc in choice["message"].get("tool_calls", []):
186
+ try:
187
+ args = json.loads(tc["function"]["arguments"])
188
+ except (json.JSONDecodeError, TypeError):
189
+ args = {}
190
+ tool_calls_out.append(
191
+ {
192
+ "id": tc["id"],
193
+ "name": tc["function"]["name"],
194
+ "arguments": args,
195
+ }
196
+ )
197
+
198
+ return {
199
+ "text": text,
200
+ "meta": meta,
201
+ "tool_calls": tool_calls_out,
202
+ "stop_reason": stop_reason,
203
+ }
204
+
205
+ # ------------------------------------------------------------------
206
+ # Streaming
207
+ # ------------------------------------------------------------------
208
+
209
+ async def generate_messages_stream(
210
+ self,
211
+ messages: list[dict[str, Any]],
212
+ options: dict[str, Any],
213
+ ) -> AsyncIterator[dict[str, Any]]:
214
+ """Yield response chunks via ModelScope streaming API."""
215
+ model = options.get("model", self.model)
216
+ model_config = self._get_model_config("modelscope", model)
217
+ tokens_param = model_config["tokens_param"]
218
+ supports_temperature = model_config["supports_temperature"]
219
+
220
+ opts = {"temperature": 1.0, "max_tokens": 512, **options}
221
+
222
+ data: dict[str, Any] = {
223
+ "model": model,
224
+ "messages": messages,
225
+ "stream": True,
226
+ "stream_options": {"include_usage": True},
227
+ }
228
+ data[tokens_param] = opts.get("max_tokens", 512)
229
+
230
+ if supports_temperature and "temperature" in opts:
231
+ data["temperature"] = opts["temperature"]
232
+
233
+ full_text = ""
234
+ prompt_tokens = 0
235
+ completion_tokens = 0
236
+
237
+ async with (
238
+ httpx.AsyncClient() as client,
239
+ client.stream(
240
+ "POST",
241
+ f"{self.base_url}/chat/completions",
242
+ headers=self.headers,
243
+ json=data,
244
+ timeout=120,
245
+ ) as response,
246
+ ):
247
+ response.raise_for_status()
248
+ async for line in response.aiter_lines():
249
+ if not line or not line.startswith("data: "):
250
+ continue
251
+ payload = line[len("data: ") :]
252
+ if payload.strip() == "[DONE]":
253
+ break
254
+ try:
255
+ chunk = json.loads(payload)
256
+ except json.JSONDecodeError:
257
+ continue
258
+
259
+ usage = chunk.get("usage")
260
+ if usage:
261
+ prompt_tokens = usage.get("prompt_tokens", 0)
262
+ completion_tokens = usage.get("completion_tokens", 0)
263
+
264
+ choices = chunk.get("choices", [])
265
+ if choices:
266
+ delta = choices[0].get("delta", {})
267
+ content = delta.get("content", "")
268
+ if content:
269
+ full_text += content
270
+ yield {"type": "delta", "text": content}
271
+
272
+ total_tokens = prompt_tokens + completion_tokens
273
+ total_cost = self._calculate_cost("modelscope", model, prompt_tokens, completion_tokens)
274
+
275
+ yield {
276
+ "type": "done",
277
+ "text": full_text,
278
+ "meta": {
279
+ "prompt_tokens": prompt_tokens,
280
+ "completion_tokens": completion_tokens,
281
+ "total_tokens": total_tokens,
282
+ "cost": round(total_cost, 6),
283
+ "raw_response": {},
284
+ "model_name": model,
285
+ },
286
+ }