plm-skill-kernel 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. plm_skill_kernel/__init__.py +33 -0
  2. plm_skill_kernel/__main__.py +13 -0
  3. plm_skill_kernel/dispatcher.py +141 -0
  4. plm_skill_kernel/registry.py +80 -0
  5. plm_skill_kernel/sdk/__init__.py +29 -0
  6. plm_skill_kernel/sdk/_registry.py +111 -0
  7. plm_skill_kernel/sdk/context.py +66 -0
  8. plm_skill_kernel/sdk/decorator.py +147 -0
  9. plm_skill_kernel/sdk/types.py +86 -0
  10. plm_skill_kernel/server.py +173 -0
  11. plm_skill_kernel/skills/__init__.py +16 -0
  12. plm_skill_kernel/skills/agents/__init__.py +13 -0
  13. plm_skill_kernel/skills/agents/suggest.py +191 -0
  14. plm_skill_kernel/skills/bpmn/__init__.py +9 -0
  15. plm_skill_kernel/skills/bpmn/generate.py +147 -0
  16. plm_skill_kernel/skills/cleansing/__init__.py +10 -0
  17. plm_skill_kernel/skills/cleansing/dedupe.py +162 -0
  18. plm_skill_kernel/skills/cleansing/normalise.py +178 -0
  19. plm_skill_kernel/skills/documents/__init__.py +12 -0
  20. plm_skill_kernel/skills/documents/parse.py +202 -0
  21. plm_skill_kernel/skills/knowledge/__init__.py +32 -0
  22. plm_skill_kernel/skills/knowledge/_v1_corpus.py +153 -0
  23. plm_skill_kernel/skills/knowledge/get_record.py +102 -0
  24. plm_skill_kernel/skills/knowledge/resolve.py +143 -0
  25. plm_skill_kernel/skills/knowledge/retrieve.py +143 -0
  26. plm_skill_kernel/skills/knowledge/search.py +161 -0
  27. plm_skill_kernel/skills/knowledge/validate.py +222 -0
  28. plm_skill_kernel/skills_legacy/__init__.py +0 -0
  29. plm_skill_kernel/skills_legacy/knowledge_pack_loader.py +17 -0
  30. plm_skill_kernel/skills_legacy/knowledge_search_engine.py +25 -0
  31. plm_skill_kernel/skills_legacy/plm_skill_registry.py +573 -0
  32. plm_skill_kernel/skills_legacy/plm_tool_definitions.py +44 -0
  33. plm_skill_kernel/skills_legacy/skill_orchestrator.py +163 -0
  34. plm_skill_kernel-1.0.0.dist-info/METADATA +135 -0
  35. plm_skill_kernel-1.0.0.dist-info/RECORD +38 -0
  36. plm_skill_kernel-1.0.0.dist-info/WHEEL +5 -0
  37. plm_skill_kernel-1.0.0.dist-info/entry_points.txt +2 -0
  38. plm_skill_kernel-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,163 @@
1
+ """Skill Orchestrator — LLM-based selection of PLM expertise profiles for Plan mode.
2
+
3
+ Uses a structured JSON LLM call to select the best expertise profile for a given
4
+ user request, with reasoning and confidence score.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import logging
10
+ from dataclasses import dataclass, field
11
+
12
+ from plm_engine_core.runtime.llm_service import llm_service
13
+ from plm_skill_kernel.skills_legacy.plm_skill_registry import PLMSkillRegistry
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class SkillSelectionResult:
20
+ profile_id: str
21
+ profile_label: str
22
+ domain: str
23
+ reasoning: str # 1-2 sentence explanation
24
+ confidence: float # 0.0 – 1.0
25
+ considered_profiles: list[str] = field(default_factory=list) # top candidates evaluated
26
+
27
+
28
+ class SkillOrchestrator:
29
+ """
30
+ Multi-criteria PLM skill selector.
31
+
32
+ Calls the LLM with a JSON schema prompt to select the best expertise profile
33
+ for a given user request. Falls back to the global PLM profile on error or
34
+ low confidence.
35
+ """
36
+
37
+ _SELECTION_SCHEMA = {
38
+ "type": "object",
39
+ "properties": {
40
+ "profile_id": {
41
+ "type": "string",
42
+ "description": "Exact ID of the selected profile from the list",
43
+ },
44
+ "reasoning": {
45
+ "type": "string",
46
+ "description": "Short explanation (1-2 sentences) of why this profile was selected",
47
+ },
48
+ "confidence": {
49
+ "type": "number",
50
+ "minimum": 0.0,
51
+ "maximum": 1.0,
52
+ "description": "Confidence score between 0 and 1",
53
+ },
54
+ "considered_profiles": {
55
+ "type": "array",
56
+ "items": {"type": "string"},
57
+ "description": "Top 3 profile IDs that were considered (including the selected one)",
58
+ },
59
+ },
60
+ "required": ["profile_id", "reasoning", "confidence", "considered_profiles"],
61
+ }
62
+
63
+ async def select_skill(
64
+ self,
65
+ user_request: str,
66
+ document_context: str = "",
67
+ ) -> SkillSelectionResult:
68
+ """
69
+ Select the most appropriate PLM skill profile for the given request.
70
+
71
+ Args:
72
+ user_request: The user's message/request text.
73
+ document_context: Optional document context excerpt.
74
+
75
+ Returns:
76
+ SkillSelectionResult with selected profile + reasoning.
77
+ Falls back to generique-plm on any error or confidence < 0.3.
78
+ """
79
+ fallback = PLMSkillRegistry.get_fallback()
80
+
81
+ try:
82
+ profiles_json = PLMSkillRegistry.get_descriptions_for_llm()
83
+ valid_ids = PLMSkillRegistry.get_ids()
84
+
85
+ context_section = ""
86
+ if document_context and document_context.strip():
87
+ context_section = (
88
+ f"\n\nContexte documentaire (extrait):\n{document_context[:800]}"
89
+ )
90
+
91
+ user_prompt = (
92
+ f"Analyse la demande suivante et sélectionne le profil d'expertise PLM le plus adapté.\n\n"
93
+ f"Demande utilisateur: {user_request[:600]}"
94
+ f"{context_section}\n\n"
95
+ f"Profils disponibles:\n{profiles_json}\n\n"
96
+ f"Sélectionne le profil dont l'id, le domaine et les mots-clés correspondent le mieux à la demande.\n"
97
+ f"Si la demande est générale ou multi-domaine, utilise \"generique-plm\".\n\n"
98
+ f"IMPORTANT: Réponds UNIQUEMENT avec un JSON valide correspondant exactement au schéma suivant:\n"
99
+ f"{json.dumps(self._SELECTION_SCHEMA, ensure_ascii=False)}"
100
+ )
101
+
102
+ system_prompt = (
103
+ "Tu es un orchestrateur d'expertise PLM. "
104
+ "Tu analyses les demandes utilisateur et sélectionnes le profil d'expertise le plus adapté. "
105
+ "Tu réponds uniquement en JSON structuré, sans texte supplémentaire."
106
+ )
107
+
108
+ result_text = await llm_service.generate_reply(
109
+ system_prompt=system_prompt,
110
+ user_prompt=user_prompt,
111
+ temperature=0.1,
112
+ )
113
+
114
+ # Strip potential markdown code blocks
115
+ result_text = result_text.strip()
116
+ if result_text.startswith("```"):
117
+ lines = result_text.split("\n")
118
+ result_text = "\n".join(lines[1:-1]) if len(lines) > 2 else result_text
119
+ result_text = result_text.replace("```json", "").replace("```", "").strip()
120
+
121
+ parsed = json.loads(result_text)
122
+
123
+ profile_id = str(parsed.get("profile_id", "")).strip()
124
+ if profile_id not in valid_ids:
125
+ logger.warning(
126
+ f"SkillOrchestrator returned unknown profile_id={profile_id!r}, falling back to generique-plm"
127
+ )
128
+ profile_id = fallback.id
129
+
130
+ confidence = float(parsed.get("confidence", 0.5))
131
+ if confidence < 0.3:
132
+ logger.info(
133
+ f"SkillOrchestrator low confidence={confidence:.2f}, falling back to generique-plm"
134
+ )
135
+ profile_id = fallback.id
136
+
137
+ profile = PLMSkillRegistry.get(profile_id) or fallback
138
+ considered_raw = parsed.get("considered_profiles", [])
139
+ considered = [str(p) for p in considered_raw if str(p) in valid_ids][:3]
140
+
141
+ return SkillSelectionResult(
142
+ profile_id=profile.id,
143
+ profile_label=profile.label,
144
+ domain=profile.domain,
145
+ reasoning=str(parsed.get("reasoning", ""))[:300],
146
+ confidence=confidence,
147
+ considered_profiles=considered,
148
+ )
149
+
150
+ except Exception as e:
151
+ logger.warning(f"SkillOrchestrator selection failed: {e!r}, using fallback")
152
+ return SkillSelectionResult(
153
+ profile_id=fallback.id,
154
+ profile_label=fallback.label,
155
+ domain=fallback.domain,
156
+ reasoning="Sélection par défaut (erreur d'orchestration).",
157
+ confidence=0.5,
158
+ considered_profiles=[fallback.id],
159
+ )
160
+
161
+
162
+ # Module-level singleton
163
+ skill_orchestrator = SkillOrchestrator()
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: plm-skill-kernel
3
+ Version: 1.0.0
4
+ Summary: TracePulse PLM Skill Kernel — Wave 2 Conv A. Decorator-based Skill SDK + V1 in-process dispatcher + Kernel HTTP server stub matching CR.10 §7.bis (POST /v1/skills/{id}/invoke). Ships 3 starter skills (cleansing.normalise, cleansing.dedupe, bpmn.generate). Sibling of plm-engine-core; the two communicate over the V1.1 HTTP loopback (SKILL_KERNEL_LOOPBACK=on) — no Python-level coupling at the dispatch boundary.
5
+ Author: TracePulse
6
+ License: Proprietary
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: plm-shared
10
+ Requires-Dist: pydantic>=2.5.0
11
+ Requires-Dist: fastapi>=0.110
12
+ Requires-Dist: uvicorn>=0.27
13
+ Requires-Dist: httpx>=0.27.0
14
+ Requires-Dist: plm-engine-core
15
+ Requires-Dist: plm-knowledge
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest; extra == "test"
18
+ Requires-Dist: pytest-asyncio; extra == "test"
19
+ Requires-Dist: httpx<1,>=0.27; extra == "test"
20
+
21
+ # plm-skill-kernel
22
+
23
+ Wave 2 Conv A (FTR-604). The Skill Kernel package — sibling of
24
+ `plm-engine-core` — that hosts the decorator-based Skill SDK, the V1
25
+ in-process dispatcher, and the Kernel HTTP server stub matching CR.10
26
+ §7.bis verbatim.
27
+
28
+ ## Layout
29
+
30
+ ```
31
+ 02_App/plm-skill-kernel/
32
+ ├── pyproject.toml # editable install + import-linter contracts
33
+ ├── plm_skill_kernel/
34
+ │ ├── __init__.py # re-exports `skill`, `SkillContext`, etc.
35
+ │ ├── __main__.py # `python -m plm_skill_kernel` → server
36
+ │ ├── server.py # FastAPI app at POST /v1/skills/{id}/invoke
37
+ │ ├── dispatcher.py # V1 in-process invoker
38
+ │ ├── registry.py # boot wiring — discovers + registers skills
39
+ │ ├── sdk/
40
+ │ │ ├── decorator.py # `@skill(id=, version=)`
41
+ │ │ ├── context.py # SkillContext Pydantic
42
+ │ │ ├── types.py # SkillInvokeRequest / SkillInvokeResult
43
+ │ │ └── _registry.py # in-process decorator registry
44
+ │ └── skills/ # 3 starter skills (Q-W2A-1 nested layout)
45
+ │ ├── cleansing/
46
+ │ │ ├── normalise.py # cleansing.normalise / 1.0.0 / L2
47
+ │ │ └── dedupe.py # cleansing.dedupe / 1.0.0 / L2
48
+ │ └── bpmn/
49
+ │ └── generate.py # bpmn.generate / 1.0.0 / L1 (thin proxy)
50
+ └── tests/ # unit + ASGI in-process integration
51
+ ```
52
+
53
+ ## Editable install
54
+
55
+ ```bash
56
+ cd 02_App/backend
57
+ .\venv\Scripts\Activate.ps1 # Windows
58
+ pip install -e ../plm-skill-kernel
59
+ ```
60
+
61
+ Mirrors the plm-engine-core editable-install pattern.
62
+
63
+ ## Dev launch (Q-W2A-3)
64
+
65
+ ```bash
66
+ python -m plm_skill_kernel # uvicorn on port 8100
67
+ plm-skill-kernel --port 8100 # equivalent CLI
68
+ ```
69
+
70
+ The backend at port 8000 is unaffected. The V1.1 loopback round-trip
71
+ (D-CONV-M-4 closure) sends HTTP from the Engine Core dispatcher to
72
+ this Kernel server when `SKILL_KERNEL_LOOPBACK=on`.
73
+
74
+ ## V1 wire contract
75
+
76
+ `POST /v1/skills/{id}/invoke` — frozen at CR.10 §7.bis.
77
+
78
+ ```
79
+ Headers:
80
+ X-Core-Caller: <UUID> (required when caller is Core)
81
+ Idempotency-Key: <uuid> (optional, V1)
82
+ traceparent: <W3C> (optional)
83
+
84
+ Request body (SkillInvokeRequest):
85
+ payload: Dict[str, Any]
86
+ version: str ("1.0.0", etc.)
87
+ autonomy_hint: Optional[str] ("L1" / "L2" / "L3")
88
+
89
+ Response body (SkillInvokeResult):
90
+ ok: bool
91
+ result: Optional[Dict[str, Any]]
92
+ error: Optional[SkillErrorEnvelope]
93
+ deferred_to: Optional[str]
94
+ cancelled: Optional[bool]
95
+ ```
96
+
97
+ ## Skill authoring (Decision #59 — decorator)
98
+
99
+ ```python
100
+ from plm_skill_kernel import skill, SkillContext
101
+
102
+ @skill(id="cleansing.normalise", version="1.0.0")
103
+ async def normalise(payload: dict, ctx: SkillContext) -> dict:
104
+ return {"normalised": _normalise_records(payload["records"])}
105
+ ```
106
+
107
+ The decorator auto-registers into the in-process registry at module
108
+ import time + carries `version` through to the federated manifest
109
+ builder (CR.9).
110
+
111
+ ## Import-linter contracts
112
+
113
+ Two Forbidden contracts live in `pyproject.toml`:
114
+
115
+ 1. `plm_skill_kernel` MUST NOT import `plm_engine_core` — federation
116
+ contract preservation. The two communicate over HTTP, not Python.
117
+ 2. `plm_skill_kernel` MUST NOT import `plm_accelerators` (Workbench)
118
+ — mirror of the plm-engine-core constraint.
119
+
120
+ Run:
121
+
122
+ ```bash
123
+ lint-imports --config 02_App/plm-skill-kernel/pyproject.toml
124
+ ```
125
+
126
+ ## Tests
127
+
128
+ ```bash
129
+ cd 02_App/plm-skill-kernel
130
+ python -m pytest -q
131
+ ```
132
+
133
+ The V1.1 HTTP round-trip integration test mounts the FastAPI app via
134
+ httpx.AsyncClient(ASGITransport) — no subprocess, no real port
135
+ (Q-W2A-4).
@@ -0,0 +1,38 @@
1
+ plm_skill_kernel/__init__.py,sha256=R2fBlPKbx_OvvcPOblSObUSmU2-IlA3W-7_OB73JIos,1245
2
+ plm_skill_kernel/__main__.py,sha256=WwG3kHkwheHiKpyYjyAjuCReKTauK3TnpPMuvKQ3FdY,424
3
+ plm_skill_kernel/dispatcher.py,sha256=t2lfGWtkeSotmKph8rpX3FzeOJCgJc70hqNrK8gJR6I,5246
4
+ plm_skill_kernel/registry.py,sha256=329aN2OGUncEL-YMX9wrp5-BdeZhEzELSCWCtKxtFWQ,3464
5
+ plm_skill_kernel/server.py,sha256=QlotMcJfSzCD4aIJxittgvfA343cQUClW2cV7aRJJPg,6022
6
+ plm_skill_kernel/sdk/__init__.py,sha256=Jmo9XVoSzdtkhxIbT4tuH2q3rsXG1hulOHCm4AWjpQ0,804
7
+ plm_skill_kernel/sdk/_registry.py,sha256=5nEp00HUyexO1hZmT_hQppz2grJVJP7d8B2FeDfQyZg,3700
8
+ plm_skill_kernel/sdk/context.py,sha256=l2Wk2mRZIatL_1oh2Qn1dON6-1wyobMgZyV7YzTWy-k,2538
9
+ plm_skill_kernel/sdk/decorator.py,sha256=MvYdIi1YvN9Xv8x_iBxXT8UVefzlbKrzMdaMKVLLHyA,5162
10
+ plm_skill_kernel/sdk/types.py,sha256=XnWo-DLPrPrUvseDwDQoPaJYqTPxCvyKffIcrvDN2cM,3032
11
+ plm_skill_kernel/skills/__init__.py,sha256=iQvcy8h4MlhVLZUJVO8u8OZHaXHjTiX0hUgleCAtYBA,770
12
+ plm_skill_kernel/skills/agents/__init__.py,sha256=z25y3B51ZoKnVEg-OKzx5l3xtTJ0Dfh42XTSgZI5cUg,532
13
+ plm_skill_kernel/skills/agents/suggest.py,sha256=JtOBs5E-62DOIqw1PXMS1a3jHoVWPzQx_yJtpCYLpuE,7313
14
+ plm_skill_kernel/skills/bpmn/__init__.py,sha256=UQjjIzPW4oV24nXgJQq_-8ziX1WGE9gYmXjzc-K02zQ,440
15
+ plm_skill_kernel/skills/bpmn/generate.py,sha256=ySNCzjB1n4gMEHueasdQhfqGLMpKx0a6nQwO5LihDOQ,5522
16
+ plm_skill_kernel/skills/cleansing/__init__.py,sha256=tapVpGsbNQDan7pf-SX1cBbc1j6h3-6xlAvt3EFttRQ,565
17
+ plm_skill_kernel/skills/cleansing/dedupe.py,sha256=g6JKcultWnRGDAFr_2uBN00HYD7B6Z84Zpqhst93v70,5075
18
+ plm_skill_kernel/skills/cleansing/normalise.py,sha256=tK-72K3M_VgQ4UG0OMS37jnHCJUGoKeh8ToQbZAeKYY,6068
19
+ plm_skill_kernel/skills/documents/__init__.py,sha256=p7w9qsS6PhR4IuuvM3oYOAIyOxJjq3pWj33K5-kzEhc,464
20
+ plm_skill_kernel/skills/documents/parse.py,sha256=xqjBtvePldFeEQJ1r2q3IgEjYnvOv_M32yygXi9QDqQ,7648
21
+ plm_skill_kernel/skills/knowledge/__init__.py,sha256=Te_RvJvw0iIw-58_MH83d9RomQwQuFe0-1sOThxmsIw,1485
22
+ plm_skill_kernel/skills/knowledge/_v1_corpus.py,sha256=snnX9n4x-MohhAWDAIN1GDzV9U2xXQcQoIqtSwMSc74,5436
23
+ plm_skill_kernel/skills/knowledge/get_record.py,sha256=arCE32GJacd7hircrWR8DHDvJqBAs6OnmWBhRHen814,3165
24
+ plm_skill_kernel/skills/knowledge/resolve.py,sha256=L-RGTaRL9kqL3k4c5zgtpi6RabplXIPCMMUh2PNJyi4,4643
25
+ plm_skill_kernel/skills/knowledge/retrieve.py,sha256=MbYr_zJS-xxiMWeGTmpdk3F8Z7USdmTVZd_t9NYYBGE,4848
26
+ plm_skill_kernel/skills/knowledge/search.py,sha256=9pwIqgwphHZy2J5vx6A205Dcp6JldbVjjZ6_nMPDCYc,5319
27
+ plm_skill_kernel/skills/knowledge/validate.py,sha256=WKGqidejpOllcM7uF3JNJhj540MRuVEVVVzYyWSl57o,8101
28
+ plm_skill_kernel/skills_legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ plm_skill_kernel/skills_legacy/knowledge_pack_loader.py,sha256=OmnH4G0FBYatvRfmmNnKtuRHBaOtw4NQmsGmQT2cSo0,607
30
+ plm_skill_kernel/skills_legacy/knowledge_search_engine.py,sha256=vlkCXv9pHsmd8l0NYHS042ziob-vW_JfpihIfElV_V4,991
31
+ plm_skill_kernel/skills_legacy/plm_skill_registry.py,sha256=_E88Q4WeYvwGPUtx4R9T3gteTNijbCZVBhe_5a2Y-uo,37491
32
+ plm_skill_kernel/skills_legacy/plm_tool_definitions.py,sha256=iYK4ENGPMILH4l5dxZG_b-J8gwP49tJGMtvmX4Revtc,1713
33
+ plm_skill_kernel/skills_legacy/skill_orchestrator.py,sha256=IhZAg9w31GKiT71xmjmKvtNAGVik3QbErMZMJqu5FRM,6472
34
+ plm_skill_kernel-1.0.0.dist-info/METADATA,sha256=GBSzfc7fXVYP3DdOwjTz0U2d5o-oJd1MSkLrNFVNrps,4894
35
+ plm_skill_kernel-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
36
+ plm_skill_kernel-1.0.0.dist-info/entry_points.txt,sha256=1vXOuJFIwhhkEQkiRdhOi8IhTUEKMVt0MeMnplC2DHA,72
37
+ plm_skill_kernel-1.0.0.dist-info/top_level.txt,sha256=nOy7tCl_qAoG0ppZHRHZQSLB8P6_FOdWbN_ylHfui8Q,17
38
+ plm_skill_kernel-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ plm-skill-kernel = plm_skill_kernel.server:_cli_entry
@@ -0,0 +1 @@
1
+ plm_skill_kernel