The tool loop is the part I would adjust before adding the second function from your examples. The displayed snippet calls get_disk_free() for every tool call, regardless of the requested function name. That works for the single-tool demonstration, but a request for system memory would need a different dispatch path.
A small function-name-to-handler mapping, with arguments validated for the selected handler, would make that extension explicit. I would also append response.message once before iterating over its tool calls, then append each tool result separately. Otherwise a response containing multiple calls repeats the same assistant message in the conversation history.