function ai(), like t() that calls the default LLM

Created on 23 June 2024, 6 months ago
Updated 11 September 2024, 3 months ago

Problem/Motivation

The cool thing about the function is that even without reading docs you already know what it does.

More details:
Providing a simple ai() so that you can do

function hook_node_article_presave(ContentEntityInterface $node) {
  $node->set('field_summary', ai('text_completion', 'Summarize ' . $node->field_body->value . ' in 2 sentences');
}

PRO

This is so easy to use that you don't need developer documentation (much like t(.
This would be especially practical for people that really just hobby coders doing some changes in like hook_presave()?
This will drive adoption.

CON

It goes against the way of how Drupal decided on static singletons instead of functions since Drupal 8 since it makes stuff self documenting in IDEs, something that functions can't do.

Proposed resolution

provide an opinionated function.

Remaining tasks

Discuss if we should do it.

User interface changes

API changes

Data model changes

🌱 Plan
Status

RTBC

Version

1.0

Component

AI Core module

Created by

🇧🇪Belgium wouters_f Leuven

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @wouters_f
  • 🇱🇹Lithuania mindaugasd

    Object oriented code can be quite extensive and flexible:

    $ai = $provider->textToImage('A cow with earrings');
    $entity->set('field_ref_media', $ai->getAsMediaReference());
    $entity->set('field_ref_file', $ai->getAsFileReference());
    
    $ai = $provider->chat($messages);
    $entity->set('body', $ai->getResponseAsString()); // ?
    
    // etc. etc.
    

    While ai() function could only have few limited uses.
    When reading the code, it is not exactly clear what values can it accept or what response can it provide, unless you memorized it.
    While object oriented code I demonstrated above can be flexible and self understood.

  • 🇱🇹Lithuania mindaugasd

    However, how does ai() look with objects today?

    Like this? (I "copied" from video)

      $service = \Drupal::service('ai.provider');
      $provider = $service->getInstance('openai');
      $messages = new ChatInput([
        new chatMessage('system', 'You are helpful assistant.');
        new chatMessage('user', $question);
      ]);
      $message = $ai->chat($messages, 'gpt-4o')->getNormalized();
      $entity->set('field_text', $text->getMessage());  
    

    📌 Add Readme Active I think is priority, video was difficult to find and "copy".

    Also ->getNormalized() is better to be ->getMessage()
    and ->getMessage() is better ->getText()
    :-)

  • 🇱🇹Lithuania mindaugasd

    Also @wouters_f, it would be useful to have proposal how you would like ai() to work. For example, how one would choose the provider (openai, mistral) or model? What exact parameters and what response? What use-case would it be best be limited to?

  • 🇧🇪Belgium wouters_f Leuven

    I love the analogy with for example cache_get . I don't want to know if we're using the memcache or db cache system (or redis).
    It would be great if things would keep working if I switch to another AI provider.

    Calling AI() ideas, good and bad

    You can go multiple directions with this.

    Point directly to the provider and model.

    ai('openai', 'gpt-4o', 'Summarize ' . $value . ' in 2 sentences');
    

    This would not allow switching to another LLM and keeping things working. But it's more verbose and clear.

    No provider
    I don't think going to the model without provider would be a good idea (different vendors with same name models).

    ai($model, 'Summarize ' . $value . ' in 2 sentences');
    

    We should not do this.

    Just take the first Available
    If you take abstraction of Providers and models you could think of this

    ai('text_completion', 'Summarize ' . $value . ' in 2 sentences');
    

    It's the simplest, with the most assumptions.
    This takes the defaults you've configured (I've configured Openai and GPT4-0 for example).
    Coming back to the cache analogy, swapping out LLM's would not break this integration.
    I absolutely agree Multiple outputs are possible here. (Generate text / generate an asset).
    In short: It takes the first available (or configured) text model (LLM) and gives it the prompt.
    I like this approach best.
    I also think that most users of ai() will just use one LLM.

    Expected behavior (ideas)

    Then whatever it returns, will be returned (maybe even "dumbed down" ) so that only the output or some basic wrapping comes back.
    I'm not knowledgable on the "normalisation" process (yet) so it could be naive.

    If you want to choose providers or have more options. I absolutely agree that you should not be doing things with ai().
    In other words ai() can be your training wheels, we'll hold your hands, Anything else should be done differently.

    MEDIA

    If you start working with media I think you're not in the target group for the ai() function. and you should be doing things differently.
    But if you would really want to make that work however:
    You could test if the input is not a string (prompt) but an array (prompt,asset) and "just" work with that.
    ai('image_to_image', [$asset, $prompt] );
    But I don't know enough about what is expected in the back to give sound examples here.
    This does feel off.

    I could be oversimplifying things here, and I'm sorry for that.

    Also: If it's not the direction we want to go: I also understand. (I can just make a submodule that provides the function call to try it).

  • 🇧🇪Belgium wouters_f Leuven

    I'll try to make a submodule that does this on the dev days, so we can play around with it.

  • 🇱🇹Lithuania mindaugasd

    I'll try to make a submodule that does this on the dev days

    👍

  • 🇧🇪Belgium wouters_f Leuven
    // Example implementation of the first one
    function ai_based_on_settings($input) {
      $instance = \Drupal::config('ai_function')->get('ai_function_provider');
      $model = \Drupal::config('ai_function')->get('ai_function_model');
      $service = \Drupal::service('ai.provider');
      $provider = $service->getInstance($instance);
      $messages = new ChatInput([
        new chatMessage('system', 'You are helpful assistant.'),
        new chatMessage('user', $input),
      ]);
      $message = $provider->chat($messages, $model)->getNormalized();
      return $message->getMessage();
    }
    
  • 🇱🇹Lithuania mindaugasd

    practical for people that really just hobby coders doing some changes

    Hobby coders in the future will code with AI help, where AI should access AI module documentation without any hassle and produce correct and high quality object oriented code.

    This is what I will continue to experiment during dev days :-)

    And that is why I am focused on #3455852, because this way AI can have the documentation easily accessible.

  • 🇧🇪Belgium wouters_f Leuven

    Looking forward to meet and brainstorm!
    Maybe I'm still in an old paradigm/mindset :D

    I've cooked up a simple version that allows one to do:

    $response = ai('Who built you');
    

    Check the branch / merge request for the code, it works!

    Tested to work with

    • Mistral
    • Openai

    So the abstraction layer works very fine :D

  • 🇧🇪Belgium wouters_f Leuven

    Altered it a tiny bit that allows the following too (type as a input param).
    Have only tried out the chat version yet however.

    $embedding = ai('Who built you'); //chat
    $embedding = ai('Who built you', 'chat');
    $embedding = ai('Who built you', 'embedding');
    $embedding = ai('Who built you', 'moderation');
    $embedding = ai('Who built you', 'text_to_image');
    $embedding = ai('Who built you', 'text_to_speech');
    
  • 🇩🇪Germany marcus_johansson

    I think you will have to eiher input a normalized object as input or the providers input, doing something in between will be really confusing since you now have two normalizing layers, and one that can't do everything that in the spec. In you example with Chat, attaching images or having chat between two humans and one AI or having chat history is not possible.

    I would also do the assumption that if someone uses a function like this, they do not want to meddle around with objects, thats what gets them to do procedural code in the first place, so taking raw data and receiving raw data, with the option of normalizing is the way to go I think. People still trying to use Drupal 7 syntax are most likely used to associative arrays, rather then objects.

    so you can do something like

    
    $messages = [
      ['role' => 'system', 'content' => 'You are an image reader'],
      ['role' => 'user', 
        'content' => [ 
           'type' => 'text,
           'text' => 'Could you describe the image for me?'
        ],
        [
          'type' => 'image_url',
          'image_url' = base64_encode_helper($someimage),
        ],
      ],
    ];
    
    ai($messages);
    
    // Returns something like in assoc array
    {
      "id": "chatcmpl-123",
      "object": "chat.completion",
      "created": 1677652288,
      "model": "gpt-3.5-turbo-0125",
      "system_fingerprint": "fp_44709d6fcb",
      "choices": [{
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "\n\nThat is an image of a dog",
        },
        "logprobs": null,
        "finish_reason": "stop"
      }],
      "usage": {
        "prompt_tokens": 9,
        "completion_tokens": 12,
        "total_tokens": 21
      }
    }
    

    This would of course not be possible to abstract into other providers since its OpenAI specific, but I'm guessing people using this are only looking for a quick fix to call the api.

    The other way would be to have ai and ai_normalized, where ai_normalize takes and returns normalization objects, but since its one function for all different calls, it will not be able to suggest whats in that object in your IDE.

    Or you could take the decision that its only for chat, but even then things like images (or other files) or conversations for chatbots needs to be solved.

    Or its a pure text producing endpoints that takes text in and spits text out?

  • 🇩🇪Germany marcus_johansson

    Also ->getMessage() is better ->getText()
    and ->getNormalized() is better to be ->getMessage()
    :-)

    @mindaugasd - I agree it should be getText(), its gets even more obvious with this ticket: https://www.drupal.org/project/ai/issues/3456629 🌱 Add Image into ChatMessage object (refactor or no breaking change) Active

    For getNormalized(), I think its good to have a known way of receiveing the data that is known to be normalized over all the operations. So you use getNormalized() for the output from text-to-image etc as well, they just happen to be different objects returned with a common interface.

  • Merge request !8Resolve #3456553 "Should we do" → (Open) created by wouters_f
  • Status changed to Needs review 6 months ago
  • Pipeline finished with Success
    6 months ago
    Total: 652s
    #215112
  • Pipeline finished with Success
    5 months ago
    Total: 146s
    #223364
  • Status changed to Needs work 5 months ago
  • 🇧🇪Belgium kevinvb

    On line 37 the _ai_check_default_provider_and_model(); should be _ai_function_check_default_provider_and_model
    Also if you didn't configure a default warnings are thrown:

    Warning: Undefined array key "chat" in ai() (line 41 of modules/contrib/ai/modules/ai_function/ai_function.module).
    And
    Warning: Trying to access array offset on value of type null in ai() (line 41 of modules/contrib/ai/modules/ai_function/ai_function.module).

    If no default is configured it would be best that any execution is halted in function ai().
    Also wouldn't it be better to have function ai() work like the t() function using a object like AiMarkup like TranslatableMarkup?

  • Status changed to Active 5 months ago
  • Assigned to wouters_f
  • Pipeline finished with Canceled
    5 months ago
    Total: 147s
    #225783
  • Pipeline finished with Canceled
    5 months ago
    Total: 76s
    #225785
  • Pipeline finished with Failed
    5 months ago
    Total: 296s
    #225786
  • Issue was unassigned.
  • Status changed to Needs review 5 months ago
  • 🇧🇪Belgium wouters_f Leuven

    On line 37 the _ai_check_default_provider_and_model(); should be _ai_function_check_default_provider_and_model
    Also if you didn't configure a default warnings are thrown:

    Solved this one.

    If no default is configured it would be best that any execution is halted in function ai().
    Also wouldn't it be better to have function ai() work like the t() function using a object like AiMarkup like TranslatableMarkup?

    I disagree. For this simple function. In T, you also just pass in a string.
    I want to make this as easy as possible.

  • Pipeline finished with Failed
    5 months ago
    Total: 153s
    #225795
  • Pipeline finished with Failed
    5 months ago
    Total: 157s
    #225936
  • 🇧🇪Belgium kevinvb

    Reviewed functionality and everything worked except the requirements in /admin/report/status
    The install file was missing the use Drupal\core\Url statement.
    I fixed that in latest commit and also provided the install file with a @file block.

    If someone can do a quick check this could also move to reviewed.

  • Pipeline finished with Failed
    5 months ago
    Total: 151s
    #226787
  • Status changed to RTBC 5 months ago
  • 🇧🇪Belgium wouters_f Leuven

    I like it!
    Thanks!

  • Assigned to marcus_johansson
  • 🇧🇪Belgium wouters_f Leuven

    So are wo doing this or what?

Production build 0.71.5 2024