dm-aioaiagent 0.3.1__py3-none-any.whl → 0.3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dm_aioaiagent/__init__.py CHANGED
@@ -2,4 +2,5 @@ from dotenv import load_dotenv
2
2
  load_dotenv()
3
3
  from .ai_agent import DMAIAgent
4
4
  from .async_ai_agent import DMAioAIAgent
5
+ from .image_message_content_builder import ImageMessageContentBuilder
5
6
  from .types import Message
dm_aioaiagent/ai_agent.py CHANGED
@@ -16,7 +16,9 @@ __all__ = ["DMAIAgent"]
16
16
  class DMAIAgent:
17
17
  agent_name = "AIAgent"
18
18
  _allowed_roles = ("user", "ai")
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
+ MAX_MEMORY_MESSAGES = 20 # Only INT greater than 0
20
22
 
21
23
  def __init__(
22
24
  self,
@@ -29,6 +31,8 @@ class DMAIAgent:
29
31
  input_output_logging: bool = True,
30
32
  is_memory_enabled: bool = True,
31
33
  max_memory_messages: int = None,
34
+ response_if_request_fail: str = None,
35
+ response_if_invalid_image: str = None
32
36
  ):
33
37
  if not os.getenv("OPENAI_API_KEY"):
34
38
  raise EnvironmentError("'OPENAI_API_KEY' environment variable is not set!")
@@ -38,6 +42,8 @@ class DMAIAgent:
38
42
  self._input_output_logging = bool(input_output_logging)
39
43
  self._is_memory_enabled = bool(is_memory_enabled)
40
44
  self._max_memory_messages = self._validate_max_memory_messages(max_memory_messages)
45
+ self._response_if_request_fail = str(response_if_request_fail or self._response_if_request_fail)
46
+ self._response_if_invalid_image = str(response_if_invalid_image or self._response_if_invalid_image)
41
47
 
42
48
  prompt = ChatPromptTemplate.from_messages([SystemMessage(content=system_message),
43
49
  MessagesPlaceholder(variable_name="messages")])
@@ -67,14 +73,24 @@ class DMAIAgent:
67
73
  state = self._graph.invoke({"input_messages": input_messages, "memory_id": memory_id})
68
74
  return state["response"]
69
75
 
70
- def get_memory_messages(self, memory_id: str = None) -> list[BaseMessage]:
71
- return self._memory.get(self._validate_memory_id(memory_id), [])
76
+ def get_memory_messages(
77
+ self,
78
+ memory_id: str = None,
79
+ *,
80
+ without_tool_m: bool = False,
81
+ return_str_m: bool = False
82
+ ) -> Union[list[BaseMessage], list[str]]:
83
+ messages = self._memory.get(self._validate_memory_id(memory_id), [])
84
+ if without_tool_m:
85
+ messages = [m for m in messages if not (m.type == "tool" or (m.type == "ai" and m.tool_calls))]
86
+ if return_str_m:
87
+ messages = [m.content for m in messages]
88
+ return messages
72
89
 
73
90
  def clear_memory(self, memory_id: str = None) -> None:
74
91
  self._memory[self._validate_memory_id(memory_id)] = []
75
92
 
76
93
  def _prepare_messages_node(self, state: State) -> State:
77
- state.memory_id = self._validate_memory_id(state.memory_id)
78
94
  state.input_messages = state.input_messages or [{"role": "user", "content": ""}]
79
95
  for item in state.input_messages:
80
96
  if isinstance(item, dict):
@@ -91,14 +107,23 @@ class DMAIAgent:
91
107
  state.messages.append(item)
92
108
 
93
109
  if self._input_output_logging:
94
- self._logger.debug(f"Query:\n{state.messages[-1].content}", memory_id=state.memory_id)
110
+ log_kwargs = {} if state.memory_id is None else {"memory_id": state.memory_id}
111
+ self._logger.debug(f"Query:\n{state.messages[-1].content}", **log_kwargs)
95
112
  if self._is_memory_enabled:
96
113
  state.messages = self.get_memory_messages(state.memory_id) + state.messages
97
114
  return state
98
115
 
99
- def _invoke_llm_node(self, state: State) -> State:
116
+ def _invoke_llm_node(self, state: State, second_attempt: bool = False) -> State:
100
117
  self._logger.debug("Run node: Invoke LLM")
101
- ai_response = self._agent.invoke({"messages": state.messages})
118
+ try:
119
+ ai_response = self._agent.invoke({"messages": state.messages})
120
+ except Exception as e:
121
+ self._logger.error(e)
122
+ if second_attempt:
123
+ response = self._response_if_invalid_image if "invalid_image_url" in str(e) else self._response_if_request_fail
124
+ state.messages.append(AIMessage(content=response))
125
+ return state
126
+ return self._invoke_llm_node(state, second_attempt=True)
102
127
  state.messages.append(ai_response)
103
128
  return state
104
129
 
@@ -137,12 +162,14 @@ class DMAIAgent:
137
162
  def _exit_node(self, state: State) -> State:
138
163
  answer = state.messages[-1].content
139
164
  if self._input_output_logging:
140
- self._logger.debug(f"Answer:\n{answer}", memory_id=state.memory_id)
165
+ log_kwargs = {} if state.memory_id is None else {"memory_id": state.memory_id}
166
+ self._logger.debug(f"Answer:\n{answer}", **log_kwargs)
141
167
 
142
168
  if self._is_memory_enabled:
169
+ memory_id = self._validate_memory_id(state.memory_id)
143
170
  messages_to_memory = state.messages[-self._max_memory_messages:]
144
171
  # drop ToolsMessages from start of list
145
- self._memory[state.memory_id] = list(dropwhile(lambda x: isinstance(x, ToolMessage), messages_to_memory))
172
+ self._memory[memory_id] = list(dropwhile(lambda x: isinstance(x, ToolMessage), messages_to_memory))
146
173
  state.response = answer
147
174
  else:
148
175
  state.response = state.messages[len(state.input_messages):]
@@ -1,6 +1,6 @@
1
1
  import sys
2
2
  import asyncio
3
- from langchain_core.messages import ToolMessage
3
+ from langchain_core.messages import AIMessage, ToolMessage
4
4
 
5
5
  from .ai_agent import DMAIAgent
6
6
  from .types import *
@@ -18,9 +18,17 @@ class DMAioAIAgent(DMAIAgent):
18
18
  state = await self._graph.ainvoke({"input_messages": input_messages, "memory_id": memory_id})
19
19
  return state["response"]
20
20
 
21
- async def _invoke_llm_node(self, state: State) -> State:
21
+ async def _invoke_llm_node(self, state: State, second_attempt: bool = False) -> State:
22
22
  self._logger.debug("Run node: Invoke LLM")
23
- ai_response = await self._agent.ainvoke({"messages": state.messages})
23
+ try:
24
+ ai_response = await self._agent.ainvoke({"messages": state.messages})
25
+ except Exception as e:
26
+ self._logger.error(e)
27
+ if second_attempt:
28
+ response = self._response_if_invalid_image if "invalid_image_url" in str(e) else self._response_if_request_fail
29
+ state.messages.append(AIMessage(content=response))
30
+ return state
31
+ return await self._invoke_llm_node(state, second_attempt=True)
24
32
  state.messages.append(ai_response)
25
33
  return state
26
34
 
@@ -0,0 +1,15 @@
1
+ class ImageMessageContentBuilder(list):
2
+ def __init__(self, image_url: str, text: str = None):
3
+ content = []
4
+ if isinstance(text, str):
5
+ content.append({
6
+ "type": "text",
7
+ "text": text
8
+ })
9
+ content.append({
10
+ "type": "image_url",
11
+ "image_url": {
12
+ "url": image_url
13
+ }
14
+ })
15
+ super().__init__(content)
dm_aioaiagent/types.py CHANGED
@@ -4,12 +4,33 @@ from pydantic import BaseModel, Field
4
4
  from langchain_core.messages import BaseMessage
5
5
 
6
6
 
7
- class Message(TypedDict):
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):
8
26
  role: Literal["user", "ai"]
9
27
  content: str
10
28
 
11
29
 
30
+ Message = Union[TextMessage, ImageMessage]
31
+
12
32
  InputMessagesType = list[Union[Message, BaseMessage]]
33
+
13
34
  ResponseType = Union[str, list[BaseMessage]]
14
35
 
15
36
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dm-aioaiagent
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: This is my custom aioaiagent client
5
5
  Home-page: https://pypi.org/project/dm-aioaiagent
6
6
  Author: dimka4621
@@ -18,6 +18,7 @@ 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: grandalf>=0.8
21
22
  Requires-Dist: langchain-community~=0.3.0
22
23
  Requires-Dist: langchain-openai~=0.2.0
23
24
 
@@ -137,6 +138,34 @@ if __name__ == "__main__":
137
138
  asyncio.run(main())
138
139
  ```
139
140
 
141
+ ### Image vision
142
+
143
+ ```python
144
+ from dm_aioaiagent import DMAIAgent, ImageMessageContentBuilder
145
+
146
+ def main():
147
+ # create an agent
148
+ ai_agent = DMAIAgent(agent_name="image_vision", model="gpt-4o")
149
+
150
+ # create an image message content
151
+ # NOTE: text argument is optional
152
+ img_content = ImageMessageContentBuilder(image_url="https://your.domain/image",
153
+ text="Hello, what is shown in the photo?")
154
+
155
+ # define the conversation message
156
+ messages = [
157
+ {"role": "user", "content": "Hello!"},
158
+ {"role": "user", "content": img_content},
159
+ ]
160
+
161
+ # call an agent
162
+ answer = ai_agent.run(messages)
163
+
164
+
165
+ if __name__ == "__main__":
166
+ main()
167
+ ```
168
+
140
169
  ### Set custom logger
141
170
 
142
171
  _If you want set up custom logger_
@@ -0,0 +1,9 @@
1
+ dm_aioaiagent/__init__.py,sha256=pq9gL6E1VN4Tkx9PD83AIW9e_-N5REqJeC0TE8bZua4,215
2
+ dm_aioaiagent/ai_agent.py,sha256=TTMSL2B-fdv_q7ytO38kC5fd8uB1Vk71ev7tS5tJMwE,9284
3
+ dm_aioaiagent/async_ai_agent.py,sha256=qg7LZRxC7MSzjjburEX09T5EF15ZXAWAd_9RB8vA0oE,2599
4
+ dm_aioaiagent/image_message_content_builder.py,sha256=mAJnsWdnpYpaFAdcCsVwgWH480Q4WfVEpM-Y6KdjyEc,435
5
+ dm_aioaiagent/types.py,sha256=H2_iICmWr6u9d1-6BtSxdt6qV6Jm8v7Zh8das5kV6I4,989
6
+ dm_aioaiagent-0.3.3.dist-info/METADATA,sha256=i06u5vC73yDmx73r5lUCr0Okw1vtf1FwKJgjpuZiKY0,5032
7
+ dm_aioaiagent-0.3.3.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
8
+ dm_aioaiagent-0.3.3.dist-info/top_level.txt,sha256=CbasLH0KI7zA77XwT6JDCnmRascxKNGvUVV9MgYjHAU,14
9
+ dm_aioaiagent-0.3.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +0,0 @@
1
- dm_aioaiagent/__init__.py,sha256=8B0XQR-XE2VieU7-LOHCjqDYv0gSYqkgAkj_eq6XAhI,145
2
- dm_aioaiagent/ai_agent.py,sha256=YqM2KpLZVMqFVWBpmqEzgJSLQSycvB1g9ndxdg2aL4k,7846
3
- dm_aioaiagent/async_ai_agent.py,sha256=n7OfJUHRwjk92gae9NkpPKQ21Xost2-JsQ0JyzYTXT4,2146
4
- dm_aioaiagent/types.py,sha256=Xvx0x1GxLAvCot_3CZGgD8BFWwnTEc1lg7moLxkkJoc,587
5
- dm_aioaiagent-0.3.1.dist-info/METADATA,sha256=BeQ8eugmPWTmAmL7WJwWUc64URWW-5IB6Qg8Fovkq1Q,4299
6
- dm_aioaiagent-0.3.1.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
7
- dm_aioaiagent-0.3.1.dist-info/top_level.txt,sha256=CbasLH0KI7zA77XwT6JDCnmRascxKNGvUVV9MgYjHAU,14
8
- dm_aioaiagent-0.3.1.dist-info/RECORD,,