dm-aioaiagent 0.3.5__tar.gz → 0.4.0__tar.gz
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.
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/PKG-INFO +13 -23
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/README.md +12 -22
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent/__init__.py +0 -1
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent/ai_agent.py +72 -56
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent/async_ai_agent.py +37 -8
- dm_aioaiagent-0.4.0/dm_aioaiagent/types.py +30 -0
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent.egg-info/PKG-INFO +13 -23
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/setup.py +1 -1
- dm_aioaiagent-0.3.5/dm_aioaiagent/types.py +0 -41
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent/openai_image_message_content.py +0 -0
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent.egg-info/SOURCES.txt +0 -0
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent.egg-info/dependency_links.txt +0 -0
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent.egg-info/requires.txt +0 -0
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/dm_aioaiagent.egg-info/top_level.txt +0 -0
- {dm_aioaiagent-0.3.5 → dm_aioaiagent-0.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dm-aioaiagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: This is my custom aioaiagent client
|
|
5
5
|
Home-page: https://pypi.org/project/dm-aioaiagent
|
|
6
6
|
Author: dimka4621
|
|
@@ -35,7 +35,7 @@ Requires-Dist: langchain-openai~=0.2.0
|
|
|
35
35
|
|
|
36
36
|
Analogue to `DMAioAIAgent` is the synchronous client `DMAIAgent`.
|
|
37
37
|
|
|
38
|
-
### Use agent *with* inner memory
|
|
38
|
+
### Use agent *with* inner memory and run *single* message
|
|
39
39
|
|
|
40
40
|
By default, agent use inner memory to store the conversation history.
|
|
41
41
|
|
|
@@ -61,35 +61,24 @@ async def main():
|
|
|
61
61
|
# if you don't want to see the input and output messages from agent
|
|
62
62
|
# you can set `input_output_logging=False` init argument
|
|
63
63
|
|
|
64
|
-
# define the conversation message
|
|
65
|
-
input_messages = [
|
|
66
|
-
{"role": "user", "content": "Hello!"},
|
|
67
|
-
]
|
|
68
|
-
|
|
69
64
|
# call an agent
|
|
70
|
-
|
|
71
|
-
answer = await ai_agent.run(input_messages)
|
|
72
|
-
|
|
73
|
-
# define the next conversation message
|
|
74
|
-
input_messages = [
|
|
75
|
-
{"role": "user", "content": "I want to know the weather in Kyiv"}
|
|
76
|
-
]
|
|
65
|
+
answer = await ai_agent.run("Hello!")
|
|
77
66
|
|
|
78
67
|
# call an agent
|
|
79
|
-
answer = await ai_agent.run(
|
|
68
|
+
answer = await ai_agent.run("I want to know the weather in Kyiv")
|
|
80
69
|
|
|
81
70
|
# get full conversation history
|
|
82
|
-
conversation_history = ai_agent.
|
|
71
|
+
conversation_history = ai_agent.memory_messages
|
|
83
72
|
|
|
84
73
|
# clear conversation history
|
|
85
|
-
ai_agent.
|
|
74
|
+
ai_agent.clear_memory_messages()
|
|
86
75
|
|
|
87
76
|
|
|
88
77
|
if __name__ == "__main__":
|
|
89
78
|
asyncio.run(main())
|
|
90
79
|
```
|
|
91
80
|
|
|
92
|
-
### Use agent *without* inner memory
|
|
81
|
+
### Use agent *without* inner memory and run *multiple* messages
|
|
93
82
|
|
|
94
83
|
If you want to control the memory of the agent, you can disable it by setting `is_memory_enabled=False`
|
|
95
84
|
|
|
@@ -114,13 +103,13 @@ async def main():
|
|
|
114
103
|
# if you don't want to see the input and output messages from agent
|
|
115
104
|
# you can set input_output_logging=False
|
|
116
105
|
|
|
117
|
-
# define the conversation message
|
|
106
|
+
# define the conversation message(s)
|
|
118
107
|
messages = [
|
|
119
108
|
{"role": "user", "content": "Hello!"}
|
|
120
109
|
]
|
|
121
110
|
|
|
122
111
|
# call an agent
|
|
123
|
-
new_messages = await ai_agent.
|
|
112
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
124
113
|
|
|
125
114
|
# add new_messages to messages
|
|
126
115
|
messages.extend(new_messages)
|
|
@@ -131,7 +120,7 @@ async def main():
|
|
|
131
120
|
)
|
|
132
121
|
|
|
133
122
|
# call an agent
|
|
134
|
-
new_messages = await ai_agent.
|
|
123
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
135
124
|
|
|
136
125
|
|
|
137
126
|
if __name__ == "__main__":
|
|
@@ -153,14 +142,15 @@ def main():
|
|
|
153
142
|
img_content = OpenAIImageMessageContent(image_url="https://your.domain/image",
|
|
154
143
|
text="Hello, what is shown in the photo?")
|
|
155
144
|
|
|
156
|
-
# define the conversation
|
|
145
|
+
# define the conversation messages
|
|
157
146
|
messages = [
|
|
158
147
|
{"role": "user", "content": "Hello!"},
|
|
159
148
|
{"role": "user", "content": img_content},
|
|
160
149
|
]
|
|
161
150
|
|
|
162
151
|
# call an agent
|
|
163
|
-
|
|
152
|
+
new_messages = ai_agent.run_messages(messages)
|
|
153
|
+
answer = new_messages[-1].content
|
|
164
154
|
|
|
165
155
|
|
|
166
156
|
if __name__ == "__main__":
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
Analogue to `DMAioAIAgent` is the synchronous client `DMAIAgent`.
|
|
13
13
|
|
|
14
|
-
### Use agent *with* inner memory
|
|
14
|
+
### Use agent *with* inner memory and run *single* message
|
|
15
15
|
|
|
16
16
|
By default, agent use inner memory to store the conversation history.
|
|
17
17
|
|
|
@@ -37,35 +37,24 @@ async def main():
|
|
|
37
37
|
# if you don't want to see the input and output messages from agent
|
|
38
38
|
# you can set `input_output_logging=False` init argument
|
|
39
39
|
|
|
40
|
-
# define the conversation message
|
|
41
|
-
input_messages = [
|
|
42
|
-
{"role": "user", "content": "Hello!"},
|
|
43
|
-
]
|
|
44
|
-
|
|
45
40
|
# call an agent
|
|
46
|
-
|
|
47
|
-
answer = await ai_agent.run(input_messages)
|
|
48
|
-
|
|
49
|
-
# define the next conversation message
|
|
50
|
-
input_messages = [
|
|
51
|
-
{"role": "user", "content": "I want to know the weather in Kyiv"}
|
|
52
|
-
]
|
|
41
|
+
answer = await ai_agent.run("Hello!")
|
|
53
42
|
|
|
54
43
|
# call an agent
|
|
55
|
-
answer = await ai_agent.run(
|
|
44
|
+
answer = await ai_agent.run("I want to know the weather in Kyiv")
|
|
56
45
|
|
|
57
46
|
# get full conversation history
|
|
58
|
-
conversation_history = ai_agent.
|
|
47
|
+
conversation_history = ai_agent.memory_messages
|
|
59
48
|
|
|
60
49
|
# clear conversation history
|
|
61
|
-
ai_agent.
|
|
50
|
+
ai_agent.clear_memory_messages()
|
|
62
51
|
|
|
63
52
|
|
|
64
53
|
if __name__ == "__main__":
|
|
65
54
|
asyncio.run(main())
|
|
66
55
|
```
|
|
67
56
|
|
|
68
|
-
### Use agent *without* inner memory
|
|
57
|
+
### Use agent *without* inner memory and run *multiple* messages
|
|
69
58
|
|
|
70
59
|
If you want to control the memory of the agent, you can disable it by setting `is_memory_enabled=False`
|
|
71
60
|
|
|
@@ -90,13 +79,13 @@ async def main():
|
|
|
90
79
|
# if you don't want to see the input and output messages from agent
|
|
91
80
|
# you can set input_output_logging=False
|
|
92
81
|
|
|
93
|
-
# define the conversation message
|
|
82
|
+
# define the conversation message(s)
|
|
94
83
|
messages = [
|
|
95
84
|
{"role": "user", "content": "Hello!"}
|
|
96
85
|
]
|
|
97
86
|
|
|
98
87
|
# call an agent
|
|
99
|
-
new_messages = await ai_agent.
|
|
88
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
100
89
|
|
|
101
90
|
# add new_messages to messages
|
|
102
91
|
messages.extend(new_messages)
|
|
@@ -107,7 +96,7 @@ async def main():
|
|
|
107
96
|
)
|
|
108
97
|
|
|
109
98
|
# call an agent
|
|
110
|
-
new_messages = await ai_agent.
|
|
99
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
111
100
|
|
|
112
101
|
|
|
113
102
|
if __name__ == "__main__":
|
|
@@ -129,14 +118,15 @@ def main():
|
|
|
129
118
|
img_content = OpenAIImageMessageContent(image_url="https://your.domain/image",
|
|
130
119
|
text="Hello, what is shown in the photo?")
|
|
131
120
|
|
|
132
|
-
# define the conversation
|
|
121
|
+
# define the conversation messages
|
|
133
122
|
messages = [
|
|
134
123
|
{"role": "user", "content": "Hello!"},
|
|
135
124
|
{"role": "user", "content": img_content},
|
|
136
125
|
]
|
|
137
126
|
|
|
138
127
|
# call an agent
|
|
139
|
-
|
|
128
|
+
new_messages = ai_agent.run_messages(messages)
|
|
129
|
+
answer = new_messages[-1].content
|
|
140
130
|
|
|
141
131
|
|
|
142
132
|
if __name__ == "__main__":
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Any
|
|
2
4
|
from pydantic import SecretStr
|
|
3
5
|
from itertools import dropwhile
|
|
4
6
|
from threading import Thread
|
|
@@ -14,10 +16,7 @@ __all__ = ["DMAIAgent"]
|
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class DMAIAgent:
|
|
17
|
-
AGENT_NAME = "AIAgent"
|
|
18
19
|
MAX_MEMORY_MESSAGES = 20 # Only INT greater than 0
|
|
19
|
-
RESPONSE_IF_REQUEST_FAIL = "I can't provide a response right now. Please try again later."
|
|
20
|
-
RESPONSE_IF_INVALID_IMAGE = "The image is unavailable or the link is incorrect."
|
|
21
20
|
_ALLOWED_ROLES = ("user", "ai")
|
|
22
21
|
|
|
23
22
|
def __init__(
|
|
@@ -27,16 +26,17 @@ class DMAIAgent:
|
|
|
27
26
|
*,
|
|
28
27
|
model: str = "gpt-4o-mini",
|
|
29
28
|
temperature: int = 1,
|
|
30
|
-
|
|
29
|
+
parallel_tool_calls: bool = True,
|
|
30
|
+
agent_name: str = "AIAgent",
|
|
31
31
|
input_output_logging: bool = True,
|
|
32
32
|
is_memory_enabled: bool = True,
|
|
33
|
+
max_memory_messages: int = MAX_MEMORY_MESSAGES,
|
|
33
34
|
save_tools_responses_in_memory: bool = True,
|
|
34
|
-
max_memory_messages: int = None,
|
|
35
35
|
llm_provider_api_key: str = "",
|
|
36
|
-
response_if_request_fail: str =
|
|
37
|
-
response_if_invalid_image: str =
|
|
36
|
+
response_if_request_fail: str = "I can't provide a response right now. Please try again later.",
|
|
37
|
+
response_if_invalid_image: str = "The image is unavailable or the link is incorrect."
|
|
38
38
|
):
|
|
39
|
-
self._logger = DMLogger(agent_name
|
|
39
|
+
self._logger = DMLogger(agent_name)
|
|
40
40
|
self._input_output_logging = bool(input_output_logging)
|
|
41
41
|
|
|
42
42
|
self._system_message = str(system_message)
|
|
@@ -44,41 +44,60 @@ class DMAIAgent:
|
|
|
44
44
|
self._is_tools_exists = bool(tools)
|
|
45
45
|
self._model = str(model)
|
|
46
46
|
self._temperature = int(temperature)
|
|
47
|
+
self._parallel_tool_calls = bool(parallel_tool_calls)
|
|
47
48
|
self._llm_provider_api_key = str(llm_provider_api_key)
|
|
48
49
|
|
|
50
|
+
self._memory_messages = []
|
|
49
51
|
self._is_memory_enabled = bool(is_memory_enabled)
|
|
50
52
|
self._save_tools_responses_in_memory = bool(save_tools_responses_in_memory)
|
|
51
53
|
self._max_memory_messages = self._validate_max_memory_messages(max_memory_messages)
|
|
52
|
-
self._response_if_request_fail = str(response_if_request_fail
|
|
53
|
-
self._response_if_invalid_image = str(response_if_invalid_image
|
|
54
|
+
self._response_if_request_fail = str(response_if_request_fail)
|
|
55
|
+
self._response_if_invalid_image = str(response_if_invalid_image)
|
|
54
56
|
|
|
57
|
+
self._check_langsmith_envs()
|
|
55
58
|
self._init_agent()
|
|
56
59
|
self._init_graph()
|
|
57
60
|
|
|
58
|
-
def run(self,
|
|
59
|
-
|
|
60
|
-
return
|
|
61
|
+
def run(self, query: str, **kwargs) -> str:
|
|
62
|
+
new_messages = self.run_messages(messages=[{"role": "user", "content": query}], **kwargs)
|
|
63
|
+
return new_messages[-1].content
|
|
61
64
|
|
|
62
|
-
def
|
|
65
|
+
def run_messages(
|
|
63
66
|
self,
|
|
64
|
-
|
|
67
|
+
messages: InputMessagesType,
|
|
65
68
|
*,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
ls_metadata: dict[str, Any] = None,
|
|
70
|
+
ls_tags: list[str] = None,
|
|
71
|
+
ls_run_id: uuid.UUID = None,
|
|
72
|
+
ls_thread_id: uuid.UUID = None
|
|
73
|
+
) -> list[BaseMessage]:
|
|
74
|
+
if ls_metadata is None:
|
|
75
|
+
ls_metadata = {}
|
|
76
|
+
if isinstance(ls_run_id, uuid.UUID):
|
|
77
|
+
ls_run_id = ls_run_id
|
|
78
|
+
if isinstance(ls_thread_id, uuid.UUID):
|
|
79
|
+
ls_metadata["thread_id"] = ls_thread_id
|
|
80
|
+
|
|
81
|
+
config_data = {
|
|
82
|
+
"metadata": ls_metadata,
|
|
83
|
+
"tags": ls_tags,
|
|
84
|
+
"run_id": ls_run_id
|
|
85
|
+
}
|
|
86
|
+
state = self._graph.invoke(input={"messages": messages, "new_messages": []},
|
|
87
|
+
config={k: v for k, v in config_data.items() if v})
|
|
88
|
+
return state["new_messages"]
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def memory_messages(self) -> list[BaseMessage]:
|
|
92
|
+
return self._memory_messages
|
|
93
|
+
|
|
94
|
+
def clear_memory_messages(self) -> None:
|
|
95
|
+
self._memory_messages.clear()
|
|
78
96
|
|
|
79
97
|
def _prepare_messages_node(self, state: State) -> State:
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
messages = state["messages"] or [{"role": "user", "content": ""}]
|
|
99
|
+
state["messages"] = []
|
|
100
|
+
for item in messages:
|
|
82
101
|
if isinstance(item, dict):
|
|
83
102
|
role = item.get("role")
|
|
84
103
|
content = item.get("content")
|
|
@@ -88,35 +107,35 @@ class DMAIAgent:
|
|
|
88
107
|
MessageClass = AIMessage
|
|
89
108
|
else:
|
|
90
109
|
MessageClass = HumanMessage
|
|
91
|
-
state
|
|
110
|
+
state["messages"].append(MessageClass(content))
|
|
92
111
|
elif isinstance(item, BaseMessage):
|
|
93
|
-
state
|
|
112
|
+
state["messages"].append(item)
|
|
94
113
|
|
|
95
114
|
if self._input_output_logging:
|
|
96
|
-
|
|
97
|
-
self._logger.debug(f"Query:\n{state.messages[-1].content}", **log_kwargs)
|
|
115
|
+
self._logger.debug(f'Query:\n{state["messages"][-1].content}')
|
|
98
116
|
if self._is_memory_enabled:
|
|
99
|
-
state
|
|
117
|
+
state["messages"] = self._memory_messages + state["messages"]
|
|
100
118
|
return state
|
|
101
119
|
|
|
102
120
|
def _invoke_llm_node(self, state: State, second_attempt: bool = False) -> State:
|
|
103
121
|
self._logger.debug("Run node: Invoke LLM")
|
|
104
122
|
try:
|
|
105
|
-
ai_response = self._agent.invoke({"messages": state
|
|
123
|
+
ai_response = self._agent.invoke({"messages": state["messages"]})
|
|
106
124
|
except Exception as e:
|
|
107
125
|
self._logger.error(e)
|
|
108
126
|
if second_attempt:
|
|
109
127
|
response = self._response_if_invalid_image if "invalid_image_url" in str(e) else self._response_if_request_fail
|
|
110
|
-
state
|
|
128
|
+
state["messages"].append(AIMessage(content=response))
|
|
111
129
|
return state
|
|
112
130
|
return self._invoke_llm_node(state, second_attempt=True)
|
|
113
|
-
state
|
|
131
|
+
state["messages"].append(ai_response)
|
|
132
|
+
state["new_messages"].append(ai_response)
|
|
114
133
|
return state
|
|
115
134
|
|
|
116
135
|
def _execute_tool_node(self, state: State) -> State:
|
|
117
136
|
self._logger.debug("Run node: Execute tool")
|
|
118
137
|
threads = []
|
|
119
|
-
for tool_call in state
|
|
138
|
+
for tool_call in state["messages"][-1].tool_calls:
|
|
120
139
|
tool_id = tool_call["id"]
|
|
121
140
|
tool_name = tool_call["name"]
|
|
122
141
|
tool_args = tool_call["args"]
|
|
@@ -134,7 +153,8 @@ class DMAIAgent:
|
|
|
134
153
|
self._logger.debug(f"Tool response:\n{tool_response}", tool_id=tool_id)
|
|
135
154
|
|
|
136
155
|
tool_message = ToolMessage(content=str(tool_response), name=tool_name, tool_call_id=tool_id)
|
|
137
|
-
state
|
|
156
|
+
state["messages"].append(tool_message)
|
|
157
|
+
state["new_messages"].append(tool_message)
|
|
138
158
|
|
|
139
159
|
threads.append(Thread(target=tool_callback, daemon=True))
|
|
140
160
|
|
|
@@ -142,34 +162,27 @@ class DMAIAgent:
|
|
|
142
162
|
t.start()
|
|
143
163
|
for t in threads:
|
|
144
164
|
t.join()
|
|
145
|
-
|
|
146
165
|
return state
|
|
147
166
|
|
|
148
167
|
def _exit_node(self, state: State) -> State:
|
|
149
|
-
answer = state.messages[-1].content
|
|
150
168
|
if self._input_output_logging:
|
|
151
|
-
|
|
152
|
-
self._logger.debug(f"Answer:\n{answer}", **log_kwargs)
|
|
169
|
+
self._logger.debug(f'Answer:\n{state["messages"][-1].content}')
|
|
153
170
|
|
|
154
171
|
if self._is_memory_enabled:
|
|
155
|
-
|
|
156
|
-
messages_to_memory = state.messages[-self._max_memory_messages:]
|
|
172
|
+
messages_to_memory = state["messages"][-self._max_memory_messages:]
|
|
157
173
|
if self._save_tools_responses_in_memory:
|
|
158
174
|
# drop ToolsMessages from start of list
|
|
159
|
-
self.
|
|
175
|
+
self._memory_messages = list(dropwhile(lambda x: isinstance(x, ToolMessage), messages_to_memory))
|
|
160
176
|
else:
|
|
161
|
-
self.
|
|
177
|
+
self._memory_messages.clear()
|
|
162
178
|
for mes in messages_to_memory:
|
|
163
179
|
if isinstance(mes, ToolMessage) or (isinstance(mes, AIMessage) and mes.tool_calls):
|
|
164
180
|
continue
|
|
165
|
-
self.
|
|
166
|
-
state.response = answer
|
|
167
|
-
else:
|
|
168
|
-
state.response = state.messages[len(state.input_messages):]
|
|
181
|
+
self._memory_messages.append(mes)
|
|
169
182
|
return state
|
|
170
183
|
|
|
171
184
|
def _messages_router(self, state: State) -> str:
|
|
172
|
-
if self._is_tools_exists and state
|
|
185
|
+
if self._is_tools_exists and state["messages"][-1].tool_calls:
|
|
173
186
|
route = "execute_tool"
|
|
174
187
|
else:
|
|
175
188
|
route = "exit"
|
|
@@ -194,13 +207,12 @@ class DMAIAgent:
|
|
|
194
207
|
|
|
195
208
|
if self._is_tools_exists:
|
|
196
209
|
self._tool_map = {t.name: t for t in self._tools}
|
|
197
|
-
llm = llm.bind_tools(self._tools)
|
|
210
|
+
llm = llm.bind_tools(self._tools, parallel_tool_calls=self._parallel_tool_calls)
|
|
198
211
|
|
|
199
212
|
prompt = ChatPromptTemplate.from_messages([SystemMessage(content=self._system_message),
|
|
200
213
|
MessagesPlaceholder(variable_name="messages")])
|
|
201
214
|
|
|
202
215
|
self._agent = prompt | llm
|
|
203
|
-
self._memory = {}
|
|
204
216
|
|
|
205
217
|
def _init_graph(self) -> None:
|
|
206
218
|
workflow = StateGraph(State)
|
|
@@ -219,8 +231,12 @@ class DMAIAgent:
|
|
|
219
231
|
self._graph = workflow.compile()
|
|
220
232
|
|
|
221
233
|
@staticmethod
|
|
222
|
-
def
|
|
223
|
-
|
|
234
|
+
def _check_langsmith_envs() -> None:
|
|
235
|
+
if os.getenv("LANGCHAIN_API_KEY"):
|
|
236
|
+
if not os.getenv("LANGCHAIN_TRACING_V2"):
|
|
237
|
+
os.environ["LANGCHAIN_TRACING_V2"] = "true"
|
|
238
|
+
if not os.getenv("LANGCHAIN_ENDPOINT"):
|
|
239
|
+
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
|
|
224
240
|
|
|
225
241
|
@classmethod
|
|
226
242
|
def _validate_max_memory_messages(cls, max_messages_in_memory: int) -> int:
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import uuid
|
|
2
3
|
import asyncio
|
|
4
|
+
from typing import Any
|
|
3
5
|
from langchain_core.messages import AIMessage, ToolMessage
|
|
4
6
|
|
|
5
7
|
from .ai_agent import DMAIAgent
|
|
@@ -14,28 +16,54 @@ __all__ = ["DMAioAIAgent"]
|
|
|
14
16
|
class DMAioAIAgent(DMAIAgent):
|
|
15
17
|
agent_name = "AsyncAIAgent"
|
|
16
18
|
|
|
17
|
-
async def run(self,
|
|
18
|
-
|
|
19
|
-
return
|
|
19
|
+
async def run(self, query: str, **kwargs) -> str:
|
|
20
|
+
new_messages = await self.run_messages(messages=[{"role": "user", "content": query}], **kwargs)
|
|
21
|
+
return new_messages[-1].content
|
|
22
|
+
|
|
23
|
+
async def run_messages(
|
|
24
|
+
self,
|
|
25
|
+
messages: InputMessagesType,
|
|
26
|
+
*,
|
|
27
|
+
ls_metadata: dict[str, Any] = None,
|
|
28
|
+
ls_tags: list[str] = None,
|
|
29
|
+
ls_run_id: uuid.UUID = None,
|
|
30
|
+
ls_thread_id: uuid.UUID = None
|
|
31
|
+
) -> list[BaseMessage]:
|
|
32
|
+
if ls_metadata is None:
|
|
33
|
+
ls_metadata = {}
|
|
34
|
+
if isinstance(ls_run_id, uuid.UUID):
|
|
35
|
+
ls_run_id = ls_run_id
|
|
36
|
+
if isinstance(ls_thread_id, uuid.UUID):
|
|
37
|
+
ls_metadata["thread_id"] = ls_thread_id
|
|
38
|
+
|
|
39
|
+
config_data = {
|
|
40
|
+
"metadata": ls_metadata,
|
|
41
|
+
"tags": ls_tags,
|
|
42
|
+
"run_id": ls_run_id
|
|
43
|
+
}
|
|
44
|
+
state = await self._graph.ainvoke(input={"messages": messages, "new_messages": []},
|
|
45
|
+
config={k: v for k, v in config_data.items() if v})
|
|
46
|
+
return state["new_messages"]
|
|
20
47
|
|
|
21
48
|
async def _invoke_llm_node(self, state: State, second_attempt: bool = False) -> State:
|
|
22
49
|
self._logger.debug("Run node: Invoke LLM")
|
|
23
50
|
try:
|
|
24
|
-
ai_response = await self._agent.ainvoke({"messages": state
|
|
51
|
+
ai_response = await self._agent.ainvoke({"messages": state["messages"]})
|
|
25
52
|
except Exception as e:
|
|
26
53
|
self._logger.error(e)
|
|
27
54
|
if second_attempt:
|
|
28
55
|
response = self._response_if_invalid_image if "invalid_image_url" in str(e) else self._response_if_request_fail
|
|
29
|
-
state
|
|
56
|
+
state["messages"].append(AIMessage(content=response))
|
|
30
57
|
return state
|
|
31
58
|
return await self._invoke_llm_node(state, second_attempt=True)
|
|
32
|
-
state
|
|
59
|
+
state["messages"].append(ai_response)
|
|
60
|
+
state["new_messages"].append(ai_response)
|
|
33
61
|
return state
|
|
34
62
|
|
|
35
63
|
async def _execute_tool_node(self, state: State) -> State:
|
|
36
64
|
self._logger.debug("Run node: Execute tool")
|
|
37
65
|
tasks = []
|
|
38
|
-
for tool_call in state
|
|
66
|
+
for tool_call in state["messages"][-1].tool_calls:
|
|
39
67
|
tool_id = tool_call["id"]
|
|
40
68
|
tool_name = tool_call["name"]
|
|
41
69
|
tool_args = tool_call["args"]
|
|
@@ -53,7 +81,8 @@ class DMAioAIAgent(DMAIAgent):
|
|
|
53
81
|
self._logger.debug(f"Tool response:\n{tool_response}", tool_id=tool_id)
|
|
54
82
|
|
|
55
83
|
tool_message = ToolMessage(content=str(tool_response), name=tool_name, tool_call_id=tool_id)
|
|
56
|
-
state
|
|
84
|
+
state["messages"].append(tool_message)
|
|
85
|
+
state["new_messages"].append(tool_message)
|
|
57
86
|
|
|
58
87
|
tasks.append(asyncio.create_task(tool_callback()))
|
|
59
88
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Literal, Union
|
|
2
|
+
from typing_extensions import TypedDict
|
|
3
|
+
from langchain_core.messages import BaseMessage
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ImageMessageTextMessage(TypedDict):
|
|
7
|
+
type: Literal['text']
|
|
8
|
+
text: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ImageMessageImageItem(TypedDict):
|
|
12
|
+
type: Literal['image_url']
|
|
13
|
+
image_url: dict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ImageMessage(TypedDict):
|
|
17
|
+
role: Literal["user"]
|
|
18
|
+
content: list[Union[ImageMessageTextMessage, ImageMessageImageItem]]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TextMessage(TypedDict):
|
|
22
|
+
role: Literal["user", "ai"]
|
|
23
|
+
content: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
InputMessagesType = list[Union[TextMessage, ImageMessage, BaseMessage]]
|
|
27
|
+
|
|
28
|
+
class State(TypedDict):
|
|
29
|
+
messages: InputMessagesType
|
|
30
|
+
new_messages: list[BaseMessage]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dm-aioaiagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: This is my custom aioaiagent client
|
|
5
5
|
Home-page: https://pypi.org/project/dm-aioaiagent
|
|
6
6
|
Author: dimka4621
|
|
@@ -35,7 +35,7 @@ Requires-Dist: langchain-openai~=0.2.0
|
|
|
35
35
|
|
|
36
36
|
Analogue to `DMAioAIAgent` is the synchronous client `DMAIAgent`.
|
|
37
37
|
|
|
38
|
-
### Use agent *with* inner memory
|
|
38
|
+
### Use agent *with* inner memory and run *single* message
|
|
39
39
|
|
|
40
40
|
By default, agent use inner memory to store the conversation history.
|
|
41
41
|
|
|
@@ -61,35 +61,24 @@ async def main():
|
|
|
61
61
|
# if you don't want to see the input and output messages from agent
|
|
62
62
|
# you can set `input_output_logging=False` init argument
|
|
63
63
|
|
|
64
|
-
# define the conversation message
|
|
65
|
-
input_messages = [
|
|
66
|
-
{"role": "user", "content": "Hello!"},
|
|
67
|
-
]
|
|
68
|
-
|
|
69
64
|
# call an agent
|
|
70
|
-
|
|
71
|
-
answer = await ai_agent.run(input_messages)
|
|
72
|
-
|
|
73
|
-
# define the next conversation message
|
|
74
|
-
input_messages = [
|
|
75
|
-
{"role": "user", "content": "I want to know the weather in Kyiv"}
|
|
76
|
-
]
|
|
65
|
+
answer = await ai_agent.run("Hello!")
|
|
77
66
|
|
|
78
67
|
# call an agent
|
|
79
|
-
answer = await ai_agent.run(
|
|
68
|
+
answer = await ai_agent.run("I want to know the weather in Kyiv")
|
|
80
69
|
|
|
81
70
|
# get full conversation history
|
|
82
|
-
conversation_history = ai_agent.
|
|
71
|
+
conversation_history = ai_agent.memory_messages
|
|
83
72
|
|
|
84
73
|
# clear conversation history
|
|
85
|
-
ai_agent.
|
|
74
|
+
ai_agent.clear_memory_messages()
|
|
86
75
|
|
|
87
76
|
|
|
88
77
|
if __name__ == "__main__":
|
|
89
78
|
asyncio.run(main())
|
|
90
79
|
```
|
|
91
80
|
|
|
92
|
-
### Use agent *without* inner memory
|
|
81
|
+
### Use agent *without* inner memory and run *multiple* messages
|
|
93
82
|
|
|
94
83
|
If you want to control the memory of the agent, you can disable it by setting `is_memory_enabled=False`
|
|
95
84
|
|
|
@@ -114,13 +103,13 @@ async def main():
|
|
|
114
103
|
# if you don't want to see the input and output messages from agent
|
|
115
104
|
# you can set input_output_logging=False
|
|
116
105
|
|
|
117
|
-
# define the conversation message
|
|
106
|
+
# define the conversation message(s)
|
|
118
107
|
messages = [
|
|
119
108
|
{"role": "user", "content": "Hello!"}
|
|
120
109
|
]
|
|
121
110
|
|
|
122
111
|
# call an agent
|
|
123
|
-
new_messages = await ai_agent.
|
|
112
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
124
113
|
|
|
125
114
|
# add new_messages to messages
|
|
126
115
|
messages.extend(new_messages)
|
|
@@ -131,7 +120,7 @@ async def main():
|
|
|
131
120
|
)
|
|
132
121
|
|
|
133
122
|
# call an agent
|
|
134
|
-
new_messages = await ai_agent.
|
|
123
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
135
124
|
|
|
136
125
|
|
|
137
126
|
if __name__ == "__main__":
|
|
@@ -153,14 +142,15 @@ def main():
|
|
|
153
142
|
img_content = OpenAIImageMessageContent(image_url="https://your.domain/image",
|
|
154
143
|
text="Hello, what is shown in the photo?")
|
|
155
144
|
|
|
156
|
-
# define the conversation
|
|
145
|
+
# define the conversation messages
|
|
157
146
|
messages = [
|
|
158
147
|
{"role": "user", "content": "Hello!"},
|
|
159
148
|
{"role": "user", "content": img_content},
|
|
160
149
|
]
|
|
161
150
|
|
|
162
151
|
# call an agent
|
|
163
|
-
|
|
152
|
+
new_messages = ai_agent.run_messages(messages)
|
|
153
|
+
answer = new_messages[-1].content
|
|
164
154
|
|
|
165
155
|
|
|
166
156
|
if __name__ == "__main__":
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from typing import Optional, Literal, Union
|
|
2
|
-
from typing_extensions import TypedDict
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
from langchain_core.messages import BaseMessage
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ImageMessageTextMessage(TypedDict):
|
|
8
|
-
type: Literal['text']
|
|
9
|
-
text: str
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ImageMessageImageItem(TypedDict):
|
|
13
|
-
type: Literal['image_url']
|
|
14
|
-
image_url: dict
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
ImageMessageContent = list[Union[ImageMessageTextMessage, ImageMessageImageItem]]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ImageMessage(TypedDict):
|
|
21
|
-
role: Literal["user"]
|
|
22
|
-
content: ImageMessageContent
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class TextMessage(TypedDict):
|
|
26
|
-
role: Literal["user", "ai"]
|
|
27
|
-
content: str
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Message = Union[TextMessage, ImageMessage]
|
|
31
|
-
|
|
32
|
-
InputMessagesType = list[Union[Message, BaseMessage]]
|
|
33
|
-
|
|
34
|
-
ResponseType = Union[str, list[BaseMessage]]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class State(BaseModel):
|
|
38
|
-
input_messages: InputMessagesType
|
|
39
|
-
memory_id: Union[str, int, None] = Field(default=0)
|
|
40
|
-
messages: Optional[list[BaseMessage]] = Field(default_factory=list)
|
|
41
|
-
response: ResponseType = Field(default="")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|