toolslm 0.3.4__tar.gz → 0.3.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolslm
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Tools to make language models a bit easier to use
5
5
  Home-page: https://github.com/AnswerDotAI/toolslm
6
6
  Author: Jeremy Howard
@@ -1,7 +1,7 @@
1
1
  [DEFAULT]
2
2
  repo = toolslm
3
3
  lib_name = toolslm
4
- version = 0.3.4
4
+ version = 0.3.5
5
5
  min_python = 3.9
6
6
  license = apache2
7
7
  black_formatting = False
@@ -0,0 +1 @@
1
+ __version__ = "0.3.5"
@@ -14,11 +14,10 @@ d = { 'settings': { 'branch': 'main',
14
14
  'toolslm.download.read_html': ('download.html#read_html', 'toolslm/download.py'),
15
15
  'toolslm.download.read_md': ('download.html#read_md', 'toolslm/download.py'),
16
16
  'toolslm.download.split_url': ('download.html#split_url', 'toolslm/download.py')},
17
- 'toolslm.funccall': { 'toolslm.funccall.PathArg': ('funccall.html#patharg', 'toolslm/funccall.py'),
17
+ 'toolslm.funccall': { 'toolslm.funccall._coerce_inputs': ('funccall.html#_coerce_inputs', 'toolslm/funccall.py'),
18
18
  'toolslm.funccall._copy_loc': ('funccall.html#_copy_loc', 'toolslm/funccall.py'),
19
19
  'toolslm.funccall._get_nested_schema': ('funccall.html#_get_nested_schema', 'toolslm/funccall.py'),
20
20
  'toolslm.funccall._handle_container': ('funccall.html#_handle_container', 'toolslm/funccall.py'),
21
- 'toolslm.funccall._handle_type': ('funccall.html#_handle_type', 'toolslm/funccall.py'),
22
21
  'toolslm.funccall._is_container': ('funccall.html#_is_container', 'toolslm/funccall.py'),
23
22
  'toolslm.funccall._is_parameterized': ('funccall.html#_is_parameterized', 'toolslm/funccall.py'),
24
23
  'toolslm.funccall._param': ('funccall.html#_param', 'toolslm/funccall.py'),
@@ -29,7 +28,10 @@ d = { 'settings': { 'branch': 'main',
29
28
  'toolslm.funccall.call_func_async': ('funccall.html#call_func_async', 'toolslm/funccall.py'),
30
29
  'toolslm.funccall.get_schema': ('funccall.html#get_schema', 'toolslm/funccall.py'),
31
30
  'toolslm.funccall.mk_ns': ('funccall.html#mk_ns', 'toolslm/funccall.py'),
32
- 'toolslm.funccall.python': ('funccall.html#python', 'toolslm/funccall.py')},
31
+ 'toolslm.funccall.mk_param': ('funccall.html#mk_param', 'toolslm/funccall.py'),
32
+ 'toolslm.funccall.mk_tool': ('funccall.html#mk_tool', 'toolslm/funccall.py'),
33
+ 'toolslm.funccall.python': ('funccall.html#python', 'toolslm/funccall.py'),
34
+ 'toolslm.funccall.schema2sig': ('funccall.html#schema2sig', 'toolslm/funccall.py')},
33
35
  'toolslm.md_hier': {},
34
36
  'toolslm.shell': { 'toolslm.shell.TerminalInteractiveShell.run_cell': ( 'shell.html#terminalinteractiveshell.run_cell',
35
37
  'toolslm/shell.py'),
@@ -1,15 +1,19 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../01_funccall.ipynb.
2
2
 
3
3
  # %% auto 0
4
- __all__ = ['empty', 'custom_types', 'get_schema', 'PathArg', 'python', 'mk_ns', 'call_func', 'call_func_async']
4
+ __all__ = ['empty', 'get_schema', 'python', 'mk_ns', 'call_func', 'call_func_async', 'mk_param', 'schema2sig', 'mk_tool']
5
5
 
6
6
  # %% ../01_funccall.ipynb
7
- import inspect
7
+ import inspect, json
8
8
  from collections import abc
9
9
  from fastcore.utils import *
10
10
  from fastcore.docments import docments
11
- from typing import get_origin, get_args, Dict, List, Optional, Tuple, Union
11
+ from typing import get_origin, get_args, Dict, List, Optional, Tuple, Union, Any
12
12
  from types import UnionType
13
+ from typing import get_type_hints
14
+ from inspect import Parameter, Signature
15
+ from decimal import Decimal
16
+ from uuid import UUID
13
17
 
14
18
  # %% ../01_funccall.ipynb
15
19
  empty = inspect.Parameter.empty
@@ -40,19 +44,6 @@ def _param(name, info):
40
44
  if info.default is not empty: pschema["default"] = info.default
41
45
  return pschema
42
46
 
43
- # %% ../01_funccall.ipynb
44
- custom_types = {Path}
45
-
46
- def _handle_type(t, defs):
47
- "Handle a single type, creating nested schemas if necessary"
48
- if t is NoneType: return {'type': 'null'}
49
- if t in custom_types: return {'type':'string', 'format':t.__name__}
50
- if t in (dict, list, tuple, set): return {'type': _types(t)[0]}
51
- if isinstance(t, type) and not issubclass(t, (int, float, str, bool)) or inspect.isfunction(t):
52
- defs[t.__name__] = _get_nested_schema(t)
53
- return {'$ref': f'#/$defs/{t.__name__}'}
54
- return {'type': _types(t)[0]}
55
-
56
47
  # %% ../01_funccall.ipynb
57
48
  def _is_container(t):
58
49
  "Check if type is a container (list, dict, tuple, set, Union)"
@@ -123,14 +114,9 @@ def get_schema(f:Union[callable,dict], pname='input_schema')->dict:
123
114
  assert desc, "Docstring missing!"
124
115
  d = docments(f, full=True)
125
116
  ret = d.pop('return')
126
- if ret.anno is not empty: desc += f'\n\nReturns:\n- type: {_types(ret.anno)[0]}'
117
+ if (ret.anno is not empty) and (ret.anno is not None): desc += f'\n\nReturns:\n- type: {_types(ret.anno)[0]}'
127
118
  return {"name": f.__name__, "description": desc, pname: schema}
128
119
 
129
- # %% ../01_funccall.ipynb
130
- def PathArg(
131
- path: str # A filesystem path
132
- ): return Path(path)
133
-
134
120
  # %% ../01_funccall.ipynb
135
121
  import ast, time, signal, traceback
136
122
  from fastcore.utils import *
@@ -193,14 +179,26 @@ def mk_ns(fs):
193
179
  elif callable(o) and hasattr(o, '__name__'): merged |= {o.__name__: o}
194
180
  return merged
195
181
 
182
+ # %% ../01_funccall.ipynb
183
+ def _coerce_inputs(func, inputs):
184
+ "Coerce inputs based on function type annotations"
185
+ hints = get_type_hints(func) if hasattr(func, '__annotations__') else {}
186
+ res = {}
187
+ for k,v in inputs.items():
188
+ ann = hints.get(k)
189
+ if ann in custom_types: res[k] = ann(v)
190
+ elif isinstance(v, dict) and callable(ann): res[k] = ann(**v)
191
+ else: res[k] = v
192
+ return res
193
+
196
194
  # %% ../01_funccall.ipynb
197
195
  def call_func(fc_name, fc_inputs, ns, raise_on_err=True):
198
196
  "Call the function `fc_name` with the given `fc_inputs` using namespace `ns`."
199
197
  if not isinstance(ns, abc.Mapping): ns = mk_ns(ns)
200
198
  func = ns[fc_name]
201
- # Clean up bad param names
202
199
  inps = {re.sub(r'\W', '', k):v for k,v in fc_inputs.items()}
203
- try: return func(**fc_inputs)
200
+ inps = _coerce_inputs(func, inps)
201
+ try: return func(**inps)
204
202
  except Exception as e:
205
203
  if raise_on_err: raise e from None
206
204
  else: return traceback.format_exc()
@@ -215,3 +213,35 @@ async def call_func_async(fc_name, fc_inputs, ns, raise_on_err=True):
215
213
  if raise_on_err: raise e from None
216
214
  else: return traceback.format_exc()
217
215
  return res
216
+
217
+ # %% ../01_funccall.ipynb
218
+ def mk_param(nm, props, req):
219
+ "Create a `Parameter` for `nm` with schema `props`"
220
+ kind = Parameter.POSITIONAL_OR_KEYWORD if nm in req else Parameter.KEYWORD_ONLY
221
+ default = Parameter.empty if nm in req else props.get('default')
222
+ if props.get('type') == 'array' and 'items' in props:
223
+ item_type = type_map.get(props['items'].get('type'), Any)
224
+ anno = list[item_type]
225
+ else: anno = type_map.get(props.get('type'), Any)
226
+ return Parameter(nm, kind, default=default, annotation=anno)
227
+
228
+ # %% ../01_funccall.ipynb
229
+ def schema2sig(tool):
230
+ "Convert json schema `tool` to a `Signature`"
231
+ props, req = tool.inputSchema['properties'], tool.inputSchema.get('required', [])
232
+ params = sorted([mk_param(k, v, req) for k, v in props.items()], key=lambda p: p.kind)
233
+ return Signature(params)
234
+
235
+ # %% ../01_funccall.ipynb
236
+ def mk_tool(dispfn, tool):
237
+ "Create a callable function from a JSON schema tool definition"
238
+ sig = schema2sig(tool)
239
+ props = tool.inputSchema['properties']
240
+ def fn(*args, **kwargs):
241
+ bound = sig.bind(*args, **kwargs)
242
+ return dispfn(tool.name, **bound.arguments)
243
+ fn.__doc__ = tool.description
244
+ fn.__signature__ = sig
245
+ fn.__name__ = fn.__qualname__ = tool.name
246
+ fn.__annotations__ = {k: p.annotation for k, p in sig.parameters.items()}
247
+ return fn
@@ -14,20 +14,32 @@ def create_heading_dict(text, rm_fenced=True):
14
14
  original_text = text
15
15
  original_lines = text.splitlines()
16
16
 
17
- # Use fenced-removed text only for finding headings
18
- text_for_headings = text
19
- if rm_fenced: text_for_headings = re.sub(r'```[\s\S]*?```', '', text)
17
+ # Build line mapping when removing fenced blocks
18
+ if rm_fenced:
19
+ filtered_lines,original_line_map,in_fenced = [],{},False
20
+ for i, line in enumerate(original_lines):
21
+ if line.strip().startswith('```'):
22
+ in_fenced = not in_fenced
23
+ continue
24
+ if not in_fenced:
25
+ original_line_map[len(filtered_lines)] = i
26
+ filtered_lines.append(line)
27
+
28
+ lines_for_headings = filtered_lines
29
+ else:
30
+ lines_for_headings = original_lines
31
+ original_line_map = {i: i for i in range(len(original_lines))}
20
32
 
21
- lines_for_headings = text_for_headings.splitlines()
22
33
  headings = []
23
34
 
24
- # Parse headings with their levels and line numbers
35
+ # Parse headings with their levels and original line numbers
25
36
  for idx, line in enumerate(lines_for_headings):
26
37
  match = re.match(r'^(#{1,6})\s+\S.*', line)
27
38
  if match:
28
39
  level = len(match.group(1))
29
40
  title = line.strip('#').strip()
30
- headings.append({'level': level, 'title': title, 'line': idx})
41
+ original_line_idx = original_line_map[idx]
42
+ headings.append({'level': level, 'title': title, 'line': original_line_idx})
31
43
 
32
44
  # Assign text content to each heading using original lines
33
45
  for i, h in enumerate(headings):
@@ -270,9 +282,26 @@ More content after code."""
270
282
  assert '💻 Computer' in result['🚀 Rocket Heading']
271
283
  assert result.text == md_content
272
284
 
285
+ def test_fenced_blocks_between_headings():
286
+ md_content = """# First Header
287
+ Content here.
288
+
289
+ ```python
290
+ # A comment that should be ignored for structure
291
+ def example():
292
+ pass
293
+ ```
294
+
295
+ # Second Header
296
+ This content belongs to Second Header."""
297
+ result = create_heading_dict(md_content)
298
+ assert "def example():" not in result['Second Header'].text
299
+ assert result['Second Header'].text == "# Second Header\nThis content belongs to Second Header."
300
+
273
301
  test_empty_input()
274
302
  test_whitespace_only()
275
303
  test_malformed_headings()
276
304
  test_unicode_and_emojis()
305
+ test_fenced_blocks_between_headings()
277
306
  print('tests passed')
278
307
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolslm
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Tools to make language models a bit easier to use
5
5
  Home-page: https://github.com/AnswerDotAI/toolslm
6
6
  Author: Jeremy Howard
@@ -1 +0,0 @@
1
- __version__ = "0.3.4"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes