Make a Simple Claude Code AI Tool Myself

It all started with a 2022 paper titled ReAct: Synergizing Reasoning and Acting in Language Models. This work laid the foundation for a powerful paradigm in AI agent design—one that doesn’t just generate answers, but actively thinks, acts, and observes step by step. The core loop is simple but transformative: a language model first produces a <thought>, then takes an <action>, receives an <observation>, and finally arrives at a <final_answer>. This cycle mimics how humans solve problems.

Numerous advanced coding tools such as Cursor, Windsurf, and the recently introduced Claude Code have drawn inspiration from this foundational paper. In light of this, we are developing our own Claude Code AI tool—an efficient yet robust assistant driven by a structured ReAct prompt template and a suite of custom tools designed specifically for software development.

```python
react_system_prompt_template = """
You need to solve a problem. To do this, you must break the problem down into multiple steps. For each step, first use <thought> to describe what you're thinking, then decide on an <action> using one of the available tools. Next, you'll receive an <observation> from the environment or tool based on your action. Continue this cycle of thinking and acting until you have enough information to provide a <final_answer>.

Please strictly format **all steps** using the following XML tag structure:
- <question> User’s question
- <thought> Your thought process
- <action> The tool operation you decide to take
- <observation> The result returned from the tool or environment
- <final_answer> The final answer

⸻

Example 1:

<question>How tall is the Eiffel Tower?</question>
<thought>I need to find out the height of the Eiffel Tower. I can use a search tool.</thought>
<action>get_height("Eiffel Tower")</action>
<observation>The Eiffel Tower is approximately 330 meters tall (including antenna).</observation>
<thought>The search result provided the height. I now have the answer.</thought>
<final_answer>The Eiffel Tower is approximately 330 meters tall.</final_answer>

⸻

Example 2:

<question>Help me find a simple tomato and egg stir-fry recipe, and check if there are tomatoes in the fridge.</question>
<thought>This task has two steps. First, find a recipe for tomato and egg stir-fry. Second, check if there are tomatoes in the fridge. I’ll start by using the find_recipe tool.</thought>
<action>find_recipe(dish="Tomato and Egg Stir-fry")</action>
<observation>Simple recipe: Beat 2 eggs, chop 2 tomatoes. Heat oil, scramble the eggs and set aside. Heat more oil, stir-fry tomatoes until soft, add eggs back in, season with salt, and serve.</observation>
<thought>Okay, I have the recipe. The recipe requires tomatoes. Now I need to use the check_fridge tool to see if there are tomatoes.</thought>
<action>check_fridge(item="Tomato")</action>
<observation>Fridge check result: 3 tomatoes available.</observation>
<thought>I found the recipe and confirmed that there are tomatoes in the fridge. I can now answer the question.</thought>
<final_answer>Simple tomato and egg stir-fry: Beat eggs, chop tomatoes. Scramble eggs, stir-fry tomatoes, mix and season with salt. There are 3 tomatoes in the fridge.</final_answer>

⸻

Please strictly follow:
- Every response must include two tags: the first must be <thought>, the second must be either <action> or <final_answer>
- After outputting <action>, stop immediately and wait for the actual <observation>; do not generate <observation> on your own, or it will cause an error
- If any tool parameter contains multiple lines, use \n to indicate line breaks, e.g.: <action>write_to_file("/tmp/test.txt", "a\nb\nc")</action>
- For file paths in tool parameters, always use **absolute paths**. Do not use just the file name. For example, use write_to_file("/tmp/test.txt", "content") instead of write_to_file("test.txt", "content")

⸻

Available tools for this task:
${tool_list}

⸻

Environment info:

Operating system: ${operating_system}
List of files in current directory: ${file_list}
"""
```

Then, considering the tool codes provided by MarkTechStation (specifically tools = [read_file, write_to_file, run_terminal_command]), it is highly likely that Claude Code possesses a significantly greater array of tools beyond these four.

import ast
import inspect
import os
import re
from string import Template
from typing import List, Callable, Tuple

import click
from dotenv import load_dotenv
from openai import OpenAI
import platform

from prompt_template import react_system_prompt_template


class ReActAgent:
    def __init__(self, tools: List[Callable], model: str, project_directory: str):
        self.tools = { func.__name__: func for func in tools }
        self.model = model
        self.project_directory = project_directory
        self.client = OpenAI(
            base_url="https://openrouter.ai/api/v1",
            api_key=ReActAgent.get_api_key(),
        )

    def run(self, user_input: str):
        messages = [
            {"role": "system", "content": self.render_system_prompt(react_system_prompt_template)},
            {"role": "user", "content": f"<question>{user_input}</question>"}
        ]

        while True:

            # 请求模型
            content = self.call_model(messages)

            # 检测 Thought
            thought_match = re.search(r"<thought>(.*?)</thought>", content, re.DOTALL)
            if thought_match:
                thought = thought_match.group(1)
                print(f"\n\n💭 Thought: {thought}")

            # 检测模型是否输出 Final Answer,如果是的话,直接返回
            if "<final_answer>" in content:
                final_answer = re.search(r"<final_answer>(.*?)</final_answer>", content, re.DOTALL)
                return final_answer.group(1)

            # 检测 Action
            action_match = re.search(r"<action>(.*?)</action>", content, re.DOTALL)
            if not action_match:
                raise RuntimeError("模型未输出 <action>")
            action = action_match.group(1)
            tool_name, args = self.parse_action(action)

            print(f"\n\n🔧 Action: {tool_name}({', '.join(args)})")
            # 只有终端命令才需要询问用户,其他的工具直接执行
            should_continue = input(f"\n\n是否继续?(Y/N)") if tool_name == "run_terminal_command" else "y"
            if should_continue.lower() != 'y':
                print("\n\n操作已取消。")
                return "操作被用户取消"

            try:
                observation = self.tools[tool_name](*args)
            except Exception as e:
                observation = f"工具执行错误:{str(e)}"
            print(f"\n\n🔍 Observation:{observation}")
            obs_msg = f"<observation>{observation}</observation>"
            messages.append({"role": "user", "content": obs_msg})


    def get_tool_list(self) -> str:
        """生成工具列表字符串,包含函数签名和简要说明"""
        tool_descriptions = []
        for func in self.tools.values():
            name = func.__name__
            signature = str(inspect.signature(func))
            doc = inspect.getdoc(func)
            tool_descriptions.append(f"- {name}{signature}: {doc}")
        return "\n".join(tool_descriptions)

    def render_system_prompt(self, system_prompt_template: str) -> str:
        """渲染系统提示模板,替换变量"""
        tool_list = self.get_tool_list()
        file_list = ", ".join(
            os.path.abspath(os.path.join(self.project_directory, f))
            for f in os.listdir(self.project_directory)
        )
        return Template(system_prompt_template).substitute(
            operating_system=self.get_operating_system_name(),
            tool_list=tool_list,
            file_list=file_list
        )

    @staticmethod
    def get_api_key() -> str:
        """Load the API key from an environment variable."""
        load_dotenv()
        api_key = os.getenv("OPENROUTER_API_KEY")
        if not api_key:
            raise ValueError("未找到 OPENROUTER_API_KEY 环境变量,请在 .env 文件中设置。")
        return api_key

    def call_model(self, messages):
        print("\n\n正在请求模型,请稍等...")
        response = self.client.chat.completions.create(
            model=self.model,
            messages=messages,
        )
        content = response.choices[0].message.content
        messages.append({"role": "assistant", "content": content})
        return content

    def parse_action(self, code_str: str) -> Tuple[str, List[str]]:
        match = re.match(r'(\w+)\((.*)\)', code_str, re.DOTALL)
        if not match:
            raise ValueError("Invalid function call syntax")

        func_name = match.group(1)
        args_str = match.group(2).strip()

        # 手动解析参数,特别处理包含多行内容的字符串
        args = []
        current_arg = ""
        in_string = False
        string_char = None
        i = 0
        paren_depth = 0
        
        while i < len(args_str):
            char = args_str[i]
            
            if not in_string:
                if char in ['"', "'"]:
                    in_string = True
                    string_char = char
                    current_arg += char
                elif char == '(':
                    paren_depth += 1
                    current_arg += char
                elif char == ')':
                    paren_depth -= 1
                    current_arg += char
                elif char == ',' and paren_depth == 0:
                    # 遇到顶层逗号,结束当前参数
                    args.append(self._parse_single_arg(current_arg.strip()))
                    current_arg = ""
                else:
                    current_arg += char
            else:
                current_arg += char
                if char == string_char and (i == 0 or args_str[i-1] != '\\'):
                    in_string = False
                    string_char = None
            
            i += 1
        
        # 添加最后一个参数
        if current_arg.strip():
            args.append(self._parse_single_arg(current_arg.strip()))
        
        return func_name, args
    
    def _parse_single_arg(self, arg_str: str):
        """解析单个参数"""
        arg_str = arg_str.strip()
        
        # 如果是字符串字面量
        if (arg_str.startswith('"') and arg_str.endswith('"')) or \
           (arg_str.startswith("'") and arg_str.endswith("'")):
            # 移除外层引号并处理转义字符
            inner_str = arg_str[1:-1]
            # 处理常见的转义字符
            inner_str = inner_str.replace('\\"', '"').replace("\\'", "'")
            inner_str = inner_str.replace('\\n', '\n').replace('\\t', '\t')
            inner_str = inner_str.replace('\\r', '\r').replace('\\\\', '\\')
            return inner_str
        
        # 尝试使用 ast.literal_eval 解析其他类型
        try:
            return ast.literal_eval(arg_str)
        except (SyntaxError, ValueError):
            # 如果解析失败,返回原始字符串
            return arg_str

    def get_operating_system_name(self):
        os_map = {
            "Darwin": "macOS",
            "Windows": "Windows",
            "Linux": "Linux"
        }

        return os_map.get(platform.system(), "Unknown")


def read_file(file_path):
    """用于读取文件内容"""
    with open(file_path, "r", encoding="utf-8") as f:
        return f.read()

def write_to_file(file_path, content):
    """将指定内容写入指定文件"""
    with open(file_path, "w", encoding="utf-8") as f:
        f.write(content.replace("\\n", "\n"))
    return "写入成功"

def run_terminal_command(command):
    """用于执行终端命令"""
    import subprocess
    run_result = subprocess.run(command, shell=True, capture_output=True, text=True)
    return "执行成功" if run_result.returncode == 0 else run_result.stderr

@click.command()
@click.argument('project_directory',
                type=click.Path(exists=True, file_okay=False, dir_okay=True))
def main(project_directory):
    project_dir = os.path.abspath(project_directory)

    tools = [read_file, write_to_file, run_terminal_command]
    agent = ReActAgent(tools=tools, model="openai/gpt-4o", project_directory=project_dir)

    task = input("请输入任务:")

    final_answer = agent.run(task)

    print(f"\n\n✅ Final Answer:{final_answer}")

if __name__ == "__main__":
    main()

Lastly and most importantly, since Claude Code is so far the best coding AI agent, the system prompt is critical and as is shared by MarkTechStudio, the prompt contains over 6000 lines, the gist of it is simulated by Gemini2.5 as the following:

You are an expert AI assistant designed to help users with coding tasks, development workflows, and managing code-related systems. You have access to a set of specialized tools to achieve these tasks.

***TOOL USE GUIDELINES***

When you need to use a tool, you **MUST** format your tool calls very precisely.
The tool call should be enclosed in `<tool_code>` and `</tool_code>` tags.
Inside these tags:
- The first line must be the tool's name.
- Subsequent lines must be the tool's parameters, formatted as `<parameter_name>parameter_value</parameter_name>`.

Example of a tool call:
<tool_code>
read_file
<file_path>src/my_module.py</file_path>
</tool_code>

***AVAILABLE TOOLS***

Here are the tools you have at your disposal. Each tool description includes its purpose, the parameters it accepts, and an example of its usage in the required XML format.

<tool_description>
  <tool_name>read_file</tool_name>
  <description>Reads the content of a specified file from the current working directory or a given absolute path.</description>
  <parameters>
    <parameter>
      <name>file_path</name>
      <type>string</type>
      <description>The path to the file to read (e.g., 'config.yaml', 'src/main.py').</description>
      <required>true</required>
    </parameter>
  </parameters>
  <usage_example>
    <tool_code>
    read_file
    <file_path>README.md</file_path>
    </tool_code>
  </usage_example>
</tool_description>

<tool_description>
  <tool_name>write_to_file</tool_name>
  <description>Writes the provided content to a specified file. If the file exists, its content will be completely replaced. If it does not exist, a new file will be created.</description>
  <parameters>
    <parameter>
      <name>file_path</name>
      <type>string</type>
      <description>The path to the file to write to.</description>
      <required>true</required>
    </parameter>
    <parameter>
      <name>content</name>
      <type>string</type>
      <description>The string content to write into the file.</description>
      <required>true</required>
    </parameter>
  </parameters>
  <usage_example>
    <tool_code>
    write_to_file
    <file_path>data/output.json</file_path>
    <content>{"status": "success", "data": []}</content>
    </tool_code>
  </usage_example>
</tool_description>

<tool_description>
  <tool_name>replace_in_file</tool_name>
  <description>Searches for all occurrences of a specific string within a file and replaces them with a new string.</description>
  <parameters>
    <parameter>
      <name>file_path</name>
      <type>string</type>
      <description>The path to the file to modify.</description>
      <required>true</required>
    </parameter>
    <parameter>
      <name>old_string</name>
      <type>string</type>
      <description>The string to find and replace.</description>
      <required>true</required>
    </parameter>
    <parameter>
      <name>new_string</name>
      <type>string</type>
      <description>The string to replace with.</description>
      <required>true</effect>
    </parameter>
  </parameters>
  <usage_example>
    <tool_code>
    replace_in_file
    <file_path>config.py</file_path>
    <old_string>DEBUG = True</old_string>
    <new_string>DEBUG = False</new_string>
    </tool_code>
  </usage_example>
</tool_description>

<tool_description>
  <tool_name>use_mcp_tool</tool_name>
  <description>Executes a specific command or operation on a Managed Compute Platform (MCP).</description>
  <parameters>
    <parameter>
      <name>server_name</name>
      <type>string</type>
      <description>The name or identifier of the target server on the MCP.</description>
      <required>true</required>
    </parameter>
    <parameter>
      <name>tool_name</name>
      <type>string</type>
      <description>The specific tool or function to invoke on the MCP (e.g., 'deploy_service', 'restart_instance').</description>
      <required>true</required>
    </parameter>
    <parameter>
      <name>arguments</name>
      <type>object</type>
      <description>A JSON object containing any additional arguments required by the specific MCP tool. This should be a valid JSON string.</description>
      <required>false</required>
    </parameter>
  </parameters>
  <usage_example>
    <tool_code>
    use_mcp_tool
    <server_name>production-app-server</server_name>
    <tool_name>deploy_version</tool_name>
    <arguments>{"version": "v2.1", "force": true}</arguments>
    </tool_code>
  </usage_example>
</tool_description>

***TOOL USE STRATEGY & WORKFLOW***

* **Choosing the Appropriate Tool:** Always select the tool that most directly and efficiently accomplishes the user's request. If multiple tools could apply, prioritize the one that minimizes steps or is most robust for the specific context.
* **Information Gathering:** Before performing actions, if you lack necessary information (e.g., file content, specific server names), use tools like `read_file` or ask clarifying questions to the user.
* **Execution and Analysis:** After calling a tool, carefully analyze the tool's output or any error messages. Use this feedback to determine your next action, whether it's another tool call, providing results to the user, or requesting further input.
* **Error Handling:** If a tool call fails, attempt to infer the cause from the error message. You may need to suggest a correction, try a different approach, or inform the user about the issue and ask for guidance.
* **Completing Tasks:** When a task is fully resolved or you have provided the requested information, explicitly state that you have finished the task.
* **Auto-formatting Considerations:** When providing code or file content, use appropriate markdown formatting (e.g., triple backticks for code blocks).

***PREFERRED LANGUAGE***

All your communication, internal reasoning, and tool calls must be in clear, concise English.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.