Merge branch 'main' into WebMenu

This commit is contained in:
hrz
2025-12-05 17:05:51 +08:00
committed by GitHub
21 changed files with 880 additions and 5 deletions
@@ -44,6 +44,7 @@ import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentChatAudioService;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentContextProviderService;
import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTemplateService;
@@ -64,6 +65,7 @@ public class AgentController {
private final AgentChatHistoryService agentChatHistoryService;
private final AgentChatAudioService agentChatAudioService;
private final AgentPluginMappingService agentPluginMappingService;
private final AgentContextProviderService agentContextProviderService;
private final RedisUtils redisUtils;
@GetMapping("/list")
@@ -135,6 +137,8 @@ public class AgentController {
agentChatHistoryService.deleteByAgentId(id, true, true);
// 删除关联的插件
agentPluginMappingService.deleteByAgentId(id);
// 删除关联的上下文源配置
agentContextProviderService.deleteByAgentId(id);
// 再删除智能体
agentService.deleteById(id);
return new Result<>();
@@ -0,0 +1,9 @@
package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
@Mapper
public interface AgentContextProviderDao extends BaseDao<AgentContextProviderEntity> {
}
@@ -69,6 +69,9 @@ public class AgentUpdateDTO implements Serializable {
@Schema(description = "排序", example = "1", nullable = true)
private Integer sort;
@Schema(description = "上下文源配置", nullable = true)
private List<ContextProviderDTO> contextProviders;
@Data
@Schema(description = "插件函数信息")
public static class FunctionInfo implements Serializable {
@@ -0,0 +1,19 @@
package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import java.util.Map;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "上下文源配置DTO")
public class ContextProviderDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "URL地址")
private String url;
@Schema(description = "请求头")
private Map<String, Object> headers;
}
@@ -0,0 +1,43 @@
package xiaozhi.modules.agent.entity;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import xiaozhi.modules.agent.dto.ContextProviderDTO;
@Data
@TableName(value = "ai_agent_context_provider", autoResultMap = true)
@Schema(description = "智能体上下文源配置")
public class AgentContextProviderEntity {
@TableId(type = IdType.ASSIGN_UUID)
@Schema(description = "主键")
private String id;
@Schema(description = "智能体ID")
private String agentId;
@Schema(description = "上下文源配置")
@TableField(typeHandler = JacksonTypeHandler.class)
private List<ContextProviderDTO> contextProviders;
@Schema(description = "创建者")
private Long creator;
@Schema(description = "创建时间")
private Date createdAt;
@Schema(description = "更新者")
private Long updater;
@Schema(description = "更新时间")
private Date updatedAt;
}
@@ -0,0 +1,25 @@
package xiaozhi.modules.agent.service;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
public interface AgentContextProviderService extends BaseService<AgentContextProviderEntity> {
/**
* 根据智能体ID获取上下文源配置
* @param agentId 智能体ID
* @return 上下文源配置实体
*/
AgentContextProviderEntity getByAgentId(String agentId);
/**
* 保存或更新上下文源配置
* @param entity 实体
*/
void saveOrUpdateByAgentId(AgentContextProviderEntity entity);
/**
* 根据智能体ID删除上下文源配置
* @param agentId 智能体ID
*/
void deleteByAgentId(String agentId);
}
@@ -0,0 +1,35 @@
package xiaozhi.modules.agent.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.modules.agent.dao.AgentContextProviderDao;
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
import xiaozhi.modules.agent.service.AgentContextProviderService;
@Service
public class AgentContextProviderServiceImpl extends BaseServiceImpl<AgentContextProviderDao, AgentContextProviderEntity> implements AgentContextProviderService {
@Override
public AgentContextProviderEntity getByAgentId(String agentId) {
return baseDao.selectOne(new QueryWrapper<AgentContextProviderEntity>().eq("agent_id", agentId));
}
@Override
public void saveOrUpdateByAgentId(AgentContextProviderEntity entity) {
AgentContextProviderEntity exist = getByAgentId(entity.getAgentId());
if (exist != null) {
entity.setId(exist.getId());
updateById(entity);
} else {
insert(entity);
}
}
@Override
public void deleteByAgentId(String agentId) {
baseDao.delete(new QueryWrapper<AgentContextProviderEntity>().eq("agent_id", agentId));
}
}
@@ -32,10 +32,12 @@ import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.dto.AgentCreateDTO;
import xiaozhi.modules.agent.dto.AgentDTO;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentContextProviderService;
import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTemplateService;
@@ -62,6 +64,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
private final AgentChatHistoryService agentChatHistoryService;
private final AgentTemplateService agentTemplateService;
private final ModelProviderService modelProviderService;
private final AgentContextProviderService agentContextProviderService;
@Override
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
@@ -85,6 +88,13 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
}
}
// 查询上下文源配置
AgentContextProviderEntity contextProviderEntity = agentContextProviderService.getByAgentId(id);
if (contextProviderEntity != null) {
agent.setContextProviders(contextProviderEntity.getContextProviders());
}
// 无需额外查询插件列表,已通过SQL查询出来
return agent;
}
@@ -331,6 +341,14 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
}
// 更新上下文源配置
if (dto.getContextProviders() != null) {
AgentContextProviderEntity contextEntity = new AgentContextProviderEntity();
contextEntity.setAgentId(agentId);
contextEntity.setContextProviders(dto.getContextProviders());
agentContextProviderService.saveOrUpdateByAgentId(contextEntity);
}
boolean b = validateLLMIntentParams(dto.getLlmModelId(), dto.getIntentModelId());
if (!b) {
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import xiaozhi.modules.agent.dto.ContextProviderDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
@@ -21,4 +22,7 @@ public class AgentInfoVO extends AgentEntity
@Schema(description = "插件列表Id")
@TableField(typeHandler = JacksonTypeHandler.class)
private List<AgentPluginMapping> functions;
@Schema(description = "上下文源配置")
private List<ContextProviderDTO> contextProviders;
}
@@ -20,10 +20,12 @@ import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.agent.dao.AgentVoicePrintDao;
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.entity.AgentVoicePrintEntity;
import xiaozhi.modules.agent.service.AgentContextProviderService;
import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService;
@@ -53,6 +55,7 @@ public class ConfigServiceImpl implements ConfigService {
private final TimbreService timbreService;
private final AgentPluginMappingService agentPluginMappingService;
private final AgentMcpAccessPointService agentMcpAccessPointService;
private final AgentContextProviderService agentContextProviderService;
private final VoiceCloneService cloneVoiceService;
private final AgentVoicePrintDao agentVoicePrintDao;
@@ -178,6 +181,13 @@ public class ConfigServiceImpl implements ConfigService {
mcpEndpoint = mcpEndpoint.replace("/mcp/", "/call/");
result.put("mcp_endpoint", mcpEndpoint);
}
// 获取上下文源配置
AgentContextProviderEntity contextProviderEntity = agentContextProviderService.getByAgentId(agent.getId());
if (contextProviderEntity != null && contextProviderEntity.getContextProviders() != null && !contextProviderEntity.getContextProviders().isEmpty()) {
result.put("context_providers", contextProviderEntity.getContextProviders());
}
// 获取声纹信息
buildVoiceprintConfig(agent.getId(), result);
@@ -0,0 +1,14 @@
-- liquibase formatted sql
-- changeset xiaozhi:202512041515
CREATE TABLE ai_agent_context_provider (
id VARCHAR(32) NOT NULL COMMENT '主键',
agent_id VARCHAR(32) NOT NULL COMMENT '智能体ID',
context_providers JSON COMMENT '上下文源配置',
creator BIGINT COMMENT '创建者',
created_at DATETIME COMMENT '创建时间',
updater BIGINT COMMENT '更新者',
updated_at DATETIME COMMENT '更新时间',
PRIMARY KEY (id),
INDEX idx_agent_id (agent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体上下文源配置表';
@@ -431,3 +431,10 @@ databaseChangeLog:
encoding: utf8
path: classpath:db/changelog/202512031517.sql
- changeSet:
id: 202512041515
author: cgd
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202512041515.sql
@@ -0,0 +1,329 @@
<template>
<el-dialog
:visible.sync="dialogVisible"
width="900px"
title="编辑源"
:close-on-click-modal="false"
custom-class="context-provider-dialog"
append-to-body
>
<div class="dialog-content">
<el-empty v-if="localProviders.length === 0" description="暂无上下文API">
<el-button type="primary" icon="el-icon-plus" @click="addProvider(0)">添加</el-button>
</el-empty>
<div
v-for="(provider, pIndex) in localProviders"
:key="pIndex"
class="provider-item"
>
<el-card class="provider-card" shadow="hover" :body-style="{ padding: '15px 20px' }">
<!-- URL Row -->
<div class="input-row">
<span class="label-text">接口地址</span>
<el-input
v-model="provider.url"
placeholder="http://api.example.com/data"
size="small"
class="flex-1"
></el-input>
</div>
<!-- Headers Section -->
<div class="headers-section">
<div class="label-text" style="margin-top: 6px;">请求头</div>
<div class="headers-list">
<div
v-for="(header, hIndex) in provider.headers"
:key="hIndex"
class="header-row"
>
<el-input
v-model="header.key"
placeholder="Key"
size="small"
style="width: 180px;"
></el-input>
<span class="separator">:</span>
<el-input
v-model="header.value"
placeholder="Value"
size="small"
class="flex-1"
></el-input>
<div class="row-controls">
<el-button
type="primary"
icon="el-icon-plus"
circle
size="mini"
plain
@click="addHeader(pIndex, hIndex + 1)"
></el-button>
<el-button
type="danger"
icon="el-icon-minus"
circle
size="mini"
plain
@click="removeHeader(pIndex, hIndex)"
></el-button>
</div>
</div>
<!-- Empty Headers State -->
<div v-if="provider.headers.length === 0" class="header-row empty-header">
<span class="no-header-text">暂无 Headers</span>
<el-button
type="text"
icon="el-icon-plus"
size="mini"
@click="addHeader(pIndex, 0)"
>添加 Header</el-button>
</div>
</div>
</div>
</el-card>
<!-- Provider Block Controls (Right Side) -->
<div class="block-controls">
<el-button
type="primary"
icon="el-icon-plus"
circle
size="medium"
@click="addProvider(pIndex + 1)"
></el-button>
<el-button
type="danger"
icon="el-icon-minus"
circle
size="medium"
@click="removeProvider(pIndex)"
></el-button>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
name: 'ContextProviderDialog',
props: {
visible: {
type: Boolean,
default: false
},
providers: {
type: Array,
default: () => []
}
},
data() {
return {
localProviders: []
};
},
computed: {
dialogVisible: {
get() {
return this.visible;
},
set(val) {
this.$emit('update:visible', val);
}
}
},
watch: {
visible(val) {
if (val) {
this.initLocalData();
}
}
},
methods: {
initLocalData() {
// 深拷贝并将 headers 对象转换为数组
this.localProviders = this.providers.map(p => {
const headers = p.headers || {};
return {
url: p.url || '',
headers: Object.entries(headers).map(([key, value]) => ({ key, value }))
};
});
// 如果为空,添加一个默认块
if (this.localProviders.length === 0) {
this.localProviders.push({ url: '', headers: [{ key: '', value: '' }] });
}
},
addProvider(index) {
this.localProviders.splice(index, 0, {
url: '',
headers: [{ key: '', value: '' }]
});
},
removeProvider(index) {
this.localProviders.splice(index, 1);
},
addHeader(pIndex, hIndex) {
this.localProviders[pIndex].headers.splice(hIndex, 0, { key: '', value: '' });
},
removeHeader(pIndex, hIndex) {
this.localProviders[pIndex].headers.splice(hIndex, 1);
},
handleConfirm() {
const result = this.localProviders
.filter(p => p.url.trim() !== '')
.map(p => {
const headersObj = {};
p.headers.forEach(h => {
if (h.key.trim()) {
headersObj[h.key.trim()] = h.value;
}
});
return {
url: p.url.trim(),
headers: headersObj
};
});
this.$emit('confirm', result);
this.dialogVisible = false;
}
}
};
</script>
<style scoped>
.dialog-content {
max-height: 60vh;
overflow-y: auto;
padding: 20px 25px;
}
.dialog-content::-webkit-scrollbar {
width: 6px;
}
.dialog-content::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
}
.dialog-content::-webkit-scrollbar-track {
background: #f5f7fa;
}
.provider-item {
display: flex;
gap: 15px;
margin-bottom: 20px;
align-items: center;
}
.provider-card {
flex: 1;
border-radius: 12px;
border: 1px solid #e4e7ed;
border-left: 4px solid #409EFF; /* 左侧强调色 */
background-color: #fff;
transition: all 0.3s ease;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.provider-card:hover {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.block-controls {
display: flex;
flex-direction: row;
gap: 8px;
}
.input-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 18px;
}
.label-text {
width: 60px;
font-weight: 600;
color: #606266;
text-align: right;
font-size: 13px;
white-space: nowrap;
line-height: 32px; /* 垂直居中对齐 */
}
.flex-1 {
flex: 1;
}
.headers-section {
display: flex;
gap: 12px;
align-items: flex-start;
}
.headers-list {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
background: #fcfcfc;
padding: 15px;
border-radius: 8px;
border: 1px dashed #dcdfe6;
transition: all 0.3s;
}
.headers-list:hover {
border-color: #c0c4cc;
background: #fff;
}
.header-row {
display: flex;
align-items: center;
gap: 10px;
}
.separator {
color: #909399;
font-weight: bold;
margin: 0 2px;
}
.row-controls {
display: flex;
gap: 6px;
margin-left: 8px;
flex-shrink: 0;
opacity: 0.6;
transition: opacity 0.2s;
}
.header-row:hover .row-controls {
opacity: 1;
}
.empty-header {
justify-content: center;
padding: 10px;
color: #909399;
font-size: 13px;
}
.no-header-text {
margin-right: 8px;
}
</style>
+49 -3
View File
@@ -55,10 +55,24 @@
</div>
</div>
</el-form-item>
<el-form-item label="上下文源:" class="context-provider-item">
<div style="display: flex; align-items: center; justify-content: space-between;">
<span style="color: #606266; font-size: 13px;">
已成功添加 {{ currentContextProviders.length }} 个源<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/master/docs/context-provider-integration.md" target="_blank" class="doc-link">如何部署上下文源</a>
</span>
<el-button
class="edit-function-btn"
size="small"
@click="openContextProviderDialog"
>
编辑源
</el-button>
</div>
</el-form-item>
<el-form-item :label="$t('roleConfig.roleIntroduction') + ''">
<el-input
type="textarea"
rows="9"
rows="8"
resize="none"
:placeholder="$t('roleConfig.pleaseEnterContent')"
v-model="form.systemPrompt"
@@ -71,7 +85,7 @@
<el-form-item :label="$t('roleConfig.memoryHis') + ''">
<el-input
type="textarea"
rows="6"
rows="4"
resize="none"
v-model="form.summaryMemory"
maxlength="2000"
@@ -275,6 +289,11 @@
@update-functions="handleUpdateFunctions"
@dialog-closed="handleDialogClosed"
/>
<context-provider-dialog
:visible.sync="showContextProviderDialog"
:providers="currentContextProviders"
@confirm="handleUpdateContext"
/>
</div>
</template>
@@ -283,15 +302,17 @@ import Api from "@/apis/api";
import { getServiceUrl } from "@/apis/api";
import RequestService from "@/apis/httpRequest";
import FunctionDialog from "@/components/FunctionDialog.vue";
import ContextProviderDialog from "@/components/ContextProviderDialog.vue";
import HeaderBar from "@/components/HeaderBar.vue";
import i18n from "@/i18n";
import featureManager from "@/utils/featureManager";
export default {
name: "RoleConfigPage",
components: { HeaderBar, FunctionDialog },
components: { HeaderBar, FunctionDialog, ContextProviderDialog },
data() {
return {
showContextProviderDialog: false,
form: {
agentCode: "",
agentName: "",
@@ -329,6 +350,7 @@ export default {
voiceDetails: {}, // 保存完整的音色信息
showFunctionDialog: false,
currentFunctions: [],
currentContextProviders: [],
allFunctions: [],
originalFunctions: [],
playingVoice: false,
@@ -370,6 +392,7 @@ export default {
paramInfo: item.params,
};
}),
contextProviders: this.currentContextProviders,
};
Api.agent.updateAgentConfig(this.$route.query.agentId, configData, ({ data }) => {
if (data.code === 0) {
@@ -486,6 +509,9 @@ export default {
};
// 后端只给了最小映射:[{ id, agentId, pluginId }, ...]
const savedMappings = data.data.functions || [];
// 加载上下文配置
this.currentContextProviders = data.data.contextProviders || [];
// 先保证 allFunctions 已经加载(如果没有,则先 fetchAllFunctions
const ensureFuncs = this.allFunctions.length
@@ -660,6 +686,12 @@ export default {
this.showFunctionDialog = true;
}
},
openContextProviderDialog() {
this.showContextProviderDialog = true;
},
handleUpdateContext(providers) {
this.currentContextProviders = providers;
},
handleUpdateFunctions(selected) {
this.currentFunctions = selected;
},
@@ -1374,4 +1406,18 @@ export default {
height: 32px;
margin-left: 8px;
}
.context-provider-item ::v-deep .el-form-item__label {
line-height: 42px !important;
}
.doc-link {
color: #1677ff;
text-decoration: none;
margin-left: 4px;
&:hover {
text-decoration: underline;
}
}
</style>
@@ -74,6 +74,7 @@
- **今天农历:** {{lunar_date}}
- **用户所在城市:** {{local_address}}
- **当地未来7天天气:** {{weather_info}}
{{ dynamic_context }}
</context>
<memory>
+8
View File
@@ -113,6 +113,14 @@ wakeup_words:
# MCP接入点地址,地址格式为:ws://你的mcp接入点ip或者域名:端口号/mcp/?token=你的token
# 详细教程 https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/mcp-endpoint-integration.md
mcp_endpoint: 你的接入点 websocket地址
# 数据上下文填充配置
# 用于在系统提示词中注入动态数据,如健康数据、股票信息等
context_providers:
- url: ""
headers:
Authorization: ""
# 插件的基础配置
plugins:
# 获取天气插件的配置,这里填写你的api_key
+3 -1
View File
@@ -162,7 +162,7 @@ class ConnectionHandler:
self.conn_from_mqtt_gateway = False
# 初始化提示词管理器
self.prompt_manager = PromptManager(config, self.logger)
self.prompt_manager = PromptManager(self.config, self.logger)
async def handle_connection(self, ws):
try:
@@ -630,6 +630,8 @@ class ConnectionHandler:
self.chat_history_conf = int(private_config["chat_history_conf"])
if private_config.get("mcp_endpoint", None) is not None:
self.config["mcp_endpoint"] = private_config["mcp_endpoint"]
if private_config.get("context_providers", None) is not None:
self.config["context_providers"] = private_config["context_providers"]
# 使用 run_in_executor 在线程池中执行 initialize_modules,避免阻塞主循环
try:
@@ -0,0 +1,64 @@
import httpx
from typing import Dict, Any, List
from config.logger import setup_logging
TAG = __name__
class ContextDataProvider:
"""数据上下文填充,负责从配置的API获取数据"""
def __init__(self, config: Dict[str, Any], logger=None):
self.config = config
self.logger = logger or setup_logging()
self.context_data = ""
def fetch_all(self, device_id: str) -> str:
"""获取所有配置的上下文数据"""
context_providers = self.config.get("context_providers", [])
if not context_providers:
return ""
formatted_lines = []
for provider in context_providers:
url = provider.get("url")
headers = provider.get("headers", {})
if not url:
continue
try:
headers = headers.copy() if isinstance(headers, dict) else {}
# 将 device_id 添加到请求头
headers["device_id"] = device_id
# 发送请求
response = httpx.get(url, headers=headers, timeout=3)
if response.status_code == 200:
result = response.json()
if isinstance(result, dict):
if result.get("code") == 0:
data = result.get("data")
# 格式化数据
if isinstance(data, dict):
for k, v in data.items():
formatted_lines.append(f"- **{k}** {v}")
elif isinstance(data, list):
for item in data:
formatted_lines.append(f"- {item}")
else:
formatted_lines.append(f"- {data}")
else:
self.logger.bind(tag=TAG).warning(f"API {url} 返回错误码: {result.get('msg')}")
else:
self.logger.bind(tag=TAG).warning(f"API {url} 返回的不是JSON字典")
else:
self.logger.bind(tag=TAG).warning(f"API {url} 请求失败: {response.status_code}")
except Exception as e:
self.logger.bind(tag=TAG).error(f"获取上下文数据 {url} 失败: {e}")
# 将所有格式化后的行拼接成一个字符串
self.context_data = "\n".join(formatted_lines)
if self.context_data:
self.logger.bind(tag=TAG).debug(f"已注入动态上下文数据:\n{self.context_data}")
return self.context_data
@@ -4,7 +4,6 @@
"""
import os
import cnlunar
from typing import Dict, Any
from config.logger import setup_logging
from jinja2 import Template
@@ -60,6 +59,11 @@ class PromptManager:
self.cache_manager = cache_manager
self.CacheType = CacheType
# 初始化数据上下文填充
from core.utils.context_provider import ContextDataProvider
self.context_provider = ContextDataProvider(config, self.logger)
self.context_data = {}
self._load_base_template()
@@ -184,6 +188,14 @@ class PromptManager:
local_address = self._get_location_info(client_ip)
# 获取天气信息(使用全局缓存)
self._get_weather_info(conn, local_address)
# 获取配置的上下文数据
if hasattr(conn, "device_id") and conn.device_id:
if self.base_prompt_template and "dynamic_context" in self.base_prompt_template:
self.context_data = self.context_provider.fetch_all(conn.device_id)
else:
self.context_data = ""
self.logger.bind(tag=TAG).debug(f"上下文信息更新完成")
except Exception as e:
@@ -230,6 +242,7 @@ class PromptManager:
emojiList=EMOJI_List,
device_id=device_id,
client_ip=client_ip,
dynamic_context=self.context_data,
*args,
**kwargs,
)