An agentic loop against Claude Haiku: the model calls Google-Places-backed tools across multiple turns to find and rank venues, capped at 8 turns as a safety limit.
# app/services/google_places/agentic_location_search.rb
# lines 169–213 (run_agentic_loop)
def run_agentic_loop
messages = [{ role: "user", content: "Please find venues that match my criteria." }]
turns = 0
loop do
turns += 1
if turns > MAX_TURNS
Rails.logger.warn("[AgenticLocationSearch] Safety limit reached after #{MAX_TURNS} turns")
break
end
response = anthropic_client.messages.create(
model: CLAUDE_MODEL,
max_tokens: 4096,
system: build_system_prompt,
tools: TOOL_DEFINITIONS,
messages: messages
)
# Official SDK returns SDK objects — serialize to plain hashes for messages history.
content_blocks = response.content
messages << { role: "assistant", content: serialize_content(content_blocks) }
# Official SDK may return stop_reason as a symbol (:tool_use) rather than
# a string ("tool_use"), so normalise before comparing.
case response.stop_reason.to_s
when "end_turn"
# Ineligible types are already stripped by the pre-filter in dispatch_tool;
# post_filter_venues applies a final Ruby check using cached Google types.
venues = post_filter_venues(extract_final_venues(content_blocks))
Rails.logger.info("[AgenticLocationSearch] Returning #{venues.size} venues after #{turns} turn(s)")
return venues
when "tool_use"
tool_results = execute_tool_calls(content_blocks)
messages << { role: "user", content: tool_results }
else
Rails.logger.warn("[AgenticLocationSearch] Unexpected stop_reason: #{response.stop_reason}")
break
end
end
[]
end
The loop hands the model real tools (Google Places search, geocoding) and keeps feeding results back until it says end_turn or the turn cap trips.
# app/services/google_places/agentic_location_search.rb
# lines 169–213 (run_agentic_loop)
def run_agentic_loop
messages = [{ role: "user", content: "Please find venues that match my criteria." }]
turns = 0
loop do
turns += 1
if turns > MAX_TURNS
Rails.logger.warn("[AgenticLocationSearch] Safety limit reached after #{MAX_TURNS} turns")
break
end
response = anthropic_client.messages.create(
model: CLAUDE_MODEL,
max_tokens: 4096,
system: build_system_prompt,
tools: TOOL_DEFINITIONS,
messages: messages
)
# Official SDK returns SDK objects — serialize to plain hashes for messages history.
content_blocks = response.content
messages << { role: "assistant", content: serialize_content(content_blocks) }
# Official SDK may return stop_reason as a symbol (:tool_use) rather than
# a string ("tool_use"), so normalise before comparing.
case response.stop_reason.to_s
when "end_turn"
# Ineligible types are already stripped by the pre-filter in dispatch_tool;
# post_filter_venues applies a final Ruby check using cached Google types.
venues = post_filter_venues(extract_final_venues(content_blocks))
Rails.logger.info("[AgenticLocationSearch] Returning #{venues.size} venues after #{turns} turn(s)")
return venues
when "tool_use"
tool_results = execute_tool_calls(content_blocks)
messages << { role: "user", content: tool_results }
else
Rails.logger.warn("[AgenticLocationSearch] Unexpected stop_reason: #{response.stop_reason}")
break
end
end
[]
end