ltcai 1.2.0 → 1.4.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.
@@ -0,0 +1,135 @@
1
+ """Tool registry, governance, and dispatch helpers.
2
+
3
+ HTTP routers and the agent runtime share this service so policy checks and
4
+ tool-response shaping are owned outside ``server_app``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import Any, Callable, Dict, Optional
11
+
12
+ from fastapi import HTTPException
13
+
14
+ from latticeai.core.agent import AgentDeps, AgentRuntime
15
+ from latticeai.core.agent_prompts import (
16
+ CRITIC_PROMPT,
17
+ EXECUTOR_PROMPT,
18
+ MEMORY_UPDATER_PROMPT,
19
+ PLANNER_PROMPT,
20
+ )
21
+ from latticeai.core.tool_registry import ToolPermission, ToolPolicy
22
+ from tools import AGENT_ROOT, DEFAULT_TOOL_REGISTRY, ToolError, ensure_agent_root
23
+
24
+
25
+ _load_users: Callable[[], Dict[str, Any]] = lambda: {}
26
+ _get_user_role: Callable[..., str] = lambda _email, _users=None: "user"
27
+
28
+ FILE_CREATE_ACTIONS = set(DEFAULT_TOOL_REGISTRY.file_create_actions)
29
+ TOOL_GOVERNANCE: Dict[str, ToolPolicy] = dict(DEFAULT_TOOL_REGISTRY.governance)
30
+ TOOL_GOVERNANCE_DEFAULT: ToolPolicy = DEFAULT_TOOL_REGISTRY.default_policy
31
+ ADMIN_ONLY_TOOLS: frozenset[str] = DEFAULT_TOOL_REGISTRY.admin_only_tools
32
+ LOCAL_WRITE_BLOCKED_PREFIXES = DEFAULT_TOOL_REGISTRY.local_write_blocked_prefixes
33
+ RISK_LEVEL_MAP = DEFAULT_TOOL_REGISTRY.risk_level_map
34
+
35
+
36
+ def configure_tool_dispatch(
37
+ *,
38
+ load_users: Callable[[], Dict[str, Any]],
39
+ get_user_role: Callable[..., str],
40
+ ) -> None:
41
+ global _load_users, _get_user_role
42
+ _load_users = load_users
43
+ _get_user_role = get_user_role
44
+
45
+
46
+ def agent_policy(action_name: str, args: dict) -> ToolPolicy:
47
+ return DEFAULT_TOOL_REGISTRY.policy_for(action_name, args)
48
+
49
+
50
+ def agent_risk(action_name: str, args: dict) -> str:
51
+ return DEFAULT_TOOL_REGISTRY.risk_level(action_name, args)
52
+
53
+
54
+ def get_tool_permission(name: str, args: Optional[dict] = None) -> ToolPermission:
55
+ return DEFAULT_TOOL_REGISTRY.permission(name, args or {})
56
+
57
+
58
+ def list_tool_permissions() -> list:
59
+ return DEFAULT_TOOL_REGISTRY.permissions()
60
+
61
+
62
+ def check_tool_role(tool_name: str, current_user: str) -> None:
63
+ if tool_name not in ADMIN_ONLY_TOOLS:
64
+ return
65
+ users = _load_users()
66
+ if _get_user_role(current_user, users) != "admin":
67
+ raise HTTPException(
68
+ status_code=403,
69
+ detail=f"'{tool_name}' 툴은 관리자 전용입니다.",
70
+ )
71
+
72
+
73
+ def collect_created_files(transcript: list) -> list:
74
+ files = []
75
+ for step in transcript:
76
+ if step.get("action") in FILE_CREATE_ACTIONS:
77
+ result = step.get("result", {})
78
+ if isinstance(result.get("created_files"), list):
79
+ for rel_path in result["created_files"]:
80
+ files.append({
81
+ "path": rel_path,
82
+ "filename": Path(rel_path).name,
83
+ "bytes": 0,
84
+ "action": step["action"],
85
+ })
86
+ continue
87
+ path = result.get("path")
88
+ if path:
89
+ files.append({
90
+ "path": path,
91
+ "filename": Path(path).name,
92
+ "bytes": result.get("bytes", 0),
93
+ "action": step["action"],
94
+ })
95
+ return files
96
+
97
+
98
+ def build_agent_runtime(
99
+ *,
100
+ model_router: Any,
101
+ execute_tool: Callable[..., Dict[str, Any]],
102
+ recent_chat_context: Callable[..., str],
103
+ clear_history: Callable[[int], Dict[str, Any]],
104
+ knowledge_save: Callable[..., Dict[str, Any]],
105
+ audit: Callable[..., None],
106
+ ) -> AgentRuntime:
107
+ ensure_agent_root()
108
+ deps = AgentDeps(
109
+ generate_as=model_router.generate_as,
110
+ generate=model_router.generate,
111
+ execute_tool=execute_tool,
112
+ policy_for=agent_policy,
113
+ risk_level=lambda policy: RISK_LEVEL_MAP.get(policy["risk"], "medium"),
114
+ check_role=check_tool_role,
115
+ tool_governance=TOOL_GOVERNANCE,
116
+ file_create_actions=frozenset(FILE_CREATE_ACTIONS),
117
+ recent_chat_context=recent_chat_context,
118
+ clear_history=clear_history,
119
+ knowledge_save=knowledge_save,
120
+ audit=audit,
121
+ planner_prompt=PLANNER_PROMPT,
122
+ executor_prompt=EXECUTOR_PROMPT,
123
+ critic_prompt=CRITIC_PROMPT,
124
+ memory_updater_prompt=MEMORY_UPDATER_PROMPT,
125
+ agent_root=AGENT_ROOT,
126
+ )
127
+ return AgentRuntime(deps)
128
+
129
+
130
+ def tool_response(fn, *args):
131
+ try:
132
+ return {"status": "ok", "workspace": str(AGENT_ROOT), "result": fn(*args)}
133
+ except ToolError as exc:
134
+ raise HTTPException(status_code=400, detail=str(exc))
135
+
@@ -0,0 +1,99 @@
1
+ """Document upload parsing, safety checks, and knowledge-graph ingestion."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import tempfile
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+
10
+ from fastapi import HTTPException, Request, UploadFile
11
+
12
+ from tools import ToolError, read_document
13
+
14
+
15
+ async def process_uploaded_document(
16
+ *,
17
+ request: Request,
18
+ file: UploadFile,
19
+ current_user: str,
20
+ enable_graph: bool,
21
+ knowledge_graph,
22
+ bytes_match_extension,
23
+ classify_sensitive_message,
24
+ append_audit_event,
25
+ enforce_rate_limit,
26
+ ) -> dict:
27
+ enforce_rate_limit(current_user, "upload")
28
+ suffix = Path(file.filename or "upload").suffix.lower()
29
+ allowed = {".pdf", ".docx", ".xlsx", ".pptx", ".txt", ".md", ".csv"}
30
+ if suffix not in allowed:
31
+ raise HTTPException(status_code=400, detail=f"지원하지 않는 형식: {suffix}")
32
+
33
+ contents = await file.read()
34
+ if len(contents) > 10 * 1024 * 1024:
35
+ raise HTTPException(status_code=400, detail="파일이 너무 큽니다. 최대 10MB.")
36
+ if not bytes_match_extension(contents, suffix):
37
+ raise HTTPException(status_code=400, detail=f"파일 내용이 확장자({suffix})와 일치하지 않습니다.")
38
+
39
+ with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
40
+ tmp.write(contents)
41
+ tmp_path = tmp.name
42
+
43
+ try:
44
+ result = read_document(tmp_path)
45
+ sensitive = classify_sensitive_message(
46
+ {
47
+ "role": "document",
48
+ "content": result.get("content") or result.get("preview") or "",
49
+ "user_email": current_user,
50
+ "timestamp": datetime.now().isoformat(),
51
+ },
52
+ -1,
53
+ )
54
+ try:
55
+ if not (enable_graph and knowledge_graph):
56
+ raise RuntimeError("graph disabled")
57
+ graph_result = knowledge_graph.ingest_document(
58
+ Path(tmp_path),
59
+ original_filename=file.filename,
60
+ mime_type=file.content_type,
61
+ uploader=current_user,
62
+ conversation_id=request.query_params.get("conversation_id"),
63
+ extracted=result,
64
+ )
65
+ result["knowledge_graph"] = {
66
+ "node_id": graph_result["node_id"],
67
+ "sha256": graph_result["sha256"],
68
+ }
69
+ except Exception as graph_error:
70
+ logging.warning("knowledge graph document ingest failed: %s", graph_error)
71
+ result["knowledge_graph"] = {"error": str(graph_error)}
72
+
73
+ append_audit_event(
74
+ "document_upload",
75
+ user_email=current_user,
76
+ conversation_id=request.query_params.get("conversation_id"),
77
+ filename=file.filename,
78
+ mime_type=file.content_type,
79
+ ext=suffix,
80
+ bytes=len(contents),
81
+ extracted_chars=result.get("chars"),
82
+ graph_node=(result.get("knowledge_graph") or {}).get("node_id"),
83
+ content_preview=sensitive.get("preview"),
84
+ sensitivity=sensitive.get("sensitivity"),
85
+ sensitive_labels=sensitive.get("labels") or [],
86
+ )
87
+ except ToolError as exc:
88
+ raise HTTPException(status_code=400, detail=str(exc))
89
+ finally:
90
+ try:
91
+ Path(tmp_path).unlink()
92
+ except OSError:
93
+ pass
94
+
95
+ result["original_filename"] = file.filename
96
+ return result
97
+
98
+
99
+ __all__ = ["process_uploaded_document"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Lattice AI Workspace OS for local-first graph, memory, agent, workflow, and skill operations",
5
5
  "homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
6
6
  "repository": {
@@ -19,12 +19,12 @@
19
19
  "dev": "python3 ltcai_cli.py --reload",
20
20
  "build": "npm run build:python",
21
21
  "build:python": "python3 -m build",
22
- "check:python": "python3 -m py_compile ltcai_cli.py server.py latticeai/server_app.py latticeai/core/tool_registry.py latticeai/core/agent_prompts.py latticeai/core/workspace_os.py knowledge_graph.py knowledge_graph_api.py local_knowledge_api.py llm_router.py p_reinforce.py telegram_bot.py tools.py codex_telegram_bot.py",
22
+ "check:python": "python3 -m py_compile ltcai_cli.py server.py latticeai/server_app.py latticeai/api/chat.py latticeai/api/computer_use.py latticeai/api/deps.py latticeai/api/garden.py latticeai/api/local_files.py latticeai/api/permissions.py latticeai/api/setup.py latticeai/api/static_routes.py latticeai/api/tools.py latticeai/services/app_context.py latticeai/services/model_runtime.py latticeai/services/tool_dispatch.py latticeai/services/upload_service.py latticeai/core/tool_registry.py latticeai/core/agent_prompts.py latticeai/core/workspace_os.py knowledge_graph.py knowledge_graph_api.py local_knowledge_api.py llm_router.py p_reinforce.py telegram_bot.py tools.py codex_telegram_bot.py",
23
23
  "test": "python3 -m pytest tests/ -v",
24
24
  "test:unit": "python3 -m pytest tests/unit/ -v",
25
25
  "test:integration": "python3 -m pytest tests/integration/ -v",
26
26
  "publish:npm": "npm publish --access public",
27
- "publish:pypi": "python3 -m twine upload --skip-existing dist/*.tar.gz dist/*.whl"
27
+ "publish:pypi": "python3 -m twine upload --skip-existing dist/ltcai-$npm_package_version.tar.gz dist/ltcai-$npm_package_version-py3-none-any.whl"
28
28
  },
29
29
  "keywords": [
30
30
  "ltcai",
@@ -1,7 +1,7 @@
1
1
  # Skill: <name>
2
2
 
3
3
  ## 메타데이터
4
- - **버전**: 0.1.0
4
+ - **버전**: 1.4.0
5
5
  - **카테고리**: coding | data | document | web | system | analysis
6
6
  - **위험도**: low | medium | high
7
7
  - **필요 권한**: none | local_read | local_write | exec | network | admin
@@ -1,7 +1,7 @@
1
1
  # Skill: code_review
2
2
 
3
3
  ## 메타데이터
4
- - **버전**: 0.1.0
4
+ - **버전**: 1.4.0
5
5
  - **카테고리**: coding
6
6
  - **위험도**: low
7
7
  - **필요 권한**: local_read
@@ -1,7 +1,7 @@
1
1
  # Skill: data_analysis
2
2
 
3
3
  ## 메타데이터
4
- - **버전**: 0.1.0
4
+ - **버전**: 1.4.0
5
5
  - **카테고리**: data
6
6
  - **위험도**: low
7
7
  - **필요 권한**: local_read
@@ -1,7 +1,7 @@
1
1
  # Skill: file_edit
2
2
 
3
3
  ## 메타데이터
4
- - **버전**: 0.1.0
4
+ - **버전**: 1.4.0
5
5
  - **카테고리**: system
6
6
  - **위험도**: medium
7
7
  - **필요 권한**: local_write
@@ -1,7 +1,7 @@
1
1
  # Skill: summarize_document
2
2
 
3
3
  ## 메타데이터
4
- - **버전**: 0.1.0
4
+ - **버전**: 1.4.0
5
5
  - **카테고리**: document
6
6
  - **위험도**: low
7
7
  - **필요 권한**: local_read
@@ -1,7 +1,7 @@
1
1
  # Skill: web_search
2
2
 
3
3
  ## 메타데이터
4
- - **버전**: 0.1.0
4
+ - **버전**: 1.4.0
5
5
  - **카테고리**: web
6
6
  - **위험도**: low
7
7
  - **필요 권한**: none