prompture 0.0.42.dev1__tar.gz → 0.0.45.dev1__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 (136) hide show
  1. {prompture-0.0.42.dev1/prompture.egg-info → prompture-0.0.45.dev1}/PKG-INFO +1 -1
  2. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/_version.py +2 -2
  3. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/async_groups.py +63 -0
  4. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/discovery.py +20 -0
  5. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_moonshot_driver.py +35 -15
  6. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/moonshot_driver.py +35 -16
  7. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/groups.py +42 -0
  8. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1/prompture.egg-info}/PKG-INFO +1 -1
  9. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.claude/skills/add-driver/SKILL.md +0 -0
  10. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.claude/skills/add-driver/references/driver-template.md +0 -0
  11. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.claude/skills/add-example/SKILL.md +0 -0
  12. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.claude/skills/add-field/SKILL.md +0 -0
  13. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.claude/skills/add-test/SKILL.md +0 -0
  14. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.claude/skills/run-tests/SKILL.md +0 -0
  15. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
  16. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.claude/skills/update-pricing/SKILL.md +0 -0
  17. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.env.copy +0 -0
  18. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.github/FUNDING.yml +0 -0
  19. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.github/scripts/update_docs_version.py +0 -0
  20. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.github/scripts/update_wrapper_version.py +0 -0
  21. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.github/workflows/dev.yml +0 -0
  22. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.github/workflows/documentation.yml +0 -0
  23. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/.github/workflows/publish.yml +0 -0
  24. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/CLAUDE.md +0 -0
  25. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/LICENSE +0 -0
  26. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/MANIFEST.in +0 -0
  27. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/README.md +0 -0
  28. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/ROADMAP.md +0 -0
  29. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/_static/custom.css +0 -0
  30. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/_templates/footer.html +0 -0
  31. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/api/core.rst +0 -0
  32. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/api/drivers.rst +0 -0
  33. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/api/field_definitions.rst +0 -0
  34. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/api/index.rst +0 -0
  35. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/api/runner.rst +0 -0
  36. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/api/tools.rst +0 -0
  37. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/api/validator.rst +0 -0
  38. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/conf.py +0 -0
  39. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/contributing.rst +0 -0
  40. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/examples.rst +0 -0
  41. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/field_definitions_reference.rst +0 -0
  42. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/index.rst +0 -0
  43. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/installation.rst +0 -0
  44. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/quickstart.rst +0 -0
  45. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/docs/source/toon_input_guide.rst +0 -0
  46. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/README.md +0 -0
  47. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/llm_to_json/README.md +0 -0
  48. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
  49. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/llm_to_json/pyproject.toml +0 -0
  50. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/llm_to_json/test.py +0 -0
  51. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/llm_to_toon/README.md +0 -0
  52. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
  53. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/llm_to_toon/pyproject.toml +0 -0
  54. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/packages/llm_to_toon/test.py +0 -0
  55. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/__init__.py +0 -0
  56. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/agent.py +0 -0
  57. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/agent_types.py +0 -0
  58. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/aio/__init__.py +0 -0
  59. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/async_agent.py +0 -0
  60. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/async_conversation.py +0 -0
  61. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/async_core.py +0 -0
  62. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/async_driver.py +0 -0
  63. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/cache.py +0 -0
  64. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/callbacks.py +0 -0
  65. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/cli.py +0 -0
  66. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/conversation.py +0 -0
  67. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/core.py +0 -0
  68. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/cost_mixin.py +0 -0
  69. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/driver.py +0 -0
  70. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/__init__.py +0 -0
  71. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/airllm_driver.py +0 -0
  72. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_airllm_driver.py +0 -0
  73. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_azure_driver.py +0 -0
  74. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_claude_driver.py +0 -0
  75. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_google_driver.py +0 -0
  76. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_grok_driver.py +0 -0
  77. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_groq_driver.py +0 -0
  78. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_hugging_driver.py +0 -0
  79. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_lmstudio_driver.py +0 -0
  80. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_local_http_driver.py +0 -0
  81. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_modelscope_driver.py +0 -0
  82. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_ollama_driver.py +0 -0
  83. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_openai_driver.py +0 -0
  84. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_openrouter_driver.py +0 -0
  85. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_registry.py +0 -0
  86. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/async_zai_driver.py +0 -0
  87. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/azure_driver.py +0 -0
  88. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/claude_driver.py +0 -0
  89. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/google_driver.py +0 -0
  90. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/grok_driver.py +0 -0
  91. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/groq_driver.py +0 -0
  92. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/hugging_driver.py +0 -0
  93. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/lmstudio_driver.py +0 -0
  94. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/local_http_driver.py +0 -0
  95. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/modelscope_driver.py +0 -0
  96. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/ollama_driver.py +0 -0
  97. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/openai_driver.py +0 -0
  98. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/openrouter_driver.py +0 -0
  99. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/registry.py +0 -0
  100. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/vision_helpers.py +0 -0
  101. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/drivers/zai_driver.py +0 -0
  102. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/field_definitions.py +0 -0
  103. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/group_types.py +0 -0
  104. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/image.py +0 -0
  105. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/ledger.py +0 -0
  106. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/logging.py +0 -0
  107. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/model_rates.py +0 -0
  108. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/persistence.py +0 -0
  109. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/persona.py +0 -0
  110. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/runner.py +0 -0
  111. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/__init__.py +0 -0
  112. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/generator.py +0 -0
  113. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
  114. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/templates/README.md.j2 +0 -0
  115. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/templates/config.py.j2 +0 -0
  116. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/templates/env.example.j2 +0 -0
  117. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/templates/main.py.j2 +0 -0
  118. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/templates/models.py.j2 +0 -0
  119. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
  120. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/serialization.py +0 -0
  121. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/server.py +0 -0
  122. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/session.py +0 -0
  123. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/settings.py +0 -0
  124. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/tools.py +0 -0
  125. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/tools_schema.py +0 -0
  126. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture/validator.py +0 -0
  127. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture.egg-info/SOURCES.txt +0 -0
  128. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture.egg-info/dependency_links.txt +0 -0
  129. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture.egg-info/entry_points.txt +0 -0
  130. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture.egg-info/requires.txt +0 -0
  131. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/prompture.egg-info/top_level.txt +0 -0
  132. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/pyproject.toml +0 -0
  133. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/requirements.txt +0 -0
  134. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/setup.cfg +0 -0
  135. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/test.py +0 -0
  136. {prompture-0.0.42.dev1 → prompture-0.0.45.dev1}/test_version_diagnosis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prompture
3
- Version: 0.0.42.dev1
3
+ Version: 0.0.45.dev1
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
@@ -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.42.dev1'
32
- __version_tuple__ = version_tuple = (0, 0, 42, 'dev1')
31
+ __version__ = version = '0.0.45.dev1'
32
+ __version_tuple__ = version_tuple = (0, 0, 45, 'dev1')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -70,6 +70,27 @@ class ParallelGroup:
70
70
  """Request graceful shutdown."""
71
71
  self._stop_requested = True
72
72
 
73
+ @property
74
+ def shared_state(self) -> dict[str, Any]:
75
+ """Return a copy of the current shared execution state."""
76
+ return dict(self._state)
77
+
78
+ def inject_state(self, state: dict[str, Any], *, recursive: bool = False) -> None:
79
+ """Merge external key-value pairs into this group's shared state.
80
+
81
+ Existing keys are NOT overwritten (uses setdefault semantics).
82
+
83
+ Args:
84
+ state: Key-value pairs to inject.
85
+ recursive: If True, also inject into nested sub-groups.
86
+ """
87
+ for k, v in state.items():
88
+ self._state.setdefault(k, v)
89
+ if recursive:
90
+ for agent, _ in self._agents:
91
+ if hasattr(agent, "inject_state"):
92
+ agent.inject_state(state, recursive=True)
93
+
73
94
  async def run_async(self, prompt: str = "") -> GroupResult:
74
95
  """Execute all agents concurrently."""
75
96
  self._stop_requested = False
@@ -213,6 +234,27 @@ class AsyncSequentialGroup:
213
234
  def stop(self) -> None:
214
235
  self._stop_requested = True
215
236
 
237
+ @property
238
+ def shared_state(self) -> dict[str, Any]:
239
+ """Return a copy of the current shared execution state."""
240
+ return dict(self._state)
241
+
242
+ def inject_state(self, state: dict[str, Any], *, recursive: bool = False) -> None:
243
+ """Merge external key-value pairs into this group's shared state.
244
+
245
+ Existing keys are NOT overwritten (uses setdefault semantics).
246
+
247
+ Args:
248
+ state: Key-value pairs to inject.
249
+ recursive: If True, also inject into nested sub-groups.
250
+ """
251
+ for k, v in state.items():
252
+ self._state.setdefault(k, v)
253
+ if recursive:
254
+ for agent, _ in self._agents:
255
+ if hasattr(agent, "inject_state"):
256
+ agent.inject_state(state, recursive=True)
257
+
216
258
  async def run(self, prompt: str = "") -> GroupResult:
217
259
  """Execute all agents in sequence (async)."""
218
260
  self._stop_requested = False
@@ -351,6 +393,27 @@ class AsyncLoopGroup:
351
393
  def stop(self) -> None:
352
394
  self._stop_requested = True
353
395
 
396
+ @property
397
+ def shared_state(self) -> dict[str, Any]:
398
+ """Return a copy of the current shared execution state."""
399
+ return dict(self._state)
400
+
401
+ def inject_state(self, state: dict[str, Any], *, recursive: bool = False) -> None:
402
+ """Merge external key-value pairs into this group's shared state.
403
+
404
+ Existing keys are NOT overwritten (uses setdefault semantics).
405
+
406
+ Args:
407
+ state: Key-value pairs to inject.
408
+ recursive: If True, also inject into nested sub-groups.
409
+ """
410
+ for k, v in state.items():
411
+ self._state.setdefault(k, v)
412
+ if recursive:
413
+ for agent, _ in self._agents:
414
+ if hasattr(agent, "inject_state"):
415
+ agent.inject_state(state, recursive=True)
416
+
354
417
  async def run(self, prompt: str = "") -> GroupResult:
355
418
  """Execute the loop (async)."""
356
419
  self._stop_requested = False
@@ -10,6 +10,7 @@ from typing import Any, overload
10
10
  import requests
11
11
 
12
12
  from .drivers import (
13
+ AirLLMDriver,
13
14
  AzureDriver,
14
15
  ClaudeDriver,
15
16
  GoogleDriver,
@@ -17,9 +18,12 @@ from .drivers import (
17
18
  GroqDriver,
18
19
  LMStudioDriver,
19
20
  LocalHTTPDriver,
21
+ ModelScopeDriver,
22
+ MoonshotDriver,
20
23
  OllamaDriver,
21
24
  OpenAIDriver,
22
25
  OpenRouterDriver,
26
+ ZaiDriver,
23
27
  )
24
28
  from .settings import settings
25
29
 
@@ -71,6 +75,10 @@ def get_available_models(
71
75
  "ollama": OllamaDriver,
72
76
  "lmstudio": LMStudioDriver,
73
77
  "local_http": LocalHTTPDriver,
78
+ "moonshot": MoonshotDriver,
79
+ "zai": ZaiDriver,
80
+ "modelscope": ModelScopeDriver,
81
+ "airllm": AirLLMDriver,
74
82
  }
75
83
 
76
84
  for provider, driver_cls in provider_classes.items():
@@ -102,6 +110,18 @@ def get_available_models(
102
110
  elif provider == "grok":
103
111
  if settings.grok_api_key or os.getenv("GROK_API_KEY"):
104
112
  is_configured = True
113
+ elif provider == "moonshot":
114
+ if settings.moonshot_api_key or os.getenv("MOONSHOT_API_KEY"):
115
+ is_configured = True
116
+ elif provider == "zai":
117
+ if settings.zhipu_api_key or os.getenv("ZHIPU_API_KEY"):
118
+ is_configured = True
119
+ elif provider == "modelscope":
120
+ if settings.modelscope_api_key or os.getenv("MODELSCOPE_API_KEY"):
121
+ is_configured = True
122
+ elif provider == "airllm":
123
+ # AirLLM runs locally, always considered configured
124
+ is_configured = True
105
125
  elif (
106
126
  provider == "ollama"
107
127
  or provider == "lmstudio"
@@ -85,20 +85,31 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
85
85
  if supports_temperature and "temperature" in opts:
86
86
  data["temperature"] = opts["temperature"]
87
87
 
88
+ # Native JSON mode support — skip for reasoning models where
89
+ # Moonshot's API does not reliably support response_format.
88
90
  if options.get("json_mode"):
89
- json_schema = options.get("json_schema")
90
- if json_schema:
91
- schema_copy = prepare_strict_schema(json_schema)
92
- data["response_format"] = {
93
- "type": "json_schema",
94
- "json_schema": {
95
- "name": "extraction",
96
- "strict": True,
97
- "schema": schema_copy,
98
- },
99
- }
100
- else:
101
- data["response_format"] = {"type": "json_object"}
91
+ from ..model_rates import get_model_capabilities
92
+
93
+ caps = get_model_capabilities("moonshot", model)
94
+ is_reasoning = caps is not None and caps.is_reasoning is True
95
+ model_supports_structured = (
96
+ caps is None or caps.supports_structured_output is not False
97
+ ) and not is_reasoning
98
+
99
+ if model_supports_structured:
100
+ json_schema = options.get("json_schema")
101
+ if json_schema:
102
+ schema_copy = prepare_strict_schema(json_schema)
103
+ data["response_format"] = {
104
+ "type": "json_schema",
105
+ "json_schema": {
106
+ "name": "extraction",
107
+ "strict": True,
108
+ "schema": schema_copy,
109
+ },
110
+ }
111
+ else:
112
+ data["response_format"] = {"type": "json_object"}
102
113
 
103
114
  async with httpx.AsyncClient() as client:
104
115
  try:
@@ -132,7 +143,13 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
132
143
  "model_name": model,
133
144
  }
134
145
 
135
- text = resp["choices"][0]["message"]["content"]
146
+ message = resp["choices"][0]["message"]
147
+ text = message.get("content") or ""
148
+
149
+ # Reasoning models may return content in reasoning_content when content is empty
150
+ if not text and message.get("reasoning_content"):
151
+ text = message["reasoning_content"]
152
+
136
153
  return {"text": text, "meta": meta}
137
154
 
138
155
  # ------------------------------------------------------------------
@@ -290,7 +307,10 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
290
307
  choices = chunk.get("choices", [])
291
308
  if choices:
292
309
  delta = choices[0].get("delta", {})
293
- content = delta.get("content", "")
310
+ content = delta.get("content") or ""
311
+ # Reasoning models stream thinking via reasoning_content
312
+ if not content:
313
+ content = delta.get("reasoning_content") or ""
294
314
  if content:
295
315
  full_text += content
296
316
  yield {"type": "delta", "text": content}
@@ -112,21 +112,31 @@ class MoonshotDriver(CostMixin, Driver):
112
112
  if supports_temperature and "temperature" in opts:
113
113
  data["temperature"] = opts["temperature"]
114
114
 
115
- # Native JSON mode support
115
+ # Native JSON mode support — skip for reasoning models where
116
+ # Moonshot's API does not reliably support response_format.
116
117
  if options.get("json_mode"):
117
- json_schema = options.get("json_schema")
118
- if json_schema:
119
- schema_copy = prepare_strict_schema(json_schema)
120
- data["response_format"] = {
121
- "type": "json_schema",
122
- "json_schema": {
123
- "name": "extraction",
124
- "strict": True,
125
- "schema": schema_copy,
126
- },
127
- }
128
- else:
129
- data["response_format"] = {"type": "json_object"}
118
+ from ..model_rates import get_model_capabilities
119
+
120
+ caps = get_model_capabilities("moonshot", model)
121
+ is_reasoning = caps is not None and caps.is_reasoning is True
122
+ model_supports_structured = (
123
+ caps is None or caps.supports_structured_output is not False
124
+ ) and not is_reasoning
125
+
126
+ if model_supports_structured:
127
+ json_schema = options.get("json_schema")
128
+ if json_schema:
129
+ schema_copy = prepare_strict_schema(json_schema)
130
+ data["response_format"] = {
131
+ "type": "json_schema",
132
+ "json_schema": {
133
+ "name": "extraction",
134
+ "strict": True,
135
+ "schema": schema_copy,
136
+ },
137
+ }
138
+ else:
139
+ data["response_format"] = {"type": "json_object"}
130
140
 
131
141
  try:
132
142
  response = requests.post(
@@ -159,7 +169,13 @@ class MoonshotDriver(CostMixin, Driver):
159
169
  "model_name": model,
160
170
  }
161
171
 
162
- text = resp["choices"][0]["message"]["content"]
172
+ message = resp["choices"][0]["message"]
173
+ text = message.get("content") or ""
174
+
175
+ # Reasoning models may return content in reasoning_content when content is empty
176
+ if not text and message.get("reasoning_content"):
177
+ text = message["reasoning_content"]
178
+
163
179
  return {"text": text, "meta": meta}
164
180
 
165
181
  # ------------------------------------------------------------------
@@ -320,7 +336,10 @@ class MoonshotDriver(CostMixin, Driver):
320
336
  choices = chunk.get("choices", [])
321
337
  if choices:
322
338
  delta = choices[0].get("delta", {})
323
- content = delta.get("content", "")
339
+ content = delta.get("content") or ""
340
+ # Reasoning models stream thinking via reasoning_content
341
+ if not content:
342
+ content = delta.get("reasoning_content") or ""
324
343
  if content:
325
344
  full_text += content
326
345
  yield {"type": "delta", "text": content}
@@ -114,6 +114,27 @@ class SequentialGroup:
114
114
  """Request graceful shutdown after the current agent finishes."""
115
115
  self._stop_requested = True
116
116
 
117
+ @property
118
+ def shared_state(self) -> dict[str, Any]:
119
+ """Return a copy of the current shared execution state."""
120
+ return dict(self._state)
121
+
122
+ def inject_state(self, state: dict[str, Any], *, recursive: bool = False) -> None:
123
+ """Merge external key-value pairs into this group's shared state.
124
+
125
+ Existing keys are NOT overwritten (uses setdefault semantics).
126
+
127
+ Args:
128
+ state: Key-value pairs to inject.
129
+ recursive: If True, also inject into nested sub-groups.
130
+ """
131
+ for k, v in state.items():
132
+ self._state.setdefault(k, v)
133
+ if recursive:
134
+ for agent, _ in self._agents:
135
+ if hasattr(agent, "inject_state"):
136
+ agent.inject_state(state, recursive=True)
137
+
117
138
  def save(self, path: str) -> None:
118
139
  """Run and save result to file. Convenience wrapper."""
119
140
  result = self.run()
@@ -267,6 +288,27 @@ class LoopGroup:
267
288
  """Request graceful shutdown."""
268
289
  self._stop_requested = True
269
290
 
291
+ @property
292
+ def shared_state(self) -> dict[str, Any]:
293
+ """Return a copy of the current shared execution state."""
294
+ return dict(self._state)
295
+
296
+ def inject_state(self, state: dict[str, Any], *, recursive: bool = False) -> None:
297
+ """Merge external key-value pairs into this group's shared state.
298
+
299
+ Existing keys are NOT overwritten (uses setdefault semantics).
300
+
301
+ Args:
302
+ state: Key-value pairs to inject.
303
+ recursive: If True, also inject into nested sub-groups.
304
+ """
305
+ for k, v in state.items():
306
+ self._state.setdefault(k, v)
307
+ if recursive:
308
+ for agent, _ in self._agents:
309
+ if hasattr(agent, "inject_state"):
310
+ agent.inject_state(state, recursive=True)
311
+
270
312
  def run(self, prompt: str = "") -> GroupResult:
271
313
  """Execute the loop."""
272
314
  self._stop_requested = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prompture
3
- Version: 0.0.42.dev1
3
+ Version: 0.0.45.dev1
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
File without changes
File without changes