ummaya 0.2.0 → 0.2.2
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/bin/ummaya +76 -31
- package/bun.lock +10 -16
- package/npm-shrinkwrap.json +32 -10
- package/package.json +2 -1
- package/pyproject.toml +2 -2
- package/src/ummaya/engine/engine.py +17 -34
- package/src/ummaya/engine/models.py +10 -4
- package/src/ummaya/engine/query.py +108 -5
- package/src/ummaya/ipc/stdio.py +530 -30
- package/src/ummaya/tools/executor.py +52 -6
- package/src/ummaya/tools/hira/hospital_search.py +15 -2
- package/src/ummaya/tools/location_adapters.py +25 -1
- package/src/ummaya/tools/mvp_surface.py +47 -46
- package/src/ummaya/tools/nmc/emergency_search.py +53 -1
- package/src/ummaya/tools/search.py +63 -2
- package/tui/package.json +1 -1
- package/tui/src/query.ts +1 -1
- package/tui/src/services/api/claude.ts +53 -3
- package/tui/src/tools/AdapterTool/AdapterTool.ts +262 -4
- package/tui/src/tools/LookupPrimitive/prompt.ts +19 -30
- package/tui/src/tools/ResolveLocationPrimitive/prompt.ts +12 -10
- package/tui/src/tools/SubmitPrimitive/prompt.ts +10 -6
- package/tui/src/tools/VerifyPrimitive/prompt.ts +10 -5
- package/uv.lock +1 -1
|
@@ -23,6 +23,8 @@ type AdapterPrimitive = 'find' | 'locate' | 'send' | 'check'
|
|
|
23
23
|
|
|
24
24
|
type InputSchema = z.ZodType<{ [key: string]: unknown }>
|
|
25
25
|
|
|
26
|
+
const ROOT_PRIMITIVE_TOOL_NAMES = new Set(['locate', 'find', 'check', 'send'])
|
|
27
|
+
|
|
26
28
|
const fallbackInputSchema = z.object({}).passthrough() as InputSchema
|
|
27
29
|
|
|
28
30
|
type JsonObject = Record<string, unknown>
|
|
@@ -214,6 +216,10 @@ export function isAdapterToolName(name: string): boolean {
|
|
|
214
216
|
return resolveAdapter(name) !== undefined
|
|
215
217
|
}
|
|
216
218
|
|
|
219
|
+
export function isRootPrimitiveToolName(name: string): boolean {
|
|
220
|
+
return ROOT_PRIMITIVE_TOOL_NAMES.has(name)
|
|
221
|
+
}
|
|
222
|
+
|
|
217
223
|
export function getAdapterToolByName(name: string): Tool | undefined {
|
|
218
224
|
const entry = resolveAdapter(name)
|
|
219
225
|
return entry ? buildAdapterTool(entry) : undefined
|
|
@@ -223,6 +229,258 @@ export function getAdapterTools(): Tools {
|
|
|
223
229
|
return listAdapters().map(buildAdapterTool)
|
|
224
230
|
}
|
|
225
231
|
|
|
232
|
+
function searchTokens(text: string): string[] {
|
|
233
|
+
return text.toLowerCase().match(/[\p{L}\p{N}_-]+/gu) ?? []
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function expandedQueryTokens(query: string): Set<string> {
|
|
237
|
+
const tokens = new Set(searchTokens(query))
|
|
238
|
+
const compact = query.toLowerCase()
|
|
239
|
+
if (/[날씨기상비강수기온습도풍속예보관측실황]/u.test(compact)) {
|
|
240
|
+
for (const token of [
|
|
241
|
+
'날씨',
|
|
242
|
+
'기상',
|
|
243
|
+
'현재',
|
|
244
|
+
'관측',
|
|
245
|
+
'실황',
|
|
246
|
+
'weather',
|
|
247
|
+
'current',
|
|
248
|
+
'observation',
|
|
249
|
+
'forecast',
|
|
250
|
+
'temperature',
|
|
251
|
+
'precipitation',
|
|
252
|
+
'humidity',
|
|
253
|
+
'wind',
|
|
254
|
+
]) {
|
|
255
|
+
tokens.add(token)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (/(응급|응급실|er|emergency)/u.test(compact)) {
|
|
259
|
+
for (const token of [
|
|
260
|
+
'응급',
|
|
261
|
+
'응급실',
|
|
262
|
+
'응급의료',
|
|
263
|
+
'응급의료센터',
|
|
264
|
+
'실시간',
|
|
265
|
+
'병상',
|
|
266
|
+
'야간',
|
|
267
|
+
'emergency',
|
|
268
|
+
'room',
|
|
269
|
+
'er',
|
|
270
|
+
'nmc',
|
|
271
|
+
]) {
|
|
272
|
+
tokens.add(token)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (/(병원|의료|진료|약국|hospital|clinic|medical)/u.test(compact)) {
|
|
276
|
+
for (const token of [
|
|
277
|
+
'병원',
|
|
278
|
+
'의료기관',
|
|
279
|
+
'진료',
|
|
280
|
+
'진료과목',
|
|
281
|
+
'야간',
|
|
282
|
+
'약국',
|
|
283
|
+
'hospital',
|
|
284
|
+
'clinic',
|
|
285
|
+
'medical',
|
|
286
|
+
'nearby',
|
|
287
|
+
]) {
|
|
288
|
+
tokens.add(token)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (/(aed|자동심장|심장충격|제세동)/u.test(compact)) {
|
|
292
|
+
for (const token of ['aed', '자동심장충격기', '자동제세동기', '심장충격기', '위치']) {
|
|
293
|
+
tokens.add(token)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (/(미세먼지|초미세|대기질|공기질|airquality|air quality)/u.test(compact)) {
|
|
297
|
+
for (const token of ['미세먼지', '대기질', '대기오염', 'airkorea', 'air', 'quality']) {
|
|
298
|
+
tokens.add(token)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (/(법률|변호사|무료상담|상담)/u.test(compact)) {
|
|
302
|
+
for (const token of ['법률', '변호사', '마을변호사', '상담', 'legal', 'lawyer']) {
|
|
303
|
+
tokens.add(token)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (/(장례|화장|봉안|장사|funeral)/u.test(compact)) {
|
|
307
|
+
for (const token of ['장례', '장례식장', '시설사용료', 'funeral', 'fee']) {
|
|
308
|
+
tokens.add(token)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (/(취업|채용|공고|공무원|job|recruit)/u.test(compact)) {
|
|
312
|
+
for (const token of ['취업', '채용', '공고', '공무원', 'public', 'job']) {
|
|
313
|
+
tokens.add(token)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (/(대학|등록금|유학생|tuition|university)/u.test(compact)) {
|
|
317
|
+
for (const token of ['대학', '등록금', '유학생', '대학알리미', 'tuition', 'university']) {
|
|
318
|
+
tokens.add(token)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (/(전력|전기|한전|계약종별|power|kepco)/u.test(compact)) {
|
|
322
|
+
for (const token of ['전력', '전기사용량', '계약종별', '한전', 'kepco', 'power', 'usage']) {
|
|
323
|
+
tokens.add(token)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (/(특보|예비특보|경보|주의보|태풍|warning|alert)/u.test(compact)) {
|
|
327
|
+
for (const token of ['특보', '예비특보', '경보', '주의보', '기상청', 'weather', 'alert']) {
|
|
328
|
+
tokens.add(token)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (/(교통사고|사고\s*위험|사고다발|위험\s*(구간|도로|지점)|어린이보호구역|보호구역|도로\s*구간|accident|hazard|hotspot)/u.test(compact)) {
|
|
332
|
+
for (const token of [
|
|
333
|
+
'교통사고',
|
|
334
|
+
'사고',
|
|
335
|
+
'위험',
|
|
336
|
+
'위험지점',
|
|
337
|
+
'사고다발',
|
|
338
|
+
'사고다발구역',
|
|
339
|
+
'어린이보호구역',
|
|
340
|
+
'행정동코드',
|
|
341
|
+
'koroad',
|
|
342
|
+
'accident',
|
|
343
|
+
'hazard',
|
|
344
|
+
'hotspot',
|
|
345
|
+
]) {
|
|
346
|
+
tokens.add(token)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (/(주소|위치|좌표|행정|[가-힣]+(시|군|구|동|읍|면|로|길))/u.test(compact)) {
|
|
350
|
+
for (const token of [
|
|
351
|
+
'locate',
|
|
352
|
+
'위치',
|
|
353
|
+
'주소',
|
|
354
|
+
'좌표',
|
|
355
|
+
'행정동',
|
|
356
|
+
'법정동',
|
|
357
|
+
'geocode',
|
|
358
|
+
'address',
|
|
359
|
+
'kakao',
|
|
360
|
+
]) {
|
|
361
|
+
tokens.add(token)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (/(근처|주변|인근|가까운|역|터미널|공항|캠퍼스|대학교|대학|해수욕장|시장|공원|랜드마크|nearby|around)/u.test(compact)) {
|
|
365
|
+
for (const token of [
|
|
366
|
+
'장소',
|
|
367
|
+
'키워드',
|
|
368
|
+
'poi',
|
|
369
|
+
'랜드마크',
|
|
370
|
+
'역',
|
|
371
|
+
'keyword',
|
|
372
|
+
'station',
|
|
373
|
+
'place',
|
|
374
|
+
]) {
|
|
375
|
+
tokens.add(token)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return tokens
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
type ScoredAdapterEntry = {
|
|
382
|
+
entry: AdapterManifestEntry
|
|
383
|
+
score: number
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function queryExplicitlyMentionsCoordinates(query: string): boolean {
|
|
387
|
+
return /좌표|위도|경도|\blat\b|\blon\b|\blongitude\b|\blatitude\b|wgs84|coord|coord2region|reverse geocode|q0|q1/i.test(query)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function isReverseGeocodeAdapter(toolId: string): boolean {
|
|
391
|
+
return toolId === 'kakao_coord_to_region' || toolId === 'sgis_adm_cd_lookup'
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function queryTargetsKoroadHazardDataset(query: string): boolean {
|
|
395
|
+
return /(사고\s*위험|위험\s*(구간|도로|지점)|도로\s*구간|어린이보호구역|보호구역|스쿨존|행정동코드|adm_cd|hazard|hotspot)/iu.test(query)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function scoreAdapterEntry(
|
|
399
|
+
entry: AdapterManifestEntry,
|
|
400
|
+
queryTokens: Set<string>,
|
|
401
|
+
query: string,
|
|
402
|
+
): number {
|
|
403
|
+
const searchHint = entry.search_hint.toLowerCase()
|
|
404
|
+
const description = (entry.llm_description ?? '').toLowerCase()
|
|
405
|
+
const haystack = [
|
|
406
|
+
entry.tool_id,
|
|
407
|
+
entry.name,
|
|
408
|
+
entry.primitive,
|
|
409
|
+
searchHint,
|
|
410
|
+
description,
|
|
411
|
+
].join(' ').toLowerCase()
|
|
412
|
+
const hintTokens = new Set(searchTokens(searchHint))
|
|
413
|
+
let score = 0
|
|
414
|
+
for (const token of queryTokens) {
|
|
415
|
+
if (!token) continue
|
|
416
|
+
if (entry.tool_id.toLowerCase().includes(token)) score += 12
|
|
417
|
+
if (hintTokens.has(token)) score += 8
|
|
418
|
+
else if (searchHint.includes(token)) score += 4
|
|
419
|
+
if (description.includes(token)) score += 2
|
|
420
|
+
if (haystack.includes(token)) score += 1
|
|
421
|
+
}
|
|
422
|
+
if (
|
|
423
|
+
isReverseGeocodeAdapter(entry.tool_id) &&
|
|
424
|
+
!queryExplicitlyMentionsCoordinates(query)
|
|
425
|
+
) {
|
|
426
|
+
score = Math.max(0, score - 24)
|
|
427
|
+
}
|
|
428
|
+
if (queryTargetsKoroadHazardDataset(query)) {
|
|
429
|
+
if (entry.tool_id === 'koroad_accident_hazard_search') score += 32
|
|
430
|
+
if (entry.tool_id === 'koroad_accident_search') score = 0
|
|
431
|
+
}
|
|
432
|
+
return score
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function selectTopKAdapterToolNamesForQuery(
|
|
436
|
+
query: string,
|
|
437
|
+
maxResults = 5,
|
|
438
|
+
): string[] {
|
|
439
|
+
const normalizedQuery = query.trim()
|
|
440
|
+
if (!normalizedQuery || maxResults <= 0) return []
|
|
441
|
+
const queryTokens = expandedQueryTokens(normalizedQuery)
|
|
442
|
+
const ranked = listAdapters()
|
|
443
|
+
.filter(entry => !ROOT_PRIMITIVE_TOOL_NAMES.has(entry.tool_id))
|
|
444
|
+
.map(entry => ({
|
|
445
|
+
entry,
|
|
446
|
+
score: scoreAdapterEntry(entry, queryTokens, normalizedQuery),
|
|
447
|
+
}))
|
|
448
|
+
.filter(candidate => candidate.score > 0)
|
|
449
|
+
.sort((a, b) => {
|
|
450
|
+
if (b.score !== a.score) return b.score - a.score
|
|
451
|
+
return a.entry.tool_id.localeCompare(b.entry.tool_id)
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
return pickDiverseAdapterToolNames(ranked, maxResults)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function pickDiverseAdapterToolNames(
|
|
458
|
+
ranked: ScoredAdapterEntry[],
|
|
459
|
+
maxResults: number,
|
|
460
|
+
): string[] {
|
|
461
|
+
const selected: string[] = []
|
|
462
|
+
const seen = new Set<string>()
|
|
463
|
+
const add = (candidate: ScoredAdapterEntry): void => {
|
|
464
|
+
if (selected.length >= maxResults || seen.has(candidate.entry.tool_id)) return
|
|
465
|
+
selected.push(candidate.entry.tool_id)
|
|
466
|
+
seen.add(candidate.entry.tool_id)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const locateCandidates = ranked.filter(candidate => candidate.entry.primitive === 'locate')
|
|
470
|
+
const actionCandidates = ranked.filter(candidate => candidate.entry.primitive !== 'locate')
|
|
471
|
+
|
|
472
|
+
if (actionCandidates.length > 0) {
|
|
473
|
+
const locateBudget = Math.min(2, Math.max(1, maxResults - 1))
|
|
474
|
+
for (const candidate of locateCandidates.slice(0, locateBudget)) add(candidate)
|
|
475
|
+
for (const candidate of actionCandidates) add(candidate)
|
|
476
|
+
} else {
|
|
477
|
+
for (const candidate of ranked) add(candidate)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
for (const candidate of ranked) add(candidate)
|
|
481
|
+
return selected
|
|
482
|
+
}
|
|
483
|
+
|
|
226
484
|
function buildAdapterTool(entry: AdapterManifestEntry): Tool {
|
|
227
485
|
const primitive = primitiveFor(entry)
|
|
228
486
|
const primitiveTool = primitiveToolFor(primitive)
|
|
@@ -231,10 +489,10 @@ function buildAdapterTool(entry: AdapterManifestEntry): Tool {
|
|
|
231
489
|
|
|
232
490
|
return buildTool({
|
|
233
491
|
name: entry.tool_id,
|
|
234
|
-
//
|
|
235
|
-
// not
|
|
236
|
-
//
|
|
237
|
-
|
|
492
|
+
// Keep concrete public-service adapters in CC's Tool object shape, but do
|
|
493
|
+
// not inline every synced adapter schema into the first LLM request. The
|
|
494
|
+
// ToolSearch path loads the few adapters relevant to the citizen request.
|
|
495
|
+
shouldDefer: true,
|
|
238
496
|
searchHint: [entry.search_hint, entry.name, entry.tool_id, primitive]
|
|
239
497
|
.filter(Boolean)
|
|
240
498
|
.join(' '),
|
|
@@ -1,44 +1,33 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// UMMAYA-original — Epic #1634 P3 · FindPrimitive prompt strings.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// user surfaced via Layer 5 frame capture (specs/2521 frames/raw.cast).
|
|
3
|
+
// 2026 migration note: root primitives are lightweight category descriptors
|
|
4
|
+
// and legacy transcript compatibility wrappers. Concrete adapter functions are
|
|
5
|
+
// loaded through CC ToolSearch/deferred schema expansion or backend top-K
|
|
6
|
+
// retrieval, then called directly with adapter schema arguments.
|
|
8
7
|
// Contract: specs/1634-tool-system-wiring/contracts/primitive-envelope.md § 2
|
|
9
8
|
|
|
10
9
|
export const FIND_TOOL_NAME = 'find'
|
|
11
10
|
|
|
12
11
|
/** Citizen-facing English description shown to the LLM (<= 240 chars). */
|
|
13
12
|
export const DESCRIPTION =
|
|
14
|
-
'
|
|
13
|
+
'Discover Korean public-service lookup adapters. Prefer concrete adapter functions loaded by ToolSearch or backend retrieval; find is a legacy wrapper only.'
|
|
15
14
|
|
|
16
15
|
/** Extended prompt included in the system-prompt tool-use section. */
|
|
17
|
-
export const FIND_TOOL_PROMPT = `
|
|
16
|
+
export const FIND_TOOL_PROMPT = `Discover Korean public-service lookup adapters registered in the UMMAYA tool registry.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
Preferred path:
|
|
19
|
+
- Call concrete adapter functions directly after their schemas are loaded.
|
|
20
|
+
- Example: kma_current_observation({ base_date: "YYYYMMDD", base_time: "HH00", nx: 97, ny: 74 })
|
|
21
|
+
- Adapter schemas are progressively disclosed by ToolSearch or by backend top-K retrieval for the current citizen request.
|
|
22
|
+
- Only top candidates should be loaded; do not expect every adapter schema in the prompt.
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Adapter discovery is a BACKEND-INTERNAL function — NOT a callable mode.
|
|
27
|
-
For every citizen turn the backend runs BM25 against the registry and
|
|
28
|
-
injects the top-K candidates into the system prompt's
|
|
29
|
-
<available_adapters> dynamic suffix. The LLM picks a tool_id from that
|
|
30
|
-
block and calls find directly.
|
|
24
|
+
Legacy root wrapper:
|
|
25
|
+
- If a concrete adapter function is not loaded and only the root primitive is available, find accepts { tool_id, params } for old transcripts and compatibility paths.
|
|
26
|
+
- tool_id must be a concrete adapter id from <available_adapters>, never "find", "locate", "check", or "send".
|
|
27
|
+
- Invalid: find({ tool_id: "find", params: {...} })
|
|
28
|
+
- Compatibility-only: find({ tool_id: "kma_current_observation", params: { base_date: "YYYYMMDD", base_time: "HH00", nx: 97, ny: 74 } })
|
|
31
29
|
|
|
32
30
|
Rules:
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
- Invalid: find({ tool_id: "find", params: {...} })
|
|
37
|
-
- Valid: find({ tool_id: "kma_current_observation", params: {...} })
|
|
38
|
-
- Pick tool_id only from <available_adapters>. Never guess an id.
|
|
39
|
-
- Do NOT call find with mode='search' / query — those payloads are
|
|
40
|
-
rejected with LookupErrorReason.invalid_params (Spec 2521).
|
|
41
|
-
- Do NOT call the same tool_id twice in a single turn — answer with the
|
|
42
|
-
result you already have, or pick a different tool_id from the list.
|
|
43
|
-
- params shape mirrors the adapter's Pydantic input_schema (see the
|
|
44
|
-
<available_adapters> hint for required keys).`
|
|
31
|
+
- Do not call find with mode='search' or query; discovery is handled outside the primitive call.
|
|
32
|
+
- Do not call the same adapter twice in a single turn unless a validation error requires corrected arguments.
|
|
33
|
+
- Use the concrete adapter schema fields exactly; never invent required keys.`
|
|
@@ -5,21 +5,23 @@ export const LOCATE_TOOL_NAME = 'locate'
|
|
|
5
5
|
|
|
6
6
|
/** Citizen-facing English description shown to the LLM. */
|
|
7
7
|
export const DESCRIPTION =
|
|
8
|
-
'
|
|
8
|
+
'Discover Korean location adapters. Prefer concrete location adapter functions loaded by ToolSearch or backend retrieval; locate is a legacy wrapper only.'
|
|
9
9
|
|
|
10
10
|
/** Extended prompt included in the system-prompt tool-use section. */
|
|
11
|
-
export const LOCATE_TOOL_PROMPT = `Resolve
|
|
11
|
+
export const LOCATE_TOOL_PROMPT = `Resolve Korean location phrases with concrete location adapters.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Preferred path:
|
|
14
|
+
- Call concrete adapter functions directly after their schemas are loaded.
|
|
15
|
+
- Examples: kakao_keyword_search({ query: "부산 사하구 다대1동" }) or kakao_coord_to_region({ lat: 35.115446, lon: 128.967669 })
|
|
16
|
+
- Adapter schemas are progressively disclosed by ToolSearch or by backend top-K retrieval for the current citizen request.
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
- tool_id must be a concrete locate adapter id
|
|
19
|
-
- Never set tool_id to a root primitive name: "locate", "find", "check", or "send".
|
|
18
|
+
Legacy root wrapper:
|
|
19
|
+
- If a concrete adapter function is not loaded and only the root primitive is available, locate accepts { tool_id, params } for old transcripts and compatibility paths.
|
|
20
|
+
- tool_id must be a concrete locate adapter id from <available_adapters>, never "locate", "find", "check", or "send".
|
|
20
21
|
- Invalid: locate({ tool_id: "locate", params: {...} })
|
|
21
|
-
-
|
|
22
|
-
|
|
22
|
+
- Compatibility-only: locate({ tool_id: "kakao_address_search", params: { query: "부산 사하구 다대1동" } })
|
|
23
|
+
|
|
24
|
+
Rules:
|
|
23
25
|
- Use kakao_keyword_search for named places, campuses, stations, landmarks, hospitals, and POIs.
|
|
24
26
|
- Coordinate-producing locate results may include KMA nx/ny; pass those exact values to KMA weather adapters that require nx and ny.
|
|
25
27
|
- Use kakao_address_search or juso_adm_cd_lookup for structured road/jibun addresses and district text.
|
|
@@ -6,14 +6,19 @@ export const SEND_TOOL_NAME = 'send'
|
|
|
6
6
|
|
|
7
7
|
/** One-line English description (<= 240 chars). */
|
|
8
8
|
export const DESCRIPTION =
|
|
9
|
-
'
|
|
9
|
+
'Discover side-effecting public-service send adapters. Prefer concrete adapter functions loaded by retrieval; send is a permission-gated legacy wrapper.'
|
|
10
10
|
|
|
11
11
|
/** Extended prompt included in the system-prompt tool-use section. */
|
|
12
|
-
export const SEND_TOOL_PROMPT = `Send a side-effecting citizen action
|
|
12
|
+
export const SEND_TOOL_PROMPT = `Send a side-effecting citizen action through a concrete UMMAYA adapter.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Preferred path:
|
|
15
|
+
- Call concrete adapter functions directly after their schemas are loaded.
|
|
16
|
+
- Adapter schemas are progressively disclosed by ToolSearch or backend top-K retrieval for the current citizen request.
|
|
17
|
+
- Use the adapter's exact schema fields and cite the resulting receipt in the citizen-facing answer.
|
|
18
|
+
|
|
19
|
+
Legacy root wrapper:
|
|
20
|
+
- If a concrete adapter function is not loaded and only the root primitive is available, send accepts { tool_id, params } for old transcripts and compatibility paths.
|
|
21
|
+
- tool_id must be a registered send adapter id, not "send", "find", "locate", or "check".
|
|
17
22
|
|
|
18
23
|
Output: { transaction_id: string, status: string, adapter_receipt: object }
|
|
19
24
|
- transaction_id: deterministically derived identifier for idempotency reasoning
|
|
@@ -21,7 +26,6 @@ Output: { transaction_id: string, status: string, adapter_receipt: object }
|
|
|
21
26
|
- adapter_receipt: adapter-specific confirmation payload
|
|
22
27
|
|
|
23
28
|
Rules:
|
|
24
|
-
- Pick the send adapter from <available_adapters>; BM25 discovery is backend-internal.
|
|
25
29
|
- send is IRREVERSIBLE — confirm intent clearly before calling.
|
|
26
30
|
- The permission gauntlet (Layer 2 orange ⓶) executes before adapter dispatch.
|
|
27
31
|
- Use transaction_id to reason about idempotency (same input → same ID).
|
|
@@ -7,14 +7,19 @@ export const CHECK_TOOL_NAME = 'check'
|
|
|
7
7
|
|
|
8
8
|
/** One-line citizen-facing English description shown to the LLM (<= 240 chars). */
|
|
9
9
|
export const DESCRIPTION =
|
|
10
|
-
'
|
|
10
|
+
'Discover credential-check adapters. Prefer concrete adapter functions loaded by retrieval; check never mints or stores credentials.'
|
|
11
11
|
|
|
12
12
|
/** Extended prompt included in the system-prompt tool-use section. */
|
|
13
|
-
export const CHECK_TOOL_PROMPT = `Delegate credential checking to a
|
|
13
|
+
export const CHECK_TOOL_PROMPT = `Delegate credential checking to a concrete UMMAYA auth adapter.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
Preferred path:
|
|
16
|
+
- Call concrete adapter functions directly after their schemas are loaded.
|
|
17
|
+
- Adapter schemas are progressively disclosed by ToolSearch or backend top-K retrieval for the current citizen request.
|
|
18
|
+
- Use the adapter's exact schema fields for scope, purpose, and session-bound evidence.
|
|
19
|
+
|
|
20
|
+
Legacy root wrapper:
|
|
21
|
+
- If a concrete adapter function is not loaded and only the root primitive is available, check accepts { tool_id, params } for old transcripts and compatibility paths.
|
|
22
|
+
- tool_id must be a registered check adapter id, not "check", "find", "locate", or "send".
|
|
18
23
|
|
|
19
24
|
Output (discriminated by auth_family):
|
|
20
25
|
- auth_family: "gongdong_injeungseo" | "geumyung_injeungseo" | "ganpyeon_injeung" | "digital_onepass" | "mobile_id" | "mydata"
|