Skip to content

Commit 9a71f40

Browse files
authored
feat: improve isp-support example (#34)
1 parent a1697ac commit 9a71f40

File tree

2 files changed

+106
-42
lines changed

2 files changed

+106
-42
lines changed

examples/isp-support/agents_factory.rb

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require_relative "tools/create_checkout_tool"
77
require_relative "tools/search_docs_tool"
88
require_relative "tools/escalate_to_human_tool"
9+
require "ruby_llm/schema"
910

1011
module ISPSupport
1112
# Factory for creating all ISP support agents with proper handoff relationships.
@@ -56,7 +57,8 @@ def create_sales_agent
5657
instructions: sales_instructions_with_state,
5758
model: "gpt-4.1-mini",
5859
tools: [ISPSupport::CreateLeadTool.new, ISPSupport::CreateCheckoutTool.new],
59-
temperature: 0.8 # Higher temperature for more persuasive, varied sales language
60+
temperature: 0.8, # Higher temperature for more persuasive, varied sales language
61+
response_schema: sales_response_schema
6062
)
6163
end
6264

@@ -70,7 +72,8 @@ def create_support_agent
7072
ISPSupport::SearchDocsTool.new,
7173
ISPSupport::EscalateToHumanTool.new
7274
],
73-
temperature: 0.5 # Balanced temperature for helpful but consistent technical support
75+
temperature: 0.5, # Balanced temperature for helpful but consistent technical support
76+
response_schema: triage_response_schema
7477
)
7578
end
7679

@@ -95,22 +98,33 @@ def triage_instructions
9598
end
9699

97100
def triage_response_schema
98-
{
99-
type: "object",
100-
properties: {
101-
response: {
102-
type: "string",
103-
description: "Your response to the customer"
104-
},
105-
intent: {
106-
type: "string",
107-
enum: %w[sales support unclear],
108-
description: "The detected intent category"
109-
}
110-
},
111-
required: %w[response intent],
112-
additionalProperties: false
113-
}
101+
RubyLLM::Schema.create do
102+
string :response, description: "Your response to the customer"
103+
string :intent, enum: %w[sales support unclear], description: "The detected intent category"
104+
array :sentiment, description: "Customer sentiment indicators" do
105+
string enum: %w[positive neutral negative frustrated urgent confused satisfied]
106+
end
107+
end
108+
end
109+
110+
def support_response_schema
111+
RubyLLM::Schema.create do
112+
string :response, description: "Your response to the customer"
113+
string :intent, enum: %w[support], description: "The intent category (always support)"
114+
array :sentiment, description: "Customer sentiment indicators" do
115+
string enum: %w[positive neutral negative frustrated urgent confused satisfied]
116+
end
117+
end
118+
end
119+
120+
def sales_response_schema
121+
RubyLLM::Schema.create do
122+
string :response, description: "Your response to the customer"
123+
string :intent, enum: %w[sales], description: "The intent category (always sales)"
124+
array :sentiment, description: "Customer sentiment indicators" do
125+
string enum: %w[positive neutral negative frustrated urgent confused satisfied]
126+
end
127+
end
114128
end
115129

116130
def sales_instructions

examples/isp-support/interactive.rb

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# frozen_string_literal: true
33

44
require "json"
5+
require "readline"
56
require_relative "../../lib/agents"
67
require_relative "agents_factory"
78

@@ -29,68 +30,96 @@ def initialize
2930
@context = {}
3031
@current_status = ""
3132

32-
puts "🏢 Welcome to ISP Customer Support!"
33-
puts "Type '/help' for commands or 'exit' to quit."
33+
puts green("🏢 Welcome to ISP Customer Support!")
34+
puts dim_text("Type '/help' for commands or 'exit' to quit.")
3435
puts
3536
end
3637

3738
def start
3839
loop do
39-
print "💬 You: "
40-
user_input = gets.chomp.strip
40+
user_input = Readline.readline(cyan("\u{1F4AC} You: "), true)
41+
next unless user_input # Handle Ctrl+D
4142

43+
user_input = user_input.strip
4244
command_result = handle_command(user_input)
4345
break if command_result == :exit
4446
next if command_result == :handled || user_input.empty?
4547

4648
# Clear any previous status and show agent is working
4749
clear_status_line
48-
print "🤖 Processing..."
50+
print yellow("🤖 Processing...")
4951

50-
# Use the runner - it automatically determines the right agent from context
51-
result = @runner.run(user_input, context: @context)
52+
begin
53+
# Use the runner - it automatically determines the right agent from context
54+
result = @runner.run(user_input, context: @context)
5255

53-
# Update our context with the returned context from Runner
54-
@context = result.context if result.respond_to?(:context) && result.context
56+
# Update our context with the returned context from Runner
57+
@context = result.context if result.respond_to?(:context) && result.context
5558

56-
# Clear status and show response
57-
clear_status_line
59+
# Clear status and show response with callback history
60+
clear_status_line
5861

59-
# Handle structured output from triage agent
60-
output = result.output || "[No output]"
61-
if @context[:current_agent] == "Triage Agent" && output.is_a?(Hash)
62-
# Display the response from structured response
63-
puts "🤖 #{output["response"]}"
64-
puts "\e[2m [Intent]: #{output["intent"]}\e[0m" if output["intent"]
65-
else
66-
puts "🤖 #{output}"
62+
# Display callback messages if any
63+
if @callback_messages.any?
64+
puts dim_text(@callback_messages.join("\n"))
65+
@callback_messages.clear
66+
end
67+
68+
# Handle structured output from agents
69+
output = result.output || "[No output]"
70+
71+
if output.is_a?(Hash) && output.key?("response")
72+
# Display the response from structured response
73+
puts "🤖 #{output["response"]}"
74+
puts dim_text(" [Intent]: #{output["intent"]}") if output["intent"]
75+
puts dim_text(" [Sentiment]: #{output["sentiment"].join(", ")}") if output["sentiment"]&.any?
76+
else
77+
puts "🤖 #{output}"
78+
end
79+
80+
puts # Add blank line after agent response
81+
rescue StandardError => e
82+
clear_status_line
83+
puts red("❌ Error: #{e.message}")
84+
puts dim_text("Please try again or type '/help' for assistance.")
85+
puts # Add blank line after error message
6786
end
6887
end
6988
end
7089

7190
private
7291

7392
def setup_callbacks
93+
@callback_messages = []
94+
7495
@runner.on_agent_thinking do |agent_name, _input|
75-
update_status("🧠 #{agent_name} is thinking...")
96+
message = "🧠 #{agent_name} is thinking..."
97+
update_status(message)
98+
@callback_messages << message
7699
end
77100

78101
@runner.on_tool_start do |tool_name, _args|
79-
update_status("🔧 Using #{tool_name}...")
102+
message = "🔧 Using #{tool_name}..."
103+
update_status(message)
104+
@callback_messages << message
80105
end
81106

82107
@runner.on_tool_complete do |tool_name, _result|
83-
update_status("✅ #{tool_name} completed")
108+
message = "✅ #{tool_name} completed"
109+
update_status(message)
110+
@callback_messages << message
84111
end
85112

86113
@runner.on_agent_handoff do |from_agent, to_agent, _reason|
87-
update_status("🔄 Handoff: #{from_agent}#{to_agent}")
114+
message = "🔄 Handoff: #{from_agent}#{to_agent}"
115+
update_status(message)
116+
@callback_messages << message
88117
end
89118
end
90119

91120
def update_status(message)
92121
clear_status_line
93-
print message
122+
print dim_text(message)
94123
$stdout.flush
95124
end
96125

@@ -197,6 +226,27 @@ def get_agent_description(key)
197226
else "Unknown agent"
198227
end
199228
end
229+
230+
# ANSI color helper methods
231+
def dim_text(text)
232+
"\e[90m#{text}\e[0m"
233+
end
234+
235+
def green(text)
236+
"\e[32m#{text}\e[0m"
237+
end
238+
239+
def yellow(text)
240+
"\e[33m#{text}\e[0m"
241+
end
242+
243+
def red(text)
244+
"\e[31m#{text}\e[0m"
245+
end
246+
247+
def cyan(text)
248+
"\e[36m#{text}\e[0m"
249+
end
200250
end
201251

202252
# Run the demo

0 commit comments

Comments
 (0)