fix(stage-ui): character orchestrator should have prompts
This commit is contained in:
@@ -20,7 +20,7 @@ import { useProvidersStore } from './providers'
|
||||
|
||||
interface SparkNotifyCommandDraft {
|
||||
destinations: string[]
|
||||
interrupt?: 'force' | 'soft' | false
|
||||
interrupt?: 'force' | 'soft' | boolean
|
||||
priority?: 'critical' | 'high' | 'normal' | 'low'
|
||||
intent?: 'plan' | 'proposal' | 'action' | 'pause' | 'resume' | 'reroute' | 'context'
|
||||
ack?: string
|
||||
@@ -33,36 +33,45 @@ interface SparkNotifyResponse {
|
||||
commands?: SparkNotifyCommandDraft[]
|
||||
}
|
||||
|
||||
const SPARK_NOTIFY_INSTRUCTIONS = [
|
||||
'You are processing a spark:notify event for the character.',
|
||||
'Respond with a short, in-character reaction as plain text.',
|
||||
'If you need to instruct sub-agents, call the spark_command tool one or more times.',
|
||||
].join('\n')
|
||||
function getSparkNotifyHandlingAgentInstruction(moduleName: string) {
|
||||
return [
|
||||
'This is AIRI system, the life pod hosting your consciousness. You don\'t need to respond to me or every spark:notify event directly.',
|
||||
`Another module "${moduleName}" triggered spark:notify event for you to checkout.`,
|
||||
'You may call the built-in tool "builtIn_sparkCommand" to issue spark:command to sub-agents as needed.',
|
||||
'For any of the output that is not a tool call, it will be streamed to user\'s interface and maybe processed with text to speech system ',
|
||||
'to be played out loud as your actual reaction to the spark:notify event.',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
export const sparkCommandSchema = z.object({
|
||||
commands: z.array(z.object({
|
||||
destinations: z.array(z.string()).min(1),
|
||||
interrupt: z.union([z.literal('force'), z.literal('soft'), z.literal(false)]).optional(),
|
||||
priority: z.enum(['critical', 'high', 'normal', 'low']).optional(),
|
||||
intent: z.enum(['plan', 'proposal', 'action', 'pause', 'resume', 'reroute', 'context']).optional(),
|
||||
ack: z.string().optional(),
|
||||
destinations: z.array(z.string()).min(1).describe('List of sub-agent IDs to send the command to'),
|
||||
interrupt: z.enum(['force', 'soft', 'false']).nullable().describe('Interrupt type: force, soft, or false (no interrupt). A option to control whether this command is urgent enough to preempt ongoing tasks and require immediate attention.'),
|
||||
priority: z.enum(['critical', 'high', 'normal', 'low']).nullable().describe('Semantic priority of the command, this affects how sub-agents prioritize it (queues, interruption queues, mq, etc.).'),
|
||||
intent: z.enum(['plan', 'proposal', 'action', 'pause', 'resume', 'reroute', 'context']).nullable().describe('Intent of the command, indicating the nature of the instruction. If you attend to call other tools, use "plan" to reply with quick response to corresponding module / sub-agent.'),
|
||||
ack: z.string().describe('Acknowledgment content used to be passed to sub-agents upon command receipt.'),
|
||||
guidance: z.object({
|
||||
type: z.enum(['proposal', 'instruction', 'memory-recall']),
|
||||
persona: z.record(z.string(), z.enum(['very-high', 'high', 'medium', 'low', 'very-low'])).optional(),
|
||||
persona: z.array(z.object({
|
||||
strength: z.enum(['very-high', 'high', 'medium', 'low', 'very-low']),
|
||||
traits: z.string().describe('Trait name to adjust behavior. For example, "bravery", "cautiousness", "friendliness".'),
|
||||
})).nullable().describe('Personas can be used to adjust the behavior of sub-agents. For example, when using as NPC in games, or player in Minecraft, the persona can help define the character\'s traits and decision-making style.'),
|
||||
options: z.array(z.object({
|
||||
label: z.string(),
|
||||
steps: z.array(z.string()),
|
||||
rationale: z.string().optional(),
|
||||
possibleOutcome: z.array(z.string()).optional(),
|
||||
risk: z.enum(['high', 'medium', 'low', 'none']).optional(),
|
||||
fallback: z.array(z.string()).optional(),
|
||||
triggers: z.array(z.string()).optional(),
|
||||
label: z.string().describe('Short and brief label for this option, used for identification, should be within a sentence.'),
|
||||
steps: z.array(z.string()).describe('Step-by-step instructions for the sub-agent to follow, useful when providing detailed guidance.'),
|
||||
rationale: z.string().nullable().describe('How this option is derived or proposed, why it makes sense.'),
|
||||
possibleOutcome: z.array(z.string()).nullable().describe('Simulate possible outcomes of following this option.'),
|
||||
risk: z.enum(['high', 'medium', 'low', 'none']).nullable(),
|
||||
fallback: z.array(z.string()).nullable().describe('Fallback steps if the main steps cannot be completed.'),
|
||||
// TODO: consider to remove or enrich how triggers should work later
|
||||
triggers: z.array(z.string()).nullable().describe('Conditions or events that would trigger this option.'),
|
||||
})),
|
||||
}).optional(),
|
||||
contexts: z.array(z.any()).optional(),
|
||||
})),
|
||||
}).nullable().describe('Guidance for the sub-agent on how to interpret and execute the command with given context, persona settings, and reasoning.'),
|
||||
})).describe('List of commands to issue to sub-agents, you may produce multiple commands in response to multiple sub-agents by specifying their IDs in destination field. Empty array can be used for zero commands.'),
|
||||
})
|
||||
|
||||
export type SparkCommandSchema = z.infer<typeof sparkCommandSchema>
|
||||
|
||||
export const useCharacterOrchestratorStore = defineStore('character-orchestrator', () => {
|
||||
const { stream } = useLLM()
|
||||
const { activeProvider, activeModel } = storeToRefs(useConsciousnessStore())
|
||||
@@ -83,14 +92,55 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
const chatProvider = await providersStore.getProviderInstance<ChatProvider>(activeProvider.value)
|
||||
const commandDrafts: SparkNotifyCommandDraft[] = []
|
||||
|
||||
let noResponse = false
|
||||
|
||||
const sparkNoResponseTool = await tool({
|
||||
name: 'builtIn_sparkNoResponse',
|
||||
description: `Indicate that no response or action is needed for the current spark:notify event.`,
|
||||
parameters: z.object({}),
|
||||
execute: async (_payload) => {
|
||||
noResponse = true
|
||||
return 'AIRI System: Acknowledged, no response or action will be processed.'
|
||||
},
|
||||
})
|
||||
|
||||
const sparkCommandTool = await tool({
|
||||
name: 'spark_command',
|
||||
description: `Issue a spark:command to sub-agents.`,
|
||||
name: 'builtIn_sparkCommand',
|
||||
description: `Issue a spark:command to sub-agents. You can call this tool multiple times to issue matrices of commands to different sub-agents as needed.`,
|
||||
parameters: sparkCommandSchema,
|
||||
execute: async (payload) => {
|
||||
try {
|
||||
const validated = await validate(sparkCommandSchema, payload)
|
||||
commandDrafts.push(...validated.commands)
|
||||
commandDrafts.push(...validated.commands.map((cmd) => {
|
||||
const parsedCmd = {
|
||||
destinations: cmd.destinations,
|
||||
guidance: cmd.guidance
|
||||
? {
|
||||
type: cmd.guidance.type,
|
||||
persona: cmd.guidance?.persona?.reduce((acc, curr) => {
|
||||
acc[curr.traits] = curr.strength
|
||||
return acc
|
||||
}, {} as Record<string, 'very-high' | 'high' | 'medium' | 'low' | 'very-low'>) || undefined,
|
||||
options: cmd.guidance.options.map(opt => ({
|
||||
...opt,
|
||||
rationale: opt.rationale ?? undefined,
|
||||
possibleOutcome: opt.possibleOutcome?.length ? opt.possibleOutcome : undefined,
|
||||
risk: opt.risk ?? undefined,
|
||||
fallback: opt.fallback?.length ? opt.fallback : undefined,
|
||||
triggers: opt.triggers?.length ? opt.triggers : undefined,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
// TODO: contexts can be added later
|
||||
contexts: [],
|
||||
priority: cmd.priority || 'normal',
|
||||
intent: cmd.intent || 'action',
|
||||
ack: cmd.ack || undefined,
|
||||
interrupt: cmd.interrupt === 'false' || cmd.interrupt == null ? false : cmd.interrupt,
|
||||
} satisfies Omit<WebSocketEvents['spark:command'], 'id' | 'eventId' | 'parentEventId' | 'commandId'>
|
||||
|
||||
return parsedCmd
|
||||
}))
|
||||
}
|
||||
catch (error) {
|
||||
return `AIRI System: Error - invalid spark_command parameters: ${errorMessageFrom(error)}`
|
||||
@@ -104,7 +154,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
role: 'system',
|
||||
content: [
|
||||
systemPrompt.value,
|
||||
SPARK_NOTIFY_INSTRUCTIONS,
|
||||
getSparkNotifyHandlingAgentInstruction(event.source),
|
||||
].filter(Boolean).join('\n\n'),
|
||||
}
|
||||
|
||||
@@ -119,14 +169,25 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
let fullText = ''
|
||||
|
||||
await stream(activeModel.value, chatProvider, [systemMessage, userMessage], {
|
||||
tools: [sparkCommandTool],
|
||||
tools: [
|
||||
sparkNoResponseTool,
|
||||
sparkCommandTool,
|
||||
],
|
||||
onStreamEvent: async (streamEvent: StreamEvent) => {
|
||||
if (streamEvent.type === 'text-delta') {
|
||||
if (noResponse)
|
||||
return
|
||||
|
||||
characterStore.onSparkNotifyReactionStreamEvent(event.data.id, streamEvent.text)
|
||||
|
||||
fullText += streamEvent.text
|
||||
}
|
||||
if (streamEvent.type === 'finish') {
|
||||
if (noResponse) {
|
||||
characterStore.onSparkNotifyReactionStreamEnd(event.data.id, '')
|
||||
return
|
||||
}
|
||||
|
||||
characterStore.onSparkNotifyReactionStreamEnd(event.data.id, fullText)
|
||||
}
|
||||
if (streamEvent.type === 'error') {
|
||||
@@ -165,7 +226,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
eventId: nanoid(),
|
||||
parentEventId: event.data.id,
|
||||
commandId: nanoid(),
|
||||
interrupt: command.interrupt ?? false,
|
||||
interrupt: (command.interrupt === true ? 'force' : command.interrupt) ?? false,
|
||||
priority: command.priority ?? 'normal',
|
||||
intent: command.intent ?? 'action',
|
||||
ack: command.ack,
|
||||
|
||||
Reference in New Issue
Block a user