用Ruby从头做一个AI Agent应用 -- Smart Agent

这是一篇系列文章,描述我如何使用Ruby语言,从头构建一个AI Agent的应用。原本打算尽快发出的第二篇,结果因为MCP的SSE,卡了好久。因为Ruby语言在LLM这个生态中,简直就是一穷二白,啥也没有。

开源地址:smart_agent

第二部分: Smart Agent

首先要讨论:到底什么是Agent?与上一章提到的Worker,有什么区别?

最简单的解释就是:

写一个简单的Agent

SmartAgent.define :smart_bot do
  result = call_worker(:smart_bot, params, with_tools: true)
  if result.call_tools
    call_tools(result)
    params[:text] = "请总结上述的结果"
    result = call_worker(:summary, params, with_tools: false, with_history: true)
  end
  result.content
end

写一个 AI Tool

SmartAgent::Tool.define :get_weather do
  desc "Get weather of an location, the user shoud supply a location first"
  param_define :location, "City or More Specific Address", :string
  param_define :date, "Specific Date or Today or Tomorrow", :string
  # Call the Weather API
  tool_proc do
    "Sunny 10 ℃ ~ 20 ℃"
  end
end

装配一个Agent,并调用

engine = SmartAgent::Engine.new("./config/agent.yml")
agent = engine.build_agent(:smart_bot, tools: [:get_weather])
puts agent.please("请用中文回答,明天上海的天气。")

输出的结果应该会类似于:


以下是关于上海明天天气的详细总结:

上海明日天气预报

如果需要更详细的信息(如湿度、风速等),可以进一步查询!


更加复杂的Tools:生成代码

SmartAgent::Tool.define :get_code do
  desc "Call the LLM to generate a Ruby function with the input details"
  param_define :name, "ruby function name", :string
  param_define :description, "ruby function description", :string
  param_define :input_params_type, "define of input parameters: (name:type, name:type ... )", :string
  param_define :output_value_type, "type of return value.", :string
  param_define :input_params, "input parameters: (value, value ...)", :string
  tool_proc do
    code = call_worker(:get_code, input_params)
    if input_params["input_params"][0] == "(" && input_params["input_params"][-1] == ")"
      code += "\n" + input_params["name"] + input_params["input_params"]
    else
      code += "\n" + input_params["name"] + "(" + input_params["input_params"] + ")"
    end
    "通过生成的代码:\n #{code} \n得到了结果: #{eval(code)}"
  end
end

装配一个Agent,并调用

engine = SmartAgent::Engine.new("./config/agent.yml")
agent = engine.build_agent(:smart_bot, tools: [:get_weather, :get_code])
puts agent.please("请通过get_code生成Ruby函数计算三角形的面积,底边长132,高为7.6,告诉我面积是多少?")

输出的结果应该会类似于:


结果总结

请求内容

生成的 Ruby 代码

def triangle_area(base, height)
  (base * height) / 2.0
end

# 调用函数并传入参数
triangle_area(132, 7.6)

计算结果

说明

  1. 函数通过公式 (base * height) / 2.0 计算三角形的面积。
  2. 由于浮点数运算的特性,结果可能存在微小的精度误差(如 501.59999999999997 而非 501.6)。
  3. 如果需要更高的精度,可以考虑使用 BigDecimal 或其他高精度计算库。

支持MCP

SmartAgent::MCPClient.define :opendigger do
  type :stdio
  command "node ~/open-digger-mcp-server/dist/index.js"
end
SmartAgent::MCPClient.define :amap do
  type :sse
  url "https://mcp.amap.com/sse?key=72adc379733dfd020dba574c65847a26"
end

如何配置

配置文件: agent.yml

logger_file: "./log/agent.log"
engine_config: "./config/llm_config.yml"
agent_path: "./agents"
tools_path: "./agents/tools"
mcp_path: "./agents/mcps"

支持流式显示

agent.on_reasoning do |reasoning_content|
  print reasoning_content.dig("choices", 0, "delta", "reasoning_content")
  print "\n" if reasoning_content.dig("choices", 0, "finish_reason") == "stop"
end

agent.on_content do |content|
  print content.dig("choices", 0, "delta", "content")
  print "\n" if content.dig("choices", 0, "finish_reason") == "stop"
end

agent.on_tool_call do |msg|
  puts msg
end

后续内容

敬请期待