app/models/ruby_conversations/concerns/conversation_chat.rb



# frozen_string_literal: true

module RubyConversations
  module Concerns
    # Handles chat-related functionality for Conversation
    module ConversationChat
      extend ActiveSupport::Concern

      attr_accessor :tools_optional

      def tools_required?
        !tools_optional
      end

      def call_llm(system_message: nil, &block)
        validate_conversation_state
        chat_messages = generate_chat_response(system_message, &block)

        validate_tool_calls(chat_messages) if tools_required?

        store_and_update_conversation(chat_messages)
      end

      alias execute call_llm

      def validate_tool_calls(chat_messages)
        return unless tools_configured?
        return if tool_calls?(chat_messages)

        last_message = chat_messages.last&.content
        error_message = "No tool call found for tool(s): '#{tool_names}'. Please check the prompt."

        raise RubyConversations::ToolCallValidationError.new(
          error_message,
          last_message: last_message,
          tool_names: tool_names
        )
      end

      def tools_configured?
        tool.present? || tools&.present?
      end

      def tool_names
        tool&.name || tools&.map(&:name)
      end

      def tool_calls?(chat_messages)
        chat_messages.any? { |message| message.tool_calls.present? }
      end

      def llm
        model_identifier
      end

      private

      def validate_conversation_state
        message = messages.last
        raise ArgumentError, 'Conversation must have at least one message to execute' unless message
      end

      def generate_chat_response(system_message, &)
        setup_llm_chat(system_message: system_message)
        chat.ask(messages.last.request, &)
        chat.messages
      end

      def store_and_update_conversation(chat_messages)
        update_last_message_response(messages.last, chat_messages)
        RubyConversations.client.store_conversation_or_message(self, messages.last)

        {
          response: chat_messages,
          conversation: self,
          content: chat_messages.last.content
        }
      end

      def setup_llm_chat(system_message: nil)
        configure_llm_credentials
        configure_tools
        chat.with_instructions(system_message) if system_message.present?
      end

      def update_last_message_response(message, chat_messages)
        message.response = chat_messages.map(&:to_h).to_json
      end

      def configure_tools
        chat.with_tool(tool) if tool.present?
        tools&.each { |tool| chat.with_tool(tool) }
      end
    end
  end
end