开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

AI-GC-手把手教你写一个小说推文生成器(Prompt开发

发表于 2024-04-24

前言

okey,那么接下来我们来看到我们如何使用Prompt来完成Agent开发。来让LLM完成我们想要让它完成的工作。当然这里的开发还是非常渐层的开发,实际上我们有复杂的,但是得益于开源生态的完善,目前一些看起来比较深层的开发例如RAG也已经被封装的太好了。
那么这里的话,我们主要有两个步骤需要处理:

  1. 得到视频分镜
  2. 将生成图像的文本翻译为英文提示词

当然这里应该是三个任务,但是我这里直接合并为两个任务,要相信LLM的推理能力,当然你也可以将任务再进行拆分。

Agent实现

okey,现在的话我们来做到我们的Agent。这里话,其实也不是啥复杂的概念。记住LLM是一个很牛逼的推理引擎就够了。

Prompt设计

那么在这里面的话,我们设计了两个提示词:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ini复制代码ExtractSegmentNovel = """
你是一个想象力非常丰富的聊天机器人,接下来你将得到一个文本,你需要尽可能找出哪些段落是可以用同一个
想象的画面描述的,一定不能遗漏掉全部文本。
之后将这些段落描述出具体的画面并且按照顺序返回描述的列表。请注意,这些描述语句将交给图像生成AI
来生成图像,图像生成AI是不理解故事内容的所以具体画面描述不要出现人名且能够准确地描述出画面。
并且严格按照格式返回。如果文本为空,返回[]空列表即可
返回格式如下:[{"场景1":"原文句子","描述1":"具体画面描述"},{"场景2":"原文句子","描述2":"具体画面描述"}]
"""

# ExtractSegmentNovel = """
# 你是一个想象力非常丰富的聊天机器人,接下来你将得到一个文本,你需要尽可能找出哪些段落是可以用同一个
# 想象的画面描述的,一定不能遗漏掉全部文本。注意具体画面描述将交给图像生成AI来生成图像,
# 图像生成AI是无法理解故事内容的所以具体画面描述不要出现人名,故事情节且能够准确地描述出画面。
# 并且严格按照格式返回。
# 返回格式如下:
# {"场景1":"原文句子","描述":"具体画面描述"},
# {"场景2":"原文句子","描述":"具体画面描述"},
# """

ToImagePrompt = """
你是翻译小能手,接下来你将得到一个文本,你需要将其翻译为英文。
注意只需要返回英文即可,如果输入的就是英文,那么不要翻译直接返回原话。
"""

使用提示词

那么当我们设计好提示词之后,我们就可以直接调用LLM了,这个调用过程也并不复杂,其实很多东西,LLM都可以理解。在这块的话就是基本的调用LLM,没啥好说的,直接看到代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
python复制代码"""
@FileName:image_prompt.py
@Author:Huterox
@Description:Go For It
@Time:2024/4/16 22:35
@Copyright:©2018-2024 awesome!
"""
import json
import re
import typing

from openai import OpenAI

from agent.prompt_template import ExtractSegmentNovel, ToImagePrompt
from utils import Config, getConfig

"""
将小说进行分段
"""

api_key = Config.settings.get("openai_api_key")
client = OpenAI(api_key=api_key,base_url=Config.settings.get("openai_api_base"))

"""
实际测试效果发现,使用"moonshot-v1-8k"效果总是稳定,这里尝试使用chat
"""
import requests
import json

config = getConfig()

class MyOpenAI():

def __init__(self):
self.url = "https://api.openai-hk.com/v1/chat/completions"

self.headers = {
"Content-Type": "application/json",
# 这里采用的是中转站的openai key
"Authorization": "Bearer "+config.get("image_api_key")
}

def chat(self,message,prompt,temperature=0.8):
data = {
"max_tokens": 1200,
"model": "gpt-3.5-turbo",
"temperature": temperature,
"top_p": 1,
"presence_penalty": 1,
"messages": [
{
"role": "system",
"content":prompt
},
{
"role": "user",
"content": message
}
]
}

response = requests.post(self.url, headers=self.headers, data=json.dumps(data).encode('utf-8'))
result = response.content.decode("utf-8")
result = json.loads(result)
result = result["choices"][0]["message"]["content"]
return result



class ImagePromptAgent(object):

def __init__(self):
self.my_open_ai = MyOpenAI()

def __send(self,message:typing.List,prompt:typing.List,temperature:float=0.8)->typing.AnyStr:
history_openai_format = [
{"role": "system",
"content":prompt
},
{"role": "user",
"content": message
},
]
completion = client.chat.completions.create(
model=Config.settings.get("default_model"),
messages=history_openai_format,
# 这里需要可能需要一约束
temperature=temperature,
)
result = completion.choices[0].message.content
# 这里我们直接使用openAI
# result = self.my_open_ai.chat(message,prompt,temperature)
return result


def ExtractSegmentNovel(self,message,temperature=0.4):
# scenes_list = json.loads(self.__send(message,ExtractSegmentNovel))
# return scenes_list
return self.__send(message,ExtractSegmentNovel,temperature)

def ToImagePrompt(self,message,temperature=0.4):
# english_prompt ="best quality,masterpiece,illustration, an extremely delicate and beautiful,extremely detailed,CG,unity,8k wallpaper, "+\
# self.__send(message,ToImagePrompt,temperature)

english_prompt = "best quality " + \
self.my_open_ai.chat(message, ToImagePrompt, temperature)+" --ar 4:3"

return english_prompt
def ToEnglish(self,text,temperature=0.8):
english_prompt = self.my_open_ai.chat(text, ToImagePrompt, temperature)
return english_prompt

封装😶

虽然我们直接实现对接完毕了,但是这里的话,为了和我们的UI配合,我们当然还是需要封装一下的。这里的话,还是直接看到上一个章节提到的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
python复制代码"""
@FileName:image_prompt.py
@Author:Huterox
@Description:Go For It
@Time:2024/4/16 22:35
@Copyright:©2018-2024 awesome!
"""
import json
import re
import typing

from openai import OpenAI

from agent.prompt_template import ExtractSegmentNovel, ToImagePrompt
from utils import Config, getConfig

"""
将小说进行分段
"""

api_key = Config.settings.get("openai_api_key")
client = OpenAI(api_key=api_key,base_url=Config.settings.get("openai_api_base"))

"""
实际测试效果发现,使用"moonshot-v1-8k"效果总是稳定,这里尝试使用chat
"""
import requests
import json

config = getConfig()

class MyOpenAI():

def __init__(self):
self.url = "https://api.openai-hk.com/v1/chat/completions"

self.headers = {
"Content-Type": "application/json",
# 这里采用的是中转站的openai key
"Authorization": "Bearer "+config.get("image_api_key")
}

def chat(self,message,prompt,temperature=0.8):
data = {
"max_tokens": 1200,
"model": "gpt-3.5-turbo",
"temperature": temperature,
"top_p": 1,
"presence_penalty": 1,
"messages": [
{
"role": "system",
"content":prompt
},
{
"role": "user",
"content": message
}
]
}

response = requests.post(self.url, headers=self.headers, data=json.dumps(data).encode('utf-8'))
result = response.content.decode("utf-8")
result = json.loads(result)
result = result["choices"][0]["message"]["content"]
return result



class ImagePromptAgent(object):

def __init__(self):
self.my_open_ai = MyOpenAI()

def __send(self,message:typing.List,prompt:typing.List,temperature:float=0.8)->typing.AnyStr:
history_openai_format = [
{"role": "system",
"content":prompt
},
{"role": "user",
"content": message
},
]
completion = client.chat.completions.create(
model=Config.settings.get("default_model"),
messages=history_openai_format,
# 这里需要可能需要一约束
temperature=temperature,
)
result = completion.choices[0].message.content
# 这里我们直接使用openAI
# result = self.my_open_ai.chat(message,prompt,temperature)
return result


def ExtractSegmentNovel(self,message,temperature=0.4):
# scenes_list = json.loads(self.__send(message,ExtractSegmentNovel))
# return scenes_list
return self.__send(message,ExtractSegmentNovel,temperature)

def ToImagePrompt(self,message,temperature=0.4):
# english_prompt ="best quality,masterpiece,illustration, an extremely delicate and beautiful,extremely detailed,CG,unity,8k wallpaper, "+\
# self.__send(message,ToImagePrompt,temperature)

english_prompt = "best quality " + \
self.my_open_ai.chat(message, ToImagePrompt, temperature)+" --ar 4:3"

return english_prompt
def ToEnglish(self,text,temperature=0.8):
english_prompt = self.my_open_ai.chat(text, ToImagePrompt, temperature)
return english_prompt

但是在这里要注意一点,那就是这里的话,我们对于结果的提取其实还是使用了正则表达进行提取,并且我们也需要做一些异常处理,这是因为,LLM返回的格式不一定是你想要的,这取决于LLM的推理能力。

至此,有关于AI的部分其实我们就封装完毕了,那么接下来的工作就是,我们造好的零件将如何组装为一辆汽车的问题。

考虑到内容还是比较长,这里还是将文章拆分😉

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

支持私有部署的云端存储双链笔记软件

发表于 2024-04-24

大家好,我是 Java陈序员。

我们无论是日常生活还是办公,常常需要使用一些工具软件来记录笔记、代办事项等。

今天,给大家介绍一款支持私有化部署、支持多端使用的笔记软件。

关注微信公众号:【Java陈序员】,获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。

项目介绍

Blossom —— 一个支持私有部署的云端双链笔记软件,将笔记,图片,个人计划安排保存在自己的服务器中。

Blossom 支持 Windows、Mac、网页客户端和网页移动端,任意设备之间可以实现实时同步。

功能特色:

  • 支持 Markdown 格式的文章编辑器,可以按照文件夹进行划分
  • 不依赖任务对象存储,就能实现文章图片保存、管理
  • 多项常用功能,主题设置,番茄钟,字数统计,字数折线图,编辑热力图,天气预报等
  • 支持创建计划和代办事项
  • 服务端部署 Docker 部署,客户端网页部署
  • 多用户隔离,允许创建多个账号
  • 响应式博客,支持移动端

项目截图

首页

编辑器

双链笔记

图片墙

代办事项清单

日历清单

便签管理

博客

博客移动端

项目部署

Blossom 依赖 Java8 和 MySQL8 环境,需要先安装好这两个环境。

Docker 一键部署

1、安装 MySQL(如果你已经安装好 MySQL,这里可以跳过)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 拉取镜像
docker pull mysql:8.0.31

# 启动容器
docker run \
-d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=root \
-p 3306:3306 \
-v /data/software/mysql/data:/var/lib/mysql \
-v /data/software/mysql-files/log:/var/lib/mysql-files \
-v /data/software/mysql/log:/var/log/mysql \
mysql:8.0.31

2、创建数据库

1
CREATE DATABASE `blossom` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

数据库名称需要与启动容器命令中参数 –spring.datasource.url 配置的数据库名称相同。

3、拉取镜像

1
docker pull jasminexzzz/blossom:latest

4、启动容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
docker run -d \
# 容器名称
--name blossom-backend \
# 指定端口映射
-p 9999:9999 \
#【需修改】挂载图片保存路径,如果是 windows 环境,可以使用 /c/home/bl/ 来指定磁盘
# 注意:只需要将冒号前的路径改为你 Docker 所在设备的某个路径,不要修改冒号后面的内容。
-v /data/software/bl/:/home/bl/ \
# 启动的镜像名称
jasminexzzz/blossom:latest \
#【需修改】配置数据库访问地址
--spring.datasource.url="jdbc:mysql://192.168.31.99:3306/blossom?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&allowMultiQueries=true&useSSL=false&&serverTimezone=GMT%2B8" \
#【可选修改】配置数据库用户名
--spring.datasource.username=root \
#【可选修改】配置数据库密码
--spring.datasource.password=root

5、测试

部署成功后,直接访问 http://{ip}:9999 测试是否成功部署。

如是云服务器,记得开放端口权限。

Jar 包部署

1、创建数据库

1
CREATE DATABASE `blossom` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

数据库名称需要与启动容器命令中参数 –spring.datasource.url 配置的数据库名称相同。

2、源码打包

可以将源码克隆下来后,使用 Maven 打成 Jar 包,或者直接在项目的 release 中下载。

3、部署 Jar 包

1
2
3
4
5
6
7
8
9
10
11
nohup java -jar ./backend-blossom.jar \
# 如果 9999 端口已被占用,注意修改
--server.port=9999 \
#【需修改】配置图片保存的磁盘路径
--project.iaas.blos.default-path="/data/software/bl/img" \
#【需修改】配置数据库访问地址
--spring.datasource.url="jdbc:mysql://192.168.31.99:3306/blossom?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&allowMultiQueries=true&useSSL=false&&serverTimezone=GMT%2B8" \
#【需修改】配置数据库用户名
--spring.datasource.username=root \
#【需修改】配置数据库密码
--spring.datasource.password=root &

5、测试

部署成功后,直接访问 http://{ip}:9999 测试是否成功部署。

如是云服务器,记得开放端口权限。

客户端部署

自 1.10.0 版本开始,部署后台时会自带网页客户端,客户端在浏览器的访问地址为 http://IP(域名):9999/editor/#/settingindex.

博客/移动端部署

自 1.10.0 版本开始,部署后台时会自带网页客户端,客户端在浏览器的访问地址为 http://IP(域名):9999/blog/#/home.

Blossom 集笔记、博客、图床、代办、计划、便签等多功能于一体。如果你正在找寻一个笔记软件,可以考虑部署使用 Blossom ~

最后,贴上项目地址:

1
https://github.com/blossom-editor/blossom

在线体验地址:

1
https://www.wangyunf.com/blossom-demo/#/home

最后

推荐的开源项目已经收录到 GitHub 项目,欢迎 Star:

1
https://github.com/chenyl8848/great-open-source-project

或者访问网站,进行在线浏览:

1
https://chencoding.top:8090/#/

大家的点赞、收藏和评论都是对作者的支持,如文章对你有帮助还请点赞转发支持下,谢谢!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

抢先看!美团、京东、360等大厂面试题解析,技术面试必备。

发表于 2024-04-24

技术面试必备!美团、京东、360等大厂面试题详解,让你轻松应对各大公司面试挑战!

往期硬核面经

哦耶!冲进腾讯了!

牛逼!上岸腾讯互娱和腾讯TEG!

腾讯的面试,强度拉满!

前几篇文章分享了上岸腾讯的最新面经。

不少粉丝股东留言说别只发腾讯的啦,其他大厂的也安排一些吧,比如美团、360、京东的。

必须安排,这篇文章就给大家安排上:

美团 一面

  1. 自我介绍
  2. leetcode141 环形链表,判断是否有环路
  3. 延申leetcode142 环形链表2,找到入口位置
  4. 判断一个sql(左右模糊查询)能否命中索引
  5. 为什么左右模糊不能命中索引
  6. 如何优化这个sql(左右模糊查询)
  7. 全文索引底层怎么实现的
  8. 建立分词后,如何进行快速的模糊检索
  9. 分词用的什么数据结构
  10. 跳表的时间复杂度,哈希表呢
  11. 哈希表有什么问题
  12. 说一下MySQL的索引
  13. 什么是聚簇索引、非聚簇索引
  14. 事务隔离级别
  15. InnoDB如何实现可重复读
  16. 死锁是什么,如何解决死锁
  17. 拷打项目
  18. 你觉得科研中什么能力比较重要

美团 二面

  1. 自我介绍
  2. 拷打项目
  3. socket、TCP/IP 的关系
  4. 输入 url 的到浏览器显示网页的过程
  5. 反问

京东一面 (30min 京东微服务中间件开发部门)

  1. 自我介绍
  2. 实习项目中确保结果准确性
  3. 实习项目上游数据有问题怎么办
  4. Golang new 和make的区别
  5. 为什么多协程优化,不使用协程呢?
  6. 实习项目过程中还有什么可以优化的地方
  7. 微服务的优势,为什么用微服务
  8. 怎么确保线程安全
  9. 信号量和锁的区别
  10. 你有什么问题吗?

360 一面

  1. 自我介绍
  2. 实现LRU算法
  3. 这个lru算法用在生产环境会出现什么问题
  4. 如果十万个线程同时put,会出现什么问题
  5. 如何控制并发,加什么锁,读写锁还是互斥锁
  6. 进程、线程、协程
  7. 程序从加载到运行的过程
  8. 为什么线程消耗比协程大,具体体现在哪些方面
  9. 生产中哪些服务用的 进程、线程,为什么要这么做,有什么好处(今天第二次问到)
  10. nginx master-worker进程、进程与redis 进程、线程
  11. 数据库事务,隔离级别
  12. 各个隔离级别怎么实现的,原理
  13. RR能解决幻读吗,为什么
  14. MVCC
  15. 数据库中的锁
  16. 乐观锁、悲观锁适用于什么情况,并发,读多写少
  17. 分布式锁
  18. zookeeper、mysql、redis 、etcd 怎么实现分布式锁,各有什么优缺点,生产中一般用那个
  19. zookeeper原理,怎么保持高可用
  20. tcp三次握手、四次挥手
  21. timewait、closewait出现,怎么处理
  22. tcp调优相关参数
  23. 拥塞控制算法、滑动窗口、零窗口报文
  24. http1.0 - http3.0 改进,优缺点

360 二面

  1. 说说 Linux 常用操作,ps 是干嘛的
  2. Go map 为什么是无序的?
  3. MySQL 索引分类,索引的优点和缺点
  4. 分库分表
  5. Cookie 和 Session 的区别和应用,分布式 Session 的实现
  6. TCP三次握手/四次挥手
  7. Redis 的数据类型及其数据结构,Bitmap 是什么
  8. Redis 的雪崩效应 ,是什么,怎么解决
  9. 线程,进程,协程区别
  10. 算法:用 Go 手撕快速排序

早日上岸!

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。

本文首发在我的同名公众号:王中阳Go,未经授权禁止转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

掌握KUBERNETES POD故障排除:高级策略和方案

发表于 2024-04-24

Kubernetes(K8s)部署通常会带来各种角度的挑战,包括 pod、服务、ingress、无响应集群、控制平面和高可用性设置。Kubernetespod是 Kubernetes 生态系统中最小的可部署单元,封装了一个或多个共享资源和网络的容器。Pod 旨在运行应用程序或进程的单个实例,并根据需要创建和处置。Pod 对于在 K8s 环境中扩展、更新和维护应用程序至关重要。

译自Master Kubernetes Pods: Advanced Troubleshooting Strategies,作者 None。

本文探讨了 Kubernetes pod 面临的挑战以及要采取的故障排除步骤。运行 Kubernetes pod 时遇到的部分错误消息包括:

  • ImagePullBackoff
  • ErrImagePull
  • InvalidImageName
  • CrashLoopBackOff

有时,您甚至不会遇到列出的错误,但仍会发现您的 pod 失败。首先,需要注意的是,在调试任何 Kubernetes 资源时,您都应该了解API 参考。它解释了如何定义各种 Kubernetes API 以及 pod/部署中的多个对象如何工作。文档在Kubernetes 网站上的 API 参考中定义得很明确。在这种情况下,在调试 pod 时,从 API 参考中选择 pod 对象以详细了解 pod 的工作原理。它定义了进入 pod 的字段,即版本、类型、元数据、规范和状态。Kubernetes 还提供了一个作弊小炒,其中包含所需命令的指南。

先决条件

本文假设读者具备以下条件:

  • 已安装Kind以进行场景演示
  • 对 Kubernetes 架构有中级了解
  • Kubectl命令行工具

Kubernetes Pod 错误 - ImagePullBackoff

该错误显示有三个不同的原因:

  • 无效镜像
  • 无效标签
  • 无效权限

当您没有有关镜像的正确信息时,就会出现这些情况。您可能也没有从其存储库(私有存储库)中提取镜像的权限。为了在下面的示例中演示这一点,我们创建了一个 nginx 部署:

1
ini复制代码➜ ~ kubectl create deploy nginx --image=nginxdeployment.apps/nginx created

Pod 正在运行后,获取 pod 名称:

1
2
3
sql复制代码➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-8f458dc5b-hcrsh 1/1 Running 0 100s

复制正在运行的 pod 的名称并获取有关它的更多信息:

1
2
3
4
5
6
7
8
9
10
11
vbnet复制代码➜ ~ kubectl describe pod nginx-8f458dc5b-hcrsh
Name: nginx-8f458dc5b-hcrsh
hable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m43s default-scheduler Successfully assigned default/nginx-8f458dc5b-hcrsh to k8s-troubleshooting-control-plane
Normal Pulling 2m43s kubelet Pulling image "nginx"
Normal Pulled 100s kubelet Successfully pulled image "nginx" in 1m2.220189835s
Normal Created 100s kubelet Created container nginx
Normal Started 100s kubelet Started container nginx

镜像已成功拉取。您的 Kubernetes pod 正在运行,没有错误。

要演示 ImagePullBackoff,请编辑部署 YAML 文件并指定一个不存在的镜像:

1
2
3
4
5
yaml复制代码➜ kubectl edit deploy nginx
containers:
-image: nginxdoestexist
imagePullPolicy: Always
name: nginx

新 pod 未成功部署

1
2
3
4
sql复制代码➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-5b847fdb95-mx4pq 0/1 ErrImagePull 0 3m40s
nginx-8f458dc5b-hcrsh 1/1 Running 0 38m

显示 ImagePullBackoff 错误

1
2
3
4
5
6
7
8
9
10
sql复制代码➜  ~ kubectl describe pod nginx-6f46cbfbcb-c92bl
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 88s default-scheduler Successfully assigned default/nginx-6f46cbfbcb-c92bl to k8s-troubleshooting-control-plane
Normal Pulling 40s (x3 over 88s) kubelet Pulling image "nginxdoesntexist"
Warning Failed 37s (x3 over 85s) kubelet Failed to pull image "nginxdoesntexist": rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/library/nginxdoesntexist:latest": failed to resolve reference "docker.io/library/nginxdoesntexist:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
Warning Failed 37s (x3 over 85s) kubelet Error: ErrImagePull
Normal BackOff 11s (x4 over 85s) kubelet Back-off pulling image "nginxdoesntexist"
Warning Failed 11s (x4 over 85s) kubelet Error: ImagePullBackOff

Kubernetes Pod 错误 - 已拉取镜像但 Pod 处于 pending 状态。

每当你在生产环境中运行 K8s 时,K8s 管理员会根据集群内运行的命名空间的要求为每个命名空间分配资源配额。命名空间用于在集群内进行逻辑分离。

当资源配额中的规范不满足 Pod 中应用程序的最低要求时,就会抛出“Image pulled, but the pod is still pending”错误。在以下示例中,创建一个名为 payments 的命名空间:

1
2
3
arduino复制代码➜ ~ kubectl create ns payments

namespace/payments created

使用相关规范创建资源配额

1
2
3
4
5
6
7
8
9
10
11
yaml复制代码➜  ~ cat resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-resources
spec:
hard:
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 4Gi

将资源配额分配给命名空间 payments

1
bash复制代码➜ ~ kubectl apply -f resourcequota.yaml -n paymentsresourcequota/compute-resources created

已创建资源配额/compute-resources

在具有资源配额限制的命名空间内创建新部署:

1
ini复制代码kubectl create deploy nginx --image=nginx -n paymentsdeployment.apps/nginx created

尽管已成功创建部署,但没有 Pod 存在:

1
2
3
arduino复制代码➜ ~ kubectl get pods -n payments

No resources found in payments namespace

已创建部署,但是没有处于准备状态的 Pod,没有更新的 Pod,也没有可用的 Pod:

1
2
3
vbnet复制代码➜  ~ kubectl get deploy -n payments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 0/1 0 0 7m4s

要进一步调试,请描述 nginx 部署。Pod 创建失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
yaml复制代码➜  ~ kubectl describe deploy nginx -n payments
Name: nginx
Namespace: payments
CreationTimestamp: Wed, 24 May 2023 21:37:55 +0300
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=nginx
Replicas: 1 desired | 0 updated | 0 total | 0 available | 1 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available False MinimumReplicasUnavailable
ReplicaFailure True FailedCreate
Progressing False ProgressDeadlineExceeded
OldReplicaSets: <none>
NewReplicaSet: nginx-8f458dc5b (0/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 10m deployment-controller Scaled up replica set nginx-8f458dc5b to 1

从 Kubernetes 事件进行的进一步分析显示 Pod 创建所需的内存不足。

1
csharp复制代码➜ ~ kubectl get events --sort-by=/metadata.creationTimestamp

当你的镜像已成功拉取,并且你的容器已创建,但你的运行时配置失败时,就会发生此错误。例如,如果你有一个正在尝试写入不存在的文件夹或没有写入该文件夹的权限的正在工作的 Python 应用程序。最初,应用程序会执行,然后遇到错误。如果你的应用程序逻辑中出现 panic ,则容器将停止。容器将进入 CrashLoopBackOff。最终,你观察到部署没有 Pod,即存在一个 Pod,但它没有运行并抛出 CrashLoopbackoff 错误。

存活和就绪探测失败

存活(Liveness)探测检测 Pod 是否已进入损坏状态且无法再提供流量。Kubernetes 将为您重新启动 Pod。就绪(readiness )探测检查您的应用程序是否已准备好处理流量。就绪探测确保您的应用程序从配置映射中提取所有必需的配置并启动其线程。只有完成此过程后,您的应用程序才准备好接收流量。如果您的应用程序在此过程中遇到错误,它也会进入 CrashLoopBackoff。

开始故障排除!

本文概述了 Kubernetes Pod 的故障排除技术。它解决了在部署 Pod 时遇到的常见错误,并提供了解决这些错误的实用解决方案。它还深入了解了在理解 Kubernetes 工作原理和有效识别和解决问题时至关重要的参考页面和备忘单。通过遵循本文中提供的指导,读者可以提高他们的故障排除技能,并简化其 Kubernetes Pod 的部署和管理。

本文在云云众生(yylives.cc/)首发,欢迎大家访问。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Coze 扣子 用插件打造 "匠心千刃" 工具

发表于 2024-04-24

在 《Coze 扣子 | AI 养育计划 - “Flutter 大师”》 一文中,介绍了 Coze 中创建 AI Bot 的过程,并且基于 知识库 和 数据库 作为 AI Bot 的大脑,提供知识储备打造 Flutter 大师。AI Bot 的聊天产出内容相对随机,如何能让 AI Bot 准确地通过自然语言,来精确处理任务呢?

本文就介绍一下,基于 Coze 扣子 插件,打造一款自然语言执行工具箱 “匠心千刃” 。

image.png


一、 插件拓展 AI Bot 的上限

插件官网介绍: www.coze.cn/docs/guides…

1. 插件是什么

AI Bot 可以接收用户的输入指令,通过知识库大脑进行分析产出结果,相当于与用户交互的 客户端。而插件可以向 AI Bot 提供精确的接口运算数据,就相当于 服务端 提供 api 接口。当用户的输入,命中插件中的接口时,可以通过对应 api 返回的数据,进行回复。

image.png

插件可以大大拓展 AI Bot 的上限。知识库 和 数据库 作为 AI Bot 的大脑,它只能基于数据做 感性 的分析;而缺乏理性的判断。插件则是通过编程语言,绝对理性地执行命令。同时用户只需通过 自然语言 来触发调用,这是非常有想象空间的。


2. 插件的价值

就像你的大脑再怎么强悍,网络查询到实时信息、对一百万个数字排序这些任务不借助外物是无法实现的。而插件就相当 让 AI Bot 的大脑,拥有编程的能力 。比如下面通过插件让 AI Bot 拥有搜索 github 仓库的功能:

image.png


或是可以给出自然语言描述,让 AI Bot 通过接口插件产出数据。如下所示,批量生成指定长度、个数的随机秘钥:

image.png


3. 插件带来的前后端分离

这样的视角下,就可以有一个很有趣现象。插件是提供数据的服务,相当于 服务端接口 。发布插件相当于部署服务,插件的代码逻辑相当于后端开发。这样 AI Bot 在发布之后,后端的数据可以发布,而不需要重新更新 AI Bot 。这就是前后端分离的思想:

image.png


打个比方,指南 命令,可以展示当前 匠心千刃 的能力。这个输出数据是通过插件接口得到的,所以可以进行更改,AI Bot 作为 前端 可以随时访问 后端 最新数据,不需更新。

image.png


二、通过 Coze IDE 创建插件

Coze 插件可以通过 python 和或 Node.js 编写代码,提供访问的 api。

官网介绍: www.coze.cn/docs/guides…

1. 创建插件

在扣子 主页/个人中心/插件/创建插件 中创建插件。由两种方式:

  • 基于已有服务,是基于已经有的服务器接口,提供数据
  • Coze IDE 创建插件,可以自己编辑服务代码,产出数据,灵活性非常高。

image.png


先通过一个简单的例子,介绍一下插件接口最简单的使用。如下所示,输入 指南 可以输出匠心千刃当前的功能。

image.png


下面是进入插件后的编辑区,可以展示当前插件中的所有接口。在代码区中书写接口代码。右侧可以运行测试,以及发布插件:

image.png

每个接口都有一个 handler 函数,用于逻辑处理。其中 Args 可以获取输入参数,当前接口没有使用输入,后面其他接口再做介绍。返回值里传出当前接口产出的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
python复制代码from runtime import Args
from typings.help.help import Input, Output

def handler(args: Args[Input])->Output:
return {"message": """
文字处理
随机秘钥, 参数:长度、个数

日期工具
输入: [日期] 或者 [date] 查看当前日期
输入: [时间戳] 转换 Unix 时间戳

搜索:
搜索 github 仓库

调用方式,命令+参数:
例: 日期
例: 随机秘钥,长度 30;个数10
"""}

2. 通过网络访问接口请求数据

如下所示,github 搜索仓库可以通过 python 进行网络请求相关接口,传入搜索的仓库名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
python复制代码def search_github_repo(repo_name):
base_url = "https://api.github.com"
search_path = "/search/repositories"
params = {"q": repo_name}
response = requests.get(base_url + search_path, params=params)
ret = []
if response.status_code == 200:
data = response.json()
repos = data.get("items", [])
for repo in repos:
my_dict = {}
my_dict['name'] = "名称:" + repo.get("name"),
my_dict['star'] = "Star:" + str(repo.get("stargazers_count")),
my_dict['URL'] = "URL:" + repo.get("html_url"),
my_dict['description'] = "描述:" + str(repo.get("description")),
ret.append(my_dict)
else:
ret.append("查询失败:"+response.text)
return ret

接口中的元数据,可以设定 输入参数 和 输出参数 。在 handler 方法中, Args 参数的 input 可以访问对应设置的参数,调用 search_github_repo 函数获取数据:

image.png

1
2
3
4
5
6
7
8
python复制代码from runtime import Args
from typings.github_search.github_search import Input, Output
import requests

def handler(args: Args[Input])->Output:
args = args.input.args
ret = search_github_repo(args)
return {"message": ret }

网络请求中用到的的 requests 库,需要增加依赖包:

image.png


3. 随机数生成器

除了调用网络接口得到数据外,插件中的代码还可以执行逻辑,产出数据。这里拿 随机数生成器 来说,输入秘钥长度和生成的个数:

image.png

1
2
3
4
5
python复制代码def handler(args: Args[Input])->Output:
len = args.input.length
count = args.input.outputCount
ret = gen(len,count)
return { "message": ret }

如下所示,提供 gen 方法生成数据,从大写字母、小写字母、数字和特殊字符中随机挑选字符组合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
python复制代码def gen(len, outputCount):
letter_map = {
LetterType.low: range_letter(ord('a'), ord('z') + 1),
LetterType.up: range_letter(ord('A'), ord('Z') + 1),
LetterType.num: range_letter(ord('0'), ord('9') + 1),
LetterType.sp: ['@', '#', '$', '%', '^', '&', '*']
}
ret = []
for _ in range(outputCount):
value = gen_secret(length=len, letter_map=letter_map)
ret.append(value)
return ret

class LetterType(Enum):
low = 1 # 小写字母
up = 2 # 大写字母
num = 3 # 数字
sp = 4 # 特殊符号


def gen_secret(length=8, enables=None, letter_map=None):
if enables is None:
enables = list(LetterType)

if letter_map is None:
letter_map = {
LetterType.low: range_letter(ord('a'), ord('z') + 1),
LetterType.up: range_letter(ord('A'), ord('Z') + 1),
LetterType.num: range_letter(ord('0'), ord('9') + 1),
LetterType.sp: ['@', '#', '$', '%', '^', '&', '*']
}

result = []
for _ in range(length):
type_index = random.randint(0, len(enables) - 1)
letters = letter_map.get(enables[type_index], [])
if letters:
result.append(random.choice(letters))

return ''.join(result)


def range_letter(start, end):
return [chr(i) for i in range(start, end)]

三、AI Bot 中使用插件

插件作为强大的后备武器库,可以在 AI Bot 中通过人设和回复逻辑中,设计命中和回复逻辑。在中间的技能区,可以在插件中选择前面设计的插件:

image.png

AI Bot 作为和用户交互的前端,人设和回复逻辑 以及中间的面板,就相当于前端开发的组件。而一个 AI Bot 可视为一个应用程序。

1
2
3
4
5
6
7
8
9
scss复制代码[] 中是盛放输入关键字
你需要严格调用 [匠心千刃] 插件
工具应该严格输出插件接口的输出内容,禁止更改

- 输入 [指南] 时, 调用 help 接口,输出内容
- 输入 [随机秘钥] 调用 random 接口,[长度]为入参 length , [个数] 为入参 outputCount
- 输入为 [日期] 或 [date] 调用 date 接口
- 输入包含 [帮助]时, 调用 help 接口,输出内容
- 输入包含[ github 搜索], 调用 github_search 接口 , 命令中下一个单词作为入参 args。输出 10 个结果,输出包含 Star 个数

按照这个视角,如果 AI Bot 可以提供一些交互的组件,比如选择、输入、导入文件图片等。那么它的上限就可以非常高,常规的应用程序核心是视觉元素的渲染,而 AI Bot 是一个以回复数据为核心的应用程序。它可以依赖插件接口,基于输入通过代码获取输出结果。


这样 匠心千刃 可以作为一个工具箱,使用者可以通过自然语言进行使用,这是和常规应用最大的区别。匠心千刃 目前只是简单介绍插件的使用,后续有时间会逐步完善。

现在 Coze 可以发布的平台越来越多了,希望 Coze 扣子可以发展的越来越强大。本文就到这里,后面有机会再介绍一下工作流的使用,谢谢观看 ~

image.png

bot ID: 7361120595800604712

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

AI-GC-手把手教你写一个小说推文生成器(对接语音功能)

发表于 2024-04-24

前言😀

上一篇文章,我们讲述了如何对接到LLM,那么现在我们来谈谈如何对接到语音。当前的话关于语音的方案确实非常多,尤其是最近开源的势头太猛了。但是,这里的话,我们这里还是直接使用最方便,最简单的方式来完成对接。
当然本章的内容是非常简单的,只需要完成语音的对接即可。之后我们直接封装到我们的功能函数里面即可。

示例

现在的话,我们先来看到我们的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
VOICE = "zh-CN-shaanxi-XiaoniNeural"
OUTPUT_FILE = "../assert/audio/test07.mp3"


async def amain() -> None:
"""Main function"""
communicate = edge_tts.Communicate(TEXT, VOICE)
await communicate.save(OUTPUT_FILE)


def main():
loop = asyncio.get_event_loop_policy().get_event_loop()
try:
loop.run_until_complete(amain())
finally:
loop.close()

if __name__ == "__main__":

main()

这样一来就非常轻松就完成了语音的生成,这里的话要注意,这里拿到语音的是时候是要做协程获取的。

那么这里的话,要注意,这里支持很多角色,来生成语言,那么我们要关注的主要就是我们中文的。在这里面主要有这些角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Name: zh-CN-XiaoyiNeural
Gender: Female

Name: zh-CN-YunjianNeural
Gender: Male

Name: zh-CN-YunxiNeural
Gender: Male

Name: zh-CN-YunxiaNeural
Gender: Male

Name: zh-CN-YunyangNeural
Gender: Male

Name: zh-CN-liaoning-XiaobeiNeural
Gender: Female

Name: zh-CN-shaanxi-XiaoniNeural
Gender: Female

封装

之后的话,我们来这些内容进行封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

class StoryBoardHandler(object):
def __init__(self):
# 专门处理分镜的Agent
self.agentStoryBoard = ImagePromptAgent()
self.text2image = Text2Image()
self.voice_map = {
"小艺":"zh-CN-XiaoyiNeural", "云建":"zh-CN-YunjianNeural",
"云溪":"zh-CN-YunxiNeural", "云霞":"zh-CN-YunxiaNeural",
"云阳":"zh-CN-YunyangNeural", "小北":"zh-CN-liaoning-XiaobeiNeural",
"小妮":"zh-CN-shaanxi-XiaoniNeural"
}
self.current_file_path = os.path.abspath(__file__)
self.current_dir = os.path.dirname(self.current_file_path)
self.resource_dir = self.current_dir+"/../resource"

# 将返回的结果解析成正常的符合要求的字典,LLM返回的格式并没有严格按照格式返回
def __extract_json(self,text):
pattern_view = r'"场景(\d+)":\s*"(.*?)"'
pattern_desc = r'"描述(\d+)":\s*"(.*?)"'
# 使用 re.DOTALL 使得 '.' 匹配包括换行符在内的所有字符
matches_view = re.findall(pattern_view, text, re.DOTALL)
extracted_scenes_views = [f'"场景{match[0]}": "{match[1]}"' for match in matches_view]
matches_desc = re.findall(pattern_desc, text, re.DOTALL)
extracted_scenes_descs = [f'"描述{match[0]}": "{match[1]}"' for match in matches_desc]

# 之后我们将关系进行匹配组装,这里用两个倒排表来完成快速匹配
views_dict = {}
for scenes in extracted_scenes_views:
key, value = scenes.split(":")
key, value = key.replace('"', ''), value.replace('"', '')
views_dict[key] = value
descs_dict = {}
for scenes in extracted_scenes_descs:
key, value = scenes.split(":")
key, value = key.replace('"', ''), value.replace('"', '')
descs_dict[key] = value

res_list = []
# 我们将对应的场景和描述联系起来
for key, value in views_dict.items():
temp = {}
temp[key] = value
temp["描述" + key[len('场景'):]] = descs_dict.get("描述" + key[len('场景'):], "请手动完善")
res_list.append(temp)

return res_list
"""
输入文本,得到我们Agent处理之后的分镜描述,这里面做了很多解析的处理
"""
def getProgressHandler(self,text,temperature=0.4):
data = self.__extract_json(self.agentStoryBoard.ExtractSegmentNovel(text,temperature))
return data

"""
将文本提示词转化为图片
"""
def getText2Img(self,prompt):
# 先得到英文的提示词
prompt = self.agentStoryBoard.ToImagePrompt(prompt)
# 然后去请求得到图片
img = self.text2image.text2image(prompt)
if(img):
img_path = self.__create_img_stream()
img.save(img_path)
return img_path
return -1

async def __amain(self,stream,text,voice) -> None:
"""异步处理拿到语音"""
communicate = edge_tts.Communicate(text, voice)
await communicate.save(stream)

def text2Audio(self,stream,text,voice):
# 获取语音,这里的话比较特殊需要使用到协程拿到语音
loop = asyncio.new_event_loop()
try:
# 在这个事件循环中运行amain协程
loop.run_until_complete(self.__amain(stream,text,voice))
finally:
# 关闭事件循环
loop.close()

def __create_img_stream(self):

now = datetime.now()
year_month_day = now.strftime("%Y%m%d")
file_uuid = uuid.uuid4()
audio_stream = self.resource_dir + "/img" + "/" + year_month_day + "/"
if (not os.path.exists(audio_stream)):
os.makedirs(audio_stream)
audio_stream += file_uuid.hex + ".jpg"
return audio_stream

def __create_stream(self):
now = datetime.now()
year_month_day = now.strftime("%Y%m%d")
file_uuid = uuid.uuid4()
audio_stream = self.resource_dir+"/audio"+"/"+year_month_day+"/"
if( not os.path.exists(audio_stream)):
os.makedirs(audio_stream)
audio_stream += file_uuid.hex+".mp3"
return audio_stream

"""
将文本转化为音频,返回流对象,可以直接进行在st上展示(重点是在内存里面)
"""
def getText2Audio(self,text,voice,mode="中文"):
voice = self.voice_map.get(voice,"zh-CN-XiaoyiNeural")
if mode == "中文":
text = text
else:
text = self.agentStoryBoard.ToEnglish(text)
try:
audio_stream = self.__create_stream()
self.text2Audio(audio_stream,text,voice)
except Exception as e:
print(e)
audio_stream = -1
return audio_strea

当然这里要注意的是,我在这里还封装了别的方法,当然这方法的使用还是需要到下一个章节才能说明。

那么到这里,这里语音的生成就算是对接完毕了,非常简单,读者到这里可以先试着,回忆回忆先前我们做的工作。
因为在下一个章节,将是我们整个项目较为复杂的部分。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

零门槛接入,开源的物联网超级中枢:ThingsBoard

发表于 2024-04-24

ThingsBoard:重塑万物互联世界,无限可能拓展- 精选真开源,释放新价值。

1.png

概览

ThingsBoard是一款强大而灵活的开源物联网(IoT)平台,以其高度可扩展性和企业级功能赢得了全球开发者与企业的青睐。它无缝集成行业标准协议如 MQTT、CoAP 和 HTTP,轻松实现各类设备的快速连接与管理。通过其精细设计的传输层,无论是单一设备还是大规模的设备集群,都能高效稳定地传输数据。


主要功能

  • 多协议支持

ThingsBoard 支持广泛接纳主流物联网通信协议,确保不同种类的设备能够顺畅无阻地连接到云端服务,简化设备上云过程。

  • 设备管理

平台内建一套完善的设备管理体系,涵盖了从注册登记、身份验证至权限管理的一系列流程,实时反映每个设备的在线状态与运行状况,帮助用户有效维护设备群组的健康运作。

4.gif

2.gif

3.gif

  • 规则引擎

配备了一款灵活且反应迅速的事件处理器,用户可通过自定义配置一系列规则链,针对实时数据流执行自动化决策和联动动作,大大提升了数据处理的效率和智能程度。

5.gif

  • 数据可视化

凭借便捷的一键生成工具,用户可以快速搭建交互式的仪表盘,清晰展现实时动态和历史趋势数据,满足不同用户的个性化展示需求。

6.gif

  • 告警与通知

具备智能监控功能,一旦检测到异常数据,系统会根据预先设定的告警规则自动触发通知,并通过多样化的方式向相关人员推送报警信息,实现即时的风险预警与应急响应。

  • 扩展能力

为了适应大规模物联网项目的需要,ThingsBoard 具备横向扩展的能力,可在高并发场景下保持稳健的服务性能。同时,平台开放了一系列丰富的 API 接口,使得第三方开发者能够方便地进行二次开发和系统集成,进一步扩展平台功能以满足特定业务场景的应用需求。


信息

截至发稿概况如下:

  • 软件地址:github.com/thingsboard…
  • 软件协议:Apache-2.0
  • 编程语言:
语言 占比
Java 60.2%
TypeScript 25.1%
HTML 8.9%
JavaScript 2.6%
SCSS 2.2%
Shell 0.3%
Other 0.7%
  • 收藏数量:15.6K

面对物联网行业中繁复的数据采集、处理、分析和应用难题,ThingsBoard 既为我们准备了现成的解决方案,也给予了开发者充分自由去定制符合自身需求的工具。随着物联网技术不断发展和应用场景日益多元,我们面临的新挑战是如何更有效地处理海量设备产生的实时数据、确保数据的安全性以及优化整体资源分配。

在这个过程中,或许我们可以倚仗像 ThingsBoard 这样实力强大的开源平台,联合社区中众多开发者的力量,共同研究并推进适应时代发展的新策略和技术升级。这很可能就是构建未来智能化物联网环境的关键步骤。

那么,在你的实际项目中,是如何理解和运用 ThingsBoard 来解决遇到的问题和痛点呢?对于这个平台,你又有哪些独特的应用场景设想或是有待挖掘的需求?

欢迎各位在讨论区畅所欲言,分享自己的见解、实践经验!


声明:本文为辣码甄源原创,转载请标注”辣码甄源原创首发”并附带原文链接。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

来自Contextual AI 介绍 RAG 20 为什

发表于 2024-04-24

今天,我们宣布推出 RAG 2.0,这是我们为企业级性能开发强大可靠的人工智能的方法。与之前的 RAG 一代不同,该系统是端到端优化的,不再拼凑冻结模型、向量数据库和质量低劣的嵌入。利用 RAG 2.0,我们创建了我们的第一套上下文语言模型(CLMs),在各种行业基准测试中取得了最先进的性能。根据我们的研究和客户反馈,CLMs 在各个方面均大幅优于基于 GPT-4 的强大 RAG 基线和最优秀的开源模型。

image.png

使用 RAG 2.0 训练的上下文语言模型,在我们所有的基准测试中表现明显优于现有的 RAG 系统。自然问题(NQ)、热点问题(HPQA)和TriviaQA使用了精确匹配度量标准。由于HaluEvalQA和TruthfulQA需要逻辑回归值,GPT-4无法直接在这些任务上进行评估。普通的RAG是零-shot的;我们称之为RAG的方法包括少量示范、精心分块和手动提示工程。我们花费了大量精力加强基线。

在本博客文章中,我们分享了我们在构建生成式人工智能系统方面的进展,这些系统不仅限于演示,而是真正的生产级系统:

  • 我们介绍了RAG和RAG 2.0之间的区别,RAG使用冻结的现成模型,而RAG 2.0则端到端地优化了语言模型和检索器,作为一个单一系统。
  • 我们展示了RAG 2.0在从开放领域问答到忠实度等各种基准测试中取得了最先进的性能,显著优于现有的RAG方法。
  • 我们强调了RAG 2.0在真实客户工作负载上的更大收益,并讨论了其在生产中的可行性。

我们很高兴与您一起在RAG 2.0上建设 — 今天就加入我们的等待列表吧。

为什么选择 RAG 2.0?

语言模型在知识密集型任务中面临困难,因为它们受限于训练过程中接触到的信息。在2020年,我们的联合创始人兼首席执行官 Douwe Kiela 及其在 Facebook AI Research 的团队引入了检索增强生成(RAG)来缓解这一问题,通过向语言模型添加一个检索器来访问外部数据源(例如维基百科、谷歌、内部公司文件)。

今天的典型 RAG 系统使用冻结的现成模型进行嵌入,使用矢量数据库进行检索,并通过提示或编排框架将其与黑盒语言模型组合在一起。这导致了一种生成式人工智能的“科学怪人”:各个组件在技术上都能工作,但整体远非最佳。这些系统脆弱,缺乏任何机器学习或针对其部署领域的专业知识,需要大量提示,并且容易发生级联错误。因此,RAG 系统很少能达到生产标准。

RAG 2.0 方法对所有组件进行预训练、微调和对齐,作为单一集成系统进行操作,通过反向传播同时优化语言模型和检索器以实现最佳性能:

image.png

深度学习的历史反复表明,端到端优化优于手工调整系统。我们将这种方法应用于超越 RAG 的局限,并开发了 RAG 2.0。简而言之:如果您知道将要进行 RAG,那么应该为执行 RAG 训练系统。

RAG 2.0 基准测试

我们在各种方面比较了基于冻结 RAG 系统的上下文语言模型(CLMs):

  • 开放域问题回答:我们使用经典的自然问题(NQ)和 TriviaQA 数据集来测试每个模型正确检索相关知识并准确生成答案的能力。我们还在单步检索设置下使用 HotpotQA(HPQA)数据集评估模型。所有数据集都使用精确匹配(EM)指标。
  • 忠实度:我们使用 HaluEvalQA 和 TruthfulQA 来衡量每个模型在检索证据和幻觉之间保持联系的能力。
  • 新鲜度:我们使用网络搜索索引来衡量每个 RAG 系统通用于快速变化的世界知识的能力,并展示最近 FreshQA 基准测试的准确性。

这些方面对于构建生产级 RAG 系统至关重要。我们展示了 CLMs 在各种强大的冻结 RAG 系统上明显提高了性能,这些系统是使用 GPT-4 或 Mixtral 等最先进的开源模型构建的。

image.png

在知识密集型基准测试中的结果。我们的普通 RAG 和标准 RAG 基线都使用冻结的搜索索引、重新排序和现成的语言模型。对于我们的 RAG 基线,我们使用少量示范设置和手动调整的提示,以展示这些变化如何在下游任务性能上比我们的普通零-shot RAG 设置产生巨大改进。我们的 HotpotQA 评估使用与 KILT 基准测试一起发布的拆分和 EM 指标。HaluEvalQA 使用基于对数概率的零-shot 二进制准确度,并且仅在给定基准上下文文档的情况下评估语言模型的忠实度。TruthfulQA 使用 MC1 指标。

我们在谷歌云最新一代的 ML 基础设施上训练和部署了我们的 RAG 2.0 模型。通过使用 A3 实例配备 H100 GPU 和最新的 TCPx 网络堆栈,我们能够大规模地训练 RAG 2.0 模型,以实现最先进的准确性。

将 RAG 2.0 应用于实际环境

当应用于真实世界数据时,CLMs相对于当前方法实现了更大的收益,正如我们在早期客户身上所见到的。

以 FinanceBench 作为说明性的代理(以维护客户数据的保密性),我们可以看到,即使在金融特定的开放式问题回答中,CLMs也优于冻结的 RAG 系统,并在其他专业领域如法律和硬件工程中也看到了类似的收益。

image.png

RAG 2.0 和长上下文窗口

在评估真实世界的实现时,一些人可能会想知道 RAG 2.0 与具有长上下文窗口的最新模型相比如何,因此我们也深入研究了这一点。

长上下文模型通常通过“草堆中的针”基准进行评估,在这种基准中,“针”(即事实)被隐藏在一个大的“草堆”(即文本语料库)中,并且通过查询来评估模型,以寻找特定的“针”。为了有意义地比较冻结 RAG 和上下文语言模型,我们改编了最近的传记基准,创建了一个非重复的 2M 令牌的草堆。使用包含 100 多个传记问题的测试集,我们评估 CLM、冻结-RAG 和 GPT-4-Turbo(仅限于 32K 令牌)在 2K 到 2M 令牌的草堆上的表现。

image.png

我们所看到的是,RAG 2.0 表现更优,特别是如果你希望进行扩展的话:与长上下文语言模型相比,RAG 2.0 在准确性上更高,并且使用的计算资源明显更少,这种差异在生产环境中变得更加有意义。

与我们一起基于 RAG 2.0 进行构建

我们相信,只有端到端的解决方案才能释放企业中生成式人工智能的全部潜力。我们对已经看到的 RAG 2.0 的结果感到非常兴奋,迫不及待地想要将其带给更多领先的企业。

无论是财富500强还是独角兽企业,他们今天都在与 Contextual 一起基于 RAG 2.0 进行构建;他们正在利用 CLMs 和我们最新的微调和对齐技术(如 GRIT、KTO 和 LENS)在 Contextual 平台上部署可以信赖的生成式人工智能。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基于Redission高级应用4-RScoredSorted

发表于 2024-04-24

概述:

原理:

RScoredSortedSet 是 Redisson 的 Java 对象,它封装了 Redis 的有序集合(sorted set)功能。Redis 的有序集合是一种特殊的数据结构,它可以存储一组不重复的元素,并且每个元素都关联一个浮点数分数。这些元素根据分数进行排序,并且可以快速访问整个集合的任何一部分。

Redis 的有序集合使用了一种名为“跳跃列表”(skiplist)的数据结构,这使得它在保持元素排序的同时,能够提供对数据的快速访问。跳跃列表是一种概率平衡数据结构,它允许平均时间复杂度为 O(log N) 的搜索、插入和删除操作。

在 Redisson 中,RScoredSortedSet 提供了一系列方法来与 Redis 的有序集合交互,包括添加元素、删除元素、获取元素的排名、迭代元素等。Redisson 还提供了异步和反应式的 API,以支持非阻塞的编程模式。

RScoredSortedSet 优点

  1. 性能:由于 Redis 的有序集合使用跳跃列表实现,它能够在对数时间内完成插入、删除和查找操作,这对于大数据集来说非常高效。
  2. 排序和范围查询:RScoredSortedSet 自然地支持排序,可以快速检索分数在特定范围内的元素,这对于排行榜和范围查询非常有用。
  3. 唯一性:集合中的每个元素都是唯一的,即使是在并发环境下也能保证元素不会重复。
  4. 分布式环境:由于 Redis 是一个分布式缓存,RScoredSortedSet 可以很好地在分布式系统中使用,提供跨多个客户端的共享访问。
  5. 扩展性:Redis 的数据结构设计使得它可以很好地扩展到处理大量数据,同时保持高性能。

RScoredSortedSet 缺点

  1. 内存限制:由于 Redis 是基于内存的,存储在 RScoredSortedSet 中的数据集大小受限于服务器的内存容量。
  2. 持久性:虽然 Redis 提供了持久化机制,但它可能不如传统的关系型数据库系统那样健壮,特别是在处理大量写操作时。
  3. 复杂性:对于需要持久化复杂关系数据的应用,Redis 的数据模型可能需要开发者手动处理数据的关联和事务。
  4. 成本:对于需要大量内存来存储数据的应用,使用 Redis 可能会比使用传统的磁盘存储数据库系统更昂贵。
  5. 数据一致性:在分布式环境中,如果不是使用单实例模式,保持数据一致性可能需要额外的策略和配置。

在使用 RScoredSortedSet 时,需要根据应用场景权衡它的优缺点,并考虑是否适合您的具体需求。

RScoredSortedSet 是 Redisson 提供的一种数据结构,它对应 Redis 中的有序集合(sorted set)。有序集合是一种非常强大的数据结构,可以用来存储具有排序分数的唯一元素集合。这些元素根据分数进行排序,可以非常快速地执行插入、删除、更新和查找操作。

RScoredSortedSet 的实战应用场景和代码示例:

场景 1: 排行榜系统

在游戏或社交应用中,经常需要展示用户的排行榜,比如根据玩家的分数或者用户的影响力进行排名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码    @Autowired
private RedissonClient redissonClient;

public void addUserScore(String username, double score) {
RScoredSortedSet<String> leaderboard = redissonClient.getScoredSortedSet("leaderboard");
leaderboard.addScore(username, score);
}

public Collection<String> getTopUsers(int topN) {
RScoredSortedSet<String> leaderboard = redissonClient.getScoredSortedSet("leaderboard");
return leaderboard.entryRangeReversed(0, topN - 1).stream()
.map(ScoredEntry::getValue)
.collect(Collectors.toList());
}

企业微信截图_17139215491426.png

在这个场景中,addUserScore 方法用于添加或更新用户的分数,而 getTopUsers 方法用于获取排名前 N 的用户列表。

场景 2: 时间序列数据

有序集合可以用来按时间排序记录事件,例如用户的登录时间或者商品的销售时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码    @Autowired
private RedissonClient redissonClient;

public void recordEvent(String eventId, long timestamp) {
RScoredSortedSet<String> timeSeries = redissonClient.getScoredSortedSet("events");
timeSeries.add(timestamp, eventId);
}

public Collection<String> getRecentEvents(int count) {
RScoredSortedSet<String> timeSeries = redissonClient.getScoredSortedSet("events");
return timeSeries.entryRangeReversed(0, count - 1).stream()
.map(ScoredEntry::getValue)
.collect(Collectors.toList());
}

企业微信截图_17139216034338.png

在这个例子中,recordEvent 方法用于记录事件及其发生的时间戳,而 getRecentEvents 方法用于获取最近发生的事件。

场景 3: 优先级队列

在任务调度或消息队列系统中,有序集合可以用来实现优先级队列。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Autowired
private RedissonClient redissonClient;

public void submitTask(String taskId, double priority) {
RScoredSortedSet<String> priorityQueue = redissonClient.getScoredSortedSet("tasks");
priorityQueue.add(priority, taskId);
}

public String fetchNextTask() {
RScoredSortedSet<String> priorityQueue = redissonClient.getScoredSortedSet("tasks");
return priorityQueue.pollFirst();
}

在这个场景中,submitTask 方法用于将任务添加到优先级队列中,而 fetchNextTask 方法用于获取并移除具有最高优先级的任务。

场景 4: 商品价格排序

电商平台可能需要根据商品的价格进行排序,以便用户可以快速找到最便宜的商品。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@Autowired
private RedissonClient redissonClient;

![企业微信截图_1713921628760.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b679bf20e44e4cb6be8b9ee7e6e369ef~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1079&h=291&s=38282&e=png&b=2c2c2c)
public void addOrUpdateProduct(String productId, double price) {
RScoredSortedSet<String> productPrices = redissonClient.getScoredSortedSet("productPrices");
productPrices.add(price, productId);
}

![企业微信截图_1713921628760.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c6e5c50fdac4894ad555c0e7fc85f1f~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1079&h=291&s=38282&e=png&b=2c2c2c)
public Collection<String> getCheapestProducts(int count) {
RScoredSortedSet<String> productPrices = redissonClient.getScoredSortedSet("productPrices");
return productPrices.entryRange(0, count - 1).stream()
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}

企业微信截图_1713921628760.png
在这个例子中,addOrUpdateProduct 方法用于添加或更新商品的价格,而 getCheapestProducts 方法用于获取价格最低的商品列表。

这些示例展示了 RScoredSortedSet 在不同场景下的应用。由于 Redisson 的 RScoredSortedSet 是基于 Redis 的有序集合,它可以高效地处理大量数据,并且由于 Redis 的性质,这些操作可以跨多个客户端和服务实例进行。

扩展:RScoredSortedSet 在实际应用中的例子:

实时排行榜

在游戏或社交应用中,根据用户的分数或者成就来显示实时排行榜。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Autowired
private RedissonClient redissonClient;

// 添加或更新用户分数
public void updateScore(String user, double score) {
RScoredSortedSet<String> scoredSortedSet = redissonClient.getScoredSortedSet("userScores");
scoredSortedSet.add(score, user);
}

// 获取前10名用户及其分数
public Map<String, Double> getTop10Users() {
RScoredSortedSet<String> scoredSortedSet = redissonClient.getScoredSortedSet("userScores");
return scoredSortedSet.entryRangeReversed(0, 9).stream()
.collect(Collectors.toMap(ScoredEntry::getValue, ScoredEntry::getScore));
}

image.png

延时队列

使用有序集合实现延时队列,元素根据需要执行的时间排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码@Autowired
private RedissonClient redissonClient;

// 提交延时任务
public void scheduleTask(String taskId, long delayInSeconds) {
RScoredSortedSet<String> delayQueue = redissonClient.getScoredSortedSet("delayQueue");
double score = System.currentTimeMillis() / 1000.0 + delayInSeconds;
delayQueue.add(score, taskId);
}

// 处理到期的任务
public void processDueTasks() {
RScoredSortedSet<String> delayQueue = redissonClient.getScoredSortedSet("delayQueue");
long now = System.currentTimeMillis() / 1000;
Collection<String> dueTasks = delayQueue.valueRange(0, true, now, true);
for (String taskId : dueTasks) {
// 处理任务
processTask(taskId);
// 从队列中移除
delayQueue.remove(taskId);
}
}

image.png

地理位置服务

根据用户的地理位置信息,显示周围的兴趣点或者商家排名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@Autowired
private RedissonClient redissonClient;

// 添加地点及其分数(例如根据距离计算)
public void addLocation(String locationId, double score) {
RScoredSortedSet<String> locations = redissonClient.getScoredSortedSet("locations");
locations.add(score, locationId);
}

// 获取附近的地点
public Collection<String> getNearbyLocations(double minScore, double maxScore) {
RScoredSortedSet<String> locations = redissonClient.getScoredSortedSet("locations");
return locations.valueRange(minScore, true, maxScore, true);
}

企业微信截图_1713921812340.png

限流器

实现 API 请求的限流,确保在一定时间内只允许一定数量的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码@Autowired
private RedissonClient redissonClient;

// 尝试获取访问权限
public boolean tryAcquire(String api, int maxCalls, long timePeriodSeconds) {
String key = "rateLimiter:" + api;
RScoredSortedSet<Long> scoredSortedSet = redissonClient.getScoredSortedSet(key);
long now = System.currentTimeMillis();
long clearBefore = now - timePeriodSeconds * 1000;

// 移除时间窗口之前的数据
scoredSortedSet.removeRangeByScore(0, true, clearBefore, true);

// 检查当前调用数是否超过限制
if (scoredSortedSet.size() < maxCalls) {
// 没有超过限制,记录当前请求
scoredSortedSet.add(now, now);
return true;
}
return false;
}

企业微信截图_17139218356635.png

扩展总结:

这些实战例子展示了 RScoredSortedSet 在不同应用场景中的灵活性和实用性。通过 Redisson 提供的 API,开发者可以方便地实现这些功能,同时利用 Redis 的性能和可扩展性。

RScoredSortedSet 的高级应用通常涉及复杂的业务逻辑和数据处理,这些应用的特点是它们不仅仅使用基本的添加和检索操作,而是结合了有序集合的特性来实现特定的业务需求。

高级实战应用的示例:

实时数据分析和监控

在金融或物联网(IoT)领域,实时监控和分析数据流非常重要。RScoredSortedSet 可以用来存储时间序列数据,并实时计算滑动窗口内的统计信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码@Autowired
private RedissonClient redissonClient;

// 记录交易数据
public void recordTransaction(String transactionId, double amount, long timestamp) {
RScoredSortedSet<Transaction> transactions = redissonClient.getScoredSortedSet("transactions");
transactions.add(new Transaction(transactionId, amount, timestamp), timestamp);
}

// 获取最近一分钟内的平均交易额
public double getAverageTransactionAmountLastMinute() {
long oneMinuteAgo = System.currentTimeMillis() - 60000;
RScoredSortedSet<Transaction> transactions = redissonClient.getScoredSortedSet("transactions");
Collection<Transaction> recentTransactions = transactions.valueRange(oneMinuteAgo, true, Double.POSITIVE_INFINITY, true);

return recentTransactions.stream()
.mapToDouble(Transaction::getAmount)
.average()
.orElse(0.0);
}

在这个例子中,我们使用 RScoredSortedSet 来存储交易数据,并利用分数(这里是时间戳)来获取最近一分钟内的交易,然后计算平均交易额。

虚拟货币交易平台的订单簿

虚拟货币交易平台需要管理大量的买卖订单,RScoredSortedSet 可以用来作为订单簿,其中买单和卖单根据价格排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
java复制代码@Autowired
private RedissonClient redissonClient;

// 提交买单
public void submitBuyOrder(String orderId, double price) {
RScoredSortedSet<Order> buyOrders = redissonClient.getScoredSortedSet("buyOrders");
buyOrders.add(price, new Order(orderId, price));
}

// 提交卖单
public void submitSellOrder(String orderId, double price) {
RScoredSortedSet<Order> sellOrders = redissonClient.getScoredSortedSet("sellOrders");
sellOrders.add(price, new Order(orderId, price));
}

// 匹配订单
public void matchOrders() {
RScoredSortedSet<Order> buyOrders = redissonClient.getScoredSortedSet("buyOrders");
RScoredSortedSet<Order> sellOrders = redissonClient.getScoredSortedSet("sellOrders");

Order highestBuyOrder = buyOrders.first();
Order lowestSellOrder = sellOrders.first();

while (highestBuyOrder != null && lowestSellOrder != null && highestBuyOrder.getPrice() >= lowestSellOrder.getPrice()) {
// 执行交易逻辑
executeTrade(highestBuyOrder, lowestSellOrder);
// 移除已匹配的订单
buyOrders.remove(highestBuyOrder);
sellOrders.remove(lowestSellOrder);
// 更新订单
highestBuyOrder = buyOrders.first();
lowestSellOrder = sellOrders.first();
}
}

在这个例子中,我们使用了两个 RScoredSortedSet,一个用于买单,一个用于卖单。通过比较最高买价和最低卖价,我们可以匹配订单并执行交易。

社交网络中的动态时间线

社交网络中,用户的时间线通常需要根据动态的发布时间来排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码@Autowired
private RedissonClient redissonClient;

// 发布动态
public void postStatus(String userId, String statusId, long timestamp) {
RScoredSortedSet<String> timeline = redissonClient.getScoredSortedSet("timeline:" + userId);
timeline.add(timestamp, statusId);
}

// 获取用户的时间线
public Collection<String> getUserTimeline(String userId, int page, int pageSize) {
RScoredSortedSet<String> timeline = redissonClient.getScoredSortedSet("timeline:" + userId);
int startIndex = page * pageSize;
int endIndex = (page + 1) * pageSize - 1;
return timeline.entryRangeReversed(startIndex, endIndex).stream()
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}

在这个例子中,我们为每个用户创建了一个 RScoredSortedSet,用于存储他们的动态。当需要获取用户时间线时,我们可以根据时间戳分数反向查询动态。

这些高级应用示例展示了 RScoredSortedSet 在解决实际问题中的强大功能。通过 Redisson 提供的丰富API,开发者可以构建出高效、可扩展的数据处理解决方案。

RScoredSortedSet 的高级应用通常涉及复杂的业务逻辑和数据处理,这些应用的特点是它们不仅仅使用基本的添加和检索操作,而是结合了有序集合的特性来实现特定的业务需求。以下是一些高级实战应用的示例:### 实时数据分析和监控

在金融或物联网(IoT)领域,实时监控和分析数据流非常重要。RScoredSortedSet 可以用来存储时间序列数据,并实时计算滑动窗口内的统计信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码@Autowired
private RedissonClient redissonClient;

// 记录交易数据
public void recordTransaction(String transactionId, double amount, long timestamp) {
RScoredSortedSet<Transaction> transactions = redissonClient.getScoredSortedSet("transactions");
transactions.add(new Transaction(transactionId, amount, timestamp), timestamp);
}

// 获取最近一分钟内的平均交易额
public double getAverageTransactionAmountLastMinute() {
long oneMinuteAgo = System.currentTimeMillis() - 60000;
RScoredSortedSet<Transaction> transactions = redissonClient.getScoredSortedSet("transactions");
Collection<Transaction> recentTransactions = transactions.valueRange(oneMinuteAgo, true, Double.POSITIVE_INFINITY, true);

return recentTransactions.stream()
.mapToDouble(Transaction::getAmount)
.average()
.orElse(0.0);
}

在这个例子中,我们使用 RScoredSortedSet 来存储交易数据,并利用分数(这里是时间戳)来获取最近一分钟内的交易,然后计算平均交易额。

虚拟货币交易平台的订单簿

虚拟货币交易平台需要管理大量的买卖订单,RScoredSortedSet 可以用来作为订单簿,其中买单和卖单根据价格排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
java复制代码@Autowired
private RedissonClient redissonClient;

// 提交买单
public void submitBuyOrder(String orderId, double price) {
RScoredSortedSet<Order> buyOrders = redissonClient.getScoredSortedSet("buyOrders");
buyOrders.add(price, new Order(orderId, price));
}

// 提交卖单
public void submitSellOrder(String orderId, double price) {
RScoredSortedSet<Order> sellOrders = redissonClient.getScoredSortedSet("sellOrders");
sellOrders.add(price, new Order(orderId, price));
}

// 匹配订单
public void matchOrders() {
RScoredSortedSet<Order> buyOrders = redissonClient.getScoredSortedSet("buyOrders");
RScoredSortedSet<Order> sellOrders = redissonClient.getScoredSortedSet("sellOrders");

Order highestBuyOrder = buyOrders.first();
Order lowestSellOrder = sellOrders.first();

while (highestBuyOrder != null && lowestSellOrder != null && highestBuyOrder.getPrice() >= lowestSellOrder.getPrice()) {
// 执行交易逻辑
executeTrade(highestBuyOrder, lowestSellOrder);
// 移除已匹配的订单
buyOrders.remove(highestBuyOrder);
sellOrders.remove(lowestSellOrder);
// 更新订单
highestBuyOrder = buyOrders.first();
lowestSellOrder = sellOrders.first();
}
}

在这个例子中,我们使用了两个 RScoredSortedSet,一个用于买单,一个用于卖单。通过比较最高买价和最低卖价,我们可以匹配订单并执行交易。

社交网络中的动态时间线

社交网络中,用户的时间线通常需要根据动态的发布时间来排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码@Autowired
private RedissonClient redissonClient;

// 发布动态
public void postStatus(String userId, String statusId, long timestamp) {
RScoredSortedSet<String> timeline = redissonClient.getScoredSortedSet("timeline:" + userId);
timeline.add(timestamp, statusId);
}

// 获取用户的时间线
public Collection<String> getUserTimeline(String userId, int page, int pageSize) {
RScoredSortedSet<String> timeline = redissonClient.getScoredSortedSet("timeline:" + userId);
int startIndex = page * pageSize;
int endIndex = (page + 1) * pageSize - 1;
return timeline.entryRangeReversed(startIndex, endIndex).stream()
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}

在这个例子中,我们为每个用户创建了一个 RScoredSortedSet,用于存储他们的动态。当需要获取用户时间线时,我们可以根据时间戳分数反向查询动态。

这些高级应用示例展示了 RScoredSortedSet 在解决实际问题中的强大功能。通过 Redisson 提供的丰富API,开发者可以构建出高效、可扩展的数据处理解决方案。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java 断言 Assert 使用教程与最佳实践

发表于 2024-04-24

本文收录于 Github.com/niumoo/Java…,Java 系列文档,数据结构与算法!

本文收录于网站:www.wdbyte.com/,我的公众号:程序猿阿朗

作为一个 Java 开发者,如果要问你 Java 中有哪些关键字,你可能会随口说出一串,如果问你 Java 有哪些最不常使用的关键字,或许你还能说出几个。但是 assert 关键字一定算是其中之一,或者,Java 写了几年,还没有用过 Java 的 assert 关键字。

这篇文章介绍 Java assert 的用法、最佳实践、特殊用法以及替代工具。

Java Assert 简介

Assert 中文我们一般称为断言,你可以理解为 “十分肯定地说” 。很多编程语言中都有断言,使用断言可以快速方便的验证程序中的某个假设条件或者状态是否成立,不成立则立即抛出异常。断言通常用于开发和测试阶段。

Java 中的断言使用 assert 关键字实现,但是因为 assert 在 Java 1.4 中才被引入,因此在 Java 1.4 之前,assert 并不是 Java 关键字,可能会被写成普通变量名。新版 Java 严格遵守向后兼容下,这可能也是 Java 默认禁用断言的原因之一,开启断言可以使用 -ea 参数手动启用。

1
shell复制代码java -ea YourClassName

启用和禁用断言

基于上述原因,Java 默认关闭了断言,手动开启断言可以使用 -ea 作为 JVM 参数启动 Java 程序。

-ea 是 -enableassertions 命令的缩写。

1
java复制代码java -ea AssertDemo

也可以使用 -ea:包路径 只为某些包开启断言,如为包 com.wdbyte 中的所有类开启断言支持。

1
java复制代码-ea:com.wdbyte...

如果某些类库过于老旧,使用了 assert 作为变量名,为了正常运行, Java 也提供了对某些包禁用断言的参数。

1
diff复制代码-da:com.wdbyte...

-da 是 -disableassertions 的缩写

Java 中使用断言

Java 中使用断言有两种语法。

方式1

1
java复制代码assert boolExpression;

使用 assert 关键词紧跟给一个布尔条件进行断言判断,这种方式断言失败时,会抛出 java.lang.AssertionError 异常,但是没有具体的错误信息。

举例:

1
2
3
java复制代码List<String> list = Arrays.asList("wdbyte", "com");
boolean result = list.remove("x");
assert result;

运行:

1
2
java复制代码Exception in thread "main" java.lang.AssertionError
at com.wdbyte.assert1.AssertDemo1.main(AssertDemo1.java:14)

方式2

1
java复制代码assert boolExpression:msg;

这种方式报错时会把 msg 通过构造函数赋值给 AssertionError。

举例:

1
java复制代码assert result : "移除失败";

运行:

1
2
java复制代码Exception in thread "main" java.lang.AssertionError: 移除失败
at com.wdbyte.assert1.AssertDemo1.main(AssertDemo1.java:15)

Assert 最佳实践

切记 assert 断言是一种调试工具,用于在开发和测试阶段检查程序的某些假设是否为真,它是开发者的一个辅助工具,不应该对线上代码的运行产生任何影响。

使用断言时的最佳实践是确保它不会成为程序的常规执行流程的一部分,而是作为一种发现内部错误和验证程序假设的手段。在性能敏感或者资源受限的环境中,开应该在开发和测试阶段使用断言,然后在部署生产版本之前禁用它们。

适用场景

  1. 开发和测试阶段的临时检查

还是要重复一次这个使用时机,首先因为 assert语句在生产环境下默认是禁用的,其次它可能会对性能产生影响,不应该被用作错误处理机制。在开发或调试期间,当你想要验证某个假设时,assert可以作为一种快速检查的方法。这些用法通常在代码达到稳定状态后被移除或替换为更健壮的错误处理机制。
2. 单元测试

使用断言对方法的执行结果进行判断,是单元测试中最为常用的操作。如果断言不通过,程序会立即抛出错误。良好的代码应该编写对应的单元测试,并且给出尽可能多的测试用例,断言通过可以保证程序的运行结果在预期之内。
3. 存在隐含约束条件

如何理解存在隐含约束条件,比如下面的代码示例中,代码中 else 部分默认 i%3 的余数为2,这种可以看做是一个隐含的约束条件。

1
2
3
4
5
6
7
java复制代码if (i % 3 == 0) {
...
} else if (i % 3 == 1) {
...
} else { // 此处,我们认为 (i % 3 == 2)
...
}

在这个例子中,当你本想通过注释来声明某个隐含的规则时,可以该改用断言。因此上述的 if 语句可以这样改写:

1
2
3
4
5
6
7
8
ini复制代码if (i % 3 == 0) {
...
} else if (i % 3 == 1) {
...
} else {
assert i % 3 == 2 : i;
...
}

注意:例子中在 i 为负数时断言会失败,这时余数是负的。

不适用场景

  1. 不要用作参数校验

断言不应该用于参数校验,首先断言可能会被禁用,禁用时断言的语句不会被执行。其次,参数校验应该抛出对应的异常,如 NullPointerException 或 IllegalArgumentException或 IndexOutOfBoundsException.
2. 不要在断言中执行代码。

因为断言可能会被禁用,如果代码依赖断言执行,那么可能不会被执行。如 assert list.remove("x");; 在断言禁用时,不会被执行,会造成程序运行结果异常。

1
2
3
4
java复制代码// assert list.remove("x") : "移除失败"; 不可取,可能不执行
// 推荐下面的方式
boolean result = list.remove("x");
assert result : "移除失败";

Assert 进阶用法

编译阶段消除断言

在性能受限的设备中开发应用,我们可能会希望完全从类文件中剔除断言。虽然可以禁用断言,但是对于在生产环境中不需要的代码,我们还是想尽可能的删去,这样不仅减小了类文件的大小,而且可以在没有高质量即时编译器(JIT)的情况下,减少资源占用并提升运行时性能。

如果你有类似的需求,可以结合 if 在编译阶段消除断言。

1
2
3
4
5
6
7
8
9
java复制代码static final boolean asserts = false; // 设置为 false 来消除断言

public static void main(String[] args) {
List<String> list = Arrays.asList("wdbyte", "com");
boolean result = list.remove("x");
if (asserts) {
assert result : "移除失败";
}
}

因为 if (asserts) 永远为 false,在编译阶段就会被优化,反编译编译后的 class 可以发现断言部分代码已经不存在了。

1
2
java复制代码List<String> list = Arrays.asList("wdbyte", "com");
boolean result = list.remove("x");

强制启用断言

如果某些关键系统希望在指定环境中不能禁用断言。下面的静态初始化示例可以实现这个强制条件。

1
2
3
4
5
6
7
java复制代码static {
boolean assertsEnabled = false;
assert assertsEnabled = true;
if (!assertsEnabled) {
throw new RuntimeException("必须启用断言!!!");
}
}

替代开源库

在Java中,除了语言内置的assert关键字外,许多开源库都提供了更强大、更灵活的断言机制,这些工具通常用于单元测试中,但也可以用于生产代码中对条件进行验证。下面列出一些广泛使用的有断言功能的开源库。

  1. JUnit: JUnit是一个广泛使用的单元测试框架,其中包含用于编写测试断言的方法。JUnit 4 使用org.junit.Assert类提供断言,而JUnit 5 则引入了org.junit.jupiter.api.Assertions类。
1
2
3
java复制代码List<String> list = Arrays.asList("wdbyte", "com");
boolean result = list.remove("x");
Assertions.assertTrue(result);
  1. AssertJ: AssertJ 提供了丰富的、流式的、易于使用的断言库,使得错误的诊断更为容易。它支持Java 8的特性,比如lambda表达式、Stream和Optional类型的断言。
1
java复制代码Assertions.assertThat("").isEmpty()
  1. Apache Commons Lang : 提供的 Validate 类可以进行常见的条件验证。
1
java复制代码Validate.isTrue(list.isEmpty(),"msg");
  1. Google Guava :Guava 提供了 Preconditions 类可以用于常见的条件验证,还提供了一个 Verify 类用于断言操作。
1
2
java复制代码Preconditions.checkNotNull("","msg");
Verify.verify(list.isEmpty(),"msg");

一如既往,文章中代码存放在 Github.com/niumoo/java….

参考

  1. docs.oracle.com/javase/8/do…
  2. junit.org/
  3. github.com/assertj/ass…

本文收录于 Github.com/niumoo/Java…,Java 系列文档,数据结构与算法!

本文收录于网站:www.wdbyte.com/,我的公众号:程序猿阿朗

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…242526…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%