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