代理型人工智能——让Ollama代您编写并运行Python代码,完全在本地完成
一款本地代理型 AI 代码生成器:Ollama 编写 Python 代码,通过代理循环运行它并读取输出结果。包括前置条件、项目结构以及完整的构建流程。
来源: Agentic AI — Let Ollama Write and Run Your Python, A Local Code Generator
本指南将逐步构建从原始材料到可运行系统的完整流程,适用于“代理型 AI——让 Ollama 编写并运行你的 Python 代码”以及“本地代码生成器”这两个项目。重点在于可操作的步骤、明确的检查点,以及无需猜测意图即可直接放入代码仓库的代码。 在概览阶段,应在修改代码之前明确输入参数、各步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行相应步骤,而无需猜测其中的隐藏状态。 可将此阶段视为输入与经过验证的输出之间的契约。为生成的成果命名,定义成功标准,并拒绝默许的半完成状态。
我们正在构建什么
在“我们正在构建什么”阶段工作时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及代币或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。 每次调用时都要记录请求ID、模型ID以及延迟时间。如果没有这些记录,间歇性的服务提供商错误就会被视为应用程序的故障。
前提条件
在完成前置条件阶段时,首先写下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将配置信息与应用程序代码分开存放。环境文件、密钥存储以及功能开关应集中于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 在每次调用时记录请求ID、模型ID以及延迟时间。如果没有这些记录,间歇性的服务错误就会被视为应用程序的故障。
ollama serve
ollama pull llama3.1:8b
pip install ollama
项目结构
在项目规划阶段,首先需明确合同条款:所需的输入参数、成功标志以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合约定。 同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续需要补充的功能。 每次调用时都要记录请求ID、模型ID以及响应延迟时间。如果没有这些记录,间歇性的服务错误就会被视为应用程序的缺陷。 在项目规划阶段,首先需明确合同条款:所需的输入参数、成功标志以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合约定。 将此阶段视为输入与验证后输出之间的契约。为相关组件命名,定义成功判定标准,杜绝无声的半完成状态。
╔═══════════╦════════════════════════════════════════════════════╗
║ File ║ Role ║
╠═══════════╬════════════════════════════════════════════════════╣
║ main.py ║ CLI, work-dir prompt / --dir, starts the loop ║
║ agent.py ║ Chat loop, tool execution, fake tool-call handling ║
║ tools.py ║ Read / write / run, plus a soft ~/repo path limit ║
║ config.py ║ Model name, iteration cap, system prompt ║
╚═══════════╩════════════════════════════════════════════════════╝
先配置(config.py)
将 Config first config py 阶段视为可度量的对象来处理时效果最佳。在扩大范围之前,先记录一份理想的测试结果、一个故障案例以及回滚说明。在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解这些成本信息,就能避免在从演示环境过渡到共享环境时出现意外费用。在讲解循环逻辑之前,先锁定解释器及依赖项的版本。笔记本电脑与持续集成环境之间的差异是 API 演示中最常见的隐性故障原因。
MODEL_NAME = "llama3.1:8b"
MAX_ITERATIONS = 15
SYSTEM_PROMPT = (
"You are an autonomous local AI assistant with file tools. "
"When creating or editing code, you MUST call write_local_file with the COMPLETE source in content. "
"Never write empty files. Never create a placeholder file first. "
"Always use relative filenames (for example 'calculator.py'). "
"Do not invent absolute paths like /repo/.... "
"Never describe or simulate tool calls in text, XML, JSON, or markdown - only use real tool calls. "
"Keep calling tools until the task is actually done; then reply with a short plain-text summary."
)
工具(tools.py)
将 py stage 这一工具视为可测量的界面使用效果最佳。在扩大范围之前,先记录一个成功的示例、一个失败案例以及回滚说明。 应将配置置于应用程序代码之外。环境文件、密钥存储和功能开关应集中存放,这样操作人员无需查看整个系统结构即可进行审计。 在编写循环代码之前,先锁定解释器及依赖项的版本。笔记本电脑与持续集成环境之间的差异是 API 演示中最常见的隐性故障原因。
read_local_file
将readlocalfile阶段视为可度量的测试对象时,其效果最佳。在扩大测试范围之前,需记录一份理想的执行日志、一个失败案例以及回滚说明。同时文档化正常流程与恢复流程,重试机制、人工审核环节以及错误处理都属于产品功能的一部分,而非后续需要补充的内容。在讲解循环逻辑之前,应先锁定解释器及依赖项的版本。笔记本电脑与持续集成环境之间的差异是API演示中最常见的隐性故障来源。将readlocalfile阶段视为输入与经过验证的输出之间的契约,为相关输出文件命名,明确成功标准,杜绝隐性部分完成的情况。
def read_local_file(filepath: str) -> str:
"""Reads and returns the contents of a local file.
Args:
filepath: The path to the file to read. Must be under ~/repo.
"""
try:
with open(_resolve_tool_path(filepath), "r") as f:
return f.read()
except Exception as e:
return f"Error reading file: {str(e)}"
write_local_file
在“写入本地文件”阶段,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境切换到共享环境时出现意外费用。应将客户端构建部分与消息循环分离,这样即便更换服务提供商,也无需重写对话状态机。
def write_local_file(filepath: str, content: str) -> str:
"""Writes or overwrites text content to a local file.
Args:
filepath: Target filename or path. Must be under ~/repo.
content: The text content to write inside the file. Must not be empty.
"""
try:
if content is None or not str(content).strip():
return (
"Error writing file: content is empty. "
"Call write_local_file again with the full file source in content."
)
path = _resolve_tool_path(filepath)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
f.write(content)
return f"Successfully wrote file to {path} ({len(content)} bytes)"
except Exception as e:
return f"Error writing file: {str(e)}"
run_python_script
在 runpythonscript 阶段,应在修改代码之前明确输入参数、该步骤的负责人以及终止条件。操作员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作员无需查看整个流程即可进行审计。 将客户端构建与消息循环分开,这样即便更换提供方,也无需重写对话状态机。
def run_python_script(filepath: str) -> str:
"""Executes a local Python file and returns STDOUT and STDERR.
Args:
filepath: The path to the Python file to run. Must be under ~/repo.
"""
try:
path = _resolve_tool_path(filepath)
result = subprocess.run(
["python3", str(path)],
capture_output=True,
text=True,
timeout=10,
cwd=str(_work_dir),
)
return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
except Exception as e:
return f"Execution error: {str(e)}"
注册
在注册阶段,修改代码之前需明确输入参数、该步骤的负责人以及退出标准。操作人员应能够从已知的检查点重新执行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理都是产品功能的一部分,而非后续需要补充的内容。 应将客户端构建逻辑与消息循环分离,这样在更换服务提供商时无需重写对话状态机。 在注册阶段,修改代码之前需明确输入参数、该步骤的负责人以及退出标准。操作人员应能够从已知的检查点重新执行该步骤,而无需猜测隐藏状态。 应将此阶段视为输入与验证后输出之间的契约。为相关产物命名,明确成功判定标准,并拒绝默许部分完成的情况。
REGISTERED_TOOLS = {
"read_local_file": read_local_file,
"write_local_file": write_local_file,
"run_python_script": run_python_script,
}
~/repo下的软路径限制
在处理阶段内的软路径限制时,首先需明确相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在路径从演示环境切换到共享环境时出现意外费用。 每次调用时都要记录请求ID、模型ID以及延迟时间。如果没有这些记录,间歇性的服务提供商错误就会被视为应用程序的故障。
ALLOWED_ROOT = (Path.home() / "repo").resolve()
_work_dir = ALLOWED_ROOT
def _under_repo(path: Path) -> Path:
resolved = path.expanduser().resolve()
try:
resolved.relative_to(ALLOWED_ROOT)
except ValueError as exc:
raise PermissionError(
f"Path '{resolved}' is outside allowed root '{ALLOWED_ROOT}'."
) from exc
return resolved
代理循环(agent.py)
在处理“代理循环”阶段时,首先需写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改不会偏离原有设计。 应将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 每次调用时都要记录请求ID、模型ID以及延迟时间。如果没有这些记录,偶尔出现的服务端错误就会被误认为是应用程序的故障。
# ollama api call
response = ollama.chat(
model=MODEL_NAME,
messages=messages,
tools=tools_list, # the Python functions from REGISTERED_TOOLS
)
message = response["message"]
messages.append(message)
tool_calls = message.get("tool_calls")
真正的工具调用是什么样的
在处理“真正的工具应如何调用”这一阶段时,首先需明确接口规范:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及错误处理方式都是产品本身的一部分,而非后续需要补充的功能。 每次调用时都要记录请求编号、模型编号以及响应延迟时间。如果没有这些记录,间歇性的服务错误就会被视为应用程序的缺陷。 在处理“真正的工具应如何调用”这一阶段时,首先需明确接口规范:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 应将这一阶段视为输入与经过验证的输出之间的契约。为相关组件命名,定义成功判定标准,并杜绝无声的半完成状态。
{
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"function": {
"name": "write_local_file",
"arguments": {
"filepath": "hello.py",
"content": "print('hi')\n"
}
}
}
]
}
}
for call in tool_calls:
func_name = call["function"]["name"]
func_args = call["function"]["arguments"]
if func_name in REGISTERED_TOOLS:
observation = REGISTERED_TOOLS[func_name](**func_args)
else:
observation = f"Error: Tool '{func_name}' is not registered."
messages.append({
"role": "tool",
"name": func_name,
"content": str(observation),
})
虚假tool_call的表现形式
将“虚假tool_call分析”环节视为可度量的指标会更有助于其发挥作用。在扩大范围之前,先记录一份理想的测试案例、一个失败案例以及回滚说明。 在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解成本情况,就能避免在从演示环境过渡到共享环境时出现意外费用。 在讲解循环逻辑之前,先锁定解释器及依赖项文件。笔记本电脑与持续集成环境之间的差异是API演示中最常见的隐性故障来源。
I'll create the file now.
<tool_call>
{"name": "write_local_file", "arguments": {"filepath": "hello.py", "content": "print('hi')"}}
</tool_call>
CONTINUE_NUDGE = (
"That response was not a valid tool call. "
"Call write_local_file now with the full file contents. "
"Do not write empty content. Do not describe the tool call in text."
)
if not tool_calls:
content = message.get("content") or ""
if _looks_like_fake_tool_call(content):
messages.append({"role": "user", "content": CONTINUE_NUDGE})
continue # next iteration; yes, this burns a turn
print(f"\n[Agent Completed Task]:\n{content}")
return
上下文剪枝
将上下文剪枝阶段视为可度量的对象来处理时,其效果最佳。在扩大范围之前,需记录一份理想运行示例、一个故障案例以及回滚说明。
def prune_context(messages: list, max_history: int = 6) -> list:
if len(messages) <= max_history + 2:
return messages
return messages[:2] + messages[-max_history:]p
命令行界面(main.py)
将命令行界面的main py阶段视为可度量的对象来处理时,其效果最佳。在扩大范围之前,需记录一份理想运行示例、一个故障案例以及回滚说明。
python main.py --dir demos/hello 'Create hello.py that prints Hello and run it to verify.'
运行方式
cd ~/repo/python-agent
python main.py 'Single-file tkinter calculator with digit buttons 0-9, + - * / = C. Show expression and result. No matplotlib. Write calculator.py only. After writing, fix any syntax or import errors. Prefer a complete working file; do not rely on running mainloop in the test harness.'
Files will be written under: /home/<user_name>/repo
Enter a directory under ~/repo for the program (relative path like 'demos/factorial', or absolute under ~/repo): calculator
Work directory: /home/<user_name>/repo/calculator
Starting agent with goal: Single-file tkinter calculator with digit buttons 0-9, + - * / = C. Show expression and result. No matplotlib. Write calculator.py only. After writing, fix any syntax or import errors. Prefer a complete working file; do not rely on running mainloop in the test harness.
[Iter 1] Executing Tool: write_local_file({'content': "import tkinter as tk\n...", 'filepath': 'calculator.py'})
-> Successfully wrote file to ... (N bytes)
[Agent Completed Task]:
This is a complete working single-file tkinter calculator with digit buttons 0-9, + - * / = C. The expression and result are shown in the entry field at the top. The 'C' button clears the entry field.
cd ~/repo/calculator
python calculator.py