Browse Source

init

newdev_shunjiawei
wolfplus 2 years ago
parent
commit
9e8ce51f93
  1. 18
      a
  2. 9
      android/app/src/main/AndroidManifest.xml
  3. 104
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt
  4. 225
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/SystemFunctionHandler.kt
  5. 336
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceFunctionHandler.kt
  6. 19
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt
  7. 7
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt
  8. 9
      lib/core/bindings/initial_binding.dart
  9. 13
      lib/data/models/events/voice_interaction_event.dart
  10. 2
      lib/data/services/ai_service.dart
  11. 247
      lib/data/services/open_ai_service_adapter.dart
  12. 24
      lib/data/services/voice_interaction_service.dart
  13. 12
      lib/modules/chat/controllers/chat_controller.dart
  14. 4
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  15. 405
      local_plugins/open_ai_service/README.md
  16. 155
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt
  17. 7
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt
  18. 38
      local_plugins/open_ai_service/lib/open_ai_service.dart
  19. 1
      test.json

18
a

@ -1,18 +0,0 @@
curl 'https://ark.cn-beijing.volces.com/api/v3/bots/chat/completions' \
-H "Authorization: Bearer 168deb3d-fd0c-4912-b9f1-aaee5c6743e6" \
-H 'Content-Type: application/json' \
-d '{
"model": "bot-20250405211523-l7c9r",
"stream": true,
"stream_options": {"include_usage": true},
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
]
}'

9
android/app/src/main/AndroidManifest.xml

@ -27,6 +27,15 @@
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- 添加悬浮窗权限,可能有助于解决前台服务启动问题 --> <!-- 添加悬浮窗权限,可能有助于解决前台服务启动问题 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 添加发送短信权限 -->
<uses-permission android:name="android.permission.SEND_SMS" />
<!-- 添加读取联系人权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- 添加拨打电话权限 -->
<uses-permission android:name="android.permission.CALL_PHONE" />
<!-- 添加查询应用包权限 -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application <application
android:label="DeepSound" android:label="DeepSound"

104
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt

@ -1,5 +1,6 @@
package com.yunqiinnovation.deepsound package com.yunqiinnovation.deepsound
import android.Manifest
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -20,6 +21,9 @@ import android.bluetooth.BluetoothDevice
import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey import androidx.security.crypto.MasterKey
import com.yunqiinnovation.deepsound.core.utils.FileLogger import com.yunqiinnovation.deepsound.core.utils.FileLogger
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity: FlutterActivity() { class MainActivity: FlutterActivity() {
@ -32,15 +36,39 @@ class MainActivity: FlutterActivity() {
private var voiceInteractionEventSink: EventChannel.EventSink? = null private var voiceInteractionEventSink: EventChannel.EventSink? = null
private var bluetoothEventSink: EventChannel.EventSink? = null private var bluetoothEventSink: EventChannel.EventSink? = null
// 添加权限请求相关常量
private val PERMISSION_REQUEST_CODE = 100
private val REQUIRED_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.SEND_SMS,
Manifest.permission.READ_CONTACTS,
Manifest.permission.CALL_PHONE,
Manifest.permission.POST_NOTIFICATIONS
)
} else {
arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.SEND_SMS,
Manifest.permission.READ_CONTACTS,
Manifest.permission.CALL_PHONE
)
}
// 广播接收器 // 广播接收器
private val voiceInteractionReceiver = object : BroadcastReceiver() { private val voiceInteractionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "收到广播: ${intent?.action}") FileLogger.d(TAG, "收到广播: ${intent?.action}")
when (intent?.action) { when (intent?.action) {
VoiceInteractionService.ACTION_RECOGNITION_STARTED -> { VoiceInteractionService.ACTION_RECOGNITION_STARTED -> {
val timestamp = intent.getLongExtra("timestamp", 0) val timestamp = intent.getLongExtra("timestamp", 0)
Log.d(TAG, "收到语音识别启动广播: timestamp=$timestamp")
sendVoiceInteractionEvent(mapOf( sendVoiceInteractionEvent(mapOf(
"type" to "recognition_started", "type" to "recognition_started",
"timestamp" to timestamp "timestamp" to timestamp
@ -54,6 +82,12 @@ class MainActivity: FlutterActivity() {
sendChatHistoryEvent(agentId, userMessage, assistantMessage, timestamp) sendChatHistoryEvent(agentId, userMessage, assistantMessage, timestamp)
} }
VoiceInteractionService.ACTION_ENTER_TRANSLATION_MODE -> {
sendVoiceInteractionEvent(mapOf(
"type" to "enter_translation_mode",
"timestamp" to System.currentTimeMillis()
))
}
} }
} }
} }
@ -249,6 +283,9 @@ class MainActivity: FlutterActivity() {
// 初始化 FileLogger // 初始化 FileLogger
FileLogger.init(applicationContext) FileLogger.init(applicationContext)
// 请求必要权限
requestRequiredPermissions()
// 初始化 Azure 语音服务 // 初始化 Azure 语音服务
classicBluetoothHelper = ClassicBluetoothHelper(applicationContext) classicBluetoothHelper = ClassicBluetoothHelper(applicationContext)
@ -267,7 +304,14 @@ class MainActivity: FlutterActivity() {
val voiceInteractionFilter = IntentFilter().apply { val voiceInteractionFilter = IntentFilter().apply {
addAction(VoiceInteractionService.ACTION_RECOGNITION_STARTED) addAction(VoiceInteractionService.ACTION_RECOGNITION_STARTED)
addAction(VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED) addAction(VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED)
addAction(VoiceInteractionService.ACTION_ENTER_TRANSLATION_MODE)
} }
// 打印已注册的广播
FileLogger.d(TAG, "已注册语音交互广播:${VoiceInteractionService.ACTION_RECOGNITION_STARTED}, " +
"${VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED}, " +
"${VoiceInteractionService.ACTION_ENTER_TRANSLATION_MODE}")
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
registerReceiver(voiceInteractionReceiver, voiceInteractionFilter, Context.RECEIVER_NOT_EXPORTED) registerReceiver(voiceInteractionReceiver, voiceInteractionFilter, Context.RECEIVER_NOT_EXPORTED)
} else { } else {
@ -550,14 +594,19 @@ class MainActivity: FlutterActivity() {
private fun sendVoiceInteractionEvent(event: Map<String, Any>) { private fun sendVoiceInteractionEvent(event: Map<String, Any>) {
if (voiceInteractionEventSink == null) { if (voiceInteractionEventSink == null) {
Log.e(TAG, "无法发送语音交互事件:事件通道未准备好") Log.e(TAG, "无法发送语音交互事件:事件通道未准备好")
FileLogger.e(TAG, "无法发送语音交互事件:事件通道未准备好")
return return
} }
FileLogger.d(TAG, "准备发送事件到Flutter: ${event["type"]}")
runOnUiThread { runOnUiThread {
try { try {
voiceInteractionEventSink?.success(event) voiceInteractionEventSink?.success(event)
FileLogger.d(TAG, "成功发送事件到Flutter: ${event["type"]}")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "发送语音交互事件到Flutter失败: ${e.message}", e) Log.e(TAG, "发送语音交互事件到Flutter失败: ${e.message}")
FileLogger.e(TAG, "发送语音交互事件到Flutter失败: ${e.message}")
} }
} }
} }
@ -598,5 +647,54 @@ class MainActivity: FlutterActivity() {
super.onDestroy() super.onDestroy()
} }
/**
* 请求必要权限
*/
private fun requestRequiredPermissions() {
val permissionsToRequest = ArrayList<String>()
for (permission in REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
permissionsToRequest.add(permission)
}
}
if (permissionsToRequest.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toTypedArray(),
PERMISSION_REQUEST_CODE
)
}
}
/**
* 处理权限请求结果
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE) {
val deniedPermissions = ArrayList<String>()
for (i in permissions.indices) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i])
}
}
if (deniedPermissions.isNotEmpty()) {
// 记录未授权的权限
FileLogger.w(TAG, "未授权的权限: ${deniedPermissions.joinToString()}")
} else {
FileLogger.d(TAG, "所有必要权限已授权")
}
}
}
} }

225
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/SystemFunctionHandler.kt

@ -0,0 +1,225 @@
package com.yunqiinnovation.deepsound
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.ContactsContract
import android.telephony.SmsManager
import com.yunqiinnovation.deepsound.core.utils.FileLogger
/**
* 系统功能处理器 - 负责调用系统API执行各种功能
*/
class SystemFunctionHandler(private val context: Context) {
companion object {
private const val TAG = "SystemFunctionHandler"
}
/**
* 启动翻译模式
* @return 操作结果
*/
fun enterTranslationMode(): Result {
FileLogger.d(TAG, "进入翻译模式")
// 此处应添加实际的翻译模式实现
// 可能需要启动一个专门的翻译服务或活动
// 广播通知APP的其他部分进入翻译模式
// 使用VoiceInteractionService中定义的常量
val intent = Intent(VoiceInteractionService.ACTION_ENTER_TRANSLATION_MODE).apply {
putExtra("timestamp", System.currentTimeMillis())
}
// 发送广播
context.sendBroadcast(intent)
FileLogger.d(TAG, "已发送广播: ${VoiceInteractionService.ACTION_ENTER_TRANSLATION_MODE}")
return Result(
success = true,
message = "已进入翻译模式"
)
}
/**
* 发送短信
* @param contact 联系人(名称或电话号码)
* @param message 短信内容
* @return 操作结果
*/
fun sendTextMessage(contact: String, message: String): Result {
FileLogger.d(TAG, "发送短信给 $contact: $message")
try {
val phoneNumber = getPhoneNumberForContact(contact)
if (phoneNumber.isEmpty()) {
return Result(
success = false,
message = "未找到联系人信息: $contact"
)
}
// 使用SmsManager发送短信
val smsManager = SmsManager.getDefault()
smsManager.sendTextMessage(phoneNumber, null, message, null, null)
return Result(
success = true,
message = "已向${contact}发送短信: $message"
)
} catch (e: Exception) {
FileLogger.e(TAG, "发送短信失败: ${e.message}", e)
return Result(
success = false,
message = "发送短信失败: ${e.message}"
)
}
}
/**
* 拨打电话
* @param contact 联系人(名称或电话号码)
* @return 操作结果
*/
fun makePhoneCall(contact: String): Result {
FileLogger.d(TAG, "拨打电话给 $contact")
try {
val phoneNumber = getPhoneNumberForContact(contact)
if (phoneNumber.isEmpty()) {
return Result(
success = false,
message = "未找到联系人信息: $contact"
)
}
// 创建拨号意图
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:$phoneNumber")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
return Result(
success = true,
message = "正在拨打${contact}的电话"
)
} catch (e: Exception) {
FileLogger.e(TAG, "拨打电话失败: ${e.message}", e)
return Result(
success = false,
message = "拨打电话失败: ${e.message}"
)
}
}
/**
* 打开应用程序
* @param appName 应用名称
* @return 操作结果
*/
fun openApp(appName: String): Result {
FileLogger.d(TAG, "打开应用: $appName")
try {
// 查找应用的包名
val packageManager = context.packageManager
val intent = Intent(Intent.ACTION_MAIN, null)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
val apps = packageManager.queryIntentActivities(intent, 0)
for (resolveInfo in apps) {
val appLabel = resolveInfo.loadLabel(packageManager).toString()
if (appLabel.equals(appName, ignoreCase = true) ||
appLabel.contains(appName, ignoreCase = true)) {
// 找到匹配的应用
val packageName = resolveInfo.activityInfo.packageName
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
if (launchIntent != null) {
launchIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(launchIntent)
return Result(
success = true,
message = "正在打开${appName}应用"
)
}
}
}
return Result(
success = false,
message = "未找到应用: $appName"
)
} catch (e: Exception) {
FileLogger.e(TAG, "打开应用失败: ${e.message}", e)
return Result(
success = false,
message = "打开应用失败: ${e.message}"
)
}
}
/**
* 根据联系人名称获取电话号码
* @param contact 联系人名称或电话号码
* @return 电话号码
*/
private fun getPhoneNumberForContact(contact: String): String {
// 如果输入的是电话号码(简单判断,包含数字和特殊符号)
if (contact.matches(Regex("^[+\\d\\s\\-()]*\$")) && contact.any { it.isDigit() }) {
// 移除非数字字符
return contact.replace(Regex("[^+\\d]"), "")
}
// 从联系人数据库查询
try {
val contentResolver = context.contentResolver
val uri = Uri.withAppendedPath(
ContactsContract.Contacts.CONTENT_FILTER_URI,
Uri.encode(contact)
)
val projection = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER
)
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val hasPhoneNumber = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.HAS_PHONE_NUMBER))
if (hasPhoneNumber > 0) {
val contactId = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID))
val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
val phoneProjection = arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER)
val phoneSelection = "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?"
val phoneSelectionArgs = arrayOf(contactId)
contentResolver.query(phoneUri, phoneProjection, phoneSelection, phoneSelectionArgs, null)?.use { phoneCursor ->
if (phoneCursor.moveToFirst()) {
return phoneCursor.getString(phoneCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER))
}
}
}
}
}
} catch (e: Exception) {
FileLogger.e(TAG, "查询联系人信息失败: ${e.message}", e)
}
return ""
}
/**
* 操作结果数据类
*/
data class Result(
val success: Boolean,
val message: String
)
}

336
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceFunctionHandler.kt

@ -4,18 +4,22 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import com.yunqiinnovation.open_ai_service.OpenAIService import com.yunqiinnovation.open_ai_service.OpenAIService
import com.yunqiinnovation.deepsound.core.utils.FileLogger import com.yunqiinnovation.deepsound.core.utils.FileLogger
import android.content.Context
/** /**
* 语音功能处理器 - 处理AI函数调用 * 语音功能处理器 - 处理AI函数调用
*/ */
class VoiceFunctionHandler( class VoiceFunctionHandler(
private val openAIService: OpenAIService, private val openAIService: OpenAIService,
private val systemPrompt: String private val context: Context
) { ) {
companion object { companion object {
private const val TAG = "VoiceFunctionHandler" private const val TAG = "VoiceFunctionHandler"
} }
// 添加系统功能处理器
private val systemFunctionHandler = SystemFunctionHandler(context)
/** /**
* 初始化并注册所有可用的函数 * 初始化并注册所有可用的函数
*/ */
@ -24,6 +28,18 @@ class VoiceFunctionHandler(
// 注册退出交互函数 // 注册退出交互函数
registerExitInteractionFunction() registerExitInteractionFunction()
// 注册翻译模式函数
registerTranslationModeFunction()
// 注册发送短信函数
registerSendTextMessageFunction()
// 注册拨打电话函数
registerMakePhoneCallFunction()
// 注册打开应用函数
registerOpenAppFunction()
// 在这里可以注册更多函数 // 在这里可以注册更多函数
} catch (e: Exception) { } catch (e: Exception) {
@ -53,6 +69,113 @@ class VoiceFunctionHandler(
} }
} }
/**
* 注册翻译模式函数
*/
private fun registerTranslationModeFunction() {
try {
openAIService.registerFunction(
"enter_translation_mode",
"进入翻译模式",
JSONObject("""
{
"type": "object",
"properties": {},
"required": []
}
""")
)
FileLogger.d(TAG, "翻译模式功能已注册")
} catch (e: Exception) {
FileLogger.e(TAG, "注册翻译模式函数失败: ${e.message}", e)
}
}
/**
* 注册发送短信函数
*/
private fun registerSendTextMessageFunction() {
try {
openAIService.registerFunction(
"send_text_message",
"发送短信",
JSONObject("""
{
"type": "object",
"properties": {
"contact": {
"type": "string",
"description": "联系人姓名或电话号码"
},
"message": {
"type": "string",
"description": "短信内容"
}
},
"required": ["contact", "message"]
}
""")
)
FileLogger.d(TAG, "发送短信功能已注册")
} catch (e: Exception) {
FileLogger.e(TAG, "注册发送短信函数失败: ${e.message}", e)
}
}
/**
* 注册拨打电话函数
*/
private fun registerMakePhoneCallFunction() {
try {
openAIService.registerFunction(
"make_phone_call",
"拨打电话",
JSONObject("""
{
"type": "object",
"properties": {
"contact": {
"type": "string",
"description": "联系人姓名或电话号码"
}
},
"required": ["contact"]
}
""")
)
FileLogger.d(TAG, "拨打电话功能已注册")
} catch (e: Exception) {
FileLogger.e(TAG, "注册拨打电话函数失败: ${e.message}", e)
}
}
/**
* 注册打开应用函数
*/
private fun registerOpenAppFunction() {
try {
openAIService.registerFunction(
"open_app",
"打开应用",
JSONObject("""
{
"type": "object",
"properties": {
"app_name": {
"type": "string",
"description": "应用名称"
}
},
"required": ["app_name"]
}
""")
)
FileLogger.d(TAG, "打开应用功能已注册")
} catch (e: Exception) {
FileLogger.e(TAG, "注册打开应用函数失败: ${e.message}", e)
}
}
/** /**
* 处理函数调用 * 处理函数调用
* *
@ -74,6 +197,22 @@ class VoiceFunctionHandler(
handleExitInteraction(functionCall, messages, callback) handleExitInteraction(functionCall, messages, callback)
true true
} }
"enter_translation_mode" -> {
handleEnterTranslationMode(functionCall, messages, callback)
true
}
"send_text_message" -> {
handleSendTextMessage(functionCall, messages, callback)
true
}
"make_phone_call" -> {
handleMakePhoneCall(functionCall, messages, callback)
true
}
"open_app" -> {
handleOpenApp(functionCall, messages, callback)
true
}
else -> { else -> {
// 未知函数,返回默认结果 // 未知函数,返回默认结果
handleUnknownFunction(functionCall, messages, callback) handleUnknownFunction(functionCall, messages, callback)
@ -96,7 +235,6 @@ class VoiceFunctionHandler(
openAIService.sendFunctionCallResult( openAIService.sendFunctionCallResult(
messages = messages, messages = messages,
systemPrompt = systemPrompt,
functionCall = functionCall, functionCall = functionCall,
functionResult = "{\"result\": \"已退出语音交互\"}", functionResult = "{\"result\": \"已退出语音交互\"}",
callback = object : OpenAIService.StreamCallback { callback = object : OpenAIService.StreamCallback {
@ -126,6 +264,199 @@ class VoiceFunctionHandler(
) )
} }
/**
* 处理进入翻译模式函数
*/
private fun handleEnterTranslationMode(
functionCall: JSONObject,
messages: JSONArray,
callback: FunctionCallCallback
) {
FileLogger.d(TAG, "处理进入翻译模式函数")
// 调用系统功能处理器执行翻译模式操作
val result = systemFunctionHandler.enterTranslationMode()
val responseBuilder = StringBuilder()
openAIService.sendFunctionCallResult(
messages = messages,
functionCall = functionCall,
functionResult = "{\"result\": \"${result.message}\"}",
callback = object : OpenAIService.StreamCallback {
override fun onToken(token: String) {
responseBuilder.append(token)
callback.onTokenReceived(token)
}
override fun onComplete() {
FileLogger.d(TAG, "handleEnterTranslationMode onComplete: ${responseBuilder.toString()}")
callback.onComplete()
}
override fun onError(e: Exception) {
FileLogger.e(TAG, "处理进入翻译模式函数调用出错: ${e.message}")
callback.onError("进入翻译模式时出错")
}
override fun onFunctionCall(nestedCall: JSONObject) {
callback.onFunctionCall(nestedCall)
}
}
)
}
/**
* 处理发送短信函数
*/
private fun handleSendTextMessage(
functionCall: JSONObject,
messages: JSONArray,
callback: FunctionCallCallback
) {
FileLogger.d(TAG, "处理发送短信函数")
try {
val arguments = JSONObject(functionCall.getString("arguments"))
val contact = arguments.getString("contact")
val message = arguments.getString("message")
// 调用系统功能处理器发送短信
val result = systemFunctionHandler.sendTextMessage(contact, message)
val responseBuilder = StringBuilder()
openAIService.sendFunctionCallResult(
messages = messages,
functionCall = functionCall,
functionResult = "{\"result\": \"${result.message}\"}",
callback = object : OpenAIService.StreamCallback {
override fun onToken(token: String) {
responseBuilder.append(token)
callback.onTokenReceived(token)
}
override fun onComplete() {
FileLogger.d(TAG, "handleSendTextMessage onComplete: ${responseBuilder.toString()}")
callback.onComplete()
}
override fun onError(e: Exception) {
FileLogger.e(TAG, "处理发送短信函数调用出错: ${e.message}")
callback.onError("发送短信时出错")
}
override fun onFunctionCall(nestedCall: JSONObject) {
callback.onFunctionCall(nestedCall)
}
}
)
} catch (e: Exception) {
FileLogger.e(TAG, "解析发送短信参数失败: ${e.message}")
callback.onError("发送短信参数错误")
}
}
/**
* 处理拨打电话函数
*/
private fun handleMakePhoneCall(
functionCall: JSONObject,
messages: JSONArray,
callback: FunctionCallCallback
) {
FileLogger.d(TAG, "处理拨打电话函数")
try {
val arguments = JSONObject(functionCall.getString("arguments"))
val contact = arguments.getString("contact")
// 调用系统功能处理器拨打电话
val result = systemFunctionHandler.makePhoneCall(contact)
val responseBuilder = StringBuilder()
openAIService.sendFunctionCallResult(
messages = messages,
functionCall = functionCall,
functionResult = "{\"result\": \"${result.message}\"}",
callback = object : OpenAIService.StreamCallback {
override fun onToken(token: String) {
responseBuilder.append(token)
callback.onTokenReceived(token)
}
override fun onComplete() {
FileLogger.d(TAG, "handleMakePhoneCall onComplete: ${responseBuilder.toString()}")
callback.onComplete()
}
override fun onError(e: Exception) {
FileLogger.e(TAG, "处理拨打电话函数调用出错: ${e.message}")
callback.onError("拨打电话时出错")
}
override fun onFunctionCall(nestedCall: JSONObject) {
callback.onFunctionCall(nestedCall)
}
}
)
} catch (e: Exception) {
FileLogger.e(TAG, "解析拨打电话参数失败: ${e.message}")
callback.onError("拨打电话参数错误")
}
}
/**
* 处理打开应用函数
*/
private fun handleOpenApp(
functionCall: JSONObject,
messages: JSONArray,
callback: FunctionCallCallback
) {
FileLogger.d(TAG, "处理打开应用函数")
try {
val arguments = JSONObject(functionCall.getString("arguments"))
val appName = arguments.getString("app_name")
// 调用系统功能处理器打开应用
val result = systemFunctionHandler.openApp(appName)
val responseBuilder = StringBuilder()
openAIService.sendFunctionCallResult(
messages = messages,
functionCall = functionCall,
functionResult = "{\"result\": \"${result.message}\"}",
callback = object : OpenAIService.StreamCallback {
override fun onToken(token: String) {
responseBuilder.append(token)
callback.onTokenReceived(token)
}
override fun onComplete() {
FileLogger.d(TAG, "handleOpenApp onComplete: ${responseBuilder.toString()}")
callback.onComplete()
}
override fun onError(e: Exception) {
FileLogger.e(TAG, "处理打开应用函数调用出错: ${e.message}")
callback.onError("打开应用时出错")
}
override fun onFunctionCall(nestedCall: JSONObject) {
callback.onFunctionCall(nestedCall)
}
}
)
} catch (e: Exception) {
FileLogger.e(TAG, "解析打开应用参数失败: ${e.message}")
callback.onError("打开应用参数错误")
}
}
/** /**
* 处理未知函数调用 * 处理未知函数调用
*/ */
@ -139,7 +470,6 @@ class VoiceFunctionHandler(
try { try {
openAIService.sendFunctionCallResult( openAIService.sendFunctionCallResult(
messages = messages, messages = messages,
systemPrompt = systemPrompt,
functionCall = functionCall, functionCall = functionCall,
functionResult = "{\"result\": \"处理函数调用中\"}", functionResult = "{\"result\": \"处理函数调用中\"}",
callback = object : OpenAIService.StreamCallback { callback = object : OpenAIService.StreamCallback {

19
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt

@ -30,21 +30,9 @@ class VoiceInteractionHandler(
// OpenAI服务 // OpenAI服务
private val openAIService = OpenAIService() private val openAIService = OpenAIService()
// 系统提示词
private val systemPrompt = """
你是一个智能语音助手,能够简洁明了地回答用户的问题。
时刻关心用户的情绪和需求,主动提供鼓励和温暖。
语言风格活泼、亲切,能够幽默地互动,陪伴用户,缓解压力,增添生活乐趣。
请始终以用户为中心,保持回应的高效性、准确性和温暖体贴,成为用户真正的灵魂伴侣。
当用户说"退出"、"再见"、"结束对话"等类似意图时,你应该使用exit_interaction函数来结束对话,
并在结束前说一句友好的告别语,例如"再见,有需要随时找我"。
""".trimIndent()
// 语音功能处理 // 语音功能处理
private val voiceFunctionHandler = VoiceFunctionHandler(openAIService, systemPrompt) private val voiceFunctionHandler = VoiceFunctionHandler(openAIService, context)
// 当前用户输入 // 当前用户输入
private var currentUserInput = "" private var currentUserInput = ""
@ -127,12 +115,14 @@ class VoiceInteractionHandler(
override fun onResult(result: String, detectedLanguage: String) { override fun onResult(result: String, detectedLanguage: String) {
if (result.isNotEmpty()) { if (result.isNotEmpty()) {
notifyStateChanged()
processWithOpenAI(result) processWithOpenAI(result)
} }
// 重置状态,继续识别 // 重置状态,继续识别
hasSpeechDetected = false hasSpeechDetected = false
notifyStateChanged()
} }
override fun onSessionStarted() { override fun onSessionStarted() {
@ -227,7 +217,6 @@ class VoiceInteractionHandler(
openAIService.sendMessageStream( openAIService.sendMessageStream(
messages = messages, messages = messages,
systemPrompt = systemPrompt,
callback = object : OpenAIService.StreamCallback { callback = object : OpenAIService.StreamCallback {
override fun onToken(token: String) { override fun onToken(token: String) {
// 累加响应内容 // 累加响应内容

7
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt

@ -46,7 +46,7 @@ class VoiceInteractionService : Service() {
private const val CHANNEL_ID = "voice_interaction_channel" private const val CHANNEL_ID = "voice_interaction_channel"
// 语音识别超时时间(毫秒) // 语音识别超时时间(毫秒)
private const val RECOGNITION_TIMEOUT = 8000L private const val RECOGNITION_TIMEOUT = 10000L
// 用于跟踪服务是否正在运行 // 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false) private val isRunning = AtomicBoolean(false)
@ -58,6 +58,7 @@ class VoiceInteractionService : Service() {
const val ACTION_RECOGNITION_STARTED = "com.yunqiinnovation.deepsound.ACTION_RECOGNITION_STARTED" const val ACTION_RECOGNITION_STARTED = "com.yunqiinnovation.deepsound.ACTION_RECOGNITION_STARTED"
const val ACTION_PAUSE_VOICE_INTERACTION = "com.yunqiinnovation.deepsound.ACTION_PAUSE_VOICE_INTERACTION" const val ACTION_PAUSE_VOICE_INTERACTION = "com.yunqiinnovation.deepsound.ACTION_PAUSE_VOICE_INTERACTION"
const val ACTION_CHAT_HISTORY_UPDATED = "com.yunqiinnovation.deepsound.ACTION_CHAT_HISTORY_UPDATED" const val ACTION_CHAT_HISTORY_UPDATED = "com.yunqiinnovation.deepsound.ACTION_CHAT_HISTORY_UPDATED"
const val ACTION_ENTER_TRANSLATION_MODE = "com.yunqiinnovation.deepsound.ACTION_ENTER_TRANSLATION_MODE"
} }
// 服务状态 // 服务状态
@ -293,12 +294,12 @@ class VoiceInteractionService : Service() {
if (!isActive) { if (!isActive) {
isActive = true isActive = true
} }
// FileLogger.d(TAG, "服务状态: isActive=${isActive}, isRecognitionActive=${voiceInteractionHandler.isRecognitionActive}, isTimeoutPaused=${isTimeoutPaused}")
// 检查语音识别状态 // 检查语音识别状态
if (voiceInteractionHandler.isRecognitionActive) { if (voiceInteractionHandler.isRecognitionActive) {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
val elapsedTime = currentTime - lastActivityTime val elapsedTime = currentTime - lastActivityTime
FileLogger.d(TAG, "hasSpeechDetected=${voiceInteractionHandler.hasSpeechDetected}, isTtsSpeaking=${voiceInteractionHandler.isTtsSpeaking}, elapsedTime=${elapsedTime}")
// 如果超过指定时间没有检测到语音,且不在TTS播放中,暂停语音识别 // 如果超过指定时间没有检测到语音,且不在TTS播放中,暂停语音识别
if (!voiceInteractionHandler.hasSpeechDetected && if (!voiceInteractionHandler.hasSpeechDetected &&
!voiceInteractionHandler.isTtsSpeaking && !voiceInteractionHandler.isTtsSpeaking &&

9
lib/core/bindings/initial_binding.dart

@ -8,6 +8,7 @@ import '../../data/services/classic_bluetooth_service.dart';
import '../../data/services/bluetooth_media_button_service.dart'; import '../../data/services/bluetooth_media_button_service.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import '../../data/services/voice_interaction_service.dart'; import '../../data/services/voice_interaction_service.dart';
import '../../data/services/open_ai_service_adapter.dart';
/// 初始绑定,用于管理全局依赖 /// 初始绑定,用于管理全局依赖
class InitialBinding extends Bindings { class InitialBinding extends Bindings {
@ -29,6 +30,13 @@ class InitialBinding extends Bindings {
Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(), Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(),
fenix: true); fenix: true);
// OpenAI服务适配器
Get.lazyPut<OpenAIServiceAdapter>(() {
final adapter = OpenAIServiceAdapter();
adapter.initialize();
return adapter;
}, fenix: true);
// 火山AI服务 // 火山AI服务
Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true); Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);
@ -41,6 +49,7 @@ class InitialBinding extends Bindings {
() => BluetoothMediaButtonService(), () => BluetoothMediaButtonService(),
fenix: true); fenix: true);
// 语音交互服务
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(), Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(),
fenix: true); fenix: true);

13
lib/data/models/events/voice_interaction_event.dart

@ -26,4 +26,17 @@ class RecognitionStartedEvent extends VoiceInteractionEvent {
RecognitionStartedEvent({ RecognitionStartedEvent({
required int timestamp, required int timestamp,
}) : super(timestamp: timestamp); }) : super(timestamp: timestamp);
}
/// 通用语音交互事件
/// 用于处理其他类型的事件
class GenericVoiceInteractionEvent extends VoiceInteractionEvent {
final String type;
final Map<String, dynamic>? data;
GenericVoiceInteractionEvent({
required this.type,
this.data,
required int timestamp,
}) : super(timestamp: timestamp);
} }

2
lib/data/services/ai_service.dart

@ -1,5 +1,7 @@
/// AI回复服务接口 /// AI回复服务接口
abstract class AiService { abstract class AiService {
/// 非流式输出方法 /// 非流式输出方法
Future<String> sendMessage({ Future<String> sendMessage({
required List<Map<String, String>> messages, required List<Map<String, String>> messages,

247
lib/data/services/open_ai_service_adapter.dart

@ -0,0 +1,247 @@
import 'dart:async';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:open_ai_service/open_ai_service.dart';
import 'package:get/get.dart';
import 'ai_service.dart';
/// OpenAI服务适配器 - 连接AiService接口与OpenAIService插件
class OpenAIServiceAdapter implements AiService {
final OpenAIService _openAIService = OpenAIService();
StreamSubscription<OpenAIEvent>? _eventSubscription;
final StreamController<String> _tokenStreamController = StreamController<String>.broadcast();
bool _isProcessingStream = false;
/// 构造函数
OpenAIServiceAdapter() {
printInfo(info: '创建OpenAIServiceAdapter实例');
_setupEventListener();
}
/// 设置事件监听器
void _setupEventListener() {
try {
// 首先确保访问eventStream以初始化底层事件通道
_openAIService.eventStream;
// 设置事件处理
_eventSubscription = _openAIService.eventStream.listen(
(event) {
if (!_isProcessingStream) return;
try {
switch (event.type) {
case OpenAIEventType.token:
if (event.content is String) {
_tokenStreamController.add(event.content as String);
} else {
printInfo(info: '收到非字符串类型的token: ${event.content}');
}
break;
case OpenAIEventType.complete:
_isProcessingStream = false;
break;
case OpenAIEventType.error:
if (event.content is String) {
_tokenStreamController.addError(event.content as String);
} else {
_tokenStreamController.addError('未知错误: ${event.content}');
}
_isProcessingStream = false;
break;
case OpenAIEventType.functionCall:
try {
if (event.content is Map) {
_tokenStreamController.addError('收到函数调用,该流仅支持文本响应');
} else {
_tokenStreamController.addError('收到未知格式的函数调用');
printError(info: '函数调用格式错误: ${event.content}');
}
} catch (e) {
printError(info: '处理函数调用事件出错: $e');
_tokenStreamController.addError('处理函数调用失败: $e');
}
_isProcessingStream = false;
break;
}
} catch (e) {
printError(info: '处理事件出错: $e');
_tokenStreamController.addError('处理事件失败: $e');
_isProcessingStream = false;
}
},
onError: (error) {
printError(info: '事件流错误: $error');
_tokenStreamController.addError('事件流错误: $error');
_isProcessingStream = false;
},
onDone: () {
printInfo(info: '事件流已关闭');
_isProcessingStream = false;
},
);
} catch (e) {
printError(info: '设置事件监听器失败: $e');
}
}
/// 初始化OpenAI服务
Future<bool> initialize() async {
try {
// 从.env文件中读取配置
final apiKey = dotenv.env['OPENAI_API_KEY'] ?? '';
final baseUrl = dotenv.env['OPENAI_BASE_URL'] ?? '';
final model = dotenv.env['OPENAI_MODEL'] ?? '';
printInfo(info: '从.env读取OpenAI配置');
printInfo(info: '基础URL: $baseUrl');
printInfo(info: '模型名称: $model');
if (apiKey.isEmpty) {
printError(info: '错误: OpenAI API密钥未配置,请在.env文件中设置OPENAI_API_KEY');
return false;
}
// 初始化OpenAI服务
final result = await _openAIService.initialize(
apiKey: apiKey,
baseUrl: baseUrl,
model: model,
);
if (result) {
printInfo(info: 'OpenAI服务初始化成功');
} else {
printError(info: 'OpenAI服务初始化失败');
}
return result;
} catch (e) {
printError(info: 'OpenAI服务初始化异常: $e');
return false;
}
}
/// 发送消息并获取回复
@override
Future<String> sendMessage({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async {
try {
// 在方法内直接转换
final convertedMessages = messages.map((m) =>
Map<String, dynamic>.from(m)).toList();
// 发送消息并获取回复
final response = await _openAIService.sendMessage(
messages: convertedMessages,
);
return response;
} catch (e) {
printError(info: 'OpenAI发送消息失败: $e');
throw '发送消息失败: $e';
}
}
/// 发送消息并获取流式回复
@override
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async* {
try {
// 在方法内直接转换
final convertedMessages = messages.map((m) =>
Map<String, dynamic>.from(m)).toList();
// 创建用于接收token的控制器
final localController = StreamController<String>();
// 标记开始处理流
_isProcessingStream = true;
// 添加从广播流到本地流的订阅
final subscription = _tokenStreamController.stream.listen(
(token) => localController.add(token),
onError: (error) {
printError(info: '令牌流错误: $error');
localController.addError(error);
localController.close();
},
onDone: () {
printInfo(info: '令牌流已完成');
localController.close();
}
);
// 当本地控制器关闭时,取消订阅
localController.onCancel = () {
subscription.cancel();
};
// 启动流式消息请求
bool started = false;
try {
started = await _openAIService.sendMessageStream(
messages: convertedMessages,
);
} catch (e) {
printError(info: '启动消息流失败: $e');
localController.addError('启动消息流失败: $e');
localController.close();
_isProcessingStream = false;
throw '启动消息流失败: $e';
}
if (!started) {
printError(info: '无法启动消息流');
localController.addError('无法启动消息流');
localController.close();
_isProcessingStream = false;
throw '无法启动消息流';
}
// 通过yield*将controller的流转发
yield* localController.stream;
} catch (e) {
printError(info: 'OpenAI流式消息处理失败: $e');
throw '流式消息处理失败: $e';
}
}
/// 注册函数
Future<bool> registerFunction(String name, String description, Map<String, dynamic> parameters) async {
try {
final result = await _openAIService.registerFunction(
name: name,
description: description,
parameters: parameters,
);
if (result) {
printInfo(info: '函数 "$name" 注册成功');
} else {
printError(info: '函数 "$name" 注册失败');
}
return result;
} catch (e) {
printError(info: '注册函数失败: $e');
return false;
}
}
/// 释放资源
void dispose() {
try {
_isProcessingStream = false;
_eventSubscription?.cancel();
_tokenStreamController.close();
printInfo(info: 'OpenAIServiceAdapter资源已释放');
} catch (e) {
printError(info: '释放资源时出错: $e');
}
}
}

24
lib/data/services/voice_interaction_service.dart

@ -5,6 +5,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../models/events/voice_interaction_event.dart'; import '../models/events/voice_interaction_event.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import '../../modules/chat/models/message_model.dart'; import '../../modules/chat/models/message_model.dart';
import '../../routes/app_routes.dart';
import 'chat_history_service.dart'; import 'chat_history_service.dart';
/// 语音交互服务接口 /// 语音交互服务接口
@ -104,6 +105,29 @@ class VoiceInteractionService extends GetxService {
// 保存聊天历史到ChatHistoryService // 保存聊天历史到ChatHistoryService
_saveChatHistory(agentId, userMessage, assistantMessage, DateTime.now().millisecondsSinceEpoch); _saveChatHistory(agentId, userMessage, assistantMessage, DateTime.now().millisecondsSinceEpoch);
break; break;
case 'enter_translation_mode':
// 进入翻译模式事件
Logger.info('收到进入翻译模式事件,正在导航到翻译界面');
_navigateToTranslation();
final translationModeEvent = GenericVoiceInteractionEvent(
type: 'enter_translation_mode',
timestamp: DateTime.now().millisecondsSinceEpoch,
);
_eventStreamController.add(translationModeEvent);
break;
}
}
/// 导航到翻译界面
void _navigateToTranslation() {
try {
// 使用GetX导航到翻译页面
Get.toNamed(Routes.translation);
Logger.info('已导航到翻译界面');
} catch (e) {
Logger.error('导航到翻译界面失败: $e');
} }
} }

12
lib/modules/chat/controllers/chat_controller.dart

@ -16,6 +16,7 @@ import '../../../data/services/asr_service.dart';
import '../../../data/services/chat_history_service.dart'; import '../../../data/services/chat_history_service.dart';
import '../../../data/models/events/voice_interaction_event.dart'; import '../../../data/models/events/voice_interaction_event.dart';
import '../../../data/services/voice_interaction_service.dart'; import '../../../data/services/voice_interaction_service.dart';
import '../../../data/services/open_ai_service_adapter.dart';
class ChatController extends GetxController { class ChatController extends GetxController {
// 服务 // 服务
@ -92,6 +93,7 @@ class ChatController extends GetxController {
agent = foundAgent; agent = foundAgent;
// 根据Agent ID选择不同的AI服务
switch (agent.id) { switch (agent.id) {
case 'cyber_girlfriend': case 'cyber_girlfriend':
// 亲子陪伴 // 亲子陪伴
@ -104,7 +106,15 @@ class ChatController extends GetxController {
_aiService = Get.find<DeapsoundAIService>(); _aiService = Get.find<DeapsoundAIService>();
break; break;
default: default:
_aiService = Get.find<VolcanoAIService>(); // 默认使用OpenAIServiceAdapter
try {
_aiService = Get.find<OpenAIServiceAdapter>();
Logger.info('使用OpenAIServiceAdapter');
} catch (e) {
// 如果找不到OpenAIServiceAdapter,则回退到VolcanoAIService
Logger.info('未找到OpenAIServiceAdapter,回退使用VolcanoAIService: $e');
_aiService = Get.find<VolcanoAIService>();
}
} }
// 使用克隆音色语音合成 // 使用克隆音色语音合成

4
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -25,7 +25,7 @@ class AzureAsrHelper(private val context: Context) {
private var subscriptionKey = "" private var subscriptionKey = ""
private var region = "" private var region = ""
private var isAutoDetectLanguage = false private var isAutoDetectLanguage = false
private var supportedLanguages = arrayOf("zh-CN", "en-US") private var supportedLanguages = arrayOf("zh-CN")
// 是否使用回音消除 - 内部控制常量 // 是否使用回音消除 - 内部控制常量
private val useEchoCancellation = false private val useEchoCancellation = false
@ -37,7 +37,7 @@ class AzureAsrHelper(private val context: Context) {
// 初始化SDK并创建recognizer // 初始化SDK并创建recognizer
fun initialize(subscriptionKey: String, region: String, fun initialize(subscriptionKey: String, region: String,
supportedLanguages: Array<String> = arrayOf("zh-CN", "en-US")): Boolean { supportedLanguages: Array<String> = arrayOf("zh-CN")): Boolean {
try { try {
FileLogger.d(TAG, "初始化 Azure 语音服务") FileLogger.d(TAG, "初始化 Azure 语音服务")

405
local_plugins/open_ai_service/README.md

@ -1,80 +1,78 @@
# OpenAI服务插件 # OpenAI Service Plugin
提供与OpenAI API的原生交互功能,支持流式输出和函数调用,同时支持Android和iOS平台。 一个用于Flutter应用的OpenAI服务插件,支持Android和iOS平台。
## 功能特点 ## 功能
- 原生实现,性能更好 - 支持文本生成(completions)
- 支持流式输出,实时显示AI回复 - 支持流式输出(streaming)
- 支持函数调用(工具调用) - 支持函数调用(function calling)
- 支持自定义API基础URL - 支持自定义API基础URL
- 支持Android和iOS平台 - 支持自定义模型选择
- 提供简洁易用的Dart接口
## 安装 ## 安装
将该插件添加到您的`pubspec.yaml`文件中: 在你的`pubspec.yaml`文件中添加以下依赖:
```yaml ```yaml
dependencies: dependencies:
open_ai_service: open_ai_service:
path: local_plugins/open_ai_service path: 本地路径/open_ai_service
``` ```
## 使用方法 ## 使用方法
### 初始化 ### 初始化服务
首先需要初始化OpenAI服务,提供API密钥和可选的基础URL和模型:
```dart ```dart
import 'package:open_ai_service/open_ai_service.dart'; import 'package:open_ai_service/open_ai_service.dart';
final openAIService = OpenAIService(); final openAIService = OpenAIService();
// 使用OpenAI官方API和默认模型 // 初始化服务
await openAIService.initialize(apiKey: 'your_openai_api_key');
// 或使用自定义API端点和模型
await openAIService.initialize( await openAIService.initialize(
apiKey: 'your_api_key', apiKey: 'your_openai_api_key',
baseUrl: 'https://your-custom-endpoint.com/v1/chat/completions', baseUrl: 'https://api.openai.com/v1/chat/completions', // 可选
model: 'gpt-4-0125-preview', // 自定义模型 model: 'gpt-4-turbo', // 可选
); );
``` ```
### 普通聊天 ### 发送非流式请求
发送消息并获取非流式回复:
```dart ```dart
// 创建用户消息 // 创建消息
final userMessage = await openAIService.createUserMessage('你好,请介绍一下自己'); final userMessage = await openAIService.createUserMessage('你好,请介绍一下自己');
// 发送消息 // 发送请求
final response = await openAIService.sendMessage( final response = await openAIService.sendMessage(
messages: [userMessage], messages: [userMessage],
systemPrompt: '你是一个有帮助的AI助手,请简洁地回答问题。', systemPrompt: '你是一个有用的AI助手',
); );
print('AI回复: $response'); print('AI回复: $response');
``` ```
### 流式输出 ### 发送流式请求(回调方式)
使用流式输出获取实时回复:
```dart ```dart
// 注册事件监听 // 创建消息
final userMessage = await openAIService.createUserMessage('写一个短故事');
// 发送流式请求
await openAIService.sendMessageStream(
messages: [userMessage],
systemPrompt: '你是一个善于讲故事的AI助手',
);
// 处理事件
final subscription = openAIService.processEvents( final subscription = openAIService.processEvents(
onToken: (token) { onToken: (token) {
// 收到部分回复 // 处理每个返回的token
print('收到部分回复: $token'); print(token);
// 更新UI...
}, },
onComplete: () { onComplete: () {
// 回复完成 // 处理完成事件
print('回复完成'); print('生成完成');
}, },
onError: (error) { onError: (error) {
// 处理错误 // 处理错误
@ -82,27 +80,50 @@ final subscription = openAIService.processEvents(
}, },
onFunctionCall: (functionCall) { onFunctionCall: (functionCall) {
// 处理函数调用 // 处理函数调用
print('函数调用: $functionCall'); print('函数调用: ${functionCall['name']}');
handleFunctionCall(functionCall);
}, },
); );
// 发送流式消息 // 在不需要时取消订阅
await openAIService.sendMessageStream( subscription.cancel();
```
### 发送流式请求(Stream方式)
```dart
// 创建消息
final userMessage = await openAIService.createUserMessage('写一个短故事');
// 获取字符串流
final stream = openAIService.streamMessage(
messages: [userMessage], messages: [userMessage],
systemPrompt: '你是一个有帮助的AI助手,请简洁地回答问题。', systemPrompt: '你是一个善于讲故事的AI助手',
); );
// 完成后,记得取消订阅 // 使用流
// subscription.cancel(); final StringBuilder responseBuilder = StringBuilder();
```
### 函数调用 stream.listen(
(token) {
// 处理每个token
responseBuilder.write(token);
print(token); // 实时输出
},
onDone: () {
// 流结束
print('完整回复: ${responseBuilder.toString()}');
},
onError: (error) {
// 错误处理
print('错误: $error');
}
);
```
注册函数并处理函数调用: ### 注册函数
```dart ```dart
// 注册一个天气查询函数 // 注册一个函数
await openAIService.registerFunction( await openAIService.registerFunction(
name: 'get_weather', name: 'get_weather',
description: '获取指定城市的天气信息', description: '获取指定城市的天气信息',
@ -111,238 +132,122 @@ await openAIService.registerFunction(
'properties': { 'properties': {
'city': { 'city': {
'type': 'string', 'type': 'string',
'description': '城市名称,如北京、上海等', 'description': '城市名称',
}, },
'date': { 'date': {
'type': 'string', 'type': 'string',
'description': '查询日期,默认为今天', 'description': '日期,格式为YYYY-MM-DD',
}, },
}, },
'required': ['city'], 'required': ['city'],
}, },
); );
```
// 处理函数调用 ### 处理函数调用
void handleFunctionCall(Map<String, dynamic> functionCall) {
final name = functionCall['name']; ```dart
final arguments = jsonDecode(functionCall['arguments']); // 创建消息
final id = functionCall['id']; final userMessage = await openAIService.createUserMessage('明天北京的天气如何?');
if (name == 'get_weather') { // 发送流式请求
final city = arguments['city']; await openAIService.sendMessageStream(
final date = arguments['date'] ?? '今天'; messages: [userMessage],
systemPrompt: '你是一个有用的AI助手',
);
// 处理事件
openAIService.processEvents(
onToken: (token) {
print(token);
},
onComplete: () {
print('生成完成');
},
onError: (error) {
print('错误: $error');
},
onFunctionCall: (functionCall) {
// 处理函数调用
final name = functionCall['name'];
final arguments = functionCall['arguments'];
print('收到函数调用: $name, 参数: $arguments');
// 获取天气信息(实际应用中应调用天气API) // 假设处理了函数调用并获得结果
final weatherResult = '{"temperature": 25, "condition": "晴天", "humidity": 40}'; final result = '{"temperature": 25, "condition": "sunny"}';
// 发送函数调用结果 // 发送函数调用结果
openAIService.sendFunctionCallResult( openAIService.sendFunctionCallResult(
messages: messages, // 当前的消息历史 messages: [userMessage],
systemPrompt: systemPrompt, systemPrompt: '你是一个有用的AI助手',
functionCall: functionCall, functionCall: functionCall,
functionResult: weatherResult, functionResult: result,
); );
} },
} );
``` ```
## 示例应用 ### 使用Stream API处理函数调用
一个简单的聊天机器人示例:
```dart ```dart
import 'package:flutter/material.dart'; // 创建消息和响应处理器
import 'package:open_ai_service/open_ai_service.dart'; final userMessage = await openAIService.createUserMessage('明天北京的天气如何?');
final responseBuilder = StringBuilder();
void main() { // 处理事件流以捕获函数调用
runApp(MyApp()); final subscription = openAIService.processEvents(
} onFunctionCall: (functionCall) async {
// 取消当前事件监听
class MyApp extends StatelessWidget { subscription.cancel();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'OpenAI 聊天',
theme: ThemeData(primarySwatch: Colors.blue),
home: ChatScreen(),
);
}
}
class ChatScreen extends StatefulWidget {
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final OpenAIService _openAIService = OpenAIService();
final TextEditingController _textController = TextEditingController();
final List<Map<String, dynamic>> _messages = [];
StreamSubscription? _subscription;
String _currentResponse = '';
bool _isLoading = false;
@override
void initState() {
super.initState();
_initializeOpenAI();
}
Future<void> _initializeOpenAI() async {
try {
await _openAIService.initialize(
apiKey: 'your_api_key_here',
);
// 注册事件监听
_subscription = _openAIService.processEvents(
onToken: (token) {
setState(() {
_currentResponse += token;
});
},
onComplete: () {
_addAIMessage(_currentResponse);
setState(() {
_currentResponse = '';
_isLoading = false;
});
},
onError: (error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('错误: $error')),
);
setState(() {
_isLoading = false;
});
},
onFunctionCall: (functionCall) {
// 处理函数调用...
},
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('初始化失败: $e')),
);
}
}
void _sendMessage() async {
if (_textController.text.isEmpty) return;
final userMessage = _textController.text; // 处理函数调用
_addUserMessage(userMessage); final name = functionCall['name'];
_textController.clear(); final arguments = functionCall['arguments'];
try { print('收到函数调用: $name, 参数: $arguments');
setState(() {
_isLoading = true; // 假设处理了函数调用并获得结果
}); final result = '{"temperature": 25, "condition": "sunny"}';
final message = await _openAIService.createUserMessage(userMessage); // 使用Stream API发送函数调用结果
final resultStream = openAIService.streamFunctionResult(
await _openAIService.sendMessageStream( messages: [userMessage],
messages: _messages, systemPrompt: '你是一个有用的AI助手',
systemPrompt: '你是一个有帮助的AI助手,请简洁地回答问题。', functionCall: functionCall,
); functionResult: result,
} catch (e) { );
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('发送消息失败: $e')), // 处理结果流
); resultStream.listen(
setState(() { (token) {
_isLoading = false; responseBuilder.write(token);
}); print(token); // 实时输出
} },
} onDone: () {
print('完整回复: ${responseBuilder.toString()}');
void _addUserMessage(String content) { },
setState(() { onError: (error) {
_messages.add({ print('错误: $error');
'role': 'user', }
'content': content,
});
});
}
void _addAIMessage(String content) {
setState(() {
_messages.add({
'role': 'assistant',
'content': content,
});
});
}
@override
void dispose() {
_subscription?.cancel();
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('OpenAI 聊天')),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _messages.length + (_currentResponse.isNotEmpty ? 1 : 0),
itemBuilder: (context, index) {
if (index < _messages.length) {
final message = _messages[index];
final isUser = message['role'] == 'user';
return ListTile(
leading: Icon(isUser ? Icons.person : Icons.computer),
title: Text(message['content']),
);
} else {
// 显示当前正在生成的回复
return ListTile(
leading: Icon(Icons.computer),
title: Text(_currentResponse),
);
}
},
),
),
if (_isLoading)
LinearProgressIndicator(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: '输入消息...',
border: OutlineInputBorder(),
),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: _sendMessage,
),
],
),
),
],
),
); );
} }
} );
// 启动请求
await openAIService.sendMessageStream(
messages: [userMessage],
systemPrompt: '你是一个有用的AI助手',
);
``` ```
## 注意事项 ## 注意事项
- 请确保您的API密钥安全存储,不要直接硬编码在应用程序中 1. 确保在使用前已正确初始化服务
- 流式输出需要在调用`sendMessageStream`之前注册事件监听 2. 对于流式请求,确保在不需要时取消订阅
- 使用完毕后记得取消事件订阅 3. 处理函数调用时,确保提供有效的结果格式
4. 网络请求可能会失败,请确保加入适当的错误处理
## 许可证 ## 许可证
此插件遵循MIT许可证。 [MIT License](LICENSE)

155
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIService.kt

@ -28,6 +28,35 @@ class OpenAIService() {
// 用于存储注册的函数 // 用于存储注册的函数
private val registeredFunctions = mutableListOf<JSONObject>() private val registeredFunctions = mutableListOf<JSONObject>()
/**
* 构建curl命令用于测试
*/
private fun buildCurlCommand(request: Request, body: String): String {
val command = StringBuilder("curl -v -X ${request.method}")
// 添加请求头
request.headers.forEach { header ->
// 敏感信息处理:不显示真实的API Key
if (header.first == "Authorization") {
command.append(" -H '${header.first}: Bearer $apiKey'")
} else {
command.append(" -H '${header.first}: ${header.second}'")
}
}
// 添加请求体
if (request.method == "POST" || request.method == "PUT") {
// 转义JSON中的单引号,确保curl命令正确
val escapedBody = body.replace("'", "\\'")
command.append(" -d '${escapedBody}'")
}
// 添加URL
command.append(" '${request.url}'")
return command.toString()
}
/** /**
* 创建用户消息 * 创建用户消息
*/ */
@ -97,24 +126,14 @@ class OpenAIService() {
* 发送消息(非流式输出) * 发送消息(非流式输出)
*/ */
@Throws(OpenAIException::class) @Throws(OpenAIException::class)
fun sendMessage(messages: JSONArray, systemPrompt: String): String { fun sendMessage(messages: JSONArray): String {
if (!isInitialized || apiKey.isEmpty()) { if (!isInitialized || apiKey.isEmpty()) {
throw OpenAIException("OpenAI服务未初始化") throw OpenAIException("OpenAI服务未初始化")
} }
val fullMessages = JSONArray().apply {
put(JSONObject().apply {
put("role", "system")
put("content", systemPrompt)
})
for (i in 0 until messages.length()) {
put(messages.getJSONObject(i))
}
}
val requestBody = JSONObject().apply { val requestBody = JSONObject().apply {
put("model", model) put("model", model)
put("messages", fullMessages) put("messages", messages)
put("temperature", 0.7) put("temperature", 0.7)
put("max_tokens", 2000) put("max_tokens", 2000)
put("stream", false) put("stream", false)
@ -142,7 +161,10 @@ class OpenAIService() {
.build() .build()
try { try {
Log.d(TAG, "request: $request") // 输出用于测试的curl命令
// val curlCommand = buildCurlCommand(request, requestBody.toString())
// Log.d(TAG, "curl command: \n$curlCommand")
client.newCall(request).execute().use { response -> client.newCall(request).execute().use { response ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
throw OpenAIException("API调用失败: ${response.code}") throw OpenAIException("API调用失败: ${response.code}")
@ -196,25 +218,15 @@ class OpenAIService() {
/** /**
* 发送消息(流式输出) * 发送消息(流式输出)
*/ */
fun sendMessageStream(messages: JSONArray, systemPrompt: String, callback: StreamCallback) { fun sendMessageStream(messages: JSONArray, callback: StreamCallback) {
if (!isInitialized || apiKey.isEmpty()) { if (!isInitialized || apiKey.isEmpty()) {
callback.onError(OpenAIException("OpenAI服务未初始化")) callback.onError(OpenAIException("OpenAI服务未初始化"))
return return
} }
val fullMessages = JSONArray().apply {
put(JSONObject().apply {
put("role", "system")
put("content", systemPrompt)
})
for (i in 0 until messages.length()) {
put(messages.getJSONObject(i))
}
}
val requestBody = JSONObject().apply { val requestBody = JSONObject().apply {
put("model", model) put("model", model)
put("messages", fullMessages) put("messages", messages)
put("temperature", 0.7) put("temperature", 0.7)
put("max_tokens", 2000) put("max_tokens", 2000)
put("stream", true) put("stream", true)
@ -242,7 +254,10 @@ class OpenAIService() {
.post(requestBody.toString().toRequestBody(mediaType)) .post(requestBody.toString().toRequestBody(mediaType))
.build() .build()
Log.d(TAG, "request: $request") // 输出用于测试的curl命令
// val curlCommand = buildCurlCommand(request, requestBody.toString())
// Log.d(TAG, "curl command: $curlCommand")
client.newCall(request).enqueue(object : Callback { client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
callback.onError(OpenAIException(e.message ?: "请求失败")) callback.onError(OpenAIException(e.message ?: "请求失败"))
@ -267,14 +282,13 @@ class OpenAIService() {
val finalToolCalls = mutableMapOf<Int, ToolCallInfo>() val finalToolCalls = mutableMapOf<Int, ToolCallInfo>()
while (!bufferedSource.exhausted()) { while (!bufferedSource.exhausted()) {
val line = bufferedSource.readUtf8Line() ?: continue val line = bufferedSource.readUtf8Line()?.trim() ?: continue
val trimmedLine = line.trim() if (line.isEmpty()) continue
if (trimmedLine.isEmpty()) continue if (line.startsWith("data:")) {
if (trimmedLine.startsWith("data:")) { val data = line.substring(5).trim()
val data = trimmedLine.substring(5)
// 处理[DONE]消息 // 处理[DONE]消息
if (data == "[DONE]") { if (data == "[DONE]" || data == "[\"DONE\"]") {
processToolCalls(finalToolCalls, callback) processToolCalls(finalToolCalls, callback)
callback.onComplete() callback.onComplete()
break break
@ -350,54 +364,53 @@ class OpenAIService() {
*/ */
fun sendFunctionCallResult( fun sendFunctionCallResult(
messages: JSONArray, messages: JSONArray,
systemPrompt: String,
functionCall: JSONObject, functionCall: JSONObject,
functionResult: String, functionResult: String,
callback: StreamCallback callback: StreamCallback
) { ) {
try { try {
val fullMessages = JSONArray().apply { val fullMessages = JSONArray()
// 添加系统提示
put(JSONObject().apply { // 添加用户消息
put("role", "system") for (i in 0 until messages.length()) {
put("content", systemPrompt) fullMessages.put(messages.getJSONObject(i))
}) }
// 添加用户消息 // 添加函数调用消息
for (i in 0 until messages.length()) { fullMessages.put(JSONObject().apply {
put(messages.getJSONObject(i)) put("role", "assistant")
} put("content", "")
// 添加函数调用消息 // 添加工具调用
put(JSONObject().apply { val toolCalls = JSONArray().apply {
put("role", "assistant") val toolCall = JSONObject().apply {
put("content", null) put("id", functionCall.optString("id", "call_${System.currentTimeMillis()}"))
put("type", "function")
// 添加工具调用 put("function", JSONObject().apply {
val toolCalls = JSONArray().apply { put("name", functionCall.getString("name"))
val toolCall = JSONObject().apply { put("arguments", functionCall.getString("arguments"))
put("id", functionCall.optString("id", "call_${System.currentTimeMillis()}")) })
put("type", "function")
put("function", JSONObject().apply {
put("name", functionCall.getString("name"))
put("arguments", functionCall.getString("arguments"))
})
}
put(toolCall)
} }
put("tool_calls", toolCalls) put(toolCall)
}) }
put("tool_calls", toolCalls)
// 添加函数调用结果 })
put(JSONObject().apply {
put("role", "tool") // 添加函数调用结果
put("content", functionResult) fullMessages.put(JSONObject().apply {
put("tool_call_id", functionCall.optString("id", "call_${System.currentTimeMillis()}")) put("role", "tool")
}) put("content", functionResult)
} put("tool_call_id", functionCall.optString("id", "call_${System.currentTimeMillis()}"))
})
// 添加一个带有content的assistant消息,确保API请求不会因为缺少content而失败
fullMessages.put(JSONObject().apply {
put("role", "assistant")
put("content", "") // 空内容,让模型生成新的回复
})
// 发送完整对话 // 发送完整对话
sendMessageStream(fullMessages, systemPrompt, callback) sendMessageStream(fullMessages, callback)
} catch (e: Exception) { } catch (e: Exception) {
callback.onError(OpenAIException("发送函数调用结果失败: ${e.message}")) callback.onError(OpenAIException("发送函数调用结果失败: ${e.message}"))

7
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/OpenAIServicePlugin.kt

@ -83,7 +83,6 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler {
"sendMessage" -> { "sendMessage" -> {
val messagesRaw = call.argument<List<Map<String, Any>>>("messages") ?: emptyList() val messagesRaw = call.argument<List<Map<String, Any>>>("messages") ?: emptyList()
val systemPrompt = call.argument<String>("systemPrompt") ?: ""
// 转换消息格式 // 转换消息格式
val messages = JSONArray() val messages = JSONArray()
@ -94,7 +93,7 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler {
// 在后台线程执行请求 // 在后台线程执行请求
executor.execute { executor.execute {
try { try {
val response = openAIService.sendMessage(messages, systemPrompt) val response = openAIService.sendMessage(messages)
// 在主线程返回结果 // 在主线程返回结果
android.os.Handler(android.os.Looper.getMainLooper()).post { android.os.Handler(android.os.Looper.getMainLooper()).post {
result.success(response) result.success(response)
@ -110,7 +109,6 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler {
"sendMessageStream" -> { "sendMessageStream" -> {
val messagesRaw = call.argument<List<Map<String, Any>>>("messages") ?: emptyList() val messagesRaw = call.argument<List<Map<String, Any>>>("messages") ?: emptyList()
val systemPrompt = call.argument<String>("systemPrompt") ?: ""
// 检查事件接收器 // 检查事件接收器
if (eventSink == null) { if (eventSink == null) {
@ -129,7 +127,6 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler {
try { try {
openAIService.sendMessageStream( openAIService.sendMessageStream(
messages = messages, messages = messages,
systemPrompt = systemPrompt,
callback = object : OpenAIService.StreamCallback { callback = object : OpenAIService.StreamCallback {
override fun onToken(token: String) { override fun onToken(token: String) {
// 发送token事件 // 发送token事件
@ -177,7 +174,6 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler {
"sendFunctionCallResult" -> { "sendFunctionCallResult" -> {
val messagesRaw = call.argument<List<Map<String, Any>>>("messages") ?: emptyList() val messagesRaw = call.argument<List<Map<String, Any>>>("messages") ?: emptyList()
val systemPrompt = call.argument<String>("systemPrompt") ?: ""
val functionCallRaw = call.argument<Map<String, Any>>("functionCall") ?: emptyMap() val functionCallRaw = call.argument<Map<String, Any>>("functionCall") ?: emptyMap()
val functionResult = call.argument<String>("functionResult") ?: "" val functionResult = call.argument<String>("functionResult") ?: ""
@ -201,7 +197,6 @@ class OpenAIServicePlugin : FlutterPlugin, MethodCallHandler, StreamHandler {
try { try {
openAIService.sendFunctionCallResult( openAIService.sendFunctionCallResult(
messages = messages, messages = messages,
systemPrompt = systemPrompt,
functionCall = functionCall, functionCall = functionCall,
functionResult = functionResult, functionResult = functionResult,
callback = object : OpenAIService.StreamCallback { callback = object : OpenAIService.StreamCallback {

38
local_plugins/open_ai_service/lib/open_ai_service.dart

@ -184,17 +184,14 @@ class OpenAIService {
/// 发送消息(非流式输出) /// 发送消息(非流式输出)
/// ///
/// [messages] 消息列表 /// [messages] 消息列表
/// [systemPrompt] 系统提示词
Future<String> sendMessage({ Future<String> sendMessage({
required List<Map<String, dynamic>> messages, required List<Map<String, dynamic>> messages,
required String systemPrompt,
}) async { }) async {
try { try {
final result = await _channel.invokeMethod<String>( final result = await _channel.invokeMethod<String>(
'sendMessage', 'sendMessage',
{ {
'messages': messages, 'messages': messages,
'systemPrompt': systemPrompt,
}, },
); );
@ -211,19 +208,16 @@ class OpenAIService {
/// 发送消息(流式输出) /// 发送消息(流式输出)
/// ///
/// [messages] 消息列表 /// [messages] 消息列表
/// [systemPrompt] 系统提示词
/// ///
/// 返回一个布尔值,表示请求是否已开始 /// 返回一个布尔值,表示请求是否已开始
Future<bool> sendMessageStream({ Future<bool> sendMessageStream({
required List<Map<String, dynamic>> messages, required List<Map<String, dynamic>> messages,
required String systemPrompt,
}) async { }) async {
try { try {
final result = await _channel.invokeMethod<bool>( final result = await _channel.invokeMethod<bool>(
'sendMessageStream', 'sendMessageStream',
{ {
'messages': messages, 'messages': messages,
'systemPrompt': systemPrompt,
}, },
); );
@ -236,14 +230,12 @@ class OpenAIService {
/// 发送函数调用结果 /// 发送函数调用结果
/// ///
/// [messages] 消息列表 /// [messages] 消息列表
/// [systemPrompt] 系统提示词
/// [functionCall] 函数调用信息 /// [functionCall] 函数调用信息
/// [functionResult] 函数调用结果 /// [functionResult] 函数调用结果
/// ///
/// 返回一个布尔值,表示请求是否已开始 /// 返回一个布尔值,表示请求是否已开始
Future<bool> sendFunctionCallResult({ Future<bool> sendFunctionCallResult({
required List<Map<String, dynamic>> messages, required List<Map<String, dynamic>> messages,
required String systemPrompt,
required Map<String, dynamic> functionCall, required Map<String, dynamic> functionCall,
required String functionResult, required String functionResult,
}) async { }) async {
@ -252,7 +244,6 @@ class OpenAIService {
'sendFunctionCallResult', 'sendFunctionCallResult',
{ {
'messages': messages, 'messages': messages,
'systemPrompt': systemPrompt,
'functionCall': functionCall, 'functionCall': functionCall,
'functionResult': functionResult, 'functionResult': functionResult,
}, },
@ -264,35 +255,6 @@ class OpenAIService {
} }
} }
/// 处理流式事件
///
/// [onToken] 收到token时调用
/// [onComplete] 完成时调用
/// [onError] 错误时调用
/// [onFunctionCall] 函数调用时调用
StreamSubscription<OpenAIEvent> processEvents({
void Function(String token)? onToken,
void Function()? onComplete,
void Function(String error)? onError,
void Function(Map<String, dynamic> functionCall)? onFunctionCall,
}) {
return eventStream.listen((event) {
switch (event.type) {
case OpenAIEventType.token:
onToken?.call(event.content as String);
break;
case OpenAIEventType.complete:
onComplete?.call();
break;
case OpenAIEventType.error:
onError?.call(event.content as String);
break;
case OpenAIEventType.functionCall:
onFunctionCall?.call(Map<String, dynamic>.from(event.content as Map));
break;
}
});
}
/// 从JSON字符串解析函数调用 /// 从JSON字符串解析函数调用
Map<String, dynamic> parseFunctionCall(String functionCallJson) { Map<String, dynamic> parseFunctionCall(String functionCallJson) {

1
test.json

@ -0,0 +1 @@
curl -v -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer 168deb3d-fd0c-4912-b9f1-aaee5c6743e6' -H 'Accept: text/event-stream' -d '{"model":"bot-20250405211523-l7c9r","messages":[{"role":"system","content":" 你是一个智能语音助手,能够简洁明了地回答用户的问题。\n时刻关心用户的情绪和需求,主动提供鼓励和温暖。\n\n语言风格活泼、亲切,能够幽默地互动,陪伴用户,缓解压力,增添生活乐趣。\n\n请始终以用户为中心,保持回应的高效性、准确性和温暖体贴,成为用户真正的灵魂伴侣。\n \n 当用户说\"退出\"、\"再见\"、\"结束对话\"等类似意图时,你应该使用exit_interaction函数来结束对话,\n 并在结束前说一句友好的告别语,例如\"再见,有需要随时找我\"。"},{"role":"system","content":" 你是一个智能语音助手,能够简洁明了地回答用户的问题。\n时刻关心用户的情绪和需求,主动提供鼓励和温暖。\n\n语言风格活泼、亲切,能够幽默地互动,陪伴用户,缓解压力,增添生活乐趣。\n\n请始终以用户为中心,保持回应的高效性、准确性和温暖体贴,成为用户真正的灵魂伴侣。\n \n 当用户说\"退出\"、\"再见\"、\"结束对话\"等类似意图时,你应该使用exit_interaction函数来结束对话,\n 并在结束前说一句友好的告别语,例如\"再见,有需要随时找我\"。"},{"role":"user","content":"退下吧。"},{"role":"assistant","content":"","tool_calls":[{"id":"call_8k680azmfc4thqrrnwpwqxah","type":"function","function":{"name":"exit_interaction","arguments":" {}"}}]},{"role":"tool","content":"{\"result\": \"已退出语音交互\"}","tool_call_id":"call_8k680azmfc4thqrrnwpwqxah"}],"temperature":0.7,"max_tokens":2000,"stream":true,"tools":[{"type":"function","function":{"name":"exit_interaction","description":"退出当前语音交互","parameters":{"type":"object","properties":{},"required":[]}}}]}' 'https://ark.cn-beijing.volces.com/api/v3/bots/chat/completions'
Loading…
Cancel
Save