目录

  1. 1. 前言
  2. 2. 什么是机器学习
  3. 3. 创建首个机器学习模型
  4. 4. 大语言模型基础
    1. 4.1. 语言模型与 Transformer 架构
    2. 4.2. 模型幻觉
  5. 5. 模型选择
    1. 5.1. 概念
      1. 5.1.1. 量化
      2. 5.1.2. 稀疏模型 vs 稠密模型
    2. 5.2. 本地
  6. 6. 提示工程
    1. 6.1. 模型采样参数
    2. 6.2. 样本提示
    3. 6.3. 指令调优
    4. 6.4. 基础提示技巧
    5. 6.5. 思维链
    6. 6.6. Prompt 设计
    7. 6.7. Prompt 框架
  7. 7. 记忆系统(Memory System)与检索增强生成(Retrieval-Augmented Generation, RAG)
  8. 8. 智能体(Agent)
    1. 8.1. 构成与运行原理
    2. 8.2. 运行机制
    3. 8.3. 感知与行动
    4. 8.4. 实现第一个智能体
    5. 8.5. 协作模式
      1. 8.5.1. 作为开发者工具
      2. 8.5.2. 作为自主协作者
      3. 8.5.3. Workflow 与 Agent 的差异
    6. 8.6. 正式构建智能体经典范式
      1. 8.6.1. 环境
      2. 8.6.2. 配置客户端
      3. 8.6.3. 封装基础 LLM 调用函数
      4. 8.6.4. ReAct 范式
      5. 8.6.5. 工具的定义与实现
  9. 9. MCP
    1. 9.1. hello world
    2. 9.2. IDA-PRO-MCP
  10. 10. 客户端
    1. 10.1. OpenWebUI

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

AI入门到入土

2024/5/5 DevOps
  |     |   总文章阅读量:

前言

之前做大创的时候有学习需求,没想到时隔一年半载后需要进行系统的学习了,AI 确实改变了许多东西 —— 20251120

参考:

https://developers.google.com/codelabs/tensorflow-1-helloworld?hl=zh-cn

https://github.com/Acmesec/theAIMythbook

https://github.com/datawhalechina/self-llm

https://datawhalechina.github.io/hello-agents/#/./README

https://www.zhihu.com/question/633365088

https://github.com/datawhalechina/all-in-rag?tab=readme-ov-file


什么是机器学习

考虑构建应用的传统方式,如下图所示:

image-20240505004015685

我们用编程语言定下”规则“(比如函数),然后再加入数据,这些规则会根据数据来执行操作,然后我们的程序就会提供一个”答案“(比如函数的返回值)

举个不知道恰不恰当的例子:1+2=3,其中 1、2 是我们的数据,而+是我们的规则,最后的 3 则是我们的答案

而机器学习检测活动状态的过程非常相似,只是轴线不同:

image-20240505004242535

我们只需要提供答案和数据,然后机器来推断出答案与数据之间关系的规则

沿用上面的例子:我们只提供了答案 3 和数据 1、2 ,然后让机器来推断从数据到答案之间的运算规则

而在实际的活动检测场景中会有如下表现:

image-20240505004632756

我们需要收集大量数据,并有效地标记其为“这是步行的样子”或“这是跑步的样子”。然后,计算机可以根据数据推断出决定某一特定活动的不同模式的规则

在传统编程中,代码被编译为通常称为程序的二进制文件。在机器学习中,通过数据和标签创建的项称为模型

于是把上面那张机器学习的图表修改一下,将操作的结果视为一个模型,在运行时就是如下所示:

image-20240505004744062

我们向模型传递一些数据,然后模型会使用从训练中推断的规则进行预测,例如,“该数据似乎表示在步行”或“该数据似乎表示在骑车”。


创建首个机器学习模型

观察下列数字之间的规律:

X -1 0 1 2 3 4
Y -2 1 4 7 10 13

我们正常计算的话可以得出 Y=3X+1 这样一个方程,即它们之间的关系

而把这个计算的步骤拆开来看的话,有这样的操作:

  • 注意到 X 的值从左向右依次增加 1,而 Y 的值相应地依次增加 3
  • 发现 Y 等于 3X 再加或减某一数字
  • X 为 0 时 Y 为 1
  • 最终得到 Y=3X+1

这就是利用代码训练模型来识别数据中模式的准确方式


大语言模型基础

语言模型与 Transformer 架构

概率统计😭

在深度学习兴起之前,统计方法是语言模型的主流。其核心思想是,一个句子出现的概率,等于该句子中每个词出现的条件概率的连乘

马尔科夫假设 (Markov Assumption) 核心思想:我们不必回溯一个词的全部历史,可以近似地认为,一个词的出现概率只与它前面有限的 n−1 个词有关


模型幻觉

模型幻觉(Hallucination)通常指的是大语言模型生成的内容与客观事实、用户输入或上下文信息相矛盾,或者生成了不存在的事实、实体或事件。

幻觉的本质是模型在生成过程中,过度自信地“编造”了信息,而非准确地检索或推理。根据其表现形式,幻觉可以被分为多种类型,例如:

  • 事实性幻觉 (Factual Hallucinations) : 模型生成与现实世界事实不符的信息。
  • 忠实性幻觉 (Faithfulness Hallucinations) : 在文本摘要、翻译等任务中,生成的内容未能忠实地反映源文本的含义。
  • 内在幻觉 (Intrinsic Hallucinations) : 模型生成的内容与输入信息直接矛盾。

解决幻觉的几种方法:

  1. 数据层面: 通过高质量数据清洗、引入事实性知识以及强化学习与人类反馈 (RLHF) 等方式,从源头减少幻觉。
  2. 模型层面: 探索新的模型架构,或让模型能够表达其对生成内容的不确定性。
  3. 推理与生成层面
    1. 检索增强生成 (Retrieval-Augmented Generation, RAG) : 这是目前缓解幻觉的有效方法之一。RAG 系统通过在生成之前从外部知识库(如文档数据库、网页)中检索相关信息,然后将检索到的信息作为上下文,引导模型生成基于事实的回答。
    2. 多步推理与验证: 引导模型进行多步推理,并在每一步进行自我检查或外部验证。
    3. 引入外部工具: 允许模型调用外部工具(如搜索引擎、计算器、代码解释器)来获取实时信息或进行精确计算。

模型选择

  • 性能与能力:这是最核心的考量。不同的模型擅长的任务不同,有的长于逻辑推理和代码生成,有的则在创意写作或多语言翻译上更胜一筹。您可以参考一些公开的基准测试排行榜(如 LMSys Chatbot Arena Leaderboard,大模型竞技场)来评估模型的综合能力。
  • 成本:对于闭源模型,成本主要体现在 API 调用费用,通常按 Token 数量计费。对于开源模型,成本则体现在本地部署所需的硬件(GPU、内存)和运维上。需要根据应用的预期使用量和预算做出选择。
  • 速度(延迟):对于需要实时交互的智能体(如客服、游戏 NPC),模型的响应速度至关重要。一些轻量级或经过优化的模型(如 GPT-3.5 Turbo, Claude 3.5 Sonnet)在延迟上表现更优。
  • 上下文窗口:模型能一次性处理的 Token 数量上限。对于需要理解长文档、分析代码库或维持长期对话记忆的智能体,选择一个拥有较大上下文窗口(如 128K Token 或更高)的模型是必要的。
  • 部署方式:使用 API 的方式最简单便捷,但数据需要发送给第三方,且受限于服务商的条款。本地部署则能确保数据隐私和最高程度的自主可控,但对技术和硬件要求更高。
  • 生态与工具链:一个模型的流行程度也决定了其周边生态的成熟度。主流模型通常拥有更丰富的社区支持、教程、预训练模型、微调工具和兼容的开发框架(如 LangChain, LlamaIndex, Hugging Face Transformers),这能极大地加速开发进程,降低开发难度。选择一个拥有活跃社区和完善工具链的模型,可以在遇到问题时更容易找到解决方案和资源。
  • 可微调性与定制化:对于需要处理特定领域数据或执行特定任务的智能体,模型的微调能力至关重要。一些模型提供了便捷的微调接口和工具,允许开发者使用自己的数据集对模型进行定制化训练,从而显著提升模型在特定场景下的性能和准确性。开源模型在这方面通常提供更大的灵活性。
  • 安全性与伦理:随着大语言模型的广泛应用,其潜在的安全风险和伦理问题也日益凸显。选择模型时,需要考虑其在偏见、毒性、幻觉等方面的表现,以及服务商或开源社区在模型安全和负责任AI方面的投入。对于面向公众或涉及敏感信息的应用,模型的安全性和伦理合规性是不可忽视的考量。

概念

量化

一种模型压缩技术,通过降低深度学习模型中权重和激活值的精度(如将32位浮点数转为8位整数或更低),在轻微牺牲模型精度的情况下,显著减少模型存储占用、加速计算速度并降低内存带宽需求,使得大模型能高效在边缘设备上运行。

模型量化的本质是通过数学映射,在精度损失可控的前提下,将模型参数从“高精度(FP32/FP16)”转换为“低精度”,实现性能与效率的平衡。一个类比:FP16像是用小数点后四位来记录一个测量值——3.1416。INT4像是只用一位整数——3。大多数时候,你说”大约3”和说”3.1416”的效果差不多,听的人都能理解你的意思。但如果你在做精密计算——比如火箭轨道或者金融衍生品定价——那1和3.1416之间的差距就是致命的。

模型量化目前主要是 GGUF、GPTQ、AWQ 三种方法,后两种只有 N 卡能用,所以主要我们的重心放在 GGUF 上(Mac 上有 MLX 这个独有的格式)

以 LLaMA-2 7B 为例,不同量化级别的 PPL(困惑度,困惑度越低,说明模型对文本的预测越准确)增量大致是这样的:

量化级别 文件大小 PPL 增量 质量评价
Q2_K 2.67 GB +0.87 极限压缩,损失明显,不推荐
Q3_K_M 3.06 GB +0.34 有损但可用,适合内存极小的设备
Q4_K_M 3.80 GB +0.14 最佳性价比,社区公认的默认选择
Q5_K_M 4.45 GB +0.05 高质量,内存充裕时首选
Q6_K 5.15 GB +0.02 接近无损
Q8_0 6.70 GB +0.004 几乎等同FP16

一些规律:精度降低了,信息一定会丢失。模型越大,量化越安全。


稀疏模型 vs 稠密模型

稀疏模型(MoE):以 Qwen3.5-35b-A3b 为例,其中有 300 多亿参数,但它内部被划分成了多个“专家”。每次预测下一个词时,只有负责该领域的“专家”(约 30 亿参数,即 3B)会被激活。因此生成 token 的速度很快

稠密模型:以 Qwen3.5-27b 为例,每次运行都会激活完整的 27B 参数,速度较慢


本地

考虑到本人的机器是 48g 的内存,选择 30b 左右的模型比较合适,这样还能剩出内存给 IDE、虚拟机、docker、浏览器等

大模型竞技场: https://lmarena.ai/zh/leaderboard/text

支持直接筛选开源模型进行对比


提示工程

如果我们把大语言模型比作一个能力极强的“大脑”,那么提示 (Prompt) 就是我们与这个“大脑”沟通的语言。提示工程,就是研究如何设计出精准的提示,从而引导模型产生我们期望输出的回复。对于构建智能体而言,一个精心设计的提示能让智能体之间协作分工变得高效。

模型采样参数

本质是通过调整模型对 “概率分布” 的采样策略,让输出匹配具体场景需求,配置合适的参数可以提升 Agent 在特定场景的性能。

Temperature:温度是控制模型输出 “随机性” 与 “确定性” 的关键参数。

  • 低温度(0 ⩽ Temperature < 0.3)时输出更 “精准、确定”。适用场景: 事实性任务:如问答、数据计算、代码生成;严谨性场景:法律条文解读、技术文档撰写、学术概念解释等场景。
  • 中温度(0.3 ⩽ Temperature < 0.7):输出 “平衡、自然”。适用场景: 日常对话:如客服交互、聊天机器人;常规创作:如邮件撰写、产品文案、简单故事创作。
  • 高温度(0.7 ⩽ Temperature < 2):输出 “创新、发散”。适用场景: 创意性任务:如诗歌创作、科幻故事构思、广告 slogan brainstorm、艺术灵感启发;发散性思考。

Top-k:原理是将所有 token 按概率从高到低排序,取排名前 k 个的 token 组成 “候选集”,随后对筛选出的 k 个 token 的概率进行 “归一化”

Top-p :其原理是将所有 token 按概率从高到低排序,从排序后的第一个 token 开始,逐步累加概率,直到累积和首次达到或超过阈值 p

在文本生成中,当同时设置 Top-p、Top-k 和温度系数时,这些参数会按照分层过滤的方式协同工作,其优先级顺序为:温度调整→Top-k→Top-p。

温度调整整体分布的陡峭程度,Top-k 会先保留概率最高的 k 个候选,然后 Top-p 会从 Top-k 的结果中选取累积概率≥p 的最小集合作为最终的候选集。不过,通常 Top-k 和 Top-p 二选一即可,若同时设置,实际候选集为两者的交集。

需要注意的是,如果将温度设置为 0,则 Top-k 和 Top-p 将变得无关紧要,因为最有可能的 Token 将成为下一个预测的 Token;如果将 Top-k 设置为 1,温度和 Top-p 也将变得无关紧要,因为只有一个 Token 通过 Top-k 标准,它将是下一个预测的 Token。

样本提示

根据我们给模型提供示例(Exemplar)的数量,提示可以分为三种类型。

  • **零样本提示 (Zero-shot Prompting)**:我们不给模型任何示例,直接让它根据指令完成任务。这得益于模型在海量数据上预训练后获得的强大泛化能力。
文本:Datawhale的AI Agent课程非常棒!
情感:正面
  • **单样本提示 (One-shot Prompting)**:我们给模型提供一个完整的示例,向它展示任务的格式和期望的输出风格。
文本:这家餐厅的服务太慢了。
情感:负面

文本:Datawhale的AI Agent课程非常棒!
情感:
  • 少样本提示 (Few-shot Prompting) :我们提供多个示例,这能让模型更准确地理解任务的细节、边界和细微差别,从而获得更好的性能。
文本:这家餐厅的服务太慢了。
情感:负面

文本:这部电影的情节很平淡。
情感:中性

文本:Datawhale的AI Agent课程非常棒!
情感:

指令调优

早期的 GPT 模型(如 GPT-3)主要是“文本补全”模型,它们擅长根据前面的文本续写,但不一定能很好地理解并执行人类的指令。(事实上,这个博客主题的早期魔改也是本人在 GPT-3 时期的产物)

指令调优 (Instruction Tuning) 是一种微调技术,它使用大量“指令-回答”格式的数据对预训练模型进行进一步的训练。经过指令调优后,模型能更好地理解并遵循用户的指令。我们今天日常工作学习中使用的所有模型(如 ChatGPTDeepSeekQwen)都是其模型家族中经过指令调优过的模型。

对“文本补全”模型的提示(你需要用少样本提示“教会”模型做什么):

这是一段将英文翻译成中文的程序。
英文:Hello
中文:你好
英文:How are you?
中文:

对“指令调优”模型的提示(你可以直接下达指令):

请将下面的英文翻译成中文:
How are you?

基础提示技巧

角色扮演 (Role-playing) 通过赋予模型一个特定的角色,我们可以引导它的回答风格、语气和知识范围,使其输出更符合特定场景的需求。

# 案例
你现在是一位资深的Python编程专家。请解释一下Python中的GIL(全局解释器锁)是什么,要让一个初学者也能听懂。

上下文示例 (In-context Example) 这与少样本提示的思想一致,通过在提示中提供清晰的输入输出示例,来“教会”模型如何处理我们的请求,尤其是在处理复杂格式或特定风格的任务时非常有效。

# 案例
我需要你从产品评论中提取产品名称和用户情感。请严格按照下面的JSON格式输出。

评论:这款“星尘”笔记本电脑的屏幕显示效果惊人,但我不太喜欢它的键盘手感。
输出:{"product_name": "星尘笔记本电脑", "sentiment": "混合"}

评论:我刚买的“声动”耳机音质很棒,续航也超出了我的预期!
输出:

思维链

对于需要逻辑推理、计算或多步骤思考的复杂问题,直接让模型给出答案往往容易出错。思维链 (Chain-of-Thought, CoT) 是一种强大的提示技巧,它通过引导模型“一步一步地思考”,提升了模型在复杂任务上的推理能力。

实现 CoT 的关键,是在提示中加入一句简单的引导语,如“请逐步思考”或“Let’s think step by step”。

# 思维链提示
一个篮球队在一个赛季的80场比赛中赢了60%。在接下来的赛季中,他们打了15场比赛,赢了12场。两个赛季的总胜率是多少?
请一步一步地思考并解答。

>>>
(模型可能会输出)
好的,我们来一步步计算。
第一步:计算第一个赛季赢得的比赛数。
80场 * 60% = 48场。
第二步:计算两个赛季的总比赛数和总胜利数。
总比赛数 = 80 + 15 = 95场。
总胜利数 = 48 + 12 = 60场。
第三步:计算总胜率。
总胜率 = (总胜利数 / 总比赛数) * 100% = (60 / 95) * 100% ≈ 63.16%。
所以,两个赛季的总胜率约为63.16%。

通过显式地展示其推理过程,模型不仅更容易得出正确的答案,也让它的回答变得更可信、更易于我们检查和纠正。

Prompt 设计

Prompt 是指你向 AI 输入的内容,它直接指示AI该做什么任务或生成什么样的输出,简而言之, Prompt 就是你与 AI 之间的“对话内容”,可以是问题、指令、描述或者任务要求,目的是引导AI进行特定的推理,生成或操作,从而得到预期的结果

Prompt 设计的质量直接决定 AI 输出的质量,一个好的 Prompt 能帮助 AI 快速理解任务要求,生成精准的结果;而一个模糊、模棱两可的 Prompt 会导致 AI 给出无关或错误的答案

例:

模糊 Prompt:分析一个漏洞并给出解决方案
AI 输出:
"漏洞可能是系统漏洞、配置漏洞或逻辑漏洞,建议排查并修复问题"
结果:输出笼统,缺乏具体性,难以直接应用

精准 Prompt:"针对一个电商平台的购物车功能,请分析可能存在的业务逻辑漏洞,并说明漏洞影响及修复方案"
AI 输出:
"可能的业务逻辑漏洞是购物车商品价格篡改攻击者通过抓包工具修改请求中的商品价格,支付时支付低于实际价格的金额影响包括财务损失和用户信任下降修复方案:在服务器端重新验证商品价格,确保最终支付金额以数据库为准"
结果:输出具体、实用,直接解决问题

Prompt 框架

框架 适用场景 核心要素 示例任务
Google 提示词 复杂漏洞分析、攻防模拟 逐步思考、任务分解、限定范围 Web应用漏洞分析
LangGPT 多变量、多阶段任务 模板、变量、角色 外部渗透测试
TAG 简单明确任务 任务、行动、目标 日志分析
COAST 信息全面复杂任务 背景、目标、行动、支持、技术 勒索病毒响应
APE 结果导向任务 行动、目的、期望 防火墙规则优化

记忆系统(Memory System)与检索增强生成(Retrieval-Augmented Generation, RAG)

根据认知心理学的研究,人类记忆可以分为以下几个层次:

  1. 感觉记忆(Sensory Memory):持续时间极短(0.5-3秒),容量巨大,负责暂时保存感官接收到的所有信息
  2. 工作记忆(Working Memory):持续时间短(15-30秒),容量有限(7±2个项目),负责当前任务的信息处理
  3. 长期记忆(Long-term Memory):持续时间长(可达终生),容量几乎无限,进一步分为:
    • 程序性记忆:技能和习惯(如骑自行车)
    • 陈述性记忆:可以用语言表达的知识,又分为:
      • 语义记忆:一般知识和概念(如”巴黎是法国首都”)
      • 情景记忆:个人经历和事件(如”昨天的会议内容”)

对于基于LLM的智能体而言,通常面临两个根本性局限:对话状态的遗忘内置知识的局限


智能体(Agent)

智能体被定义为任何能够通过 传感器(Sensors) 感知其所处 环境(Environment) ,并自主地通过 执行器(Actuators) 采取 行动(Action) 以达成特定目标的实体。

构成与运行原理

要理解智能体的运作,我们必须先理解它所处的任务环境。在人工智能领域,通常使用 PEAS 模型 来精确描述一个任务环境:性能度量(Performance)、环境(Environment)、执行器(Actuators)和传感器(Sensors)

以智能旅行助手为例,其 PEAS 为:

  1. 环境往往是部分可观察的。例如,旅行助手在查询航班时,无法一次性获取所有航空公司的全部实时座位信息。它只能通过调用航班预订 API,看到该 API 返回的部分数据,这就要求智能体必须具备记忆(记住已查询过的航线)和探索(尝试不同的查询日期)的能力。
  2. 行动的结果也并非总是确定的。根据结果的可预测性,环境可分为确定性随机性。旅行助手的任务环境就是典型的随机性环境。当它搜索票价时,两次相邻的调用返回的机票价格和余票数量都可能不同,这就要求智能体必须具备处理不确定性、监控变化并及时决策的能力。
  3. 环境中还可能存在其他行动者,从而形成多智能体(Multi-agent) 环境。对于旅行助手而言,其他用户的预订行为、其他自动化脚本,甚至航司的动态调价系统,都是环境中的其他“智能体”。它们的行动(例如,订走最后一张特价票)会直接改变旅行助手所处环境的状态,这对智能体的快速响应和策略选择提出了更高要求。
  4. 几乎所有任务都发生在序贯动态的环境中。“序贯”意味着当前动作会影响未来;而“动态”则意味着环境自身可能在智能体决策时发生变化。这就要求智能体的“感知-思考-行动-观察”循环必须能够快速、灵活地适应持续变化的世界。

运行机制

智能体并非一次性完成任务,而是通过一个持续的循环与环境进行交互,这个核心机制被称为 智能体循环 (Agent Loop)

  1. **感知 (Perception):这是循环的起点。智能体通过其传感器(例如,API 的监听端口、用户输入接口)接收来自环境的输入信息。这些信息,即观察 (Observation)**,既可以是用户的初始指令,也可以是上一步行动所导致的环境状态变化反馈。
  2. **思考 (Thought)**:接收到观察信息后,智能体进入其核心决策阶段。对于 LLM 智能体而言,这通常是由大语言模型驱动的内部推理过程。如图所示,“思考”阶段可进一步细分为两个关键环节:
    • **规划 (Planning)**:智能体基于当前的观察和其内部记忆,更新对任务和环境的理解,并制定或调整一个行动计划。这可能涉及将复杂目标分解为一系列更具体的子任务。
    • **工具选择 (Tool Selection)**:根据当前计划,智能体从其可用的工具库中,选择最适合执行下一步骤的工具,并确定调用该工具所需的具体参数。
  3. **行动 (Action)**:决策完成后,智能体通过其执行器(Actuators)执行具体的行动。这通常表现为调用一个选定的工具(如代码解释器、搜索引擎 API),从而对环境施加影响,意图改变环境的状态。

行动并非循环的终点。智能体的行动会引起环境 (Environment) 的状态变化 (State Change),环境随即会产生一个新的观察 (Observation) 作为结果反馈。这个新的观察又会在下一轮循环中被智能体的感知系统捕获,形成一个持续的“感知-思考-行动-观察”的闭环。智能体正是通过不断重复这一循环,逐步推进任务,从初始状态向目标状态演进。

感知与行动

在工程实践中,为了让 LLM 能够有效驱动这个循环,我们需要一套明确的交互协议 (Interaction Protocol) 来规范其与环境之间的信息交换。

在许多现代智能体框架中,这一协议体现在对智能体每一次输出的结构化定义上。智能体的输出不再是单一的自然语言回复,而是一段遵循特定格式的文本,其中明确地展示了其内部的推理过程与最终决策。

这个结构通常包含两个核心部分:

  • **Thought (思考)**:这是智能体内部决策的“快照”。它以自然语言形式阐述了智能体如何分析当前情境、回顾上一步的观察结果、进行自我反思与问题分解,并最终规划出下一步的具体行动。
  • **Action (行动)**:这是智能体基于思考后,决定对环境施加的具体操作,通常以函数调用的形式表示。

例如,一个正在规划旅行的智能体可能会生成如下格式化的输出:

Thought: 用户想知道北京的天气。我需要调用天气查询工具。
Action: get_weather("北京")

这里的 Action 字段构成了对外部世界的指令。一个外部的解析器 (Parser) 会捕捉到这个指令,并调用相应的 get_weather 函数。

行动执行后,环境会返回一个结果。例如,get_weather 函数可能返回一个包含详细天气数据的 JSON 对象。然而,原始的机器可读数据(如 JSON)通常包含 LLM 无需关注的冗余信息,且格式不符合其自然语言处理的习惯。

因此,感知系统的一个重要职责就是扮演传感器的角色:将这个原始输出处理并封装成一段简洁、清晰的自然语言文本,即观察。

Observation: 北京当前天气为晴,气温25摄氏度,微风。

这段 Observation 文本会被反馈给智能体,作为下一轮循环的主要输入信息,供其进行新一轮的 ThoughtAction

于是这三者构成了 Thought-Action-Observation 的交互范式


实现第一个智能体

这里尝试构建一个基本的 CTF Agent,能够访问网页与一些基本渗透工具的使用

使用 uv 构建一个新项目并安装依赖

uv init
uv add requests openai

指令模板:

驱动真实 LLM 的关键在于提示工程(Prompt Engineering)。我们需要设计一个“指令模板”,告诉 LLM 它应该扮演什么角色、拥有哪些工具、以及如何格式化它的思考和行动。这是我们智能体的“说明书”,它将作为 system_prompt 传递给 LLM。

先准备一个客户端调用 OpenAI 接口

from openai import OpenAI

class OpenAICompatibleClient:
    """
    一个用于调用任何兼容OpenAI接口的LLM服务的客户端。
    """
    def __init__(self, model: str, api_key: str, base_url: str):
        self.model = model
        self.client = OpenAI(api_key=api_key, base_url=base_url)

    def generate(self, prompt: str, system_prompt: str) -> str:
        """调用LLM API来生成回应。"""
        print("正在调用大语言模型...")
        try:
            messages = [
                {'role': 'system', 'content': system_prompt},
                {'role': 'user', 'content': prompt}
            ]
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                stream=False
            )
            answer = response.choices[0].message.content
            print("大语言模型响应成功。")
            return answer
        except Exception as e:
            print(f"调用LLM API时发生错误: {e}")
            return "错误:调用语言模型服务时出错。"

然后是准备 agent 工具,这里以 http_get 为例:

"""HTTP GET 工具:通过 requests 执行简单的 GET 请求并返回预览内容。"""
try:
    import requests
except Exception:
    requests = None


def http_get(url: str = "", timeout: int = 10) -> str:
    """通过 HTTP GET 获取内容,返回状态与部分内容(避免返回过大内容)。"""
    if not url:
        return "错误: 未提供 url"
    if requests is None:
        return "错误: requests 未安装"
    try:
        r = requests.get(url, timeout=int(timeout))
        text = r.text
        return f"HTTP {r.status_code} {r.reason}: {text[:4000]}"
    except Exception as e:
        return f"错误: http_get 失败: {e}"

接下来是 PROMPT,关于 CTF 相关的系统提示词,可以在这上面找到: https://docsbot.ai/prompts/tags?tag=CTF

尝试构建一个用于 CTF 的 PROMPT,给出可用工具与格式化输出

AGENT_SYSTEM_PROMPT = """
你是一个CTF助手,正在尝试完成一道CTF赛题。你的任务是分析用户的请求,并使用可用工具一步步地解决问题,必要时可以自行编写python脚本,使用uv run运行。

# 可用工具:
- `http_get(url="...", timeout=10)`: 通过 HTTP GET 获取指定 URL 的内容,返回状态码和内容。
  * 必要时可以访问页面中的 javascript 文件并分析其中可能出现的路由信息,如出现 `path: "/"` 等字符串。
  * 不要遗漏任何可能包含有用信息的文件,如 robots.txt、sitemap.xml 等。
- `grep(pattern="...", text="...")`: 在给定文本中查找正则表达式 pattern,返回第一个匹配或说明未找到。
- `dirsearch(target="...")`: 对指定 URL 进行目录扫描,返回扫描结果中响应码为 200 的内容。
- `nmap_scan(target="...", options="...")`: 使用 nmap 扫描指定目标,返回扫描结果的结构化数据。
- `run_script(code="...", filename="...")`: 写入并运行指定的 Python 脚本文件,返回其输出结果。
  * 注意:code 参数必须是**单行字符串**。
  * 必须使用 `\\n` 来表示换行。
  * 必须转义内部的双引号(例如 `\\"`),或者在内部使用单引号。

# 工具调用示例:
正确: run_script(code="import os\\nprint('hello')", filename="test.py")
错误: run_script(code="import os
print("hello")", filename="test.py")  <-- 严禁直接换行或未转义引号
  
# 行动格式:
你的回答必须严格遵循以下格式。首先是你的思考过程,然后是你要执行的具体行动,每次回复只输出一对Thought-Action:
Thought: [这里是你的思考过程和下一步计划]
Action: 你决定采取的行动,必须是以下格式之一:
- `function_name(arg_name="arg_value")`:调用一个可用工具。
- `Finish[最终答案]`:当你认为已经获得最终答案时。
- 当你收集到足够的信息,能够回答用户的最终问题时,你必须在Action:字段后使用 Finish[最终答案] 来输出最终答案。

请开始吧!
"""

最后组装起来:

main.py

import OpenAICompatibleClient
import re
from prompt import AGENT_SYSTEM_PROMPT
from tools import http_get, grep, dirsearch, nmap_scan, run_python_script

available_tools = {
    "http_get": lambda **kwargs: http_get(**kwargs),
    "grep": lambda **kwargs: grep(**kwargs),
    "dirsearch": lambda **kwargs: dirsearch(**kwargs),
    "nmap_scan": lambda **kwargs: nmap_scan(**kwargs),
    "run_script": lambda **kwargs: run_python_script(**kwargs),
}
# --- 1. 配置LLM客户端 ---
# 请根据您使用的服务,将这里替换成对应的凭证和地址
API_KEY = "lm-studio"
BASE_URL = "http://localhost:1234/v1"
MODEL_ID = "qwen/qwen3-30b-a3b-2507"

llm = OpenAICompatibleClient.OpenAICompatibleClient(model=MODEL_ID,
                                                    api_key=API_KEY,
                                                    base_url=BASE_URL)

user_prompt = "访问 http://xxx.xxx.xxx.xxx,进行信息收集以获取flag。"
prompt_history = [f"用户请求: {user_prompt}"]

print(f"用户输入: {user_prompt}\n" + "=" * 40)

# --- 3. 运行主循环 ---
for i in range(7):  # 设置最大循环次数
    print(f"--- 循环 {i+1} ---\n")

    # 3.1. 构建Prompt
    full_prompt = "\n".join(prompt_history)

    # 3.2. 调用LLM进行思考
    llm_output = llm.generate(full_prompt, system_prompt=AGENT_SYSTEM_PROMPT)
    # 模型可能会输出多余的Thought-Action,需要截断
    match = re.search(
        r'(Thought:.*?Action:.*?)(?=\n\s*(?:Thought:|Action:|Observation:)|\Z)',
        llm_output, re.DOTALL)
    if match:
        truncated = match.group(1).strip()
        if truncated != llm_output.strip():
            llm_output = truncated
            print("已截断多余的 Thought-Action 对")
    print(f"模型输出:\n{llm_output}\n")
    prompt_history.append(llm_output)

    # 3.3. 解析并执行行动
    action_match = re.search(r"Action: (.*)", llm_output, re.DOTALL)
    if not action_match:
        observation = "错误: 未能解析到 Action 字段。请确保你的回复严格遵循 'Thought: ... Action: ...' 的格式。"
        observation_str = f"Observation: {observation}"
        print(f"{observation_str}\n" + "=" * 40)
        prompt_history.append(observation_str)
        continue
    action_str = action_match.group(1).strip()

    if action_str.startswith("Finish"):
        final_answer = re.match(r"Finish\[(.*)\]", action_str).group(1)
        print(f"任务完成,最终答案: {final_answer}")
        break

    tool_name = re.search(r"(\w+)\(", action_str).group(1)
    args_str = re.search(r"\((.*)\)", action_str).group(1)
    kwargs = dict(re.findall(r'(\w+)="([^"]*)"', args_str))

    if tool_name in available_tools:
        observation = available_tools[tool_name](**kwargs)
    else:
        observation = f"错误:未定义的工具 '{tool_name}'"

    # 3.4. 记录观察结果
    observation_str = f"Observation: {observation}"
    print(f"{observation_str}\n" + "=" * 40)
    prompt_history.append(observation_str)

运行,这里展示部分输出

用户输入: 访问 http://xxx.xxx.xxx.xxx,进行信息收集以获取flag。
========================================
--- 循环 1 ---

正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: 我需要先访问目标网站,获取其基础信息。首先尝试使用 http_get 获取主页内容,查看是否有明显线索或隐藏的 flag。
Action: http_get(url="http://xxx.xxx.xxx.xxx", timeout=10)

Observation: HTTP 200 OK: Hello,你知道信息泄露吗?
========================================
--- 循环 2 ---

正在调用大语言模型...
大语言模型响应成功。
已截断多余的 Thought-Action 对
模型输出:
Thought: 主页返回了提示“Hello,你知道信息泄露吗?”,这暗示可能存在信息泄露的路径或隐藏内容。我需要进一步扫描可能的敏感路径,例如robots.txt、sitemap.xml或常见的隐藏文件。
Action: dirsearch(target="http://xxx.xxx.xxx.xxx")

Observation: EXIT 0: [11:49:19] 403 -  299B  - http://xxx.xxx.xxx.xxx/.ht_wsr.txt
[11:49:19] 403 -  302B  - http://xxx.xxx.xxx.xxx/.htaccess.save
[11:49:19] 403 -  304B  - http://xxx.xxx.xxx.xxx/.htaccess.sample
[11:49:19] 403 -  302B  - http://xxx.xxx.xxx.xxx/.htaccess.orig
[11:49:19] 403 -  302B  - http://xxx.xxx.xxx.xxx/.htaccess.bak1
[11:49:19] 403 -  300B  - http://xxx.xxx.xxx.xxx/.htaccess_sc
[11:49:19] 403 -  303B  - http://xxx.xxx.xxx.xxx/.htaccess_extra
[11:49:19] 403 -  302B  - http://xxx.xxx.xxx.xxx/.htaccess_orig
[11:49:19] 403 -  300B  - http://xxx.xxx.xxx.xxx/.htaccessBAK
[11:49:19] 403 -  300B  - http://xxx.xxx.xxx.xxx/.htaccessOLD
[11:49:19] 403 -  301B  - http://xxx.xxx.xxx.xxx/.htaccessOLD2
[11:49:19] 403 -  293B  - http://xxx.xxx.xxx.xxx/.html
[11:49:19] 403 -  292B  - http://xxx.xxx.xxx.xxx/.htm
[11:49:19] 403 -  298B  - http://xxx.xxx.xxx.xxx/.htpasswds
[11:49:19] 403 -  299B  - http://xxx.xxx.xxx.xxx/.httr-oauth
[11:49:19] 403 -  302B  - http://xxx.xxx.xxx.xxx/.htpasswd_test
[11:49:45] 403 -  301B  - http://xxx.xxx.xxx.xxx/server-status
[11:49:45] 403 -  302B  - http://xxx.xxx.xxx.xxx/server-status/
[11:49:52] 200 -  202B  - http://xxx.xxx.xxx.xxx/www.zip

========================================
--- 循环 3 ---

正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: dirsearch 扫描发现了一个名为 `www.zip` 的文件,响应码为 200,这很可能是一个压缩包,可能包含敏感信息或 flag。我需要下载该文件并解压以检查其内容。
Action: http_get(url="http://xxx.xxx.xxx.xxx/www.zip", timeout=10)

Observation: HTTP 200 OK: P��T�<
VV  index.php<?php
echo "Hello,你知道信息泄露吗?";
$flag = "flag{you_find_me}";
?>P��T�<
VV   index.phpPK7}
========================================
--- 循环 4 ---

正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: 下载的 `www.zip` 文件中包含一个名为 `index.php` 的文件,其内容中直接包含了 flag:`flag{you_find_me}`。这表明 flag 已经明文存在于压缩包的源代码中。  
Action: Finish[flag{you_find_me}]

任务完成,最终答案: flag{you_find_me}

可以看到这个案例演示了基于Thought-Action-Observation范式的智能体所具备的四项基本能力:任务分解、工具调用、上下文理解和结果合成


协作模式

作为开发者工具

GitHubCopilot、Claude Code、Trae、Cursor

作为自主协作者

架构范式可以归纳为几个主流方向:

  • 单智能体自主循环:以 AgentGPT 为代表,其核心是一个通用智能体通过“思考-规划-执行-反思”的闭环,不断进行自我提示和迭代,以完成一个开放式的高层级目标
  • 多智能体协作:这是当前最主流的探索方向,旨在通过模拟人类团队的协作模式来解决复杂问题。它又可细分为不同模式: 角色扮演式对话:如 CAMEL 框架,通过为两个智能体(例如,“程序员”和“产品经理”)设定明确的角色和沟通协议,让它们在一个结构化的对话中协同完成任务。 组织化工作流:如 MetaGPT 和 CrewAI,它们模拟一个分工明确的“虚拟团队”(如软件公司或咨询小组)。每个智能体都有预设的职责和工作流程(SOP),通过层级化或顺序化的方式协作,产出高质量的复杂成果(如完整的代码库或研究报告)。AutoGen 和 AgentScope 则提供了更灵活的对话模式,允许开发者自定义智能体间的复杂交互网络。
  • 高级控制流架构:诸如 LangGraph 等框架,则更侧重于为智能体提供更强大的底层工程基础。它将智能体的执行过程建模为状态图(State Graph),从而能更灵活、更可靠地实现循环、分支、回溯以及人工介入等复杂流程。

对于 CTF 多智能体协作的应用,可以参考一下这个: https://github.com/yhy0/CHYing-agent

Workflow 与 Agent 的差异

Workflow 是让 AI 按部就班地执行指令,而 Agent 则是赋予 AI 自由度去自主达成目标。


正式构建智能体经典范式

  • ReAct (Reasoning and Acting): 一种将“思考”和“行动”紧密结合的范式,让智能体边想边做,动态调整。
  • Plan-and-Solve: 一种“三思而后行”的范式,智能体首先生成一个完整的行动计划,然后严格执行。
  • Reflection: 一种赋予智能体“反思”能力的范式,通过自我批判和修正来优化结果。

很明显对于 CTF 而言,ReAct 是最正确的选择

环境

uv,python 3.12

openai 库:与大语言模型交互

python-dotenv 库:安全管理 API 密钥

配置客户端

配置 API:

.env 文件

LLM_API_KEY="lm-studio"
LLM_MODEL_ID="qwen/qwen3-30b-a3b-2507"
LLM_BASE_URL="http://localhost:1234/v1"

封装基础 LLM 调用函数

CTFAgentsLLM.py

import os
from openai import OpenAI
from dotenv import load_dotenv
from typing import List, Dict

# 加载 .env 文件中的环境变量
load_dotenv()


class CTFAgentsLLM:

    def __init__(self,
                 model: str = None,
                 apiKey: str = None,
                 baseUrl: str = None,
                 timeout: int = None):
        """
        初始化客户端。优先使用传入参数,如果未提供,则从环境变量加载。
        """
        self.model = model or os.getenv("LLM_MODEL_ID")
        apiKey = apiKey or os.getenv("LLM_API_KEY")
        baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
        timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))

        if not all([self.model, apiKey, baseUrl]):
            raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")

        self.client = OpenAI(api_key=apiKey, base_url=baseUrl, timeout=timeout)

    def think(self,
              messages: List[Dict[str, str]],
              temperature: float = 0) -> str:
        print(f"🧠 正在调用 {self.model} 模型...")
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                temperature=temperature,
                stream=True,
            )

            # 处理流式响应
            print("✅ 大语言模型响应成功:")
            collected_content = []
            for chunk in response:
                content = chunk.choices[0].delta.content or ""
                print(content, end="", flush=True)
                collected_content.append(content)
            print()  # 在流式输出结束后换行
            return "".join(collected_content)

        except Exception as e:
            print(f"❌ 调用LLM API时发生错误: {e}")
            return None


# --- 客户端使用示例 ---
if __name__ == '__main__':
    try:
        llmClient = CTFAgentsLLM()

        exampleMessages = [{
            "role":
            "system",
            "content":
            "You are a helpful assistant that writes Python code."
        }, {
            "role": "user",
            "content": "写一个DES CBC加密的Python代码示例。"
        }]

        print("--- 调用LLM ---")
        responseText = llmClient.think(exampleMessages)
        if responseText:
            print("\n\n--- 完整模型响应 ---")
            print(responseText)

    except ValueError as e:
        print(e)

ReAct 范式

思考与行动相辅相成

在我们实现的第一个智能体中即使用了 ReAct 范式:

  • Thought (思考): 这是智能体的“内心独白”。它会分析当前情况、分解任务、制定下一步计划,或者反思上一步的结果。
  • Action (行动): 这是智能体决定采取的具体动作,通常是调用一个外部工具,例如 Search['华为最新款手机']
  • Observation (观察): 这是执行 Action 后从外部工具返回的结果,例如搜索结果的摘要或API的返回值。

智能体将不断重复这个 Thought -> Action -> Observation 的循环,将新的观察结果追加到历史记录中,形成一个不断增长的上下文,直到它在 Thought 中认为已经找到了最终答案,然后输出结果。这个过程形成了一个强大的协同效应:推理使得行动更具目的性,而行动则为推理提供了事实依据。

这种机制特别适用于以下场景:

  • 需要外部知识的任务:如查询实时信息(天气、新闻、股价)、搜索专业领域的知识等。
  • 需要精确计算的任务:将数学问题交给计算器工具,避免LLM的计算错误。
  • 需要与API交互的任务:如操作数据库、调用某个服务的API来完成特定功能。

工具的定义与实现

一个良好定义的工具应包含以下三个核心要素:

  1. **名称 (Name)**: 一个简洁、唯一的标识符,供智能体在 Action 中调用,例如 Search
  2. 描述 (Description): 一段清晰的自然语言描述,说明这个工具的用途。这是整个机制中最关键的部分,因为大语言模型会依赖这段描述来判断何时使用哪个工具。
  3. **执行逻辑 (Execution Logic)**: 真正执行任务的函数或方法

MCP

这里使用 vscode,版本 1.105.1,原先的扩展商店处下方有 MCP 扩展,点击即可启用 chat.mcp.gallery.enabled (setting.json 中)

hello world

第一个 mcp 搭建: https://www.runoob.com/vscode/vscode-mcp-servers.html

准备一个 test.py

import sys
import json

# 读取 MCP 初始化请求
_ = json.load(sys.stdin)

# 输出 MCP 响应(标准 JSON)
json.dump({
    "type": "text",
    "text": "Hello World from MCP!"
}, sys.stdout)

mcp.json

{
  "servers": {
    "HelloWorldServer": {
      "type": "stdio",
      "command": "python3",
      "args": ["test.py"]
    }
  }
}

IDA-PRO-MCP

参考:

https://www.cnblogs.com/alexander17/p/19089720

https://bbs.kanxue.com/thread-286813.htm

首先先确定自己 ida 用的 python 版本,需要 >= 3.11.0

具体在 ida 安装目录下使用 idapyswitch 查看

然后使用对应版本的 python 安装 ida-pro-mcp,这里使用 pipx 方便版本管理

pipx install --python python3.13 git+https://github.com/mrexodia/ida-pro-mcp

pipx 安装完 ida-pro-mcp 依赖后执行该依赖

ida-pro-mcp --install
ida-pro-mcp --config

前者会将插件安装到 IDA PRO 中

后者生成对应的 MCP 连接格式

[HTTP MCP CONFIGURATION]
{
  "mcpServers": {
    "ida-pro-mcp": {
      "type": "http",
      "url": "http://127.0.0.1:13337/mcp"
    }
  }
}

[STDIO MCP CONFIGURATION]
{
  "mcpServers": {
    "ida-pro-mcp": {
      "command": "/opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/bin/python3",
      "args": [
        "/Users/username/.local/pipx/venvs/ida-pro-mcp/lib/python3.13/site-packages/ida_pro_mcp/server.py",
        "--ida-rpc",
        "http://127.0.0.1:13337"
      ]
    }
  }
}

然后这里以 copliot 为例,导入 MCP 并调用

首先启动 IDA 中的 MCP plugin 服务

然后配置工具导入,直接以 http 方式连接,输入上面的 HTTP 链接即可

然后大模型就能够调用 IDA 进行分析了


客户端

OpenWebUI

文档: https://docs.openwebui.com/getting-started/quick-start

拉取镜像

docker pull ghcr.io/open-webui/open-webui:main
docker pull ghcr.io/open-webui/open-webui:main-slim	# 不包含预打包的模型

运行容器

docker run -d -p 3939:8080 -v open-webui:/app/backend/data --name open-webui ghcr.io/open-webui/open-webui:main

docker run -d -p 3000:8080 -v open-webui:/app/backend/data --name open-webui ghcr.io/open-webui/open-webui:main-slim

单用户模式(禁用登录)

docker run -d -p 3000:8080 -e WEBUI_AUTH=False -v open-webui:/app/backend/data --name open-webui ghcr.io/open-webui/open-webui:main

手动更新版本:

docker rm -f open-webui
docker pull ghcr.io/open-webui/open-webui:main
docker run -d -p 3000:8080 -v open-webui:/app/backend/data --name open-webui ghcr.io/open-webui/open-webui:main