CrewAICrewAI 1.15 · Python 3.10 to 3.13
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
32 small wins to finish your pathNext lesson

A model you write yourself

CrewAI reaches every model through one class, BaseLLM. Subclass it with a call method that returns text, and agents treat it as a real model.

CrewAI's own classes for OpenAI, Anthropic, Gemini and the rest all subclass BaseLLM. A subclass has to supply one method, call, which receives the conversation as a list of messages and returns the model's reply. Everything else has a default.

The imports, and three settings

Exampleshop_llm.py
import os
import re

from crewai import BaseLLM

os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
os.environ["CREWAI_TRACING_ENABLED"] = "false"
os.environ["CREWAI_DISABLE_VERSION_CHECK"] = "true"

The three environment variables from lesson 1 are set here, first thing, so that every script that imports the model also keeps CrewAI offline. CrewAI reads them when it is about to send something, so they work even when crewai was imported earlier.

Deciding on a reply

Exampleshop_llm.py, continued
class ShopLLM(BaseLLM):
    def call(self, messages, tools=None, **kwargs):
        if isinstance(messages, str):
            messages = [{"role": "user", "content": messages}]
        text = messages[-1]["content"]
        orders = re.findall(r"\b[A-Z]\d+\b", text)
        if orders:
            return f"I have no way to look up {orders[0]} yet."
        return "Hello. Which order is this about?"

A message is a dictionary with a role, who is speaking, and content, what they said. call reads the last one and looks for an order id, a capital letter followed by digits. With one, it admits it cannot look the order up yet. Without one, it asks which order the customer means. It also accepts a plain string, which it wraps as a user message.

Save both pieces as shop_llm.py. Every lesson from here on imports it.

Calling it

Example
from shop_llm import ShopLLM

llm = ShopLLM(model="shop")
print(llm.call("Where is my order A17?"))
print(llm.call("Hello"))

model is a name for the model. A hosted model uses it to pick which model to run; yours only stores it.

A model with no name

Example
from shop_llm import ShopLLM

llm = ShopLLM()

BaseLLM is a Pydantic model, and it rejects an empty model. Some examples pass the model through a constructor; in 1.15.22 fields are declared on the class instead, as lesson 9 does for its own field.

Example
from shop_llm import ShopLLM

llm = ShopLLM(model="shop")
conversation = [
    {"role": "system", "content": "You help customers of a small online shop."},
    {"role": "user", "content": "Is C40 in stock?"},
]
print(llm.call(conversation))

A list of messages works the same way. The model reads only the last one, so the system message changes nothing here. A hosted model reads them all.

A hosted model works the same way
A hosted model's call sends the messages to the provider's servers and returns what comes back. Yours decides with a few lines of Python. The agents in the next lesson cannot tell the difference, which is why no key appears anywhere below.
Try it yourself
  • Call it with "Hi, is B22 on its way?" and check which order it names.
  • Change the reply for an order id so it names every id it found.
  • Pass model="" and compare the error with the one above.

Slow is fine. Stopping is the only problem.