Skip to content

Commit 02d3435

Browse files
committed
feat(cli): add example input support for JSON conversion
- Introduced `-e` option to accept JSON examples directly. - Implemented `convert_example_to_schema` function for dynamic schema generation. - Updated README with usage examples for new feature.
1 parent 4c79cd7 commit 02d3435

File tree

2 files changed

+132
-4
lines changed

2 files changed

+132
-4
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ and receiving free-form text, you can do this:
5050
ask2api -p "Where is the capital of France?" -sf schema.json
5151
```
5252

53+
Or pass an example directly without a schema file:
54+
55+
```bash
56+
ask2api -p "Where is the capital of France?" -e '{"country": "string", "city": "string"}'
57+
```
58+
5359
And get a structured API response:
5460

5561
```json
@@ -59,6 +65,44 @@ And get a structured API response:
5965
}
6066
```
6167

68+
For more complex structures with different data types:
69+
70+
```bash
71+
ask2api -p "Analyze carbon element" -e '{
72+
"symbol": "element symbol",
73+
"atomic_number": 1234,
74+
"atomic_weight": 12.34,
75+
"is_metal": true,
76+
"isotopes": ["name of the isotope"],
77+
"properties": {
78+
"melting_point": 1234.5,
79+
"boiling_point": 2345.6,
80+
"magnetic": true
81+
}
82+
}'
83+
```
84+
85+
Output:
86+
87+
```
88+
{
89+
"symbol": "C",
90+
"atomic_number": 6,
91+
"atomic_weight": 12.011,
92+
"is_metal": false,
93+
"isotopes": [
94+
"C-12",
95+
"C-13",
96+
"C-14"
97+
],
98+
"properties": {
99+
"melting_point": 3550,
100+
"boiling_point": 4827,
101+
"magnetic": false
102+
}
103+
}
104+
```
105+
62106
### Vision modality
63107

64108
You can also analyze images and get structured JSON responses:

ask2api.py

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010

1111
API_KEY = os.getenv("OPENAI_API_KEY")
1212
ENV_VAR_PREFIX = "ASK2API_"
13+
TYPE_HINTS = {
14+
"string": "string",
15+
"str": "string",
16+
"number": "number",
17+
"int": "integer",
18+
"integer": "integer",
19+
"float": "number",
20+
"bool": "boolean",
21+
"boolean": "boolean",
22+
"array": "array",
23+
"list": "array",
24+
"object": "object",
25+
"dict": "object",
26+
}
1327

1428

1529
@dataclass
@@ -91,6 +105,66 @@ def get_version():
91105
return "dev"
92106

93107

108+
def convert_example_to_schema(example, _cache=None):
109+
"""Convert a JSON example to a JSON Schema with memoization."""
110+
if _cache is None:
111+
_cache = {}
112+
113+
# Use id() for memoization key to handle nested structures
114+
cache_key = id(example)
115+
if cache_key in _cache:
116+
return _cache[cache_key]
117+
118+
if isinstance(example, dict):
119+
schema = {
120+
"type": "object",
121+
"properties": {},
122+
"required": list(example.keys()),
123+
"additionalProperties": False,
124+
}
125+
126+
for key, value in example.items():
127+
if isinstance(value, str):
128+
schema["properties"][key] = {
129+
"type": TYPE_HINTS.get(value.lower(), "string")
130+
}
131+
elif isinstance(value, bool):
132+
schema["properties"][key] = {"type": "boolean"}
133+
elif isinstance(value, int):
134+
schema["properties"][key] = {"type": "integer"}
135+
elif isinstance(value, float):
136+
schema["properties"][key] = {"type": "number"}
137+
elif isinstance(value, list):
138+
schema["properties"][key] = {
139+
"type": "array",
140+
"items": convert_example_to_schema(value[0], _cache)
141+
if value
142+
else {},
143+
}
144+
elif isinstance(value, dict):
145+
schema["properties"][key] = convert_example_to_schema(value, _cache)
146+
else:
147+
schema["properties"][key] = {"type": "string"}
148+
149+
_cache[cache_key] = schema
150+
return schema
151+
152+
elif isinstance(example, list):
153+
schema = {
154+
"type": "array",
155+
"items": convert_example_to_schema(example[0], _cache) if example else {},
156+
}
157+
_cache[cache_key] = schema
158+
return schema
159+
160+
else:
161+
# Primitive types - use type() for faster checking
162+
type_map = {str: "string", bool: "boolean", int: "integer", float: "number"}
163+
schema = {"type": type_map.get(type(example), "string")}
164+
_cache[cache_key] = schema
165+
return schema
166+
167+
94168
def main():
95169
env_vars_help = """
96170
Environment Variables:
@@ -109,10 +183,15 @@ def main():
109183
required=True,
110184
help="Natural language prompt",
111185
)
112-
parser.add_argument(
186+
schema_group = parser.add_mutually_exclusive_group(required=True)
187+
schema_group.add_argument(
188+
"-e",
189+
"--example",
190+
help='JSON example as a string (e.g., \'{"country": "France", "city": "Paris"}\')',
191+
)
192+
schema_group.add_argument(
113193
"-sf",
114194
"--schema-file",
115-
required=True,
116195
help="Path to JSON schema file",
117196
)
118197
parser.add_argument(
@@ -128,8 +207,13 @@ def main():
128207
)
129208
args = parser.parse_args()
130209

131-
with open(args.schema_file, "r", encoding="utf-8") as f:
132-
schema = json.load(f)
210+
# Load schema from file or parse from string
211+
if args.schema_file:
212+
with open(args.schema_file, "r", encoding="utf-8") as f:
213+
schema = json.load(f)
214+
else:
215+
example = json.loads(args.example)
216+
schema = convert_example_to_schema(example)
133217

134218
system_prompt = """
135219
You are a JSON API engine.

0 commit comments

Comments
 (0)