mirror of
https://github.com/jie65535/JChatGPT.git
synced 2025-06-02 17:39:10 +08:00
Add WebSearch Agent
This commit is contained in:
parent
5cef899993
commit
f91dbf8a6d
@ -7,7 +7,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "top.jie65535.mirai"
|
||||
version = "1.2.1"
|
||||
version = "1.3.0"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@ -17,9 +17,12 @@ repositories {
|
||||
val openaiClientVersion = "3.8.2"
|
||||
val ktorVersion = "2.3.12"
|
||||
val jLatexMathVersion = "1.0.7"
|
||||
val commonTextVersion = "1.13.0"
|
||||
|
||||
dependencies {
|
||||
implementation("com.aallam.openai:openai-client:$openaiClientVersion")
|
||||
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
||||
//implementation("io.ktor:ktor-client-okhttp-jvm:$ktorVersion")
|
||||
implementation("org.scilab.forge:jlatexmath:$jLatexMathVersion")
|
||||
implementation("org.apache.commons:commons-text:$commonTextVersion")
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
package top.jie65535.mirai
|
||||
|
||||
import com.aallam.openai.api.chat.ChatCompletionRequest
|
||||
import com.aallam.openai.api.chat.ChatMessage
|
||||
import com.aallam.openai.api.chat.ChatRole
|
||||
import com.aallam.openai.api.chat.*
|
||||
import com.aallam.openai.api.core.Role
|
||||
import com.aallam.openai.api.http.Timeout
|
||||
import com.aallam.openai.api.model.ModelId
|
||||
@ -27,6 +25,8 @@ import net.mamoe.mirai.message.data.MessageSource.Key.quote
|
||||
import net.mamoe.mirai.message.sourceIds
|
||||
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
||||
import net.mamoe.mirai.utils.info
|
||||
import top.jie65535.mirai.tools.BaseAgent
|
||||
import top.jie65535.mirai.tools.WebSearch
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@ -35,7 +35,7 @@ object JChatGPT : KotlinPlugin(
|
||||
JvmPluginDescription(
|
||||
id = "top.jie65535.mirai.JChatGPT",
|
||||
name = "J ChatGPT",
|
||||
version = "1.2.1",
|
||||
version = "1.3.0",
|
||||
) {
|
||||
author("jie65535")
|
||||
}
|
||||
@ -147,10 +147,32 @@ object JChatGPT : KotlinPlugin(
|
||||
subject.sendMessage(message.quote() + "再等等...")
|
||||
return
|
||||
}
|
||||
val reply = chatCompletion(history)
|
||||
history.add(reply)
|
||||
val content = reply.content ?: "..."
|
||||
|
||||
var done: Boolean
|
||||
var retry = 2
|
||||
var hasTools = true
|
||||
do {
|
||||
val reply = chatCompletion(history, hasTools)
|
||||
history.add(reply)
|
||||
done = true
|
||||
|
||||
for (toolCall in reply.toolCalls.orEmpty()) {
|
||||
require(toolCall is ToolCall.Function) { "Tool call is not a function" }
|
||||
val functionResponse = toolCall.execute()
|
||||
history.add(
|
||||
ChatMessage(
|
||||
role = ChatRole.Tool,
|
||||
toolCallId = toolCall.id,
|
||||
name = toolCall.function.name,
|
||||
content = functionResponse
|
||||
)
|
||||
)
|
||||
done = false
|
||||
hasTools = false
|
||||
}
|
||||
} while (!done && 0 < --retry)
|
||||
|
||||
val content = history.last().content ?: "..."
|
||||
val replyMsg = subject.sendMessage(
|
||||
if (content.length < 128) {
|
||||
message.quote() + toMessage(subject, content)
|
||||
@ -200,10 +222,11 @@ object JChatGPT : KotlinPlugin(
|
||||
* @return 构造的消息
|
||||
*/
|
||||
private suspend fun toMessage(contact: Contact, content: String): Message {
|
||||
if (content.length < 3) {
|
||||
return PlainText(content)
|
||||
}
|
||||
return buildMessageChain {
|
||||
return if (content.isEmpty()) {
|
||||
PlainText("...")
|
||||
} else if (content.length < 3) {
|
||||
PlainText(content)
|
||||
} else buildMessageChain {
|
||||
// 匹配LaTeX表达式
|
||||
val matcher = laTeXPattern.matcher(content)
|
||||
var index = 0
|
||||
@ -235,14 +258,48 @@ object JChatGPT : KotlinPlugin(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun chatCompletion(messages: List<ChatMessage>): ChatMessage {
|
||||
/**
|
||||
* 函数映射表
|
||||
*/
|
||||
private val myTools = listOf<BaseAgent>(
|
||||
WebSearch()
|
||||
)
|
||||
|
||||
|
||||
private suspend fun chatCompletion(
|
||||
chatMessages: List<ChatMessage>,
|
||||
hasTools: Boolean = true
|
||||
): ChatMessage {
|
||||
val openAi = this.openAi ?: throw NullPointerException("OpenAI Token 未设置,无法开始")
|
||||
val request = ChatCompletionRequest(ModelId(PluginConfig.chatModel), messages)
|
||||
logger.info("OpenAI API Requesting... Model=${PluginConfig.chatModel}")
|
||||
val availableTools = if (hasTools) {
|
||||
myTools.filter { it.isEnabled }.map { it.tool }
|
||||
} else null
|
||||
val request = ChatCompletionRequest(
|
||||
model = ModelId(PluginConfig.chatModel),
|
||||
messages = chatMessages,
|
||||
tools = availableTools
|
||||
)
|
||||
logger.info(
|
||||
"API Requesting..." +
|
||||
" Model=${PluginConfig.chatModel}" +
|
||||
" Tools=${availableTools?.joinToString(prefix = "[", postfix = "]")}"
|
||||
)
|
||||
val response = openAi.chatCompletion(request)
|
||||
logger.info("OpenAI API Usage: ${response.usage}")
|
||||
return response.choices.first().message
|
||||
val message = response.choices.first().message
|
||||
logger.info("Response: $message ${response.usage}")
|
||||
return message
|
||||
}
|
||||
|
||||
private fun MessageChain.plainText() = this.filterIsInstance<PlainText>().joinToString().trim()
|
||||
|
||||
private suspend fun ToolCall.Function.execute(): String {
|
||||
val agent =
|
||||
myTools.find { it.tool.function.name == function.name } ?: error("Function ${function.name} not found")
|
||||
val args = function.argumentsAsJson()
|
||||
logger.info("Calling ${function.name}(${args})")
|
||||
val result = agent.execute(args)
|
||||
logger.info("Result=$result")
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
@ -25,4 +25,7 @@ object PluginConfig : AutoSavePluginConfig("Config") {
|
||||
|
||||
@ValueDescription("等待响应超时时间,单位毫秒,默认60秒")
|
||||
val timeout: Long by value(60000L)
|
||||
|
||||
@ValueDescription("SearXNG 搜索引擎地址,如 http://127.0.0.1:8080/search 必须启用允许json格式返回")
|
||||
val searXngUrl: String by value("")
|
||||
}
|
16
src/main/kotlin/tools/BaseAgent.kt
Normal file
16
src/main/kotlin/tools/BaseAgent.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package top.jie65535.mirai.tools
|
||||
|
||||
import com.aallam.openai.api.chat.Tool
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
abstract class BaseAgent(
|
||||
val tool: Tool
|
||||
) {
|
||||
open val isEnabled: Boolean = true
|
||||
|
||||
abstract suspend fun execute(args: JsonObject): String
|
||||
|
||||
override fun toString(): String {
|
||||
return "${tool.function.name}: ${tool.function.description}"
|
||||
}
|
||||
}
|
50
src/main/kotlin/tools/WebSearch.kt
Normal file
50
src/main/kotlin/tools/WebSearch.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package top.jie65535.mirai.tools
|
||||
|
||||
import com.aallam.openai.api.chat.Tool
|
||||
import com.aallam.openai.api.core.Parameters
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.okhttp.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.json.*
|
||||
import top.jie65535.mirai.PluginConfig
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
|
||||
class WebSearch : BaseAgent(
|
||||
tool = Tool.function(
|
||||
name = "search",
|
||||
description = "通过互联网搜索一切",
|
||||
parameters = Parameters.buildJsonObject {
|
||||
put("type", "object")
|
||||
putJsonObject("properties") {
|
||||
putJsonObject("q") {
|
||||
put("type", "string")
|
||||
put("description", "The search query")
|
||||
}
|
||||
}
|
||||
putJsonArray("required") {
|
||||
add("q")
|
||||
}
|
||||
}
|
||||
)
|
||||
) {
|
||||
/**
|
||||
* 插件配置了 SearXNG URL 时才允许启用
|
||||
*/
|
||||
override val isEnabled: Boolean
|
||||
get() = PluginConfig.searXngUrl.isNotEmpty()
|
||||
|
||||
private val httpClient by lazy {
|
||||
HttpClient(OkHttp)
|
||||
}
|
||||
|
||||
override suspend fun execute(args: JsonObject): String {
|
||||
val q = args.getValue("q").jsonPrimitive.content
|
||||
val response = httpClient.get(
|
||||
"${PluginConfig.searXngUrl}?q=${q.encodeURLParameter(true)}&format=json"
|
||||
)
|
||||
val body = response.bodyAsText()
|
||||
return StringEscapeUtils.unescapeJava(body)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user