langroid 0.1.52__py3-none-any.whl → 0.1.54__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 +78 -5
- langroid/agent/chat_agent.py +2 -1
- langroid/agent/chat_document.py +8 -3
- langroid/agent/special/doc_chat_agent.py +11 -11
- langroid/agent/task.py +24 -20
- langroid/io/base.py +0 -0
- langroid/io/cmd_line.py +0 -0
- langroid/io/refs.md +1 -0
- langroid/io/websocket.py +0 -0
- langroid/language_models/azure_openai.py +72 -0
- langroid/language_models/base.py +35 -4
- langroid/language_models/openai_gpt.py +49 -16
- langroid/prompts/templates.py +5 -4
- {langroid-0.1.52.dist-info → langroid-0.1.54.dist-info}/METADATA +23 -2
- {langroid-0.1.52.dist-info → langroid-0.1.54.dist-info}/RECORD +17 -12
- {langroid-0.1.52.dist-info → langroid-0.1.54.dist-info}/LICENSE +0 -0
- {langroid-0.1.52.dist-info → langroid-0.1.54.dist-info}/WHEEL +0 -0
langroid/agent/base.py
CHANGED
@@ -1,9 +1,20 @@
|
|
1
1
|
import inspect
|
2
2
|
import json
|
3
3
|
import logging
|
4
|
+
import textwrap
|
4
5
|
from abc import ABC
|
5
6
|
from contextlib import ExitStack
|
6
|
-
from typing import
|
7
|
+
from typing import (
|
8
|
+
Callable,
|
9
|
+
Dict,
|
10
|
+
List,
|
11
|
+
Optional,
|
12
|
+
Set,
|
13
|
+
Tuple,
|
14
|
+
Type,
|
15
|
+
cast,
|
16
|
+
no_type_check,
|
17
|
+
)
|
7
18
|
|
8
19
|
from pydantic import BaseSettings, ValidationError
|
9
20
|
from rich import print
|
@@ -15,6 +26,9 @@ from langroid.agent.tool_message import INSTRUCTION, ToolMessage
|
|
15
26
|
from langroid.language_models.base import (
|
16
27
|
LanguageModel,
|
17
28
|
LLMConfig,
|
29
|
+
LLMMessage,
|
30
|
+
LLMResponse,
|
31
|
+
LLMTokenUsage,
|
18
32
|
)
|
19
33
|
from langroid.mytypes import DocMetaData, Entity
|
20
34
|
from langroid.parsing.json import extract_top_level_json
|
@@ -60,6 +74,8 @@ class Agent(ABC):
|
|
60
74
|
self.llm_tools_map: Dict[str, Type[ToolMessage]] = {}
|
61
75
|
self.llm_tools_handled: Set[str] = set()
|
62
76
|
self.llm_tools_usable: Set[str] = set()
|
77
|
+
self.total_llm_token_cost = 0.0
|
78
|
+
self.total_llm_token_usage = 0
|
63
79
|
self.default_human_response: Optional[str] = None
|
64
80
|
self._indent = ""
|
65
81
|
self.llm = LanguageModel.create(config.llm)
|
@@ -315,7 +331,7 @@ class Agent(ABC):
|
|
315
331
|
else:
|
316
332
|
user_msg = Prompt.ask(
|
317
333
|
f"[blue]{self.indent}Human "
|
318
|
-
|
334
|
+
"(respond or q, x to exit current level, "
|
319
335
|
f"or hit enter to continue)\n{self.indent}",
|
320
336
|
).strip()
|
321
337
|
|
@@ -410,6 +426,7 @@ class Agent(ABC):
|
|
410
426
|
if self.llm.get_stream():
|
411
427
|
console.print(f"[green]{self.indent}", end="")
|
412
428
|
response = self.llm.generate(prompt, output_len)
|
429
|
+
|
413
430
|
displayed = False
|
414
431
|
if not self.llm.get_stream() or response.cached:
|
415
432
|
# we would have already displayed the msg "live" ONLY if
|
@@ -417,7 +434,7 @@ class Agent(ABC):
|
|
417
434
|
console.print(f"[green]{self.indent}", end="")
|
418
435
|
print("[green]" + response.message)
|
419
436
|
displayed = True
|
420
|
-
|
437
|
+
self.update_token_usage(response, prompt, self.llm.get_stream())
|
421
438
|
return ChatDocument.from_LLMResponse(response, displayed)
|
422
439
|
|
423
440
|
def get_tool_messages(self, msg: str | ChatDocument) -> List[ToolMessage]:
|
@@ -594,10 +611,66 @@ class Agent(ABC):
|
|
594
611
|
result = f"Error in tool/function-call {tool_name} usage: {type(e)}: {e}"
|
595
612
|
return result # type: ignore
|
596
613
|
|
597
|
-
def num_tokens(self, prompt: str) -> int:
|
614
|
+
def num_tokens(self, prompt: str | List[LLMMessage]) -> int:
|
598
615
|
if self.parser is None:
|
599
616
|
raise ValueError("Parser must be set, to count tokens")
|
600
|
-
|
617
|
+
if isinstance(prompt, str):
|
618
|
+
return self.parser.num_tokens(prompt)
|
619
|
+
else:
|
620
|
+
return sum([self.parser.num_tokens(m.content) for m in prompt])
|
621
|
+
|
622
|
+
def update_token_usage(
|
623
|
+
self, response: LLMResponse, prompt: str | List[LLMMessage], stream: bool
|
624
|
+
) -> None:
|
625
|
+
"""
|
626
|
+
Updates `response.usage` obj (token usage and cost fields).the usage memebr
|
627
|
+
It updates the cost after checking the cache and updates the
|
628
|
+
tokens (prompts and completion) if the response stream is True, because OpenAI
|
629
|
+
doesn't returns these fields.
|
630
|
+
|
631
|
+
Args:
|
632
|
+
response (LLMResponse): LLMResponse object
|
633
|
+
prompt (str | List[LLMMessage]): prompt or list of LLMMessage objects
|
634
|
+
stream (bool): whether to update the usage in the response object
|
635
|
+
if the response is not cached.
|
636
|
+
"""
|
637
|
+
if response is not None:
|
638
|
+
# Note: If response was not streamed, then
|
639
|
+
# `response.usage` would already have been set by the API,
|
640
|
+
# so we only need to update in the stream case.
|
641
|
+
if stream:
|
642
|
+
# usage, cost = 0 when response is from cache
|
643
|
+
prompt_tokens = 0
|
644
|
+
completion_tokens = 0
|
645
|
+
cost = 0.0
|
646
|
+
if not response.cached:
|
647
|
+
prompt_tokens = self.num_tokens(prompt)
|
648
|
+
completion_tokens = self.num_tokens(response.message)
|
649
|
+
cost = self.compute_token_cost(prompt_tokens, completion_tokens)
|
650
|
+
response.usage = LLMTokenUsage(
|
651
|
+
prompt_tokens=prompt_tokens,
|
652
|
+
completion_tokens=completion_tokens,
|
653
|
+
cost=cost,
|
654
|
+
)
|
655
|
+
|
656
|
+
if settings.debug and response.usage is not None:
|
657
|
+
print(
|
658
|
+
textwrap.dedent(
|
659
|
+
f"""
|
660
|
+
Stream: {stream}
|
661
|
+
prompt_tokens: {response.usage.prompt_tokens}
|
662
|
+
completion_tokens: {response.usage.completion_tokens}
|
663
|
+
""".lstrip()
|
664
|
+
)
|
665
|
+
)
|
666
|
+
# update total counters
|
667
|
+
if response.usage is not None:
|
668
|
+
self.total_llm_token_cost += response.usage.cost
|
669
|
+
self.total_llm_token_usage += response.usage.total_tokens
|
670
|
+
|
671
|
+
def compute_token_cost(self, prompt: int, completion: int) -> float:
|
672
|
+
price = cast(LanguageModel, self.llm).chat_cost()
|
673
|
+
return (price[0] * prompt + price[1] * completion) / 1000
|
601
674
|
|
602
675
|
def ask_agent(
|
603
676
|
self,
|
langroid/agent/chat_agent.py
CHANGED
@@ -453,7 +453,8 @@ class ChatAgent(Agent):
|
|
453
453
|
else:
|
454
454
|
response_str = response.message
|
455
455
|
print(cached + "[green]" + response_str)
|
456
|
-
|
456
|
+
stream = self.llm.get_stream() # type: ignore
|
457
|
+
self.update_token_usage(response, messages, stream)
|
457
458
|
return ChatDocument.from_LLMResponse(response, displayed)
|
458
459
|
|
459
460
|
def _llm_response_temp_context(self, message: str, prompt: str) -> ChatDocument:
|
langroid/agent/chat_document.py
CHANGED
@@ -7,6 +7,7 @@ from langroid.language_models.base import (
|
|
7
7
|
LLMFunctionCall,
|
8
8
|
LLMMessage,
|
9
9
|
LLMResponse,
|
10
|
+
LLMTokenUsage,
|
10
11
|
Role,
|
11
12
|
)
|
12
13
|
from langroid.mytypes import DocMetaData, Document, Entity
|
@@ -29,7 +30,7 @@ class ChatDocMetaData(DocMetaData):
|
|
29
30
|
block: None | Entity = None
|
30
31
|
sender_name: str = ""
|
31
32
|
recipient: str = ""
|
32
|
-
usage:
|
33
|
+
usage: Optional[LLMTokenUsage]
|
33
34
|
cached: bool = False
|
34
35
|
displayed: bool = False
|
35
36
|
|
@@ -119,7 +120,8 @@ class ChatDocument(Document):
|
|
119
120
|
|
120
121
|
@staticmethod
|
121
122
|
def from_LLMResponse(
|
122
|
-
response: LLMResponse,
|
123
|
+
response: LLMResponse,
|
124
|
+
displayed: bool = False,
|
123
125
|
) -> "ChatDocument":
|
124
126
|
recipient, message = response.get_recipient_and_message()
|
125
127
|
return ChatDocument(
|
@@ -183,7 +185,10 @@ class ChatDocument(Document):
|
|
183
185
|
content = message
|
184
186
|
|
185
187
|
return LLMMessage(
|
186
|
-
role=sender_role,
|
188
|
+
role=sender_role,
|
189
|
+
content=content,
|
190
|
+
function_call=fun_call,
|
191
|
+
name=sender_name,
|
187
192
|
)
|
188
193
|
|
189
194
|
|
@@ -7,7 +7,7 @@ Functionality includes:
|
|
7
7
|
"""
|
8
8
|
import logging
|
9
9
|
from contextlib import ExitStack
|
10
|
-
from typing import List, Optional, no_type_check
|
10
|
+
from typing import List, Optional, Tuple, no_type_check
|
11
11
|
|
12
12
|
from rich import print
|
13
13
|
from rich.console import Console
|
@@ -304,7 +304,7 @@ class DocChatAgent(ChatAgent):
|
|
304
304
|
)
|
305
305
|
|
306
306
|
@no_type_check
|
307
|
-
def get_relevant_extracts(self, query: str) -> List[Document]:
|
307
|
+
def get_relevant_extracts(self, query: str) -> Tuple[str, List[Document]]:
|
308
308
|
"""
|
309
309
|
Get list of docs or extracts relevant to a query. These could be:
|
310
310
|
- the original docs, if they exist and are not too long, or
|
@@ -316,6 +316,7 @@ class DocChatAgent(ChatAgent):
|
|
316
316
|
query (str): query to search for
|
317
317
|
|
318
318
|
Returns:
|
319
|
+
query (str): stand-alone version of input query
|
319
320
|
List[Document]: list of relevant docs
|
320
321
|
|
321
322
|
"""
|
@@ -341,20 +342,18 @@ class DocChatAgent(ChatAgent):
|
|
341
342
|
k=self.config.parsing.n_similar_docs,
|
342
343
|
)
|
343
344
|
if len(docs_and_scores) == 0:
|
344
|
-
return []
|
345
|
+
return query, []
|
345
346
|
passages = [
|
346
347
|
Document(content=d.content, metadata=d.metadata)
|
347
348
|
for (d, _) in docs_and_scores
|
348
349
|
]
|
349
350
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
with StreamingIfAllowed(self.llm, False):
|
355
|
-
extracts = self.llm.get_verbatim_extracts(query, passages)
|
351
|
+
with console.status("[cyan]LLM Extracting verbatim passages..."):
|
352
|
+
with StreamingIfAllowed(self.llm, False):
|
353
|
+
extracts = self.llm.get_verbatim_extracts(query, passages)
|
354
|
+
extracts = [e for e in extracts if e.content != NO_ANSWER]
|
356
355
|
|
357
|
-
return extracts
|
356
|
+
return query, extracts
|
358
357
|
|
359
358
|
@no_type_check
|
360
359
|
def answer_from_docs(self, query: str) -> Document:
|
@@ -373,7 +372,8 @@ class DocChatAgent(ChatAgent):
|
|
373
372
|
source="None",
|
374
373
|
),
|
375
374
|
)
|
376
|
-
|
375
|
+
# query may be updated to a stand-alone version
|
376
|
+
query, extracts = self.get_relevant_extracts(query)
|
377
377
|
if len(extracts) == 0:
|
378
378
|
return response
|
379
379
|
with ExitStack() as stack:
|
langroid/agent/task.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import logging
|
4
|
-
from typing import Callable, Dict, List, Optional, Type, cast
|
4
|
+
from typing import Callable, Dict, List, Optional, Set, Type, cast
|
5
5
|
|
6
6
|
from rich import print
|
7
7
|
|
@@ -155,7 +155,8 @@ class Task:
|
|
155
155
|
|
156
156
|
# other sub_tasks this task can delegate to
|
157
157
|
self.sub_tasks: List[Task] = []
|
158
|
-
self.parent_task:
|
158
|
+
self.parent_task: Set[Task] = set()
|
159
|
+
self.caller: Task | None = None # which task called this task's `run` method
|
159
160
|
|
160
161
|
def __repr__(self) -> str:
|
161
162
|
return f"{self.name}"
|
@@ -165,10 +166,9 @@ class Task:
|
|
165
166
|
|
166
167
|
@property
|
167
168
|
def _level(self) -> int:
|
168
|
-
if self.
|
169
|
+
if self.caller is None:
|
169
170
|
return 0
|
170
|
-
|
171
|
-
return self.parent_task._level + 1
|
171
|
+
return self.caller._level + 1
|
172
172
|
|
173
173
|
@property
|
174
174
|
def _indent(self) -> str:
|
@@ -199,7 +199,7 @@ class Task:
|
|
199
199
|
return
|
200
200
|
assert isinstance(task, Task), f"added task must be a Task, not {type(task)}"
|
201
201
|
|
202
|
-
task.parent_task
|
202
|
+
task.parent_task.add(self) # add myself to set of parent tasks of `task`
|
203
203
|
self.sub_tasks.append(task)
|
204
204
|
self.name_sub_task_map[task.name] = task
|
205
205
|
self.responders.append(cast(Responder, task))
|
@@ -226,18 +226,18 @@ class Task:
|
|
226
226
|
)
|
227
227
|
else:
|
228
228
|
self.pending_message = msg
|
229
|
-
if self.pending_message is not None and self.
|
230
|
-
# msg may have come from
|
229
|
+
if self.pending_message is not None and self.caller is not None:
|
230
|
+
# msg may have come from `caller`, so we pretend this is from
|
231
231
|
# the CURRENT task's USER entity
|
232
232
|
self.pending_message.metadata.sender = Entity.USER
|
233
233
|
|
234
|
-
if self.
|
235
|
-
self.logger = self.
|
234
|
+
if self.caller is not None and self.caller.logger is not None:
|
235
|
+
self.logger = self.caller.logger
|
236
236
|
else:
|
237
237
|
self.logger = RichFileLogger(f"logs/{self.name}.log", color=self.color_log)
|
238
238
|
|
239
|
-
if self.
|
240
|
-
self.tsv_logger = self.
|
239
|
+
if self.caller is not None and self.caller.tsv_logger is not None:
|
240
|
+
self.tsv_logger = self.caller.tsv_logger
|
241
241
|
else:
|
242
242
|
self.tsv_logger = setup_file_logger("tsv_logger", f"logs/{self.name}.tsv")
|
243
243
|
header = ChatDocLoggerFields().tsv_header()
|
@@ -250,6 +250,7 @@ class Task:
|
|
250
250
|
self,
|
251
251
|
msg: Optional[str | ChatDocument] = None,
|
252
252
|
turns: int = -1,
|
253
|
+
caller: None | Task = None,
|
253
254
|
) -> Optional[ChatDocument]:
|
254
255
|
"""
|
255
256
|
Loop over `step()` until task is considered done or `turns` is reached.
|
@@ -264,6 +265,7 @@ class Task:
|
|
264
265
|
LLM or Human (User).
|
265
266
|
turns (int): number of turns to run the task for;
|
266
267
|
default is -1, which means run until task is done.
|
268
|
+
caller (Task|None): the calling task, if any
|
267
269
|
|
268
270
|
Returns:
|
269
271
|
Optional[ChatDocument]: valid response from the agent
|
@@ -281,7 +283,7 @@ class Task:
|
|
281
283
|
):
|
282
284
|
# this task is not the intended recipient so return None
|
283
285
|
return None
|
284
|
-
|
286
|
+
self.caller = caller
|
285
287
|
self.init(msg)
|
286
288
|
# sets indentation to be printed prior to any output from agent
|
287
289
|
self.agent.indent = self._indent
|
@@ -447,20 +449,22 @@ class Task:
|
|
447
449
|
|
448
450
|
def response(self, e: Responder, turns: int = -1) -> Optional[ChatDocument]:
|
449
451
|
"""
|
450
|
-
Get response to `self.pending_message` from
|
452
|
+
Get response to `self.pending_message` from a responder.
|
451
453
|
If response is __valid__ (i.e. it ends the current turn of seeking
|
452
454
|
responses):
|
453
455
|
-then return the response as a ChatDocument object,
|
454
456
|
-otherwise return None.
|
455
457
|
Args:
|
456
|
-
e (
|
458
|
+
e (Responder): responder to get response from.
|
459
|
+
turns (int): number of turns to run the task for.
|
460
|
+
Default is -1, which means run until task is done.
|
457
461
|
Returns:
|
458
462
|
Optional[ChatDocument]: response to `self.pending_message` from entity if
|
459
463
|
valid, None otherwise
|
460
464
|
"""
|
461
465
|
if isinstance(e, Task):
|
462
466
|
actual_turns = e.turns if e.turns > 0 else turns
|
463
|
-
return e.run(self.pending_message, turns=actual_turns)
|
467
|
+
return e.run(self.pending_message, turns=actual_turns, caller=self)
|
464
468
|
else:
|
465
469
|
return self._entity_responder_map[cast(Entity, e)](self.pending_message)
|
466
470
|
|
@@ -521,10 +525,10 @@ class Task:
|
|
521
525
|
self.pending_message is None
|
522
526
|
# LLM decided task is done
|
523
527
|
or DONE in self.pending_message.content
|
524
|
-
or ( # current task is addressing message to
|
525
|
-
self.
|
526
|
-
and self.
|
527
|
-
and self.pending_message.metadata.recipient == self.
|
528
|
+
or ( # current task is addressing message to caller task
|
529
|
+
self.caller is not None
|
530
|
+
and self.caller.name != ""
|
531
|
+
and self.pending_message.metadata.recipient == self.caller.name
|
528
532
|
)
|
529
533
|
or (
|
530
534
|
# Task controller is "stuck", has nothing to say
|
langroid/io/base.py
ADDED
File without changes
|
langroid/io/cmd_line.py
ADDED
File without changes
|
langroid/io/refs.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
https://chat.openai.com/share/7c440b3f-ddbf-4ae6-a26f-ac28d947d403
|
langroid/io/websocket.py
ADDED
File without changes
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
import openai
|
4
|
+
from dotenv import load_dotenv
|
5
|
+
|
6
|
+
from langroid.language_models.openai_gpt import OpenAIGPT, OpenAIGPTConfig
|
7
|
+
|
8
|
+
|
9
|
+
class AzureConfig(OpenAIGPTConfig):
|
10
|
+
"""
|
11
|
+
Configuration for Azure OpenAI GPT. You need to supply the env vars listed in
|
12
|
+
``.azure_env_template`` after renaming the file to ``.azure_env``. Because this file
|
13
|
+
is used by this class to find the env vars.
|
14
|
+
Attributes:
|
15
|
+
type (str): should be ``azure``
|
16
|
+
api_version (str): can be set inside the ``.azure_env``
|
17
|
+
deployment_name (str): can be set inside the ``.azure_env`` and should be based
|
18
|
+
the custom name you chose for your deployment when you deployed a model
|
19
|
+
"""
|
20
|
+
|
21
|
+
type: str = "azure"
|
22
|
+
api_version: str = "2023-07-01-preview"
|
23
|
+
deployment_name: str = ""
|
24
|
+
|
25
|
+
|
26
|
+
class AzureGPT(OpenAIGPT):
|
27
|
+
"""
|
28
|
+
Class to access OpenAI LLMs via Azure. These env variables can be obtained from the
|
29
|
+
file `.azure_env`. Azure OpenAI doesn't support ``completion``
|
30
|
+
Attributes:
|
31
|
+
config: AzureConfig object
|
32
|
+
api_key: Azure API key
|
33
|
+
api_base: Azure API base url
|
34
|
+
api_version: Azure API version
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __init__(self, config: AzureConfig):
|
38
|
+
super().__init__(config)
|
39
|
+
self.config: AzureConfig = config
|
40
|
+
self.api_type = config.type
|
41
|
+
openai.api_type = self.api_type
|
42
|
+
load_dotenv(dotenv_path=".azure_env")
|
43
|
+
self.api_key = os.getenv("AZURE_API_KEY", "")
|
44
|
+
if self.api_key == "":
|
45
|
+
raise ValueError(
|
46
|
+
"""
|
47
|
+
AZURE_API_KEY not set in .env file,
|
48
|
+
please set it to your Azure API key."""
|
49
|
+
)
|
50
|
+
|
51
|
+
self.api_base = os.getenv("OPENAI_API_BASE", "")
|
52
|
+
if self.api_base == "":
|
53
|
+
raise ValueError(
|
54
|
+
"""
|
55
|
+
OPENAI_API_BASE not set in .env file,
|
56
|
+
please set it to your Azure API key."""
|
57
|
+
)
|
58
|
+
# we don't need this for ``api_key`` because it's handled inside
|
59
|
+
# ``openai_gpt.py`` methods before invoking chat/completion calls
|
60
|
+
else:
|
61
|
+
openai.api_base = self.api_base
|
62
|
+
|
63
|
+
self.api_version = os.getenv("OPENAI_API_VERSION", "") or config.api_version
|
64
|
+
openai.api_version = self.api_version
|
65
|
+
|
66
|
+
self.deployment_name = os.getenv("OPENAI_DEPLOYMENT_NAME", "")
|
67
|
+
if self.deployment_name == "":
|
68
|
+
raise ValueError(
|
69
|
+
"""
|
70
|
+
OPENAI_DEPLOYMENT_NAME not set in .env file,
|
71
|
+
please set it to your Azure API key."""
|
72
|
+
)
|
langroid/language_models/base.py
CHANGED
@@ -36,6 +36,9 @@ class LLMConfig(BaseSettings):
|
|
36
36
|
stream: bool = False # stream output from API?
|
37
37
|
cache_config: None | RedisCacheConfig | MomentoCacheConfig = None
|
38
38
|
|
39
|
+
# Dict of model -> (input/prompt cost, output/completion cost)
|
40
|
+
cost_per_1k_tokens: Optional[Dict[str, Tuple[float, float]]] = None
|
41
|
+
|
39
42
|
|
40
43
|
class LLMFunctionCall(BaseModel):
|
41
44
|
"""
|
@@ -63,6 +66,16 @@ class LLMFunctionSpec(BaseModel):
|
|
63
66
|
parameters: Dict[str, Any]
|
64
67
|
|
65
68
|
|
69
|
+
class LLMTokenUsage(BaseModel):
|
70
|
+
prompt_tokens: int = 0
|
71
|
+
completion_tokens: int = 0
|
72
|
+
cost: float = 0.0
|
73
|
+
|
74
|
+
@property
|
75
|
+
def total_tokens(self) -> int:
|
76
|
+
return self.prompt_tokens + self.completion_tokens
|
77
|
+
|
78
|
+
|
66
79
|
class Role(str, Enum):
|
67
80
|
USER = "user"
|
68
81
|
SYSTEM = "system"
|
@@ -116,7 +129,7 @@ class LLMResponse(BaseModel):
|
|
116
129
|
|
117
130
|
message: str
|
118
131
|
function_call: Optional[LLMFunctionCall] = None
|
119
|
-
usage:
|
132
|
+
usage: Optional[LLMTokenUsage]
|
120
133
|
cached: bool = False
|
121
134
|
|
122
135
|
def to_LLMMessage(self) -> LLMMessage:
|
@@ -193,13 +206,21 @@ class LanguageModel(ABC):
|
|
193
206
|
config: configuration for language model
|
194
207
|
Returns: instance of language model
|
195
208
|
"""
|
209
|
+
from langroid.language_models.azure_openai import AzureGPT
|
196
210
|
from langroid.language_models.openai_gpt import OpenAIGPT
|
197
211
|
|
198
212
|
if config is None or config.type is None:
|
199
213
|
return None
|
214
|
+
|
215
|
+
openai: Union[Type[AzureGPT], Type[OpenAIGPT]]
|
216
|
+
|
217
|
+
if config.type == "azure":
|
218
|
+
openai = AzureGPT
|
219
|
+
else:
|
220
|
+
openai = OpenAIGPT
|
200
221
|
cls = dict(
|
201
|
-
openai=
|
202
|
-
).get(config.type,
|
222
|
+
openai=openai,
|
223
|
+
).get(config.type, openai)
|
203
224
|
return cls(config) # type: ignore
|
204
225
|
|
205
226
|
@abstractmethod
|
@@ -248,6 +269,13 @@ class LanguageModel(ABC):
|
|
248
269
|
raise ValueError("No context length specified")
|
249
270
|
return self.config.context_length[self.config.completion_model]
|
250
271
|
|
272
|
+
def chat_cost(self) -> Tuple[float, float]:
|
273
|
+
if self.config.chat_model is None:
|
274
|
+
raise ValueError("No chat model specified")
|
275
|
+
if self.config.cost_per_1k_tokens is None:
|
276
|
+
raise ValueError("No cost per 1k tokens specified")
|
277
|
+
return self.config.cost_per_1k_tokens[self.config.chat_model]
|
278
|
+
|
251
279
|
def followup_to_standalone(
|
252
280
|
self, chat_history: List[Tuple[str, str]], question: str
|
253
281
|
) -> str:
|
@@ -368,7 +396,10 @@ class LanguageModel(ABC):
|
|
368
396
|
sources = ""
|
369
397
|
return Document(
|
370
398
|
content=content,
|
371
|
-
metadata={
|
399
|
+
metadata={
|
400
|
+
"source": "SOURCE: " + sources,
|
401
|
+
"cached": llm_response.cached,
|
402
|
+
},
|
372
403
|
)
|
373
404
|
|
374
405
|
|
@@ -20,6 +20,7 @@ from langroid.language_models.base import (
|
|
20
20
|
LLMFunctionSpec,
|
21
21
|
LLMMessage,
|
22
22
|
LLMResponse,
|
23
|
+
LLMTokenUsage,
|
23
24
|
Role,
|
24
25
|
)
|
25
26
|
from langroid.language_models.utils import (
|
@@ -62,6 +63,12 @@ class OpenAIGPTConfig(LLMConfig):
|
|
62
63
|
OpenAIChatModel.GPT4_NOFUNC: 8192,
|
63
64
|
OpenAICompletionModel.TEXT_DA_VINCI_003: 4096,
|
64
65
|
}
|
66
|
+
cost_per_1k_tokens: Dict[str, Tuple[float, float]] = {
|
67
|
+
# (input/prompt cost, output/completion cost)
|
68
|
+
OpenAIChatModel.GPT3_5_TURBO: (0.0015, 0.002),
|
69
|
+
OpenAIChatModel.GPT4: (0.03, 0.06), # 8K context
|
70
|
+
OpenAIChatModel.GPT4_NOFUNC: (0.03, 0.06),
|
71
|
+
}
|
65
72
|
|
66
73
|
|
67
74
|
class OpenAIResponse(BaseModel):
|
@@ -208,7 +215,6 @@ class OpenAIGPT(LanguageModel):
|
|
208
215
|
return ( # type: ignore
|
209
216
|
LLMResponse(
|
210
217
|
message=completion,
|
211
|
-
usage=0,
|
212
218
|
cached=False,
|
213
219
|
function_call=function_call if has_function else None,
|
214
220
|
),
|
@@ -229,6 +235,31 @@ class OpenAIGPT(LanguageModel):
|
|
229
235
|
# Try to get the result from the cache
|
230
236
|
return hashed_key, self.cache.retrieve(hashed_key)
|
231
237
|
|
238
|
+
def _cost_chat_model(self, prompt: int, completion: int) -> float:
|
239
|
+
price = self.chat_cost()
|
240
|
+
return (price[0] * prompt + price[1] * completion) / 1000
|
241
|
+
|
242
|
+
def _handle_token_usage(
|
243
|
+
self, cached: bool, response: Dict[str, Any]
|
244
|
+
) -> LLMTokenUsage:
|
245
|
+
cost = 0.0
|
246
|
+
prompt_tokens = 0
|
247
|
+
completion_tokens = 0
|
248
|
+
if not cached and not self.config.stream:
|
249
|
+
prompt_tokens = response["usage"]["prompt_tokens"]
|
250
|
+
completion_tokens = response["usage"]["completion_tokens"]
|
251
|
+
cost = self._cost_chat_model(
|
252
|
+
response["usage"]["prompt_tokens"],
|
253
|
+
response["usage"]["completion_tokens"],
|
254
|
+
)
|
255
|
+
# if not self.config.stream:
|
256
|
+
# prompt_tokens = response["usage"]["prompt_tokens"]
|
257
|
+
# completion_tokens = response["usage"]["completion_tokens"]
|
258
|
+
|
259
|
+
return LLMTokenUsage(
|
260
|
+
prompt_tokens=prompt_tokens, completion_tokens=completion_tokens, cost=cost
|
261
|
+
)
|
262
|
+
|
232
263
|
def generate(self, prompt: str, max_tokens: int) -> LLMResponse:
|
233
264
|
try:
|
234
265
|
return self._generate(prompt, max_tokens)
|
@@ -236,7 +267,7 @@ class OpenAIGPT(LanguageModel):
|
|
236
267
|
# capture exceptions not handled by retry, so we don't crash
|
237
268
|
err_msg = str(e)[:500]
|
238
269
|
logging.error(f"OpenAI API error: {err_msg}")
|
239
|
-
return LLMResponse(message=NO_ANSWER,
|
270
|
+
return LLMResponse(message=NO_ANSWER, cached=False)
|
240
271
|
|
241
272
|
def _generate(self, prompt: str, max_tokens: int) -> LLMResponse:
|
242
273
|
if self.config.use_chat_for_completion:
|
@@ -265,8 +296,9 @@ class OpenAIGPT(LanguageModel):
|
|
265
296
|
self.cache.store(hashed_key, result)
|
266
297
|
return cached, hashed_key, result
|
267
298
|
|
299
|
+
key_name = "engine" if self.config.type == "azure" else "model"
|
268
300
|
cached, hashed_key, response = completions_with_backoff(
|
269
|
-
|
301
|
+
**{key_name: self.config.completion_model},
|
270
302
|
prompt=prompt,
|
271
303
|
max_tokens=max_tokens, # for output/completion
|
272
304
|
request_timeout=self.config.timeout,
|
@@ -275,9 +307,8 @@ class OpenAIGPT(LanguageModel):
|
|
275
307
|
stream=self.config.stream,
|
276
308
|
)
|
277
309
|
|
278
|
-
usage = response["usage"]["total_tokens"]
|
279
310
|
msg = response["choices"][0]["text"].strip()
|
280
|
-
return LLMResponse(message=msg,
|
311
|
+
return LLMResponse(message=msg, cached=cached)
|
281
312
|
|
282
313
|
async def agenerate(self, prompt: str, max_tokens: int) -> LLMResponse:
|
283
314
|
try:
|
@@ -286,7 +317,7 @@ class OpenAIGPT(LanguageModel):
|
|
286
317
|
# capture exceptions not handled by retry, so we don't crash
|
287
318
|
err_msg = str(e)[:500]
|
288
319
|
logging.error(f"OpenAI API error: {err_msg}")
|
289
|
-
return LLMResponse(message=NO_ANSWER,
|
320
|
+
return LLMResponse(message=NO_ANSWER, cached=False)
|
290
321
|
|
291
322
|
async def _agenerate(self, prompt: str, max_tokens: int) -> LLMResponse:
|
292
323
|
openai.api_key = self.api_key
|
@@ -324,7 +355,6 @@ class OpenAIGPT(LanguageModel):
|
|
324
355
|
temperature=self.config.temperature,
|
325
356
|
stream=self.config.stream,
|
326
357
|
)
|
327
|
-
usage = response["usage"]["total_tokens"]
|
328
358
|
msg = response["choices"][0]["message"]["content"].strip()
|
329
359
|
else:
|
330
360
|
|
@@ -349,9 +379,8 @@ class OpenAIGPT(LanguageModel):
|
|
349
379
|
echo=False,
|
350
380
|
stream=self.config.stream,
|
351
381
|
)
|
352
|
-
usage = response["usage"]["total_tokens"]
|
353
382
|
msg = response["choices"][0]["text"].strip()
|
354
|
-
return LLMResponse(message=msg,
|
383
|
+
return LLMResponse(message=msg, cached=cached)
|
355
384
|
|
356
385
|
def chat(
|
357
386
|
self,
|
@@ -366,7 +395,7 @@ class OpenAIGPT(LanguageModel):
|
|
366
395
|
# capture exceptions not handled by retry, so we don't crash
|
367
396
|
err_msg = str(e)[:500]
|
368
397
|
logging.error(f"OpenAI API error: {err_msg}")
|
369
|
-
return LLMResponse(message=NO_ANSWER,
|
398
|
+
return LLMResponse(message=NO_ANSWER, cached=False)
|
370
399
|
|
371
400
|
def _chat(
|
372
401
|
self,
|
@@ -421,8 +450,14 @@ class OpenAIGPT(LanguageModel):
|
|
421
450
|
self.cache.store(hashed_key, result)
|
422
451
|
return cached, hashed_key, result
|
423
452
|
|
453
|
+
if self.config.type == "azure":
|
454
|
+
key_name = "engine"
|
455
|
+
if hasattr(self, "deployment_name"):
|
456
|
+
self.config.chat_model = self.deployment_name
|
457
|
+
else:
|
458
|
+
key_name = "model"
|
424
459
|
args: Dict[str, Any] = dict(
|
425
|
-
|
460
|
+
**{key_name: self.config.chat_model},
|
426
461
|
messages=[m.api_dict() for m in llm_messages],
|
427
462
|
max_tokens=max_tokens,
|
428
463
|
n=1,
|
@@ -447,7 +482,6 @@ class OpenAIGPT(LanguageModel):
|
|
447
482
|
self.cache.store(hashed_key, openai_response)
|
448
483
|
return llm_response
|
449
484
|
|
450
|
-
usage = response["usage"]["total_tokens"]
|
451
485
|
# openAI response will look like this:
|
452
486
|
"""
|
453
487
|
{
|
@@ -477,7 +511,6 @@ class OpenAIGPT(LanguageModel):
|
|
477
511
|
}
|
478
512
|
}
|
479
513
|
"""
|
480
|
-
|
481
514
|
message = response["choices"][0]["message"]
|
482
515
|
msg = message["content"] or ""
|
483
516
|
if message.get("function_call") is None:
|
@@ -489,10 +522,10 @@ class OpenAIGPT(LanguageModel):
|
|
489
522
|
fun_call.arguments = fun_args
|
490
523
|
except (ValueError, SyntaxError):
|
491
524
|
logging.warning(
|
492
|
-
|
525
|
+
"Could not parse function arguments: "
|
493
526
|
f"{message['function_call']['arguments']} "
|
494
527
|
f"for function {message['function_call']['name']} "
|
495
|
-
|
528
|
+
"treating as normal non-function message"
|
496
529
|
)
|
497
530
|
fun_call = None
|
498
531
|
msg = message["content"] + message["function_call"]["arguments"]
|
@@ -500,6 +533,6 @@ class OpenAIGPT(LanguageModel):
|
|
500
533
|
return LLMResponse(
|
501
534
|
message=msg.strip() if msg is not None else "",
|
502
535
|
function_call=fun_call,
|
503
|
-
usage=usage,
|
504
536
|
cached=cached,
|
537
|
+
usage=self._handle_token_usage(cached, response),
|
505
538
|
)
|
langroid/prompts/templates.py
CHANGED
@@ -8,15 +8,16 @@ EXTRACT_RELEVANT = """
|
|
8
8
|
Question:{question}
|
9
9
|
Relevant text, if any: """.strip()
|
10
10
|
|
11
|
-
EXTRACTION_PROMPT_GPT4 = """
|
11
|
+
EXTRACTION_PROMPT_GPT4 = f"""
|
12
12
|
Given the content and question below, extract COMPLETE SENTENCES OR PHRASES
|
13
13
|
VERBATIM from the content, that are relevant to answering the question (if such text
|
14
14
|
exists), even if it contradicts your knowledge, and even if it is factually incorrect.
|
15
15
|
Do not make up an answer that is not supported by the content.
|
16
|
-
When you answer, be concise, no need to explain anything.
|
16
|
+
When you answer, be concise, no need to explain anything. If there is no relevant text,
|
17
|
+
simply say {NO_ANSWER}.
|
17
18
|
|
18
|
-
Content: {content}
|
19
|
-
Question: {question}
|
19
|
+
Content: {{content}}
|
20
|
+
Question: {{question}}
|
20
21
|
Relevant text, if any:
|
21
22
|
"""
|
22
23
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.54
|
4
4
|
Summary: Harness LLMs with Multi-Agent Programming
|
5
5
|
License: MIT
|
6
6
|
Author: Prasad Chalasani
|
@@ -75,7 +75,7 @@ Description-Content-Type: text/markdown
|
|
75
75
|
|
76
76
|
<div align="center">
|
77
77
|
|
78
|
-
[](https://pypi.org/project/langroid/)
|
79
79
|
[](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
|
80
80
|
[](https://codecov.io/gh/langroid/langroid)
|
81
81
|
[](https://github.com/langroid/langroid/actions/workflows/validate.yml)
|
@@ -135,6 +135,7 @@ for ideas on what to contribute.
|
|
135
135
|
<summary> <b>:fire: Updates/Releases</b></summary>
|
136
136
|
|
137
137
|
- **Aug 2023:**
|
138
|
+
- **[Hierarchical computation](https://langroid.github.io/langroid/examples/agent-tree/)** example using Langroid agents and task orchestration.
|
138
139
|
- **0.1.51:** Support for global state, see [test_global_state.py](tests/main/test_global_state.py).
|
139
140
|
- **:whale: Langroid Docker image**, available, see instructions below.
|
140
141
|
- [**RecipientTool**](langroid/agent/tools/recipient_tool.py) enables (+ enforces) LLM to
|
@@ -328,6 +329,26 @@ GOOGLE_CSE_ID=your-cse-id
|
|
328
329
|
```
|
329
330
|
</details>
|
330
331
|
|
332
|
+
<details>
|
333
|
+
<summary><b>Setup instructions for Microsoft Azure OpenAI(click to expand)</b></summary>
|
334
|
+
In the root of the repo, copy the `.azure_env_template` file to a new file `.azure_env`:
|
335
|
+
|
336
|
+
```bash
|
337
|
+
cp .azure_env_template .azure_env
|
338
|
+
```
|
339
|
+
|
340
|
+
The file `.azure_env` contains four environment variables that are required to use Azure OpenAI: `AZURE_API_KEY`, `OPENAI_API_BASE`, `OPENAI_API_VERSION`, and `OPENAI_DEPLOYMENT_NAME`
|
341
|
+
|
342
|
+
This page [Microsoft Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart?tabs=command-line&pivots=programming-language-python#environment-variables)
|
343
|
+
provides more information, and you can set each environment variable as follows:
|
344
|
+
|
345
|
+
- `AZURE_API_KEY`, from the value of `API_KEY`
|
346
|
+
- `OPENAI_API_BASE` from the value of `ENDPOINT`, typically looks like `https://your.domain.azure.com`.
|
347
|
+
- For `OPENAI_API_VERSION`, you can use the default value in `.azure_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)
|
348
|
+
- `OPENAI_DEPLOYMENT_NAME` is the deployment name you chose when you deployed the GPT-35-Turbo or GPT-4 models.
|
349
|
+
|
350
|
+
</details>
|
351
|
+
|
331
352
|
---
|
332
353
|
|
333
354
|
# :whale: Docker Instructions
|
@@ -1,12 +1,12 @@
|
|
1
1
|
langroid/__init__.py,sha256=sEKJ_5WJBAMZApevfeE3gxLK-eotVzJMJlT83G0rAko,30
|
2
2
|
langroid/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
langroid/agent/base.py,sha256=
|
4
|
-
langroid/agent/chat_agent.py,sha256=
|
5
|
-
langroid/agent/chat_document.py,sha256=
|
3
|
+
langroid/agent/base.py,sha256=bnqa_PZsw1_RWDv1w67g1rMrhbGTdt_mTPWcZ_uAZIk,26530
|
4
|
+
langroid/agent/chat_agent.py,sha256=Sma0-5XPHDzBOcduthwwlWBmkBgqpk8gGzStF8rcrps,22643
|
5
|
+
langroid/agent/chat_document.py,sha256=apaYj38sDu7ALCnsA8tJwoj3Z8zLNmIsNPd4-IujnGk,6153
|
6
6
|
langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
|
8
8
|
langroid/agent/special/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
langroid/agent/special/doc_chat_agent.py,sha256=
|
9
|
+
langroid/agent/special/doc_chat_agent.py,sha256=yyf_kXj0rFmNWY_TjllGstZBg_hfFNDLiZ4p5P_P5Rg,16528
|
10
10
|
langroid/agent/special/recipient_validator_agent.py,sha256=R3Rit93BNWQar_9stuDBGzmLr2W-IYOQ7oq-tlNNlps,6035
|
11
11
|
langroid/agent/special/retriever_agent.py,sha256=DeOB5crFjXBvDEZT9k9ZVinOfFM2VgS6tQWWFyXSk9o,7204
|
12
12
|
langroid/agent/special/sql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -14,7 +14,7 @@ langroid/agent/special/sql/sql_chat_agent.py,sha256=DVvBI_q1HqTAk_r_4-B2vGOWjneb
|
|
14
14
|
langroid/agent/special/sql/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
langroid/agent/special/sql/utils/description_extractors.py,sha256=mxgbIZMqGnMj3vtU614bJJSpXY-JLjhX4iEB2R1-qlI,3921
|
16
16
|
langroid/agent/special/table_chat_agent.py,sha256=PTCE7MmunQj7tFiKAMIh7kvdIeQYU5ceXgBabwsxdg8,7244
|
17
|
-
langroid/agent/task.py,sha256=
|
17
|
+
langroid/agent/task.py,sha256=de8JY1Nq3YZ13dThPSYoVgj7BHxbXyifHiQtKQi5vQc,28111
|
18
18
|
langroid/agent/tool_message.py,sha256=08vKug6t6m8aSNuO268bc3xfz-rivDU5brxBOSGdqNM,5935
|
19
19
|
langroid/agent/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
20
|
langroid/agent/tools/google_search_tool.py,sha256=64F9oMNdS237BBOitrvYXN4Il_ES_fNrHkh35tBEDfA,1160
|
@@ -28,9 +28,14 @@ langroid/embedding_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
28
28
|
langroid/embedding_models/base.py,sha256=176jDrjEAAhNzdFCG8pfossd8SAhvHR8Q5Y8pOOm0LI,983
|
29
29
|
langroid/embedding_models/clustering.py,sha256=tZWElUqXl9Etqla0FAa7og96iDKgjqWjucZR_Egtp-A,6684
|
30
30
|
langroid/embedding_models/models.py,sha256=1xcv9hqmCTsbUbS8v7XeZRsf25Tu79JUoSipIYpvNoo,2765
|
31
|
+
langroid/io/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
+
langroid/io/cmd_line.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
+
langroid/io/refs.md,sha256=B1lrcHhg0IOWm74OHHZrv8o41pVvX6hSiG5pJjpDF90,67
|
34
|
+
langroid/io/websocket.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
35
|
langroid/language_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
-
langroid/language_models/
|
33
|
-
langroid/language_models/
|
36
|
+
langroid/language_models/azure_openai.py,sha256=Axzg-oysiMwKgJR6uGLBcnqVFf3NgykVN0b0Mex2rGY,2543
|
37
|
+
langroid/language_models/base.py,sha256=CHSMWJd9kFwMsI38pLmFcPtgkBUUQ3a47sj77kD8-bw,14743
|
38
|
+
langroid/language_models/openai_gpt.py,sha256=-qiQV2OldX9PShHX7UqknGTnudPpCn2C7n1NL2S-Be4,20748
|
34
39
|
langroid/language_models/utils.py,sha256=rmnSn-sJ3aKl_wBdeLPkck0Li4Ed6zkCxZYYl7n1V34,4668
|
35
40
|
langroid/mytypes.py,sha256=YA42IJcooJnTxAwk-B4FmZ1hqzIIF1ZZKcpUKzBTGGo,1537
|
36
41
|
langroid/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -51,7 +56,7 @@ langroid/parsing/web_search.py,sha256=hGUVoSJNdpoT5rsm-ikAteMiUropHrzKaxN8EVVqO2
|
|
51
56
|
langroid/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
52
57
|
langroid/prompts/dialog.py,sha256=SpfiSyofSgy2pwD1YboHR_yHO3LEEMbv6j2sm874jKo,331
|
53
58
|
langroid/prompts/prompts_config.py,sha256=EMK1Fm7EmS8y3CV4AkrVgn5K4NipiM4m7J8819W1KeM,98
|
54
|
-
langroid/prompts/templates.py,sha256=
|
59
|
+
langroid/prompts/templates.py,sha256=4X-07tnmUQ8Z_zaWRQAUUyKiErGztp3tERujqnG8sGA,6369
|
55
60
|
langroid/prompts/transforms.py,sha256=GsQo1klGxUy0fACh6j0lTblk6XEl2erRnhRWlN2M4-c,2706
|
56
61
|
langroid/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
62
|
langroid/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -74,7 +79,7 @@ langroid/vector_store/base.py,sha256=QZx3NUNwf2I0r3A7iuoUHIRGbqt_pFGD0hq1R-Yg8iM
|
|
74
79
|
langroid/vector_store/chromadb.py,sha256=s5pQkKjaMP-Tt5A8M10EInFzttaALPbJAq7q4gf0TKg,5235
|
75
80
|
langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
|
76
81
|
langroid/vector_store/qdrantdb.py,sha256=KRvIIj1IZG2zFqejofMnRs2hT86B-27LgBEnuczdqOU,9072
|
77
|
-
langroid-0.1.
|
78
|
-
langroid-0.1.
|
79
|
-
langroid-0.1.
|
80
|
-
langroid-0.1.
|
82
|
+
langroid-0.1.54.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
83
|
+
langroid-0.1.54.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
84
|
+
langroid-0.1.54.dist-info/METADATA,sha256=ZNXtQxEqxLa1l-4Bd2WHksnrCwAuv2Xf3zpapwZUahM,34974
|
85
|
+
langroid-0.1.54.dist-info/RECORD,,
|
File without changes
|
File without changes
|