11 · 智能体运行时

Experimental:智能体运行时、ace-rpc 跨进程、ace-autopilot 处于 0.1.0 早期,API 可能演进。

ACE 内置一个轻量智能体(Agent)运行时,用于把「LLM + 工具调用循环」暴露为 HTTP 服务,也供框架自调度。核心在 ace-agent(import aceboot::agent.*)。

核心类型

public enum Role { | User | Assistant | System | ToolResult }

public class Message {
    public let role: Role
    public let content: String
    public let tool: String                 // 仅 ToolResult 用,标识对应工具
    public init(role: Role, content: String)
    public init(role: Role, content: String, tool: String)
}

public enum LlmResponse {
    | Text(String)                          // 最终文本答复
    | ToolUse(String, String)               // 请求调用工具:(工具名, 参数 JSON 字符串)
}

public interface LlmProvider {              // 可插拔 LLM 抽象
    func chat(messages: ArrayList<Message>, tools: ArrayList<Tool>): LlmResponse
}

public interface Tool {                     // 可调用工具
    prop name: String
    prop description: String
    func call(args: String): String         // 入参/出参均为 String
}

public class Agent {
    public init(llm: LlmProvider, tools: ArrayList<Tool>, maxTurns!: Int64 = 8)
    public func run(input: String): String
    public func asHandler(): (Context) -> String
}

工具调用循环

Agent.run(input) 的循环(最多 maxTurns,默认 8 轮):

  1. 用当前对话历史调 llm.chat(messages, tools);
  2. 返回 Text(t) → 直接返回 t(最终输出);
  3. 返回 ToolUse(name, args) → 按 name 匹配工具,把 tool.call(args) 结果作为 ToolResult 消息回填历史,进入下一轮;未知工具回填错误文本让 LLM 自愈(不抛异常);
  4. 达到 maxTurns 仍无 Text → 返回 "[ace-agent] max turns reached"。

把 Agent 暴露为 HTTP 服务

package my_app
import aceboot::framework.*
import aceboot::framework_macros.*
import aceboot::framework_boot.*
import aceboot::agent.*
import std.collection.*

// 1) 实现 LlmProvider(这里是 mock;接真实 LLM 见下)
class EchoLlm <: LlmProvider {
    public func chat(messages: ArrayList<Message>, tools: ArrayList<Tool>): LlmResponse {
        Text("agent says: ${messages[0].content}")
    }
}

// 2) 实现工具
class AddTool <: Tool {
    public prop name: String { get() { "add" } }
    public prop description: String { get() { "add two ints from 'a,b'" } }
    public func call(args: String): String {
        let p = args.split(",")
        "${Int64.parse(p[0]) + Int64.parse(p[1])}"
    }
}

// 3) 构造 Agent(顶层单例)
let tools = ArrayList<Tool>()
let chatAgent = Agent(EchoLlm(), tools)        // tools.add(AddTool()) 注册工具

// 4) 控制器暴露
@Controller["/chat"]
public class ChatController {
    @Post["/"]
    public func ask(body: String): String {
        chatAgent.run(body)
    }
}

main(): Int64 { AceApplication.run("127.0.0.1", 8080); return 0 }

也可用 chatAgent.asHandler() 直接得到一个 (Context) -> String 处理器(输入取 rawBody,回退 ?q=)。

接入真实 LLM(Claude)

ace-agent-claude(import aceboot::agent_claude.*)把 ace-agent 对话模型翻译为 Anthropic Messages API 报文。它是纯函数、stdx-free,真实 HTTP 发送以注入方式外置:

public type ClaudeTransport = (String) -> LlmResponse        // 注入:发送请求报文 → 解析返回

public class ClaudeProvider <: LlmProvider {
    public init(
        transport: ClaudeTransport,
        model!: String = "claude-opus-4-8",
        maxTokens!: Int64 = 1024,
        system!: String = ""
    )
    // chat() 内部:buildMessagesRequest(...) 构造报文 → transport(req) → LlmResponse
}

// 报文构造(纯函数,可单测):
buildMessagesRequest(model, maxTokens, system, messages, tools): String

接线:在 boot/example 层实现一个 ClaudeTransport——用 stdx.net.http POST /v1/messages(带 x-api-key,从环境注入、勿硬编码;anthropic-version 头),用 stdx.encoding.json 解析 content[0](text → Text、tool_use → ToolUse(name, input)),再把 ClaudeProvider(transport) 传给 Agent。

仓库只提供报文构造与适配器骨架,未内置真实 HTTP transport(它是注入点)。当前工具的 input_schema 恒为 {"type":"object"},无逐参数 schema。

ace-rpc:单体/微服务透明切换

业务面向接口编程;提供本地实现(进程内直调)与远程代理(经 Transport.send 转发)两种实现,装配处绑定哪个即在单体/微服务间切换,业务零改动。

public interface Transport {
    func send(service: String, method: String, payload: String): String
}
public class InProcessTransport <: Transport {        // 进程内传输(单体/测试)
    public func register(service: String, handler: (String, String) -> String): Unit
    public func send(service: String, method: String, payload: String): String
}

跨网络 HTTP transport 标注「后续补」,当前仅进程内。

ace-autopilot:分级护栏控制面

一个安全控制面——智能体只「提议」调参,执行边界由分级护栏强制(安全不交给模型):

public enum KnobTier { | Whitelist | Dangerous }
public enum Decision { | Applied(String, Float64) | Proposed(String, Float64) | Rejected(String, String) }

public class Autopilot {
    public init(autoExecuteWhitelist!: Bool = true)
    public func registerKnob(k: Knob): Unit
    public func propose(name: String, value: Float64): Decision    // 钳制到[min,max]→按分级 Applied/Proposed→审计
    public func observeErrorRate(knobName, errorRate, threshold): ?Decision  // metrics→决策闭环示例
    public func auditLog(): Array<String>
}

Whitelist 旋钮自动执行,Dangerous 永远只建议(需人工确认),未知旋钮拒绝;所有决策入审计日志。

下一章:测试与部署。