A model you can run for free
A browser cannot call OpenAI. So rather than stop here, you are going to write a model.
The SDK reaches a model through exactly one method, get_response. Anything that provides it is a model as far as the SDK is concerned. That is the whole opening, and it is what makes this course runnable.
What a model hands back
Two shapes, and no more. Either words, or a request for a tool. These two helpers build them, and they are the only verbose part of the file because they are the real types the SDK uses.
def say(text):
"""Plain words, in the shape the SDK expects back."""
return ResponseOutputMessage(
id="msg", role="assistant", status="completed", type="message",
content=[ResponseOutputText(text=text, type="output_text", annotations=[])],
)def call(tool_name, **arguments):
"""A request for a tool, instead of an answer."""
n = next(_calls)
return ResponseFunctionToolCall(
id=f"fc_{n}", call_id=f"call_{n}", name=tool_name,
arguments=json.dumps(arguments), type="function_call",
)Each request gets its own number. Reusing one makes the SDK think the same request came back twice, and it stops with an error rather than guessing. Worth knowing, because it is the sort of thing a hand written fake gets wrong.
The model itself
It works two ways, and every lesson after this uses one of them.
class PretendModel(Model):
def __init__(self, replies=None):
self.replies = list(replies) if replies else None
self.turn = 0
async def get_response(self, system_instructions, input, model_settings, tools,
output_schema, handoffs, tracing, **kwargs):
item = self._next(input, tools)
self.turn += 1
return ModelResponse(output=[item], usage=Usage(), response_id=None)That signature is long because it is the real one. The SDK hands a model everything it might need: the instructions, the conversation, the tools, the shape the answer should take, and the agents this one is allowed to pass work to. This model reads two of them and ignores the rest, and lessons 10 and 13 are where the other two start to matter.
Hand it a list of replies and it returns them in order. Most lessons do that, because the lesson is about the plumbing and not about guessing what a model would say.
Hand it nothing and it falls back on three rules: if a tool has just answered, repeat what it said; if the question mentions an order id and there are tools, ask for one; otherwise ask a question back.
def _next(self, input, tools):
if self.replies is not None:
i = min(self.turn, len(self.replies) - 1)
reply = self.replies[i]
return say(reply) if isinstance(reply, str) else reply
answered = _last_tool_output(input)
if answered:
return say(f"Here is what I found. {answered}")
...Run it and see both shapes
The bottom of the file builds one of each and prints them, so you can see exactly what the SDK is handed.
words = say("Order A17 shipped on 3 March.")
request = call("lookup_order", order_id="A17")
print("answering with words:", words.content[0].text)
print("asking for a tool: ", request.name, request.arguments)A real model reads the words and predicts. This one reads the words and follows rules you can point at. Everything else about it, the types it returns and the method it provides, is exactly what a real one does.
- Change the text in
sayand run it again. - Build a scripted model with two replies and print what each turn returns.
- Print the whole
wordsobject and look at the fields a real model fills in.
Slow is fine. Stopping is the only problem.