Arisa Blog - Bocchi Hitori
type: channel
link: https://blog.fufubest.com/index.php/author/1/
description: Bocchi Hitori
items: 跨站脚本（XSS）
items: 扫盲：企业安全视角下的安全漏洞
items: 网络安全：序章
items: 名词概念解释（持续更新）
items: 插件nonebot-plugin-deepseek的记录
items: ANI-RSS （ARSS）替代AB的更优方案
items: Mikanani搭配Auto_Bangumi的全自动番剧RSS订阅方案
items: 服务器重装记录（4o整理
items: 使用LLM进行数据标注与信息提取
items: 使用LLM去调用外界的API

跨站脚本（XSS）
type: item
date: 2025-07-02T09:41:01+08:00
description: XSS概述对于前端开发而言，应该都是知道这个漏洞的。全称为：跨站脚本（Cross Site Script），因为简写成CSS会冲突，所以叫做XSS这个漏洞的本质其实就是通过前端提供出去的功能，可以注入JS代码，然后可以被前端执行来一个简单的实例： ... 输入测试内容 提交 清除 输出结果 输出内容将显示在这里... ... 这里只把需要用到的地方列出来了。html代码自己尝试的时候可以交给AI补全。这就是一个输入框，然后下方还有一个输出内容的区域，从下面的前端代码可以看到，获取到内容之后，通过innerHTML属性以标签的方式插入内容，这就是一个典型的 反射XSS通常，验证XSS的方法，就是通过alert()或者console.log()函数憨憨是否会回显进行简单的验证。由于Chrome浏览器本身也有一定的XSS的防护机制，直接使用： 就可以看到弹窗了，这样，我们就证明了漏洞的存在，同时，我们F12打开开发者工具， 也可以看到img标签被写入了进去：危害XSS的核心危害就是通过在网站里植入恶意的JS代码，窃取用户cookie或者利用用户cookie进行未授权操作，通俗来说，就是窃取凭证，盗号。cookie是账号的唯一凭证，拿到了它就相当于拿到了账号权限，这也就是XSS漏洞的核心目标。当然了，除了这个核心目标之外，还有一些针对于前端页面的危害：篡改页面内容，诱导用户操作，进行钓鱼攻击植入恶意的连接，对页面进行重定向通过引入外链的方式绕过内容安全审查策略，打广告或者非法宣传。具体来说就是通过 标签引入违规图片或者利用标签引入外链。（注意区分：如果一个触发点不能执行JS代码，而是只能植入HTML标签，那么这一类的漏洞不能称之为XSS，而是称之为HTML注入）XSS类型对于XSS漏洞的分类，我根据 触发方式、触发位置、触发影响 的不同，可以有下面三种方式：触发方式可以分为 单次触发 长期触发 ，分别对应的是 反射型XSS 和 存储型XSS反射型通过前端输入立即触发执行，直接渲染到前端里，不持久存储在服务器中。上面的那个动图就是经典的反射型XSS。经常发生在包含JS代码的恶意链接，多用于点击挟持，钓鱼等攻击场景，需要主动触发JS代码。存储型顾名思义，就是持久性存储在服务器数据库中的。漏洞点多为恶意代码通过POST请求写入后端数据库，每次访问该页面的时候，后端从数据库中拉取恶意数据返回，前端拿到数据后渲染的时候都会触发并且执行JS代码，属于是被动触发。这种被动触发的攻击方式也可以叫做 路过攻击 或者 水坑攻击，在你神不知鬼不觉的时候触发了恶意代码，所以这一类XSS危害较大，影响面更广，常见于一些持久性存储的区域，比如论坛帖子。个人信息查看页面。触发位置可以分为 DOM渲染 和 代码触发，可以对应为 DOM型XSS 和 非DOM型XSSDOM型所谓DOM节点，通俗的说就是HTML里面的标签，那么该类型的XSS的特征就是在各种标签中触发JS代码，上面的实例漏洞demo就是这样的，核心漏洞触发点就是document标签element对象中的innerHTML属性document.createElement("X").innerHTML document.getElementById("xx").innerHTML通过页面逻辑创建了一个 简单来说就是通过路由获取到用户输入的内容，然后直接执行用户输入的内容。比如说我现在访问：http://example.com/reflected_xss_demo.html?input=alert('XSS')那么代码就会变成：eval("alert('XSS')");于是乎就发生了弹窗。Self-XSS浏览XSS分类教程时，有看到这么一个特殊的分类：Self-XSS ，这种其实都不能算安全漏洞：举个例子👇🏻现在有一个页面存在XSS漏洞，但是只能由你自己访问这个页面，比如说个人资料页面。其他用户无法看到这个页面，所以危害就无法扩散到其他用户，这类型顶多算代码缺陷，完全无危害，这一点从名字来看，就知道了。但是这种缺陷如果被发现之后从安全层面角度还是要修复的，只是低优先级而已。因为你无法保证未来的增量代码逻辑肯定不会引用存在Self-XSS的页面，这个时候就有危害了，而且也不再属于Self-XSS。所以说现实中，当一些白帽子发现了Self-XSS的时候，会努力地找其他可以影响其他用户的触发点，也可能会每隔一段时间看看是否有新增的功能可以扩大该漏洞的利用空间。不然一个Self-XSS正常是不会被SRC收录的。漏洞实战漏洞场景这里我们来模拟一下简化之后的存储XSS漏洞场景——留言板系统，也是业务中比较常见的漏洞点，我们可以借此来看一下危害：需求我们有一个留言板应用，用户可以提交评论，这些评论会被存储在服务器的数据库中然…

扫盲：企业安全视角下的安全漏洞
type: item
date: 2025-06-26T23:08:39+08:00
description: 安全漏洞什么是漏洞？bug的由来，大家都知道吧，是一桩趣事，计算机有史以来第一起故障是由一个虫子（bug）引起的，所以bug后来被用于描述计算机程序故障，运行缺陷，逻辑错误等等。然后把找bug的行为称之为debug。在bug包含的场景中，有一些缺陷场景很是特殊，它们本身会破坏程序的本身（拒绝服务），做出程序不该做的危险操作（终端指令执行），甚至可以拿到其他用户的数据（越权）等等。为了区分，下载乃常用 vulnerability（弱点，脆弱性）来特指安全类的bug。在大陆，大众理解的bug就是指代的程序缺陷：按钮点击未响应页面加载异常等等；但是，对于安全类的缺陷常用漏洞来指代。注意，接下来这个文集讲解的所有漏洞基本都泛指安全漏洞我学到这里的时候，有想到计算机病毒，后来查阅了一下相关资料之后，可以简单的说一下区别：计算机病毒本质上就是为了恶意攻击而开发的程序安全漏洞是设计缺陷而被利用的被动弱点这里举个很经典的例子：于2017年5月12日集中爆发的WannaCry勒索病毒，就是利用了Windows系统的SMBv1漏洞组件进行了勒索攻击。远程发送特制的数据包，寻找445端口开启的主机，利用EternalBlue工具远程注入恶意代码，成功之后获取系统的控制权，然后修改用户文件，进行加密，后缀改成.WNCRY，除非支付比特币赎金，否则无法解密。并且还可以横向传播，对局域网的其他主机相继进行感染。这个故事很经典，感兴趣可以搜索相关信息查看。作为信息安全的安全漏洞的反面教程是最为合适的。总而言之，安全漏洞属于程序预期之外的可造成安全问题的缺陷漏洞分类了解完了安全漏洞相关的概念之后，接着来说一下常用的漏洞分类。根据我的学习了解，行业内根据漏洞是否具有普遍性，将其分为了 通用性漏洞 和 事件型漏洞通用性漏洞简单来说就是全网通杀，多见于第三方组件、常用框架、或者是售卖给第三方的系统软件（OA系统）等这种在互联网上有一定数量规模的独立部署或者使用的程序。特点总结：漏洞程序或者组件是第三方提供的漏洞程序或者组件是独立部署或者使用的，不依托于提供商平台漏洞程序或者组件在市面上有一定数量规模的使用举个例子：2021年年底的Log4J2反序列化命令执行漏洞。Log4J2作为一个Java日志记录的知名第三方组件，当自身的漏洞被挖掘之后，全网所有使用该组件的应用也就通通都存在了漏洞。事件型漏洞属于业务自己的应用系统独有的漏洞，可以被称之为某个漏洞点，修改之后就没有安全风险了，可以作为一次是影响到了自己的安全事件进行对待。这一类漏洞通常是跟自身业务逻辑代码挂钩的，与第三方组件或者应用没有关系的。比如说：业务系统有一个接口没有经过授权就可以查看用户的信息，它影响的就仅仅是自身平台的用户，这就是属于事件型漏洞。我这次的文集学习内容也会集中到这一类漏洞。漏洞风险程度了解完漏洞的相关概念，我们来讲讲漏洞的危害，因为这关系到了为什么要修复漏洞，说不定可以就此解决一些疑惑：”为什么如此注重安全？“风险三要素漏洞危害：直接代表一个漏洞可以导致的安全结果，比如：我可以随意执行命令，从而批量获取用户信息，或者可以造成系统拒绝服务。影响范围：表示一个漏洞可以波及到的量级（数据量或者持续时间）；比如：是只能影响到一个用户，还是能够影响到多个用户，是中断服务几分钟，还是中断服务一整天？甚至更久？利用难度：代表一个利用该漏洞时需要付出的成本（资源，时间成本）；比如：一个需要用户交互的漏洞肯定是要比一个可以直接利用的漏洞的利用难度要高。这里说个题外话，有人说黑客为啥不拿下某宝某东？严格意义上来说，没有绝对安全的系统，如果要拿下是有办法的（硬要说的话人就是最大的安全隐患），但是为什么不拿下呢？因为贵，攻击的成本要大于产出，故而没有必要，这就是典型的难度高的例子。风险定级当总结出了漏洞风险三要素之后，就可以正确的看待漏洞的风险承担了。那么，凡是都有个轻重缓急，总不能是个漏洞就马上修复。因为大家平时开发也挺忙的。所以！这里就对漏洞进行了分级，每个级别对应了不同的修复时间：业内的共识对漏洞总共分为了五个等级：严重：十万火急，晚一秒就有巨额损失，马上修复！高危：风险很大，影响很广，利用难度低，需要尽快修复（3天之内）中危：风险适中，利用难度较大，有一定的利用成本，可以暂缓修复（一周到15天左右）低危：风险低，损失有限，修复时间可以缓一缓无危害：直接忽略，没有关系漏洞修复谈完漏洞本身，现在要说一下企业内部为了治理漏洞需要做那些建设：（以下内容直接取自互联网大牛总结）仅仅靠程序上线之后再发现漏洞，那么明显风险已经暴露出去了，网络上多少攻击都盯着呢。所以漏洞治理要融入到程序开发的流程之中，在行内，大家把这个东西叫做软件安全开发生命周期（SDLC)常见的SDLC实践是：需求分析阶段安全意识培训：提高开发安全意…

网络安全：序章
type: item
date: 2025-06-26T23:08:27+08:00
description: 今天浏览到一份很不错的教程，涉及到了现代业务下的WEB架构安全研发。感觉需要记录一下：前言没有绝对安全的系统对于研发而言，感觉网络安全可能和自己的日常代码没有太大关系，在工作中，实际上我们很多时候感觉安全这个问题离我们很远。在进入公司就职时，就看到过很多信息安全的案例，最严重的情况下，会引发法律风险。想象一下：你辛辛苦苦写的代码，一旦上线就被发现漏洞百出，然后回退代码修复重新上线，耽误发布时间。因为自己代码的小漏洞，公司的重要数据被泄露，甚至引发了法律问题。好像听起来很骇人听闻？这些都是有案例的。一个未经处理的GitHub仓库上传，一次忽略的API鉴权，一次不经意的上线，都会成为问题的导火索。网络安全是什么？首先要清楚：网络只是一个数据通信的载体，真正被攻击的永远都是网络传输的终端、或者是大家写出来的一行行代码。所以说，研发过程才是安全的第一线，而，安全就是日常开发中最容易忽视的问题，所以说提高研发同学的安全意识，养成良好的代码编码习惯就很重要了。当前现状经过我的了解，有一些公司会针对这个现象成立专门的安全团队，主要就是对代码安全，有一个叫做SDL(安全开发生命周期) 的概念：在业务进行项目评审，代码测试，代码上线的时候，或多或少地会有安全部门插手。这些安全手段确实会扼杀很多安全问题的萌芽，但是其实还是无法根绝，始终都会有漏网之鱼。更何况我供职的公司也压根没有这种团队，也没有安全人员进行兜底，这就是最可怕的，意识不到安全问题的存在目的所以，为了弥补我这缺失的安全意识，我决定记录这些学习的过程，并且将其编辑为文集，侧重于基础漏洞代码的修复。根据我个人职业规划的方向，这次的研究方向主要集中在当下WEB架构下的安全，对前后端基础漏洞，业务逻辑漏洞，以及部分数据安全问题进行展开研究，主要针对于代码层面进行漏洞研究最终目的：了解漏洞，熟悉修复方法，知道怎么避免安全隐患

名词概念解释（持续更新）
type: item
date: 2025-06-26T23:08:03+08:00
description: 这里对网络安全这个文集罗列一下会用到的一些概念词：Payload：攻击载荷，指可造成攻击行为的输入数据Poc：漏洞验证脚本，仅作漏洞验证，不实际作出攻击行为Exploit：利用漏洞进行攻击的手法、脚本或程序GetShell：通过漏洞获取目标系统的命令行控制权限C2：命令与控制，远程控制受害主机的通信架构白帽子：善意的安全研究人员，帮助企业发现并修复漏洞，SRC常客CTF：夺旗赛（Catch The Flag），在网络安全领域下是一种技术比赛，通过利用漏洞的方式进行解谜APT：高级持续威胁，通过各种手段的长期定向攻击，具有高度隐蔽性脚本小子：缺乏技术深度，依赖现成工具的攻击者

插件nonebot-plugin-deepseek的记录
type: item
date: 2025-06-25T11:45:31+08:00
description: 本插件直接安装倒是没有什么问题。。但是在安装之后，因为QQ本身没有处理Markdown的能力，加上大语言模型基本上都是返回的Markdown文本，所以我们需要开启image，使用playwright来进行渲染截图，详细需要注意的点如下：1. 要在nb项目的env里加入配置信息要设置为true2. image的依赖内容需要使用pip install在nb项目下直接运行：pip install 'nonebot-plugin-deepseek[image]'成功之后，这个会直接安装playwright，我们需要在服务器里安装对应的浏览器内核，我们可以干脆一步到位：playwright install接着，我遇到的一个坑是，哪怕安装好playwright的内核，还是无法正常使用Markdown转图片：怎么办呢？插件文档也没有写，在翻阅code之后：发现还需要安装nonebot_plugin_htmlrendernb plugin install nonebot_plugin_htmlrender之后，如果linux系统没有安装中文字体，可以一起运行这个命令：sudo apt install fonts-noto -y然后重启nb之后，就可以正常使用啦！

ANI-RSS （ARSS）替代AB的更优方案
type: item
date: 2025-06-23T16:16:00+08:00
description: 没想到我才发布一周的AB方案这么快就被我找到了替代的优化版本。。。什么是ANI-RSS？这也是一款基于蜜柑计划的全自动番剧下载工具（敲重点！）和之前介绍的AB（AutoBangumi）一样，ARSS（简称）也是通过RSS订阅番剧，调用的是QBittorrent（当然也支持Aria2与Transmission）下载。但是和AB不同的是，这个ARSS不需要注册蜜柑计划的账号，也不需要在蜜柑官网订阅番剧，所有的操作，全部都在程序内部执行。当然，和AB不太一样的点，就是ARSS支持剧集偏移，并且可以自动识别偏移集数，支持配置不同的RSS订阅链接然后自动洗板；把番剧文件夹挂载到ARSS的前提之下，可以自动跳过已经下载的文件；如果你还设置了做种规则，在完成了做种任务之后，也可以自动删除种子。另外，还提供了简单明了的操作UI，按照星期分类的番剧卡片，移动端也可以方便订阅（有一个前提是你需要在PC上先准备好），随时随地添加新订阅。至于代理，使用TMDB的标题、获取年份，telegram与webHook通知等等，都是AB的原有功能。安装还是和AB方案一样，我们需要先安装docker，docker安装我就跳过了。# 使用 bind mount mkdir -p ${HOME}/RSS-subscribe/arss mkdir -p ${HOME}/RSS-subscribe/qb mkdir -p ${HOME}/RSS-subscribe/bangumi cd ${HOME}/RSS-subscribe touch compose.yml & touch .env将以下内容复制到compose.yml里面：services: qb: image: superng6/qbittorrentee:latest container_name: qb ports: - ${QB_PORT}:${QB_PORT} #qb端口 environment: - PUID=${PUID} - PGID=${PGID} - WEBUIPORT=${QB_PORT} - TZ=${TZ} volumes: - ./qb:/config - ${Bangumi_Location}:/bangumi #番剧挂载路径 restart: unless-stopped networks: - ani-rss ani-rss: image: wushuo894/ani-rss:latest container_name: ani-rss ports: - ${ASS_PORT}:${ASS_PORT} environment: - PORT=${ASS_PORT} - CONFIG=/config - TZ=${TZ} volumes: - ./ani-rss:/config - ${Bangumi_Location}:/bangumi #番剧挂载路径 restart: unless-stopped networks: - ani-rss networks: ani-rss: external: true鉴于 qBit­tor­rent 和 ani-rss 有不少共用变量，因此再创建一个 .env 文件存储这些变量，后续有修改在这个文件中操作就好，注意：不同的终端其中的uid和gid都不太一样，你可以在系统终端里使用：$ id会出现你当前系统的uid和gid，把他们复制到.env里：PUID=501 PGID=20 TZ=Asia/Shanghai QB_PORT=8989 ASS_PORT=7789 Bangumi_Location=/YourPath/RSS-subscribe/bangumi接下来我们来创建docker网络：名为ani-rssdocker network create -d bridge ani-rss持续化启动：docker compose up -d配置qBittorrent打开qb地址配置qBittorrent。应该没有忘记吧？qBittorrent的密码需要在终端里查看，我们使用命令查看日志：docker logs qb输出如下：WebUI 将在内部准备不久后启动。请稍等… ******** 信息 ******** 要控制 qBittorrent，请访问下列地址的 WebUI：http://localhost:8989 WebUI 管理员用户名是：admin 未设置 WebUI 管理员密码。为此会话提供了一个临时密码：NQDSMfujC 你应该在程序首选项中设置你自己的密码登录上去之后，记得修改密码，密码可以参考我之前的文章，其实也不难找：AB安装QBittorrent本身只是作为下载器，不承担订阅RSS的角色，所以我们只需要配置一下下面的内容：为…

Mikanani搭配Auto_Bangumi的全自动番剧RSS订阅方案
type: item
date: 2025-06-16T16:34:00+08:00
description: 前排提示：建议换用 ANI-RSS，已追更↓↓↓↓↓ANI-RSS先安装AutoBangumi准备工作以下简称AutoBangumi为AB根据官方文档的建议，为了保证AB在每次更新之后数据和配置不丢失，推荐使用bind mount 或者 Docker volume进行数据和配置的持久化。我们先来创建一个RSS订阅的工具集合的文件夹，（因为我们还需要qBittorrent）# 使用 bind mount mkdir -p ${HOME}/RSS-subscribe/AutoBangumi/{config,data} cd ${HOME}/RSS-subscribe/AutoBangumi# 使用 Docker volume docker volume create AutoBangumi_config docker volume create AutoBangumi_data使用Docker安装AutoBangumi使用以下命令之前请确保处于上面我们创建的AutoBangumi文件夹目录下。这里我们使用Docker-cli部署docker run -d \ --name=AutoBangumi \ -v ${HOME}/RSS-subscribe/AutoBangumi/config:/app/config \ -v ${HOME}/RSS-subscribe/AutoBangumi/data:/app/data \ -p 7892:7892 \ -e TZ=Asia/Shanghai \ -e PUID=$(id -u) \ -e PGID=$(id -g) \ -e UMASK=022 \ --network=bridge \ --dns=8.8.8.8 \ --restart unless-stopped \ ghcr.io/estrellaxd/auto_bangumi:latest接下来让我们访问下面的地址：http://localhost:7892AutoBangumi 默认的账号是admin，密码是adminadmin登录进来之后我们修改一下账号密码：在进行最关键的调整步骤之前，我们需要先安装一下qBittorrent和获取Mikanani的RSS订阅。安装qBittorrent如果你也和我一样，RSS全套订阅工具都放在了root目录下面，那么你可以直接复制下面的内容：mkdir -p ~/RSS-subscribe/qbittorrent/config ~/RSS-subscribe/qbittorrent/downloads && \ docker run -d \ --name qbittorrent \ -e PUID=$(id -u) \ -e PGID=$(id -g) \ -e TZ=Asia/Shanghai \ -e WEBUI_PORT=8080 \ -p 8080:8080 \ -p 6881:6881 \ -p 6881:6881/udp \ -v ~/RSS-subscribe/qbittorrent/config:/config \ -v ~/RSS-subscribe/qbittorrent/downloads:/downloads \ --restart unless-stopped \ lscr.io/linuxserver/qbittorrent:latest注意，qBittorrent在后续的新版本取消了一开始的adminadmin密码，取而代之的是在docker logs里面出现的随机密码，并且在登陆之后该密码失效：登陆之后，记得修改密码：位于Tools > Options的WebUI栏目有我勾起来的这部分，这里修改密码。如此这般，qBittorrent的配置到此结束。获取Mikanani的RSS请注意！此教程全程默认你的网络环境是有VPN的，请不要在没有VPN的环境下尝试，亦或是自行寻找大陆网络替代方案！Mikanani需要登录才可以进行RSS订阅勾选你要的番剧，之后在首页上可以看到RSS订阅的按钮，复制这个按钮的链接。复制后获取到的RSS格式通常来说是这样的：https://mikanani.me/RSS/MyBangumi?token=xxxxxxxxxxxxxxx配置 AutoBangumi填入下载器的地址，端口，用户名和密码 点击Apply 保存配置，AB会重启运行，右上角的圆点变成绿色代表正常运行 点击右上角的添加按钮，勾选聚合RSS，选择解析器类型mikan，填入刚刚复制的RSS地址 等待 AB 解析聚合 RSS，解析完成会自动添加番剧并且下载管理。RSS订阅资源一有更新就会触发下载。很方便吧！

服务器重装记录（4o整理
type: item
date: 2025-06-16T12:19:48+08:00
description: Ubuntu 系统重装后建站完整流程文档（含 Nginx + PHP + Typecho + HTTPS + 自动续期）适用于 Ubuntu 22.04 LTS 系统环境，自编译 Nginx + PHP-FPM 架构，脱离 LNMP 全家桶，含 HTTPS 和自动续期配置。🧱 一、系统准备1. 更新系统sudo apt update && sudo apt upgrade -y2. 安装依赖包sudo apt install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev unzip curl -y🌐 二、安装 Nginx（源码方式）1. 下载并编译cd /usr/local/src wget http://nginx.org/download/nginx-1.24.0.tar.gz tar zxvf nginx-1.24.0.tar.gz cd nginx-1.24.0 ./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_gzip_static_module make && sudo make install2. 创建目录结构sudo mkdir -p /usr/local/nginx/conf/sites-enabled sudo mkdir -p /var/log/nginx3. 修改主配置文件 nginx.conf加入：include /usr/local/nginx/conf/sites-enabled/*.conf;⚠️ 如果未修改 nginx.conf 并且每个站点的配置都已经直接加入 /usr/local/nginx/conf/nginx.conf 中，也可以保持现状。推荐使用 include 统一管理，方便扩展与维护。🐘 三、安装 PHP-FPMsudo apt install php8.1 php8.1-fpm php8.1-mysql -y sudo systemctl enable --now php8.1-fpm默认监听路径：/run/php/php8.1-fpm.sock🌐 四、网站文件恢复与配置网站目录统一为：/home/wwwroot/恢复 blog（Typecho）等静态站点：cp -a /opt/server/site/wwwroot/* /home/wwwroot/ chown -R www-data:www-data /home/wwwroot✏️ 五、Nginx 虚拟主机配置参考1. 博客站点（PHP）blog.fufubest.com.confserver { listen 80; server_name blog.fufubest.com; root /home/wwwroot/blog.fufubest.com; index index.php index.html; access_log /var/log/nginx/blog.access.log; error_log /var/log/nginx/blog.error.log; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php/php8.1-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }2. 静态站点参考（如 fufubest.com）server { listen 80; server_name fufubest.com; root /home/wwwroot/fufubest.com/dist; index index.html; access_log /var/log/nginx/fufubest.access.log; error_log /var/log/nginx/fufubest.error.log; location / { try_files $uri $uri/ =404; } location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ { expires 30d; access_log off; add_header Cache-Control "public"; gzip_s…

使用LLM进行数据标注与信息提取
type: item
date: 2024-12-30T22:48:00+08:00
description: 在上一篇文章中，我们是直接用的OpenAI的原生API去使用Function Calling（Tools）功能，正如我们文末总结的那样，自己写需要自己维护历史，写参数类型，自己实现函数的调用，比较繁琐。在这里，我要试试在Langchain使用该功能，会极大的减缓使用门槛，并且很容易集成到现在有的Chain里。在Langchain使用Tools在Langchain里，一般用zod来定义Tools函数的JSON Schema。我们可以专注在参数的描述上面，参数的类型定义和require都交给zod来做。在后续定义Agent Tool的时候，zod也可以加入进行辅助的参数类型检查。deno install zodzod的用法简单讲讲zod怎么用。zod是JS生态常见的类型定义和验证的工具库：import { z } from 'zod' const stringSchema = z.string() stringSchema.parse("hi") stringSchema.parse(123) // Error: Invalid input: expected string, received number这里就会报错：Stack trace: ZodError: [ { "code": "invalid_type", "expected": "string", "received": "number", "path": [], "message": "Expected string, received number" } ]报错信息的可读性也很高，这种交给LLM去读取也是非常有效，可以高效地纠正自己的错误。下面再给一系列的示例来迅速地介绍足够定义Tools参数的zod知识：import { z } from 'zod' // 基础类型 const stringSchema = z.string() const numberSchema = z.number() const booleanSchema = z.boolean() // 数组 const stringArraySchema = z.array(z.string()) stringArraySchema.parse(["hi", "there"]) // 对象 const personSchema = z.object({ name: z.string(), age: z.number(), // 可选字段 isStudent: z.boolean().optional(), // 默认值 home: z.string().default("sichuan") }) // 联合类型 const stringOrNumber = z.union([z.string(), z.number()]) stringOrNumber.parse("hi") stringOrNumber.parse(123)考虑到方便LLM调用，以便于让它理解参数和传递参数，我们一般不会把参数设置的过于复杂，否则容易让LLM犯错。使用zod来定义函数的Schema让我们继续说回之前的那个天气函数，我们用zod来定义Schemaimport { z } from "zod" const getCurrentWeatherSchema = z.object({ location: z.string().describe("The city and state, e.g San Francisco, CA"), unit: z.enum(["celsius", "fahrenheit"]).describe("The unit of temperature") })这里就是定义了两个参数location是string类型，并且添加了描述unit就是枚举类型，并且也添加了对应的描述我们没有指定optional，默认就是require的可以使用zod-to-json-schema来把zod定义的Schema转换为JSON Schema：zod-to-json-schemaimport { zodToJsonSchema } from "zod-to-json-schema" ... const paramsSchema = zodToJsonSchema(getCurrentWeatherSchema) paramsSchema我们就可以转换为OpenAI Tools需要的JSON Schema了：{ type: "object", properties: { location: { type: "string", description: "The city and state, e.g San Fra…

使用LLM去调用外界的API
type: item
date: 2024-12-30T18:59:00+08:00
description: 接下来我们将要步入Agent领域。function Calling是？在正式开始之前，我们先来看看一切AI Agent的基础——「Function Calling」这个东西的本质就是给LLM了解和调用外界函数的能力，LLM会根据他的理解，在最合适的时间，返回对函数的调用与参数，然后根据函数调用的结果进行回答。比如，在构建一个旅游计划的chatbot，用户给出问题：“我计划于12月25日到日本涩谷参加圣诞节，请帮我查询那个时间的天气和当地节日规定”。这样，LLM就会判断需要调用12月25日的实时天气API来获取日本涩谷的那一天的天气情况，并且查询涩谷对于圣诞节的安排。根据返回的结果，进行回答。这里，我们先不用Langchain，来用OpenAI官方API去尝试理解一下Function Calling这个API。OpenAI已经将Function Calling改名成了tools，不过目前大多数主流资料依然将其成为Function Calling，我们这里谨遵OpenAI的标准，之后都会以tools来称呼这个APIfunction Calling 案例我们就用获取天气这个经典操作来作为使用案例吧，这个需要实时获取外部API的结果，LLM无法回答。这里我们就继续用回NoteBook+deno，因为可以更细致化地观察每一步运行的过程。我们来使用OpenAI官方库，尝试引入并且初始化：import { load } from "dotenv" const env = await load() ... import OpenAI from "openai"; const openai = new OpenAI({ apiKey: env["OPENAI_API_KEY"], baseURL: env["OPENAI_API_URL"] });创建一个假的获取天气的函数function getCurrentWeather({location, unit="fahrenheit"}) { const weather_info = { "location": location, "unit": unit, "temperature": 72, "forecast": ["sunny","windy"] } return JSON.stringify(weather_info) }然后我们按照OpenAI官方API文档指定的格式，创建这个函数的描述信息：const tools = [ { type: "function", function: { name: "getCurrentWeather", description: "Get the current weather in a given location", parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, eg. 'San Francisco, CA'" }, unit: { type: "string", description: "The unit for temperature", enum: ["fahrenheit", "celsius"], // 限定取值范围 } }, require : ["location"] } } } ]我们来解析一下这里的定义：type: "function"：这里目前只支持Function，是必须指定的内容，没有的话会抛出错误。function：对具体函数的描述name：函数名字，需要和你那边要调用的函数的名字一样，我们才可以实现对函数名的调用description：对函数的描述，可以理解成，提供对工具功能的简要描述，帮助模型理解并正确使用工具parameters：函数的参数，OpenAI使用的是通用的JSON Schema去描述函数的各个参数关于parameters，这里分别详细说一下：外层的type描述参数的顶层数据结构类型，通常就是Objectproperties列出对象中的所有参数，并定义每个参数的名称、类型和约束require，定义必需参数的数组properties里又包含了每个参数属性的详细信息，分别是：type：参数的数据类型（如 string、number、boolean）description：描述参数的用途enum：限定参数值的可选范围default：为参数提供默认值（如果适用）format：限制参数的格式（如 email、uri）然后我们来试试调用LLM的tools功能：const messages = [ { "role": "user", "content":…