trustgraph-cli 2.1.16__tar.gz → 2.1.18__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.
Files changed (88) hide show
  1. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/PKG-INFO +1 -1
  2. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_agent.py +164 -2
  3. trustgraph_cli-2.1.18/trustgraph/cli/invoke_document_rag.py +235 -0
  4. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_graph_rag.py +107 -5
  5. trustgraph_cli-2.1.18/trustgraph/cli/list_explain_traces.py +167 -0
  6. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/query_graph.py +12 -29
  7. trustgraph_cli-2.1.18/trustgraph/cli/show_explain_trace.py +546 -0
  8. trustgraph_cli-2.1.18/trustgraph/cli_version.py +1 -0
  9. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/PKG-INFO +1 -1
  10. trustgraph_cli-2.1.16/trustgraph/cli/invoke_document_rag.py +0 -128
  11. trustgraph_cli-2.1.16/trustgraph/cli/list_explain_traces.py +0 -304
  12. trustgraph_cli-2.1.16/trustgraph/cli/show_explain_trace.py +0 -784
  13. trustgraph_cli-2.1.16/trustgraph/cli_version.py +0 -1
  14. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/README.md +0 -0
  15. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/pyproject.toml +0 -0
  16. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/setup.cfg +0 -0
  17. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/__init__.py +0 -0
  18. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/add_library_document.py +0 -0
  19. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_collection.py +0 -0
  20. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_config_item.py +0 -0
  21. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_flow_blueprint.py +0 -0
  22. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_kg_core.py +0 -0
  23. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_mcp_tool.py +0 -0
  24. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_tool.py +0 -0
  25. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/dump_msgpack.py +0 -0
  26. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/dump_queues.py +0 -0
  27. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/get_config_item.py +0 -0
  28. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/get_document_content.py +0 -0
  29. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/get_flow_blueprint.py +0 -0
  30. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/get_kg_core.py +0 -0
  31. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/graph_to_turtle.py +0 -0
  32. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/init_pulsar_manager.py +0 -0
  33. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/init_trustgraph.py +0 -0
  34. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_document_embeddings.py +0 -0
  35. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_embeddings.py +0 -0
  36. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_graph_embeddings.py +0 -0
  37. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_llm.py +0 -0
  38. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_mcp_tool.py +0 -0
  39. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_nlp_query.py +0 -0
  40. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_prompt.py +0 -0
  41. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_row_embeddings.py +0 -0
  42. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_rows_query.py +0 -0
  43. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_structured_query.py +0 -0
  44. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/list_collections.py +0 -0
  45. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/list_config_items.py +0 -0
  46. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_doc_embeds.py +0 -0
  47. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_kg_core.py +0 -0
  48. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_knowledge.py +0 -0
  49. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_sample_documents.py +0 -0
  50. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_structured_data.py +0 -0
  51. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_turtle.py +0 -0
  52. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/put_config_item.py +0 -0
  53. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/put_flow_blueprint.py +0 -0
  54. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/put_kg_core.py +0 -0
  55. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/remove_library_document.py +0 -0
  56. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/save_doc_embeds.py +0 -0
  57. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_collection.py +0 -0
  58. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_mcp_tool.py +0 -0
  59. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_prompt.py +0 -0
  60. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_token_costs.py +0 -0
  61. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_tool.py +0 -0
  62. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_config.py +0 -0
  63. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_document_hierarchy.py +0 -0
  64. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_flow_blueprints.py +0 -0
  65. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_flow_state.py +0 -0
  66. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_flows.py +0 -0
  67. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_graph.py +0 -0
  68. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_kg_cores.py +0 -0
  69. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_library_documents.py +0 -0
  70. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_library_processing.py +0 -0
  71. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_mcp_tools.py +0 -0
  72. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_parameter_types.py +0 -0
  73. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_processor_state.py +0 -0
  74. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_prompts.py +0 -0
  75. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_token_costs.py +0 -0
  76. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_token_rate.py +0 -0
  77. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_tools.py +0 -0
  78. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/start_flow.py +0 -0
  79. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/start_library_processing.py +0 -0
  80. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/stop_flow.py +0 -0
  81. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/stop_library_processing.py +0 -0
  82. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/unload_kg_core.py +0 -0
  83. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/verify_system_status.py +0 -0
  84. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/SOURCES.txt +0 -0
  85. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/dependency_links.txt +0 -0
  86. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/entry_points.txt +0 -0
  87. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/requires.txt +0 -0
  88. {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/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: 2.1.16
3
+ Version: 2.1.18
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
@@ -4,8 +4,19 @@ Uses the agent service to answer a question
4
4
 
5
5
  import argparse
6
6
  import os
7
+ import sys
7
8
  import textwrap
8
- from trustgraph.api import Api
9
+ from trustgraph.api import (
10
+ Api,
11
+ ExplainabilityClient,
12
+ ProvenanceEvent,
13
+ Question,
14
+ Analysis,
15
+ Conclusion,
16
+ AgentThought,
17
+ AgentObservation,
18
+ AgentAnswer,
19
+ )
9
20
 
10
21
  default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
11
22
  default_token = os.getenv("TRUSTGRAPH_TOKEN", None)
@@ -97,11 +108,148 @@ def output(text, prefix="> ", width=78):
97
108
  )
98
109
  print(out)
99
110
 
111
+ def question_explainable(
112
+ url, question_text, flow_id, user, collection,
113
+ state=None, group=None, verbose=False, token=None, debug=False
114
+ ):
115
+ """Execute agent with explainability - shows provenance events inline."""
116
+ api = Api(url=url, token=token)
117
+ socket = api.socket()
118
+ flow = socket.flow(flow_id)
119
+ explain_client = ExplainabilityClient(flow, retry_delay=0.2, max_retries=10)
120
+
121
+ try:
122
+ # Track last chunk type for formatting
123
+ last_chunk_type = None
124
+ current_outputter = None
125
+
126
+ # Stream agent with explainability - process events as they arrive
127
+ for item in flow.agent_explain(
128
+ question=question_text,
129
+ user=user,
130
+ collection=collection,
131
+ state=state,
132
+ group=group,
133
+ ):
134
+ if isinstance(item, AgentThought):
135
+ if last_chunk_type != "thought":
136
+ if current_outputter:
137
+ current_outputter.__exit__(None, None, None)
138
+ current_outputter = None
139
+ print() # Blank line between message types
140
+ if verbose:
141
+ current_outputter = Outputter(width=78, prefix="\U0001f914 ")
142
+ current_outputter.__enter__()
143
+ last_chunk_type = "thought"
144
+ if current_outputter:
145
+ current_outputter.output(item.content)
146
+ if current_outputter.word_buffer:
147
+ print(current_outputter.word_buffer, end="", flush=True)
148
+ current_outputter.column += len(current_outputter.word_buffer)
149
+ current_outputter.word_buffer = ""
150
+
151
+ elif isinstance(item, AgentObservation):
152
+ if last_chunk_type != "observation":
153
+ if current_outputter:
154
+ current_outputter.__exit__(None, None, None)
155
+ current_outputter = None
156
+ print()
157
+ if verbose:
158
+ current_outputter = Outputter(width=78, prefix="\U0001f4a1 ")
159
+ current_outputter.__enter__()
160
+ last_chunk_type = "observation"
161
+ if current_outputter:
162
+ current_outputter.output(item.content)
163
+ if current_outputter.word_buffer:
164
+ print(current_outputter.word_buffer, end="", flush=True)
165
+ current_outputter.column += len(current_outputter.word_buffer)
166
+ current_outputter.word_buffer = ""
167
+
168
+ elif isinstance(item, AgentAnswer):
169
+ if last_chunk_type != "answer":
170
+ if current_outputter:
171
+ current_outputter.__exit__(None, None, None)
172
+ current_outputter = None
173
+ print()
174
+ last_chunk_type = "answer"
175
+ # Print answer content directly
176
+ print(item.content, end="", flush=True)
177
+
178
+ elif isinstance(item, ProvenanceEvent):
179
+ # Process provenance event immediately
180
+ prov_id = item.explain_id
181
+ explain_graph = item.explain_graph or "urn:graph:retrieval"
182
+
183
+ entity = explain_client.fetch_entity(
184
+ prov_id,
185
+ graph=explain_graph,
186
+ user=user,
187
+ collection=collection
188
+ )
189
+
190
+ if entity is None:
191
+ if debug:
192
+ print(f"\n [warning] Could not fetch entity: {prov_id}", file=sys.stderr)
193
+ continue
194
+
195
+ # Display based on entity type
196
+ if isinstance(entity, Question):
197
+ print(f"\n [session] {prov_id}", file=sys.stderr)
198
+ if entity.query:
199
+ print(f" Query: {entity.query}", file=sys.stderr)
200
+ if entity.timestamp:
201
+ print(f" Time: {entity.timestamp}", file=sys.stderr)
202
+
203
+ elif isinstance(entity, Analysis):
204
+ print(f"\n [iteration] {prov_id}", file=sys.stderr)
205
+ if entity.thought:
206
+ thought_short = entity.thought[:80] + "..." if len(entity.thought) > 80 else entity.thought
207
+ print(f" Thought: {thought_short}", file=sys.stderr)
208
+ if entity.action:
209
+ print(f" Action: {entity.action}", file=sys.stderr)
210
+
211
+ elif isinstance(entity, Conclusion):
212
+ print(f"\n [conclusion] {prov_id}", file=sys.stderr)
213
+ if entity.answer:
214
+ print(f" Answer length: {len(entity.answer)} chars", file=sys.stderr)
215
+
216
+ else:
217
+ if debug:
218
+ print(f"\n [unknown] {prov_id} (type: {entity.entity_type})", file=sys.stderr)
219
+
220
+ # Close any remaining outputter
221
+ if current_outputter:
222
+ current_outputter.__exit__(None, None, None)
223
+ current_outputter = None
224
+
225
+ # Final newline if we ended with answer
226
+ if last_chunk_type == "answer":
227
+ print()
228
+
229
+ finally:
230
+ socket.close()
231
+
232
+
100
233
  def question(
101
234
  url, question, flow_id, user, collection,
102
235
  plan=None, state=None, group=None, verbose=False, streaming=True,
103
- token=None
236
+ token=None, explainable=False, debug=False
104
237
  ):
238
+ # Explainable mode uses the API to capture and process provenance events
239
+ if explainable:
240
+ question_explainable(
241
+ url=url,
242
+ question_text=question,
243
+ flow_id=flow_id,
244
+ user=user,
245
+ collection=collection,
246
+ state=state,
247
+ group=group,
248
+ verbose=verbose,
249
+ token=token,
250
+ debug=debug
251
+ )
252
+ return
105
253
 
106
254
  if verbose:
107
255
  output(wrap(question), "\U00002753 ")
@@ -270,6 +418,18 @@ def main():
270
418
  help=f'Disable streaming (use legacy mode)'
271
419
  )
272
420
 
421
+ parser.add_argument(
422
+ '-x', '--explainable',
423
+ action='store_true',
424
+ help='Show provenance events: Session, Iterations, Conclusion (implies streaming)'
425
+ )
426
+
427
+ parser.add_argument(
428
+ '--debug',
429
+ action='store_true',
430
+ help='Show debug output for troubleshooting'
431
+ )
432
+
273
433
  args = parser.parse_args()
274
434
 
275
435
  try:
@@ -286,6 +446,8 @@ def main():
286
446
  verbose = args.verbose,
287
447
  streaming = not args.no_streaming,
288
448
  token = args.token,
449
+ explainable = args.explainable,
450
+ debug = args.debug,
289
451
  )
290
452
 
291
453
  except Exception as e:
@@ -0,0 +1,235 @@
1
+ """
2
+ Uses the DocumentRAG service to answer a question
3
+ """
4
+
5
+ import argparse
6
+ import os
7
+ import sys
8
+ from trustgraph.api import (
9
+ Api,
10
+ ExplainabilityClient,
11
+ RAGChunk,
12
+ ProvenanceEvent,
13
+ Question,
14
+ Exploration,
15
+ Synthesis,
16
+ )
17
+
18
+ default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
19
+ default_token = os.getenv("TRUSTGRAPH_TOKEN", None)
20
+ default_user = 'trustgraph'
21
+ default_collection = 'default'
22
+ default_doc_limit = 10
23
+
24
+
25
+ def question_explainable(
26
+ url, flow_id, question_text, user, collection, doc_limit, token=None, debug=False
27
+ ):
28
+ """Execute document RAG with explainability - shows provenance events inline."""
29
+ api = Api(url=url, token=token)
30
+ socket = api.socket()
31
+ flow = socket.flow(flow_id)
32
+ explain_client = ExplainabilityClient(flow, retry_delay=0.2, max_retries=10)
33
+
34
+ try:
35
+ # Stream DocumentRAG with explainability - process events as they arrive
36
+ for item in flow.document_rag_explain(
37
+ query=question_text,
38
+ user=user,
39
+ collection=collection,
40
+ doc_limit=doc_limit,
41
+ ):
42
+ if isinstance(item, RAGChunk):
43
+ # Print response content
44
+ print(item.content, end="", flush=True)
45
+
46
+ elif isinstance(item, ProvenanceEvent):
47
+ # Process provenance event immediately
48
+ prov_id = item.explain_id
49
+ explain_graph = item.explain_graph or "urn:graph:retrieval"
50
+
51
+ entity = explain_client.fetch_entity(
52
+ prov_id,
53
+ graph=explain_graph,
54
+ user=user,
55
+ collection=collection
56
+ )
57
+
58
+ if entity is None:
59
+ if debug:
60
+ print(f"\n [warning] Could not fetch entity: {prov_id}", file=sys.stderr)
61
+ continue
62
+
63
+ # Display based on entity type
64
+ if isinstance(entity, Question):
65
+ print(f"\n [question] {prov_id}", file=sys.stderr)
66
+ if entity.query:
67
+ print(f" Query: {entity.query}", file=sys.stderr)
68
+ if entity.timestamp:
69
+ print(f" Time: {entity.timestamp}", file=sys.stderr)
70
+
71
+ elif isinstance(entity, Exploration):
72
+ print(f"\n [exploration] {prov_id}", file=sys.stderr)
73
+ if entity.chunk_count:
74
+ print(f" Chunks retrieved: {entity.chunk_count}", file=sys.stderr)
75
+
76
+ elif isinstance(entity, Synthesis):
77
+ print(f"\n [synthesis] {prov_id}", file=sys.stderr)
78
+ if entity.content:
79
+ print(f" Synthesis length: {len(entity.content)} chars", file=sys.stderr)
80
+
81
+ else:
82
+ if debug:
83
+ print(f"\n [unknown] {prov_id} (type: {entity.entity_type})", file=sys.stderr)
84
+
85
+ print() # Final newline
86
+
87
+ finally:
88
+ socket.close()
89
+
90
+
91
+ def question(
92
+ url, flow_id, question_text, user, collection, doc_limit,
93
+ streaming=True, token=None, explainable=False, debug=False
94
+ ):
95
+ # Explainable mode uses the API to capture and process provenance events
96
+ if explainable:
97
+ question_explainable(
98
+ url=url,
99
+ flow_id=flow_id,
100
+ question_text=question_text,
101
+ user=user,
102
+ collection=collection,
103
+ doc_limit=doc_limit,
104
+ token=token,
105
+ debug=debug
106
+ )
107
+ return
108
+
109
+ # Create API client
110
+ api = Api(url=url, token=token)
111
+
112
+ if streaming:
113
+ # Use socket client for streaming
114
+ socket = api.socket()
115
+ flow = socket.flow(flow_id)
116
+
117
+ try:
118
+ response = flow.document_rag(
119
+ query=question_text,
120
+ user=user,
121
+ collection=collection,
122
+ doc_limit=doc_limit,
123
+ streaming=True
124
+ )
125
+
126
+ # Stream output
127
+ for chunk in response:
128
+ print(chunk, end="", flush=True)
129
+ print() # Final newline
130
+
131
+ finally:
132
+ socket.close()
133
+ else:
134
+ # Use REST API for non-streaming
135
+ flow = api.flow().id(flow_id)
136
+ resp = flow.document_rag(
137
+ query=question_text,
138
+ user=user,
139
+ collection=collection,
140
+ doc_limit=doc_limit,
141
+ )
142
+ print(resp)
143
+
144
+
145
+ def main():
146
+
147
+ parser = argparse.ArgumentParser(
148
+ prog='tg-invoke-document-rag',
149
+ description=__doc__,
150
+ )
151
+
152
+ parser.add_argument(
153
+ '-u', '--url',
154
+ default=default_url,
155
+ help=f'API URL (default: {default_url})',
156
+ )
157
+
158
+ parser.add_argument(
159
+ '-t', '--token',
160
+ default=default_token,
161
+ help='Authentication token (default: $TRUSTGRAPH_TOKEN)',
162
+ )
163
+
164
+ parser.add_argument(
165
+ '-f', '--flow-id',
166
+ default="default",
167
+ help=f'Flow ID (default: default)'
168
+ )
169
+
170
+ parser.add_argument(
171
+ '-q', '--question',
172
+ required=True,
173
+ help=f'Question to answer',
174
+ )
175
+
176
+ parser.add_argument(
177
+ '-U', '--user',
178
+ default=default_user,
179
+ help=f'User ID (default: {default_user})'
180
+ )
181
+
182
+ parser.add_argument(
183
+ '-C', '--collection',
184
+ default=default_collection,
185
+ help=f'Collection ID (default: {default_collection})'
186
+ )
187
+
188
+ parser.add_argument(
189
+ '-d', '--doc-limit',
190
+ type=int,
191
+ default=default_doc_limit,
192
+ help=f'Document limit (default: {default_doc_limit})'
193
+ )
194
+
195
+ parser.add_argument(
196
+ '--no-streaming',
197
+ action='store_true',
198
+ help='Disable streaming (use non-streaming mode)'
199
+ )
200
+
201
+ parser.add_argument(
202
+ '-x', '--explainable',
203
+ action='store_true',
204
+ help='Show provenance events: Question, Exploration, Synthesis (implies streaming)'
205
+ )
206
+
207
+ parser.add_argument(
208
+ '--debug',
209
+ action='store_true',
210
+ help='Show debug output for troubleshooting'
211
+ )
212
+
213
+ args = parser.parse_args()
214
+
215
+ try:
216
+
217
+ question(
218
+ url=args.url,
219
+ flow_id=args.flow_id,
220
+ question_text=args.question,
221
+ user=args.user,
222
+ collection=args.collection,
223
+ doc_limit=args.doc_limit,
224
+ streaming=not args.no_streaming,
225
+ token=args.token,
226
+ explainable=args.explainable,
227
+ debug=args.debug,
228
+ )
229
+
230
+ except Exception as e:
231
+
232
+ print("Exception:", e, flush=True)
233
+
234
+ if __name__ == "__main__":
235
+ main()
@@ -8,7 +8,16 @@ import os
8
8
  import sys
9
9
  import websockets
10
10
  import asyncio
11
- from trustgraph.api import Api
11
+ from trustgraph.api import (
12
+ Api,
13
+ ExplainabilityClient,
14
+ RAGChunk,
15
+ ProvenanceEvent,
16
+ Question,
17
+ Exploration,
18
+ Focus,
19
+ Synthesis,
20
+ )
12
21
 
13
22
  default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
14
23
  default_token = os.getenv("TRUSTGRAPH_TOKEN", None)
@@ -602,18 +611,111 @@ async def _question_explainable(
602
611
  print() # Final newline
603
612
 
604
613
 
614
+ def _question_explainable_api(
615
+ url, flow_id, question_text, user, collection, entity_limit, triple_limit,
616
+ max_subgraph_size, max_path_length, token=None, debug=False
617
+ ):
618
+ """Execute graph RAG with explainability using the new API classes."""
619
+ api = Api(url=url, token=token)
620
+ socket = api.socket()
621
+ flow = socket.flow(flow_id)
622
+ explain_client = ExplainabilityClient(flow, retry_delay=0.2, max_retries=10)
623
+
624
+ try:
625
+ # Stream GraphRAG with explainability - process events as they arrive
626
+ for item in flow.graph_rag_explain(
627
+ query=question_text,
628
+ user=user,
629
+ collection=collection,
630
+ max_subgraph_size=max_subgraph_size,
631
+ max_subgraph_count=5,
632
+ max_entity_distance=max_path_length,
633
+ ):
634
+ if isinstance(item, RAGChunk):
635
+ # Print response content
636
+ print(item.content, end="", flush=True)
637
+
638
+ elif isinstance(item, ProvenanceEvent):
639
+ # Process provenance event immediately
640
+ prov_id = item.explain_id
641
+ explain_graph = item.explain_graph or "urn:graph:retrieval"
642
+
643
+ entity = explain_client.fetch_entity(
644
+ prov_id,
645
+ graph=explain_graph,
646
+ user=user,
647
+ collection=collection
648
+ )
649
+
650
+ if entity is None:
651
+ if debug:
652
+ print(f"\n [warning] Could not fetch entity: {prov_id}", file=sys.stderr)
653
+ continue
654
+
655
+ # Display based on entity type
656
+ if isinstance(entity, Question):
657
+ print(f"\n [question] {prov_id}", file=sys.stderr)
658
+ if entity.query:
659
+ print(f" Query: {entity.query}", file=sys.stderr)
660
+ if entity.timestamp:
661
+ print(f" Time: {entity.timestamp}", file=sys.stderr)
662
+
663
+ elif isinstance(entity, Exploration):
664
+ print(f"\n [exploration] {prov_id}", file=sys.stderr)
665
+ if entity.edge_count:
666
+ print(f" Edges explored: {entity.edge_count}", file=sys.stderr)
667
+
668
+ elif isinstance(entity, Focus):
669
+ print(f"\n [focus] {prov_id}", file=sys.stderr)
670
+ if entity.selected_edge_uris:
671
+ print(f" Focused on {len(entity.selected_edge_uris)} edge(s)", file=sys.stderr)
672
+
673
+ # Fetch full focus with edge details
674
+ focus_full = explain_client.fetch_focus_with_edges(
675
+ prov_id,
676
+ graph=explain_graph,
677
+ user=user,
678
+ collection=collection
679
+ )
680
+ if focus_full and focus_full.edge_selections:
681
+ for edge_sel in focus_full.edge_selections:
682
+ if edge_sel.edge:
683
+ # Resolve labels for edge components
684
+ s_label, p_label, o_label = explain_client.resolve_edge_labels(
685
+ edge_sel.edge, user, collection
686
+ )
687
+ print(f" Edge: ({s_label}, {p_label}, {o_label})", file=sys.stderr)
688
+ if edge_sel.reasoning:
689
+ r_short = edge_sel.reasoning[:100] + "..." if len(edge_sel.reasoning) > 100 else edge_sel.reasoning
690
+ print(f" Reason: {r_short}", file=sys.stderr)
691
+
692
+ elif isinstance(entity, Synthesis):
693
+ print(f"\n [synthesis] {prov_id}", file=sys.stderr)
694
+ if entity.content:
695
+ print(f" Synthesis length: {len(entity.content)} chars", file=sys.stderr)
696
+
697
+ else:
698
+ if debug:
699
+ print(f"\n [unknown] {prov_id} (type: {entity.entity_type})", file=sys.stderr)
700
+
701
+ print() # Final newline
702
+
703
+ finally:
704
+ socket.close()
705
+
706
+
605
707
  def question(
606
708
  url, flow_id, question, user, collection, entity_limit, triple_limit,
607
709
  max_subgraph_size, max_path_length, streaming=True, token=None,
608
710
  explainable=False, debug=False
609
711
  ):
610
712
 
611
- # Explainable mode uses direct websocket to capture provenance events
713
+ # Explainable mode uses the API to capture and process provenance events
612
714
  if explainable:
613
- asyncio.run(_question_explainable(
715
+ _question_explainable_api(
614
716
  url=url,
615
717
  flow_id=flow_id,
616
- question=question,
718
+ question_text=question,
617
719
  user=user,
618
720
  collection=collection,
619
721
  entity_limit=entity_limit,
@@ -622,7 +724,7 @@ def question(
622
724
  max_path_length=max_path_length,
623
725
  token=token,
624
726
  debug=debug
625
- ))
727
+ )
626
728
  return
627
729
 
628
730
  # Create API client