flare-kernel 0.1.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.
@@ -0,0 +1,330 @@
1
+ """Mode orchestration helpers for the kernel runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ _REQUIREMENT_CANVAS_NEXT_ACTIONS: list[dict[str, Any]] = [
9
+ {
10
+ "action_key": "continue_collection",
11
+ "label": "继续梳理",
12
+ "target_mode": "requirement_canvas",
13
+ "priority": 1,
14
+ "status": "blocked",
15
+ "reason": "字段已完整,当前可以直接进入下一步。",
16
+ "description": "继续补齐字段,提升需求结构化完整度。",
17
+ },
18
+ {
19
+ "action_key": "handoff_to_sourcing",
20
+ "label": "进入智能寻源",
21
+ "target_mode": "intelligent_sourcing",
22
+ "priority": 2,
23
+ "status": "available",
24
+ "reason": "必填字段已补齐,可以进入智能寻源。",
25
+ "description": "在必填字段完整后切换到候选匹配与风险摘要。",
26
+ },
27
+ ]
28
+
29
+
30
+ def _coerce_message_excerpt(payload: dict[str, Any]) -> str:
31
+ message = payload.get("message")
32
+ if isinstance(message, str):
33
+ return message.strip()
34
+ return ""
35
+
36
+
37
+ def _coerce_mapping(value: Any) -> dict[str, Any]:
38
+ return value if isinstance(value, dict) else {}
39
+
40
+
41
+ def _build_requirement_canvas_events(
42
+ *,
43
+ mode_key: str,
44
+ session_id: str,
45
+ payload: dict[str, Any],
46
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], str]:
47
+ fields = _coerce_mapping(payload.get("fields"))
48
+ sources = _coerce_mapping(payload.get("field_sources"))
49
+ required_fields = payload.get("required_fields")
50
+ if not isinstance(required_fields, list):
51
+ required_fields = []
52
+
53
+ collected = [key for key, value in fields.items() if value not in (None, "", [], {})]
54
+ missing = [field for field in required_fields if field not in collected]
55
+ progress = 0.0
56
+ if required_fields:
57
+ progress = len(collected) / len(required_fields)
58
+
59
+ field_progress_payload = {
60
+ "mode_key": mode_key,
61
+ "session_id": session_id,
62
+ "project_id": payload.get("project_id"),
63
+ "message_excerpt": _coerce_message_excerpt(payload),
64
+ "fields": fields,
65
+ "sources": sources,
66
+ "field_entries": payload.get("field_entries", []),
67
+ "field_groups": payload.get("field_groups", {}),
68
+ "extra_fields": payload.get("extra_fields", []),
69
+ "progress": progress,
70
+ "required_progress": payload.get("required_progress", progress),
71
+ "recommended_progress": payload.get("recommended_progress"),
72
+ "optional_progress": payload.get("optional_progress"),
73
+ "collected": collected,
74
+ "missing": missing,
75
+ "required_collected": payload.get("required_collected", []),
76
+ "required_missing": payload.get("required_missing", missing),
77
+ "recommended_collected": payload.get("recommended_collected", []),
78
+ "recommended_missing": payload.get("recommended_missing", []),
79
+ "optional_collected": payload.get("optional_collected", []),
80
+ "optional_missing": payload.get("optional_missing", []),
81
+ "last_field": payload.get("last_field"),
82
+ "refresh_reason": payload.get("refresh_reason"),
83
+ "field_definitions": payload.get("field_definitions", []),
84
+ "required_fields": required_fields,
85
+ "recommended_fields": payload.get("recommended_fields", []),
86
+ "optional_fields": payload.get("optional_fields", []),
87
+ "field_priorities": payload.get("field_priorities", {}),
88
+ "mode_state": "collecting",
89
+ "render_hint": "field_progress",
90
+ }
91
+
92
+ requirement_draft_payload = {
93
+ "mode_key": mode_key,
94
+ "session_id": session_id,
95
+ "project_id": payload.get("project_id"),
96
+ "message_excerpt": _coerce_message_excerpt(payload),
97
+ "draft": {
98
+ "fields": fields,
99
+ "summary": payload.get("summary") or payload.get("draft_summary") or "",
100
+ "status": payload.get("draft_status") or "collecting",
101
+ },
102
+ "mode_state": "drafting",
103
+ "render_hint": "requirement_draft",
104
+ }
105
+
106
+ next_actions_payload = {
107
+ "mode_key": mode_key,
108
+ "session_id": session_id,
109
+ "project_id": payload.get("project_id"),
110
+ "message_excerpt": _coerce_message_excerpt(payload),
111
+ "actions": _REQUIREMENT_CANVAS_NEXT_ACTIONS,
112
+ "next_actions": _REQUIREMENT_CANVAS_NEXT_ACTIONS,
113
+ "completion_state": "collecting",
114
+ "required_missing": missing,
115
+ "recommended_missing": payload.get("recommended_missing", []),
116
+ "optional_missing": payload.get("optional_missing", []),
117
+ "ready_for_sourcing": True,
118
+ "status": "available",
119
+ "reason": "必填字段已补齐,可以进入智能寻源。",
120
+ "mode_state": "collecting",
121
+ "render_hint": "next_actions",
122
+ }
123
+
124
+ events = [
125
+ {
126
+ "event_type": "field_progress",
127
+ "step_id": "field_progress",
128
+ "agent": "ModeOrchestration",
129
+ "mode_key": mode_key,
130
+ "payload": field_progress_payload,
131
+ },
132
+ {
133
+ "event_type": "requirement_draft",
134
+ "step_id": "requirement_draft",
135
+ "agent": "ModeOrchestration",
136
+ "mode_key": mode_key,
137
+ "payload": requirement_draft_payload,
138
+ },
139
+ {
140
+ "event_type": "next_actions",
141
+ "step_id": "next_actions",
142
+ "agent": "ModeOrchestration",
143
+ "mode_key": mode_key,
144
+ "payload": next_actions_payload,
145
+ },
146
+ ]
147
+ return events, _REQUIREMENT_CANVAS_NEXT_ACTIONS, "collecting"
148
+
149
+
150
+ def _build_intelligent_sourcing_events(
151
+ *,
152
+ mode_key: str,
153
+ session_id: str,
154
+ payload: dict[str, Any],
155
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], str]:
156
+ base_fields = _coerce_mapping(payload.get("base_fields"))
157
+ base_collected = [key for key, value in base_fields.items() if value not in (None, "", [], {})]
158
+ base_missing = [key for key in ("category", "region") if key not in base_fields]
159
+ base_total = max(len(base_fields), len(base_collected) + len(base_missing))
160
+ base_progress = len(base_collected) / base_total if base_total else 0.0
161
+
162
+ candidates_payload = {
163
+ "mode_key": mode_key,
164
+ "session_id": session_id,
165
+ "project_id": payload.get("project_id"),
166
+ "message_excerpt": _coerce_message_excerpt(payload),
167
+ "missing": base_missing,
168
+ "base_fields": list(base_fields.keys()),
169
+ "base_collected": base_collected,
170
+ "base_missing": base_missing,
171
+ "base_total": base_total,
172
+ "base_progress": base_progress,
173
+ "base_ready_for_matching": True,
174
+ "mode_state": payload.get("mode_state") or "matching",
175
+ "candidate_count": len(payload.get("candidates", [])) if isinstance(payload.get("candidates"), list) else None,
176
+ "is_placeholder": False,
177
+ "required_missing": payload.get("required_missing", []),
178
+ "recommended_missing": payload.get("recommended_missing", []),
179
+ "optional_missing": payload.get("optional_missing", []),
180
+ "actions": payload.get("actions", []),
181
+ "candidates": payload.get("candidates", payload.get("sourcing_candidates", [])),
182
+ "reasoning": payload.get("reasoning", []),
183
+ "summary": payload.get("summary") or payload.get("sourcing_summary"),
184
+ "render_hint": "sourcing_candidates",
185
+ }
186
+
187
+ risk_summary_payload = {
188
+ "mode_key": mode_key,
189
+ "session_id": session_id,
190
+ "project_id": payload.get("project_id"),
191
+ "message_excerpt": _coerce_message_excerpt(payload),
192
+ "mode_state": "screening",
193
+ "base_fields": list(base_fields.keys()),
194
+ "base_collected": base_collected,
195
+ "base_missing": base_missing,
196
+ "base_total": base_total,
197
+ "base_progress": base_progress,
198
+ "base_ready_for_matching": True,
199
+ "is_placeholder": False,
200
+ "actions": payload.get("actions", []),
201
+ "risks": payload.get("risks", payload.get("risk_items", [])),
202
+ "sources": payload.get("sources", payload.get("references", [])),
203
+ "summary": payload.get("risk_summary") or payload.get("summary") or "",
204
+ "render_hint": "risk_summary",
205
+ }
206
+
207
+ shortlist = payload.get("shortlist")
208
+ if not isinstance(shortlist, list):
209
+ shortlist = payload.get("shortlist_updated")
210
+ if not isinstance(shortlist, list):
211
+ shortlist = payload.get("selected_candidates", [])
212
+ if not isinstance(shortlist, list):
213
+ shortlist = []
214
+
215
+ shortlist_payload = {
216
+ "mode_key": mode_key,
217
+ "session_id": session_id,
218
+ "project_id": payload.get("project_id"),
219
+ "message_excerpt": _coerce_message_excerpt(payload),
220
+ "mode_state": "shortlisted",
221
+ "base_fields": list(base_fields.keys()),
222
+ "base_collected": base_collected,
223
+ "base_missing": base_missing,
224
+ "base_total": base_total,
225
+ "base_progress": base_progress,
226
+ "base_ready_for_matching": True,
227
+ "shortlist": shortlist,
228
+ "status": payload.get("status") or payload.get("state") or "available",
229
+ "reason": payload.get("reason") or payload.get("message") or "",
230
+ "selected_count": len(shortlist),
231
+ "is_placeholder": False,
232
+ "actions": payload.get("actions", []),
233
+ "render_hint": "shortlist_updated",
234
+ }
235
+
236
+ report = payload.get("report")
237
+ if not isinstance(report, dict):
238
+ report = payload.get("evaluation_report_ready") if isinstance(payload.get("evaluation_report_ready"), dict) else {}
239
+
240
+ evaluation_report_ready_payload = {
241
+ "mode_key": mode_key,
242
+ "session_id": session_id,
243
+ "project_id": payload.get("project_id"),
244
+ "message_excerpt": _coerce_message_excerpt(payload),
245
+ "mode_state": "report_ready",
246
+ "base_fields": list(base_fields.keys()),
247
+ "base_collected": base_collected,
248
+ "base_missing": base_missing,
249
+ "base_total": base_total,
250
+ "base_progress": base_progress,
251
+ "base_ready_for_matching": True,
252
+ "is_placeholder": False,
253
+ "actions": payload.get("actions", []),
254
+ "report": {
255
+ **report,
256
+ "sections": report.get("sections", []) if isinstance(report.get("sections"), list) else [],
257
+ },
258
+ "compare_table": payload.get("compare_table") or payload.get("comparison_table") or payload.get("compare_rows"),
259
+ "render_hint": "evaluation_report_ready",
260
+ }
261
+
262
+ events = [
263
+ {
264
+ "event_type": "sourcing_candidates",
265
+ "step_id": "sourcing_candidates",
266
+ "agent": "ModeOrchestration",
267
+ "mode_key": mode_key,
268
+ "payload": candidates_payload,
269
+ },
270
+ {
271
+ "event_type": "risk_summary",
272
+ "step_id": "risk_summary",
273
+ "agent": "ModeOrchestration",
274
+ "mode_key": mode_key,
275
+ "payload": risk_summary_payload,
276
+ },
277
+ {
278
+ "event_type": "shortlist_updated",
279
+ "step_id": "shortlist_updated",
280
+ "agent": "ModeOrchestration",
281
+ "mode_key": mode_key,
282
+ "payload": shortlist_payload,
283
+ },
284
+ {
285
+ "event_type": "evaluation_report_ready",
286
+ "step_id": "evaluation_report_ready",
287
+ "agent": "ModeOrchestration",
288
+ "mode_key": mode_key,
289
+ "payload": evaluation_report_ready_payload,
290
+ },
291
+ ]
292
+ return events, [], "report_ready"
293
+
294
+
295
+ def build_mode_orchestration(
296
+ *,
297
+ trace_id: str,
298
+ session_id: str,
299
+ payload: dict[str, Any],
300
+ mode_runtime: dict[str, str],
301
+ agent_runtime: dict[str, Any],
302
+ skill_runtime: dict[str, Any],
303
+ domain_pack: dict[str, Any] | None,
304
+ ) -> dict[str, Any]:
305
+ del trace_id, agent_runtime, skill_runtime, domain_pack
306
+
307
+ mode_key = mode_runtime.get("mode_key", "unknown")
308
+ if mode_key == "requirement_canvas":
309
+ mode_events, next_actions, mode_state = _build_requirement_canvas_events(
310
+ mode_key=mode_key,
311
+ session_id=session_id,
312
+ payload=payload,
313
+ )
314
+ elif mode_key == "intelligent_sourcing":
315
+ mode_events, next_actions, mode_state = _build_intelligent_sourcing_events(
316
+ mode_key=mode_key,
317
+ session_id=session_id,
318
+ payload=payload,
319
+ )
320
+ else:
321
+ mode_events = []
322
+ next_actions = []
323
+ mode_state = mode_runtime.get("mode_state", "unknown")
324
+
325
+ return {
326
+ "mode_key": mode_key,
327
+ "mode_state": mode_state,
328
+ "mode_events": mode_events,
329
+ "next_actions": next_actions,
330
+ }
@@ -0,0 +1,262 @@
1
+ """Placeholder mode/context runtime helpers for the kernel."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import math
7
+ import os
8
+ from functools import lru_cache
9
+ from typing import Any
10
+
11
+ from flare_kernel.runtime.domain_pack_loader import load_requirement_schema
12
+
13
+ _MODE_STATE_BY_KEY = {
14
+ "requirement_canvas": "collecting",
15
+ "intelligent_sourcing": "collecting",
16
+ }
17
+
18
+ _MODE_ALIASES = {
19
+ "requirement_canvas": "requirement_canvas",
20
+ "requirement_refinement": "requirement_canvas",
21
+ "需求梳理": "requirement_canvas",
22
+ "intelligent_sourcing": "intelligent_sourcing",
23
+ "智能寻源": "intelligent_sourcing",
24
+ }
25
+
26
+ _SYSTEM_INSTRUCTION = "You are FLARE kernel. Respond concisely and helpfully."
27
+
28
+
29
+ def _env(name: str, default: str | None = None) -> str | None:
30
+ raw = os.getenv(name)
31
+ if raw is None:
32
+ return default
33
+ value = raw.strip()
34
+ return value if value else default
35
+
36
+
37
+ def _int_env(name: str, default: int) -> int:
38
+ raw = _env(name)
39
+ if raw is None:
40
+ return default
41
+ try:
42
+ return max(0, int(raw))
43
+ except ValueError:
44
+ return default
45
+
46
+
47
+ def _coerce_text(value: Any) -> str:
48
+ if value is None:
49
+ return ""
50
+ if isinstance(value, str):
51
+ return value.strip()
52
+ if isinstance(value, (dict, list, tuple)):
53
+ return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
54
+ return str(value).strip()
55
+
56
+
57
+ def _estimate_tokens(value: Any) -> int:
58
+ text = _coerce_text(value)
59
+ if not text:
60
+ return 0
61
+ return max(1, math.ceil(len(text) / 4))
62
+
63
+
64
+ @lru_cache(maxsize=1)
65
+ def get_context_budget_config() -> dict[str, int]:
66
+ return {
67
+ "total_tokens": _int_env("FLARE_KERNEL_CONTEXT_BUDGET_TOTAL_TOKENS", 2048),
68
+ "current_input_tokens": _int_env("FLARE_KERNEL_CONTEXT_BUDGET_CURRENT_INPUT_TOKENS", 256),
69
+ "session_state_tokens": _int_env("FLARE_KERNEL_CONTEXT_BUDGET_SESSION_STATE_TOKENS", 256),
70
+ "history_summary_tokens": _int_env("FLARE_KERNEL_CONTEXT_BUDGET_HISTORY_SUMMARY_TOKENS", 384),
71
+ "system_instruction_tokens": _int_env("FLARE_KERNEL_CONTEXT_BUDGET_SYSTEM_INSTRUCTION_TOKENS", 256),
72
+ "retrieved_knowledge_tokens": _int_env("FLARE_KERNEL_CONTEXT_BUDGET_RETRIEVED_KNOWLEDGE_TOKENS", 512),
73
+ "memory_recall_tokens": _int_env("FLARE_KERNEL_CONTEXT_BUDGET_MEMORY_RECALL_TOKENS", 256),
74
+ }
75
+
76
+
77
+ def _resolve_mode_key(intent: str, function_type: str | None) -> str:
78
+ candidate = function_type or intent
79
+ if not isinstance(candidate, str):
80
+ return "unknown"
81
+ normalized = candidate.strip()
82
+ return _MODE_ALIASES.get(normalized, "unknown")
83
+
84
+
85
+ def _resolve_mode_state(mode_key: str) -> str:
86
+ return _MODE_STATE_BY_KEY.get(mode_key, "unknown")
87
+
88
+
89
+ def _resolve_function_type(req_payload: dict[str, Any]) -> str | None:
90
+ raw_function_type = req_payload.get("function_type")
91
+ if isinstance(raw_function_type, str):
92
+ value = raw_function_type.strip()
93
+ return value or None
94
+ return None
95
+
96
+
97
+ def _build_session_state(
98
+ *,
99
+ trace_id: str,
100
+ tenant_id: str,
101
+ instance_id: str,
102
+ domain_pack_version: str,
103
+ session_id: str,
104
+ intent: str,
105
+ function_type: str | None,
106
+ mode_key: str,
107
+ mode_state: str,
108
+ instance_profile: dict[str, Any] | None,
109
+ ) -> dict[str, Any]:
110
+ session_state: dict[str, Any] = {
111
+ "trace_id": trace_id,
112
+ "tenant_id": tenant_id,
113
+ "instance_id": instance_id,
114
+ "domain_pack_version": domain_pack_version,
115
+ "session_id": session_id,
116
+ "intent": intent,
117
+ "function_type": function_type,
118
+ "mode_key": mode_key,
119
+ "mode_state": mode_state,
120
+ }
121
+ if isinstance(instance_profile, dict):
122
+ product_name = instance_profile.get("product_name")
123
+ if isinstance(product_name, str) and product_name.strip():
124
+ session_state["product_name"] = product_name.strip()
125
+ return session_state
126
+
127
+
128
+ def build_mode_runtime(*, intent: str, function_type: str | None) -> dict[str, str]:
129
+ mode_key = _resolve_mode_key(intent, function_type)
130
+ return {
131
+ "mode_key": mode_key,
132
+ "mode_state": _resolve_mode_state(mode_key),
133
+ }
134
+
135
+
136
+ def build_context_usage(
137
+ *,
138
+ trace_id: str,
139
+ tenant_id: str,
140
+ instance_id: str,
141
+ domain_pack_version: str,
142
+ session_id: str,
143
+ intent: str,
144
+ function_type: str | None,
145
+ payload: dict[str, Any],
146
+ instance_profile: dict[str, Any] | None,
147
+ mode_runtime: dict[str, str],
148
+ ) -> dict[str, Any]:
149
+ session_state = _build_session_state(
150
+ trace_id=trace_id,
151
+ tenant_id=tenant_id,
152
+ instance_id=instance_id,
153
+ domain_pack_version=domain_pack_version,
154
+ session_id=session_id,
155
+ intent=intent,
156
+ function_type=function_type,
157
+ mode_key=mode_runtime["mode_key"],
158
+ mode_state=mode_runtime["mode_state"],
159
+ instance_profile=instance_profile,
160
+ )
161
+ current_input = payload.get("message") if isinstance(payload.get("message"), str) else ""
162
+ history_summary = payload.get("history_summary")
163
+ retrieved_knowledge = payload.get("retrieved_knowledge")
164
+ memory_recall = payload.get("memory_recall")
165
+
166
+ token_estimate = {
167
+ "current_input": _estimate_tokens(current_input),
168
+ "session_state": _estimate_tokens(session_state),
169
+ "history_summary": _estimate_tokens(history_summary),
170
+ "system_instruction": _estimate_tokens(_SYSTEM_INSTRUCTION),
171
+ "retrieved_knowledge": _estimate_tokens(retrieved_knowledge),
172
+ "memory_recall": _estimate_tokens(memory_recall),
173
+ }
174
+ token_estimate["total_prompt_tokens_estimate"] = sum(token_estimate.values())
175
+
176
+ budget = get_context_budget_config()
177
+ total_tokens = budget["total_tokens"]
178
+ total_estimate = token_estimate["total_prompt_tokens_estimate"]
179
+ if total_tokens <= 0:
180
+ profile = "unknown"
181
+ else:
182
+ ratio = total_estimate / total_tokens
183
+ if ratio <= 0.33:
184
+ profile = "light"
185
+ elif ratio <= 0.66:
186
+ profile = "medium"
187
+ else:
188
+ profile = "heavy"
189
+
190
+ return {
191
+ "trace_id": trace_id,
192
+ "session_id": session_id,
193
+ "intent": intent,
194
+ "function_type": function_type,
195
+ "mode_key": mode_runtime["mode_key"],
196
+ "mode_state": mode_runtime["mode_state"],
197
+ "profile": profile,
198
+ "token_estimate": token_estimate,
199
+ "budget_tokens": budget,
200
+ }
201
+
202
+
203
+ def build_kernel_runtime(
204
+ *,
205
+ trace_id: str,
206
+ tenant_id: str,
207
+ instance_id: str,
208
+ domain_pack_version: str,
209
+ session_id: str,
210
+ intent: str,
211
+ payload: dict[str, Any],
212
+ instance_profile: dict[str, Any] | None,
213
+ ) -> dict[str, Any]:
214
+ from flare_kernel.runtime.agent_runtime import build_agent_runtime
215
+ from flare_kernel.runtime.skill_runtime import build_skill_runtime
216
+ from flare_kernel.runtime.mode_orchestration import build_mode_orchestration
217
+
218
+ function_type = _resolve_function_type(payload)
219
+ domain_pack_schema = load_requirement_schema(instance_id)
220
+ mode_runtime = build_mode_runtime(intent=intent, function_type=function_type)
221
+ context_usage = build_context_usage(
222
+ trace_id=trace_id,
223
+ tenant_id=tenant_id,
224
+ instance_id=instance_id,
225
+ domain_pack_version=domain_pack_version,
226
+ session_id=session_id,
227
+ intent=intent,
228
+ function_type=function_type,
229
+ payload=payload,
230
+ instance_profile=instance_profile,
231
+ mode_runtime=mode_runtime,
232
+ )
233
+ agent_runtime = build_agent_runtime(
234
+ trace_id=trace_id,
235
+ session_id=session_id,
236
+ intent=intent,
237
+ function_type=function_type,
238
+ mode_runtime=mode_runtime,
239
+ )
240
+ skill_runtime = build_skill_runtime(
241
+ trace_id=trace_id,
242
+ session_id=session_id,
243
+ intent=intent,
244
+ function_type=function_type,
245
+ mode_runtime=mode_runtime,
246
+ )
247
+ mode_orchestration = build_mode_orchestration(
248
+ trace_id=trace_id,
249
+ session_id=session_id,
250
+ payload=payload,
251
+ mode_runtime=mode_runtime,
252
+ agent_runtime=agent_runtime,
253
+ skill_runtime=skill_runtime,
254
+ domain_pack=domain_pack_schema,
255
+ )
256
+ return {
257
+ "mode_runtime": mode_runtime,
258
+ "context_usage": context_usage,
259
+ "agent_runtime": agent_runtime,
260
+ "skill_runtime": skill_runtime,
261
+ **mode_orchestration,
262
+ }
@@ -0,0 +1,105 @@
1
+ """Placeholder skill runtime helpers for the kernel."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ _SKILL_CALLS_BY_MODE = {
8
+ "requirement_canvas": [
9
+ {
10
+ "skill_id": "mode_routing",
11
+ "agent_id": "intent_router_agent",
12
+ "status": "planned",
13
+ "permission_scope": "read_only",
14
+ "side_effect": "none",
15
+ "reason": "route the request into the active mode",
16
+ },
17
+ {
18
+ "skill_id": "requirement_field_capture",
19
+ "agent_id": "requirement_collector_agent",
20
+ "status": "planned",
21
+ "permission_scope": "read_only",
22
+ "side_effect": "none",
23
+ "reason": "capture the current requirement fields",
24
+ },
25
+ {
26
+ "skill_id": "knowledge_injection",
27
+ "agent_id": "knowledge_injector_agent",
28
+ "status": "planned",
29
+ "permission_scope": "read_only",
30
+ "side_effect": "none",
31
+ "reason": "inject supporting knowledge when needed",
32
+ },
33
+ ],
34
+ "intelligent_sourcing": [
35
+ {
36
+ "skill_id": "mode_routing",
37
+ "agent_id": "intent_router_agent",
38
+ "status": "planned",
39
+ "permission_scope": "read_only",
40
+ "side_effect": "none",
41
+ "reason": "route the request into the active mode",
42
+ },
43
+ {
44
+ "skill_id": "candidate_matching",
45
+ "agent_id": "sourcing_matcher_agent",
46
+ "status": "planned",
47
+ "permission_scope": "read_only",
48
+ "side_effect": "none",
49
+ "reason": "produce a candidate set from the current context",
50
+ },
51
+ {
52
+ "skill_id": "risk_summary",
53
+ "agent_id": "risk_summarizer_agent",
54
+ "status": "planned",
55
+ "permission_scope": "read_only",
56
+ "side_effect": "none",
57
+ "reason": "summarize sourcing risks for the candidate set",
58
+ },
59
+ {
60
+ "skill_id": "report_building",
61
+ "agent_id": "report_builder_agent",
62
+ "status": "planned",
63
+ "permission_scope": "read_only",
64
+ "side_effect": "none",
65
+ "reason": "assemble a structured sourcing report",
66
+ },
67
+ ],
68
+ "unknown": [
69
+ {
70
+ "skill_id": "mode_routing",
71
+ "agent_id": "intent_router_agent",
72
+ "status": "planned",
73
+ "permission_scope": "read_only",
74
+ "side_effect": "none",
75
+ "reason": "hold the request until a mode can be inferred",
76
+ }
77
+ ],
78
+ }
79
+
80
+
81
+ def _skill_calls_for_mode(mode_key: str) -> list[dict[str, Any]]:
82
+ return [dict(skill_call, call_index=index) for index, skill_call in enumerate(_SKILL_CALLS_BY_MODE.get(mode_key, _SKILL_CALLS_BY_MODE["unknown"]))]
83
+
84
+
85
+ def build_skill_runtime(
86
+ *,
87
+ trace_id: str,
88
+ session_id: str,
89
+ intent: str,
90
+ function_type: str | None,
91
+ mode_runtime: dict[str, str],
92
+ ) -> dict[str, Any]:
93
+ mode_key = mode_runtime.get("mode_key", "unknown")
94
+ mode_state = mode_runtime.get("mode_state", "unknown")
95
+ skill_call = _skill_calls_for_mode(mode_key)
96
+ return {
97
+ "trace_id": trace_id,
98
+ "session_id": session_id,
99
+ "intent": intent,
100
+ "function_type": function_type,
101
+ "mode_key": mode_key,
102
+ "mode_state": mode_state,
103
+ "skill_call": skill_call,
104
+ "skill_call_count": len(skill_call),
105
+ }