langroid 0.6.6__py3-none-any.whl → 0.8.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 +206 -21
- langroid/agent/callbacks/chainlit.py +1 -1
- langroid/agent/chat_agent.py +124 -29
- langroid/agent/chat_document.py +132 -28
- langroid/agent/openai_assistant.py +8 -3
- langroid/agent/special/neo4j/neo4j_chat_agent.py +1 -9
- langroid/agent/special/sql/sql_chat_agent.py +69 -13
- langroid/agent/task.py +36 -9
- langroid/agent/tool_message.py +8 -5
- langroid/agent/tools/rewind_tool.py +1 -1
- langroid/language_models/.chainlit/config.toml +121 -0
- langroid/language_models/.chainlit/translations/en-US.json +231 -0
- langroid/language_models/base.py +111 -10
- langroid/language_models/mock_lm.py +10 -1
- langroid/language_models/openai_gpt.py +260 -36
- {langroid-0.6.6.dist-info → langroid-0.8.0.dist-info}/METADATA +3 -1
- {langroid-0.6.6.dist-info → langroid-0.8.0.dist-info}/RECORD +20 -18
- pyproject.toml +1 -1
- {langroid-0.6.6.dist-info → langroid-0.8.0.dist-info}/LICENSE +0 -0
- {langroid-0.6.6.dist-info → langroid-0.8.0.dist-info}/WHEEL +0 -0
@@ -4,6 +4,7 @@ import logging
|
|
4
4
|
import os
|
5
5
|
import sys
|
6
6
|
import warnings
|
7
|
+
from collections import defaultdict
|
7
8
|
from enum import Enum
|
8
9
|
from functools import cache
|
9
10
|
from itertools import chain
|
@@ -37,7 +38,10 @@ from langroid.language_models.base import (
|
|
37
38
|
LLMMessage,
|
38
39
|
LLMResponse,
|
39
40
|
LLMTokenUsage,
|
41
|
+
OpenAIToolCall,
|
42
|
+
OpenAIToolSpec,
|
40
43
|
Role,
|
44
|
+
ToolChoiceTypes,
|
41
45
|
)
|
42
46
|
from langroid.language_models.config import HFPromptFormatterConfig
|
43
47
|
from langroid.language_models.prompt_formatter.hf_formatter import (
|
@@ -544,7 +548,7 @@ class OpenAIGPT(LanguageModel):
|
|
544
548
|
Order of priority:
|
545
549
|
- (1) Params (mainly max_tokens) in the chat/achat/generate/agenerate call
|
546
550
|
(these are passed in via kwargs)
|
547
|
-
- (2) Params in
|
551
|
+
- (2) Params in OpenAIGPTConfig.params (of class OpenAICallParams)
|
548
552
|
- (3) Specific Params in OpenAIGPTConfig (just temperature for now)
|
549
553
|
"""
|
550
554
|
params = dict(
|
@@ -614,6 +618,7 @@ class OpenAIGPT(LanguageModel):
|
|
614
618
|
self,
|
615
619
|
event,
|
616
620
|
chat: bool = False,
|
621
|
+
tool_deltas: List[Dict[str, Any]] = [],
|
617
622
|
has_function: bool = False,
|
618
623
|
completion: str = "",
|
619
624
|
function_args: str = "",
|
@@ -637,6 +642,7 @@ class OpenAIGPT(LanguageModel):
|
|
637
642
|
choices = [{}]
|
638
643
|
event_args = ""
|
639
644
|
event_fn_name = ""
|
645
|
+
event_tool_deltas: Optional[List[Dict[str, Any]]] = None
|
640
646
|
|
641
647
|
# The first two events in the stream of Azure OpenAI is useless.
|
642
648
|
# In the 1st: choices list is empty, in the 2nd: the dict delta has null content
|
@@ -648,6 +654,10 @@ class OpenAIGPT(LanguageModel):
|
|
648
654
|
event_fn_name = delta["function_call"]["name"]
|
649
655
|
if "arguments" in delta["function_call"]:
|
650
656
|
event_args = delta["function_call"]["arguments"]
|
657
|
+
if "tool_calls" in delta and delta["tool_calls"] is not None:
|
658
|
+
# it's a list of deltas, usually just one
|
659
|
+
event_tool_deltas = delta["tool_calls"]
|
660
|
+
tool_deltas += event_tool_deltas
|
651
661
|
else:
|
652
662
|
event_text = choices[0]["text"]
|
653
663
|
if event_text:
|
@@ -670,7 +680,31 @@ class OpenAIGPT(LanguageModel):
|
|
670
680
|
sys.stdout.write(Colors().GREEN + event_args)
|
671
681
|
sys.stdout.flush()
|
672
682
|
self.config.streamer(event_args)
|
673
|
-
|
683
|
+
|
684
|
+
if event_tool_deltas is not None:
|
685
|
+
# print out streaming tool calls
|
686
|
+
for td in event_tool_deltas:
|
687
|
+
if td["function"]["name"] is not None:
|
688
|
+
tool_fn_name = td["function"]["name"]
|
689
|
+
if not is_async:
|
690
|
+
sys.stdout.write(
|
691
|
+
Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
|
692
|
+
)
|
693
|
+
sys.stdout.flush()
|
694
|
+
self.config.streamer(tool_fn_name)
|
695
|
+
if td["function"]["arguments"] != "":
|
696
|
+
tool_fn_args = td["function"]["arguments"]
|
697
|
+
if not is_async:
|
698
|
+
sys.stdout.write(Colors().GREEN + tool_fn_args)
|
699
|
+
sys.stdout.flush()
|
700
|
+
self.config.streamer(tool_fn_args)
|
701
|
+
|
702
|
+
# show this delta in the stream
|
703
|
+
if choices[0].get("finish_reason", "") in [
|
704
|
+
"stop",
|
705
|
+
"function_call",
|
706
|
+
"tool_calls",
|
707
|
+
]:
|
674
708
|
# for function_call, finish_reason does not necessarily
|
675
709
|
# contain "function_call" as mentioned in the docs.
|
676
710
|
# So we check for "stop" or "function_call" here.
|
@@ -699,6 +733,7 @@ class OpenAIGPT(LanguageModel):
|
|
699
733
|
sys.stdout.write(Colors().GREEN)
|
700
734
|
sys.stdout.flush()
|
701
735
|
has_function = False
|
736
|
+
tool_deltas: List[Dict[str, Any]] = []
|
702
737
|
try:
|
703
738
|
for event in response:
|
704
739
|
(
|
@@ -710,6 +745,7 @@ class OpenAIGPT(LanguageModel):
|
|
710
745
|
) = self._process_stream_event(
|
711
746
|
event,
|
712
747
|
chat=chat,
|
748
|
+
tool_deltas=tool_deltas,
|
713
749
|
has_function=has_function,
|
714
750
|
completion=completion,
|
715
751
|
function_args=function_args,
|
@@ -725,11 +761,11 @@ class OpenAIGPT(LanguageModel):
|
|
725
761
|
|
726
762
|
return self._create_stream_response(
|
727
763
|
chat=chat,
|
764
|
+
tool_deltas=tool_deltas,
|
728
765
|
has_function=has_function,
|
729
766
|
completion=completion,
|
730
767
|
function_args=function_args,
|
731
768
|
function_name=function_name,
|
732
|
-
is_async=False,
|
733
769
|
)
|
734
770
|
|
735
771
|
@async_retry_with_exponential_backoff
|
@@ -754,6 +790,7 @@ class OpenAIGPT(LanguageModel):
|
|
754
790
|
sys.stdout.write(Colors().GREEN)
|
755
791
|
sys.stdout.flush()
|
756
792
|
has_function = False
|
793
|
+
tool_deltas: List[Dict[str, Any]] = []
|
757
794
|
try:
|
758
795
|
async for event in response:
|
759
796
|
(
|
@@ -765,6 +802,7 @@ class OpenAIGPT(LanguageModel):
|
|
765
802
|
) = self._process_stream_event(
|
766
803
|
event,
|
767
804
|
chat=chat,
|
805
|
+
tool_deltas=tool_deltas,
|
768
806
|
has_function=has_function,
|
769
807
|
completion=completion,
|
770
808
|
function_args=function_args,
|
@@ -780,52 +818,182 @@ class OpenAIGPT(LanguageModel):
|
|
780
818
|
|
781
819
|
return self._create_stream_response(
|
782
820
|
chat=chat,
|
821
|
+
tool_deltas=tool_deltas,
|
783
822
|
has_function=has_function,
|
784
823
|
completion=completion,
|
785
824
|
function_args=function_args,
|
786
825
|
function_name=function_name,
|
787
|
-
is_async=True,
|
788
826
|
)
|
789
827
|
|
828
|
+
@staticmethod
|
829
|
+
def tool_deltas_to_tools(tools: List[Dict[str, Any]]) -> Tuple[
|
830
|
+
str,
|
831
|
+
List[OpenAIToolCall],
|
832
|
+
List[Dict[str, Any]],
|
833
|
+
]:
|
834
|
+
"""
|
835
|
+
Convert accumulated tool-call deltas to OpenAIToolCall objects.
|
836
|
+
Adapted from this excellent code:
|
837
|
+
https://community.openai.com/t/help-for-function-calls-with-streaming/627170/2
|
838
|
+
|
839
|
+
Args:
|
840
|
+
tools: list of tool deltas received from streaming API
|
841
|
+
|
842
|
+
Returns:
|
843
|
+
str: plain text corresponding to tool calls that failed to parse
|
844
|
+
List[OpenAIToolCall]: list of OpenAIToolCall objects
|
845
|
+
List[Dict[str, Any]]: list of tool dicts
|
846
|
+
(to reconstruct OpenAI API response, so it can be cached)
|
847
|
+
"""
|
848
|
+
# Initialize a dictionary with default values
|
849
|
+
|
850
|
+
# idx -> dict repr of tool
|
851
|
+
# (used to simulate OpenAIResponse object later, and also to
|
852
|
+
# accumulate function args as strings)
|
853
|
+
idx2tool_dict: Dict[str, Dict[str, Any]] = defaultdict(
|
854
|
+
lambda: {
|
855
|
+
"id": None,
|
856
|
+
"function": {"arguments": "", "name": None},
|
857
|
+
"type": None,
|
858
|
+
}
|
859
|
+
)
|
860
|
+
|
861
|
+
for tool_delta in tools:
|
862
|
+
if tool_delta["id"] is not None:
|
863
|
+
idx2tool_dict[tool_delta["index"]]["id"] = tool_delta["id"]
|
864
|
+
|
865
|
+
if tool_delta["function"]["name"] is not None:
|
866
|
+
idx2tool_dict[tool_delta["index"]]["function"]["name"] = tool_delta[
|
867
|
+
"function"
|
868
|
+
]["name"]
|
869
|
+
|
870
|
+
idx2tool_dict[tool_delta["index"]]["function"]["arguments"] += tool_delta[
|
871
|
+
"function"
|
872
|
+
]["arguments"]
|
873
|
+
|
874
|
+
if tool_delta["type"] is not None:
|
875
|
+
idx2tool_dict[tool_delta["index"]]["type"] = tool_delta["type"]
|
876
|
+
|
877
|
+
# (try to) parse the fn args of each tool
|
878
|
+
contents: List[str] = []
|
879
|
+
good_indices = []
|
880
|
+
id2args: Dict[str, None | Dict[str, Any]] = {}
|
881
|
+
for idx, tool_dict in idx2tool_dict.items():
|
882
|
+
failed_content, args_dict = OpenAIGPT._parse_function_args(
|
883
|
+
tool_dict["function"]["arguments"]
|
884
|
+
)
|
885
|
+
# used to build tool_calls_list below
|
886
|
+
id2args[tool_dict["id"]] = args_dict or None # if {}, store as None
|
887
|
+
if failed_content != "":
|
888
|
+
contents.append(failed_content)
|
889
|
+
else:
|
890
|
+
good_indices.append(idx)
|
891
|
+
|
892
|
+
# remove the failed tool calls
|
893
|
+
idx2tool_dict = {
|
894
|
+
idx: tool_dict
|
895
|
+
for idx, tool_dict in idx2tool_dict.items()
|
896
|
+
if idx in good_indices
|
897
|
+
}
|
898
|
+
|
899
|
+
# create OpenAIToolCall list
|
900
|
+
tool_calls_list = [
|
901
|
+
OpenAIToolCall(
|
902
|
+
id=tool_dict["id"],
|
903
|
+
function=LLMFunctionCall(
|
904
|
+
name=tool_dict["function"]["name"],
|
905
|
+
arguments=id2args.get(tool_dict["id"]),
|
906
|
+
),
|
907
|
+
type=tool_dict["type"],
|
908
|
+
)
|
909
|
+
for tool_dict in idx2tool_dict.values()
|
910
|
+
]
|
911
|
+
return "\n".join(contents), tool_calls_list, list(idx2tool_dict.values())
|
912
|
+
|
913
|
+
@staticmethod
|
914
|
+
def _parse_function_args(args: str) -> Tuple[str, Dict[str, Any]]:
|
915
|
+
"""
|
916
|
+
Try to parse the `args` string as function args.
|
917
|
+
|
918
|
+
Args:
|
919
|
+
args: string containing function args
|
920
|
+
|
921
|
+
Returns:
|
922
|
+
Tuple of content, function name and args dict.
|
923
|
+
If parsing unsuccessful, returns the original string as content,
|
924
|
+
else returns the args dict.
|
925
|
+
"""
|
926
|
+
content = ""
|
927
|
+
args_dict = {}
|
928
|
+
try:
|
929
|
+
stripped_fn_args = args.strip()
|
930
|
+
dict_or_list = parse_imperfect_json(stripped_fn_args)
|
931
|
+
if not isinstance(dict_or_list, dict):
|
932
|
+
raise ValueError(
|
933
|
+
f"""
|
934
|
+
Invalid function args: {stripped_fn_args}
|
935
|
+
parsed as {dict_or_list},
|
936
|
+
which is not a valid dict.
|
937
|
+
"""
|
938
|
+
)
|
939
|
+
args_dict = dict_or_list
|
940
|
+
except (SyntaxError, ValueError) as e:
|
941
|
+
logging.warning(
|
942
|
+
f"""
|
943
|
+
Parsing OpenAI function args failed: {args};
|
944
|
+
treating args as normal message. Error detail:
|
945
|
+
{e}
|
946
|
+
"""
|
947
|
+
)
|
948
|
+
content = args
|
949
|
+
|
950
|
+
return content, args_dict
|
951
|
+
|
790
952
|
def _create_stream_response(
|
791
953
|
self,
|
792
954
|
chat: bool = False,
|
955
|
+
tool_deltas: List[Dict[str, Any]] = [],
|
793
956
|
has_function: bool = False,
|
794
957
|
completion: str = "",
|
795
958
|
function_args: str = "",
|
796
959
|
function_name: str = "",
|
797
|
-
is_async: bool = False,
|
798
960
|
) -> Tuple[LLMResponse, Dict[str, Any]]:
|
961
|
+
"""
|
962
|
+
Create an LLMResponse object from the streaming API response.
|
963
|
+
|
964
|
+
Args:
|
965
|
+
chat: whether in chat-mode (or else completion-mode)
|
966
|
+
tool_deltas: list of tool deltas received from streaming API
|
967
|
+
has_function: whether the response contains a function_call
|
968
|
+
completion: completion text
|
969
|
+
function_args: string representing function args
|
970
|
+
function_name: name of the function
|
971
|
+
Returns:
|
972
|
+
Tuple consisting of:
|
973
|
+
LLMResponse object (with message, usage),
|
974
|
+
Dict version of OpenAIResponse object (with choices, usage)
|
975
|
+
(this is needed so we can cache the response, as if it were
|
976
|
+
a non-streaming response)
|
977
|
+
"""
|
799
978
|
# check if function_call args are valid, if not,
|
800
979
|
# treat this as a normal msg, not a function call
|
801
|
-
args = {}
|
980
|
+
args: Dict[str, Any] = {}
|
802
981
|
if has_function and function_args != "":
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
if not isinstance(dict_or_list, dict):
|
807
|
-
raise ValueError(
|
808
|
-
f"""
|
809
|
-
Invalid function args: {stripped_fn_args}
|
810
|
-
parsed as {dict_or_list},
|
811
|
-
which is not a valid dict.
|
812
|
-
"""
|
813
|
-
)
|
814
|
-
args = dict_or_list
|
815
|
-
except (SyntaxError, ValueError) as e:
|
816
|
-
logging.warning(
|
817
|
-
f"""
|
818
|
-
Parsing OpenAI function args failed: {function_args};
|
819
|
-
treating args as normal message. Error detail:
|
820
|
-
{e}
|
821
|
-
"""
|
822
|
-
)
|
982
|
+
content, args = self._parse_function_args(function_args)
|
983
|
+
completion = completion + content
|
984
|
+
if content != "":
|
823
985
|
has_function = False
|
824
|
-
completion = completion + function_args
|
825
986
|
|
826
987
|
# mock openai response so we can cache it
|
827
988
|
if chat:
|
989
|
+
failed_content, tool_calls, tool_dicts = OpenAIGPT.tool_deltas_to_tools(
|
990
|
+
tool_deltas,
|
991
|
+
)
|
992
|
+
completion = completion + "\n" + failed_content
|
828
993
|
msg: Dict[str, Any] = dict(message=dict(content=completion))
|
994
|
+
if len(tool_dicts) > 0:
|
995
|
+
msg["message"]["tool_calls"] = tool_dicts
|
996
|
+
|
829
997
|
if has_function:
|
830
998
|
function_call = LLMFunctionCall(name=function_name)
|
831
999
|
function_call_dict = function_call.dict()
|
@@ -839,6 +1007,8 @@ class OpenAIGPT(LanguageModel):
|
|
839
1007
|
# non-chat mode has no function_call
|
840
1008
|
msg = dict(text=completion)
|
841
1009
|
|
1010
|
+
# create an OpenAIResponse object so we can cache it as if it were
|
1011
|
+
# a non-streaming response
|
842
1012
|
openai_response = OpenAIResponse(
|
843
1013
|
choices=[msg],
|
844
1014
|
usage=dict(total_tokens=0),
|
@@ -847,6 +1017,7 @@ class OpenAIGPT(LanguageModel):
|
|
847
1017
|
LLMResponse(
|
848
1018
|
message=completion,
|
849
1019
|
cached=False,
|
1020
|
+
oai_tool_calls=tool_calls or None, # don't allow empty list [] here
|
850
1021
|
function_call=function_call if has_function else None,
|
851
1022
|
),
|
852
1023
|
openai_response.dict(),
|
@@ -1061,16 +1232,19 @@ class OpenAIGPT(LanguageModel):
|
|
1061
1232
|
self,
|
1062
1233
|
messages: Union[str, List[LLMMessage]],
|
1063
1234
|
max_tokens: int = 200,
|
1235
|
+
tools: Optional[List[OpenAIToolSpec]] = None,
|
1236
|
+
tool_choice: ToolChoiceTypes | Dict[str, str | Dict[str, str]] = "auto",
|
1064
1237
|
functions: Optional[List[LLMFunctionSpec]] = None,
|
1065
1238
|
function_call: str | Dict[str, str] = "auto",
|
1066
1239
|
) -> LLMResponse:
|
1067
1240
|
self.run_on_first_use()
|
1068
1241
|
|
1069
|
-
if functions
|
1242
|
+
if [functions, tools] != [None, None] and not self.is_openai_chat_model():
|
1070
1243
|
raise ValueError(
|
1071
1244
|
f"""
|
1072
|
-
`functions` can only be specified for OpenAI chat
|
1073
|
-
|
1245
|
+
`functions` and `tools` can only be specified for OpenAI chat LLMs,
|
1246
|
+
or LLMs served via an OpenAI-compatible API.
|
1247
|
+
{self.config.chat_model} does not support function-calling or tools.
|
1074
1248
|
Instead, please use Langroid's ToolMessages, which are equivalent.
|
1075
1249
|
In the ChatAgentConfig, set `use_functions_api=False`
|
1076
1250
|
and `use_tools=True`, this will enable ToolMessages.
|
@@ -1094,7 +1268,9 @@ class OpenAIGPT(LanguageModel):
|
|
1094
1268
|
prompt = self.config.hf_formatter.format(messages)
|
1095
1269
|
return self.generate(prompt=prompt, max_tokens=max_tokens)
|
1096
1270
|
try:
|
1097
|
-
return self._chat(
|
1271
|
+
return self._chat(
|
1272
|
+
messages, max_tokens, tools, tool_choice, functions, function_call
|
1273
|
+
)
|
1098
1274
|
except Exception as e:
|
1099
1275
|
# log and re-raise exception
|
1100
1276
|
logging.error(friendly_error(e, "Error in OpenAIGPT.chat: "))
|
@@ -1104,16 +1280,18 @@ class OpenAIGPT(LanguageModel):
|
|
1104
1280
|
self,
|
1105
1281
|
messages: Union[str, List[LLMMessage]],
|
1106
1282
|
max_tokens: int = 200,
|
1283
|
+
tools: Optional[List[OpenAIToolSpec]] = None,
|
1284
|
+
tool_choice: ToolChoiceTypes | Dict[str, str | Dict[str, str]] = "auto",
|
1107
1285
|
functions: Optional[List[LLMFunctionSpec]] = None,
|
1108
1286
|
function_call: str | Dict[str, str] = "auto",
|
1109
1287
|
) -> LLMResponse:
|
1110
1288
|
self.run_on_first_use()
|
1111
1289
|
|
1112
|
-
if functions
|
1290
|
+
if [functions, tools] != [None, None] and not self.is_openai_chat_model():
|
1113
1291
|
raise ValueError(
|
1114
1292
|
f"""
|
1115
|
-
`functions` can only be specified for OpenAI chat models;
|
1116
|
-
{self.config.chat_model} does not support function-calling.
|
1293
|
+
`functions` and `tools` can only be specified for OpenAI chat models;
|
1294
|
+
{self.config.chat_model} does not support function-calling or tools.
|
1117
1295
|
Instead, please use Langroid's ToolMessages, which are equivalent.
|
1118
1296
|
In the ChatAgentConfig, set `use_functions_api=False`
|
1119
1297
|
and `use_tools=True`, this will enable ToolMessages.
|
@@ -1146,7 +1324,14 @@ class OpenAIGPT(LanguageModel):
|
|
1146
1324
|
prompt = formatter.format(messages)
|
1147
1325
|
return await self.agenerate(prompt=prompt, max_tokens=max_tokens)
|
1148
1326
|
try:
|
1149
|
-
result = await self._achat(
|
1327
|
+
result = await self._achat(
|
1328
|
+
messages,
|
1329
|
+
max_tokens,
|
1330
|
+
tools,
|
1331
|
+
tool_choice,
|
1332
|
+
functions,
|
1333
|
+
function_call,
|
1334
|
+
)
|
1150
1335
|
return result
|
1151
1336
|
except Exception as e:
|
1152
1337
|
# log and re-raise exception
|
@@ -1209,9 +1394,12 @@ class OpenAIGPT(LanguageModel):
|
|
1209
1394
|
self,
|
1210
1395
|
messages: Union[str, List[LLMMessage]],
|
1211
1396
|
max_tokens: int,
|
1397
|
+
tools: Optional[List[OpenAIToolSpec]] = None,
|
1398
|
+
tool_choice: ToolChoiceTypes | Dict[str, str | Dict[str, str]] = "auto",
|
1212
1399
|
functions: Optional[List[LLMFunctionSpec]] = None,
|
1213
1400
|
function_call: str | Dict[str, str] = "auto",
|
1214
1401
|
) -> Dict[str, Any]:
|
1402
|
+
"""Prepare args for LLM chat-completion API call"""
|
1215
1403
|
if isinstance(messages, str):
|
1216
1404
|
llm_messages = [
|
1217
1405
|
LLMMessage(role=Role.SYSTEM, content="You are a helpful assistant."),
|
@@ -1243,6 +1431,19 @@ class OpenAIGPT(LanguageModel):
|
|
1243
1431
|
function_call=function_call,
|
1244
1432
|
)
|
1245
1433
|
)
|
1434
|
+
if tools is not None:
|
1435
|
+
args.update(
|
1436
|
+
dict(
|
1437
|
+
tools=[
|
1438
|
+
dict(
|
1439
|
+
type="function",
|
1440
|
+
function=t.function.dict(),
|
1441
|
+
)
|
1442
|
+
for t in tools
|
1443
|
+
],
|
1444
|
+
tool_choice=tool_choice,
|
1445
|
+
)
|
1446
|
+
)
|
1246
1447
|
return args
|
1247
1448
|
|
1248
1449
|
def _process_chat_completion_response(
|
@@ -1281,6 +1482,7 @@ class OpenAIGPT(LanguageModel):
|
|
1281
1482
|
"""
|
1282
1483
|
message = response["choices"][0]["message"]
|
1283
1484
|
msg = message["content"] or ""
|
1485
|
+
|
1284
1486
|
if message.get("function_call") is None:
|
1285
1487
|
fun_call = None
|
1286
1488
|
else:
|
@@ -1297,10 +1499,24 @@ class OpenAIGPT(LanguageModel):
|
|
1297
1499
|
args_str = message["function_call"]["arguments"] or ""
|
1298
1500
|
msg_str = message["content"] or ""
|
1299
1501
|
msg = msg_str + args_str
|
1300
|
-
|
1502
|
+
oai_tool_calls = None
|
1503
|
+
if message.get("tool_calls") is not None:
|
1504
|
+
oai_tool_calls = []
|
1505
|
+
for tool_call_dict in message["tool_calls"]:
|
1506
|
+
try:
|
1507
|
+
tool_call = OpenAIToolCall.from_dict(tool_call_dict)
|
1508
|
+
oai_tool_calls.append(tool_call)
|
1509
|
+
except (ValueError, SyntaxError):
|
1510
|
+
logging.warning(
|
1511
|
+
"Could not parse tool call: "
|
1512
|
+
f"{json.dumps(tool_call_dict)} "
|
1513
|
+
"treating as normal non-tool message"
|
1514
|
+
)
|
1515
|
+
msg = msg + "\n" + json.dumps(tool_call_dict)
|
1301
1516
|
return LLMResponse(
|
1302
1517
|
message=msg.strip() if msg is not None else "",
|
1303
1518
|
function_call=fun_call,
|
1519
|
+
oai_tool_calls=oai_tool_calls or None, # don't allow empty list [] here
|
1304
1520
|
cached=cached,
|
1305
1521
|
usage=self._get_non_stream_token_usage(cached, response),
|
1306
1522
|
)
|
@@ -1309,6 +1525,8 @@ class OpenAIGPT(LanguageModel):
|
|
1309
1525
|
self,
|
1310
1526
|
messages: Union[str, List[LLMMessage]],
|
1311
1527
|
max_tokens: int,
|
1528
|
+
tools: Optional[List[OpenAIToolSpec]] = None,
|
1529
|
+
tool_choice: ToolChoiceTypes | Dict[str, str | Dict[str, str]] = "auto",
|
1312
1530
|
functions: Optional[List[LLMFunctionSpec]] = None,
|
1313
1531
|
function_call: str | Dict[str, str] = "auto",
|
1314
1532
|
) -> LLMResponse:
|
@@ -1333,6 +1551,8 @@ class OpenAIGPT(LanguageModel):
|
|
1333
1551
|
args = self._prep_chat_completion(
|
1334
1552
|
messages,
|
1335
1553
|
max_tokens,
|
1554
|
+
tools,
|
1555
|
+
tool_choice,
|
1336
1556
|
functions,
|
1337
1557
|
function_call,
|
1338
1558
|
)
|
@@ -1351,6 +1571,8 @@ class OpenAIGPT(LanguageModel):
|
|
1351
1571
|
self,
|
1352
1572
|
messages: Union[str, List[LLMMessage]],
|
1353
1573
|
max_tokens: int,
|
1574
|
+
tools: Optional[List[OpenAIToolSpec]] = None,
|
1575
|
+
tool_choice: ToolChoiceTypes | Dict[str, str | Dict[str, str]] = "auto",
|
1354
1576
|
functions: Optional[List[LLMFunctionSpec]] = None,
|
1355
1577
|
function_call: str | Dict[str, str] = "auto",
|
1356
1578
|
) -> LLMResponse:
|
@@ -1360,6 +1582,8 @@ class OpenAIGPT(LanguageModel):
|
|
1360
1582
|
args = self._prep_chat_completion(
|
1361
1583
|
messages,
|
1362
1584
|
max_tokens,
|
1585
|
+
tools,
|
1586
|
+
tool_choice,
|
1363
1587
|
functions,
|
1364
1588
|
function_call,
|
1365
1589
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.8.0
|
4
4
|
Summary: Harness LLMs with Multi-Agent Programming
|
5
5
|
License: MIT
|
6
6
|
Author: Prasad Chalasani
|
@@ -235,6 +235,8 @@ teacher_task.run()
|
|
235
235
|
<details>
|
236
236
|
<summary> <b>Click to expand</b></summary>
|
237
237
|
|
238
|
+
- **Aug 2024:**
|
239
|
+
- **[0.7.0](https://github.com/langroid/langroid/releases/tag/0.7.0)** OpenAI tools API support.
|
238
240
|
- **Jul 2024:**
|
239
241
|
- **[0.3.0](https://github.com/langroid/langroid/releases/tag/0.3.0)**: Added [FastEmbed](https://qdrant.github.io/fastembed/qdrant/Usage_With_Qdrant/) embeddings from Qdrant
|
240
242
|
- **Jun 2024:**
|
@@ -1,14 +1,14 @@
|
|
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=
|
3
|
+
langroid/agent/base.py,sha256=EOAdZ2K0Yjjdt3zkjNsUhHgRvYZT-eIgRbY4ldjt6hY,46995
|
4
4
|
langroid/agent/batch.py,sha256=feRA_yRG768ElOQjrKEefcRv6Aefd_yY7qktuYUQDwc,10040
|
5
5
|
langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
langroid/agent/callbacks/chainlit.py,sha256=
|
7
|
-
langroid/agent/chat_agent.py,sha256=
|
8
|
-
langroid/agent/chat_document.py,sha256=
|
6
|
+
langroid/agent/callbacks/chainlit.py,sha256=Qedk1-CBCgo9PdaIa7AboLBFCTgAMg9q5nGmoqpZ378,22050
|
7
|
+
langroid/agent/chat_agent.py,sha256=4bgLHBFSxL1dw1UAEF5zfCqnp_ss0MUUTOnjKkfOJRQ,45401
|
8
|
+
langroid/agent/chat_document.py,sha256=y3QBdpclTBuqs42c3lLO9HU4s70PyK133HXZxyaxNZ8,16322
|
9
9
|
langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
|
11
|
-
langroid/agent/openai_assistant.py,sha256=
|
11
|
+
langroid/agent/openai_assistant.py,sha256=TlMi5QecrxxhZ7TW03wB161elSSQYwL7H0xk8g7HawU,33879
|
12
12
|
langroid/agent/special/__init__.py,sha256=gik_Xtm_zV7U9s30Mn8UX3Gyuy4jTjQe9zjiE3HWmEo,1273
|
13
13
|
langroid/agent/special/doc_chat_agent.py,sha256=8NPAhMnHkFUolQ8EHos40tz5Vwuz_m33NjUfjheXWXY,54569
|
14
14
|
langroid/agent/special/lance_doc_chat_agent.py,sha256=Hjpu6u9UPAFMg5J6K97PRFaLbNrGhInC0N9oGi09CeY,10006
|
@@ -23,22 +23,22 @@ langroid/agent/special/lance_rag_new/query_planner_agent.py,sha256=JqO_5fKW8HPn-
|
|
23
23
|
langroid/agent/special/lance_tools.py,sha256=BznV_r3LAFyybvBRa9KQ0oU7mPM3uQVfri7PFp7M_qc,1894
|
24
24
|
langroid/agent/special/neo4j/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
25
|
langroid/agent/special/neo4j/csv_kg_chat.py,sha256=dRsAgMBa1H_EMI2YYgJR2Xyv1D7e4o3G9M64mTewq_c,6409
|
26
|
-
langroid/agent/special/neo4j/neo4j_chat_agent.py,sha256=
|
26
|
+
langroid/agent/special/neo4j/neo4j_chat_agent.py,sha256=dvZ4rrMWwKuOIOjC3iWQIe-cSxNXkaU_6W0rQII_4Q8,13347
|
27
27
|
langroid/agent/special/neo4j/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
28
|
langroid/agent/special/neo4j/utils/system_message.py,sha256=_2NKX2Sx_7nLGNk_0rUyDkgTHZJuKm_DgtG7M_BFPkY,2920
|
29
29
|
langroid/agent/special/relevance_extractor_agent.py,sha256=zIx8GUdVo1aGW6ASla0NPQjYYIpmriK_TYMijqAx3F8,4796
|
30
30
|
langroid/agent/special/retriever_agent.py,sha256=lvMvf-u9rSosg4YASuFdUbGLgkzLPknXAbJZfZ1LZCc,1868
|
31
31
|
langroid/agent/special/sql/__init__.py,sha256=mWfmm1QpXCezpFOS2eI57M0L_Ok3q5_ukG8tXBnBrEA,319
|
32
|
-
langroid/agent/special/sql/sql_chat_agent.py,sha256=
|
32
|
+
langroid/agent/special/sql/sql_chat_agent.py,sha256=PArNz9up0atw0cJet1LOfWlpW6xgofVBDR7Z3xvpzHk,16592
|
33
33
|
langroid/agent/special/sql/utils/__init__.py,sha256=JFif6CRTrN-bc91uuAI4K9fe2ndIWSNMVxJ0WA68--M,446
|
34
34
|
langroid/agent/special/sql/utils/description_extractors.py,sha256=cX8TIpmTPXZXQTMpIi3OUFwFsPywxFFdurpx717Kq0I,6529
|
35
35
|
langroid/agent/special/sql/utils/populate_metadata.py,sha256=1J22UsyEPKzwK0XlJZtYn9r6kYc0FXIr8-lZrndYlhc,3131
|
36
36
|
langroid/agent/special/sql/utils/system_message.py,sha256=qKLHkvQWRQodTtPLPxr1GSLUYUFASZU8x-ybV67cB68,1885
|
37
37
|
langroid/agent/special/sql/utils/tools.py,sha256=vFYysk6Vi7HJjII8B4RitA3pt_z3gkSglDNdhNVMiFc,1332
|
38
38
|
langroid/agent/special/table_chat_agent.py,sha256=d9v2wsblaRx7oMnKhLV7uO_ujvk9gh59pSGvBXyeyNc,9659
|
39
|
-
langroid/agent/task.py,sha256=
|
39
|
+
langroid/agent/task.py,sha256=eCA91yr6DSaRRGteTbSki-liMl6nspzRd_5Gin_ZFYw,75176
|
40
40
|
langroid/agent/team.py,sha256=88VNRSmK35WEl620GfBzuIrBASXYSeBZ8yDKX-nP_Bo,75778
|
41
|
-
langroid/agent/tool_message.py,sha256=
|
41
|
+
langroid/agent/tool_message.py,sha256=XPM6whazpUOQu_vM2D4Q_L9tV6Vs9OTXNrGchwcqD_o,9768
|
42
42
|
langroid/agent/tools/__init__.py,sha256=e-63cfwQNk_ftRKQwgDAJQK16QLbRVWDBILeXIc7wLk,402
|
43
43
|
langroid/agent/tools/duckduckgo_search_tool.py,sha256=NhsCaGZkdv28nja7yveAhSK_w6l_Ftym8agbrdzqgfo,1935
|
44
44
|
langroid/agent/tools/extract_tool.py,sha256=u5lL9rKBzaLBOrRyLnTAZ97pQ1uxyLP39XsWMnpaZpw,3789
|
@@ -48,7 +48,7 @@ langroid/agent/tools/metaphor_search_tool.py,sha256=qj4gt453cLEX3EGW7nVzVu6X7LCd
|
|
48
48
|
langroid/agent/tools/note_tool.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
49
|
langroid/agent/tools/recipient_tool.py,sha256=NrLxIeQT-kbMv7AeYX0uqvGeMK4Q3fIDvG15OVzlgk8,9624
|
50
50
|
langroid/agent/tools/retrieval_tool.py,sha256=2q2pfoYbZNfbWQ0McxrtmfF0ekGglIgRl-6uF26pa-E,871
|
51
|
-
langroid/agent/tools/rewind_tool.py,sha256=
|
51
|
+
langroid/agent/tools/rewind_tool.py,sha256=XAXL3BpNhCmBGYq_qi_sZfHJuIw7NY2jp4wnojJ7WRs,5606
|
52
52
|
langroid/agent/tools/run_python_code.py,sha256=BvoxYzzHijU-p4703n2iVlt5BCieR1oMSy50w0tQZAg,1787
|
53
53
|
langroid/agent/tools/segment_extract_tool.py,sha256=__srZ_VGYLVOdPrITUM8S0HpmX4q7r5FHWMDdHdEv8w,1440
|
54
54
|
langroid/agent_config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -67,12 +67,14 @@ langroid/embedding_models/protoc/embeddings_pb2.pyi,sha256=UkNy7BrNsmQm0vLb3NtGX
|
|
67
67
|
langroid/embedding_models/protoc/embeddings_pb2_grpc.py,sha256=9dYQqkW3JPyBpSEjeGXTNpSqAkC-6FPtBHyteVob2Y8,2452
|
68
68
|
langroid/embedding_models/remote_embeds.py,sha256=6_kjXByVbqhY9cGwl9R83ZcYC2km-nGieNNAo1McHaY,5151
|
69
69
|
langroid/exceptions.py,sha256=w_Cr41nPAmsa6gW5nNFaO9yDcBCWdQqRspL1jYvZf5w,2209
|
70
|
+
langroid/language_models/.chainlit/config.toml,sha256=1t5lHORGzc2E6dkaO9P15jYHu2w-4Kl9pYjpDPc84vs,3716
|
71
|
+
langroid/language_models/.chainlit/translations/en-US.json,sha256=DAFz2HjOFFfboCStrUfKFg2BpplJPK_OOtixwF_GivY,9931
|
70
72
|
langroid/language_models/__init__.py,sha256=1sUGobooTqq77XC7LxKsvME0RgSd5GGmeyrPo9SMh4U,940
|
71
73
|
langroid/language_models/azure_openai.py,sha256=G4le3j4YLHV7IwgB2C37hO3MKijZ1KjynbYlEvpIF7Y,6214
|
72
|
-
langroid/language_models/base.py,sha256=
|
74
|
+
langroid/language_models/base.py,sha256=L76syCytos5IaQ5tpgToYJHThch4gA9uyjeMmjWjZ8E,21757
|
73
75
|
langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
|
74
|
-
langroid/language_models/mock_lm.py,sha256=
|
75
|
-
langroid/language_models/openai_gpt.py,sha256=
|
76
|
+
langroid/language_models/mock_lm.py,sha256=2Ka05SVGSUy096bsa2AyjaqC5jmcFoe7HycpdnICTIw,3031
|
77
|
+
langroid/language_models/openai_gpt.py,sha256=Bv-FCva9q0oO85VSqNpaxEYI8zPwVXpHmmfV7O8QRhU,61325
|
76
78
|
langroid/language_models/prompt_formatter/__init__.py,sha256=2-5cdE24XoFDhifOLl8yiscohil1ogbP1ECkYdBlBsk,372
|
77
79
|
langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
|
78
80
|
langroid/language_models/prompt_formatter/hf_formatter.py,sha256=TFL6ppmeQWnzr6CKQzRZFYY810zE1mr8DZnhw6i85ok,5217
|
@@ -134,8 +136,8 @@ langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3Hmh
|
|
134
136
|
langroid/vector_store/momento.py,sha256=qR-zBF1RKVHQZPZQYW_7g-XpTwr46p8HJuYPCkfJbM4,10534
|
135
137
|
langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
|
136
138
|
langroid/vector_store/qdrantdb.py,sha256=v88lqFkepADvlN6lByUj9I4NEKa9X9lWH16uTPPbYrE,17457
|
137
|
-
pyproject.toml,sha256=
|
138
|
-
langroid-0.
|
139
|
-
langroid-0.
|
140
|
-
langroid-0.
|
141
|
-
langroid-0.
|
139
|
+
pyproject.toml,sha256=pK1C4vWrb5-NkREBDkFao-3Pi-xXJXMjh_wjedpjFAk,7063
|
140
|
+
langroid-0.8.0.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
141
|
+
langroid-0.8.0.dist-info/METADATA,sha256=i2_aKdjkgYcGAYMXEG2F3NC59yVI1w3uidn3edm5wAY,54518
|
142
|
+
langroid-0.8.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
143
|
+
langroid-0.8.0.dist-info/RECORD,,
|
pyproject.toml
CHANGED
File without changes
|
File without changes
|