insika 0.9.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +237 -2
- data/README.md +5 -3
- data/bin/insika +3 -0
- data/bin/insika-router +0 -1
- data/docs/AGENTS.md +90 -7
- data/docs/ARCHITECTURE.md +12 -7
- data/docs/ARTIFACTS.md +33 -3
- data/docs/BENCHMARK.md +50 -7
- data/docs/CONTEXT.md +63 -0
- data/docs/CROSS-HARNESS-BENCH.md +157 -0
- data/docs/DEPLOY.md +57 -11
- data/docs/EMBEDDING.md +10 -0
- data/docs/EVALS.md +33 -4
- data/docs/FACTS.md +8 -6
- data/docs/HARVEST.md +8 -3
- data/docs/KNOWLEDGE.md +7 -0
- data/docs/MEDIA.md +6 -2
- data/docs/OBSERVABILITY.md +143 -22
- data/docs/PERFORMANCE.md +110 -0
- data/docs/PLUGINS.md +4 -4
- data/docs/POLICY.md +22 -13
- data/docs/RELEASING.md +5 -0
- data/docs/ROUTER.md +7 -0
- data/docs/RUBYLLM_2_MIGRATION.md +307 -0
- data/docs/SECURITY.md +7 -0
- data/docs/SHARED-CONVERSATIONS.md +40 -0
- data/docs/SKILLS.md +5 -3
- data/docs/STUDIO.md +122 -0
- data/docs/TOOLS.md +111 -9
- data/docs/operate.md +3 -0
- data/lib/insika/agent_defaults.rb +140 -0
- data/lib/insika/agent_file_store.rb +12 -0
- data/lib/insika/agent_profile.rb +78 -8
- data/lib/insika/artifact_signing.rb +11 -0
- data/lib/insika/chat_builder.rb +79 -25
- data/lib/insika/checkpoint.rb +6 -1
- data/lib/insika/checkpoint_store.rb +59 -7
- data/lib/insika/commands/agent_payload.rb +3 -3
- data/lib/insika/commands/delete_kit.rb +26 -0
- data/lib/insika/commands/gate_harvest.rb +3 -0
- data/lib/insika/commands/review_guardrail_flag.rb +38 -0
- data/lib/insika/commands/run_harvest.rb +17 -1
- data/lib/insika/commands/seed_session.rb +11 -1
- data/lib/insika/commands/send_message.rb +9 -3
- data/lib/insika/commands/update_settings.rb +23 -1
- data/lib/insika/commands/write_kit.rb +27 -0
- data/lib/insika/compaction.rb +33 -7
- data/lib/insika/config_store.rb +87 -7
- data/lib/insika/context/priority.rb +3 -0
- data/lib/insika/context/provider.rb +2 -2
- data/lib/insika/context/providers/knowledge.rb +25 -2
- data/lib/insika/context/providers/memory.rb +53 -4
- data/lib/insika/context/providers/pending_confirmation.rb +52 -0
- data/lib/insika/context/providers/prompt.rb +17 -4
- data/lib/insika/context/providers/session.rb +39 -3
- data/lib/insika/context/reranker.rb +75 -0
- data/lib/insika/distill.rb +22 -13
- data/lib/insika/doctor.rb +34 -4
- data/lib/insika/dsl/runtime.rb +36 -4
- data/lib/insika/dsl/server_boot.rb +4 -12
- data/lib/insika/dsl.rb +13 -5
- data/lib/insika/edge_limiter.rb +23 -15
- data/lib/insika/egress_guard.rb +11 -3
- data/lib/insika/env_schema.rb +33 -73
- data/lib/insika/evals/assertions.rb +171 -16
- data/lib/insika/evals/golden.rb +141 -4
- data/lib/insika/evals/judge.rb +19 -10
- data/lib/insika/evals/moderator_compare.rb +47 -0
- data/lib/insika/evals/report.rb +61 -9
- data/lib/insika/evals/runner.rb +44 -4
- data/lib/insika/evals/simulator.rb +10 -1
- data/lib/insika/evals/transport.rb +200 -0
- data/lib/insika/event_stream.rb +63 -7
- data/lib/insika/evidence.rb +29 -8
- data/lib/insika/executor.rb +668 -141
- data/lib/insika/harvest.rb +26 -14
- data/lib/insika/harvest_engine.rb +1 -0
- data/lib/insika/harvest_store.rb +12 -0
- data/lib/insika/http_client.rb +87 -4
- data/lib/insika/kits.rb +54 -0
- data/lib/insika/knowledge.rb +34 -20
- data/lib/insika/llm_configurator.rb +46 -4
- data/lib/insika/llm_provider_store.rb +1 -3
- data/lib/insika/llm_trace_store.rb +54 -0
- data/lib/insika/mcp_client.rb +27 -63
- data/lib/insika/mcp_live_tool.rb +55 -9
- data/lib/insika/mcp_store.rb +32 -1
- data/lib/insika/mcp_tool_registry.rb +69 -26
- data/lib/insika/media.rb +38 -25
- data/lib/insika/memory_store.rb +4 -3
- data/lib/insika/memory_trim.rb +60 -0
- data/lib/insika/middleware.rb +12 -3
- data/lib/insika/model_metrics_store.rb +285 -0
- data/lib/insika/model_registry.rb +110 -0
- data/lib/insika/model_resolver.rb +1 -1
- data/lib/insika/model_selection.rb +14 -21
- data/lib/insika/model_visible.rb +17 -6
- data/lib/insika/onboarding.rb +1 -0
- data/lib/insika/outcome_store.rb +1 -1
- data/lib/insika/overlay_tool_registry.rb +25 -2
- data/lib/insika/pack_importer.rb +54 -9
- data/lib/insika/packaging.rb +14 -5
- data/lib/insika/pending_action_store.rb +45 -18
- data/lib/insika/plugin/loader.rb +4 -2
- data/lib/insika/profile_source.rb +4 -0
- data/lib/insika/provider_error_classifier.rb +18 -3
- data/lib/insika/queue_policy.rb +7 -2
- data/lib/insika/refinement/proposer.rb +42 -17
- data/lib/insika/reliability.rb +12 -3
- data/lib/insika/router/app.rb +14 -5
- data/lib/insika/router/session_key.rb +35 -5
- data/lib/insika/safety/config.rb +20 -3
- data/lib/insika/safety/corpus.rb +10 -3
- data/lib/insika/safety/factory.rb +39 -1
- data/lib/insika/safety/input_guardrail.rb +21 -1
- data/lib/insika/safety/judge_moderator.rb +70 -0
- data/lib/insika/safety/output_validator.rb +29 -5
- data/lib/insika/safety/prompt_echo.rb +89 -0
- data/lib/insika/schema_guard.rb +53 -76
- data/lib/insika/server/app.rb +17 -8
- data/lib/insika/server/responses.rb +19 -1
- data/lib/insika/session_actor.rb +58 -28
- data/lib/insika/session_store.rb +159 -10
- data/lib/insika/settings_store.rb +47 -1
- data/lib/insika/shared_conversations.rb +305 -0
- data/lib/insika/skill_catalog.rb +28 -2
- data/lib/insika/skill_store.rb +6 -0
- data/lib/insika/soak/runner.rb +1 -1
- data/lib/insika/steer_injector.rb +7 -48
- data/lib/insika/store.rb +29 -1
- data/lib/insika/stores/json_value.rb +37 -0
- data/lib/insika/stores/memory.rb +22 -35
- data/lib/insika/stores/postgres.rb +295 -0
- data/lib/insika/stores/sqlite.rb +257 -43
- data/lib/insika/stores/turn_counter.rb +25 -0
- data/lib/insika/studio/app.rb +1453 -161
- data/lib/insika/studio/assets/dist/application.css +1 -1
- data/lib/insika/studio/assets/dist/application.js +25 -25
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-sans-latin-wght-normal.woff2 +0 -0
- data/lib/insika/studio/forms.rb +171 -9
- data/lib/insika/studio/nav_icons.rb +3 -0
- data/lib/insika/studio/views/_agent_config_retrieval.erb +73 -0
- data/lib/insika/studio/views/_agent_picker.erb +15 -0
- data/lib/insika/studio/views/_agent_tab_config.erb +78 -242
- data/lib/insika/studio/views/_agent_tab_history.erb +56 -1
- data/lib/insika/studio/views/_agent_tab_performance.erb +34 -0
- data/lib/insika/studio/views/_agent_tab_prompts.erb +7 -9
- data/lib/insika/studio/views/_agent_tab_skills.erb +16 -7
- data/lib/insika/studio/views/_agent_tab_tools.erb +90 -0
- data/lib/insika/studio/views/_agents_master.erb +8 -12
- data/lib/insika/studio/views/_cfg_alerts.erb +4 -0
- data/lib/insika/studio/views/_cfg_budget.erb +20 -0
- data/lib/insika/studio/views/_cfg_distill.erb +26 -0
- data/lib/insika/studio/views/_cfg_followup.erb +30 -0
- data/lib/insika/studio/views/_cfg_grounding.erb +21 -0
- data/lib/insika/studio/views/_cfg_guardrails.erb +47 -0
- data/lib/insika/studio/views/_cfg_harvest.erb +35 -0
- data/lib/insika/studio/views/_cfg_reliability.erb +32 -0
- data/lib/insika/studio/views/_chats_master.erb +66 -0
- data/lib/insika/studio/views/_customers_master.erb +37 -0
- data/lib/insika/studio/views/_empty.erb +15 -0
- data/lib/insika/studio/views/_facts_master.erb +42 -0
- data/lib/insika/studio/views/_harvest_candidate_detail.erb +14 -0
- data/lib/insika/studio/views/_identity.erb +16 -0
- data/lib/insika/studio/views/_kpi.erb +17 -0
- data/lib/insika/studio/views/_message.erb +40 -2
- data/lib/insika/studio/views/_model_chart.erb +82 -0
- data/lib/insika/studio/views/_refinement_master.erb +38 -0
- data/lib/insika/studio/views/{tool_edit.erb → _tool_edit.erb} +32 -9
- data/lib/insika/studio/views/_tool_users.erb +17 -0
- data/lib/insika/studio/views/_why.erb +53 -0
- data/lib/insika/studio/views/agent_detail.erb +17 -10
- data/lib/insika/studio/views/agents.erb +28 -16
- data/lib/insika/studio/views/approvals.erb +16 -11
- data/lib/insika/studio/views/artifact.erb +20 -7
- data/lib/insika/studio/views/artifacts.erb +19 -14
- data/lib/insika/studio/views/chats.erb +22 -29
- data/lib/insika/studio/views/customer.erb +88 -43
- data/lib/insika/studio/views/customers.erb +16 -26
- data/lib/insika/studio/views/evals.erb +49 -31
- data/lib/insika/studio/views/fact_detail.erb +114 -0
- data/lib/insika/studio/views/facts.erb +15 -116
- data/lib/insika/studio/views/followups.erb +63 -52
- data/lib/insika/studio/views/funnel.erb +104 -43
- data/lib/insika/studio/views/harvest.erb +85 -59
- data/lib/insika/studio/views/home.erb +138 -23
- data/lib/insika/studio/views/inbox.erb +36 -0
- data/lib/insika/studio/views/kits.erb +176 -0
- data/lib/insika/studio/views/knowledge.erb +92 -80
- data/lib/insika/studio/views/layout.erb +42 -11
- data/lib/insika/studio/views/live.erb +45 -0
- data/lib/insika/studio/views/mcp.erb +43 -13
- data/lib/insika/studio/views/models.erb +187 -0
- data/lib/insika/studio/views/parity.erb +38 -32
- data/lib/insika/studio/views/playground.erb +13 -9
- data/lib/insika/studio/views/refinement.erb +15 -224
- data/lib/insika/studio/views/refinement_detail.erb +243 -0
- data/lib/insika/studio/views/session.erb +152 -117
- data/lib/insika/studio/views/settings.erb +86 -22
- data/lib/insika/studio/views/skills.erb +53 -19
- data/lib/insika/studio/views/system_files.erb +7 -7
- data/lib/insika/studio/views/task.erb +45 -15
- data/lib/insika/studio/views/tasks.erb +35 -18
- data/lib/insika/studio/views/tools.erb +62 -73
- data/lib/insika/task_actor.rb +4 -1
- data/lib/insika/task_store.rb +67 -1
- data/lib/insika/telemetry/pricing.rb +16 -14
- data/lib/insika/telemetry/recorder.rb +103 -9
- data/lib/insika/telemetry/ruby_llm_instrumenter.rb +110 -0
- data/lib/insika/telemetry.rb +12 -2
- data/lib/insika/templates/daily-digest/README.md +13 -0
- data/lib/insika/templates/daily-digest/agent.rb +22 -11
- data/lib/insika/testing/store_contract.rb +65 -0
- data/lib/insika/text_tool_call.rb +66 -0
- data/lib/insika/tick.rb +7 -1
- data/lib/insika/token_store.rb +5 -0
- data/lib/insika/tool_batch.rb +1 -1
- data/lib/insika/tool_catalog.rb +12 -2
- data/lib/insika/tool_definition.rb +10 -4
- data/lib/insika/tool_envelope.rb +165 -28
- data/lib/insika/tool_manifest.rb +1 -0
- data/lib/insika/tool_store.rb +3 -0
- data/lib/insika/tool_trace_store.rb +15 -8
- data/lib/insika/tool_usage_report.rb +2 -2
- data/lib/insika/tools/a2a_remote.rb +1 -1
- data/lib/insika/tools/agent_enum.rb +1 -1
- data/lib/insika/tools/confirm_pending.rb +112 -0
- data/lib/insika/tools/data_defined_tool.rb +23 -15
- data/lib/insika/tools/generate_image.rb +2 -2
- data/lib/insika/tools/load_knowledge.rb +7 -3
- data/lib/insika/tools/load_skill.rb +3 -3
- data/lib/insika/tools/present.rb +1 -8
- data/lib/insika/tools/remember.rb +7 -2
- data/lib/insika/tools/run_persona_eval.rb +3 -5
- data/lib/insika/tools/save_artifact.rb +16 -6
- data/lib/insika/tools/schedule_followup.rb +3 -3
- data/lib/insika/tools/stuck_signal.rb +3 -3
- data/lib/insika/tools/subagent.rb +5 -5
- data/lib/insika/tools/subagents.rb +3 -3
- data/lib/insika/tools/tool_search.rb +7 -5
- data/lib/insika/tools/tts.rb +2 -2
- data/lib/insika/tools/update_briefing.rb +4 -4
- data/lib/insika/turn_output.rb +2 -2
- data/lib/insika/turn_state.rb +18 -0
- data/lib/insika/turn_timing.rb +51 -14
- data/lib/insika/version.rb +1 -1
- data/lib/insika/wiring/graph.rb +81 -16
- data/lib/insika/workflow.rb +25 -64
- data/lib/insika.rb +19 -3
- metadata +69 -13
- data/lib/insika/commands/import_mcp_tools.rb +0 -48
- data/lib/insika/mcp_http_client.rb +0 -67
- data/lib/insika/mcp_tool_ingestor.rb +0 -165
|
@@ -14,13 +14,13 @@ module Insika
|
|
|
14
14
|
# loads it lazily inside create_chat.
|
|
15
15
|
class LoadSkill < RubyLLM::Tool
|
|
16
16
|
description "Loads the complete instructions (SKILL.md) of a skill by name"
|
|
17
|
-
|
|
17
|
+
parameter :name, description: "Exact skill name, as listed in <available_skills>"
|
|
18
18
|
|
|
19
19
|
# RubyLLM::Tool#name derives from self.class.name — for a nested class it produces
|
|
20
20
|
# "insika--tools--load_skill", not "load_skill" (which wire_callbacks/
|
|
21
21
|
# :skill_activated and SkillCatalog#format_for_prompt assume). Explicit
|
|
22
22
|
# override. Coexists with
|
|
23
|
-
# `
|
|
23
|
+
# `parameter :name` (verified: the param is still present).
|
|
24
24
|
def name = "load_skill"
|
|
25
25
|
|
|
26
26
|
# trace_recorder/state are OPTIONAL (nil = no trace, parity): this tool is
|
|
@@ -86,7 +86,7 @@ module Insika
|
|
|
86
86
|
|
|
87
87
|
@trace_recorder.record(
|
|
88
88
|
session_id: @state.task.session_id,
|
|
89
|
-
entry: { "turn" => @state.turn, "tool" => "load_skill", "call_id" => "",
|
|
89
|
+
entry: { "task_id" => @state.task.id.to_s, "turn" => @state.turn, "tool" => "load_skill", "call_id" => "",
|
|
90
90
|
"args" => { "name" => name.to_s }, "result" => result,
|
|
91
91
|
"ms" => ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000).round,
|
|
92
92
|
"at" => Time.now.utc.iso8601 }
|
data/lib/insika/tools/present.rb
CHANGED
|
@@ -41,14 +41,7 @@ module Insika
|
|
|
41
41
|
|
|
42
42
|
def name = @definition.name
|
|
43
43
|
def description = @definition.description
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
def parameters
|
|
47
|
-
@parameters ||= @definition.top_level_params.each_with_object({}) do |p, acc|
|
|
48
|
-
sym = p[:name].to_sym
|
|
49
|
-
acc[sym] = RubyLLM::Parameter.new(sym, type: p[:type], desc: p[:description], required: p[:required])
|
|
50
|
-
end
|
|
51
|
-
end
|
|
44
|
+
def parameters_schema = @definition.parameters
|
|
52
45
|
|
|
53
46
|
def execute(**kwargs)
|
|
54
47
|
spec = @definition.presentation
|
|
@@ -13,8 +13,8 @@ module Insika
|
|
|
13
13
|
description "Stores information to remember in future conversations. Use `key` " \
|
|
14
14
|
"for a durable key-value fact (overwrites the previous one); omit " \
|
|
15
15
|
"`key` for a free-form note."
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
parameter :value, description: "The content to remember"
|
|
17
|
+
parameter :key, description: "Fact key (e.g.: 'plan', 'name'); omit for a note", required: false
|
|
18
18
|
|
|
19
19
|
# otherwise RubyLLM derives "insika--tools--remember" from the class name.
|
|
20
20
|
def name = "remember"
|
|
@@ -28,6 +28,11 @@ module Insika
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def execute(value:, key: nil)
|
|
31
|
+
if @state.shared_conversations
|
|
32
|
+
kind = key.to_s.strip.empty? ? 'note' : 'fact'
|
|
33
|
+
record = @state.shared_conversations.propose_memory(task:@state.task,id:kind == 'note' ? SecureRandom.uuid : key.to_s,value:value.to_s,kind:kind)
|
|
34
|
+
return {proposed:kind,id:record['id'],revision:record['revision']}
|
|
35
|
+
end
|
|
31
36
|
if key.to_s.strip.empty?
|
|
32
37
|
note = @store.add_note(tenant: @tenant, text: value.to_s)
|
|
33
38
|
emit("note", note.id)
|
|
@@ -45,7 +45,7 @@ module Insika
|
|
|
45
45
|
"its target agent and score the whole conversation with the configured " \
|
|
46
46
|
"judge panel. Refuses if the target exposes a tool that could write for " \
|
|
47
47
|
"real."
|
|
48
|
-
|
|
48
|
+
parameter :case_id, description: "Id of the persona case to run"
|
|
49
49
|
|
|
50
50
|
def name = "run_persona_eval"
|
|
51
51
|
|
|
@@ -111,7 +111,7 @@ module Insika
|
|
|
111
111
|
"#{super} Cases you may run: #{ids.join(', ')}."
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
-
def
|
|
114
|
+
def parameters_schema
|
|
115
115
|
Insika::Tools::AgentEnum.inject(super, case_ids, path: %i[case_id])
|
|
116
116
|
end
|
|
117
117
|
|
|
@@ -319,9 +319,7 @@ module Insika
|
|
|
319
319
|
return if @budget_ledger.nil? || meter.empty?
|
|
320
320
|
|
|
321
321
|
tokens = meter.sum do |m|
|
|
322
|
-
m.
|
|
323
|
-
(m.respond_to?(:cached_tokens) ? m.cached_tokens.to_i : 0) +
|
|
324
|
-
(m.respond_to?(:cache_creation_tokens) ? m.cache_creation_tokens.to_i : 0)
|
|
322
|
+
m.tokens.input.to_i + m.tokens.output.to_i + m.tokens.cache_read.to_i + m.tokens.cache_write.to_i
|
|
325
323
|
end
|
|
326
324
|
return if tokens.zero?
|
|
327
325
|
|
|
@@ -17,11 +17,21 @@ module Insika
|
|
|
17
17
|
class SaveArtifact < RubyLLM::Tool
|
|
18
18
|
description "Save an HTML (or markdown / SVG) page — a report, a digest, " \
|
|
19
19
|
"a summary — and get back a URL to share. Use when the answer " \
|
|
20
|
-
"is a document the channel message cannot carry."
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
"is a document the channel message cannot carry. Executive reports " \
|
|
21
|
+
"need a clear title, period, generation date, evidence-backed takeaway, " \
|
|
22
|
+
"sources and limitations."
|
|
23
|
+
parameter :title, description: "Short human-readable title (<= 200 chars)"
|
|
24
|
+
parameter :content, description: "HTML, markdown, or SVG. HTML reports: use warm neutrals " \
|
|
25
|
+
"and one amber or merchant accent, consistent type/spacing/KPI cards, " \
|
|
26
|
+
"responsive SVG or HTML charts with labels, units and exact values/table. " \
|
|
27
|
+
"Use CSS hover/focus highlights and hoverable tooltips in reserved space, " \
|
|
28
|
+
"labeled native radio chart/table " \
|
|
29
|
+
"choices and details; keep values accessible on touch and focus " \
|
|
30
|
+
"visible. Support system dark mode, print with full tables/details, " \
|
|
31
|
+
"and 140ms transitions disabled for reduced motion. Date the data " \
|
|
32
|
+
"snapshot; never imply live updates. No scripts, event handlers, " \
|
|
33
|
+
"forms, external resources or decorative animation."
|
|
34
|
+
parameter :mime, description: "text/html (default), text/markdown, or image/svg+xml",
|
|
25
35
|
required: false
|
|
26
36
|
|
|
27
37
|
def name = "save_artifact"
|
|
@@ -92,4 +102,4 @@ module Insika
|
|
|
92
102
|
end
|
|
93
103
|
end
|
|
94
104
|
end
|
|
95
|
-
end
|
|
105
|
+
end
|
|
@@ -20,8 +20,8 @@ module Insika
|
|
|
20
20
|
"when the customer agrees to be contacted again (a product, a " \
|
|
21
21
|
"cart, a pending payment). The cancellation policy is permanent: " \
|
|
22
22
|
"a customer who opted out can never be rescheduled."
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
parameter :at, description: "ISO 8601 (absolute) or relative '+6h' / '+2d'"
|
|
24
|
+
parameter :reason, description: "Short machine-readable reason, e.g. 'pix pending, " \
|
|
25
25
|
"customer said she would pay tonight'"
|
|
26
26
|
|
|
27
27
|
def name = "schedule"
|
|
@@ -127,7 +127,7 @@ module Insika
|
|
|
127
127
|
class CancelFollowup < RubyLLM::Tool
|
|
128
128
|
description "Cancel a previously scheduled follow-up by its id. A follow-up " \
|
|
129
129
|
"that already fired cannot be cancelled."
|
|
130
|
-
|
|
130
|
+
parameter :id, description: "The id returned by the schedule tool"
|
|
131
131
|
|
|
132
132
|
def name = "cancel_followup"
|
|
133
133
|
|
|
@@ -19,8 +19,8 @@ module Insika
|
|
|
19
19
|
description "Signal that you cannot proceed and end the turn. Use when the " \
|
|
20
20
|
"request is out of your scope, you lack the data to help, or a human " \
|
|
21
21
|
"must take over. Write your final sentence to the customer first."
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
parameter :reason, description: "Why you cannot proceed (goes to the operator, not the customer)"
|
|
23
|
+
parameter :message, description: "Optional final message if you wrote none", required: false
|
|
24
24
|
|
|
25
25
|
def name = "signal_stuck"
|
|
26
26
|
|
|
@@ -34,7 +34,7 @@ module Insika
|
|
|
34
34
|
# A Halt ends the tool loop here, so the turn cannot continue after declaring
|
|
35
35
|
# stuck. The payload's `say` is the fallback final message when the model
|
|
36
36
|
# wrote no lead-in (the executor's halt_answer already prefers the lead-in).
|
|
37
|
-
|
|
37
|
+
Insika::ToolDefinition::Halt.new(Insika::ToolDefinition.wrap_halt(
|
|
38
38
|
{ "reason" => reason.to_s },
|
|
39
39
|
message.to_s
|
|
40
40
|
))
|
|
@@ -24,10 +24,10 @@ module Insika
|
|
|
24
24
|
"it BLOCKS and returns the child's final answer. Set async:true to " \
|
|
25
25
|
"fire-and-forget a long task: it returns immediately and the child's " \
|
|
26
26
|
"result arrives later as a new message on this conversation."
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
parameter :agent, description: "Id of the child agent to delegate to (must be one this agent may spawn)"
|
|
28
|
+
parameter :message, description: "The self-contained task/prompt for the child agent"
|
|
29
|
+
parameter :async, type: :boolean, required: false,
|
|
30
|
+
description: "true = dispatch and continue (result delivered later); default false = wait for the answer"
|
|
31
31
|
|
|
32
32
|
# otherwise RubyLLM derives "insika--tools--subagent" from the class name.
|
|
33
33
|
def name = "spawn_subagent"
|
|
@@ -51,7 +51,7 @@ module Insika
|
|
|
51
51
|
"#{super} Agents you may spawn: #{@allowed.join(', ')}."
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
def
|
|
54
|
+
def parameters_schema
|
|
55
55
|
@agent_enum_schema ||= Insika::Tools::AgentEnum.inject(super, @allowed, path: %i[agent])
|
|
56
56
|
end
|
|
57
57
|
|
|
@@ -20,9 +20,9 @@ module Insika
|
|
|
20
20
|
"subtasks whose results you'll combine: it runs them concurrently " \
|
|
21
21
|
"(much faster). Each child runs in an ISOLATED context, so put " \
|
|
22
22
|
"everything it needs in its `message`."
|
|
23
|
-
# array-of-objects param via the explicit JSON-schema form (the `
|
|
23
|
+
# array-of-objects param via the explicit JSON-schema form (the `parameter` DSL
|
|
24
24
|
# only reaches strings/scalars). Top-level `tasks` arrives as a kwarg to execute.
|
|
25
|
-
|
|
25
|
+
parameters(
|
|
26
26
|
type: "object",
|
|
27
27
|
properties: {
|
|
28
28
|
tasks: {
|
|
@@ -60,7 +60,7 @@ module Insika
|
|
|
60
60
|
"#{super} Agents you may spawn: #{@allowed.join(', ')}."
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def
|
|
63
|
+
def parameters_schema
|
|
64
64
|
@agent_enum_schema ||= Insika::Tools::AgentEnum.inject(super, @allowed, path: %i[tasks agent])
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -6,13 +6,13 @@ module Insika
|
|
|
6
6
|
module Tools
|
|
7
7
|
# Level 2 of TOOLS progressive disclosure (analog of LoadSkill):
|
|
8
8
|
# searches the deferred catalog and PROMOTES the relevant ones into the live chat via
|
|
9
|
-
# chat.with_tools (
|
|
10
|
-
#
|
|
9
|
+
# chat.with_tools (propagates on the next round of the same `ask`).
|
|
10
|
+
# `require "ruby_llm"` stays in THIS file (it inherits from
|
|
11
11
|
# RubyLLM::Tool) — it does not enter lib/insika.rb; the Executor loads it lazily in
|
|
12
12
|
# configure_chat (like LoadSkill).
|
|
13
13
|
class ToolSearch < RubyLLM::Tool
|
|
14
14
|
description "Searches and enables additional tools by describing the need"
|
|
15
|
-
|
|
15
|
+
parameter :query, description: "What you need to do (e.g.: 'send email', 'generate invoice')"
|
|
16
16
|
|
|
17
17
|
# RubyLLM::Tool#name derives from self.class.name — for a nested class
|
|
18
18
|
# (Insika::Tools::ToolSearch) it produces "insika--tools--tool_search", not
|
|
@@ -81,11 +81,13 @@ module Insika
|
|
|
81
81
|
|
|
82
82
|
def describe(entry)
|
|
83
83
|
tool = @tool_registry.resolve(entry.name)
|
|
84
|
+
schema = Coercion.deep_stringify(tool.parameters_schema || {})
|
|
84
85
|
{
|
|
85
86
|
name: entry.name,
|
|
86
87
|
description: entry.description,
|
|
87
|
-
parameters:
|
|
88
|
-
{ type:
|
|
88
|
+
parameters: schema.fetch("properties", {}).to_h do |name, property|
|
|
89
|
+
[name.to_sym, { type: property["type"], description: property["description"],
|
|
90
|
+
required: Array(schema["required"]).include?(name) }]
|
|
89
91
|
end
|
|
90
92
|
}
|
|
91
93
|
rescue Insika::NotFoundError
|
data/lib/insika/tools/tts.rb
CHANGED
|
@@ -20,8 +20,8 @@ module Insika
|
|
|
20
20
|
description "Synthesize speech for the reply — a voice clip travels as an " \
|
|
21
21
|
"output part in the envelope, the channel supports it. Use when the " \
|
|
22
22
|
"customer should HEAR the answer rather than read it."
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
parameter :text, description: "The words to speak"
|
|
24
|
+
parameter :voice, description: "Optional voice override (default from the agent config)",
|
|
25
25
|
required: false
|
|
26
26
|
|
|
27
27
|
def name = "tts"
|
|
@@ -53,8 +53,8 @@ module Insika
|
|
|
53
53
|
names.empty? ? BASE_DESCRIPTION : "#{BASE_DESCRIPTION} field must be one of: #{names.join(', ')}."
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
parameter :field, description: "The briefing field name (one of the declared list)"
|
|
57
|
+
parameter :value, description: "The value learned. Blank clears the field."
|
|
58
58
|
|
|
59
59
|
def name = "update_briefing"
|
|
60
60
|
|
|
@@ -72,7 +72,7 @@ module Insika
|
|
|
72
72
|
|
|
73
73
|
# The declared names become the `field` enum in the schema the provider
|
|
74
74
|
# sees — per-turn data, like the subagent allowlist (Tools::AgentEnum).
|
|
75
|
-
def
|
|
75
|
+
def parameters_schema
|
|
76
76
|
@field_enum_schema ||= Insika::Tools::AgentEnum.inject(super, @fields, path: %i[field])
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -99,7 +99,7 @@ module Insika
|
|
|
99
99
|
|
|
100
100
|
description "Records the next step agreed with the customer " \
|
|
101
101
|
"(e.g. 'send the payment link tomorrow at 10'). Blank clears it."
|
|
102
|
-
|
|
102
|
+
parameter :text, description: "The agreed next step, in one sentence"
|
|
103
103
|
|
|
104
104
|
def name = "set_next_step"
|
|
105
105
|
|
data/lib/insika/turn_output.rb
CHANGED
|
@@ -49,12 +49,12 @@ module Insika
|
|
|
49
49
|
# public_intermediate: the agent opted this channel in (`edge_stream`), so the
|
|
50
50
|
# narration is TAGGED and `/v1/responses` gives it its own frame. Default false:
|
|
51
51
|
# an internal event stays internal unless someone said otherwise.
|
|
52
|
-
def initialize(filter:, emit:, public_intermediate: false)
|
|
52
|
+
def initialize(filter:, emit:, public_intermediate: false, halt_text: "")
|
|
53
53
|
@filter = filter
|
|
54
54
|
@emit = emit
|
|
55
55
|
@public_intermediate = public_intermediate
|
|
56
56
|
@pending = +"" # text of the message currently streaming
|
|
57
|
-
@last_intermediate =
|
|
57
|
+
@last_intermediate = halt_text.to_s # already filtered when checkpointed
|
|
58
58
|
@candidate = nil
|
|
59
59
|
end
|
|
60
60
|
|
data/lib/insika/turn_state.rb
CHANGED
|
@@ -6,6 +6,10 @@ module Insika
|
|
|
6
6
|
class TurnState
|
|
7
7
|
attr_reader :task, :profile, :turn # turn identity (1-based)
|
|
8
8
|
attr_accessor :message, # input (Middleware may rewrite)
|
|
9
|
+
:caller_context, # payload `context`: text the caller sends WITH the message
|
|
10
|
+
# (channel rules, what the customer already saw). The model
|
|
11
|
+
# and the transcript get it ahead of the message; guardrails
|
|
12
|
+
# and retrieval read `message` alone. nil = none.
|
|
9
13
|
:context, # ContextPackage from the Builder
|
|
10
14
|
:allowed_tools, # Resolution from the Policy Engine
|
|
11
15
|
:allowed_skills,
|
|
@@ -111,7 +115,9 @@ module Insika
|
|
|
111
115
|
# to serialize the turn's real exchange — user + assistant(tool_calls) + tool
|
|
112
116
|
# results + final assistant — into the transcript. nil = no chat recorded
|
|
113
117
|
# (workflow/halt) → persist_turn falls back to the {user, assistant} pair.
|
|
118
|
+
attr_accessor :shared_conversations
|
|
114
119
|
attr_accessor :chat_baseline
|
|
120
|
+
attr_accessor :tool_halt
|
|
115
121
|
|
|
116
122
|
# Internal: impl_name(String) -> STABLE name of the capability that
|
|
117
123
|
# resolved it, computed by resolve_capabilities BEFORE the policy_request and
|
|
@@ -141,6 +147,9 @@ module Insika
|
|
|
141
147
|
# Internal (memory): the turn's tenant (from the Command), scope of the write path
|
|
142
148
|
# (`remember` tool). Set in run_pipeline; nil = DEFAULT_TENANT in the MemoryStore.
|
|
143
149
|
attr_accessor :tenant
|
|
150
|
+
# The phrase an injection-flagging InputGuardrail matched (nil = none). The
|
|
151
|
+
# ChatBuilder hands the model a per-turn notice built from it.
|
|
152
|
+
attr_accessor :security_notice
|
|
144
153
|
|
|
145
154
|
# Internal: turn context deposited into the data-tools to
|
|
146
155
|
# resolve {{ctx.*}} (chat_id/agent_id/tenant/store_id) and emit
|
|
@@ -155,6 +164,9 @@ module Insika
|
|
|
155
164
|
# (workflow) or provider without counts.
|
|
156
165
|
attr_accessor :usage
|
|
157
166
|
|
|
167
|
+
# Native rerank usage events, separate from the chat/Responses token totals.
|
|
168
|
+
attr_accessor :rerank_usage
|
|
169
|
+
|
|
158
170
|
# Internal (Tool Search): ids of side-effects already completed in the
|
|
159
171
|
# interrupted turn, propagated to the tools PROMOTED by tool_search (the same `skip`
|
|
160
172
|
# that the eager tools' wrap_tools receives). Set in run_pipeline;
|
|
@@ -166,6 +178,12 @@ module Insika
|
|
|
166
178
|
# creates the PendingAction/suspends/waits; `actor` = the turn's mailbox (used
|
|
167
179
|
# by the coordinator for await(:approval)).
|
|
168
180
|
attr_accessor :requires_approval, :approval_coordinator, :actor
|
|
181
|
+
attr_accessor :native_approvals, :approval_continuation, :turn_output
|
|
182
|
+
|
|
183
|
+
# Customer confirmation: the store a `customer_confirm` call is held in by the
|
|
184
|
+
# ToolEnvelope and resolved from by confirm_pending/cancel_pending. nil = the
|
|
185
|
+
# deployment has no PendingActionStore (a hold then fails loud, like approval).
|
|
186
|
+
attr_accessor :pending_action_store
|
|
169
187
|
|
|
170
188
|
# Internal: the turn's shared in-flight cap for tool calls —
|
|
171
189
|
# ONE Async::Semaphore(tool_concurrency), installed by ToolAssembly#wrap_tools
|
data/lib/insika/turn_timing.rb
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Insika
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Per-turn latency clock. Every turn runs one (a few clock reads); the turn's
|
|
5
|
+
# numbers land in ModelMetricsStore's turn rows (#metrics). INSIKA_TURN_TIMING
|
|
6
|
+
# only decides what the RESPONSE exposes (#to_h) and whether store calls count.
|
|
7
7
|
#
|
|
8
8
|
# Splits a turn into the three windows that answer "is TTFB local or provider?":
|
|
9
9
|
# prep_ms — prep_start -> ask: ALL local work before the provider call
|
|
10
10
|
# (context build, policy, guardrail detectors, chat assembly).
|
|
11
11
|
# ttft_ms — ask -> first_token: the provider round-trip to the 1st token.
|
|
12
12
|
# gen_ms — first_token -> done: streaming the rest of the response.
|
|
13
|
+
# store_calls — store calls this turn made (+ store_calls_by: "op scope" => n).
|
|
13
14
|
#
|
|
14
15
|
# Marks are monotonic; `mark` is first-write-wins so `first_token` records the
|
|
15
16
|
# FIRST content chunk even though it is called on every chunk.
|
|
@@ -26,26 +27,35 @@ module Insika
|
|
|
26
27
|
Insika::EnvSchema.truthy?(Insika::EnvSchema.read("INSIKA_TURN_TIMING", env))
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
BREAKDOWN_MARKS = %i[prep_start ask first_token done].freeze
|
|
33
|
-
private_constant :BREAKDOWN_MARKS
|
|
30
|
+
# Fiber-storage slot holding the running turn's clock. Child fibers (parallel
|
|
31
|
+
# tool calls, context providers) inherit it, so their store calls count too.
|
|
32
|
+
FIBER_KEY = :insika_turn_timing
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
#
|
|
34
|
+
def self.current = Fiber[FIBER_KEY]
|
|
35
|
+
|
|
36
|
+
# "config:<scope>" keeps its config scope; any other scope keeps its first
|
|
37
|
+
# segment, so ids embedded in scope names do not split the breakdown.
|
|
38
|
+
def self.scope_group(scope)
|
|
39
|
+
parts = scope.to_s.split(":")
|
|
40
|
+
parts.first == "config" ? parts.first(2).join(":") : parts.first.to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# breakdown: true (the default) exposes prep/ttft/gen/total in #to_h.
|
|
44
|
+
# false exposes only first_balloon_ms (the flag-off contract); every mark is
|
|
45
|
+
# still taken, so #metrics is complete either way.
|
|
38
46
|
def initialize(breakdown: true)
|
|
39
47
|
@marks = {}
|
|
40
48
|
@breakdown = breakdown
|
|
49
|
+
@tools = []
|
|
41
50
|
end
|
|
42
51
|
|
|
43
52
|
def mark(name)
|
|
44
|
-
return unless @breakdown || !BREAKDOWN_MARKS.include?(name)
|
|
45
|
-
|
|
46
53
|
@marks[name] ||= Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
47
54
|
end
|
|
48
55
|
|
|
56
|
+
# One tool call of this turn (ToolEnvelope#trace): [name, ok, ms].
|
|
57
|
+
def tool(name, ok, ms) = @tools << [name.to_s, ok, ms]
|
|
58
|
+
|
|
49
59
|
# Has this mark fired? Used by `SendMessage` to prove the channel clock was
|
|
50
60
|
# stamped at 202 acceptance (`:inbound` before the debounce window), and by
|
|
51
61
|
# the pipeline to know a threaded clock already started.
|
|
@@ -56,13 +66,40 @@ module Insika
|
|
|
56
66
|
# first_balloon_ms is the inbound -> first outbox flush window.
|
|
57
67
|
# A missing number is never a zero — no mark, no key.
|
|
58
68
|
def to_h
|
|
59
|
-
{
|
|
69
|
+
h = {
|
|
60
70
|
prep_ms: delta(:prep_start, :ask),
|
|
61
71
|
ttft_ms: delta(:ask, :first_token),
|
|
62
72
|
gen_ms: delta(:first_token, :done),
|
|
63
73
|
total_ms: delta(:prep_start, :done),
|
|
64
74
|
first_balloon_ms: delta(:inbound, :first_balloon)
|
|
65
75
|
}.compact
|
|
76
|
+
h = h.slice(:first_balloon_ms) unless @breakdown
|
|
77
|
+
return h unless @store_calls
|
|
78
|
+
|
|
79
|
+
h.merge(store_calls: @store_calls.values.sum,
|
|
80
|
+
store_calls_by: @store_calls.sort_by { |call, n| [-n, call] }.to_h)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The turn row ModelMetricsStore keeps: every window, whatever the flag.
|
|
84
|
+
# queue_ms is inbound -> pipeline start (debounce + FIFO wait), the part of
|
|
85
|
+
# first_balloon_ms that is not the turn's own work. tools_ms sums the calls,
|
|
86
|
+
# so parallel calls count more than their wall time.
|
|
87
|
+
def metrics
|
|
88
|
+
{
|
|
89
|
+
"prep_ms" => delta(:prep_start, :ask), "ttft_ms" => delta(:ask, :first_token),
|
|
90
|
+
"gen_ms" => delta(:first_token, :done), "total_ms" => delta(:prep_start, :done),
|
|
91
|
+
"first_balloon_ms" => delta(:inbound, :first_balloon), "queue_ms" => delta(:inbound, :prep_start),
|
|
92
|
+
"tools_ms" => (@tools.sum { |_, _, ms| ms.to_i } unless @tools.empty?),
|
|
93
|
+
"tools" => (@tools unless @tools.empty?)
|
|
94
|
+
}.compact
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# One store call made during this turn (see Stores::TurnCounter).
|
|
98
|
+
def count_store(op, scope)
|
|
99
|
+
return unless @breakdown
|
|
100
|
+
|
|
101
|
+
@store_calls ||= Hash.new(0)
|
|
102
|
+
@store_calls["#{op} #{self.class.scope_group(scope)}"] += 1
|
|
66
103
|
end
|
|
67
104
|
|
|
68
105
|
private
|
data/lib/insika/version.rb
CHANGED