Skip to content

Commit

Permalink
chore: Change create_tool endpoint on v1 routes to error instead of u…
Browse files Browse the repository at this point in the history
…psert (letta-ai#2102)
  • Loading branch information
mattzh72 authored Nov 25, 2024
1 parent 84031a9 commit 80d0748
Show file tree
Hide file tree
Showing 17 changed files with 270 additions and 258 deletions.
104 changes: 0 additions & 104 deletions .github/workflows/test_groq.yml

This file was deleted.

2 changes: 1 addition & 1 deletion examples/docs/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def roll_d20() -> str:


# create a tool from the function
tool = client.create_tool(roll_d20)
tool = client.create_or_update_tool(roll_d20)
print(f"Created tool with name {tool.name}")

# create a new agent
Expand Down
2 changes: 1 addition & 1 deletion examples/notebooks/Agentic RAG with Letta.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@
"metadata": {},
"outputs": [],
"source": [
"birthday_tool = client.create_tool(query_birthday_db)"
"birthday_tool = client.create_or_update_tool(query_birthday_db)"
]
},
{
Expand Down
10 changes: 5 additions & 5 deletions examples/notebooks/Multi-agent recruiting workflow.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@
"\n",
"# TODO: add an archival andidate tool (provide justification) \n",
"\n",
"read_resume_tool = client.create_tool(read_resume) \n",
"submit_evaluation_tool = client.create_tool(submit_evaluation)"
"read_resume_tool = client.create_or_update_tool(read_resume) \n",
"submit_evaluation_tool = client.create_or_update_tool(submit_evaluation)"
]
},
{
Expand Down Expand Up @@ -239,7 +239,7 @@
" print(\"Pretend to email:\", content)\n",
" return\n",
"\n",
"email_candidate_tool = client.create_tool(email_candidate)"
"email_candidate_tool = client.create_or_update_tool(email_candidate)"
]
},
{
Expand Down Expand Up @@ -713,8 +713,8 @@
"\n",
"\n",
"# create tools \n",
"search_candidate_tool = client.create_tool(search_candidates_db)\n",
"consider_candidate_tool = client.create_tool(consider_candidate)\n",
"search_candidate_tool = client.create_or_update_tool(search_candidates_db)\n",
"consider_candidate_tool = client.create_or_update_tool(consider_candidate)\n",
"\n",
"# delete agent if exists \n",
"if client.get_agent_id(\"recruiter_agent\"): \n",
Expand Down
4 changes: 2 additions & 2 deletions examples/swarm/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ def transfer_agent_a(self):
swarm.client.set_default_llm_config(LLMConfig.default_config(model_name="gpt-4"))

# create tools
transfer_a = swarm.client.create_tool(transfer_agent_a)
transfer_b = swarm.client.create_tool(transfer_agent_b)
transfer_a = swarm.client.create_or_update_tool(transfer_agent_a)
transfer_b = swarm.client.create_or_update_tool(transfer_agent_b)

# create agents
if swarm.client.get_agent_id("agentb"):
Expand Down
2 changes: 1 addition & 1 deletion examples/tool_rule_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def main():
functions = [first_secret_word, second_secret_word, third_secret_word, fourth_secret_word, auto_error]
tools = []
for func in functions:
tool = client.create_tool(func)
tool = client.create_or_update_tool(func)
tools.append(tool)
tool_names = [t.name for t in tools[:-1]]

Expand Down
2 changes: 1 addition & 1 deletion letta/cli/cli_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def add_tool(
func = eval(func_def.name)

# 4. Add or update the tool
tool = client.create_tool(func=func, name=name, tags=tags, update=update)
tool = client.create_or_update_tool(func=func, name=name, tags=tags, update=update)
print(f"Tool {tool.name} added successfully")


Expand Down
119 changes: 72 additions & 47 deletions letta/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ def create_tool(
) -> Tool:
raise NotImplementedError

def create_or_update_tool(
self,
func,
name: Optional[str] = None,
tags: Optional[List[str]] = None,
) -> Tool:
raise NotImplementedError

def update_tool(
self,
id: str,
Expand Down Expand Up @@ -532,7 +540,7 @@ def create_agent(
# add memory tools
memory_functions = get_memory_functions(memory)
for func_name, func in memory_functions.items():
tool = self.create_tool(func, name=func_name, tags=["memory", "letta-base"])
tool = self.create_or_update_tool(func, name=func_name, tags=["memory", "letta-base"])
tool_names.append(tool.name)

# check if default configs are provided
Expand Down Expand Up @@ -1440,18 +1448,39 @@ def create_tool(
Returns:
tool (Tool): The created tool.
"""
source_code = parse_source_code(func)
source_type = "python"

# TODO: check tool update code
# TODO: check if tool already exists
# call server function
request = ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags)
response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
if response.status_code != 200:
raise ValueError(f"Failed to create tool: {response.text}")
return Tool(**response.json())

# TODO: how to load modules?
# parse source code/schema
def create_or_update_tool(
self,
func: Callable,
name: Optional[str] = None,
tags: Optional[List[str]] = None,
) -> Tool:
"""
Creates or updates a tool. This stores the source code of function on the server, so that the server can execute the function and generate an OpenAI JSON schemas for it when using with an agent.
Args:
func (callable): The function to create a tool for.
name: (str): Name of the tool (must be unique per-user.)
tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
Returns:
tool (Tool): The created tool.
"""
source_code = parse_source_code(func)
source_type = "python"

# call server function
request = ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags)
response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
response = requests.put(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
if response.status_code != 200:
raise ValueError(f"Failed to create tool: {response.text}")
return Tool(**response.json())
Expand Down Expand Up @@ -1489,45 +1518,6 @@ def update_tool(
raise ValueError(f"Failed to update tool: {response.text}")
return Tool(**response.json())

# def create_tool(
# self,
# func,
# name: Optional[str] = None,
# update: Optional[bool] = True, # TODO: actually use this
# tags: Optional[List[str]] = None,
# ):
# """Create a tool

# Args:
# func (callable): The function to create a tool for.
# tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
# update (bool, optional): Update the tool if it already exists. Defaults to True.

# Returns:
# Tool object
# """

# # TODO: check if tool already exists
# # TODO: how to load modules?
# # parse source code/schema
# source_code = parse_source_code(func)
# json_schema = generate_schema(func, name)
# source_type = "python"
# json_schema["name"]

# # create data
# data = {"source_code": source_code, "source_type": source_type, "tags": tags, "json_schema": json_schema, "update": update}
# try:
# CreateToolRequest(**data) # validate data
# except Exception as e:
# raise ValueError(f"Failed to create tool: {e}, invalid input {data}")

# # make REST request
# response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=data, headers=self.headers)
# if response.status_code != 200:
# raise ValueError(f"Failed to create tool: {response.text}")
# return ToolModel(**response.json())

def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
"""
List available tools for the user.
Expand Down Expand Up @@ -1977,7 +1967,7 @@ def create_agent(
# add memory tools
memory_functions = get_memory_functions(memory)
for func_name, func in memory_functions.items():
tool = self.create_tool(func, name=func_name, tags=["memory", "letta-base"])
tool = self.create_or_update_tool(func, name=func_name, tags=["memory", "letta-base"])
tool_names.append(tool.name)

self.interface.clear()
Expand Down Expand Up @@ -2573,7 +2563,6 @@ def load_composio_tool(self, action: "ActionType") -> Tool:
tool_create = ToolCreate.from_composio(action=action)
return self.server.tool_manager.create_or_update_tool(pydantic_tool=Tool(**tool_create.model_dump()), actor=self.user)

# TODO: Use the above function `add_tool` here as there is duplicate logic
def create_tool(
self,
func,
Expand Down Expand Up @@ -2601,6 +2590,42 @@ def create_tool(
if not tags:
tags = []

# call server function
return self.server.tool_manager.create_tool(
Tool(
source_type=source_type,
source_code=source_code,
name=name,
tags=tags,
description=description,
),
actor=self.user,
)

def create_or_update_tool(
self,
func,
name: Optional[str] = None,
tags: Optional[List[str]] = None,
description: Optional[str] = None,
) -> Tool:
"""
Creates or updates a tool. This stores the source code of function on the server, so that the server can execute the function and generate an OpenAI JSON schemas for it when using with an agent.
Args:
func (callable): The function to create a tool for.
name: (str): Name of the tool (must be unique per-user.)
tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
description (str, optional): The description.
Returns:
tool (Tool): The created tool.
"""
source_code = parse_source_code(func)
source_type = "python"
if not tags:
tags = []

# call server function
return self.server.tool_manager.create_or_update_tool(
Tool(
Expand Down
8 changes: 8 additions & 0 deletions letta/orm/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ class NoResultFound(Exception):

class MalformedIdError(Exception):
"""An id not in the right format, most likely violating uuid4 format."""


class UniqueConstraintViolationError(ValueError):
"""Custom exception for unique constraint violations."""


class ForeignKeyConstraintViolationError(ValueError):
"""Custom exception for foreign key constraint violations."""
Loading

0 comments on commit 80d0748

Please sign in to comment.