naas-abi 1.0.8__py3-none-any.whl → 1.0.10__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.
- naas_abi/__init__.py +16 -1
- naas_abi/agents/AbiAgent.py +27 -18
- naas_abi/agents/EntitytoSPARQLAgent.py +22 -17
- naas_abi/agents/KnowledgeGraphBuilderAgent.py +17 -15
- naas_abi/agents/OntologyEngineerAgent.py +4 -15
- naas_abi/apps/oxigraph_admin/main.py +7 -4
- naas_abi/apps/sparql_terminal/main.py +7 -4
- naas_abi/pipelines/AIAgentOntologyGenerationPipeline_test.py +4 -3
- naas_abi/pipelines/AddIndividualPipeline_test.py +10 -7
- naas_abi/pipelines/InsertDataSPARQLPipeline.py +6 -4
- naas_abi/pipelines/InsertDataSPARQLPipeline_test.py +9 -6
- naas_abi/pipelines/MergeIndividualsPipeline.py +6 -2
- naas_abi/pipelines/MergeIndividualsPipeline_test.py +10 -9
- naas_abi/pipelines/RemoveIndividualPipeline.py +6 -2
- naas_abi/pipelines/RemoveIndividualPipeline_test.py +8 -8
- naas_abi/pipelines/UpdateDataPropertyPipeline.py +6 -2
- naas_abi/workflows/GetObjectPropertiesFromClassWorkflow_test.py +7 -2
- naas_abi/workflows/GetSubjectGraphWorkflow_test.py +8 -3
- naas_abi/workflows/SearchIndividualWorkflow_test.py +6 -6
- {naas_abi-1.0.8.dist-info → naas_abi-1.0.10.dist-info}/METADATA +2 -2
- naas_abi-1.0.10.dist-info/RECORD +49 -0
- naas_abi/apps/terminal_agent/main.py +0 -553
- naas_abi/apps/terminal_agent/terminal_style.py +0 -175
- naas_abi/mappings.py +0 -83
- naas_abi/triggers.py +0 -131
- naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow.py +0 -210
- naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow_test.py +0 -78
- naas_abi/workflows/CreateClassOntologyYamlWorkflow.py +0 -214
- naas_abi/workflows/CreateClassOntologyYamlWorkflow_test.py +0 -65
- naas_abi/workflows/CreateIndividualOntologyYamlWorkflow.py +0 -183
- naas_abi/workflows/CreateIndividualOntologyYamlWorkflow_test.py +0 -86
- naas_abi/workflows/ExportGraphInstancesToExcelWorkflow.py +0 -450
- naas_abi/workflows/ExportGraphInstancesToExcelWorkflow_test.py +0 -33
- naas_abi-1.0.8.dist-info/RECORD +0 -61
- {naas_abi-1.0.8.dist-info → naas_abi-1.0.10.dist-info}/WHEEL +0 -0
- {naas_abi-1.0.8.dist-info → naas_abi-1.0.10.dist-info}/top_level.txt +0 -0
|
@@ -1,553 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import sys
|
|
3
|
-
import threading
|
|
4
|
-
import time
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Any, Optional, Union
|
|
8
|
-
|
|
9
|
-
from langchain_core.messages import ToolMessage
|
|
10
|
-
from langgraph.types import Command
|
|
11
|
-
from naas_abi.apps.terminal_agent.terminal_style import (
|
|
12
|
-
console, # Add console import
|
|
13
|
-
print_image,
|
|
14
|
-
print_tool_response,
|
|
15
|
-
print_tool_usage,
|
|
16
|
-
)
|
|
17
|
-
from naas_abi_core import logger
|
|
18
|
-
from naas_abi_core.services.agent.Agent import Agent
|
|
19
|
-
|
|
20
|
-
# Global variable to track active agent for context-aware conversations
|
|
21
|
-
conversation_file = None
|
|
22
|
-
|
|
23
|
-
# Fixed width for consistent conversation logs (matches typical wide terminal)
|
|
24
|
-
TERMINAL_WIDTH = 77 # Matches the separator length from the user's example
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def init_conversation_file():
|
|
28
|
-
"""Initialize a new conversation file with timestamp"""
|
|
29
|
-
global conversation_file
|
|
30
|
-
|
|
31
|
-
# Create timestamp in format YYYYMMDDTHHMMSS
|
|
32
|
-
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
|
33
|
-
|
|
34
|
-
# Create directory structure
|
|
35
|
-
conversation_dir = Path("storage/datastore/interfaces/terminal_agent")
|
|
36
|
-
conversation_dir.mkdir(parents=True, exist_ok=True)
|
|
37
|
-
|
|
38
|
-
# Create conversation file path
|
|
39
|
-
conversation_file = conversation_dir / f"{timestamp}.txt"
|
|
40
|
-
|
|
41
|
-
# Initialize file with header
|
|
42
|
-
with open(conversation_file, "w", encoding="utf-8") as f:
|
|
43
|
-
f.write(
|
|
44
|
-
f"# ABI Terminal Conversation - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
|
45
|
-
)
|
|
46
|
-
f.write(f"# Session started at: {timestamp}\n")
|
|
47
|
-
f.write("=" * 80 + "\n\n")
|
|
48
|
-
|
|
49
|
-
logger.info(f"💾 Conversation logging to: {conversation_file}")
|
|
50
|
-
return conversation_file
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def save_to_conversation(line: str):
|
|
54
|
-
"""Save exactly what appears in terminal to the conversation file"""
|
|
55
|
-
global conversation_file
|
|
56
|
-
|
|
57
|
-
if conversation_file is None:
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
try:
|
|
61
|
-
with open(conversation_file, "a", encoding="utf-8") as f:
|
|
62
|
-
f.write(line + "\n")
|
|
63
|
-
except Exception as e:
|
|
64
|
-
# Print error to terminal
|
|
65
|
-
print(f"⚠️ Error saving to conversation file: {e}")
|
|
66
|
-
# Try to log the error itself if possible
|
|
67
|
-
try:
|
|
68
|
-
with open(conversation_file, "a", encoding="utf-8") as f:
|
|
69
|
-
f.write(f"⚠️ LOGGING ERROR: {e}\n")
|
|
70
|
-
except Exception:
|
|
71
|
-
pass # If we can't log the error, give up
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def get_input_with_placeholder(prompt="> ", placeholder="Send a message (/? for help)"):
|
|
75
|
-
"""Get user input with a placeholder that disappears when typing starts
|
|
76
|
-
|
|
77
|
-
Supports pasting large text and multiline input.
|
|
78
|
-
Press Enter twice (empty line) to submit multiline input.
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
# Check if input is piped (not interactive terminal)
|
|
82
|
-
if not sys.stdin.isatty():
|
|
83
|
-
# For piped input, use simple input() without fancy terminal handling
|
|
84
|
-
print(f"\n{prompt}", end="", flush=True)
|
|
85
|
-
try:
|
|
86
|
-
return input()
|
|
87
|
-
except: # noqa: E722
|
|
88
|
-
return "/bye"
|
|
89
|
-
|
|
90
|
-
# Show prompt with placeholder
|
|
91
|
-
print(f"\n{prompt}", end="", flush=True)
|
|
92
|
-
|
|
93
|
-
# Show placeholder in grey
|
|
94
|
-
print(f"\033[90m{placeholder}\033[0m", end="", flush=True)
|
|
95
|
-
|
|
96
|
-
# Move cursor back to start of placeholder
|
|
97
|
-
print(f"\033[{len(placeholder)}D", end="", flush=True)
|
|
98
|
-
|
|
99
|
-
lines = []
|
|
100
|
-
first_line = True
|
|
101
|
-
|
|
102
|
-
try:
|
|
103
|
-
while True:
|
|
104
|
-
try:
|
|
105
|
-
if first_line:
|
|
106
|
-
# First line - use the placeholder display
|
|
107
|
-
line = input()
|
|
108
|
-
# Clear the placeholder line
|
|
109
|
-
print(f"\033[A\033[2K{prompt}{line}", flush=True)
|
|
110
|
-
first_line = False
|
|
111
|
-
else:
|
|
112
|
-
# Continuation lines
|
|
113
|
-
line = input("... ")
|
|
114
|
-
|
|
115
|
-
# If line is empty and we have previous content, submit
|
|
116
|
-
if not line.strip() and lines:
|
|
117
|
-
break
|
|
118
|
-
# If line is not empty, add it
|
|
119
|
-
elif line.strip():
|
|
120
|
-
lines.append(line)
|
|
121
|
-
# If first line is empty, return empty (single enter)
|
|
122
|
-
elif not lines:
|
|
123
|
-
return ""
|
|
124
|
-
|
|
125
|
-
except EOFError:
|
|
126
|
-
# Ctrl+D pressed
|
|
127
|
-
break
|
|
128
|
-
|
|
129
|
-
except KeyboardInterrupt:
|
|
130
|
-
print()
|
|
131
|
-
raise
|
|
132
|
-
|
|
133
|
-
# Join all lines with newlines
|
|
134
|
-
result = "\n".join(lines)
|
|
135
|
-
return result
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def on_tool_response(message: Union[str, Command, dict[str, Any], ToolMessage]) -> None:
|
|
139
|
-
try:
|
|
140
|
-
message_content: str = ""
|
|
141
|
-
if isinstance(message, str):
|
|
142
|
-
message_content = message
|
|
143
|
-
elif isinstance(message, dict) and "content" in message:
|
|
144
|
-
message_content = str(message["content"])
|
|
145
|
-
elif isinstance(message, ToolMessage):
|
|
146
|
-
message_content = str(message.content)
|
|
147
|
-
else:
|
|
148
|
-
print("Unknown message type:")
|
|
149
|
-
print(type(message))
|
|
150
|
-
message_content = str(message)
|
|
151
|
-
|
|
152
|
-
print_tool_response(message_content)
|
|
153
|
-
|
|
154
|
-
# Check if the message contains a path to an image file
|
|
155
|
-
if isinstance(message_content, str):
|
|
156
|
-
# Look for image file paths in the message
|
|
157
|
-
words = message_content.split(" ")
|
|
158
|
-
for word in words:
|
|
159
|
-
if any(
|
|
160
|
-
word.lower().endswith(ext)
|
|
161
|
-
for ext in [".png", ".jpg", ".jpeg", ".gif"]
|
|
162
|
-
):
|
|
163
|
-
print_image(word)
|
|
164
|
-
except Exception as e:
|
|
165
|
-
error_msg = f"⚠️ Tool Response Error: {e}"
|
|
166
|
-
print(error_msg)
|
|
167
|
-
# Log the error to conversation file
|
|
168
|
-
save_to_conversation(error_msg)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def on_ai_message(message: Any, agent_name: str) -> None:
|
|
172
|
-
if len(message.content) == 0:
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
print("\r" + " " * 15 + "\r", end="", flush=True)
|
|
176
|
-
|
|
177
|
-
from rich.markdown import Markdown
|
|
178
|
-
|
|
179
|
-
# Filter out think tags and their content
|
|
180
|
-
think_content = re.findall(r"<think>.*?</think>", message.content, flags=re.DOTALL)
|
|
181
|
-
|
|
182
|
-
# Prepare thoughts section for both display and logging
|
|
183
|
-
thoughts_for_log = ""
|
|
184
|
-
if len(think_content) > 0:
|
|
185
|
-
console.print("Thoughts:", style="grey66")
|
|
186
|
-
thoughts_for_log += "Thoughts: \n\n"
|
|
187
|
-
for think in think_content:
|
|
188
|
-
think_text = think.replace("<think>", "").replace("</think>", "").strip()
|
|
189
|
-
console.print(think_text, style="grey66")
|
|
190
|
-
thoughts_for_log += think_text + "\n"
|
|
191
|
-
thoughts_for_log += "\n" # Extra line after thoughts
|
|
192
|
-
|
|
193
|
-
content = re.sub(
|
|
194
|
-
r"<think>.*?</think>", "", message.content, flags=re.DOTALL
|
|
195
|
-
).strip()
|
|
196
|
-
|
|
197
|
-
# Print agent name dynamically using the real intent_target
|
|
198
|
-
|
|
199
|
-
# Use the actual agent name from intent_target, with color coding for readability
|
|
200
|
-
if "abi" in agent_name.lower():
|
|
201
|
-
color = "bold green"
|
|
202
|
-
elif "claude" in agent_name.lower():
|
|
203
|
-
color = "bold dark_orange" # Anthropic's orange, use rich color
|
|
204
|
-
elif "chatgpt" in agent_name.lower():
|
|
205
|
-
color = "bold bright_green" # OpenAI's green
|
|
206
|
-
elif "deepseek" in agent_name.lower():
|
|
207
|
-
color = "bold blue" # DeepSeek's blue
|
|
208
|
-
elif "gemini" in agent_name.lower():
|
|
209
|
-
color = "bold bright_blue" # Google's blue
|
|
210
|
-
elif "gemma" in agent_name.lower():
|
|
211
|
-
color = "bold bright_cyan" # Google's cyan/blue
|
|
212
|
-
elif "grok" in agent_name.lower():
|
|
213
|
-
color = "bold white" # X/Twitter's white
|
|
214
|
-
elif "llama" in agent_name.lower():
|
|
215
|
-
color = "bold bright_blue" # Meta's blue
|
|
216
|
-
elif "mistral" in agent_name.lower():
|
|
217
|
-
color = "bold dark_orange" # Mistral's orange, use rich color
|
|
218
|
-
elif "perplexity" in agent_name.lower():
|
|
219
|
-
color = "bold white" # Perplexity's white
|
|
220
|
-
elif "qwen" in agent_name.lower():
|
|
221
|
-
color = "bold bright_cyan" # Alibaba's cyan
|
|
222
|
-
else:
|
|
223
|
-
color = "bold magenta"
|
|
224
|
-
|
|
225
|
-
# Format exactly as it appears in terminal
|
|
226
|
-
agent_message_line = f"{agent_name}: {content}"
|
|
227
|
-
|
|
228
|
-
# Display the real agent name
|
|
229
|
-
console.print(f"{agent_name}:", style=color, end=" ")
|
|
230
|
-
|
|
231
|
-
md = Markdown(content)
|
|
232
|
-
console.print(md, style="bright_white")
|
|
233
|
-
console.print("─" * console.width, style="dim")
|
|
234
|
-
print() # Add spacing after separator
|
|
235
|
-
|
|
236
|
-
# Save exact terminal format to conversation file (with fixed width) including thoughts
|
|
237
|
-
if thoughts_for_log:
|
|
238
|
-
save_to_conversation(thoughts_for_log)
|
|
239
|
-
save_to_conversation(agent_message_line)
|
|
240
|
-
save_to_conversation("─" * TERMINAL_WIDTH)
|
|
241
|
-
save_to_conversation("") # Empty line
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def run_agent(agent: Agent):
|
|
245
|
-
# Initialize conversation logging
|
|
246
|
-
init_conversation_file()
|
|
247
|
-
|
|
248
|
-
# All agents
|
|
249
|
-
all_agents = agent.agents + [agent]
|
|
250
|
-
|
|
251
|
-
# Show greeting when truly ready for input - instant like responses
|
|
252
|
-
greeting_line = f"{agent.name}: Hello, World!"
|
|
253
|
-
|
|
254
|
-
console.print(f"{agent.name}:", style="bold green", end=" ")
|
|
255
|
-
console.print("Hello, World!", style="bright_white")
|
|
256
|
-
console.print("─" * console.width, style="dim")
|
|
257
|
-
print() # Add spacing after separator
|
|
258
|
-
|
|
259
|
-
# Save exact terminal output to conversation file (with fixed width)
|
|
260
|
-
save_to_conversation(greeting_line)
|
|
261
|
-
save_to_conversation("─" * TERMINAL_WIDTH)
|
|
262
|
-
save_to_conversation("") # Empty line
|
|
263
|
-
|
|
264
|
-
# Just start chatting naturally - like the screenshot
|
|
265
|
-
while True:
|
|
266
|
-
# Get current active agent and its model
|
|
267
|
-
current_active_agent = agent.state.current_active_agent
|
|
268
|
-
if current_active_agent is None:
|
|
269
|
-
current_active_agent = agent.name
|
|
270
|
-
|
|
271
|
-
model_info = "unknown"
|
|
272
|
-
|
|
273
|
-
# Find the active agent in our agents list
|
|
274
|
-
import pydash as _
|
|
275
|
-
|
|
276
|
-
current_agent = _.find(
|
|
277
|
-
all_agents,
|
|
278
|
-
lambda a: a.name.lower() == current_active_agent.lower()
|
|
279
|
-
if a.name is not None
|
|
280
|
-
else False,
|
|
281
|
-
)
|
|
282
|
-
if current_agent:
|
|
283
|
-
if hasattr(current_agent.chat_model, "model_name"):
|
|
284
|
-
model_info = current_agent.chat_model.model_name
|
|
285
|
-
elif hasattr(current_agent.chat_model, "model"):
|
|
286
|
-
model_info = current_agent.chat_model.model
|
|
287
|
-
|
|
288
|
-
# Create clean status line showing active agent with model info
|
|
289
|
-
if current_active_agent:
|
|
290
|
-
status_line = f"Active: {current_active_agent} (model: {model_info})"
|
|
291
|
-
else:
|
|
292
|
-
status_line = "No active agent"
|
|
293
|
-
|
|
294
|
-
# Print the status line before the input prompt
|
|
295
|
-
console.print(status_line, style="dim")
|
|
296
|
-
|
|
297
|
-
# Save status line to conversation file
|
|
298
|
-
save_to_conversation(status_line)
|
|
299
|
-
save_to_conversation("") # Empty line for spacing
|
|
300
|
-
|
|
301
|
-
user_input = get_input_with_placeholder()
|
|
302
|
-
|
|
303
|
-
# Clean the input and check for exit commands
|
|
304
|
-
clean_input = user_input.strip().lower()
|
|
305
|
-
|
|
306
|
-
# Skip empty input
|
|
307
|
-
if not user_input.strip():
|
|
308
|
-
continue
|
|
309
|
-
|
|
310
|
-
# Display user message with color coding and separator (except for commands)
|
|
311
|
-
if not clean_input.startswith("/") and clean_input not in [
|
|
312
|
-
"exit",
|
|
313
|
-
"quit",
|
|
314
|
-
"reset",
|
|
315
|
-
]:
|
|
316
|
-
# Format exactly as it appears in terminal
|
|
317
|
-
user_message_line = f"You: {user_input.strip()}"
|
|
318
|
-
|
|
319
|
-
# Show formatted message in chat history (same format as Abi)
|
|
320
|
-
console.print("You:", style="bold cyan", end=" ")
|
|
321
|
-
console.print(user_input.strip(), style="bright_white")
|
|
322
|
-
console.print("─" * console.width, style="dim")
|
|
323
|
-
print() # Add spacing after separator
|
|
324
|
-
|
|
325
|
-
# Save exact terminal format to conversation file (with fixed width)
|
|
326
|
-
save_to_conversation(user_message_line)
|
|
327
|
-
save_to_conversation("─" * TERMINAL_WIDTH)
|
|
328
|
-
save_to_conversation("") # Empty line
|
|
329
|
-
|
|
330
|
-
if clean_input in ["exit", "/exit", "/bye", "quit", "/quit"]:
|
|
331
|
-
# Save session end to conversation file
|
|
332
|
-
save_to_conversation("") # Empty line
|
|
333
|
-
save_to_conversation("# Session ended by user")
|
|
334
|
-
print(f"\n👋 See you later! Conversation saved to: {conversation_file}")
|
|
335
|
-
return
|
|
336
|
-
elif clean_input in ["reset", "/reset"]:
|
|
337
|
-
agent.reset()
|
|
338
|
-
print("🔄 Starting fresh...")
|
|
339
|
-
continue
|
|
340
|
-
elif clean_input == "/?":
|
|
341
|
-
print("\n📋 Available Commands:")
|
|
342
|
-
print(" /? - Show this help")
|
|
343
|
-
print(" /reset - Start fresh conversation")
|
|
344
|
-
print(" /bye or /exit - End conversation")
|
|
345
|
-
print("\n📝 Input Tips:")
|
|
346
|
-
print(" • Paste large text directly - it will be captured properly")
|
|
347
|
-
print(
|
|
348
|
-
" • For multiline input: keep typing on new lines, press Enter twice to submit"
|
|
349
|
-
)
|
|
350
|
-
print(
|
|
351
|
-
" • Single Enter on first line submits immediately (for quick messages)"
|
|
352
|
-
)
|
|
353
|
-
print("\n🤖 Available AI Agents:")
|
|
354
|
-
print(" Cloud Agents:")
|
|
355
|
-
cloud_agents = [
|
|
356
|
-
"@gemini",
|
|
357
|
-
"@claude",
|
|
358
|
-
"@mistral",
|
|
359
|
-
"@chatgpt",
|
|
360
|
-
"@perplexity",
|
|
361
|
-
"@llama",
|
|
362
|
-
]
|
|
363
|
-
print(f" {' '.join(cloud_agents)}")
|
|
364
|
-
print(" Local Agents (Privacy-focused):")
|
|
365
|
-
local_agents = ["@qwen", "@deepseek", "@gemma"]
|
|
366
|
-
print(f" {' '.join(local_agents)}")
|
|
367
|
-
print("\n💡 Usage: Type @agent or 'ask agent' to switch agents")
|
|
368
|
-
print(
|
|
369
|
-
" Example: '@qwen help me code' or 'ask deepseek solve this math problem'"
|
|
370
|
-
)
|
|
371
|
-
continue
|
|
372
|
-
|
|
373
|
-
# Matrix-style animated loading indicator
|
|
374
|
-
import threading
|
|
375
|
-
import time
|
|
376
|
-
|
|
377
|
-
# Animation control
|
|
378
|
-
loading = True
|
|
379
|
-
|
|
380
|
-
def matrix_loader():
|
|
381
|
-
i = 0
|
|
382
|
-
while loading:
|
|
383
|
-
dots_count = i % 4 # 0, 1, 2, 3, then repeat
|
|
384
|
-
if dots_count == 0:
|
|
385
|
-
dots = " " # No dots, just spaces
|
|
386
|
-
else:
|
|
387
|
-
dots = "." * dots_count + " " * (
|
|
388
|
-
3 - dots_count
|
|
389
|
-
) # Pad to 3 char width
|
|
390
|
-
print(f"\r\033[92mResponding{dots}\033[0m", end="", flush=True)
|
|
391
|
-
time.sleep(0.5)
|
|
392
|
-
i += 1
|
|
393
|
-
|
|
394
|
-
# Start the animation in a separate thread
|
|
395
|
-
loader_thread = threading.Thread(target=matrix_loader)
|
|
396
|
-
loader_thread.start()
|
|
397
|
-
|
|
398
|
-
# # Update the agent's shared state with current active agent info
|
|
399
|
-
# if hasattr(agent, '_state') and hasattr(agent._state, 'set_current_active_agent'):
|
|
400
|
-
# agent._state.set_current_active_agent(current_active_agent)
|
|
401
|
-
|
|
402
|
-
# Get the response with real streaming support
|
|
403
|
-
try:
|
|
404
|
-
# Stop the animation first
|
|
405
|
-
loading = False
|
|
406
|
-
loader_thread.join()
|
|
407
|
-
print("\r" + " " * 15 + "\r", end="", flush=True)
|
|
408
|
-
|
|
409
|
-
# Use the agent system properly
|
|
410
|
-
agent.invoke(user_input)
|
|
411
|
-
|
|
412
|
-
except Exception as e:
|
|
413
|
-
# Stop the animation if still running
|
|
414
|
-
loading = False
|
|
415
|
-
if "loader_thread" in locals():
|
|
416
|
-
loader_thread.join()
|
|
417
|
-
|
|
418
|
-
# Clear the loading line
|
|
419
|
-
print("\r" + " " * 15 + "\r", end="", flush=True)
|
|
420
|
-
|
|
421
|
-
# Display and log the error
|
|
422
|
-
error_msg = f"❌ Agent Error: {e}"
|
|
423
|
-
console.print(error_msg, style="bold red")
|
|
424
|
-
save_to_conversation(error_msg)
|
|
425
|
-
|
|
426
|
-
# Log the full traceback for debugging
|
|
427
|
-
import traceback
|
|
428
|
-
|
|
429
|
-
traceback_msg = f"Full traceback:\n{traceback.format_exc()}"
|
|
430
|
-
console.print(traceback_msg, style="dim red")
|
|
431
|
-
save_to_conversation(traceback_msg)
|
|
432
|
-
save_to_conversation("─" * TERMINAL_WIDTH)
|
|
433
|
-
save_to_conversation("") # Empty line
|
|
434
|
-
continue # Continue conversation instead of crashing
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
def load_agent(agent_class: str) -> Agent | None:
|
|
438
|
-
from naas_abi import modules
|
|
439
|
-
|
|
440
|
-
if agent_class is None:
|
|
441
|
-
print(
|
|
442
|
-
"No agent class provided. Please set the AGENT_CLASS environment variable."
|
|
443
|
-
)
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
for module in modules:
|
|
447
|
-
for agent in module.agents:
|
|
448
|
-
if agent.__class__.__name__ == agent_class:
|
|
449
|
-
agent.on_tool_usage(lambda message: print_tool_usage(message))
|
|
450
|
-
agent.on_tool_response(on_tool_response)
|
|
451
|
-
agent.on_ai_message(on_ai_message)
|
|
452
|
-
|
|
453
|
-
return agent
|
|
454
|
-
|
|
455
|
-
return None
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
def list_available_agents():
|
|
459
|
-
from naas_abi import modules
|
|
460
|
-
|
|
461
|
-
print("\nAvailable agents:\n")
|
|
462
|
-
agents = []
|
|
463
|
-
for module in modules:
|
|
464
|
-
for agent in module.agents:
|
|
465
|
-
agents.append(agent.__class__.__name__)
|
|
466
|
-
|
|
467
|
-
# Sort the agents alphabetically
|
|
468
|
-
agents.sort()
|
|
469
|
-
|
|
470
|
-
# Print the agents
|
|
471
|
-
for agent in agents:
|
|
472
|
-
print(f" - {agent}")
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
class ConsoleLoader:
|
|
476
|
-
def start(self, message: str):
|
|
477
|
-
# Matrix-style startup animation
|
|
478
|
-
self.loading = True
|
|
479
|
-
|
|
480
|
-
def startup_loader():
|
|
481
|
-
i = 0
|
|
482
|
-
while self.loading:
|
|
483
|
-
dots_count = i % 4 # 0, 1, 2, 3, then repeat
|
|
484
|
-
if dots_count == 0:
|
|
485
|
-
dots = " " # No dots, just spaces
|
|
486
|
-
else:
|
|
487
|
-
dots = "." * dots_count + " " * (
|
|
488
|
-
3 - dots_count
|
|
489
|
-
) # Pad to 3 char width
|
|
490
|
-
print(f"\r\033[92m{message}{dots}\033[0m", end="", flush=True)
|
|
491
|
-
time.sleep(0.5)
|
|
492
|
-
i += 1
|
|
493
|
-
|
|
494
|
-
# Start the animation
|
|
495
|
-
self.loader_thread = threading.Thread(target=startup_loader)
|
|
496
|
-
self.loader_thread.daemon = True # Make thread die when main thread exits
|
|
497
|
-
self.loader_thread.start()
|
|
498
|
-
|
|
499
|
-
def stop(self):
|
|
500
|
-
self.loading = False
|
|
501
|
-
self.loader_thread.join()
|
|
502
|
-
|
|
503
|
-
# Clear the loading line
|
|
504
|
-
print("\r" + " " * 20 + "\r", end="", flush=True)
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
def generic_run_agent(agent_class: Optional[str] = None) -> None:
|
|
508
|
-
"""Run an agent dynamically loaded from the src/modules directory.
|
|
509
|
-
|
|
510
|
-
This method provides a generic way to run any agent that is loaded from the modules
|
|
511
|
-
directory, eliminating the need to create individual run functions for each agent.
|
|
512
|
-
The agents are automatically discovered and loaded through the module system.
|
|
513
|
-
|
|
514
|
-
Args:
|
|
515
|
-
agent_class (str, optional): The class name of the agent to run. If None, will
|
|
516
|
-
print an error message. The class name should match exactly with the agent's
|
|
517
|
-
class name in the modules.
|
|
518
|
-
|
|
519
|
-
Example:
|
|
520
|
-
>>> generic_run_agent("AbiAgent") # Runs the AbiAgent agent
|
|
521
|
-
>>> generic_run_agent("ContentAssistant") # Runs the ContentAssistant agent
|
|
522
|
-
|
|
523
|
-
Note:
|
|
524
|
-
This replaces the need for individual run_*_agent() functions by dynamically
|
|
525
|
-
finding and running the requested agent from the loaded modules. The agent
|
|
526
|
-
must be properly registered in a module under src/modules for this to work.
|
|
527
|
-
"""
|
|
528
|
-
|
|
529
|
-
assert agent_class is not None, "Agent class is required"
|
|
530
|
-
|
|
531
|
-
agent = load_agent(agent_class)
|
|
532
|
-
|
|
533
|
-
if agent is None:
|
|
534
|
-
print(f"Agent {agent_class} not found")
|
|
535
|
-
list_available_agents()
|
|
536
|
-
|
|
537
|
-
return
|
|
538
|
-
|
|
539
|
-
run_agent(agent)
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if __name__ == "__main__":
|
|
543
|
-
import sys
|
|
544
|
-
|
|
545
|
-
# Get the function name from command line argument
|
|
546
|
-
if len(sys.argv) > 1:
|
|
547
|
-
function_name = sys.argv[1]
|
|
548
|
-
if function_name in globals():
|
|
549
|
-
globals()[function_name](*sys.argv[2:])
|
|
550
|
-
else:
|
|
551
|
-
print(f"Function {function_name} not found")
|
|
552
|
-
else:
|
|
553
|
-
print("Please specify a function to run")
|