Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate call_func from claudette to toolslm #14

Merged
merged 4 commits into from
Oct 17, 2024
Merged

Migrate call_func from claudette to toolslm #14

merged 4 commits into from
Oct 17, 2024

Conversation

ncoop57
Copy link
Contributor

@ncoop57 ncoop57 commented Oct 16, 2024

This PR migrates the call_func logic from claudette to toolslm in order to help with reusability of tool calling across our various LLM API wrappers like cosette. Some changes were made to properly abstract away the specifics of each API provider.

Specifically, call_func now takes in the function name, the function parameters, and a namespace which is required.

def call_func(fc_name, fc_inputs, ns):
    "Call the function `fc_name` with the given `fc_inputs` using namespace `ns`."
    if not isinstance(ns, abc.Mapping): ns = mk_ns(*ns)
    func = ns[fc_name]
    return func(**fc_inputs)

Previously, the namespace (if given as 'none') would be created based on globals. However, since imported modules have their own separate namespace from the importer of the module, you would end up missing keys. Therefore, I think it's better to require the caller to actually define the namespace that is going to be searched.

I also moved the helper function for making a namespace given a list of items to here, along with a new function called obj2ns which handles converting objects (like classes) into a proper namespace object.

def obj2ns(obj): return {n: getattr(obj,n) for n,_ in inspect.getmembers(obj.__class__,inspect.isfunction)}

class Dummy:
    def subs(self,a:int,b:int=1) -> int: return a - b
d = Dummy()

ns = obj2ns(d); ns
# OUTPUT:
# {'subs': <bound method Dummy.subs of <__main__.Dummy object at 0x105870ee0>>}

Previously this was handled differently with class objects where we would only attempt to get the attribute of the object's functions if it existed.

def call_func(fc:ToolUseBlock, # Tool use block from Claude's message
              ns:Optional[abc.Mapping]=None, # Namespace to search for tools, defaults to `globals()`
              obj:Optional=None # Object to search for tools
             ):
    "Call the function in the tool response `tr`, using namespace `ns`."
    if ns is None: ns=globals()
    if not isinstance(ns, abc.Mapping): ns = _mk_ns(*ns)
    func = getattr(obj, fc.name, None)
    if not func: func = ns[fc.name]
    return func(**fc.input)

However, I felt that we could abstract this away just into the general concept of having namespaces where we convert objects to namespaces. This simplifies the code greatly, especially when modifying, for example, Claudette to now use this API.

ncoop57 added 3 commits October 16, 2024 09:50
- Add `_mk_ns`, `obj2ns`, and `call_func` for working with function namespaces
- Add documentation and tests for the new utility functions
Update the `_mk_ns` function to be named `mk_ns`, and modify the `call_func` function to use `mk_ns` instead of `_mk_ns`.
@ncoop57 ncoop57 requested a review from jph00 October 16, 2024 17:55
@jph00
Copy link
Collaborator

jph00 commented Oct 17, 2024

Did you still want me to review this now @ncoop57 ?

@ncoop57
Copy link
Contributor Author

ncoop57 commented Oct 17, 2024

@jph00 no not yet sorry. Need to redesign a bit after our convl

Improve `mk_ns` function to handle classes, objects, and callables.
Add support for calling static methods, class methods, and instance methods
from the created namespace.
@ncoop57
Copy link
Contributor Author

ncoop57 commented Oct 17, 2024

Okay, @jph00 it should be good for you to review now. I removed the function obj2ns and moved the logic into mk_ns to make it cleaner.

@jph00
Copy link
Collaborator

jph00 commented Oct 17, 2024

Super job @ncoop57 ! :D Also I forgot that the pipe operator was added for dictionaries in Python 3.9, which is now the oldest non-EOL version of Python, so we can safely use them! Very exciting. :D

@jph00 jph00 merged commit ac7d27d into main Oct 17, 2024
@jph00 jph00 deleted the call_func branch October 17, 2024 20:57
@jph00 jph00 added the enhancement New feature or request label Oct 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants