Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
02a42a7f
Commit
02a42a7f
authored
Jul 09, 2023
by
John Wang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add multi function call agent
parent
96cd7966
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
191 additions
and
133 deletions
+191
-133
calc_token_mixin.py
api/core/agent/agent/calc_token_mixin.py
+3
-3
openai_function_call.py
api/core/agent/agent/openai_function_call.py
+10
-130
openai_function_call_summarize_mixin.py
api/core/agent/agent/openai_function_call_summarize_mixin.py
+131
-0
openai_multi_function_call.py
api/core/agent/agent/openai_multi_function_call.py
+47
-0
No files found.
api/core/agent/agent/calc_token_mixin.py
View file @
02a42a7f
...
...
@@ -8,11 +8,11 @@ from langchain.schema import BaseMessage
class
CalcTokenMixin
:
def
get_num_tokens_from_messages
(
self
,
llm
:
BaseLanguageModel
,
messages
:
List
[
BaseMessage
])
->
int
:
def
get_num_tokens_from_messages
(
self
,
llm
:
BaseLanguageModel
,
messages
:
List
[
BaseMessage
]
,
**
kwargs
)
->
int
:
llm
=
cast
(
ChatOpenAI
,
llm
)
return
llm
.
get_num_tokens_from_messages
(
messages
)
def
get_message_rest_tokens
(
self
,
llm
:
BaseLanguageModel
,
messages
:
List
[
BaseMessage
])
->
int
:
def
get_message_rest_tokens
(
self
,
llm
:
BaseLanguageModel
,
messages
:
List
[
BaseMessage
]
,
**
kwargs
)
->
int
:
"""
Got the rest tokens available for the model after excluding messages tokens and completion max tokens
...
...
@@ -23,7 +23,7 @@ class CalcTokenMixin:
llm
=
cast
(
ChatOpenAI
,
llm
)
llm_max_tokens
=
OpenAI
.
modelname_to_contextsize
(
llm
.
model_name
)
completion_max_tokens
=
llm
.
max_tokens
used_tokens
=
self
.
get_num_tokens_from_messages
(
llm
,
messages
)
used_tokens
=
self
.
get_num_tokens_from_messages
(
llm
,
messages
,
**
kwargs
)
rest_tokens
=
llm_max_tokens
-
completion_max_tokens
-
used_tokens
return
rest_tokens
...
...
api/core/agent/agent/openai_function_call.py
View file @
02a42a7f
from
typing
import
List
,
Tuple
,
Any
,
Union
,
cast
from
typing
import
List
,
Tuple
,
Any
,
Union
from
langchain.agents
import
OpenAIFunctionsAgent
from
langchain.agents.openai_functions_agent.base
import
_parse_ai_message
,
\
_format_intermediate_steps
from
langchain.base_language
import
BaseLanguageModel
from
langchain.callbacks.manager
import
Callbacks
from
langchain.chat_models
import
ChatOpenAI
from
langchain.chat_models.openai
import
_convert_message_to_dict
from
langchain.memory.summary
import
SummarizerMixin
from
langchain.schema
import
AgentAction
,
AgentFinish
,
BaseMessage
,
SystemMessage
,
HumanMessage
,
AIMessage
from
langchain.schema
import
AgentAction
,
AgentFinish
from
core.agent.agent.calc_token_mixin
import
CalcTokenMixin
,
ExceededLLMTokensLimitError
from
core.agent.agent.calc_token_mixin
import
ExceededLLMTokensLimitError
from
core.agent.agent.openai_function_call_summarize_mixin
import
OpenAIFunctionCallSummarizeMixin
class
AutoSummarizingOpenAIFunctionCallAgent
(
OpenAIFunctionsAgent
,
CalcTokenMixin
):
moving_summary_buffer
:
str
=
""
moving_summary_index
:
int
=
0
summary_llm
:
BaseLanguageModel
class
AutoSummarizingOpenAIFunctionCallAgent
(
OpenAIFunctionsAgent
,
OpenAIFunctionCallSummarizeMixin
):
def
plan
(
self
,
...
...
@@ -41,128 +35,14 @@ class AutoSummarizingOpenAIFunctionCallAgent(OpenAIFunctionsAgent, CalcTokenMixi
prompt
=
self
.
prompt
.
format_prompt
(
**
full_inputs
)
messages
=
prompt
.
to_messages
()
# calculate rest tokens and summarize previous function observation messages if rest_tokens < 0
rest_tokens
=
self
.
get_message_rest_tokens
(
self
.
llm
,
messages
)
rest_tokens
=
rest_tokens
-
20
# to deal with the inaccuracy of rest_tokens
if
rest_tokens
<
0
:
try
:
messages
=
self
.
summarize_messages
(
messages
)
except
ExceededLLMTokensLimitError
as
e
:
return
AgentFinish
(
return_values
=
{
"output"
:
str
(
e
)},
log
=
str
(
e
))
# summarize messages if rest_tokens < 0
try
:
messages
=
self
.
summarize_messages_if_needed
(
self
.
llm
,
messages
,
functions
=
self
.
functions
)
except
ExceededLLMTokensLimitError
as
e
:
return
AgentFinish
(
return_values
=
{
"output"
:
str
(
e
)},
log
=
str
(
e
))
predicted_message
=
self
.
llm
.
predict_messages
(
messages
,
functions
=
self
.
functions
,
callbacks
=
callbacks
)
agent_decision
=
_parse_ai_message
(
predicted_message
)
return
agent_decision
def
summarize_messages
(
self
,
messages
:
List
[
BaseMessage
])
->
List
[
BaseMessage
]:
system_message
=
None
human_message
=
None
should_summary_messages
=
[]
for
message
in
messages
:
if
isinstance
(
message
,
SystemMessage
):
system_message
=
message
elif
isinstance
(
message
,
HumanMessage
):
human_message
=
message
else
:
should_summary_messages
.
append
(
message
)
if
len
(
should_summary_messages
)
>
2
:
ai_message
=
should_summary_messages
[
-
2
]
function_message
=
should_summary_messages
[
-
1
]
should_summary_messages
=
should_summary_messages
[
self
.
moving_summary_index
:
-
2
]
self
.
moving_summary_index
=
len
(
should_summary_messages
)
else
:
error_msg
=
"Exceeded LLM tokens limit, stopped."
raise
ExceededLLMTokensLimitError
(
error_msg
)
new_messages
=
[
system_message
,
human_message
]
if
self
.
moving_summary_index
==
0
:
should_summary_messages
.
insert
(
0
,
human_message
)
summary_handler
=
SummarizerMixin
(
llm
=
self
.
summary_llm
)
self
.
moving_summary_buffer
=
summary_handler
.
predict_new_summary
(
messages
=
should_summary_messages
,
existing_summary
=
self
.
moving_summary_buffer
)
new_messages
.
append
(
AIMessage
(
content
=
self
.
moving_summary_buffer
))
new_messages
.
append
(
ai_message
)
new_messages
.
append
(
function_message
)
return
new_messages
def
get_num_tokens_from_messages
(
self
,
llm
:
BaseLanguageModel
,
messages
:
List
[
BaseMessage
])
->
int
:
"""Calculate num tokens for gpt-3.5-turbo and gpt-4 with tiktoken package.
Official documentation: https://github.com/openai/openai-cookbook/blob/
main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb"""
llm
=
cast
(
ChatOpenAI
,
llm
)
model
,
encoding
=
llm
.
_get_encoding_model
()
if
model
.
startswith
(
"gpt-3.5-turbo"
):
# every message follows <im_start>{role/name}\n{content}<im_end>\n
tokens_per_message
=
4
# if there's a name, the role is omitted
tokens_per_name
=
-
1
elif
model
.
startswith
(
"gpt-4"
):
tokens_per_message
=
3
tokens_per_name
=
1
else
:
raise
NotImplementedError
(
f
"get_num_tokens_from_messages() is not presently implemented "
f
"for model {model}."
"See https://github.com/openai/openai-python/blob/main/chatml.md for "
"information on how messages are converted to tokens."
)
num_tokens
=
0
for
m
in
messages
:
message
=
_convert_message_to_dict
(
m
)
num_tokens
+=
tokens_per_message
for
key
,
value
in
message
.
items
():
if
key
==
"function_call"
:
for
f_key
,
f_value
in
value
.
items
():
num_tokens
+=
len
(
encoding
.
encode
(
f_key
))
num_tokens
+=
len
(
encoding
.
encode
(
f_value
))
else
:
num_tokens
+=
len
(
encoding
.
encode
(
value
))
if
key
==
"name"
:
num_tokens
+=
tokens_per_name
# every reply is primed with <im_start>assistant
num_tokens
+=
3
if
self
.
functions
:
for
function
in
self
.
functions
:
num_tokens
+=
len
(
encoding
.
encode
(
'name'
))
num_tokens
+=
len
(
encoding
.
encode
(
function
.
get
(
"name"
)))
num_tokens
+=
len
(
encoding
.
encode
(
'description'
))
num_tokens
+=
len
(
encoding
.
encode
(
function
.
get
(
"description"
)))
parameters
=
function
.
get
(
"parameters"
)
num_tokens
+=
len
(
encoding
.
encode
(
'parameters'
))
if
'title'
in
parameters
:
num_tokens
+=
len
(
encoding
.
encode
(
'title'
))
num_tokens
+=
len
(
encoding
.
encode
(
parameters
.
get
(
"title"
)))
num_tokens
+=
len
(
encoding
.
encode
(
'type'
))
num_tokens
+=
len
(
encoding
.
encode
(
parameters
.
get
(
"type"
)))
if
'properties'
in
parameters
:
num_tokens
+=
len
(
encoding
.
encode
(
'properties'
))
for
key
,
value
in
parameters
.
get
(
'properties'
)
.
items
():
num_tokens
+=
len
(
encoding
.
encode
(
key
))
for
field_key
,
field_value
in
value
.
items
():
num_tokens
+=
len
(
encoding
.
encode
(
field_key
))
if
field_key
==
'enum'
:
for
enum_field
in
field_value
:
num_tokens
+=
3
num_tokens
+=
len
(
encoding
.
encode
(
enum_field
))
else
:
num_tokens
+=
len
(
encoding
.
encode
(
field_key
))
num_tokens
+=
len
(
encoding
.
encode
(
str
(
field_value
)))
if
'required'
in
parameters
:
num_tokens
+=
len
(
encoding
.
encode
(
'required'
))
for
required_field
in
parameters
[
'required'
]:
num_tokens
+=
3
num_tokens
+=
len
(
encoding
.
encode
(
required_field
))
return
num_tokens
api/core/agent/agent/openai_function_call_summarize_mixin.py
0 → 100644
View file @
02a42a7f
from
typing
import
cast
,
List
from
langchain.chat_models
import
ChatOpenAI
from
langchain.chat_models.openai
import
_convert_message_to_dict
from
langchain.memory.summary
import
SummarizerMixin
from
langchain.schema
import
SystemMessage
,
HumanMessage
,
BaseMessage
,
AIMessage
,
BaseLanguageModel
from
core.agent.agent.calc_token_mixin
import
ExceededLLMTokensLimitError
,
CalcTokenMixin
class
OpenAIFunctionCallSummarizeMixin
(
CalcTokenMixin
):
moving_summary_buffer
:
str
=
""
moving_summary_index
:
int
=
0
summary_llm
:
BaseLanguageModel
def
summarize_messages_if_needed
(
self
,
llm
:
BaseLanguageModel
,
messages
:
List
[
BaseMessage
],
**
kwargs
)
->
List
[
BaseMessage
]:
# calculate rest tokens and summarize previous function observation messages if rest_tokens < 0
rest_tokens
=
self
.
get_message_rest_tokens
(
llm
,
messages
,
**
kwargs
)
rest_tokens
=
rest_tokens
-
20
# to deal with the inaccuracy of rest_tokens
if
rest_tokens
>=
0
:
return
messages
system_message
=
None
human_message
=
None
should_summary_messages
=
[]
for
message
in
messages
:
if
isinstance
(
message
,
SystemMessage
):
system_message
=
message
elif
isinstance
(
message
,
HumanMessage
):
human_message
=
message
else
:
should_summary_messages
.
append
(
message
)
if
len
(
should_summary_messages
)
>
2
:
ai_message
=
should_summary_messages
[
-
2
]
function_message
=
should_summary_messages
[
-
1
]
should_summary_messages
=
should_summary_messages
[
self
.
moving_summary_index
:
-
2
]
self
.
moving_summary_index
=
len
(
should_summary_messages
)
else
:
error_msg
=
"Exceeded LLM tokens limit, stopped."
raise
ExceededLLMTokensLimitError
(
error_msg
)
new_messages
=
[
system_message
,
human_message
]
if
self
.
moving_summary_index
==
0
:
should_summary_messages
.
insert
(
0
,
human_message
)
summary_handler
=
SummarizerMixin
(
llm
=
self
.
summary_llm
)
self
.
moving_summary_buffer
=
summary_handler
.
predict_new_summary
(
messages
=
should_summary_messages
,
existing_summary
=
self
.
moving_summary_buffer
)
new_messages
.
append
(
AIMessage
(
content
=
self
.
moving_summary_buffer
))
new_messages
.
append
(
ai_message
)
new_messages
.
append
(
function_message
)
return
new_messages
def
get_num_tokens_from_messages
(
self
,
llm
:
BaseLanguageModel
,
messages
:
List
[
BaseMessage
],
**
kwargs
)
->
int
:
"""Calculate num tokens for gpt-3.5-turbo and gpt-4 with tiktoken package.
Official documentation: https://github.com/openai/openai-cookbook/blob/
main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb"""
llm
=
cast
(
ChatOpenAI
,
llm
)
model
,
encoding
=
llm
.
_get_encoding_model
()
if
model
.
startswith
(
"gpt-3.5-turbo"
):
# every message follows <im_start>{role/name}\n{content}<im_end>\n
tokens_per_message
=
4
# if there's a name, the role is omitted
tokens_per_name
=
-
1
elif
model
.
startswith
(
"gpt-4"
):
tokens_per_message
=
3
tokens_per_name
=
1
else
:
raise
NotImplementedError
(
f
"get_num_tokens_from_messages() is not presently implemented "
f
"for model {model}."
"See https://github.com/openai/openai-python/blob/main/chatml.md for "
"information on how messages are converted to tokens."
)
num_tokens
=
0
for
m
in
messages
:
message
=
_convert_message_to_dict
(
m
)
num_tokens
+=
tokens_per_message
for
key
,
value
in
message
.
items
():
if
key
==
"function_call"
:
for
f_key
,
f_value
in
value
.
items
():
num_tokens
+=
len
(
encoding
.
encode
(
f_key
))
num_tokens
+=
len
(
encoding
.
encode
(
f_value
))
else
:
num_tokens
+=
len
(
encoding
.
encode
(
value
))
if
key
==
"name"
:
num_tokens
+=
tokens_per_name
# every reply is primed with <im_start>assistant
num_tokens
+=
3
if
kwargs
.
get
(
'functions'
):
for
function
in
kwargs
.
get
(
'functions'
):
num_tokens
+=
len
(
encoding
.
encode
(
'name'
))
num_tokens
+=
len
(
encoding
.
encode
(
function
.
get
(
"name"
)))
num_tokens
+=
len
(
encoding
.
encode
(
'description'
))
num_tokens
+=
len
(
encoding
.
encode
(
function
.
get
(
"description"
)))
parameters
=
function
.
get
(
"parameters"
)
num_tokens
+=
len
(
encoding
.
encode
(
'parameters'
))
if
'title'
in
parameters
:
num_tokens
+=
len
(
encoding
.
encode
(
'title'
))
num_tokens
+=
len
(
encoding
.
encode
(
parameters
.
get
(
"title"
)))
num_tokens
+=
len
(
encoding
.
encode
(
'type'
))
num_tokens
+=
len
(
encoding
.
encode
(
parameters
.
get
(
"type"
)))
if
'properties'
in
parameters
:
num_tokens
+=
len
(
encoding
.
encode
(
'properties'
))
for
key
,
value
in
parameters
.
get
(
'properties'
)
.
items
():
num_tokens
+=
len
(
encoding
.
encode
(
key
))
for
field_key
,
field_value
in
value
.
items
():
num_tokens
+=
len
(
encoding
.
encode
(
field_key
))
if
field_key
==
'enum'
:
for
enum_field
in
field_value
:
num_tokens
+=
3
num_tokens
+=
len
(
encoding
.
encode
(
enum_field
))
else
:
num_tokens
+=
len
(
encoding
.
encode
(
field_key
))
num_tokens
+=
len
(
encoding
.
encode
(
str
(
field_value
)))
if
'required'
in
parameters
:
num_tokens
+=
len
(
encoding
.
encode
(
'required'
))
for
required_field
in
parameters
[
'required'
]:
num_tokens
+=
3
num_tokens
+=
len
(
encoding
.
encode
(
required_field
))
return
num_tokens
api/core/agent/agent/openai_multi_function_call.py
0 → 100644
View file @
02a42a7f
from
typing
import
List
,
Tuple
,
Any
,
Union
from
langchain.agents.openai_functions_multi_agent.base
import
OpenAIMultiFunctionsAgent
,
_format_intermediate_steps
,
\
_parse_ai_message
from
langchain.callbacks.manager
import
Callbacks
from
langchain.schema
import
AgentAction
,
AgentFinish
from
core.agent.agent.calc_token_mixin
import
ExceededLLMTokensLimitError
from
core.agent.agent.openai_function_call_summarize_mixin
import
OpenAIFunctionCallSummarizeMixin
class
AutoSummarizingOpenMultiAIFunctionCallAgent
(
OpenAIMultiFunctionsAgent
,
OpenAIFunctionCallSummarizeMixin
):
def
plan
(
self
,
intermediate_steps
:
List
[
Tuple
[
AgentAction
,
str
]],
callbacks
:
Callbacks
=
None
,
**
kwargs
:
Any
,
)
->
Union
[
AgentAction
,
AgentFinish
]:
"""Given input, decided what to do.
Args:
intermediate_steps: Steps the LLM has taken to date, along with observations
**kwargs: User inputs.
Returns:
Action specifying what tool to use.
"""
agent_scratchpad
=
_format_intermediate_steps
(
intermediate_steps
)
selected_inputs
=
{
k
:
kwargs
[
k
]
for
k
in
self
.
prompt
.
input_variables
if
k
!=
"agent_scratchpad"
}
full_inputs
=
dict
(
**
selected_inputs
,
agent_scratchpad
=
agent_scratchpad
)
prompt
=
self
.
prompt
.
format_prompt
(
**
full_inputs
)
messages
=
prompt
.
to_messages
()
# summarize messages if rest_tokens < 0
try
:
messages
=
self
.
summarize_messages_if_needed
(
self
.
llm
,
messages
,
functions
=
self
.
functions
)
except
ExceededLLMTokensLimitError
as
e
:
return
AgentFinish
(
return_values
=
{
"output"
:
str
(
e
)},
log
=
str
(
e
))
predicted_message
=
self
.
llm
.
predict_messages
(
messages
,
functions
=
self
.
functions
,
callbacks
=
callbacks
)
agent_decision
=
_parse_ai_message
(
predicted_message
)
return
agent_decision
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment