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.
- {agentic_blocks-0.1.23/src/agentic_blocks.egg-info → agentic_blocks-0.1.25}/PKG-INFO +5 -1
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/pyproject.toml +5 -1
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/agent.py +18 -2
- agentic_blocks-0.1.25/src/agentic_blocks/agent_rich.py +169 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/messages.py +18 -8
- agentic_blocks-0.1.25/src/agentic_blocks/utils/logger.py +14 -0
- agentic_blocks-0.1.25/src/agentic_blocks/utils/rich_logger.py +59 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25/src/agentic_blocks.egg-info}/PKG-INFO +5 -1
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks.egg-info/SOURCES.txt +3 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks.egg-info/requires.txt +4 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/LICENSE +0 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/README.md +0 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/setup.cfg +0 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/__init__.py +0 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/llm.py +0 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/mcp_client.py +0 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/utils/tools_utils.py +0 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/visualization/visualize.py +0 -0
- {agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks.egg-info/dependency_links.txt +0 -0
- {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.
|
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.
|
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
|
-
|
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
|
-
|
247
|
-
|
248
|
-
|
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.
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks/visualization/visualize.py
RENAMED
File without changes
|
{agentic_blocks-0.1.23 → agentic_blocks-0.1.25}/src/agentic_blocks.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|