langroid 0.50.2__py3-none-any.whl → 0.50.3__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 +9 -5
- langroid/agent/special/doc_chat_task.py +0 -0
- langroid/language_models/base.py +1 -1
- langroid/language_models/openai_gpt.py +109 -32
- {langroid-0.50.2.dist-info → langroid-0.50.3.dist-info}/METADATA +1 -1
- {langroid-0.50.2.dist-info → langroid-0.50.3.dist-info}/RECORD +8 -7
- {langroid-0.50.2.dist-info → langroid-0.50.3.dist-info}/WHEEL +0 -0
- {langroid-0.50.2.dist-info → langroid-0.50.3.dist-info}/licenses/LICENSE +0 -0
langroid/agent/base.py
CHANGED
@@ -1929,10 +1929,13 @@ class Agent(ABC):
|
|
1929
1929
|
print_response_stats: bool = True,
|
1930
1930
|
) -> None:
|
1931
1931
|
"""
|
1932
|
-
Updates `response.usage` obj (token usage and cost fields)
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1932
|
+
Updates `response.usage` obj (token usage and cost fields) if needed.
|
1933
|
+
An update is needed only if:
|
1934
|
+
- stream is True (i.e. streaming was enabled), and
|
1935
|
+
- the response was NOT obtained from cached, and
|
1936
|
+
- the API did NOT provide the usage/cost fields during streaming
|
1937
|
+
(As of Sep 2024, the OpenAI API started providing these; for other APIs
|
1938
|
+
this may not necessarily be the case).
|
1936
1939
|
|
1937
1940
|
Args:
|
1938
1941
|
response (LLMResponse): LLMResponse object
|
@@ -1945,10 +1948,11 @@ class Agent(ABC):
|
|
1945
1948
|
if response is None or self.llm is None:
|
1946
1949
|
return
|
1947
1950
|
|
1951
|
+
no_usage_info = response.usage is None or response.usage.prompt_tokens == 0
|
1948
1952
|
# Note: If response was not streamed, then
|
1949
1953
|
# `response.usage` would already have been set by the API,
|
1950
1954
|
# so we only need to update in the stream case.
|
1951
|
-
if stream:
|
1955
|
+
if stream and no_usage_info:
|
1952
1956
|
# usage, cost = 0 when response is from cache
|
1953
1957
|
prompt_tokens = 0
|
1954
1958
|
completion_tokens = 0
|
File without changes
|
langroid/language_models/base.py
CHANGED
@@ -216,7 +216,7 @@ class LLMTokenUsage(BaseModel):
|
|
216
216
|
prompt_tokens: int = 0
|
217
217
|
completion_tokens: int = 0
|
218
218
|
cost: float = 0.0
|
219
|
-
calls: int = 0 # how many API calls
|
219
|
+
calls: int = 0 # how many API calls - not used as of 2025-04-04
|
220
220
|
|
221
221
|
def reset(self) -> None:
|
222
222
|
self.prompt_tokens = 0
|
@@ -780,22 +780,39 @@ class OpenAIGPT(LanguageModel):
|
|
780
780
|
reasoning: str = "",
|
781
781
|
function_args: str = "",
|
782
782
|
function_name: str = "",
|
783
|
-
) -> Tuple[bool, bool, str, str]:
|
783
|
+
) -> Tuple[bool, bool, str, str, Dict[str, int]]:
|
784
784
|
"""Process state vars while processing a streaming API response.
|
785
785
|
Returns a tuple consisting of:
|
786
786
|
- is_break: whether to break out of the loop
|
787
787
|
- has_function: whether the response contains a function_call
|
788
788
|
- function_name: name of the function
|
789
789
|
- function_args: args of the function
|
790
|
+
- completion: completion text
|
791
|
+
- reasoning: reasoning text
|
792
|
+
- usage: usage dict
|
790
793
|
"""
|
791
794
|
# convert event obj (of type ChatCompletionChunk) to dict so rest of code,
|
792
795
|
# which expects dicts, works as it did before switching to openai v1.x
|
793
796
|
if not isinstance(event, dict):
|
794
797
|
event = event.model_dump()
|
795
798
|
|
799
|
+
usage = event.get("usage", {}) or {}
|
796
800
|
choices = event.get("choices", [{}])
|
797
|
-
if len(choices) == 0:
|
801
|
+
if choices is None or len(choices) == 0:
|
798
802
|
choices = [{}]
|
803
|
+
if len(usage) > 0 and len(choices[0]) == 0:
|
804
|
+
# we have a "usage" chunk, and empty choices, so we're done
|
805
|
+
# ASSUMPTION: a usage chunk ONLY arrives AFTER all normal completion text!
|
806
|
+
# If any API does not follow this, we need to change this code.
|
807
|
+
return (
|
808
|
+
True,
|
809
|
+
has_function,
|
810
|
+
function_name,
|
811
|
+
function_args,
|
812
|
+
completion,
|
813
|
+
reasoning,
|
814
|
+
usage,
|
815
|
+
)
|
799
816
|
event_args = ""
|
800
817
|
event_fn_name = ""
|
801
818
|
event_tool_deltas: Optional[List[Dict[str, Any]]] = None
|
@@ -876,23 +893,23 @@ class OpenAIGPT(LanguageModel):
|
|
876
893
|
self.config.streamer(tool_fn_args, StreamEventType.TOOL_ARGS)
|
877
894
|
|
878
895
|
# show this delta in the stream
|
879
|
-
|
896
|
+
is_break = finish_reason in [
|
880
897
|
"stop",
|
881
898
|
"function_call",
|
882
899
|
"tool_calls",
|
883
|
-
]
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
900
|
+
]
|
901
|
+
# for function_call, finish_reason does not necessarily
|
902
|
+
# contain "function_call" as mentioned in the docs.
|
903
|
+
# So we check for "stop" or "function_call" here.
|
904
|
+
return (
|
905
|
+
is_break,
|
906
|
+
has_function,
|
907
|
+
function_name,
|
908
|
+
function_args,
|
909
|
+
completion,
|
910
|
+
reasoning,
|
911
|
+
usage,
|
912
|
+
)
|
896
913
|
|
897
914
|
@no_type_check
|
898
915
|
async def _process_stream_event_async(
|
@@ -912,15 +929,30 @@ class OpenAIGPT(LanguageModel):
|
|
912
929
|
- has_function: whether the response contains a function_call
|
913
930
|
- function_name: name of the function
|
914
931
|
- function_args: args of the function
|
932
|
+
- completion: completion text
|
933
|
+
- reasoning: reasoning text
|
934
|
+
- usage: usage dict
|
915
935
|
"""
|
916
936
|
# convert event obj (of type ChatCompletionChunk) to dict so rest of code,
|
917
937
|
# which expects dicts, works as it did before switching to openai v1.x
|
918
938
|
if not isinstance(event, dict):
|
919
939
|
event = event.model_dump()
|
920
940
|
|
941
|
+
usage = event.get("usage", {}) or {}
|
921
942
|
choices = event.get("choices", [{}])
|
922
943
|
if len(choices) == 0:
|
923
944
|
choices = [{}]
|
945
|
+
if len(usage) > 0 and len(choices[0]) == 0:
|
946
|
+
# we got usage chunk, and empty choices, so we're done
|
947
|
+
return (
|
948
|
+
True,
|
949
|
+
has_function,
|
950
|
+
function_name,
|
951
|
+
function_args,
|
952
|
+
completion,
|
953
|
+
reasoning,
|
954
|
+
usage,
|
955
|
+
)
|
924
956
|
event_args = ""
|
925
957
|
event_fn_name = ""
|
926
958
|
event_tool_deltas: Optional[List[Dict[str, Any]]] = None
|
@@ -996,23 +1028,23 @@ class OpenAIGPT(LanguageModel):
|
|
996
1028
|
)
|
997
1029
|
|
998
1030
|
# show this delta in the stream
|
999
|
-
|
1031
|
+
is_break = choices[0].get("finish_reason", "") in [
|
1000
1032
|
"stop",
|
1001
1033
|
"function_call",
|
1002
1034
|
"tool_calls",
|
1003
|
-
]
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1035
|
+
]
|
1036
|
+
# for function_call, finish_reason does not necessarily
|
1037
|
+
# contain "function_call" as mentioned in the docs.
|
1038
|
+
# So we check for "stop" or "function_call" here.
|
1039
|
+
return (
|
1040
|
+
is_break,
|
1041
|
+
has_function,
|
1042
|
+
function_name,
|
1043
|
+
function_args,
|
1044
|
+
completion,
|
1045
|
+
reasoning,
|
1046
|
+
usage,
|
1047
|
+
)
|
1016
1048
|
|
1017
1049
|
@retry_with_exponential_backoff
|
1018
1050
|
def _stream_response( # type: ignore
|
@@ -1038,6 +1070,8 @@ class OpenAIGPT(LanguageModel):
|
|
1038
1070
|
sys.stdout.flush()
|
1039
1071
|
has_function = False
|
1040
1072
|
tool_deltas: List[Dict[str, Any]] = []
|
1073
|
+
token_usage: Dict[str, int] = {}
|
1074
|
+
done: bool = False
|
1041
1075
|
try:
|
1042
1076
|
for event in response:
|
1043
1077
|
(
|
@@ -1047,6 +1081,7 @@ class OpenAIGPT(LanguageModel):
|
|
1047
1081
|
function_args,
|
1048
1082
|
completion,
|
1049
1083
|
reasoning,
|
1084
|
+
usage,
|
1050
1085
|
) = self._process_stream_event(
|
1051
1086
|
event,
|
1052
1087
|
chat=chat,
|
@@ -1057,8 +1092,17 @@ class OpenAIGPT(LanguageModel):
|
|
1057
1092
|
function_args=function_args,
|
1058
1093
|
function_name=function_name,
|
1059
1094
|
)
|
1095
|
+
if len(usage) > 0:
|
1096
|
+
# capture the token usage when non-empty
|
1097
|
+
token_usage = usage
|
1060
1098
|
if is_break:
|
1061
|
-
|
1099
|
+
if not self.get_stream() or done:
|
1100
|
+
# if not streaming, then we don't wait for last "usage" chunk
|
1101
|
+
break
|
1102
|
+
else:
|
1103
|
+
# mark done, so we quit after the last "usage" chunk
|
1104
|
+
done = True
|
1105
|
+
|
1062
1106
|
except Exception as e:
|
1063
1107
|
logging.warning("Error while processing stream response: %s", str(e))
|
1064
1108
|
|
@@ -1073,6 +1117,7 @@ class OpenAIGPT(LanguageModel):
|
|
1073
1117
|
reasoning=reasoning,
|
1074
1118
|
function_args=function_args,
|
1075
1119
|
function_name=function_name,
|
1120
|
+
usage=token_usage,
|
1076
1121
|
)
|
1077
1122
|
|
1078
1123
|
@async_retry_with_exponential_backoff
|
@@ -1100,6 +1145,8 @@ class OpenAIGPT(LanguageModel):
|
|
1100
1145
|
sys.stdout.flush()
|
1101
1146
|
has_function = False
|
1102
1147
|
tool_deltas: List[Dict[str, Any]] = []
|
1148
|
+
token_usage: Dict[str, int] = {}
|
1149
|
+
done: bool = False
|
1103
1150
|
try:
|
1104
1151
|
async for event in response:
|
1105
1152
|
(
|
@@ -1109,6 +1156,7 @@ class OpenAIGPT(LanguageModel):
|
|
1109
1156
|
function_args,
|
1110
1157
|
completion,
|
1111
1158
|
reasoning,
|
1159
|
+
usage,
|
1112
1160
|
) = await self._process_stream_event_async(
|
1113
1161
|
event,
|
1114
1162
|
chat=chat,
|
@@ -1119,8 +1167,17 @@ class OpenAIGPT(LanguageModel):
|
|
1119
1167
|
function_args=function_args,
|
1120
1168
|
function_name=function_name,
|
1121
1169
|
)
|
1170
|
+
if len(usage) > 0:
|
1171
|
+
# capture the token usage when non-empty
|
1172
|
+
token_usage = usage
|
1122
1173
|
if is_break:
|
1123
|
-
|
1174
|
+
if not self.get_stream() or done:
|
1175
|
+
# if not streaming, then we don't wait for last "usage" chunk
|
1176
|
+
break
|
1177
|
+
else:
|
1178
|
+
# mark done, so we quit after the next "usage" chunk
|
1179
|
+
done = True
|
1180
|
+
|
1124
1181
|
except Exception as e:
|
1125
1182
|
logging.warning("Error while processing stream response: %s", str(e))
|
1126
1183
|
|
@@ -1135,6 +1192,7 @@ class OpenAIGPT(LanguageModel):
|
|
1135
1192
|
reasoning=reasoning,
|
1136
1193
|
function_args=function_args,
|
1137
1194
|
function_name=function_name,
|
1195
|
+
usage=token_usage,
|
1138
1196
|
)
|
1139
1197
|
|
1140
1198
|
@staticmethod
|
@@ -1272,6 +1330,7 @@ class OpenAIGPT(LanguageModel):
|
|
1272
1330
|
reasoning: str = "",
|
1273
1331
|
function_args: str = "",
|
1274
1332
|
function_name: str = "",
|
1333
|
+
usage: Dict[str, int] = {},
|
1275
1334
|
) -> Tuple[LLMResponse, Dict[str, Any]]:
|
1276
1335
|
"""
|
1277
1336
|
Create an LLMResponse object from the streaming API response.
|
@@ -1281,8 +1340,10 @@ class OpenAIGPT(LanguageModel):
|
|
1281
1340
|
tool_deltas: list of tool deltas received from streaming API
|
1282
1341
|
has_function: whether the response contains a function_call
|
1283
1342
|
completion: completion text
|
1343
|
+
reasoning: reasoning text
|
1284
1344
|
function_args: string representing function args
|
1285
1345
|
function_name: name of the function
|
1346
|
+
usage: token usage dict
|
1286
1347
|
Returns:
|
1287
1348
|
Tuple consisting of:
|
1288
1349
|
LLMResponse object (with message, usage),
|
@@ -1347,6 +1408,14 @@ class OpenAIGPT(LanguageModel):
|
|
1347
1408
|
# don't allow empty list [] here
|
1348
1409
|
oai_tool_calls=tool_calls or None if len(tool_deltas) > 0 else None,
|
1349
1410
|
function_call=function_call if has_function else None,
|
1411
|
+
usage=LLMTokenUsage(
|
1412
|
+
prompt_tokens=usage.get("prompt_tokens", 0),
|
1413
|
+
completion_tokens=usage.get("completion_tokens", 0),
|
1414
|
+
cost=self._cost_chat_model(
|
1415
|
+
usage.get("prompt_tokens", 0),
|
1416
|
+
usage.get("completion_tokens", 0),
|
1417
|
+
),
|
1418
|
+
),
|
1350
1419
|
),
|
1351
1420
|
openai_response.dict(),
|
1352
1421
|
)
|
@@ -1833,6 +1902,14 @@ class OpenAIGPT(LanguageModel):
|
|
1833
1902
|
max_tokens=max_tokens,
|
1834
1903
|
stream=self.get_stream(),
|
1835
1904
|
)
|
1905
|
+
if self.get_stream():
|
1906
|
+
args.update(
|
1907
|
+
dict(
|
1908
|
+
# get token-usage numbers in stream mode from OpenAI API,
|
1909
|
+
# and possibly other OpenAI-compatible APIs.
|
1910
|
+
stream_options=dict(include_usage=True),
|
1911
|
+
)
|
1912
|
+
)
|
1836
1913
|
args.update(self._openai_api_call_params(args))
|
1837
1914
|
# only include functions-related args if functions are provided
|
1838
1915
|
# since the OpenAI API will throw an error if `functions` is None or []
|
@@ -3,7 +3,7 @@ langroid/exceptions.py,sha256=OPjece_8cwg94DLPcOGA1ddzy5bGh65pxzcHMnssTz8,2995
|
|
3
3
|
langroid/mytypes.py,sha256=HIcYAqGeA9OK0Hlscym2FI5Oax9QFljDZoVgRlomhRk,4014
|
4
4
|
langroid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
langroid/agent/__init__.py,sha256=ll0Cubd2DZ-fsCMl7e10hf9ZjFGKzphfBco396IKITY,786
|
6
|
-
langroid/agent/base.py,sha256=
|
6
|
+
langroid/agent/base.py,sha256=bs5OLCf534mhsdR7Rgf27GqVNuSV2bOVbD46Y86mGFA,79829
|
7
7
|
langroid/agent/batch.py,sha256=vi1r5i1-vN80WfqHDSwjEym_KfGsqPGUtwktmiK1nuk,20635
|
8
8
|
langroid/agent/chat_agent.py,sha256=Z53oleOUcOXVs_UL90spttGoAooe0mrx3tDtOuhKVms,85214
|
9
9
|
langroid/agent/chat_document.py,sha256=xzMtrPbaW-Y-BnF7kuhr2dorsD-D5rMWzfOqJ8HAoo8,17885
|
@@ -15,6 +15,7 @@ langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
15
15
|
langroid/agent/callbacks/chainlit.py,sha256=UHB6P_J40vsVnssosqkpkOVWRf9NK4TOY0_G2g_Arsg,20900
|
16
16
|
langroid/agent/special/__init__.py,sha256=gik_Xtm_zV7U9s30Mn8UX3Gyuy4jTjQe9zjiE3HWmEo,1273
|
17
17
|
langroid/agent/special/doc_chat_agent.py,sha256=dOL9Y0xAslkwepCdKU8Dc1m5Vk8qgk-gLbU4JzsmTII,65234
|
18
|
+
langroid/agent/special/doc_chat_task.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
19
|
langroid/agent/special/lance_doc_chat_agent.py,sha256=s8xoRs0gGaFtDYFUSIRchsgDVbS5Q3C2b2mr3V1Fd-Q,10419
|
19
20
|
langroid/agent/special/lance_tools.py,sha256=qS8x4wi8mrqfbYV2ztFzrcxyhHQ0ZWOc-zkYiH7awj0,2105
|
20
21
|
langroid/agent/special/relevance_extractor_agent.py,sha256=zIx8GUdVo1aGW6ASla0NPQjYYIpmriK_TYMijqAx3F8,4796
|
@@ -68,11 +69,11 @@ langroid/embedding_models/protoc/embeddings_pb2.pyi,sha256=UkNy7BrNsmQm0vLb3NtGX
|
|
68
69
|
langroid/embedding_models/protoc/embeddings_pb2_grpc.py,sha256=9dYQqkW3JPyBpSEjeGXTNpSqAkC-6FPtBHyteVob2Y8,2452
|
69
70
|
langroid/language_models/__init__.py,sha256=3aD2qC1lz8v12HX4B-dilv27gNxYdGdeu1QvDlkqqHs,1095
|
70
71
|
langroid/language_models/azure_openai.py,sha256=SW0Fp_y6HpERr9l6TtF6CYsKgKwjUf_hSL_2mhTV4wI,5034
|
71
|
-
langroid/language_models/base.py,sha256=
|
72
|
+
langroid/language_models/base.py,sha256=aCEHqmxNM2CD5mt3SyMi7Mf8R4IjkyFwGX-IAUqjxmM,26277
|
72
73
|
langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
|
73
74
|
langroid/language_models/mock_lm.py,sha256=5BgHKDVRWFbUwDT_PFgTZXz9-k8wJSA2e3PZmyDgQ1k,4022
|
74
75
|
langroid/language_models/model_info.py,sha256=tfBBxL0iUf2mVN6CjcvqflzFUVg2oZqOJZexZ8jHTYA,12216
|
75
|
-
langroid/language_models/openai_gpt.py,sha256=
|
76
|
+
langroid/language_models/openai_gpt.py,sha256=yNfiWxhH5BxA_mKiw69D3L4Bu__agI6WVg80IF3P5UI,85785
|
76
77
|
langroid/language_models/utils.py,sha256=L4_CbihDMTGcsg0TOG1Yd5JFEto46--h7CX_14m89sQ,5016
|
77
78
|
langroid/language_models/prompt_formatter/__init__.py,sha256=2-5cdE24XoFDhifOLl8yiscohil1ogbP1ECkYdBlBsk,372
|
78
79
|
langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
|
@@ -128,7 +129,7 @@ langroid/vector_store/pineconedb.py,sha256=otxXZNaBKb9f_H75HTaU3lMHiaR2NUp5MqwLZ
|
|
128
129
|
langroid/vector_store/postgres.py,sha256=wHPtIi2qM4fhO4pMQr95pz1ZCe7dTb2hxl4VYspGZoA,16104
|
129
130
|
langroid/vector_store/qdrantdb.py,sha256=O6dSBoDZ0jzfeVBd7LLvsXu083xs2fxXtPa9gGX3JX4,18443
|
130
131
|
langroid/vector_store/weaviatedb.py,sha256=Yn8pg139gOy3zkaPfoTbMXEEBCiLiYa1MU5d_3UA1K4,11847
|
131
|
-
langroid-0.50.
|
132
|
-
langroid-0.50.
|
133
|
-
langroid-0.50.
|
134
|
-
langroid-0.50.
|
132
|
+
langroid-0.50.3.dist-info/METADATA,sha256=5c4f7md0dqoJqMQuCBZwh3HBvpUS-_rz1liE3LeoPKM,63641
|
133
|
+
langroid-0.50.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
134
|
+
langroid-0.50.3.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
135
|
+
langroid-0.50.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|