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.
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/PKG-INFO +1 -1
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_agent.py +164 -2
- trustgraph_cli-2.1.18/trustgraph/cli/invoke_document_rag.py +235 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_graph_rag.py +107 -5
- trustgraph_cli-2.1.18/trustgraph/cli/list_explain_traces.py +167 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/query_graph.py +12 -29
- trustgraph_cli-2.1.18/trustgraph/cli/show_explain_trace.py +546 -0
- trustgraph_cli-2.1.18/trustgraph/cli_version.py +1 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/PKG-INFO +1 -1
- trustgraph_cli-2.1.16/trustgraph/cli/invoke_document_rag.py +0 -128
- trustgraph_cli-2.1.16/trustgraph/cli/list_explain_traces.py +0 -304
- trustgraph_cli-2.1.16/trustgraph/cli/show_explain_trace.py +0 -784
- trustgraph_cli-2.1.16/trustgraph/cli_version.py +0 -1
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/README.md +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/pyproject.toml +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/setup.cfg +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/__init__.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/add_library_document.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_collection.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_config_item.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_flow_blueprint.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_kg_core.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_mcp_tool.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/delete_tool.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/dump_msgpack.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/dump_queues.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/get_config_item.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/get_document_content.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/get_flow_blueprint.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/get_kg_core.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/graph_to_turtle.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/init_pulsar_manager.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/init_trustgraph.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_document_embeddings.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_embeddings.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_graph_embeddings.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_llm.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_mcp_tool.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_nlp_query.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_prompt.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_row_embeddings.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_rows_query.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/invoke_structured_query.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/list_collections.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/list_config_items.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_doc_embeds.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_kg_core.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_knowledge.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_sample_documents.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_structured_data.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/load_turtle.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/put_config_item.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/put_flow_blueprint.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/put_kg_core.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/remove_library_document.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/save_doc_embeds.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_collection.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_mcp_tool.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_prompt.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_token_costs.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/set_tool.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_config.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_document_hierarchy.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_flow_blueprints.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_flow_state.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_flows.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_graph.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_kg_cores.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_library_documents.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_library_processing.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_mcp_tools.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_parameter_types.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_processor_state.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_prompts.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_token_costs.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_token_rate.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/show_tools.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/start_flow.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/start_library_processing.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/stop_flow.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/stop_library_processing.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/unload_kg_core.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph/cli/verify_system_status.py +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/SOURCES.txt +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/dependency_links.txt +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/entry_points.txt +0 -0
- {trustgraph_cli-2.1.16 → trustgraph_cli-2.1.18}/trustgraph_cli.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
713
|
+
# Explainable mode uses the API to capture and process provenance events
|
|
612
714
|
if explainable:
|
|
613
|
-
|
|
715
|
+
_question_explainable_api(
|
|
614
716
|
url=url,
|
|
615
717
|
flow_id=flow_id,
|
|
616
|
-
|
|
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
|