agentic-blocks 0.1.5__tar.gz → 0.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-blocks
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Simple building blocks for agentic AI systems with MCP client and conversation management
5
5
  Author-email: Magnus Bjelkenhed <bjelkenhed@gmail.com>
6
6
  License: MIT
@@ -14,7 +14,7 @@ agentic_blocks = []
14
14
 
15
15
  [project]
16
16
  name = "agentic-blocks"
17
- version = "0.1.5"
17
+ version = "0.1.7"
18
18
  description = "Simple building blocks for agentic AI systems with MCP client and conversation management"
19
19
  readme = "README.md"
20
20
  requires-python = ">=3.11"
@@ -48,12 +48,20 @@ def call_llm(
48
48
  # Get API key
49
49
  if not api_key:
50
50
  api_key = os.getenv("OPENAI_API_KEY")
51
+ if not api_key:
52
+ api_key = os.getenv("OPENROUTER_API_KEY")
51
53
 
52
54
  if not api_key and not base_url:
53
55
  raise LLMError(
54
- "OpenAI API key not found. Set OPENAI_API_KEY environment variable or pass api_key parameter."
56
+ "API key not found. Set OPENROUTER_API_KEY or OPENAI_API_KEY environment variable or pass api_key parameter."
55
57
  )
56
58
 
59
+ if api_key and api_key.startswith("sk-or"):
60
+ base_url = "https://openrouter.ai/api/v1"
61
+
62
+ if base_url and not api_key:
63
+ api_key = "EMPTY"
64
+
57
65
  # Initialize OpenAI client
58
66
  client_kwargs = {}
59
67
  if api_key:
@@ -137,10 +145,12 @@ def example_usage():
137
145
  # Call with raw message list
138
146
  print("\nUsing raw message list:")
139
147
  response2 = call_llm(messages_list, tools=tools, temperature=0.5)
140
- if hasattr(response2, 'tool_calls') and response2.tool_calls:
148
+ if hasattr(response2, "tool_calls") and response2.tool_calls:
141
149
  print(f"Tool calls requested: {len(response2.tool_calls)}")
142
150
  for i, tool_call in enumerate(response2.tool_calls):
143
- print(f" {i+1}. {tool_call.function.name}({tool_call.function.arguments})")
151
+ print(
152
+ f" {i + 1}. {tool_call.function.name}({tool_call.function.arguments})"
153
+ )
144
154
  else:
145
155
  print(f"Response: {response2.content}")
146
156
 
@@ -68,9 +68,11 @@ class Messages:
68
68
  tool_call: The tool call dictionary with id, type, function, etc.
69
69
  """
70
70
  # Check if the latest message is an assistant message with tool_calls
71
- if (self.messages
72
- and self.messages[-1].get("role") == "assistant"
73
- and "tool_calls" in self.messages[-1]):
71
+ if (
72
+ self.messages
73
+ and self.messages[-1].get("role") == "assistant"
74
+ and "tool_calls" in self.messages[-1]
75
+ ):
74
76
  # Append to existing assistant message
75
77
  self.messages[-1]["tool_calls"].append(tool_call)
76
78
  else:
@@ -82,6 +84,33 @@ class Messages:
82
84
  }
83
85
  self.messages.append(assistant_message)
84
86
 
87
+ def add_tool_calls(self, tool_calls):
88
+ """
89
+ Add multiple tool calls from ChatCompletionMessageFunctionToolCall objects.
90
+
91
+ Args:
92
+ tool_calls: A list of ChatCompletionMessageFunctionToolCall objects or a single object
93
+ """
94
+ # Handle single tool call or list of tool calls
95
+ if not isinstance(tool_calls, list):
96
+ tool_calls = [tool_calls]
97
+
98
+ # Create assistant message with empty content and tool calls
99
+ assistant_message = {"role": "assistant", "content": "", "tool_calls": []}
100
+
101
+ for tool_call in tool_calls:
102
+ tool_call_dict = {
103
+ "id": tool_call.id,
104
+ "type": tool_call.type,
105
+ "function": {
106
+ "name": tool_call.function.name,
107
+ "arguments": tool_call.function.arguments,
108
+ },
109
+ }
110
+ assistant_message["tool_calls"].append(tool_call_dict)
111
+
112
+ self.messages.append(assistant_message)
113
+
85
114
  def add_tool_response(self, tool_call_id: str, content: str):
86
115
  """
87
116
  Add a tool response message.
@@ -108,7 +137,7 @@ class Messages:
108
137
  for response in tool_responses:
109
138
  tool_call_id = response.get("tool_call_id", "unknown")
110
139
  is_error = response.get("is_error", False)
111
-
140
+
112
141
  if is_error:
113
142
  content = f"Error: {response.get('error', 'Unknown error')}"
114
143
  else:
@@ -125,6 +154,23 @@ class Messages:
125
154
 
126
155
  self.add_tool_response(tool_call_id, content)
127
156
 
157
+ def add_response_message(self, model_response):
158
+ """
159
+ Add a response message (ChatCompletionMessage) to the conversation.
160
+
161
+ Args:
162
+ model_response: A ChatCompletionMessage object with role, content, and potentially tool_calls
163
+ """
164
+ # If there are tool calls, use add_tool_calls
165
+ if model_response.tool_calls:
166
+ self.add_tool_calls(model_response.tool_calls)
167
+ # If there's also content, update the message content
168
+ if model_response.content:
169
+ self.messages[-1]["content"] = model_response.content
170
+ else:
171
+ # No tool calls, just add content as assistant message
172
+ self.add_assistant_message(model_response.content or "")
173
+
128
174
  def get_messages(self) -> List[Dict[str, Any]]:
129
175
  """Get the current messages list."""
130
176
  return self.messages
@@ -140,17 +186,20 @@ class Messages:
140
186
  return False
141
187
 
142
188
  last_message = self.messages[-1]
143
-
189
+
144
190
  # Check if the last message is an assistant message with tool calls
145
191
  if last_message.get("role") == "assistant" and "tool_calls" in last_message:
146
192
  # Check if there are subsequent tool responses
147
193
  tool_call_ids = {tc.get("id") for tc in last_message["tool_calls"]}
148
-
194
+
149
195
  # Look for tool responses after this message
150
196
  for msg in reversed(self.messages):
151
- if msg.get("role") == "tool" and msg.get("tool_call_id") in tool_call_ids:
197
+ if (
198
+ msg.get("role") == "tool"
199
+ and msg.get("tool_call_id") in tool_call_ids
200
+ ):
152
201
  tool_call_ids.remove(msg.get("tool_call_id"))
153
-
202
+
154
203
  # If there are still unresponded tool call IDs, we have pending calls
155
204
  return len(tool_call_ids) > 0
156
205
 
@@ -172,7 +221,7 @@ class Messages:
172
221
  for j, tool_call in enumerate(message["tool_calls"], 1):
173
222
  function_name = tool_call.get("function", {}).get("name", "unknown")
174
223
  lines.append(f" └─ Tool Call {j}: {function_name}")
175
-
224
+
176
225
  # Handle tool messages
177
226
  elif role == "tool":
178
227
  tool_call_id = message.get("tool_call_id", "unknown")
@@ -180,7 +229,7 @@ class Messages:
180
229
  if len(content) > 200:
181
230
  content = content[:197] + "..."
182
231
  lines.append(f"{i}. {role} [{tool_call_id[:8]}...]: {content}")
183
-
232
+
184
233
  # Handle other message types
185
234
  else:
186
235
  # Truncate long content for readability
@@ -198,28 +247,28 @@ def example_usage():
198
247
  messages = Messages(
199
248
  system_prompt="You are a helpful assistant.",
200
249
  user_prompt="Hello, how are you?",
201
- add_date_and_time=True
250
+ add_date_and_time=True,
202
251
  )
203
-
252
+
204
253
  # Add assistant response
205
254
  messages.add_assistant_message("I'm doing well, thank you!")
206
-
255
+
207
256
  # Add a tool call
208
257
  tool_call = {
209
258
  "id": "call_123",
210
259
  "type": "function",
211
- "function": {"name": "get_weather", "arguments": '{"location": "Paris"}'}
260
+ "function": {"name": "get_weather", "arguments": '{"location": "Paris"}'},
212
261
  }
213
262
  messages.add_tool_call(tool_call)
214
-
263
+
215
264
  # Add tool response
216
265
  messages.add_tool_response("call_123", "The weather in Paris is sunny, 22°C")
217
-
266
+
218
267
  print("Conversation:")
219
268
  print(messages)
220
-
269
+
221
270
  print(f"\nHas pending tool calls: {messages.has_pending_tool_calls()}")
222
271
 
223
272
 
224
273
  if __name__ == "__main__":
225
- example_usage()
274
+ example_usage()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-blocks
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Simple building blocks for agentic AI systems with MCP client and conversation management
5
5
  Author-email: Magnus Bjelkenhed <bjelkenhed@gmail.com>
6
6
  License: MIT
File without changes
File without changes
File without changes