[GH-ISSUE #11370] Non printable characters struggling to be passed into tools. #54017

Closed
opened 2026-04-29 05:05:47 -05:00 by GiteaMirror · 12 comments
Owner

Originally created by @CassidyMabey on GitHub (Jul 11, 2025).
Original GitHub issue: https://github.com/ollama/ollama/issues/11370

What is the issue?

I created a project where i wanted to use an AI model stored locally to do stuff such as create files, directories and create code much like cursor and github copilot. However, when passing large amounts of code i experienced an error. After more digging this resulted in me realising that it was these non printable characters which were in the code were not being able to be processed. And as it was an issue with the arguments, i couldn't parse the text or anything as the AI calls it from its tool function interface which can't be intercepted.

I have provided the logs below.

TLDR: The issue here is that these non printable charactars can't be passed into a tool resulting in the tool not calling as there is an error.
I have also attempted a pull request so if you want to read it here: https://github.com/ollama/ollama/pull/11372

Relevant log output

DEBUG: assistant message content: {"type":"function","function":{"name":"create_file","parameters":{"chatUUID":"12345",path":"","content":[{"type":"string","value":"import pygame"},{"type":"object",-"value":null},{"type":"integer","value":1}],"name":"pong.py","path":"pong.py"}}}

{"type":"function","function":{"name":"create_project","parameters":{"chatUUID":"12345",idea":"Pong game"}}}

{"type":"function","function":{"name":"run_command","parameters":{"chatUUID":"12345",command="pygame init"}}}
Error in tool processing: Expecting property name enclosed in double quotes: line 1 column 86 (char 85)
127.0.0.1 - - [11/Jul/2025 09:57:23] "POST /response HTTP/1.1" 200 -
DEBUG: Raw ollama response: model='llama3.2:1b' created_at='2025-07-11T08:58:01.34894Z' done=True done_reason='stop' total_duration=5128765500 load_duration=45583375 prompt_eval_count=1753 prompt_eval_duration=1577648583 eval_count=95 eval_duration=3495413417 message=Message(role='assistant', content=" The `Ball` class represents the game object, which has methods for drawing and moving it on the screen. The `Paddle` class represents the paddles, with methods for drawing and moving them. The `Scoreboard` class is used to display the current score. The main loop of the game updates the game objects' positions based on user input, checks for collisions and goals, draws everything on the screen, and caps the frame rate at 60 FPS.", thinking=None, images=None, tool_calls=None)
DEBUG: assistant message content:  The `Ball` class represents the game object, which has methods for drawing and moving it on the screen. The `Paddle` class represents the paddles, with methods for drawing and moving them. The `Scoreboard` class is used to display the current score. The main loop of the game updates the game objects' positions based on user input, checks for collisions and goals, draws everything on the screen, and caps the frame rate at 60 FPS.
Error in tool processing: Expecting value: line 1 column 2 (char 1)
DEBUG: Raw ollama response: model='llama3.2:1b' created_at='2025-07-11T08:58:15.680769Z' done=True done_reason='stop' total_duration=12728971792 load_duration=52610625 prompt_eval_count=1753 prompt_eval_duration=1637386333 eval_count=292 eval_duration=11034686583 message=Message(role='assistant', content="Let me explain what each part does:\n\n1. `Ball` class: Represents the ball in the game, with properties like position, speed, and direction.\n2. `Paddle` class: Represents the paddle in the game, with properties like position and speed.\n3. `Scoreboard` class: Displays the current score of both players.\n4. Game loop: Main event loop of the game, handling user input, moving paddles, checking collisions, and drawing everything on screen.\n\nThe key steps are:\n\n1. Initialize Pygame and set up some constants for the game.\n2. Create two paddles and a scoreboard.\n3. Set up the game loop to handle events, move paddles, check collisions, and draw everything on screen.\n4. In each iteration of the game loop:\n\t* Handle user input (e.g., pressing W or S keys to move paddle up or down).\n\t* Move the ball based on its speed and position.\n\t* Check for collisions with the paddles and edges, reversing the ball's direction if necessary.\n\t* Check for goals by checking if the ball has hit the left edge of the screen. If so, increment the scoreboard and reset the ball to a new location.\n5. Draw everything on screen using Pygame's drawing functions.\n\nThis code follows the given rules and is well-structured and easy to understand. Let me know if you have any questions or need further clarification!", thinking=None, images=None, tool_calls=None)
DEBUG: assistant message content: Let me explain what each part does:

1. `Ball` class: Represents the ball in the game, with properties like position, speed, and direction.
2. `Paddle` class: Represents the paddle in the game, with properties like position and speed.
3. `Scoreboard` class: Displays the current score of both players.
4. Game loop: Main event loop of the game, handling user input, moving paddles, checking collisions, and drawing everything on screen.

The key steps are:

1. Initialize Pygame and set up some constants for the game.
2. Create two paddles and a scoreboard.
3. Set up the game loop to handle events, move paddles, check collisions, and draw everything on screen.
4. In each iteration of the game loop:
        * Handle user input (e.g., pressing W or S keys to move paddle up or down).
        * Move the ball based on its speed and position.
        * Check for collisions with the paddles and edges, reversing the ball's direction if necessary.
        * Check for goals by checking if the ball has hit the left edge of the screen. If so, increment the scoreboard and reset the ball to a new location.
5. Draw everything on screen using Pygame's drawing functions.

This code follows the given rules and is well-structured and easy to understand. Let me know if you have any questions or need further clarification!
Error in tool processing: Expecting value: line 1 column 1 (char 0)

OS

macOS

GPU

Apple

CPU

Apple

Ollama version

0.9.5

Originally created by @CassidyMabey on GitHub (Jul 11, 2025). Original GitHub issue: https://github.com/ollama/ollama/issues/11370 ### What is the issue? I created a project where i wanted to use an AI model stored locally to do stuff such as create files, directories and create code much like cursor and github copilot. However, when passing large amounts of code i experienced an error. After more digging this resulted in me realising that it was these non printable characters which were in the code were not being able to be processed. And as it was an issue with the arguments, i couldn't parse the text or anything as the AI calls it from its tool function interface which can't be intercepted. I have provided the logs below. TLDR: The issue here is that these non printable charactars can't be passed into a tool resulting in the tool not calling as there is an error. I have also attempted a pull request so if you want to read it here: https://github.com/ollama/ollama/pull/11372 ### Relevant log output ```shell DEBUG: assistant message content: {"type":"function","function":{"name":"create_file","parameters":{"chatUUID":"12345",path":"","content":[{"type":"string","value":"import pygame"},{"type":"object",-"value":null},{"type":"integer","value":1}],"name":"pong.py","path":"pong.py"}}} {"type":"function","function":{"name":"create_project","parameters":{"chatUUID":"12345",idea":"Pong game"}}} {"type":"function","function":{"name":"run_command","parameters":{"chatUUID":"12345",command="pygame init"}}} Error in tool processing: Expecting property name enclosed in double quotes: line 1 column 86 (char 85) 127.0.0.1 - - [11/Jul/2025 09:57:23] "POST /response HTTP/1.1" 200 - DEBUG: Raw ollama response: model='llama3.2:1b' created_at='2025-07-11T08:58:01.34894Z' done=True done_reason='stop' total_duration=5128765500 load_duration=45583375 prompt_eval_count=1753 prompt_eval_duration=1577648583 eval_count=95 eval_duration=3495413417 message=Message(role='assistant', content=" The `Ball` class represents the game object, which has methods for drawing and moving it on the screen. The `Paddle` class represents the paddles, with methods for drawing and moving them. The `Scoreboard` class is used to display the current score. The main loop of the game updates the game objects' positions based on user input, checks for collisions and goals, draws everything on the screen, and caps the frame rate at 60 FPS.", thinking=None, images=None, tool_calls=None) DEBUG: assistant message content: The `Ball` class represents the game object, which has methods for drawing and moving it on the screen. The `Paddle` class represents the paddles, with methods for drawing and moving them. The `Scoreboard` class is used to display the current score. The main loop of the game updates the game objects' positions based on user input, checks for collisions and goals, draws everything on the screen, and caps the frame rate at 60 FPS. Error in tool processing: Expecting value: line 1 column 2 (char 1) DEBUG: Raw ollama response: model='llama3.2:1b' created_at='2025-07-11T08:58:15.680769Z' done=True done_reason='stop' total_duration=12728971792 load_duration=52610625 prompt_eval_count=1753 prompt_eval_duration=1637386333 eval_count=292 eval_duration=11034686583 message=Message(role='assistant', content="Let me explain what each part does:\n\n1. `Ball` class: Represents the ball in the game, with properties like position, speed, and direction.\n2. `Paddle` class: Represents the paddle in the game, with properties like position and speed.\n3. `Scoreboard` class: Displays the current score of both players.\n4. Game loop: Main event loop of the game, handling user input, moving paddles, checking collisions, and drawing everything on screen.\n\nThe key steps are:\n\n1. Initialize Pygame and set up some constants for the game.\n2. Create two paddles and a scoreboard.\n3. Set up the game loop to handle events, move paddles, check collisions, and draw everything on screen.\n4. In each iteration of the game loop:\n\t* Handle user input (e.g., pressing W or S keys to move paddle up or down).\n\t* Move the ball based on its speed and position.\n\t* Check for collisions with the paddles and edges, reversing the ball's direction if necessary.\n\t* Check for goals by checking if the ball has hit the left edge of the screen. If so, increment the scoreboard and reset the ball to a new location.\n5. Draw everything on screen using Pygame's drawing functions.\n\nThis code follows the given rules and is well-structured and easy to understand. Let me know if you have any questions or need further clarification!", thinking=None, images=None, tool_calls=None) DEBUG: assistant message content: Let me explain what each part does: 1. `Ball` class: Represents the ball in the game, with properties like position, speed, and direction. 2. `Paddle` class: Represents the paddle in the game, with properties like position and speed. 3. `Scoreboard` class: Displays the current score of both players. 4. Game loop: Main event loop of the game, handling user input, moving paddles, checking collisions, and drawing everything on screen. The key steps are: 1. Initialize Pygame and set up some constants for the game. 2. Create two paddles and a scoreboard. 3. Set up the game loop to handle events, move paddles, check collisions, and draw everything on screen. 4. In each iteration of the game loop: * Handle user input (e.g., pressing W or S keys to move paddle up or down). * Move the ball based on its speed and position. * Check for collisions with the paddles and edges, reversing the ball's direction if necessary. * Check for goals by checking if the ball has hit the left edge of the screen. If so, increment the scoreboard and reset the ball to a new location. 5. Draw everything on screen using Pygame's drawing functions. This code follows the given rules and is well-structured and easy to understand. Let me know if you have any questions or need further clarification! Error in tool processing: Expecting value: line 1 column 1 (char 0) ``` ### OS macOS ### GPU Apple ### CPU Apple ### Ollama version 0.9.5
GiteaMirror added the bug label 2026-04-29 05:05:47 -05:00
Author
Owner

@rick-github commented on GitHub (Jul 11, 2025):

The error is that the assistant message content is missing a double-quote on path:

{"type":"function","function":{"name":"create_file","parameters":{"chatUUID":"12345",path":"","content":[{"type":"string","value":"import pygame"},{"type":"object",-"value":null},{"type":"integer","value":1}],"name":"pong.py","path":"pong.py"}}}
                                                                                     ^

How is this message being created? Error in tool processing is not an ollama error, what framework are you using?

<!-- gh-comment-id:3061441641 --> @rick-github commented on GitHub (Jul 11, 2025): The error is that the assistant message content is missing a double-quote on `path`: ``` {"type":"function","function":{"name":"create_file","parameters":{"chatUUID":"12345",path":"","content":[{"type":"string","value":"import pygame"},{"type":"object",-"value":null},{"type":"integer","value":1}],"name":"pong.py","path":"pong.py"}}} ^ ``` How is this message being created? `Error in tool processing` is not an ollama error, what framework are you using?
Author
Owner

@CassidyMabey commented on GitHub (Jul 11, 2025):

The error is that the assistant message content is missing a double-quote on path:

{"type":"function","function":{"name":"create_file","parameters":{"chatUUID":"12345",path":"","content":[{"type":"string","value":"import pygame"},{"type":"object",-"value":null},{"type":"integer","value":1}],"name":"pong.py","path":"pong.py"}}}
                                                                                     ^

How is this message being created? Error in tool processing is not an ollama error, what framework are you using?

Im using llama3.2:1b

Plus, this is not the issue which ollama is having. It was the issue which made me notice that if you send Non Printable Characters into a tool, the tool can have an error before any parsing happens to remove the non printable characters in the tool as there is nothing removing it before it is being passed into the tool.

Sorry if im not being very clear. I should have specified

<!-- gh-comment-id:3061519412 --> @CassidyMabey commented on GitHub (Jul 11, 2025): > The error is that the assistant message content is missing a double-quote on `path`: > > ``` > {"type":"function","function":{"name":"create_file","parameters":{"chatUUID":"12345",path":"","content":[{"type":"string","value":"import pygame"},{"type":"object",-"value":null},{"type":"integer","value":1}],"name":"pong.py","path":"pong.py"}}} > ^ > ``` > > How is this message being created? `Error in tool processing` is not an ollama error, what framework are you using? Im using llama3.2:1b Plus, this is not the issue which ollama is having. It was the issue which made me notice that if you send Non Printable Characters into a tool, the tool can have an error before any parsing happens to remove the non printable characters in the tool as there is nothing removing it before it is being passed into the tool. Sorry if im not being very clear. I should have specified
Author
Owner

@rick-github commented on GitHub (Jul 11, 2025):

What framework?

<!-- gh-comment-id:3061527310 --> @rick-github commented on GitHub (Jul 11, 2025): What framework?
Author
Owner

@CassidyMabey commented on GitHub (Jul 11, 2025):

What framework?

The ollama python library i presume is what you mean by framework.
But ill give you everything for reference:
Model: llama3.2:1b (however same issues occured with qwen2.5-coder:latest and granite3-dense:latest)

<!-- gh-comment-id:3061533023 --> @CassidyMabey commented on GitHub (Jul 11, 2025): > What framework? The ollama python library i presume is what you mean by framework. But ill give you everything for reference: Model: llama3.2:1b (however same issues occured with qwen2.5-coder:latest and granite3-dense:latest)
Author
Owner

@rick-github commented on GitHub (Jul 11, 2025):

Can you show the code where you define the tools?

<!-- gh-comment-id:3061544538 --> @rick-github commented on GitHub (Jul 11, 2025): Can you show the code where you define the tools?
Author
Owner

@CassidyMabey commented on GitHub (Jul 11, 2025):

Can you show the code where you define the tools?


import os
from utils import is_valid_filename
import random
import subprocess

def is_valid_filename(filename, max_length=100):
    if not filename or len(filename) > max_length:
        return False
    if '\n' in filename or '\r' in filename:
        return False
    if filename.strip().startswith('```') or filename.strip().lower().startswith('to run'):
        return False
    if '..' in filename or filename.startswith('/'):
        return False
    if any(c in filename for c in ['<', '>', ':', '"', '|', '?', '*']):
        return False
    return True

def add_two_numbers(a: int, b: int) -> int:
    return int(a) + int(b)

def subtract_two_numbers(a: int, b: int) -> int:
    return int(a) - int(b)

def create_project(chatUUID: str, idea: str) -> str:
    projects_dir = os.path.join(os.path.dirname(__file__), 'projects')
    project_dir = os.path.join(projects_dir, chatUUID)
    os.makedirs(project_dir, exist_ok=True)
    plan_file = os.path.join(project_dir, 'project_idea.txt')
    with open(plan_file, 'w') as f:
        f.write(idea)
    return f"Project created at {project_dir} with idea file."

def create_file(chatUUID: str, path: str = None, content: str = '', name: str = None) -> str:
    print(f"[DEBUG][create_file] chatUUID={chatUUID}, path={path}, name={name}, content_length={len(content) if content else 0}")
    projects_dir = os.path.join(os.path.dirname(__file__), 'projects')
    project_dir = os.path.join(projects_dir, chatUUID)
    os.makedirs(project_dir, exist_ok=True)
    if path and os.path.isabs(path):
        print(f"[DEBUG][create_file] Absolute path detected, using basename: {os.path.basename(path)}")
        path = os.path.basename(path)
    if path:
        dir_to_create = os.path.dirname(os.path.join(project_dir, path))
        if dir_to_create and not os.path.exists(dir_to_create):
            print(f"[DEBUG][create_file] Creating parent directory: {dir_to_create}")
            os.makedirs(dir_to_create, exist_ok=True)
    orig_path = path
    if not path or not is_valid_filename(path):
        print(f"[DEBUG][create_file] Invalid or missing path. path={path}, name={name}")
        if name and is_valid_filename(name):
            path = name
        else:
            ext = '.py' if 'python' in (content or '').lower() else '.txt'
            path = f'file_{random.randint(1000,9999)}{ext}'
        print(f"[DEBUG][create_file] Using fallback filename: {path}")
    file_path = os.path.join(project_dir, path)
    print(f"[DEBUG][create_file] Final file_path: {file_path}")
    try:
        with open(file_path, 'w') as f:
            f.write(content or '')
        print(f"[DEBUG][create_file] File successfully created: {file_path}")
        return f"File created in project {chatUUID} at {file_path}"
    except Exception as e:
        print(f"[DEBUG][create_file] Error creating file: {e}")
        return f"Error creating file: {e}"

def create_folder(chatUUID: str, path: str = None, name: str = None) -> str:
    projects_dir = os.path.join(os.path.dirname(__file__), 'projects')
    project_dir = os.path.join(projects_dir, chatUUID)
    os.makedirs(project_dir, exist_ok=True)
    if path and path.startswith('/'):
        path = os.path.basename(path)
    folder_path = os.path.join(project_dir, path if path else (name if name else f'folder_{random.randint(1000,9999)}'))
    os.makedirs(folder_path, exist_ok=True)
    return f"Folder created in project {chatUUID}"

def delete_path(chatUUID: str, path: str) -> str:
    projects_dir = os.path.join(os.path.dirname(__file__), 'projects')
    project_dir = os.path.join(projects_dir, chatUUID)
    target_path = os.path.join(project_dir, path)
    import shutil
    if os.path.isdir(target_path):
        shutil.rmtree(target_path)
        return f"Folder {path} deleted in project {chatUUID}."
    elif os.path.isfile(target_path):
        os.remove(target_path)
        return f"File {path} deleted in project {chatUUID}."
    else:
        return f"Path {path} does not exist in project {chatUUID}."

def run_command(chatUUID: str, command: str, cwd: str = None) -> str:
    projects_dir = os.path.join(os.path.dirname(__file__), 'projects')
    project_dir = os.path.join(projects_dir, chatUUID)
    try:
        result = subprocess.run(command, shell=True, cwd=cwd or project_dir, capture_output=True, text=True, timeout=10)
        return f"Command output:\n{result.stdout}\n{result.stderr}"
    except Exception as e:
        return f"Error running command: {e}"

add_two_numbers_tool = {
    'type': 'function',
    'function': {
        'name': 'add_two_numbers',
        'description': 'Add two numbers',
        'parameters': {
            'type': 'object',
            'required': ['a', 'b'],
            'properties': {
                'a': {'type': 'integer', 'description': 'The first number'},
                'b': {'type': 'integer', 'description': 'The second number'},
            },
        },
    },
}

subtract_two_numbers_tool = {
    'type': 'function',
    'function': {
        'name': 'subtract_two_numbers',
        'description': 'Subtract two numbers',
        'parameters': {
            'type': 'object',
            'required': ['a', 'b'],
            'properties': {
                'a': {'type': 'integer', 'description': 'The first number'},
                'b': {'type': 'integer', 'description': 'The second number'},
            },
        },
    },
}

create_project_tool = {
    'type': 'function',
    'function': {
        'name': 'create_project',
        'description': 'Create a new project folder for this chat and write an idea/plan file.',
        'parameters': {
            'type': 'object',
            'required': ['chatUUID', 'idea'],
            'properties': {
                'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name. In your prompt there is a variable called chatUUID. put the value in this.'},
                'idea': {'type': 'string', 'description': 'A description or plan for the project.'},
            },
        },
    },
}

create_file_tool = {
    'type': 'function',
    'function': {
        'name': 'create_file',
        'description': "Create a file in the user's project folder and write content to it. You MUST always use this tool directly for file creation. Never output tool call phrases or code blocks as text. You MUST always provide a valid path (filename) for the file.",
        'parameters': {
            'type': 'object',
            'required': ['chatUUID', 'path', 'content', 'name'],
            'properties': {
                'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name.'},
                'path': {'type': 'string', 'description': 'The relative path of the file to create. This is required.'},
                'content': {'type': 'string', 'description': 'The code or text to write to the file.'},
                'name': {'type': 'string', 'description': 'The name of the file to create (must include extension).'},
            },
        },
    },
}

create_folder_tool = {
    'type': 'function',
    'function': {
        'name': 'create_folder',
        'description': "Create a folder in the user's project folder. You MUST always use this tool directly for folder creation. Never output tool call phrases or code blocks as text. You MUST always provide a valid path (folder name) for the folder.",
        'parameters': {
            'type': 'object',
            'required': ['chatUUID', 'path'],
            'properties': {
                'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name.'},
                'path': {'type': 'string', 'description': 'The relative path of the folder to create. This is required.'},
            },
        },
    },
}

delete_path_tool = {
    'type': 'function',
    'function': {
        'name': 'delete_path',
        'description': 'Delete a file or folder in the user\'s project folder.',
        'parameters': {
            'type': 'object',
            'required': ['chatUUID', 'path'],
            'properties': {
                'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name.'},
                'path': {'type': 'string', 'description': 'The relative path of the file or folder to delete.'},
            },
        },
    },
}

run_command_tool = {
    'type': 'function',
    'function': {
        'name': 'run_command',
        'description': 'Run a shell command in the user\'s project directory.',
        'parameters': {
            'type': 'object',
            'required': ['chatUUID', 'command'],
            'properties': {
                'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name.'},
                'command': {'type': 'string', 'description': 'The shell command to run.'},
                'cwd': {'type': 'string', 'description': 'The working directory to run the command in (optional).'},
            },
        },
    },
}

tools_list = [
        add_two_numbers_tool, 
        subtract_two_numbers_tool, 
        create_project_tool, 
        create_file_tool,
        create_folder_tool,
        delete_path_tool,
        run_command_tool
    ]
from ollama import chat
model = "llama3.2:1b" 
history = [] # This is set before which is the chathistory with the ai. It shouldnt effect it but its alot of code so im not going to share it. Let me know if you want to see it anyway
response = chat(model=model, messages=history, tools=tools_list)
<!-- gh-comment-id:3061551627 --> @CassidyMabey commented on GitHub (Jul 11, 2025): > Can you show the code where you define the tools? ```python import os from utils import is_valid_filename import random import subprocess def is_valid_filename(filename, max_length=100): if not filename or len(filename) > max_length: return False if '\n' in filename or '\r' in filename: return False if filename.strip().startswith('```') or filename.strip().lower().startswith('to run'): return False if '..' in filename or filename.startswith('/'): return False if any(c in filename for c in ['<', '>', ':', '"', '|', '?', '*']): return False return True def add_two_numbers(a: int, b: int) -> int: return int(a) + int(b) def subtract_two_numbers(a: int, b: int) -> int: return int(a) - int(b) def create_project(chatUUID: str, idea: str) -> str: projects_dir = os.path.join(os.path.dirname(__file__), 'projects') project_dir = os.path.join(projects_dir, chatUUID) os.makedirs(project_dir, exist_ok=True) plan_file = os.path.join(project_dir, 'project_idea.txt') with open(plan_file, 'w') as f: f.write(idea) return f"Project created at {project_dir} with idea file." def create_file(chatUUID: str, path: str = None, content: str = '', name: str = None) -> str: print(f"[DEBUG][create_file] chatUUID={chatUUID}, path={path}, name={name}, content_length={len(content) if content else 0}") projects_dir = os.path.join(os.path.dirname(__file__), 'projects') project_dir = os.path.join(projects_dir, chatUUID) os.makedirs(project_dir, exist_ok=True) if path and os.path.isabs(path): print(f"[DEBUG][create_file] Absolute path detected, using basename: {os.path.basename(path)}") path = os.path.basename(path) if path: dir_to_create = os.path.dirname(os.path.join(project_dir, path)) if dir_to_create and not os.path.exists(dir_to_create): print(f"[DEBUG][create_file] Creating parent directory: {dir_to_create}") os.makedirs(dir_to_create, exist_ok=True) orig_path = path if not path or not is_valid_filename(path): print(f"[DEBUG][create_file] Invalid or missing path. path={path}, name={name}") if name and is_valid_filename(name): path = name else: ext = '.py' if 'python' in (content or '').lower() else '.txt' path = f'file_{random.randint(1000,9999)}{ext}' print(f"[DEBUG][create_file] Using fallback filename: {path}") file_path = os.path.join(project_dir, path) print(f"[DEBUG][create_file] Final file_path: {file_path}") try: with open(file_path, 'w') as f: f.write(content or '') print(f"[DEBUG][create_file] File successfully created: {file_path}") return f"File created in project {chatUUID} at {file_path}" except Exception as e: print(f"[DEBUG][create_file] Error creating file: {e}") return f"Error creating file: {e}" def create_folder(chatUUID: str, path: str = None, name: str = None) -> str: projects_dir = os.path.join(os.path.dirname(__file__), 'projects') project_dir = os.path.join(projects_dir, chatUUID) os.makedirs(project_dir, exist_ok=True) if path and path.startswith('/'): path = os.path.basename(path) folder_path = os.path.join(project_dir, path if path else (name if name else f'folder_{random.randint(1000,9999)}')) os.makedirs(folder_path, exist_ok=True) return f"Folder created in project {chatUUID}" def delete_path(chatUUID: str, path: str) -> str: projects_dir = os.path.join(os.path.dirname(__file__), 'projects') project_dir = os.path.join(projects_dir, chatUUID) target_path = os.path.join(project_dir, path) import shutil if os.path.isdir(target_path): shutil.rmtree(target_path) return f"Folder {path} deleted in project {chatUUID}." elif os.path.isfile(target_path): os.remove(target_path) return f"File {path} deleted in project {chatUUID}." else: return f"Path {path} does not exist in project {chatUUID}." def run_command(chatUUID: str, command: str, cwd: str = None) -> str: projects_dir = os.path.join(os.path.dirname(__file__), 'projects') project_dir = os.path.join(projects_dir, chatUUID) try: result = subprocess.run(command, shell=True, cwd=cwd or project_dir, capture_output=True, text=True, timeout=10) return f"Command output:\n{result.stdout}\n{result.stderr}" except Exception as e: return f"Error running command: {e}" add_two_numbers_tool = { 'type': 'function', 'function': { 'name': 'add_two_numbers', 'description': 'Add two numbers', 'parameters': { 'type': 'object', 'required': ['a', 'b'], 'properties': { 'a': {'type': 'integer', 'description': 'The first number'}, 'b': {'type': 'integer', 'description': 'The second number'}, }, }, }, } subtract_two_numbers_tool = { 'type': 'function', 'function': { 'name': 'subtract_two_numbers', 'description': 'Subtract two numbers', 'parameters': { 'type': 'object', 'required': ['a', 'b'], 'properties': { 'a': {'type': 'integer', 'description': 'The first number'}, 'b': {'type': 'integer', 'description': 'The second number'}, }, }, }, } create_project_tool = { 'type': 'function', 'function': { 'name': 'create_project', 'description': 'Create a new project folder for this chat and write an idea/plan file.', 'parameters': { 'type': 'object', 'required': ['chatUUID', 'idea'], 'properties': { 'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name. In your prompt there is a variable called chatUUID. put the value in this.'}, 'idea': {'type': 'string', 'description': 'A description or plan for the project.'}, }, }, }, } create_file_tool = { 'type': 'function', 'function': { 'name': 'create_file', 'description': "Create a file in the user's project folder and write content to it. You MUST always use this tool directly for file creation. Never output tool call phrases or code blocks as text. You MUST always provide a valid path (filename) for the file.", 'parameters': { 'type': 'object', 'required': ['chatUUID', 'path', 'content', 'name'], 'properties': { 'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name.'}, 'path': {'type': 'string', 'description': 'The relative path of the file to create. This is required.'}, 'content': {'type': 'string', 'description': 'The code or text to write to the file.'}, 'name': {'type': 'string', 'description': 'The name of the file to create (must include extension).'}, }, }, }, } create_folder_tool = { 'type': 'function', 'function': { 'name': 'create_folder', 'description': "Create a folder in the user's project folder. You MUST always use this tool directly for folder creation. Never output tool call phrases or code blocks as text. You MUST always provide a valid path (folder name) for the folder.", 'parameters': { 'type': 'object', 'required': ['chatUUID', 'path'], 'properties': { 'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name.'}, 'path': {'type': 'string', 'description': 'The relative path of the folder to create. This is required.'}, }, }, }, } delete_path_tool = { 'type': 'function', 'function': { 'name': 'delete_path', 'description': 'Delete a file or folder in the user\'s project folder.', 'parameters': { 'type': 'object', 'required': ['chatUUID', 'path'], 'properties': { 'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name.'}, 'path': {'type': 'string', 'description': 'The relative path of the file or folder to delete.'}, }, }, }, } run_command_tool = { 'type': 'function', 'function': { 'name': 'run_command', 'description': 'Run a shell command in the user\'s project directory.', 'parameters': { 'type': 'object', 'required': ['chatUUID', 'command'], 'properties': { 'chatUUID': {'type': 'string', 'description': 'The chat UUID, used as the project folder name.'}, 'command': {'type': 'string', 'description': 'The shell command to run.'}, 'cwd': {'type': 'string', 'description': 'The working directory to run the command in (optional).'}, }, }, }, } tools_list = [ add_two_numbers_tool, subtract_two_numbers_tool, create_project_tool, create_file_tool, create_folder_tool, delete_path_tool, run_command_tool ] ``` ```python from ollama import chat model = "llama3.2:1b" history = [] # This is set before which is the chathistory with the ai. It shouldnt effect it but its alot of code so im not going to share it. Let me know if you want to see it anyway response = chat(model=model, messages=history, tools=tools_list) ```
Author
Owner

@rick-github commented on GitHub (Jul 11, 2025):

And where are the non printable characters, in the prompt?

<!-- gh-comment-id:3061576196 --> @rick-github commented on GitHub (Jul 11, 2025): And where are the non printable characters, in the prompt?
Author
Owner

@CassidyMabey commented on GitHub (Jul 11, 2025):

And where are the non printable characters, in the prompt?

in the response from the AI. often appears in large prompts or code blocks. Then it instantly calls the tools from the tool function interface before these characters can be removed.

<!-- gh-comment-id:3061584039 --> @CassidyMabey commented on GitHub (Jul 11, 2025): > And where are the non printable characters, in the prompt? in the response from the AI. often appears in large prompts or code blocks. Then it instantly calls the tools from the tool function interface before these characters can be removed.
Author
Owner

@rick-github commented on GitHub (Jul 11, 2025):

ollama doesn't have a "tool function interface", that's a function of the client.

As I don't know the specifics of the client, the dataflow is not really clear. Based on the log snippets, it looks like the client is trying to process the content field from ollama as a tool call, rather than the tool_calls field which is normally used. For example:

DEBUG: Raw ollama response: model='llama3.2:1b' created_at='2025-07-11T08:58:01.34894Z' done=True
 done_reason='stop' total_duration=5128765500 load_duration=45583375 prompt_eval_count=1753
 prompt_eval_duration=1577648583 eval_count=95 eval_duration=3495413417
 message=Message(role='assistant', content=" The `Ball` class represents the game object, which has methods for drawing and moving it on the screen. The `Paddle` class represents the paddles, with methods for drawing and moving them. The `Scoreboard` class is used to display the current score. The main loop of the game updates the game objects' positions based on user input, checks for collisions and goals, draws everything on the screen, and caps the frame rate at 60 FPS.",
 thinking=None, images=None, tool_calls=None)
DEBUG: assistant message content:  The `Ball` class represents the game object, which has methods for drawing and moving it on the screen. The `Paddle` class represents the paddles, with methods for drawing and moving them. The `Scoreboard` class is used to display the current score. The main loop of the game updates the game objects' positions based on user input, checks for collisions and goals, draws everything on the screen, and caps the frame rate at 60 FPS.
Error in tool processing: Expecting value: line 1 column 2 (char 1)

The response from ollama has content (" The `Ball` class ...") but no tool_calls (None). The client extracts the content field ("assistant message content") and then tries to parse it as a tool call. It skips the leading space in the content but then throws an error when the next character ("T") is not the start of a valid JSON structure that would signify a tool call.

This can be seen in the first DEBUG statement where the parser processes the content until it gets to the missing double-quote.

So, I'm guessing that the client is expecting a response from ollama that is a tool call, and when it can't find a tool call in tool_calls, it assumes that the tool call must be in content, tries to parse it and fails because it's not a tool call. The first response is almost a tool call, the second and third responses are definitely not.

Failing to return a tool call when one is expected is not unusual for models, particularly small ones like llama3.2:1b. A bigger, more recent model might give you better results - qwen3 is quite a good tool user. The client could handle the case of failed tool calls by retrying the prompt that was supposed to generate the tool call.

As to the non-ASCII characters, if it seems to occur when you use large prompts, it might be due to context buffer overrun. A buffer overrun could also explain why the tool calls sometimes fail - the tools are at the head of the buffer, and exceeding the size of the buffer causes ollama to shift the buffer contents to make room for new tokens, resulting in the tools being lost. See here for ways to manage the size of the context buffer.

<!-- gh-comment-id:3061712989 --> @rick-github commented on GitHub (Jul 11, 2025): ollama doesn't have a "tool function interface", that's a function of the client. As I don't know the specifics of the client, the dataflow is not really clear. Based on the log snippets, it looks like the client is trying to process the `content` field from ollama as a tool call, rather than the `tool_calls` field which is normally used. For example: ``` DEBUG: Raw ollama response: model='llama3.2:1b' created_at='2025-07-11T08:58:01.34894Z' done=True done_reason='stop' total_duration=5128765500 load_duration=45583375 prompt_eval_count=1753 prompt_eval_duration=1577648583 eval_count=95 eval_duration=3495413417 message=Message(role='assistant', content=" The `Ball` class represents the game object, which has methods for drawing and moving it on the screen. The `Paddle` class represents the paddles, with methods for drawing and moving them. The `Scoreboard` class is used to display the current score. The main loop of the game updates the game objects' positions based on user input, checks for collisions and goals, draws everything on the screen, and caps the frame rate at 60 FPS.", thinking=None, images=None, tool_calls=None) DEBUG: assistant message content: The `Ball` class represents the game object, which has methods for drawing and moving it on the screen. The `Paddle` class represents the paddles, with methods for drawing and moving them. The `Scoreboard` class is used to display the current score. The main loop of the game updates the game objects' positions based on user input, checks for collisions and goals, draws everything on the screen, and caps the frame rate at 60 FPS. Error in tool processing: Expecting value: line 1 column 2 (char 1) ``` The response from ollama has `content` (" The \`Ball\` class ...") but no `tool_calls` (None). The client extracts the `content` field ("assistant message content") and then tries to parse it as a tool call. It skips the leading space in the content but then throws an error when the next character ("T") is not the start of a valid JSON structure that would signify a tool call. This can be seen in the first DEBUG statement where the parser processes the content until it gets to the missing double-quote. So, I'm guessing that the client is expecting a response from ollama that is a tool call, and when it can't find a tool call in `tool_calls`, it assumes that the tool call must be in `content`, tries to parse it and fails because it's not a tool call. The first response is almost a tool call, the second and third responses are definitely not. Failing to return a tool call when one is expected is not unusual for models, particularly small ones like llama3.2:1b. A bigger, more recent model might give you better results - qwen3 is quite a good tool user. The client could handle the case of failed tool calls by retrying the prompt that was supposed to generate the tool call. As to the non-ASCII characters, if it seems to occur when you use large prompts, it might be due to context buffer overrun. A buffer overrun could also explain why the tool calls sometimes fail - the tools are at the head of the buffer, and exceeding the size of the buffer causes ollama to shift the buffer contents to make room for new tokens, resulting in the tools being lost. See [here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-specify-the-context-window-size) for ways to manage the size of the context buffer.
Author
Owner

@CassidyMabey commented on GitHub (Jul 11, 2025):

Thank you for the response. Yes, you are right the error definitely occured due to the smaller model, however in this scenario, the AI produces a response which contains JSON to call the tools.
Then to be able to read it you have to decode it where it is then trying to decode non printable characters and potentially other english / natural language than the JSON format giving an error. Im just saying this can be fixed by removing these non printable characters from being added into the arguments of a tool.

<!-- gh-comment-id:3061791917 --> @CassidyMabey commented on GitHub (Jul 11, 2025): Thank you for the response. Yes, you are right the error definitely occured due to the smaller model, however in this scenario, the AI produces a response which contains JSON to call the tools. Then to be able to read it you have to decode it where it is then trying to decode non printable characters and potentially other english / natural language than the JSON format giving an error. Im just saying this can be fixed by removing these non printable characters from being added into the arguments of a tool.
Author
Owner

@rick-github commented on GitHub (Jul 11, 2025):

Decoding content is the wrong way to do it. The client should be checking tool_calls and retrying the request if no tools calls were found if one was expected. There are no non-printable characters in the responses in the supplied log snippet.

<!-- gh-comment-id:3061807207 --> @rick-github commented on GitHub (Jul 11, 2025): Decoding `content` is the wrong way to do it. The client should be checking `tool_calls` and retrying the request if no tools calls were found if one was expected. There are no non-printable characters in the responses in the supplied log snippet.
Author
Owner

@CassidyMabey commented on GitHub (Jul 11, 2025):

I understand. Thank you.

However, this is about the wider part where the tools in the response do not get sanitsed before they are parsed into the tools. This results in lots of issues down the road.

if p.state == toolsState_LookingForTag {
		i, found := p.findTag()
		if i == -1 {
			content = string(p.buffer)
			p.buffer = []byte{}
		} else {
			content = string(p.buffer[:i])
			p.buffer = p.buffer[i:]
		}

		// for models where { or [ are used as tool calling
		// tags, we only support parsing tools if the first non-
		// whitespace character is { or [
		if p.tag == "{" || p.tag == "[" {
			if strings.TrimSpace(content) != "" {
				p.state = toolsState_Done
				return nil, content + string(p.buffer)
			}
		}

		if !found {
			return nil, content
		}

		p.state = toolsState_ToolCalling
	}

As you can see here ollama tries to sanitise the json to help smaller models, however it is not nearly as smart to solve these issues.

Thanks for your help

<!-- gh-comment-id:3061842013 --> @CassidyMabey commented on GitHub (Jul 11, 2025): I understand. Thank you. However, this is about the wider part where the tools in the response do not get sanitsed before they are parsed into the tools. This results in lots of issues down the road. ```go if p.state == toolsState_LookingForTag { i, found := p.findTag() if i == -1 { content = string(p.buffer) p.buffer = []byte{} } else { content = string(p.buffer[:i]) p.buffer = p.buffer[i:] } // for models where { or [ are used as tool calling // tags, we only support parsing tools if the first non- // whitespace character is { or [ if p.tag == "{" || p.tag == "[" { if strings.TrimSpace(content) != "" { p.state = toolsState_Done return nil, content + string(p.buffer) } } if !found { return nil, content } p.state = toolsState_ToolCalling } ``` As you can see here ollama tries to sanitise the json to help smaller models, however it is not nearly as smart to solve these issues. Thanks for your help
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#54017