ltcai 1.1.0 → 1.2.0
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.
- package/README.md +19 -0
- package/docs/CHANGELOG.md +49 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/health.py +45 -0
- package/latticeai/api/workspace.py +748 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +68 -705
- package/latticeai/services/__init__.py +6 -0
- package/latticeai/services/chat_service.py +53 -0
- package/latticeai/services/model_service.py +51 -0
- package/latticeai/services/workspace_service.py +117 -0
- package/package.json +1 -1
package/latticeai/server_app.py
CHANGED
|
@@ -101,6 +101,11 @@ from latticeai.core.enterprise import (
|
|
|
101
101
|
capability_registry,
|
|
102
102
|
detect_edition,
|
|
103
103
|
)
|
|
104
|
+
from latticeai.services.workspace_service import WorkspaceService
|
|
105
|
+
from latticeai.services.model_service import ModelService
|
|
106
|
+
from latticeai.services.chat_service import ChatService
|
|
107
|
+
from latticeai.api.workspace import create_workspace_router
|
|
108
|
+
from latticeai.api.health import create_health_router
|
|
104
109
|
from latticeai.core.agent import (
|
|
105
110
|
AgentState,
|
|
106
111
|
AgentRunContext,
|
|
@@ -357,6 +362,9 @@ SSO_FILE = DATA_DIR / "sso_config.json"
|
|
|
357
362
|
KNOWLEDGE_GRAPH = KnowledgeGraphStore(DATA_DIR / "knowledge_graph.sqlite", DATA_DIR / "knowledge_graph_blobs") if ENABLE_GRAPH else None
|
|
358
363
|
LOCAL_KG_WATCHER = LocalKnowledgeWatcher(lambda: KNOWLEDGE_GRAPH) if ENABLE_GRAPH else None
|
|
359
364
|
WORKSPACE_OS = WorkspaceOSStore(DATA_DIR)
|
|
365
|
+
# Service layer (latticeai.services) wraps the store with scope/permission
|
|
366
|
+
# guardrails; routers and the app assembly share this single instance.
|
|
367
|
+
WORKSPACE_SERVICE = WorkspaceService(WORKSPACE_OS)
|
|
360
368
|
|
|
361
369
|
def _require_graph():
|
|
362
370
|
if not ENABLE_GRAPH or KNOWLEDGE_GRAPH is None:
|
|
@@ -727,6 +735,10 @@ def get_history():
|
|
|
727
735
|
logging.warning("get_history failed: %s", e)
|
|
728
736
|
return []
|
|
729
737
|
|
|
738
|
+
# Chat service seam: behaviour-preserving façade for history access and
|
|
739
|
+
# Workspace-OS answer-trace recording used by the (unchanged) streaming chat path.
|
|
740
|
+
CHAT_SERVICE = ChatService(store=WORKSPACE_OS, get_history=get_history)
|
|
741
|
+
|
|
730
742
|
def conversation_title(item: Dict) -> str:
|
|
731
743
|
content = str(item.get("content") or "").strip()
|
|
732
744
|
content = re.sub(r"\s+", " ", content)
|
|
@@ -1317,22 +1329,8 @@ async def admin_page():
|
|
|
1317
1329
|
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
|
1318
1330
|
return response
|
|
1319
1331
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
require_user(request)
|
|
1323
|
-
workspace_path = STATIC_DIR / "workspace.html"
|
|
1324
|
-
if not workspace_path.exists():
|
|
1325
|
-
raise HTTPException(status_code=404, detail="Workspace OS UI not found.")
|
|
1326
|
-
return ui_file_response(workspace_path)
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
@app.get("/onboarding")
|
|
1330
|
-
async def onboarding_page(request: Request):
|
|
1331
|
-
require_user(request)
|
|
1332
|
-
workspace_path = STATIC_DIR / "workspace.html"
|
|
1333
|
-
if not workspace_path.exists():
|
|
1334
|
-
raise HTTPException(status_code=404, detail="Workspace OS UI not found.")
|
|
1335
|
-
return ui_file_response(workspace_path)
|
|
1332
|
+
# /workspace and /onboarding UI pages are served by the workspace router
|
|
1333
|
+
# (latticeai.api.workspace), included below after its dependencies are defined.
|
|
1336
1334
|
|
|
1337
1335
|
@app.get("/status")
|
|
1338
1336
|
async def status():
|
|
@@ -1437,102 +1435,7 @@ class VerifyCloudRequest(BaseModel):
|
|
|
1437
1435
|
provider: Optional[str] = None
|
|
1438
1436
|
|
|
1439
1437
|
|
|
1440
|
-
|
|
1441
|
-
step: str
|
|
1442
|
-
status: str = "complete"
|
|
1443
|
-
data: Dict = {}
|
|
1444
|
-
error: str = ""
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
class WorkspaceOnboardingCompleteRequest(BaseModel):
|
|
1448
|
-
data: Dict = {}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
class WorkspaceSnapshotRequest(BaseModel):
|
|
1452
|
-
name: str = "Workspace snapshot"
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
class WorkspaceSnapshotCompareRequest(BaseModel):
|
|
1456
|
-
before_id: str
|
|
1457
|
-
after_id: str
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
class WorkspaceMemoryRequest(BaseModel):
|
|
1461
|
-
kind: str
|
|
1462
|
-
content: str
|
|
1463
|
-
tags: List[str] = []
|
|
1464
|
-
memory_id: Optional[str] = None
|
|
1465
|
-
metadata: Dict = {}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
class WorkspaceAgentRunRequest(BaseModel):
|
|
1469
|
-
agent_id: str = "agent:executor"
|
|
1470
|
-
status: str = "ok"
|
|
1471
|
-
input: str = ""
|
|
1472
|
-
output: str = ""
|
|
1473
|
-
timeline: List[Dict] = []
|
|
1474
|
-
relationships: List[str] = []
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
class WorkspaceWorkflowRequest(BaseModel):
|
|
1478
|
-
name: str
|
|
1479
|
-
steps: List[Dict] = []
|
|
1480
|
-
metadata: Dict = {}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
class WorkspaceWorkflowEventRequest(BaseModel):
|
|
1484
|
-
event_type: str
|
|
1485
|
-
payload: Dict = {}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
class WorkspaceComputerMemoryRequest(BaseModel):
|
|
1489
|
-
enabled: bool = False
|
|
1490
|
-
consent: Dict = {}
|
|
1491
|
-
scopes: List[str] = []
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
class WorkspaceComputerActivityRequest(BaseModel):
|
|
1495
|
-
activity: Dict = {}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
class WorkspaceSkillActionRequest(BaseModel):
|
|
1499
|
-
skill: str
|
|
1500
|
-
plugin: Optional[str] = None
|
|
1501
|
-
enabled: Optional[bool] = None
|
|
1502
|
-
version: Optional[str] = None
|
|
1503
|
-
metadata: Dict = {}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
class WorkspaceVSCodeRequest(BaseModel):
|
|
1507
|
-
action: str
|
|
1508
|
-
file_path: Optional[str] = None
|
|
1509
|
-
language: Optional[str] = None
|
|
1510
|
-
content: str = ""
|
|
1511
|
-
selection: str = ""
|
|
1512
|
-
prompt: str = ""
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
class WorkspaceCreateRequest(BaseModel):
|
|
1516
|
-
name: str
|
|
1517
|
-
settings: Dict = {}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
class WorkspaceUpdateRequest(BaseModel):
|
|
1521
|
-
name: Optional[str] = None
|
|
1522
|
-
settings: Optional[Dict] = None
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
class WorkspaceMemberRequest(BaseModel):
|
|
1526
|
-
user_id: str
|
|
1527
|
-
role: str = "member"
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
class WorkspaceMemberRoleRequest(BaseModel):
|
|
1531
|
-
role: str
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
class WorkspaceActivateRequest(BaseModel):
|
|
1535
|
-
workspace_id: str
|
|
1438
|
+
# Workspace request models moved to latticeai.api.workspace (v1.2.0 modularization).
|
|
1536
1439
|
|
|
1537
1440
|
|
|
1538
1441
|
class GardenRequest(BaseModel):
|
|
@@ -1736,562 +1639,37 @@ def _workspace_graph():
|
|
|
1736
1639
|
return KNOWLEDGE_GRAPH if (ENABLE_GRAPH and KNOWLEDGE_GRAPH) else None
|
|
1737
1640
|
|
|
1738
1641
|
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
async def workspace_onboarding_step(req: WorkspaceOnboardingStepRequest, request: Request):
|
|
1771
|
-
current_user = require_user(request)
|
|
1772
|
-
return WORKSPACE_OS.update_onboarding_step(
|
|
1773
|
-
req.step,
|
|
1774
|
-
status=req.status,
|
|
1775
|
-
data=req.data,
|
|
1776
|
-
error=req.error,
|
|
1777
|
-
user_email=current_user or None,
|
|
1778
|
-
)
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
@app.post("/workspace/onboarding/complete")
|
|
1782
|
-
async def workspace_onboarding_complete(req: WorkspaceOnboardingCompleteRequest, request: Request):
|
|
1783
|
-
current_user = require_user(request)
|
|
1784
|
-
append_audit_event("onboarding_complete", user_email=current_user, platform="AI Workspace OS")
|
|
1785
|
-
return WORKSPACE_OS.complete_onboarding(req.data, user_email=current_user or None)
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
@app.get("/workspace/onboarding/hardware")
|
|
1789
|
-
async def workspace_onboarding_hardware(request: Request):
|
|
1790
|
-
require_user(request)
|
|
1791
|
-
env = await asyncio.to_thread(scan_environment)
|
|
1792
|
-
sysinfo = await local_sysinfo(request)
|
|
1793
|
-
payload = {"environment": env, "sysinfo": sysinfo, "scanned_at": datetime.now().isoformat()}
|
|
1794
|
-
WORKSPACE_OS.update_onboarding_step("hardware", status="complete", data=payload, user_email=get_current_user(request))
|
|
1795
|
-
return payload
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
@app.get("/workspace/onboarding/model-recommendations")
|
|
1799
|
-
async def workspace_onboarding_model_recommendations(request: Request):
|
|
1800
|
-
require_user(request)
|
|
1801
|
-
env = await asyncio.to_thread(scan_environment)
|
|
1802
|
-
recommendations = get_recommendations(env)
|
|
1803
|
-
payload = {
|
|
1804
|
-
"environment": env,
|
|
1805
|
-
"recommendations": recommendations,
|
|
1806
|
-
"default_local_model": LOCAL_MODEL,
|
|
1807
|
-
"default_public_model": PUBLIC_MODEL,
|
|
1808
|
-
}
|
|
1809
|
-
WORKSPACE_OS.update_onboarding_step("model_recommendation", status="complete", data=payload, user_email=get_current_user(request))
|
|
1810
|
-
return payload
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
@app.get("/workspace/traces")
|
|
1814
|
-
async def workspace_traces(request: Request, conversation_id: Optional[str] = None, limit: int = 50):
|
|
1815
|
-
require_user(request)
|
|
1816
|
-
return WORKSPACE_OS.list_traces(conversation_id=conversation_id, limit=limit, workspace_id=_workspace_scope(request))
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
@app.get("/workspace/indexing")
|
|
1820
|
-
async def workspace_indexing_dashboard(request: Request):
|
|
1821
|
-
require_user(request)
|
|
1822
|
-
graph = _workspace_graph()
|
|
1823
|
-
watcher_status = LOCAL_KG_WATCHER.status() if LOCAL_KG_WATCHER else {"available": False, "active": {}}
|
|
1824
|
-
return WORKSPACE_OS.build_indexing_dashboard(graph, watcher_status)
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
@app.post("/workspace/indexing/{source_id}/pause")
|
|
1828
|
-
async def workspace_indexing_pause(source_id: str, request: Request):
|
|
1829
|
-
require_user(request)
|
|
1830
|
-
_require_graph()
|
|
1831
|
-
return WORKSPACE_OS.pause_indexing(KNOWLEDGE_GRAPH, source_id, LOCAL_KG_WATCHER)
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
@app.post("/workspace/indexing/{source_id}/resume")
|
|
1835
|
-
async def workspace_indexing_resume(source_id: str, request: Request):
|
|
1836
|
-
require_user(request)
|
|
1837
|
-
_require_graph()
|
|
1838
|
-
return WORKSPACE_OS.resume_indexing(KNOWLEDGE_GRAPH, source_id, LOCAL_KG_WATCHER)
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
@app.post("/workspace/indexing/{source_id}/remove")
|
|
1842
|
-
async def workspace_indexing_remove(source_id: str, request: Request):
|
|
1843
|
-
require_user(request)
|
|
1844
|
-
_require_graph()
|
|
1845
|
-
return WORKSPACE_OS.remove_index_source(KNOWLEDGE_GRAPH, source_id, LOCAL_KG_WATCHER)
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
@app.get("/workspace/snapshots")
|
|
1849
|
-
async def workspace_snapshots(request: Request):
|
|
1850
|
-
require_user(request)
|
|
1851
|
-
return WORKSPACE_OS.list_snapshots(workspace_id=_workspace_scope(request))
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
@app.post("/workspace/snapshots")
|
|
1855
|
-
async def workspace_snapshot_create(req: WorkspaceSnapshotRequest, request: Request):
|
|
1856
|
-
current_user = require_user(request)
|
|
1857
|
-
result = WORKSPACE_OS.create_snapshot(
|
|
1858
|
-
name=req.name,
|
|
1859
|
-
graph=_workspace_graph(),
|
|
1860
|
-
history=get_history(),
|
|
1861
|
-
settings=_workspace_settings_payload(),
|
|
1862
|
-
models=_workspace_models_payload(),
|
|
1863
|
-
workspace_id=_workspace_scope(request),
|
|
1864
|
-
)
|
|
1865
|
-
append_audit_event("workspace_snapshot", user_email=current_user, snapshot_id=result["snapshot"]["id"])
|
|
1866
|
-
return result
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
@app.post("/workspace/snapshots/compare")
|
|
1870
|
-
async def workspace_snapshot_compare(req: WorkspaceSnapshotCompareRequest, request: Request):
|
|
1871
|
-
require_user(request)
|
|
1872
|
-
try:
|
|
1873
|
-
return WORKSPACE_OS.compare_snapshots(req.before_id, req.after_id)
|
|
1874
|
-
except FileNotFoundError as exc:
|
|
1875
|
-
raise HTTPException(status_code=404, detail=f"Snapshot not found: {exc}") from exc
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
@app.get("/workspace/snapshots/{snapshot_id}")
|
|
1879
|
-
async def workspace_snapshot_get(snapshot_id: str, request: Request):
|
|
1880
|
-
require_user(request)
|
|
1881
|
-
try:
|
|
1882
|
-
return WORKSPACE_OS.get_snapshot(snapshot_id)
|
|
1883
|
-
except FileNotFoundError as exc:
|
|
1884
|
-
raise HTTPException(status_code=404, detail=f"Snapshot not found: {exc}") from exc
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
@app.get("/workspace/snapshots/{snapshot_id}/{area}")
|
|
1888
|
-
async def workspace_snapshot_area(snapshot_id: str, area: str, request: Request):
|
|
1889
|
-
require_user(request)
|
|
1890
|
-
try:
|
|
1891
|
-
return WORKSPACE_OS.snapshot_view(snapshot_id, area)
|
|
1892
|
-
except FileNotFoundError as exc:
|
|
1893
|
-
raise HTTPException(status_code=404, detail=f"Snapshot not found: {exc}") from exc
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
@app.post("/workspace/snapshots/{snapshot_id}/export")
|
|
1897
|
-
async def workspace_snapshot_export(snapshot_id: str, request: Request):
|
|
1898
|
-
current_user = require_user(request)
|
|
1899
|
-
try:
|
|
1900
|
-
result = WORKSPACE_OS.export_snapshot(snapshot_id)
|
|
1901
|
-
except FileNotFoundError as exc:
|
|
1902
|
-
raise HTTPException(status_code=404, detail=f"Snapshot not found: {exc}") from exc
|
|
1903
|
-
append_audit_event("workspace_snapshot_export", user_email=current_user, snapshot_id=snapshot_id, path=result.get("export_path"))
|
|
1904
|
-
return result
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
@app.get("/workspace/time-machine")
|
|
1908
|
-
async def workspace_time_machine(request: Request, limit: int = 100):
|
|
1909
|
-
require_user(request)
|
|
1910
|
-
return WORKSPACE_OS.timeline(get_audit_log(), limit=limit, workspace_id=_workspace_scope(request))
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
@app.get("/workspace/time-machine/{snapshot_id}/{area}")
|
|
1914
|
-
async def workspace_time_machine_view(snapshot_id: str, area: str, request: Request):
|
|
1915
|
-
require_user(request)
|
|
1916
|
-
try:
|
|
1917
|
-
return WORKSPACE_OS.snapshot_view(snapshot_id, area)
|
|
1918
|
-
except FileNotFoundError as exc:
|
|
1919
|
-
raise HTTPException(status_code=404, detail=f"Snapshot not found: {exc}") from exc
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
@app.get("/workspace/memories")
|
|
1923
|
-
async def workspace_memories(request: Request, kind: Optional[str] = None):
|
|
1924
|
-
current_user = require_user(request)
|
|
1925
|
-
return WORKSPACE_OS.list_memories(user_email=current_user or None, kind=kind, workspace_id=_workspace_scope(request))
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
@app.get("/workspace/memories/search")
|
|
1929
|
-
async def workspace_memory_search(q: str, request: Request, limit: int = 20):
|
|
1930
|
-
current_user = require_user(request)
|
|
1931
|
-
return WORKSPACE_OS.search_memories(q, user_email=current_user or None, limit=limit, workspace_id=_workspace_scope(request))
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
@app.post("/workspace/memories")
|
|
1935
|
-
async def workspace_memory_upsert(req: WorkspaceMemoryRequest, request: Request):
|
|
1936
|
-
current_user = require_user(request)
|
|
1937
|
-
try:
|
|
1938
|
-
record = WORKSPACE_OS.upsert_memory(
|
|
1939
|
-
kind=req.kind,
|
|
1940
|
-
content=req.content,
|
|
1941
|
-
tags=req.tags,
|
|
1942
|
-
memory_id=req.memory_id,
|
|
1943
|
-
metadata=req.metadata,
|
|
1944
|
-
user_email=current_user or None,
|
|
1945
|
-
graph=_workspace_graph(),
|
|
1946
|
-
workspace_id=_workspace_scope(request),
|
|
1947
|
-
)
|
|
1948
|
-
except ValueError as exc:
|
|
1949
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
1950
|
-
return {"memory": record}
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
@app.delete("/workspace/memories/{memory_id}")
|
|
1954
|
-
async def workspace_memory_delete(memory_id: str, request: Request):
|
|
1955
|
-
require_user(request)
|
|
1956
|
-
try:
|
|
1957
|
-
return WORKSPACE_OS.delete_memory(memory_id)
|
|
1958
|
-
except FileNotFoundError as exc:
|
|
1959
|
-
raise HTTPException(status_code=404, detail=f"Memory not found: {exc}") from exc
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
@app.get("/workspace/agents")
|
|
1963
|
-
async def workspace_agents(request: Request):
|
|
1964
|
-
require_user(request)
|
|
1965
|
-
return WORKSPACE_OS.list_agents(workspace_id=_workspace_scope(request))
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
@app.post("/workspace/agents/runs")
|
|
1969
|
-
async def workspace_agent_run(req: WorkspaceAgentRunRequest, request: Request):
|
|
1970
|
-
current_user = require_user(request)
|
|
1971
|
-
run = WORKSPACE_OS.record_agent_run(
|
|
1972
|
-
agent_id=req.agent_id,
|
|
1973
|
-
status=req.status,
|
|
1974
|
-
input_text=req.input,
|
|
1975
|
-
output_text=req.output,
|
|
1976
|
-
timeline=req.timeline,
|
|
1977
|
-
relationships=req.relationships,
|
|
1978
|
-
user_email=current_user or None,
|
|
1979
|
-
graph=_workspace_graph(),
|
|
1980
|
-
workspace_id=_workspace_scope(request),
|
|
1981
|
-
)
|
|
1982
|
-
return {"run": run}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
@app.get("/workspace/relationships/{node_id:path}")
|
|
1986
|
-
async def workspace_relationships(node_id: str, request: Request, target_id: Optional[str] = None):
|
|
1987
|
-
require_user(request)
|
|
1988
|
-
_require_graph()
|
|
1989
|
-
return WORKSPACE_OS.relationship_explorer(KNOWLEDGE_GRAPH, node_id, target_id=target_id)
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
@app.get("/workspace/computer-memory")
|
|
1993
|
-
async def workspace_computer_memory(request: Request):
|
|
1994
|
-
require_user(request)
|
|
1995
|
-
return WORKSPACE_OS.load_state().get("computer_memory")
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
@app.post("/workspace/computer-memory")
|
|
1999
|
-
async def workspace_computer_memory_config(req: WorkspaceComputerMemoryRequest, request: Request):
|
|
2000
|
-
current_user = require_user(request)
|
|
2001
|
-
try:
|
|
2002
|
-
config = WORKSPACE_OS.configure_computer_memory(
|
|
2003
|
-
enabled=req.enabled,
|
|
2004
|
-
approved_by=current_user or None,
|
|
2005
|
-
consent=req.consent,
|
|
2006
|
-
scopes=req.scopes or None,
|
|
2007
|
-
)
|
|
2008
|
-
except PermissionError as exc:
|
|
2009
|
-
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
2010
|
-
append_audit_event("computer_memory_config", user_email=current_user, enabled=req.enabled)
|
|
2011
|
-
return {"computer_memory": config}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
@app.post("/workspace/computer-memory/activity")
|
|
2015
|
-
async def workspace_computer_memory_activity(req: WorkspaceComputerActivityRequest, request: Request):
|
|
2016
|
-
require_user(request)
|
|
2017
|
-
return WORKSPACE_OS.record_computer_activity(req.activity, graph=_workspace_graph())
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
@app.get("/workspace/workflows")
|
|
2021
|
-
async def workspace_workflows(request: Request, q: str = ""):
|
|
2022
|
-
require_user(request)
|
|
2023
|
-
return WORKSPACE_OS.list_workflows(query=q, workspace_id=_workspace_scope(request))
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
@app.post("/workspace/workflows")
|
|
2027
|
-
async def workspace_workflow_create(req: WorkspaceWorkflowRequest, request: Request):
|
|
2028
|
-
current_user = require_user(request)
|
|
2029
|
-
workflow = WORKSPACE_OS.create_workflow(
|
|
2030
|
-
name=req.name,
|
|
2031
|
-
steps=req.steps,
|
|
2032
|
-
metadata=req.metadata,
|
|
2033
|
-
user_email=current_user or None,
|
|
2034
|
-
graph=_workspace_graph(),
|
|
2035
|
-
workspace_id=_workspace_scope(request),
|
|
2036
|
-
)
|
|
2037
|
-
return {"workflow": workflow}
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
@app.post("/workspace/workflows/{workflow_id}/events")
|
|
2041
|
-
async def workspace_workflow_event(workflow_id: str, req: WorkspaceWorkflowEventRequest, request: Request):
|
|
2042
|
-
require_user(request)
|
|
2043
|
-
try:
|
|
2044
|
-
return {"workflow": WORKSPACE_OS.record_workflow_event(workflow_id, req.event_type, req.payload)}
|
|
2045
|
-
except FileNotFoundError as exc:
|
|
2046
|
-
raise HTTPException(status_code=404, detail=f"Workflow not found: {exc}") from exc
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
@app.get("/workspace/skills")
|
|
2050
|
-
async def workspace_skills(request: Request):
|
|
2051
|
-
require_user(request)
|
|
2052
|
-
marketplace = []
|
|
2053
|
-
try:
|
|
2054
|
-
marketplace = await _fetch_skills_marketplace()
|
|
2055
|
-
except Exception as exc:
|
|
2056
|
-
logging.warning("workspace skills marketplace unavailable: %s", exc)
|
|
2057
|
-
return WORKSPACE_OS.list_skill_registry(SKILLS_DIR, marketplace)
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
@app.post("/workspace/skills/install")
|
|
2061
|
-
async def workspace_skill_install(req: WorkspaceSkillActionRequest, request: Request):
|
|
2062
|
-
admin_email, _ = require_admin(request)
|
|
2063
|
-
if req.plugin:
|
|
2064
|
-
result = await install_skill(req.plugin, req.skill)
|
|
2065
|
-
else:
|
|
2066
|
-
result = {"status": "recorded", "skill": req.skill}
|
|
2067
|
-
entry = WORKSPACE_OS.mark_skill_installed(req.skill, version=req.version or "local", metadata={"install_result": result, **req.metadata})
|
|
2068
|
-
append_audit_event("skill_install", user_email=admin_email, plugin=req.plugin, skill=req.skill, workspace_os=True)
|
|
2069
|
-
return {"skill": entry, "install": result}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
@app.post("/workspace/skills/uninstall")
|
|
2073
|
-
async def workspace_skill_uninstall(req: WorkspaceSkillActionRequest, request: Request):
|
|
2074
|
-
admin_email, _ = require_admin(request)
|
|
2075
|
-
removal = remove_skill_directory(SKILLS_DIR, req.skill)
|
|
2076
|
-
entry = WORKSPACE_OS.mark_skill_uninstalled(req.skill)
|
|
2077
|
-
append_audit_event("skill_uninstall", user_email=admin_email, skill=req.skill, workspace_os=True)
|
|
2078
|
-
return {"skill": entry, "removal": removal}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
@app.post("/workspace/skills/enable")
|
|
2082
|
-
async def workspace_skill_enable(req: WorkspaceSkillActionRequest, request: Request):
|
|
2083
|
-
require_user(request)
|
|
2084
|
-
return {"skill": WORKSPACE_OS.set_skill_enabled(req.skill, True)}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
@app.post("/workspace/skills/disable")
|
|
2088
|
-
async def workspace_skill_disable(req: WorkspaceSkillActionRequest, request: Request):
|
|
2089
|
-
require_user(request)
|
|
2090
|
-
return {"skill": WORKSPACE_OS.set_skill_enabled(req.skill, False)}
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
@app.post("/workspace/skills/update")
|
|
2094
|
-
async def workspace_skill_update(req: WorkspaceSkillActionRequest, request: Request):
|
|
2095
|
-
admin_email, _ = require_admin(request)
|
|
2096
|
-
if req.plugin:
|
|
2097
|
-
result = await install_skill(req.plugin, req.skill)
|
|
2098
|
-
else:
|
|
2099
|
-
result = {"status": "version_recorded", "skill": req.skill}
|
|
2100
|
-
entry = WORKSPACE_OS.mark_skill_installed(req.skill, version=req.version or "latest", metadata={"update_result": result, **req.metadata})
|
|
2101
|
-
append_audit_event("skill_update", user_email=admin_email, plugin=req.plugin, skill=req.skill, workspace_os=True)
|
|
2102
|
-
return {"skill": entry, "update": result}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
@app.get("/workspace/audit-timeline")
|
|
2106
|
-
async def workspace_audit_timeline(
|
|
2107
|
-
request: Request,
|
|
2108
|
-
user: Optional[str] = None,
|
|
2109
|
-
event_type: Optional[str] = None,
|
|
2110
|
-
model: Optional[str] = None,
|
|
2111
|
-
since: Optional[str] = None,
|
|
2112
|
-
until: Optional[str] = None,
|
|
2113
|
-
limit: int = 100,
|
|
2114
|
-
):
|
|
2115
|
-
require_admin(request)
|
|
2116
|
-
return WORKSPACE_OS.filter_audit_timeline(
|
|
2117
|
-
get_audit_log(),
|
|
2118
|
-
user=user,
|
|
2119
|
-
event_type=event_type,
|
|
2120
|
-
model=model,
|
|
2121
|
-
since=since,
|
|
2122
|
-
until=until,
|
|
2123
|
-
limit=limit,
|
|
2124
|
-
)
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
@app.post("/workspace/vscode/send")
|
|
2128
|
-
async def workspace_vscode_send(req: WorkspaceVSCodeRequest, request: Request):
|
|
2129
|
-
current_user = require_user(request)
|
|
2130
|
-
content = req.selection or req.content or req.prompt
|
|
2131
|
-
workflow = WORKSPACE_OS.create_workflow(
|
|
2132
|
-
name=f"VS Code: {req.action}",
|
|
2133
|
-
steps=[
|
|
2134
|
-
{"action": req.action, "file_path": req.file_path, "language": req.language},
|
|
2135
|
-
{"action": "send_to_lattice", "chars": len(content or "")},
|
|
2136
|
-
],
|
|
2137
|
-
metadata={
|
|
2138
|
-
"file_path": req.file_path,
|
|
2139
|
-
"language": req.language,
|
|
2140
|
-
"content_preview": redact_secret_text(content or "")[:500],
|
|
2141
|
-
},
|
|
2142
|
-
user_email=current_user or None,
|
|
2143
|
-
graph=_workspace_graph(),
|
|
2144
|
-
)
|
|
2145
|
-
if _workspace_graph() is not None and content:
|
|
2146
|
-
try:
|
|
2147
|
-
_workspace_graph().ingest_event(
|
|
2148
|
-
"VSCodeWorkflow",
|
|
2149
|
-
req.action,
|
|
2150
|
-
user_email=current_user or None,
|
|
2151
|
-
source="vscode",
|
|
2152
|
-
metadata={
|
|
2153
|
-
"file_path": req.file_path,
|
|
2154
|
-
"language": req.language,
|
|
2155
|
-
"chars": len(content),
|
|
2156
|
-
"workflow_id": workflow["id"],
|
|
2157
|
-
},
|
|
2158
|
-
)
|
|
2159
|
-
except Exception as exc:
|
|
2160
|
-
logging.warning("vscode workflow graph ingest failed: %s", exc)
|
|
2161
|
-
return {"status": "ok", "workflow": workflow}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
# ── Organization Workspaces, membership, roles, and edition seam ─────────────────
|
|
2165
|
-
|
|
2166
|
-
@app.get("/workspace/registry")
|
|
2167
|
-
async def workspace_registry(request: Request):
|
|
2168
|
-
user = require_user(request)
|
|
2169
|
-
return WORKSPACE_OS.list_workspaces(user_id=user or None)
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
@app.get("/workspace/editions")
|
|
2173
|
-
async def workspace_editions(request: Request):
|
|
2174
|
-
require_user(request)
|
|
2175
|
-
return capability_registry.describe()
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
@app.post("/workspace/activate")
|
|
2179
|
-
async def workspace_activate(req: WorkspaceActivateRequest, request: Request):
|
|
2180
|
-
user = require_user(request)
|
|
2181
|
-
try:
|
|
2182
|
-
return WORKSPACE_OS.set_active_workspace(req.workspace_id, user_id=user or None)
|
|
2183
|
-
except FileNotFoundError as exc:
|
|
2184
|
-
raise HTTPException(status_code=404, detail=f"Workspace not found: {exc}") from exc
|
|
2185
|
-
except PermissionError as exc:
|
|
2186
|
-
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
@app.post("/workspace/orgs")
|
|
2190
|
-
async def workspace_org_create(req: WorkspaceCreateRequest, request: Request):
|
|
2191
|
-
user = require_user(request)
|
|
2192
|
-
try:
|
|
2193
|
-
workspace = WORKSPACE_OS.create_organization_workspace(
|
|
2194
|
-
name=req.name,
|
|
2195
|
-
owner_user_id=user or None,
|
|
2196
|
-
settings=req.settings,
|
|
2197
|
-
)
|
|
2198
|
-
except ValueError as exc:
|
|
2199
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
2200
|
-
append_audit_event("workspace_created", user_email=user, workspace_id=workspace["workspace_id"])
|
|
2201
|
-
return {"workspace": workspace}
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
@app.get("/workspace/orgs/{workspace_id}")
|
|
2205
|
-
async def workspace_org_get(workspace_id: str, request: Request):
|
|
2206
|
-
user = require_user(request)
|
|
2207
|
-
try:
|
|
2208
|
-
return {"workspace": WORKSPACE_OS.get_workspace(workspace_id, user_id=user or None)}
|
|
2209
|
-
except FileNotFoundError as exc:
|
|
2210
|
-
raise HTTPException(status_code=404, detail=f"Workspace not found: {exc}") from exc
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
@app.get("/workspace/orgs/{workspace_id}/summary")
|
|
2214
|
-
async def workspace_org_summary(workspace_id: str, request: Request):
|
|
2215
|
-
user = require_user(request)
|
|
2216
|
-
try:
|
|
2217
|
-
return WORKSPACE_OS.workspace_summary(workspace_id, user_id=user or None)
|
|
2218
|
-
except FileNotFoundError as exc:
|
|
2219
|
-
raise HTTPException(status_code=404, detail=f"Workspace not found: {exc}") from exc
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
@app.patch("/workspace/orgs/{workspace_id}")
|
|
2223
|
-
async def workspace_org_update(workspace_id: str, req: WorkspaceUpdateRequest, request: Request):
|
|
2224
|
-
user = require_user(request)
|
|
2225
|
-
try:
|
|
2226
|
-
workspace = WORKSPACE_OS.update_workspace(workspace_id, name=req.name, settings=req.settings, actor=user or None)
|
|
2227
|
-
except FileNotFoundError as exc:
|
|
2228
|
-
raise HTTPException(status_code=404, detail=f"Workspace not found: {exc}") from exc
|
|
2229
|
-
except PermissionError as exc:
|
|
2230
|
-
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
2231
|
-
except ValueError as exc:
|
|
2232
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
2233
|
-
append_audit_event("workspace_updated", user_email=user, workspace_id=workspace_id)
|
|
2234
|
-
return {"workspace": workspace}
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
@app.post("/workspace/orgs/{workspace_id}/archive")
|
|
2238
|
-
async def workspace_org_archive(workspace_id: str, request: Request):
|
|
2239
|
-
user = require_user(request)
|
|
2240
|
-
try:
|
|
2241
|
-
workspace = WORKSPACE_OS.archive_workspace(workspace_id, actor=user or None)
|
|
2242
|
-
except FileNotFoundError as exc:
|
|
2243
|
-
raise HTTPException(status_code=404, detail=f"Workspace not found: {exc}") from exc
|
|
2244
|
-
except PermissionError as exc:
|
|
2245
|
-
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
2246
|
-
except ValueError as exc:
|
|
2247
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
2248
|
-
append_audit_event("workspace_archived", user_email=user, workspace_id=workspace_id)
|
|
2249
|
-
return {"workspace": workspace}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
@app.post("/workspace/orgs/{workspace_id}/members")
|
|
2253
|
-
async def workspace_org_add_member(workspace_id: str, req: WorkspaceMemberRequest, request: Request):
|
|
2254
|
-
user = require_user(request)
|
|
2255
|
-
try:
|
|
2256
|
-
workspace = WORKSPACE_OS.add_member(workspace_id, user_id=req.user_id, role=req.role, actor=user or None)
|
|
2257
|
-
except FileNotFoundError as exc:
|
|
2258
|
-
raise HTTPException(status_code=404, detail=f"Workspace not found: {exc}") from exc
|
|
2259
|
-
except PermissionError as exc:
|
|
2260
|
-
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
2261
|
-
except ValueError as exc:
|
|
2262
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
2263
|
-
append_audit_event("workspace_member_added", user_email=user, workspace_id=workspace_id, member=req.user_id, role=req.role)
|
|
2264
|
-
return {"workspace": workspace}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
@app.patch("/workspace/orgs/{workspace_id}/members/{user_id}")
|
|
2268
|
-
async def workspace_org_update_member(workspace_id: str, user_id: str, req: WorkspaceMemberRoleRequest, request: Request):
|
|
2269
|
-
user = require_user(request)
|
|
2270
|
-
try:
|
|
2271
|
-
workspace = WORKSPACE_OS.update_member_role(workspace_id, user_id=user_id, role=req.role, actor=user or None)
|
|
2272
|
-
except FileNotFoundError as exc:
|
|
2273
|
-
raise HTTPException(status_code=404, detail=f"Not found: {exc}") from exc
|
|
2274
|
-
except PermissionError as exc:
|
|
2275
|
-
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
2276
|
-
except ValueError as exc:
|
|
2277
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
2278
|
-
append_audit_event("workspace_member_role_updated", user_email=user, workspace_id=workspace_id, member=user_id, role=req.role)
|
|
2279
|
-
return {"workspace": workspace}
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
@app.delete("/workspace/orgs/{workspace_id}/members/{user_id}")
|
|
2283
|
-
async def workspace_org_remove_member(workspace_id: str, user_id: str, request: Request):
|
|
2284
|
-
user = require_user(request)
|
|
2285
|
-
try:
|
|
2286
|
-
workspace = WORKSPACE_OS.remove_member(workspace_id, user_id=user_id, actor=user or None)
|
|
2287
|
-
except FileNotFoundError as exc:
|
|
2288
|
-
raise HTTPException(status_code=404, detail=f"Not found: {exc}") from exc
|
|
2289
|
-
except PermissionError as exc:
|
|
2290
|
-
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
2291
|
-
except ValueError as exc:
|
|
2292
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
2293
|
-
append_audit_event("workspace_member_removed", user_email=user, workspace_id=workspace_id, member=user_id)
|
|
2294
|
-
return {"workspace": workspace}
|
|
1642
|
+
# ── Workspace OS + Organization router (latticeai.api.workspace, v1.2.0) ──────
|
|
1643
|
+
app.include_router(create_workspace_router(
|
|
1644
|
+
service=WORKSPACE_SERVICE,
|
|
1645
|
+
require_user=require_user,
|
|
1646
|
+
require_admin=require_admin,
|
|
1647
|
+
get_current_user=get_current_user,
|
|
1648
|
+
append_audit_event=append_audit_event,
|
|
1649
|
+
graph_stats=_graph_stats_safe,
|
|
1650
|
+
workspace_models=_workspace_models_payload,
|
|
1651
|
+
workspace_settings=_workspace_settings_payload,
|
|
1652
|
+
get_history=get_history,
|
|
1653
|
+
get_audit_log=get_audit_log,
|
|
1654
|
+
require_graph=_require_graph,
|
|
1655
|
+
workspace_graph=_workspace_graph,
|
|
1656
|
+
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
1657
|
+
local_kg_watcher=LOCAL_KG_WATCHER,
|
|
1658
|
+
load_users=load_users,
|
|
1659
|
+
scan_environment=scan_environment,
|
|
1660
|
+
local_sysinfo=local_sysinfo,
|
|
1661
|
+
get_recommendations=get_recommendations,
|
|
1662
|
+
fetch_skills_marketplace=_fetch_skills_marketplace,
|
|
1663
|
+
install_skill=install_skill,
|
|
1664
|
+
remove_skill_directory=remove_skill_directory,
|
|
1665
|
+
redact_secret_text=redact_secret_text,
|
|
1666
|
+
skills_dir=SKILLS_DIR,
|
|
1667
|
+
capability_registry=capability_registry,
|
|
1668
|
+
ui_file_response=ui_file_response,
|
|
1669
|
+
static_dir=STATIC_DIR,
|
|
1670
|
+
local_model=LOCAL_MODEL,
|
|
1671
|
+
public_model=PUBLIC_MODEL,
|
|
1672
|
+
))
|
|
2295
1673
|
|
|
2296
1674
|
|
|
2297
1675
|
# ── Health & Info ──────────────────────────────────────────────────────────────
|
|
@@ -4182,37 +3560,22 @@ async def verify_cloud_models(force: bool = False, provider_filter: Optional[str
|
|
|
4182
3560
|
results[model_ref] = record
|
|
4183
3561
|
return results
|
|
4184
3562
|
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
"features": runtime_features(),
|
|
4202
|
-
"providers": router.detected_cloud_models(),
|
|
4203
|
-
"engines": engines,
|
|
4204
|
-
}
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
@app.get("/mode")
|
|
4208
|
-
@app.get("/runtime_features")
|
|
4209
|
-
async def mode():
|
|
4210
|
-
return runtime_features()
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
@app.get("/engines")
|
|
4214
|
-
async def engines():
|
|
4215
|
-
return {"engines": await asyncio.to_thread(engine_status), "current": router.current_model_id}
|
|
3563
|
+
# ── Health / status / engine-summary router (latticeai.api.health, v1.2.0) ───
|
|
3564
|
+
# /health, /mode, /runtime_features, /engines(GET) now live in the health router.
|
|
3565
|
+
# Heavier engine mutation endpoints remain below in server_app.
|
|
3566
|
+
MODEL_SERVICE = ModelService(
|
|
3567
|
+
model_router=router,
|
|
3568
|
+
runtime_features=runtime_features,
|
|
3569
|
+
is_public=IS_PUBLIC_MODE,
|
|
3570
|
+
)
|
|
3571
|
+
app.include_router(create_health_router(
|
|
3572
|
+
model_service=MODEL_SERVICE,
|
|
3573
|
+
engine_status=engine_status,
|
|
3574
|
+
get_current_user=get_current_user,
|
|
3575
|
+
require_auth=REQUIRE_AUTH,
|
|
3576
|
+
app_version=APP_VERSION,
|
|
3577
|
+
app_mode=APP_MODE,
|
|
3578
|
+
))
|
|
4216
3579
|
|
|
4217
3580
|
|
|
4218
3581
|
@app.post("/engines/install")
|
|
@@ -4615,7 +3978,7 @@ async def chat(req: ChatRequest, request: Request):
|
|
|
4615
3978
|
except Exception:
|
|
4616
3979
|
pass
|
|
4617
3980
|
|
|
4618
|
-
trace_seed =
|
|
3981
|
+
trace_seed = CHAT_SERVICE.build_graph_trace(
|
|
4619
3982
|
req.message,
|
|
4620
3983
|
KNOWLEDGE_GRAPH if (ENABLE_GRAPH and KNOWLEDGE_GRAPH) else None,
|
|
4621
3984
|
context,
|
|
@@ -4653,7 +4016,7 @@ async def chat(req: ChatRequest, request: Request):
|
|
|
4653
4016
|
full_text += footnote
|
|
4654
4017
|
session.update(graph_md, full_text, req.conversation_id)
|
|
4655
4018
|
save_to_history("assistant", full_text, source=req.source or "web", conversation_id=req.conversation_id, **history_user)
|
|
4656
|
-
trace_record =
|
|
4019
|
+
trace_record = CHAT_SERVICE.record_trace(
|
|
4657
4020
|
question=req.message,
|
|
4658
4021
|
response=full_text,
|
|
4659
4022
|
conversation_id=req.conversation_id,
|
|
@@ -4679,7 +4042,7 @@ async def chat(req: ChatRequest, request: Request):
|
|
|
4679
4042
|
result += footnote
|
|
4680
4043
|
session.update(graph_md, result, req.conversation_id)
|
|
4681
4044
|
save_to_history("assistant", str(result), source=req.source or "web", conversation_id=req.conversation_id, **history_user)
|
|
4682
|
-
trace_record =
|
|
4045
|
+
trace_record = CHAT_SERVICE.record_trace(
|
|
4683
4046
|
question=req.message,
|
|
4684
4047
|
response=str(result),
|
|
4685
4048
|
conversation_id=req.conversation_id,
|
|
@@ -4716,7 +4079,7 @@ async def chat(req: ChatRequest, request: Request):
|
|
|
4716
4079
|
result = await router.generate(req.message, full_context, req.max_tokens, req.temperature, req.image_data)
|
|
4717
4080
|
|
|
4718
4081
|
save_to_history("assistant", str(result), source=req.source or "web", conversation_id=req.conversation_id, **history_user)
|
|
4719
|
-
trace_record =
|
|
4082
|
+
trace_record = CHAT_SERVICE.record_trace(
|
|
4720
4083
|
question=req.message,
|
|
4721
4084
|
response=str(result),
|
|
4722
4085
|
conversation_id=req.conversation_id,
|
|
@@ -4820,12 +4183,12 @@ async def _stream_chat(
|
|
|
4820
4183
|
yield f"data: {json.dumps({'chunk': clean_chunk, 'model': router.current_model_id}, ensure_ascii=False)}\n\n"
|
|
4821
4184
|
history_user = get_history_user(req.user_email, req.user_nickname)
|
|
4822
4185
|
save_to_history("assistant", full_response, source=req.source or "web", conversation_id=req.conversation_id, **history_user)
|
|
4823
|
-
trace_record =
|
|
4186
|
+
trace_record = CHAT_SERVICE.record_trace(
|
|
4824
4187
|
question=req.message,
|
|
4825
4188
|
response=full_response,
|
|
4826
4189
|
conversation_id=req.conversation_id,
|
|
4827
4190
|
user_email=effective_email or req.user_email,
|
|
4828
|
-
trace=trace_seed or
|
|
4191
|
+
trace=trace_seed or CHAT_SERVICE.build_graph_trace(
|
|
4829
4192
|
req.message,
|
|
4830
4193
|
KNOWLEDGE_GRAPH if (ENABLE_GRAPH and KNOWLEDGE_GRAPH) else None,
|
|
4831
4194
|
context,
|