2024年3月

介绍

将容器化应用程序部署到 Kubernetes 集群时,由于从 registry 中提取必要的容器镜像需要时间,因此可能会出现延迟。在应用程序需要横向扩展或处理高速实时数据的情况下,这种延迟尤其容易造成问题。幸运的是,有几种工具和策略可以改善 Kubernetes 中容器镜像的可用性和缓存。在本篇文章中,我们将全面介绍这些工具和策略,包括
kube-fledged、kuik、Kubernetes 内置的镜像缓存功能
、本地缓存以及监控和清理未使用的镜像。

前提

将工作负载部署到 Kubernetes 时,某个 Pod 中的容器自然会基于 OCI 容器镜像。这些镜像可以从多种私有/公共存储库中提取。Kubernetes 会在拉取镜像的每个节点上本地缓存镜像,以便其他 Pod 使用相同的镜像。

然而在大多数用例中,这还不够。如今,
大多数云 Kubernetes 集群都需要自动扩展,并根据客户的使用情况动态分配节点
。如果多个节点必须多次调用同一个镜像怎么办?如果这个镜像很重,那可能需要几分钟时间。在应用自动伸缩的情况下,需要相对较长的时间。

现有解决方案

预期的解决方案需要
在 Kubernetes 上建立一个缓存层
,这样 Kubernetes 就有了一个集中的镜像缓存,所有节点都能从其中 "提取 "镜像。但是,由于缓存需要非常快,因此缓存解决方案需要位于 Kubernetes 内部,所有节点都应该以最快的延迟到达缓存。

要解决从 registry 中提取容器镜像的延迟问题,广泛使用的方法是在集群内运行 registry 镜像。

两种广泛使用的解决方案是
集群内自托管 registry

推送缓存 (pull-through cache)

在前一种解决方案中,本地 registry 在 Kubernetes 集群内运行,并在容器运行时配置为镜像 registry。任何镜像拉取请求都会指向集群内的 registry。在后一种解决方案中,容器镜像的缓存直接在工作节点上构建和管理。

其他现有解决方案包括使用 kuik 等可靠的缓存解决方案、在 Kubernetes 中启用镜像缓存、使用本地缓存、优化容器镜像构建以及监控和清理未使用的镜像。

Harbor

Harbor 是一个 CNCF 毕业项目,它的功能是容器 registry ,但最重要的是它还是一个
推送代理缓存 (Pull Through Proxy Cache)

推送代理缓存是一种缓存机制,旨在
优化容器 registry 环境中容器镜像的分发和检索
。它充当用户端(如容器运行时或构建系统)和上游容器 registry 之间的中介。

当用户端请求容器镜像时,直通式代理缓存会检查它是否已经拥有所请求镜像的本地副本。如果镜像存在,代理缓存会直接将其提供给客户端,而无需从上游 registry 下载。这样可以减少网络延迟并节省带宽。

如果本地缓存中没有请求的镜像,代理缓存就会充当普通代理,将请求转发到上游 registry。然后,代理缓存会从 registry 中检索镜像,并将其提供给客户端。此外,
代理缓存还会在其本地缓存中存储一份镜像副本,以备将来请求之用

kube-fledged

kube-fledged 是一个 K8s 附加组件或 operator,用于
直接在 Kubernetes 集群的工作节点上创建和管理容器镜像缓存
。它允许用户定义镜像列表,并将这些镜像缓存到哪个工作节点上。kube-fledged 提供了 CRUD API 来管理镜像缓存的生命周期,并支持多个可配置参数,以便根据个人需求定制功能。

kube-fledged 是为
管理 Kubernetes 中的镜像缓存而设计和构建的通用解决方案
。虽然主要用例是实现 Pod 的快速启动和扩展,但该解决方案支持下列的各种实例。

工作原理

kube-fledged 定义了一种名为 “
ImageCache
” 的自定义资源,并实现了一个自定义控制器(名为 kubefledged-controller)。
用户可以使用 kubectl 命令创建和删除 ImageCache 资源

Kubernetes-image-puller

为了缓存镜像,Kubernetes Image Puller 会在所需集群上创建一个 Daemonset,然后在集群中的每个节点上创建一个 pod,其中包含一个命令 sleep 720h 的容器列表。这样就能确保集群中的所有节点都缓存了这些镜像。使用的 sleep 二进制基于 golang(请参阅 Scratch Images:
https://github.com/che-incubator/kubernetes-image-puller#scratch-images)。
我们还会定期检查守护进程集的健康状况,并在必要时重新创建它。

可以通过 Helm 或处理和应 OpenShift 模板来部署应用程序。此外,OperatorHub 上还有一个社区支持的 Operator。

kubernetes-image-puller 部署了大量容器(每个镜像和每个节点一个容器,缓存机制使用 daemonset),以实现缓存功能。
举个例子:缓存中有 5 个节点和 10 个镜像,而我们在集群中已经有 50 个容器专门用于缓存功能。

Tugger

Tugger 使用单一配置文件,通过其 Helm 文件值定义。它不允许我们将“系统”配置(例如:从缓存系统中排除特定图片)和 “用户”配置分开。

Tugger 使用通过 Helm 文件值定义的单一配置文件。它不允许分离 "系统 "配置,比如从缓存系统中排除特定镜像,和 "用户 "配置。

kube-image-keeper (kuik)

kube-image-keeper(又名 kuik,类似于 “quick”)是
Kubernetes 的容器镜像缓存系统
。它能将 pod 使用的容器镜像保存在自己的本地 registry 中,这样在原始镜像不可用时,这些镜像仍可使用。

工作原理

创建 pod 时,kuik 的 webhook 会即时重写其镜像,并添加
localhost:{port}/
前缀(默认
port
为 7439,可配置)。


localhost:{port}
上有一个镜像代理,它从 kuik 的缓存 registry (当镜像已被缓存时)或直接从原始 registry (当镜像尚未被缓存时)提供镜像。

控制器负责监控 pod,当发现新的镜像时,就会为这些镜像创建
CachedImage
自定义资源。另一个控制器会监测这些
CachedImage
自定义资源,并相应地将镜像从源 registry 复制到 kuik 的缓存 registry 中。

架构和组件

在 kuik 的命名空间中,您可以找到:

  • 运行 kuik 控制器的
    Deployment
  • 运行 kuik 镜像代理的
    DaemonSet
  • 当该组件在 HA 模式下运行时,会使用
    StatefulSet
    来运行 kuik 的镜像缓存,而不是
    Deployment

运行镜像缓存显然需要一定的磁盘空间(请参考 Garbage collection and limitations:
https://github.com/enix/kube-image-keeper#garbage-collection-and-limitations)。除此之外,就计算资源而言,kuik
组件是相当轻量级的。这显示了默认设置下的 CPU 和 RAM 使用情况,其中两个控制器处于 HA 模式:

$ kubectl top pods
NAME                                             CPU(cores)   MEMORY(bytes)
kube-image-keeper-0                              1m           86Mi
kube-image-keeper-controllers-5b5cc9fcc6-bv6cp   1m           16Mi
kube-image-keeper-controllers-5b5cc9fcc6-tjl7t   3m           24Mi
kube-image-keeper-proxy-54lzk                    1m           19Mi

Warm-image

WarmImage
CRD 获取镜像参考,并将其预取到集群中的每个节点上。

要在集群中安装这一自定义资源,只需运行:

# Install the CRD and Controller.
curl https://raw.githubusercontent.com/mattmoor/warm-image/master/release.yaml \
  | kubectl create -f -

或者,您也可以
git clone
该仓库并运行:

# Install the CRD and Controller.
kubectl create -f release.yaml

结论

在这篇文章中,我们向您展示了如何通过在节点上缓存镜像来加快 Pod 的启动速度。通过在 kubernetes 集群的工作节点上预取容器镜像,您可以显著缩短 Pod 的启动时间,即使是大型镜像,也可以缩短到几秒钟。这项技术能让运行机器学习、仿真、数据分析和代码构建等工作负载的客户受益匪浅,提高容器启动性能和整体工作负载效率。

由于无需额外管理基础设施或 Kubernetes 资源,这种方法为解决基于 Kubernetes 的环境中容器启动缓慢的问题提供了一种经济高效的解决方案。

这次继续整理代码。此应用主要是以前的VB6的Dll文件需要进行注册然后才能够配合EXE应用进行使用,于是就将原来的VB6的代码用C#也实现了一下,有了此文。

1、
项目目录;

2、
源码介绍;

3、
运行界面;

4、
使用介绍;

直接运行应用,将DLL文件拖放进列表框。然后运行注册即可,或者点击解除注册进行解除。

5、
源码下载;

提供源码下载:
https://download.csdn.net/download/lzhdim/15448820

6、
其它建议;

该应用提供了VB和C#的两套代码,直接进行复用即可。

这个DLL文件注册器是一个简单的例子,而且现在也少用DLL注册了,此应用权当用来练手了。

热点随笔:

·
京东大佬问我:下单后30分钟未支付,自动取消有什么设计方案么?
(
帝莘
)
·
园子的新版 favicon,您觉得哪款更好看
(
博客园团队
)
·
Garnet: 力压Redis的C#高性能分布式存储数据库
(
InCerry
)
·
没想到三天10KStar的营销利器MediaCrawler开源作者已经删库了
(
aehyok
)
·
一天涨 23k Star 的开源项目「GitHub 热点速览」
(
削微寒
)
·
《优化接口设计的思路》系列:第九篇—用好缓存,让你的接口速度飞起来
(
sum墨
)
·
记一次 .NET某游戏后端API服务 CPU爆高分析
(
一线码农
)
·
为什么ASP.NET Core的路由处理器可以使用一个任意类型的Delegate
(
Artech
)
·
工良出品,从零设计开发 .NET 开发框架:框架源码和教程电子书
(
痴者工良
)
·
.NET Emit 入门教程:第一部分:Emit 介绍
(
路过秋天
)
·
不要升级!不要升级!MacOS 14.4 引发Java 应用崩溃
(
程序猿DD
)
·
系统渐渐沦为“屎山”,原因是..
(
程序员老猫
)

热点新闻:

·
英伟达发布全球最强AI芯片,性能暴涨30倍,老黄才是这个时代的乔布斯
·
Reddit和天涯:一个上市,一个“上坟”
·
马斯克突然开源Grok:3140亿参数巨无霸,免费可商用
·
AI程序员Devin卧底人类群聊修bug!和CTO聊技术,"顶级码农水平"
·
网页太臃肿严重影响浏览性能
·
脑后插管玩《文明6》!马斯克Neuralink首个人类患者直播意念象棋
·
农夫山泉们,该警惕“犯众怒”了
·
韩国大企业撤离中国
·
马化腾点名批评:腾讯如何输掉派对游戏关键一役
·
嫦娥揽月|长八火箭托举鹊桥二号奔赴月球,中国探月再启新征程
·
小米公司回应测试车高速逃费:已补缴所有过路费
·
“量子龙卷风”吹开黑洞研究之门

前言

springboot 整合 ES 有两种方案,ES 官方提供的
Elasticsearch Java API Client
和 spring 提供的 [Spring Data Elasticsearch](
Spring Data Elasticsearch
)

两种方案各有优劣

Spring:高度封装,用着舒服。缺点是更新不及时,有可能无法使用 ES 的新 API

ES 官方:更新及时,灵活,缺点是太灵活了,基本是一比一复制 REST APIs,项目中使用需要二次封装。

Elasticsearch Java API Client

目前最新版本 ES8.12,要求 jdk8 以上,API 里面使用了大量的 builder 和 lambda

官方也提供了
测试用例

兼容

翻了不少博客,大部分都是使用 High Level Rest Client,这是旧版本的 api,新版本使用 Elasticsearch Java API Client,如何兼容旧版本,官方也提供了
解决方案
)

下文描述的均是新版 API

添加 jar 包

官方文档:[installation](
安装| Elasticsearch Java API 客户端 [8.12] |松紧带 --- Installation | Elasticsearch Java API Client [8.12] | Elastic
)

使用的是 maven,在 pom.xml 中添加

<dependency>  
    <groupId>co.elastic.clients</groupId>  
    <artifactId>elasticsearch-java</artifactId>  
    <version>8.12.2</version>  
</dependency>

<!-- 如果有添加springmvc,此包可不引入 -->
<dependency>  
    <groupId>com.fasterxml.jackson.core</groupId>  
    <artifactId>jackson-databind</artifactId>  
    <version>2.12.3</version>  
</dependency>

如果报错
ClassNotFoundException: jakarta.json.spi.JsonProvider
,则还需要添加

<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.0.1</version>
</dependency>

打印请求

在 application. yml 中添加配置,打印 es 的 http 请求(建议在开发调试时使用)

logging:  
  level:  
    tracer: TRACE

连接 ES

配置文件如下,后续所有 ES 操作都通过 ElasticsearchClient 对象

更多配置请看
Common configuration

@Configuration  
public class ElasticSearchConfig {  
  
    @Bean  
    public ElasticsearchClient esClient() {  
        // ES服务器URL  
        String serverUrl = "http://127.0.0.1:9200";  
        // ES用户名和密码  
        String userName = "xxx";  
        String password = "xxx";  
  
        BasicCredentialsProvider credsProv = new BasicCredentialsProvider();  
        credsProv.setCredentials(  
                AuthScope.ANY, new UsernamePasswordCredentials(userName, password)  
        );  
  
        RestClient restClient = RestClient  
                .builder(HttpHost.create(serverUrl))  
                .setHttpClientConfigCallback(hc -> hc.setDefaultCredentialsProvider(credsProv))  
                .build();  
  
        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());  
        return new ElasticsearchClient(transport);  
    }  
  
}

索引操作

代码中的 esClient 就是 ElasticsearchClient,请自行注入 bean

// 索引名字  
String indexName = "student";  
  
// 索引是否存在  
BooleanResponse books = esClient.indices().exists(e -> e.index(indexName));  
System.out.println("索引是否存在:" + books.value());  
  
// 创建索引  
esClient.indices().create(c -> c  
        .index(indexName)  
        .mappings(mappings -> mappings  // 映射  
                .properties("name", p -> p  
                        .text(t -> t // text类型,index=false  
                                .index(false)  
                        )  
                )  
                .properties("age", p -> p  
                        .long_(t -> t) // long类型  
                )  
        )  
);  
  
// 删除索引  
esClient.indices().delete(d -> d.index(indexName));

文档操作 (CRUD)

下文以官方测试数据
account. json
为例

实体类

首先定义实体类,用于 ES 中的字段

public class Account {
	private String id;
	// 解决ES中字段与实体类字段不一致的问题  
	@JsonProperty("account_number")
    private Long account_number;  
    private String address;  
    private Integer age;  
    private Long balance;  
    private String city;  
    private String email;  
    private String employer;  
    private String firstname;  
    private String lastname;  
    private String gender;  
    private String state;  
	... 省略get、set方法
}

新增

String indexName = "account";  // 索引名字
Account account = new Account();
account.setId("1");
account.setLastname("guyu");   

// 新增
CreateResponse createResponse = esClient.create(c -> c  
        .index(indexName) // 索引名字  
        .id(account.getId()) // id  
        .document(account) // 实体类  
);

修改

UpdateResponse<Account> updateResp = esClient.update(u -> u  
                .index(indexName)  
                .id(account.getId())  
                .doc(account),  
        Account.class  
);

删除

DeleteResponse deleteResp = esClient.delete(d -> d.index(indexName).id("1"));

批量新增

批量操作需要使用到 bulk

List<Account> accountList = ...
BulkRequest.Builder br = new BulkRequest.Builder();  
for (Account acc : accountList) {  
    br.operations(op -> op  
            .create(c -> c  
                    .index(indexName)  
                    .id(acc.getId())  
                    .document(acc)  
            )  
    );  
}  
BulkResponse bulkResp = esClient.bulk(br.build());

有没有觉得批量新增的 .create () 里面的参数很眼熟,批量删除和更新请举一反三

根据 id 查询

// 定义实体类
GetResponse<Account> getResp = esClient.get(g -> g.index(indexName).id("1"), Account.class);  
if (getResp.found()) {  
    Account source = getResp.source();  // 这就是得到的实体类
    source.setId(getResp.id());  
}

// 不定义实体类
GetResponse<ObjectNode> getResp = esClient.get(g -> g  
                .index(indexName)  
                .id("1"),  
        ObjectNode.class  
);  
if (getResp.found()) {  
    ObjectNode json = getResp.source();  
    String firstname = json.get("firstname").asText();  
    System.out.println(firstname);  
}

搜索

搜索全部

SearchResponse<Account> searchResp = esClient.search(s -> s  
        .index(indexName)  
        .query(q -> q.matchAll(m -> m))  // 搜索全部
        , Account.class  
);

HitsMetadata<Account> hits = searchResp.hits();  
long totalValue = hits.total().value(); // 匹配到的数量  
hits.hits().forEach(h -> {  
    Account acc = h.source(); // 这就是得到的实体类  
    acc.setId(h.id());  
});

ES API 的对象定义,基本与返回的 json 一一对应的,所以 SearchResponse 就不过多赘述。

搜索 firstname = Amber

SearchResponse<Account> searchResp = esClient.search(s -> s  
        .index(indexName)  
        .query(q -> q  // 查询  
                .match(t -> t  
                        .field("firstname")  
                        .query("Amber")  
                )  
        )  
        , Account.class  
);

// 也可以这样写
Query firstNameQuery = MatchQuery.of(m -> m.field("firstname").query("Amber"))._toQuery();
SearchResponse<Account> searchResp = esClient.search(s -> s  
        .index(indexName)  
        .query(firstNameQuery)  
        , Account.class  
);

嵌套查询,比如搜索 firstname = Amber AND age = 32

Query firstNameQuery = MatchQuery.of(m -> m.field("firstname").query("Amber"))._toQuery();  
Query ageQuery = MatchQuery.of(m -> m.field("age").query(32))._toQuery();  
  
SearchResponse<Account> searchResp = esClient.search(s -> s  
        .index(indexName)  
        .query(q -> q  
                .bool(b -> b.must(firstNameQuery, ageQuery))  
        )  
        , Account.class  
);

浅分页

from 和 size 参数类似于 mysql 的 limit,详细说明见
Paginate search results

SearchResponse<Account> searchResp = esClient.search(s -> s  
        .index(indexName)   
        .from(0)  // 分页参数
		.size(20)  // 分页参数
        , Account.class  
);

排序

SearchResponse<Account> searchResp = esClient.search(s -> s  
        .index(indexName)   
        .sort(so -> so  // 排序字段1
                .field(f -> f  
                        .field("age")  
                        .order(SortOrder.Asc)  
                )  
        )  
        .sort(so -> so  // 排序字段2
                .field(f -> f  
                        .field("account_number")  
                        .order(SortOrder.Desc)  
                )  
        )  
        , Account.class  
);

Spring Data Elasticsearch

文档:
Spring Data Elasticsearch

添加 jar 和配置

pom.xml添加依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

yml 配置

spring:  
  elasticsearch:  
    uris: http://xxx:9200  
    username: xxx  
    password: xxx
logging:  
  level:  
  # 输出es的查询参数(调试用)
    tracer: TRACE  

索引操作

实体类

@Data  
@Document(indexName = "account")  
public class Account {  
  
    @Id  
    private String id;  
    // 解决ES中字段与实体类字段不一致的问题  
    @Field(name = "account_number", type = FieldType.Long)  
    private Long accountNumber;  
    @Field(type = FieldType.Text)  
    private String address;  
    @Field(type = FieldType.Integer)  
    private Integer age;  
    @Field(type = FieldType.Long)  
    private Long balance;  
    @Field(type = FieldType.Text)  
    private String city;  
    @Field(type = FieldType.Text)  
    private String email;  
    @Field(type = FieldType.Text)  
    private String employer;  
    @Field(type = FieldType.Text)  
    private String firstname;  
    @Field(type = FieldType.Text)  
    private String lastname;  
    @Field(type = FieldType.Text)  
    private String gender;  
    @Field(type = FieldType.Text)  
    private String state;  
	... 省略get、set 方法
}
IndexOperations idxOpt = template.indexOps(Account.class);  
// 索引是否存在  
boolean idxExist = idxOpt.exists();  
  
// 创建索引  
boolean createSuccess = idxOpt.createWithMapping();  
System.out.println(createSuccess);  
  
// 删除索引  
boolean deleted = idxOpt.delete();

文档操作(CRUD)

Account account = new Account();  
account.setId("1");  
account.setLastname("guyu");  
  
// 这是插入或覆盖,如果id存在了就是覆盖  
template.save(account);  
  
// 修改,用的是es的_update  
template.update(account);

// 删除  
template.delete(account)

// 批量新增(用的是es的_bulk)
List<Account> accountList = ...
template.save(accountList);

// 根据id查询
Account account = template.get("1", Account.class);

搜索 + 排序 + 分页

// 搜索 firstname = Amber AND age = 32
Criteria criteria = new Criteria();  
criteria.and(new Criteria("firstname").is("Amber"));  
criteria.and(new Criteria("age").is(32));  
  
// 分页  
int pageNum = 1; // 页码  
int pageSize = 20; // 每页数量  
Query query = new CriteriaQueryBuilder(criteria)  
        .withSort(Sort.by(new Order(Sort.Direction.ASC, "age"))) // 排序字段1  
        .withSort(Sort.by(new Order(Sort.Direction.DESC, "balance"))) // 排序字段1  
        .withPageable(PageRequest.of(pageNum - 1, pageSize)) // 浅分页  
        // 不需要查询的字段
        .withSourceFilter(new FetchSourceFilterBuilder().withExcludes("email", "address").build())
        .build();  
  
SearchHits<Account> searchHits = template.search(query, Account.class);  
long totalValue = searchHits.getTotalHits(); // 匹配到的数量  
for (SearchHit<Account> searchHit : searchHits.getSearchHits()) {  
    Account account = searchHit.getContent(); // 这就是得到的实体类  
}

总结

本文介绍了 SpringBoot 整合 ElasticSearch 的两种方案,但均只是简单提及,更详细的用法需要自行查看官方文档。

AI Agent(人工智能代理)是一种能够感知环境、进行决策和执行动作的智能实体。AI Agent可以称为“智能体”,也可以理解为“智能业务助理”,指在大模型技术驱动下,让人们以自然语言为交互方式高自动化地执行和处理专业或繁复的工作任务,从而极大程度释放人员精力。

灵境矩阵
是百度推出的基于文心大模型的智能体(Agent)平台,支持广大开发者根据自身行业领域、应用场景,选取不同类型的开发方式,打造大模型时代的产品能力。开发者可以通过 prompt 编排的方式低成本开发智能体(Agent),可参见视频
《灵境矩阵平台介绍》

《大模型发展趋势与开发范式》

在灵境矩阵平台上,即使是毫无编程经验的小白也能轻松创建属于自己的AI智能体,需要的只是创意。

本文介绍了零代码、低代码两种创建智能体的方法,操作非常简单。

首先登录
智能体创建开发地址
:https://agents.baidu.com/center(使用百度账号,如未注册过可以用手机号验证码方式登录,即可同步注册)。

一、零代码创建智能体
1、点击左侧的“
创建智能体
”,在“
零代码
”这一栏点击“
立即创建
”。

2、在“创建”页面,直接以
文字描述
的方式输入你对这个智能体的
角色要求
,可尽量描述清晰、具体一些。

比如可以包括:你需要智能体能够做什么,不要做什么,你希望他的回复是什么格式的,或语气是什么风格的。

3、当创建完成后,平台会询问你希望使用什么样的
头像
,并根据你的描述帮你AI生成头像。

你可以对头像进行修改,也可以在“配置”页面上传自己选择的图片作为这个智能体的头像。

4、你可以持续对这个智能体提出更细节的要求,平台会立即对智能体进行
优化

于此同时,你可以在右侧看到这个智能体的实际运行效果,并通过和智能体进行对话来
测试效果

5、进到“
配置
”页面,可以对智能体的名称、头像进行修改,并设置一个合适的
开场白
。当用户打开这个智能体之后,智能体会自动先发出预设的开场白。

配置页面已经根据你创建时对智能体提出的要求,自动生成了
指令
,你可以点击“查看示例”,对指令进行优化和完善。

6、点击“
新建数据集
”,可以将一些相关的文档资料上传到数据集中,作为这个智能体的补充资料库。

7、看一下这个智能体的
运行效果

8、保存,并
提交
这个智能体,将它设置为
公开
,等待平台
审核
通过即可上线了。

9、
补充知识点:Prompt工程

(1)Prompt概念

在人工智能和机器学习的世界里,特别是在自然语言处理(NLP)的领域,"prompt"扮演着至关重要的角色。简而言之,prompt是一段文本或一个问题,它的作用是引导AI模型产生预期的回答或反应。这种互动方式对于语言模型尤为关键,因为它们依赖于prompt来构建和生成文本。
Prompt的形式多样,
可以是一个简单的问题、一个详细的段落,或者一系列具体的指令
,这完全取决于用户的具体需求和应用场景。
当模型接收到prompt时,它会尽力解读其含义,并据此生成一个相应的回答。由此可见,prompt的重要性不言而喻,因为它直接塑造了模型输出文本的内容、风格和整体质量。
举个例子:
如果我们把 AI 模型比作一名员工,Prompt 就相当于给员工的具体指令。
指令的明确性和详细性决定了模型的输出效果。

(2) Prompt的作用和应用

Prompt在AI领域的应用不仅关乎技术的发展,也涉及到伦理、用户体验和社会影响等多个层面。
随着AI技术的不断进步,对prompt的研究和应用将继续扩展,以满足不断变化的需求和挑战。

(3)Prompt的
局限性

时效性:

Prompt 提示词与 AI 大语言模型交互的知识来自训练时接触到的数据,无法提供未知信息或新的发现。
举例:如果您询问关于一项最新科学研究的问题,模型可能无法提供相关信息,因为它的知识截止日期通常在训练数据截止日期之前。
上下文记忆:
上下文记忆问题可能是由于模型在处理超长文本时遇到了字节长度限制或者信息丢失的情况导致的。这可能会导致模型在较长的对话或文本中失去一些先前的上下文信息。
要解决这个问题,可以尝试:将文本分割成较短的段落或句子;尽量使用清晰、简洁的语言,避免复杂的句子结构;在对话中确保提出的问题或请求是明确的;可以逐步引导模型,提供必要的背景信息,确保模型能够理解上下文。
AI幻觉:

例如AI 大语言模型被询问有关一个虚构的现象,最后回复并未表明该现象并不存在,或者没有相关信息可供查询,反而编造了一个听似合理的解答,并配以看似道理十足的引用,也就是在胡说八道。

精确数字:

AI很难按你要求的数字完成一段话。例如让它写一篇标题为“我的一天”的 200 字日记文章,模型可能会写得很长,详细地描述你一天的起床、吃饭、上班等点滴过程,结果字数远超过要求。因为对它来说,生成更多相关细节会让文章更流畅丰富,所以它不会自觉在刚好 200 字时就停止。

(4)
Prompt技巧

二、低代码创建智能体

1、点击左侧的“
创建智能体
”,在“低代码”这一栏点击“立即创建”。

2、在弹出的窗口中,
设置
智能体的名称、头像、简介,点击“创建”按钮。

3、进到编辑页面,进行
可视化拖拽开发

左侧就是常用的套件,其中
“链”
是应用的最基础的单元,
知识库
也就是向量库,
模型
指的是文心一言的模型能力,
提示词
模板编排确保了开发的准确性和高效性。

提供了两个
工具
:HTTP请求工具、百度搜索工具,提供了
提问气泡交互组件

4、拖拽式开发的第一步,就是将“模型”下面的“
文心模型
”,用鼠标拖拽到编辑区内。

5、将“链”下面的“
大模型链
”拖拽到编辑区,然后
将大模型链与模型相连接

鼠标放到大模型链的“模型”前那个小圆圈上,直到鼠标变成一个加号+,然后按下鼠标,拖拽一条线连接到文心模型右下角“链”的小圆圈上。

一个基础的智能体通常由一个模型(文心模型)和一个大模型链组成,当大模型链与模型相连接时,就创建了一个最基础的智能体。

6、这个智能体调用了通用大模型的最基础能力,可以进行测试了。

点击页面右上方的“
测试
”按钮,显示测试通过,预览按钮变为可用状态,就可以进行互动了。

7、点击“
预览
”按钮,提示:预览8小时后失效,请合理安排时间。 假设我们需要智能体能够为用户推荐想看的书籍,就直接输入问题,模型识别到问题就能生成答案。

8、点击上图中的“
去设置
”,可以对模型的
引导语、推荐问题
等进行设置。

再进行测试时,可以看到已经带上了引导语和推荐问题。

推荐问题相当于为用户提供了快捷方式,让用户能更方便地了解和使用您的智能体,这将有助于提高用户体验。

看一下
运行效果

9、
添加提示词
,提示词扮演着重要的角色,它能够提供一种模板编排的方式。

例如如果用户输入了某个人物的名字,我们可以把问题转化为对包含这个人物名字的书籍的推荐。

把“
提示词模板
”拖拽到编辑区,可以编辑提示词模板、编辑输出格式。

例如,编辑模板如下:

然后将提示词右下角的“链”与大模型链“提示语”前面的圆圈连接起来。通过这种方式,用户只需要输入关键信息,我们就可以根据模板为其生成完整的问题。

本例中未限定输出格式,当然也可以编辑输出格式,比如可以要求模型以json格式输出:

10、至此,一个简单的低代码智能体就创建完成了,可以
提交发布
,等待审核了。

11、如果需要创建
更复杂一些的智能体
,可以继续进行下面的尝试(下面以一个用于推荐酒店的智能体为例):

(1)
工具链
:工具链和大模型链,可以通过输入和输出来连接。这种连接方式描述了智能体的处理逻辑,是从工具链开始处理,然后将工具链的输出作为大模型链的输入,再经过第二个链的处理,这是一个链的链式调用过程。

在工具链的配置中,首先让它发起一个http请求接口(使用HTTP请求工具),这个接口可以是模拟的一个数据源,主要用于测试模型返回酒店列表的功能。在实际的酒店智能体开发中,可以把它设置为一个实际的数据来源,比如某酒店的数据库。

当用户输入一个城市名称时,工具链将请求酒店数据,并捕获返回的结果,这些结果将被传递给一个大模型,该模型负责解读数据,示例中使用了一个假数据集模拟捕获的结果。

(2)
分支链
:分支链本质上也是一个大模型链,因此需要连接到一个模型(文心模型)。

分支链的配置中,有一个“意图关键词”,用于描述用户的输入所表达的目的,并根据该意图执行相应的功能。通过配置意图,可以根据用户的输入来触发不同的处理逻辑。如果意图之间的关联性模糊,会导致意图识别的准确性降低,因此需要清晰地表达意图。

(3)
提问链

提问气泡
:提问链和分支链通常配合使用(见上图)。

如果识别到用户要进行翻译,可以在提问气泡中配置一个表单组件,并列举出支持的语言选项。配套的提示词模板,会将用户的输入翻译成指定的语言。提问链中提问气泡的提示词,代表了这个提问所对应的问题,{_input_}就是提问链生成的问题。

用户回答后,会触发一个强制意图,即执行翻译。强制意图会执行一个新的链:负责执行实际翻译工作的大模型链。为了确保输出格式符合要求,可创建一个提示,如果用户没有命中任何一个意图,系统将执行“默认意图”,使用这个通用模型来回答。

(4)
工具链
:可以调用工具(如HTTP请求工具)进行查询,例如可以获取到一个平台的日志,再将日志数据输入给一个大模型。

(5)
检索链
:当提问相关问题,就会进到一个检索链,默认意图也可以链接到一个检索链。

相关的
视频教材
,可参考:https://datawhaler.feishu.cn/wiki/CXfdwUNE0ioQFukgJTLcamIQnVf