langroid 0.20.0__py3-none-any.whl → 0.21.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
@@ -880,7 +880,13 @@ class Agent(ABC):
880
880
  return cdoc
881
881
 
882
882
  def has_tool_message_attempt(self, msg: str | ChatDocument | None) -> bool:
883
- """Check whether msg contains a Tool/fn-call attempt (by the LLM)"""
883
+ """
884
+ Check whether msg contains a Tool/fn-call attempt (by the LLM).
885
+
886
+ CAUTION: This uses self.get_tool_messages(msg) which as a side-effect
887
+ may update msg.tool_messages when msg is a ChatDocument, if there are
888
+ any tools in msg.
889
+ """
884
890
  if msg is None:
885
891
  return False
886
892
  try:
@@ -921,6 +927,9 @@ class Agent(ABC):
921
927
  ) -> List[ToolMessage]:
922
928
  """
923
929
  Get ToolMessages recognized in msg, handle-able by this agent.
930
+ NOTE: as a side-effect, this will update msg.tool_messages
931
+ when msg is a ChatDocument and msg contains tool messages.
932
+
924
933
  If all_tools is True:
925
934
  - return all tools, i.e. any tool in self.llm_tools_known,
926
935
  whether it is handled by this agent or not;
@@ -27,6 +27,7 @@ from langroid.agent.special.arangodb.tools import (
27
27
  aql_retrieval_tool_name,
28
28
  arango_schema_tool_name,
29
29
  )
30
+ from langroid.agent.special.arangodb.utils import count_fields, trim_schema
30
31
  from langroid.agent.tools.orchestration import DoneTool, ForwardTool
31
32
  from langroid.exceptions import LangroidImportError
32
33
  from langroid.mytypes import Entity
@@ -88,11 +89,14 @@ class QueryResult(BaseModel):
88
89
  class ArangoChatAgentConfig(ChatAgentConfig):
89
90
  arango_settings: ArangoSettings = ArangoSettings()
90
91
  system_message: str = DEFAULT_ARANGO_CHAT_SYSTEM_MESSAGE
91
- kg_schema: Optional[Dict[str, List[Dict[str, Any]]]] = None
92
+ kg_schema: str | Dict[str, List[Dict[str, Any]]] | None = None
92
93
  database_created: bool = False
93
- use_schema_tools: bool = True
94
+ prepopulate_schema: bool = True
94
95
  use_functions_api: bool = True
96
+ max_num_results: int = 10 # how many results to return from AQL query
95
97
  max_result_tokens: int = 1000 # truncate long results to this many tokens
98
+ max_schema_fields: int = 500 # max fields to show in schema
99
+ max_tries: int = 10 # how many attempts to answer user question
96
100
  use_tools: bool = False
97
101
  schema_sample_pct: float = 0
98
102
  # whether the agent is used in a continuous chat with user,
@@ -103,16 +107,81 @@ class ArangoChatAgentConfig(ChatAgentConfig):
103
107
 
104
108
  class ArangoChatAgent(ChatAgent):
105
109
  def __init__(self, config: ArangoChatAgentConfig):
110
+ super().__init__(config)
106
111
  self.config: ArangoChatAgentConfig = config
112
+ self.init_state()
107
113
  self._validate_config()
108
114
  self._import_arango()
109
115
  self._initialize_db()
110
116
  self._init_tools_sys_message()
111
- self.init_state()
112
117
 
113
118
  def init_state(self) -> None:
114
119
  super().init_state()
115
120
  self.current_retrieval_aql_query: str = ""
121
+ self.current_schema_params: ArangoSchemaTool = ArangoSchemaTool()
122
+ self.num_tries = 0 # how many attempts to answer user question
123
+
124
+ def user_response(
125
+ self,
126
+ msg: Optional[str | ChatDocument] = None,
127
+ ) -> Optional[ChatDocument]:
128
+ response = super().user_response(msg)
129
+ if response is None:
130
+ return None
131
+ response_str = response.content if response is not None else ""
132
+ if response_str != "":
133
+ self.num_tries = 0 # reset number of tries if user responds
134
+ return response
135
+
136
+ def llm_response(
137
+ self, message: Optional[str | ChatDocument] = None
138
+ ) -> Optional[ChatDocument]:
139
+ if self.num_tries > self.config.max_tries:
140
+ if self.config.chat_mode:
141
+ return self.create_llm_response(
142
+ content=f"""
143
+ {self.config.addressing_prefix}User
144
+ I give up, since I have exceeded the
145
+ maximum number of tries ({self.config.max_tries}).
146
+ Feel free to give me some hints!
147
+ """
148
+ )
149
+ else:
150
+ return self.create_llm_response(
151
+ tool_messages=[
152
+ DoneTool(
153
+ content=f"""
154
+ Exceeded maximum number of tries ({self.config.max_tries}).
155
+ """
156
+ )
157
+ ]
158
+ )
159
+
160
+ if isinstance(message, ChatDocument) and message.metadata.sender == Entity.USER:
161
+ message.content = (
162
+ message.content
163
+ + "\n"
164
+ + """
165
+ (REMEMBER, Do NOT use more than ONE TOOL/FUNCTION at a time!
166
+ you must WAIT for a helper to send you the RESULT(S) before
167
+ making another TOOL/FUNCTION call)
168
+ """
169
+ )
170
+
171
+ response = super().llm_response(message)
172
+ if (
173
+ response is not None
174
+ and self.config.chat_mode
175
+ and self.config.addressing_prefix in response.content
176
+ and self.has_tool_message_attempt(response)
177
+ ):
178
+ # response contains both a user-addressing and a tool, which
179
+ # is not allowed, so remove the user-addressing prefix
180
+ response.content = response.content.replace(
181
+ self.config.addressing_prefix, ""
182
+ )
183
+
184
+ return response
116
185
 
117
186
  def _validate_config(self) -> None:
118
187
  assert isinstance(self.config, ArangoChatAgentConfig)
@@ -230,6 +299,7 @@ class ArangoChatAgent(ChatAgent):
230
299
  try:
231
300
  cursor = self.db.aql.execute(query, bind_vars=bind_vars)
232
301
  records = [doc for doc in cursor] # type: ignore
302
+ records = records[: self.config.max_num_results]
233
303
  logger.warning(f"Records retrieved: {records}")
234
304
  return QueryResult(success=True, data=records if records else [])
235
305
  except Exception as e:
@@ -273,6 +343,28 @@ class ArangoChatAgent(ChatAgent):
273
343
  success=False, data=f"Failed after max retries: {str(e)}"
274
344
  )
275
345
 
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
+
276
368
  def aql_retrieval_tool(self, msg: AQLRetrievalTool) -> str:
277
369
  """Handle AQL query for data retrieval"""
278
370
  if not self.tried_schema:
@@ -285,7 +377,13 @@ class ArangoChatAgent(ChatAgent):
285
377
  return """
286
378
  You need to create the database first using `{aql_creation_tool_name}`.
287
379
  """
380
+ self.num_tries += 1
288
381
  query = msg.aql_query
382
+ if query == self.current_retrieval_aql_query:
383
+ return """
384
+ You have already tried this query, so you will get the same results again!
385
+ If you need to retry, please MODIFY the query to get different results.
386
+ """
289
387
  self.current_retrieval_aql_query = query
290
388
  logger.info(f"Executing AQL query: {query}")
291
389
  response = self.read_query(query)
@@ -299,28 +397,11 @@ class ArangoChatAgent(ChatAgent):
299
397
  """
300
398
  # truncate long results
301
399
  result = str(response.data)
302
- n_toks = self.num_tokens(result)
303
- if n_toks > self.config.max_result_tokens:
304
- logger.warning(
305
- f"""
306
- Your query resulted in a large result of
307
- {n_toks} tokens,
308
- which will be truncated to {self.config.max_result_tokens} tokens.
309
- If this does not give satisfactory results,
310
- please retry with a more focused query.
311
- """
312
- )
313
- if self.parser is not None:
314
- result = self.parser.truncate_tokens(
315
- result,
316
- self.config.max_result_tokens,
317
- )
318
- else:
319
- result = result[: self.config.max_result_tokens * 4] # truncate roughly
320
- return result
400
+ return self._limit_tokens(result)
321
401
 
322
402
  def aql_creation_tool(self, msg: AQLCreationTool) -> str:
323
403
  """Handle AQL query for creating data"""
404
+ self.num_tries += 1
324
405
  query = msg.aql_query
325
406
  logger.info(f"Executing AQL query: {query}")
326
407
  response = self.write_query(query)
@@ -334,12 +415,45 @@ class ArangoChatAgent(ChatAgent):
334
415
  self,
335
416
  msg: ArangoSchemaTool | None,
336
417
  ) -> Dict[str, List[Dict[str, Any]]] | str:
337
- """Get database schema including collections, properties, and relationships"""
418
+ """Get database schema. If collections=None, include all collections.
419
+ If properties=False, show only connection info,
420
+ else show all properties and example-docs.
421
+ """
422
+
423
+ if (
424
+ msg is not None
425
+ and msg.collections == self.current_schema_params.collections
426
+ and msg.properties == self.current_schema_params.properties
427
+ ):
428
+ return """
429
+ You have already tried this schema TOOL, so you will get the same results
430
+ again! Please MODIFY the tool params `collections` or `properties` to get
431
+ different results.
432
+ """
433
+
434
+ if msg is not None:
435
+ collections = msg.collections
436
+ properties = msg.properties
437
+ else:
438
+ collections = None
439
+ properties = True
338
440
  self.tried_schema = True
339
- if self.config.kg_schema is not None and len(self.config.kg_schema) > 0:
441
+ if (
442
+ self.config.kg_schema is not None
443
+ and len(self.config.kg_schema) > 0
444
+ and msg is None
445
+ ):
446
+ # we are trying to pre-populate full schema before the agent runs,
447
+ # so get it if it's already available
448
+ # (Note of course that this "full schema" may actually be incomplete)
340
449
  return self.config.kg_schema
450
+
451
+ # increment tries only if the LLM is asking for the schema,
452
+ # in which case msg will not be None
453
+ self.num_tries += msg is not None
454
+
341
455
  try:
342
- # Get graph schemas
456
+ # Get graph schemas (keeping full graph info)
343
457
  graph_schema = [
344
458
  {"graph_name": g["name"], "edge_definitions": g["edge_definitions"]}
345
459
  for g in self.db.graphs() # type: ignore
@@ -348,57 +462,78 @@ class ArangoChatAgent(ChatAgent):
348
462
  # Get collection schemas
349
463
  collection_schema = []
350
464
  for collection in self.db.collections(): # type: ignore
351
- if collection["name"].startswith("_"): # Skip system collections
465
+ if collection["name"].startswith("_"):
352
466
  continue
353
467
 
354
468
  col_name = collection["name"]
469
+ if collections and col_name not in collections:
470
+ continue
471
+
355
472
  col_type = collection["type"]
356
473
  col_size = self.db.collection(col_name).count()
357
474
 
358
- if col_size == 0: # Skip empty collections
475
+ if col_size == 0:
359
476
  continue
360
477
 
361
- # Calculate sample size
362
- limit_amount = (
363
- ceil(
364
- self.config.schema_sample_pct * col_size / 100.0 # type: ignore
365
- )
366
- or 1
367
- )
368
-
369
- # Query to get sample documents and their properties
370
- sample_query = f"""
371
- FOR doc in {col_name}
372
- LIMIT {limit_amount}
373
- RETURN doc
374
- """
478
+ if properties:
479
+ # Full property collection with sampling
480
+ lim = self.config.schema_sample_pct * col_size # type: ignore
481
+ limit_amount = ceil(lim / 100.0) or 1
482
+ sample_query = f"""
483
+ FOR doc in {col_name}
484
+ LIMIT {limit_amount}
485
+ RETURN doc
486
+ """
375
487
 
376
- properties = []
377
- example_doc = None
378
-
379
- def simplify_doc(doc: Any) -> Any:
380
- if isinstance(doc, list) and len(doc) > 0:
381
- return [simplify_doc(doc[0])]
382
- if isinstance(doc, dict):
383
- return {k: simplify_doc(v) for k, v in doc.items()}
384
- return doc
385
-
386
- for doc in self.db.aql.execute(sample_query): # type: ignore
387
- if example_doc is None:
388
- example_doc = simplify_doc(doc)
389
- for key, value in doc.items():
390
- prop = {"name": key, "type": type(value).__name__}
391
- if prop not in properties:
392
- properties.append(prop)
393
-
394
- collection_schema.append(
395
- {
488
+ properties_list = []
489
+ example_doc = None
490
+
491
+ def simplify_doc(doc: Any) -> Any:
492
+ if isinstance(doc, list) and len(doc) > 0:
493
+ return [simplify_doc(doc[0])]
494
+ if isinstance(doc, dict):
495
+ return {k: simplify_doc(v) for k, v in doc.items()}
496
+ return doc
497
+
498
+ for doc in self.db.aql.execute(sample_query): # type: ignore
499
+ if example_doc is None:
500
+ example_doc = simplify_doc(doc)
501
+ for key, value in doc.items():
502
+ prop = {"name": key, "type": type(value).__name__}
503
+ if prop not in properties_list:
504
+ properties_list.append(prop)
505
+
506
+ collection_schema.append(
507
+ {
508
+ "collection_name": col_name,
509
+ "collection_type": col_type,
510
+ f"{col_type}_properties": properties_list,
511
+ f"example_{col_type}": example_doc,
512
+ }
513
+ )
514
+ else:
515
+ # Basic info + from/to for edges only
516
+ collection_info = {
396
517
  "collection_name": col_name,
397
518
  "collection_type": col_type,
398
- f"{col_type}_properties": properties,
399
- f"example_{col_type}": example_doc,
400
519
  }
401
- )
520
+ if col_type == "edge":
521
+ # Get a sample edge to extract from/to fields
522
+ sample_edge = next(
523
+ self.db.aql.execute( # type: ignore
524
+ f"FOR e IN {col_name} LIMIT 1 RETURN e"
525
+ ),
526
+ None,
527
+ )
528
+ if sample_edge:
529
+ collection_info["from_collection"] = sample_edge[
530
+ "_from"
531
+ ].split("/")[0]
532
+ collection_info["to_collection"] = sample_edge["_to"].split(
533
+ "/"
534
+ )[0]
535
+
536
+ collection_schema.append(collection_info)
402
537
 
403
538
  schema = {
404
539
  "Graph Schema": graph_schema,
@@ -406,10 +541,41 @@ class ArangoChatAgent(ChatAgent):
406
541
  }
407
542
  schema_str = json.dumps(schema, indent=2)
408
543
  logger.warning(f"Schema retrieved:\n{schema_str}")
409
- # save schema to file "logs/arangoo-schema.json"
410
544
  with open("logs/arango-schema.json", "w") as f:
411
545
  f.write(schema_str)
412
- self.config.kg_schema = schema # type: ignore
546
+ if (n_fields := count_fields(schema)) > self.config.max_schema_fields:
547
+ logger.warning(
548
+ f"""
549
+ Schema has {n_fields} fields, which exceeds the maximum of
550
+ {self.config.max_schema_fields}. Showing a trimmed version
551
+ that only includes edge info and no other properties.
552
+ """
553
+ )
554
+ schema = trim_schema(schema)
555
+ n_fields = count_fields(schema)
556
+ logger.warning(f"Schema trimmed down to {n_fields} fields.")
557
+ schema_str = (
558
+ json.dumps(schema)
559
+ + "\n"
560
+ + f"""
561
+
562
+ CAUTION: The requested schema was too large, so
563
+ the schema has been trimmed down to show only all collection names,
564
+ their types,
565
+ and edge relationships (from/to collections) without any properties.
566
+ To find out more about the schema, you can EITHER:
567
+ - Use the `{arango_schema_tool_name}` tool again with the
568
+ `properties` arg set to True, and `collections` arg set to
569
+ specific collections you want to know more about, OR
570
+ - Use the `{aql_retrieval_tool_name}` tool to learn more about
571
+ the schema by querying the database.
572
+
573
+ """
574
+ )
575
+ if msg is None:
576
+ self.config.kg_schema = schema_str
577
+ return schema_str
578
+ self.config.kg_schema = schema
413
579
  return schema
414
580
 
415
581
  except Exception as e:
@@ -432,9 +598,10 @@ class ArangoChatAgent(ChatAgent):
432
598
 
433
599
  super().__init__(self.config)
434
600
  # Note we are enabling GraphSchemaTool regardless of whether
435
- # self.config.use_schema_tools is True or False, because
601
+ # self.config.prepopulate_schema is True or False, because
436
602
  # even when schema provided, the agent may later want to get the schema,
437
- # e.g. if the db evolves, or if it needs to bring in the schema
603
+ # e.g. if the db evolves, or schema was trimmed due to size, or
604
+ # if it needs to bring in the schema into recent context.
438
605
 
439
606
  self.enable_message(
440
607
  [
@@ -454,7 +621,7 @@ class ArangoChatAgent(ChatAgent):
454
621
  assert isinstance(self.config, ArangoChatAgentConfig)
455
622
  return (
456
623
  SCHEMA_TOOLS_SYS_MSG
457
- if self.config.use_schema_tools
624
+ if not self.config.prepopulate_schema
458
625
  else SCHEMA_PROVIDED_SYS_MSG.format(schema=self.arango_schema_tool(None))
459
626
  )
460
627
 
@@ -9,7 +9,7 @@ done_tool_name = DoneTool.default_value("request")
9
9
 
10
10
  arango_schema_tool_description = f"""
11
11
  `{arango_schema_tool_name}` tool/function-call to find the schema
12
- of the graph database, i.e. get all the collections
12
+ of the graph database, or for some SPECIFIC collections, i.e. get information on
13
13
  (document and edge), their attributes, and graph definitions available in your
14
14
  ArangoDB database. You MUST use this tool BEFORE attempting to use the
15
15
  `{aql_retrieval_tool_name}` tool/function-call, to ensure that you are using the
@@ -18,7 +18,8 @@ correct collection names and attributes in your `{aql_retrieval_tool_name}` tool
18
18
 
19
19
  aql_retrieval_tool_description = f"""
20
20
  `{aql_retrieval_tool_name}` tool/function-call to retrieve information from
21
- the database using AQL (ArangoDB Query Language) queries.
21
+ the database using AQL (ArangoDB Query Language) queries, to answer
22
+ the user's questions, OR for you to learn more about the SCHEMA of the database.
22
23
  """
23
24
 
24
25
  aql_creation_tool_description = f"""
@@ -26,6 +27,29 @@ aql_creation_tool_description = f"""
26
27
  documents/edges in the database.
27
28
  """
28
29
 
30
+ aql_retrieval_query_example = """
31
+ EXAMPLE:
32
+ Suppose you are asked this question "Does Bob have a father?".
33
+ Then you will go through the following steps, where YOU indicates
34
+ the message YOU will be sending, and RESULTS indicates the RESULTS
35
+ you will receive from the helper executing the query:
36
+
37
+ 1. YOU:
38
+ {{ "request": "aql_retrieval_tool",
39
+ "aql_query": "FOR v, e, p in ... [query truncated for brevity]..."}}
40
+
41
+ 2. RESULTS:
42
+ [.. results from the query...]
43
+ 3. YOU: [ since results were not satisfactory, you try ANOTHER query]
44
+ {{ "request": "aql_retrieval_tool",
45
+ "aql_query": "blah blah ... [query truncated for brevity]..."}}
46
+ }}
47
+ 4. RESULTS:
48
+ [.. results from the query...]
49
+ 5. YOU: [ now you have the answer, you can generate your response ]
50
+ The answer is YES, Bob has a father, and his name is John.
51
+ """
52
+
29
53
  aql_query_instructions = """
30
54
  When writing AQL queries:
31
55
  1. Use the exact property names shown in the schema
@@ -63,6 +87,7 @@ REMEMBER:
63
87
  with your response. DO NOT MAKE UP RESULTS FROM A TOOL!
64
88
  [3] YOU MUST NOT ANSWER queries from your OWN KNOWLEDGE; ALWAYS RELY ON
65
89
  the result of a TOOL/FUNCTION to compose your response.
90
+ [4] Use ONLY ONE TOOL/FUNCTION at a TIME!
66
91
  """
67
92
  # sys msg to use when schema already provided initially,
68
93
  # so agent should not use schema tool
@@ -77,6 +102,7 @@ and their attribute keys available in your ArangoDB database.
77
102
  {{schema}}
78
103
  === END SCHEMA ===
79
104
 
105
+
80
106
  To help with the user's question or database update/creation request,
81
107
  you have access to these tools:
82
108
 
@@ -84,10 +110,6 @@ you have access to these tools:
84
110
 
85
111
  - {aql_creation_tool_description}
86
112
 
87
- Since the schema has been provided, you may not need to use the tool below,
88
- but you may use it if you need to remind yourself about the schema:
89
-
90
- - {arango_schema_tool_description}
91
113
 
92
114
  {tool_result_instruction}
93
115
  """
@@ -113,27 +135,34 @@ DEFAULT_ARANGO_CHAT_SYSTEM_MESSAGE = f"""
113
135
  {{mode}}
114
136
 
115
137
  You do not need to be able to answer a question with just one query.
116
- You could make a sequence of AQL queries to find the answer to the question.
138
+ You can make a query, WAIT for the result,
139
+ THEN make ANOTHER query, WAIT for result,
140
+ THEN make ANOTHER query, and so on, until you have the answer.
117
141
 
118
142
  {aql_query_instructions}
119
143
 
120
144
  RETRY-SUGGESTIONS:
121
145
  If you receive a null or other unexpected result,
122
146
  (a) make sure you use the available TOOLs correctly,
123
- (b) USE `{arango_schema_tool_name}` tool/function-call to get all collections,
124
- their attributes and graph definitions available in your ArangoDB database.
147
+ (b) learn more about the schema using EITHER:
148
+ - `{arango_schema_tool_name}` tool/function-call to find properties of specific
149
+ collections or other parts of the schema, OR
150
+ - `{aql_retrieval_tool_name}` tool/function-call to use AQL queries to
151
+ find specific parts of the schema.
125
152
  (c) Collection names are CASE-SENSITIVE -- make sure you adhere to the exact
126
153
  collection name you found in the schema.
127
154
  (d) see if you have made an assumption in your AQL query, and try another way,
128
155
  or use `{aql_retrieval_tool_name}` to explore the database contents before
129
156
  submitting your final query.
130
- (f) Try APPROXIMATE or PARTIAL MATCHES to strings in the user's query,
157
+ (e) Try APPROXIMATE or PARTIAL MATCHES to strings in the user's query,
131
158
  e.g. user may ask about "Godfather" instead of "The Godfather",
132
159
  or try using CASE-INSENSITIVE MATCHES.
133
160
 
134
161
  Start by asking what the user needs help with.
135
162
 
136
163
  {tool_result_instruction}
164
+
165
+ {aql_retrieval_query_example}
137
166
  """
138
167
 
139
168
  ADDRESSING_INSTRUCTION = """
@@ -1,3 +1,5 @@
1
+ from typing import List, Tuple
2
+
1
3
  from langroid.agent.tool_message import ToolMessage
2
4
 
3
5
 
@@ -5,11 +7,46 @@ class AQLRetrievalTool(ToolMessage):
5
7
  request: str = "aql_retrieval_tool"
6
8
  purpose: str = """
7
9
  To send an <aql_query> in response to a user's request/question,
10
+ OR to find SCHEMA information,
8
11
  and WAIT for results of the <aql_query> BEFORE continuing with response.
9
12
  You will receive RESULTS from this tool, and ONLY THEN you can continue.
10
13
  """
11
14
  aql_query: str
12
15
 
16
+ @classmethod
17
+ def examples(cls) -> List[ToolMessage | Tuple[str, ToolMessage]]:
18
+ """Few-shot examples to include in tool instructions."""
19
+ return [
20
+ (
21
+ "I want to see who Bob's Father is",
22
+ cls(
23
+ aql_query="""
24
+ FOR v, e, p IN 1..1 OUTBOUND 'users/Bob' GRAPH 'family_tree'
25
+ FILTER p.edges[0].type == 'father'
26
+ RETURN v
27
+ """
28
+ ),
29
+ ),
30
+ (
31
+ "I want to know the properties of the Actor node",
32
+ cls(
33
+ aql_query="""
34
+ FOR doc IN Actor
35
+ LIMIT 1
36
+ RETURN ATTRIBUTES(doc)
37
+ """
38
+ ),
39
+ ),
40
+ ]
41
+
42
+ @classmethod
43
+ def instructions(cls) -> str:
44
+ return """
45
+ When using this TOOL/Function-call, you must WAIT to receive the RESULTS
46
+ of the AQL query, before continuing your response!
47
+ DO NOT ASSUME YOU KNOW THE RESULTs BEFORE RECEIVING THEM.
48
+ """
49
+
13
50
 
14
51
  aql_retrieval_tool_name = AQLRetrievalTool.default_value("request")
15
52
 
@@ -23,6 +60,23 @@ class AQLCreationTool(ToolMessage):
23
60
  """
24
61
  aql_query: str
25
62
 
63
+ @classmethod
64
+ def examples(cls) -> List[ToolMessage | Tuple[str, ToolMessage]]:
65
+ """Few-shot examples to include in tool instructions."""
66
+ return [
67
+ (
68
+ "Create a new document in the collection 'users'",
69
+ cls(
70
+ aql_query="""
71
+ INSERT {
72
+ "name": "Alice",
73
+ "age": 30
74
+ } INTO users
75
+ """
76
+ ),
77
+ ),
78
+ ]
79
+
26
80
 
27
81
  aql_creation_tool_name = AQLCreationTool.default_value("request")
28
82
 
@@ -30,10 +84,19 @@ aql_creation_tool_name = AQLCreationTool.default_value("request")
30
84
  class ArangoSchemaTool(ToolMessage):
31
85
  request: str = "arango_schema_tool"
32
86
  purpose: str = """
33
- To get the schema of the Arango graph database.
87
+ To get the schema of the Arango graph database,
88
+ or some part of it. Follow these instructions:
89
+ 1. Set <properties> to True to get the properties of the collections,
90
+ and False if you only want to see the graph structure and get only the
91
+ from/to relations of the edges.
92
+ 2. Set <collections> to a list of collection names if you want to see,
93
+ or leave it as None to see all ALL collections.
34
94
  IMPORTANT: YOU MUST WAIT FOR THE RESULT OF THE TOOL BEFORE CONTINUING.
35
95
  You will receive RESULTS from this tool, and ONLY THEN you can continue.
36
96
  """
37
97
 
98
+ properties: bool = True
99
+ collections: List[str] | None = None
100
+
38
101
 
39
102
  arango_schema_tool_name = ArangoSchemaTool.default_value("request")
@@ -0,0 +1,36 @@
1
+ from typing import Any, Dict, List
2
+
3
+
4
+ def count_fields(schema: Dict[str, List[Dict[str, Any]]]) -> int:
5
+ total = 0
6
+ for coll in schema["Collection Schema"]:
7
+ # Count all keys in each collection's dict
8
+ total += len(coll)
9
+ # Also count properties if they exist
10
+ props = coll.get(f"{coll['collection_type']}_properties", [])
11
+ total += len(props)
12
+ return total
13
+
14
+
15
+ def trim_schema(
16
+ schema: Dict[str, List[Dict[str, Any]]]
17
+ ) -> Dict[str, List[Dict[str, Any]]]:
18
+ """Keep only edge connection info, remove properties and examples"""
19
+ trimmed: Dict[str, List[Dict[str, Any]]] = {
20
+ "Graph Schema": schema["Graph Schema"],
21
+ "Collection Schema": [],
22
+ }
23
+ for coll in schema["Collection Schema"]:
24
+ col_info: Dict[str, Any] = {
25
+ "collection_name": coll["collection_name"],
26
+ "collection_type": coll["collection_type"],
27
+ }
28
+ if coll["collection_type"] == "edge":
29
+ # preserve from/to info if present
30
+ if f"example_{coll['collection_type']}" in coll:
31
+ example = coll[f"example_{coll['collection_type']}"]
32
+ if example and "_from" in example:
33
+ col_info["from_collection"] = example["_from"].split("/")[0]
34
+ col_info["to_collection"] = example["_to"].split("/")[0]
35
+ trimmed["Collection Schema"].append(col_info)
36
+ return trimmed
langroid/agent/task.py CHANGED
@@ -1330,6 +1330,9 @@ class Task:
1330
1330
  max_cost=self.max_cost,
1331
1331
  max_tokens=self.max_tokens,
1332
1332
  )
1333
+ # update result.tool_messages if any
1334
+ if isinstance(result, ChatDocument):
1335
+ self.agent.get_tool_messages(result)
1333
1336
  if result is not None:
1334
1337
  content, id2result, oai_tool_id = self.agent.process_tool_results(
1335
1338
  result.content,
@@ -1358,6 +1361,9 @@ class Task:
1358
1361
  else:
1359
1362
  response_fn = self._entity_responder_map[cast(Entity, e)]
1360
1363
  result = response_fn(self.pending_message)
1364
+ # update result.tool_messages if any
1365
+ if isinstance(result, ChatDocument):
1366
+ self.agent.get_tool_messages(result)
1361
1367
 
1362
1368
  result_chat_doc = self.agent.to_ChatDocument(
1363
1369
  result,
@@ -1388,7 +1394,7 @@ class Task:
1388
1394
  # ignore all string-based signaling/routing
1389
1395
  return result
1390
1396
  # parse various routing/addressing strings in result
1391
- is_pass, recipient, content = parse_routing(
1397
+ is_pass, recipient, content = self._parse_routing(
1392
1398
  result,
1393
1399
  addressing_prefix=self.config.addressing_prefix,
1394
1400
  )
@@ -1521,7 +1527,7 @@ class Task:
1521
1527
  oai_tool_id2result = result_msg.oai_tool_id2result if result_msg else None
1522
1528
  fun_call = result_msg.function_call if result_msg else None
1523
1529
  tool_messages = result_msg.tool_messages if result_msg else []
1524
- # if there is an LLMDoneTool or AgentDoneTool among these,
1530
+ # if there is an DoneTool or AgentDoneTool among these,
1525
1531
  # we extract content and tools from here, and ignore all others
1526
1532
  for t in tool_messages:
1527
1533
  if isinstance(t, FinalResultTool):
@@ -1533,6 +1539,8 @@ class Task:
1533
1539
  # there shouldn't be multiple tools like this; just take the first
1534
1540
  content = to_string(t.content)
1535
1541
  content_any = t.content
1542
+ fun_call = None
1543
+ oai_tool_calls = None
1536
1544
  if isinstance(t, AgentDoneTool):
1537
1545
  # AgentDoneTool may have tools, unlike DoneTool
1538
1546
  tool_messages = t.tools
@@ -1940,58 +1948,72 @@ class Task:
1940
1948
  """
1941
1949
  self.color_log = enable
1942
1950
 
1951
+ def _parse_routing(
1952
+ self,
1953
+ msg: ChatDocument | str,
1954
+ addressing_prefix: str = "",
1955
+ ) -> Tuple[bool | None, str | None, str | None]:
1956
+ """
1957
+ Parse routing instruction if any, of the form:
1958
+ PASS:<recipient> (pass current pending msg to recipient)
1959
+ SEND:<recipient> <content> (send content to recipient)
1960
+ @<recipient> <content> (send content to recipient)
1961
+ Args:
1962
+ msg (ChatDocument|str|None): message to parse
1963
+ addressing_prefix (str): prefix to address other agents or entities,
1964
+ (e.g. "@". See documentation of `TaskConfig` for details).
1965
+ Returns:
1966
+ Tuple[bool|None, str|None, str|None]:
1967
+ bool: true=PASS, false=SEND, or None if neither
1968
+ str: recipient, or None
1969
+ str: content to send, or None
1970
+ """
1971
+ # handle routing instruction-strings in result if any,
1972
+ # such as PASS, PASS_TO, or SEND
1943
1973
 
1944
- def parse_routing(
1945
- msg: ChatDocument | str,
1946
- addressing_prefix: str = "",
1947
- ) -> Tuple[bool | None, str | None, str | None]:
1948
- """
1949
- Parse routing instruction if any, of the form:
1950
- PASS:<recipient> (pass current pending msg to recipient)
1951
- SEND:<recipient> <content> (send content to recipient)
1952
- @<recipient> <content> (send content to recipient)
1953
- Args:
1954
- msg (ChatDocument|str|None): message to parse
1955
- addressing_prefix (str): prefix to address other agents or entities,
1956
- (e.g. "@". See documentation of `TaskConfig` for details).
1957
- Returns:
1958
- Tuple[bool|None, str|None, str|None]:
1959
- bool: true=PASS, false=SEND, or None if neither
1960
- str: recipient, or None
1961
- str: content to send, or None
1962
- """
1963
- # handle routing instruction in result if any,
1964
- # of the form PASS=<recipient>
1965
- content = msg.content if isinstance(msg, ChatDocument) else msg
1966
- content = content.strip()
1967
- if PASS in content and PASS_TO not in content:
1968
- return True, None, None
1969
- if PASS_TO in content and content.split(":")[1] != "":
1970
- return True, content.split(":")[1], None
1971
- if (
1972
- SEND_TO in content
1973
- and (addressee_content := parse_addressed_message(content, SEND_TO))[0]
1974
- is not None
1975
- ):
1976
- (addressee, content_to_send) = addressee_content
1977
- # if no content then treat same as PASS_TO
1978
- if content_to_send == "":
1979
- return True, addressee, None
1980
- else:
1981
- return False, addressee, content_to_send
1982
- if (
1983
- addressing_prefix != ""
1984
- and addressing_prefix in content
1985
- and (addressee_content := parse_addressed_message(content, addressing_prefix))[
1986
- 0
1987
- ]
1988
- is not None
1989
- ):
1990
- (addressee, content_to_send) = addressee_content
1991
- # if no content then treat same as PASS_TO
1992
- if content_to_send == "":
1993
- return True, addressee, None
1994
- else:
1995
- return False, addressee, content_to_send
1974
+ msg_str = msg.content if isinstance(msg, ChatDocument) else msg
1975
+ if (
1976
+ self.agent.has_tool_message_attempt(msg)
1977
+ and not msg_str.startswith(PASS)
1978
+ and not msg_str.startswith(PASS_TO)
1979
+ and not msg_str.startswith(SEND_TO)
1980
+ ):
1981
+ # if there's an attempted tool-call, we ignore any routing strings,
1982
+ # unless they are at the start of the msg
1983
+ return None, None, None
1984
+
1985
+ content = msg.content if isinstance(msg, ChatDocument) else msg
1986
+ content = content.strip()
1987
+ if PASS in content and PASS_TO not in content:
1988
+ return True, None, None
1989
+ if PASS_TO in content and content.split(":")[1] != "":
1990
+ return True, content.split(":")[1], None
1991
+ if (
1992
+ SEND_TO in content
1993
+ and (addressee_content := parse_addressed_message(content, SEND_TO))[0]
1994
+ is not None
1995
+ ):
1996
+ # Note this will discard any portion of content BEFORE SEND_TO.
1997
+ # TODO maybe make this configurable.
1998
+ (addressee, content_to_send) = addressee_content
1999
+ # if no content then treat same as PASS_TO
2000
+ if content_to_send == "":
2001
+ return True, addressee, None
2002
+ else:
2003
+ return False, addressee, content_to_send
2004
+ if (
2005
+ addressing_prefix != ""
2006
+ and addressing_prefix in content
2007
+ and (
2008
+ addressee_content := parse_addressed_message(content, addressing_prefix)
2009
+ )[0]
2010
+ is not None
2011
+ ):
2012
+ (addressee, content_to_send) = addressee_content
2013
+ # if no content then treat same as PASS_TO
2014
+ if content_to_send == "":
2015
+ return True, addressee, None
2016
+ else:
2017
+ return False, addressee, content_to_send
1996
2018
 
1997
- return None, None, None
2019
+ return None, None, None
@@ -41,11 +41,11 @@ class DoneTool(ToolMessage):
41
41
  """Tool for Agent Entity (i.e. agent_response) or LLM entity (i.e. llm_response) to
42
42
  signal the current task is done, with some content as the result."""
43
43
 
44
- purpose = """
44
+ purpose: str = """
45
45
  To signal the current task is done, along with an optional message <content>
46
46
  of arbitrary type (default None).
47
47
  """
48
- request = "done_tool"
48
+ request: str = "done_tool"
49
49
  content: str = ""
50
50
 
51
51
  def response(self, agent: ChatAgent) -> ChatDocument:
@@ -77,7 +77,7 @@ class ResultTool(ToolMessage):
77
77
  Note:
78
78
  - when defining a tool handler or agent_response, you can directly return
79
79
  ResultTool(field1 = val1, ...),
80
- where the values can be aribitrary data structures, including nested
80
+ where the values can be arbitrary data structures, including nested
81
81
  Pydantic objs, or you can define a subclass of ResultTool with the
82
82
  fields you want to return.
83
83
  - This is a special ToolMessage that is NOT meant to be used or handled
@@ -143,10 +143,10 @@ class PassTool(ToolMessage):
143
143
  Similar to ForwardTool, but without specifying the recipient agent.
144
144
  """
145
145
 
146
- purpose = """
146
+ purpose: str = """
147
147
  To pass the current message so that other agents can handle it.
148
148
  """
149
- request = "pass_tool"
149
+ request: str = "pass_tool"
150
150
 
151
151
  def response(self, agent: ChatAgent, chat_doc: ChatDocument) -> ChatDocument:
152
152
  """When this tool is enabled for an Agent, this will result in a method
@@ -178,10 +178,10 @@ class DonePassTool(PassTool):
178
178
  Similar to PassTool, except we append a DoneTool to the result tool_messages.
179
179
  """
180
180
 
181
- purpose = """
181
+ purpose: str = """
182
182
  To signal the current task is done, with results set to the current/incoming msg.
183
183
  """
184
- request = "done_pass_tool"
184
+ request: str = "done_pass_tool"
185
185
 
186
186
  def response(self, agent: ChatAgent, chat_doc: ChatDocument) -> ChatDocument:
187
187
  # use PassTool to get the right ChatDocument to pass...
@@ -17,6 +17,7 @@ from .base import (
17
17
  from .openai_gpt import (
18
18
  OpenAIChatModel,
19
19
  AnthropicModel,
20
+ GeminiModel,
20
21
  OpenAICompletionModel,
21
22
  OpenAIGPTConfig,
22
23
  OpenAIGPT,
@@ -41,6 +42,7 @@ __all__ = [
41
42
  "LLMResponse",
42
43
  "OpenAIChatModel",
43
44
  "AnthropicModel",
45
+ "GeminiModel",
44
46
  "OpenAICompletionModel",
45
47
  "OpenAIGPTConfig",
46
48
  "OpenAIGPT",
@@ -66,6 +66,7 @@ if "OLLAMA_HOST" in os.environ:
66
66
  else:
67
67
  OLLAMA_BASE_URL = "http://localhost:11434/v1"
68
68
 
69
+ GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/"
69
70
  OLLAMA_API_KEY = "ollama"
70
71
  DUMMY_API_KEY = "xxx"
71
72
 
@@ -92,6 +93,14 @@ class OpenAIChatModel(str, Enum):
92
93
  O1_MINI = "o1-mini"
93
94
 
94
95
 
96
+ class GeminiModel(str, Enum):
97
+ """Enum for Gemini models"""
98
+
99
+ GEMINI_1_5_FLASH = "gemini-1.5-flash"
100
+ GEMINI_1_5_FLASH_8B = "gemini-1.5-flash-8b"
101
+ GEMINI_1_5_PRO = "gemini-1.5-pro"
102
+
103
+
95
104
  class OpenAICompletionModel(str, Enum):
96
105
  """Enum for OpenAI Completion models"""
97
106
 
@@ -503,6 +512,7 @@ class OpenAIGPT(LanguageModel):
503
512
 
504
513
  self.is_groq = self.config.chat_model.startswith("groq/")
505
514
  self.is_cerebras = self.config.chat_model.startswith("cerebras/")
515
+ self.is_gemini = self.config.chat_model.startswith("gemini/")
506
516
 
507
517
  if self.is_groq:
508
518
  self.config.chat_model = self.config.chat_model.replace("groq/", "")
@@ -524,6 +534,11 @@ class OpenAIGPT(LanguageModel):
524
534
  api_key=self.api_key,
525
535
  )
526
536
  else:
537
+ if self.is_gemini:
538
+ self.config.chat_model = self.config.chat_model.replace("gemini/", "")
539
+ self.api_key = os.getenv("GEMINI_API_KEY", DUMMY_API_KEY)
540
+ self.api_base = GEMINI_BASE_URL
541
+
527
542
  self.client = OpenAI(
528
543
  api_key=self.api_key,
529
544
  base_url=self.api_base,
@@ -623,7 +638,13 @@ class OpenAIGPT(LanguageModel):
623
638
  Currently main troublemaker is o1* series.
624
639
  """
625
640
  match self.config.chat_model:
626
- case OpenAIChatModel.O1_MINI | OpenAIChatModel.O1_PREVIEW:
641
+ case (
642
+ OpenAIChatModel.O1_MINI
643
+ | OpenAIChatModel.O1_PREVIEW
644
+ | GeminiModel.GEMINI_1_5_FLASH
645
+ | GeminiModel.GEMINI_1_5_FLASH_8B
646
+ | GeminiModel.GEMINI_1_5_PRO
647
+ ):
627
648
  return {"max_tokens": "max_completion_tokens"}
628
649
  case _:
629
650
  return {}
@@ -1229,13 +1250,10 @@ class OpenAIGPT(LanguageModel):
1229
1250
  cost = 0.0
1230
1251
  prompt_tokens = 0
1231
1252
  completion_tokens = 0
1232
- if not cached and not self.get_stream():
1253
+ if not cached and not self.get_stream() and response["usage"] is not None:
1233
1254
  prompt_tokens = response["usage"]["prompt_tokens"]
1234
1255
  completion_tokens = response["usage"]["completion_tokens"]
1235
- cost = self._cost_chat_model(
1236
- response["usage"]["prompt_tokens"],
1237
- response["usage"]["completion_tokens"],
1238
- )
1256
+ cost = self._cost_chat_model(prompt_tokens, completion_tokens)
1239
1257
 
1240
1258
  return LLMTokenUsage(
1241
1259
  prompt_tokens=prompt_tokens, completion_tokens=completion_tokens, cost=cost
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.20.0
3
+ Version: 0.21.0
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -248,6 +248,9 @@ teacher_task.run()
248
248
  <details>
249
249
  <summary> <b>Click to expand</b></summary>
250
250
 
251
+ - **Nov 2024:**
252
+ - **[0.20.0](https://github.com/langroid/langroid/releases/tag/0.20.0)** Support for
253
+ ArangoDB Knowledge Graphs.
251
254
  - **Oct 2024:**
252
255
  - **[0.18.0]** [LLMConfig.async_stream_quiet](https://langroid.github.io/langroid/notes/async-streaming/) flag to
253
256
  turn off LLM output in async + stream mode.
@@ -1,6 +1,6 @@
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=uCjFJ0Mjm6hu4MSCsz7vzGwzpbkrxddDIduhxS24jps,65034
3
+ langroid/agent/base.py,sha256=sOZapdzHaB4kbCLu8vI_zZx78jIhv9fmWn0EWV4yTAE,65371
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
@@ -11,9 +11,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=5X8yQr1MdTLchNK6L7g3gArFYVEiql4g35YPMw_4TIU,19579
15
- langroid/agent/special/arangodb/system_messages.py,sha256=j-vNz4SLKzE6-1a31ZFw7NHMD_uBepS8vxJ_bsjgMC4,5801
16
- langroid/agent/special/arangodb/tools.py,sha256=3CW2HsLqcM0JGemHsmihm8F5zZwwbFvAwyHzzaJ2S90,1319
14
+ langroid/agent/special/arangodb/arangodb_agent.py,sha256=CXD1Z4yXnWwre4ybg7BMhVFTgQq0zVXzobutyxIYCP4,26862
15
+ langroid/agent/special/arangodb/system_messages.py,sha256=udwfLleTdyz_DuxHuoiv2wHEZoAPBPbwdF_ivjIfP5c,6867
16
+ langroid/agent/special/arangodb/tools.py,sha256=WasFERC1cToLOWi1cWqUs-TujU0A68gZWbhbP128obo,3499
17
+ langroid/agent/special/arangodb/utils.py,sha256=LIevtkayIdVVXyj3jlbKH2WgdZTtH5-JLgbXOHC7uxs,1420
17
18
  langroid/agent/special/doc_chat_agent.py,sha256=xIqBOyLax_jMU0UevxqXf_aQUrRkW6MQUKpKnKvaqkQ,59281
18
19
  langroid/agent/special/lance_doc_chat_agent.py,sha256=s8xoRs0gGaFtDYFUSIRchsgDVbS5Q3C2b2mr3V1Fd-Q,10419
19
20
  langroid/agent/special/lance_rag/__init__.py,sha256=QTbs0IVE2ZgDg8JJy1zN97rUUg4uEPH7SLGctFNumk4,174
@@ -37,14 +38,14 @@ langroid/agent/special/sql/utils/system_message.py,sha256=qKLHkvQWRQodTtPLPxr1GS
37
38
  langroid/agent/special/sql/utils/tools.py,sha256=vFYysk6Vi7HJjII8B4RitA3pt_z3gkSglDNdhNVMiFc,1332
38
39
  langroid/agent/special/table_chat_agent.py,sha256=d9v2wsblaRx7oMnKhLV7uO_ujvk9gh59pSGvBXyeyNc,9659
39
40
  langroid/agent/structured_message.py,sha256=y7pud1EgRNeTFZlJmBkLmwME3yQJ_IYik-Xds9kdZbY,282
40
- langroid/agent/task.py,sha256=p7YsccpRIZcX0dFo5ll9EAaetWUcxZnCZal0i7Xsl1Y,85433
41
+ langroid/agent/task.py,sha256=f7clh6p6Md0G4YGHqbFeeT88U4XoP0i3eatekV21hHE,86643
41
42
  langroid/agent/tool_message.py,sha256=jkN7uq7YwUC_wBcSCNUYjrB_His2YCfQay_lqIa4Tww,10498
42
43
  langroid/agent/tools/__init__.py,sha256=IMgCte-_ZIvCkozGQmvMqxIw7_nKLKzD78ccJL1bnQU,804
43
44
  langroid/agent/tools/duckduckgo_search_tool.py,sha256=NhsCaGZkdv28nja7yveAhSK_w6l_Ftym8agbrdzqgfo,1935
44
45
  langroid/agent/tools/file_tools.py,sha256=GjPB5YDILucYapElnvvoYpGJuZQ25ecLs2REv7edPEo,7292
45
46
  langroid/agent/tools/google_search_tool.py,sha256=y7b-3FtgXf0lfF4AYxrZ3K5pH2dhidvibUOAGBE--WI,1456
46
47
  langroid/agent/tools/metaphor_search_tool.py,sha256=qj4gt453cLEX3EGW7nVzVu6X7LCdrwjSlcNY0qJW104,2489
47
- langroid/agent/tools/orchestration.py,sha256=vp2Qx-DYPtDnACosxKqwHGy6DeD1QnEllWz0Ht81Cyc,10880
48
+ langroid/agent/tools/orchestration.py,sha256=EDv1EMVGYqX82x3bCRbTn9gFNs66oosiUM8WTSZkUJg,10909
48
49
  langroid/agent/tools/recipient_tool.py,sha256=dr0yTxgNEIoxUYxH6TtaExC4G_8WdJ0xGohIa4dFLhY,9808
49
50
  langroid/agent/tools/retrieval_tool.py,sha256=2q2pfoYbZNfbWQ0McxrtmfF0ekGglIgRl-6uF26pa-E,871
50
51
  langroid/agent/tools/rewind_tool.py,sha256=XAXL3BpNhCmBGYq_qi_sZfHJuIw7NY2jp4wnojJ7WRs,5606
@@ -69,12 +70,12 @@ langroid/embedding_models/remote_embeds.py,sha256=6_kjXByVbqhY9cGwl9R83ZcYC2km-n
69
70
  langroid/exceptions.py,sha256=G60UVDChkUlBDVWHFr_43zUUszZHSejoU00tX_dfD68,2322
70
71
  langroid/language_models/.chainlit/config.toml,sha256=1t5lHORGzc2E6dkaO9P15jYHu2w-4Kl9pYjpDPc84vs,3716
71
72
  langroid/language_models/.chainlit/translations/en-US.json,sha256=DAFz2HjOFFfboCStrUfKFg2BpplJPK_OOtixwF_GivY,9931
72
- langroid/language_models/__init__.py,sha256=1sUGobooTqq77XC7LxKsvME0RgSd5GGmeyrPo9SMh4U,940
73
+ langroid/language_models/__init__.py,sha256=8o8D8Lxaq961_oxVpB_bC2iEJ1GXJqYXMlwUcn6OJb8,976
73
74
  langroid/language_models/azure_openai.py,sha256=G4le3j4YLHV7IwgB2C37hO3MKijZ1KjynbYlEvpIF7Y,6214
74
75
  langroid/language_models/base.py,sha256=xMFg8syIHiA7ABRNWPXeFM1vGeY_1EN84ki8C3dycfw,22722
75
76
  langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
76
77
  langroid/language_models/mock_lm.py,sha256=HuiAvjHiCfffYF5xjFJUq945HVTW0QPbeUUctOnNCzQ,3868
77
- langroid/language_models/openai_gpt.py,sha256=-A2RB7bhzGUDUVM-JcBYO84WYsS2YHtb5xXqqM_dXG4,68874
78
+ langroid/language_models/openai_gpt.py,sha256=CVRwYvIT8lt02GqI0G5_sTQXLcQbiwwNXpU8_qsw6q8,69584
78
79
  langroid/language_models/prompt_formatter/__init__.py,sha256=2-5cdE24XoFDhifOLl8yiscohil1ogbP1ECkYdBlBsk,372
79
80
  langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
80
81
  langroid/language_models/prompt_formatter/hf_formatter.py,sha256=PVJppmjRvD-2DF-XNC6mE05vTZ9wbu37SmXwZBQhad0,5055
@@ -141,8 +142,8 @@ langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3Hmh
141
142
  langroid/vector_store/momento.py,sha256=qR-zBF1RKVHQZPZQYW_7g-XpTwr46p8HJuYPCkfJbM4,10534
142
143
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
143
144
  langroid/vector_store/qdrantdb.py,sha256=v88lqFkepADvlN6lByUj9I4NEKa9X9lWH16uTPPbYrE,17457
144
- pyproject.toml,sha256=DQbq0Xo4Bc3sJYF2-1qA9qnnKT0X-x5BzC99OrxcTNg,7488
145
- langroid-0.20.0.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
146
- langroid-0.20.0.dist-info/METADATA,sha256=5uni8xDGH809QGehHz0YfMguGFtvKDSJ9A7CfbduDnE,56758
147
- langroid-0.20.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
148
- langroid-0.20.0.dist-info/RECORD,,
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,,
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.20.0"
3
+ version = "0.21.0"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"