trustgraph-cli 1.4.20__tar.gz → 1.6.5__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.
Potentially problematic release.
This version of trustgraph-cli might be problematic. Click here for more details.
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/PKG-INFO +2 -2
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/pyproject.toml +2 -1
- trustgraph_cli-1.6.5/trustgraph/cli/dump_queues.py +362 -0
- trustgraph_cli-1.6.5/trustgraph/cli/invoke_agent.py +315 -0
- trustgraph_cli-1.6.5/trustgraph/cli/invoke_document_rag.py +170 -0
- trustgraph_cli-1.6.5/trustgraph/cli/invoke_graph_rag.py +208 -0
- trustgraph_cli-1.6.5/trustgraph/cli/invoke_llm.py +121 -0
- trustgraph_cli-1.6.5/trustgraph/cli/invoke_prompt.py +151 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/set_mcp_tool.py +25 -7
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/set_tool.py +1 -1
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_mcp_tools.py +6 -0
- trustgraph_cli-1.6.5/trustgraph/cli_version.py +1 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph_cli.egg-info/PKG-INFO +2 -2
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph_cli.egg-info/SOURCES.txt +1 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph_cli.egg-info/entry_points.txt +1 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph_cli.egg-info/requires.txt +1 -1
- trustgraph_cli-1.4.20/trustgraph/cli/invoke_agent.py +0 -187
- trustgraph_cli-1.4.20/trustgraph/cli/invoke_document_rag.py +0 -86
- trustgraph_cli-1.4.20/trustgraph/cli/invoke_graph_rag.py +0 -115
- trustgraph_cli-1.4.20/trustgraph/cli/invoke_llm.py +0 -68
- trustgraph_cli-1.4.20/trustgraph/cli/invoke_prompt.py +0 -88
- trustgraph_cli-1.4.20/trustgraph/cli_version.py +0 -1
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/README.md +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/setup.cfg +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/__init__.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/add_library_document.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/delete_collection.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/delete_config_item.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/delete_flow_class.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/delete_kg_core.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/delete_mcp_tool.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/delete_tool.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/dump_msgpack.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/get_config_item.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/get_flow_class.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/get_kg_core.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/graph_to_turtle.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/init_pulsar_manager.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/init_trustgraph.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/invoke_mcp_tool.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/invoke_nlp_query.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/invoke_objects_query.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/invoke_structured_query.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/list_collections.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/list_config_items.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/load_doc_embeds.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/load_kg_core.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/load_knowledge.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/load_pdf.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/load_sample_documents.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/load_structured_data.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/load_text.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/load_turtle.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/put_config_item.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/put_flow_class.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/put_kg_core.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/remove_library_document.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/save_doc_embeds.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/set_collection.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/set_prompt.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/set_token_costs.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_config.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_flow_classes.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_flow_state.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_flows.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_graph.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_kg_cores.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_library_documents.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_library_processing.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_parameter_types.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_processor_state.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_prompts.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_token_costs.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_token_rate.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/show_tools.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/start_flow.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/start_library_processing.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/stop_flow.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/stop_library_processing.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph/cli/unload_kg_core.py +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph_cli.egg-info/dependency_links.txt +0 -0
- {trustgraph_cli-1.4.20 → trustgraph_cli-1.6.5}/trustgraph_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trustgraph-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.5
|
|
4
4
|
Summary: TrustGraph provides a means to run a pipeline of flexible AI processing components in a flexible means to achieve a processing pipeline.
|
|
5
5
|
Author-email: "trustgraph.ai" <security@trustgraph.ai>
|
|
6
6
|
Project-URL: Homepage, https://github.com/trustgraph-ai/trustgraph
|
|
@@ -8,7 +8,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
8
8
|
Classifier: Operating System :: OS Independent
|
|
9
9
|
Requires-Python: >=3.8
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
|
-
Requires-Dist: trustgraph-base<1.
|
|
11
|
+
Requires-Dist: trustgraph-base<1.7,>=1.6
|
|
12
12
|
Requires-Dist: requests
|
|
13
13
|
Requires-Dist: pulsar-client
|
|
14
14
|
Requires-Dist: aiohttp
|
|
@@ -10,7 +10,7 @@ description = "TrustGraph provides a means to run a pipeline of flexible AI proc
|
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.8"
|
|
12
12
|
dependencies = [
|
|
13
|
-
"trustgraph-base>=1.
|
|
13
|
+
"trustgraph-base>=1.6,<1.7",
|
|
14
14
|
"requests",
|
|
15
15
|
"pulsar-client",
|
|
16
16
|
"aiohttp",
|
|
@@ -34,6 +34,7 @@ tg-delete-mcp-tool = "trustgraph.cli.delete_mcp_tool:main"
|
|
|
34
34
|
tg-delete-kg-core = "trustgraph.cli.delete_kg_core:main"
|
|
35
35
|
tg-delete-tool = "trustgraph.cli.delete_tool:main"
|
|
36
36
|
tg-dump-msgpack = "trustgraph.cli.dump_msgpack:main"
|
|
37
|
+
tg-dump-queues = "trustgraph.cli.dump_queues:main"
|
|
37
38
|
tg-get-flow-class = "trustgraph.cli.get_flow_class:main"
|
|
38
39
|
tg-get-kg-core = "trustgraph.cli.get_kg_core:main"
|
|
39
40
|
tg-graph-to-turtle = "trustgraph.cli.graph_to_turtle:main"
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Multi-queue Pulsar message dumper for debugging TrustGraph message flows.
|
|
3
|
+
|
|
4
|
+
This utility monitors multiple Pulsar queues simultaneously and logs all messages
|
|
5
|
+
to a file with timestamps and pretty-printed formatting. Useful for debugging
|
|
6
|
+
message flows, diagnosing stuck services, and understanding system behavior.
|
|
7
|
+
|
|
8
|
+
Uses TrustGraph's Subscriber abstraction for future-proof pub/sub compatibility.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import pulsar
|
|
12
|
+
from pulsar.schema import BytesSchema
|
|
13
|
+
import sys
|
|
14
|
+
import json
|
|
15
|
+
import asyncio
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
import argparse
|
|
18
|
+
|
|
19
|
+
from trustgraph.base.subscriber import Subscriber
|
|
20
|
+
|
|
21
|
+
def format_message(queue_name, msg):
|
|
22
|
+
"""Format a message with timestamp and queue name."""
|
|
23
|
+
timestamp = datetime.now().isoformat()
|
|
24
|
+
|
|
25
|
+
# Try to parse as JSON and pretty-print
|
|
26
|
+
try:
|
|
27
|
+
# Handle both Message objects and raw bytes
|
|
28
|
+
if hasattr(msg, 'value'):
|
|
29
|
+
# Message object with .value() method
|
|
30
|
+
value = msg.value()
|
|
31
|
+
else:
|
|
32
|
+
# Raw bytes from schema-less subscription
|
|
33
|
+
value = msg
|
|
34
|
+
|
|
35
|
+
# If it's bytes, decode it
|
|
36
|
+
if isinstance(value, bytes):
|
|
37
|
+
value = value.decode('utf-8')
|
|
38
|
+
|
|
39
|
+
# If it's a string, try to parse as JSON
|
|
40
|
+
if isinstance(value, str):
|
|
41
|
+
try:
|
|
42
|
+
parsed = json.loads(value)
|
|
43
|
+
body = json.dumps(parsed, indent=2)
|
|
44
|
+
except (json.JSONDecodeError, TypeError):
|
|
45
|
+
body = value
|
|
46
|
+
else:
|
|
47
|
+
# Try to convert to dict for pretty printing
|
|
48
|
+
try:
|
|
49
|
+
# Pulsar schema objects have __dict__ or similar
|
|
50
|
+
if hasattr(value, '__dict__'):
|
|
51
|
+
parsed = {k: v for k, v in value.__dict__.items()
|
|
52
|
+
if not k.startswith('_')}
|
|
53
|
+
else:
|
|
54
|
+
parsed = str(value)
|
|
55
|
+
body = json.dumps(parsed, indent=2, default=str)
|
|
56
|
+
except (TypeError, AttributeError):
|
|
57
|
+
body = str(value)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
body = f"<Error formatting message: {e}>\n{str(msg)}"
|
|
61
|
+
|
|
62
|
+
# Format the output
|
|
63
|
+
header = f"\n{'='*80}\n[{timestamp}] Queue: {queue_name}\n{'='*80}\n"
|
|
64
|
+
return header + body + "\n"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def monitor_queue(subscriber, queue_name, central_queue, monitor_id, shutdown_event):
|
|
68
|
+
"""
|
|
69
|
+
Monitor a single queue via Subscriber and forward messages to central queue.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
subscriber: Subscriber instance for this queue
|
|
73
|
+
queue_name: Name of the queue (for logging)
|
|
74
|
+
central_queue: asyncio.Queue to forward messages to
|
|
75
|
+
monitor_id: Unique ID for this monitor's subscription
|
|
76
|
+
shutdown_event: asyncio.Event to signal shutdown
|
|
77
|
+
"""
|
|
78
|
+
msg_queue = None
|
|
79
|
+
try:
|
|
80
|
+
# Subscribe to all messages from this Subscriber
|
|
81
|
+
msg_queue = await subscriber.subscribe_all(monitor_id)
|
|
82
|
+
|
|
83
|
+
while not shutdown_event.is_set():
|
|
84
|
+
try:
|
|
85
|
+
# Read from Subscriber's internal queue with timeout
|
|
86
|
+
msg = await asyncio.wait_for(msg_queue.get(), timeout=0.5)
|
|
87
|
+
timestamp = datetime.now()
|
|
88
|
+
formatted = format_message(queue_name, msg)
|
|
89
|
+
|
|
90
|
+
# Forward to central queue for writing
|
|
91
|
+
await central_queue.put((timestamp, queue_name, formatted))
|
|
92
|
+
except asyncio.TimeoutError:
|
|
93
|
+
# No message, check shutdown flag again
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
if not shutdown_event.is_set():
|
|
98
|
+
error_msg = f"\n{'='*80}\n[{datetime.now().isoformat()}] ERROR in monitor for {queue_name}\n{'='*80}\n{e}\n"
|
|
99
|
+
await central_queue.put((datetime.now(), queue_name, error_msg))
|
|
100
|
+
finally:
|
|
101
|
+
# Clean unsubscribe
|
|
102
|
+
if msg_queue is not None:
|
|
103
|
+
try:
|
|
104
|
+
await subscriber.unsubscribe_all(monitor_id)
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def log_writer(central_queue, file_handle, shutdown_event, console_output=True):
|
|
110
|
+
"""
|
|
111
|
+
Write messages from central queue to file.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
central_queue: asyncio.Queue containing (timestamp, queue_name, formatted_msg) tuples
|
|
115
|
+
file_handle: Open file handle to write to
|
|
116
|
+
shutdown_event: asyncio.Event to signal shutdown
|
|
117
|
+
console_output: Whether to print abbreviated messages to console
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
while not shutdown_event.is_set():
|
|
121
|
+
try:
|
|
122
|
+
# Wait for messages with timeout to check shutdown flag
|
|
123
|
+
timestamp, queue_name, formatted_msg = await asyncio.wait_for(
|
|
124
|
+
central_queue.get(), timeout=0.5
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Write to file
|
|
128
|
+
file_handle.write(formatted_msg)
|
|
129
|
+
file_handle.flush()
|
|
130
|
+
|
|
131
|
+
# Print abbreviated message to console
|
|
132
|
+
if console_output:
|
|
133
|
+
time_str = timestamp.strftime('%H:%M:%S')
|
|
134
|
+
print(f"[{time_str}] {queue_name}: Message received")
|
|
135
|
+
except asyncio.TimeoutError:
|
|
136
|
+
# No message, check shutdown flag again
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
finally:
|
|
140
|
+
# Flush remaining messages after shutdown
|
|
141
|
+
while not central_queue.empty():
|
|
142
|
+
try:
|
|
143
|
+
timestamp, queue_name, formatted_msg = central_queue.get_nowait()
|
|
144
|
+
file_handle.write(formatted_msg)
|
|
145
|
+
file_handle.flush()
|
|
146
|
+
except asyncio.QueueEmpty:
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def async_main(queues, output_file, pulsar_host, listener_name, subscriber_name, append_mode):
|
|
151
|
+
"""
|
|
152
|
+
Main async function to monitor multiple queues concurrently.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
queues: List of queue names to monitor
|
|
156
|
+
output_file: Path to output file
|
|
157
|
+
pulsar_host: Pulsar connection URL
|
|
158
|
+
listener_name: Pulsar listener name
|
|
159
|
+
subscriber_name: Base name for subscribers
|
|
160
|
+
append_mode: Whether to append to existing file
|
|
161
|
+
"""
|
|
162
|
+
print(f"TrustGraph Queue Dumper")
|
|
163
|
+
print(f"Monitoring {len(queues)} queue(s):")
|
|
164
|
+
for q in queues:
|
|
165
|
+
print(f" - {q}")
|
|
166
|
+
print(f"Output file: {output_file}")
|
|
167
|
+
print(f"Mode: {'append' if append_mode else 'overwrite'}")
|
|
168
|
+
print(f"Press Ctrl+C to stop\n")
|
|
169
|
+
|
|
170
|
+
# Connect to Pulsar
|
|
171
|
+
try:
|
|
172
|
+
client = pulsar.Client(pulsar_host, listener_name=listener_name)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
print(f"Error connecting to Pulsar at {pulsar_host}: {e}", file=sys.stderr)
|
|
175
|
+
sys.exit(1)
|
|
176
|
+
|
|
177
|
+
# Create Subscribers and central queue
|
|
178
|
+
central_queue = asyncio.Queue()
|
|
179
|
+
subscribers = []
|
|
180
|
+
|
|
181
|
+
for queue_name in queues:
|
|
182
|
+
try:
|
|
183
|
+
sub = Subscriber(
|
|
184
|
+
client=client,
|
|
185
|
+
topic=queue_name,
|
|
186
|
+
subscription=subscriber_name,
|
|
187
|
+
consumer_name=f"{subscriber_name}-{queue_name}",
|
|
188
|
+
schema=None, # No schema - accept any message type
|
|
189
|
+
)
|
|
190
|
+
await sub.start()
|
|
191
|
+
subscribers.append((queue_name, sub))
|
|
192
|
+
print(f"✓ Subscribed to: {queue_name}")
|
|
193
|
+
except Exception as e:
|
|
194
|
+
print(f"✗ Error subscribing to {queue_name}: {e}", file=sys.stderr)
|
|
195
|
+
|
|
196
|
+
if not subscribers:
|
|
197
|
+
print("\nNo subscribers created. Exiting.", file=sys.stderr)
|
|
198
|
+
client.close()
|
|
199
|
+
sys.exit(1)
|
|
200
|
+
|
|
201
|
+
print(f"\nListening for messages...\n")
|
|
202
|
+
|
|
203
|
+
# Open output file
|
|
204
|
+
mode = 'a' if append_mode else 'w'
|
|
205
|
+
try:
|
|
206
|
+
with open(output_file, mode) as f:
|
|
207
|
+
f.write(f"\n{'#'*80}\n")
|
|
208
|
+
f.write(f"# Session started: {datetime.now().isoformat()}\n")
|
|
209
|
+
f.write(f"# Monitoring queues: {', '.join(queues)}\n")
|
|
210
|
+
f.write(f"{'#'*80}\n")
|
|
211
|
+
f.flush()
|
|
212
|
+
|
|
213
|
+
# Create shutdown event for clean coordination
|
|
214
|
+
shutdown_event = asyncio.Event()
|
|
215
|
+
|
|
216
|
+
# Start monitoring tasks
|
|
217
|
+
tasks = []
|
|
218
|
+
try:
|
|
219
|
+
# Create one monitor task per subscriber
|
|
220
|
+
for queue_name, sub in subscribers:
|
|
221
|
+
task = asyncio.create_task(
|
|
222
|
+
monitor_queue(sub, queue_name, central_queue, "logger", shutdown_event)
|
|
223
|
+
)
|
|
224
|
+
tasks.append(task)
|
|
225
|
+
|
|
226
|
+
# Create single writer task
|
|
227
|
+
writer_task = asyncio.create_task(
|
|
228
|
+
log_writer(central_queue, f, shutdown_event)
|
|
229
|
+
)
|
|
230
|
+
tasks.append(writer_task)
|
|
231
|
+
|
|
232
|
+
# Wait for all tasks (they check shutdown_event)
|
|
233
|
+
await asyncio.gather(*tasks)
|
|
234
|
+
|
|
235
|
+
except KeyboardInterrupt:
|
|
236
|
+
print("\n\nStopping...")
|
|
237
|
+
finally:
|
|
238
|
+
# Signal shutdown to all tasks
|
|
239
|
+
shutdown_event.set()
|
|
240
|
+
|
|
241
|
+
# Wait for tasks to finish cleanly (with timeout)
|
|
242
|
+
try:
|
|
243
|
+
await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=2.0)
|
|
244
|
+
except asyncio.TimeoutError:
|
|
245
|
+
print("Warning: Shutdown timeout", file=sys.stderr)
|
|
246
|
+
|
|
247
|
+
# Write session end marker
|
|
248
|
+
f.write(f"\n{'#'*80}\n")
|
|
249
|
+
f.write(f"# Session ended: {datetime.now().isoformat()}\n")
|
|
250
|
+
f.write(f"{'#'*80}\n")
|
|
251
|
+
|
|
252
|
+
except IOError as e:
|
|
253
|
+
print(f"Error writing to {output_file}: {e}", file=sys.stderr)
|
|
254
|
+
sys.exit(1)
|
|
255
|
+
finally:
|
|
256
|
+
# Clean shutdown of Subscribers
|
|
257
|
+
for _, sub in subscribers:
|
|
258
|
+
await sub.stop()
|
|
259
|
+
client.close()
|
|
260
|
+
|
|
261
|
+
print(f"\nMessages logged to: {output_file}")
|
|
262
|
+
|
|
263
|
+
def main():
|
|
264
|
+
parser = argparse.ArgumentParser(
|
|
265
|
+
prog='tg-dump-queues',
|
|
266
|
+
description='Monitor and dump messages from multiple Pulsar queues',
|
|
267
|
+
epilog="""
|
|
268
|
+
Examples:
|
|
269
|
+
# Monitor agent and prompt queues
|
|
270
|
+
tg-dump-queues non-persistent://tg/request/agent:default \\
|
|
271
|
+
non-persistent://tg/request/prompt:default
|
|
272
|
+
|
|
273
|
+
# Monitor with custom output file
|
|
274
|
+
tg-dump-queues non-persistent://tg/request/agent:default \\
|
|
275
|
+
--output debug.log
|
|
276
|
+
|
|
277
|
+
# Append to existing log file
|
|
278
|
+
tg-dump-queues non-persistent://tg/request/agent:default \\
|
|
279
|
+
--output queue.log --append
|
|
280
|
+
|
|
281
|
+
Common queue patterns:
|
|
282
|
+
- Agent requests: non-persistent://tg/request/agent:default
|
|
283
|
+
- Agent responses: non-persistent://tg/response/agent:default
|
|
284
|
+
- Prompt requests: non-persistent://tg/request/prompt:default
|
|
285
|
+
- Prompt responses: non-persistent://tg/response/prompt:default
|
|
286
|
+
- LLM requests: non-persistent://tg/request/text-completion:default
|
|
287
|
+
- LLM responses: non-persistent://tg/response/text-completion:default
|
|
288
|
+
|
|
289
|
+
IMPORTANT:
|
|
290
|
+
This tool subscribes to queues without a schema (schema-less mode). To avoid
|
|
291
|
+
schema conflicts, ensure that TrustGraph services and flows are already started
|
|
292
|
+
before running this tool. If this tool subscribes first, the real services may
|
|
293
|
+
encounter schema mismatch errors when they try to connect.
|
|
294
|
+
|
|
295
|
+
Best practice: Start services → Set up flows → Run tg-dump-queues
|
|
296
|
+
""",
|
|
297
|
+
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
parser.add_argument(
|
|
301
|
+
'queues',
|
|
302
|
+
nargs='+',
|
|
303
|
+
help='Pulsar queue names to monitor'
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
parser.add_argument(
|
|
307
|
+
'--output', '-o',
|
|
308
|
+
default='queue.log',
|
|
309
|
+
help='Output file (default: queue.log)'
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
parser.add_argument(
|
|
313
|
+
'--append', '-a',
|
|
314
|
+
action='store_true',
|
|
315
|
+
help='Append to output file instead of overwriting'
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
parser.add_argument(
|
|
319
|
+
'--pulsar-host',
|
|
320
|
+
default='pulsar://localhost:6650',
|
|
321
|
+
help='Pulsar host URL (default: pulsar://localhost:6650)'
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
parser.add_argument(
|
|
325
|
+
'--listener-name',
|
|
326
|
+
default='localhost',
|
|
327
|
+
help='Pulsar listener name (default: localhost)'
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
parser.add_argument(
|
|
331
|
+
'--subscriber',
|
|
332
|
+
default='debug',
|
|
333
|
+
help='Subscriber name for queue subscription (default: debug)'
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
args = parser.parse_args()
|
|
337
|
+
|
|
338
|
+
# Filter out any accidentally included flags
|
|
339
|
+
queues = [q for q in args.queues if not q.startswith('--')]
|
|
340
|
+
|
|
341
|
+
if not queues:
|
|
342
|
+
parser.error("No queues specified")
|
|
343
|
+
|
|
344
|
+
# Run async main
|
|
345
|
+
try:
|
|
346
|
+
asyncio.run(async_main(
|
|
347
|
+
queues=queues,
|
|
348
|
+
output_file=args.output,
|
|
349
|
+
pulsar_host=args.pulsar_host,
|
|
350
|
+
listener_name=args.listener_name,
|
|
351
|
+
subscriber_name=args.subscriber,
|
|
352
|
+
append_mode=args.append
|
|
353
|
+
))
|
|
354
|
+
except KeyboardInterrupt:
|
|
355
|
+
# Already handled in async_main
|
|
356
|
+
pass
|
|
357
|
+
except Exception as e:
|
|
358
|
+
print(f"Fatal error: {e}", file=sys.stderr)
|
|
359
|
+
sys.exit(1)
|
|
360
|
+
|
|
361
|
+
if __name__ == '__main__':
|
|
362
|
+
main()
|