Clean up the schema of the Pydantic class (which can recursively contain other Pydantic classes), to create a version compatible with OpenAI Function-call API. Adapted from this excellent library: https://github.com/jxnl/instructor/blob/main/instructor/funct
(
cls,
request: bool = False,
defaults: bool = True,
)
| 296 | |
| 297 | @classmethod |
| 298 | def llm_function_schema( |
| 299 | cls, |
| 300 | request: bool = False, |
| 301 | defaults: bool = True, |
| 302 | ) -> LLMFunctionSpec: |
| 303 | """ |
| 304 | Clean up the schema of the Pydantic class (which can recursively contain |
| 305 | other Pydantic classes), to create a version compatible with OpenAI |
| 306 | Function-call API. |
| 307 | |
| 308 | Adapted from this excellent library: |
| 309 | https://github.com/jxnl/instructor/blob/main/instructor/function_calls.py |
| 310 | |
| 311 | Args: |
| 312 | request: whether to include the "request" field in the schema. |
| 313 | (we set this to True when using Langroid-native TOOLs as opposed to |
| 314 | OpenAI Function calls) |
| 315 | defaults: whether to include fields with default values in the schema, |
| 316 | in the "properties" section. |
| 317 | |
| 318 | Returns: |
| 319 | LLMFunctionSpec: the schema as an LLMFunctionSpec |
| 320 | |
| 321 | """ |
| 322 | schema = copy.deepcopy(cls.model_json_schema()) |
| 323 | docstring = parse(cls.__doc__ or "") |
| 324 | parameters = { |
| 325 | k: v for k, v in schema.items() if k not in ("title", "description") |
| 326 | } |
| 327 | for param in docstring.params: |
| 328 | if (name := param.arg_name) in parameters["properties"] and ( |
| 329 | description := param.description |
| 330 | ): |
| 331 | if "description" not in parameters["properties"][name]: |
| 332 | parameters["properties"][name]["description"] = description |
| 333 | |
| 334 | excludes = cls._get_excluded_fields().copy() |
| 335 | if not request: |
| 336 | excludes = excludes.union({"request"}) |
| 337 | # exclude 'excludes' from parameters["properties"]: |
| 338 | parameters["properties"] = { |
| 339 | field: details |
| 340 | for field, details in parameters["properties"].items() |
| 341 | if field not in excludes and (defaults or details.get("default") is None) |
| 342 | } |
| 343 | parameters["required"] = sorted( |
| 344 | k |
| 345 | for k, v in parameters["properties"].items() |
| 346 | if ("default" not in v and k not in excludes) |
| 347 | ) |
| 348 | if request: |
| 349 | parameters["required"].append("request") |
| 350 | |
| 351 | # If request is present it must match the default value |
| 352 | # Similar to defining request as a literal type |
| 353 | parameters["request"] = { |
| 354 | "enum": [cls.default_value("request")], |
| 355 | "type": "string", |