Browse Source

修复天气卡片

weicu
fdp 1 year ago
parent
commit
a7ce01603b
  1. 256
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  2. 425
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift

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

@ -625,95 +625,11 @@ audioStream.saveAudioDataTo(data: audioData)
}
}
//解析卡片数据
@discardableResult
internal func autoHandleFunctionCallResult(toolName: String, functionCallResult: [String: Any]) -> [String:Any] {
if let contentArray = functionCallResult["content"] as? [[String: Any]],
let firstContent = contentArray.first,
let textStr = firstContent["text"] as? String,
let jsonData = textStr.data(using: .utf8) {
do {
var broadcast = true;
var notreply = false;
if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
print("解析成功:\(jsonObject)")
var metadata: [String: Any] = [:]
metadata[toolName] = jsonObject
if let _broadcast = jsonObject["broadcast"] as? Bool {
broadcast = _broadcast
}
if let _notreply = jsonObject["notreply"] as? Bool {
notreply = _notreply
}
metadata["notreply"] = notreply
// 将 metadata 转为 JSON 字符串
let metaStr: String
if let jsonData = try? JSONSerialization.data(withJSONObject: metadata, options: [.prettyPrinted]),
let jsonString = String(data: jsonData, encoding: .utf8) {
metaStr = jsonString
} else {
metaStr = ""
}
// 处理 card_music
if let cardMusic = jsonObject["card_music"] as? [String: Any] {
let id = cardMusic["id"] as? Int ?? 0
let url = cardMusic["url"] as? String ?? ""
let title = cardMusic["name"] as? String ?? ""
let artist = cardMusic["sgener"] as? String ?? ""
let coverUrl = cardMusic["image"] as? String ?? ""
processMusicPlay([
"id": id,
"url": url,
"title": title,
"artist": artist,
"coverUrl": coverUrl
])
// 处理 card_musiclist
} else if let cardMusiclist = jsonObject["card_musiclist"] as? [String: Any],
let musics = cardMusiclist["musics"] as? [[String: Any]] {
var playlist: [[String: Any]] = []
for item in musics {
let id = item["id"] as? Int ?? 0
let url = item["url"] as? String ?? ""
let title = item["name"] as? String ?? ""
let artist = item["sgener"] as? String ?? ""
let coverUrl = item["image"] as? String ?? ""
let ts = item["ts"] as? Int ?? 0
let song: [String: Any] = [
"id": id,
"url": url,
"title": title,
"artist": artist,
"coverUrl": coverUrl
]
playlist.append(song)
}
if !playlist.isEmpty {
print("自动播放音乐列表: \(playlist)")
processMusicPlayList(playlist)
} else {
print("card_musiclist 中没有有效的音乐条目")
}
}else if let cardNavigation = jsonObject["card_navigation"] as? [String: Any] { //导航
let start:String = cardNavigation["start"] as? String ?? ""
let end:String = cardNavigation["end"] as? String ?? ""
}
return [
"meta": metaStr,
"broadcast": broadcast,
"notreply": notreply
]
}
} catch {
print("JSON 解析失败: \(error)")
}
}
// 解析卡片数据
@discardableResult
internal func autoHandleFunctionCallResult(toolName: String, functionCallResult: [String: Any]) -> [String:Any] {
// 1. 从functionCallResult获取context
guard let contextValue = functionCallResult["context"] else {
return [
"meta": "",
"broadcast": true,
@ -721,6 +637,162 @@ audioStream.saveAudioDataTo(data: audioData)
]
}
var contextStr = ""
// 处理不同类型的context值
if let strValue = contextValue as? String {
contextStr = strValue
} else if let data = try? JSONSerialization.data(withJSONObject: contextValue, options: []),
let str = String(data: data, encoding: .utf8) {
contextStr = str
}
// 尝试解析JSON
if let jsonData = contextStr.data(using: .utf8),
let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) {
// 处理字典类型的JSON
if let jsonDict = jsonObject as? [String: Any] {
return handleJSONDictionary(jsonDict, toolName: toolName)
}
// 处理数组类型的JSON
else if let jsonArray = jsonObject as? [[String: Any]] {
return handleJSONDictionary(["array": jsonArray], toolName: toolName)
}
// 处理基础类型
else {
return handlePrimitiveType(jsonObject, toolName: toolName)
}
}
// 纯文本内容
return handlePlainText(contextStr, toolName: toolName)
}
// 处理字典类型的JSON
private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -> [String: Any] {
var broadcast = true
var notreply = false
var metadata: [String: Any] = [:]
metadata[toolName] = jsonDict
if let _broadcast = jsonDict["broadcast"] as? Bool {
broadcast = _broadcast
}
if let _notreply = jsonDict["notreply"] as? Bool {
notreply = _notreply
}
metadata["notreply"] = notreply
// 处理卡片数据
if let cardMusic = jsonDict["card_music"] as? [String: Any] {
processMusicCard(cardMusic)
} else if let cardMusiclist = jsonDict["card_musiclist"] as? [String: Any],
let musics = cardMusiclist["musics"] as? [[String: Any]] {
processMusicListCard(musics)
} else if let cardNavigation = jsonDict["card_navigation"] as? [String: Any] {
processNavigationCard(cardNavigation)
}
let metaStr: String
if let jsonData = try? JSONSerialization.data(withJSONObject: metadata, options: []),
let jsonString = String(data: jsonData, encoding: .utf8) {
metaStr = jsonString
} else {
metaStr = ""
}
return [
"meta": metaStr,
"broadcast": broadcast,
"notreply": notreply
]
}
// 处理基本类型(字符串、数字等)
private func handlePrimitiveType(_ value: Any, toolName: String) -> [String: Any] {
let metaStr = "{\"\(toolName)\": \"\(value)\"}"
return [
"meta": metaStr,
"broadcast": true,
"notreply": false
]
}
// 处理纯文本内容
private func handlePlainText(_ text: String, toolName: String) -> [String: Any] {
let metaStr = "{\"\(toolName)\": \"\(text)\"}"
return [
"meta": metaStr,
"broadcast": true,
"notreply": false
]
}
/// 辅助方法:将字典转换为JSON字符串
private func jsonToString(_ json: [String: Any]) -> String? {
do {
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
return String(data: jsonData, encoding: .utf8)
} catch {
print("JSON序列化失败: \(error)")
return nil
}
}
// 处理音乐卡片
private func processMusicCard(_ cardMusic: [String: Any]) {
let id = cardMusic["id"] as? Int ?? 0
let url = cardMusic["url"] as? String ?? ""
let title = cardMusic["name"] as? String ?? ""
let artist = cardMusic["sgener"] as? String ?? ""
let coverUrl = cardMusic["image"] as? String ?? ""
let ts = cardMusic["ts"] as? Int ?? 0
processMusicPlay([
"id": id,
"url": url,
"title": title,
"artist": artist,
"coverUrl": coverUrl,
"ts": ts
])
}
// 处理音乐列表卡片
private func processMusicListCard(_ musics: [[String: Any]]) {
var playlist: [[String: Any]] = []
for item in musics {
let id = item["id"] as? Int ?? 0
let url = item["url"] as? String ?? ""
let title = item["name"] as? String ?? ""
let artist = item["sgener"] as? String ?? ""
let coverUrl = item["image"] as? String ?? ""
let ts = item["ts"] as? Int ?? 0
playlist.append([
"id": id,
"url": url,
"title": title,
"artist": artist,
"coverUrl": coverUrl,
"ts": ts
])
}
if !playlist.isEmpty {
processMusicPlayList(playlist)
}
}
// 处理导航卡片
private func processNavigationCard(_ cardNavigation: [String: Any]) {
let start = cardNavigation["start"] as? String ?? ""
let end = cardNavigation["end"] as? String ?? ""
// 实际处理导航逻辑
print("启动导航: 从 \(start) 到 \(end)")
}
// 后台播放单曲
private func processMusicPlay(_ song: [String: Any]) {
sendEvent(name: "music_play", data: song)
@ -917,8 +989,8 @@ class ChatApiStreamCallback: StreamCallback {
}
let name = functionCall["name"] as? String ?? ""
os_log("收到函数调用: %{public}@", log: agentService.logger, type: .info, name)
try agentService.audioPlayer?.playCallingSound()
print("onFunctionCall=functionCall\(functionCall)")
// try agentService.audioPlayer?.playCallingSound()
try agentService.sendEvent(name: "function_call", data: [
"name": name,
@ -943,7 +1015,7 @@ class ChatApiStreamCallback: StreamCallback {
let name = functionCall["name"] as? String ?? ""
os_log("收到函数调用结果: %{public}@", log: agentService.logger, type: .info, functionCallResult)
agentService.audioPlayer?.stopCallingSound()
// agentService.audioPlayer?.stopCallingSound()
let resultmap = agentService.autoHandleFunctionCallResult(toolName:name,functionCallResult:functionCallResult)
metadata = resultmap["meta"] as? String ?? ""
broadcast = resultmap["broadcast"] as? Bool ?? true

425
local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift

@ -252,192 +252,313 @@ public class ChatApiService: NSObject {
}
/// 发送消息(流式输出)
public func sendMessageStream(_ sessionId:String,messages: [[String: Any]]) {
// 1. 立即中止旧会话
abortCurrentSession()
// 2. 创建新会话
currSessionId = sessionId
guard isInitialized && !apiKey.isEmpty, let openAI = openAI else {
let error = ChatApiException("ChatAPI服务未初始化")
getSessionCallback(sessionId)?.onError(sessionId,error)
return
}
// 重置状态
currentMessages = messages
toolCalls = [:]
// 检查是否包含图片,决定使用哪个模型
var currentModel = model
if containsImage(in: messages) {
currentModel = visionModel
}
// MARK: - 流式请求实现
public func sendMessageStream(_ sessionId: String, messages: [[String: Any]]) {
// 1. 立即中止旧会话
abortCurrentSession()
// 2. 创建新会话
currSessionId = sessionId
guard isInitialized && !apiKey.isEmpty, let openAI = openAI else {
let error = ChatApiException("ChatAPI服务未初始化")
getSessionCallback(sessionId)?.onError(sessionId, error)
return
}
// 保存当前消息和回调
currentMessages = messages
toolCalls = [:]
sessionCallbacks[sessionId] = getSessionCallback(sessionId)
// 检查是否包含图片,决定模型
let currentModel = containsImage(in: messages) ? visionModel : model
currentStreamTask = Task { [weak self] in
guard let self = self else { return }
var replyContext = "" // 收集AI回复内容
currentStreamTask = Task { [weak self] in
guard let self = self else { return }
do {
// 转换消息格式
let chatMessages = try self.convertToChatMessages(messages)
do {
// 转换消息格式
let chatMessages = try self.convertToChatMessages(messages)
// 获取MCP工具列表
let tools = self.mcpClient?.getToolMaps() ?? []
// 构建请求
let query: ChatQuery
if !tools.isEmpty {
// 转换工具格式
let chatTools = self.convertToOpenAITools(tools)
query = ChatQuery(
messages: chatMessages,
model: currentModel,
maxTokens: 2000,
temperature: 0.7,
tools: chatTools
)
} else {
query = ChatQuery(
messages: chatMessages,
model: currentModel,
maxTokens: 2000,
temperature: 0.7
)
// 获取工具列表
let tools = self.mcpClient?.getToolMaps() ?? []
// 构建请求
let query: ChatQuery
if !tools.isEmpty {
let chatTools = self.convertToOpenAITools(tools)
query = ChatQuery(
messages: chatMessages,
model: currentModel,
maxTokens: 2000,
temperature: 0.7,
tools: chatTools
)
} else {
query = ChatQuery(
messages: chatMessages,
model: currentModel,
maxTokens: 2000,
temperature: 0.7
)
}
let chatsStream: AsyncThrowingStream<ChatStreamResult, Error> = openAI.chatsStream(query: query)
for try await result in chatsStream {
// 检查会话是否已变更
if sessionId != self.currSessionId {
return
}
let chatsStream: AsyncThrowingStream<ChatStreamResult, Error> = openAI.chatsStream(query: query)
guard let choice = result.choices.first else { continue }
let delta = choice.delta
// 处理文本内容
if let content = delta.content {
replyContext += content
self.getSessionCallback(sessionId)?.onToken(sessionId, content)
}
for try await result in chatsStream {
if sessionId != self.currSessionId {
return
}
if let choice = result.choices.first {
let delta = choice.delta
// 收集工具调用信息
if let toolCalls = delta.toolCalls {
for toolCall in toolCalls {
let index = toolCall.index
// 处理普通文本内容
if let content = delta.content {
getSessionCallback(sessionId)?.onToken(sessionId,content)
// 初始化工具调用信息
if self.toolCalls[index] == nil {
self.toolCalls[index] = ToolCallInfo()
}
// 收集工具调用信息
if let toolCalls = delta.toolCalls {
for toolCall in toolCalls {
let index = toolCall.index
// 创建或获取现有的工具调用信息
if self.toolCalls[index] == nil {
self.toolCalls[index] = ToolCallInfo()
}
let toolCallInfo = self.toolCalls[index]!
// 更新ID
if let id = toolCall.id {
toolCallInfo.id = id
}
// 更新函数信息
if let function = toolCall.function {
if let name = function.name {
toolCallInfo.name = name
}
if let arguments = function.arguments {
toolCallInfo.arguments += arguments
}
}
guard let toolCallInfo = self.toolCalls[index] else { continue }
// 更新ID
if let id = toolCall.id {
toolCallInfo.id = id
}
// 更新函数信息
if let function = toolCall.function {
if let name = function.name {
toolCallInfo.name = name
}
if let arguments = function.arguments {
toolCallInfo.arguments += arguments
}
}
}
}
}
// 流式响应结束
if sessionId == self.currSessionId {
print("发送消息(流式输出)\(replyContext)")
// 计算token使用量
let promptTokens = self.estimatePromptTokens(messages: self.currentMessages)
let completionTokens = self.roughTokenCount(text: replyContext)
let totalTokens = promptTokens + completionTokens
self.getSessionCallback(sessionId)?.onUsage(sessionId, promptTokens, completionTokens, totalTokens)
if sessionId == self.currSessionId {
// 处理工具调用或完成
let hasToolCalls = await self.processToolCalls(sessionId)
if !hasToolCalls {
getSessionCallback(sessionId)?.onComplete(sessionId)
}
// 处理工具调用
let hasToolCalls = await self.processToolCalls(sessionId)
if !hasToolCalls {
self.getSessionCallback(sessionId)?.onComplete(sessionId)
self.clearCurrentSession()
}
} catch {
if sessionId == self.currSessionId {
let chatApiError = ChatApiException("流式请求失败: \(error.localizedDescription)")
getSessionCallback(sessionId)?.onError(sessionId,chatApiError)
}
} catch {
if sessionId == self.currSessionId {
let errorMessage = "流式请求失败: \(error.localizedDescription)"
let chatApiError = ChatApiException(errorMessage)
self.getSessionCallback(sessionId)?.onError(sessionId, chatApiError)
self.clearCurrentSession()
}
}
}
}
// MARK: - 新增辅助方法
private func estimatePromptTokens(messages: [[String: Any]]) -> Int {
var totalText = ""
for message in messages {
if let content = message["content"] as? String {
totalText += content + " "
} else if let contentArray = message["content"] as? [[String: Any]] {
for item in contentArray {
if let type = item["type"] as? String,
type == "text",
let text = item["text"] as? String {
totalText += text + " "
}
}
}
}
return roughTokenCount(text: totalText)
}
private func roughTokenCount(text: String) -> Int {
let separators = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters)
let tokens = text.components(separatedBy: separators).filter { !$0.isEmpty }
return tokens.count
}
/// 处理工具调用
private func processToolCalls(_ sessionId:String) async -> Bool {
guard let firstToolCall = toolCalls.values.first, firstToolCall.isValid() else {
return false
}
// 创建函数调用字典
let functionCall: [String: Any] = [
"name": firstToolCall.name,
"arguments": firstToolCall.arguments,
"id": firstToolCall.id
]
// 通知上层工具调用事件
getSessionCallback(sessionId)?.onFunctionCall(sessionId,functionCall)
// 在后台队列处理工具调用
Task {
do {
if sessionId == self.currSessionId {
// 调用工具
let result: [String: Any]
let args = try self.parseJsonArguments(firstToolCall.arguments)
if let client = self.mcpClient {
result = await client.callTool(name: firstToolCall.name, arguments: args) ?? ["context": "工具调用失败"]
} else {
result = ["context": "MCP客户端未初始化"]
}
if sessionId == self.currSessionId {
// 处理结果
getSessionCallback(sessionId)?.onFunctionCallResult(sessionId,functionCall, result)
/// 处理工具调用
private func processToolCalls(_ sessionId:String) async -> Bool {
// 验证工具调用集合不为空
if toolCalls.isEmpty {
print("[Session: \(sessionId)] 没有工具调用需要处理")
return false
}
// 输出所有收集到的工具调用信息(用于调试)
print("[Session: \(sessionId)] 收集到 \(toolCalls.count) 个工具调用")
for (index, toolCall) in toolCalls {
print("[Session: \(sessionId)] 工具调用[\(index)]: name=\(toolCall.name), args.length=\(toolCall.arguments.count), args=\(toolCall.arguments.prefix(100))...")
}
// 只处理有效的工具调用
let validToolCalls = toolCalls.values.filter { $0.isValid() }
if validToolCalls.isEmpty {
print("[Session: \(sessionId)] 没有有效的工具调用(参数可能不完整)")
return false
}
// 处理第一个有效的工具调用
let firstToolCall = validToolCalls.first!
print("[Session: \(sessionId)] 处理工具调用: \(firstToolCall.name),参数: \(firstToolCall.arguments)")
// 创建函数调用字典
let functionCall: [String: Any] = [
"name": firstToolCall.name,
"arguments": firstToolCall.arguments,
"id": firstToolCall.id
]
// 转换为JSON对象格式
let jsonFunctionCall = convertMapToJsonObject(functionCall)
getSessionCallback(sessionId)?.onFunctionCall(sessionId, jsonFunctionCall)
// 通知上层工具调用事件
// getSessionCallback(sessionId)?.onFunctionCall(sessionId, functionCall)
// 在后台处理工具调用
Task { [weak self] in
guard let self = self else { return }
do {
if sessionId == self.currSessionId {
// 解析参数
let args = try self.parseJsonArguments(firstToolCall.arguments)
// 通过MCP客户端处理工具调用
var result: [String: Any] = [:]
if let client = self.mcpClient {
// 调用MCP工具
if let toolResult = await client.callTool(name: firstToolCall.name, arguments: args) {
print("[Session: \(sessionId)] AI调用工具结果: \(firstToolCall.name), 参数: \(args), 结果: \(toolResult)")
// 将result转换为JSON字符串
let context: String
do {
let jsonData = try JSONSerialization.data(withJSONObject: result, options: .prettyPrinted)
context = String(data: jsonData, encoding: .utf8) ?? "工具调用失败"
} catch {
context = "工具调用失败"
// 统一结果格式
if toolResult["isError"] as? Bool == true {
if let content = toolResult["content"] as? [[String: Any]],
let firstContent = content.first,
let errorText = firstContent["text"] as? String {
result = ["context": errorText]
} else {
result = ["context": "Tool execution failed"]
}
} else if let context = toolResult["context"] {
// 本地函数结果
result = ["context": context]
} else if let content = toolResult["content"] as? [[String: Any]],
let firstContent = content.first,
let text = firstContent["text"] as? String {
// MCP工具结果
result = ["context": text]
} else {
print("[Session: \(sessionId)] MCP工具调用返回无法解析的结果")
result = ["context": "Tool call failed"]
}
// 将结果发送回OpenAI继续对话
await self.sendFunctionCallResultInternal(
sessionId:sessionId,
messages: self.currentMessages,
functionCall: functionCall,
functionResult: context
)
} else {
print("[Session: \(sessionId)] MCP工具调用返回nil")
result = ["context": "Tool call failed"]
}
} else {
// 工具不存在
result = ["context": "Tool not found: \(firstToolCall.name)"]
}
} catch {
if sessionId == self.currSessionId {
let errorMessage = "工具调用处理失败: \(error.localizedDescription)"
// 处理结果
self.getSessionCallback(sessionId)?.onFunctionCallResult(
sessionId,
functionCall,
result
)
// 将结果发送回OpenAI继续对话
await self.sendFunctionCallResultInternal(
sessionId:sessionId,
sessionId: sessionId,
messages: self.currentMessages,
functionCall: functionCall,
functionResult: errorMessage
functionResult: self.jsonToString(result) ?? "{}"
)
}
}
} catch {
print("[Session: \(sessionId)] 工具调用处理过程中出错: \(error.localizedDescription)")
if sessionId == self.currSessionId {
let errorMessage = "工具调用处理失败: \(error.localizedDescription)"
await self.sendFunctionCallResultInternal(
sessionId: sessionId,
messages: self.currentMessages,
functionCall: functionCall,
functionResult: errorMessage
)
}
}
return true
}
return true
}
/// 辅助方法:将字典转换为JSON字符串
private func jsonToString(_ json: [String: Any]) -> String? {
do {
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
return String(data: jsonData, encoding: .utf8)
} catch {
print("JSON序列化失败: \(error)")
return nil
}
}
// MARK: - JSON转换工具
private func convertMapToJsonObject(_ map: [String: Any]) -> [String: Any] {
var result = [String: Any]()
for (key, value) in map {
if let nestedMap = value as? [String: Any] {
result[key] = convertMapToJsonObject(nestedMap)
} else if let array = value as? [Any] {
var jsonArray = [Any]()
for item in array {
if let itemMap = item as? [String: Any] {
jsonArray.append(convertMapToJsonObject(itemMap))
} else {
jsonArray.append(item)
}
}
result[key] = jsonArray
} else {
result[key] = value
}
}
return result
}
/// 发送函数调用结果(内部方法)
private func sendFunctionCallResultInternal(
sessionId:String,

Loading…
Cancel
Save