Be a purist 👋

I’m Jiaming, a Software Developer, Film Photographer, Magician and Digital Drawing Enthusiast.

How to Build a Simple AI Conversational Assistant Based on Langchain.js

本科的最后一门专业课:软件开发实践,要开发一个虚拟宠物医院学习系统,需要实现一个AI对话助手,具体的需求比较模糊。考虑到其他部分都是很低级的增删改查,因此一开始的想法就是能否将增删改查与AI对话相结合,提问的话问数据库中上传的药品/病例信息。 具体架构还是采取了RAG架构,本着一切从简的原则采取了Langchain.js纯前端实现,用OpenAI提供的api接口和Pinecone提供的远程向量数据库服务。 What is RAG(@GPT4): RAG(Retrieval-Augmented Generation)是一种结合了信息检索和生成模型的技术,旨在提高自然语言处理(NLP)任务中的生成质量和相关性。它通过利用外部的知识源(如文档数据库或信息库)来增强生成模型的能力,特别适用于问答、摘要、翻译等任务。 RAG的工作原理 RAG模型主要由两部分组成:检索器和生成器。 检索器: 检索器的任务是从一个大型的文档集合中检索出与输入查询最相关的文档或信息片段。这通常通过向量相似性搜索实现,其中查询和文档都被编码为向量,通过计算向量之间的相似度来找到最匹配的条目。 生成器: 生成器通常是一个基于 Transformer 的语言模型,如 GPT 或 BERT。它使用检索到的文档作为上下文信息来生成答案。这使得生成的内容不仅依赖于模型内部的知识(训练数据中学到的),而且还依赖于外部的、具体的、与当前查询直接相关的信息。 RAG的优势 信息量更丰富:与传统的生成模型相比,RAG通过访问外部数据库增加了处理问题时可以利用的信息量。 准确性和相关性提高:在依赖外部知识进行回答的任务中,RAG可以提供更准确、更详细的答案。 灵活性:可以根据需要访问任何形式的结构化或非结构化数据源。 应用场景 RAG模型在多种应用中都非常有效,例如: 问答系统:能够生成详细的答案,尤其是在需要引用外部信息源进行事实查证时。 内容摘要:从一组相关文档中提取关键信息,生成凝练的摘要。 文章写作:在创建文章内容时,能够参考和整合多种资料和报告,提高内容的丰富性和深度。 技术挑战 尽管RAG技术提供了许多优势,但实现它也面临一些挑战: 检索效率:高效检索相关文档对于实时应用尤为关键,需要优化索引和查询处理。 数据同步:确保所依赖的外部知识库是最新的,这对于保证生成内容的准确性和相关性非常重要。 结果的一致性:生成内容需要与检索到的文档保持一致性,避免产生误导信息或错误。 具体实现 首先在Pinecone官网注册一个账号(省略了) Pinecone中是分一个project可以有很多index,一个index下可以划分很多namespace。由于用的免费版只能有一个project,一个index,因此只能通过定义namespace的方式来区别不同的部分。 例如本项目有药品(medicine)和化验(lab)的查询,下面定义了这两个namespace。namespace是插入时的参数,不用手动设定。查询时可以在智能namespace下查找,十分方便。 对于每条record,namespace和ID不可修改,但是可以根据ID来更新其中的Values和metadata(就是下面的Key-Value键值对。 对于Values,就是执行embedding时转换后的数组。我前端用的是OpenAI的text-embedding-ada-002,转换后有1536维,因此这里的length就是1536。 查阅Pinecone的API接口,先写插入的函数,其中upsert就是首先看id是否存在,存在的话就更新,否则就插入。 export const pineconeAdd = async (id, namespace, input_text, metadata) => { const embedding = await embedText(input_text); try { const insertResponse = await index.namespace(namespace).upsert([ { id: `${namespace}${id}`, values: embedding, metadata: metadata } ]); return { success: true, message: 'Pinecone 添加成功', detail: insertResponse }; } catch (error) { return { success: false, message: 'Pinecone 插入错误', error }; } }; 前端插入部分的逻辑,这部分跟往Mysql插入同时执行。更新也是一样:...

April 22, 2024 · 2 min · 289 words · Me

Leetcode record - July 2023

关于区间集合: 这个问题的难点在于如何有效地处理会议的冲突。具体来说,需要在每个时间点追踪哪些会议正在进行,然后决定是否需要额外的会议室。下面是一些思考的建议和线索: 排序:这是一个重要的线索。当你看到这种需要比较或者组织时间区间的问题时,排序通常是一个有用的第一步。在本问题中,按照开始时间对会议进行排序可以帮助我们逐个地处理会议,确保我们先处理开始时间早的会议。 优先队列(最小堆):在处理一系列事件,其中每个事件都有开始和结束时间,并且可能会发生冲突的情况下,优先队列(特别是最小堆)经常会被用到。在本问题中,我们需要知道当前正在进行的所有会议中最早结束的那一个,以决定是否需要一个新的会议室。最小堆正好可以提供这个功能,因为它总是把最小的元素放在前面。 贪心思想:这是一个隐藏的线索。这个问题可以通过一种贪心的方式来解决,即总是尽可能地复用会议室。具体来说,每次当一个新的会议开始时,如果有一个会议已经结束,那么我们就可以在同一个会议室举行这个新的会议,而不需要新开一个会议室。这个策略保证了我们总是使用最少数量的会议室。 重叠区间问题:类似的问题经常出现在算法问题中,所以可以作为一个模式来识别。当你看到需要处理重叠区间的问题时,可以想到使用排序和优先队列来解决。 在碰到这类问题时,练习和经验也很重要。一开始可能不容易看出解决方案,但通过解决更多的类似问题,你将能够更容易地识别出相应的模式,并找到正确的方法来解决这类问题。 253. Meeting Rooms II Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required. Example 1: Input: intervals = [[0,30],[5,10],[15,20]] Output: 2 Example 2: Input: intervals = [[7,10],[2,4]] Output: 1 Constraints: 1 <= intervals.length <= 104 0 <= starti < endi <= 106 class Solution { public int minMeetingRooms(int[][] intervals) { Arrays.sort(intervals, (a, b)->Integer....

July 29, 2023 · 5 min · 940 words · Me

Use Swagger to show API in Django

Use Swagger to show API in Django Document: https://drf-yasg.readthedocs.io/en/stable/readme.html#usage A good example: https://debrajbhal.hashnode.dev/adding-custom-documentation-to-apis-in-django-rest-framework-using-drf-yasg Add Post parameters: https://stackoverflow.com/questions/50929098/django-rest-framework-how-to-add-post-parameters-to-api-documentdrf-yasg Hard code response parameters: https://stackoverflow.com/questions/62914407/drf-yasg-how-to-hide-django-rest-framework-schema #import from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework.decorators import api_view For GET method: #CBV version class TsView: @staticmethod @api_view(['GET']) #it depend. Add it when you choose to write CBV @swagger_auto_schema( operation_description="get ts info based on ts number", responses={ 200: openapi.Response("request success", schema=openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT))), 400: "request failed", }, ) def get_ts_info(request, ts_num): """ Retrieve the detailed information of a specific ts by its number....

July 29, 2023 · 2 min · 245 words · Me

Leetcode record - June 2023

624. Maximum Distance in Arrays You are given m arrays, where each array is sorted in ascending order. You can pick up two integers from two different arrays (each array picks one) and calculate the distance. We define the distance between two integers a and b to be their absolute difference |a - b|. Return the maximum distance. Example 1: Input: arrays = [[1,2,3],[4,5],[1,2,3]] Output: 4 Explanation: One way to reach the maximum distance 4 is to pick 1 in the first or third array and pick 5 in the second array....

June 28, 2023 · 3 min · 621 words · Me

Leetcode record - March 2023

Binary Search Find left bound/right bound public int[] searchRange(int[] nums, int target) { return new int[]{helper(nums, target, true), helper(nums, target, false)}; } public int helper(int[] nums, int target,boolean trueIfSearchLeftBound){ int l = 0; int r = nums.length-1; int res = -1; while (l<=r){ int midl = (l+r)/2; if (nums[midl]>target){ r = midl-1; }else if(nums[midl]<target){ l = midl+1; }else{ res = midl; if (trueIfSearchLeftBound){ r = midl-1; }else{ l = midl+1; } } } System....

March 21, 2023 · 5 min · 1022 words · Me

基于Vue-Springboot的论文管理系统

演示地址:ECNU-2022Spring-DatabaseTermProject第四组_哔哩哔哩_bilibili 关于登录信息、权限显示 这里利用sessionStorage将用户的信息跟权限(理论上应该一起作为user信息返回的,这里分开存储。后续页面左侧Aside的访问也可以根据存储的sessionStorage解决。 *sessionStorage 属性允许你访问一个 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage 里面存储的数据没有过期时间设置,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除。页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。**在新标签或窗口打开一个页面时会在顶级浏览上下文中初始化一个新的会话,*这点和 session cookies 的运行方式不同。 request.post("/api/login", this.form).then(res => { console.log(res) if (res.code === 0) { console.log(3333) this.$message({ type: "success", message: "登录成功" }) sessionStorage.setItem("user",JSON.stringify(res.data)) // 缓存用户信息 request.post("/api/permission?user_id=" + res.data.user_id).then(res1 => { console.log(res1) console.log("permission Get") sessionStorage.setItem("userPermission",JSON.stringify(res1)) // 缓存用户信息 let userStrr =sessionStorage.getItem("userPermission") || "{}" this.permissionList =JSON.parse(userStrr) activeRouter() this.$router.push("/") //登录成功之后进行页面的跳转,跳转到主页 }) // 登录成功的时候更新当前路由 //activeRouter() } else { this.$message({ type: "error", message: res.msg }) } }) 关于路由配置 需要避免任何用户都能通过改url的方式访问对应页面,因此考虑如果此用户没有访问该页面的权限,便不进行路由加载。采取循环的方式进行路由注册。(addRoute)...

February 26, 2023 · 2 min · 381 words · Me