mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 12:33:16 -05:00
fix(session): give host tools the product scope of their turn
A harness may call a tool with a context that carries nothing of the turn. Lend the values of the turn to the tool, so a status read in the tool shares the turn's observation instead of starting without one. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
3 files changed
+48
-2
No files matched your search
@@ -1390,7 +1390,10 @@ func (c *Chat) guardTools(tools []Tool) []Tool {
|
||||
if tctx == nil {
|
||||
tctx = c.m.ctx
|
||||
}
|
||||
rctx, cancel := context.WithCancel(ctx)
|
||||
// The harness context carries none of the product state of the turn
|
||||
// (see Options.ProductScope), so a tool would not see it. Lend the
|
||||
// values of the turn to the tool.
|
||||
rctx, cancel := context.WithCancel(turnValues{Context: ctx, turn: tctx})
|
||||
defer cancel()
|
||||
defer context.AfterFunc(tctx, cancel)()
|
||||
return run(rctx, args)
|
||||
@@ -1418,3 +1421,18 @@ func (c *Chat) releaseMemory(ctx context.Context) {
|
||||
c.bus = closedBus()
|
||||
c.evMu.Unlock()
|
||||
}
|
||||
|
||||
// turnValues is a context whose values fall back to those of the turn: the
|
||||
// product state ProductScope attached, which the harness does not pass on.
|
||||
// Deadline and cancellation stay those of the embedded context.
|
||||
type turnValues struct {
|
||||
context.Context
|
||||
turn context.Context
|
||||
}
|
||||
|
||||
func (t turnValues) Value(key any) any {
|
||||
if v := t.Context.Value(key); v != nil {
|
||||
return v
|
||||
}
|
||||
return t.turn.Value(key)
|
||||
}
|
||||
@@ -123,6 +123,26 @@ var _ = Describe("product request scopes", func() {
|
||||
Expect(got).To(Equal([]model.ObservationID{"1", "2", "2", "2", "3", "3", "3"}))
|
||||
})
|
||||
|
||||
It("gives a tool the product scope of the turn when the harness calls it with its own context", func() {
|
||||
seen := make(chan bool, 1)
|
||||
o := session.Options{Harness: &sessiontest.FakeHarness{OwnToolContext: true, Turns: [][]sessiontest.Step{{{Tool: "status", Args: "{}"}}}}}
|
||||
o.ProductScope = func(ctx context.Context, _ session.Access) (context.Context, func()) {
|
||||
return context.WithValue(ctx, productScopeKey{}, &productScope{}), func() {}
|
||||
}
|
||||
o.ChatTools = func(context.Context, session.Access) (session.TurnTools, error) {
|
||||
return session.TurnTools{Tools: []session.Tool{{Name: "status", Description: "status", Schema: json.RawMessage(`{"type":"object"}`),
|
||||
Run: func(ctx context.Context, _ json.RawMessage) (string, error) {
|
||||
_, ok := ctx.Value(productScopeKey{}).(*productScope)
|
||||
seen <- ok
|
||||
return "ok", nil
|
||||
}}}}, nil
|
||||
}
|
||||
chat := newChat(o)
|
||||
Expect(chat.Send(context.Background(), session.Message{Text: "hello"})).To(Succeed())
|
||||
waitTurn(chat)
|
||||
Expect(<-seen).To(BeTrue(), "the tool sees the scope of its turn")
|
||||
})
|
||||
|
||||
for _, stop := range []bool{false, true} {
|
||||
It(fmt.Sprintf("closes pending observations at the real turn boundary (stop=%t)", stop), func() {
|
||||
errors := make(chan error, 8)
|
||||
|
||||
@@ -44,6 +44,10 @@ type FakeHarness struct {
|
||||
// FailNextStarts makes that many of the next Starts fail. Set it while no
|
||||
// Start runs.
|
||||
FailNextStarts int
|
||||
// OwnToolContext makes the harness call a tool with a context of its own,
|
||||
// derived from nothing of the turn, as a harness that receives tool calls
|
||||
// from another process does.
|
||||
OwnToolContext bool
|
||||
|
||||
mu sync.Mutex
|
||||
envs []session.HarnessEnv
|
||||
@@ -172,7 +176,11 @@ func (s *fakeSession) Send(ctx context.Context, text string) error {
|
||||
args := json.RawMessage(st.Args)
|
||||
ev := &session.ToolEvent{Name: st.Tool, Summary: session.Summarize(args)}
|
||||
s.env.Emit(session.Event{Kind: session.KindToolStart, Tool: ev})
|
||||
out, err := tl.Run(ctx, args)
|
||||
toolCtx := ctx
|
||||
if s.h.OwnToolContext {
|
||||
toolCtx = context.Background()
|
||||
}
|
||||
out, err := tl.Run(toolCtx, args)
|
||||
end := *ev
|
||||
end.OK, end.Output = err == nil, session.CapText(out, 2048)
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user