Empower your AI agents with custom Python functions through OpenAI-compatible Function Calling
- What is a Tool?
- Architecture Overview
- Built-in Tools Reference
- Creating Custom Tools
- Writing Python Logic
- Using Tools in AI Flows
- Advanced Features
- Importing and Exporting
- Best Practices
- Troubleshooting
A Tool is a bridge between your AI agent and the external world. It consists of two essential parts:
| Component | Purpose | Format |
|---|---|---|
| Definition | Describes the function to the LLM so it knows how and when to call it | JSON Schema |
| Implementation | The actual Python code that executes when the tool is called | Python Code |
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ User │────▶│ LLM Core │────▶│ Tool Call │────▶│ Tool │
│ Request │ │ (decides) │ │ Request │ │ Dispatcher │
└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘
│
┌────────────────────────────────────┘
▼
┌─────────────┐
│ Python │
│ Execution │
└──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Result │────▶│ LLM │
│ Return │ │ Response │
└─────────────┘ └─────────────┘
The Tool Library is built on these core components:
- Tool Definitions (
modules/tools/tools.json): Stores tool metadata and JSON schemas.tools.json.lockis a runtime lock file used during concurrent writes — do not delete it manually. - Library Directory (
modules/tools/library/): Contains standard Python implementation files (16 standard tools) - RLM Library (
modules/tools/rlm_library/): Contains Recursive Language Model tool implementations (7 RLM tools) - Tool Dispatcher Node (
tool_dispatcher): Executes tool calls within AI Flows - System Prompt Integration: Injects available tools into LLM context
- Sandbox (
modules/tools/sandbox.py): Restricted execution environment for all tool code
NeuroCore comes with 23 built-in tools: 16 standard tools in library/ and 7 RLM tools in rlm_library/.
| Tool | Description | Key Parameters |
|---|---|---|
| Weather | Get current weather for any location | location (string) |
| SystemTime | Get current system date and time | None |
| TimeZoneConverter | Convert times between time zones | source_tz, target_tz, time_string |
| ConversionCalculator | Convert between units (temp, length, weight, volume) | value, from_unit, to_unit |
| Calculator | Evaluate mathematical expressions | expression (string) |
| Tool | Description | Key Parameters |
|---|---|---|
| FetchURL | Fetch raw text content from any URL | url (string) |
| WikipediaLookup | Search and retrieve Wikipedia articles | query, mode (summary/full) |
| ArXivSearch | Search academic papers on ArXiv | query, max_results |
| YouTubeTranscript | Fetch video transcripts | url (YouTube URL) |
| CurrencyConverter | Real-time currency conversion | amount, from_currency, to_currency |
| Tool | Description | Key Parameters |
|---|---|---|
| SaveReminder | Save calendar events and reminders | title, time (ISO 8601) |
| CheckCalendar | Check calendar for events | date (YYYY-MM-DD, optional) |
| Tool | Description | Key Parameters |
|---|---|---|
| SendEmail | Send emails via SMTP | to_email, subject, body |
| Tool | Description | Key Parameters |
|---|---|---|
| SetGoal | Create new goals for the agent | description, priority, deadline |
| MarkGoalComplete | Mark goals as completed | goal_id (number) |
| DeleteGoal | Remove goals from system | goal_id (number) |
Used by agents for memory manipulation, variable storage, and recursive sub-calls when processing long or complex inputs.
| Tool | Description | Key Parameters |
|---|---|---|
| Peek | View a slice of the prompt | start, end |
| Search | Find regex matches in prompt | pattern, max_results |
| Chunk | Split prompt into chunks | size, overlap |
| SubCall | Recursively call an LLM | prompt, model, max_tokens |
| SetVariable | Store intermediate results | name, value |
| GetVariable | Retrieve stored results | name |
| SetFinal | Set final answer and terminate | value |
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}{
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The mathematical expression to evaluate (e.g., '2 + 2', 'sqrt(16)', 'sin(pi/2)')"
}
},
"required": ["expression"]
}{
"type": "object",
"properties": {
"time_string": {
"type": "string",
"description": "The date and time to convert (ISO 8601 format). If omitted, uses current system time."
},
"source_tz": {
"type": "string",
"description": "The source time zone (IANA format, e.g., 'America/New_York', 'UTC')"
},
"target_tz": {
"type": "string",
"description": "The target time zone (IANA format, e.g., 'Europe/London', 'Asia/Tokyo')"
}
},
"required": ["source_tz", "target_tz"]
}{
"type": "object",
"properties": {
"value": {
"type": "number",
"description": "The numerical value to convert"
},
"from_unit": {
"type": "string",
"description": "The unit to convert from (e.g., 'meters', 'celsius', 'lbs')"
},
"to_unit": {
"type": "string",
"description": "The unit to convert to (e.g., 'feet', 'fahrenheit', 'kg')"
}
},
"required": ["value", "from_unit", "to_unit"]
}{
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to fetch content from"
}
},
"required": ["url"]
}{
"type": "object",
"properties": {
"amount": {
"type": "number",
"description": "The amount to convert"
},
"from_currency": {
"type": "string",
"description": "Source currency code (e.g., USD, EUR, JPY)"
},
"to_currency": {
"type": "string",
"description": "Target currency code (e.g., EUR, GBP, CAD)"
}
},
"required": ["amount", "from_currency", "to_currency"]
}{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search term or topic"
},
"mode": {
"type": "string",
"enum": ["summary", "full"],
"description": "Whether to retrieve just the summary or full article text"
}
},
"required": ["query"]
}{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query (keywords, topics, paper titles)"
},
"max_results": {
"type": "number",
"description": "Maximum number of results to return (default 5, max 20)"
}
},
"required": ["query"]
}{
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL of the YouTube video"
}
},
"required": ["url"]
}{
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The title of the event"
},
"time": {
"type": "string",
"description": "The date and time (ISO 8601 preferred). If omitted, uses current time"
}
},
"required": ["title"]
}{
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "The date to check (YYYY-MM-DD). If omitted, returns upcoming events"
}
},
"required": []
}{
"type": "object",
"properties": {
"to_email": {
"type": "string",
"description": "The recipient's email address"
},
"subject": {
"type": "string",
"description": "The subject of the email"
},
"body": {
"type": "string",
"description": "The content of the email"
}
},
"required": ["to_email", "subject", "body"]
}{
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "The goal description - what should be achieved"
},
"priority": {
"type": "number",
"description": "Priority level (higher = more important, default 0)"
},
"deadline": {
"type": "number",
"description": "Optional deadline as Unix timestamp"
}
},
"required": ["description"]
}{
"type": "object",
"properties": {
"goal_id": {
"type": "number",
"description": "The ID of the goal"
}
},
"required": ["goal_id"]
}{
"type": "object",
"properties": {
"start": {
"type": "number",
"description": "Start character position (inclusive)."
},
"end": {
"type": "number",
"description": "End character position (exclusive)."
}
},
"required": ["start", "end"]
}{
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "The regex pattern to search for."
},
"max_results": {
"type": "number",
"description": "Maximum number of matches to return (default 20)."
}
},
"required": ["pattern"]
}{
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "The prompt to send to the sub-call LLM."
},
"model": {
"type": "string",
"description": "Optional model to use for sub-call."
}
},
"required": ["prompt"]
}Navigate to Settings -> Tools in the NeuroCore dashboard. Click + New Tool.
| Field | Description | Tips |
|---|---|---|
| Function Name | Unique identifier (snake_case) | Use descriptive names like get_weather, search_database |
| Description | Clear explanation for the LLM | Be specific: "Calculates square root" not "Math function" |
| Parameters | JSON Schema object | Define types, descriptions, and required fields |
| Python Logic | Implementation code | Use args for input, result for output |
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}Tool code runs inside the NeuroCore sandbox — a restricted Python execution environment. Understanding what is and is not available is essential to writing working tools.
These names are available in every tool without any import statement:
| Name | Type | Description |
|---|---|---|
args |
dict |
Arguments provided by the LLM call |
result |
(assign to this) | Set this variable to return a value |
json |
module | Standard json module |
httpx |
SafeHttpxClient |
Domain-restricted HTTP client (see below) |
os |
SafeEnv |
Environment variable access only (see below) |
You can also use import httpx or import os at the top of your code — both resolve to the same safe substitutes listed above.
The following standard library modules can be imported freely:
math, random, datetime, time, calendar, decimal, fractions, numbers,
statistics, itertools, functools, operator, copy, string, re,
collections, enum, typing, hashlib, base64, binascii, uuid, json,
html, html.entities, html.parser, urllib.parse, textwrap, unicodedata,
stringprep, codecs, dataclasses, abc, contextlib, contextvars, types,
weakref, array, bisect, heapq, copyreg, ast, zoneinfo,
smtplib, ssl, email, email.mime.text, email.mime.multipart
Additionally, youtube_transcript_api is importable if the package is installed.
httpx inside the sandbox is a SafeHttpxClient, not the real httpx library. It enforces:
- Domain allowlist — only requests to approved domains succeed. The default allowlist includes:
- Public weather/data APIs:
wttr.in,api.weatherapi.com,api.frankfurter.app - Research/media:
en.wikipedia.org,export.arxiv.org,www.youtube.com - LLM providers:
api.openai.com,api.anthropic.com,api.mistral.ai, and others - Internal server:
127.0.0.1,localhost(for same-host module APIs) - Code hosting:
github.com,raw.githubusercontent.com
- Public weather/data APIs:
- SSRF protection — blocks requests to private IP ranges (10.x, 192.168.x, 172.16–31.x, 169.254.x)
- Response size limit — responses over 10 MB are rejected
- Timeout — capped at the sandbox timeout (default 30 s)
Available methods: get, post, put, patch, delete (same signature as real httpx).
Requests to domains not in the allowlist raise SecurityError. If your tool needs a new domain, add it to SafeHttpxClient.DEFAULT_ALLOWED_DOMAINS in modules/tools/sandbox.py.
# Both of these work identically — httpx is already injected as a global
import httpx
resp = httpx.get("https://wttr.in/London?format=j1", timeout=10)
data = resp.json()os inside the sandbox is a SafeEnv mock. Only two things work:
import os
# Read an environment variable (returns None or default if not set)
api_key = os.getenv("MY_API_KEY", "default")
# Read via environ
api_key = os.environ.get("MY_API_KEY")Accessing any other os attribute (os.path, os.system, os.listdir, etc.) raises SecurityError. Use environment variables for secrets and configuration; never hardcode credentials in tool code.
Tools can import from NeuroCore's own package. This is how built-in tools access live app singletons:
from modules.calendar.events import event_manager
events = event_manager.get_upcoming(limit=5)This pass-through is intentional — built-in library tools are first-party trusted code. Custom tools should generally not need internal imports; use the HTTP API (httpx → localhost) instead.
The following modules are always blocked, even if installed:
os (real module)*, sys, subprocess, socket, multiprocessing, threading,
ctypes, mmap, resource, signal, pickle, cPickle, marshal, imp, importlib,
site, warnings, traceback, gc, inspect, code, pdb, bdb,
shutil, tempfile, pathlib, glob, fnmatch, posix, nt
* import os is allowed but returns SafeEnv (not the real os module).
Attempting to import a blocked module raises SecurityError immediately.
Importing a module that is not in the allowlist and not in the blocklist raises ImportError (not SecurityError). This means tools can use a graceful fallback:
try:
import some_optional_library
result = some_optional_library.do_thing(args['input'])
except ImportError:
result = "Error: some_optional_library is not installed."The following built-in functions are removed from the sandbox environment:
eval, exec, compile, open (unless file dirs are explicitly allowed), __import__, breakpoint, input, help, quit, exit
Parameters:
{
"type": "object",
"properties": {
"location": { "type": "string" }
},
"required": ["location"]
}Python Code:
from urllib.parse import quote
location = args.get('location')
if not location:
result = "Error: No location provided."
else:
try:
# httpx is pre-injected as a SafeHttpxClient — wttr.in is in the domain allowlist
resp = httpx.get(f"https://wttr.in/{quote(location)}?format=j1", timeout=10)
resp.raise_for_status()
data = resp.json()
current = data['current_condition'][0]
result = f"{current['temp_C']}°C, {current['weatherDesc'][0]['value']}"
except Exception as e:
result = f"Failed to fetch weather: {e}"limit = args.get('limit', 10)
offset = args.get('offset', 0)count = int(args.get('count', 0))
enabled = bool(args.get('enabled', False))items = args.get('items', [])
if not isinstance(items, list):
items = [items] # Normalize single item to listimport os
# Reads from the host environment — set these before starting NeuroCore
api_key = os.getenv("MY_SERVICE_API_KEY")
if not api_key:
result = "Error: MY_SERVICE_API_KEY environment variable is not set."To enable tool usage, configure the System Prompt and add a Tool Dispatcher.
- Open your AI Flow
- Select the System Prompt node
- In the configuration panel, check the tools you want to enable
- This injects tool definitions into the LLM context
The LLM requests tool calls, but cannot execute code itself. The Tool Dispatcher handles execution.
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ System │────▶│ LLM Core │────▶│ Conditional │
│ Prompt │ │ (with tools) │ │ Router │
│ (+tools) │ └──────────────────┘ └──────┬──────┘
└─────────────┘ │
┌──────────────────────┘
│ check_field: "tool_calls"
▼
┌─────────────┐ ┌─────────────┐
│ Tool │────▶│ LLM │
│ Dispatcher │ │ (interpret)│
└─────────────┘ └─────────────┘
Configuration Steps:
- Add a Tool Dispatcher node to your flow
- Connect LLM Core output to Tool Dispatcher input
- Use a Conditional Router to check if
tool_callsexists - Connect Tool Dispatcher output back to LLM Core for result interpretation
| Option | Description | Default |
|---|---|---|
allowed_tools |
List of permitted tools (empty = all) | [] |
max_tools_per_turn |
Maximum tools per execution | 5 |
Some tools support additional configuration:
Configure via environment variables or the tool config UI:
SMTP_SERVER- SMTP server hostnameSMTP_PORT- Server port (default: 587)SMTP_EMAIL- Sender email addressSMTP_PASSWORD- Authentication password
The Tool Dispatcher provides robust error handling:
| Error Type | Behavior |
|---|---|
| JSON Parse Error | Returns error message to LLM |
| Tool Not Found | Returns "Tool not found" error |
| Execution Error | Returns exception details |
| Tool Not Allowed | Returns "Tool not enabled" error |
Control tool execution with max_tools_per_turn:
- Prevents runaway tool loops
- Remaining tools are queued for next turn
- Tracked via
_tool_countand_remaining_tool_calls
All tool code (standard and custom) executes in modules/tools/sandbox.py with these protections:
| Layer | Mechanism |
|---|---|
| Static Analysis | Regex scanning for dangerous patterns (subprocess, sys, ctypes, etc.) before execution |
| Import Blocklist | RestrictedImport blocks sys, subprocess, socket, shutil, importlib, pickle, ctypes, mmap, multiprocessing, pathlib, and 15+ more |
| Module Mocking | import httpx → SafeHttpxClient; import os → SafeEnv. Real modules are never exposed. |
| Module Passthrough | modules.* imports pass through to the real NeuroCore package (first-party trusted code) |
| Restricted Builtins | Removes eval, exec, open, __import__, compile, breakpoint, input |
| File Access Control | SafeOpen — file reads only allowed if specific directories are whitelisted |
| Network Restrictions | SafeHttpxClient enforces a domain allowlist; requests to non-listed domains raise SecurityError |
| SSRF Protection | Resolves hostnames via DNS and rejects requests to private/internal IP ranges |
| Resource Limits | Timeout (default 30 s), max output size (100 KB), optional memory limit |
Error types raised by the sandbox:
| Error | Meaning |
|---|---|
SecurityError |
Import of a blocked module, access to a blocked os attribute, request to a non-whitelisted domain |
ImportError |
Import of an unknown module (not blocked, not in allowlist) — allows except ImportError fallbacks |
ResourceLimitError |
Timeout, output size, or memory limit exceeded |
These are caught by the Tool Dispatcher and returned to the LLM as error strings so the agent can react gracefully.
Share tools between NeuroCore instances, back them up, or download from the Community Marketplace.
Tools published to the Community Marketplace can be imported directly from the item detail page. Clicking Import to NeuroCore on a tool item:
- Downloads the tool file (
.jsonor.py) - Parses the definition and code
- Registers the tool in
modules/tools/tools.jsonusingfilelockfor thread safety - Writes the implementation to
modules/tools/library/{name}.py - The tool is immediately available in the Tools settings page and in flow editors
The import button changes to Imported after a successful import and shows Update Available if the marketplace version is newer than the local one.
Originality note: Tools that were themselves imported from the marketplace are tagged as
marketplace_importin their metadata. The marketplace upload form will reject re-uploading these files unchanged to prevent duplication.
| Method | Action | Output |
|---|---|---|
| Single Tool | Click Export Tool | {name}.json |
| Entire Library | Click Export Entire Library | neurocore_tools.json |
Export Format:
[
{
"name": "Weather",
"enabled": true,
"description": "Get current weather...",
"parameters": { ... },
"code": "import httpx\n..."
}
]| File Type | Handling |
|---|---|
.json |
Full tool definition with code |
.py |
Wrapped as tool (adjust schema manually) |
Import Steps:
- Click Import Tool
- Select
.jsonor.pyfile - Review and adjust parameter schema if needed
- Save
✅ Good Examples:
- "Fetches current weather conditions including temperature, humidity, and forecast"
- "Searches the product database by name and returns matching items with price and availability"
- "Converts between 50+ currencies using real-time exchange rates"
❌ Bad Examples:
- "Weather tool"
- "Database search"
- "Money converter"
- Use clear, descriptive names -
locationnotloc - Provide examples in descriptions - "e.g., San Francisco, CA"
- Mark truly required fields - Don't overuse
required - Use enums for limited options -
["celsius", "fahrenheit"] - Set sensible defaults - Handle missing optional parameters gracefully
- Use
allowed_toolsin Tool Dispatcher to restrict access - Validate all inputs before processing
- Never expose sensitive credentials in tool code
- Use environment variables for API keys
- Sanitize user inputs to prevent injection attacks
- Keep tool execution fast (< 5 seconds ideal)
- Use timeouts for external API calls
- Cache results when appropriate
- Handle rate limits gracefully
| Symptom | Cause | Solution |
|---|---|---|
| LLM doesn't call tools | Tools not enabled in System Prompt | Check tool checkboxes in System Prompt config |
| "Tool not found" error | Tool name mismatch | Verify function name matches definition |
| JSON parse errors | Malformed LLM arguments | Tool Dispatcher handles this automatically |
| Tool executes but no result | Missing result assignment |
Ensure result = ... in Python code |
| Import fails | Invalid JSON or encoding | Validate JSON syntax, check file encoding |
Import of module 'X' is not permitted |
Module not in sandbox allowlist | Add it to SAFE_MODULES in sandbox.py, or use an HTTP API via httpx instead |
Import of module 'X' is not allowed |
Module is in the danger blocklist | Use a safe alternative; blocked modules cannot be enabled |
| HTTP request blocked (domain not whitelisted) | SafeHttpxClient domain check |
Add the domain to SafeHttpxClient.DEFAULT_ALLOWED_DOMAINS in sandbox.py |
os.X is not permitted |
Accessing a blocked os attribute |
Only os.getenv() and os.environ.get() are available; use env vars for config |
| Tool times out | Slow external API or infinite loop | Add timeouts to HTTP calls; check for logic errors |
-
Check Tool Definitions
cat modules/tools/tools.json | jq '.'
-
Verify Python Code
- Check syntax with
python -m py_compile library/{tool_name}.py - Ensure
resultvariable is assigned
- Check syntax with
-
Test in Isolation
- Create a simple flow with just System Prompt → LLM Core → Tool Dispatcher
- Enable only one tool at a time
-
Review Logs
- Check console output for execution errors
- Look for
Error executing toolmessages
- Check the NeuroCore Documentation
- Review example tools in
modules/tools/library/ - Enable debug mode in settings for verbose logging
💡 Pro Tip: Start with built-in tools to understand patterns, then create custom tools for your specific use cases!