Browse Source

上传工具调用界面优化以及工具调用提示语句

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
59bd7d05c4
  1. 1
      lib/core/translations/language/zh_cn.dart
  2. 7
      lib/data/models/message_model.dart
  3. 3
      lib/data/services/ble_manager.dart
  4. 51
      lib/modules/agent/controllers/agent_controller.dart
  5. 203
      lib/modules/agent/views/message_bubble.dart
  6. 21
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  7. 2
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  8. 3
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt
  9. 87
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  10. 12
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift
  11. 6
      local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift

1
lib/core/translations/language/zh_cn.dart

@ -511,6 +511,7 @@ const Map<String, String> zhCN = {
"loadMoreMessages": "加载更多消息...",
"loadFailedClickRetry": "加载失败,点击重试",
"swipeUpForMore": "向上滑动查看更多",
"callingTool": "正在为您调用工具查询相关信息",
// 预设提示词
"presetMusic": "来点音乐", // 来点音乐

7
lib/data/models/message_model.dart

@ -11,7 +11,7 @@ class Message {
final bool hasImage; // 是否包含图片
final String? imagePath; // 图片路径
final Map? meta; //卡片数据
final List<String>? functionCalls; // 函数调用
final Map<String, Map<String, dynamic>>? functionCalls; // 函数调用
bool isToolCallsExpanded; // 工具调用是否展开
Message({
@ -39,7 +39,8 @@ class Message {
hasImage: json['hasImage'] as bool? ?? false,
imagePath: json['imagePath'] as String?,
meta: json['meta'] as Map?,
functionCalls: json['functionCalls'] as List<String>?,
functionCalls:
json['functionCalls'] as Map<String, Map<String, dynamic>>?,
isToolCallsExpanded: json['isToolCallsExpanded'] as bool? ?? false,
);
}
@ -80,7 +81,7 @@ class Message {
bool? hasImage,
String? imagePath,
Map? meta,
List<String>? functionCalls,
Map<String, Map<String, dynamic>>? functionCalls,
bool? isToolCallsExpanded,
}) {
return Message(

3
lib/data/services/ble_manager.dart

@ -736,7 +736,8 @@ class BleManager extends GetxService {
'usertoken': jsonString,
'supportedLanguages': supportedLanguages,
'musicServiceType': musicServiceType,
"navigationMode": navigationMode
"navigationMode": navigationMode,
"callingTool": "callingTool".tr
};
// 记录日志(不记录敏感信息)

51
lib/modules/agent/controllers/agent_controller.dart

@ -775,7 +775,9 @@ class AgentController extends GetxController with WidgetsBindingObserver {
sessionid: sessionid,
isUser: false,
text: '',
functionCalls: [functionName]));
functionCalls: {
functionName: {'arguments': arguments}
}));
_isNewAssistantResponse = false;
} else {
// 否则更新最后一条消息
@ -788,8 +790,10 @@ class AgentController extends GetxController with WidgetsBindingObserver {
text: messages[lastIndex].text,
meta: messages[lastIndex].meta,
timestamp: messages[lastIndex].timestamp,
functionCalls: messages[lastIndex].functionCalls ?? []
..add(functionName),
functionCalls: messages[lastIndex].functionCalls ?? {}
..addAll({
functionName: {'arguments': arguments}
}),
);
}
}
@ -797,10 +801,42 @@ class AgentController extends GetxController with WidgetsBindingObserver {
case AgentServiceEventType.functionCallResult:
final sessionid = event.data['sessionid'] ?? '';
// final result = event.data['result'];
final function_name = event.data['function_name'] ?? '';
final function_result = event.data['result'] ?? '';
final metaStr = event.data['meta'] ?? '';
final functionCall = event.data['function_call'] ?? '';
final index = messages.lastIndexWhere(
(msg) => msg.sessionid == sessionid && !msg.isUser);
if (index != -1) {
// 获取当前的 functionCalls,如果为空则创建新的 Map
final functionCalls = Map<String, Map<String, dynamic>>.from(
messages[index].functionCalls?.map((key, value) => MapEntry(
key, Map<String, dynamic>.from(value as Map))) ??
{});
// 更新对应工具的调用结果
if (function_name.isNotEmpty) {
// 如果该工具已存在,更新其结果;如果不存在,创建新的条目
if (functionCalls.containsKey(function_name)) {
functionCalls[function_name]!['result'] = function_result;
} else {
functionCalls[function_name] = {
'result': function_result,
};
}
}
messages[index] = Message(
sessionid: sessionid,
isUser: false,
text: messages[index].text,
meta: messages[index].meta,
timestamp: messages[index].timestamp,
functionCalls: functionCalls,
isToolCallsExpanded: messages[index].isToolCallsExpanded,
);
}
Logger.i(TAG, 'Flutter 结束调用 $sessionid mcp: $metaStr');
if (metaStr.isNotEmpty) {
Map<String, dynamic> metaResult = {};
metaResult = jsonDecode(metaStr) as Map<String, dynamic>;
@ -815,8 +851,7 @@ class AgentController extends GetxController with WidgetsBindingObserver {
//不回复直接返回
break;
}
final index = messages.lastIndexWhere(
(msg) => msg.sessionid == sessionid && !msg.isUser);
Logger.i(TAG, 'Flutter 结束调用 卡片数据 $index $metaResult');
// 判断是否为新的回复或响应ID是否改变
if (index == -1) {
@ -902,7 +937,7 @@ class AgentController extends GetxController with WidgetsBindingObserver {
Logger.i(TAG, 'Flutter 结束调用 解析失败 $metaStr');
}
}
if (functionCall.contains("music")) {
if (function_name.contains("music")) {
Future.delayed(Duration(seconds: 1), () {
_qqmusicManager.syncCurrentPlayInfo(isOpenPlay: false);
});

203
lib/modules/agent/views/message_bubble.dart

@ -9,6 +9,7 @@ import '../../../modules/agent/views/navigation_card.dart';
import 'weather_card.dart';
import 'travel_card.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // 添加这个导入
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'dart:io';
@ -26,7 +27,7 @@ class MessageBubble extends GetView<AgentController> {
final bool hasImage;
final String? imagePath;
final Map? meta;
final List<String>? functionCalls;
final Map<String, Map<String, dynamic>>? functionCalls;
final bool isToolCallsExpanded;
const MessageBubble({
Key? key,
@ -86,7 +87,7 @@ class MessageBubble extends GetView<AgentController> {
padding: (!isUser &&
functionCalls != null &&
functionCalls!.isNotEmpty)
? const EdgeInsets.only(top: 20)
? const EdgeInsets.only(top: 30)
: EdgeInsets.zero,
child: _buildMessageContent(context),
),
@ -612,43 +613,50 @@ class MessageBubble extends GetView<AgentController> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 可点击的标题行
GestureDetector(
onTap: () async {
await controller.updateToolCallsExpanded(
sessionid,
!isToolCallsExpanded,
);
},
child: Container(
height: 30, // 增加固定高度,给字体更多空间
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
// 标题行
Container(
height: 40, // 增加固定高度,给字体更多空间
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark
? const Color.fromARGB(255, 71, 68, 68)?.withOpacity(0.8)
: Colors.grey[100]?.withOpacity(0.9),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(6),
topRight: Radius.circular(6),
),
border: Border.all(
color: Theme.of(context).brightness == Brightness.dark
? const Color.fromARGB(255, 71, 68, 68)
?.withOpacity(0.8)
: Colors.grey[100]?.withOpacity(0.9),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(6),
topRight: Radius.circular(6),
),
border: Border.all(
? Colors.white.withOpacity(0.1)
: Colors.black.withOpacity(0.1),
width: 0.5,
),
),
child: Row(
children: [
Icon(
Icons.build, // 工具icon
size: 20, // 减小图标大小
color: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.1)
: Colors.black.withOpacity(0.1),
width: 0.5,
? Colors.white.withOpacity(0.7)
: Colors.black54,
),
),
child: Row(
children: [
// 显示最后一个工具名称
Expanded(
const SizedBox(width: 4),
// 显示最后一个工具名称 - 可点击展开
Expanded(
child: GestureDetector(
onTap: () async {
await controller.updateToolCallsExpanded(
sessionid,
!isToolCallsExpanded,
);
},
child: Text(
functionCalls != null && functionCalls!.isNotEmpty
? functionCalls!.last
? functionCalls!.keys.last
: '工具调用',
style: TextStyle(
fontSize: 12, // 适当增大字体
fontSize: 16, // 适当增大字体
color:
Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.7)
@ -659,19 +667,47 @@ class MessageBubble extends GetView<AgentController> {
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 4),
// 箭头放在右侧
Icon(
isToolCallsExpanded
? Icons.keyboard_arrow_up // 展开时显示向上箭头
: Icons.keyboard_arrow_down, // 收起时显示向下箭头
size: 14, // 减小图标大小
color: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.7)
: Colors.black54,
),
const SizedBox(width: 4),
// 拷贝按钮
GestureDetector(
onTap: () {
_copyFunctionCallsToClipboard();
},
child: Container(
padding: const EdgeInsets.all(2),
child: Icon(
Icons.copy,
size: 20,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.7)
: Colors.black54,
),
),
],
),
),
const SizedBox(width: 4),
// 展开按钮
GestureDetector(
onTap: () async {
await controller.updateToolCallsExpanded(
sessionid,
!isToolCallsExpanded,
);
},
child: Container(
padding: const EdgeInsets.all(2), // 增加 padding 从 2 到 8
child: Icon(
isToolCallsExpanded
? Icons.keyboard_arrow_down // 展开时显示向上箭头
: Icons.keyboard_arrow_up, // 收起时显示向下箭头
size: 20, // 增加图标大小从 14 到 16
color: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.7)
: Colors.black54,
),
),
),
],
),
),
// 展开的工具调用列表
@ -710,33 +746,38 @@ class MessageBubble extends GetView<AgentController> {
// 添加滚动,防止超界
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: functionCalls!.asMap().entries.map((entry) {
int index = entry.key;
String call = entry.value;
children: functionCalls!.entries.map((entry) {
String toolName = entry.key;
dynamic toolResult = entry.value;
return Padding(
padding: EdgeInsets.only(
bottom: index < functionCalls!.length - 1
? 2
: 0), // 减少间距
padding: const EdgeInsets.only(bottom: 8), // 减少间距
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${index + 1}. ',
style: TextStyle(
fontSize: 12, // 修改为更小的字体
color: Theme.of(context).brightness ==
Brightness.dark
? Colors.white.withOpacity(0.6)
: Colors.black45,
fontWeight: FontWeight.w500,
// 工具名称 - 固定宽度
SizedBox(
width: 80, // 固定宽度
child: Text(
toolName,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).brightness ==
Brightness.dark
? Colors.white.withOpacity(0.6)
: Colors.black45,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 12), // 增加间距
// 工具结果 - 剩余空间
Expanded(
child: Text(
call,
toolResult?.toString() ?? '无结果',
style: TextStyle(
fontSize: 12, // 修改为更小的字体
fontSize: 12,
color: Theme.of(context).brightness ==
Brightness.dark
? Colors.white.withOpacity(0.8)
@ -756,6 +797,44 @@ class MessageBubble extends GetView<AgentController> {
),
));
}
/// 拷贝工具调用内容到剪贴板
void _copyFunctionCallsToClipboard() {
if (functionCalls == null || functionCalls!.isEmpty) {
Get.snackbar(
'提示',
'没有工具调用内容可以拷贝',
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 2),
);
return;
}
StringBuffer buffer = StringBuffer();
buffer.writeln('工具调用详情:');
buffer.writeln('=' * 30);
functionCalls!.forEach((toolName, toolData) {
buffer.writeln('🔧 工具名称: $toolName');
if (toolData is Map) {
toolData.forEach((key, value) {
buffer.writeln(' $key: $value');
});
} else {
buffer.writeln(' 结果: $toolData');
}
buffer.writeln('-' * 20);
});
Clipboard.setData(ClipboardData(text: buffer.toString()));
Get.snackbar(
'成功',
'工具调用内容已复制到剪贴板',
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 2),
);
}
}
/// 动画点组件

21
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -88,6 +88,8 @@ object AgentService : CoroutineScope {
// 会话ID,用于区分不同聊天上下文
private val agentId = "default_agent"
//工具调用提示
private var callingTool = ""
// 服务端连接
private var serverurl = ""
private var usertoken = ""
@ -192,6 +194,7 @@ object AgentService : CoroutineScope {
try {
serverurl = config["serverurl"]?.toString() ?: "";
usertoken = config["usertoken"]?.toString() ?: "";
callingTool = config["callingTool"]?.toString() ?: "";
// 初始化上下文和服务
this.context = appContext.applicationContext
this.chatApiService = ChatApiService(context)
@ -902,7 +905,8 @@ object AgentService : CoroutineScope {
var userMetadata = JSONObject()
var reply = true; //是否回复
var broadcast = true //是否播发
var iscard = false;
var iscard = false; //是否是卡片
var iscallingTool = true; //是否调用工具
// 如果有图片,添加图片信息到元数据
if (hasImage) {
@ -1070,21 +1074,24 @@ object AgentService : CoroutineScope {
try {
// audioPlayer?.playAudio(R.raw.calling, true)
val name = call.getString("name")
val arguments = call.getString("arguments")
sendEvent(
"function_call", mapOf(
"sessionid" to sessionid,
"name" to name,
"arguments" to call.toString()
"arguments" to arguments
)
)
if (name == "exit_interaction") {
BleService.closeCodec()
stopRecognition()
}
//播报工具调用
ttsService?.speakStream("正在调用工具:${name}")
Log.d(TAG, "liwei--------------- AI Call onFunctionCall iscallingTool:$iscallingTool callingTool:$callingTool")
if (iscallingTool) {
ttsService?.speakStream(callingTool)
iscallingTool = false
}
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onFunctionCall 异常", e)
}
@ -1112,6 +1119,7 @@ object AgentService : CoroutineScope {
sendEvent(
"function_call_result", mapOf(
"sessionid" to sessionid,
"function_name" to name,
"function_call" to functionCall.toString(),
"result" to functionCallResult.toString(),
"meta" to aiMetadata,
@ -1121,6 +1129,7 @@ object AgentService : CoroutineScope {
sendEvent(
"function_call_result", mapOf(
"sessionid" to sessionid,
"function_name" to name,
"function_call" to functionCall.toString(),
"result" to functionCallResult.toString(),
)

2
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -77,6 +77,8 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
config["usertoken"]?.toString()?.let { intent.putExtra("usertoken", it) }
config["musicServiceType"]?.toString()?.let { intent.putExtra("musicServiceType", it) }
config["navigationMode"]?.toString()?.let { intent.putExtra("navigationMode", it) }
config["callingTool"]?.toString()?.let { intent.putExtra("callingTool", it) }
// 添加 supportedLanguages 参数处理
config["supportedLanguages"]?.let { languages ->

3
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt

@ -54,6 +54,7 @@ class BleMediaService : Service(), CoroutineScope {
private const val KEY_SUPPORTED_LANGUAGES = "supportedLanguages"
private const val KEY_MUSICSERVICE_TYPE = "musicServiceType"
private const val APP_NAVIGATION_MODE = "navigationMode"
private const val APP_CALLING_TOOL = "callingTool"
// 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false)
@ -183,6 +184,7 @@ class BleMediaService : Service(), CoroutineScope {
it.getStringExtra(KEY_USER_TOKEN)?.let { value -> config[KEY_USER_TOKEN] = value }
it.getStringExtra(KEY_MUSICSERVICE_TYPE)?.let { value -> config[KEY_MUSICSERVICE_TYPE] = value }
it.getStringExtra(APP_NAVIGATION_MODE)?.let { value -> config[APP_NAVIGATION_MODE] = value }
it.getStringExtra(APP_CALLING_TOOL)?.let { value -> config[APP_CALLING_TOOL] = value }
// 提取 supportedLanguages 参数
it.getStringArrayListExtra(KEY_SUPPORTED_LANGUAGES)?.let { languages ->
if (languages.isNotEmpty()) {
@ -243,6 +245,7 @@ class BleMediaService : Service(), CoroutineScope {
config[KEY_USER_TOKEN] = getConfigString(prefs, KEY_USER_TOKEN, "")
config[KEY_MUSICSERVICE_TYPE] = getConfigString(prefs, KEY_MUSICSERVICE_TYPE, "")
config[APP_NAVIGATION_MODE] = getConfigString(prefs, APP_NAVIGATION_MODE, "")
config[APP_CALLING_TOOL] = getConfigString(prefs, APP_CALLING_TOOL, "")
// 添加 supportedLanguages 参数处理
val supportedLanguagesStr = getConfigString(prefs, KEY_SUPPORTED_LANGUAGES, "")

87
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -60,6 +60,8 @@ class AgentServiceImpl: NSObject {
// 会话ID,用于区分不同聊天上下文
private let agentId = "default_agent"
//工具调用提示
public var callingTool = ""
private var isInitialized: Bool = false
private var isRecognizing: Bool = false
@ -162,6 +164,11 @@ class AgentServiceImpl: NSObject {
// os_log("initialize: config=%{public}@", log: logger, type: .info, config)
self.config = config
if let callingTool = config["callingTool"] as? String {
self.callingTool = callingTool
}
if let serverurl = config["serverurl"] as? String {
self.serverurl = serverurl
}
@ -642,7 +649,7 @@ audioStream.saveAudioDataTo(data: audioData)
os_log("添加用户消息", log: logger, type: .info)
addToHistoryMessages(chatApiService.createUserMessage(content: displayText))
currsessionId = sessionid
let callback = ChatApiStreamCallback(
agentService: self,
speakResponse: speakResponse,
@ -650,11 +657,10 @@ audioStream.saveAudioDataTo(data: audioData)
hasImage: hasImage,
imagePath: imagePath
)
currsessionId = sessionid
azureTtsHelper?.startspeak(sessionid: currsessionId)
azureTtsHelper?.startspeak(sessionid: sessionid)
os_log("设置回调并调用sendMessageStream", log: logger, type: .info)
chatApiService.setStreamCallback(currsessionId,callback)
chatApiService.sendMessageStream(currsessionId,messages: messages)
chatApiService.setStreamCallback(sessionid,callback)
chatApiService.sendMessageStream(sessionid,messages: messages)
audioPlayer?.playAwaitSound()
os_log("sendMessageStream调用完成", log: logger, type: .info)
}
@ -1613,6 +1619,7 @@ class ChatApiStreamCallback: StreamCallback {
private var iscard = false;
private var reply = true; //是否回复
private var broadcast = true //是否播发
private var iscallingTool = true; //是否调用工具
init(agentService: AgentServiceImpl, speakResponse: Bool, displayText: String, hasImage: Bool,imagePath:String) {
self.agentService = agentService
@ -1627,45 +1634,46 @@ class ChatApiStreamCallback: StreamCallback {
func onUsage(_ sessionid: String,_ prompt_tokens: Int?,_ completion_tokens: Int?,_ total_tokens: Int?){
}
func onToken(_ sessionId:String,_ token: String) {
func onToken(_ sessionid:String,_ token: String) {
do {
// print("liwei--------------- AI Call onToken! sessionId:\(sessionId) token:\(token)")
guard let agentService = try agentService else { return }
if (sessionId != agentService.currsessionId) {
guard let agentService = try agentService else { return }
// print("liwei--------------- AI Call onToken! sessionId:\(sessionid) currsessionId:\(agentService.currsessionId) token:\(token)")
if (sessionid != agentService.currsessionId) {
return
}
responseBuilder += token
if speakResponse && reply && broadcast{
try agentService.azureTtsHelper?.speakStream(sessionid:sessionId,token)
try agentService.azureTtsHelper?.speakStream(sessionid:sessionid,token)
// 在开始流式TTS时立即停止气泡音
}
if (reply && token != "") {
try agentService.sendEvent(name: "assistant_token", data: ["token": token])
try agentService.sendEvent(name: "assistant_token", data: ["sessionid":sessionid,"token": token])
}
}catch {
print("liwei--------------- AI Call onToken 异常: \(error)")
}
}
func onComplete(_ sessionId:String) {
func onComplete(_ sessionid:String) {
do {
guard let agentService = try agentService else { return }
if sessionId == agentService.currsessionId {
if sessionid == agentService.currsessionId {
// 停止等待音效
agentService.audioPlayer?.stopAwaitSound()
}
if speakResponse && reply && broadcast && sessionId == agentService.currsessionId{
agentService.azureTtsHelper?.flushStream(sessionid:sessionId)
if speakResponse && reply && broadcast && sessionid == agentService.currsessionId{
agentService.azureTtsHelper?.flushStream(sessionid:sessionid)
}
let response = responseBuilder
var responseData: [String: Any] = [
"sessionid":sessionid,
"text": response,
"userInput": displayText
]
@ -1684,8 +1692,9 @@ class ChatApiStreamCallback: StreamCallback {
try agentService.addToHistoryMessages(assistantMessage)
}
// 保存聊天记录
try agentService.saveChatMessage(sessionId: sessionId,userMessage: displayText, assistantMessage: response, metadata: metadata,userMetadata:toJSONString(userMetadata))
try agentService.saveChatMessage(sessionId: sessionid,userMessage: displayText, assistantMessage: response, metadata: metadata,userMetadata:toJSONString(userMetadata))
agentService.isAiStreaming = false
iscallingTool = true
os_log("设置AI流式状态为false", log: agentService.logger, type: .info)
}catch{
print("liwei--------------- AI Call onComplete 异常: \(error)")
@ -1693,20 +1702,21 @@ class ChatApiStreamCallback: StreamCallback {
}
func onError(_ sessionId:String,_ error: Error) {
func onError(_ sessionid:String,_ error: Error) {
do {
guard let agentService = try agentService else { return }
if sessionId == agentService.currsessionId {
if sessionid == agentService.currsessionId {
// 停止等待音效
agentService.audioPlayer?.stopAwaitSound()
}
os_log("ChatAPI错误: %{public}@", log: agentService.logger, type: .error, error.localizedDescription)
if (sessionId != agentService.currsessionId) {
if (sessionid != agentService.currsessionId) {
return
}
try agentService.sendEvent(name: "error", data: [
"sessionid":sessionid,
"code": "AI_ERROR",
"message": error.localizedDescription
])
@ -1719,27 +1729,34 @@ class ChatApiStreamCallback: StreamCallback {
}
func onFunctionCall(_ sessionId:String,_ functionCall: [String: Any]) {
func onFunctionCall(_ sessionid:String,_ functionCall: [String: Any]) {
do {
guard let agentService = try agentService else { return }
if (sessionId != agentService.currsessionId) {
if (sessionid != agentService.currsessionId) {
return
}
let name = functionCall["name"] as? String ?? ""
os_log("收到函数调用: %{public}@", log: agentService.logger, type: .info, name)
let arguments = functionCall["arguments"] as? String ?? ""
os_log("收到函数调用:%{public}@", log: agentService.logger, type: .info, functionCall)
print("onFunctionCall=functionCall\(functionCall)")
if (name == "exit_interaction") {
// 关闭编码器 (设备侧)
BleService.shared.closeCodec()
agentService.stopRecognition()
}
BleService.shared.closeCodec()
agentService.stopRecognition()
}
try agentService.sendEvent(name: "function_call", data: [
"sessionid":sessionid,
"name": name,
"arguments": functionCall
"arguments": arguments
])
if (iscallingTool) {
os_log("收到函数调用: callingTool:%{public}@", log: agentService.logger, type: .info, agentService.callingTool)
agentService.azureTtsHelper?.speakStream(sessionid: sessionid,agentService.callingTool)
iscallingTool = false
}
}catch{
@ -1748,7 +1765,7 @@ class ChatApiStreamCallback: StreamCallback {
}
func onFunctionCallResult(_ sessionId:String,_ functionCall: [String: Any], _ functionCallResult: [String: Any]) {
func onFunctionCallResult(_ sessionid:String,_ functionCall: [String: Any], _ functionCallResult: [String: Any]) {
do {
guard let agentService = agentService else { return }
@ -1763,6 +1780,8 @@ class ChatApiStreamCallback: StreamCallback {
iscard = !(resultmap["iscard"] as? Bool ?? false)
if (iscard){
agentService.sendEvent(name: "function_call_result", data: [
"sessionid":sessionid,
"function_name": name,
"function_call": functionCall,
"result": functionCallResult,
"meta":metadata,
@ -2066,7 +2085,7 @@ class AudioPlayer {
extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback {
func onResult(sessionid:String ,_ text: String, _ detectedLanguage: String) {
if !text.isEmpty && isKeepResult {
var data: [String: Any] = ["text": text]
var data: [String: Any] = ["sessionid":sessionid,"text": text]
if !detectedLanguage.isEmpty {
data["language"] = detectedLanguage
}
@ -2093,7 +2112,7 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback {
restartIdleCheck()
}
var data: [String: Any] = ["text": recognizing]
var data: [String: Any] = ["sessionid":sessionid,"text": recognizing]
if !detectedLanguage.isEmpty {
data["language"] = detectedLanguage
}
@ -2106,7 +2125,7 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback {
}
func onSessionStarted(sessionid:String) {
sendEvent(name: "recognition_started", data: ["status": "started"])
sendEvent(name: "recognition_started", data: ["sessionid":sessionid,"status": "started"])
isRecognizing = true
hasSpeechDetected = false
// 新增:会话真正启动,复位"启动中"状态
@ -2127,7 +2146,7 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback {
}
func onSessionStopped(sessionid:String) {
sendEvent(name: "recognition_stopped", data: ["status": "stopped"])
sendEvent(name: "recognition_stopped", data: ["sessionid":sessionid,"status": "stopped"])
isRecognizing = false
hasSpeechDetected = false
stopIdleCheck()
@ -2137,7 +2156,7 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback {
}
func onCanceled(sessionid:String ,_ reason: String, _ errorDetails: String) {
var data: [String: Any] = [:]
var data: [String: Any] = ["sessionid":sessionid]
if !reason.isEmpty {
data["reason"] = reason
}
@ -2154,7 +2173,7 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback {
}
func onError(sessionid:String ,_ errorCode: Int, _ error: String) {
let data: [String: Any] = ["message": error.isEmpty ? "未知错误" : error]
let data: [String: Any] = ["sessionid":sessionid,"message": error.isEmpty ? "未知错误" : error]
sendEvent(name: "error", data: data)
isRecognizing = false
// 新增:出错时复位"启动中/待停止"状态

12
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift

@ -1211,6 +1211,7 @@ private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback {
func onResult(sessionid:String,_ text: String, _ detectedLanguage: String) {
plugin?.sendAsrEvent([
"type": "result",
"sessionid": sessionid,
"text": text,
"detectedLanguage": detectedLanguage
])
@ -1224,6 +1225,7 @@ private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback {
func onRecognizing(sessionid:String,_ text: String, _ detectedLanguage: String) {
plugin?.sendAsrEvent([
"type": "recognizing",
"sessionid": sessionid,
"text": text,
"detectedLanguage": detectedLanguage
])
@ -1234,7 +1236,8 @@ private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback {
*/
func onSessionStarted(sessionid:String) {
plugin?.sendAsrEvent([
"type": "sessionStarted"
"type": "sessionStarted",
"sessionid": sessionid
])
}
@ -1243,7 +1246,8 @@ private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback {
*/
func onSessionStopped(sessionid:String) {
plugin?.sendAsrEvent([
"type": "sessionStopped"
"type": "sessionStopped",
"sessionid": sessionid
])
}
@ -1254,7 +1258,8 @@ private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback {
*/
func onCanceled(sessionid:String,_ reason: String, _ errorDetails: String) {
plugin?.sendAsrEvent([
"type": "canceled",
"type": "canceled",
"sessionid": sessionid,
"reason": reason,
"errorDetails": errorDetails
])
@ -1268,6 +1273,7 @@ private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback {
func onError(sessionid:String,_ errorCode: Int, _ error: String) {
plugin?.sendAsrEvent([
"type": "error",
"sessionid": sessionid,
"code": errorCode,
"message": error
])

6
local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift

@ -276,7 +276,7 @@ public class ChatStorageHelper {
os_log("删除特定消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
}
} else {
os_log("删除消息参数无效 - sessionId和messageIds都为空", log: logger, type: .error)
os_log("删除消息参数无效 - agentId和messageIds都为空", log: logger, type: .error)
return false
}
@ -309,7 +309,7 @@ public class ChatStorageHelper {
var messages: [[String: Any]] = []
// 首先检查数据库中是否有该会话的消息
let countQuery = "SELECT COUNT(*) FROM messages WHERE session_id = ?"
let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?"
var countStatement: OpaquePointer?
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
@ -325,7 +325,7 @@ public class ChatStorageHelper {
SELECT * FROM (
SELECT id, message, sender, metadata, created_at
FROM messages
WHERE session_id = ?
WHERE agent_id = ?
ORDER BY created_at DESC
LIMIT ?
) tmp ORDER BY created_at ASC

Loading…
Cancel
Save