mrmd-ai 0.1.0__py3-none-any.whl → 0.1.2__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.
- mrmd_ai/custom_programs.py +215 -0
- mrmd_ai/juice.py +403 -61
- mrmd_ai/modules/__init__.py +11 -0
- mrmd_ai/modules/edit.py +102 -0
- mrmd_ai/server.py +275 -23
- mrmd_ai/signatures/__init__.py +15 -0
- mrmd_ai/signatures/edit.py +173 -0
- {mrmd_ai-0.1.0.dist-info → mrmd_ai-0.1.2.dist-info}/METADATA +2 -1
- {mrmd_ai-0.1.0.dist-info → mrmd_ai-0.1.2.dist-info}/RECORD +12 -8
- mrmd_ai-0.1.2.dist-info/licenses/LICENSE +21 -0
- {mrmd_ai-0.1.0.dist-info → mrmd_ai-0.1.2.dist-info}/WHEEL +0 -0
- {mrmd_ai-0.1.0.dist-info → mrmd_ai-0.1.2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Program Factory - Generate DSPy modules from user-defined templates.
|
|
3
|
+
|
|
4
|
+
This module allows users to create custom AI commands without writing code.
|
|
5
|
+
Users define their commands with:
|
|
6
|
+
- name: Display name for the command
|
|
7
|
+
- inputType: What text to process (selection, cursor, fullDoc)
|
|
8
|
+
- outputType: What to do with result (replace, insert)
|
|
9
|
+
- instructions: Natural language instructions for the AI
|
|
10
|
+
|
|
11
|
+
The factory generates DSPy Signature and Module classes dynamically.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
import dspy
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_custom_signature(
|
|
19
|
+
name: str,
|
|
20
|
+
instructions: str,
|
|
21
|
+
input_type: str = "selection",
|
|
22
|
+
output_type: str = "replace",
|
|
23
|
+
) -> type:
|
|
24
|
+
"""Create a DSPy Signature class from user configuration.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
name: Command name (used for class naming)
|
|
28
|
+
instructions: User's instructions for the AI
|
|
29
|
+
input_type: "selection" | "cursor" | "fullDoc"
|
|
30
|
+
output_type: "replace" | "insert"
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
A DSPy Signature class
|
|
34
|
+
"""
|
|
35
|
+
# Build the docstring from user instructions
|
|
36
|
+
docstring = f"""{instructions}
|
|
37
|
+
|
|
38
|
+
IMPORTANT RULES:
|
|
39
|
+
- Output ONLY the result text, no explanations or meta-commentary
|
|
40
|
+
- Maintain appropriate formatting (markdown, code style, etc.)
|
|
41
|
+
- Be concise and direct
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# Define input fields based on input type
|
|
45
|
+
if input_type == "selection":
|
|
46
|
+
input_fields = {
|
|
47
|
+
"text": dspy.InputField(desc="The selected text to process"),
|
|
48
|
+
"local_context": dspy.InputField(desc="Text surrounding the selection for context"),
|
|
49
|
+
"document_context": dspy.InputField(desc="Broader document context"),
|
|
50
|
+
}
|
|
51
|
+
elif input_type == "cursor":
|
|
52
|
+
input_fields = {
|
|
53
|
+
"text_before_cursor": dspy.InputField(desc="Text before the cursor position"),
|
|
54
|
+
"local_context": dspy.InputField(desc="Text surrounding the cursor for context"),
|
|
55
|
+
"document_context": dspy.InputField(desc="Broader document context"),
|
|
56
|
+
}
|
|
57
|
+
else: # fullDoc
|
|
58
|
+
input_fields = {
|
|
59
|
+
"document_context": dspy.InputField(desc="The full document content"),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Define output field
|
|
63
|
+
output_field_name = "result"
|
|
64
|
+
output_fields = {
|
|
65
|
+
output_field_name: dspy.OutputField(desc="The processed result text. Output ONLY the result.")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Create the Signature class dynamically
|
|
69
|
+
# Clean name for class naming (remove spaces, special chars)
|
|
70
|
+
class_name = "".join(c for c in name if c.isalnum()) + "Signature"
|
|
71
|
+
|
|
72
|
+
signature_class = type(
|
|
73
|
+
class_name,
|
|
74
|
+
(dspy.Signature,),
|
|
75
|
+
{
|
|
76
|
+
"__doc__": docstring,
|
|
77
|
+
"__annotations__": {
|
|
78
|
+
**{k: str for k in input_fields},
|
|
79
|
+
**{k: str for k in output_fields},
|
|
80
|
+
},
|
|
81
|
+
**input_fields,
|
|
82
|
+
**output_fields,
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return signature_class
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def create_custom_module(
|
|
90
|
+
name: str,
|
|
91
|
+
instructions: str,
|
|
92
|
+
input_type: str = "selection",
|
|
93
|
+
output_type: str = "replace",
|
|
94
|
+
) -> type:
|
|
95
|
+
"""Create a DSPy Module class from user configuration.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
name: Command name
|
|
99
|
+
instructions: User's instructions for the AI
|
|
100
|
+
input_type: "selection" | "cursor" | "fullDoc"
|
|
101
|
+
output_type: "replace" | "insert"
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
A DSPy Module class (not instance)
|
|
105
|
+
"""
|
|
106
|
+
signature = create_custom_signature(name, instructions, input_type, output_type)
|
|
107
|
+
|
|
108
|
+
# Clean name for class naming
|
|
109
|
+
class_name = "".join(c for c in name if c.isalnum()) + "Predict"
|
|
110
|
+
|
|
111
|
+
class CustomModule(dspy.Module):
|
|
112
|
+
"""Dynamically generated custom command module."""
|
|
113
|
+
|
|
114
|
+
def __init__(self):
|
|
115
|
+
super().__init__()
|
|
116
|
+
self.predictor = dspy.Predict(signature)
|
|
117
|
+
self._input_type = input_type
|
|
118
|
+
self._output_type = output_type
|
|
119
|
+
|
|
120
|
+
def forward(self, **kwargs) -> Any:
|
|
121
|
+
return self.predictor(**kwargs)
|
|
122
|
+
|
|
123
|
+
# Set the class name
|
|
124
|
+
CustomModule.__name__ = class_name
|
|
125
|
+
CustomModule.__qualname__ = class_name
|
|
126
|
+
|
|
127
|
+
return CustomModule
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class CustomProgramRegistry:
|
|
131
|
+
"""Registry for user-defined custom programs.
|
|
132
|
+
|
|
133
|
+
Manages creation and caching of custom DSPy modules.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(self):
|
|
137
|
+
self._modules: dict[str, type] = {}
|
|
138
|
+
self._configs: dict[str, dict] = {}
|
|
139
|
+
|
|
140
|
+
def register(self, program_id: str, config: dict) -> type:
|
|
141
|
+
"""Register a custom program from configuration.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
program_id: Unique identifier for this program
|
|
145
|
+
config: Dict with name, instructions, inputType, outputType
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The generated Module class
|
|
149
|
+
"""
|
|
150
|
+
module_class = create_custom_module(
|
|
151
|
+
name=config.get("name", program_id),
|
|
152
|
+
instructions=config.get("instructions", "Process this text."),
|
|
153
|
+
input_type=config.get("inputType", "selection"),
|
|
154
|
+
output_type=config.get("outputType", "replace"),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
self._modules[program_id] = module_class
|
|
158
|
+
self._configs[program_id] = config
|
|
159
|
+
|
|
160
|
+
return module_class
|
|
161
|
+
|
|
162
|
+
def get(self, program_id: str) -> type | None:
|
|
163
|
+
"""Get a registered module class by ID."""
|
|
164
|
+
return self._modules.get(program_id)
|
|
165
|
+
|
|
166
|
+
def get_config(self, program_id: str) -> dict | None:
|
|
167
|
+
"""Get the configuration for a registered program."""
|
|
168
|
+
return self._configs.get(program_id)
|
|
169
|
+
|
|
170
|
+
def unregister(self, program_id: str) -> bool:
|
|
171
|
+
"""Remove a program from the registry."""
|
|
172
|
+
if program_id in self._modules:
|
|
173
|
+
del self._modules[program_id]
|
|
174
|
+
del self._configs[program_id]
|
|
175
|
+
return True
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
def clear(self):
|
|
179
|
+
"""Clear all registered programs."""
|
|
180
|
+
self._modules.clear()
|
|
181
|
+
self._configs.clear()
|
|
182
|
+
|
|
183
|
+
def list_programs(self) -> list[str]:
|
|
184
|
+
"""List all registered program IDs."""
|
|
185
|
+
return list(self._modules.keys())
|
|
186
|
+
|
|
187
|
+
def is_registered(self, program_id: str) -> bool:
|
|
188
|
+
"""Check if a program is registered."""
|
|
189
|
+
return program_id in self._modules
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# Global registry instance
|
|
193
|
+
custom_registry = CustomProgramRegistry()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def register_custom_programs(commands: list[dict]) -> None:
|
|
197
|
+
"""Register multiple custom programs from a list of command configs.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
commands: List of command configurations, each with:
|
|
201
|
+
- id or program: Unique identifier
|
|
202
|
+
- name: Display name
|
|
203
|
+
- instructions: AI instructions
|
|
204
|
+
- inputType: selection | cursor | fullDoc
|
|
205
|
+
- outputType: replace | insert
|
|
206
|
+
"""
|
|
207
|
+
for cmd in commands:
|
|
208
|
+
program_id = cmd.get("program") or cmd.get("id")
|
|
209
|
+
if program_id:
|
|
210
|
+
custom_registry.register(program_id, cmd)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def get_custom_program(program_id: str) -> type | None:
|
|
214
|
+
"""Get a custom program module class by ID."""
|
|
215
|
+
return custom_registry.get(program_id)
|