letta-nightly 0.5.2.dev20241118104226__py3-none-any.whl → 0.5.3.dev20241120010849__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

letta/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.5.2"
1
+ __version__ = "0.5.3"
2
2
 
3
3
  # import clients
4
4
  from letta.client.client import LocalClient, RESTClient, create_client
letta/agent.py CHANGED
@@ -27,6 +27,7 @@ from letta.llm_api.llm_api_tools import create
27
27
  from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
28
28
  from letta.memory import ArchivalMemory, RecallMemory, summarize_messages
29
29
  from letta.metadata import MetadataStore
30
+ from letta.orm import User
30
31
  from letta.persistence_manager import LocalStateManager
31
32
  from letta.schemas.agent import AgentState, AgentStepResponse
32
33
  from letta.schemas.block import Block
@@ -46,6 +47,7 @@ from letta.schemas.passage import Passage
46
47
  from letta.schemas.tool import Tool
47
48
  from letta.schemas.tool_rule import TerminalToolRule
48
49
  from letta.schemas.usage import LettaUsageStatistics
50
+ from letta.services.block_manager import BlockManager
49
51
  from letta.services.source_manager import SourceManager
50
52
  from letta.services.user_manager import UserManager
51
53
  from letta.streaming_interface import StreamingRefreshCLIInterface
@@ -234,6 +236,7 @@ class Agent(BaseAgent):
234
236
  # agents can be created from providing agent_state
235
237
  agent_state: AgentState,
236
238
  tools: List[Tool],
239
+ user: User,
237
240
  # memory: Memory,
238
241
  # extras
239
242
  messages_total: Optional[int] = None, # TODO remove?
@@ -245,6 +248,8 @@ class Agent(BaseAgent):
245
248
  self.agent_state = agent_state
246
249
  assert isinstance(self.agent_state.memory, Memory), f"Memory object is not of type Memory: {type(self.agent_state.memory)}"
247
250
 
251
+ self.user = user
252
+
248
253
  # link tools
249
254
  self.link_tools(tools)
250
255
 
@@ -1221,7 +1226,9 @@ class Agent(BaseAgent):
1221
1226
  # future if we expect templates to change often.
1222
1227
  continue
1223
1228
  block_id = block.get("id")
1224
- db_block = ms.get_block(block_id=block_id)
1229
+
1230
+ # TODO: This is really hacky and we should probably figure out how to
1231
+ db_block = BlockManager().get_block_by_id(block_id=block_id, actor=self.user)
1225
1232
  if db_block is None:
1226
1233
  # this case covers if someone has deleted a shared block by interacting
1227
1234
  # with some other agent.
@@ -1584,7 +1591,8 @@ class Agent(BaseAgent):
1584
1591
 
1585
1592
  def count_tokens(self) -> int:
1586
1593
  """Count the tokens in the current context window"""
1587
- return self.get_context_window().context_window_size_current
1594
+ context_window_breakdown = self.get_context_window()
1595
+ return context_window_breakdown.context_window_size_current
1588
1596
 
1589
1597
 
1590
1598
  def save_agent(agent: Agent, ms: MetadataStore):
@@ -1597,7 +1605,7 @@ def save_agent(agent: Agent, ms: MetadataStore):
1597
1605
 
1598
1606
  # NOTE: we're saving agent memory before persisting the agent to ensure
1599
1607
  # that allocated block_ids for each memory block are present in the agent model
1600
- save_agent_memory(agent=agent, ms=ms)
1608
+ save_agent_memory(agent=agent)
1601
1609
 
1602
1610
  if ms.get_agent(agent_id=agent.agent_state.id):
1603
1611
  ms.update_agent(agent_state)
@@ -1608,7 +1616,7 @@ def save_agent(agent: Agent, ms: MetadataStore):
1608
1616
  assert isinstance(agent.agent_state.memory, Memory), f"Memory is not a Memory object: {type(agent_state.memory)}"
1609
1617
 
1610
1618
 
1611
- def save_agent_memory(agent: Agent, ms: MetadataStore):
1619
+ def save_agent_memory(agent: Agent):
1612
1620
  """
1613
1621
  Save agent memory to metadata store. Memory is a collection of blocks and each block is persisted to the block table.
1614
1622
 
@@ -1617,14 +1625,12 @@ def save_agent_memory(agent: Agent, ms: MetadataStore):
1617
1625
 
1618
1626
  for block_dict in agent.memory.to_dict()["memory"].values():
1619
1627
  # TODO: block creation should happen in one place to enforce these sort of constraints consistently.
1620
- if block_dict.get("user_id", None) is None:
1621
- block_dict["user_id"] = agent.agent_state.user_id
1622
1628
  block = Block(**block_dict)
1623
1629
  # FIXME: should we expect for block values to be None? If not, we need to figure out why that is
1624
1630
  # the case in some tests, if so we should relax the DB constraint.
1625
1631
  if block.value is None:
1626
1632
  block.value = ""
1627
- ms.update_or_create_block(block)
1633
+ BlockManager().create_or_update_block(block, actor=agent.user)
1628
1634
 
1629
1635
 
1630
1636
  def strip_name_field_from_user_message(user_message_text: str) -> Tuple[str, Optional[str]]:
letta/agent_store/db.py CHANGED
@@ -380,9 +380,10 @@ class PostgresStorageConnector(SQLStorageConnector):
380
380
  else:
381
381
  raise ValueError(f"Table type {table_type} not implemented")
382
382
 
383
- for c in self.db_model.__table__.columns:
384
- if c.name == "embedding":
385
- assert isinstance(c.type, Vector), f"Embedding column must be of type Vector, got {c.type}"
383
+ if settings.pg_uri:
384
+ for c in self.db_model.__table__.columns:
385
+ if c.name == "embedding":
386
+ assert isinstance(c.type, Vector), f"Embedding column must be of type Vector, got {c.type}"
386
387
 
387
388
  from letta.server.server import db_context
388
389
 
letta/cli/cli.py CHANGED
@@ -220,7 +220,7 @@ def run(
220
220
 
221
221
  # create agent
222
222
  tools = [server.tool_manager.get_tool_by_name(tool_name=tool_name, actor=client.user) for tool_name in agent_state.tools]
223
- letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools)
223
+ letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools, user=client.user)
224
224
 
225
225
  else: # create new agent
226
226
  # create new agent config: override defaults with args if provided
@@ -320,6 +320,7 @@ def run(
320
320
  tools=tools,
321
321
  # gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
322
322
  first_message_verify_mono=True if (model is not None and "gpt-4" in model) else False,
323
+ user=client.user,
323
324
  )
324
325
  save_agent(agent=letta_agent, ms=ms)
325
326
  typer.secho(f"🎉 Created new agent '{letta_agent.agent_state.name}' (id={letta_agent.agent_state.id})", fg=typer.colors.GREEN)
letta/client/client.py CHANGED
@@ -12,12 +12,10 @@ from letta.memory import get_memory_functions
12
12
  from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgentState
13
13
  from letta.schemas.block import (
14
14
  Block,
15
- CreateBlock,
16
- CreateHuman,
17
- CreatePersona,
15
+ BlockCreate,
16
+ BlockUpdate,
18
17
  Human,
19
18
  Persona,
20
- UpdateBlock,
21
19
  UpdateHuman,
22
20
  UpdatePersona,
23
21
  )
@@ -883,8 +881,8 @@ class RESTClient(AbstractClient):
883
881
  else:
884
882
  return [Block(**block) for block in response.json()]
885
883
 
886
- def create_block(self, label: str, text: str, template_name: Optional[str] = None, template: bool = False) -> Block: #
887
- request = CreateBlock(label=label, value=text, template=template, template_name=template_name)
884
+ def create_block(self, label: str, value: str, template_name: Optional[str] = None, is_template: bool = False) -> Block: #
885
+ request = BlockCreate(label=label, value=value, template=is_template, template_name=template_name)
888
886
  response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks", json=request.model_dump(), headers=self.headers)
889
887
  if response.status_code != 200:
890
888
  raise ValueError(f"Failed to create block: {response.text}")
@@ -896,7 +894,7 @@ class RESTClient(AbstractClient):
896
894
  return Block(**response.json())
897
895
 
898
896
  def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
899
- request = UpdateBlock(id=block_id, template_name=name, value=text)
897
+ request = BlockUpdate(id=block_id, template_name=name, value=text)
900
898
  response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", json=request.model_dump(), headers=self.headers)
901
899
  if response.status_code != 200:
902
900
  raise ValueError(f"Failed to update block: {response.text}")
@@ -950,7 +948,7 @@ class RESTClient(AbstractClient):
950
948
  Returns:
951
949
  human (Human): Human block
952
950
  """
953
- return self.create_block(label="human", template_name=name, text=text, template=True)
951
+ return self.create_block(label="human", template_name=name, value=text, is_template=True)
954
952
 
955
953
  def update_human(self, human_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Human:
956
954
  """
@@ -990,7 +988,7 @@ class RESTClient(AbstractClient):
990
988
  Returns:
991
989
  persona (Persona): Persona block
992
990
  """
993
- return self.create_block(label="persona", template_name=name, text=text, template=True)
991
+ return self.create_block(label="persona", template_name=name, value=text, is_template=True)
994
992
 
995
993
  def update_persona(self, persona_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Persona:
996
994
  """
@@ -2125,8 +2123,7 @@ class LocalClient(AbstractClient):
2125
2123
  # humans / personas
2126
2124
 
2127
2125
  def get_block_id(self, name: str, label: str) -> str:
2128
-
2129
- block = self.server.get_blocks(name=name, label=label, user_id=self.user_id, template=True)
2126
+ block = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label=label, is_template=True)
2130
2127
  if not block:
2131
2128
  return None
2132
2129
  return block[0].id
@@ -2142,7 +2139,7 @@ class LocalClient(AbstractClient):
2142
2139
  Returns:
2143
2140
  human (Human): Human block
2144
2141
  """
2145
- return self.server.create_block(CreateHuman(template_name=name, value=text, user_id=self.user_id), user_id=self.user_id)
2142
+ return self.server.block_manager.create_or_update_block(Human(template_name=name, value=text), actor=self.user)
2146
2143
 
2147
2144
  def create_persona(self, name: str, text: str):
2148
2145
  """
@@ -2155,7 +2152,7 @@ class LocalClient(AbstractClient):
2155
2152
  Returns:
2156
2153
  persona (Persona): Persona block
2157
2154
  """
2158
- return self.server.create_block(CreatePersona(template_name=name, value=text, user_id=self.user_id), user_id=self.user_id)
2155
+ return self.server.block_manager.create_or_update_block(Persona(template_name=name, value=text), actor=self.user)
2159
2156
 
2160
2157
  def list_humans(self):
2161
2158
  """
@@ -2164,7 +2161,7 @@ class LocalClient(AbstractClient):
2164
2161
  Returns:
2165
2162
  humans (List[Human]): List of human blocks
2166
2163
  """
2167
- return self.server.get_blocks(label="human", user_id=self.user_id, template=True)
2164
+ return self.server.block_manager.get_blocks(actor=self.user, label="human", is_template=True)
2168
2165
 
2169
2166
  def list_personas(self) -> List[Persona]:
2170
2167
  """
@@ -2173,7 +2170,7 @@ class LocalClient(AbstractClient):
2173
2170
  Returns:
2174
2171
  personas (List[Persona]): List of persona blocks
2175
2172
  """
2176
- return self.server.get_blocks(label="persona", user_id=self.user_id, template=True)
2173
+ return self.server.block_manager.get_blocks(actor=self.user, label="persona", is_template=True)
2177
2174
 
2178
2175
  def update_human(self, human_id: str, text: str):
2179
2176
  """
@@ -2186,7 +2183,9 @@ class LocalClient(AbstractClient):
2186
2183
  Returns:
2187
2184
  human (Human): Updated human block
2188
2185
  """
2189
- return self.server.update_block(UpdateHuman(id=human_id, value=text, user_id=self.user_id, template=True))
2186
+ return self.server.block_manager.update_block(
2187
+ block_id=human_id, block_update=UpdateHuman(value=text, is_template=True), actor=self.user
2188
+ )
2190
2189
 
2191
2190
  def update_persona(self, persona_id: str, text: str):
2192
2191
  """
@@ -2199,7 +2198,9 @@ class LocalClient(AbstractClient):
2199
2198
  Returns:
2200
2199
  persona (Persona): Updated persona block
2201
2200
  """
2202
- return self.server.update_block(UpdatePersona(id=persona_id, value=text, user_id=self.user_id, template=True))
2201
+ return self.server.block_manager.update_block(
2202
+ block_id=persona_id, block_update=UpdatePersona(value=text, is_template=True), actor=self.user
2203
+ )
2203
2204
 
2204
2205
  def get_persona(self, id: str) -> Persona:
2205
2206
  """
@@ -2212,7 +2213,7 @@ class LocalClient(AbstractClient):
2212
2213
  persona (Persona): Persona block
2213
2214
  """
2214
2215
  assert id, f"Persona ID must be provided"
2215
- return Persona(**self.server.get_block(id).model_dump())
2216
+ return Persona(**self.server.block_manager.get_block_by_id(id, actor=self.user).model_dump())
2216
2217
 
2217
2218
  def get_human(self, id: str) -> Human:
2218
2219
  """
@@ -2225,7 +2226,7 @@ class LocalClient(AbstractClient):
2225
2226
  human (Human): Human block
2226
2227
  """
2227
2228
  assert id, f"Human ID must be provided"
2228
- return Human(**self.server.get_block(id).model_dump())
2229
+ return Human(**self.server.block_manager.get_block_by_id(id, actor=self.user).model_dump())
2229
2230
 
2230
2231
  def get_persona_id(self, name: str) -> str:
2231
2232
  """
@@ -2237,7 +2238,7 @@ class LocalClient(AbstractClient):
2237
2238
  Returns:
2238
2239
  id (str): ID of the persona block
2239
2240
  """
2240
- persona = self.server.get_blocks(name=name, label="persona", user_id=self.user_id, template=True)
2241
+ persona = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label="persona", is_template=True)
2241
2242
  if not persona:
2242
2243
  return None
2243
2244
  return persona[0].id
@@ -2252,7 +2253,7 @@ class LocalClient(AbstractClient):
2252
2253
  Returns:
2253
2254
  id (str): ID of the human block
2254
2255
  """
2255
- human = self.server.get_blocks(name=name, label="human", user_id=self.user_id, template=True)
2256
+ human = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label="human", is_template=True)
2256
2257
  if not human:
2257
2258
  return None
2258
2259
  return human[0].id
@@ -2264,7 +2265,7 @@ class LocalClient(AbstractClient):
2264
2265
  Args:
2265
2266
  id (str): ID of the persona block
2266
2267
  """
2267
- self.server.delete_block(id)
2268
+ self.delete_block(id)
2268
2269
 
2269
2270
  def delete_human(self, id: str):
2270
2271
  """
@@ -2273,7 +2274,7 @@ class LocalClient(AbstractClient):
2273
2274
  Args:
2274
2275
  id (str): ID of the human block
2275
2276
  """
2276
- self.server.delete_block(id)
2277
+ self.delete_block(id)
2277
2278
 
2278
2279
  # tools
2279
2280
  def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
@@ -2661,9 +2662,9 @@ class LocalClient(AbstractClient):
2661
2662
  Returns:
2662
2663
  blocks (List[Block]): List of blocks
2663
2664
  """
2664
- return self.server.get_blocks(label=label, template=templates_only)
2665
+ return self.server.block_manager.get_blocks(actor=self.user, label=label, is_template=templates_only)
2665
2666
 
2666
- def create_block(self, label: str, text: str, template_name: Optional[str] = None, template: bool = False) -> Block: #
2667
+ def create_block(self, label: str, value: str, template_name: Optional[str] = None, is_template: bool = False) -> Block: #
2667
2668
  """
2668
2669
  Create a block
2669
2670
 
@@ -2675,8 +2676,8 @@ class LocalClient(AbstractClient):
2675
2676
  Returns:
2676
2677
  block (Block): Created block
2677
2678
  """
2678
- return self.server.create_block(
2679
- CreateBlock(label=label, template_name=template_name, value=text, user_id=self.user_id, template=template), user_id=self.user_id
2679
+ return self.server.block_manager.create_or_update_block(
2680
+ Block(label=label, template_name=template_name, value=value, is_template=is_template), actor=self.user
2680
2681
  )
2681
2682
 
2682
2683
  def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
@@ -2691,7 +2692,9 @@ class LocalClient(AbstractClient):
2691
2692
  Returns:
2692
2693
  block (Block): Updated block
2693
2694
  """
2694
- return self.server.update_block(UpdateBlock(id=block_id, template_name=name, value=text))
2695
+ return self.server.block_manager.update_block(
2696
+ block_id=block_id, block_update=BlockUpdate(template_name=name, value=text), actor=self.user
2697
+ )
2695
2698
 
2696
2699
  def get_block(self, block_id: str) -> Block:
2697
2700
  """
@@ -2703,7 +2706,7 @@ class LocalClient(AbstractClient):
2703
2706
  Returns:
2704
2707
  block (Block): Block
2705
2708
  """
2706
- return self.server.get_block(block_id)
2709
+ return self.server.block_manager.get_block_by_id(block_id, actor=self.user)
2707
2710
 
2708
2711
  def delete_block(self, id: str) -> Block:
2709
2712
  """
@@ -2715,7 +2718,7 @@ class LocalClient(AbstractClient):
2715
2718
  Returns:
2716
2719
  block (Block): Deleted block
2717
2720
  """
2718
- return self.server.delete_block(id)
2721
+ return self.server.block_manager.delete_block(id, actor=self.user)
2719
2722
 
2720
2723
  def set_default_llm_config(self, llm_config: LLMConfig):
2721
2724
  """
letta/constants.py CHANGED
@@ -19,7 +19,7 @@ IN_CONTEXT_MEMORY_KEYWORD = "CORE_MEMORY"
19
19
  TOOL_CALL_ID_MAX_LEN = 29
20
20
 
21
21
  # minimum context window size
22
- MIN_CONTEXT_WINDOW = 4000
22
+ MIN_CONTEXT_WINDOW = 4096
23
23
 
24
24
  # embeddings
25
25
  MAX_EMBEDDING_DIM = 4096 # maximum supported embeding size - do NOT change or else DBs will need to be reset
@@ -158,3 +158,6 @@ RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE = 5
158
158
  # TODO Is this config or constant?
159
159
  CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 2000
160
160
  CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 2000
161
+
162
+ MAX_FILENAME_LENGTH = 255
163
+ RESERVED_FILENAMES = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "LPT1", "LPT2"}
@@ -3,7 +3,7 @@ import inspect
3
3
  import os
4
4
  from textwrap import dedent # remove indentation
5
5
  from types import ModuleType
6
- from typing import Optional
6
+ from typing import Optional, List
7
7
 
8
8
  from letta.constants import CLI_WARNING_PREFIX
9
9
  from letta.functions.schema_generator import generate_schema
@@ -15,6 +15,7 @@ def derive_openai_json_schema(source_code: str, name: Optional[str] = None) -> d
15
15
  # Define a custom environment with necessary imports
16
16
  env = {
17
17
  "Optional": Optional, # Add any other required imports here
18
+ "List": List
18
19
  }
19
20
 
20
21
  env.update(globals())
@@ -25,6 +25,7 @@ from letta.local_llm.constants import (
25
25
  INNER_THOUGHTS_KWARG,
26
26
  INNER_THOUGHTS_KWARG_DESCRIPTION,
27
27
  )
28
+ from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
28
29
  from letta.schemas.llm_config import LLMConfig
29
30
  from letta.schemas.message import Message
30
31
  from letta.schemas.openai.chat_completion_request import (
@@ -33,6 +34,7 @@ from letta.schemas.openai.chat_completion_request import (
33
34
  cast_message_to_subtype,
34
35
  )
35
36
  from letta.schemas.openai.chat_completion_response import ChatCompletionResponse
37
+ from letta.settings import ModelSettings
36
38
  from letta.streaming_interface import (
37
39
  AgentChunkStreamingInterface,
38
40
  AgentRefreshStreamingInterface,
@@ -122,10 +124,19 @@ def create(
122
124
  """Return response to chat completion with backoff"""
123
125
  from letta.utils import printd
124
126
 
127
+ # Count the tokens first, if there's an overflow exit early by throwing an error up the stack
128
+ # NOTE: we want to include a specific substring in the error message to trigger summarization
129
+ messages_oai_format = [m.to_openai_dict() for m in messages]
130
+ prompt_tokens = num_tokens_from_messages(messages=messages_oai_format, model=llm_config.model)
131
+ function_tokens = num_tokens_from_functions(functions=functions, model=llm_config.model) if functions else 0
132
+ if prompt_tokens + function_tokens > llm_config.context_window:
133
+ raise Exception(f"Request exceeds maximum context length ({prompt_tokens + function_tokens} > {llm_config.context_window} tokens)")
134
+
125
135
  if not model_settings:
126
136
  from letta.settings import model_settings
127
137
 
128
138
  model_settings = model_settings
139
+ assert isinstance(model_settings, ModelSettings)
129
140
 
130
141
  printd(f"Using model {llm_config.model_endpoint_type}, endpoint: {llm_config.model_endpoint}")
131
142
 
@@ -326,6 +337,33 @@ def create(
326
337
 
327
338
  return response
328
339
 
340
+ elif llm_config.model_endpoint_type == "together":
341
+ """TogetherAI endpoint that goes via /completions instead of /chat/completions"""
342
+
343
+ if stream:
344
+ raise NotImplementedError(f"Streaming not yet implemented for TogetherAI (via the /completions endpoint).")
345
+
346
+ if model_settings.together_api_key is None and llm_config.model_endpoint == "https://api.together.ai/v1/completions":
347
+ raise ValueError(f"TogetherAI key is missing from letta config file")
348
+
349
+ return get_chat_completion(
350
+ model=llm_config.model,
351
+ messages=messages,
352
+ functions=functions,
353
+ functions_python=functions_python,
354
+ function_call=function_call,
355
+ context_window=llm_config.context_window,
356
+ endpoint=llm_config.model_endpoint,
357
+ endpoint_type="vllm", # NOTE: use the vLLM path through /completions
358
+ wrapper=llm_config.model_wrapper,
359
+ user=str(user_id),
360
+ # hint
361
+ first_message=first_message,
362
+ # auth-related
363
+ auth_type="bearer_token", # NOTE: Together expects bearer token auth
364
+ auth_key=model_settings.together_api_key,
365
+ )
366
+
329
367
  # local model
330
368
  else:
331
369
  if stream:
letta/llm_api/openai.py CHANGED
@@ -536,7 +536,6 @@ def openai_chat_completions_request(
536
536
  tool["function"] = convert_to_structured_output(tool["function"])
537
537
 
538
538
  response_json = make_post_request(url, headers, data)
539
-
540
539
  return ChatCompletionResponse(**response_json)
541
540
 
542
541
 
letta/local_llm/utils.py CHANGED
@@ -94,7 +94,10 @@ def num_tokens_from_functions(functions: List[dict], model: str = "gpt-4"):
94
94
  num_tokens = 0
95
95
  for function in functions:
96
96
  function_tokens = len(encoding.encode(function["name"]))
97
- function_tokens += len(encoding.encode(function["description"]))
97
+ if function["description"]:
98
+ function_tokens += len(encoding.encode(function["description"]))
99
+ else:
100
+ raise ValueError(f"Function {function['name']} has no description, function: {function}")
98
101
 
99
102
  if "parameters" in function:
100
103
  parameters = function["parameters"]
@@ -184,6 +187,7 @@ def num_tokens_from_messages(messages: List[dict], model: str = "gpt-4") -> int:
184
187
  https://community.openai.com/t/how-to-calculate-the-tokens-when-using-function-call/266573/11
185
188
  """
186
189
  try:
190
+ # Attempt to search for the encoding based on the model string
187
191
  encoding = tiktoken.encoding_for_model(model)
188
192
  except KeyError:
189
193
  # print("Warning: model not found. Using cl100k_base encoding.")
@@ -228,7 +232,13 @@ def num_tokens_from_messages(messages: List[dict], model: str = "gpt-4") -> int:
228
232
  # num_tokens += len(encoding.encode(value["arguments"]))
229
233
 
230
234
  else:
231
- num_tokens += len(encoding.encode(value))
235
+ if value is None:
236
+ # raise ValueError(f"Message has null value: {key} with value: {value} - message={message}")
237
+ warnings.warn(f"Message has null value: {key} with value: {value} - message={message}")
238
+ else:
239
+ if not isinstance(value, str):
240
+ raise ValueError(f"Message has non-string value: {key} with value: {value} - message={message}")
241
+ num_tokens += len(encoding.encode(value))
232
242
 
233
243
  if key == "name":
234
244
  num_tokens += tokens_per_name