dm-aioaiagent 0.3.6__tar.gz → 0.4.1__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.6 → dm_aioaiagent-0.4.1}/PKG-INFO +27 -24
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/README.md +12 -22
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent/__init__.py +0 -1
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent/ai_agent.py +69 -55
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent/async_ai_agent.py +36 -9
- dm_aioaiagent-0.4.1/dm_aioaiagent/types.py +30 -0
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent.egg-info/PKG-INFO +27 -24
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent.egg-info/requires.txt +2 -0
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/setup.py +3 -1
- dm_aioaiagent-0.3.6/dm_aioaiagent/types.py +0 -41
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent/openai_image_message_content.py +0 -0
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent.egg-info/SOURCES.txt +0 -0
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent.egg-info/dependency_links.txt +0 -0
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/dm_aioaiagent.egg-info/top_level.txt +0 -0
- {dm_aioaiagent-0.3.6 → dm_aioaiagent-0.4.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: dm-aioaiagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: This is my custom aioaiagent client
|
|
5
5
|
Home-page: https://pypi.org/project/dm-aioaiagent
|
|
6
6
|
Author: dimka4621
|
|
@@ -18,9 +18,22 @@ Requires-Dist: pydantic<3.0.0,>=2.9.2
|
|
|
18
18
|
Requires-Dist: langchain~=0.3.0
|
|
19
19
|
Requires-Dist: langchain-core~=0.3.5
|
|
20
20
|
Requires-Dist: langgraph~=0.2.23
|
|
21
|
+
Requires-Dist: langsmith~=0.1.144
|
|
21
22
|
Requires-Dist: grandalf>=0.8
|
|
22
23
|
Requires-Dist: langchain-community~=0.3.0
|
|
23
24
|
Requires-Dist: langchain-openai~=0.2.0
|
|
25
|
+
Requires-Dist: langchain-anthropic~=0.3.0
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: keywords
|
|
33
|
+
Dynamic: project-url
|
|
34
|
+
Dynamic: requires-dist
|
|
35
|
+
Dynamic: requires-python
|
|
36
|
+
Dynamic: summary
|
|
24
37
|
|
|
25
38
|
# DM-aioaiagent
|
|
26
39
|
|
|
@@ -35,7 +48,7 @@ Requires-Dist: langchain-openai~=0.2.0
|
|
|
35
48
|
|
|
36
49
|
Analogue to `DMAioAIAgent` is the synchronous client `DMAIAgent`.
|
|
37
50
|
|
|
38
|
-
### Use agent *with* inner memory
|
|
51
|
+
### Use agent *with* inner memory and run *single* message
|
|
39
52
|
|
|
40
53
|
By default, agent use inner memory to store the conversation history.
|
|
41
54
|
|
|
@@ -61,35 +74,24 @@ async def main():
|
|
|
61
74
|
# if you don't want to see the input and output messages from agent
|
|
62
75
|
# you can set `input_output_logging=False` init argument
|
|
63
76
|
|
|
64
|
-
# define the conversation message
|
|
65
|
-
input_messages = [
|
|
66
|
-
{"role": "user", "content": "Hello!"},
|
|
67
|
-
]
|
|
68
|
-
|
|
69
77
|
# 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
|
-
]
|
|
78
|
+
answer = await ai_agent.run("Hello!")
|
|
77
79
|
|
|
78
80
|
# call an agent
|
|
79
|
-
answer = await ai_agent.run(
|
|
81
|
+
answer = await ai_agent.run("I want to know the weather in Kyiv")
|
|
80
82
|
|
|
81
83
|
# get full conversation history
|
|
82
|
-
conversation_history = ai_agent.
|
|
84
|
+
conversation_history = ai_agent.memory_messages
|
|
83
85
|
|
|
84
86
|
# clear conversation history
|
|
85
|
-
ai_agent.
|
|
87
|
+
ai_agent.clear_memory_messages()
|
|
86
88
|
|
|
87
89
|
|
|
88
90
|
if __name__ == "__main__":
|
|
89
91
|
asyncio.run(main())
|
|
90
92
|
```
|
|
91
93
|
|
|
92
|
-
### Use agent *without* inner memory
|
|
94
|
+
### Use agent *without* inner memory and run *multiple* messages
|
|
93
95
|
|
|
94
96
|
If you want to control the memory of the agent, you can disable it by setting `is_memory_enabled=False`
|
|
95
97
|
|
|
@@ -114,13 +116,13 @@ async def main():
|
|
|
114
116
|
# if you don't want to see the input and output messages from agent
|
|
115
117
|
# you can set input_output_logging=False
|
|
116
118
|
|
|
117
|
-
# define the conversation message
|
|
119
|
+
# define the conversation message(s)
|
|
118
120
|
messages = [
|
|
119
121
|
{"role": "user", "content": "Hello!"}
|
|
120
122
|
]
|
|
121
123
|
|
|
122
124
|
# call an agent
|
|
123
|
-
new_messages = await ai_agent.
|
|
125
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
124
126
|
|
|
125
127
|
# add new_messages to messages
|
|
126
128
|
messages.extend(new_messages)
|
|
@@ -131,7 +133,7 @@ async def main():
|
|
|
131
133
|
)
|
|
132
134
|
|
|
133
135
|
# call an agent
|
|
134
|
-
new_messages = await ai_agent.
|
|
136
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
135
137
|
|
|
136
138
|
|
|
137
139
|
if __name__ == "__main__":
|
|
@@ -153,14 +155,15 @@ def main():
|
|
|
153
155
|
img_content = OpenAIImageMessageContent(image_url="https://your.domain/image",
|
|
154
156
|
text="Hello, what is shown in the photo?")
|
|
155
157
|
|
|
156
|
-
# define the conversation
|
|
158
|
+
# define the conversation messages
|
|
157
159
|
messages = [
|
|
158
160
|
{"role": "user", "content": "Hello!"},
|
|
159
161
|
{"role": "user", "content": img_content},
|
|
160
162
|
]
|
|
161
163
|
|
|
162
164
|
# call an agent
|
|
163
|
-
|
|
165
|
+
new_messages = ai_agent.run_messages(messages)
|
|
166
|
+
answer = new_messages[-1].content
|
|
164
167
|
|
|
165
168
|
|
|
166
169
|
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__(
|
|
@@ -28,16 +27,16 @@ class DMAIAgent:
|
|
|
28
27
|
model: str = "gpt-4o-mini",
|
|
29
28
|
temperature: int = 1,
|
|
30
29
|
parallel_tool_calls: bool = True,
|
|
31
|
-
agent_name: str =
|
|
30
|
+
agent_name: str = "AIAgent",
|
|
32
31
|
input_output_logging: bool = True,
|
|
33
32
|
is_memory_enabled: bool = True,
|
|
33
|
+
max_memory_messages: int = MAX_MEMORY_MESSAGES,
|
|
34
34
|
save_tools_responses_in_memory: bool = True,
|
|
35
|
-
max_memory_messages: int = None,
|
|
36
35
|
llm_provider_api_key: str = "",
|
|
37
|
-
response_if_request_fail: str =
|
|
38
|
-
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."
|
|
39
38
|
):
|
|
40
|
-
self._logger = DMLogger(agent_name
|
|
39
|
+
self._logger = DMLogger(agent_name)
|
|
41
40
|
self._input_output_logging = bool(input_output_logging)
|
|
42
41
|
|
|
43
42
|
self._system_message = str(system_message)
|
|
@@ -48,39 +47,57 @@ class DMAIAgent:
|
|
|
48
47
|
self._parallel_tool_calls = bool(parallel_tool_calls)
|
|
49
48
|
self._llm_provider_api_key = str(llm_provider_api_key)
|
|
50
49
|
|
|
50
|
+
self._memory_messages = []
|
|
51
51
|
self._is_memory_enabled = bool(is_memory_enabled)
|
|
52
52
|
self._save_tools_responses_in_memory = bool(save_tools_responses_in_memory)
|
|
53
53
|
self._max_memory_messages = self._validate_max_memory_messages(max_memory_messages)
|
|
54
|
-
self._response_if_request_fail = str(response_if_request_fail
|
|
55
|
-
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)
|
|
56
56
|
|
|
57
|
+
self._check_langsmith_envs()
|
|
57
58
|
self._init_agent()
|
|
58
59
|
self._init_graph()
|
|
59
60
|
|
|
60
|
-
def run(self,
|
|
61
|
-
|
|
62
|
-
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
|
|
63
64
|
|
|
64
|
-
def
|
|
65
|
+
def run_messages(
|
|
65
66
|
self,
|
|
66
|
-
|
|
67
|
+
messages: InputMessagesType,
|
|
67
68
|
*,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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()
|
|
80
96
|
|
|
81
97
|
def _prepare_messages_node(self, state: State) -> State:
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
messages = state["messages"] or [{"role": "user", "content": ""}]
|
|
99
|
+
state["messages"] = []
|
|
100
|
+
for item in messages:
|
|
84
101
|
if isinstance(item, dict):
|
|
85
102
|
role = item.get("role")
|
|
86
103
|
content = item.get("content")
|
|
@@ -90,35 +107,35 @@ class DMAIAgent:
|
|
|
90
107
|
MessageClass = AIMessage
|
|
91
108
|
else:
|
|
92
109
|
MessageClass = HumanMessage
|
|
93
|
-
state
|
|
110
|
+
state["messages"].append(MessageClass(content))
|
|
94
111
|
elif isinstance(item, BaseMessage):
|
|
95
|
-
state
|
|
112
|
+
state["messages"].append(item)
|
|
96
113
|
|
|
97
114
|
if self._input_output_logging:
|
|
98
|
-
|
|
99
|
-
self._logger.debug(f"Query:\n{state.messages[-1].content}", **log_kwargs)
|
|
115
|
+
self._logger.debug(f'Query:\n{state["messages"][-1].content}')
|
|
100
116
|
if self._is_memory_enabled:
|
|
101
|
-
state
|
|
117
|
+
state["messages"] = self._memory_messages + state["messages"]
|
|
102
118
|
return state
|
|
103
119
|
|
|
104
120
|
def _invoke_llm_node(self, state: State, second_attempt: bool = False) -> State:
|
|
105
121
|
self._logger.debug("Run node: Invoke LLM")
|
|
106
122
|
try:
|
|
107
|
-
ai_response = self._agent.invoke({"messages": state
|
|
123
|
+
ai_response = self._agent.invoke({"messages": state["messages"]})
|
|
108
124
|
except Exception as e:
|
|
109
125
|
self._logger.error(e)
|
|
110
126
|
if second_attempt:
|
|
111
127
|
response = self._response_if_invalid_image if "invalid_image_url" in str(e) else self._response_if_request_fail
|
|
112
|
-
state
|
|
128
|
+
state["messages"].append(AIMessage(content=response))
|
|
113
129
|
return state
|
|
114
130
|
return self._invoke_llm_node(state, second_attempt=True)
|
|
115
|
-
state
|
|
131
|
+
state["messages"].append(ai_response)
|
|
132
|
+
state["new_messages"].append(ai_response)
|
|
116
133
|
return state
|
|
117
134
|
|
|
118
135
|
def _execute_tool_node(self, state: State) -> State:
|
|
119
136
|
self._logger.debug("Run node: Execute tool")
|
|
120
137
|
threads = []
|
|
121
|
-
for tool_call in state
|
|
138
|
+
for tool_call in state["messages"][-1].tool_calls:
|
|
122
139
|
tool_id = tool_call["id"]
|
|
123
140
|
tool_name = tool_call["name"]
|
|
124
141
|
tool_args = tool_call["args"]
|
|
@@ -136,7 +153,8 @@ class DMAIAgent:
|
|
|
136
153
|
self._logger.debug(f"Tool response:\n{tool_response}", tool_id=tool_id)
|
|
137
154
|
|
|
138
155
|
tool_message = ToolMessage(content=str(tool_response), name=tool_name, tool_call_id=tool_id)
|
|
139
|
-
state
|
|
156
|
+
state["messages"].append(tool_message)
|
|
157
|
+
state["new_messages"].append(tool_message)
|
|
140
158
|
|
|
141
159
|
threads.append(Thread(target=tool_callback, daemon=True))
|
|
142
160
|
|
|
@@ -144,34 +162,27 @@ class DMAIAgent:
|
|
|
144
162
|
t.start()
|
|
145
163
|
for t in threads:
|
|
146
164
|
t.join()
|
|
147
|
-
|
|
148
165
|
return state
|
|
149
166
|
|
|
150
167
|
def _exit_node(self, state: State) -> State:
|
|
151
|
-
answer = state.messages[-1].content
|
|
152
168
|
if self._input_output_logging:
|
|
153
|
-
|
|
154
|
-
self._logger.debug(f"Answer:\n{answer}", **log_kwargs)
|
|
169
|
+
self._logger.debug(f'Answer:\n{state["messages"][-1].content}')
|
|
155
170
|
|
|
156
171
|
if self._is_memory_enabled:
|
|
157
|
-
|
|
158
|
-
messages_to_memory = state.messages[-self._max_memory_messages:]
|
|
172
|
+
messages_to_memory = state["messages"][-self._max_memory_messages:]
|
|
159
173
|
if self._save_tools_responses_in_memory:
|
|
160
174
|
# drop ToolsMessages from start of list
|
|
161
|
-
self.
|
|
175
|
+
self._memory_messages = list(dropwhile(lambda x: isinstance(x, ToolMessage), messages_to_memory))
|
|
162
176
|
else:
|
|
163
|
-
self.
|
|
177
|
+
self._memory_messages.clear()
|
|
164
178
|
for mes in messages_to_memory:
|
|
165
179
|
if isinstance(mes, ToolMessage) or (isinstance(mes, AIMessage) and mes.tool_calls):
|
|
166
180
|
continue
|
|
167
|
-
self.
|
|
168
|
-
state.response = answer
|
|
169
|
-
else:
|
|
170
|
-
state.response = state.messages[len(state.input_messages):]
|
|
181
|
+
self._memory_messages.append(mes)
|
|
171
182
|
return state
|
|
172
183
|
|
|
173
184
|
def _messages_router(self, state: State) -> str:
|
|
174
|
-
if self._is_tools_exists and state
|
|
185
|
+
if self._is_tools_exists and state["messages"][-1].tool_calls:
|
|
175
186
|
route = "execute_tool"
|
|
176
187
|
else:
|
|
177
188
|
route = "exit"
|
|
@@ -202,7 +213,6 @@ class DMAIAgent:
|
|
|
202
213
|
MessagesPlaceholder(variable_name="messages")])
|
|
203
214
|
|
|
204
215
|
self._agent = prompt | llm
|
|
205
|
-
self._memory = {}
|
|
206
216
|
|
|
207
217
|
def _init_graph(self) -> None:
|
|
208
218
|
workflow = StateGraph(State)
|
|
@@ -221,8 +231,12 @@ class DMAIAgent:
|
|
|
221
231
|
self._graph = workflow.compile()
|
|
222
232
|
|
|
223
233
|
@staticmethod
|
|
224
|
-
def
|
|
225
|
-
|
|
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"
|
|
226
240
|
|
|
227
241
|
@classmethod
|
|
228
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
|
|
@@ -12,30 +14,54 @@ __all__ = ["DMAioAIAgent"]
|
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class DMAioAIAgent(DMAIAgent):
|
|
15
|
-
|
|
17
|
+
async def run(self, query: str, **kwargs) -> str:
|
|
18
|
+
new_messages = await self.run_messages(messages=[{"role": "user", "content": query}], **kwargs)
|
|
19
|
+
return new_messages[-1].content
|
|
16
20
|
|
|
17
|
-
async def
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
async def run_messages(
|
|
22
|
+
self,
|
|
23
|
+
messages: InputMessagesType,
|
|
24
|
+
*,
|
|
25
|
+
ls_metadata: dict[str, Any] = None,
|
|
26
|
+
ls_tags: list[str] = None,
|
|
27
|
+
ls_run_id: uuid.UUID = None,
|
|
28
|
+
ls_thread_id: uuid.UUID = None
|
|
29
|
+
) -> list[BaseMessage]:
|
|
30
|
+
if ls_metadata is None:
|
|
31
|
+
ls_metadata = {}
|
|
32
|
+
if isinstance(ls_run_id, uuid.UUID):
|
|
33
|
+
ls_run_id = ls_run_id
|
|
34
|
+
if isinstance(ls_thread_id, uuid.UUID):
|
|
35
|
+
ls_metadata["thread_id"] = ls_thread_id
|
|
36
|
+
|
|
37
|
+
config_data = {
|
|
38
|
+
"metadata": ls_metadata,
|
|
39
|
+
"tags": ls_tags,
|
|
40
|
+
"run_id": ls_run_id
|
|
41
|
+
}
|
|
42
|
+
state = await self._graph.ainvoke(input={"messages": messages, "new_messages": []},
|
|
43
|
+
config={k: v for k, v in config_data.items() if v})
|
|
44
|
+
return state["new_messages"]
|
|
20
45
|
|
|
21
46
|
async def _invoke_llm_node(self, state: State, second_attempt: bool = False) -> State:
|
|
22
47
|
self._logger.debug("Run node: Invoke LLM")
|
|
23
48
|
try:
|
|
24
|
-
ai_response = await self._agent.ainvoke({"messages": state
|
|
49
|
+
ai_response = await self._agent.ainvoke({"messages": state["messages"]})
|
|
25
50
|
except Exception as e:
|
|
26
51
|
self._logger.error(e)
|
|
27
52
|
if second_attempt:
|
|
28
53
|
response = self._response_if_invalid_image if "invalid_image_url" in str(e) else self._response_if_request_fail
|
|
29
|
-
state
|
|
54
|
+
state["messages"].append(AIMessage(content=response))
|
|
30
55
|
return state
|
|
31
56
|
return await self._invoke_llm_node(state, second_attempt=True)
|
|
32
|
-
state
|
|
57
|
+
state["messages"].append(ai_response)
|
|
58
|
+
state["new_messages"].append(ai_response)
|
|
33
59
|
return state
|
|
34
60
|
|
|
35
61
|
async def _execute_tool_node(self, state: State) -> State:
|
|
36
62
|
self._logger.debug("Run node: Execute tool")
|
|
37
63
|
tasks = []
|
|
38
|
-
for tool_call in state
|
|
64
|
+
for tool_call in state["messages"][-1].tool_calls:
|
|
39
65
|
tool_id = tool_call["id"]
|
|
40
66
|
tool_name = tool_call["name"]
|
|
41
67
|
tool_args = tool_call["args"]
|
|
@@ -53,7 +79,8 @@ class DMAioAIAgent(DMAIAgent):
|
|
|
53
79
|
self._logger.debug(f"Tool response:\n{tool_response}", tool_id=tool_id)
|
|
54
80
|
|
|
55
81
|
tool_message = ToolMessage(content=str(tool_response), name=tool_name, tool_call_id=tool_id)
|
|
56
|
-
state
|
|
82
|
+
state["messages"].append(tool_message)
|
|
83
|
+
state["new_messages"].append(tool_message)
|
|
57
84
|
|
|
58
85
|
tasks.append(asyncio.create_task(tool_callback()))
|
|
59
86
|
|
|
@@ -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
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: dm-aioaiagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: This is my custom aioaiagent client
|
|
5
5
|
Home-page: https://pypi.org/project/dm-aioaiagent
|
|
6
6
|
Author: dimka4621
|
|
@@ -18,9 +18,22 @@ Requires-Dist: pydantic<3.0.0,>=2.9.2
|
|
|
18
18
|
Requires-Dist: langchain~=0.3.0
|
|
19
19
|
Requires-Dist: langchain-core~=0.3.5
|
|
20
20
|
Requires-Dist: langgraph~=0.2.23
|
|
21
|
+
Requires-Dist: langsmith~=0.1.144
|
|
21
22
|
Requires-Dist: grandalf>=0.8
|
|
22
23
|
Requires-Dist: langchain-community~=0.3.0
|
|
23
24
|
Requires-Dist: langchain-openai~=0.2.0
|
|
25
|
+
Requires-Dist: langchain-anthropic~=0.3.0
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: keywords
|
|
33
|
+
Dynamic: project-url
|
|
34
|
+
Dynamic: requires-dist
|
|
35
|
+
Dynamic: requires-python
|
|
36
|
+
Dynamic: summary
|
|
24
37
|
|
|
25
38
|
# DM-aioaiagent
|
|
26
39
|
|
|
@@ -35,7 +48,7 @@ Requires-Dist: langchain-openai~=0.2.0
|
|
|
35
48
|
|
|
36
49
|
Analogue to `DMAioAIAgent` is the synchronous client `DMAIAgent`.
|
|
37
50
|
|
|
38
|
-
### Use agent *with* inner memory
|
|
51
|
+
### Use agent *with* inner memory and run *single* message
|
|
39
52
|
|
|
40
53
|
By default, agent use inner memory to store the conversation history.
|
|
41
54
|
|
|
@@ -61,35 +74,24 @@ async def main():
|
|
|
61
74
|
# if you don't want to see the input and output messages from agent
|
|
62
75
|
# you can set `input_output_logging=False` init argument
|
|
63
76
|
|
|
64
|
-
# define the conversation message
|
|
65
|
-
input_messages = [
|
|
66
|
-
{"role": "user", "content": "Hello!"},
|
|
67
|
-
]
|
|
68
|
-
|
|
69
77
|
# 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
|
-
]
|
|
78
|
+
answer = await ai_agent.run("Hello!")
|
|
77
79
|
|
|
78
80
|
# call an agent
|
|
79
|
-
answer = await ai_agent.run(
|
|
81
|
+
answer = await ai_agent.run("I want to know the weather in Kyiv")
|
|
80
82
|
|
|
81
83
|
# get full conversation history
|
|
82
|
-
conversation_history = ai_agent.
|
|
84
|
+
conversation_history = ai_agent.memory_messages
|
|
83
85
|
|
|
84
86
|
# clear conversation history
|
|
85
|
-
ai_agent.
|
|
87
|
+
ai_agent.clear_memory_messages()
|
|
86
88
|
|
|
87
89
|
|
|
88
90
|
if __name__ == "__main__":
|
|
89
91
|
asyncio.run(main())
|
|
90
92
|
```
|
|
91
93
|
|
|
92
|
-
### Use agent *without* inner memory
|
|
94
|
+
### Use agent *without* inner memory and run *multiple* messages
|
|
93
95
|
|
|
94
96
|
If you want to control the memory of the agent, you can disable it by setting `is_memory_enabled=False`
|
|
95
97
|
|
|
@@ -114,13 +116,13 @@ async def main():
|
|
|
114
116
|
# if you don't want to see the input and output messages from agent
|
|
115
117
|
# you can set input_output_logging=False
|
|
116
118
|
|
|
117
|
-
# define the conversation message
|
|
119
|
+
# define the conversation message(s)
|
|
118
120
|
messages = [
|
|
119
121
|
{"role": "user", "content": "Hello!"}
|
|
120
122
|
]
|
|
121
123
|
|
|
122
124
|
# call an agent
|
|
123
|
-
new_messages = await ai_agent.
|
|
125
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
124
126
|
|
|
125
127
|
# add new_messages to messages
|
|
126
128
|
messages.extend(new_messages)
|
|
@@ -131,7 +133,7 @@ async def main():
|
|
|
131
133
|
)
|
|
132
134
|
|
|
133
135
|
# call an agent
|
|
134
|
-
new_messages = await ai_agent.
|
|
136
|
+
new_messages = await ai_agent.run_messages(messages)
|
|
135
137
|
|
|
136
138
|
|
|
137
139
|
if __name__ == "__main__":
|
|
@@ -153,14 +155,15 @@ def main():
|
|
|
153
155
|
img_content = OpenAIImageMessageContent(image_url="https://your.domain/image",
|
|
154
156
|
text="Hello, what is shown in the photo?")
|
|
155
157
|
|
|
156
|
-
# define the conversation
|
|
158
|
+
# define the conversation messages
|
|
157
159
|
messages = [
|
|
158
160
|
{"role": "user", "content": "Hello!"},
|
|
159
161
|
{"role": "user", "content": img_content},
|
|
160
162
|
]
|
|
161
163
|
|
|
162
164
|
# call an agent
|
|
163
|
-
|
|
165
|
+
new_messages = ai_agent.run_messages(messages)
|
|
166
|
+
answer = new_messages[-1].content
|
|
164
167
|
|
|
165
168
|
|
|
166
169
|
if __name__ == "__main__":
|
|
@@ -8,7 +8,7 @@ def readme():
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name='dm-aioaiagent',
|
|
11
|
-
version='v0.
|
|
11
|
+
version='v0.4.1',
|
|
12
12
|
author='dimka4621',
|
|
13
13
|
author_email='mismartconfig@gmail.com',
|
|
14
14
|
description='This is my custom aioaiagent client',
|
|
@@ -23,9 +23,11 @@ setup(
|
|
|
23
23
|
'langchain~=0.3.0',
|
|
24
24
|
'langchain-core~=0.3.5',
|
|
25
25
|
'langgraph~=0.2.23',
|
|
26
|
+
'langsmith~=0.1.144',
|
|
26
27
|
'grandalf>=0.8',
|
|
27
28
|
'langchain-community~=0.3.0',
|
|
28
29
|
'langchain-openai~=0.2.0',
|
|
30
|
+
'langchain-anthropic~=0.3.0'
|
|
29
31
|
],
|
|
30
32
|
classifiers=[
|
|
31
33
|
'Programming Language :: Python :: 3.8',
|
|
@@ -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
|