需求5:优化prompt

1.提示词优化

学习了prompt工程,主要的优化思路有(参考连接):

  1. 角色提示:“假设我是一个刚入行的电商买家”,类似这种可以将gpt带入角色,以该角色的视角进行输出
  2. 思维链提示:让gpt一步一步进行思考,例如计算1+2+3,先让gpt算出1+2=3,然后再去算3+3=6,有了思维链能够使ai清晰思考路径,犯错的可能性降低
  3. 思维树提示:跟上面的思维链比起来,思维树要考虑不同的分叉,在执行过程中要考虑不同的情况
  4. 自一致性提示:将模型的温度升高,多次去执行一个问题,返回其中最高频率的答案。对于推荐词的场景不太适用,因为调用次数有限而且时延较大,被mentor否决。
  5. ReAct提示:用python的api进行调用,功能强大但用不了。
    综合以上的方法,只有思维链可以作为优化。
  1. 近似词:中译英
1
2
3
String synonymsPrompt = MessageFormat.format("与\"{0}\"意思相近的产品有哪些,
请用{1}列举10个产品名。要求不包含\"{0}\",
输出格式为:jsonArray", formatValue, language) + ",[]";
1
2
3
String synonymsPrompt = MessageFormat.format("What are the products with the similar meaning of \"{0}\",\n" +
"Please list 10 product names in {1}. My request is not to include \"{0}\",\n" +
"The output format is jsonArray", formatValue, language) + ",[]";
  1. 电商词:转为中文思维链
1
2
3
4
5
String platformPrompt = MessageFormat.format("假设我是一个新入行的电商卖家,
主要经营的是\"{0}\"。我希望能有更多的用户在amazon.com等同类电商网站中搜索到我,
我可以使用怎样的{1}产品名对自己的产品进行描述?要求不包含\"{0}\",
请去掉尽可能多的长尾词,提炼共同的产品描述词。帮我用{2}同义替换10个不同的产品描述。仅输出替换后的数据,
格式为:jsonArray", formatValue, language, language) + ",[]";
1
2
3
4
5
String platformPrompt = MessageFormat.format("请按照如下提示一步步推理:\n" +
"第一步,请将产品\"{0}\"从品牌、型号、材料、用途等方面进行扩展,尽量细分且具体到某个品牌的某个产品。\n" +
"第二步,思考该产品在amazon.com等同类电商网站中显示给用户的商品词是什么。\n" +
"第三步,提取商品词中的关键信息,不允许出现品牌和型号,长度严格限制在30个字母以内。\n" +
"综合以上三步,仅用{1}输出十个这样的产品名,要求不包含\"{0}\"格式为:jsonArray,[]", formatValue, language) + ",[]";
  1. 行业词:中译英
1
2
3
4
5
6
7
8
9
10
11
12
13
14
prompt = "我是一个新入行的外贸供应商,主要经营的是"+ formatValue +"。我的产品可以应用于哪些行业的哪些产品?帮我列举3个行业,并描述推荐这些行业的理由,在每个行业下列举5个使用该产品制造出来的产品名称。\n" +
"用json输出,格式为:\n" +
"行业1\n" +
"行业名:xxx\n" +
"推荐理由:xxx\n" +
"相关产品:xxx\n" +
"要求:\n" +
"1、行业名称用{"+languageVersion+"}。\n" +
"2、推荐理由用{"+languageVersion+"}。\n" +
"3、相关产品用{"+language+"}。\n" +
"4、产品名称不能重复。\n" +
"5、产品名称不需要包含材质等限制。\n" +
"6、相关产品用#格开。" +
"7、不包含\""+formatValue+"\"。";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
prompt = "I am a new foreign trade supplier, the main business is \""+formatValue+"\". Which products can my product be used in which industries? Give me a list of 3 industries and describe the reasons why you recommend them, and list 5 names of products made with that product under each industry.\n" +
"Output in json format:\n" +
" 行业1\n" +
" 行业名:xxx\n" +
" 推荐理由:xxx\n" +
" 相关产品:xxx\n" +
"Requirement: \"\n" +
"1. the industry name with {"+languageVersion+"}.\n" +
"2. recommended reasons use {"+languageVersion+"+}.\n" +
"3. Use {"+language+"} for related products.\n" +
"4. the product name can not be repeated.\n" +
"5. the product name does not need to include material and other restrictions.\n" +
"6. related products are delimited with #\n" +
"7. \""+formatValue+"\" is not included.";

三个小小的优化耗费了我一周的时间,实际上也没多少提升。

2.代码逻辑

近似词和电商词

  • 首先判断是否为空,或者是否为敏感词(调用网易易盾),随后传入用户信息,访问服务。
  • 服务内部:传入参数中语言为空默认为英语,然后用CompletableFuture.supplyAsync()搭配线程池完成gpt的两次请求。线程池如下:
1
2
3
4
//线程工厂仅仅改名,拒绝策略是发起者自己消化
private static final ThreadFactory GPT_THREAD_FACTORY = new ThreadFactoryBuilder().setNameFormat("GptGrpcWrapper-gpt-pool-%d").build();
private static final ExecutorService GPT_REQUEST_EXECUTOR = new ThreadPoolExecutor(20,
40, 60 * 5L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3000), GPT_THREAD_FACTORY, new ThreadPoolExecutor.CallerRunsPolicy());
  • 请求成功的结果缓存在redis中100天,缓存的key是”cacheKeyPreFix + “v6” + “:” + gptRecommendReqVO.getLanguage()+”:”+gptRecommendReqVO.getValue();”
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
private Set<String> getRecommendWordV1(GptRecommendReqVO gptRecommendReqVO, AuthInfo authInfo, String prompt, String cacheKeyPreFix) {
String cacheValue = "";
try {
if (StringUtils.isBlank(gptRecommendReqVO.getValue())) {
return Collections.emptySet();
}
String formatValue = FormatUtil.escapeFormat(gptRecommendReqVO.getValue());
if(StringUtils.isBlank(formatValue) || formatValue.length()<=1){
log.info("GptGrpcWrapper value:{},formatValue:{}",gptRecommendReqVO.getValue(),formatValue);
return Collections.emptySet();
}
if(gptRecommendReqVO.getLanguage()==null){
gptRecommendReqVO.setLanguage("英语");
}

String language = gptRecommendReqVO.getLanguage();
String cacheKey = cacheKeyPreFix + "v6" + ":" + gptRecommendReqVO.getLanguage()+":"+gptRecommendReqVO.getValue();
cacheValue = recommendWordCache.get(cacheKey);
//命中
if(StringUtils.isNotBlank(cacheValue)){
log.info("GptGrpcWrapper call success,param:{},result:{}",JSON.toJSONString(gptRecommendReqVO),cacheValue);
//大段文字中的["xxx1","xxx2","xxx3"]
if(cacheValue.contains("[") && cacheValue.contains("]")){
cacheValue = cacheValue.substring(cacheValue.indexOf("["), cacheValue.lastIndexOf("]") + 1);
}
//只取前6个,这个数值是前端定的
Set<String> synonyms = getLimitSize(Lists.newArrayList(JSON.parseArray(cacheValue, String.class)), gptRecommendReqVO.getSize());
return synonyms;
}
//未命中
log.info("GptGrpcWrapper call param:{},prompt:{}",JSON.toJSONString(gptRecommendReqVO),prompt);
CompletableFuture<String> future = CompletableFuture.supplyAsync(
() -> gptRequest(authInfo.getOrgId(),authInfo.getAccId(),authInfo.getEmail(),prompt, GPTModelVersionEnum.GPT_4O_MINI.getVersion()), GPT_REQUEST_EXECUTOR);
//这里实际上还是同步请求,"gpt-recommard-future"是一个日志的名称,超时时间120s
String res = (String)FutureResultUtil.getResult("gpt-recommard-future",future,120, TimeUnit.SECONDS);
if (StringUtils.isBlank(res)) {
log.info("getRecommend failed.name:{},language:{}",formatValue, language);
return Collections.emptySet();
}
cacheValue = res;
// 推荐词缓存100天
recommendWordCache.set(cacheKey,cacheValue,TimeUnit.DAYS.toMillis(100));
log.info("GptGrpcWrapper call success,param:{},result:{}",JSON.toJSONString(gptRecommendReqVO),cacheValue);
if(cacheValue.contains("[") && cacheValue.contains("]")){
cacheValue = cacheValue.substring(cacheValue.indexOf("["), cacheValue.lastIndexOf("]") + 1);
}
Set<String> synonyms = getLimitSize(Lists.newArrayList(JSON.parseArray(cacheValue, String.class)), gptRecommendReqVO.getSize());
return synonyms;
}catch (Exception e) {
//gpt出的词如果不常规。例如是“好的,接下来为您输出....”,在JSON.parseArray就会报异常不返回给前端。
log.error("getGptRecommend param:{}",JSON.toJSONString(gptRecommendReqVO), e);
return Collections.emptySet();
}
}
  1. redis只存gpt原始回答,可以改成处理后的回答
  2. 线程池实际上只将近似词和电商词做了异步,在这两个单独的请求里面用future实际还是同步。
  3. gpt如果输出不理想有两重处理,首先看是否包含了”[“xxx1”,”xxx2”,”xxx3”]”,用subString提取出来,实在是没有那在parseObject就会报错,结果返回为空。
  • 最后处理近似词和电商词的结果,用一个大的hashset去重(但是大小写敏感,这里可以优化成全部小写再比较)
  • 调用GRPC(有道翻译)兜底,先判断是否出现中文,有中文就翻译。批量翻译返回值是map,key是原文,value是翻译后的。最后进行组装返回。

总结:调用易盾进行敏感词检测,线程池+CompletableFuture完成两次gpt查询,gpt查询做了一系列异常处理,在redis中缓存100天,最后调用有道翻译的grpc进行兜底。

行业词

  • 首先判断是否为空,或者是否为敏感词(调用网易易盾),随后传入用户信息,访问服务。
  • 服务内部:处理逻辑跟上文类似,在redis中存储gpt原始回答,但是由于输出的是json,相比于近似词和行业词的数组,这里有改变。
  • 去除”```json”和”```“这类md语法,再根据”行业一,二,三”json形式包装。
  • 这里要做一些异常处理以增加可用性和鲁棒性,虽然提示词里面写的是用”#“隔开,但是实际上可能返回的还是”,”或者”、”,用一个函数对其进行拆分。
  • 最后调用有道翻译兜底。

需求6:订阅更新任务

订阅更新链路:

  1. 用户前端点击页面就会更新订阅公司的watchTime字段,一天之内只更新一次。
  2. xxljob每周扫库,搜索的是订阅公司的那个表,将watchTime在前一周的公司发送kafka(自增id而不是公司id)
  3. kafka消费者拿到这些id去数据库找对应的公司,将订阅公司表项和公司实体项(从es来的,如果前者有companyId就直接查询,如果没有就需要根据名字和地区来查询并保存)作为参数传分别分析facebook信息,facebook提及信息,海关信息,联系人信息,开了一个线程池并行处理这些。

1.facebook信息

代码太长了,梳理一下就这几部分:

  1. 根据公司实体中的相关facebook链接去请求GRPC接口,返回facebook实体,其中包含个人信息,点赞数量,最近推文等信息。
  2. 从这里开始要注意逻辑,比对的是GRPC请求来的数据当前数据库中最新的log
  3. 去数据库中查询7种type的log,先全部查出来,然后再通过stream和sort得到最近时间的log。
  4. 将这些log分别与GRPC请求来的数据进行比较,例如判断facebook是否更新了,将最近的一条相关log的内容拉出来与GRPC请求来的数据进行比较,如果不一样就需要更新,在库里面新增一条。
  1. facebook地址初始化/变更
  2. facebook邮箱初始化/变更
  3. facebook网站地址初始化/变更
  4. facebook电话初始化/变更
  5. facebook点赞、关注初始化
  6. facebook中发布了新帖子
  7. facebook获得1个新评论

2.facebook提及信息

  1. 类似上面的,提及消息也是从GRPC来的,如果返回为null直接返回,这里是一个list,只关注最新的,所以也有stream+sort。
  2. 去数据库查询当前最新的log,主要关注的是这个记录的createTime。
  3. 如果数据库没有,那就说明是最新更新的,向数据库插入一条新的log表明有被提及,直接返回。
  4. 如果数据库有但log的时间比GRPC来的早,说明有新提及,进行更新。

3.海关信息

  1. 将公司名称格式化,随后去海关数据里面查找新增进出口数据,分别放在两个list里面。
  2. 如果有进出口就分别新增log。
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
public void customProcess(TbGlobalCollectEntity record, CompanySearchBO companyBO, Date startDate, Date endDate) {
//海关那边的统一格式"CompanyName-COMBINE-TANZANIA"例如"101 INVESTMENT-COMBINE-TANZANIA"
String nameAndCountry = CompanyFormatUtils.combineCompanyNameAndCountry(companyBO.getName(), companyBO.getCountry());
//去取出昨天的新增hscode,这个表主要是海关系统在维护
Map<String, CustomInfoBO> map = customDataComponent.batchGetCompanyNewTrxHscode(Lists.newArrayList(nameAndCountry), startDate, endDate);
List<TbGlobalCollectLogEntity> tbGlobalCollectLogEntities = Lists.newArrayList();
if(map.containsKey(nameAndCountry)){
CustomInfoBO customInfoBO = map.get(nameAndCountry);
//出口
if(!CollectionUtils.isEmpty(customInfoBO.getExportHsCodeList())){
TbGlobalCollectLogEntity t = new TbGlobalCollectLogEntity();
t.setCollectId(record.getId());
t.setLogDesc("新供应了hscode为" + Strings.join(customInfoBO.getExportHsCodeList(), ",") + "等的商品");
t.setContent(JSON.toJSONString(customInfoBO.getExportHsCodeList()));
t.setType(CollectLogTypeEnum.CUSTOM_SHN.getType());
t.setCreateTime(new Date());
t.setUpdateTime(new Date());
t.setInit12(0);
tbGlobalCollectLogEntities.add(t);
}
//进口
if(!CollectionUtils.isEmpty(customInfoBO.getImportHsCodeList())){
TbGlobalCollectLogEntity t = new TbGlobalCollectLogEntity();
t.setCollectId(record.getId());
t.setLogDesc("新采购了hscode为" + Strings.join(customInfoBO.getImportHsCodeList(), ",") + "等的商品");
t.setContent(JSON.toJSONString(customInfoBO.getImportHsCodeList()));
t.setType(CollectLogTypeEnum.CUSTOM_CON.getType());
t.setCreateTime(new Date());
t.setUpdateTime(new Date());
t.setInit12(0);
tbGlobalCollectLogEntities.add(t);
}
}
if(!CollectionUtils.isEmpty(tbGlobalCollectLogEntities)){
tbGlobalCollectLogEntityService.saveAll(tbGlobalCollectLogEntities);
}
}

4.联系人信息