Compare commits

..

6 Commits

Author SHA1 Message Date
e79bcd9983 Update tool list
Disable EPIC Free game tool
Fix the issue where invalid JSON parameters caused exceptions
2025-08-21 13:57:03 +08:00
b6aa638b1a Add DashScope API Config 2025-08-21 13:55:21 +08:00
bc2eb437e6 Update VisualAgent tool name to imageRecognition 2025-08-21 13:55:08 +08:00
b534606bb0 Update runCode tool description 2025-08-21 13:54:46 +08:00
867d9ad56f Update http client timeout 2025-08-21 13:54:20 +08:00
92a6879cc1 Add imageEdit tool 2025-08-21 13:54:02 +08:00
6 changed files with 147 additions and 22 deletions

View File

@@ -344,8 +344,7 @@ object JChatGPT : KotlinPlugin(
val toolCallTasks = mutableListOf<Deferred<ChatMessage>>()
// 处理聊天流式响应
responseFlow.collect { chunk ->
val delta = chunk.choices[0].delta
if (delta == null) return@collect
val delta = chunk.choices[0].delta ?: return@collect
// 处理内容更新
if (delta.content != null) {
@@ -408,6 +407,7 @@ object JChatGPT : KotlinPlugin(
// 移除思考内容
val responseContent = responseMessageBuilder?.replace(thinkRegex, "")?.trim()
logger.info("LLM Response: $responseContent")
// 记录AI回答
history.add(ChatMessage.Assistant(
content = responseContent,
@@ -440,11 +440,14 @@ object JChatGPT : KotlinPlugin(
if (!done) {
history.add(ChatMessage.User(
buildString {
append("系统提示:本次运行还剩${retry-1}")
appendLine("系统提示:本次运行最多还剩${retry-1}")
appendLine("如果要多次发言,可以一次性调用多次发言工具。")
appendLine("如果没有什么要做的,可以提前结束。")
appendLine("当前时间:" + dateTimeFormatter.format(OffsetDateTime.now()))
val newMessages = getAfterHistory(startedAt, event)
if (newMessages.isNotEmpty()) {
append("\n以下是上次运行至今的新消息\n\n$newMessages")
append("以下是上次运行至今的新消息\n\n$newMessages")
}
}
))
@@ -535,12 +538,12 @@ object JChatGPT : KotlinPlugin(
// 构造消息链
buildMessageChain {
var index = 0
for ((range, msg) in t.sortedBy { it.range.start }) {
if (index < range.start) {
append(content, index, range.start)
for ((range, msg) in t.sortedBy { it.range.first }) {
if (index < range.first) {
append(content, index, range.first)
}
append(msg)
index = range.endInclusive + 1
index = range.last + 1
}
// 拼接后续消息
if (index < content.length) {
@@ -560,15 +563,15 @@ object JChatGPT : KotlinPlugin(
// 发送组合消息
SendCompositeMessage(),
// 结束循环
StopLoopAgent(),
// 记忆代理
MemoryAppend(),
// 记忆修改
MemoryReplace(),
// 结束循环
StopLoopAgent(),
// 网页搜索
WebSearch(),
@@ -584,11 +587,14 @@ object JChatGPT : KotlinPlugin(
// 视觉代理
VisualAgent(),
// 图像编辑模型
ImageEdit(),
// 天气服务
WeatherService(),
// Epic 免费游戏
EpicFreeGame(),
// EpicFreeGame(),
// 群管代理
GroupManageAgent(),
@@ -666,15 +672,15 @@ object JChatGPT : KotlinPlugin(
val receipt = if (agent.loadingMessage.isNotEmpty()) {
event.subject.sendMessage(agent.loadingMessage)
} else null
// 提取参数
val args = function.argumentsAsJsonOrNull()
logger.info("Calling ${function.name}(${args})")
// 执行函数
val result = try {
// 提取参数
val args = function.argumentsAsJsonOrNull()
logger.info("Calling ${function.name}(${args})")
agent.execute(args, event)
} catch (e: Throwable) {
logger.error("Failed to call ${function.name}", e)
"工具调用失败,请尝试自行回答用户,或如实告知。"
"工具调用失败,请尝试自行回答用户,或如实告知。\n异常信息:${e.message}"
}
logger.info("Result=\"$result\"")
// 过会撤回加载消息

View File

@@ -35,6 +35,12 @@ object PluginConfig : AutoSavePluginConfig("Config") {
@ValueDescription("视觉模型")
var visualModel: String by value("qwen-vl-plus")
@ValueDescription("百炼平台API KEY")
val dashScopeApiKey: String by value("")
@ValueDescription("百炼平台图片编辑模型")
val imageEditModel: String by value("qwen-image-edit")
@ValueDescription("Jina API Key")
val jinaApiKey by value("")

View File

@@ -29,9 +29,9 @@ abstract class BaseAgent(
protected val httpClient by lazy {
HttpClient(OkHttp) {
install(HttpTimeout) {
requestTimeoutMillis = 60000
connectTimeoutMillis = 5000
socketTimeoutMillis = 15000
requestTimeoutMillis = 120_000
connectTimeoutMillis = 30_000
socketTimeoutMillis = 120_000
}
}
}

View File

@@ -0,0 +1,112 @@
package top.jie65535.mirai.tools
import com.aallam.openai.api.chat.Tool
import com.aallam.openai.api.core.Parameters
import io.ktor.client.call.body
import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.add
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import kotlinx.serialization.json.putJsonObject
import top.jie65535.mirai.JChatGPT
import top.jie65535.mirai.PluginConfig
class ImageEdit : BaseAgent(
tool = Tool.function(
name = "imageEdit",
description = "可通过调用图像编辑模型来修改图片。备注:该方法成本较高,非必要尽量不要调用。编辑图片前无需识别图片内容,图像编辑模型自己会理解图片内容!",
parameters = Parameters.buildJsonObject {
put("type", "object")
putJsonObject("properties") {
putJsonObject("image_url") {
put("type", "string")
put("description", "原始图片地址")
}
putJsonObject("prompt") {
put("type", "string")
put("description", "正向提示词,用来描述需要对图片进行修改的要求。")
}
putJsonObject("negative_prompt") {
put("type", "string")
put("description", "反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制。" +
"示例值:低分辨率、错误、最差质量、低质量、残缺、多余的手指、比例不良等。")
}
}
putJsonArray("required") {
add("image_url")
add("prompt")
}
}
)
) {
companion object {
const val API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
}
override val isEnabled: Boolean
get() = PluginConfig.dashScopeApiKey.isNotEmpty()
override val loadingMessage: String
get() = "图片编辑中..."
override suspend fun execute(args: JsonObject?): String {
requireNotNull(args)
val imageUrl = args.getValue("image_url").jsonPrimitive.content
val prompt = args.getValue("prompt").jsonPrimitive.content
val negativePrompt = args["negative_prompt"]?.jsonPrimitive?.content
val response = httpClient.post(API_URL) {
contentType(ContentType("application", "json"))
header("Authorization", "Bearer " + PluginConfig.dashScopeApiKey)
setBody(buildJsonObject {
put("model", PluginConfig.imageEditModel)
putJsonObject("input") {
putJsonArray("messages") {
addJsonObject {
put("role", "user")
putJsonArray("content") {
addJsonObject {
put("image", imageUrl)
}
addJsonObject {
put("text", prompt)
}
}
}
}
}
if (negativePrompt != null) {
putJsonObject("parameters") {
put("negative_prompt", negativePrompt)
}
}
}.toString())
}
val responseJson = response.bodyAsText()
val responseObject = Json.parseToJsonElement(responseJson).jsonObject
return try {
val url = responseObject
.getValue("output").jsonObject
.getValue("choices").jsonArray[0].jsonObject
.getValue("message").jsonObject
.getValue("content").jsonArray[0].jsonObject
.getValue("image").jsonPrimitive.content
"图片已编辑完成发送时请务必包含完整的url和查询参数因为下载地址存在鉴权。图片地址$url"
} catch (e: Exception) {
JChatGPT.logger.error("图像编辑结果解析异常", e)
responseObject.toString()
}
}
}

View File

@@ -11,7 +11,8 @@ import top.jie65535.mirai.PluginConfig
class RunCode : BaseAgent(
tool = Tool.function(
name = "runCode",
description = "执行代码,请尽量避免需要运行时输入或可能导致死循环的代码!",
description = "运行目标代码,请尽量避免需要运行时输入或可能导致死循环的代码!" +
"注意,这些代码对用户不可见,如果用户要求展示代码,你应该直接发送相关代码而不是执行。",
parameters = Parameters.buildJsonObject {
put("type", "object")
putJsonObject("properties") {

View File

@@ -18,8 +18,8 @@ import top.jie65535.mirai.PluginConfig
class VisualAgent : BaseAgent(
tool = Tool.function(
name = "visualAgent",
description = "可通过调用视觉模型识别图片。",
name = "imageRecognition",
description = "可通过调用视觉模型识别图片内容。备注:该方法成本较高,非必要尽量不要调用",
parameters = Parameters.buildJsonObject {
put("type", "object")
putJsonObject("properties") {