mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-19 16:28:46 +00:00
fix: improve proxyai error handling and clean up code
This commit is contained in:
parent
dbb6158162
commit
4c293dff73
8 changed files with 111 additions and 126 deletions
|
|
@ -166,6 +166,16 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
});
|
||||
}
|
||||
|
||||
public void displayInvalidCredential() {
|
||||
String message = "Invalid API key. Open <a href=\"#\">Settings</a> to update your API key.";
|
||||
displayErrorMessage(message, e -> {
|
||||
if (e.getEventType() == ACTIVATED) {
|
||||
ShowSettingsUtil.getInstance()
|
||||
.showSettingsDialog(project, GeneralSettingsConfigurable.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void displayQuotaExceeded() {
|
||||
String message = "You exceeded your current quota, please check your plan and billing details, "
|
||||
+ "or <a href=\"#CHANGE_PROVIDER\">change</a> to a different LLM provider.";
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public class ChatToolWindowScrollablePanel extends ScrollablePanel {
|
|||
It looks like you haven't configured your API key yet. Visit <a href="#OPEN_SETTINGS">ProxyAI settings</a> to do so.
|
||||
</p>
|
||||
<p style="margin-top: 4px; margin-bottom: 4px;">
|
||||
Don't have an account? <a href="https://tryproxy.io/signin">Sign up</a> to get the most out of ProxyAI.
|
||||
Don't have an account? <a href="https://tryproxy.io/signin">Sign up</a> to get started.
|
||||
</p>
|
||||
</html>""",
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import ee.carlrobert.codegpt.settings.service.ServiceType
|
|||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
|
||||
import ee.carlrobert.codegpt.toolwindow.agent.AgentCreditsEvent
|
||||
import java.time.LocalDate
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
object AgentFactory {
|
||||
|
||||
|
|
@ -184,17 +183,7 @@ object AgentFactory {
|
|||
}
|
||||
|
||||
private fun createRetryingExecutor(client: LLMClient): PromptExecutor {
|
||||
val retryingClient = RetryingLLMClient(
|
||||
client,
|
||||
RetryConfig(
|
||||
maxAttempts = 3,
|
||||
initialDelay = 1.seconds,
|
||||
maxDelay = 20.seconds,
|
||||
backoffMultiplier = 2.0,
|
||||
jitterFactor = 0.2
|
||||
)
|
||||
)
|
||||
return SingleLLMPromptExecutor(retryingClient)
|
||||
return SingleLLMPromptExecutor(RetryingLLMClient(client))
|
||||
}
|
||||
|
||||
private fun createGeneralPurposeAgent(
|
||||
|
|
|
|||
|
|
@ -168,6 +168,10 @@ object ProxyAIAgent {
|
|||
}
|
||||
}
|
||||
|
||||
onNodeExecutionFailed { ctx ->
|
||||
logger.error(ctx.throwable) { "Node execution failed: $ctx" }
|
||||
}
|
||||
|
||||
onToolCallStarting { ctx ->
|
||||
val id = ctx.toolCallId ?: UUID.randomUUID().toString()
|
||||
if (ctx.toolCallId == null) {
|
||||
|
|
@ -196,6 +200,10 @@ object ProxyAIAgent {
|
|||
onAgentCompleted { context ->
|
||||
events.onAgentCompleted(context)
|
||||
}
|
||||
|
||||
onAgentExecutionFailed {
|
||||
logger.error(it.throwable) { "Agent execution failed: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
return agent
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import ai.koog.prompt.message.Message
|
|||
import ai.koog.prompt.message.ResponseMetaInfo
|
||||
import ai.koog.prompt.params.LLMParams
|
||||
import ai.koog.prompt.streaming.StreamFrameFlowBuilder
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceChatCompletionSettingsState
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.ktor.client.*
|
||||
|
|
@ -185,9 +186,9 @@ public class CustomOpenAILLMClient(
|
|||
If the tool has no arguments, OpenRouter puts an empty string in the arguments instead of an empty object
|
||||
But we always expect arguments to be a JSON object. Fixing this.
|
||||
*/
|
||||
content = toolCall.function.arguments
|
||||
content = StringUtil.escapeStringCharacters(toolCall.function.arguments
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?: "{}",
|
||||
?: "{}"),
|
||||
metaInfo = metaInfo
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -31,10 +31,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi
|
|||
public class ProxyAIClientSettings(
|
||||
baseUrl: String = DEFAULT_BASE_URL,
|
||||
chatCompletionsPath: String = DEFAULT_CHAT_COMPLETIONS_PATH,
|
||||
timeoutConfig: ConnectionTimeoutConfig = ConnectionTimeoutConfig(
|
||||
requestTimeoutMillis = 120_000,
|
||||
socketTimeoutMillis = 120_000
|
||||
)
|
||||
timeoutConfig: ConnectionTimeoutConfig = ConnectionTimeoutConfig()
|
||||
) : OpenAIBaseSettings(baseUrl, chatCompletionsPath, timeoutConfig) {
|
||||
public companion object {
|
||||
public const val DEFAULT_BASE_URL: String = "https://codegpt-api.carlrobert.ee"
|
||||
|
|
|
|||
|
|
@ -67,19 +67,6 @@ internal class SingleRunStrategyProvider : AgentRunStrategyProvider {
|
|||
llm.writeSession {
|
||||
if (previousCheckpoint == null) {
|
||||
projectInstructions?.let {
|
||||
appendPrompt {
|
||||
message(
|
||||
Message.User(
|
||||
it,
|
||||
RequestMetaInfo(clock.now(), buildJsonObject {
|
||||
put("cache_control", buildJsonObject {
|
||||
put("type", JsonPrimitive("ephemeral"))
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
appendPrompt {
|
||||
user(it)
|
||||
}
|
||||
|
|
@ -136,7 +123,7 @@ internal class SingleRunStrategyProvider : AgentRunStrategyProvider {
|
|||
msg is Message.Tool.Call && msg.tool == "TodoWrite"
|
||||
}
|
||||
|
||||
if (toolCallMessages >= 2 && !todoWriteToolUsed) {
|
||||
if (toolCallMessages >= 3 && !todoWriteToolUsed) {
|
||||
appendPrompt {
|
||||
user("It seems that you haven't created a todo list yet. If the task on hand requires multiple steps then create a todo list to track your changes.")
|
||||
}
|
||||
|
|
@ -166,6 +153,7 @@ internal class SingleRunStrategyProvider : AgentRunStrategyProvider {
|
|||
edge(nodeCallLLM forwardTo nodeExecuteTool onMultipleToolCalls { true })
|
||||
edge(nodeCallLLM forwardTo nodeFinish onSingleAssistantResponse { true })
|
||||
edge(nodeExecuteTool forwardTo nodeSendToolResult)
|
||||
edge(nodeSendToolResult forwardTo nodeFinish onEmptyOutput { true })
|
||||
edge(nodeSendToolResult forwardTo nodeFinish onSingleAssistantResponse { true })
|
||||
edge(nodeSendToolResult forwardTo nodeExecuteTool onMultipleToolCalls { true })
|
||||
}
|
||||
|
|
@ -299,3 +287,17 @@ private infix fun <IncomingOutput, OutgoingInput> AIAgentEdgeBuilderIntermediate
|
|||
.onCondition { messages -> block(messages[0]) }
|
||||
.transformed { it[0].content }
|
||||
}
|
||||
|
||||
@EdgeTransformationDslMarker
|
||||
private infix fun <IncomingOutput, OutgoingInput> AIAgentEdgeBuilderIntermediate<IncomingOutput, List<Message.Response>, OutgoingInput>.onEmptyOutput(
|
||||
block: suspend (Message.Response) -> Boolean
|
||||
): AIAgentEdgeBuilderIntermediate<IncomingOutput, String, OutgoingInput> {
|
||||
return onIsInstance(List::class)
|
||||
.transformed { response ->
|
||||
response.filter { item -> item is Message.Response && item !is Message.Reasoning }
|
||||
.filterIsInstance<Message.Response>()
|
||||
}
|
||||
.onCondition { it.isEmpty() }
|
||||
.onCondition { messages -> block(messages[0]) }
|
||||
.transformed { it[0].content }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ class AgentEventHandler(
|
|||
|
||||
private val mainToolCards = ConcurrentHashMap<String, ToolCallCard>()
|
||||
|
||||
private val toolOutputPublisher = ApplicationManager.getApplication()
|
||||
.messageBus
|
||||
.syncPublisher(AgentToolOutputNotifier.AGENT_TOOL_OUTPUT_TOPIC)
|
||||
|
||||
@Volatile
|
||||
private var lastReportedPromptTokens: Long = 0
|
||||
|
||||
|
|
@ -90,14 +94,64 @@ class AgentEventHandler(
|
|||
approvalQueue.clear()
|
||||
currentQuestion = null
|
||||
questionQueue.clear()
|
||||
approvalContainer.removeAll()
|
||||
approvalContainer.isVisible = false
|
||||
clearApprovalContainer()
|
||||
todoListPanel.clearTodos()
|
||||
runViewHolder = null
|
||||
subagentViewHolders.clear()
|
||||
lastReportedPromptTokens = 0
|
||||
}
|
||||
|
||||
private fun clearApprovalContainer() {
|
||||
approvalContainer.removeAll()
|
||||
approvalContainer.isVisible = false
|
||||
approvalContainer.revalidate()
|
||||
approvalContainer.repaint()
|
||||
}
|
||||
|
||||
private fun monitorBackgroundProcessOutput(
|
||||
bgId: String,
|
||||
toolId: String,
|
||||
onComplete: (() -> Unit)? = null
|
||||
) {
|
||||
serviceScope.launch {
|
||||
try {
|
||||
var outPos = 0
|
||||
var errPos = 0
|
||||
while (true) {
|
||||
val po = BackgroundProcessManager.getOutput(bgId) ?: break
|
||||
val stdout = po.stdout.toString()
|
||||
val stderr = po.stderr.toString()
|
||||
if (outPos < stdout.length) {
|
||||
stdout.substring(outPos).split('\n').forEach { line ->
|
||||
if (line.isNotEmpty()) toolOutputPublisher.toolOutput(
|
||||
toolId,
|
||||
line,
|
||||
false
|
||||
)
|
||||
}
|
||||
outPos = stdout.length
|
||||
}
|
||||
if (errPos < stderr.length) {
|
||||
stderr.substring(errPos).split('\n').forEach { line ->
|
||||
if (line.isNotEmpty()) toolOutputPublisher.toolOutput(
|
||||
toolId,
|
||||
line,
|
||||
true
|
||||
)
|
||||
}
|
||||
errPos = stderr.length
|
||||
}
|
||||
if (po.isComplete) break
|
||||
delay(300)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
logger.warn("Failed to monitor background process output", ex)
|
||||
} finally {
|
||||
onComplete?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setCurrentResponseBody(responseBody: ChatMessageResponseBody) {
|
||||
currentResponseBody = responseBody
|
||||
}
|
||||
|
|
@ -135,11 +189,16 @@ class AgentEventHandler(
|
|||
|
||||
private fun handleProxyAIException(ex: KoogHttpClientException) {
|
||||
when (ex.statusCode) {
|
||||
403 -> {
|
||||
401 -> {
|
||||
currentResponseBody?.displayMissingCredential()
|
||||
handleDone()
|
||||
}
|
||||
|
||||
403 -> {
|
||||
currentResponseBody?.displayInvalidCredential()
|
||||
handleDone()
|
||||
}
|
||||
|
||||
429 -> {
|
||||
currentResponseBody?.displayCreditsExhausted()
|
||||
handleDone()
|
||||
|
|
@ -276,38 +335,9 @@ class AgentEventHandler(
|
|||
if (bgId == null) {
|
||||
mainToolCards.remove(keyFor(id))
|
||||
} else {
|
||||
val publisher =
|
||||
ApplicationManager.getApplication()
|
||||
.messageBus
|
||||
.syncPublisher(AgentToolOutputNotifier.AGENT_TOOL_OUTPUT_TOPIC)
|
||||
serviceScope.launch {
|
||||
try {
|
||||
var outPos = 0
|
||||
var errPos = 0
|
||||
while (true) {
|
||||
val po = BackgroundProcessManager.getOutput(bgId) ?: break
|
||||
val stdout = po.stdout.toString()
|
||||
val stderr = po.stderr.toString()
|
||||
if (outPos < stdout.length) {
|
||||
stdout.substring(outPos).split('\n').forEach { line ->
|
||||
if (line.isNotEmpty()) publisher.toolOutput(id, line, false)
|
||||
}
|
||||
outPos = stdout.length
|
||||
}
|
||||
if (errPos < stderr.length) {
|
||||
stderr.substring(errPos).split('\n').forEach { line ->
|
||||
if (line.isNotEmpty()) publisher.toolOutput(id, line, true)
|
||||
}
|
||||
errPos = stderr.length
|
||||
}
|
||||
if (po.isComplete) break
|
||||
delay(300)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
} finally {
|
||||
runInEdt {
|
||||
mainToolCards.remove(keyFor(id))
|
||||
}
|
||||
monitorBackgroundProcessOutput(bgId, id) {
|
||||
runInEdt {
|
||||
mainToolCards.remove(keyFor(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -405,44 +435,7 @@ class AgentEventHandler(
|
|||
|
||||
val bgId = (result as? BashTool.Result)?.bashId
|
||||
if (bgId != null) {
|
||||
val publisher =
|
||||
ApplicationManager.getApplication()
|
||||
.messageBus
|
||||
.syncPublisher(AgentToolOutputNotifier.AGENT_TOOL_OUTPUT_TOPIC)
|
||||
serviceScope.launch {
|
||||
try {
|
||||
var outPos = 0
|
||||
var errPos = 0
|
||||
while (true) {
|
||||
val po = BackgroundProcessManager.getOutput(bgId) ?: break
|
||||
val stdout = po.stdout.toString()
|
||||
val stderr = po.stderr.toString()
|
||||
if (outPos < stdout.length) {
|
||||
stdout.substring(outPos).split('\n').forEach { line ->
|
||||
if (line.isNotEmpty()) publisher.toolOutput(
|
||||
childId,
|
||||
line,
|
||||
false
|
||||
)
|
||||
}
|
||||
outPos = stdout.length
|
||||
}
|
||||
if (errPos < stderr.length) {
|
||||
stderr.substring(errPos).split('\n').forEach { line ->
|
||||
if (line.isNotEmpty()) publisher.toolOutput(
|
||||
childId,
|
||||
line,
|
||||
true
|
||||
)
|
||||
}
|
||||
errPos = stderr.length
|
||||
}
|
||||
if (po.isComplete) break
|
||||
delay(300)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
monitorBackgroundProcessOutput(bgId, childId)
|
||||
}
|
||||
}
|
||||
scrollablePanel.update()
|
||||
|
|
@ -486,10 +479,7 @@ class AgentEventHandler(
|
|||
private fun maybeShowNextApproval() {
|
||||
if (currentApproval != null) return
|
||||
val next = approvalQueue.pollFirst() ?: run {
|
||||
approvalContainer.removeAll()
|
||||
approvalContainer.isVisible = false
|
||||
approvalContainer.revalidate()
|
||||
approvalContainer.repaint()
|
||||
clearApprovalContainer()
|
||||
maybeShowNextQuestion()
|
||||
return
|
||||
}
|
||||
|
|
@ -536,10 +526,7 @@ class AgentEventHandler(
|
|||
|
||||
next.deferred.complete(true)
|
||||
currentApproval = null
|
||||
approvalContainer.removeAll()
|
||||
approvalContainer.isVisible = false
|
||||
approvalContainer.revalidate()
|
||||
approvalContainer.repaint()
|
||||
clearApprovalContainer()
|
||||
runCatching {
|
||||
project.service<AgentToolWindowContentManager>()
|
||||
.setTabStatus(sessionId, AgentToolWindowTabbedPane.TabStatus.RUNNING)
|
||||
|
|
@ -549,10 +536,7 @@ class AgentEventHandler(
|
|||
onReject = {
|
||||
next.deferred.complete(false)
|
||||
currentApproval = null
|
||||
approvalContainer.removeAll()
|
||||
approvalContainer.isVisible = false
|
||||
approvalContainer.revalidate()
|
||||
approvalContainer.repaint()
|
||||
clearApprovalContainer()
|
||||
|
||||
try {
|
||||
project.service<AgentService>().cancelCurrentRun(sessionId)
|
||||
|
|
@ -585,10 +569,7 @@ class AgentEventHandler(
|
|||
onSubmit = { answers ->
|
||||
next.deferred.complete(answers)
|
||||
currentQuestion = null
|
||||
approvalContainer.removeAll()
|
||||
approvalContainer.isVisible = false
|
||||
approvalContainer.revalidate()
|
||||
approvalContainer.repaint()
|
||||
clearApprovalContainer()
|
||||
runCatching {
|
||||
project.service<AgentToolWindowContentManager>()
|
||||
.setTabStatus(sessionId, AgentToolWindowTabbedPane.TabStatus.RUNNING)
|
||||
|
|
@ -599,10 +580,7 @@ class AgentEventHandler(
|
|||
onCancel = {
|
||||
next.deferred.complete(emptyMap())
|
||||
currentQuestion = null
|
||||
approvalContainer.removeAll()
|
||||
approvalContainer.isVisible = false
|
||||
approvalContainer.revalidate()
|
||||
approvalContainer.repaint()
|
||||
clearApprovalContainer()
|
||||
runCatching {
|
||||
project.service<AgentToolWindowContentManager>()
|
||||
.setTabStatus(sessionId, AgentToolWindowTabbedPane.TabStatus.RUNNING)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue