agentic-blocks 0.1.23__tar.gz → 0.1.25__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.
Files changed (20) hide show
  1. {agentic_blocks-0.1.23/src/agentic_blocks.egg-info → agentic_blocks-0.1.25}/PKG-INFO +5 -1
  2. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/pyproject.toml +5 -1
  3. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/agent.py +18 -2
  4. agentic_blocks-0.1.25/src/agentic_blocks/agent_rich.py +169 -0
  5. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/messages.py +18 -8
  6. agentic_blocks-0.1.25/src/agentic_blocks/utils/logger.py +14 -0
  7. agentic_blocks-0.1.25/src/agentic_blocks/utils/rich_logger.py +59 -0
  8. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25/src/agentic_blocks.egg-info}/PKG-INFO +5 -1
  9. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks.egg-info/SOURCES.txt +3 -0
  10. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks.egg-info/requires.txt +4 -0
  11. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/LICENSE +0 -0
  12. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/README.md +0 -0
  13. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/setup.cfg +0 -0
  14. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/__init__.py +0 -0
  15. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/llm.py +0 -0
  16. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/mcp_client.py +0 -0
  17. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/utils/tools_utils.py +0 -0
  18. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/visualization/visualize.py +0 -0
  19. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks.egg-info/dependency_links.txt +0 -0
  20. {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-blocks
3
- Version: 0.1.23
3
+ Version: 0.1.25
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
@@ -27,6 +27,10 @@ Requires-Dist: langchain-core
27
27
  Requires-Dist: langfuse
28
28
  Requires-Dist: pocketflow
29
29
  Requires-Dist: pocketflow-tracing
30
+ Requires-Dist: agno
31
+ Requires-Dist: ipywidgets
32
+ Requires-Dist: rich[jupyter]
33
+ Requires-Dist: fastapi
30
34
  Provides-Extra: test
31
35
  Requires-Dist: pytest; extra == "test"
32
36
  Provides-Extra: dev
@@ -14,7 +14,7 @@ agentic_blocks = []
14
14
 
15
15
  [project]
16
16
  name = "agentic-blocks"
17
- version = "0.1.23"
17
+ version = "0.1.25"
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"
@@ -43,6 +43,10 @@ dependencies = [
43
43
  "langfuse",
44
44
  "pocketflow",
45
45
  "pocketflow-tracing",
46
+ "agno",
47
+ "ipywidgets",
48
+ "rich[jupyter]",
49
+ "fastapi",
46
50
  ]
47
51
 
48
52
  [project.urls]
@@ -3,8 +3,11 @@ from agentic_blocks.utils.tools_utils import (
3
3
  create_tool_registry,
4
4
  execute_pending_tool_calls,
5
5
  )
6
+ from agentic_blocks.utils.logger import get_logger
6
7
  from agentic_blocks import call_llm, Messages
7
8
 
9
+ logger = get_logger(__name__)
10
+
8
11
 
9
12
  class Agent:
10
13
  def __init__(self, system_prompt: str, tools: list):
@@ -32,10 +35,16 @@ class Agent:
32
35
  self.tools = tools
33
36
 
34
37
  def prep(self, shared):
35
- return shared["messages"]
38
+ messages = shared["messages"]
39
+ return messages
36
40
 
37
41
  def exec(self, messages) -> Messages:
42
+ latest_message = messages.get_messages()[-1]
43
+ if latest_message["role"] == "user":
44
+ logger.info(f'message: "{latest_message["content"]}"')
45
+
38
46
  response = call_llm(messages=messages, tools=self.tools)
47
+ # logger.info(f'response: "{response["content"] or "tool_calls"}"')
39
48
  messages.add_response_message(response)
40
49
  return messages
41
50
 
@@ -56,7 +65,13 @@ class Agent:
56
65
  def prep(self, shared):
57
66
  return shared["messages"]
58
67
 
59
- def exec(self, messages) -> Messages:
68
+ def exec(self, messages: Messages) -> Messages:
69
+ tool_calls = messages.get_pending_tool_calls()
70
+ for tool_call in tool_calls:
71
+ logger.info(
72
+ f'tool_call: "{tool_call["tool_name"]}({tool_call["arguments"]})"'
73
+ )
74
+
60
75
  tool_responses = execute_pending_tool_calls(
61
76
  messages, self.tool_registry
62
77
  )
@@ -79,6 +94,7 @@ class Agent:
79
94
 
80
95
  def invoke(self, user_prompt: str) -> str:
81
96
  messages = Messages(user_prompt=user_prompt)
97
+ logger.info(f'message: "{user_prompt}"')
82
98
  if self.system_prompt:
83
99
  messages.add_system_message(self.system_prompt)
84
100
 
@@ -0,0 +1,169 @@
1
+ from pocketflow import Node, Flow
2
+ from agentic_blocks.utils.tools_utils import (
3
+ create_tool_registry,
4
+ execute_pending_tool_calls,
5
+ )
6
+ from agentic_blocks import call_llm, Messages
7
+
8
+
9
+ from rich.console import Group, Console
10
+ from rich.json import JSON
11
+ from rich.live import Live
12
+ from rich.markdown import Markdown
13
+ from rich.status import Status
14
+ from rich.text import Text
15
+ from rich.box import HEAVY
16
+ from rich.panel import Panel
17
+ from agentic_blocks.utils.rich_logger import print_response
18
+
19
+
20
+ class Agent:
21
+ def __init__(self, system_prompt: str, tools: list):
22
+ self.system_prompt = system_prompt
23
+ self.tools = tools
24
+ self.tool_registry = create_tool_registry(tools)
25
+ self.panels = []
26
+
27
+ # Create nodes
28
+ self.llm_node = self._create_llm_node()
29
+ self.tool_node = self._create_tool_node()
30
+ self.answer_node = self._create_answer_node()
31
+
32
+ # Set up flow
33
+ self.llm_node - "tool_node" >> self.tool_node
34
+ self.tool_node - "llm_node" >> self.llm_node
35
+ self.llm_node - "answer_node" >> self.answer_node
36
+
37
+ self.flow = Flow(self.llm_node)
38
+
39
+ def _create_llm_node(self):
40
+ class LLMNode(Node):
41
+ def __init__(self, system_prompt, tools):
42
+ super().__init__()
43
+ self.system_prompt = system_prompt
44
+ self.tools = tools
45
+
46
+ def prep(self, shared):
47
+ messages = shared["messages"]
48
+ return messages
49
+
50
+ def exec(self, messages) -> Messages:
51
+ response = call_llm(messages=messages, tools=self.tools)
52
+ messages.add_response_message(response)
53
+ return messages
54
+
55
+ def post(self, shared, prep_res, messages):
56
+ if messages.has_pending_tool_calls():
57
+ return "tool_node"
58
+ else:
59
+ return "answer_node"
60
+
61
+ return LLMNode(self.system_prompt, self.tools)
62
+
63
+ def _create_tool_node(self):
64
+ class ToolNode(Node):
65
+ def __init__(self, tool_registry, agent):
66
+ super().__init__()
67
+ self.tool_registry = tool_registry
68
+ self.agent = agent
69
+
70
+ def prep(self, shared):
71
+ return shared["messages"]
72
+
73
+ def exec(self, messages) -> Messages:
74
+ tool_calls = messages.get_pending_tool_calls()[0]
75
+ tool_name = tool_calls["tool_name"]
76
+ tool_arguments = tool_calls["arguments"]
77
+
78
+ # Format arguments nicely
79
+ if isinstance(tool_arguments, dict):
80
+ args_str = ", ".join(
81
+ [f"{k}={v}" for k, v in tool_arguments.items()]
82
+ )
83
+ formatted_call = f"{tool_name}({args_str})"
84
+ else:
85
+ formatted_call = f"{tool_name}({tool_arguments})"
86
+
87
+ tool_panel = self.agent.create_panel(formatted_call, "Tool Calls")
88
+ self.agent.panels.append(tool_panel)
89
+
90
+ self.agent.live_log.update(Group(*self.agent.panels))
91
+ self.agent.live_log.refresh()
92
+
93
+ tool_responses = execute_pending_tool_calls(
94
+ messages, self.tool_registry
95
+ )
96
+ messages.add_tool_responses(tool_responses)
97
+ return messages
98
+
99
+ def post(self, shared, prep_res, messages):
100
+ return "llm_node"
101
+
102
+ return ToolNode(self.tool_registry, self)
103
+
104
+ def _create_answer_node(self):
105
+ class AnswerNode(Node):
106
+ def prep(self, shared):
107
+ messages = shared["messages"]
108
+ shared["answer"] = messages.get_messages()[-1]["content"]
109
+ return messages
110
+
111
+ return AnswerNode()
112
+
113
+ def invoke(self, user_prompt: str) -> str:
114
+ messages = Messages(user_prompt=user_prompt)
115
+ if self.system_prompt:
116
+ messages.add_system_message(self.system_prompt)
117
+
118
+ shared = {"messages": messages}
119
+ self.flow.run(shared)
120
+
121
+ return shared["answer"]
122
+
123
+ def create_panel(self, content, title, border_style="blue"):
124
+ return Panel(
125
+ content,
126
+ title=title,
127
+ title_align="left",
128
+ border_style=border_style,
129
+ box=HEAVY,
130
+ expand=True,
131
+ padding=(1, 1),
132
+ )
133
+
134
+ def print_response(self, user_prompt: str):
135
+ messages = Messages(user_prompt=user_prompt)
136
+ if self.system_prompt:
137
+ messages.add_system_message(self.system_prompt)
138
+
139
+ shared = {"messages": messages}
140
+
141
+ with Live(console=Console(), auto_refresh=False) as self.live_log:
142
+ status = Status("Thinking...", spinner="aesthetic", speed=0.4)
143
+ self.live_log.update(status)
144
+ self.live_log.refresh() # Explicit refresh for Jupyter
145
+
146
+ self.panels = [status]
147
+
148
+ message_panel = self.create_panel(
149
+ content=Text(user_prompt, style="green"),
150
+ title="Message",
151
+ border_style="cyan",
152
+ )
153
+
154
+ self.panels.append(message_panel)
155
+ self.live_log.update(Group(*self.panels))
156
+ self.live_log.refresh()
157
+
158
+ self.flow.run(shared)
159
+ response = shared["answer"]
160
+
161
+ response_panel = self.create_panel(
162
+ content=Text(response, style="bold blue"),
163
+ title="Response",
164
+ border_style="green",
165
+ )
166
+
167
+ self.panels.append(response_panel)
168
+ self.live_log.update(Group(*self.panels))
169
+ self.live_log.refresh()
@@ -175,6 +175,13 @@ class Messages:
175
175
  """Get the current messages list."""
176
176
  return self.messages
177
177
 
178
+ def get_user_message(self) -> str:
179
+ """Get the user message."""
180
+ for message in reversed(self.messages):
181
+ if message.get("role") == "user":
182
+ return message.get("content") or ""
183
+ return ""
184
+
178
185
  def has_pending_tool_calls(self) -> bool:
179
186
  """
180
187
  Check if the last message has tool calls that need execution.
@@ -213,7 +220,7 @@ class Messages:
213
220
  List of dictionaries with 'tool_name', 'arguments', and 'tool_call_id' keys
214
221
  """
215
222
  pending_calls = []
216
-
223
+
217
224
  if not self.messages:
218
225
  return pending_calls
219
226
 
@@ -234,19 +241,22 @@ class Messages:
234
241
  function_info = tool_call.get("function", {})
235
242
  tool_name = function_info.get("name")
236
243
  arguments_str = function_info.get("arguments", "{}")
237
-
244
+
238
245
  # Parse arguments JSON string to dict
239
246
  import json
247
+
240
248
  try:
241
249
  arguments = json.loads(arguments_str)
242
250
  except json.JSONDecodeError:
243
251
  arguments = {}
244
-
245
- pending_calls.append({
246
- "tool_name": tool_name,
247
- "arguments": arguments,
248
- "tool_call_id": tool_call_id
249
- })
252
+
253
+ pending_calls.append(
254
+ {
255
+ "tool_name": tool_name,
256
+ "arguments": arguments,
257
+ "tool_call_id": tool_call_id,
258
+ }
259
+ )
250
260
 
251
261
  return pending_calls
252
262
 
@@ -0,0 +1,14 @@
1
+ import logging
2
+
3
+ # Auto-configure logging when module is imported
4
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
5
+
6
+ # Suppress HTTP request logs
7
+ logging.getLogger("openai").setLevel(logging.WARNING)
8
+ logging.getLogger("httpx").setLevel(logging.WARNING)
9
+ logging.getLogger("urllib3").setLevel(logging.WARNING)
10
+
11
+
12
+ def get_logger(name):
13
+ """Get a logger with the given name."""
14
+ return logging.getLogger(name)
@@ -0,0 +1,59 @@
1
+ import time
2
+ from rich.console import Group, Console
3
+ from rich.json import JSON
4
+ from rich.live import Live
5
+ from rich.markdown import Markdown
6
+ from rich.status import Status
7
+ from rich.text import Text
8
+ from rich.box import HEAVY
9
+ from rich.panel import Panel
10
+
11
+ # Create a console instance for Jupyter
12
+
13
+
14
+ def create_panel(content, title, border_style="blue"):
15
+ return Panel(
16
+ content,
17
+ title=title,
18
+ title_align="left",
19
+ border_style=border_style,
20
+ box=HEAVY,
21
+ expand=True,
22
+ padding=(1, 1),
23
+ )
24
+
25
+
26
+ def print_response(user_prompt):
27
+ with Live(console=Console(), auto_refresh=False) as live_log:
28
+ status = Status("Thinking...", spinner="aesthetic", speed=0.4)
29
+ live_log.update(status)
30
+ live_log.refresh() # Explicit refresh for Jupyter
31
+
32
+ # Create panels
33
+ panels = [status]
34
+
35
+ message_panel = create_panel(
36
+ content=Text(user_prompt, style="green"),
37
+ title="Message",
38
+ border_style="cyan",
39
+ )
40
+
41
+ panels.append(message_panel)
42
+ live_log.update(Group(*panels))
43
+ live_log.refresh() # Explicit refresh for Jupyter
44
+
45
+ # Add some delay to see the display
46
+
47
+ for i in range(3):
48
+ time.sleep(2)
49
+
50
+ # Add a response panel
51
+ response_panel = create_panel(
52
+ content=Text("Response: " + str(i), style="bold blue"),
53
+ title="Response",
54
+ border_style="green",
55
+ )
56
+
57
+ panels.append(response_panel)
58
+ live_log.update(Group(*panels))
59
+ live_log.refresh()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-blocks
3
- Version: 0.1.23
3
+ Version: 0.1.25
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
@@ -27,6 +27,10 @@ Requires-Dist: langchain-core
27
27
  Requires-Dist: langfuse
28
28
  Requires-Dist: pocketflow
29
29
  Requires-Dist: pocketflow-tracing
30
+ Requires-Dist: agno
31
+ Requires-Dist: ipywidgets
32
+ Requires-Dist: rich[jupyter]
33
+ Requires-Dist: fastapi
30
34
  Provides-Extra: test
31
35
  Requires-Dist: pytest; extra == "test"
32
36
  Provides-Extra: dev
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  src/agentic_blocks/__init__.py
5
5
  src/agentic_blocks/agent.py
6
+ src/agentic_blocks/agent_rich.py
6
7
  src/agentic_blocks/llm.py
7
8
  src/agentic_blocks/mcp_client.py
8
9
  src/agentic_blocks/messages.py
@@ -11,5 +12,7 @@ src/agentic_blocks.egg-info/SOURCES.txt
11
12
  src/agentic_blocks.egg-info/dependency_links.txt
12
13
  src/agentic_blocks.egg-info/requires.txt
13
14
  src/agentic_blocks.egg-info/top_level.txt
15
+ src/agentic_blocks/utils/logger.py
16
+ src/agentic_blocks/utils/rich_logger.py
14
17
  src/agentic_blocks/utils/tools_utils.py
15
18
  src/agentic_blocks/visualization/visualize.py
@@ -6,6 +6,10 @@ langchain-core
6
6
  langfuse
7
7
  pocketflow
8
8
  pocketflow-tracing
9
+ agno
10
+ ipywidgets
11
+ rich[jupyter]
12
+ fastapi
9
13
 
10
14
  [dev]
11
15
  pytest
File without changes