langroid 0.21.0__py3-none-any.whl → 0.22.0__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.
langroid/agent/base.py CHANGED
@@ -1450,6 +1450,40 @@ class Agent(ABC):
1450
1450
 
1451
1451
  return None
1452
1452
 
1453
+ def _maybe_truncate_result(
1454
+ self, result: str | ChatDocument | None, max_tokens: int | None
1455
+ ) -> str | ChatDocument | None:
1456
+ """
1457
+ Truncate the result string to `max_tokens` tokens.
1458
+ """
1459
+ if result is None or max_tokens is None:
1460
+ return result
1461
+ result_str = result.content if isinstance(result, ChatDocument) else result
1462
+ num_tokens = (
1463
+ self.parser.num_tokens(result_str)
1464
+ if self.parser is not None
1465
+ else len(result_str) / 4.0
1466
+ )
1467
+ if num_tokens <= max_tokens:
1468
+ return result
1469
+ truncate_warning = f"""
1470
+ The TOOL result was large, so it was truncated to {max_tokens} tokens.
1471
+ To get the full result, the TOOL must be called again.
1472
+ """
1473
+ if isinstance(result, str):
1474
+ return (
1475
+ self.parser.truncate_tokens(result, max_tokens)
1476
+ if self.parser is not None
1477
+ else result[: max_tokens * 4] # approx truncate
1478
+ ) + truncate_warning
1479
+ elif isinstance(result, ChatDocument):
1480
+ result.content = (
1481
+ self.parser.truncate_tokens(result.content, max_tokens)
1482
+ if self.parser is not None
1483
+ else result.content[: max_tokens * 4] # approx truncate
1484
+ ) + truncate_warning
1485
+ return result
1486
+
1453
1487
  def handle_tool_message(
1454
1488
  self,
1455
1489
  tool: ToolMessage,
@@ -1485,7 +1519,9 @@ class Agent(ABC):
1485
1519
  # not a pydantic validation error,
1486
1520
  # which we check in `handle_message`
1487
1521
  raise e
1488
- return result # type: ignore
1522
+ return self._maybe_truncate_result(
1523
+ result, tool._max_result_tokens
1524
+ ) # type: ignore
1489
1525
 
1490
1526
  def num_tokens(self, prompt: str | List[LLMMessage]) -> int:
1491
1527
  if self.parser is None:
@@ -623,6 +623,33 @@ class ChatAgent(Agent):
623
623
  self.llm_tools_usable.discard(r)
624
624
  self.llm_functions_usable.discard(r)
625
625
 
626
+ def _reduce_raw_tool_results(self, message: ChatDocument) -> None:
627
+ """
628
+ If message is the result of a ToolMessage that had the
629
+ flag `_retain_raw_results = False`, then we replace contents
630
+ with a placeholder message.
631
+ """
632
+ parent_message: ChatDocument | None = message.parent
633
+ tools = [] if parent_message is None else parent_message.tool_messages
634
+ truncate_tools = [t for t in tools if t._max_retained_tokens is not None]
635
+ limiting_tool = truncate_tools[0] if len(truncate_tools) > 0 else None
636
+ if limiting_tool is not None and limiting_tool._max_retained_tokens is not None:
637
+ tool_name = limiting_tool.default_value("request")
638
+ max_tokens: int = limiting_tool._max_retained_tokens
639
+ truncation_warning = f"""
640
+ The result of the {tool_name} tool were too large,
641
+ and has been truncated to {max_tokens} tokens.
642
+ To obtain the full result, the tool needs to be re-used.
643
+ """
644
+ llm_msg = self.message_history[message.metadata.msg_idx]
645
+ orig_content = llm_msg.content
646
+ new_content = (
647
+ self.parser.truncate_tokens(orig_content, max_tokens)
648
+ if self.parser is not None
649
+ else orig_content[: max_tokens * 4] # approx truncation
650
+ )
651
+ llm_msg.content = new_content + "\n\n" + truncation_warning
652
+
626
653
  def llm_response(
627
654
  self, message: Optional[str | ChatDocument] = None
628
655
  ) -> Optional[ChatDocument]:
@@ -650,6 +677,8 @@ class ChatAgent(Agent):
650
677
  self.message_history.extend(ChatDocument.to_LLMMessage(response))
651
678
  response.metadata.msg_idx = len(self.message_history) - 1
652
679
  response.metadata.agent_id = self.id
680
+ if isinstance(message, ChatDocument):
681
+ self._reduce_raw_tool_results(message)
653
682
  # Preserve trail of tool_ids for OpenAI Assistant fn-calls
654
683
  response.metadata.tool_ids = (
655
684
  []
@@ -681,6 +710,8 @@ class ChatAgent(Agent):
681
710
  self.message_history.extend(ChatDocument.to_LLMMessage(response))
682
711
  response.metadata.msg_idx = len(self.message_history) - 1
683
712
  response.metadata.agent_id = self.id
713
+ if isinstance(message, ChatDocument):
714
+ self._reduce_raw_tool_results(message)
684
715
  # Preserve trail of tool_ids for OpenAI Assistant fn-calls
685
716
  response.metadata.tool_ids = (
686
717
  []
@@ -94,7 +94,6 @@ class ArangoChatAgentConfig(ChatAgentConfig):
94
94
  prepopulate_schema: bool = True
95
95
  use_functions_api: bool = True
96
96
  max_num_results: int = 10 # how many results to return from AQL query
97
- max_result_tokens: int = 1000 # truncate long results to this many tokens
98
97
  max_schema_fields: int = 500 # max fields to show in schema
99
98
  max_tries: int = 10 # how many attempts to answer user question
100
99
  use_tools: bool = False
@@ -343,28 +342,6 @@ class ArangoChatAgent(ChatAgent):
343
342
  success=False, data=f"Failed after max retries: {str(e)}"
344
343
  )
345
344
 
346
- def _limit_tokens(self, text: str) -> str:
347
- result = text
348
- n_toks = self.num_tokens(result)
349
- if n_toks > self.config.max_result_tokens:
350
- logger.warning(
351
- f"""
352
- Your query resulted in a large result of
353
- {n_toks} tokens,
354
- which will be truncated to {self.config.max_result_tokens} tokens.
355
- If this does not give satisfactory results,
356
- please retry with a more focused query.
357
- """
358
- )
359
- if self.parser is not None:
360
- result = self.parser.truncate_tokens(
361
- result,
362
- self.config.max_result_tokens,
363
- )
364
- else:
365
- result = result[: self.config.max_result_tokens * 4] # truncate roughly
366
- return result
367
-
368
345
  def aql_retrieval_tool(self, msg: AQLRetrievalTool) -> str:
369
346
  """Handle AQL query for data retrieval"""
370
347
  if not self.tried_schema:
@@ -395,9 +372,7 @@ class ArangoChatAgent(ChatAgent):
395
372
  Try modifying your query based on the RETRY-SUGGESTIONS
396
373
  in your instructions.
397
374
  """
398
- # truncate long results
399
- result = str(response.data)
400
- return self._limit_tokens(result)
375
+ return str(response.data)
401
376
 
402
377
  def aql_creation_tool(self, msg: AQLCreationTool) -> str:
403
378
  """Handle AQL query for creating data"""
@@ -13,6 +13,9 @@ class AQLRetrievalTool(ToolMessage):
13
13
  """
14
14
  aql_query: str
15
15
 
16
+ _max_result_tokens = 500
17
+ _max_retained_tokens = 200
18
+
16
19
  @classmethod
17
20
  def examples(cls) -> List[ToolMessage | Tuple[str, ToolMessage]]:
18
21
  """Few-shot examples to include in tool instructions."""
@@ -98,5 +101,7 @@ class ArangoSchemaTool(ToolMessage):
98
101
  properties: bool = True
99
102
  collections: List[str] | None = None
100
103
 
104
+ _max_result_tokens = 500
105
+
101
106
 
102
107
  arango_schema_tool_name = ArangoSchemaTool.default_value("request")
@@ -44,7 +44,19 @@ class ToolMessage(ABC, BaseModel):
44
44
 
45
45
  _allow_llm_use: bool = True # allow an LLM to use (i.e. generate) this tool?
46
46
 
47
- # model_config = ConfigDict(extra=Extra.allow)
47
+ # Optional param to limit number of result tokens to retain in msg history.
48
+ # Some tools can have large results that we may not want to fully retain,
49
+ # e.g. result of a db query, which the LLM later reduces to a summary, so
50
+ # in subsequent dialog we may only want to retain the summary,
51
+ # and replace this raw result truncated to _max_result_tokens.
52
+ # Important to note: unlike _max_result_tokens, this param is used
53
+ # NOT used to immediately truncate the result;
54
+ # it is only used to truncate what is retained in msg history AFTER the
55
+ # response to this result.
56
+ _max_retained_tokens: int | None = None
57
+
58
+ # Optional param to limit number of tokens in the result of the tool.
59
+ _max_result_tokens: int | None = None
48
60
 
49
61
  class Config:
50
62
  extra = Extra.allow
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.21.0
3
+ Version: 0.22.0
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -207,7 +207,7 @@ import langroid.language_models as lm
207
207
  # set up LLM
208
208
  llm_cfg = lm.OpenAIGPTConfig( # or OpenAIAssistant to use Assistant API
209
209
  # any model served via an OpenAI-compatible API
210
- chat_model=lm.OpenAIChatModel.GPT4_TURBO, # or, e.g., "ollama/mistral"
210
+ chat_model=lm.OpenAIChatModel.GPT4o, # or, e.g., "ollama/mistral"
211
211
  )
212
212
  # use LLM directly
213
213
  mdl = lm.OpenAIGPT(llm_cfg)
@@ -249,6 +249,9 @@ teacher_task.run()
249
249
  <summary> <b>Click to expand</b></summary>
250
250
 
251
251
  - **Nov 2024:**
252
+ - **[0.22.0](https://langroid.github.io/langroid/notes/large-tool-results/)**:
253
+ Optional parameters to truncate large tool results.
254
+ - **[0.21.0](https://langroid.github.io/langroid/notes/gemini/)** Direct support for Gemini models via OpenAI client instead of using LiteLLM.
252
255
  - **[0.20.0](https://github.com/langroid/langroid/releases/tag/0.20.0)** Support for
253
256
  ArangoDB Knowledge Graphs.
254
257
  - **Oct 2024:**
@@ -364,7 +367,7 @@ teacher_task.run()
364
367
  decides a filter and rephrase query to send to a RAG agent.
365
368
  - **[0.1.141](https://github.com/langroid/langroid/releases/tag/0.1.141):**
366
369
  API Simplifications to reduce boilerplate:
367
- auto-select an available OpenAI model (preferring gpt-4-turbo), simplifies defaults.
370
+ auto-select an available OpenAI model (preferring gpt-4o), simplifies defaults.
368
371
  Simpler `Task` initialization with default `ChatAgent`.
369
372
  - **Nov 2023:**
370
373
  - **[0.1.126](https://github.com/langroid/langroid/releases/tag/0.1.126):**
@@ -648,7 +651,7 @@ provides more information, and you can set each environment variable as follows:
648
651
  - `AZURE_OPENAI_API_BASE` from the value of `ENDPOINT`, typically looks like `https://your.domain.azure.com`.
649
652
  - For `AZURE_OPENAI_API_VERSION`, you can use the default value in `.env-template`, and latest version can be found [here](https://learn.microsoft.com/en-us/azure/ai-services/openai/whats-new#azure-openai-chat-completion-general-availability-ga)
650
653
  - `AZURE_OPENAI_DEPLOYMENT_NAME` is the name of the deployed model, which is defined by the user during the model setup
651
- - `AZURE_OPENAI_MODEL_NAME` Azure OpenAI allows specific model names when you select the model for your deployment. You need to put precisly the exact model name that was selected. For example, GPT-3.5 (should be `gpt-35-turbo-16k` or `gpt-35-turbo`) or GPT-4 (should be `gpt-4-32k` or `gpt-4`).
654
+ - `AZURE_OPENAI_MODEL_NAME` Azure OpenAI allows specific model names when you select the model for your deployment. You need to put precisly the exact model name that was selected. For example, GPT-4 (should be `gpt-4-32k` or `gpt-4`).
652
655
  - `AZURE_OPENAI_MODEL_VERSION` is required if `AZURE_OPENAI_MODEL_NAME = gpt=4`, which will assist Langroid to determine the cost of the model
653
656
  </details>
654
657
 
@@ -1,19 +1,19 @@
1
1
  langroid/__init__.py,sha256=z_fCOLQJPOw3LLRPBlFB5-2HyCjpPgQa4m4iY5Fvb8Y,1800
2
2
  langroid/agent/__init__.py,sha256=ll0Cubd2DZ-fsCMl7e10hf9ZjFGKzphfBco396IKITY,786
3
- langroid/agent/base.py,sha256=sOZapdzHaB4kbCLu8vI_zZx78jIhv9fmWn0EWV4yTAE,65371
3
+ langroid/agent/base.py,sha256=FEAIVwFKlmkOSV3RfRR0-v5UofJBfsBUUSsSEE2ioUI,66824
4
4
  langroid/agent/batch.py,sha256=QZdlt1563hx4l3AXrCaGovE-PNG93M3DsvQAbDzdiS8,13705
5
5
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  langroid/agent/callbacks/chainlit.py,sha256=JJXI3UGTyTDg2FFath4rqY1GyUo_0pbVBt8CZpvdtn4,23289
7
- langroid/agent/chat_agent.py,sha256=GVuKXAwHACeTc1gC0y7Ywj8CmJfqu7InJ0AXtmz8nRw,50000
7
+ langroid/agent/chat_agent.py,sha256=2aUDqM6jsdeU8xt-P86JU2TuZhTpPkUVx9mkXFMy8Rs,51646
8
8
  langroid/agent/chat_document.py,sha256=FZ_PkeKU5OVp1IUlMvspfqxIXzlyd7J_F32DSYrxQ7E,17651
9
9
  langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
11
11
  langroid/agent/openai_assistant.py,sha256=2rjCZw45ysNBEGNzQM4uf0bTC4KkatGYAWcVcW4xcek,34337
12
12
  langroid/agent/special/__init__.py,sha256=gik_Xtm_zV7U9s30Mn8UX3Gyuy4jTjQe9zjiE3HWmEo,1273
13
13
  langroid/agent/special/arangodb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- langroid/agent/special/arangodb/arangodb_agent.py,sha256=CXD1Z4yXnWwre4ybg7BMhVFTgQq0zVXzobutyxIYCP4,26862
14
+ langroid/agent/special/arangodb/arangodb_agent.py,sha256=12Y54c84c9qXV-YXRBcI5HaqyiY75JR4TmqlURYKJAM,25851
15
15
  langroid/agent/special/arangodb/system_messages.py,sha256=udwfLleTdyz_DuxHuoiv2wHEZoAPBPbwdF_ivjIfP5c,6867
16
- langroid/agent/special/arangodb/tools.py,sha256=WasFERC1cToLOWi1cWqUs-TujU0A68gZWbhbP128obo,3499
16
+ langroid/agent/special/arangodb/tools.py,sha256=Mixl9WS0r0Crd4nrw2YAB0eY33fTsKISul1053eyeio,3590
17
17
  langroid/agent/special/arangodb/utils.py,sha256=LIevtkayIdVVXyj3jlbKH2WgdZTtH5-JLgbXOHC7uxs,1420
18
18
  langroid/agent/special/doc_chat_agent.py,sha256=xIqBOyLax_jMU0UevxqXf_aQUrRkW6MQUKpKnKvaqkQ,59281
19
19
  langroid/agent/special/lance_doc_chat_agent.py,sha256=s8xoRs0gGaFtDYFUSIRchsgDVbS5Q3C2b2mr3V1Fd-Q,10419
@@ -39,7 +39,7 @@ langroid/agent/special/sql/utils/tools.py,sha256=vFYysk6Vi7HJjII8B4RitA3pt_z3gkS
39
39
  langroid/agent/special/table_chat_agent.py,sha256=d9v2wsblaRx7oMnKhLV7uO_ujvk9gh59pSGvBXyeyNc,9659
40
40
  langroid/agent/structured_message.py,sha256=y7pud1EgRNeTFZlJmBkLmwME3yQJ_IYik-Xds9kdZbY,282
41
41
  langroid/agent/task.py,sha256=f7clh6p6Md0G4YGHqbFeeT88U4XoP0i3eatekV21hHE,86643
42
- langroid/agent/tool_message.py,sha256=jkN7uq7YwUC_wBcSCNUYjrB_His2YCfQay_lqIa4Tww,10498
42
+ langroid/agent/tool_message.py,sha256=qQebCWogYTtptznIF1mLERwspWjmVxDWFJeERBO-YsI,11207
43
43
  langroid/agent/tools/__init__.py,sha256=IMgCte-_ZIvCkozGQmvMqxIw7_nKLKzD78ccJL1bnQU,804
44
44
  langroid/agent/tools/duckduckgo_search_tool.py,sha256=NhsCaGZkdv28nja7yveAhSK_w6l_Ftym8agbrdzqgfo,1935
45
45
  langroid/agent/tools/file_tools.py,sha256=GjPB5YDILucYapElnvvoYpGJuZQ25ecLs2REv7edPEo,7292
@@ -142,8 +142,8 @@ langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3Hmh
142
142
  langroid/vector_store/momento.py,sha256=qR-zBF1RKVHQZPZQYW_7g-XpTwr46p8HJuYPCkfJbM4,10534
143
143
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
144
144
  langroid/vector_store/qdrantdb.py,sha256=v88lqFkepADvlN6lByUj9I4NEKa9X9lWH16uTPPbYrE,17457
145
- pyproject.toml,sha256=yiLcLTge7QxJ1EWYE05yUHpBADouFh36WO3SlRzJKL0,7488
146
- langroid-0.21.0.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
147
- langroid-0.21.0.dist-info/METADATA,sha256=TSl0anzCbcnchyI2yTczV4fK2KBkfJnUu2Jl8oqGGLA,56893
148
- langroid-0.21.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
149
- langroid-0.21.0.dist-info/RECORD,,
145
+ pyproject.toml,sha256=uLe37eP_bS9-a9g2l1uNFzDJgpCRqJvbmmwmLQlD3AA,7488
146
+ langroid-0.22.0.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
147
+ langroid-0.22.0.dist-info/METADATA,sha256=M15Qy48jMSppZvJRZLNNVLMZpQR9gNoDeFjoLBCKr6M,57107
148
+ langroid-0.22.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
149
+ langroid-0.22.0.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.21.0"
3
+ version = "0.22.0"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"