naas-abi 1.0.8__py3-none-any.whl → 1.0.9__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.
Files changed (36) hide show
  1. naas_abi/__init__.py +16 -1
  2. naas_abi/agents/AbiAgent.py +18 -14
  3. naas_abi/agents/EntitytoSPARQLAgent.py +22 -17
  4. naas_abi/agents/KnowledgeGraphBuilderAgent.py +17 -15
  5. naas_abi/agents/OntologyEngineerAgent.py +4 -15
  6. naas_abi/apps/oxigraph_admin/main.py +7 -4
  7. naas_abi/apps/sparql_terminal/main.py +7 -4
  8. naas_abi/pipelines/AIAgentOntologyGenerationPipeline_test.py +4 -3
  9. naas_abi/pipelines/AddIndividualPipeline_test.py +10 -7
  10. naas_abi/pipelines/InsertDataSPARQLPipeline.py +6 -4
  11. naas_abi/pipelines/InsertDataSPARQLPipeline_test.py +9 -6
  12. naas_abi/pipelines/MergeIndividualsPipeline.py +6 -2
  13. naas_abi/pipelines/MergeIndividualsPipeline_test.py +10 -9
  14. naas_abi/pipelines/RemoveIndividualPipeline.py +6 -2
  15. naas_abi/pipelines/RemoveIndividualPipeline_test.py +8 -8
  16. naas_abi/pipelines/UpdateDataPropertyPipeline.py +6 -2
  17. naas_abi/workflows/GetObjectPropertiesFromClassWorkflow_test.py +7 -2
  18. naas_abi/workflows/GetSubjectGraphWorkflow_test.py +8 -3
  19. naas_abi/workflows/SearchIndividualWorkflow_test.py +6 -6
  20. {naas_abi-1.0.8.dist-info → naas_abi-1.0.9.dist-info}/METADATA +2 -2
  21. naas_abi-1.0.9.dist-info/RECORD +49 -0
  22. naas_abi/apps/terminal_agent/main.py +0 -553
  23. naas_abi/apps/terminal_agent/terminal_style.py +0 -175
  24. naas_abi/mappings.py +0 -83
  25. naas_abi/triggers.py +0 -131
  26. naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow.py +0 -210
  27. naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow_test.py +0 -78
  28. naas_abi/workflows/CreateClassOntologyYamlWorkflow.py +0 -214
  29. naas_abi/workflows/CreateClassOntologyYamlWorkflow_test.py +0 -65
  30. naas_abi/workflows/CreateIndividualOntologyYamlWorkflow.py +0 -183
  31. naas_abi/workflows/CreateIndividualOntologyYamlWorkflow_test.py +0 -86
  32. naas_abi/workflows/ExportGraphInstancesToExcelWorkflow.py +0 -450
  33. naas_abi/workflows/ExportGraphInstancesToExcelWorkflow_test.py +0 -33
  34. naas_abi-1.0.8.dist-info/RECORD +0 -61
  35. {naas_abi-1.0.8.dist-info → naas_abi-1.0.9.dist-info}/WHEEL +0 -0
  36. {naas_abi-1.0.8.dist-info → naas_abi-1.0.9.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")