toolslm 0.0.7__py3-none-any.whl → 0.1.1__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.
toolslm/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.0.7"
1
+ __version__ = "0.1.1"
toolslm/_modidx.py CHANGED
@@ -14,10 +14,13 @@ 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._copy_loc': ('funccall.html#_copy_loc', 'toolslm/funccall.py'),
17
+ 'toolslm.funccall': { 'toolslm.funccall.PathArg': ('funccall.html#patharg', 'toolslm/funccall.py'),
18
+ 'toolslm.funccall._copy_loc': ('funccall.html#_copy_loc', 'toolslm/funccall.py'),
18
19
  'toolslm.funccall._get_nested_schema': ('funccall.html#_get_nested_schema', 'toolslm/funccall.py'),
19
20
  'toolslm.funccall._handle_container': ('funccall.html#_handle_container', 'toolslm/funccall.py'),
20
21
  'toolslm.funccall._handle_type': ('funccall.html#_handle_type', 'toolslm/funccall.py'),
22
+ 'toolslm.funccall._is_container': ('funccall.html#_is_container', 'toolslm/funccall.py'),
23
+ 'toolslm.funccall._is_parameterized': ('funccall.html#_is_parameterized', 'toolslm/funccall.py'),
21
24
  'toolslm.funccall._param': ('funccall.html#_param', 'toolslm/funccall.py'),
22
25
  'toolslm.funccall._process_property': ('funccall.html#_process_property', 'toolslm/funccall.py'),
23
26
  'toolslm.funccall._run': ('funccall.html#_run', 'toolslm/funccall.py'),
toolslm/download.py CHANGED
@@ -27,10 +27,10 @@ def read_md(url, rm_comments=True, rm_details=True, **kwargs):
27
27
  return clean_md(get(url, **kwargs).text, rm_comments=rm_comments, rm_details=rm_details)
28
28
 
29
29
  # %% ../03_download.ipynb 7
30
- def html2md(s:str):
30
+ def html2md(s:str, ignore_links=True):
31
31
  "Convert `s` from HTML to markdown"
32
32
  o = HTML2Text(bodywidth=5000)
33
- o.ignore_links = True
33
+ o.ignore_links = ignore_links
34
34
  o.mark_code = True
35
35
  o.ignore_images = True
36
36
  return o.handle(s)
@@ -42,6 +42,7 @@ def read_html(url, # URL to read
42
42
  rm_details=True, # Removes `<details>` tags
43
43
  multi=False, # Get all matches to `sel` or first one
44
44
  wrap_tag=None, #If multi, each selection wrapped with <wrap_tag>content</wrap_tag>
45
+ ignore_links=True,
45
46
  ): # Cleaned markdown
46
47
  "Get `url`, optionally selecting CSS selector `sel`, and convert to clean markdown"
47
48
  page = get(url).text
@@ -51,12 +52,12 @@ def read_html(url, # URL to read
51
52
  page = [str(el) for el in soup.select(sel)]
52
53
  if not wrap_tag: page = "\n".join(page)
53
54
  else: page = str(soup.select_one(sel))
54
- mds = map(lambda x: clean_md(html2md(x), rm_comments, rm_details=rm_details), tuplify(page))
55
+ mds = map(lambda x: clean_md(html2md(x, ignore_links=ignore_links), rm_comments, rm_details=rm_details), tuplify(page))
55
56
  if wrap_tag: return '\n'.join([f"\n<{wrap_tag}>\n{o}</{wrap_tag}>\n" for o in mds])
56
57
  else: return'\n'.join(mds)
57
58
 
58
59
 
59
- # %% ../03_download.ipynb 12
60
+ # %% ../03_download.ipynb 13
60
61
  def get_llmstxt(url, optional=False, n_workers=None):
61
62
  "Get llms.txt file from and expand it with `llms_txt.create_ctx()`"
62
63
  if not url.endswith('llms.txt'): return None
@@ -64,7 +65,7 @@ def get_llmstxt(url, optional=False, n_workers=None):
64
65
  if resp.status_code!=200: return None
65
66
  return create_ctx(resp.text, optional=optional, n_workers=n_workers)
66
67
 
67
- # %% ../03_download.ipynb 14
68
+ # %% ../03_download.ipynb 15
68
69
  def split_url(url):
69
70
  "Split `url` into base, path, and file name, normalising name to '/' if empty"
70
71
  parsed = urlparse(url.strip('/'))
@@ -74,13 +75,13 @@ def split_url(url):
74
75
  if not path and not fname: path='/'
75
76
  return base,path,fname
76
77
 
77
- # %% ../03_download.ipynb 16
78
+ # %% ../03_download.ipynb 17
78
79
  def _tryget(url):
79
80
  "Return response from `url` if `status_code!=404`, otherwise `None`"
80
81
  res = get(url)
81
82
  return None if res.status_code==404 else url
82
83
 
83
- # %% ../03_download.ipynb 17
84
+ # %% ../03_download.ipynb 18
84
85
  def find_docs(url):
85
86
  "If available, return LLM-friendly llms.txt context or markdown file location from `url`"
86
87
  base,path,fname = split_url(url)
toolslm/funccall.py CHANGED
@@ -1,13 +1,15 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../01_funccall.ipynb.
2
2
 
3
3
  # %% auto 0
4
- __all__ = ['empty', 'get_schema', 'python', 'mk_ns', 'call_func']
4
+ __all__ = ['empty', 'custom_types', 'get_schema', 'PathArg', 'python', 'mk_ns', 'call_func']
5
5
 
6
6
  # %% ../01_funccall.ipynb 2
7
7
  import inspect
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
12
+ from types import UnionType
11
13
 
12
14
  # %% ../01_funccall.ipynb 3
13
15
  empty = inspect.Parameter.empty
@@ -18,11 +20,18 @@ def _types(t:type)->tuple[str,Optional[str]]:
18
20
  if t is empty: raise TypeError('Missing type')
19
21
  tmap = {int:"integer", float:"number", str:"string", bool:"boolean", list:"array", dict:"object"}
20
22
  tmap.update({k.__name__: v for k, v in tmap.items()})
21
- if getattr(t, '__origin__', None) in (list,tuple): return "array", tmap.get(t.__args__[0].__name__, "object")
23
+ if getattr(t, '__origin__', None) in (list,tuple):
24
+ args = getattr(t, '__args__', None)
25
+ item_type = "object" if not args else tmap.get(t.__args__[0].__name__, "object")
26
+ return "array", item_type
27
+ # if t is a string like 'int', directly use the string as the key
22
28
  elif isinstance(t, str): return tmap.get(t, "object"), None
29
+ # if t is the type itself and a container
30
+ elif get_origin(t): return tmap.get(get_origin(t).__name__, "object"), None
31
+ # if t is the type itself like int, use the __name__ representation as the key
23
32
  else: return tmap.get(t.__name__, "object"), None
24
33
 
25
- # %% ../01_funccall.ipynb 16
34
+ # %% ../01_funccall.ipynb 18
26
35
  def _param(name, info):
27
36
  "json schema parameter given `name` and `info` from docments full dict."
28
37
  paramt,itemt = _types(info.anno)
@@ -31,17 +40,33 @@ def _param(name, info):
31
40
  if info.default is not empty: pschema["default"] = info.default
32
41
  return pschema
33
42
 
34
- # %% ../01_funccall.ipynb 19
43
+ # %% ../01_funccall.ipynb 21
44
+ custom_types = {Path}
45
+
35
46
  def _handle_type(t, defs):
36
47
  "Handle a single type, creating nested schemas if necessary"
37
- if isinstance(t, type) and not issubclass(t, (int, float, str, bool)):
48
+ if t is NoneType: return {'type': 'null'}
49
+ if t in custom_types: return {'type':'string', 'format':t.__name__}
50
+ if isinstance(t, type) and not issubclass(t, (int, float, str, bool)) or inspect.isfunction(t):
38
51
  defs[t.__name__] = _get_nested_schema(t)
39
52
  return {'$ref': f'#/$defs/{t.__name__}'}
40
53
  return {'type': _types(t)[0]}
41
54
 
42
- # %% ../01_funccall.ipynb 20
55
+ # %% ../01_funccall.ipynb 23
56
+ def _is_container(t):
57
+ "Check if type is a container (list, dict, tuple, set, Union)"
58
+ origin = get_origin(t)
59
+ return origin in (list, dict, tuple, set, Union) if origin else False
60
+
61
+ def _is_parameterized(t):
62
+ "Check if type has arguments (e.g. list[int] vs list, dict[str, int] vs dict)"
63
+ return _is_container(t) and (get_args(t) != ())
64
+
65
+ # %% ../01_funccall.ipynb 29
43
66
  def _handle_container(origin, args, defs):
44
- "Handle container types like dict, list, tuple, set"
67
+ "Handle container types like dict, list, tuple, set, and Union"
68
+ if origin is Union or origin is UnionType:
69
+ return {"anyOf": [_handle_type(arg, defs) for arg in args]}
45
70
  if origin is dict:
46
71
  value_type = args[1].__args__[0] if hasattr(args[1], '__args__') else args[1]
47
72
  return {
@@ -58,34 +83,35 @@ def _handle_container(origin, args, defs):
58
83
  return schema
59
84
  return None
60
85
 
61
- # %% ../01_funccall.ipynb 21
86
+ # %% ../01_funccall.ipynb 30
62
87
  def _process_property(name, obj, props, req, defs):
63
88
  "Process a single property of the schema"
64
89
  p = _param(name, obj)
65
90
  props[name] = p
66
91
  if obj.default is empty: req[name] = True
67
-
68
- if hasattr(obj.anno, '__origin__'):
69
- p.update(_handle_container(obj.anno.__origin__, obj.anno.__args__, defs))
92
+
93
+ if _is_container(obj.anno) and _is_parameterized(obj.anno):
94
+ p.update(_handle_container(get_origin(obj.anno), get_args(obj.anno), defs))
70
95
  else:
96
+ # Non-container type or container without arguments
71
97
  p.update(_handle_type(obj.anno, defs))
72
98
 
73
- # %% ../01_funccall.ipynb 22
99
+ # %% ../01_funccall.ipynb 31
74
100
  def _get_nested_schema(obj):
75
101
  "Generate nested JSON schema for a class or function"
76
102
  d = docments(obj, full=True)
77
103
  props, req, defs = {}, {}, {}
78
-
104
+
79
105
  for n, o in d.items():
80
106
  if n != 'return' and n != 'self':
81
107
  _process_property(n, o, props, req, defs)
82
-
108
+
83
109
  schema = dict(type='object', properties=props, title=obj.__name__ if isinstance(obj, type) else None)
84
110
  if req: schema['required'] = list(req)
85
111
  if defs: schema['$defs'] = defs
86
112
  return schema
87
113
 
88
- # %% ../01_funccall.ipynb 26
114
+ # %% ../01_funccall.ipynb 35
89
115
  def get_schema(f:callable, pname='input_schema')->dict:
90
116
  "Generate JSON schema for a class, function, or method"
91
117
  schema = _get_nested_schema(f)
@@ -96,11 +122,16 @@ def get_schema(f:callable, pname='input_schema')->dict:
96
122
  if ret.anno is not empty: desc += f'\n\nReturns:\n- type: {_types(ret.anno)[0]}'
97
123
  return {"name": f.__name__, "description": desc, pname: schema}
98
124
 
99
- # %% ../01_funccall.ipynb 39
125
+ # %% ../01_funccall.ipynb 46
126
+ def PathArg(
127
+ path: str # A filesystem path
128
+ ): return Path(path)
129
+
130
+ # %% ../01_funccall.ipynb 66
100
131
  import ast, time, signal, traceback
101
132
  from fastcore.utils import *
102
133
 
103
- # %% ../01_funccall.ipynb 40
134
+ # %% ../01_funccall.ipynb 67
104
135
  def _copy_loc(new, orig):
105
136
  "Copy location information from original node to new node and all children."
106
137
  new = ast.copy_location(new, orig)
@@ -109,7 +140,7 @@ def _copy_loc(new, orig):
109
140
  elif isinstance(o, list): setattr(new, field, [_copy_loc(value, orig) for value in o])
110
141
  return new
111
142
 
112
- # %% ../01_funccall.ipynb 42
143
+ # %% ../01_funccall.ipynb 69
113
144
  def _run(code:str ):
114
145
  "Run `code`, returning final expression (similar to IPython)"
115
146
  tree = ast.parse(code)
@@ -132,7 +163,7 @@ def _run(code:str ):
132
163
  if _result is not None: return _result
133
164
  return stdout_buffer.getvalue().strip()
134
165
 
135
- # %% ../01_funccall.ipynb 47
166
+ # %% ../01_funccall.ipynb 74
136
167
  def python(code, # Code to execute
137
168
  timeout=5 # Maximum run time in seconds before a `TimeoutError` is raised
138
169
  ): # Result of last node, if it's an expression, or `None` otherwise
@@ -145,7 +176,7 @@ def python(code, # Code to execute
145
176
  except Exception as e: return traceback.format_exc()
146
177
  finally: signal.alarm(0)
147
178
 
148
- # %% ../01_funccall.ipynb 54
179
+ # %% ../01_funccall.ipynb 81
149
180
  def mk_ns(*funcs_or_objs):
150
181
  merged = {}
151
182
  for o in funcs_or_objs:
@@ -154,7 +185,7 @@ def mk_ns(*funcs_or_objs):
154
185
  if callable(o) and hasattr(o, '__name__'): merged |= {o.__name__: o}
155
186
  return merged
156
187
 
157
- # %% ../01_funccall.ipynb 63
188
+ # %% ../01_funccall.ipynb 90
158
189
  def call_func(fc_name, fc_inputs, ns):
159
190
  "Call the function `fc_name` with the given `fc_inputs` using namespace `ns`."
160
191
  if not isinstance(ns, abc.Mapping): ns = mk_ns(*ns)
toolslm/md_hier.py CHANGED
@@ -7,9 +7,15 @@ def markdown_to_dict(markdown_content):
7
7
 
8
8
  lines = markdown_content.splitlines()
9
9
  headings = []
10
+ in_code_block = False
10
11
 
11
12
  # Parse headings with their levels and line numbers
12
13
  for idx, line in enumerate(lines):
14
+ # Toggle code block state when encountering fence
15
+ if line.strip().startswith('```'): in_code_block = not in_code_block
16
+
17
+ # Only detect headings when not in a code block
18
+ if in_code_block: continue
13
19
  match = re.match(r'^(#{1,6})\s*(.*)', line)
14
20
  if match:
15
21
  level = len(match.group(1))
@@ -29,13 +35,15 @@ def markdown_to_dict(markdown_content):
29
35
 
30
36
  # Build the dictionary with hierarchical keys
31
37
  result,stack = {},[]
38
+ first_level = headings[0]['level']
32
39
  for h in headings:
33
- stack = stack[:h['level'] - 1] + [clean_heading(h['text'])]
40
+ stack = stack[:h['level'] - first_level] + [clean_heading(h['text'])]
34
41
  key = '.'.join(stack)
35
42
  result[key] = h['content']
36
43
  return dict2obj(result)
37
44
 
38
45
  def create_heading_dict(text):
46
+ text = re.sub(r'```[\s\S]*?```', '', text)
39
47
  headings = re.findall(r'^#+.*', text, flags=re.MULTILINE)
40
48
  result = {}
41
49
  stack = [result]
@@ -126,11 +134,42 @@ Admin users management.
126
134
  assert result['Parent.Child'] == '## Child\nChild content.\n### Grandchild\nGrandchild content.'
127
135
  assert result['Parent.Child.Grandchild'] == '### Grandchild\nGrandchild content.'
128
136
 
137
+ def test_multiple_level2_siblings():
138
+ md_content = "##Sib 1\n##Sib 2\n##Sib 3\n##Sib 4\n##Sib 5'"
139
+ result = markdown_to_dict(md_content)
140
+ assert 'Sib 1' in result
141
+ assert 'Sib 2' in result
142
+ assert 'Sib 3' in result
143
+ assert 'Sib 4' in result
144
+ assert 'Sib 5' in result
145
+
146
+ def test_code_chunks_escaped():
147
+ md_content = "# Parent\nParent content.\n## Child\nChild content.\n```python\n# Code comment\nprint('Hello, world!')\n```"
148
+ result = markdown_to_dict(md_content)
149
+ assert 'Code comment' not in result
150
+ assert "# Code comment" in result['Parent.Child']
151
+
129
152
  test_empty_content()
130
153
  test_special_characters()
131
154
  test_duplicate_headings()
132
155
  test_no_content()
133
156
  test_different_levels()
134
157
  test_parent_includes_subheadings()
158
+ test_multiple_level2_siblings()
159
+ test_code_chunks_escaped()
135
160
  print('tests passed')
136
161
 
162
+ def test_nested_headings():
163
+ md_content = "# Parent\nParent content.\n## Child\nChild content.\n### Grandchild\nGrandchild content."
164
+ result = create_heading_dict(md_content)
165
+ assert 'Child' in result['Parent']
166
+ assert 'Grandchild' in result['Parent']['Child']
167
+
168
+ def test_code_chunks_escaped():
169
+ md_content = "# Parent\nParent content.\n## Child\nChild content.\n```python\n# Code comment\nprint('Hello, world!')\n```"
170
+ result = create_heading_dict(md_content)
171
+ assert 'Code comment' not in result
172
+
173
+ test_nested_headings()
174
+ test_code_chunks_escaped()
175
+ print('tests passed')
toolslm/xml.py CHANGED
@@ -10,7 +10,7 @@ from collections import namedtuple
10
10
  from fastcore.utils import *
11
11
  from fastcore.meta import delegates
12
12
  from fastcore.xtras import hl_md
13
- from fastcore.xml import to_xml, Document, Documents, Document_content, Source
13
+ from fastcore.xml import to_xml, Document, Documents, Document_content, Src
14
14
  from fastcore.script import call_parse
15
15
  try: from IPython import display
16
16
  except: display=None
@@ -32,7 +32,7 @@ def json_to_xml(d:dict, # JSON dictionary to convert
32
32
  return ET.tostring(root, encoding='unicode')
33
33
 
34
34
  # %% ../00_xml.ipynb 9
35
- doctype = namedtuple('doctype', ['source', 'content'])
35
+ doctype = namedtuple('doctype', ['src', 'content'])
36
36
 
37
37
  # %% ../00_xml.ipynb 11
38
38
  def _add_nls(s):
@@ -42,40 +42,40 @@ def _add_nls(s):
42
42
  if s[-1]!='\n': s = s+'\n'
43
43
  return s
44
44
 
45
- # %% ../00_xml.ipynb 13
45
+ # %% ../00_xml.ipynb 16
46
46
  def mk_doctype(content:str, # The document content
47
- source:Optional[str]=None # URL, filename, etc; defaults to `md5(content)` if not provided
47
+ src:Optional[str]=None # URL, filename, etc; defaults to `md5(content)` if not provided
48
48
  ) -> namedtuple:
49
49
  "Create a `doctype` named tuple"
50
- if source is None: source = hashlib.md5(content.encode()).hexdigest()[:8]
51
- return doctype(_add_nls(str(source).strip()), _add_nls(content.strip()))
50
+ if src is None: src = hashlib.md5(content.encode()).hexdigest()[:8]
51
+ return doctype(_add_nls(str(src).strip()), _add_nls(content.strip()))
52
52
 
53
- # %% ../00_xml.ipynb 16
53
+ # %% ../00_xml.ipynb 19
54
54
  def mk_doc(index:int, # The document index
55
55
  content:str, # The document content
56
- source:Optional[str]=None, # URL, filename, etc; defaults to `md5(content)` if not provided
56
+ src:Optional[str]=None, # URL, filename, etc; defaults to `md5(content)` if not provided
57
57
  **kwargs
58
58
  ) -> tuple:
59
59
  "Create an `ft` format tuple for a single doc in Anthropic's recommended format"
60
- dt = mk_doctype(content, source)
61
- content = Document_content(dt.content)
62
- source = Source(dt.source)
63
- return Document(source, content, index=index, **kwargs)
60
+ dt = mk_doctype(content, src)
61
+ content = Document_content(NotStr(dt.content))
62
+ src = Src(NotStr(dt.src))
63
+ return Document(src, content, index=index, **kwargs)
64
64
 
65
- # %% ../00_xml.ipynb 19
65
+ # %% ../00_xml.ipynb 22
66
66
  def docs_xml(docs:list[str], # The content of each document
67
- sources:Optional[list]=None, # URLs, filenames, etc; each one defaults to `md5(content)` if not provided
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?
69
69
  details:Optional[list]=None # Optional list of dicts with additional attrs for each doc
70
70
  )->str:
71
71
  "Create an XML string containing `docs` in Anthropic's recommended format"
72
72
  pre = 'Here are some documents for you to reference for your task:\n\n' if prefix else ''
73
- if sources is None: sources = [None]*len(docs)
73
+ if srcs is None: srcs = [None]*len(docs)
74
74
  if details is None: details = [{}]*len(docs)
75
- docs = (mk_doc(i+1, d, s, **kw) for i,(d,s,kw) in enumerate(zip(docs,sources,details)))
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 26
78
+ # %% ../00_xml.ipynb 29
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 29
87
+ # %% ../00_xml.ipynb 32
88
88
  @delegates(globtastic)
89
89
  def folder2ctx(
90
90
  folder:Union[str,Path], # Folder name containing files to add to context
@@ -94,11 +94,11 @@ def folder2ctx(
94
94
  fnames = globtastic(folder, **kwargs)
95
95
  return files2ctx(fnames, prefix=prefix)
96
96
 
97
- # %% ../00_xml.ipynb 31
97
+ # %% ../00_xml.ipynb 34
98
98
  @call_parse
99
99
  @delegates(folder2ctx)
100
100
  def folder2ctx_cli(
101
101
  folder:str, # Folder name containing files to add to context
102
102
  **kwargs # Passed to `folder2ctx`
103
103
  )->str: # XML for Claude context
104
- return folder2ctx(folder, **kwargs)
104
+ print(folder2ctx(folder, **kwargs))
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.1
2
+ Name: toolslm
3
+ Version: 0.1.1
4
+ Summary: Tools to make language models a bit easier to use
5
+ Home-page: https://github.com/AnswerDotAI/toolslm
6
+ Author: Jeremy Howard
7
+ Author-email: j@fast.ai
8
+ License: Apache Software License 2.0
9
+ Keywords: nbdev jupyter notebook python
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: fastcore>=1.5.47
20
+ Requires-Dist: beautifulsoup4
21
+ Requires-Dist: html2text
22
+ Requires-Dist: httpx
23
+ Requires-Dist: llms_txt
24
+ Provides-Extra: dev
25
+
26
+ # toolslm
27
+
28
+
29
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
30
+
31
+ This is a work in progress…
32
+
33
+ ## Install
34
+
35
+ ``` sh
36
+ pip install toolslm
37
+ ```
38
+
39
+ ## How to use
40
+
41
+ ### Context creation
42
+
43
+ toolslm has some helpers to make it easier to generate XML context from
44
+ files, for instance
45
+ [`folder2ctx`](https://AnswerDotAI.github.io/toolslm/xml.html#folder2ctx):
46
+
47
+ ``` python
48
+ print(folder2ctx('samples', prefix=False, file_glob='*.py'))
49
+ ```
50
+
51
+ <documents><document index="1"><src>
52
+ samples/sample_core.py
53
+ </src><document-content>
54
+ import inspect
55
+ empty = inspect.Parameter.empty
56
+ models = 'claude-3-opus-20240229','claude-3-sonnet-20240229','claude-3-haiku-20240307'
57
+ </document-content></document></documents>
58
+
59
+ JSON doesn’t map as nicely to XML as the `ft` data structure from
60
+ `fastcore.xml`, but for simple XML trees it can be convenient. The
61
+ [`json_to_xml`](https://AnswerDotAI.github.io/toolslm/xml.html#json_to_xml)
62
+ function handles that conversion:
63
+
64
+ ``` python
65
+ a = dict(surname='Howard', firstnames=['Jeremy','Peter'],
66
+ address=dict(state='Queensland',country='Australia'))
67
+ print(json_to_xml(a, 'person'))
68
+ ```
69
+
70
+ <person>
71
+ <surname>Howard</surname>
72
+ <firstnames>
73
+ <item>Jeremy</item>
74
+ <item>Peter</item>
75
+ </firstnames>
76
+ <address>
77
+ <state>Queensland</state>
78
+ <country>Australia</country>
79
+ </address>
80
+ </person>
@@ -0,0 +1,13 @@
1
+ toolslm/__init__.py,sha256=rnObPjuBcEStqSO0S6gsdS_ot8ITOQjVj_-P1LUUYpg,22
2
+ toolslm/_modidx.py,sha256=h7qjjV4foGgkJ-ewxIAqm117xOhVKdd977ATOyo0fSs,4250
3
+ toolslm/download.py,sha256=d84O8PCX7uAbLg0HTbNNfCdANPcnhXFu4qzOtcfJfHU,4465
4
+ toolslm/funccall.py,sha256=PBgouTFOnwd2mFQB8Z-ZpyKr5KkAxJoMI-8xnmxTDbU,7911
5
+ toolslm/md_hier.py,sha256=4uC12443tPBduYJgIZZIcEat2VG0x7JYC8-SwDdS2JY,6360
6
+ toolslm/shell.py,sha256=GVqfL74NHw66zzZ7jvGVLjE55ZNJGBPvEb8kLz4aoYc,1576
7
+ toolslm/xml.py,sha256=QNwUavoMkFK84D7dMwnBjqlYJwN-pJ7u3BxOeDuNAmk,4088
8
+ toolslm-0.1.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
9
+ toolslm-0.1.1.dist-info/METADATA,sha256=GtZY2OWdzRjOsvsiAzWrc-FatlogRTwvgSECBAG-ZZY,2205
10
+ toolslm-0.1.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
11
+ toolslm-0.1.1.dist-info/entry_points.txt,sha256=xFz0Eymlo5X7BGpaO6DI9gMxvN5A7faebzrlr8ctp5I,95
12
+ toolslm-0.1.1.dist-info/top_level.txt,sha256=4hRTrFWayz_Kz5221XjvlpCwVFrW3WPi1P0fllkTq9s,8
13
+ toolslm-0.1.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,154 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: toolslm
3
- Version: 0.0.7
4
- Summary: Tools to make language models a bit easier to use
5
- Home-page: https://github.com/AnswerDotAI/toolslm
6
- Author: Jeremy Howard
7
- Author-email: j@fast.ai
8
- License: Apache Software License 2.0
9
- Keywords: nbdev jupyter notebook python
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Natural Language :: English
13
- Classifier: Programming Language :: Python :: 3.9
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: License :: OSI Approved :: Apache Software License
16
- Requires-Python: >=3.9
17
- Description-Content-Type: text/markdown
18
- License-File: LICENSE
19
- Requires-Dist: fastcore >=1.5.47
20
- Requires-Dist: beautifulsoup4
21
- Requires-Dist: html2text
22
- Requires-Dist: httpx
23
- Requires-Dist: llms-txt
24
- Provides-Extra: dev
25
-
26
- # toolslm
27
-
28
-
29
- <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
30
-
31
- This is a work in progress…
32
-
33
- ## Install
34
-
35
- ``` sh
36
- pip install toolslm
37
- ```
38
-
39
- ## How to use
40
-
41
- ### Context creation
42
-
43
- toolslm has some helpers to make it easier to generate XML context from
44
- files, for instance `folder2ctx`:
45
-
46
- ``` python
47
- print(folder2ctx('samples', prefix=False, file_glob='*.py'))
48
- ```
49
-
50
- <documents>
51
- <document index="1">
52
- <source>
53
- samples/sample_core.py
54
- </source>
55
- <document_content>
56
- import inspect
57
- empty = inspect.Parameter.empty
58
- models = 'claude-3-opus-20240229','claude-3-sonnet-20240229','claude-3-haiku-20240307'
59
- </document_content>
60
- </document>
61
- </documents>
62
-
63
- ### XML helpers
64
-
65
- Many language models work well with XML inputs, but XML can be a bit
66
- clunky to work with manually. Therefore, toolslm includes a couple of
67
- more streamlined approaches for XML generation.
68
-
69
- An XML node contains a tag, optional children, and optional attributes.
70
- `xt` creates a tuple of these three things, which we will use to general
71
- XML shortly. Attributes are passed as kwargs; since these might conflict
72
- with reserved words in Python, you can optionally add a `_` prefix and
73
- it’ll be stripped off.
74
-
75
- ``` python
76
- xt('x-custom', ['hi'], _class='bar')
77
- ```
78
-
79
- ('x-custom', ['hi'], {'class': 'bar'})
80
-
81
- Claudette has functions defined for some common HTML elements to create
82
- `xt` tuples more easily, including these:
83
-
84
- ``` python
85
- from toolslm.xml import div,img,h1,h2,p,hr,html
86
- ```
87
-
88
- ``` python
89
- a = html([
90
- p('This is a paragraph'),
91
- hr(),
92
- img(src='http://example.prg'),
93
- div([
94
- h1('This is a header'),
95
- h2('This is a sub-header', style='k:v'),
96
- ], _class='foo')
97
- ])
98
- a
99
- ```
100
-
101
- ('html',
102
- [('p', 'This is a paragraph', {}),
103
- ('hr', None, {}),
104
- ('img', None, {'src': 'http://example.prg'}),
105
- ('div',
106
- [('h1', 'This is a header', {}),
107
- ('h2', 'This is a sub-header', {'style': 'k:v'})],
108
- {'class': 'foo'})],
109
- {})
110
-
111
- To convert a tuple data structure created with `xt` and friends into
112
- XML, use `to_xml`, adding the `hl` parameter to optionally add syntax
113
- highlighting:
114
-
115
- ``` python
116
- to_xml(a, hl=True)
117
- ```
118
-
119
- ``` xml
120
- <html>
121
- <p>This is a paragraph</p>
122
- <hr />
123
- <img src="http://example.prg" />
124
- <div class="foo">
125
- <h1>This is a header</h1>
126
- <h2 style="k:v">This is a sub-header</h2>
127
- </div>
128
- </html>
129
- ```
130
-
131
- JSON doesn’t map as nicely to XML as the `xt` data structure, but for
132
- simple XML trees it can be convenient. The `json_to_xml` function
133
- handles that conversion:
134
-
135
- ``` python
136
- a = dict(surname='Howard', firstnames=['Jeremy','Peter'],
137
- address=dict(state='Queensland',country='Australia'))
138
- print(json_to_xml(a, 'person'))
139
- ```
140
-
141
- <person>
142
- <surname>Howard</surname>
143
- <firstnames>
144
- <item>Jeremy</item>
145
- <item>Peter</item>
146
- </firstnames>
147
- <address>
148
- <state>Queensland</state>
149
- <country>Australia</country>
150
- </address>
151
- </person>
152
-
153
- See the `xml source` section for a walkthru of XML and document context
154
- generation functionality.
@@ -1,13 +0,0 @@
1
- toolslm/__init__.py,sha256=R9xOYoYrWKcfO5zvTeGC3m_eDNOvxMd8CocQs2tLufo,22
2
- toolslm/_modidx.py,sha256=EIl2FBWhcZUS46r1AU0wURYg2O6Z3aXTPUr3p8Smrqk,3882
3
- toolslm/download.py,sha256=tf0TGFzJ6qbxCjjuG9iRC2i6lutcF9GviWY0fJc_lSU,4378
4
- toolslm/funccall.py,sha256=hSvBvfMv-YcBSUUs4-NrYu1f8jg4gfu2s82cPyIHVkU,6534
5
- toolslm/md_hier.py,sha256=hkCjuOfIFWuMEiM2_XCoD9QIBjy9huLOSvpX_bMdn0Y,4645
6
- toolslm/shell.py,sha256=GVqfL74NHw66zzZ7jvGVLjE55ZNJGBPvEb8kLz4aoYc,1576
7
- toolslm/xml.py,sha256=Alcd96KfNO8LklVefyc51LbXBoVLRSgifrpMVZPqYsc,4120
8
- toolslm-0.0.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
9
- toolslm-0.0.7.dist-info/METADATA,sha256=sdRs3kCMl1xI8Z1if4xsGWuGaX9hbYGB0zs0BbRhQp0,3882
10
- toolslm-0.0.7.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
11
- toolslm-0.0.7.dist-info/entry_points.txt,sha256=xFz0Eymlo5X7BGpaO6DI9gMxvN5A7faebzrlr8ctp5I,95
12
- toolslm-0.0.7.dist-info/top_level.txt,sha256=4hRTrFWayz_Kz5221XjvlpCwVFrW3WPi1P0fllkTq9s,8
13
- toolslm-0.0.7.dist-info/RECORD,,