跳到主要内容从零构建 Python AI Agent:原理与实战 | 极客日志PythonAI算法
从零构建 Python AI Agent:原理与实战
使用 Python 从零构建基于 ReAct 范式的 AI Agent。涵盖环境配置、LLM 客户端封装、工具系统(计算器、文件读写、网络请求)、对话记忆管理及核心逻辑实现。通过实际代码示例,展示 Agent 如何自主规划步骤、调用工具并完成任务,并提供扩展优化建议及常见问题排查指南。
29244083716 浏览 从零构建 Python AI Agent:原理与实战
为什么现在要关注 Agent?
如果说 2024 年是大模型的元年,那么当下正是 AI Agent 走向实用的关键节点。现在的 AI 不再只是简单的问答机器人,它开始接管 IDE、终端甚至整个工作流。GitHub 上相关仓库数量激增,Agent 框架类项目增速尤为明显。
很多人对 Agent 的理解还停留在'ChatGPT 加插件'的阶段。这篇文章的目标是把 Agent 的底层逻辑讲透,带你从零写出一个真正能用的 Agent,理解每一行代码在做什么,而不是仅仅调用现成框架。
一、AI Agent 到底是什么?
1.1 普通 LLM 调用 vs Agent
普通 LLM 调用是单向的:
用户输入 → 大模型 → 输出结果
一问一答,模型不会主动做任何事。
AI Agent 则是闭环的:
用户给目标 → Agent 自主规划步骤 → 调用工具执行 → 观察结果 → 继续规划 → ... → 完成目标
核心在于一个循环:思考(Think)→ 行动(Act)→ 观察(Observe),不断迭代直到任务完成。这个范式被称为 ReAct(Reasoning + Acting)。
1.2 Agent 的三个核心组件
| 组件 | 作用 | 类比 |
|---|
| 大脑(LLM) | 负责推理、规划、决策 | 人的大脑 |
| 工具(Tools) | 执行具体操作(搜索、计算、读写文件等) | 人的双手 |
| 记忆(Memory) | 存储历史对话和中间结果 | 人的记忆 |
理解了这三个组件,你就掌握了 Agent 的本质。
1.3 本教程目标
我们将构建一个任务助手 Agent,它能够:
- 接收自然语言任务描述
- 自主拆解任务步骤
- 调用工具(计算器、文件读写、网络请求)
- 根据工具返回结果调整计划
- 最终输出完整的任务结果
二、环境准备
2.1 安装依赖
建议创建虚拟环境以避免依赖冲突。
python -m venv agent-env
source agent-env/bin/activate
pip install openai python-dotenv requests colorama
python -c "import openai; print('OpenAI SDK 安装成功')"
💡 提示:如果没有 OpenAI API Key,可以使用国内兼容接口(如 DeepSeek、智谱 GLM),只需修改 即可。
base_url
2.2 项目结构初始化
mkdir my-agent && cd my-agent
mkdir -p src/{tools,memory,core}
mkdir -p tests
mkdir -p logs
touch src/__init__.py
touch src/tools/__init__.py
touch src/memory/__init__.py
touch src/core/__init__.py
touch .env
touch main.py
my-agent/
├── src/
│ ├── core/
│ │ ├── __init__.py
│ │ ├── agent.py
│ │ └── llm_client.py
│ ├── tools/
│ │ ├── __init__.py
│ │ ├── calculator.py
│ │ ├── file_tool.py
│ │ └── web_tool.py
│ └── memory/
│ ├── __init__.py
│ └── conversation.py
├── tests/
├── logs/
├── .env
└── main.py
2.3 配置 API Key
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
OPENAI_BASE_URL=https://api.openai.com/v1
MODEL_NAME=gpt-4o-mini
三、构建核心组件
3.1 封装 LLM 客户端
新建 src/core/llm_client.py,统一管理大模型调用。
""" LLM 客户端封装
统一管理大模型调用,支持多种 API 提供商
"""
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
class LLMClient:
"""大模型调用客户端"""
def __init__(self):
self.client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"),
)
self.model = os.getenv("MODEL_NAME", "gpt-4o-mini")
self.total_tokens = 0
def chat(self, messages: list, temperature: float = 0.7) -> str:
"""发送对话请求
Args:
messages: 对话历史,格式为 [{"role": "user/assistant/system", "content": "..."}]
temperature: 温度参数,越高越随机(0~2)
Returns:
模型回复的文本内容
"""
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
)
self.total_tokens += response.usage.total_tokens
return response.choices[0].message.content
def get_token_usage(self) -> dict:
"""获取 token 使用统计"""
return {"total_tokens": self.total_tokens, "model": self.model}
if __name__ == "__main__":
client = LLMClient()
messages = [
{"role": "system", "content": "你是一个有帮助的助手。"},
{"role": "user", "content": "你好,请用一句话介绍自己。"},
]
reply = client.chat(messages)
print(f"模型回复:{reply}")
print(f"Token 消耗:{client.get_token_usage()}")
3.2 构建工具系统
Agent 的能力来自工具。我们先实现三个最常用的工具。
工具一:计算器
""" 计算器工具
让 Agent 能够执行数学计算,避免大模型的计算幻觉
"""
import math
class CalculatorTool:
"""安全的数学计算工具"""
name = "calculator"
description = (
"执行数学计算。输入一个数学表达式字符串,返回计算结果。"
"支持:加减乘除、幂运算、开方、三角函数等。"
"示例输入:'2 + 3 * 4'、'sqrt(16)'、'sin(3.14/2)'"
)
SAFE_FUNCTIONS = {
"abs": abs, "round": round, "sqrt": math.sqrt, "pow": math.pow,
"sin": math.sin, "cos": math.cos, "tan": math.tan,
"log": math.log, "log10": math.log10, "log2": math.log2,
"pi": math.pi, "e": math.e, "ceil": math.ceil, "floor": math.floor,
}
def run(self, expression: str) -> str:
"""执行数学计算
Args:
expression: 数学表达式字符串
Returns:
计算结果字符串,或错误信息
"""
try:
result = eval(expression, {"__builtins__": {}}, self.SAFE_FUNCTIONS)
return f"计算结果:{expression} = {result}"
except ZeroDivisionError:
return "错误:除数不能为零"
except Exception as e:
return f"计算错误:{str(e)},请检查表达式格式"
if __name__ == "__main__":
calc = CalculatorTool()
print(calc.run("2 + 3 * 4"))
print(calc.run("sqrt(144)"))
print(calc.run("sin(pi/2)"))
print(calc.run("log(e)"))
工具二:文件读写
""" 文件读写工具
让 Agent 能够读取和写入本地文件
"""
import os
class FileTool:
"""文件操作工具"""
name = "file_tool"
description = (
"读取或写入本地文件。"
"操作类型:'read'(读取文件内容)或 'write'(写入内容到文件)。"
"输入格式:'read:文件路径' 或 'write:文件路径:文件内容'"
)
ALLOWED_DIR = "./workspace"
def __init__(self):
os.makedirs(self.ALLOWED_DIR, exist_ok=True)
def run(self, command: str) -> str:
"""执行文件操作
Args:
command: 操作命令,格式见 description
Returns:
操作结果字符串
"""
parts = command.split(":", 2)
if len(parts) < 2:
return "错误:命令格式不正确,请使用 'read:路径' 或 'write:路径:内容'"
action = parts[0].strip().lower()
file_path = os.path.join(self.ALLOWED_DIR, parts[1].strip())
if not os.path.abspath(file_path).startswith(os.path.abspath(self.ALLOWED_DIR)):
return "错误:不允许访问工作目录以外的文件"
if action == "read":
return self._read_file(file_path)
elif action == "write":
if len(parts) < 3:
return "错误:写入操作需要提供文件内容"
content = parts[2]
return self._write_file(file_path, content)
else:
return f"错误:不支持的操作类型 '{action}',请使用 'read' 或 'write'"
def _read_file(self, path: str) -> str:
"""读取文件"""
if not os.path.exists(path):
return f"错误:文件 '{path}' 不存在"
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
return f"文件内容:\n{content}"
except Exception as e:
return f"读取失败:{str(e)}"
def _write_file(self, path: str, content: str) -> str:
"""写入文件"""
try:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"成功写入文件:{path}({len(content)} 字符)"
except Exception as e:
return f"写入失败:{str(e)}"
if __name__ == "__main__":
tool = FileTool()
print(tool.run("write:test.txt:Hello, Agent World!"))
print(tool.run("read:test.txt"))
工具三:网络请求
""" 网络请求工具
让 Agent 能够获取网页内容(简化版)
"""
import requests
from urllib.parse import urlparse
import re
class WebTool:
"""网络请求工具"""
name = "web_tool"
description = (
"获取指定 URL 的网页文本内容。"
"输入一个完整的 URL(需包含 http:// 或 https://),"
"返回页面的纯文本内容(前 2000 字符)。"
"示例:'https://example.com'"
)
TIMEOUT = 10
MAX_CONTENT_LENGTH = 2000
def run(self, url: str) -> str:
"""获取网页内容
Args:
url: 目标 URL
Returns:
网页文本内容或错误信息
"""
parsed = urlparse(url.strip())
if parsed.scheme not in ("http", "https"):
return "错误:URL 必须以 http:// 或 https:// 开头"
try:
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36"
)
}
response = requests.get(url, headers=headers, timeout=self.TIMEOUT)
response.raise_for_status()
response.encoding = response.apparent_encoding
text = self._strip_html(response.text)
truncated = text[: self.MAX_CONTENT_LENGTH]
return f"网页内容(前{self.MAX_CONTENT_LENGTH}字符):\n{truncated}"
except requests.exceptions.Timeout:
return f"错误:请求超时(>{self.TIMEOUT}秒)"
except requests.exceptions.HTTPError as e:
return f"错误:HTTP 请求失败,状态码 {e.response.status_code}"
except Exception as e:
return f"请求失败:{str(e)}"
def _strip_html(self, html: str) -> str:
"""简单去除 HTML 标签"""
html = re.sub(r"<(script|style)[^>]*>.*?</\1>", "", html, flags=re.DOTALL)
html = re.sub(r"<[^>]+>", " ", html)
html = re.sub(r"\s+", " ", html).strip()
return html
3.3 构建对话记忆
新建 src/memory/conversation.py,管理上下文历史。
""" 对话记忆模块
管理 Agent 的上下文历史,支持长度限制和摘要压缩
"""
class ConversationMemory:
"""对话历史管理"""
def __init__(self, max_turns: int = 20, system_prompt: str = ""):
"""
Args:
max_turns: 最大保留的对话轮数(超出后自动裁剪旧记录)
system_prompt: 系统提示词
"""
self.max_turns = max_turns
self.system_prompt = system_prompt
self._history: list[dict] = []
def add_message(self, role: str, content: str):
"""添加一条消息到历史
Args:
role: 角色,'user' / 'assistant' / 'system'
content: 消息内容
"""
self._history.append({"role": role, "content": content})
non_system = [m for m in self._history if m["role"] != "system"]
if len(non_system) > self.max_turns * 2:
for i, msg in enumerate(self._history):
if msg["role"] == "user":
self._history.pop(i)
if i < len(self._history) and self._history[i]["role"] == "assistant":
self._history.pop(i)
break
def get_messages(self) -> list[dict]:
"""获取完整的消息列表(包含 system prompt)
Returns:
适合直接传给 LLM 的消息列表
"""
messages = []
if self.system_prompt:
messages.append({"role": "system", "content": self.system_prompt})
messages.extend(self._history)
return messages
def clear(self):
"""清空对话历史"""
self._history.clear()
def __len__(self) -> int:
return len(self._history)
def __repr__(self) -> str:
return f"ConversationMemory(turns={len(self._history)//2}, max={self.max_turns})"
四、实现 Agent 核心逻辑
4.1 ReAct Agent 实现
新建 src/core/agent.py,思路遵循 Thought(思考)→ Action(行动)→ Observation(观察)→ 循环。
""" ReAct Agent 核心实现
思路:Thought(思考)→ Action(行动)→ Observation(观察)→ 循环
"""
import re
import json
from colorama import Fore, Style, init
from src.core.llm_client import LLMClient
from src.memory.conversation import ConversationMemory
from src.tools.calculator import CalculatorTool
from src.tools.file_tool import FileTool
from src.tools.web_tool import WebTool
init(autoreset=True)
SYSTEM_PROMPT = """你是一个智能任务助手,能够通过调用工具来完成用户交给你的任务。
## 你拥有以下工具:{tool_descriptions}
## 工作流程(严格遵守):
每次回复必须按照以下格式,直到任务完成:
Thought: \[分析当前情况,思考下一步该做什么\]
Action: \[工具名称\]
Action Input: \[工具的输入参数\]
当工具返回结果后,你会收到:
Observation: \[工具返回的结果\]
然后继续思考,直到任务完成,最后输出:
Thought: \[最终思考,确认任务已完成\]
Final Answer: \[给用户的最终回答\]
## 重要规则:
1. 每次只能调用一个工具
2. 如果不需要工具,直接输出 Final Answer
3. 遇到计算问题,必须使用 calculator 工具,不要自己心算
4. Action 字段只能填写工具名称,不能有其他内容
5. 如果工具返回错误,分析原因并尝试修正后重试
"""
class ReActAgent:
"""基于 ReAct 范式的 AI Agent
ReAct = Reasoning(推理)+ Acting(行动)
核心循环:Thought → Action → Observation → Thought → ...
"""
MAX_ITERATIONS = 10
def __init__(self):
self.tools = {
"calculator": CalculatorTool(),
"file_tool": FileTool(),
"web_tool": WebTool(),
}
tool_descriptions = "\n".join(
f"- **{name}**:{tool.description}" for name, tool in self.tools.items()
)
self.llm = LLMClient()
self.memory = ConversationMemory(
max_turns=20,
system_prompt=SYSTEM_PROMPT.format(tool_descriptions=tool_descriptions),
)
def run(self, task: str) -> str:
"""执行一个任务
Args:
task: 用户的任务描述(自然语言)
Returns:
Agent 的最终回答
"""
print(f"\n{Fore.CYAN}{'='*60}")
print(f"🤖 任务开始:{task}")
print(f"{'='*60}{Style.RESET_ALL}\n")
self.memory.add_message("user", task)
for iteration in range(1, self.MAX_ITERATIONS + 1):
print(f"{Fore.YELLOW}── 第 {iteration} 轮迭代 ──{Style.RESET_ALL}")
response = self.llm.chat(self.memory.get_messages())
print(f"{Fore.GREEN}LLM 输出:\n{response}{Style.RESET_ALL}\n")
parsed = self._parse_response(response)
if parsed["type"] == "final_answer":
self.memory.add_message("assistant", response)
final = parsed["content"]
print(f"\n{Fore.CYAN}{'='*60}")
print(f"✅ 任务完成!")
print(f"最终答案:{final}")
print(f"Token 消耗:{self.llm.get_token_usage()}")
print(f"{'='*60}{Style.RESET_ALL}\n")
return final
elif parsed["type"] == "action":
tool_name = parsed["tool"]
tool_input = parsed["input"]
print(f"{Fore.MAGENTA}🔧 调用工具:{tool_name}")
print(f" 输入:{tool_input}{Style.RESET_ALL}")
observation = self._execute_tool(tool_name, tool_input)
print(f"{Fore.BLUE}📋 工具返回:{observation}{Style.RESET_ALL}\n")
self.memory.add_message("assistant", response)
self.memory.add_message(
"user", f"Observation: {observation}"
)
else:
print(f"{Fore.RED}⚠️ 输出格式解析失败,提示 LLM 修正{Style.RESET_ALL}")
self.memory.add_message("assistant", response)
self.memory.add_message(
"user", "你的输出格式不正确。请严格按照 Thought/Action/Action Input 或 Final Answer 格式回复。",
)
return "任务未能在规定步骤内完成,请尝试简化任务描述。"
def _parse_response(self, response: str) -> dict:
"""解析 LLM 的输出,提取 Action 或 Final Answer
Returns:
{"type": "action", "tool": "...", "input": "..."}
{"type": "final_answer", "content": "..."}
{"type": "unknown"}
"""
final_match = re.search(
r"Final Answer:\s*(.+?)(?:\n|$)", response, re.DOTALL
)
if final_match:
return {"type": "final_answer", "content": final_match.group(1).strip()}
action_match = re.search(r"Action:\s*(\w+)", response)
input_match = re.search(
r"Action Input:\s*(.+?)(?:\nThought|\nAction|\nObservation|$)", response, re.DOTALL,
)
if action_match and input_match:
return {
"type": "action",
"tool": action_match.group(1).strip(),
"input": input_match.group(1).strip(),
}
return {"type": "unknown"}
def _execute_tool(self, tool_name: str, tool_input: str) -> str:
"""执行指定工具
Args:
tool_name: 工具名称
tool_input: 工具输入
Returns:
工具执行结果
"""
if tool_name not in self.tools:
available = ", ".join(self.tools.keys())
return f"错误:工具 '{tool_name}' 不存在。可用工具:{available}"
try:
return self.tools[tool_name].run(tool_input)
except Exception as e:
return f"工具执行异常:{str(e)}"
def reset(self):
"""重置 Agent 状态(清空对话历史)"""
self.memory.clear()
print("Agent 状态已重置")
五、组装并运行
5.1 创建入口文件
""" AI Agent 入口文件 """
from src.core.agent import ReActAgent
def main():
agent = ReActAgent()
print("="*60)
print("🤖 AI Agent 已启动!输入 'quit' 退出,'reset' 重置对话")
print("="*60)
while True:
try:
task = input("\n📝 请输入任务:").strip()
except (KeyboardInterrupt, EOFError):
print("\n\n👋 再见!")
break
if not task:
continue
if task.lower() == "quit":
print("👋 再见!")
break
if task.lower() == "reset":
agent.reset()
continue
agent.run(task)
if __name__ == "__main__":
main()
5.2 运行!
============================================================
🤖 AI Agent 已启动!输入 'quit' 退出,'reset' 重置对话
============================================================
📝 请输入任务:计算 (123 + 456) * 789 的结果,然后把结果写入 result.txt 文件
============================================================
🤖 任务开始:计算 (123 + 456) * 789 的结果,然后把结果写入 result.txt 文件
============================================================
── 第 1 轮迭代 ──
LLM 输出:
Thought: 用户需要我先计算数学表达式,再把结果写入文件。先用计算器工具计算。
Action: calculator
Action Input: (123 + 456) * 789
🔧 调用工具:calculator
输入:(123 + 456) * 789
📋 工具返回:计算结果:(123 + 456) * 789 = 466731
── 第 2 轮迭代 ──
LLM 输出:
Thought: 计算结果是 466731,现在把这个结果写入 result.txt 文件。
Action: file_tool
Action Input: write:result.txt:计算结果:(123 + 456) * 789 = 466731
🔧 调用工具:file_tool
输入:write:result.txt:计算结果:(123 + 456) * 789 = 466731
📋 工具返回:成功写入文件:./workspace/result.txt(26 字符)
── 第 3 轮迭代 ──
LLM 输出:
Thought: 两个任务都完成了,可以给出最终答案。
Final Answer: 计算结果为 466731,已成功写入 result.txt 文件。
============================================================
✅ 任务完成!
最终答案:计算结果为 466731,已成功写入 result.txt 文件。
Token 消耗:{'total_tokens': 847, 'model': 'gpt-4o-mini'}
============================================================
六、扩展与优化
6.1 添加更多工具
工具系统是 Agent 能力的上限。你可以轻松扩展更多工具:
class TimeTool:
name = "get_time"
description = "获取当前的日期和时间。无需输入参数,直接调用即可。"
def run(self, _: str = "") -> str:
from datetime import datetime
now = datetime.now()
return f"当前时间:{now.strftime('%Y年%m月%d日 %H:%M:%S')}"
class WeatherTool:
name = "get_weather"
description = "查询指定城市的天气。输入城市名称(中文),返回天气信息。"
def run(self, city: str) -> str:
import requests
try:
url = f"https://wttr.in/{city}?format=3&lang=zh"
resp = requests.get(url, timeout=5)
return f"天气信息:{resp.text.strip()}"
except Exception as e:
return f"天气查询失败:{str(e)}"
self.tools["get_time"] = TimeTool()
self.tools["get_weather"] = WeatherTool()
6.2 添加流式输出
def chat_stream(self, messages: list):
"""流式输出版本"""
stream = self.client.chat.completions.create(
model=self.model,
messages=messages,
stream=True,
)
full_response = ""
for chunk in stream:
delta = chunk.choices[0].delta.content or ""
print(delta, end="", flush=True)
full_response += delta
print()
return full_response
6.3 持久化对话历史
import json
import os
class PersistentMemory(ConversationMemory):
"""支持持久化的对话记忆"""
def __init__(self, save_path: str = "./logs/memory.json", **kwargs):
super().__init__(**kwargs)
self.save_path = save_path
self._load()
def add_message(self, role: str, content: str):
super().add_message(role, content)
self._save()
def _save(self):
os.makedirs(os.path.dirname(self.save_path), exist_ok=True)
with open(self.save_path, "w", encoding="utf-8") as f:
json.dump(self._history, f, ensure_ascii=False, indent=2)
def _load(self):
if os.path.exists(self.save_path):
with open(self.save_path, "r", encoding="utf-8") as f:
self._history = json.load(f)
print(f"已加载 {len(self._history)} 条历史记录")
七、常见问题 & 排错指南
Q:运行报错 AuthenticationError?
检查 .env 文件中的 API Key 是否正确,注意不要有多余的空格或引号。
Q:Agent 陷入死循环,一直调用同一个工具?
通常是 system prompt 写得不够清晰,或者工具描述有歧义。可以在 system prompt 中加一条:"如果同一个工具连续调用 3 次仍然失败,请直接输出 Final Answer 说明原因。"
Q:LLM 输出格式不对,解析总是失败?
换用更强的模型(如 gpt-4o 替代 gpt-4o-mini),或者在 system prompt 中加入更多格式示例(few-shot)。
- 对工具结果加缓存(相同输入直接返回缓存结果)
- 对不依赖顺序的工具调用,改为并行执行
- 减少 MAX_ITERATIONS,强制 Agent 更高效地规划
Q:想用本地模型(Ollama)怎么接入?
Ollama 完全兼容 OpenAI API 格式,只需修改 .env:
OPENAI_API_KEY=ollama
OPENAI_BASE_URL=http://localhost:11434/v1
MODEL_NAME=qwen2.5:7b
八、总结
- AI Agent 的核心概念(LLM + Tools + Memory)
- ReAct 范式(Thought → Action → Observation)
- 如何封装 LLM 调用客户端
- 如何设计和实现工具系统
- 如何管理对话上下文记忆
- 如何解析 LLM 的结构化输出
- 如何扩展 Agent 能力(添加新工具)
- RAG(检索增强生成):给 Agent 接入知识库,让它能回答私有文档的问题
- Multi-Agent:多个 Agent 协作完成复杂任务
- 长期记忆:用向量数据库存储历史,让 Agent 真正'记住'你
- Web Agent:给 Agent 接入浏览器控制能力,自动操作网页
- 异步 Agent:用
asyncio 实现并发工具调用,大幅提升效率
希望这篇教程能成为你探索 Agent 领域的起点。动手写起来,你会发现其中的乐趣远超想象。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online