dsh-biocore 0.1.3 → 0.1.4
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 +170 -27
- 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 +25 -1
- package/ui/panel.js +57 -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,41 @@ 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
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function writeFullConfig(file, config) {
|
|
174
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
175
|
+
writeFileSync(file, JSON.stringify({
|
|
176
|
+
embedding: config.embedding,
|
|
177
|
+
rerank: config.rerank,
|
|
178
|
+
expression: config.expression,
|
|
179
|
+
}, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 })
|
|
180
|
+
try { chmodSync(file, 0o600) } catch { /* Windows may ignore POSIX modes */ }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** GET/POST /api/biocore/settings: embedding + rerank + expression. */
|
|
148
184
|
export async function handleSettings(req, res, dataDir) {
|
|
149
185
|
const paths = settingsPaths(dataDir)
|
|
150
186
|
const method = String((req && req.method) || 'GET').toUpperCase()
|
|
151
187
|
if (method === 'GET') {
|
|
152
|
-
const
|
|
188
|
+
const config = readFullConfig(paths.dir)
|
|
153
189
|
return sendJson(res, 200, {
|
|
154
190
|
ok: true,
|
|
155
|
-
embedding: maskedEmbedding(
|
|
156
|
-
|
|
191
|
+
embedding: maskedEmbedding(config.embedding),
|
|
192
|
+
rerank: maskedRerank(config.rerank),
|
|
193
|
+
expression: config.expression,
|
|
157
194
|
restart_required: false,
|
|
158
195
|
})
|
|
159
196
|
}
|
|
@@ -172,33 +209,133 @@ export async function handleSettings(req, res, dataDir) {
|
|
|
172
209
|
if (body.confirm !== true) {
|
|
173
210
|
return sendJson(res, 400, { ok: false, reason: 'confirm_required' })
|
|
174
211
|
}
|
|
175
|
-
const
|
|
176
|
-
let embedding = storedEmbedding(stored)
|
|
177
|
-
let expression = Object.assign({}, EXPRESSION_DEFAULTS, stored.expression || {})
|
|
212
|
+
const config = readFullConfig(paths.dir)
|
|
178
213
|
const hasEmbedding = body.embedding && typeof body.embedding === 'object'
|
|
214
|
+
const hasRerank = body.rerank && typeof body.rerank === 'object'
|
|
179
215
|
const hasExpression = body.expression && typeof body.expression === 'object'
|
|
180
|
-
if (!hasEmbedding && !hasExpression) {
|
|
216
|
+
if (!hasEmbedding && !hasRerank && !hasExpression) {
|
|
181
217
|
return sendJson(res, 400, { ok: false, reason: 'no_settings' })
|
|
182
218
|
}
|
|
183
219
|
if (hasEmbedding) {
|
|
184
220
|
const raw = body.embedding
|
|
185
|
-
embedding = normalizeEmbedding(raw)
|
|
221
|
+
const embedding = normalizeEmbedding(raw)
|
|
186
222
|
if (!embedding.mode) {
|
|
187
223
|
return sendJson(res, 400, { ok: false, reason: 'bad_mode', allowed: Array.from(EMBEDDING_MODES) })
|
|
188
224
|
}
|
|
189
|
-
if (!('api_key' in raw)
|
|
225
|
+
if (!('api_key' in raw) || String(raw.api_key || '').startsWith('••')) {
|
|
226
|
+
embedding.api_key = config.embedding.api_key
|
|
227
|
+
}
|
|
228
|
+
config.embedding = embedding
|
|
190
229
|
}
|
|
191
|
-
if (
|
|
192
|
-
|
|
230
|
+
if (hasRerank) {
|
|
231
|
+
const raw = body.rerank
|
|
232
|
+
const rerank = normalizeRerank(raw)
|
|
233
|
+
if (!('api_key' in raw) || String(raw.api_key || '').startsWith('••')) {
|
|
234
|
+
rerank.api_key = config.rerank.api_key
|
|
235
|
+
}
|
|
236
|
+
config.rerank = rerank
|
|
237
|
+
}
|
|
238
|
+
if (hasExpression) config.expression = normalizeExpression(body.expression)
|
|
239
|
+
writeFullConfig(paths.file, config)
|
|
193
240
|
return sendJson(res, 200, {
|
|
194
241
|
ok: true,
|
|
195
|
-
embedding: maskedEmbedding(embedding),
|
|
196
|
-
|
|
242
|
+
embedding: maskedEmbedding(config.embedding),
|
|
243
|
+
rerank: maskedRerank(config.rerank),
|
|
244
|
+
expression: config.expression,
|
|
197
245
|
restart_required: true,
|
|
198
246
|
hint: '重启 BioCore 生效',
|
|
199
247
|
})
|
|
200
248
|
}
|
|
201
249
|
|
|
250
|
+
function notConfigured(kind, detail) {
|
|
251
|
+
return { ok: false, kind, reason: 'not_configured', detail }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** One minimal API call; never returns or logs the plaintext key. */
|
|
255
|
+
export async function testApiConnection(kind, config, options) {
|
|
256
|
+
const opts = options || {}
|
|
257
|
+
const fetchImpl = typeof opts.fetchImpl === 'function' ? opts.fetchImpl : globalThis.fetch
|
|
258
|
+
const timeoutMs = Number(opts.timeoutMs) || 6000
|
|
259
|
+
if (typeof fetchImpl !== 'function') return { ok: false, kind, reason: 'fetch_unavailable' }
|
|
260
|
+
let url = ''
|
|
261
|
+
let payload = null
|
|
262
|
+
if (kind === 'embedding') {
|
|
263
|
+
const embedding = config && config.embedding ? config.embedding : {}
|
|
264
|
+
if (embedding.mode === 'off' || embedding.mode === 'local') {
|
|
265
|
+
return notConfigured(kind, 'embedding mode is ' + embedding.mode)
|
|
266
|
+
}
|
|
267
|
+
if (!embedding.base_url || !embedding.model || !embedding.api_key) {
|
|
268
|
+
return notConfigured(kind, 'embedding base_url/model/api_key missing')
|
|
269
|
+
}
|
|
270
|
+
url = String(embedding.base_url).replace(/\/+$/, '') + '/embeddings'
|
|
271
|
+
payload = { model: embedding.model, input: 'ping' }
|
|
272
|
+
} else if (kind === 'rerank') {
|
|
273
|
+
const rerank = config && config.rerank ? config.rerank : {}
|
|
274
|
+
if (rerank.enabled !== true) return notConfigured(kind, 'rerank disabled')
|
|
275
|
+
if (!rerank.base_url || !rerank.model || !rerank.api_key) {
|
|
276
|
+
return notConfigured(kind, 'rerank base_url/model/api_key missing')
|
|
277
|
+
}
|
|
278
|
+
url = String(rerank.base_url).replace(/\/+$/, '') + '/rerank'
|
|
279
|
+
payload = {
|
|
280
|
+
model: rerank.model,
|
|
281
|
+
query: 'ping',
|
|
282
|
+
documents: ['ping'],
|
|
283
|
+
top_n: 1,
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
return { ok: false, kind: String(kind || ''), reason: 'bad_kind' }
|
|
287
|
+
}
|
|
288
|
+
const key = kind === 'embedding'
|
|
289
|
+
? String(config.embedding.api_key || '')
|
|
290
|
+
: String(config.rerank.api_key || '')
|
|
291
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null
|
|
292
|
+
const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null
|
|
293
|
+
const started = Date.now()
|
|
294
|
+
try {
|
|
295
|
+
const response = await fetchImpl(url, {
|
|
296
|
+
method: 'POST',
|
|
297
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer ' + key },
|
|
298
|
+
body: JSON.stringify(payload),
|
|
299
|
+
signal: controller ? controller.signal : undefined,
|
|
300
|
+
})
|
|
301
|
+
const status = Number(response && response.status) || 0
|
|
302
|
+
const elapsed = Date.now() - started
|
|
303
|
+
if (response && response.ok) {
|
|
304
|
+
return { ok: true, kind, status_code: status, elapsed_ms: elapsed }
|
|
305
|
+
}
|
|
306
|
+
return { ok: false, kind, reason: 'http_error', status_code: status, elapsed_ms: elapsed }
|
|
307
|
+
} catch (error) {
|
|
308
|
+
const name = error && error.name ? String(error.name) : ''
|
|
309
|
+
return {
|
|
310
|
+
ok: false,
|
|
311
|
+
kind,
|
|
312
|
+
reason: name === 'AbortError' ? 'timeout' : 'network_error',
|
|
313
|
+
elapsed_ms: Date.now() - started,
|
|
314
|
+
}
|
|
315
|
+
} finally {
|
|
316
|
+
if (timer) clearTimeout(timer)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** POST /api/biocore/settings/test {kind:'embedding'|'rerank'}. */
|
|
321
|
+
export async function handleSettingsTest(req, res, options) {
|
|
322
|
+
const opts = options || {}
|
|
323
|
+
const dataDir = opts.dataDir
|
|
324
|
+
const method = String((req && req.method) || 'POST').toUpperCase()
|
|
325
|
+
if (method !== 'POST') return sendJson(res, 405, { ok: false, reason: 'method_not_allowed' })
|
|
326
|
+
let body = null
|
|
327
|
+
try { body = await readJsonBody(req) } catch (error) {
|
|
328
|
+
return sendJson(res, 400, { ok: false, reason: (error && error.reason) || 'bad_payload' })
|
|
329
|
+
}
|
|
330
|
+
const kind = String(body && body.kind || '').trim()
|
|
331
|
+
if (kind !== 'embedding' && kind !== 'rerank') {
|
|
332
|
+
return sendJson(res, 400, { ok: false, reason: 'bad_kind', allowed: ['embedding', 'rerank'] })
|
|
333
|
+
}
|
|
334
|
+
const config = readFullConfig(dataDir)
|
|
335
|
+
const result = await testApiConnection(kind, config, { fetchImpl: opts.fetchImpl })
|
|
336
|
+
return sendJson(res, 200, result)
|
|
337
|
+
}
|
|
338
|
+
|
|
202
339
|
const PERSONA_PREVIEW_FIELDS = [
|
|
203
340
|
['E', '外向', 2],
|
|
204
341
|
['A', '宜人', 2],
|
|
@@ -692,6 +829,12 @@ export function apply(ctx, config) {
|
|
|
692
829
|
path: SETTINGS_ROUTE_PATH,
|
|
693
830
|
handler: (req, res) => handleSettings(req, res, dataDir),
|
|
694
831
|
}))
|
|
832
|
+
// API connection test (embedding / rerank); never logs the key.
|
|
833
|
+
keep(webServer.register({
|
|
834
|
+
kind: 'exact',
|
|
835
|
+
path: SETTINGS_ROUTE_PATH + '/test',
|
|
836
|
+
handler: (req, res) => handleSettingsTest(req, res, { dataDir }),
|
|
837
|
+
}))
|
|
695
838
|
})
|
|
696
839
|
}
|
|
697
840
|
} catch { /* headless */ }
|
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"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
ACTION_KINDS = (
|
|
6
|
+
"communicate",
|
|
7
|
+
"approach",
|
|
8
|
+
"avoid",
|
|
9
|
+
"explore",
|
|
10
|
+
"rest",
|
|
11
|
+
"repair",
|
|
12
|
+
"protect",
|
|
13
|
+
"seek_help",
|
|
14
|
+
"give_help",
|
|
15
|
+
"withdraw",
|
|
16
|
+
"appraise",
|
|
17
|
+
"forage",
|
|
18
|
+
"drink",
|
|
19
|
+
"heal",
|
|
20
|
+
)
|
|
21
|
+
MODALITIES = ("text", "voice", "gesture", "none")
|
|
22
|
+
|
|
23
|
+
STYLE_TO_KIND = {
|
|
24
|
+
"speak": "communicate",
|
|
25
|
+
"silence": "withdraw",
|
|
26
|
+
"ask": "seek_help",
|
|
27
|
+
"repair": "repair",
|
|
28
|
+
"avoid": "avoid",
|
|
29
|
+
"approach": "approach",
|
|
30
|
+
"appraise": "appraise",
|
|
31
|
+
"forage": "forage",
|
|
32
|
+
"rest": "rest",
|
|
33
|
+
"socialize": "approach",
|
|
34
|
+
"drink": "drink",
|
|
35
|
+
"heal": "heal",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def kind_for_style(style: str) -> str:
|
|
40
|
+
return STYLE_TO_KIND.get(str(style or "").lower(), "communicate")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Action:
|
|
45
|
+
"""A domain-general action, not a chat token.
|
|
46
|
+
|
|
47
|
+
kind: what the base does
|
|
48
|
+
modality: how it is expressed (chat is only communicate/text)
|
|
49
|
+
style: optional sub-style for language (speak/repair/avoid...)
|
|
50
|
+
"""
|
|
51
|
+
kind: str = "communicate"
|
|
52
|
+
modality: str = "text"
|
|
53
|
+
style: str = ""
|
|
54
|
+
|
|
55
|
+
def as_dict(self) -> dict:
|
|
56
|
+
return {"kind": self.kind, "modality": self.modality, "style": self.style}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def expected_free_energy(load: float, pred_err: float, social: float, energy: float, action: str, activity: float = 0.0) -> float:
|
|
7
|
+
"""Expected free energy for a binary action (speak/silence).
|
|
8
|
+
|
|
9
|
+
risk = expected allostatic deviation after action
|
|
10
|
+
ambiguity= self-prediction uncertainty
|
|
11
|
+
cost = action cost
|
|
12
|
+
"""
|
|
13
|
+
if action == "speak":
|
|
14
|
+
risk = max(0.0, load - 0.10 * social)
|
|
15
|
+
cost = 0.05 + 0.05 * max(0.0, 0.3 - energy) + 0.10 * max(0.0, float(activity))
|
|
16
|
+
else:
|
|
17
|
+
risk = load + 0.08 * social
|
|
18
|
+
cost = 0.02
|
|
19
|
+
ambiguity = pred_err * pred_err
|
|
20
|
+
return risk + 0.5 * ambiguity + cost
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def choose(load: float, pred_err: float, social: float, energy: float) -> Dict[str, object]:
|
|
24
|
+
speak = expected_free_energy(load, pred_err, social, energy, "speak")
|
|
25
|
+
silence = expected_free_energy(load, pred_err, social, energy, "silence")
|
|
26
|
+
action = "speak" if speak < silence else "silence"
|
|
27
|
+
return {"action": action, "efe_speak": round(speak, 4), "efe_silence": round(silence, 4)}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ActiveInference:
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.w_risk = 1.0
|
|
33
|
+
self.w_amb = 0.5
|
|
34
|
+
self.w_cost = 1.0
|
|
35
|
+
self.lr = 0.005
|
|
36
|
+
|
|
37
|
+
def choose(self, load: float, pred_err: float, social: float, energy: float, activity: float = 0.0, valence: float = 0.0):
|
|
38
|
+
def efe(action: str) -> float:
|
|
39
|
+
if action == "speak":
|
|
40
|
+
risk = max(0.0, load - 0.10 * social - 0.15 * float(valence))
|
|
41
|
+
cost = 0.05 + 0.05 * max(0.0, 0.3 - energy) + 0.10 * max(0.0, float(activity))
|
|
42
|
+
else:
|
|
43
|
+
risk = load + 0.08 * social + 0.10 * float(valence)
|
|
44
|
+
cost = 0.02
|
|
45
|
+
return self.w_risk * risk + self.w_amb * pred_err * pred_err + self.w_cost * cost
|
|
46
|
+
s, q = efe("speak"), efe("silence")
|
|
47
|
+
return {"action": "speak" if s < q else "silence", "efe_speak": round(s, 4), "efe_silence": round(q, 4)}
|
|
48
|
+
|
|
49
|
+
def learn(self, rpe: float, load: float, pred_err: float) -> None:
|
|
50
|
+
self.w_risk = max(0.1, min(3.0, self.w_risk - self.lr * float(rpe) * float(load)))
|
|
51
|
+
self.w_amb = max(0.05, min(3.0, self.w_amb - self.lr * float(rpe) * float(pred_err) ** 2))
|
|
52
|
+
|
|
53
|
+
def snapshot(self) -> dict:
|
|
54
|
+
return {"w_risk": round(self.w_risk, 4), "w_amb": round(self.w_amb, 4), "w_cost": self.w_cost}
|