September 12, 2026 Update

I. Adding Custom AI Tools
Custom AI tools are now supported.
You can configure them via the top menu bar → Help → AI.
- Checked – Enabled
- Unchecked – Disabled
Currently, several example tools are provided, and you can also extend them in specific ways (see Chapter 4 for details).
II. Custom System Prompt
You can now customize the system prompt. In the AI panel, click the More (three dots in the top-right corner) button → System Prompt to edit it. The prompt added here will be automatically sent to the AI on every call. You can add restrictions and requirements for the AI here.
III. Exporting the Work Audit Log
In the tool interface, click the More (three dots in the top-right corner) button → Export Audit Log to export the AI's work audit log. You can find it in the save directory. By reading this log, you can learn how the AI tools have been used.
IV. How to Extend Tools
1 What Is a Tool
1.1 Purpose
A Tool is a bridge for interaction between the AI and the game world. It is a C# method that can be called by the AI. The AI reads the tool's description and parameters, determines in which scenarios to call it, and continues reasoning based on the returned result.
1.2 Workflow
Player input → AI Agent analyzes intent → Decides to call a Tool → Tool executes specific logic → Returns result to AI → AI generates a reply based on the result or continues calling tools
1.3 Example
When the player says "Push that red box into the corner," the AI's workflow is: AI analysis: Need to find the red box first → Call find_object("red box") Result obtained: The box is at (5, 0, 3) → Call move_object("box", target position) After completion: Reply to the player, "I have pushed the red box into the corner for you."
2 Quick Start: Create Your First Tool
2.1 Create a Script File
Create a new C# script in the Assets/YourProjectName/Scripts/Tools/ directory, for example SayHelloTool.cs.
2.2 Minimal Working Template
csharp
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using UnAI.Tools;
namespace RuntimeEditor.Tools.Examples
{
public class SayHelloTool : ToolBase
{
// ========== Basic Metadata ==========
public override string Id => "say_hello";
public override string DisplayName => "Say Hello";
public override string Description => "Greets the player and returns a greeting message.";
public override ToolCategory Category => ToolCategory.General;
public override ToolRiskLevel RiskLevel => ToolRiskLevel.Low;
public override bool DefaultEnabled => true;
// ========== Parameter Definition ==========
public override UnaiToolDefinition Definition => new()
{
Name = Id,
Description = Description,
ParametersSchema = JObject.Parse(@"
{
""type"": ""object"",
""properties"": {
""playerName"": {
""type"": ""string"",
""description"": ""The player's name""
}
},
""required"": [""playerName""]
}")
};
// ========== Execution Logic ==========
public override Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
var args = call.GetArguments();
string playerName = args["playerName"]?.ToString() ?? "Adventurer";
return Task.FromResult(new UnaiToolResult
{
Content = $"Hello, {playerName}! Welcome to this world."
});
}
}
}
2.3 Automatic Registration
No additional action is required. ToolManager will automatically scan and discover all classes inheriting from ToolBase when the game starts.
2.4 Feature Verification
- Run the game → Open the tool management panel
- Find the "Say Hello" tool → Confirm it is enabled
- Talk to the AI: "Say hello to me" → The AI will call this tool and return a greeting
3 ToolBase Base Class Detailed Explanation
All custom tools must inherit from ToolBase and implement the following members.
3.1 Required Properties
Table 1 Required Properties of ToolBase
Property
Type
Description
Id
string
Unique tool identifier (used to save state and audit logs). Must be globally unique. Snake_case naming is recommended, such as get_player_status
DisplayName
string
Tool display name (shown in UI), such as "Get Player Status"
Description
string
Tool description (used by UI + AI). This is the key for the AI to determine when to call the tool, so it should be clear and specific
Category
ToolCategory
Tool category (see Section 7)
Definition
UnaiToolDefinition
Tool schema definition (see Section 4)
ExecuteAsync
Task
Tool execution logic (see Section 5)
3.2 Optional Virtual Properties
Table 2 Optional Virtual Properties of ToolBase
Property
Default Value
Description
RiskLevel
Low
Risk level (see Section 7)
DefaultEnabled
true
Whether enabled by default
RequiresConfirmation
RiskLevel >= High
Whether player confirmation is required
SupportsUndo
false
Whether undo is supported
TimeoutSeconds
30
Execution timeout (seconds); 0 means no limit
DependsOn
[]
Prerequisite tool IDs
RecommendedNext
[]
Recommended follow-up tool IDs
4 Parameter Definition (ParametersSchema)
ParametersSchema uses JSON Schema format to describe the tool's parameters. The AI will generate the correct call parameters based on this schema.
4.1 Basic Types
csharp
ParametersSchema = JObject.Parse(@"{
""type"": ""object"",
""properties"": {
""name"": { ""type"": ""string"", ""description"": ""Name"" },
""count"": { ""type"": ""integer"", ""description"": ""Count"" },
""price"": { ""type"": ""number"", ""description"": ""Price"" },
""enabled"": { ""type"": ""boolean"", ""description"": ""Whether enabled"" }
},
""required"": [""name""]
}")
4.2 Array Type
csharp
ParametersSchema = JObject.Parse(@"{
""type"": ""object"",
""properties"": {
""tags"": {
""type"": ""array"",
""items"": { ""type"": ""string"" },
""description"": ""List of tags""
}
}
}")
4.3 Enum Values
csharp
ParametersSchema = JObject.Parse(@"{
""type"": ""object"",
""properties"": {
""direction"": {
""type"": ""string"",
""enum"": [""north"", ""south"", ""east"", ""west""],
""description"": ""Movement direction""
}
}
}")
4.4 Nested Objects
csharp
ParametersSchema = JObject.Parse(@"{
""type"": ""object"",
""properties"": {
""position"": {
""type"": ""object"",
""properties"": {
""x"": { ""type"": ""number"" },
""y"": { ""type"": ""number"" },
""z"": { ""type"": ""number"" }
},
""required"": [""x"", ""y"", ""z""],
""description"": ""Target coordinates""
}
}
}")
4.5 Description Writing Suggestions
The description should be as clear as documentation written for a new colleague. The AI relies entirely on it to determine the meaning of parameters.
Bad description:
json
"time" : { "description" : "Time" }
Good description:
json
"time": {
"type": "integer",
"description": "Waiting time in seconds. For example, 5 means waiting for 5 seconds. Value range: 1-60."
}
5 Execution Logic (ExecuteAsync)
ExecuteAsync is the core of the tool. Parameters are obtained through call.GetArguments(), and it returns an UnaiToolResult.
5.1 Basic Template
csharp
public override async Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
// 1. Parse parameters
var args = call.GetArguments();
string name = args["name"]?.ToString();
int count = Convert.ToInt32(args["count"]);
// 2. Validate parameters
if (string.IsNullOrEmpty(name))
return new UnaiToolResult { Content = "Error: name parameter cannot be empty" };
// 3. Execute logic
try
{
// Write the specific business logic here
string result = DoSomething(name, count);
// 4. Return result
return new UnaiToolResult
{
Content = $"Execution successful: {result}"
};
}
catch (Exception ex)
{
return new UnaiToolResult { Content = $"Execution failed: {ex.Message}" };
}
}
5.2 Asynchronous Operations
For time-consuming operations (network requests, file reads/writes), use await:
csharp
public override async Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
// Use UnityWebRequest for an asynchronous request
using (var request = UnityWebRequest.Get("https://example.com"))
{
var operation = request.SendWebRequest();
while (!operation.isDone)
{
if (ct.IsCancellationRequested)
{
request.Abort();
return new UnaiToolResult { Content = "Operation canceled" };
}
await Task.Yield();
}
if (request.result != UnityWebRequest.Result.Success)
return new UnaiToolResult { Content = $"Request failed: {request.error}" };
return new UnaiToolResult { Content = request.downloadHandler.text };
}
}
5.3 Return Result Specification
Successful return:
csharp
return new UnaiToolResult { Content = "Operation completed successfully. The result is..." };
Failed return (describe the error in natural language):
csharp
return new UnaiToolResult { Content = "Error: Target object not found" };
Tip: The content of Content will be fed back to the AI as the result, and the AI will continue reasoning based on it. Therefore, even on failure, provide a clear error description so the AI has a chance to adjust its strategy.
6 Advanced Features
6.1 Progress Reporting
For long-running tools, you can use ReportProgress to report progress to the UI:
csharp
public override async Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
ReportProgress(0.1f, "Preparing...");
await SomeStep1();
ReportProgress(0.5f, "Processing...");
await SomeStep2();
ReportProgress(1f, "Completed");
return new UnaiToolResult { Content = "Operation completed" };
}
6.2 Undo Support
If the tool supports undo (such as modifying files or moving objects), override SupportsUndo and UndoAsync:
csharp
public override bool SupportsUndo => true;
private string _originalContent;
public override async Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
// Save the original state
_originalContent = File.ReadAllText(_filePath);
// Modify the file
File.WriteAllText(_filePath, "New content");
return new UnaiToolResult { Content = "File modified" };
}
public override Task<bool> UndoAsync()
{
if (!string.IsNullOrEmpty(_originalContent))
{
File.WriteAllText(_filePath, _originalContent);
return Task.FromResult(true);
}
return Task.FromResult(false);
}
6.3 Dependency Declaration
If tools have a calling order, you can declare dependencies:
csharp
// Before this tool executes, it is recommended to execute read_script first
public override string[] DependsOn => new[] { "read_script" };
// After this tool executes, it is recommended to call compile_project
public override string[] RecommendedNext => new[] { "compile_project" };
7 Tool Categories and Risk Levels
7.1 Tool Categories (ToolCategory)
Table 3 Tool Categories (ToolCategory)
Category
Description
Typical Tools
General
General
Greeting, time query
ProjectRead
Project reading
Read scripts, search files
CodeGeneration
Code generation
Create scripts, generate Prefabs
CodeEditing
Code editing
Modify scripts, rename
Compilation
Compilation verification
Compile project, get errors
SceneOperation
Scene operation
Create objects, move objects
AssetOperation
Asset operation
Load materials, manage assets
Debugging
Debugging and diagnostics
Read logs, web search
System
System-level
Shutdown, restart
7.2 Risk Levels (ToolRiskLevel)
Table 4 Risk Levels (ToolRiskLevel)
Level
Description
Confirmation Required by Default
Typical Tools
Low
Read-only operation, no side effects
false
Read, query, search
Medium
Modifies but reversible
false
Move, create, write files
High
Modifies and difficult to recover
true
Delete, overwrite, batch operations
Critical
Irreversible operation
true
Format, system-level operations
7.3 Configuration Suggestions
csharp
// Read-only tool: low risk, enabled by default
public override ToolCategory Category => ToolCategory.ProjectRead;
public override ToolRiskLevel RiskLevel => ToolRiskLevel.Low;
public override bool DefaultEnabled => true;
// Modifying tool: medium risk, enabled by default but logged
public override ToolCategory Category => ToolCategory.SceneOperation;
public override ToolRiskLevel RiskLevel => ToolRiskLevel.Medium;
// Dangerous tool: high risk, disabled by default
public override ToolCategory Category => ToolCategory.CodeEditing;
public override ToolRiskLevel RiskLevel => ToolRiskLevel.High;
public override bool DefaultEnabled => false;
8 Complete Example Set
8.1 Query-Type Tool (No Parameters)
csharp
public class GetTimeTool : ToolBase
{
public override string Id => "get_time";
public override string DisplayName => "Get Current Time";
public override string Description => "Returns the current time in the game world (hours and minutes).";
public override ToolCategory Category => ToolCategory.General;
public override ToolRiskLevel RiskLevel => ToolRiskLevel.Low;
public override UnaiToolDefinition Definition => new()
{
Name = Id,
Description = Description,
ParametersSchema = JObject.Parse(@"{
""type"": ""object"",
""properties"": {}
}")
};
public override Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
string time = DateTime.Now.ToString("HH:mm");
return Task.FromResult(new UnaiToolResult { Content = $"The current time is {time}" });
}
}
8.2 Action-Type Tool (With Parameters)
csharp
public class SpawnEnemyTool : ToolBase
{
public override string Id => "spawn_enemy";
public override string DisplayName => "Spawn Enemy";
public override string Description => "Spawns an enemy at the specified coordinates.";
public override ToolCategory Category => ToolCategory.SceneOperation;
public override ToolRiskLevel RiskLevel => ToolRiskLevel.Medium;
public override bool DefaultEnabled => false;
public override UnaiToolDefinition Definition => new()
{
Name = Id,
Description = Description,
ParametersSchema = JObject.Parse(@"{
""type"": ""object"",
""properties"": {
""enemyType"": {
""type"": ""string"",
""enum"": [""goblin"", ""skeleton"", ""dragon""],
""description"": ""Enemy type""
},
""x"": { ""type"": ""number"", ""description"": ""X coordinate"" },
""y"": { ""type"": ""number"", ""description"": ""Y coordinate"" },
""z"": { ""type"": ""number"", ""description"": ""Z coordinate"" }
},
""required"": [""enemyType"", ""x"", ""y"", ""z""]
}")
};
public override Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
var args = call.GetArguments();
string type = args["enemyType"]?.ToString();
float x = Convert.ToSingle(args["x"]);
float y = Convert.ToSingle(args["y"]);
float z = Convert.ToSingle(args["z"]);
// Actual enemy spawning logic
// Instantiate(enemyPrefab, new Vector3(x, y, z), Quaternion.identity);
return Task.FromResult(new UnaiToolResult
{
Content = $"Spawned {type} at ({x}, {y}, {z})"
});
}
}
8.3 Dangerous Tool (Requires Confirmation)
csharp
public class DeleteObjectTool : ToolBase
{
public override string Id => "delete_object";
public override string DisplayName => "Delete Object";
public override string Description => "Permanently deletes the specified game object in the scene. This operation is irreversible.";
public override ToolCategory Category => ToolCategory.SceneOperation;
public override ToolRiskLevel RiskLevel => ToolRiskLevel.High;
public override bool DefaultEnabled => false;
// RequiresConfirmation defaults to true (because RiskLevel >= High)
public override UnaiToolDefinition Definition => new()
{
Name = Id,
Description = Description,
ParametersSchema = JObject.Parse(@"{
""type"": ""object"",
""properties"": {
""objectName"": {
""type"": ""string"",
""description"": ""Name of the object to delete""
}
},
""required"": [""objectName""]
}")
};
public override Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
var args = call.GetArguments();
string name = args["objectName"]?.ToString();
// GameObject obj = GameObject.Find(name);
// if (obj != null) Destroy(obj);
return Task.FromResult(new UnaiToolResult
{
Content = $"Deleted object {name}"
});
}
}
9 Debugging and Troubleshooting
9.1 The Tool Does Not Appear in the Panel
Table 5 Troubleshooting Items for a Tool Not Appearing in the Panel
Item
Description
Whether it inherits ToolBase
Check the class declaration: public class XxxTool : ToolBase
Whether it has a parameterless constructor
It must have a public parameterless constructor (the default one is fine)
Whether there are compilation errors
Check whether there are red errors in the Console
Whether it is in the correct assembly
If using .asmdef, the tool class and ToolManager must be in the same assembly (or modify the discovery logic to scan all assemblies)
Check logs
Look for the log [ToolManager] Discovered tool: xxx
9.2 The Tool Is Called by the AI but Has No Effect
Table 6 Troubleshooting Items for a Tool Being Called but Having No Effect
Item
Description
Whether Content is empty
The return result is passed to the AI, so it must contain meaningful content
Whether parameter parsing is correct
Use Debug.Log(args.ToString()) to print the parameters and confirm
Whether an exception is thrown
Check the Console for exception logs
9.3 Adding Debug Logs
csharp
public override async Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
var args = call.GetArguments();
Debug.Log($"[MyTool] Received parameters: {args}");
// ... execution logic ...
Debug.Log($"[MyTool] Returned result: {result}");
return result;
}
9.4 Audit Log
ToolManager automatically records every tool execution. You can view it by clicking "Export Log" in the tool management panel:
[2026-09-11 10:30:00] true Get Player Status Parameters: {} Result: Health 100, Position (10.0, 0.0, 5.0) Duration: 12ms
10 Best Practices
10.1 Naming Conventions
Table 7 Naming Conventions
Item
Convention
Example
Class name
PascalCase + Tool suffix
GetPlayerStatusTool
Id
snake_case
get_player_status
DisplayName
Short name
Get Player Status
Description
Complete sentence explaining the purpose
Gets the player's current health, position, and current area.
10.2 Description Writing Tips
Description is the core basis for the AI to determine whether to call the tool. Write it as if explaining this feature to a new colleague:
Bad description:
"Player status"
Good description:
"Gets the player's current health, position, and current area. Use this when you need to understand the player's status, determine whether it is safe, or plan a movement route."
10.3 Parameter Design Principles
• Use as few parameters as possible: Make it easier for the AI to generate correct parameters
• Use enums to limit ranges: Avoid invalid values generated by the AI
• Provide default values: Set reasonable defaults for optional parameters
• Make parameter descriptions detailed: Include units, ranges, and formats
10.4 Error Handling
Always wrap execution logic in try-catch and return meaningful error information:
csharp
try
{
// Execution logic
}
catch (Exception ex)
{
// Not only log it, but also return it to the AI so the AI has a chance to adjust
return new UnaiToolResult
{
Content = $"Operation failed: {ex.Message}. Please check whether the parameters are correct."
};
}
10.5 Security Principles
• Never trust parameters generated by the AI: They must be validated
• High-risk operations must set RiskLevel = High or above
• Do not execute arbitrary code in tools (such as eval or reflection calls to unauthorized methods)
• Restrict file operations to the project directory (such as Assets/)
10.6 Performance Considerations
• Avoid time-consuming synchronous operations in ExecuteAsync: Use await Task.Run() or asynchronous APIs
• Report progress for long operations: Use ReportProgress
• Set a reasonable TimeoutSeconds: Prevent the AI from getting stuck
Appendix A: Quick Reference Card
The following template can be copied and used directly. It covers all elements of tool definition, parameter declaration, and execution logic:
csharp
public class MyCustomTool : ToolBase
{
// ========== Required ==========
public override string Id => "my_custom_tool";
public override string DisplayName => "My Tool";
public override string Description => "Describe what this tool does. The more specific, the better.";
public override ToolCategory Category => ToolCategory.General;
public override UnaiToolDefinition Definition => new()
{
Name = Id,
Description = Description,
ParametersSchema = JObject.Parse(@"{
""type"": ""object"",
""properties"": {
""param1"": { ""type"": ""string"", ""description"": ""Parameter description"" }
},
""required"": [""param1""]
}")
};
public override async Task<UnaiToolResult> ExecuteAsync(UnaiToolCall call, CancellationToken ct)
{
var args = call.GetArguments();
string param1 = args["param1"]?.ToString();
if (string.IsNullOrEmpty(param1))
return new UnaiToolResult { Content = "Error: param1 cannot be empty" };
try
{
// Your logic
return new UnaiToolResult { Content = "Execution result" };
}
catch (Exception ex)
{
return new UnaiToolResult { Content = $"Error: {ex.Message}" };
}
}
// ========== Optional Overrides ==========
public override ToolRiskLevel RiskLevel => ToolRiskLevel.Low;
public override bool DefaultEnabled => true;
public override bool SupportsUndo => false;
public override int TimeoutSeconds => 30;
public override string[] DependsOn => new string[] { };
public override string[] RecommendedNext => new string[] { };
}
This announcement was originally published by Institute of Card Game Studies on Steam on Sep 12, 2026.
Open the original on Steam