Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 2277aa2

Browse files
committed
work in progress, support for smarter chain exits
1 parent 7e4f937 commit 2277aa2

File tree

6 files changed

+59
-16
lines changed

6 files changed

+59
-16
lines changed

lib/ai_bot/bot.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ class Bot
66
attr_reader :model
77

88
BOT_NOT_FOUND = Class.new(StandardError)
9+
910
# the future is agentic, allow for more turns
10-
MAX_COMPLETIONS = 8
11+
MAX_COMPLETIONS = 2
12+
1113
# limit is arbitrary, but 5 which was used in the past was too low
1214
MAX_TOOLS = 20
1315

@@ -71,6 +73,8 @@ def get_updated_title(conversation_context, post, user)
7173
end
7274

7375
def force_tool_if_needed(prompt, context)
76+
return if prompt.tool_choice == :none
77+
7478
context[:chosen_tools] ||= []
7579
forced_tools = persona.force_tool_use.map { |tool| tool.name }
7680
force_tool = forced_tools.find { |name| !context[:chosen_tools].include?(name) }
@@ -105,7 +109,7 @@ def reply(context, &update_blk)
105109
needs_newlines = false
106110
tools_ran = 0
107111

108-
while total_completions <= MAX_COMPLETIONS && ongoing_chain
112+
while total_completions < MAX_COMPLETIONS && ongoing_chain
109113
tool_found = false
110114
force_tool_if_needed(prompt, context)
111115

@@ -202,8 +206,10 @@ def reply(context, &update_blk)
202206

203207
total_completions += 1
204208

205-
# do not allow tools when we are at the end of a chain (total_completions == MAX_COMPLETIONS)
206-
prompt.tools = [] if total_completions == MAX_COMPLETIONS
209+
# do not allow tools when we are at the end of a chain (total_completions == MAX_COMPLETIONS - 1)
210+
prompt.tool_choice = :none if total_completions == MAX_COMPLETIONS - 1
211+
puts total_completions
212+
puts prompt.tool_choice
207213
end
208214

209215
embed_thinking(raw_context)

lib/completions/dialects/dialect.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ def tool_choice
6666
prompt.tool_choice
6767
end
6868

69+
def no_more_tool_calls_text
70+
# note, Anthropic must never prefill with an ending whitespace
71+
"Since you explicitly asked me not to use tools any more I will not call tools any more.\nHere is the best, complete, answer I can come up with given the information I know:"
72+
end
73+
6974
def translate
7075
messages = prompt.messages
7176

lib/completions/endpoints/anthropic.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,22 @@ def prepare_payload(prompt, model_params, dialect)
9595
if prompt.has_tools?
9696
payload[:tools] = prompt.tools
9797
if dialect.tool_choice.present?
98-
payload[:tool_choice] = { type: "tool", name: dialect.tool_choice }
98+
if dialect.tool_choice == :none
99+
payload[:tool_choice] = { type: "none" }
100+
101+
# prefill prompt to nudge LLM to generate a response that is useful.
102+
# without this LLM (even 3.7) can get confused and start text preambles for a tool calls.
103+
payload[:messages] << {
104+
role: "assistant",
105+
content: dialect.no_more_tool_calls_text,
106+
}
107+
else
108+
payload[:tool_choice] = { type: "tool", name: prompt.tool_choice }
109+
end
99110
end
100111
end
101112

113+
puts "tool_choice: #{payload[:tool_choice]} - #{dialect.tool_choice}"
102114
payload
103115
end
104116

lib/completions/endpoints/aws_bedrock.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,19 @@ def prepare_payload(prompt, model_params, dialect)
122122
if prompt.has_tools?
123123
payload[:tools] = prompt.tools
124124
if dialect.tool_choice.present?
125-
payload[:tool_choice] = { type: "tool", name: dialect.tool_choice }
125+
if dialect.tool_choice == :none
126+
# not supported on bedrock as of 2025-03-24
127+
# retest in 6 months
128+
# payload[:tool_choice] = { type: "none" }
129+
130+
# prefill prompt to nudge LLM to generate a response that is useful, instead of trying to call a tool
131+
payload[:messages] << {
132+
role: "assistant",
133+
content: dialect.no_more_tool_calls_text,
134+
}
135+
else
136+
payload[:tool_choice] = { type: "tool", name: prompt.tool_choice }
137+
end
126138
end
127139
end
128140
elsif dialect.is_a?(DiscourseAi::Completions::Dialects::Nova)

lib/completions/endpoints/gemini.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,14 @@ def prepare_payload(prompt, model_params, dialect)
7272

7373
function_calling_config = { mode: "AUTO" }
7474
if dialect.tool_choice.present?
75-
function_calling_config = {
76-
mode: "ANY",
77-
allowed_function_names: [dialect.tool_choice],
78-
}
75+
if dialect.tool_choice == :none
76+
function_calling_config = { mode: "NONE" }
77+
else
78+
function_calling_config = {
79+
mode: "ANY",
80+
allowed_function_names: [dialect.tool_choice],
81+
}
82+
end
7983
end
8084

8185
payload[:tool_config] = { function_calling_config: function_calling_config }

lib/completions/endpoints/open_ai.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,16 @@ def prepare_payload(prompt, model_params, dialect)
9292
if dialect.tools.present?
9393
payload[:tools] = dialect.tools
9494
if dialect.tool_choice.present?
95-
payload[:tool_choice] = {
96-
type: "function",
97-
function: {
98-
name: dialect.tool_choice,
99-
},
100-
}
95+
if dialect.tool_choice == :none
96+
payload[:tool_choice] = "none"
97+
else
98+
payload[:tool_choice] = {
99+
type: "function",
100+
function: {
101+
name: dialect.tool_choice,
102+
},
103+
}
104+
end
101105
end
102106
end
103107
end

0 commit comments

Comments
 (0)