# LLM tools


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Error handling helpers

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L19"
target="_blank" style="float:right; font-size:smaller">source</a>

### explain_exc

``` python

def explain_exc(
    task:str=''
):

```

*Convert an current exception to an LLM friendly error message.*

``` python
def calc_div(a:int,b:int):
    "Divides a/b - example tool"
    try: return a/b
    except: return explain_exc("dividing")
calc_div(1,0)
```

    'Error: division by zero'

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L28"
target="_blank" style="float:right; font-size:smaller">source</a>

### ensure

``` python

def ensure(
    b:bool, msg:str=''
):

```

*Works like assert b, msg but raise ValueError and is not disabled when
run with python -O*

``` python
def calc_div(a:int,b:int):
    "Divides a/b - example tool"
    try:
        ensure(b != 0, "B cannot be zero") 
        return a/b
    except: return explain_exc("dividing")
calc_div(1,0)
```

    'Error: B cannot be zero'

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L43"
target="_blank" style="float:right; font-size:smaller">source</a>

### valid_path

``` python

def valid_path(
    path:str, must_exist:bool=True, chk_perms:bool=True
)->Path:

```

*Return expanded/resolved Path, raising FileNotFoundError if must_exist
and missing*

You can add valid paths here:

``` python
print(xdg_config_home() / 'fc_tools_paths')
```

    /Users/jhoward/.config/fc_tools_paths

``` python
assert valid_path('.')
assert valid_path('/tmp')

test_fail(lambda: valid_path('../../..'), exc=PermissionError)
```

``` python
try: valid_path(".missing")
except: print(explain_exc())
```

    Error: File not found: /Users/jhoward/aai-ws/fastcore/nbs/.missing

## Bash Tools

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L52"
target="_blank" style="float:right; font-size:smaller">source</a>

### run_cmd

``` python

def run_cmd(
    cmd:str, # The command name to run
    argstr:str='', # All args to the command, will be split with shlex
    disallow_re:str=None, # optional regex which, if matched on argstr, will disallow the command
    allow_re:str=None, # optional regex which, if not matched on argstr, will disallow the command
):

```

*Run `cmd` passing split `argstr`, optionally checking for allowed
argstr*

With this little function, we can now run any cli command:

``` python
print(run_cmd('ls', '~')[:128])
```

    aai-ws
    Applications
    books
    cachy.jsonl
    chats
    CRAFT.ipynb
    CRAFT.py
    CRAFTs
    

Note that, for tool safety, this is not passed through the shell, so
wildcards, env vars, etc will not work (although `~` will):

``` python
print(run_cmd('ls', 'f*')[:128])
```

    ls: f*: No such file or directory

Let’s create some useful functions from this that will allow for
searching, reading and modifing content on the file system.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L76"
target="_blank" style="float:right; font-size:smaller">source</a>

### rg

``` python

def rg(
    argstr:str, # All args to the command, will be split with shlex. No shell escaping needed for regex chars like `|`
    disallow_re:str=None, # optional regex which, if matched on argstr, will disallow the command. Use for LLM tool calling restrictions, not passed to rg; leave blank if unsure.
    allow_re:str=None, # optional regex which, if not matched on argstr, will disallow the command. Use for LLM tool calling restrictions, not passed to rg; leave blank if unsure.
):

```

*Run the [`rg`](https://fastcore.fast.ai/tools.html#rg) command with the
args in `argstr`*

``` python
rg('fast.ai CNAME')
```

    '1:fastcore.fast.ai\n'

``` python
print(rg('"^\s+.def ' + r'rg\(" 12_tools.ipynb'))
```

    347:    "def rg(\n",

Functions implemented with
[`run_cmd`](https://fastcore.fast.ai/tools.html#run_cmd) like this one
can be passed regexps to allow or disallow arg strs, i.e to block parent
or root directories:

``` python
disallowed = r' /|\.\.'
rg('info@fast.ai ..', disallow_re=disallowed)
```

    'Error: args disallowed'

``` python
rg('info@fast.ai /', disallow_re=disallowed)
```

    'Error: args disallowed'

``` python
print(rg('fast.ai CNAME', disallow_re=disallowed))
```

    1:fastcore.fast.ai

NB: These tools have special behavior around errors. Since these have
been speficially designed for work with LLMs, any exceptions created
from their use is returned as a string to help them debug their work.

``` python
run_cmd('asdfe')
```

    "Error: [Errno 2] No such file or directory: 'asdfe'"

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L86"
target="_blank" style="float:right; font-size:smaller">source</a>

### sed

``` python

def sed(
    argstr:str, # All args to the command, will be split with shlex. No shell escaping needed for regex chars like `|`
    disallow_re:str=None, # optional regex which, if matched on argstr, will disallow the command. Use for LLM tool calling restrictions, not passed to rg; leave blank if unsure.
    allow_re:str=None, # optional regex which, if not matched on argstr, will disallow the command. Use for LLM tool calling restrictions, not passed to rg; leave blank if unsure.
):

```

*Run the [`sed`](https://fastcore.fast.ai/tools.html#sed) command with
the args in `argstr` (e.g for reading a section of a file)*

``` python
print(sed('-n "1,5 p" _quarto.yml'))
```

    project:
      type: website
      pre-render: 
        - pysym2md --output_file apilist.txt fastcore
      post-render: 

``` python
# Print line numbers too
print(sed('-n "1,5 {=;p;}" _quarto.yml'))
```

    1
    project:
    2
      type: website
    3
      pre-render: 
    4
        - pysym2md --output_file apilist.txt fastcore
    5
      post-render: 

## Text Edit Tools

Python implementations of the text editor tools from
[Anthropic](https://docs.claude.com/en/docs/agents-and-tools/tool-use/text-editor-tool),
plus more. These tools are especially useful in an AI’s tool loop. See
[`claudette`](https://claudette.answer.ai/text_editor.html) for
examples.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L106"
target="_blank" style="float:right; font-size:smaller">source</a>

### view

``` python

def view(
    path:str, # Path to directory or file to view
    view_range:tuple=None, # Optional 1-indexed (start, end) line range for files, end=-1 for EOF. Do NOT use unless it's known that the file is too big to keep in context—simply view the WHOLE file when possible
    nums:bool=False, # Whether to show line numbers
    skip_folders:tuple=('_proc', '__pycache__'), # Folder names to skip when listing directories
):

```

*View directory or file contents with optional line range and numbers*

You can specify line ranges and whether to have the output contain line
numbers:

``` python
print(view('_quarto.yml', (1,10), nums=True))
```

         1 │ project:
         2 │   type: website
         3 │   pre-render: 
         4 │     - pysym2md --output_file apilist.txt fastcore
         5 │   post-render: 
         6 │     - llms_txt2ctx llms.txt --optional true --save_nbdev_fname llms-ctx-full.txt
         7 │     - llms_txt2ctx llms.txt --save_nbdev_fname llms-ctx.txt
         8 │   resources: 
         9 │     - "*.txt"
        10 │   preview:

Here’s what the output looks like when viewing a directory:

``` python
print(view('.', (1,5)).replace(os.getcwd(), '/path'))
```

    Directory contents of /path:
    /path/llms.txt (3.7k)
    /path/000_tour.ipynb (18.2k)
    /path/parallel_test.py (0.6k)
    /path/_quarto.yml (0.8k)
    /path/08_style.ipynb (12.3k)

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L133"
target="_blank" style="float:right; font-size:smaller">source</a>

### create

``` python

def create(
    path:str, # Path where the new file should be created
    file_text:str, # Content to write to the file
    overwrite:bool=False, # Whether to overwrite existing files
)->str:

```

*Creates a new file with the given content at the specified path*

``` python
print(create('test.txt', 'Hello, world!').replace(os.getcwd(), '/path'))
f = Path('test.txt')
test_eq(f.exists(), True)
print('Contents:\n', view(f, nums=True))
```

    Created file /path/test.txt.
    Contents:
          1 │ Hello, world!

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L150"
target="_blank" style="float:right; font-size:smaller">source</a>

### insert

``` python

def insert(
    path:str, # Path to the file to modify
    insert_line:int, # Line number where to insert (0-based indexing)
    new_str:str, # Text to insert at the specified line
)->str:

```

*Insert new_str at specified line number*

``` python
insert(f, 0, 'Let\'s add a new line')
print(view(f, nums=True))
```

         1 │ Let's add a new line
         2 │ Hello, world!

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L168"
target="_blank" style="float:right; font-size:smaller">source</a>

### str_replace

``` python

def str_replace(
    path:str, # Path to the file to modify
    old_str:str, # Text to find and replace
    new_str:str, # Text to replace with
)->str:

```

*Replace first occurrence of old_str with new_str in file*

``` python
print(str_replace(f, 'new line','new line:'))
print(view(f, nums=True))
```

    Replaced text in /Users/jhoward/aai-ws/fastcore/nbs/test.txt
         1 │ Let's add a new line:
         2 │ Hello, world!

``` python
str_replace(f, 'missing line', '')
```

    'Error: Text "missing l…" not found in file'

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L187"
target="_blank" style="float:right; font-size:smaller">source</a>

### strs_replace

``` python

def strs_replace(
    path:str, # Path to the file to modify
    old_strs:list, # List of strings to find and replace
    new_strs:list, # List of replacement strings (must match length of old_strs)
):

```

*Replace for each str pair in old_strs,new_strs*

``` python
print(strs_replace(f, ["add a new line", "world!"], ["just say", "friends!\nNice to see you."]))
print(view(f, nums=True))
```

    Results for each replacement:
    Replaced text in /Users/jhoward/aai-ws/fastcore/nbs/test.txt; Replaced text in /Users/jhoward/aai-ws/fastcore/nbs/test.txt
         1 │ Let's just say:
         2 │ Hello, friends!
         3 │ Nice to see you.

``` python
print(strs_replace(f, ["a missing", "and shouldn't polute the context"], ["", ""]))
print(view(f, nums=True))
```

    Results for each replacement:
    Error: Text "a missing" not found in file; Error: Text "and shoul…" not found in file
         1 │ Let's just say:
         2 │ Hello, friends!
         3 │ Nice to see you.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L198"
target="_blank" style="float:right; font-size:smaller">source</a>

### replace_lines

``` python

def replace_lines(
    path:str, # Path to the file to modify
    start_line:int, # Starting line number to replace (1-based indexing)
    end_line:int, # Ending line number to replace (1-based indexing, inclusive)
    new_content:str, # New content to replace the specified lines
):

```

*Replace lines in file using start and end line-numbers (index starting
at 1)*

``` python
replace_lines('test.txt', 1, 2, 'Replaced first two lines')
print(view('test.txt', nums=True))
```

         1 │ Replaced first two lines
         2 │ Nice to see you.

``` python
replace_lines('missing.txt', 1, 2, 'Replaced first two lines').replace(os.getcwd(), '/path')
```

    'Error: File not found: /path/missing.txt'

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L216"
target="_blank" style="float:right; font-size:smaller">source</a>

### move_lines

``` python

def move_lines(
    path:str, # Path to the file to modify
    start_line:int, # Starting line number to move (1-based)
    end_line:int, # Ending line number to move (1-based, inclusive)
    dest_line:int, # Destination line number (1-based, where lines will be inserted before)
)->str:

```

*Move lines from start_line:end_line to before dest_line*

The [`move_lines`](https://fastcore.fast.ai/tools.html#move_lines)
function relocates a range of lines within a file to a new position. It
handles the tricky index adjustment when the destination is after the
removed chunk.

Let’s test it by creating a simple 5-line file:

``` python
create('move_test.txt', 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5', overwrite=True)
print(view('move_test.txt', nums=True))
```

         1 │ Line 1
         2 │ Line 2
         3 │ Line 3
         4 │ Line 4
         5 │ Line 5

Move lines 4-5 up to before line 2:

``` python
print(move_lines('move_test.txt', 4, 5, 2))
print(view('move_test.txt', nums=True))
```

    Moved lines 4-5 to line 2
         1 │ Line 1
         2 │ Line 4
         3 │ Line 5
         4 │ Line 2
         5 │ Line 3

Move lines down — moving lines 1-2 to the end (line 6) correctly adjusts
the destination index after removal:

``` python
print(move_lines('move_test.txt', 1, 2, 6))
print(view('move_test.txt', nums=True))
```

    Moved lines 1-2 to line 4
         1 │ Line 5
         2 │ Line 2
         3 │ Line 3
         4 │ Line 1
         5 │ Line 4

Error handling — destination within source range, invalid line ranges,
and invalid destinations are all caught:

``` python
print(move_lines('move_test.txt', 2, 3, 3))  # dest within source range
print(move_lines('move_test.txt', 10, 12, 1))  # invalid range
print(move_lines('move_test.txt', 1, 2, 99))  # invalid destination
print(move_lines('mising.txt', 1, 2, 99).replace(os.getcwd(), '/path'))  # missing file
```

    Error: Destination within source range
    Error: Invalid range 10-12
    Error: Invalid destination 99
    Error: File not found: /path/mising.txt

``` python
Path('move_test.txt').unlink()
```

``` python
f.unlink()
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/tools.py#L240"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_callable

``` python

def get_callable(
    
):

```

*Return callable objects defined in caller’s module*
