toolslm 0.2.1__tar.gz → 0.2.3__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.2.1
3
+ Version: 0.2.3
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.2.1
4
+ version = 0.2.3
5
5
  min_python = 3.9
6
6
  license = apache2
7
7
  black_formatting = False
@@ -35,7 +35,7 @@ clean_ids = True
35
35
  clear_all = False
36
36
  conda_user = fastai
37
37
  console_scripts = folder2ctx=toolslm.xml:folder2ctx_cli
38
- cell_number = True
38
+ cell_number = False
39
39
  skip_procs =
40
40
  update_pyproject = True
41
41
 
@@ -0,0 +1 @@
1
+ __version__ = "0.2.3"
@@ -3,26 +3,26 @@
3
3
  # %% auto 0
4
4
  __all__ = ['clean_md', 'read_md', 'html2md', 'read_html', 'get_llmstxt', 'split_url', 'find_docs', 'read_docs']
5
5
 
6
- # %% ../03_download.ipynb 2
6
+ # %% ../03_download.ipynb
7
7
  from fastcore.utils import *
8
8
  from httpx import get
9
9
  from fastcore.meta import delegates
10
10
  from urllib.parse import urlparse, urljoin
11
11
 
12
- # %% ../03_download.ipynb 4
12
+ # %% ../03_download.ipynb
13
13
  def clean_md(text, rm_comments=True, rm_details=True):
14
14
  "Remove comments and `<details>` sections from `text`"
15
15
  if rm_comments: text = re.sub(r'\n?<!--.*?-->\n?', '', text, flags=re.DOTALL)
16
16
  if rm_details: text = re.sub(r'\n?<details>.*?</details>\n?', '', text, flags=re.DOTALL)
17
17
  return text
18
18
 
19
- # %% ../03_download.ipynb 5
19
+ # %% ../03_download.ipynb
20
20
  @delegates(get)
21
21
  def read_md(url, rm_comments=True, rm_details=True, **kwargs):
22
22
  "Read text from `url` and clean with `clean_docs`"
23
23
  return clean_md(get(url, **kwargs).text, rm_comments=rm_comments, rm_details=rm_details)
24
24
 
25
- # %% ../03_download.ipynb 7
25
+ # %% ../03_download.ipynb
26
26
  def html2md(s:str, ignore_links=True):
27
27
  "Convert `s` from HTML to markdown"
28
28
  import html2text
@@ -32,7 +32,7 @@ def html2md(s:str, ignore_links=True):
32
32
  o.ignore_images = True
33
33
  return o.handle(s)
34
34
 
35
- # %% ../03_download.ipynb 8
35
+ # %% ../03_download.ipynb
36
36
  def read_html(url, # URL to read
37
37
  sel=None, # Read only outerHTML of CSS selector `sel`
38
38
  rm_comments=True, # Removes HTML comments
@@ -54,7 +54,7 @@ def read_html(url, # URL to read
54
54
  if wrap_tag: return '\n'.join([f"\n<{wrap_tag}>\n{o}</{wrap_tag}>\n" for o in mds])
55
55
  else: return'\n'.join(mds)
56
56
 
57
- # %% ../03_download.ipynb 13
57
+ # %% ../03_download.ipynb
58
58
  def get_llmstxt(url, optional=False, n_workers=None):
59
59
  "Get llms.txt file from and expand it with `llms_txt.create_ctx()`"
60
60
  if not url.endswith('llms.txt'): return None
@@ -63,7 +63,7 @@ def get_llmstxt(url, optional=False, n_workers=None):
63
63
  if resp.status_code!=200: return None
64
64
  return llms_txt.create_ctx(resp.text, optional=optional, n_workers=n_workers)
65
65
 
66
- # %% ../03_download.ipynb 15
66
+ # %% ../03_download.ipynb
67
67
  def split_url(url):
68
68
  "Split `url` into base, path, and file name, normalising name to '/' if empty"
69
69
  parsed = urlparse(url.strip('/'))
@@ -73,13 +73,13 @@ def split_url(url):
73
73
  if not path and not fname: path='/'
74
74
  return base,path,fname
75
75
 
76
- # %% ../03_download.ipynb 17
76
+ # %% ../03_download.ipynb
77
77
  def _tryget(url):
78
78
  "Return response from `url` if `status_code!=404`, otherwise `None`"
79
79
  res = get(url)
80
80
  return None if res.status_code==404 else url
81
81
 
82
- # %% ../03_download.ipynb 18
82
+ # %% ../03_download.ipynb
83
83
  def find_docs(url):
84
84
  "If available, return LLM-friendly llms.txt context or markdown file location from `url`"
85
85
  base,path,fname = split_url(url)
@@ -99,7 +99,7 @@ def find_docs(url):
99
99
  if parsed_url.path == '/' or not parsed_url.path: return None
100
100
  return find_docs(urljoin(url, '..'))
101
101
 
102
- # %% ../03_download.ipynb 23
102
+ # %% ../03_download.ipynb
103
103
  def read_docs(url, optional=False, n_workers=None, rm_comments=True, rm_details=True):
104
104
  "If available, return LLM-friendly llms.txt context or markdown file response for `url`"
105
105
  url = find_docs(url)
@@ -3,7 +3,7 @@
3
3
  # %% auto 0
4
4
  __all__ = ['empty', 'custom_types', 'get_schema', 'PathArg', 'python', 'mk_ns', 'call_func', 'call_func_async']
5
5
 
6
- # %% ../01_funccall.ipynb 2
6
+ # %% ../01_funccall.ipynb
7
7
  import inspect
8
8
  from collections import abc
9
9
  from fastcore.utils import *
@@ -11,10 +11,10 @@ from fastcore.docments import docments
11
11
  from typing import get_origin, get_args, Dict, List, Optional, Tuple, Union
12
12
  from types import UnionType
13
13
 
14
- # %% ../01_funccall.ipynb 4
14
+ # %% ../01_funccall.ipynb
15
15
  empty = inspect.Parameter.empty
16
16
 
17
- # %% ../01_funccall.ipynb 12
17
+ # %% ../01_funccall.ipynb
18
18
  def _types(t:type)->tuple[str,Optional[str]]:
19
19
  "Tuple of json schema type name and (if appropriate) array item name."
20
20
  if t is empty: raise TypeError('Missing type')
@@ -31,7 +31,7 @@ def _types(t:type)->tuple[str,Optional[str]]:
31
31
  # if t is the type itself like int, use the __name__ representation as the key
32
32
  else: return tmap.get(t.__name__, "object"), None
33
33
 
34
- # %% ../01_funccall.ipynb 19
34
+ # %% ../01_funccall.ipynb
35
35
  def _param(name, info):
36
36
  "json schema parameter given `name` and `info` from docments full dict."
37
37
  paramt,itemt = _types(info.anno)
@@ -40,19 +40,20 @@ def _param(name, info):
40
40
  if info.default is not empty: pschema["default"] = info.default
41
41
  return pschema
42
42
 
43
- # %% ../01_funccall.ipynb 22
43
+ # %% ../01_funccall.ipynb
44
44
  custom_types = {Path}
45
45
 
46
46
  def _handle_type(t, defs):
47
47
  "Handle a single type, creating nested schemas if necessary"
48
48
  if t is NoneType: return {'type': 'null'}
49
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]}
50
51
  if isinstance(t, type) and not issubclass(t, (int, float, str, bool)) or inspect.isfunction(t):
51
52
  defs[t.__name__] = _get_nested_schema(t)
52
53
  return {'$ref': f'#/$defs/{t.__name__}'}
53
54
  return {'type': _types(t)[0]}
54
55
 
55
- # %% ../01_funccall.ipynb 24
56
+ # %% ../01_funccall.ipynb
56
57
  def _is_container(t):
57
58
  "Check if type is a container (list, dict, tuple, set, Union)"
58
59
  origin = get_origin(t)
@@ -62,7 +63,7 @@ def _is_parameterized(t):
62
63
  "Check if type has arguments (e.g. list[int] vs list, dict[str, int] vs dict)"
63
64
  return _is_container(t) and (get_args(t) != ())
64
65
 
65
- # %% ../01_funccall.ipynb 30
66
+ # %% ../01_funccall.ipynb
66
67
  def _handle_container(origin, args, defs):
67
68
  "Handle container types like dict, list, tuple, set, and Union"
68
69
  if origin is Union or origin is UnionType:
@@ -83,7 +84,7 @@ def _handle_container(origin, args, defs):
83
84
  return schema
84
85
  return None
85
86
 
86
- # %% ../01_funccall.ipynb 31
87
+ # %% ../01_funccall.ipynb
87
88
  def _process_property(name, obj, props, req, defs):
88
89
  "Process a single property of the schema"
89
90
  p = _param(name, obj)
@@ -91,12 +92,12 @@ def _process_property(name, obj, props, req, defs):
91
92
  if obj.default is empty: req[name] = True
92
93
 
93
94
  if _is_container(obj.anno) and _is_parameterized(obj.anno):
94
- p.update(_handle_container(get_origin(obj.anno), get_args(obj.anno), defs))
95
+ p.update(_handle_container(get_origin(obj.anno), get_args(obj.anno), defs))
95
96
  else:
96
97
  # Non-container type or container without arguments
97
98
  p.update(_handle_type(obj.anno, defs))
98
99
 
99
- # %% ../01_funccall.ipynb 32
100
+ # %% ../01_funccall.ipynb
100
101
  def _get_nested_schema(obj):
101
102
  "Generate nested JSON schema for a class or function"
102
103
  d = docments(obj, full=True)
@@ -111,7 +112,7 @@ def _get_nested_schema(obj):
111
112
  if defs: schema['$defs'] = defs
112
113
  return schema
113
114
 
114
- # %% ../01_funccall.ipynb 36
115
+ # %% ../01_funccall.ipynb
115
116
  def get_schema(f:Union[callable,dict], pname='input_schema')->dict:
116
117
  "Generate JSON schema for a class, function, or method"
117
118
  if isinstance(f, dict): return f
@@ -123,16 +124,16 @@ def get_schema(f:Union[callable,dict], pname='input_schema')->dict:
123
124
  if ret.anno is not empty: desc += f'\n\nReturns:\n- type: {_types(ret.anno)[0]}'
124
125
  return {"name": f.__name__, "description": desc, pname: schema}
125
126
 
126
- # %% ../01_funccall.ipynb 47
127
+ # %% ../01_funccall.ipynb
127
128
  def PathArg(
128
129
  path: str # A filesystem path
129
130
  ): return Path(path)
130
131
 
131
- # %% ../01_funccall.ipynb 67
132
+ # %% ../01_funccall.ipynb
132
133
  import ast, time, signal, traceback
133
134
  from fastcore.utils import *
134
135
 
135
- # %% ../01_funccall.ipynb 68
136
+ # %% ../01_funccall.ipynb
136
137
  def _copy_loc(new, orig):
137
138
  "Copy location information from original node to new node and all children."
138
139
  new = ast.copy_location(new, orig)
@@ -141,7 +142,7 @@ def _copy_loc(new, orig):
141
142
  elif isinstance(o, list): setattr(new, field, [_copy_loc(value, orig) for value in o])
142
143
  return new
143
144
 
144
- # %% ../01_funccall.ipynb 70
145
+ # %% ../01_funccall.ipynb
145
146
  def _run(code:str, glb:dict=None, loc:dict=None):
146
147
  "Run `code`, returning final expression (similar to IPython)"
147
148
  tree = ast.parse(code)
@@ -164,14 +165,14 @@ def _run(code:str, glb:dict=None, loc:dict=None):
164
165
  if _result is not None: return _result
165
166
  return stdout_buffer.getvalue().strip()
166
167
 
167
- # %% ../01_funccall.ipynb 75
168
- def python(code:str, # Code to execute
169
- glb:Optional[dict]=None, # Globals namespace
170
- loc:Optional[dict]=None, # Locals namespace
171
- timeout:int=3600 # Maximum run time in seconds before a `TimeoutError` is raised
172
- ): # Result of last node, if it's an expression, or `None` otherwise
173
- """Executes python `code` with `timeout` and returning final expression (similar to IPython).
174
- Raised exceptions are returned as a string, with a stack trace."""
168
+ # %% ../01_funccall.ipynb
169
+ def python(
170
+ code:str, # Code to execute
171
+ glb:Optional[dict]=None, # Globals namespace
172
+ loc:Optional[dict]=None, # Locals namespace
173
+ timeout:int=3600 # Maximum run time in seconds
174
+ ):
175
+ "Executes python `code` with `timeout` and returning final expression (similar to IPython)."
175
176
  def handler(*args): raise TimeoutError()
176
177
  if glb is None: glb = inspect.currentframe().f_back.f_globals
177
178
  if loc is None: loc=glb
@@ -181,32 +182,36 @@ def python(code:str, # Code to execute
181
182
  except Exception as e: return traceback.format_exc()
182
183
  finally: signal.alarm(0)
183
184
 
184
- # %% ../01_funccall.ipynb 86
185
- def mk_ns(*funcs_or_objs):
185
+ # %% ../01_funccall.ipynb
186
+ def mk_ns(fs):
187
+ if isinstance(fs, abc.Mapping): return fs
186
188
  merged = {}
187
- for o in funcs_or_objs:
188
- if isinstance(o, type): merged |= {n:getattr(o,n) for n,m in o.__dict__.items() if isinstance(m, (staticmethod, classmethod))}
189
- if isinstance(o, object): merged |= {n:getattr(o,n) for n, m in inspect.getmembers(o, inspect.ismethod)} | {n:m for n,m in o.__class__.__dict__.items() if isinstance(m, staticmethod)}
190
- if callable(o) and hasattr(o, '__name__'): merged |= {o.__name__: o}
189
+ for o in listify(fs):
190
+ if isinstance(o, dict): merged |= o
191
+ elif isinstance(o, type): merged |= {n:getattr(o,n) for n,m in o.__dict__.items() if isinstance(m, (staticmethod, classmethod))}
192
+ elif callable(o) and hasattr(o, '__name__'): merged |= {o.__name__: o}
193
+ merged |= {n:getattr(o,n) for n, m in inspect.getmembers(o, inspect.ismethod)} | {n:m for n,m in o.__class__.__dict__.items() if isinstance(m, staticmethod)}
191
194
  return merged
192
195
 
193
- # %% ../01_funccall.ipynb 95
196
+ # %% ../01_funccall.ipynb
194
197
  def call_func(fc_name, fc_inputs, ns, raise_on_err=True):
195
198
  "Call the function `fc_name` with the given `fc_inputs` using namespace `ns`."
196
- if not isinstance(ns, abc.Mapping): ns = mk_ns(*ns)
199
+ if not isinstance(ns, abc.Mapping): ns = mk_ns(ns)
197
200
  func = ns[fc_name]
201
+ # Clean up bad param names
202
+ inps = {re.sub(r'\W', '', k):v for k,v in fc_inputs.items()}
198
203
  try: return func(**fc_inputs)
199
204
  except Exception as e:
200
- if raise_on_err: raise e
205
+ if raise_on_err: raise e from None
201
206
  else: return traceback.format_exc()
202
207
 
203
- # %% ../01_funccall.ipynb 106
208
+ # %% ../01_funccall.ipynb
204
209
  async def call_func_async(fc_name, fc_inputs, ns, raise_on_err=True):
205
210
  "Awaits the function `fc_name` with the given `fc_inputs` using namespace `ns`."
206
211
  res = call_func(fc_name, fc_inputs, ns, raise_on_err=raise_on_err)
207
212
  if inspect.iscoroutine(res):
208
213
  try: res = await res
209
214
  except Exception as e:
210
- if raise_on_err: raise e
215
+ if raise_on_err: raise e from None
211
216
  else: return traceback.format_exc()
212
217
  return res
@@ -3,18 +3,18 @@
3
3
  # %% auto 0
4
4
  __all__ = ['get_shell']
5
5
 
6
- # %% ../02_shell.ipynb 2
6
+ # %% ../02_shell.ipynb
7
7
  import ast, time, signal, traceback
8
8
  from fastcore.utils import *
9
9
 
10
- # %% ../02_shell.ipynb 4
10
+ # %% ../02_shell.ipynb
11
11
  from IPython.terminal.interactiveshell import TerminalInteractiveShell
12
12
  from IPython.utils.capture import capture_output
13
13
 
14
- # %% ../02_shell.ipynb 7
14
+ # %% ../02_shell.ipynb
15
15
  TerminalInteractiveShell.orig_run = TerminalInteractiveShell.run_cell
16
16
 
17
- # %% ../02_shell.ipynb 8
17
+ # %% ../02_shell.ipynb
18
18
  @patch
19
19
  def run_cell(self:TerminalInteractiveShell, cell, timeout=None):
20
20
  "Wrapper for original `run_cell` which adds timeout and output capture"
@@ -31,7 +31,7 @@ def run_cell(self:TerminalInteractiveShell, cell, timeout=None):
31
31
  finally:
32
32
  if timeout: signal.alarm(0)
33
33
 
34
- # %% ../02_shell.ipynb 9
34
+ # %% ../02_shell.ipynb
35
35
  def get_shell()->TerminalInteractiveShell:
36
36
  "Get a `TerminalInteractiveShell` with minimal functionality"
37
37
  sh = TerminalInteractiveShell()
@@ -3,7 +3,7 @@
3
3
  # %% auto 0
4
4
  __all__ = ['doctype', 'json_to_xml', 'mk_doctype', 'mk_doc', 'docs_xml', 'files2ctx', 'folder2ctx', 'folder2ctx_cli']
5
5
 
6
- # %% ../00_xml.ipynb 3
6
+ # %% ../00_xml.ipynb
7
7
  import hashlib,xml.etree.ElementTree as ET
8
8
  from collections import namedtuple
9
9
 
@@ -15,7 +15,7 @@ from fastcore.script import call_parse
15
15
  try: from IPython import display
16
16
  except: display=None
17
17
 
18
- # %% ../00_xml.ipynb 4
18
+ # %% ../00_xml.ipynb
19
19
  def json_to_xml(d:dict, # JSON dictionary to convert
20
20
  rnm:str # Root name
21
21
  )->str:
@@ -31,10 +31,10 @@ def json_to_xml(d:dict, # JSON dictionary to convert
31
31
  ET.indent(root)
32
32
  return ET.tostring(root, encoding='unicode')
33
33
 
34
- # %% ../00_xml.ipynb 9
34
+ # %% ../00_xml.ipynb
35
35
  doctype = namedtuple('doctype', ['src', 'content'])
36
36
 
37
- # %% ../00_xml.ipynb 11
37
+ # %% ../00_xml.ipynb
38
38
  def _add_nls(s):
39
39
  "Add newlines to start and end of `s` if missing"
40
40
  if not s: return s
@@ -42,7 +42,7 @@ def _add_nls(s):
42
42
  if s[-1]!='\n': s = s+'\n'
43
43
  return s
44
44
 
45
- # %% ../00_xml.ipynb 16
45
+ # %% ../00_xml.ipynb
46
46
  def mk_doctype(content:str, # The document content
47
47
  src:Optional[str]=None # URL, filename, etc; defaults to `md5(content)` if not provided
48
48
  ) -> namedtuple:
@@ -50,7 +50,7 @@ def mk_doctype(content:str, # The document content
50
50
  if src is None: src = hashlib.md5(content.encode()).hexdigest()[:8]
51
51
  return doctype(_add_nls(str(src).strip()), _add_nls(content.strip()))
52
52
 
53
- # %% ../00_xml.ipynb 19
53
+ # %% ../00_xml.ipynb
54
54
  def mk_doc(index:int, # The document index
55
55
  content:str, # The document content
56
56
  src:Optional[str]=None, # URL, filename, etc; defaults to `md5(content)` if not provided
@@ -62,7 +62,7 @@ def mk_doc(index:int, # The document index
62
62
  src = Src(NotStr(dt.src))
63
63
  return Document(src, content, index=index, **kwargs)
64
64
 
65
- # %% ../00_xml.ipynb 22
65
+ # %% ../00_xml.ipynb
66
66
  def docs_xml(docs:list[str], # The content of each document
67
67
  srcs:Optional[list]=None, # URLs, filenames, etc; each one defaults to `md5(content)` if not provided
68
68
  prefix:bool=True, # Include Anthropic's suggested prose intro?
@@ -75,7 +75,7 @@ def docs_xml(docs:list[str], # The content of each document
75
75
  docs = (mk_doc(i+1, d, s, **kw) for i,(d,s,kw) in enumerate(zip(docs,srcs,details)))
76
76
  return pre + to_xml(Documents(docs))
77
77
 
78
- # %% ../00_xml.ipynb 29
78
+ # %% ../00_xml.ipynb
79
79
  def files2ctx(
80
80
  fnames:list[Union[str,Path]], # List of file names to add to context
81
81
  prefix:bool=True # Include Anthropic's suggested prose intro?
@@ -84,7 +84,7 @@ def files2ctx(
84
84
  contents = [o.read_text() for o in fnames]
85
85
  return docs_xml(contents, fnames, prefix=prefix)
86
86
 
87
- # %% ../00_xml.ipynb 32
87
+ # %% ../00_xml.ipynb
88
88
  @delegates(globtastic)
89
89
  def folder2ctx(
90
90
  folder:Union[str,Path], # Folder name containing files to add to context
@@ -94,7 +94,7 @@ def folder2ctx(
94
94
  fnames = globtastic(folder, **kwargs)
95
95
  return files2ctx(fnames, prefix=prefix)
96
96
 
97
- # %% ../00_xml.ipynb 34
97
+ # %% ../00_xml.ipynb
98
98
  @call_parse
99
99
  @delegates(folder2ctx)
100
100
  def folder2ctx_cli(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolslm
3
- Version: 0.2.1
3
+ Version: 0.2.3
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.2.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes