prompture 0.0.29.dev8__py3-none-any.whl → 0.0.38.dev2__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.
Files changed (79) hide show
  1. prompture/__init__.py +264 -23
  2. prompture/_version.py +34 -0
  3. prompture/agent.py +924 -0
  4. prompture/agent_types.py +156 -0
  5. prompture/aio/__init__.py +74 -0
  6. prompture/async_agent.py +880 -0
  7. prompture/async_conversation.py +789 -0
  8. prompture/async_core.py +803 -0
  9. prompture/async_driver.py +193 -0
  10. prompture/async_groups.py +551 -0
  11. prompture/cache.py +469 -0
  12. prompture/callbacks.py +55 -0
  13. prompture/cli.py +63 -4
  14. prompture/conversation.py +826 -0
  15. prompture/core.py +894 -263
  16. prompture/cost_mixin.py +51 -0
  17. prompture/discovery.py +187 -0
  18. prompture/driver.py +206 -5
  19. prompture/drivers/__init__.py +175 -67
  20. prompture/drivers/airllm_driver.py +109 -0
  21. prompture/drivers/async_airllm_driver.py +26 -0
  22. prompture/drivers/async_azure_driver.py +123 -0
  23. prompture/drivers/async_claude_driver.py +113 -0
  24. prompture/drivers/async_google_driver.py +316 -0
  25. prompture/drivers/async_grok_driver.py +97 -0
  26. prompture/drivers/async_groq_driver.py +90 -0
  27. prompture/drivers/async_hugging_driver.py +61 -0
  28. prompture/drivers/async_lmstudio_driver.py +148 -0
  29. prompture/drivers/async_local_http_driver.py +44 -0
  30. prompture/drivers/async_ollama_driver.py +135 -0
  31. prompture/drivers/async_openai_driver.py +102 -0
  32. prompture/drivers/async_openrouter_driver.py +102 -0
  33. prompture/drivers/async_registry.py +133 -0
  34. prompture/drivers/azure_driver.py +42 -9
  35. prompture/drivers/claude_driver.py +257 -34
  36. prompture/drivers/google_driver.py +295 -42
  37. prompture/drivers/grok_driver.py +35 -32
  38. prompture/drivers/groq_driver.py +33 -26
  39. prompture/drivers/hugging_driver.py +6 -6
  40. prompture/drivers/lmstudio_driver.py +97 -19
  41. prompture/drivers/local_http_driver.py +6 -6
  42. prompture/drivers/ollama_driver.py +168 -23
  43. prompture/drivers/openai_driver.py +184 -9
  44. prompture/drivers/openrouter_driver.py +37 -25
  45. prompture/drivers/registry.py +306 -0
  46. prompture/drivers/vision_helpers.py +153 -0
  47. prompture/field_definitions.py +106 -96
  48. prompture/group_types.py +147 -0
  49. prompture/groups.py +530 -0
  50. prompture/image.py +180 -0
  51. prompture/logging.py +80 -0
  52. prompture/model_rates.py +217 -0
  53. prompture/persistence.py +254 -0
  54. prompture/persona.py +482 -0
  55. prompture/runner.py +49 -47
  56. prompture/scaffold/__init__.py +1 -0
  57. prompture/scaffold/generator.py +84 -0
  58. prompture/scaffold/templates/Dockerfile.j2 +12 -0
  59. prompture/scaffold/templates/README.md.j2 +41 -0
  60. prompture/scaffold/templates/config.py.j2 +21 -0
  61. prompture/scaffold/templates/env.example.j2 +8 -0
  62. prompture/scaffold/templates/main.py.j2 +86 -0
  63. prompture/scaffold/templates/models.py.j2 +40 -0
  64. prompture/scaffold/templates/requirements.txt.j2 +5 -0
  65. prompture/serialization.py +218 -0
  66. prompture/server.py +183 -0
  67. prompture/session.py +117 -0
  68. prompture/settings.py +19 -1
  69. prompture/tools.py +219 -267
  70. prompture/tools_schema.py +254 -0
  71. prompture/validator.py +3 -3
  72. prompture-0.0.38.dev2.dist-info/METADATA +369 -0
  73. prompture-0.0.38.dev2.dist-info/RECORD +77 -0
  74. {prompture-0.0.29.dev8.dist-info → prompture-0.0.38.dev2.dist-info}/WHEEL +1 -1
  75. prompture-0.0.29.dev8.dist-info/METADATA +0 -368
  76. prompture-0.0.29.dev8.dist-info/RECORD +0 -27
  77. {prompture-0.0.29.dev8.dist-info → prompture-0.0.38.dev2.dist-info}/entry_points.txt +0 -0
  78. {prompture-0.0.29.dev8.dist-info → prompture-0.0.38.dev2.dist-info}/licenses/LICENSE +0 -0
  79. {prompture-0.0.29.dev8.dist-info → prompture-0.0.38.dev2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,153 @@
1
+ """Shared helpers for converting universal vision message blocks to provider-specific formats."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def _prepare_openai_vision_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
9
+ """Convert universal image blocks to OpenAI-compatible vision format.
10
+
11
+ Works for OpenAI, Azure, Groq, Grok, LM Studio, and OpenRouter.
12
+
13
+ Universal format::
14
+
15
+ {"type": "image", "source": ImageContent(...)}
16
+
17
+ OpenAI format::
18
+
19
+ {"type": "image_url", "image_url": {"url": "data:mime;base64,..."}}
20
+ """
21
+ out: list[dict[str, Any]] = []
22
+ for msg in messages:
23
+ content = msg.get("content")
24
+ if not isinstance(content, list):
25
+ out.append(msg)
26
+ continue
27
+ new_blocks: list[dict[str, Any]] = []
28
+ for block in content:
29
+ if isinstance(block, dict) and block.get("type") == "image":
30
+ source = block["source"]
31
+ if source.source_type == "url" and source.url:
32
+ url = source.url
33
+ else:
34
+ url = f"data:{source.media_type};base64,{source.data}"
35
+ new_blocks.append(
36
+ {
37
+ "type": "image_url",
38
+ "image_url": {"url": url},
39
+ }
40
+ )
41
+ else:
42
+ new_blocks.append(block)
43
+ out.append({**msg, "content": new_blocks})
44
+ return out
45
+
46
+
47
+ def _prepare_claude_vision_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
48
+ """Convert universal image blocks to Anthropic Claude format.
49
+
50
+ Claude format::
51
+
52
+ {"type": "image", "source": {"type": "base64", "media_type": "...", "data": "..."}}
53
+ """
54
+ out: list[dict[str, Any]] = []
55
+ for msg in messages:
56
+ content = msg.get("content")
57
+ if not isinstance(content, list):
58
+ out.append(msg)
59
+ continue
60
+ new_blocks: list[dict[str, Any]] = []
61
+ for block in content:
62
+ if isinstance(block, dict) and block.get("type") == "image":
63
+ source = block["source"]
64
+ if source.source_type == "url" and source.url:
65
+ new_blocks.append(
66
+ {
67
+ "type": "image",
68
+ "source": {
69
+ "type": "url",
70
+ "url": source.url,
71
+ },
72
+ }
73
+ )
74
+ else:
75
+ new_blocks.append(
76
+ {
77
+ "type": "image",
78
+ "source": {
79
+ "type": "base64",
80
+ "media_type": source.media_type,
81
+ "data": source.data,
82
+ },
83
+ }
84
+ )
85
+ else:
86
+ new_blocks.append(block)
87
+ out.append({**msg, "content": new_blocks})
88
+ return out
89
+
90
+
91
+ def _prepare_google_vision_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
92
+ """Convert universal image blocks to Google Gemini format.
93
+
94
+ Gemini expects ``parts`` arrays containing text and inline_data dicts::
95
+
96
+ {"role": "user", "parts": [
97
+ "text prompt",
98
+ {"inline_data": {"mime_type": "image/png", "data": "base64..."}},
99
+ ]}
100
+ """
101
+ out: list[dict[str, Any]] = []
102
+ for msg in messages:
103
+ content = msg.get("content")
104
+ if not isinstance(content, list):
105
+ out.append(msg)
106
+ continue
107
+ # Convert content blocks to Gemini parts
108
+ parts: list[Any] = []
109
+ for block in content:
110
+ if isinstance(block, dict) and block.get("type") == "text":
111
+ parts.append(block["text"])
112
+ elif isinstance(block, dict) and block.get("type") == "image":
113
+ source = block["source"]
114
+ parts.append(
115
+ {
116
+ "inline_data": {
117
+ "mime_type": source.media_type,
118
+ "data": source.data,
119
+ }
120
+ }
121
+ )
122
+ else:
123
+ parts.append(block)
124
+ out.append({**msg, "content": parts, "_vision_parts": True})
125
+ return out
126
+
127
+
128
+ def _prepare_ollama_vision_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
129
+ """Convert universal image blocks to Ollama format.
130
+
131
+ Ollama expects images as a separate field::
132
+
133
+ {"role": "user", "content": "text", "images": ["base64..."]}
134
+ """
135
+ out: list[dict[str, Any]] = []
136
+ for msg in messages:
137
+ content = msg.get("content")
138
+ if not isinstance(content, list):
139
+ out.append(msg)
140
+ continue
141
+ text_parts: list[str] = []
142
+ images: list[str] = []
143
+ for block in content:
144
+ if isinstance(block, dict) and block.get("type") == "text":
145
+ text_parts.append(block["text"])
146
+ elif isinstance(block, dict) and block.get("type") == "image":
147
+ source = block["source"]
148
+ images.append(source.data)
149
+ new_msg = {**msg, "content": " ".join(text_parts)}
150
+ if images:
151
+ new_msg["images"] = images
152
+ out.append(new_msg)
153
+ return out