1. 项目概述:当系统提示词遇上Markdown
如果你最近在折腾AI Agent或者大语言模型应用,尤其是关注过OpenClaw这类开源框架,那你大概率会听说过“系统提示词”这个概念。简单来说,它就是那个在对话开始前,你偷偷塞给AI的“小纸条”,用来定义它的角色、能力边界和行事规则。比如,“你是一个专业的代码助手,请用中文回答,代码要带注释”,这就是一个最简单的系统提示词。
传统的写法,往往就是一段冗长的、结构模糊的纯文本。维护起来像在维护一堵“文字墙”,想改个规则,得在一大段话里大海捞针。而Nanobot这个项目,作为OpenClaw的一个轻量级替代方案,它提出了一个让我眼前一亮的思路:用Markdown来驱动系统提示词。这可不是简单地把提示词写在Markdown文件里,而是深度利用Markdown的语法结构——标题、列表、代码块、引用块——来结构化地定义AI的整个行为逻辑和知识体系。
这就像是从用记事本写小说,升级到了用Scrivener或幕布这样的专业写作工具。Markdown天生的层级清晰、格式分明的特性,恰好完美匹配了系统提示词需要模块化、可读、易维护的需求。今天,我们就来深度解析Nanobot源码中,是如何实现这一巧妙设计的,看看它如何将一份.md文件,转化成一个AI Agent的“灵魂蓝图”。
2. 核心设计思路:为何选择Markdown作为载体?
在深入代码之前,我们必须先理解这个设计决策背后的“为什么”。这不仅仅是技术选型,更是一种工程哲学。
2.1 传统提示词编写的痛点
在我早期接触LLM应用开发时,系统提示词通常是一个巨大的字符串常量,被硬编码在Python或JavaScript文件里。它可能长这样:
system_prompt = """ 你是一个资深技术专家,擅长Python和系统架构。请遵循以下规则: 1. 回答必须准确、简洁。 2. 如果涉及代码,必须提供完整可运行的示例。 3. 对于不确定的问题,要明确说明。 4. 不要编造信息。 ...(后面还有二十条) """这种写法有几个致命伤:
- 可读性差:没有视觉层次,所有规则挤在一起,难以快速定位。
- 维护成本高:想调整第三条和第五条规则的顺序?或者为“代码示例”增加一个子规则?你不得不小心翼翼地在一大段文本中编辑,极易出错。
- 难以复用和组合:不同的技能或角色可能需要复用部分规则。从一大段文本中抽取特定部分,既麻烦又不优雅。
- 缺乏版本控制友好性:因为是一整段,任何微小的修改在Git diff中都会显示为一大片文本的变动,不利于审查。
2.2 Markdown带来的结构化优势
Nanobot的思路是将系统提示词视为一个结构化文档,而Markdown是描述这种结构最自然、最通用的语言。
- 标题 (
#,##) 定义模块与层级:一级标题可以代表核心角色(如# 数据分析助手),二级标题定义主要能力域(如## 数据清洗规则、## 可视化建议),三级标题则可以细化具体操作(如### 缺失值处理)。这种层级关系一目了然。 - 列表 (
-,1.) 枚举规则与步骤:用于清晰地列出行为准则、输入输出格式、检查清单等。有序列表适合表达有先后顺序的流程,无序列表适合表达并列的规则。 - 代码块 (```) 封装示例与模板:这是最关键的一环。你可以在提示词中直接嵌入高质量的输入输出示例(Few-shot Learning),或者定义严格的响应模板(JSON Schema描述)。代码块的语法高亮(即使对AI不可见)也极大地提升了人类开发者的阅读体验。
- 引用块 (
>) 强调核心原则与警告:用于突出最重要的全局性约束或警告信息,比如“绝对不可以执行任何危险操作”。 - 粗体/斜体进行重点强调:在自然语言描述中,对关键术语进行强调,引导AI的注意力。
通过这种方式,一份系统提示词.md文件,本身就是一个设计文档。开发者可以像写技术文档一样去设计和迭代AI的行为逻辑,而AI在解析时,也能利用这些结构信息更好地理解其职责的边界和组织方式。
实操心得:这种“文档即配置”的思想,在DevOps领域很常见(如Dockerfile, Ansible Playbook)。将其应用到AI提示工程中,极大地降低了认知负荷。我现在设计复杂Agent时,会先新建一个Markdown文档,用标题搭出框架,再往里面填充内容,思路非常清晰。
3. 源码解析:Markdown提示词的加载与解析流程
Nanobot的源码结构清晰,我们聚焦于其核心模块,看它如何实现从Markdown文件到内存中结构化提示词的转换。这个过程大致可以分为三步:文件加载 -> 语法解析 -> 上下文构建。
3.1 文件加载与资源定位
首先,Nanobot需要找到并读取你的Markdown提示词文件。相关代码通常位于nanobot/core/prompt_loader.py或类似的模块中。
# 示例性代码,展示核心逻辑 import os from pathlib import Path from typing import Optional class MarkdownPromptLoader: def __init__(self, prompt_dir: str = "./prompts"): self.prompt_dir = Path(prompt_dir) def load_system_prompt(self, name: str) -> str: """加载指定名称的系统提示词Markdown文件""" # 1. 构建文件路径,支持多种扩展名 possible_paths = [ self.prompt_dir / f"{name}.md", self.prompt_dir / f"{name}.markdown", self.prompt_dir / name, # name本身可能是完整路径 ] for path in possible_paths: if path.exists() and path.is_file(): # 2. 读取文件内容 with open(path, 'r', encoding='utf-8') as f: content = f.read() return content raise FileNotFoundError(f"System prompt '{name}' not found in {self.prompt_dir}")关键点解析:
- 灵活性:代码会尝试多个可能的路径和扩展名,这提高了容错性。你可以将提示词文件放在项目默认的
prompts目录下,也可以直接传入绝对路径。 - 编码:明确使用
utf-8编码读取,这是处理多语言提示词(中英文混合)的基础,避免了乱码问题。 - 错误处理:如果找不到文件,会抛出明确的异常,而不是返回空字符串或None,这有助于在启动阶段快速发现问题。
注意事项:在实际项目中,建议将所有的提示词模板集中放在一个目录(如
prompts/)下管理。可以进一步按功能子目录分类,如prompts/coder/,prompts/analyst/。Nanobot的加载器可以扩展为支持递归查找。
3.2 Markdown语法解析与结构化提取
仅仅读取文本是不够的,我们需要理解其结构。Nanobot并不会自己实现一个完整的Markdown解析器,那样太重了。它更可能利用现有的、轻量的库,比如Python的mistune或markdown,或者进行一种“最小化解析”。
其目的不是将Markdown渲染为HTML,而是提取出对构建提示词有用的结构化信息。我们来看一个假设的解析器核心:
import re from dataclasses import dataclass from typing import List @dataclass class PromptSection: level: int # 标题级别,如1,2,3 title: str # 标题文本 content: str # 该标题下的所有内容(直到下一个同级或更高级标题) children: List['PromptSection'] # 子章节 class MarkdownPromptParser: def __init__(self): # 用于匹配标题的正则表达式,例如:### 三级标题 self.heading_re = re.compile(r'^(#{1,6})\s+(.+)$', re.MULTILINE) def parse(self, markdown_text: str) -> PromptSection: """将Markdown文本解析成树形结构""" lines = markdown_text.split('\n') root = PromptSection(level=0, title="Root", content="", children=[]) stack = [root] # 使用栈来维护当前解析路径 current_content = [] for line in lines: heading_match = self.heading_re.match(line) if heading_match: # 遇到新标题,先将之前积累的内容保存到当前章节 if current_content: stack[-1].content = '\n'.join(current_content).strip() current_content = [] level = len(heading_match.group(1)) # '###' -> 3 title = heading_match.group(2).strip() # 调整栈,找到父节点 while stack[-1].level >= level: stack.pop() new_section = PromptSection(level=level, title=title, content="", children=[]) stack[-1].children.append(new_section) stack.append(new_section) else: # 非标题行,累积到当前章节内容 current_content.append(line) # 处理文件末尾的最后一部分内容 if current_content: stack[-1].content = '\n'.join(current_content).strip() return root关键点解析:
- 正则表达式解析:这里采用了一个相对简单但高效的方法,使用正则表达式匹配Markdown标题。
re.MULTILINE标志使得^和$能匹配每一行的开头结尾。 - 树形结构构建:解析的核心是构建一个树形结构(
PromptSection)。根节点(Level 0)代表整个文档,每个标题成为一个节点,其content包含从该标题开始到下一个同级或更高级标题之前的所有文本。 - 栈(Stack)的巧妙运用:这是算法关键。栈
stack始终保存着从根节点到当前正在解析的章节节点的路径。当遇到一个新标题时,通过while循环将栈中层级大于等于当前标题的节点弹出,直到找到父节点,然后将新节点挂载上去,并压入栈顶。这个过程自动处理了标题层级嵌套。 - 内容累积:所有非标题行都被累积到
current_content列表中,直到遇到下一个标题时,才将这些内容赋值给栈顶节点(即上一个标题对应的章节)。
通过这个解析过程,一份平面的Markdown文本就被转化成了一棵有层级的树。这棵树是后续进行动态提示词组装、条件化加载(例如只加载某个子模块的规则)的基础数据结构。
实操心得:这种基于正则的轻量解析,对于提示词模板来说通常足够了。它不追求处理所有Markdown边缘情况(比如行内代码、复杂的嵌套引用),因为我们的目标是提取人类可读的结构,而不是进行精确的渲染。如果提示词中需要包含非常复杂的Markdown,可以考虑换用
mistune这类库进行AST(抽象语法树)级别的解析,但复杂度会提高。
3.3 上下文构建与最终提示词组装
解析出结构树后,下一步就是将其与具体的对话上下文结合,生成最终发送给大语言模型的“系统消息”。这个过程可能涉及变量替换、模块选择和格式标准化。
假设我们有一个用于代码审查的Agent,其提示词code_review.md如下:
# 代码审查助手 你是团队中的资深代码审查员,负责审查Python代码。 ## 审查原则 - 优先关注安全性和性能。 - 提出建设性意见,避免指责性语言。 - 对于明显的错误,直接给出修改建议。 ## 审查模板 请按照以下格式输出审查结果: ```json { "score": 0-10, "summary": "总体评价", "issues": [ {"type": "bug|style|performance", "line": 行号, "comment": "具体问题描述"} ], "suggestions": ["具体的改进建议1", "建议2"] }示例
以下是一个审查示例: 用户代码:
def calc(a, b): return a + b你的审查输出应为:
{ "score": 6, "summary": "函数功能简单,但缺乏类型提示和错误处理。", "issues": [ {"type": "style", "line": 1, "comment": "建议为函数参数和返回值添加类型提示。"} ], "suggestions": ["考虑使用 `def calc(a: int, b: int) -> int:`", "增加对输入参数的校验。"] }Nanobot的上下文构建器需要做: 1. **变量替换**:提示词中可能包含像`{{user_name}}`、`{{today_date}}`这样的占位符。构建器需要从当前会话或配置中获取这些值并进行替换。 2. **条件化包含**:可能通过特定的指令或标签,实现动态包含或排除某些章节。例如,如果本次会话是“快速审查”,则可以跳过“## 示例”章节。 3. **格式标准化**:将树形结构重新扁平化为一个连贯的、格式良好的字符串。这里需要注意保留原有的Markdown格式符号(如`#`、`-`、```),因为像GPT-4这类模型,是能够理解并利用这些格式来更好地解析指令的。 ```python class PromptContextBuilder: def __init__(self, parsed_tree: PromptSection): self.tree = parsed_tree def build(self, context_vars: dict = None, include_sections: List[str] = None) -> str: """构建最终提示词字符串""" context_vars = context_vars or {} include_sections = include_sections or [] # 如果为空,则包含所有 final_parts = [] def dfs_collect(node: PromptSection, current_path: str): # 构建当前章节的完整路径标识,例如 “/审查原则” section_id = f"{current_path}/{node.title}" if current_path else node.title # 检查是否需要包含此章节 # 如果 include_sections 为空,或当前章节ID在包含列表中,则添加 should_include = not include_sections or any(section_id.startswith(inc) for inc in include_sections) if not should_include: return # 添加标题 if node.level > 0: final_parts.append('#' * node.level + ' ' + node.title) # 处理并添加内容(进行变量替换) if node.content: processed_content = node.content for key, value in context_vars.items(): placeholder = f"{{{{{key}}}}}" processed_content = processed_content.replace(placeholder, str(value)) final_parts.append(processed_content) # 递归处理子章节 for child in node.children: dfs_collect(child, section_id) # 从根节点的子节点开始(跳过虚拟的Root节点) for child in self.tree.children: dfs_collect(child, "") return '\n\n'.join(final_parts) # 用两个换行连接各部分,保持可读性关键点解析:
- 深度优先搜索(DFS):
dfs_collect函数递归地遍历整个提示词树。这是一种处理树形结构的标准方法。 - 条件化包含逻辑:
should_include逻辑允许根据include_sections参数动态过滤章节。section_id构建了一个简单的路径标识符(如/审查原则/审查模板),方便进行前缀匹配。这实现了提示词的模块化加载。 - 变量替换:在将每个章节的内容添加到最终结果前,会遍历
context_vars字典,替换所有类似{{var_name}}的占位符。这使得提示词可以动态化,例如“你好,{{user}},今天是{{date}},我将为你审查代码。” - 格式保留:最终使用
\n\n(两个换行)来连接各个部分,这符合Markdown的段落分隔习惯,能确保生成的结果既是AI可读的,也保持了对人类友好的格式。
最终,builder.build(context_vars={"user": "开发者张三"})将输出一个替换了变量、结构清晰的完整系统提示词字符串,可以直接放入LLM API的system参数中。
4. 高级特性与实战技巧
理解了基础流程后,我们来看看Nanobot可能在此基础上实现的一些高级特性,以及我们在实际使用中可以运用的技巧。
4.1 多文件组合与继承
复杂的Agent往往需要组合多个技能模块。Nanobot的Markdown驱动设计可以很自然地支持这一点。例如,你可以有一个base_agent.md定义通用行为准则,一个python_expert.md定义Python领域的专业知识,一个code_review.md定义代码审查的专项规则。
在加载时,解析器可以支持特殊的指令,比如:
# 我的全能技术助手 {{> base_agent.md}} ## Python专项能力 {{> python_expert.md}} ## 代码审查模式 当用户要求进行代码审查时,启用以下规则: {{> code_review.md}}解析器在遇到{{> file.md}}时,会递归地加载、解析并嵌入对应的文件内容。这实现了提示词的模块化、复用和继承,是管理大型提示词项目的关键。
实现思路:在PromptContextBuilder的build方法或解析器的早期阶段,需要增加一个预处理步骤,识别并处理这些“包含指令”,将它们替换为对应文件解析后的内容字符串。需要注意循环包含的问题,可以通过一个已加载文件集合来检测和避免。
4.2 基于上下文的动态章节选择
这是更精细的控制。除了在构建时通过参数include_sections硬性指定,还可以在提示词内部定义“条件块”。例如,根据用户输入的语言是Python还是JavaScript,自动包含不同的示例章节。
Markdown本身不支持逻辑,但我们可以通过约定特殊的注释语法来实现:
## 示例 <!-- if: language == "python" --> 以下是Python示例: ```python print("Hello")以下是JavaScript示例:
console.log("Hello");解析器在构建最终提示词时,需要评估这些条件表达式(`language == "python"`),表达式中的变量来自`context_vars`。只有条件为真的块才会被保留。 **实现思路**:这需要在解析内容时,不仅识别标题,还要识别这些自定义的指令注释。在`dfs_collect`过程中,维护一个当前上下文变量的作用域,并对每个条件块进行求值。这增加了复杂性,但带来了极大的灵活性。 ### 4.3 与Few-Shot Learning和Function Calling的深度结合 Markdown的代码块语法,为Few-Shot Learning(少样本学习)提供了天然的容器。如上文示例所示,你可以将高质量的输入输出对清晰地放在代码块中。解析器可以专门识别标记为`example`或`few-shot`的代码块,并在构建时确保它们以正确的格式呈现给模型。 更重要的是,对于支持Function Calling(函数调用)的模型,你可以在Markdown中直接描述工具(函数)的规范。例如,用一个JSON代码块来定义可供AI调用的数据查询工具: ````markdown ## 可用工具 你可以调用以下工具来获取数据: ```json { "tools": [ { "type": "function", "function": { "name": "query_database", "description": "根据SQL查询语句获取数据", "parameters": { "type": "object", "properties": { "sql": { "type": "string", "description": "合法的SELECT查询语句" } }, "required": ["sql"] } } } ] }解析器可以将这个代码块的内容提取出来,在调用LLM API时,不仅作为系统提示词的一部分,还可能将其单独提取出来,填充到API的`tools`参数中(如果所用API支持)。这实现了提示词与工具定义的统一管理。 > **避坑技巧**:在编写包含JSON Schema的代码块时,务必保证JSON格式的绝对正确。一个多余的逗号或缺失的引号都可能导致解析失败。建议先在专门的JSON验证器中校验,再粘贴到Markdown中。可以使用 `json.loads()` 在解析阶段进行验证,提前发现格式错误。 ## 5. 常见问题与排查实录 在实际使用Nanobot这类Markdown驱动提示词的框架时,你可能会遇到一些典型问题。以下是我在实践中的记录和解决方案。 ### 5.1 格式错乱导致AI理解偏差 **问题现象**:AI似乎忽略了某些规则,或者对指令的理解出现混乱。 **排查思路**: 1. **检查最终生成的提示词字符串**:这是第一步也是最重要的一步。在`PromptContextBuilder`的`build`方法末尾,将生成的字符串打印或记录到日志中。肉眼检查: - 标题层级是否正确?(`#`后面有空格吗?) - 列表的缩进是否一致?(建议使用2个空格进行缩进) - 代码块的开始和结束标记(```` ``` ````)是否配对?是否指定了语言? - 是否有意外的空行或特殊字符(如制表符`\t`)? 2. **简化测试**:创建一个极简的提示词文件,只包含一条核心规则和一个简单任务,看AI是否能正确响应。逐步增加复杂度,定位引入问题的格式点。 3. **模型差异**:不同的模型对Markdown格式的敏感度不同。GPT-4通常理解得很好,但一些较小的或专用模型可能表现不佳。如果怀疑是模型问题,尝试将Markdown转换为更朴素的纯文本格式(如去掉`#`,用`**规则:**`代替标题)进行对比测试。 **解决方案**: - 为项目制定一个**Markdown提示词编写规范**。例如:一级标题用`#`,二级用`##`;列表前统一用两个空格;代码块语言标识符必须写等等。并使用Prettier或markdownlint等工具在保存时自动格式化。 - 在解析器输出最终字符串后,可以增加一个可选的“格式化清理”步骤,自动修正常见的缩进和空格问题。 ### 5.2 变量替换失败或冲突 **问题现象**:`{{user_name}}`没有被替换,或者被替换成了错误的值。 **排查思路**: 1. **检查变量名**:确保上下文变量字典中的键(如`user_name`)与提示词中的占位符(`{{user_name}}`)完全匹配,包括大小写。 2. **检查变量值**:确保传递的值是字符串类型。如果是其他类型(如整数、对象),在替换前需要显式转换为`str`,否则可能在拼接时出错。 3. **冲突转义**:如果变量值本身包含`{{`或`}}`,会导致解析混乱。需要设计转义机制,例如用`\{\{`来表示字面量。 4. **作用域问题**:在多文件包含或条件块中,确认变量在哪个作用域下是有效的。 **解决方案**: - 在`PromptContextBuilder`的替换逻辑中,使用更健壮的模板引擎,如Python的`string.Template`(它使用`$var`语法)或`Jinja2`(功能强大但较重)。如果坚持用`{{}}`,可以先用正则匹配出所有占位符,再进行替换,避免嵌套问题。 - 在替换前对值进行`str()`转换和可能的HTML/特殊字符转义,防止注入问题(虽然提示词是文本,但安全第一)。 - 记录替换日志,输出类似“将 {{user_name}} 替换为 ‘张三’”的信息,便于调试。 ### 5.3 包含指令或条件指令解析错误 **问题现象**:`{{> another.md}}` 没有生效,或者条件块`<!-- if -->`被原样发送给了AI。 **排查思路**: 1. **指令语法**:确认你使用的指令语法与Nanobot版本支持的语法一致。是`{{>`还是`@include`?查看框架文档。 2. **文件路径**:被包含的文件路径是相对路径还是绝对路径?相对路径是相对于当前文件还是项目根目录?路径中不能有中文或特殊字符。 3. **循环包含**:A文件包含B,B文件又包含A,导致无限递归。解析器应检测到这种循环依赖并报错。 4. **条件表达式语法**:`language == "python"`,这个表达式是如何求值的?它是否支持复杂的逻辑运算?变量`language`是否已在上下文中定义? **解决方案**: - 实现解析器时,对于包含指令,维护一个“已解析文件”栈或集合。当要解析一个新文件时,检查其绝对路径是否已在集合中,如果在,则抛出异常,提示“循环包含依赖”。 - 对于条件表达式,可以集成一个简单的表达式求值器,或者直接使用Python的`eval()`函数(**注意:在生产环境中使用`eval()`有严重安全风险,必须严格限制可用的变量和函数名,或使用`ast.literal_eval`等安全替代方案**)。更安全的做法是只支持简单的变量相等性判断。 - 提供清晰的错误信息。当指令解析失败时,不要静默忽略,而是抛出包含文件名和行号的详细错误,例如“在文件 `main_prompt.md` 第12行:无法找到包含文件 `special_rules.md`”。 ### 5.4 性能问题与缓存策略 **问题现象**:每次请求都重新解析Markdown文件,在频繁调用或提示词文件很大时,导致响应延迟。 **排查思路**:这是典型的“空间换时间”问题。解析Markdown、构建树形结构、处理包含指令,这些操作都是CPU密集型的,尤其是当提示词文件达到数百KB时。 **解决方案**: - **实现缓存**:在`MarkdownPromptLoader`或`PromptContextBuilder`级别实现缓存。缓存键(Key)可以是`(文件路径, 文件最后修改时间戳)`的元组。如果文件未修改,则直接返回缓存的解析树。 - **分级缓存**: - 第一级:原始文件内容缓存。 - 第二级:解析后的语法树缓存。 - 第三级:根据不同上下文变量和包含章节参数组合构建的最终提示词字符串缓存。这一级缓存需要谨慎,因为参数组合可能很多。 - **预热缓存**:在应用启动时,主动加载和解析常用的提示词模板到缓存中。 - **监控与调优**:记录解析耗时,对于特别大或复杂的提示词文件,考虑将其拆分成更小的、可独立缓存的模块。 我个人在实践中,会为每个提示词模板计算一个MD5哈希值作为缓存键的一部分,只有当文件内容真正变化时,哈希值才会变,这比依赖文件修改时间更可靠。同时,我会设置一个合理的缓存过期时间或大小上限,防止内存无限增长。 ## 6. 总结与最佳实践建议 通过以上对Nanobot源码设计思路和实现细节的拆解,我们可以看到,用Markdown驱动系统提示词绝非简单的存储格式变化,而是一种提升AI应用可维护性、可读性和动态能力的工程实践。它将提示词从“魔法字符串”变成了可管理、可测试的代码资产。 回顾整个流程,从文件加载、结构化解析到上下文构建,每一个环节都体现了关注点分离和模块化设计的思想。这种设计使得我们可以: 1. **版本控制提示词**:`.md`文件可以很好地用Git管理,方便查看历史修改、进行Code Review。 2. **A/B测试**:可以轻松创建不同版本的提示词(`v1.md`, `v2.md`),进行效果对比。 3. **国际化**:可以为不同语言创建不同的Markdown文件(`prompt_zh.md`, `prompt_en.md`),运行时根据用户语言选择加载。 4. **与CI/CD集成**:可以将提示词文件的格式检查(linting)和基础验证纳入持续集成流程。 **给开发者的最佳实践建议**: - **从简单的纯文本提示词开始**:不要一开始就追求复杂的Markdown结构。先用一个简单的`.txt`或`.md`文件把核心指令写清楚、跑通流程。 - **渐进式结构化**:当规则超过10条,或者你发现自己在反复复制粘贴某些段落时,就是引入Markdown结构化的好时机。先用标题分模块,再用列表细化规则。 - **为代码块和变量命名**:给重要的代码块(如Few-Shot示例)一个简短的描述性标题作为注释,给变量起一个清晰的名字(如`{{customer_name}}`而非`{{name}}`)。 - **编写“提示词的提示词”**:可以创建一个标准的Markdown模板文件,里面写清楚“如何编写一个好的提示词”,包括章节结构建议、书写规范等,供团队参考。 - **测试、测试、再测试**:任何对提示词的修改,都要像测试代码一样进行测试。准备一组标准的测试用例(用户输入),观察AI的输出是否符合预期。可以将这些测试用例和预期输出也写成Markdown,放在项目里。 最后,记住工具是为人服务的。Nanobot提供的这种Markdown驱动模式,其最终目的是让我们能更高效、更可靠地与大语言模型协作。当你下次再面对一段需要精心雕琢的系统提示词时,不妨打开你的Markdown编辑器,用结构化的思维去构建它,你会发现,管理AI的行为,也可以像编写文档一样清晰有序。