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
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
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
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
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.
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.
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.- 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.