feat: allow handle/response of a `ToolMessage` to return an arbitrary type of result, or an enabled/non-enabled ToolMessage.
(This supersedes 0.9.0 release notes)
- tests: See `test_tool_handlers_and_results` in [test_tool_messages.py](https://github.com/langroid/langroid/blob/main/tests/main/test_tool_messages.py)
- implementation: `handle_tool_message` in [agent/base.py](https://github.com/langroid/langroid/blob/main/langroid/agent/base.py)
- example: https://github.com/langroid/langroid/blob/main/examples/basic/tool-extract-short-example.py
The result R of a handle/response method of a `ToolMessage` ends up either in the `content` field (as a string) of the returned `ChatDocument`, OR in the `tool_messages` field of the returned `ChatDocument`. This is determined as follows:
- if R is already a **string** OR **`ChatDocument`**, it is returned as is (=> `content` field)
- else if R is a `ToolMessage`:
- if it is a "handleable" tool message, its handler method is called recursively
- else it is added to the `tool_messages` field of the returned `ChatDocument`
- A special case of this is if R is an instance of `FinalResultTool`: this acts as a "short-circuit termination", i.e.:
- (a) the current task as well as all parent tasks up to the root task are terminated, and
- (b) the result R appears in the final returned `ChatDocument`'s `tool_messages` list
- else if R is a **pydantic** object (i.e. derived from `BaseModel`), it is converted to a string using `obj.json()` (=> `content` field)
- else an attempt is made to convert it to string using `json.dumps(R)` or `str(R)` (=> `content` field)