dsh-biocore 0.1.3 → 0.1.5
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/lib/index.js +176 -27
- package/lib/modules/core-turn.js +17 -2
- package/lib/modules/sanitize-reply.js +60 -0
- package/package.json +1 -1
- package/python/biocore_runtime/pipeline.py +42 -0
- package/python/biocore_runtime/rerank.py +201 -0
- package/python/mindbase/__init__.py +18 -0
- package/python/mindbase/__main__.py +3 -0
- package/python/mindbase/actions.py +56 -0
- package/python/mindbase/active_inference.py +54 -0
- package/python/mindbase/addressing.py +69 -0
- package/python/mindbase/affect.py +86 -0
- package/python/mindbase/affective.py +111 -0
- package/python/mindbase/allostasis.py +28 -0
- package/python/mindbase/appraisal.py +101 -0
- package/python/mindbase/attachment.py +49 -0
- package/python/mindbase/attention.py +30 -0
- package/python/mindbase/autonomy.py +81 -0
- package/python/mindbase/autonomy_bench.py +251 -0
- package/python/mindbase/autonomy_evolve.py +342 -0
- package/python/mindbase/autonomy_loop.py +65 -0
- package/python/mindbase/autonomy_meta.py +91 -0
- package/python/mindbase/base_only.py +28 -0
- package/python/mindbase/benchmark.py +61 -0
- package/python/mindbase/bio_adaptation.py +274 -0
- package/python/mindbase/bio_noise.py +39 -0
- package/python/mindbase/bio_semantic.py +138 -0
- package/python/mindbase/bm25.py +46 -0
- package/python/mindbase/bootstrap.py +256 -0
- package/python/mindbase/bootstrap_real.py +231 -0
- package/python/mindbase/brain.py +178 -0
- package/python/mindbase/care.py +78 -0
- package/python/mindbase/chaos_search.py +79 -0
- package/python/mindbase/character_profile.py +33 -0
- package/python/mindbase/circadian.py +38 -0
- package/python/mindbase/circuit.py +235 -0
- package/python/mindbase/clock.py +53 -0
- package/python/mindbase/companion_runtime.py +366 -0
- package/python/mindbase/companion_train.py +126 -0
- package/python/mindbase/config.py +66 -0
- package/python/mindbase/connection_state.py +76 -0
- package/python/mindbase/connectome_prior.py +35 -0
- package/python/mindbase/connectome_world.py +158 -0
- package/python/mindbase/constructs.py +88 -0
- package/python/mindbase/context_builder.py +58 -0
- package/python/mindbase/critical.py +36 -0
- package/python/mindbase/criticality.py +123 -0
- package/python/mindbase/defaults.py +27 -0
- package/python/mindbase/demo.py +163 -0
- package/python/mindbase/diary.py +116 -0
- package/python/mindbase/diary_service.py +40 -0
- package/python/mindbase/digital_prior.py +37 -0
- package/python/mindbase/diversity.py +110 -0
- package/python/mindbase/drives.py +106 -0
- package/python/mindbase/embedding.py +132 -0
- package/python/mindbase/embedding_memory.py +74 -0
- package/python/mindbase/embodiment.py +83 -0
- package/python/mindbase/emotion_calibration.py +39 -0
- package/python/mindbase/emotion_calibrator.py +50 -0
- package/python/mindbase/emotion_core.py +113 -0
- package/python/mindbase/emotion_dynamics.py +104 -0
- package/python/mindbase/emotion_intent.py +164 -0
- package/python/mindbase/emotion_layer.py +200 -0
- package/python/mindbase/emotion_schema.py +92 -0
- package/python/mindbase/engine.py +781 -0
- package/python/mindbase/episode_log.py +90 -0
- package/python/mindbase/evaluation.py +493 -0
- package/python/mindbase/event_appraisal.py +84 -0
- package/python/mindbase/evolution.py +77 -0
- package/python/mindbase/evolve_offline.py +232 -0
- package/python/mindbase/experiments.py +358 -0
- package/python/mindbase/expression.py +54 -0
- package/python/mindbase/external_brain.py +157 -0
- package/python/mindbase/fly_mb.py +159 -0
- package/python/mindbase/fly_tasks.py +45 -0
- package/python/mindbase/forage.py +179 -0
- package/python/mindbase/goal_generator.py +98 -0
- package/python/mindbase/goals.py +153 -0
- package/python/mindbase/grid_world.py +123 -0
- package/python/mindbase/group_autonomy.py +92 -0
- package/python/mindbase/group_context.py +67 -0
- package/python/mindbase/homeostatic.py +71 -0
- package/python/mindbase/homeostatic_action.py +38 -0
- package/python/mindbase/human_standard.py +53 -0
- package/python/mindbase/identity_suite.py +50 -0
- package/python/mindbase/image_context.py +27 -0
- package/python/mindbase/impression.py +119 -0
- package/python/mindbase/initiative_quota.py +145 -0
- package/python/mindbase/insight.py +51 -0
- package/python/mindbase/intelligence_search.py +108 -0
- package/python/mindbase/intention.py +76 -0
- package/python/mindbase/interaction_log.py +30 -0
- package/python/mindbase/interoception.py +67 -0
- package/python/mindbase/invariants.py +53 -0
- package/python/mindbase/iteration.py +29 -0
- package/python/mindbase/layered_memory.py +170 -0
- package/python/mindbase/layers/__init__.py +18 -0
- package/python/mindbase/layers/l0_substrate.py +39 -0
- package/python/mindbase/layers/l1_homeostasis.py +6 -0
- package/python/mindbase/layers/l2_affect.py +6 -0
- package/python/mindbase/layers/l3_attention.py +6 -0
- package/python/mindbase/layers/l4_learning.py +17 -0
- package/python/mindbase/layers/l5_self_social.py +35 -0
- package/python/mindbase/layers/l6_expression.py +23 -0
- package/python/mindbase/layers/l7_evaluation.py +45 -0
- package/python/mindbase/learner.py +196 -0
- package/python/mindbase/lexicon.py +33 -0
- package/python/mindbase/life_state.py +169 -0
- package/python/mindbase/lifecycle.py +87 -0
- package/python/mindbase/liquid_readout.py +48 -0
- package/python/mindbase/llm_adapter.py +233 -0
- package/python/mindbase/llm_cache.py +77 -0
- package/python/mindbase/llm_gate.py +85 -0
- package/python/mindbase/llm_openai.py +130 -0
- package/python/mindbase/llm_setup.py +99 -0
- package/python/mindbase/longrun.py +83 -0
- package/python/mindbase/longterm_memory.py +606 -0
- package/python/mindbase/mb/__init__.py +14 -0
- package/python/mindbase/mb/apl.py +22 -0
- package/python/mindbase/mb/dan.py +14 -0
- package/python/mindbase/mb/habituation.py +27 -0
- package/python/mindbase/mb/kc.py +28 -0
- package/python/mindbase/mb/mbon.py +23 -0
- package/python/mindbase/mb/mushroom_body.py +116 -0
- package/python/mindbase/memory.py +201 -0
- package/python/mindbase/memory_framework.py +158 -0
- package/python/mindbase/memory_retriever.py +110 -0
- package/python/mindbase/memory_summarizer.py +177 -0
- package/python/mindbase/memory_task.py +32 -0
- package/python/mindbase/memory_trace.py +46 -0
- package/python/mindbase/meta.py +32 -0
- package/python/mindbase/motivation.py +31 -0
- package/python/mindbase/multi_session.py +19 -0
- package/python/mindbase/narrative.py +87 -0
- package/python/mindbase/needs.py +77 -0
- package/python/mindbase/neural.py +45 -0
- package/python/mindbase/neuron_models.py +134 -0
- package/python/mindbase/noise.py +309 -0
- package/python/mindbase/one_on_one.py +60 -0
- package/python/mindbase/online_iteration.py +63 -0
- package/python/mindbase/ostracism.py +35 -0
- package/python/mindbase/parallel_eval.py +60 -0
- package/python/mindbase/parallel_qd.py +64 -0
- package/python/mindbase/persona.py +221 -0
- package/python/mindbase/persona_ab.py +64 -0
- package/python/mindbase/persona_bridge.py +129 -0
- package/python/mindbase/persona_compile.py +34 -0
- package/python/mindbase/persona_dsh.py +120 -0
- package/python/mindbase/persona_learner.py +157 -0
- package/python/mindbase/physiology.py +197 -0
- package/python/mindbase/population_lifecycle.py +83 -0
- package/python/mindbase/preferences.py +50 -0
- package/python/mindbase/proactive_agent.py +274 -0
- package/python/mindbase/proactive_gate.py +53 -0
- package/python/mindbase/proactive_group.py +119 -0
- package/python/mindbase/proactive_policy.py +422 -0
- package/python/mindbase/proactive_profile.py +196 -0
- package/python/mindbase/proactive_timing.py +28 -0
- package/python/mindbase/pyramid_stages.py +61 -0
- package/python/mindbase/quantum.py +132 -0
- package/python/mindbase/quantum_chaos.py +98 -0
- package/python/mindbase/reflection.py +68 -0
- package/python/mindbase/regulation_posture.py +50 -0
- package/python/mindbase/relationship_session.py +431 -0
- package/python/mindbase/relationship_suite.py +45 -0
- package/python/mindbase/relationships.py +70 -0
- package/python/mindbase/replay.py +22 -0
- package/python/mindbase/reply_diversity.py +53 -0
- package/python/mindbase/reply_gate.py +52 -0
- package/python/mindbase/rerank.py +41 -0
- package/python/mindbase/rerank_local.py +46 -0
- package/python/mindbase/ripple.py +187 -0
- package/python/mindbase/robust_search.py +68 -0
- package/python/mindbase/runtime.py +113 -0
- package/python/mindbase/scoped_identity.py +116 -0
- package/python/mindbase/self_insight.py +51 -0
- package/python/mindbase/self_model.py +116 -0
- package/python/mindbase/selftest.py +118 -0
- package/python/mindbase/semantic_memory.py +108 -0
- package/python/mindbase/session_store.py +175 -0
- package/python/mindbase/session_window.py +31 -0
- package/python/mindbase/shadow_eval.py +42 -0
- package/python/mindbase/sim_env.py +86 -0
- package/python/mindbase/social.py +138 -0
- package/python/mindbase/social_constraint.py +141 -0
- package/python/mindbase/social_feedback.py +95 -0
- package/python/mindbase/social_norm.py +225 -0
- package/python/mindbase/spiking_reservoir.py +204 -0
- package/python/mindbase/spontaneity.py +40 -0
- package/python/mindbase/story_loop.py +203 -0
- package/python/mindbase/survival_gauntlet.py +128 -0
- package/python/mindbase/symbolic.py +170 -0
- package/python/mindbase/teacher.py +244 -0
- package/python/mindbase/theory.py +42 -0
- package/python/mindbase/theory_of_mind.py +98 -0
- package/python/mindbase/translation_layer.py +80 -0
- package/python/mindbase/turn_eval.py +52 -0
- package/python/mindbase/types.py +78 -0
- package/python/mindbase/uncertainty_field.py +61 -0
- package/python/mindbase/user_loop.py +45 -0
- package/python/mindbase/user_profile.py +73 -0
- package/python/mindbase/value_model.py +64 -0
- package/python/mindbase/values.py +102 -0
- package/python/mindbase/vector_store.py +78 -0
- package/python/mindbase/viability.py +41 -0
- package/python/mindbase/virtual_body.py +90 -0
- package/python/mindbase/will.py +60 -0
- package/python/mindbase/working_memory.py +32 -0
- package/python/mindbase/workspace.py +55 -0
- package/ui/panel.css +13 -1
- package/ui/panel.html +26 -1
- package/ui/panel.js +58 -15
package/lib/index.js
CHANGED
|
@@ -106,6 +106,27 @@ function normalizeEmbedding(value) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
export const RERANK_DEFAULTS = Object.freeze({
|
|
110
|
+
enabled: false,
|
|
111
|
+
base_url: 'https://api.jina.ai/v1',
|
|
112
|
+
model: 'jina-reranker-v2-base-multilingual',
|
|
113
|
+
api_key: '',
|
|
114
|
+
top_n: 20,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
function normalizeRerank(value) {
|
|
118
|
+
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
119
|
+
const rawTop = Number(source.top_n)
|
|
120
|
+
const top = Number.isFinite(rawTop) ? Math.max(1, Math.min(100, Math.round(rawTop))) : RERANK_DEFAULTS.top_n
|
|
121
|
+
return {
|
|
122
|
+
enabled: source.enabled === true,
|
|
123
|
+
base_url: String(source.base_url || RERANK_DEFAULTS.base_url).trim().slice(0, 2000),
|
|
124
|
+
model: String(source.model || RERANK_DEFAULTS.model).trim().slice(0, 200),
|
|
125
|
+
api_key: typeof source.api_key === 'string' ? source.api_key.trim().slice(0, 4000) : '',
|
|
126
|
+
top_n: top,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
/** GET must never leak the plaintext key. */
|
|
110
131
|
export function maskApiKey(key) {
|
|
111
132
|
const text = String(key || '')
|
|
@@ -114,23 +135,14 @@ export function maskApiKey(key) {
|
|
|
114
135
|
return text.slice(0, 2) + '••••••' + text.slice(-2)
|
|
115
136
|
}
|
|
116
137
|
|
|
117
|
-
function readStoredSettings(dataDir) {
|
|
118
|
-
return readConfig(dataDir)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function writeSettingsFile(file, embedding, expression) {
|
|
122
|
-
mkdirSync(dirname(file), { recursive: true })
|
|
123
|
-
writeFileSync(file, JSON.stringify({ embedding, expression }, null, 2) + '\n', {
|
|
124
|
-
encoding: 'utf8',
|
|
125
|
-
mode: 0o600,
|
|
126
|
-
})
|
|
127
|
-
try { chmodSync(file, 0o600) } catch { /* Windows may ignore POSIX modes */ }
|
|
128
|
-
}
|
|
129
|
-
|
|
130
138
|
function maskedEmbedding(embedding) {
|
|
131
139
|
return Object.assign({}, embedding, { api_key: maskApiKey(embedding.api_key) })
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
function maskedRerank(rerank) {
|
|
143
|
+
return Object.assign({}, rerank, { api_key: maskApiKey(rerank.api_key) })
|
|
144
|
+
}
|
|
145
|
+
|
|
134
146
|
function defaultEmbedding() {
|
|
135
147
|
const embedding = emptyEmbedding()
|
|
136
148
|
embedding.mode = 'auto'
|
|
@@ -144,16 +156,44 @@ function storedEmbedding(stored) {
|
|
|
144
156
|
return embedding
|
|
145
157
|
}
|
|
146
158
|
|
|
147
|
-
/**
|
|
159
|
+
/** Full config.json projection with defaults for all three sections. */
|
|
160
|
+
export function readFullConfig(dataDir) {
|
|
161
|
+
let doc = null
|
|
162
|
+
try {
|
|
163
|
+
doc = JSON.parse(readFileSync(join(String(dataDir || '').trim(), SETTINGS_FILE_NAME), 'utf8'))
|
|
164
|
+
} catch { doc = null }
|
|
165
|
+
const source = doc && typeof doc === 'object' && !Array.isArray(doc) ? doc : {}
|
|
166
|
+
return {
|
|
167
|
+
embedding: storedEmbedding(source),
|
|
168
|
+
rerank: normalizeRerank(source.rerank),
|
|
169
|
+
expression: normalizeExpression(source.expression),
|
|
170
|
+
sanitize_reply: source.sanitize_reply !== false,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function writeFullConfig(file, config) {
|
|
175
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
176
|
+
writeFileSync(file, JSON.stringify({
|
|
177
|
+
embedding: config.embedding,
|
|
178
|
+
rerank: config.rerank,
|
|
179
|
+
expression: config.expression,
|
|
180
|
+
sanitize_reply: config.sanitize_reply !== false,
|
|
181
|
+
}, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 })
|
|
182
|
+
try { chmodSync(file, 0o600) } catch { /* Windows may ignore POSIX modes */ }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** GET/POST /api/biocore/settings: embedding + rerank + expression. */
|
|
148
186
|
export async function handleSettings(req, res, dataDir) {
|
|
149
187
|
const paths = settingsPaths(dataDir)
|
|
150
188
|
const method = String((req && req.method) || 'GET').toUpperCase()
|
|
151
189
|
if (method === 'GET') {
|
|
152
|
-
const
|
|
190
|
+
const config = readFullConfig(paths.dir)
|
|
153
191
|
return sendJson(res, 200, {
|
|
154
192
|
ok: true,
|
|
155
|
-
embedding: maskedEmbedding(
|
|
156
|
-
|
|
193
|
+
embedding: maskedEmbedding(config.embedding),
|
|
194
|
+
rerank: maskedRerank(config.rerank),
|
|
195
|
+
expression: config.expression,
|
|
196
|
+
sanitize_reply: config.sanitize_reply !== false,
|
|
157
197
|
restart_required: false,
|
|
158
198
|
})
|
|
159
199
|
}
|
|
@@ -172,33 +212,136 @@ export async function handleSettings(req, res, dataDir) {
|
|
|
172
212
|
if (body.confirm !== true) {
|
|
173
213
|
return sendJson(res, 400, { ok: false, reason: 'confirm_required' })
|
|
174
214
|
}
|
|
175
|
-
const
|
|
176
|
-
let embedding = storedEmbedding(stored)
|
|
177
|
-
let expression = Object.assign({}, EXPRESSION_DEFAULTS, stored.expression || {})
|
|
215
|
+
const config = readFullConfig(paths.dir)
|
|
178
216
|
const hasEmbedding = body.embedding && typeof body.embedding === 'object'
|
|
217
|
+
const hasRerank = body.rerank && typeof body.rerank === 'object'
|
|
179
218
|
const hasExpression = body.expression && typeof body.expression === 'object'
|
|
180
|
-
|
|
219
|
+
const hasSanitize = typeof body.sanitize_reply === 'boolean'
|
|
220
|
+
if (!hasEmbedding && !hasRerank && !hasExpression && !hasSanitize) {
|
|
181
221
|
return sendJson(res, 400, { ok: false, reason: 'no_settings' })
|
|
182
222
|
}
|
|
183
223
|
if (hasEmbedding) {
|
|
184
224
|
const raw = body.embedding
|
|
185
|
-
embedding = normalizeEmbedding(raw)
|
|
225
|
+
const embedding = normalizeEmbedding(raw)
|
|
186
226
|
if (!embedding.mode) {
|
|
187
227
|
return sendJson(res, 400, { ok: false, reason: 'bad_mode', allowed: Array.from(EMBEDDING_MODES) })
|
|
188
228
|
}
|
|
189
|
-
if (!('api_key' in raw)
|
|
229
|
+
if (!('api_key' in raw) || String(raw.api_key || '').startsWith('••')) {
|
|
230
|
+
embedding.api_key = config.embedding.api_key
|
|
231
|
+
}
|
|
232
|
+
config.embedding = embedding
|
|
190
233
|
}
|
|
191
|
-
if (
|
|
192
|
-
|
|
234
|
+
if (hasRerank) {
|
|
235
|
+
const raw = body.rerank
|
|
236
|
+
const rerank = normalizeRerank(raw)
|
|
237
|
+
if (!('api_key' in raw) || String(raw.api_key || '').startsWith('••')) {
|
|
238
|
+
rerank.api_key = config.rerank.api_key
|
|
239
|
+
}
|
|
240
|
+
config.rerank = rerank
|
|
241
|
+
}
|
|
242
|
+
if (hasExpression) config.expression = normalizeExpression(body.expression)
|
|
243
|
+
if (hasSanitize) config.sanitize_reply = body.sanitize_reply === true
|
|
244
|
+
writeFullConfig(paths.file, config)
|
|
193
245
|
return sendJson(res, 200, {
|
|
194
246
|
ok: true,
|
|
195
|
-
embedding: maskedEmbedding(embedding),
|
|
196
|
-
|
|
247
|
+
embedding: maskedEmbedding(config.embedding),
|
|
248
|
+
rerank: maskedRerank(config.rerank),
|
|
249
|
+
expression: config.expression,
|
|
250
|
+
sanitize_reply: config.sanitize_reply !== false,
|
|
197
251
|
restart_required: true,
|
|
198
252
|
hint: '重启 BioCore 生效',
|
|
199
253
|
})
|
|
200
254
|
}
|
|
201
255
|
|
|
256
|
+
function notConfigured(kind, detail) {
|
|
257
|
+
return { ok: false, kind, reason: 'not_configured', detail }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** One minimal API call; never returns or logs the plaintext key. */
|
|
261
|
+
export async function testApiConnection(kind, config, options) {
|
|
262
|
+
const opts = options || {}
|
|
263
|
+
const fetchImpl = typeof opts.fetchImpl === 'function' ? opts.fetchImpl : globalThis.fetch
|
|
264
|
+
const timeoutMs = Number(opts.timeoutMs) || 6000
|
|
265
|
+
if (typeof fetchImpl !== 'function') return { ok: false, kind, reason: 'fetch_unavailable' }
|
|
266
|
+
let url = ''
|
|
267
|
+
let payload = null
|
|
268
|
+
if (kind === 'embedding') {
|
|
269
|
+
const embedding = config && config.embedding ? config.embedding : {}
|
|
270
|
+
if (embedding.mode === 'off' || embedding.mode === 'local') {
|
|
271
|
+
return notConfigured(kind, 'embedding mode is ' + embedding.mode)
|
|
272
|
+
}
|
|
273
|
+
if (!embedding.base_url || !embedding.model || !embedding.api_key) {
|
|
274
|
+
return notConfigured(kind, 'embedding base_url/model/api_key missing')
|
|
275
|
+
}
|
|
276
|
+
url = String(embedding.base_url).replace(/\/+$/, '') + '/embeddings'
|
|
277
|
+
payload = { model: embedding.model, input: 'ping' }
|
|
278
|
+
} else if (kind === 'rerank') {
|
|
279
|
+
const rerank = config && config.rerank ? config.rerank : {}
|
|
280
|
+
if (rerank.enabled !== true) return notConfigured(kind, 'rerank disabled')
|
|
281
|
+
if (!rerank.base_url || !rerank.model || !rerank.api_key) {
|
|
282
|
+
return notConfigured(kind, 'rerank base_url/model/api_key missing')
|
|
283
|
+
}
|
|
284
|
+
url = String(rerank.base_url).replace(/\/+$/, '') + '/rerank'
|
|
285
|
+
payload = {
|
|
286
|
+
model: rerank.model,
|
|
287
|
+
query: 'ping',
|
|
288
|
+
documents: ['ping'],
|
|
289
|
+
top_n: 1,
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
return { ok: false, kind: String(kind || ''), reason: 'bad_kind' }
|
|
293
|
+
}
|
|
294
|
+
const key = kind === 'embedding'
|
|
295
|
+
? String(config.embedding.api_key || '')
|
|
296
|
+
: String(config.rerank.api_key || '')
|
|
297
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null
|
|
298
|
+
const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null
|
|
299
|
+
const started = Date.now()
|
|
300
|
+
try {
|
|
301
|
+
const response = await fetchImpl(url, {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer ' + key },
|
|
304
|
+
body: JSON.stringify(payload),
|
|
305
|
+
signal: controller ? controller.signal : undefined,
|
|
306
|
+
})
|
|
307
|
+
const status = Number(response && response.status) || 0
|
|
308
|
+
const elapsed = Date.now() - started
|
|
309
|
+
if (response && response.ok) {
|
|
310
|
+
return { ok: true, kind, status_code: status, elapsed_ms: elapsed }
|
|
311
|
+
}
|
|
312
|
+
return { ok: false, kind, reason: 'http_error', status_code: status, elapsed_ms: elapsed }
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const name = error && error.name ? String(error.name) : ''
|
|
315
|
+
return {
|
|
316
|
+
ok: false,
|
|
317
|
+
kind,
|
|
318
|
+
reason: name === 'AbortError' ? 'timeout' : 'network_error',
|
|
319
|
+
elapsed_ms: Date.now() - started,
|
|
320
|
+
}
|
|
321
|
+
} finally {
|
|
322
|
+
if (timer) clearTimeout(timer)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** POST /api/biocore/settings/test {kind:'embedding'|'rerank'}. */
|
|
327
|
+
export async function handleSettingsTest(req, res, options) {
|
|
328
|
+
const opts = options || {}
|
|
329
|
+
const dataDir = opts.dataDir
|
|
330
|
+
const method = String((req && req.method) || 'POST').toUpperCase()
|
|
331
|
+
if (method !== 'POST') return sendJson(res, 405, { ok: false, reason: 'method_not_allowed' })
|
|
332
|
+
let body = null
|
|
333
|
+
try { body = await readJsonBody(req) } catch (error) {
|
|
334
|
+
return sendJson(res, 400, { ok: false, reason: (error && error.reason) || 'bad_payload' })
|
|
335
|
+
}
|
|
336
|
+
const kind = String(body && body.kind || '').trim()
|
|
337
|
+
if (kind !== 'embedding' && kind !== 'rerank') {
|
|
338
|
+
return sendJson(res, 400, { ok: false, reason: 'bad_kind', allowed: ['embedding', 'rerank'] })
|
|
339
|
+
}
|
|
340
|
+
const config = readFullConfig(dataDir)
|
|
341
|
+
const result = await testApiConnection(kind, config, { fetchImpl: opts.fetchImpl })
|
|
342
|
+
return sendJson(res, 200, result)
|
|
343
|
+
}
|
|
344
|
+
|
|
202
345
|
const PERSONA_PREVIEW_FIELDS = [
|
|
203
346
|
['E', '外向', 2],
|
|
204
347
|
['A', '宜人', 2],
|
|
@@ -692,6 +835,12 @@ export function apply(ctx, config) {
|
|
|
692
835
|
path: SETTINGS_ROUTE_PATH,
|
|
693
836
|
handler: (req, res) => handleSettings(req, res, dataDir),
|
|
694
837
|
}))
|
|
838
|
+
// API connection test (embedding / rerank); never logs the key.
|
|
839
|
+
keep(webServer.register({
|
|
840
|
+
kind: 'exact',
|
|
841
|
+
path: SETTINGS_ROUTE_PATH + '/test',
|
|
842
|
+
handler: (req, res) => handleSettingsTest(req, res, { dataDir }),
|
|
843
|
+
}))
|
|
695
844
|
})
|
|
696
845
|
}
|
|
697
846
|
} catch { /* headless */ }
|
package/lib/modules/core-turn.js
CHANGED
|
@@ -26,6 +26,11 @@ import { applyGateText, assistantTextOf } from './safety-gate.js'
|
|
|
26
26
|
import { defaultStoreDir } from './trace.js'
|
|
27
27
|
import { runtimeAction, runTurn } from '../py-bridge.js'
|
|
28
28
|
import { EXPRESSION_DEFAULTS, readExpressionSettings } from './expression-config.js'
|
|
29
|
+
import {
|
|
30
|
+
readSanitizeReplySetting,
|
|
31
|
+
sanitizeReply,
|
|
32
|
+
sanitizeReplyEnabled,
|
|
33
|
+
} from './sanitize-reply.js'
|
|
29
34
|
|
|
30
35
|
export const TURN_CONTEXT_NAME = 'biocore-expression-turn'
|
|
31
36
|
export const TURN_CONTEXT_ORDER = -190
|
|
@@ -118,6 +123,7 @@ export function turnEndPayload(options) {
|
|
|
118
123
|
store: opts.store || defaultStoreDir(),
|
|
119
124
|
capture: false,
|
|
120
125
|
memoryEnabled: false,
|
|
126
|
+
sanitize_reply: opts.sanitizeReply !== false,
|
|
121
127
|
diagnostics,
|
|
122
128
|
}, diagnostics, {
|
|
123
129
|
modules_trace: diagnostics.modules,
|
|
@@ -135,6 +141,9 @@ export function makeCoreSessionListener(options) {
|
|
|
135
141
|
const getExpression = typeof opts.getExpression === 'function'
|
|
136
142
|
? opts.getExpression
|
|
137
143
|
: () => readExpressionSettings(stateDir)
|
|
144
|
+
const getSanitizeReply = typeof opts.getSanitizeReply === 'function'
|
|
145
|
+
? opts.getSanitizeReply
|
|
146
|
+
: () => readSanitizeReplySetting(stateDir)
|
|
138
147
|
const store = opts.store || defaultStoreDir()
|
|
139
148
|
const log = typeof opts.log === 'function' ? opts.log : (line) => {
|
|
140
149
|
try { console.error(line) } catch { /* noop */ }
|
|
@@ -186,9 +195,11 @@ export function makeCoreSessionListener(options) {
|
|
|
186
195
|
}).catch(() => null)
|
|
187
196
|
return
|
|
188
197
|
}
|
|
189
|
-
// assistant/message: gate + trace via one turn_end call.
|
|
198
|
+
// assistant/message: sanitize -> gate + trace via one turn_end call.
|
|
190
199
|
const original = assistantTextOf(event)
|
|
191
200
|
if (!original) return
|
|
201
|
+
const sanitizeEnabled = sanitizeReplyEnabled(getSanitizeReply())
|
|
202
|
+
const cleaned = sanitizeEnabled ? sanitizeReply(original) : original
|
|
192
203
|
const userText = lastUser.get(sessionId) || ''
|
|
193
204
|
void (async () => {
|
|
194
205
|
try {
|
|
@@ -198,11 +209,12 @@ export function makeCoreSessionListener(options) {
|
|
|
198
209
|
const state = read && !read.error && !read.missing ? read.state : {}
|
|
199
210
|
const payload = turnEndPayload({
|
|
200
211
|
sessionId,
|
|
201
|
-
assistantText:
|
|
212
|
+
assistantText: cleaned,
|
|
202
213
|
userText,
|
|
203
214
|
switches,
|
|
204
215
|
store,
|
|
205
216
|
state,
|
|
217
|
+
sanitizeReply: sanitizeEnabled,
|
|
206
218
|
})
|
|
207
219
|
const result = await runAction('turn_end', payload)
|
|
208
220
|
if (!result || result.ok !== true) return
|
|
@@ -216,6 +228,9 @@ export function makeCoreSessionListener(options) {
|
|
|
216
228
|
+ ' blocked=' + (gate.blocked === true ? '1' : '0')
|
|
217
229
|
+ ' rule=' + stringId(gate.rule || '')
|
|
218
230
|
+ ' chars=' + original.length + '->' + gate.text.length)
|
|
231
|
+
} else if (cleaned !== original) {
|
|
232
|
+
// No gate rewrite: still write the sanitized text back.
|
|
233
|
+
applyGateText(event, cleaned)
|
|
219
234
|
}
|
|
220
235
|
if (result.trace_file) {
|
|
221
236
|
logLine(log, CORE_TRACE_PREFIX + ' session=' + sessionId
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Strip BioCore output markers from assistant replies (task-223).
|
|
2
|
+
*
|
|
3
|
+
* Removes:
|
|
4
|
+
* - <bc>...</bc> and <biocore>...</biocore> blocks (also unclosed to EOL/EOS)
|
|
5
|
+
* - bare compact JSON objects that only carry e/i[/n] keys
|
|
6
|
+
* - extra whitespace; empty result stays empty (never falls back to input)
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from 'node:fs'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_SANITIZE_REPLY = true
|
|
12
|
+
|
|
13
|
+
const TAG_RE = /<(bc|biocore)\b[^>]*>/gi
|
|
14
|
+
const BARE_JSON_RE = /\{\s*"e"\s*:\s*"[^"\\]*(?:\\.[^"\\]*)*"\s*,\s*"i"\s*:\s*-?\d+(?:\.\d+)?(?:\s*,\s*"n"\s*:\s*-?\d+(?:\.\d+)?)?\s*\}/g
|
|
15
|
+
|
|
16
|
+
function stripClosedTags(text) {
|
|
17
|
+
return String(text)
|
|
18
|
+
.replace(/<bc\b[^>]*>[\s\S]*?<\/bc\s*>/gi, '')
|
|
19
|
+
.replace(/<biocore\b[^>]*>[\s\S]*?<\/biocore\s*>/gi, '')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function stripUnclosedTags(text) {
|
|
23
|
+
// Until line end or a Chinese/ASCII sentence terminator; the terminator stays.
|
|
24
|
+
return String(text)
|
|
25
|
+
.replace(/<bc\b[^>]*>[\s\S]*?(?:[\n。!?!?]|$)/gi, '')
|
|
26
|
+
.replace(/<biocore\b[^>]*>[\s\S]*?(?:[\n。!?!?]|$)/gi, '')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeWhitespace(text) {
|
|
30
|
+
return String(text)
|
|
31
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
32
|
+
.replace(/[ \t]*\n[ \t]*/g, '\n')
|
|
33
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
34
|
+
.trim()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Main entry: returns the cleaned reply (may be ''); never reverts to input. */
|
|
38
|
+
export function sanitizeReply(text, options) {
|
|
39
|
+
const opts = options || {}
|
|
40
|
+
if (opts.enabled === false) return String(text == null ? '' : text)
|
|
41
|
+
const source = String(text == null ? '' : text)
|
|
42
|
+
let out = stripClosedTags(source)
|
|
43
|
+
out = stripUnclosedTags(out)
|
|
44
|
+
out = out.replace(BARE_JSON_RE, '')
|
|
45
|
+
return normalizeWhitespace(out)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Config.json top-level flag: only an explicit false disables cleaning. */
|
|
49
|
+
export function readSanitizeReplySetting(dataDir) {
|
|
50
|
+
try {
|
|
51
|
+
const doc = JSON.parse(readFileSync(join(String(dataDir || '').trim(), 'config.json'), 'utf8'))
|
|
52
|
+
if (doc && typeof doc === 'object' && doc.sanitize_reply === false) return false
|
|
53
|
+
} catch { /* missing/bad config -> default on */ }
|
|
54
|
+
return DEFAULT_SANITIZE_REPLY
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Payload helper: sanitize_reply defaults to true unless explicitly false. */
|
|
58
|
+
export function sanitizeReplyEnabled(value) {
|
|
59
|
+
return value !== false
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -91,6 +91,11 @@ try:
|
|
|
91
91
|
except Exception: # pragma: no cover
|
|
92
92
|
_intero_core = None
|
|
93
93
|
|
|
94
|
+
try:
|
|
95
|
+
from biocore_runtime import rerank as _rerank_mod
|
|
96
|
+
except Exception: # pragma: no cover
|
|
97
|
+
_rerank_mod = None
|
|
98
|
+
|
|
94
99
|
try: # self-layer modules (all pure; optional so a stripped checkout still runs)
|
|
95
100
|
from biocore_model import x_directives as _x_directives
|
|
96
101
|
from biocore_model import self_intent as _self_intent
|
|
@@ -634,6 +639,26 @@ def tool_call(name, args, store, session_id=None):
|
|
|
634
639
|
row["score"] = score
|
|
635
640
|
rows.append(row)
|
|
636
641
|
rows.sort(key=lambda row: (-row["score"], -_num(row.get("ts"), 0.0)))
|
|
642
|
+
rerank_config = args.get("rerank_config")
|
|
643
|
+
if (_rerank_mod is not None and isinstance(rerank_config, dict)
|
|
644
|
+
and rerank_config.get("rerank_enabled")):
|
|
645
|
+
try:
|
|
646
|
+
reranked = _rerank_mod.rerank_documents(
|
|
647
|
+
query, [row.get("text", "") for row in rows], rerank_config)
|
|
648
|
+
if reranked:
|
|
649
|
+
seen = set()
|
|
650
|
+
ordered = []
|
|
651
|
+
for item in reranked:
|
|
652
|
+
index = int(item.get("index", -1))
|
|
653
|
+
if 0 <= index < len(rows) and index not in seen:
|
|
654
|
+
seen.add(index)
|
|
655
|
+
ordered.append(rows[index])
|
|
656
|
+
for index, row in enumerate(rows):
|
|
657
|
+
if index not in seen:
|
|
658
|
+
ordered.append(row)
|
|
659
|
+
rows = ordered
|
|
660
|
+
except Exception:
|
|
661
|
+
pass # rerank is optional; recall keeps the local order
|
|
637
662
|
return {"ok": True, "count": min(limit, len(rows)),
|
|
638
663
|
"results": rows[:limit]}
|
|
639
664
|
target = str(args.get("id") or args.get("query") or "").strip()
|
|
@@ -1835,6 +1860,23 @@ def dispatch(action, payload=None):
|
|
|
1835
1860
|
current = normalize_switches(data.get("switches"))
|
|
1836
1861
|
current[key] = bool(data.get("value"))
|
|
1837
1862
|
return {"ok": True, "switches": current}
|
|
1863
|
+
if action == "recall":
|
|
1864
|
+
query = str(data.get("query") or "").strip()
|
|
1865
|
+
documents = data.get("documents")
|
|
1866
|
+
if not isinstance(documents, list):
|
|
1867
|
+
documents = data.get("docs") if isinstance(data.get("docs"), list) else []
|
|
1868
|
+
fallback = [{"index": index,
|
|
1869
|
+
"document": str((item.get("text") or item.get("document"))
|
|
1870
|
+
if isinstance(item, dict) else (item or "")).strip()}
|
|
1871
|
+
for index, item in enumerate(documents)]
|
|
1872
|
+
if _rerank_mod is None:
|
|
1873
|
+
return {"ok": True, "reranked": False, "results": fallback,
|
|
1874
|
+
"reason": "unavailable"}
|
|
1875
|
+
config = data.get("config") if isinstance(data.get("config"), dict) else data
|
|
1876
|
+
result = _rerank_mod.rerank_documents(query, documents, config)
|
|
1877
|
+
if not result:
|
|
1878
|
+
return {"ok": True, "reranked": False, "results": fallback}
|
|
1879
|
+
return {"ok": True, "reranked": True, "results": result}
|
|
1838
1880
|
if action == "trace":
|
|
1839
1881
|
session_id = str(data.get("session_id") or "").strip()
|
|
1840
1882
|
if not session_id:
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Optional remote cross-encoder rerank (stdlib only, silent degradation).
|
|
3
|
+
|
|
4
|
+
Config keys (all optional, defaults shown):
|
|
5
|
+
rerank_enabled False
|
|
6
|
+
rerank_base_url ""
|
|
7
|
+
rerank_model ""
|
|
8
|
+
rerank_api_key ""
|
|
9
|
+
rerank_timeout 8.0 seconds
|
|
10
|
+
rerank_top_n 20
|
|
11
|
+
|
|
12
|
+
Two common wire protocols are tried automatically:
|
|
13
|
+
POST {base_url}/rerank {"model", "query", "documents", "top_n"}
|
|
14
|
+
POST {base_url}/v1/rerank same payload
|
|
15
|
+
|
|
16
|
+
A disabled call, missing config, timeout, non-2xx response, bad JSON or an
|
|
17
|
+
invalid result list returns None and records a short reason; callers keep the
|
|
18
|
+
original ordering. Only stdlib urllib is used.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import math
|
|
25
|
+
import socket
|
|
26
|
+
import urllib.error
|
|
27
|
+
import urllib.request
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
DEFAULTS = {
|
|
32
|
+
"rerank_enabled": False,
|
|
33
|
+
"rerank_base_url": "",
|
|
34
|
+
"rerank_model": "",
|
|
35
|
+
"rerank_api_key": "",
|
|
36
|
+
"rerank_timeout": 8.0,
|
|
37
|
+
"rerank_top_n": 20,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _num(value, default=0.0):
|
|
42
|
+
try:
|
|
43
|
+
number = float(value)
|
|
44
|
+
except Exception:
|
|
45
|
+
return float(default)
|
|
46
|
+
return number if math.isfinite(number) else float(default)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _as_bool(value, default=False):
|
|
50
|
+
if isinstance(value, bool):
|
|
51
|
+
return value
|
|
52
|
+
if isinstance(value, (int, float)):
|
|
53
|
+
return bool(value)
|
|
54
|
+
if isinstance(value, str):
|
|
55
|
+
lowered = value.strip().lower()
|
|
56
|
+
if lowered in ("1", "true", "yes", "on"):
|
|
57
|
+
return True
|
|
58
|
+
if lowered in ("0", "false", "no", "off", ""):
|
|
59
|
+
return False
|
|
60
|
+
return bool(default)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def normalize_config(config=None):
|
|
64
|
+
"""Merge caller config with defaults and clamp timeout/top_n."""
|
|
65
|
+
data = config if isinstance(config, dict) else {}
|
|
66
|
+
out = dict(DEFAULTS)
|
|
67
|
+
out["rerank_enabled"] = _as_bool(data.get("rerank_enabled"), False)
|
|
68
|
+
for key in ("rerank_base_url", "rerank_model", "rerank_api_key"):
|
|
69
|
+
out[key] = str(data.get(key) or "").strip()
|
|
70
|
+
timeout = _num(data.get("rerank_timeout"), DEFAULTS["rerank_timeout"])
|
|
71
|
+
out["rerank_timeout"] = max(0.1, min(60.0, timeout))
|
|
72
|
+
top_n = int(_num(data.get("rerank_top_n"), DEFAULTS["rerank_top_n"]))
|
|
73
|
+
out["rerank_top_n"] = max(1, min(1000, top_n))
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _document_text(item):
|
|
78
|
+
if isinstance(item, dict):
|
|
79
|
+
return str(item.get("text") or item.get("document") or "").strip()
|
|
80
|
+
return str(item or "").strip()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _candidate_urls(base_url):
|
|
84
|
+
base = str(base_url or "").strip().rstrip("/")
|
|
85
|
+
if not base:
|
|
86
|
+
return []
|
|
87
|
+
if base.endswith("/rerank"):
|
|
88
|
+
return [base]
|
|
89
|
+
urls = [base + "/rerank"]
|
|
90
|
+
if not base.endswith("/v1"):
|
|
91
|
+
urls.append(base + "/v1/rerank")
|
|
92
|
+
return urls
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _urllib_transport(url, payload, timeout, headers):
|
|
96
|
+
request = urllib.request.Request(
|
|
97
|
+
url, data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
|
|
98
|
+
headers=headers, method="POST")
|
|
99
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
100
|
+
status = int(getattr(response, "status", 200) or 200)
|
|
101
|
+
body = response.read()
|
|
102
|
+
return status, body
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _notify(callback, reason):
|
|
106
|
+
try:
|
|
107
|
+
logger.info("[rerank] fallback: %s", reason)
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
if callable(callback):
|
|
111
|
+
try:
|
|
112
|
+
callback(reason)
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def rerank_documents(query, documents, config=None, transport=None,
|
|
118
|
+
on_reason=None):
|
|
119
|
+
"""Return [{index, document, relevance_score}] sorted/truncated, or None."""
|
|
120
|
+
cfg = normalize_config(config)
|
|
121
|
+
if not cfg["rerank_enabled"]:
|
|
122
|
+
_notify(on_reason, "disabled")
|
|
123
|
+
return None
|
|
124
|
+
if not cfg["rerank_base_url"] or not cfg["rerank_model"]:
|
|
125
|
+
_notify(on_reason, "missing_config")
|
|
126
|
+
return None
|
|
127
|
+
text = str(query or "").strip()
|
|
128
|
+
if not text:
|
|
129
|
+
_notify(on_reason, "empty_query")
|
|
130
|
+
return None
|
|
131
|
+
text_docs = []
|
|
132
|
+
for index, item in enumerate(documents or []):
|
|
133
|
+
content = _document_text(item)
|
|
134
|
+
if content:
|
|
135
|
+
text_docs.append((index, content))
|
|
136
|
+
if not text_docs:
|
|
137
|
+
_notify(on_reason, "empty_documents")
|
|
138
|
+
return None
|
|
139
|
+
top_n = min(cfg["rerank_top_n"], len(text_docs))
|
|
140
|
+
payload = {
|
|
141
|
+
"model": cfg["rerank_model"],
|
|
142
|
+
"query": text,
|
|
143
|
+
"documents": [content for _index, content in text_docs],
|
|
144
|
+
"top_n": top_n,
|
|
145
|
+
}
|
|
146
|
+
headers = {"Content-Type": "application/json"}
|
|
147
|
+
if cfg["rerank_api_key"]:
|
|
148
|
+
headers["Authorization"] = "Bearer " + cfg["rerank_api_key"]
|
|
149
|
+
call = transport or _urllib_transport
|
|
150
|
+
reason = "failed"
|
|
151
|
+
for url in _candidate_urls(cfg["rerank_base_url"]):
|
|
152
|
+
try:
|
|
153
|
+
status, body = call(url, payload, cfg["rerank_timeout"], headers)
|
|
154
|
+
except (TimeoutError, socket.timeout):
|
|
155
|
+
_notify(on_reason, "timeout")
|
|
156
|
+
return None
|
|
157
|
+
except urllib.error.HTTPError as error:
|
|
158
|
+
reason = "http_%s" % getattr(error, "code", "error")
|
|
159
|
+
continue
|
|
160
|
+
except Exception:
|
|
161
|
+
_notify(on_reason, "request_failed")
|
|
162
|
+
return None
|
|
163
|
+
try:
|
|
164
|
+
status = int(status)
|
|
165
|
+
except Exception:
|
|
166
|
+
status = 0
|
|
167
|
+
if status < 200 or status >= 300:
|
|
168
|
+
reason = "http_%s" % status
|
|
169
|
+
continue
|
|
170
|
+
try:
|
|
171
|
+
if isinstance(body, bytes):
|
|
172
|
+
body = body.decode("utf-8", "replace")
|
|
173
|
+
data = json.loads(body)
|
|
174
|
+
except Exception:
|
|
175
|
+
reason = "bad_json"
|
|
176
|
+
continue
|
|
177
|
+
results = data.get("results") if isinstance(data, dict) else None
|
|
178
|
+
if not isinstance(results, list):
|
|
179
|
+
reason = "bad_results"
|
|
180
|
+
continue
|
|
181
|
+
parsed = []
|
|
182
|
+
by_index = dict(text_docs)
|
|
183
|
+
for item in results:
|
|
184
|
+
if not isinstance(item, dict):
|
|
185
|
+
continue
|
|
186
|
+
try:
|
|
187
|
+
index = int(item.get("index"))
|
|
188
|
+
score = float(item.get("relevance_score"))
|
|
189
|
+
except Exception:
|
|
190
|
+
continue
|
|
191
|
+
if index not in by_index or not math.isfinite(score):
|
|
192
|
+
continue
|
|
193
|
+
parsed.append({"index": index, "document": by_index[index],
|
|
194
|
+
"relevance_score": round(score, 6)})
|
|
195
|
+
if not parsed:
|
|
196
|
+
reason = "empty_results"
|
|
197
|
+
continue
|
|
198
|
+
parsed.sort(key=lambda row: (-row["relevance_score"], row["index"]))
|
|
199
|
+
return parsed[:top_n]
|
|
200
|
+
_notify(on_reason, reason)
|
|
201
|
+
return None
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""mindbase: a layered, paper-grounded base for a human-like agent.
|
|
2
|
+
|
|
3
|
+
Layers:
|
|
4
|
+
L0 substrate/noise -> noise.py
|
|
5
|
+
L1 drives/allostasis -> drives.py, clock.py
|
|
6
|
+
L2 affect/appraisal -> appraisal.py, affect.py
|
|
7
|
+
L3 attention/WM -> attention.py, working_memory.py
|
|
8
|
+
L4 memory -> memory.py
|
|
9
|
+
L5 social/persona/self/meta/autonomy -> social.py, persona.py, self_model.py, meta.py, autonomy.py
|
|
10
|
+
L6 expression -> (not implemented; LLM adapter later)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .config import MindConfig
|
|
14
|
+
from .actions import Action
|
|
15
|
+
from .types import Event, Appraisal, Outcome, Feedback, LLMRequest
|
|
16
|
+
from .engine import MindBase
|
|
17
|
+
|
|
18
|
+
__all__ = ["MindConfig", "Event", "Appraisal", "Outcome", "LLMRequest", "Feedback", "Action", "MindBase"]
|