mirror of
https://github.com/jie65535/JChatGPT.git
synced 2025-06-02 17:39:10 +08:00
Optimize search results
Add Weather Service Agent Add Crazy Thursday Agent
This commit is contained in:
parent
f91dbf8a6d
commit
98bcd3785b
@ -22,7 +22,6 @@ val commonTextVersion = "1.13.0"
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.aallam.openai:openai-client:$openaiClientVersion")
|
implementation("com.aallam.openai:openai-client:$openaiClientVersion")
|
||||||
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
||||||
//implementation("io.ktor:ktor-client-okhttp-jvm:$ktorVersion")
|
|
||||||
implementation("org.scilab.forge:jlatexmath:$jLatexMathVersion")
|
implementation("org.scilab.forge:jlatexmath:$jLatexMathVersion")
|
||||||
implementation("org.apache.commons:commons-text:$commonTextVersion")
|
implementation("org.apache.commons:commons-text:$commonTextVersion")
|
||||||
}
|
}
|
@ -7,6 +7,8 @@ import com.aallam.openai.api.model.ModelId
|
|||||||
import com.aallam.openai.client.OpenAI
|
import com.aallam.openai.client.OpenAI
|
||||||
import com.aallam.openai.client.OpenAIHost
|
import com.aallam.openai.client.OpenAIHost
|
||||||
import io.ktor.util.collections.*
|
import io.ktor.util.collections.*
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||||
import net.mamoe.mirai.console.permission.PermissionId
|
import net.mamoe.mirai.console.permission.PermissionId
|
||||||
@ -25,11 +27,13 @@ import net.mamoe.mirai.message.data.MessageSource.Key.quote
|
|||||||
import net.mamoe.mirai.message.sourceIds
|
import net.mamoe.mirai.message.sourceIds
|
||||||
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
||||||
import net.mamoe.mirai.utils.info
|
import net.mamoe.mirai.utils.info
|
||||||
import top.jie65535.mirai.tools.BaseAgent
|
import top.jie65535.mirai.tools.*
|
||||||
import top.jie65535.mirai.tools.WebSearch
|
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.format.TextStyle
|
||||||
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
object JChatGPT : KotlinPlugin(
|
object JChatGPT : KotlinPlugin(
|
||||||
JvmPluginDescription(
|
JvmPluginDescription(
|
||||||
@ -127,7 +131,11 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getSystemPrompt(): String {
|
private fun getSystemPrompt(): String {
|
||||||
return PluginConfig.prompt.replace("{time}", OffsetDateTime.now().toString())
|
val now = OffsetDateTime.now()
|
||||||
|
return PluginConfig.prompt.replace(
|
||||||
|
"{time}",
|
||||||
|
"$now ${now.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.CHINA)}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun MessageEvent.startChat(context: List<ChatMessage>? = null) {
|
private suspend fun MessageEvent.startChat(context: List<ChatMessage>? = null) {
|
||||||
@ -158,7 +166,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
|
|
||||||
for (toolCall in reply.toolCalls.orEmpty()) {
|
for (toolCall in reply.toolCalls.orEmpty()) {
|
||||||
require(toolCall is ToolCall.Function) { "Tool call is not a function" }
|
require(toolCall is ToolCall.Function) { "Tool call is not a function" }
|
||||||
val functionResponse = toolCall.execute()
|
val functionResponse = toolCall.execute(this)
|
||||||
history.add(
|
history.add(
|
||||||
ChatMessage(
|
ChatMessage(
|
||||||
role = ChatRole.Tool,
|
role = ChatRole.Tool,
|
||||||
@ -259,10 +267,12 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 函数映射表
|
* 工具列表
|
||||||
*/
|
*/
|
||||||
private val myTools = listOf<BaseAgent>(
|
private val myTools = listOf(
|
||||||
WebSearch()
|
WebSearch(),
|
||||||
|
WeatherService(),
|
||||||
|
CrazyKfc()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -277,7 +287,8 @@ object JChatGPT : KotlinPlugin(
|
|||||||
val request = ChatCompletionRequest(
|
val request = ChatCompletionRequest(
|
||||||
model = ModelId(PluginConfig.chatModel),
|
model = ModelId(PluginConfig.chatModel),
|
||||||
messages = chatMessages,
|
messages = chatMessages,
|
||||||
tools = availableTools
|
tools = availableTools,
|
||||||
|
toolChoice = ToolChoice.Auto
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"API Requesting..." +
|
"API Requesting..." +
|
||||||
@ -292,13 +303,23 @@ object JChatGPT : KotlinPlugin(
|
|||||||
|
|
||||||
private fun MessageChain.plainText() = this.filterIsInstance<PlainText>().joinToString().trim()
|
private fun MessageChain.plainText() = this.filterIsInstance<PlainText>().joinToString().trim()
|
||||||
|
|
||||||
private suspend fun ToolCall.Function.execute(): String {
|
private suspend fun ToolCall.Function.execute(event: MessageEvent): String {
|
||||||
val agent =
|
val agent = myTools.find { it.tool.function.name == function.name }
|
||||||
myTools.find { it.tool.function.name == function.name } ?: error("Function ${function.name} not found")
|
?: return "Function ${function.name} not found"
|
||||||
val args = function.argumentsAsJson()
|
// 提示正在执行函数
|
||||||
|
val receipt = if (agent.loadingMessage.isNotEmpty()) {
|
||||||
|
event.subject.sendMessage(event.message.quote() + agent.loadingMessage)
|
||||||
|
} else null
|
||||||
|
// 提取参数
|
||||||
|
val args = function.argumentsAsJsonOrNull()
|
||||||
logger.info("Calling ${function.name}(${args})")
|
logger.info("Calling ${function.name}(${args})")
|
||||||
|
// 执行函数
|
||||||
val result = agent.execute(args)
|
val result = agent.execute(args)
|
||||||
logger.info("Result=$result")
|
logger.info("Result=$result")
|
||||||
|
// 过会撤回加载消息
|
||||||
|
if (receipt != null) {
|
||||||
|
launch { delay(2.seconds); receipt.recall() }
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,28 @@
|
|||||||
package top.jie65535.mirai.tools
|
package top.jie65535.mirai.tools
|
||||||
|
|
||||||
import com.aallam.openai.api.chat.Tool
|
import com.aallam.openai.api.chat.Tool
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.okhttp.*
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
|
||||||
abstract class BaseAgent(
|
abstract class BaseAgent(
|
||||||
val tool: Tool
|
val tool: Tool
|
||||||
) {
|
) {
|
||||||
|
/**
|
||||||
|
* 是否启用该工具
|
||||||
|
*/
|
||||||
open val isEnabled: Boolean = true
|
open val isEnabled: Boolean = true
|
||||||
|
|
||||||
abstract suspend fun execute(args: JsonObject): String
|
/**
|
||||||
|
* 加载时消息 可用于提示用户正在执行
|
||||||
|
*/
|
||||||
|
open val loadingMessage: String = ""
|
||||||
|
|
||||||
|
protected val httpClient by lazy {
|
||||||
|
HttpClient(OkHttp)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract suspend fun execute(args: JsonObject?): String
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "${tool.function.name}: ${tool.function.description}"
|
return "${tool.function.name}: ${tool.function.description}"
|
||||||
|
20
src/main/kotlin/tools/CrazyKfc.kt
Normal file
20
src/main/kotlin/tools/CrazyKfc.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package top.jie65535.mirai.tools
|
||||||
|
|
||||||
|
import com.aallam.openai.api.chat.Tool
|
||||||
|
import com.aallam.openai.api.core.Parameters
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
|
||||||
|
class CrazyKfc : BaseAgent(
|
||||||
|
tool = Tool.function(
|
||||||
|
name = "crazyThursday",
|
||||||
|
description = "获取一条KFC疯狂星期四文案",
|
||||||
|
parameters = Parameters.Empty
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
override suspend fun execute(args: JsonObject?): String {
|
||||||
|
val response = httpClient.get("https://api.52vmy.cn/api/wl/yan/kfc")
|
||||||
|
return response.bodyAsText()
|
||||||
|
}
|
||||||
|
}
|
56
src/main/kotlin/tools/WeatherService.kt
Normal file
56
src/main/kotlin/tools/WeatherService.kt
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package top.jie65535.mirai.tools
|
||||||
|
|
||||||
|
import com.aallam.openai.api.chat.Tool
|
||||||
|
import com.aallam.openai.api.core.Parameters
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
|
||||||
|
class WeatherService : BaseAgent(
|
||||||
|
tool = Tool.function(
|
||||||
|
name = "queryWeather",
|
||||||
|
description = "可用于查询某城市地区天气.",
|
||||||
|
parameters = Parameters.buildJsonObject {
|
||||||
|
put("type", "object")
|
||||||
|
putJsonObject("properties") {
|
||||||
|
putJsonObject("city") {
|
||||||
|
put("type", "string")
|
||||||
|
put("description", "城市地区,如\"深圳市\"")
|
||||||
|
}
|
||||||
|
putJsonObject("time_range") {
|
||||||
|
put("type", "string")
|
||||||
|
putJsonArray("enum") {
|
||||||
|
add("day")
|
||||||
|
add("three")
|
||||||
|
add("many")
|
||||||
|
}
|
||||||
|
put("description", "时间范围,仅当天天气可获得最详细信息,三天和更多只能获得简单信息。")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
putJsonArray("required") {
|
||||||
|
add("city")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
override val loadingMessage: String
|
||||||
|
get() = "查询天气中..."
|
||||||
|
|
||||||
|
override suspend fun execute(args: JsonObject?): String {
|
||||||
|
requireNotNull(args)
|
||||||
|
val city = args.getValue("city").jsonPrimitive.content
|
||||||
|
val timeRange = args["time_range"]?.jsonPrimitive?.contentOrNull
|
||||||
|
val response = httpClient.get(
|
||||||
|
buildString {
|
||||||
|
append(when (timeRange) {
|
||||||
|
"many" -> "https://api.52vmy.cn/api/query/tian/many"
|
||||||
|
"three" -> "https://api.52vmy.cn/api/query/tian/three"
|
||||||
|
else -> "https://api.52vmy.cn/api/query/tian"
|
||||||
|
})
|
||||||
|
append("?city=")
|
||||||
|
append(city)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return response.bodyAsText()
|
||||||
|
}
|
||||||
|
}
|
@ -2,25 +2,51 @@ package top.jie65535.mirai.tools
|
|||||||
|
|
||||||
import com.aallam.openai.api.chat.Tool
|
import com.aallam.openai.api.chat.Tool
|
||||||
import com.aallam.openai.api.core.Parameters
|
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.request.*
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.statement.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import top.jie65535.mirai.PluginConfig
|
|
||||||
import org.apache.commons.text.StringEscapeUtils
|
import org.apache.commons.text.StringEscapeUtils
|
||||||
|
import top.jie65535.mirai.PluginConfig
|
||||||
|
|
||||||
class WebSearch : BaseAgent(
|
class WebSearch : BaseAgent(
|
||||||
tool = Tool.function(
|
tool = Tool.function(
|
||||||
name = "search",
|
name = "search",
|
||||||
description = "通过互联网搜索一切",
|
description = "Provides meta-search functionality through SearXNG," +
|
||||||
|
" aggregating results from multiple search engines.",
|
||||||
parameters = Parameters.buildJsonObject {
|
parameters = Parameters.buildJsonObject {
|
||||||
put("type", "object")
|
put("type", "object")
|
||||||
putJsonObject("properties") {
|
putJsonObject("properties") {
|
||||||
putJsonObject("q") {
|
putJsonObject("q") {
|
||||||
put("type", "string")
|
put("type", "string")
|
||||||
put("description", "The search query")
|
put("description", "查询内容关键字")
|
||||||
|
}
|
||||||
|
putJsonObject("categories") {
|
||||||
|
put("type", "array")
|
||||||
|
putJsonObject("items") {
|
||||||
|
put("type", "string")
|
||||||
|
putJsonArray("enum") {
|
||||||
|
add("general")
|
||||||
|
add("images")
|
||||||
|
add("videos")
|
||||||
|
add("news")
|
||||||
|
add("music")
|
||||||
|
add("it")
|
||||||
|
add("science")
|
||||||
|
add("files")
|
||||||
|
add("social_media")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
put("description", "可选择多项查询分类,通常情况下不传或用general即可。")
|
||||||
|
}
|
||||||
|
putJsonObject("time_range") {
|
||||||
|
put("type", "string")
|
||||||
|
putJsonArray("enum") {
|
||||||
|
add("day")
|
||||||
|
add("month")
|
||||||
|
add("year")
|
||||||
|
}
|
||||||
|
put("description", "可选择获取最新消息,例如day表示只查询最近一天相关信息,以此类推。")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
putJsonArray("required") {
|
putJsonArray("required") {
|
||||||
@ -35,16 +61,66 @@ class WebSearch : BaseAgent(
|
|||||||
override val isEnabled: Boolean
|
override val isEnabled: Boolean
|
||||||
get() = PluginConfig.searXngUrl.isNotEmpty()
|
get() = PluginConfig.searXngUrl.isNotEmpty()
|
||||||
|
|
||||||
private val httpClient by lazy {
|
override val loadingMessage: String
|
||||||
HttpClient(OkHttp)
|
get() = "联网搜索中..."
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun execute(args: JsonObject): String {
|
override suspend fun execute(args: JsonObject?): String {
|
||||||
|
requireNotNull(args)
|
||||||
val q = args.getValue("q").jsonPrimitive.content
|
val q = args.getValue("q").jsonPrimitive.content
|
||||||
|
val categories = args["categories"]?.jsonArray
|
||||||
|
val timeRange = args["time_range"]?.jsonPrimitive?.contentOrNull
|
||||||
val response = httpClient.get(
|
val response = httpClient.get(
|
||||||
"${PluginConfig.searXngUrl}?q=${q.encodeURLParameter(true)}&format=json"
|
buildString {
|
||||||
|
append(PluginConfig.searXngUrl)
|
||||||
|
append("?q=")
|
||||||
|
append(q.encodeURLParameter())
|
||||||
|
append("&format=json")
|
||||||
|
if (categories != null) {
|
||||||
|
append("&")
|
||||||
|
append(categories.joinToString { it.jsonPrimitive.content })
|
||||||
|
}
|
||||||
|
if (timeRange != null) {
|
||||||
|
append("&")
|
||||||
|
append(timeRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
val body = response.bodyAsText()
|
val body = response.bodyAsText()
|
||||||
return StringEscapeUtils.unescapeJava(body)
|
val unescapedBody = StringEscapeUtils.unescapeJava(body)
|
||||||
|
val responseJsonElement = Json.parseToJsonElement(unescapedBody)
|
||||||
|
return buildJsonObject {
|
||||||
|
val root = responseJsonElement.jsonObject
|
||||||
|
// 查询内容原样转发
|
||||||
|
root["query"]?.let { put("query", it) }
|
||||||
|
|
||||||
|
// 过滤搜索结果
|
||||||
|
val results = root["results"]?.jsonArray
|
||||||
|
if (results != null) {
|
||||||
|
val filteredResults = results
|
||||||
|
.filter {
|
||||||
|
// 去掉所有内容为空的结果
|
||||||
|
!it.jsonObject.getValue("content").jsonPrimitive.contentOrNull.isNullOrEmpty()
|
||||||
|
}.sortedByDescending {
|
||||||
|
it.jsonObject.getValue("score").jsonPrimitive.double
|
||||||
|
}.take(5) // 只取得分最高的前5条结果
|
||||||
|
.map {
|
||||||
|
// 移除掉我不想要的字段
|
||||||
|
val item = it.jsonObject.toMutableMap()
|
||||||
|
item.remove("engine")
|
||||||
|
item.remove("parsed_url")
|
||||||
|
item.remove("template")
|
||||||
|
item.remove("engines")
|
||||||
|
item.remove("positions")
|
||||||
|
item.remove("metadata")
|
||||||
|
item.remove("thumbnail")
|
||||||
|
JsonObject(item)
|
||||||
|
}
|
||||||
|
put("results", JsonArray(filteredResults))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 答案和信息盒子原样转发
|
||||||
|
root["answers"]?.let { put("answers", it) }
|
||||||
|
root["infoboxes"]?.let { put("infoboxes", it) }
|
||||||
|
}.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user