AI探索实践12 - Typescript开发AI应用4:大模型响应数据的格式化输出

news2025/1/15 13:15:18

大家好,我是feng,感谢你阅读我的博文,如果你也关注AI应用开发,欢迎关注公众号和我一起​探索。如果文章对你有所启发,请为我点赞!

一、重点回顾

在介绍本文之前的文章中,我们先来回顾一下使用LangChain开发应用程序的逻辑:

  1. 创建一个模型对象(ChatOllama)。指定模型访问的URL、模型名称和temperature等主要参数

  2. 创建一个提示语模板对象(ChatPromptTemplate)

  3. 创建一个Chain对象:使用ChatPromptTemplate的pipe方法

  4. 调用Chain对象的invoke/batch/stream等方法,向模型发送请求并处理响应。

运行结果如图1

图1

注意内容字段的值。

二、问题发现

当我们调用LangChain时,通过输出我们可以发现,内容字段的值中包含有换行符   \n   ,有时会出现连接字符串的  号。这看起来并不美观,而且如果将这样的结果作为外部系统接口的输入可能还会造成不必要的异常和故障。

幸运的是LangChain提供了一个非常重要的功能 :Output Parsers(输出解析器)。通过输出解析器,我们可以控制从大模型返回的响应数据的结构和格式。无论我们期望输出的是数组、对象或其他的格式,LangChain都支持以你指定的方式来格式化这些内容。

接下来,我们来了解如何使用Typescript编写代码,来实现大模型输出内容的自定义格式化。

三、TS实现格式化输出

在网址 https://js.langchain.com/docs/modules/model_io/output_parsers/types/ ,我们可以看到LangChain支持的输出解析器列表,通常你应该可以找到你需要的特定用例的解析器。

我们重点来介绍3个解析器。

3.1 解析成字符串

如果希望将大模型的输出转为字符串,我们需要用到 StringOutputParser 类。代码实现非常简单:

import { StringOutputParser } from '@langchain/core/output_parsers';

// 创建一个模型
const model = new ChatOllama({
  baseUrl: 'http://localhost:11434', // Default value
  model: 'qwen:4b',
});

// 其次,创建一个提示语模板对象。
const prompt = ChatPromptTemplate.fromMessages([
  ['system', '根据用户提供的词语,来创作笑话'],
  ['human', '{input}'],
]);

首先,我们需要导入 StringOutputParser 类。然后创建一个模型和提示语模板对象。为了增强代码的可读性,我们将不同的格式化实现,单独封装到一个函数里。

创建一个字符串格式化处理函数:

async function callStringOutputParser() {
  // 创建一个输出解析器
  const outputParser = new StringOutputParser();

  // 通过pipe创建一个chain
  const chain = prompt.pipe(model).pipe(outputParser);
  
  // 这如同我们在编写java语言中的链式调用方法。在LangChain中,
  // 通过pipe将不同的对象都附加到整个链上,并且可以将前一个对象的输出,
  // 输入传递给后一个对象。
  // 上面代码中 prompt 的输出,作为  chatModel 的输入,
  // chatModel的输出,作为 parser的输入。

  return await chain.invoke({
    input: '小狗',
  });
}

// 执行函数
const response = await callStringOutputParser();
// 打印大模型的响应
console.log(response);

代码中注释的比较清楚,我们需要声明一个 StringOutputParser 对象,并通过pipe方法附加到链上。这样在执行链时,大模型的推理结果就会作为输入传入到字符串输出解析器对象中,经过该对象进行处理,最终会输出字符串格式的数据。我们来观察一下结果:

图2

如图2所示,我们可以观察到和图1相比,经过 StringOutputParser 处理后 ,模型响应的结果只保留了内容字段的值,并且去掉了换行符等特殊字符,一个纯粹的字符串。

3.2 解析成数组

有时候,我们会希望传递给外部系统的数据格式为数组。对于这样的场景,LangChain提供了 CommaSeparatedListOutputParser 来支持。

实现逻辑与使用 StringOutputParser 相同。只是这一次我们进行一点改变,注意观察提示语模板的声明。

创建一个数组格式化处理函数:

import { CommaSeparatedListOutputParser } from '@langchain/core/output_parsers';

async function callListOutputParser() {
  // 创建一个提示语模板对象
  const prompt = ChatPromptTemplate.fromTemplate(`
    Provide 3 synonyms, seperated by commas, for the following word {word}
  `);

  // 创建一个输出解析器:逗号分割的数组输出解析器
  const outputParser = new CommaSeparatedListOutputParser();

  // 通过pipe创建一个chain
  const chain = prompt.pipe(model).pipe(outputParser);

  return await chain.invoke({
    word: 'happy',
  });
}


// 执行函数
const response = await callListOutputParser();
// 打印大模型的响应
console.log(response, typeof response);

来观察输出结果,见图3:

图3 

提示语时要求大模型输出用户提供的词语的3个同义词。代码中,提供的是:happy。经过3次运行测试,大模型返回的是一个数组对象,包含3个元素。

注:在测试过程中,我发现2个问题,留待后续研究:

  1. 数组解析处理器,对于中文的分隔处理成数组还不稳定,会出现解析异常的情况。尚不确定是本地4B的千问模型处理能力比较弱的问题,或者是我的中文提示语写的有问题。因此,为了说清楚代码的实现逻辑,代码中暂时用英语来写提示语模板。
  2. 数组的最后一个元素,会出现句点符号。这应该是 CommaSeparatedListOutputParser 的实现逻辑的问题。

3.3 解析成json对象

json,是我们在日常开发过程中最常用的格式。LangChain提供了2种解析成Json格式的方法:

  1. 内置的,解析成简单的json对象
  2. Zod模式,通过第三方库Zod,解析成可嵌套的、复杂的json对象

要想解析为json对象,我们需要使用LangChain提供的结构化解析器 —  StructuredOutputParser

3.3.1 解析为简单json对象

创建一个结构化格式化处理函数:

import { StructuredOutputParser } from 'langchain/output_parsers';

async function callStructuredParser() {
  const prompt = ChatPromptTemplate.fromTemplate(`
     从短语中提取信息。
     格式化说明: {desc}
     短语: {string}
  `);

  const outputParser = StructuredOutputParser.fromNamesAndDescriptions({
    name: '人的姓名',
    age: '人的年龄',
    address: '家庭住址',
  });

  const chain = prompt.pipe(model).pipe(outputParser);

  return await chain.invoke({
    string: '王山今年19岁了,住在科学大道208号5-4。',
    desc: outputParser.getFormatInstructions(),
  });
}

// 执行函数
const response = await callStructuredParser();
// 打印大模型的响应
console.log(response, typeof response);

StructuredOutputParser 类只有2个方法实现对数据的格式化处理,其中 fromNamesAndDescriptions 方法是格式化成简单json对象。从名称可以看出函数主要是提取数据中的名称和对应的描述。

我们来理解一下 callStructuredParser 函数的逻辑:

1、当调用这个链时(也就是执行 chain.invoke方法),我们传入了2个参数:string 和 desc

名为 “string”,值为:“王山今年19岁了,住在科学大道208号5-4。”的字符串。这个值会被传入到提示语模板对象中的“短语”中的占位符变量。

名为“desc”,值为解析器的实例。该解析器实例规定了一个json对象的结构和取值的含义:如果发现“人的姓名”,则赋值给属性:“name”,如果发现“人的年龄”则赋值给属性:“age”,如果发现“家庭住址”则赋值给属性:“address”。

2、string 和 desc 传入到提示语模板中,结合模板中已经定义的大模型的指令:“从短语中提取信息”等,组成一个完整的提示语,并传递给大模型。

3、大模型接收到这个完整的提示语后,进行推理。并按要求提取了信息的语义,并将信息传递给解析器对象。

4、解析器对象根据预先定义好的json对象的结构和大模型提供的语义做出如下的动作:

  1. 将“王山”赋值给“name”
  2. 将“19岁”赋值给“age”
  3. 将“科学大道208号5-4”赋值给“address”
  4. 组成一个json对象,并返回

看看输出结果:

3.3.2 解析为可嵌套的、复杂的json对象 

我们需要借助Zod库来实现这个需求。在终端执行下面的命令。此处使用yarn,你当然可以使用npm进行安装。

yarn add zod

创建一个zod格式化处理函数:

import { z } from 'zod';

async function callZodOutputParser() {
  const prompt = ChatPromptTemplate.fromTemplate(`
     从短语中提取信息。
     格式化说明: {desc}
     短语: {string}
  `);

  const outputParser = StructuredOutputParser.fromZodSchema(
    z.object({
      recipe: z.string().describe('食谱名称'),
      ingredients: z.array(z.string()).describe('组成部分'),
    }),
  );

  const chain = prompt.pipe(model).pipe(outputParser);

  return await chain.invoke({
    string: '意大利面的成分是西红柿、碎牛肉、大蒜、酒和香草',
    desc: outputParser.getFormatInstructions(),
  });
}

// 执行函数
const response = await callZodOutputParser();
// 打印大模型的响应
console.log(response, typeof response);

这是一个分析食谱的请求。在定义输出解析器时,使用了 StructuredOutputParser.fromZodSchema ,参数是一个对象定义:

z.object,代表了格式化返回的是一个对象类型。它的结构自定义为:

  1. 有一个名为 recipe 的属性,这个属性是字符串类型,代表含义是“食谱名称”,
  2. 有一个名为ingredients的属性,这个属性是数组类型,代表的含义是“组成部分”

看看执行结果:

至此,我们实现了使用Typescript和LangChain对大模型响应数据的自定义格式化的目标。

四、总结

对于大模型响应数据的格式化处理,是我们在开发AI应用时应该掌握的技能。通常情况下,我们不会仅仅依靠大模型来实现交互,而是会将一些大模型推理后的数据交付给AI应用外部的系统进行处理。这些系统通常是企业中已存在的系统。

但是,我们也看到一些问题。LangChain在处理中文时,还存在一些不完善。我们必须在应用中发现并完善这些点。随着LangChain的升级,相信前端工程师能够在AI大模型开发中,承担越来越重要的职责。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1502225.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode 剑指 Offer II 068.搜索插入位置

题目难度: 简单 原题链接 今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个排序的整数数组 nums 和一个整数目标值 target &#xf…

粘包与拆包

优质博文:IT-BLOG-CN 一、粘包出现的原因 服务端与客户端没有约定好要使用的数据结构。Socket Client实际是将数据包发送到一个缓存buffer中,通过buffer刷到数据链路层。因服务端接收数据包时,不能断定数据包1何时结束,就有可能出…

MySQL 篇-快速了解事务、索引

🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 事务概述 1.1 事务四大特性(ACID) 2.0 索引概述 2.1 关于 “索引一定要创建在主键上?” 的问题 2.2 索引操作语法 2.3 索引结构 1.0 事务概述 事务是…

【ElasticSearch】es索引、映射、文档基本操作复杂查询

各位小伙伴们大家好,欢迎来到这个小扎扎的ElasticSearch专栏,本篇博客由B战尚硅谷的ElasticSearch视频总结而来,鉴于 看到就是学到、学到就是赚到 精神,这波依然是血赚 ┗|`O′|┛ 🌆 内容速览 1 es数据格…

[Electron]中的BrowserView

Electron中BrowserView BrowserView 被用来让 BrowserWindow 嵌入更多的 web 内容。 它就像一个子窗口,除了它的位置是相对于父窗口。 这意味着可以替代webview标签. 示例 const { app, BrowserView, BrowserWindow } require(electron) ​ app.whenReady().the…

[服务器]RTSP服务与ffmpeg推送-简单搭建-Windows与Linux

文章目录 下载地址rtsp服务-mediamtx推流工具-ffmpegVLC播放器 Linux下载安装解压启动 Windows下载安装解压启动 VLC查看视频串流window查看本地摄像头 公司来了个临时需求,正好自己一直想搞一下,例如在VR Chat上放自己的视频[滑稽],所以简单…

Matlab|2机5节点牛拉法(含报告)

目录 主要内容 下载链接 主要内容 采用牛拉法计算2机5节点的潮流计算程序,程序迭代稳定,运行可靠,含报告资料。 下载链接

.NET高级面试指南专题十五【 原型模式介绍,Clone要这样去用】

介绍: 原型模式是一种创建型设计模式,其主要目的是通过克隆现有对象来创建新对象,而不是通过实例化新的对象。这种模式在需要创建相似对象时非常有用,尤其是当对象的创建过程比较昂贵或复杂时。 实现原理: 原型模式通过…

探索Web中的颜色选择:不同取色方法的实现

在Web开发中,提供用户选择颜色的功能是很常见的需求。无论是为了个性化UI主题,还是为了图像编辑工具,一个直观且易用的取色器都是必不可少的。本文将介绍几种在Web应用中实现取色功能的方法,从简单的HTML输入到利用现代API的高级技…

Kafka MQ 主题和分区

Kafka MQ 主题和分区 Kafka 的消息通过 主题 进行分类。主题就好比数据库的表,或者文件系统里的文件夹。主题可以被分为若干个 分区 ,一个分区就是一个提交日志。消息以追加的方式写入分区,然 后以先入先出的顺序读取。要注意,由…

OPC UA 学习:文件传输

本博文是OPC 10000-20: UA Part 20: File Transfer 的学习笔记。 OPC UA的客户端需要读写服务器端的文件,OPCUA 规范中,是通过文件模型实现的。客户端通过调用文件模型中的方法来处理文件。 在控制系统中,需要下载配置文件,工艺文…

掌握java中继承

目录 1.概念: 2.使用: 3.super关键字 4.子类构造方法 5.super和this关键字 6.初始化时代码块的执行顺序 7.继承的方式 8.final关键字 1.概念: 是面向对象程序设计代码可以重复使用的重要手段,允许程序员在保持原有类特性的…

java注释的详尽解析

一、什么是注解 (1).注解的作用 ①:注解一般用于对程序的说明,就像注释一样,但是区别是注释是给人看的,但是注解是给程序看的。 ②:让编译器进行编译检查的作用,比如下边这个Override注解是重写的意思&am…

漏洞复现-蓝凌LandrayOA系列

蓝凌OA系列 🔪 是否利用过 优先级从高到低 发现日期从近到远 公司团队名_产品名_大版本号_特定小版本号_接口文件名_漏洞类型发现日期.载荷格式LandrayOA_Custom_SSRF_JNDI漏洞 LandrayOA_sysSearchMain_Rce漏洞 LandrayOA_Custom_FileRead漏洞

成功解决TypeError: ‘str‘ object does not support item assignment

成功解决TypeError: ‘str’ object does not support item assignment 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程👈 …

Linux系统安装及简单操作

目录 一、Linux系统安装 二、Linux系统启动 三、Linux系统本地登录 四、Linux系统操作方式 五、Linux的七种运行级别(runlevel) 六、shell 七、命令 一、Linux系统安装 场景1:直接通过光盘安装到硬件上(方法和Windows安装…

一篇论文回顾 Sora 文生视频技术的背景、技术和应用。

一篇论文回顾 Sora 文生视频技术的背景、技术和应用。 追赶 Sora,成为了很多科技公司当下阶段的新目标。研究者们好奇的是:Sora 是如何被 OpenAI 发掘出来的?未来又有哪些演进和应用方向? Sora 的技术报告披露了一些技术细节&…

反编译修改halcondonet.dll的名字

安装ILSpy 打开halcondonet.dll 用VS打开 点击生成Dll会提示很多错误 然后点击错误,所有点一遍,错误基本会自己消失, 还需要把.net改为4.5.2 然后生成成功 把名称改为mysql5dotnet 找到HalconAPI类,将里面的所有字符串“hal…

linux下改变主机名,永久生效的方法

hostnamectl set-hostname test 例子 #支持大写必须就要这样写 hostnamectl set-hostname 名称 --static

LCR 164. 破解闯关密码

解题思路&#xff1a; 贪心 class Solution {public String crackPassword(int[] password) {String[] strs new String[password.length];for(int i 0; i < password.length; i)strs[i] String.valueOf(password[i]);Arrays.sort(strs, (x, y) -> (x y).compareTo(…