mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: offer no memory id for legacy manual memory, document linked history inputs
This commit is contained in:
@@ -453,9 +453,9 @@ pub async fn handle_ai_agent_job(
|
||||
));
|
||||
};
|
||||
|
||||
// A linked step takes its brain and tools from the resource and keeps only the flow-local
|
||||
// inputs (user message, attachments, memory id and messages) of its own; both stay rigid, so the one thing it may
|
||||
// bind to this flow is the tools' inputs, overlaid from `tool_inputs` below.
|
||||
// A linked step takes its brain and tools from the resource and keeps only the flow-local inputs
|
||||
// (user message, attachments, memory id and messages) of its own; both stay rigid, so the one
|
||||
// thing it may bind to this flow is the tools' inputs, overlaid from `tool_inputs` below.
|
||||
let (mut args, tools): (AIAgentArgs, Vec<AgentTool>) = if let Some(agent_ref) = agent.as_deref()
|
||||
{
|
||||
let agent_path = agent_ref
|
||||
|
||||
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -65,7 +65,8 @@ The worker reconciles them once per agent invocation, nested agent tools include
|
||||
|
||||
Memory is stored per (memory id, step id), in `ai_agent_memory` or S3 at
|
||||
`memory/{workspace}/{memory id}/{step}.json`. The chat transcript (`flow_conversation_message`)
|
||||
always follows the run's id, even when a step sets its own.
|
||||
always follows the run's id, even when a step sets its own. Nothing expires stored memory: deleting a chat conversation deletes
|
||||
its memory, and a memory named by a string id stays until it is overwritten.
|
||||
|
||||
Compatibility runs one way. New workers read every older shape. The editor rewrites a legacy step
|
||||
only when the author changes it, so a flow nobody edits keeps running on older workers, while a
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@ import {
|
||||
AGENT_FIELD_BY_KEY,
|
||||
AGENT_FIELDS,
|
||||
AGENT_HISTORY_KEYS,
|
||||
memoryIdUnusedNote,
|
||||
agentFieldIsSet,
|
||||
agentStreamingEnabled,
|
||||
initialVisibleAgentFields
|
||||
@@ -127,3 +128,16 @@ describe('agentStreamingEnabled', () => {
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('memoryIdUnusedNote', () => {
|
||||
// Mirrors the worker's order: offering a memory id that a run would ignore misleads the author.
|
||||
it('offers a memory id only when the policy reads memory', () => {
|
||||
expect(memoryIdUnusedNote(undefined)).toMatch(/off/)
|
||||
expect(memoryIdUnusedNote({ kind: 'off' })).toMatch(/off/)
|
||||
expect(memoryIdUnusedNote({ kind: 'window', context_length: 0 })).toMatch(/off/)
|
||||
expect(memoryIdUnusedNote({ kind: 'auto' })).toMatch(/off/)
|
||||
expect(memoryIdUnusedNote({ kind: 'manual', messages: [] })).toMatch(/fixed list/)
|
||||
expect(memoryIdUnusedNote({ kind: 'window', context_length: 10 })).toBeUndefined()
|
||||
expect(memoryIdUnusedNote({ kind: 'auto', context_length: 4, memory_id: 'x' })).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -44,6 +44,19 @@ export function memoryPolicyIsOff(memory: any): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/** Why a step's history row offers no memory id, when it offers none: the agent keeps no memory, or
|
||||
* its memory setting supplies a fixed list of messages, which the worker sends before it would
|
||||
* read any memory. */
|
||||
export function memoryIdUnusedNote(memory: any): string | undefined {
|
||||
if (memory?.kind === 'manual') {
|
||||
return 'This agent sends a fixed list of messages set in its memory, so no memory id applies.'
|
||||
}
|
||||
if (memoryPolicyIsOff(memory)) {
|
||||
return "This agent's memory is off, so no memory id applies. Turn memory on in the agent to change this."
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** A memory setting in words, for a linked agent's summary. */
|
||||
export function describeMemoryPolicy(memory: any): string {
|
||||
if (memory?.kind === 'manual') return 'Provided messages'
|
||||
|
||||
@@ -121,7 +121,8 @@ export function inputTransformsToAgentConfig(
|
||||
|
||||
/**
|
||||
* Reduce the AI agent schema to only the flow-local inputs. Used when a step is linked to a saved
|
||||
* agent: the brain fields come from the resource, so only user_message/user_attachments stay editable.
|
||||
* agent: the brain fields come from the resource, so only the user message, attachments and history
|
||||
* inputs stay editable.
|
||||
*/
|
||||
export function flowLocalAgentSchema(schema: any): any {
|
||||
if (!schema?.properties) {
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
label: string
|
||||
tooltip?: string
|
||||
chatInputEnabled?: boolean
|
||||
/** The agent keeps no memory, so no memory id applies to this step. */
|
||||
memoryOff?: boolean
|
||||
/** Why no memory id applies to this step, when the agent reads no memory. */
|
||||
memoryUnusedNote?: string
|
||||
/** The step's editor for one history input, with the error to show under it. */
|
||||
field: Snippet<[AgentHistoryKey, string | undefined]>
|
||||
/** Called for each key this row removes, so the form forgets its validity. */
|
||||
@@ -27,7 +27,7 @@
|
||||
label,
|
||||
tooltip = undefined,
|
||||
chatInputEnabled = false,
|
||||
memoryOff = false,
|
||||
memoryUnusedNote = undefined,
|
||||
field,
|
||||
onRemoveKey = undefined
|
||||
}: Props = $props()
|
||||
@@ -58,11 +58,8 @@
|
||||
<div class="flex min-h-7 items-end">
|
||||
<FieldHeader {label} simpleTooltip={tooltip} displayType={false} />
|
||||
</div>
|
||||
{#if memoryOff}
|
||||
<p class="text-xs text-secondary">
|
||||
This agent's memory is off, so no memory id applies. Turn memory on in the agent to change
|
||||
this.
|
||||
</p>
|
||||
{#if memoryUnusedNote}
|
||||
<p class="text-xs text-secondary">{memoryUnusedNote}</p>
|
||||
{#if source === 'here'}
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge color="yellow" small>Ignored</Badge>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
AGENT_FIELDS,
|
||||
AGENT_HISTORY_ROW,
|
||||
AGENT_TOOLS_ROW,
|
||||
memoryPolicyIsOff,
|
||||
memoryIdUnusedNote,
|
||||
AGENT_FIELD_GROUPS,
|
||||
agentFieldAppliesTo,
|
||||
initialVisibleAgentFields,
|
||||
@@ -164,14 +164,14 @@
|
||||
|
||||
// The brain edited here, or the linked agent's. An expression, or a linked agent still loading,
|
||||
// reads as keeping memory, so the history row never offers less than a run may use.
|
||||
let memoryOff = $derived.by(() => {
|
||||
let memoryUnusedNote = $derived.by(() => {
|
||||
if ('memory' in schemaProperties) {
|
||||
const transform = args?.memory
|
||||
return transform == undefined || transform.type === 'static'
|
||||
? memoryPolicyIsOff(transform?.value)
|
||||
: false
|
||||
? memoryIdUnusedNote(transform?.value)
|
||||
: undefined
|
||||
}
|
||||
return linkedMemory ? memoryPolicyIsOff(linkedMemory.memory) : false
|
||||
return linkedMemory ? memoryIdUnusedNote(linkedMemory.memory) : undefined
|
||||
})
|
||||
|
||||
let scopedFields = $derived(
|
||||
@@ -415,7 +415,7 @@
|
||||
label={spec.label}
|
||||
tooltip={spec.tooltip}
|
||||
{chatInputEnabled}
|
||||
{memoryOff}
|
||||
{memoryUnusedNote}
|
||||
onRemoveKey={(key) => delete inputCheck[key]}
|
||||
>
|
||||
{#snippet field(key, error)}
|
||||
|
||||
@@ -1155,7 +1155,7 @@ components:
|
||||
Path of a reusable `ai_agent` resource (hybrid linking). When set, the agent brain
|
||||
config (provider/model/system prompt/etc.) and tool set are resolved at runtime from
|
||||
that resource; the module's input_transforms then only carry the flow-local inputs
|
||||
(user_message/user_attachments).
|
||||
(user_message, user_attachments and the history inputs memory_id and messages).
|
||||
tool_inputs:
|
||||
type: object
|
||||
description: |
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user