Add visit web tool

Add send message tools
Update version to v1.7.0
This commit is contained in:
2025-07-11 13:08:06 +08:00
parent 6c034ab2a7
commit 89794b587e
11 changed files with 695 additions and 304 deletions

View File

@@ -3,7 +3,11 @@ package top.jie65535.mirai.tools
import com.aallam.openai.api.chat.Tool
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.serialization.json.JsonObject
import net.mamoe.mirai.event.events.MessageEvent
abstract class BaseAgent(
val tool: Tool
@@ -18,11 +22,27 @@ abstract class BaseAgent(
*/
open val loadingMessage: String = ""
/**
* HTTP客户端
*/
protected val httpClient by lazy {
HttpClient(OkHttp)
}
abstract suspend fun execute(args: JsonObject?): String
/**
* 协程作用域
*/
protected val scope by lazy {
CoroutineScope(Dispatchers.IO + SupervisorJob())
}
open suspend fun execute(args: JsonObject?): String {
return "OK"
}
open suspend fun execute(args: JsonObject?, event: MessageEvent): String {
return execute(args)
}
override fun toString(): String {
return "${tool.function.name}: ${tool.function.description}"

View File

@@ -43,7 +43,7 @@ class ReasoningAgent : BaseAgent(
val answerContent = StringBuilder()
llm.chatCompletions(ChatCompletionRequest(
model = ModelId(PluginConfig.reasoningModel),
messages = listOf(ChatMessage.Companion.User(prompt))
messages = listOf(ChatMessage.User(prompt))
)).collect {
if (it.choices.isNotEmpty()) {
val delta = it.choices[0].delta ?: return@collect

View 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 kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.add
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import kotlinx.serialization.json.putJsonObject
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.message.data.buildForwardMessage
import top.jie65535.mirai.JChatGPT
import top.jie65535.mirai.PluginConfig
import kotlin.collections.getValue
class SendCompositeMessage : BaseAgent(
tool = Tool.function(
name = "sendCompositeMessage",
description = "发送组合消息适合发送较长消息而避免刷屏不支持Markdown",
parameters = Parameters.buildJsonObject {
put("type", "object")
putJsonObject("properties") {
putJsonObject("content") {
put("type", "string")
put("description", "消息内容")
}
}
putJsonArray("required") {
add("content")
}
}
)
) {
override suspend fun execute(args: JsonObject?, event: MessageEvent): String {
requireNotNull(args)
val content = args.getValue("content").jsonPrimitive.content
val msg = JChatGPT.toMessage(event.subject, content)
event.subject.sendMessage(
if (content.length > PluginConfig.messageMergeThreshold) {
event.buildForwardMessage {
event.bot says msg
}
} else {
msg
}
)
return "OK"
}
}

View File

@@ -0,0 +1,33 @@
package top.jie65535.mirai.tools
import com.aallam.openai.api.chat.Tool
import com.aallam.openai.api.core.Parameters
import kotlinx.serialization.json.*
import net.mamoe.mirai.event.events.MessageEvent
import top.jie65535.mirai.JChatGPT
class SendSingleMessageAgent : BaseAgent(
tool = Tool.function(
name = "sendSingleMessage",
description = "发送一条消息适合发送一行以内的短句不支持Markdown",
parameters = Parameters.buildJsonObject {
put("type", "object")
putJsonObject("properties") {
putJsonObject("content") {
put("type", "string")
put("description", "消息内容")
}
}
putJsonArray("required") {
add("content")
}
}
)
) {
override suspend fun execute(args: JsonObject?, event: MessageEvent): String {
requireNotNull(args)
val content = args.getValue("content").jsonPrimitive.content
event.subject.sendMessage(JChatGPT.toMessage(event.subject, content))
return "OK"
}
}

View File

@@ -0,0 +1,12 @@
package top.jie65535.mirai.tools
import com.aallam.openai.api.chat.Tool
import com.aallam.openai.api.core.Parameters
class StopLoopAgent : BaseAgent(
tool = Tool.function(
name = "endConversation",
description = "结束本轮对话",
parameters = Parameters.Empty
)
)

View File

@@ -0,0 +1,71 @@
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.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.serialization.json.*
import top.jie65535.mirai.PluginConfig
class VisitWeb : BaseAgent(
tool = Tool.function(
name = "visit",
description = "Visit webpage(s) and return the summary of the content.",
parameters = Parameters.buildJsonObject {
put("type", "object")
putJsonObject("properties") {
putJsonObject("url") {
putJsonArray("type") {
add("string")
add("array")
}
putJsonObject("items") {
put("type", "string")
}
put("minItems", 1)
put("description", "The URL(s) of the webpage(s) to visit. Can be a single URL or an array of URLs.")
}
}
putJsonArray("required") {
add("url")
}
}
)
) {
companion object {
// Visit Tool (Using Jina Reader)
const val JINA_READER_URL_PREFIX = "https://r.jina.ai/"
}
override val isEnabled: Boolean
get() = PluginConfig.jinaApiKey.isNotEmpty()
override val loadingMessage: String
get() = "访问网页中..."
override suspend fun execute(args: JsonObject?): String {
requireNotNull(args)
val urlJson = args.getValue("url")
if (urlJson is JsonPrimitive) {
return jinaReadPage(urlJson.content)
} else if (urlJson is JsonArray) {
return urlJson.map {
scope.async { jinaReadPage(it.jsonPrimitive.content) }
}.awaitAll().joinToString()
}
return ""
}
private suspend fun jinaReadPage(url: String): String {
return try {
httpClient.get(JINA_READER_URL_PREFIX + url) {
header("Authorization", "Bearer ${PluginConfig.jinaApiKey}")
}.bodyAsText()
} catch (e: Throwable) {
"Error fetching \"$url\": ${e.message}"
}
}
}

View File

@@ -7,6 +7,7 @@ import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.json.*
import org.apache.commons.text.StringEscapeUtils
import top.jie65535.mirai.JChatGPT
import top.jie65535.mirai.PluginConfig
class WebSearch : BaseAgent(
@@ -21,33 +22,6 @@ class WebSearch : BaseAgent(
put("type", "string")
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") {
add("q")
@@ -67,25 +41,17 @@ class WebSearch : BaseAgent(
override suspend fun execute(args: JsonObject?): String {
requireNotNull(args)
val q = args.getValue("q").jsonPrimitive.content
val categories = args["categories"]?.jsonArray
val timeRange = args["time_range"]?.jsonPrimitive?.contentOrNull
val response = httpClient.get(
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 url = buildString {
append(PluginConfig.searXngUrl)
append("?q=")
append(q.encodeURLParameter())
append("&format=json")
}
val response = httpClient.get(url)
JChatGPT.logger.info("Request: $url")
val body = response.bodyAsText()
JChatGPT.logger.info("Response: $body")
val responseJsonElement = Json.parseToJsonElement(body)
val filteredResponse = buildJsonObject {
val root = responseJsonElement.jsonObject