pikuri-core 0.0.7 → 0.1.0
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/README.md +1 -1
- data/lib/pikuri/agent/chat_transport.rb +73 -93
- data/lib/pikuri/agent/configurator.rb +46 -106
- data/lib/pikuri/agent/context_window_detector.rb +44 -85
- data/lib/pikuri/agent/control/cancellable.rb +87 -66
- data/lib/pikuri/agent/control/interloper.rb +127 -105
- data/lib/pikuri/agent/control/step_limit.rb +25 -41
- data/lib/pikuri/agent/control.rb +14 -34
- data/lib/pikuri/agent/event.rb +123 -188
- data/lib/pikuri/agent/extension.rb +118 -94
- data/lib/pikuri/agent/extension_context.rb +50 -77
- data/lib/pikuri/agent/history.rb +653 -0
- data/lib/pikuri/agent/listener/rate_limited.rb +40 -66
- data/lib/pikuri/agent/listener/terminal.rb +143 -117
- data/lib/pikuri/agent/listener/token_log.rb +101 -140
- data/lib/pikuri/agent/listener.rb +23 -43
- data/lib/pikuri/agent/listener_list.rb +26 -47
- data/lib/pikuri/agent/synthesizer.rb +45 -87
- data/lib/pikuri/agent.rb +816 -474
- data/lib/pikuri/bundler_env.rb +68 -0
- data/lib/pikuri/extractor/html.rb +63 -110
- data/lib/pikuri/extractor/passthrough.rb +20 -30
- data/lib/pikuri/extractor.rb +93 -154
- data/lib/pikuri/file_type.rb +63 -135
- data/lib/pikuri/finalizers.rb +32 -47
- data/lib/pikuri/paths.rb +104 -13
- data/lib/pikuri/ruby_llm_patches.rb +106 -0
- data/lib/pikuri/sanitizer.rb +45 -67
- data/lib/pikuri/subprocess.rb +75 -119
- data/lib/pikuri/testing.rb +296 -0
- data/lib/pikuri/tool/calculator.rb +56 -66
- data/lib/pikuri/tool/execute_context.rb +42 -0
- data/lib/pikuri/tool/fetch.rb +51 -77
- data/lib/pikuri/tool/parameters.rb +21 -29
- data/lib/pikuri/tool/scraper.rb +55 -97
- data/lib/pikuri/tool/search/brave.rb +52 -80
- data/lib/pikuri/tool/search/duckduckgo.rb +59 -91
- data/lib/pikuri/tool/search/engines.rb +230 -97
- data/lib/pikuri/tool/search/exa.rb +56 -90
- data/lib/pikuri/tool/search/rate_limiter.rb +61 -38
- data/lib/pikuri/tool/search/result.rb +10 -15
- data/lib/pikuri/tool/trifecta_legs.rb +217 -0
- data/lib/pikuri/tool/web_scrape.rb +38 -54
- data/lib/pikuri/tool/web_search.rb +100 -24
- data/lib/pikuri/tool.rb +140 -65
- data/lib/pikuri/trifecta/contribution.rb +43 -0
- data/lib/pikuri/trifecta/node.rb +47 -0
- data/lib/pikuri/trifecta/report.rb +230 -0
- data/lib/pikuri/trifecta.rb +127 -0
- data/lib/pikuri/url_cache.rb +33 -49
- data/lib/pikuri/version.rb +1 -1
- data/lib/pikuri-core.rb +72 -88
- data/prompts/agent-loop.txt +5 -0
- data/prompts/pikuri-chat.txt +3 -12
- metadata +14 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2703004f3ce0c935f4ee932493db6231109f4ecde7bcfe0ec1481dfecc21001
|
|
4
|
+
data.tar.gz: adf19fd1af6df3aad4ef58d10d5845ce38932566ba5c9d33a7f45791c34287a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7475e59d9dc941226a77c875a7236c6649f678e933938bc7d9bbe18a76cbb1b8b492c3d471d94db1e68dc242f2030e25a48c236b3690cb8191a77d403467bae
|
|
7
|
+
data.tar.gz: 28345b25378c2ae0dabe37d58b8cc592d2f45c4a634d57f134ecd0c15bad7b157c912aa09f9f93dab46a8e78e035400faf69f0e6a2e4222874ffe88e1c73503f
|
data/README.md
CHANGED
|
@@ -70,7 +70,7 @@ handling, and cancellation.
|
|
|
70
70
|
|
|
71
71
|
## Further reading
|
|
72
72
|
|
|
73
|
-
- **Narrative walkthrough:** [chapter
|
|
73
|
+
- **Narrative walkthrough:** [the first-conversation chapter of the pikuri guide](../book/chat.md)
|
|
74
74
|
— install llama.cpp, start the server, run `pikuri-chat`, the
|
|
75
75
|
agentic loop, the four bundled tools, and search-provider
|
|
76
76
|
privacy postures.
|
|
@@ -2,106 +2,86 @@
|
|
|
2
2
|
|
|
3
3
|
module Pikuri
|
|
4
4
|
class Agent
|
|
5
|
-
# Everything that
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# class — every forwarding site (the synthesizer rescue in
|
|
13
|
-
# {Agent#run_loop}, the +agent+ tool from +pikuri-subagents+
|
|
14
|
-
# spawning a sub-agent, a mid-conversation model switch) used to
|
|
15
|
-
# pass the resolution fields individually, and dropping one routed
|
|
16
|
-
# the chat to a different server or raised
|
|
17
|
-
# +RubyLLM::ModelNotFoundError+ on the unknown model id. With a
|
|
18
|
-
# single value object the call site can't silently miss a field.
|
|
5
|
+
# Everything that must travel together for a chat to resolve to the same
|
|
6
|
+
# model *on the same server* on every construction: model id, provider
|
|
7
|
+
# hint, registry-bypass flag, and — when the model lives off the
|
|
8
|
+
# process-global +RubyLLM.config+ default — that server's base URL and API
|
|
9
|
+
# key. One value object so a forwarding site (the synthesizer, a sub-agent
|
|
10
|
+
# spawn, a mid-conversation switch) can't silently drop a field and route
|
|
11
|
+
# the chat to the wrong server or raise +RubyLLM::ModelNotFoundError+.
|
|
19
12
|
#
|
|
20
13
|
# == Why +api_base+ / +api_key+ live here
|
|
21
14
|
#
|
|
22
|
-
# +RubyLLM::Chat#with_model+ swaps only
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# +#{provider}_api_key+) via a per-chat +RubyLLM::Context+; both are
|
|
30
|
-
# +nil+ for a transport that rides the process-global config.
|
|
15
|
+
# +RubyLLM::Chat#with_model+ swaps only model/provider against the chat's
|
|
16
|
+
# *existing* connection, so switching to a model on a different server
|
|
17
|
+
# needs the connection to travel with the model — else the new id is sent
|
|
18
|
+
# to the old server's URL with the old key. {Agent} maps these two generic
|
|
19
|
+
# fields onto the provider's config slots (+#{provider}_api_base+ /
|
|
20
|
+
# +#{provider}_api_key+) via a per-chat +RubyLLM::Context+; both +nil+ for a
|
|
21
|
+
# transport riding the global config.
|
|
31
22
|
#
|
|
32
|
-
# Pure data carrier
|
|
33
|
-
#
|
|
23
|
+
# Pure data carrier — no +RubyLLM+ references here, so the seam stays in
|
|
24
|
+
# {Agent}, +bin/pikuri-chat+, and {Tool}.
|
|
34
25
|
#
|
|
35
26
|
# @!attribute [r] model
|
|
36
|
-
# @return [String, nil] LLM
|
|
37
|
-
# +RubyLLM.config.default_model+ at {Agent} construction
|
|
27
|
+
# @return [String, nil] LLM id; +nil+ defers to
|
|
28
|
+
# +RubyLLM.config.default_model+ at {Agent} construction.
|
|
38
29
|
# @!attribute [r] provider
|
|
39
|
-
# @return [Symbol, nil] forwarded to +RubyLLM.chat+. Required
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
# +api_base+ / +api_key+ is set (it names the config slots).
|
|
30
|
+
# @return [Symbol, nil] forwarded to +RubyLLM.chat+. Required with
|
|
31
|
+
# +assume_model_exists+ for a local OpenAI-compatible server whose ids
|
|
32
|
+
# aren't in ruby_llm's registry, and whenever +api_base+/+api_key+ is
|
|
33
|
+
# set (it names the config slots).
|
|
44
34
|
# @!attribute [r] assume_model_exists
|
|
45
|
-
# @return [Boolean] forwarded to +RubyLLM.chat+; +true+ skips
|
|
46
|
-
#
|
|
47
|
-
# Requires +provider+.
|
|
35
|
+
# @return [Boolean] forwarded to +RubyLLM.chat+; +true+ skips the registry
|
|
36
|
+
# lookup and trusts the model id. Requires +provider+.
|
|
48
37
|
# @!attribute [r] api_base
|
|
49
|
-
# @return [String, nil]
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# +#{provider}_api_base+ slot by {Agent}.
|
|
38
|
+
# @return [String, nil] this server's base URL (e.g.
|
|
39
|
+
# +http://localhost:8080/v1+); +nil+ rides the global config. Mapped to
|
|
40
|
+
# +#{provider}_api_base+ by {Agent}.
|
|
53
41
|
# @!attribute [r] api_key
|
|
54
|
-
# @return [String, nil] API key for this
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# so it never leaks into a log line or backtrace.
|
|
42
|
+
# @return [String, nil] API key for this server; +nil+ rides the global
|
|
43
|
+
# config. Mapped to +#{provider}_api_key+ by {Agent}. Redacted in
|
|
44
|
+
# {#inspect}.
|
|
58
45
|
# @!attribute [r] context_window
|
|
59
46
|
# @return [Integer, nil] explicit context-window cap for this
|
|
60
|
-
# model
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
# {
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# connection slot) — pure pikuri metadata read by
|
|
67
|
-
# {Agent#detect_and_emit_context_cap!}. The cap-inheritance
|
|
68
|
-
# channel too: the +agent+ tool from +pikuri-subagents+ and the
|
|
69
|
-
# synthesizer hand a spawned agent +parent.transport.with(
|
|
70
|
-
# context_window: parent.context_window_cap)+ so the parent's
|
|
71
|
-
# *resolved* cap (explicit or probed) rides along without a
|
|
47
|
+
# model-on-server, or +nil+ to defer to {ContextWindowDetector}. Travels
|
|
48
|
+
# with the model because the cap *is* per-model-per-server. Never sent to
|
|
49
|
+
# ruby_llm — pure pikuri metadata read by
|
|
50
|
+
# {Agent#detect_and_emit_context_cap!}, and the cap-inheritance channel:
|
|
51
|
+
# a spawned agent gets +parent.transport.with(context_window:
|
|
52
|
+
# parent.context_window_cap)+ so the resolved cap rides along without a
|
|
72
53
|
# re-probe.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
54
|
+
# @!attribute [r] faraday_adapter
|
|
55
|
+
# @return [Class, Symbol, nil] a custom Faraday adapter to install on this
|
|
56
|
+
# agent's per-chat +RubyLLM::Context+ (via +config.faraday_adapter=+),
|
|
57
|
+
# or +nil+ for ruby_llm's default (+:net_http+). A generic HTTP seam —
|
|
58
|
+
# inject a VCR / logging / *scripted-fake* adapter without touching the
|
|
59
|
+
# global +RubyLLM.config+. Its worked use is
|
|
60
|
+
# {Pikuri::Testing.fake_transport}, which bakes an anonymous scripted
|
|
61
|
+
# adapter here so the real ruby_llm loop runs against canned HTTP. Only
|
|
62
|
+
# honoured on the connection-override path ({#connection_overrides?}),
|
|
63
|
+
# which any adapter-carrying transport is on (it also sets +api_base+).
|
|
64
|
+
class ChatTransport < Data.define(:model, :provider, :assume_model_exists, :api_base, :api_key,
|
|
65
|
+
:context_window, :faraday_adapter)
|
|
66
|
+
# Build an +:openai+-provider transport for an OpenAI-compatible server
|
|
67
|
+
# (local llama.cpp, cloud endpoint, ...), carrying its connection so the
|
|
68
|
+
# agent rides a per-chat +RubyLLM::Context+ instead of the global config.
|
|
69
|
+
# The +bin/pikuri-*+ host-boot factory in place of +RubyLLM.configure+ —
|
|
70
|
+
# one isolated connection per agent.
|
|
82
71
|
#
|
|
83
|
-
# +server+ is
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
# +https://api.x.ai/v1/+ all normalize to the same +.../v1+ base.
|
|
88
|
-
# Without this, a +server+ value that already ended in +/v1+ would
|
|
89
|
-
# double to +/v1/v1+ and every request would 404.
|
|
72
|
+
# A trailing +/v1+ on +server+ is stripped and re-appended exactly once,
|
|
73
|
+
# so +https://api.x.ai+, +.../v1+, and +.../v1/+ all normalize to the same
|
|
74
|
+
# +.../v1+ base — without which a +/v1+-terminated value would double to
|
|
75
|
+
# +/v1/v1+ and 404.
|
|
90
76
|
#
|
|
91
|
-
# @param server [String] server origin, with or without a trailing
|
|
92
|
-
# +/v1+, e.g. +"http://localhost:8080"+ or +"https://api.x.ai/v1"+
|
|
77
|
+
# @param server [String] server origin, with or without a trailing +/v1+
|
|
93
78
|
# @param model [String] model id served there, trusted verbatim
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# @param
|
|
97
|
-
# +
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# +/props+ probe (the right default for a local llama.cpp, which
|
|
101
|
-
# reports its launched +n_ctx+; the right *override* for a cloud
|
|
102
|
-
# server the probe can't reach, e.g. a 2M-window model on x.ai)
|
|
103
|
-
# @return [ChatTransport] a transport whose +api_base+ is the
|
|
104
|
-
# normalized +.../v1+ URL and whose +api_key+ is +api_key+
|
|
79
|
+
# @param api_key [String] API key; the +"not-needed"+ placeholder for a
|
|
80
|
+
# keyless local server
|
|
81
|
+
# @param context_window [Integer, nil] explicit cap, or +nil+ to defer to
|
|
82
|
+
# {ContextWindowDetector}'s +/props+ probe (right for local llama.cpp;
|
|
83
|
+
# an override for a cloud server the probe can't reach)
|
|
84
|
+
# @return [ChatTransport]
|
|
105
85
|
def self.from_openai_server(server:, model:, api_key: 'not-needed', context_window: nil)
|
|
106
86
|
base = server.to_s.strip.chomp('/').delete_suffix('/v1')
|
|
107
87
|
new(
|
|
@@ -120,11 +100,12 @@ module Pikuri
|
|
|
120
100
|
# @param api_base [String, nil]
|
|
121
101
|
# @param api_key [String, nil]
|
|
122
102
|
# @param context_window [Integer, nil]
|
|
103
|
+
# @param faraday_adapter [Class, Symbol, nil]
|
|
123
104
|
# @raise [ArgumentError] if +api_base+ or +api_key+ is set without
|
|
124
105
|
# a +provider+ (the provider names the config slots the
|
|
125
106
|
# connection overrides map onto)
|
|
126
107
|
def initialize(model:, provider: nil, assume_model_exists: false,
|
|
127
|
-
api_base: nil, api_key: nil, context_window: nil)
|
|
108
|
+
api_base: nil, api_key: nil, context_window: nil, faraday_adapter: nil)
|
|
128
109
|
if (api_base || api_key) && provider.nil?
|
|
129
110
|
raise ArgumentError, "api_base/api_key require a provider, got #{provider.inspect}"
|
|
130
111
|
end
|
|
@@ -132,10 +113,9 @@ module Pikuri
|
|
|
132
113
|
super
|
|
133
114
|
end
|
|
134
115
|
|
|
135
|
-
#
|
|
136
|
-
# +RubyLLM::Context#chat+. Excludes the connection fields
|
|
137
|
-
#
|
|
138
|
-
# call itself.
|
|
116
|
+
# Model-resolution kwargs to spread into +RubyLLM.chat+ /
|
|
117
|
+
# +RubyLLM::Context#chat+. Excludes the connection fields (those configure
|
|
118
|
+
# the +Context+, not the +chat+ call).
|
|
139
119
|
#
|
|
140
120
|
# @return [Hash{Symbol => String, Symbol, Boolean, nil}]
|
|
141
121
|
def chat_kwargs
|
|
@@ -150,15 +130,15 @@ module Pikuri
|
|
|
150
130
|
!api_base.nil? || !api_key.nil?
|
|
151
131
|
end
|
|
152
132
|
|
|
153
|
-
# Default +Data#inspect+ would print +api_key+ verbatim
|
|
154
|
-
#
|
|
155
|
-
# that touches the transport. Redact it.
|
|
133
|
+
# Default +Data#inspect+ would print +api_key+ verbatim into any log line
|
|
134
|
+
# or backtrace. Redact it.
|
|
156
135
|
#
|
|
157
136
|
# @return [String]
|
|
158
137
|
def inspect
|
|
159
138
|
"#<#{self.class} model=#{model.inspect} provider=#{provider.inspect} " \
|
|
160
139
|
"assume_model_exists=#{assume_model_exists} api_base=#{api_base.inspect} " \
|
|
161
|
-
"api_key=#{api_key.nil? ? 'nil' : '[REDACTED]'} context_window=#{context_window.inspect}
|
|
140
|
+
"api_key=#{api_key.nil? ? 'nil' : '[REDACTED]'} context_window=#{context_window.inspect} " \
|
|
141
|
+
"faraday_adapter=#{faraday_adapter.inspect}>"
|
|
162
142
|
end
|
|
163
143
|
alias to_s inspect
|
|
164
144
|
end
|
|
@@ -2,64 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
module Pikuri
|
|
4
4
|
class Agent
|
|
5
|
-
# Build-time collector yielded into the +Pikuri::Agent.new+ block.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# == Why this exists
|
|
13
|
-
#
|
|
14
|
-
# Splits "configure the agent" from "the agent's runtime state".
|
|
15
|
-
# Hosts can write a block that reads cleanly:
|
|
5
|
+
# Build-time collector yielded into the +Pikuri::Agent.new+ block. Hosts
|
|
6
|
+
# and {Extension}s call its methods to declare tools, listeners,
|
|
7
|
+
# +on_close+ handlers, and extension instances; {Agent#initialize} drains
|
|
8
|
+
# the collected state into the agent's wiring before returning, then
|
|
9
|
+
# discards the Configurator (it carries no runtime state). System-prompt
|
|
10
|
+
# contributions are *not* collected here — the Agent pulls them from each
|
|
11
|
+
# extension's {Extension#system_prompt_snippets} at assembly time.
|
|
16
12
|
#
|
|
17
13
|
# Pikuri::Agent.new(transport: ..., system_prompt: ...) do |c|
|
|
18
14
|
# c.add_listener Pikuri::Agent::Listener::Terminal.new
|
|
19
15
|
# c.add_tool Pikuri::Tool::WebSearch.build
|
|
20
|
-
# c.
|
|
21
|
-
# c.add_tool Pikuri::Tool::FETCH
|
|
22
|
-
# c.add_extension Pikuri::Skill::Extension.new(catalog: catalog)
|
|
16
|
+
# c.add_extension Pikuri::Skill::Extension.new(catalog: catalog, root: project_root)
|
|
23
17
|
# end
|
|
24
18
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# {#add_tool} registers a tool the parent agent can call: it lands
|
|
28
|
-
# in {#tools} and gets handed to ruby_llm via +chat.with_tool+.
|
|
29
|
-
# {#add_sub_agent_tool} registers a tool the parent *cannot* call
|
|
30
|
-
# — it lands in {#sub_agent_tools} and is never sent to ruby_llm
|
|
31
|
-
# for the parent, but is visible to {Pikuri::SubAgent::Extension}'s
|
|
32
|
-
# persona-tool-name resolution. The use case is the lethal-trifecta
|
|
33
|
-
# defense in {Pikuri::Code::Bash::Sandbox} terms: keep network tools
|
|
34
|
-
# (+web_search+ / +web_scrape+ / +fetch+) off the parent so a prompt-
|
|
35
|
-
# injected file read cannot egress through the parent's own tools,
|
|
36
|
-
# while still letting the +researcher+ persona reach them via the
|
|
37
|
-
# +agent+ delegation tool. See SECURITY.md §"Defense: capability
|
|
38
|
-
# boundaries via sub-agents".
|
|
19
|
+
# Extensions implement +configure(c)+ against this same type, so
|
|
20
|
+
# block-users and extensions share one API.
|
|
39
21
|
#
|
|
40
|
-
#
|
|
41
|
-
# type, so the call sites for "block users add stuff" and
|
|
42
|
-
# "extensions add stuff" share one API.
|
|
43
|
-
#
|
|
44
|
-
# == Lifecycle
|
|
22
|
+
# == Two tool pools: regular vs. sub-agent-only
|
|
45
23
|
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# (
|
|
49
|
-
#
|
|
50
|
-
#
|
|
24
|
+
# {#add_tool} registers a tool the parent agent can call (lands in {#tools},
|
|
25
|
+
# handed to ruby_llm). {#add_sub_agent_tool} registers one the parent
|
|
26
|
+
# *cannot* call (lands in {#sub_agent_tools}, never sent to ruby_llm for the
|
|
27
|
+
# parent) but which {Pikuri::SubAgent::Extension}'s persona-tool-name
|
|
28
|
+
# resolution can reach. The use case is the lethal-trifecta defense: keep
|
|
29
|
+
# network tools (+web_search+ / +web_scrape+ / +fetch+) off the parent so a
|
|
30
|
+
# prompt-injected file read can't egress through it, while the +researcher+
|
|
31
|
+
# persona still reaches them via the +agent+ tool. See SECURITY.md.
|
|
51
32
|
class Configurator
|
|
52
|
-
# @return [Agent::ChatTransport] same transport the Agent will
|
|
53
|
-
#
|
|
54
|
-
# (e.g. an MCP description-synthesizer that calls the same
|
|
55
|
-
# model the agent itself uses).
|
|
33
|
+
# @return [Agent::ChatTransport] same transport the Agent will use, so an
|
|
34
|
+
# extension can wire helpers against the same model.
|
|
56
35
|
attr_reader :transport
|
|
57
36
|
|
|
58
|
-
# @return [String] the +system_prompt:+ kwarg
|
|
59
|
-
#
|
|
60
|
-
# prompt via {#append_system_prompt} rather than mutating
|
|
61
|
-
# this; the attribute exists so a peek at the base is
|
|
62
|
-
# available for diagnostics.
|
|
37
|
+
# @return [String] the +system_prompt:+ kwarg, untouched — the base the
|
|
38
|
+
# Agent prepends to the pulled {Extension#system_prompt_snippets}.
|
|
63
39
|
attr_reader :system_prompt_base
|
|
64
40
|
|
|
65
41
|
# @return [String] this agent's unique identifier; empty for the
|
|
@@ -74,28 +50,22 @@ module Pikuri
|
|
|
74
50
|
# to the Agent ctor, or +nil+.
|
|
75
51
|
attr_reader :step_limit
|
|
76
52
|
|
|
77
|
-
# @return [Control::Cancellable, nil] cancellation control
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# synthesizer) share this so a user cancel during boot
|
|
81
|
-
# propagates correctly.
|
|
53
|
+
# @return [Control::Cancellable, nil] cancellation control, or +nil+.
|
|
54
|
+
# Extensions running sub-LLM calls in +configure+ share it so a user
|
|
55
|
+
# cancel during boot propagates.
|
|
82
56
|
attr_reader :cancellable
|
|
83
57
|
|
|
84
58
|
# @return [Control::Interloper, nil] mid-loop user-input queue
|
|
85
59
|
# passed to the Agent ctor, or +nil+.
|
|
86
60
|
attr_reader :interloper
|
|
87
61
|
|
|
88
|
-
# @return [Array<Tool>] tools added via {#add_tool}, in
|
|
89
|
-
#
|
|
90
|
-
# registered with ruby_llm so the parent LLM can call them.
|
|
62
|
+
# @return [Array<Tool>] tools added via {#add_tool}, in declaration order;
|
|
63
|
+
# drained by {Agent#initialize} and registered with ruby_llm.
|
|
91
64
|
attr_reader :tools
|
|
92
65
|
|
|
93
|
-
# @return [Array<Tool>] tools added via {#add_sub_agent_tool},
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# LLM, available only to sub-agents through
|
|
97
|
-
# {Pikuri::SubAgent::Extension}'s persona-tool-name
|
|
98
|
-
# resolution. See the class header.
|
|
66
|
+
# @return [Array<Tool>] tools added via {#add_sub_agent_tool}, in
|
|
67
|
+
# declaration order; drained but *not* registered with ruby_llm —
|
|
68
|
+
# available only to sub-agents (see the class header).
|
|
99
69
|
attr_reader :sub_agent_tools
|
|
100
70
|
|
|
101
71
|
# @return [Array<Listener::Base>] listeners added via
|
|
@@ -103,21 +73,14 @@ module Pikuri
|
|
|
103
73
|
# {Agent#initialize}.
|
|
104
74
|
attr_reader :listeners
|
|
105
75
|
|
|
106
|
-
# @return [Array<String>] system-prompt snippets added via
|
|
107
|
-
# {#append_system_prompt}, in declaration order. Joined with
|
|
108
|
-
# double-newline separators between the base prompt and each
|
|
109
|
-
# snippet by {Agent#initialize}.
|
|
110
|
-
attr_reader :system_prompt_additions
|
|
111
|
-
|
|
112
76
|
# @return [Array<Proc>] +on_close+ handlers added via
|
|
113
77
|
# {#on_close}, in declaration order. Fired by {Agent#close}
|
|
114
78
|
# in LIFO order with per-handler rescue.
|
|
115
79
|
attr_reader :on_close_handlers
|
|
116
80
|
|
|
117
81
|
# @return [Array<#configure>] extension instances added via
|
|
118
|
-
# {#add_extension}, in declaration order. The Agent
|
|
119
|
-
#
|
|
120
|
-
# on each with the agent's {ExtensionContext}.
|
|
82
|
+
# {#add_extension}, in declaration order. The Agent calls +bind+ on
|
|
83
|
+
# each after wiring is complete.
|
|
121
84
|
attr_reader :extensions
|
|
122
85
|
|
|
123
86
|
# @param transport [Agent::ChatTransport]
|
|
@@ -127,15 +90,14 @@ module Pikuri
|
|
|
127
90
|
# @param step_limit [Control::StepLimit, nil]
|
|
128
91
|
# @param cancellable [Control::Cancellable, nil]
|
|
129
92
|
# @param interloper [Control::Interloper, nil]
|
|
130
|
-
# @param on_close_sink [Array<Proc>, nil] array
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# later extension's +configure+ raises. Defaults to a fresh
|
|
136
|
-
# array for standalone use (e.g. specs).
|
|
93
|
+
# @param on_close_sink [Array<Proc>, nil] array {#on_close} appends to.
|
|
94
|
+
# {Agent#initialize} passes its live +@on_close_handlers+ so a handler
|
|
95
|
+
# armed via +c.on_close+ is reachable instantly — what lets the
|
|
96
|
+
# constructor close a half-built agent if a later +configure+ raises.
|
|
97
|
+
# Defaults to a fresh array (standalone/spec use).
|
|
137
98
|
def initialize(transport:, system_prompt_base:, id:, streaming:,
|
|
138
|
-
step_limit:, cancellable:, interloper:,
|
|
99
|
+
step_limit:, cancellable:, interloper:,
|
|
100
|
+
on_close_sink: nil)
|
|
139
101
|
@transport = transport
|
|
140
102
|
@system_prompt_base = system_prompt_base
|
|
141
103
|
@id = id
|
|
@@ -147,7 +109,6 @@ module Pikuri
|
|
|
147
109
|
@tools = []
|
|
148
110
|
@sub_agent_tools = []
|
|
149
111
|
@listeners = []
|
|
150
|
-
@system_prompt_additions = []
|
|
151
112
|
@on_close_handlers = on_close_sink || []
|
|
152
113
|
@extensions = []
|
|
153
114
|
end
|
|
@@ -202,30 +163,10 @@ module Pikuri
|
|
|
202
163
|
nil
|
|
203
164
|
end
|
|
204
165
|
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
# +<available_mcps>+, and similar advertisement blocks. Block
|
|
210
|
-
# users typically pass the full system prompt via the ctor's
|
|
211
|
-
# +system_prompt:+ kwarg instead.
|
|
212
|
-
#
|
|
213
|
-
# @param snippet [String]
|
|
214
|
-
# @return [void]
|
|
215
|
-
def append_system_prompt(snippet)
|
|
216
|
-
@system_prompt_additions << snippet
|
|
217
|
-
nil
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Register an extension. The extension's +configure(self)+ is
|
|
221
|
-
# called immediately so source-order matches execution-order.
|
|
222
|
-
# The instance is also retained for the +bind(ctx)+ sweep
|
|
223
|
-
# that runs at the end of {Agent#initialize}.
|
|
224
|
-
#
|
|
225
|
-
# Extensions must implement both +configure+ and +bind+. The
|
|
226
|
-
# easy way is to +include Pikuri::Agent::Extension+ — that
|
|
227
|
-
# mixes in empty defaults for both, so an extension overrides
|
|
228
|
-
# only what it cares about and leaves the other as a no-op.
|
|
166
|
+
# Register an extension: its +configure(self)+ runs immediately (so
|
|
167
|
+
# source order matches execution order), and the instance is retained for
|
|
168
|
+
# the +bind(ctx)+ sweep at the end of {Agent#initialize}. Both hooks are
|
|
169
|
+
# required — +include Pikuri::Agent::Extension+ for no-op defaults.
|
|
229
170
|
#
|
|
230
171
|
# @param extension [Extension, #configure, #bind] extension instance
|
|
231
172
|
# @return [void]
|
|
@@ -235,9 +176,8 @@ module Pikuri
|
|
|
235
176
|
nil
|
|
236
177
|
end
|
|
237
178
|
|
|
238
|
-
# Register a handler called by {Agent#close}
|
|
239
|
-
#
|
|
240
|
-
# +ensure+-block cleanup discipline.
|
|
179
|
+
# Register a handler called by {Agent#close} (LIFO, each inside its own
|
|
180
|
+
# +rescue+).
|
|
241
181
|
#
|
|
242
182
|
# @yield called with no arguments at close time
|
|
243
183
|
# @return [void]
|