trustgraph-cli 2.1.9__tar.gz → 2.1.10__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.9 → trustgraph_cli-2.1.10}/PKG-INFO +1 -1
- trustgraph_cli-2.1.10/trustgraph/cli/invoke_graph_rag.py +780 -0
- trustgraph_cli-2.1.10/trustgraph/cli_version.py +1 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph_cli.egg-info/PKG-INFO +1 -1
- trustgraph_cli-2.1.9/trustgraph/cli/invoke_graph_rag.py +0 -164
- trustgraph_cli-2.1.9/trustgraph/cli_version.py +0 -1
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/README.md +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/pyproject.toml +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/setup.cfg +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/__init__.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/add_library_document.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/delete_collection.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/delete_config_item.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/delete_flow_blueprint.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/delete_kg_core.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/delete_mcp_tool.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/delete_tool.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/dump_msgpack.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/dump_queues.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/get_config_item.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/get_document_content.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/get_flow_blueprint.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/get_kg_core.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/graph_to_turtle.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/init_pulsar_manager.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/init_trustgraph.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_agent.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_document_embeddings.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_document_rag.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_embeddings.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_graph_embeddings.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_llm.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_mcp_tool.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_nlp_query.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_prompt.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_row_embeddings.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_rows_query.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/invoke_structured_query.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/list_collections.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/list_config_items.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/load_doc_embeds.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/load_kg_core.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/load_knowledge.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/load_sample_documents.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/load_structured_data.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/load_turtle.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/put_config_item.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/put_flow_blueprint.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/put_kg_core.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/remove_library_document.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/save_doc_embeds.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/set_collection.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/set_mcp_tool.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/set_prompt.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/set_token_costs.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/set_tool.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_config.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_flow_blueprints.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_flow_state.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_flows.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_graph.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_kg_cores.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_library_documents.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_library_processing.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_mcp_tools.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_parameter_types.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_processor_state.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_prompts.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_token_costs.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_token_rate.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/show_tools.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/start_flow.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/start_library_processing.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/stop_flow.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/stop_library_processing.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/unload_kg_core.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph/cli/verify_system_status.py +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph_cli.egg-info/SOURCES.txt +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph_cli.egg-info/dependency_links.txt +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph_cli.egg-info/entry_points.txt +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/trustgraph_cli.egg-info/requires.txt +0 -0
- {trustgraph_cli-2.1.9 → trustgraph_cli-2.1.10}/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.10
|
|
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
|
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Uses the GraphRAG service to answer a question
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import websockets
|
|
10
|
+
import asyncio
|
|
11
|
+
from trustgraph.api import Api
|
|
12
|
+
|
|
13
|
+
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
|
|
14
|
+
default_token = os.getenv("TRUSTGRAPH_TOKEN", None)
|
|
15
|
+
default_user = 'trustgraph'
|
|
16
|
+
default_collection = 'default'
|
|
17
|
+
default_entity_limit = 50
|
|
18
|
+
default_triple_limit = 30
|
|
19
|
+
default_max_subgraph_size = 150
|
|
20
|
+
default_max_path_length = 2
|
|
21
|
+
|
|
22
|
+
# Provenance predicates
|
|
23
|
+
TG = "https://trustgraph.ai/ns/"
|
|
24
|
+
TG_QUERY = TG + "query"
|
|
25
|
+
TG_EDGE_COUNT = TG + "edgeCount"
|
|
26
|
+
TG_SELECTED_EDGE = TG + "selectedEdge"
|
|
27
|
+
TG_EDGE = TG + "edge"
|
|
28
|
+
TG_REASONING = TG + "reasoning"
|
|
29
|
+
TG_CONTENT = TG + "content"
|
|
30
|
+
TG_REIFIES = TG + "reifies"
|
|
31
|
+
PROV = "http://www.w3.org/ns/prov#"
|
|
32
|
+
PROV_STARTED_AT_TIME = PROV + "startedAtTime"
|
|
33
|
+
PROV_WAS_DERIVED_FROM = PROV + "wasDerivedFrom"
|
|
34
|
+
RDFS_LABEL = "http://www.w3.org/2000/01/rdf-schema#label"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_event_type(prov_id):
|
|
38
|
+
"""Extract event type from provenance_id"""
|
|
39
|
+
if "session" in prov_id:
|
|
40
|
+
return "session"
|
|
41
|
+
elif "retrieval" in prov_id:
|
|
42
|
+
return "retrieval"
|
|
43
|
+
elif "selection" in prov_id:
|
|
44
|
+
return "selection"
|
|
45
|
+
elif "answer" in prov_id:
|
|
46
|
+
return "answer"
|
|
47
|
+
return "provenance"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _format_provenance_details(event_type, triples):
|
|
51
|
+
"""Format provenance details based on event type and triples"""
|
|
52
|
+
lines = []
|
|
53
|
+
|
|
54
|
+
if event_type == "session":
|
|
55
|
+
# Show query and timestamp
|
|
56
|
+
for s, p, o in triples:
|
|
57
|
+
if p == TG_QUERY:
|
|
58
|
+
lines.append(f" Query: {o}")
|
|
59
|
+
elif p == PROV_STARTED_AT_TIME:
|
|
60
|
+
lines.append(f" Time: {o}")
|
|
61
|
+
|
|
62
|
+
elif event_type == "retrieval":
|
|
63
|
+
# Show edge count
|
|
64
|
+
for s, p, o in triples:
|
|
65
|
+
if p == TG_EDGE_COUNT:
|
|
66
|
+
lines.append(f" Edges retrieved: {o}")
|
|
67
|
+
|
|
68
|
+
elif event_type == "selection":
|
|
69
|
+
# For selection, just count edge selection URIs
|
|
70
|
+
# The actual edge details are fetched separately via edge_selections parameter
|
|
71
|
+
edge_sel_uris = []
|
|
72
|
+
for s, p, o in triples:
|
|
73
|
+
if p == TG_SELECTED_EDGE:
|
|
74
|
+
edge_sel_uris.append(o)
|
|
75
|
+
if edge_sel_uris:
|
|
76
|
+
lines.append(f" Selected {len(edge_sel_uris)} edge(s)")
|
|
77
|
+
|
|
78
|
+
elif event_type == "answer":
|
|
79
|
+
# Show content length (not full content - it's already streamed)
|
|
80
|
+
for s, p, o in triples:
|
|
81
|
+
if p == TG_CONTENT:
|
|
82
|
+
lines.append(f" Answer length: {len(o)} chars")
|
|
83
|
+
|
|
84
|
+
return lines
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def _query_triples_once(ws_url, flow_id, prov_id, user, collection, debug=False):
|
|
88
|
+
"""Query triples for a provenance node (single attempt)"""
|
|
89
|
+
request = {
|
|
90
|
+
"id": "triples-request",
|
|
91
|
+
"service": "triples",
|
|
92
|
+
"flow": flow_id,
|
|
93
|
+
"request": {
|
|
94
|
+
"s": {"t": "i", "i": prov_id},
|
|
95
|
+
"user": user,
|
|
96
|
+
"collection": collection,
|
|
97
|
+
"limit": 100
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if debug:
|
|
102
|
+
print(f" [debug] querying triples for s={prov_id}", file=sys.stderr)
|
|
103
|
+
|
|
104
|
+
triples = []
|
|
105
|
+
try:
|
|
106
|
+
async with websockets.connect(ws_url, ping_interval=20, ping_timeout=30) as websocket:
|
|
107
|
+
await websocket.send(json.dumps(request))
|
|
108
|
+
|
|
109
|
+
async for raw_message in websocket:
|
|
110
|
+
response = json.loads(raw_message)
|
|
111
|
+
|
|
112
|
+
if debug:
|
|
113
|
+
print(f" [debug] response: {json.dumps(response)[:200]}", file=sys.stderr)
|
|
114
|
+
|
|
115
|
+
if response.get("id") != "triples-request":
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
if "error" in response:
|
|
119
|
+
if debug:
|
|
120
|
+
print(f" [debug] error: {response['error']}", file=sys.stderr)
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
if "response" in response:
|
|
124
|
+
resp = response["response"]
|
|
125
|
+
# Handle triples response
|
|
126
|
+
# Response format: {"response": [triples...]}
|
|
127
|
+
# Each triple uses compact keys: "i" for iri, "v" for value, "t" for type
|
|
128
|
+
triple_list = resp.get("response", [])
|
|
129
|
+
for t in triple_list:
|
|
130
|
+
s = t.get("s", {}).get("i", t.get("s", {}).get("v", ""))
|
|
131
|
+
p = t.get("p", {}).get("i", t.get("p", {}).get("v", ""))
|
|
132
|
+
# Handle quoted triples (type "t") and regular values
|
|
133
|
+
o_term = t.get("o", {})
|
|
134
|
+
if o_term.get("t") == "t":
|
|
135
|
+
# Quoted triple - extract s, p, o from nested structure
|
|
136
|
+
tr = o_term.get("tr", {})
|
|
137
|
+
o = {
|
|
138
|
+
"s": tr.get("s", {}).get("i", ""),
|
|
139
|
+
"p": tr.get("p", {}).get("i", ""),
|
|
140
|
+
"o": tr.get("o", {}).get("i", tr.get("o", {}).get("v", "")),
|
|
141
|
+
}
|
|
142
|
+
else:
|
|
143
|
+
o = o_term.get("i", o_term.get("v", ""))
|
|
144
|
+
triples.append((s, p, o))
|
|
145
|
+
|
|
146
|
+
if resp.get("complete") or response.get("complete"):
|
|
147
|
+
break
|
|
148
|
+
except Exception as e:
|
|
149
|
+
if debug:
|
|
150
|
+
print(f" [debug] exception: {e}", file=sys.stderr)
|
|
151
|
+
|
|
152
|
+
if debug:
|
|
153
|
+
print(f" [debug] got {len(triples)} triples", file=sys.stderr)
|
|
154
|
+
|
|
155
|
+
return triples
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def _query_triples(ws_url, flow_id, prov_id, user, collection, max_retries=5, retry_delay=0.2, debug=False):
|
|
159
|
+
"""Query triples for a provenance node with retries for race condition"""
|
|
160
|
+
for attempt in range(max_retries):
|
|
161
|
+
triples = await _query_triples_once(ws_url, flow_id, prov_id, user, collection, debug)
|
|
162
|
+
if triples:
|
|
163
|
+
return triples
|
|
164
|
+
# Wait before retry if empty (triples may not be stored yet)
|
|
165
|
+
if attempt < max_retries - 1:
|
|
166
|
+
if debug:
|
|
167
|
+
print(f" [debug] retry {attempt + 1}/{max_retries}...", file=sys.stderr)
|
|
168
|
+
await asyncio.sleep(retry_delay)
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def _query_edge_provenance(ws_url, flow_id, edge_s, edge_p, edge_o, user, collection, debug=False):
|
|
173
|
+
"""
|
|
174
|
+
Query for provenance of an edge (s, p, o) in the knowledge graph.
|
|
175
|
+
|
|
176
|
+
Finds statements that reify the edge via tg:reifies, then follows
|
|
177
|
+
prov:wasDerivedFrom to find source documents.
|
|
178
|
+
|
|
179
|
+
Returns list of source URIs (chunks, pages, documents).
|
|
180
|
+
"""
|
|
181
|
+
# Query for statements that reify this edge: ?stmt tg:reifies <<s p o>>
|
|
182
|
+
request = {
|
|
183
|
+
"id": "edge-prov-request",
|
|
184
|
+
"service": "triples",
|
|
185
|
+
"flow": flow_id,
|
|
186
|
+
"request": {
|
|
187
|
+
"p": {"t": "i", "i": TG_REIFIES},
|
|
188
|
+
"o": {
|
|
189
|
+
"t": "t", # Quoted triple type
|
|
190
|
+
"tr": {
|
|
191
|
+
"s": {"t": "i", "i": edge_s},
|
|
192
|
+
"p": {"t": "i", "i": edge_p},
|
|
193
|
+
"o": {"t": "i", "i": edge_o} if edge_o.startswith("http") or edge_o.startswith("urn:") else {"t": "l", "v": edge_o},
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"user": user,
|
|
197
|
+
"collection": collection,
|
|
198
|
+
"limit": 10
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if debug:
|
|
203
|
+
print(f" [debug] querying edge provenance for ({edge_s}, {edge_p}, {edge_o})", file=sys.stderr)
|
|
204
|
+
|
|
205
|
+
stmt_uris = []
|
|
206
|
+
try:
|
|
207
|
+
async with websockets.connect(ws_url, ping_interval=20, ping_timeout=30) as websocket:
|
|
208
|
+
await websocket.send(json.dumps(request))
|
|
209
|
+
|
|
210
|
+
async for raw_message in websocket:
|
|
211
|
+
response = json.loads(raw_message)
|
|
212
|
+
|
|
213
|
+
if response.get("id") != "edge-prov-request":
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
if "error" in response:
|
|
217
|
+
if debug:
|
|
218
|
+
print(f" [debug] error: {response['error']}", file=sys.stderr)
|
|
219
|
+
break
|
|
220
|
+
|
|
221
|
+
if "response" in response:
|
|
222
|
+
resp = response["response"]
|
|
223
|
+
triple_list = resp.get("response", [])
|
|
224
|
+
for t in triple_list:
|
|
225
|
+
s = t.get("s", {}).get("i", "")
|
|
226
|
+
if s:
|
|
227
|
+
stmt_uris.append(s)
|
|
228
|
+
|
|
229
|
+
if resp.get("complete") or response.get("complete"):
|
|
230
|
+
break
|
|
231
|
+
except Exception as e:
|
|
232
|
+
if debug:
|
|
233
|
+
print(f" [debug] exception querying edge provenance: {e}", file=sys.stderr)
|
|
234
|
+
|
|
235
|
+
if debug:
|
|
236
|
+
print(f" [debug] found {len(stmt_uris)} reifying statements", file=sys.stderr)
|
|
237
|
+
|
|
238
|
+
# For each statement, query wasDerivedFrom to find sources
|
|
239
|
+
sources = []
|
|
240
|
+
for stmt_uri in stmt_uris:
|
|
241
|
+
# Query: stmt_uri prov:wasDerivedFrom ?source
|
|
242
|
+
request = {
|
|
243
|
+
"id": "derived-from-request",
|
|
244
|
+
"service": "triples",
|
|
245
|
+
"flow": flow_id,
|
|
246
|
+
"request": {
|
|
247
|
+
"s": {"t": "i", "i": stmt_uri},
|
|
248
|
+
"p": {"t": "i", "i": PROV_WAS_DERIVED_FROM},
|
|
249
|
+
"user": user,
|
|
250
|
+
"collection": collection,
|
|
251
|
+
"limit": 10
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
async with websockets.connect(ws_url, ping_interval=20, ping_timeout=30) as websocket:
|
|
257
|
+
await websocket.send(json.dumps(request))
|
|
258
|
+
|
|
259
|
+
async for raw_message in websocket:
|
|
260
|
+
response = json.loads(raw_message)
|
|
261
|
+
|
|
262
|
+
if response.get("id") != "derived-from-request":
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
if "error" in response:
|
|
266
|
+
break
|
|
267
|
+
|
|
268
|
+
if "response" in response:
|
|
269
|
+
resp = response["response"]
|
|
270
|
+
triple_list = resp.get("response", [])
|
|
271
|
+
for t in triple_list:
|
|
272
|
+
o = t.get("o", {}).get("i", "")
|
|
273
|
+
if o:
|
|
274
|
+
sources.append(o)
|
|
275
|
+
|
|
276
|
+
if resp.get("complete") or response.get("complete"):
|
|
277
|
+
break
|
|
278
|
+
except Exception as e:
|
|
279
|
+
if debug:
|
|
280
|
+
print(f" [debug] exception querying wasDerivedFrom: {e}", file=sys.stderr)
|
|
281
|
+
|
|
282
|
+
if debug:
|
|
283
|
+
print(f" [debug] found {len(sources)} source(s): {sources}", file=sys.stderr)
|
|
284
|
+
|
|
285
|
+
return sources
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
async def _query_derived_from(ws_url, flow_id, uri, user, collection, debug=False):
|
|
289
|
+
"""Query for the prov:wasDerivedFrom parent of a URI. Returns None if no parent."""
|
|
290
|
+
request = {
|
|
291
|
+
"id": "parent-request",
|
|
292
|
+
"service": "triples",
|
|
293
|
+
"flow": flow_id,
|
|
294
|
+
"request": {
|
|
295
|
+
"s": {"t": "i", "i": uri},
|
|
296
|
+
"p": {"t": "i", "i": PROV_WAS_DERIVED_FROM},
|
|
297
|
+
"user": user,
|
|
298
|
+
"collection": collection,
|
|
299
|
+
"limit": 1
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
async with websockets.connect(ws_url, ping_interval=20, ping_timeout=30) as websocket:
|
|
305
|
+
await websocket.send(json.dumps(request))
|
|
306
|
+
|
|
307
|
+
async for raw_message in websocket:
|
|
308
|
+
response = json.loads(raw_message)
|
|
309
|
+
|
|
310
|
+
if response.get("id") != "parent-request":
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
if "error" in response:
|
|
314
|
+
break
|
|
315
|
+
|
|
316
|
+
if "response" in response:
|
|
317
|
+
resp = response["response"]
|
|
318
|
+
triple_list = resp.get("response", [])
|
|
319
|
+
if triple_list:
|
|
320
|
+
return triple_list[0].get("o", {}).get("i", None)
|
|
321
|
+
|
|
322
|
+
if resp.get("complete") or response.get("complete"):
|
|
323
|
+
break
|
|
324
|
+
except Exception as e:
|
|
325
|
+
if debug:
|
|
326
|
+
print(f" [debug] exception querying parent: {e}", file=sys.stderr)
|
|
327
|
+
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
async def _trace_provenance_chain(ws_url, flow_id, source_uri, user, collection, label_cache, debug=False):
|
|
332
|
+
"""
|
|
333
|
+
Trace the full provenance chain from a source URI up to the root document.
|
|
334
|
+
Returns a list of (uri, label) tuples from leaf to root.
|
|
335
|
+
"""
|
|
336
|
+
chain = []
|
|
337
|
+
current = source_uri
|
|
338
|
+
max_depth = 10 # Prevent infinite loops
|
|
339
|
+
|
|
340
|
+
for _ in range(max_depth):
|
|
341
|
+
if not current:
|
|
342
|
+
break
|
|
343
|
+
|
|
344
|
+
# Get label for current entity
|
|
345
|
+
label = await _query_label(ws_url, flow_id, current, user, collection, label_cache, debug)
|
|
346
|
+
chain.append((current, label))
|
|
347
|
+
|
|
348
|
+
# Get parent
|
|
349
|
+
parent = await _query_derived_from(ws_url, flow_id, current, user, collection, debug)
|
|
350
|
+
if not parent or parent == current:
|
|
351
|
+
break
|
|
352
|
+
current = parent
|
|
353
|
+
|
|
354
|
+
return chain
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _format_provenance_chain(chain):
|
|
358
|
+
"""
|
|
359
|
+
Format a provenance chain as a human-readable string.
|
|
360
|
+
Chain is [(uri, label), ...] from leaf to root.
|
|
361
|
+
"""
|
|
362
|
+
if not chain:
|
|
363
|
+
return ""
|
|
364
|
+
|
|
365
|
+
# Show labels, from leaf to root
|
|
366
|
+
labels = [label for uri, label in chain]
|
|
367
|
+
return " → ".join(labels)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _is_iri(value):
|
|
371
|
+
"""Check if a value looks like an IRI."""
|
|
372
|
+
if not isinstance(value, str):
|
|
373
|
+
return False
|
|
374
|
+
return value.startswith("http://") or value.startswith("https://") or value.startswith("urn:")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
async def _query_label(ws_url, flow_id, iri, user, collection, label_cache, debug=False):
|
|
378
|
+
"""
|
|
379
|
+
Query for the rdfs:label of an IRI.
|
|
380
|
+
Uses label_cache to avoid repeated queries.
|
|
381
|
+
Returns the label if found, otherwise returns the IRI.
|
|
382
|
+
"""
|
|
383
|
+
if not _is_iri(iri):
|
|
384
|
+
return iri
|
|
385
|
+
|
|
386
|
+
# Check cache first
|
|
387
|
+
if iri in label_cache:
|
|
388
|
+
return label_cache[iri]
|
|
389
|
+
|
|
390
|
+
request = {
|
|
391
|
+
"id": "label-request",
|
|
392
|
+
"service": "triples",
|
|
393
|
+
"flow": flow_id,
|
|
394
|
+
"request": {
|
|
395
|
+
"s": {"t": "i", "i": iri},
|
|
396
|
+
"p": {"t": "i", "i": RDFS_LABEL},
|
|
397
|
+
"user": user,
|
|
398
|
+
"collection": collection,
|
|
399
|
+
"limit": 1
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
label = iri # Default to IRI if no label found
|
|
404
|
+
try:
|
|
405
|
+
async with websockets.connect(ws_url, ping_interval=20, ping_timeout=30) as websocket:
|
|
406
|
+
await websocket.send(json.dumps(request))
|
|
407
|
+
|
|
408
|
+
async for raw_message in websocket:
|
|
409
|
+
response = json.loads(raw_message)
|
|
410
|
+
|
|
411
|
+
if response.get("id") != "label-request":
|
|
412
|
+
continue
|
|
413
|
+
|
|
414
|
+
if "error" in response:
|
|
415
|
+
break
|
|
416
|
+
|
|
417
|
+
if "response" in response:
|
|
418
|
+
resp = response["response"]
|
|
419
|
+
triple_list = resp.get("response", [])
|
|
420
|
+
if triple_list:
|
|
421
|
+
# Get the label value
|
|
422
|
+
o = triple_list[0].get("o", {})
|
|
423
|
+
label = o.get("v", o.get("i", iri))
|
|
424
|
+
|
|
425
|
+
if resp.get("complete") or response.get("complete"):
|
|
426
|
+
break
|
|
427
|
+
except Exception as e:
|
|
428
|
+
if debug:
|
|
429
|
+
print(f" [debug] exception querying label for {iri}: {e}", file=sys.stderr)
|
|
430
|
+
|
|
431
|
+
# Cache the result
|
|
432
|
+
label_cache[iri] = label
|
|
433
|
+
return label
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
async def _resolve_edge_labels(ws_url, flow_id, edge_triple, user, collection, label_cache, debug=False):
|
|
437
|
+
"""
|
|
438
|
+
Resolve labels for all IRI components of an edge triple.
|
|
439
|
+
Returns (s_label, p_label, o_label).
|
|
440
|
+
"""
|
|
441
|
+
s = edge_triple.get("s", "?")
|
|
442
|
+
p = edge_triple.get("p", "?")
|
|
443
|
+
o = edge_triple.get("o", "?")
|
|
444
|
+
|
|
445
|
+
s_label = await _query_label(ws_url, flow_id, s, user, collection, label_cache, debug)
|
|
446
|
+
p_label = await _query_label(ws_url, flow_id, p, user, collection, label_cache, debug)
|
|
447
|
+
o_label = await _query_label(ws_url, flow_id, o, user, collection, label_cache, debug)
|
|
448
|
+
|
|
449
|
+
return s_label, p_label, o_label
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
async def _question_explainable(
|
|
453
|
+
url, flow_id, question, user, collection, entity_limit, triple_limit,
|
|
454
|
+
max_subgraph_size, max_path_length, token=None, debug=False
|
|
455
|
+
):
|
|
456
|
+
"""Execute graph RAG with explainability - shows provenance events with details"""
|
|
457
|
+
# Convert HTTP URL to WebSocket URL
|
|
458
|
+
if url.startswith("http://"):
|
|
459
|
+
ws_url = url.replace("http://", "ws://", 1)
|
|
460
|
+
elif url.startswith("https://"):
|
|
461
|
+
ws_url = url.replace("https://", "wss://", 1)
|
|
462
|
+
else:
|
|
463
|
+
ws_url = f"ws://{url}"
|
|
464
|
+
|
|
465
|
+
ws_url = f"{ws_url.rstrip('/')}/api/v1/socket"
|
|
466
|
+
if token:
|
|
467
|
+
ws_url = f"{ws_url}?token={token}"
|
|
468
|
+
|
|
469
|
+
# Cache for label lookups to avoid repeated queries
|
|
470
|
+
label_cache = {}
|
|
471
|
+
|
|
472
|
+
request = {
|
|
473
|
+
"id": "cli-request",
|
|
474
|
+
"service": "graph-rag",
|
|
475
|
+
"flow": flow_id,
|
|
476
|
+
"request": {
|
|
477
|
+
"query": question,
|
|
478
|
+
"user": user,
|
|
479
|
+
"collection": collection,
|
|
480
|
+
"entity-limit": entity_limit,
|
|
481
|
+
"triple-limit": triple_limit,
|
|
482
|
+
"max-subgraph-size": max_subgraph_size,
|
|
483
|
+
"max-path-length": max_path_length,
|
|
484
|
+
"streaming": True
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async with websockets.connect(ws_url, ping_interval=20, ping_timeout=300) as websocket:
|
|
489
|
+
await websocket.send(json.dumps(request))
|
|
490
|
+
|
|
491
|
+
async for raw_message in websocket:
|
|
492
|
+
response = json.loads(raw_message)
|
|
493
|
+
|
|
494
|
+
if response.get("id") != "cli-request":
|
|
495
|
+
continue
|
|
496
|
+
|
|
497
|
+
if "error" in response:
|
|
498
|
+
print(f"\nError: {response['error']}", file=sys.stderr)
|
|
499
|
+
break
|
|
500
|
+
|
|
501
|
+
if "response" in response:
|
|
502
|
+
resp = response["response"]
|
|
503
|
+
|
|
504
|
+
# Check for errors in response
|
|
505
|
+
if "error" in resp and resp["error"]:
|
|
506
|
+
err = resp["error"]
|
|
507
|
+
print(f"\nError: {err.get('message', 'Unknown error')}", file=sys.stderr)
|
|
508
|
+
break
|
|
509
|
+
|
|
510
|
+
message_type = resp.get("message_type", "")
|
|
511
|
+
|
|
512
|
+
if debug:
|
|
513
|
+
print(f" [debug] message_type={message_type}, keys={list(resp.keys())}", file=sys.stderr)
|
|
514
|
+
|
|
515
|
+
if message_type == "explain":
|
|
516
|
+
# Display explain event with details
|
|
517
|
+
explain_id = resp.get("explain_id", "")
|
|
518
|
+
explain_collection = resp.get("explain_collection", "explainability")
|
|
519
|
+
if explain_id:
|
|
520
|
+
event_type = _get_event_type(explain_id)
|
|
521
|
+
print(f"\n [{event_type}] {explain_id}", file=sys.stderr)
|
|
522
|
+
|
|
523
|
+
# Query triples for this explain node (using explain collection from event)
|
|
524
|
+
triples = await _query_triples(
|
|
525
|
+
ws_url, flow_id, explain_id, user, explain_collection, debug=debug
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Format and display details
|
|
529
|
+
details = _format_provenance_details(event_type, triples)
|
|
530
|
+
for line in details:
|
|
531
|
+
print(line, file=sys.stderr)
|
|
532
|
+
|
|
533
|
+
# For selection events, query each edge selection for details
|
|
534
|
+
if event_type == "selection":
|
|
535
|
+
for s, p, o in triples:
|
|
536
|
+
if debug:
|
|
537
|
+
print(f" [debug] triple: p={p}, o={o}, o_type={type(o).__name__}", file=sys.stderr)
|
|
538
|
+
if p == TG_SELECTED_EDGE and isinstance(o, str):
|
|
539
|
+
if debug:
|
|
540
|
+
print(f" [debug] querying edge selection: {o}", file=sys.stderr)
|
|
541
|
+
# Query the edge selection entity (using explain collection from event)
|
|
542
|
+
edge_triples = await _query_triples(
|
|
543
|
+
ws_url, flow_id, o, user, explain_collection, debug=debug
|
|
544
|
+
)
|
|
545
|
+
if debug:
|
|
546
|
+
print(f" [debug] got {len(edge_triples)} edge triples", file=sys.stderr)
|
|
547
|
+
# Extract edge and reasoning
|
|
548
|
+
edge_triple = None # Store the actual triple for provenance lookup
|
|
549
|
+
reasoning = None
|
|
550
|
+
for es, ep, eo in edge_triples:
|
|
551
|
+
if debug:
|
|
552
|
+
print(f" [debug] edge triple: ep={ep}, eo={eo}", file=sys.stderr)
|
|
553
|
+
if ep == TG_EDGE and isinstance(eo, dict):
|
|
554
|
+
# eo is a quoted triple dict
|
|
555
|
+
edge_triple = eo
|
|
556
|
+
elif ep == TG_REASONING:
|
|
557
|
+
reasoning = eo
|
|
558
|
+
if edge_triple:
|
|
559
|
+
# Resolve labels for edge components
|
|
560
|
+
s_label, p_label, o_label = await _resolve_edge_labels(
|
|
561
|
+
ws_url, flow_id, edge_triple, user, collection,
|
|
562
|
+
label_cache, debug=debug
|
|
563
|
+
)
|
|
564
|
+
print(f" Edge: ({s_label}, {p_label}, {o_label})", file=sys.stderr)
|
|
565
|
+
if reasoning:
|
|
566
|
+
r_short = reasoning[:100] + "..." if len(reasoning) > 100 else reasoning
|
|
567
|
+
print(f" Reason: {r_short}", file=sys.stderr)
|
|
568
|
+
|
|
569
|
+
# Trace edge provenance in the user's collection (not explainability)
|
|
570
|
+
if edge_triple:
|
|
571
|
+
sources = await _query_edge_provenance(
|
|
572
|
+
ws_url, flow_id,
|
|
573
|
+
edge_triple.get("s", ""),
|
|
574
|
+
edge_triple.get("p", ""),
|
|
575
|
+
edge_triple.get("o", ""),
|
|
576
|
+
user, collection, # Use the query collection, not explainability
|
|
577
|
+
debug=debug
|
|
578
|
+
)
|
|
579
|
+
if sources:
|
|
580
|
+
for src in sources:
|
|
581
|
+
# Trace full chain from source to root document
|
|
582
|
+
chain = await _trace_provenance_chain(
|
|
583
|
+
ws_url, flow_id, src, user, collection,
|
|
584
|
+
label_cache, debug=debug
|
|
585
|
+
)
|
|
586
|
+
chain_str = _format_provenance_chain(chain)
|
|
587
|
+
print(f" Source: {chain_str}", file=sys.stderr)
|
|
588
|
+
|
|
589
|
+
elif message_type == "chunk" or not message_type:
|
|
590
|
+
# Display response chunk
|
|
591
|
+
chunk = resp.get("response", "")
|
|
592
|
+
if chunk:
|
|
593
|
+
print(chunk, end="", flush=True)
|
|
594
|
+
|
|
595
|
+
# Check if session is complete
|
|
596
|
+
if resp.get("end_of_session"):
|
|
597
|
+
break
|
|
598
|
+
|
|
599
|
+
print() # Final newline
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def question(
|
|
603
|
+
url, flow_id, question, user, collection, entity_limit, triple_limit,
|
|
604
|
+
max_subgraph_size, max_path_length, streaming=True, token=None,
|
|
605
|
+
explainable=False, debug=False
|
|
606
|
+
):
|
|
607
|
+
|
|
608
|
+
# Explainable mode uses direct websocket to capture provenance events
|
|
609
|
+
if explainable:
|
|
610
|
+
asyncio.run(_question_explainable(
|
|
611
|
+
url=url,
|
|
612
|
+
flow_id=flow_id,
|
|
613
|
+
question=question,
|
|
614
|
+
user=user,
|
|
615
|
+
collection=collection,
|
|
616
|
+
entity_limit=entity_limit,
|
|
617
|
+
triple_limit=triple_limit,
|
|
618
|
+
max_subgraph_size=max_subgraph_size,
|
|
619
|
+
max_path_length=max_path_length,
|
|
620
|
+
token=token,
|
|
621
|
+
debug=debug
|
|
622
|
+
))
|
|
623
|
+
return
|
|
624
|
+
|
|
625
|
+
# Create API client
|
|
626
|
+
api = Api(url=url, token=token)
|
|
627
|
+
|
|
628
|
+
if streaming:
|
|
629
|
+
# Use socket client for streaming
|
|
630
|
+
socket = api.socket()
|
|
631
|
+
flow = socket.flow(flow_id)
|
|
632
|
+
|
|
633
|
+
try:
|
|
634
|
+
response = flow.graph_rag(
|
|
635
|
+
query=question,
|
|
636
|
+
user=user,
|
|
637
|
+
collection=collection,
|
|
638
|
+
entity_limit=entity_limit,
|
|
639
|
+
triple_limit=triple_limit,
|
|
640
|
+
max_subgraph_size=max_subgraph_size,
|
|
641
|
+
max_path_length=max_path_length,
|
|
642
|
+
streaming=True
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
# Stream output
|
|
646
|
+
for chunk in response:
|
|
647
|
+
print(chunk, end="", flush=True)
|
|
648
|
+
print() # Final newline
|
|
649
|
+
|
|
650
|
+
finally:
|
|
651
|
+
socket.close()
|
|
652
|
+
else:
|
|
653
|
+
# Use REST API for non-streaming
|
|
654
|
+
flow = api.flow().id(flow_id)
|
|
655
|
+
resp = flow.graph_rag(
|
|
656
|
+
query=question,
|
|
657
|
+
user=user,
|
|
658
|
+
collection=collection,
|
|
659
|
+
entity_limit=entity_limit,
|
|
660
|
+
triple_limit=triple_limit,
|
|
661
|
+
max_subgraph_size=max_subgraph_size,
|
|
662
|
+
max_path_length=max_path_length
|
|
663
|
+
)
|
|
664
|
+
print(resp)
|
|
665
|
+
|
|
666
|
+
def main():
|
|
667
|
+
|
|
668
|
+
parser = argparse.ArgumentParser(
|
|
669
|
+
prog='tg-invoke-graph-rag',
|
|
670
|
+
description=__doc__,
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
parser.add_argument(
|
|
674
|
+
'-u', '--url',
|
|
675
|
+
default=default_url,
|
|
676
|
+
help=f'API URL (default: {default_url})',
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
parser.add_argument(
|
|
680
|
+
'-t', '--token',
|
|
681
|
+
default=default_token,
|
|
682
|
+
help='Authentication token (default: $TRUSTGRAPH_TOKEN)',
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
parser.add_argument(
|
|
686
|
+
'-f', '--flow-id',
|
|
687
|
+
default="default",
|
|
688
|
+
help=f'Flow ID (default: default)'
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
parser.add_argument(
|
|
692
|
+
'-q', '--question',
|
|
693
|
+
required=True,
|
|
694
|
+
help=f'Question to answer',
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
parser.add_argument(
|
|
698
|
+
'-U', '--user',
|
|
699
|
+
default=default_user,
|
|
700
|
+
help=f'User ID (default: {default_user})'
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
parser.add_argument(
|
|
704
|
+
'-C', '--collection',
|
|
705
|
+
default=default_collection,
|
|
706
|
+
help=f'Collection ID (default: {default_collection})'
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
parser.add_argument(
|
|
710
|
+
'-e', '--entity-limit',
|
|
711
|
+
type=int,
|
|
712
|
+
default=default_entity_limit,
|
|
713
|
+
help=f'Entity limit (default: {default_entity_limit})'
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
parser.add_argument(
|
|
717
|
+
'--triple-limit',
|
|
718
|
+
type=int,
|
|
719
|
+
default=default_triple_limit,
|
|
720
|
+
help=f'Triple limit (default: {default_triple_limit})'
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
parser.add_argument(
|
|
724
|
+
'-s', '--max-subgraph-size',
|
|
725
|
+
type=int,
|
|
726
|
+
default=default_max_subgraph_size,
|
|
727
|
+
help=f'Max subgraph size (default: {default_max_subgraph_size})'
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
parser.add_argument(
|
|
731
|
+
'-p', '--max-path-length',
|
|
732
|
+
type=int,
|
|
733
|
+
default=default_max_path_length,
|
|
734
|
+
help=f'Max path length (default: {default_max_path_length})'
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
parser.add_argument(
|
|
738
|
+
'--no-streaming',
|
|
739
|
+
action='store_true',
|
|
740
|
+
help='Disable streaming (use non-streaming mode)'
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
parser.add_argument(
|
|
744
|
+
'-x', '--explainable',
|
|
745
|
+
action='store_true',
|
|
746
|
+
help='Show provenance events for explainability (implies streaming)'
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
parser.add_argument(
|
|
750
|
+
'--debug',
|
|
751
|
+
action='store_true',
|
|
752
|
+
help='Show debug output for troubleshooting'
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
args = parser.parse_args()
|
|
756
|
+
|
|
757
|
+
try:
|
|
758
|
+
|
|
759
|
+
question(
|
|
760
|
+
url=args.url,
|
|
761
|
+
flow_id=args.flow_id,
|
|
762
|
+
question=args.question,
|
|
763
|
+
user=args.user,
|
|
764
|
+
collection=args.collection,
|
|
765
|
+
entity_limit=args.entity_limit,
|
|
766
|
+
triple_limit=args.triple_limit,
|
|
767
|
+
max_subgraph_size=args.max_subgraph_size,
|
|
768
|
+
max_path_length=args.max_path_length,
|
|
769
|
+
streaming=not args.no_streaming,
|
|
770
|
+
token=args.token,
|
|
771
|
+
explainable=args.explainable,
|
|
772
|
+
debug=args.debug,
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
except Exception as e:
|
|
776
|
+
|
|
777
|
+
print("Exception:", e, flush=True)
|
|
778
|
+
|
|
779
|
+
if __name__ == "__main__":
|
|
780
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.10"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trustgraph-cli
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.10
|
|
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
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Uses the GraphRAG service to answer a question
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import argparse
|
|
6
|
-
import os
|
|
7
|
-
from trustgraph.api import Api
|
|
8
|
-
|
|
9
|
-
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
|
|
10
|
-
default_token = os.getenv("TRUSTGRAPH_TOKEN", None)
|
|
11
|
-
default_user = 'trustgraph'
|
|
12
|
-
default_collection = 'default'
|
|
13
|
-
default_entity_limit = 50
|
|
14
|
-
default_triple_limit = 30
|
|
15
|
-
default_max_subgraph_size = 150
|
|
16
|
-
default_max_path_length = 2
|
|
17
|
-
|
|
18
|
-
def question(
|
|
19
|
-
url, flow_id, question, user, collection, entity_limit, triple_limit,
|
|
20
|
-
max_subgraph_size, max_path_length, streaming=True, token=None
|
|
21
|
-
):
|
|
22
|
-
|
|
23
|
-
# Create API client
|
|
24
|
-
api = Api(url=url, token=token)
|
|
25
|
-
|
|
26
|
-
if streaming:
|
|
27
|
-
# Use socket client for streaming
|
|
28
|
-
socket = api.socket()
|
|
29
|
-
flow = socket.flow(flow_id)
|
|
30
|
-
|
|
31
|
-
try:
|
|
32
|
-
response = flow.graph_rag(
|
|
33
|
-
query=question,
|
|
34
|
-
user=user,
|
|
35
|
-
collection=collection,
|
|
36
|
-
entity_limit=entity_limit,
|
|
37
|
-
triple_limit=triple_limit,
|
|
38
|
-
max_subgraph_size=max_subgraph_size,
|
|
39
|
-
max_path_length=max_path_length,
|
|
40
|
-
streaming=True
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
# Stream output
|
|
44
|
-
for chunk in response:
|
|
45
|
-
print(chunk, end="", flush=True)
|
|
46
|
-
print() # Final newline
|
|
47
|
-
|
|
48
|
-
finally:
|
|
49
|
-
socket.close()
|
|
50
|
-
else:
|
|
51
|
-
# Use REST API for non-streaming
|
|
52
|
-
flow = api.flow().id(flow_id)
|
|
53
|
-
resp = flow.graph_rag(
|
|
54
|
-
query=question,
|
|
55
|
-
user=user,
|
|
56
|
-
collection=collection,
|
|
57
|
-
entity_limit=entity_limit,
|
|
58
|
-
triple_limit=triple_limit,
|
|
59
|
-
max_subgraph_size=max_subgraph_size,
|
|
60
|
-
max_path_length=max_path_length
|
|
61
|
-
)
|
|
62
|
-
print(resp)
|
|
63
|
-
|
|
64
|
-
def main():
|
|
65
|
-
|
|
66
|
-
parser = argparse.ArgumentParser(
|
|
67
|
-
prog='tg-invoke-graph-rag',
|
|
68
|
-
description=__doc__,
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
parser.add_argument(
|
|
72
|
-
'-u', '--url',
|
|
73
|
-
default=default_url,
|
|
74
|
-
help=f'API URL (default: {default_url})',
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
parser.add_argument(
|
|
78
|
-
'-t', '--token',
|
|
79
|
-
default=default_token,
|
|
80
|
-
help='Authentication token (default: $TRUSTGRAPH_TOKEN)',
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
parser.add_argument(
|
|
84
|
-
'-f', '--flow-id',
|
|
85
|
-
default="default",
|
|
86
|
-
help=f'Flow ID (default: default)'
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
parser.add_argument(
|
|
90
|
-
'-q', '--question',
|
|
91
|
-
required=True,
|
|
92
|
-
help=f'Question to answer',
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
parser.add_argument(
|
|
96
|
-
'-U', '--user',
|
|
97
|
-
default=default_user,
|
|
98
|
-
help=f'User ID (default: {default_user})'
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
parser.add_argument(
|
|
102
|
-
'-C', '--collection',
|
|
103
|
-
default=default_collection,
|
|
104
|
-
help=f'Collection ID (default: {default_collection})'
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
parser.add_argument(
|
|
108
|
-
'-e', '--entity-limit',
|
|
109
|
-
type=int,
|
|
110
|
-
default=default_entity_limit,
|
|
111
|
-
help=f'Entity limit (default: {default_entity_limit})'
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
parser.add_argument(
|
|
115
|
-
'--triple-limit',
|
|
116
|
-
type=int,
|
|
117
|
-
default=default_triple_limit,
|
|
118
|
-
help=f'Triple limit (default: {default_triple_limit})'
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
parser.add_argument(
|
|
122
|
-
'-s', '--max-subgraph-size',
|
|
123
|
-
type=int,
|
|
124
|
-
default=default_max_subgraph_size,
|
|
125
|
-
help=f'Max subgraph size (default: {default_max_subgraph_size})'
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
parser.add_argument(
|
|
129
|
-
'-p', '--max-path-length',
|
|
130
|
-
type=int,
|
|
131
|
-
default=default_max_path_length,
|
|
132
|
-
help=f'Max path length (default: {default_max_path_length})'
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
parser.add_argument(
|
|
136
|
-
'--no-streaming',
|
|
137
|
-
action='store_true',
|
|
138
|
-
help='Disable streaming (use non-streaming mode)'
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
args = parser.parse_args()
|
|
142
|
-
|
|
143
|
-
try:
|
|
144
|
-
|
|
145
|
-
question(
|
|
146
|
-
url=args.url,
|
|
147
|
-
flow_id=args.flow_id,
|
|
148
|
-
question=args.question,
|
|
149
|
-
user=args.user,
|
|
150
|
-
collection=args.collection,
|
|
151
|
-
entity_limit=args.entity_limit,
|
|
152
|
-
triple_limit=args.triple_limit,
|
|
153
|
-
max_subgraph_size=args.max_subgraph_size,
|
|
154
|
-
max_path_length=args.max_path_length,
|
|
155
|
-
streaming=not args.no_streaming,
|
|
156
|
-
token=args.token,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
except Exception as e:
|
|
160
|
-
|
|
161
|
-
print("Exception:", e, flush=True)
|
|
162
|
-
|
|
163
|
-
if __name__ == "__main__":
|
|
164
|
-
main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.1.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|