本章核心:深度剖析JoyAgent-JDGenie项目的LLM集成架构设计与提示词工程实践,从统一抽象层到多模型支持,从动态提示词生成到流式输出处理,从token优化到错误恢复策略,全面解析现代多智能体系统的大模型集成精髓。
引言:LLM集成的技术价值与架构地位
在JoyAgent-JDGenie多智能体系统中,LLM集成扮演着"智能核心"的关键角色。它不仅要提供统一的模型调用接口,更要实现高效的多模型支持、智能的提示词工程、可靠的流式处理以及完善的性能优化机制。通过精心设计的抽象层架构和现代化的工程实践,构建了一个高性能、高可用、易扩展的LLM服务系统。
本章将采用"总-分-总"的架构体系,首先概述LLM集成的整体设计理念,然后深入分析提示词工程的实现细节,最后总结模型调用优化的核心策略和最佳实践经验。
第一部分:LLM抽象层设计与架构理念 🔧
1.1 统一LLM抽象层架构
1.1.1 LLM核心抽象类设计
JoyAgent-JDGenie项目实现了高度抽象化的LLM集成架构,通过统一的接口支持多种大模型:
/**
* LLM 类
*/
@Slf4j
@Data
public class LLM {
private static final Map<String, LLM> instances = new ConcurrentHashMap<>();
private final String model;
private final String llmErp;
private final int maxTokens;
private final double temperature;
private final String apiKey;
private final String baseUrl;
private final String interfaceUrl;
private final String functionCallType;
private final TokenCounter tokenCounter;
private final ObjectMapper objectMapper;
private final Map<String, Object> extParams;
private int totalInputTokens;
private Integer maxInputTokens;
public LLM(String modelName, String llmErp) {
this.llmErp = llmErp;
LLMSettings config = Config.getLLMConfig(modelName);
this.model = config.getModel();
this.maxTokens = config.getMaxTokens();
this.temperature = config.getTemperature();
this.apiKey = config.getApiKey();
this.baseUrl = config.getBaseUrl();
this.interfaceUrl = StringUtils.isNotEmpty(config.getInterfaceUrl()) ? config.getInterfaceUrl() : "/v1/chat/completions";
this.functionCallType = config.getFunctionCallType();
// 初始化 token 计数相关属性
this.totalInputTokens = 0;
this.maxInputTokens = config.getMaxInputTokens();
this.extParams = config.getExtParams();
// 初始化 tokenizer
this.tokenCounter = new TokenCounter();
this.objectMapper = new ObjectMapper();
}
LLM抽象层设计亮点:
- 单例模式管理:通过ConcurrentHashMap实现多模型实例的安全管理
- 配置驱动架构:基于LLMSettings的灵活配置体系
- 多模型统一接口:支持OpenAI、Claude等不同厂商的模型
- 内置优化机制:集成token计数器和性能监控
1.1.2 LLM配置管理体系
系统通过完善的配置管理体系支持多模型的灵活切换:
/**
* LLM 配置类
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LLMSettings {
/**
* 模型名称
*/
private String model;
/**
* 最大生成 token 数量
*/
private int maxTokens;
/**
* 温度参数
*/
private double temperature;
/**
* API 类型(openai 或 azure)
*/
private String apiType;
/**
* API 密钥
*/
private String apiKey;
/**
* API 版本(仅适用于 Azure)
*/
private String apiVersion;
/**
* 基础 URL
*/
private String baseUrl;
/**
* 接口 URL
*/
private String interfaceUrl;
/**
* FunctionCall类型
*/
private String functionCallType;
/**
* 最大输入 token 数量
*/
private int maxInputTokens;
/**
* 额外参数
*/
private Map<String, Object> extParams;
}
1.1.3 配置文件驱动的模型管理
application.yml配置文件实现了声明式的多模型配置:
spring:
application:
name: genie-backend
config:
encoding: UTF-8
server:
port: 8080
logging:
level:
root: INFO
llm:
default:
base_url: '<input llm server here>'
apikey: '<input llm key here>'
interface_url: '/chat/completions'
model: gpt-4.1
max_tokens: 16384
settings: '{"claude-3-7-sonnet-v1": {
"model": "claude-3-7-sonnet-v1",
"max_tokens": 8192,
"temperature": 0,
"base_url": "<input llm server here>",
"apikey": "<input llm key here>",
"interface_url": "/chat/completions",
"max_input_tokens": 128000
}}'
配置管理精髓:
- 多环境支持:default配置与特定模型配置的有机结合
- 动态切换能力:支持运行时模型切换而无需重启
- 参数精细控制:每个模型可独立配置参数
- 向前兼容性:良好的默认值和向前兼容机制
1.1.4 配置加载与动态管理
Config工具类实现了智能的配置加载和管理机制:
/**
* 配置工具类
*/
@Slf4j
public class Config {
/**
* 获取 LLM 配置
*/
public static LLMSettings getLLMConfig(String modelName) {
ApplicationContext applicationContext = SpringContextHolder.getApplicationContext();
GenieConfig genieConfig = applicationContext.getBean(GenieConfig.class);
if (Objects.nonNull(genieConfig.getLlmSettingsMap())) {
return genieConfig.getLlmSettingsMap().getOrDefault(modelName, getDefaultConfig());
}
return getDefaultConfig();
}
/**
* 加载 LLM 配置
*/
private static LLMSettings getDefaultConfig() {
Resource resource = new ClassPathResource("application.yml");
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
yamlFactory.setResources(resource);
Properties props = yamlFactory.getObject();
// 创建默认配置
return LLMSettings.builder()
.model(props.getProperty("llm.default.model", "gpt-4o-0806"))
.maxTokens(Integer.parseInt(props.getProperty("llm.default.max_tokens", "16384")))
.temperature(Double.parseDouble(props.getProperty("llm.default.temperature", "0")))
.baseUrl(props.getProperty("llm.default.base_url", ""))
.interfaceUrl(props.getProperty("llm.default.interface_url", "/v1/chat/completions"))
.functionCallType(props.getProperty("llm.default.function_call_type", "function_call"))
.apiKey(props.getProperty("llm.default.apikey", ""))
.maxInputTokens(Integer.parseInt(props.getProperty("llm.default.max_input_tokens", "100000")))
.build();
}
}
1.2 多模型支持与格式适配
1.2.1 统一消息格式化机制
LLM类实现了对不同模型API格式的统一适配:
/**
* 格式化消息
*/
public static List<Map<String, Object>> formatMessages(List<Message> messages, boolean isClaude) {
List<Map<String, Object>> formattedMessages = new ArrayList<>();
for (Message message : messages) {
Map<String, Object> messageMap = new HashMap<>();
if (message.getFiles() != null && !message.getFiles().isEmpty()) {
// 多模态内容处理
List<Map<String, Object>> multimodalContent = new ArrayList<>();
Map<String, Object> outerMap = new HashMap<>();
multimodalContent.add(outerMap);
Map<String, Object> contentMap = new HashMap<>();
outerMap.put("type", "text");
outerMap.put("text", message.getContent());
multimodalContent.add(contentMap);
messageMap.put("role", message.getRole().getValue());
messageMap.put("content", multimodalContent);
} else if (message.getToolCalls() != null && !message.getToolCalls().isEmpty()) {
if (isClaude) {
// Claude格式的工具调用处理
messageMap.put("role", message.getRole().getValue());
List<Map<String, Object>> claudeToolCalls = new ArrayList<>();
for (ToolCall toolCall : message.getToolCalls()) {
Map<String, Object> claudeToolCall = new HashMap<>();
claudeToolCall.put("type", "tool_use");
claudeToolCall.put("id", toolCall.getId());
claudeToolCall.put("name", toolCall.getFunction().getName());
claudeToolCall.put("input", JSON.parseObject(toolCall.getFunction().getArguments()));
claudeToolCalls.add(claudeToolCall);
}
messageMap.put("content", claudeToolCalls);
} else {
messageMap.put("role", message.getRole().getValue());
List<Map<String, Object>> toolCallsMap = JSON.parseObject(JSON.toJSONString(message.getToolCalls()),
new TypeReference<List<Map<String, Object>>>() {
});
messageMap.put("tool_calls", toolCallsMap);
}
} else if (message.getToolCallId() != null && !message.getToolCallId().isEmpty()) {
// 敏感词过滤
GenieConfig genieConfig = SpringContextHolder.getApplicationContext().getBean(GenieConfig.class);
String content = StringUtil.textDesensitization(message.getContent(), genieConfig.getSensitivePatterns());
if (isClaude) {
// Claude格式的工具调用结果处理
messageMap.put("role", "user");
List<Map<String, Object>> claudeToolCalls = new ArrayList<>();
Map<String, Object> claudeToolCall = new HashMap<>();
claudeToolCall.put("type", "tool_result");
claudeToolCall.put("tool_use_id", message.getToolCallId());
claudeToolCall.put("content", content);
claudeToolCalls.add(claudeToolCall);
messageMap.put("content", claudeToolCalls);
} else {
messageMap.put("role", message.getRole().getValue());
messageMap.put("content", content);
messageMap.put("tool_call_id", message.getToolCallId());
}
} else {
messageMap.put("role", message.getRole().getValue());
messageMap.put("content", message.getContent());
}
formattedMessages.add(messageMap);
}
return formattedMessages;
}
消息格式化的核心特性:
- 多模型适配:统一支持OpenAI和Claude的不同API格式
- 多模态支持:图文混合内容的智能处理
- 工具调用适配:不同模型工具调用格式的统一处理
- 安全性保障:内置敏感词过滤机制
1.2.2 Claude模型特殊适配
系统对Claude模型进行了专门的格式适配处理:
/**
* 将OpenAI GPT工具定义转换为Claude工具格式
*/
public List<Map<String, Object>> gptToClaudeTool(List<Map<String, Object>> gptTools) {
List<Map<String, Object>> newGptTools = deepCopy(gptTools);
List<Map<String, Object>> claudeTools = new ArrayList<>();
for (Map<String, Object> gptToolWrapper : newGptTools) {
Map<String, Object> gptTool = (Map<String, Object>) gptToolWrapper.get("function");
Map<String, Object> claudeTool = new HashMap<>();
claudeTool.put("name", gptTool.get("name"));
claudeTool.put("description", gptTool.get("description"));
claudeTool.put("input_schema", gptTool.get("parameters"));
claudeTools.add(claudeTool);
}
return claudeTools;
}
1.3 GenieConfig配置管理系统
1.3.1 全局配置管理架构
GenieConfig类实现了系统级的配置管理,支持提示词、模型参数、工具配置等全方位管理:
@Slf4j
@Getter
@Configuration
public class GenieConfig {
private Map<String, String> plannerSystemPromptMap = new HashMap<>();
@Value("${autobots.autoagent.planner.system_prompt:{}}")
public void setPlannerSystemPromptMap(String list) {
plannerSystemPromptMap = JSONObject.parseObject(list, new TypeReference<Map<String, String>>() {
});
}
private Map<String, String> plannerNextStepPromptMap = new HashMap<>();
@Value("${autobots.autoagent.planner.next_step_prompt:{}}")
public void setPlannerNextStepPromptMap(String list) {
plannerNextStepPromptMap = JSONObject.parseObject(list, new TypeReference<Map<String, String>>() {
});
}
private Map<String, String> executorSystemPromptMap = new HashMap<>();
@Value("${autobots.autoagent.executor.system_prompt:{}}")
public void setExecutorSystemPromptMap(String list) {
executorSystemPromptMap = JSONObject.parseObject(list, new TypeReference<Map<String, String>>() {
});
}
@Value("${autobots.autoagent.planner.model_name:gpt-4o-0806}")
private String plannerModelName;
@Value("${autobots.autoagent.executor.model_name:gpt-4o-0806}")
private String executorModelName;
@Value("${autobots.autoagent.react.model_name:gpt-4o-0806}")
private String reactModelName;
/**
* LLM Settings
*/
private Map<String, LLMSettings> llmSettingsMap;
@Value("${llm.settings:{}}")
public void setLLMSettingsMap(String jsonStr) {
this.llmSettingsMap = JSON.parseObject(jsonStr, new TypeReference<Map<String, LLMSettings>>() {
});
}
private Map<String, String> sensitivePatterns = new HashMap<>();
@Value("${autobots.autoagent.sensitive_patterns:{}}")
public void setSensitivePatterns(String jsonStr) {
this.sensitivePatterns = JSON.parseObject(jsonStr, new TypeReference<Map<String, String>>() {
});
}
private Map<String, String> messageInterval = new HashMap<>();
@Value("${autobots.autoagent.message_interval:{}}")
public void setMessageInterval(String jsonStr) {
this.messageInterval = JSON.parseObject(jsonStr, new TypeReference<Map<String, String>>() {
});
}
}
配置管理系统特色:
- 分层配置设计:智能体、工具、模型的分层配置管理
- 类型安全解析:基于FastJson的类型安全配置解析
- 动态配置更新:支持运行时配置的动态加载和更新
- 配置验证机制:内置配置有效性验证和默认值设置
第二部分:提示词工程设计与实现 📝
2.1 分层提示词架构设计
2.1.1 智能体专用提示词体系
JoyAgent-JDGenie为不同智能体设计了专门的提示词模板,实现精确的角色定位和行为控制:
/**
* 规划代理的提示词常量
*/
public class PlanningPrompt {
public static final String SYSTEM_PROMPT = "\\n{{sopPrompt}}\\n\\n===\\n# 环境变量\\n## 当前日期\\n{{date}}\\n\\n# 当前可用的文件名及描述\\n{{files}}\\n\\n# 约束\\n- 思考过程中,不要透露你的工具名称\\n- 调用planning生成任务列表,完成所有子任务就能完成任务。\\n- 以上是你需要遵循的指令。\\n\\nLet's think step by step (让我们一步步思考)\\n";
public static final String NEXT_STEP_PROMPT = "工具planing的参数有\\n必填参数1:命令command\\n可选参数2:当前步状态step_status。\\n\\n必填参数1:命令command的枚举值有:\\n'mark_step', 'finish'\\n含义如下:\\n- 'finish' 根据已有的执行结果,可以判断出任务已经完成,输出任务结束,命令command为:finish\\n- 'mark_step' 标记当前任务规划的状态,设置当前任务的step_status\\n\\n当参数command值为mark_step时,需要可选参数2step_status,其中当前步状态step_status的枚举值如下:\\n- 没有开始'not_started'\\n- 进行中'in_progress' \\n- 已完成'completed'\\n\\n对应如下几种情况:\\n1.当前任务是否执行完成,完成以及失败都算执行完成,执行完成将入参step_status设置为`completed`\\n\\n一步一步分析完成任务,确定工具planing的入参,调用planing工具";
}
/**
* 工具调用代理的提示词常量
*/
public class ToolCallPrompt {
public static final String SYSTEM_PROMPT = "# 要求\\n- 如果回答用户问题时,需要输出文件、输出报告,尽量使用HTML网页报告输出网页版报告,如果用户指定"输出表格"尽量使用excel或者csv输出数据\\n- 输出报告前,尽量使用搜索工具搜索\\n\\n# 解决问题的流程\\n请使用交替进行的"思考(Thought)、行动(Action)、观察(Observation)'三个步骤来系统地解决回答任务。\\n\\n思考:基于当前获得的信息进行推理和反思,明确下一步行动的目标。\\n\\n行动:用于表示需要调用的工具,每一步行动必须是以下两种之一:\\n1、工具调用 [Function Calling]:根据任务需要,确定调用工具。\\n2、Finish[答案]:得出明确答案后使用此操作,返回答案并终止任务。\\n\\n观察:记录前一步行动的结果。\\n\\n你可以进行多轮推理和检索,但必须严格按照上述格式进行操作,尤其是每一步"行动"只能使用上述两种类型之一。\\n\\n# 示例\\n\\n问题:科罗拉多造山带东部区域延伸到的区域的海拔范围是多少?\\n\\n思考:我需要搜索"科罗拉多造山带",获取它的概况,特别是东部延伸区域的信息。\\n行动:搜索[科罗拉多造山带]\\n观察:科罗拉多造山带是科罗拉多及其周边地区造山运动的一段。\\n\\n思考:这里没有提到东部延伸区域的具体信息,我需要继续查找"东部区域"。\\n行动:查找 [东部区域]\\n观察:(结果 1 / 1)东部区域延伸至高平原,称为中原造山带。\\n\\n思考:我已经知道东部区域是高平原,我需要查找高平原的海拔范围。\\n行动:搜索 [高平原 海拔]\\n观察:高平原的海拔高度从 1800 到 7000 英尺。\\n\\n思考:我已经得到了答案,可以结束任务。\\n行动:Finish[1800 到 7000 英尺]\\n\\n# 语言设置\\n- 默认工作语言为**中文**,如用户明确指定其他语言,则按用户要求切换。\\n- 所有思考、推理与输出均应使用当前工作语言。\\n# 当前环境变量\\n- 当前日期:{{date}}\\n- 可用文件及描述:{{files}}\\n- 用户原始任务内容:{{query}} 现在请回答以下问题:";
}
2.1.2 工具专用提示词模板
Python工具服务也实现了专门的提示词模板体系,以代码解释器为例:
system_prompt: |-
You are AI assistant who can solve any task using python code. You will be given a task to solve as best you can.
To do so, you have been given access to a list of tools: these tools are basically Python functions which you can call with code.
To solve the task, you must plan forward to proceed in a series of steps, in a cycle of 'Thought:', 'Code:', and 'Observation:' sequences.
At each step, in the 'Task:' sequence, you can give a brief task description.
And in the 'Thought:' sequence, you should first explain your reasoning towards solving the task and the tools that you want to use.
Then in the 'Code:' sequence, you should write the code in simple Python. The code sequence must end with '</code>' sequence.
During each intermediate step, you can use 'print()' to save whatever important information you will then need.
These print outputs will then appear in the 'Observation:' field, which will be available as input for the next step. **Crucially, ensure that only key metric data is printed, and avoid printing file saving descriptions or file paths.**
In the end you have to return a final answer using the `final_answer` tool.
Please follow the format below to solve the given task step by step:
---
Task: {Task Description. One brief sentence, no punctuation}
Thought: {Your reasoning and planned tools}
Code:
<code>
{Code block}
</code>
Observation: {Observed output from code}
---
Here are the rules you should always follow to solve your task:
1. Always provide a 'Task:' sequence and a 'Thought:' sequence followed by a 'Code:' sequence, ending with '</code>'. Failure to do so will result in task failure.
2. Avoid chaining too many sequential tool calls within a single code block, especially when output formats are unpredictable. For tools like search, which have variable return formats, use 'print()' to pass results between blocks.
3. Call tools only when necessary, and avoid redundant calls with identical parameters.
4. Do not use tool names as variable names (e.g., avoid naming a variable 'final_answer').
5. Do not create notional variables that are not actually used in the code, as this can disrupt the logging of true variables.
6. Variables and imported modules persist between code executions.
7. All actions must be performed using Python code; manual processing is prohibited.
8. NOTICE: **Falsification of data is strictly prohibited**!
# language rules
- Default working language: **Chinese**
- Use the language specified by user in messages as the working language when explicitly provided
- All thinking and responses must be in the working language
- Natural language arguments in tool calls must be in the working language
- Avoid using pure lists and bullet points format in any language
- But **Python Code uses English**.
task_template: |-
{% if files %}
你有如下文件可以参考,对于 csv、excel、等数据文件则提供的只是部分数据,如果需要请你读取文件获取全文信息
<docs>
{% for file in files %}
<doc>
<path>{{ file['path'] }}</path>
<abstract>{{ file['abstract'] }}</abstract>
</doc>
{% endfor %}
</docs>
{% endif %}
## 要求
1. 如果有 excel、csv 文件,使用 pandas 读取、保存,使用 openpyxl 引擎,其他文件类型不要直接读取成 DataFrame;
2. 不要做额外的校验逻辑,比如路径校验;
3. 代码专注在用户的需求,内容完整、代码简洁;
4. 需要保存 DataFrame 数据的,请使用 excel 格式,确保文件编码正确;其他的保存成对应格式的文本文件;
5. 需要打印出分析结果
6. 只生成一份文件,文件名称不要和输入的文件名一致,文件名为中文
输出文件写入到 {{ output_dir }} 这个目录下,目录文件已经创建,不需要再判断路径是否存在以及创建输出路径
你的任务如下:
{{ task }}
2.1.3 多格式报告生成提示词
报告生成工具实现了针对不同输出格式的专用提示词:
ppt_prompt: |-
你是一个资深的前端工程师,同时也是 PPT制作高手,根据用户的【任务】和提供的【文本内容】,生成一份 PPT,使用 HTML 语言。
当前时间:{{ date }}
作者:Genie
## 要求
### 风格要求
- 整体设计要有**高级感**、**科技感**,每页(slide)、以及每页的卡片内容设计要统一;
- 页面使用**扁平化风格**,**卡片样式**,注意卡片的配色、间距布局合理,和整体保持和谐统一;
- 根据用户内容设计合适的色系配色(如莫兰迪色系、高级灰色系、孟菲斯色系、蒙德里安色系等);
- 禁止使用渐变色,文字和背景色不要使用相近的颜色;
- 避免普通、俗气设计,没有明确要求不要使用白色背景;
- 整个页面就是一个容器,不再单独设计卡片,同时禁止卡片套卡片的设计;
- 页面使用 16:9 的宽高比,每个**页面大小必须一样**;
- ppt-container、slide 的 css 样式中 width: 100%、height: 100%;
- 页面提供切换按钮、进度条和播放功能(不支持循环播放),设计要简洁,小巧精美,与整体风格保持一致,放在页面右下角
html_prompt: |-
# Context
你是一位世界级的前端设计大师,擅长美工以及前端UI设计,作为经验丰富的前端工程师,可以根据用户提供的内容及任务要求,能够构建专业、内容丰富、美观的网页来完成一切任务。
# 要求 - Requirements
## 网页格式要求
- 使用CDN(jsdelivr)加载所需资源
- 使用Tailwind CSS (使用CDN加速地址:https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css)提高代码效率
- 使用CSS样式美化不同模块的样式,可以使用javascript来增强与用户的交互,使用Echart(使用CDN加速地址:https://unpkg.com/echarts@5.6.0/dist/echarts.min.js)工具体现数据与数据变化趋势
- 数据准确性: 报告中的所有数据和结论都应基于<任务内容>提供的信息,不要产生幻觉,也不要出现没有提供的数据内容,避免误导性信息。
- 完整性: HTML 页面应包含<任务内容>中所有重要的内容信息。
- 逻辑性: 报告各部分之间应保持逻辑联系,确保读者能够理解报告的整体思路。
- 输出的HTML网页应包含上述内容,并且应该是可交互的,允许用户查看和探索数据。
- 不要输出空dom节点,例如'<div class="chart-container mb-6" id="future-scenario-chart"></div>' 是一个典型的空dom节点,严禁输出类似的空dom节点。
- 网页页面底部footer标识出:Created by Autobots \n 页面内容均由 AI 生成,仅供参考
markdown_prompt: |-
## 角色
你是一名经验丰富的报告生成助手。请根据用户提出的查询问题,以及提供的知识库,严格按照以下要求与步骤,生成一份详细、准确、客观且内容丰富的中文报告。
你的主要任务是**做整理,而不是做摘要**,尽量将相关的信息都整理出来,**不要遗漏**!!!
## 总体要求(必须严格遵守)
- **语言要求**:报告必须全程使用中文输出,一些非中文的专有名词可以不用使用中文。
- **信息来源**:报告内容必须严格基于给定的知识库内容,**不允许编造任何未提供的信息,尤其禁止捏造、推断数据**。
- **客观中立**:严禁任何形式的主观评价、推测或个人观点,只允许客观地归纳和总结知识库中明确提供的信息、数据。
- **细节深入**:用户为专业的信息收集者,对细节敏感,请提供尽可能详细、具体的信息。
- **内容丰富**:生成的报告要内容丰富,在提取到的相关信息的基础上附带知识库中提供的背景信息、数据等详细的细节信息。
- **来源标注**:对于所有数据、关键性结论,给出 markdown 的引用链接,如果回答引用相关资料,在每个段落后标注对应的引用。编号格式为:[[编号]](链接),如[[1]](www.baidu.com)。
- **逻辑连贯性**:要按照从前到后的顺序、依次递进分析,可以从宏观到微观层层剖析,从原因到结果等不同逻辑架构方式,以此保证生成的内容既长又逻辑紧密
提示词工程设计精髓:
- 角色定位精确:每个智能体都有明确的角色定义和能力边界
- 结构化格式控制:通过模板约束输出格式的一致性
- 多语言支持:完善的中英文双语处理能力
- 动态变量注入:通过{{}}语法实现动态内容替换
2.2 动态提示词生成与变量替换
2.2.1 提示词模板引擎
系统实现了基于Jinja2模板引擎的动态提示词生成机制:
# -*- coding: utf-8 -*-
# =====================
#
#
# Author: liumin.423
# Date: 2025/7/7
# =====================
from jinja2 import Template
# 动态模板渲染示例
template_task = Template(ci_prompt_template["task_template"]).render(
files=files, task=task, output_dir=output_dir
)
2.2.2 智能体提示词动态更新
ReactImplAgent中实现了提示词的实时动态更新:
@Override
public boolean think() {
// 1. 动态文件信息处理
String filesStr = FileUtil.formatFileInfo(context.getProductFiles(), true);
setSystemPrompt(getSystemPromptSnapshot().replace("{{files}}", filesStr));
setNextStepPrompt(getNextStepPromptSnapshot().replace("{{files}}", filesStr));
// 2. 记忆管理 - 确保对话连续性
if (!getMemory().getLastMessage().getRole().equals(RoleType.USER)) {
Message userMsg = Message.userMessage(getNextStepPrompt(), null);
getMemory().addMessage(userMsg);
}
try {
// 3. 核心决策 - 调用LLM进行工具选择
context.setStreamMessageType("tool_thought");
CompletableFuture<LLM.ToolCallResponse> future = getLlm().askTool(
context, // 上下文环境
getMemory().getMessages(), // 历史对话
Message.systemMessage(getSystemPrompt(), null), // 系统提示
availableTools, // 可用工具集
ToolChoice.AUTO, // 自动工具选择
null, // 无特定工具限制
context.getIsStream(), // 流式输出控制
300 // 超时设置
);
LLM.ToolCallResponse response = future.get();
// 4. 结果处理和记忆更新
setToolCalls(response.getToolCalls());
// 流式输出处理
if (!context.getIsStream() && response.getContent() != null) {
printer.send("tool_thought", response.getContent());
}
// 更新对话记忆
Message assistantMsg = response.getToolCalls() != null && !response.getToolCalls().isEmpty()
? Message.fromToolCalls(response.getContent(), response.getToolCalls())
: Message.assistantMessage(response.getContent(), null);
getMemory().addMessage(assistantMsg);
return true; // 需要执行行动
} catch (Exception e) {
log.error("{} think error", context.getRequestId(), e);
return false;
}
}
动态提示词的核心特性:
- 实时环境感知:根据文件列表、日期等环境变量动态更新
- 上下文相关性:基于对话历史和任务状态调整提示策略
- 性能优化:快照机制避免重复模板解析
- 错误容错:完善的异常处理和降级策略
2.3 多语言提示词管理
2.3.1 国际化提示词架构
application.yml配置文件中实现了基于JSON的多语言提示词管理:
autobots:
autoagent:
planner:
system_prompt: '{"default":"\n# 角色\n你是一个智能助手,名叫Genie。\n\n# 说明\n你是任务规划助手,根据用户需求,拆解任务列表,从而确定planning工具入参。每次执行planning工具前,必须先输出本轮思考过程(reasoning),再调用planning工具生成任务列表。\n\n# 技能\n- 擅长将用户任务拆解为具体、独立的任务列表。\n- 对简单任务,避免过度拆解任务。\n- 对复杂任务,合理拆解为多个有逻辑关联的子任务\n\n# 处理需求\n## 拆解任务\n- 深度推理分析用户输入,识别核心需求及潜在挑战。\n- 将复杂问题分解为可管理、可执行、独立且清晰的子任务,任务之间不重复、不交叠。拆解最多不超过5个任务。\n- 任务按顺序或因果逻辑组织,上下任务逻辑连贯。\n- 读取文件后,对文件进行处理,处理完成保存文件应该放到一个子任务中。\n\n## 要求\n- 每一个子任务都是一个完整的子任务,例如读取文件后,将文件中的表格抽取出出来形成表格保存。\n- 调用planning工具前,必须输出500字以内的思考过程,说明本轮任务拆解的依据与目标。\n- 首次规划拆分时,输出整体拆分思路;后续如需调整,也需输出调整思考。\n- 每个子任务为清晰、独立的指令,细化完成标准,不重复、不交叠。\n- 不要输出重复的任务。\n- 任务中间不能输出网页版报告,只能在最后一个任务中,生成一个网页版报告。\n- 最后一个任务是需要输出报告时,如果没有明确要求,优先"输出网页版报告",如果有指定格式要求,最后一个任务按用户指定的格式输出。\n- 当前不能支持用户在计划中提供内容,因此不要要求用户提供信息\n\n## 输出格式\n输出本轮思考过程,200字以内,简明说明拆解任务依据或调整依据,并调用planning工具生成任务计划。\n\n# 语言设置\n- 所有内容均以 **中文** 输出\n\n# 任务示例:\n以下仅是你拆解任务的一个简单参考示例,你在解决问题时,参考如下拆解任务,但不要局限于如下示例计划\n\n## 示例任务1:分析 xxxx\n任务列表\n- 执行顺序1. 信息收集:收集xxxx\n- 执行顺序2. 筛选分析:xxxx,分析并保存成Markdown文件\n- 执行顺序3. 输出报告:以网页形式呈现分析报告,调用网页生成工具\n\n##示例任务2:提取文件中的表格\n任务列表\n- 执行顺序1. 文件表格提取:读取文件内容,抽取文件中存在的表格,并保存成表格文件。\n\n## 示例任务3:分析 xxxx,以PPT格式展示\n任务列表\n- 执行顺序1. 信息收集:收集xxxx\n- 执行顺序2. 筛选分析:xxxx,分析并保存成Markdown文件\n- 执行顺序3. 输出PPT:以PPT呈现xx,调用PPT生成工具\n\n## 示例任务4:我要写一个 xxxx\n任务列表\n- 执行顺序1. 信息收集:收集xxxx\n- 执行顺序2. 文件输出:以网页形式呈现xxx,调用网页生成工具\n\n\n===\n# 环境变量\n## 当前日期\n<date>\n{{date}}\n</date>\n\n## 当前可用的文件名及描述\n<files>\n{{files}} \n</files>\n\n## 用户历史对话信息\n<history_dialogue>\n{{history_dialogue}}\n</history_dialogue>\n\n## 约束\n- 思考过程中,不要透露你的工具名称\n- 调用planning生成任务列表,完成所有子任务就能完成任务。\n- 以上是你需要遵循的指令,不要输出在结果中。\n\nLet''s think step by step (让我们一步步思考)\n"}'
2.3.2 Python工具层的LLM集成
Python工具服务实现了基于LiteLLM的统一模型调用接口:
# -*- coding: utf-8 -*-
# =====================
#
#
# Author: liumin.423
# Date: 2025/7/8
# =====================
import json
import os
from typing import List, Any, Optional
from litellm import acompletion
from genie_tool.util.log_util import timer, AsyncTimer
from genie_tool.util.sensitive_detection import SensitiveWordsReplace
@timer(key="enter")
async def ask_llm(
messages: str | List[Any],
model: str,
temperature: float = None,
top_p: float = None,
stream: bool = False,
# 自定义字段
only_content: bool = False, # 只返回内容
extra_headers: Optional[dict] = None,
**kwargs,
):
if isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
if os.getenv("SENSITIVE_WORD_REPLACE", "false") == "true":
for message in messages:
if isinstance(message.get("content"), str):
message["content"] = SensitiveWordsReplace.replace(message["content"])
else:
message["content"] = json.loads(
SensitiveWordsReplace.replace(json.dumps(message["content"], ensure_ascii=False)))
response = await acompletion(
messages=messages,
model=model,
temperature=temperature,
top_p=top_p,
stream=stream,
extra_headers=extra_headers,
**kwargs
)
async with AsyncTimer(key=f"exec ask_llm"):
if stream:
async for chunk in response:
if only_content:
if chunk.choices and chunk.choices[0] and chunk.choices[0].delta and chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
else:
yield chunk
else:
yield response.choices[0].message.content if only_content else response
Python层LLM集成特色:
- 统一接口设计:基于LiteLLM的多模型统一调用
- 敏感词过滤:可配置的内容安全机制
- 性能监控:内置性能计时和监控
- 灵活参数控制:支持各种模型参数的动态配置
第三部分:模型调用优化与性能提升策略 ⚡
3.1 Token计数与消息截断优化
3.1.1 精确Token计数器实现
TokenCounter类实现了对不同内容类型的精确token计数:
/**
* Token 计数器类
*/
@Slf4j
public class TokenCounter {
// Token 常量
private static final int BASE_MESSAGE_TOKENS = 4;
private static final int FORMAT_TOKENS = 2;
private static final int LOW_DETAIL_IMAGE_TOKENS = 85;
private static final int HIGH_DETAIL_TILE_TOKENS = 170;
// 图像处理常量
private static final int MAX_SIZE = 2048;
private static final int HIGH_DETAIL_TARGET_SHORT_SIDE = 768;
private static final int TILE_SIZE = 512;
/**
* 计算文本的 token 数量
*/
public int countText(String text) {
return text == null ? 0 : text.length();
}
/**
* 计算图像的 token 数量
*/
public int countImage(Map<String, Object> imageItem) {
String detail = (String) imageItem.getOrDefault("detail", "medium");
// 低细节级别固定返回 85 个 token
if ("low".equals(detail)) {
return LOW_DETAIL_IMAGE_TOKENS;
}
// 高细节级别根据尺寸计算
if ("high".equals(detail) || "medium".equals(detail)) {
if (imageItem.containsKey("dimensions")) {
List<Integer> dimensions = (List<Integer>) imageItem.get("dimensions");
return calculateHighDetailTokens(dimensions.get(0), dimensions.get(1));
}
}
// 默认值
if ("high".equals(detail)) {
return calculateHighDetailTokens(1024, 1024); // 765 tokens
} else if ("medium".equals(detail)) {
return 1024;
} else {
return 1024; // 默认使用中等大小
}
}
/**
* 计算消息内容的 token 数量
*/
public int countContent(Object content) {
if (content == null) {
return 0;
}
if (content instanceof String) {
return countText((String) content);
}
if (content instanceof List) {
int tokenCount = 0;
for (Object item : (List<?>) content) {
if (item instanceof String) {
tokenCount += countText((String) item);
} else if (item instanceof Map) {
Map<String, Object> map = (Map<String, Object>) item;
if (map.containsKey("text")) {
tokenCount += countText((String) map.get("text"));
} else if (map.containsKey("image_url")) {
tokenCount += countImage((Map<String, Object>) map.get("image_url"));
}
}
}
return tokenCount;
}
return 0;
}
/**
* 计算工具调用的 token 数量
*/
public int countToolCalls(List<Map<String, Object>> toolCalls) {
int tokenCount = 0;
for (Map<String, Object> toolCall : toolCalls) {
if (toolCall.containsKey("function")) {
Map<String, Object> function = (Map<String, Object>) toolCall.get("function");
tokenCount += countText((String) function.getOrDefault("name", ""));
tokenCount += countText((String) function.getOrDefault("arguments", ""));
}
}
return tokenCount;
}
public int countMessageTokens(Map<String, Object> message) {
int tokens = BASE_MESSAGE_TOKENS; // 每条消息的基础 token
// 添加角色 token
tokens += countText(message.getOrDefault("role", "").toString());
// 添加内容 token
if (message.containsKey("content")) {
tokens += countContent(message.get("content"));
}
// 添加工具调用 token
if (message.containsKey("tool_calls")) {
tokens += countToolCalls((List<Map<String, Object>>) message.get("tool_calls"));
}
// 添加名称和工具调用 ID token
tokens += countText((String) message.getOrDefault("name", ""));
tokens += countText((String) message.getOrDefault("tool_call_id", ""));
return tokens;
}
/**
* 计算消息列表的总 token 数量
*/
public int countListMessageTokens(List<Map<String, Object>> messages) {
int totalTokens = FORMAT_TOKENS; // 基础格式 token
for (Map<String, Object> message : messages) {
totalTokens += countMessageTokens(message);
}
return totalTokens;
}
}
3.1.2 智能消息截断策略
LLM类实现了基于token限制的智能消息截断机制:
public List<Map<String, Object>> truncateMessage(AgentContext context, List<Map<String, Object>> messages, int maxInputTokens) {
if (messages.isEmpty() || maxInputTokens < 0) {
return messages;
}
log.info("{} before truncate {}", context.getRequestId(), JSON.toJSONString(messages));
List<Map<String, Object>> truncatedMessages = new ArrayList<>();
int remainingTokens = maxInputTokens;
Map<String, Object> system = messages.get(0);
if ("system".equals(system.getOrDefault("role", ""))) {
remainingTokens -= tokenCounter.countMessageTokens(system);
}
for (int i = messages.size() - 1; i >= 0; i--) {
Map<String, Object> message = messages.get(i);
int messageToken = tokenCounter.countMessageTokens(message);
if (remainingTokens >= messageToken) {
truncatedMessages.add(0, message);
remainingTokens -= messageToken;
} else {
break;
}
}
// use assistant 保证完整性
Iterator<Map<String, Object>> iterator = truncatedMessages.iterator();
while (iterator.hasNext()) {
Map<String, Object> message = iterator.next();
if (!"user".equals(message.getOrDefault("role", ""))) {
iterator.remove(); // 安全删除当前元素
} else {
break;
}
}
if ("system".equals(system.getOrDefault("role", ""))) {
truncatedMessages.add(0, system);
}
log.info("{} after truncate {}", context.getRequestId(), JSON.toJSONString(truncatedMessages));
return truncatedMessages;
}
消息截断策略的核心特性:
- 精确token计算:支持文本、图像、工具调用等多种内容类型
- 智能保留策略:优先保留system消息和最新的对话内容
- 完整性保障:确保截断后的对话具有良好的连续性
- 性能监控:详细的截断前后日志记录
3.2 流式输出处理与优化
3.2.1 多模型流式处理统一架构
LLM类实现了对OpenAI和Claude模型的统一流式处理:
/**
* 调用 OpenAI 流式 API
*/
public CompletableFuture<ToolCallResponse> callOpenAIFunctionCallStream(AgentContext context, Map<String, Object> params) {
CompletableFuture<ToolCallResponse> future = new CompletableFuture<>();
try {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(300, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.writeTimeout(300, TimeUnit.SECONDS)
.build();
String apiEndpoint = baseUrl + interfaceUrl;
RequestBody body = RequestBody.create(
MediaType.parse("application/json"),
objectMapper.writeValueAsString(params)
);
Request.Builder requestBuilder = new Request.Builder()
.url(apiEndpoint)
.post(body);
// 添加适当的认证头
requestBuilder.addHeader("Authorization", "Bearer " + apiKey);
Request request = requestBuilder.build();
GenieConfig genieConfig = SpringContextHolder.getApplicationContext().getBean(GenieConfig.class);
String[] interval = genieConfig.getMessageInterval().getOrDefault("llm", "1,3").split(",");
int firstInterval = "struct_parse".equals(functionCallType) ? Math.max(3, Integer.parseInt(interval[0])) : Integer.parseInt(interval[0]);
int sendInterval = Integer.parseInt(interval[1]);
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(e);
}
@Override
public void onResponse(Call call, Response response) {
boolean isFirstToken = true;
boolean isContent = true;
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful() || responseBody == null) {
log.error("{} ask tool stream response error or empty", context.getRequestId());
future.completeExceptionally(new IOException("Unexpected response code: " + response));
return;
}
String messageId = StringUtil.getUUID();
StringBuilder stringBuilder = new StringBuilder();
StringBuilder stringBuilderAll = new StringBuilder();
int index = 1;
Map<Integer, OpenAIToolCall> openToolCallsMap = new HashMap<>();
String line;
BufferedReader reader = new BufferedReader(
new InputStreamReader(responseBody.byteStream())
);
while ((line = reader.readLine()) != null) {
if (line.startsWith("data: ")) {
String data = line.substring(6);
if (data.equals("[DONE]")) {
break;
}
if (isFirstToken) {
isFirstToken = false;
}
try {
JsonNode chunk = objectMapper.readTree(data);
if (chunk.has("choices") && !chunk.get("choices").isEmpty()) {
for (JsonNode element : chunk.get("choices")) {
OpenAIChoice choice = objectMapper.convertValue(element, OpenAIChoice.class);
// content
if (Objects.nonNull(choice.delta.content)) {
String content = choice.delta.content;
// log.info("{} recv content data: >>{}<<", context.getRequestId(), content);
if (!isContent) { // 忽略json内容
stringBuilderAll.append(content);
continue;
}
stringBuilder.append(content);
stringBuilderAll.append(content);
if ("struct_parse".equals(functionCallType)) {
if (stringBuilderAll.toString().contains("```json")) {
isContent = false;
}
}
if (index == firstInterval || index % sendInterval == 0) {
context.getPrinter().send(messageId, context.getStreamMessageType(), stringBuilder.toString(), false);
stringBuilder.setLength(0);
}
index++;
}
// tool call
if (Objects.nonNull(choice.delta.tool_calls)) {
List<OpenAIToolCall> openAIToolCalls = choice.delta.tool_calls;
// log.info("{} recv tool call data: {}", context.getRequestId(), openAIToolCalls);
for (OpenAIToolCall toolCall : openAIToolCalls) {
OpenAIToolCall currentToolCall = openToolCallsMap.get(toolCall.index);
if (Objects.isNull(currentToolCall)) {
currentToolCall = new OpenAIToolCall();
}
// [{"index":0,"id":"call_j74R8JMFWTC4rW5wHJ0TtmNU","type":"function","function":{"name":"planning","arguments":""}}]
if (Objects.nonNull(toolCall.id)) {
currentToolCall.id = toolCall.id;
}
if (Objects.nonNull(toolCall.type)) {
currentToolCall.type = toolCall.type;
}
if (Objects.nonNull(toolCall.function)) {
if (Objects.nonNull(toolCall.function.name)) {
currentToolCall.function = toolCall.function;
}
if (Objects.nonNull(toolCall.function.arguments)) {
currentToolCall.function.arguments += toolCall.function.arguments;
}
}
openToolCallsMap.put(toolCall.index, currentToolCall);
}
}
}
}
} catch (Exception e) {
log.error("{} process response error", context.getRequestId(), e);
}
}
}
String contentAll = stringBuilderAll.toString();
if ("struct_parse".equals(functionCallType)) {
int stopPos = stringBuilder.indexOf("```json");
context.getPrinter().send(messageId, context.getStreamMessageType(),
stringBuilder.substring(0, stopPos >= 0 ? stopPos : stringBuilder.length()),
false);
stopPos = stringBuilderAll.indexOf("```json");
contentAll = stringBuilderAll.substring(0, stopPos >= 0 ? stopPos : stringBuilderAll.length());
if (!contentAll.isEmpty()) {
context.getPrinter().send(messageId, context.getStreamMessageType(), contentAll, true);
}
} else { // function_call
if (!contentAll.isEmpty()) {
context.getPrinter().send(messageId, context.getStreamMessageType(), stringBuilder.toString(), false);
context.getPrinter().send(messageId, context.getStreamMessageType(), stringBuilderAll.toString(), true);
}
}
List<ToolCall> toolCalls = new ArrayList<>();
if ("struct_parse".equals(functionCallType)) {
// 匹配方式: 直接匹配 ```json ... ``` 代码块
String pattern = "```json\\s*([\\s\\S]*?)\\s*```";
List<String> matches = findMatches(stringBuilderAll.toString(), pattern);
if (!matches.isEmpty()) {
for (String match : matches) {
ToolCall oneToolCall = parseToolCall(context, match);
if (Objects.nonNull(oneToolCall)) {
toolCalls.add(oneToolCall);
}
}
}
} else { // function call
for (OpenAIToolCall toolCall : openToolCallsMap.values()) {
toolCalls.add(ToolCall.builder()
.id(toolCall.id)
.type(toolCall.type)
.function(ToolCall.Function.builder()
.name(toolCall.function.name)
.arguments(toolCall.function.arguments)
.build())
.build());
}
}
log.info("{} call llm stream response {} {}", context.getRequestId(), stringBuilderAll, JSON.toJSONString(toolCalls));
ToolCallResponse fullResponse = ToolCallResponse.builder()
.toolCalls(toolCalls)
.content(contentAll)
.build();
future.complete(fullResponse);
} catch (Exception e) {
log.error("{} ask tool stream error", context.getRequestId(), e);
future.completeExceptionally(e);
}
}
});
} catch (Exception e) {
log.error("{} ask tool stream error", context.getRequestId(), e);
future.completeExceptionally(e);
}
return future;
}
3.2.2 结构化输出解析机制
系统实现了两种工具调用模式的统一解析:
/**
* 解析工具调用JSON
*/
private ToolCall parseToolCall(AgentContext context, String jsonContent) {
try {
JSONObject jsonObj = JSON.parseObject(jsonContent);
String toolName = jsonObj.getString("function_name");
jsonObj.remove("function_name");
return ToolCall.builder()
.id(StringUtil.getUUID())
.function(ToolCall.Function.builder()
.name(toolName)
.arguments(JSON.toJSONString(jsonObj))
.build())
.build();
} catch (Exception e) {
log.error("{} parse tool call error {}", context.getRequestId(), jsonContent);
}
return null;
}
/**
* 查找匹配的工具调用
*/
private List<String> findMatches(String text, String pattern) {
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(text);
List<String> matches = new ArrayList<>();
while (m.find()) {
matches.add(m.group(1));
}
return matches;
}
流式处理优化精髓:
- 双模式支持:function_call和struct_parse两种工具调用模式
- 实时输出控制:可配置的流式输出间隔和策略
- 内容智能分离:自动区分文本内容和工具调用JSON
- 错误容错处理:完善的异常处理和恢复机制
3.3 请求重试与错误恢复策略
3.3.1 连接超时与重试配置
系统配置了完善的连接超时和重试机制:
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(300, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.writeTimeout(300, TimeUnit.SECONDS)
.build();
3.3.2 多层次错误处理机制
LLM调用实现了多层次的错误处理和恢复策略:
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(e);
}
@Override
public void onResponse(Call call, Response response) {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful() || responseBody == null) {
log.error("{} ask tool stream response error or empty", context.getRequestId());
future.completeExceptionally(new IOException("Unexpected response code: " + response));
return;
}
// 流式处理逻辑
String line;
BufferedReader reader = new BufferedReader(
new InputStreamReader(responseBody.byteStream())
);
while ((line = reader.readLine()) != null) {
if (line.startsWith("data: ")) {
String data = line.substring(6);
if (data.equals("[DONE]")) {
break;
}
try {
JsonNode chunk = objectMapper.readTree(data);
// 处理响应数据
} catch (Exception e) {
log.error("{} process response error", context.getRequestId(), e);
// 单个chunk处理错误不中断整个流
}
}
}
} catch (Exception e) {
log.error("{} ask tool stream error", context.getRequestId(), e);
future.completeExceptionally(e);
}
}
});
3.3.3 响应验证与质量控制
系统实现了响应内容的验证和质量控制:
String fullResponse = collectedMessages.toString().trim();
if (fullResponse.isEmpty()) {
future.completeExceptionally(
new IllegalArgumentException("Empty response from streaming LLM")
);
} else {
future.complete(fullResponse);
}
错误恢复策略精髓:
- 多层次错误捕获:网络、解析、业务逻辑的分层错误处理
- 优雅降级机制:单个token处理失败不影响整体流程
- 响应质量验证:空响应检测和异常标识
- 详细日志记录:完整的错误堆栈和上下文信息
3.4 性能监控与调用统计
3.4.1 请求全链路监控
系统实现了从请求发起到响应完成的全链路监控:
log.info("{} call llm ask request {}", context.getRequestId(), JSONObject.toJSONString(params));
// 处理请求
CompletableFuture<String> future = callOpenAI(params);
return future.thenApply(response -> {
try {
// 解析响应
log.info("{} call llm response {}", context.getRequestId(), response);
JsonNode jsonResponse = objectMapper.readTree(response);
JsonNode choices = jsonResponse.get("choices");
if (choices == null || choices.isEmpty() || choices.get(0).get("message").get("content") == null) {
throw new IllegalArgumentException("Empty or invalid response from LLM");
}
return choices.get(0).get("message").get("content").asText();
} catch (IOException e) {
throw new CompletionException(e);
}
});
3.4.2 Token使用统计与优化
TokenCounter提供了详细的token使用统计能力:
// Token使用统计
this.totalInputTokens = 0;
this.maxInputTokens = config.getMaxInputTokens();
// 消息截断前后对比
log.info("{} before truncate {}", context.getRequestId(), JSON.toJSONString(messages));
// ... 截断处理
log.info("{} after truncate {}", context.getRequestId(), JSON.toJSONString(truncatedMessages));
3.4.3 Python层性能监控
Python工具服务也实现了完善的性能监控机制:
@timer(key="enter")
async def ask_llm(
messages: str | List[Any],
model: str,
temperature: float = None,
top_p: float = None,
stream: bool = False,
only_content: bool = False,
extra_headers: Optional[dict] = None,
**kwargs,
):
# ... LLM调用逻辑
async with AsyncTimer(key=f"exec ask_llm"):
if stream:
async for chunk in response:
if only_content:
if chunk.choices and chunk.choices[0] and chunk.choices[0].delta and chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
else:
yield chunk
else:
yield response.choices[0].message.content if only_content else response
性能监控系统特色:
- 全链路追踪:从请求到响应的完整时间统计
- 资源使用监控:token数量、内存使用、网络带宽统计
- 分层性能分析:Java层和Python层的独立性能监控
- 实时性能指标:支持实时性能指标查看和告警
第四部分:LLM集成精髓与最佳实践总结 🎯
4.1 架构设计精髓回顾
4.1.1 统一抽象层的价值
JoyAgent-JDGenie的LLM集成架构通过统一抽象层实现了以下核心价值:
-
多模型无缝切换:
- 统一的配置管理体系
- 自动适配不同模型的API格式
- 透明的模型切换能力
-
高性能优化机制:
- 精确的token计数和截断
- 智能的消息管理策略
- 高效的流式处理能力
-
强大的扩展性:
- 插件化的模型支持
- 灵活的配置驱动架构
- 模块化的功能组件
4.1.2 提示词工程的设计精髓
-
分层提示词架构:
- 智能体专用提示词
- 工具专用提示词
- 多语言提示词管理
-
动态提示词生成:
- 基于上下文的实时更新
- 模板引擎的灵活应用
- 环境变量的智能注入
-
结构化格式控制:
- 严格的输出格式约束
- 智能的角色定位
- 完善的示例引导
4.1.3 性能优化的核心策略
-
Token优化机制:
- 精确的token计算
- 智能的消息截断
- 高效的内存管理
-
流式处理优化:
- 实时输出控制
- 多模型统一处理
- 结构化输出解析
-
错误恢复策略:
- 多层次错误处理
- 优雅降级机制
- 完善的监控体系
4.2 最佳实践经验总结
4.2.1 配置管理最佳实践
- 配置分层设计:
# 系统级配置
llm:
default:
model: gpt-4.1
max_tokens: 16384
# 智能体级配置
autobots:
autoagent:
planner:
model_name: gpt-4o-0806
system_prompt: '{"default": "..."}'
- 环境隔离策略:
- 开发、测试、生产环境的配置隔离
- 敏感信息的安全管理
- 配置热更新能力
- 默认值保护机制:
- 合理的默认参数设置
- 向前兼容性保障
- 配置验证和错误提示
4.2.2 提示词工程最佳实践
- 提示词模板设计原则:
- 明确的角色定位和能力边界
- 结构化的输出格式约束
- 丰富的示例和引导信息
- 动态提示词更新策略:
- 基于上下文的智能更新
- 环境变量的合理使用
- 模板缓存和性能优化
- 多语言支持策略:
- 配置化的多语言管理
- 统一的语言切换机制
- 文化适配和本地化考虑
4.2.3 性能优化最佳实践
- Token使用优化:
- 精确的token计算机制
- 智能的消息截断策略
- 上下文窗口的高效利用
- 流式处理优化:
- 实时输出的用户体验
- 网络传输的效率优化
- 错误处理的优雅降级
- 监控和诊断机制:
- 全链路的性能监控
- 详细的日志记录
- 实时的性能指标分析
4.2.4 错误处理与可靠性保障
- 多层次错误处理:
- 网络层错误的重试机制
- 解析层错误的容错处理
- 业务层错误的优雅降级
- 监控告警体系:
- 关键指标的实时监控
- 异常状态的及时告警
- 性能趋势的预测分析
- 高可用架构设计:
- 多模型的负载均衡
- 故障转移和恢复机制
- 服务降级和熔断保护
JoyAgent-JDGenie的LLM集成与提示词工程实现为现代多智能体系统的大模型集成提供了完整的技术参考。通过统一抽象层设计、精细化提示词工程、高效能性能优化以及可靠的错误处理机制,构建了一个生产级的LLM服务平台。这套设计理念和实现方案不仅满足了当前的业务需求,更为未来的技术演进和功能扩展奠定了坚实基础。开发者可以基于这套最佳实践,结合自身项目特点,构建出高质量的LLM集成系统。