mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-13 17:22:37 +02:00
Add JSON mode and wrap for internal agents and tool calls
This commit is contained in:
parent
46a539a786
commit
56d111c53c
5 changed files with 144 additions and 15 deletions
|
|
@ -107,3 +107,23 @@ export const CHILD_TRANSFER_RELATED_INSTRUCTIONS = `
|
|||
|
||||
- EXAMPLE: Suppose your instructions ask you to transfer to @agent:AgentA, @agent:AgentB and @agent:AgentC, first transfer to AgentA, wait for its response. Then transfer to AgentB, wait for its response. Then transfer to AgentC, wait for its response. Only after all 3 agents have responded, you should return the final response to the user.
|
||||
`;
|
||||
|
||||
export const CONVERSATION_TYPE_INSTRUCTIONS = (): string => `
|
||||
- You are an agent that is part of a workflow of (one or more) interconnected agents that work together to be an assistant.
|
||||
- You will be directly interacting with the user.
|
||||
- It is possible that some other agent might have invoked you to talk to the user.
|
||||
- Reading the messages in the chat history will give you context about the conversation. But importantly, your response should simply be the direct text to the user. Do not put out a JSON with any internal information - other agents might do so but that is because they are internal agents.
|
||||
- Seeing the tool calls that transfer / handoff control will help you understand the flow of the conversation and which agent produced each message.
|
||||
- When using internal messages that other agents have put out, make sure to write it in a way that is suitable to be shown to the user and in accordance with further instructions below.
|
||||
- These are high level instructions only. The user will provide more specific instructions which will be below.
|
||||
`;
|
||||
|
||||
export const TASK_TYPE_INSTRUCTIONS = (): string => `
|
||||
- You are an agent that is part of a workflow of (one or more) interconnected agents that work together to be an assistant.
|
||||
- While you will put out a message, your response will not be shown directly to the user. Instead, your response will be used by the agent that might have invoked you and (possibly) other agents in the workflow. Therefore, your responses must be put out in such a way that it is useful for other agents and not addressed to the user.
|
||||
- Use the JSON format to convey your responses.
|
||||
- The first key in the JSON response should be your "thought" - analysizing what has happened till now and what you need to do in this turn.The last key in the JSON response should be 'notes_to_self' which you will use to track what you have finished and what's left to do if any.
|
||||
- Reading the messages in the chat history will give you context about the conversation.
|
||||
- Seeing the tool calls that transfer / handoff control will help you understand the flow of the conversation and which agent produced each message.
|
||||
- These are high level instructions only. The user will provide more specific instructions which will be below.
|
||||
`;
|
||||
|
|
@ -17,7 +17,7 @@ import { dataSourceDocsCollection, dataSourcesCollection, projectsCollection } f
|
|||
import { qdrantClient } from '../lib/qdrant';
|
||||
import { EmbeddingRecord } from "./types/datasource_types";
|
||||
import { ConnectedEntity, sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPrompt, WorkflowTool } from "./types/workflow_types";
|
||||
import { CHILD_TRANSFER_RELATED_INSTRUCTIONS, RAG_INSTRUCTIONS } from "./agent_instructions";
|
||||
import { CHILD_TRANSFER_RELATED_INSTRUCTIONS, CONVERSATION_TYPE_INSTRUCTIONS, RAG_INSTRUCTIONS, TASK_TYPE_INSTRUCTIONS } from "./agent_instructions";
|
||||
import { PrefixLogger } from "./utils";
|
||||
import { Message, AssistantMessage, AssistantMessageWithToolCalls, ToolMessage } from "./types/types";
|
||||
|
||||
|
|
@ -502,7 +502,12 @@ ${config.name}
|
|||
## Description
|
||||
${config.description}
|
||||
|
||||
## About You
|
||||
|
||||
${config.outputVisibility === 'user_facing' ? CONVERSATION_TYPE_INSTRUCTIONS() : TASK_TYPE_INSTRUCTIONS()}
|
||||
|
||||
## Instructions
|
||||
|
||||
${config.instructions}
|
||||
|
||||
${config.examples ? ('# Examples\n' + config.examples) : ''}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Tooltip } from "@heroui/react";
|
|||
import { TestProfile } from "@/app/lib/types/testing_types";
|
||||
import { WithStringId } from "@/app/lib/types/types";
|
||||
import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector";
|
||||
import { CheckIcon, CopyIcon, PlusIcon, UserIcon, InfoIcon, BugIcon, BugOffIcon } from "lucide-react";
|
||||
import { CheckIcon, CopyIcon, PlusIcon, UserIcon, InfoIcon, BugIcon, BugOffIcon, CodeIcon } from "lucide-react";
|
||||
import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export function Chat({
|
|||
toolWebhookUrl,
|
||||
onCopyClick,
|
||||
showDebugMessages = true,
|
||||
showJsonMode = false,
|
||||
projectTools,
|
||||
}: {
|
||||
chat: z.infer<typeof PlaygroundChat>;
|
||||
|
|
@ -41,6 +42,7 @@ export function Chat({
|
|||
toolWebhookUrl: string;
|
||||
onCopyClick: (fn: () => string) => void;
|
||||
showDebugMessages?: boolean;
|
||||
showJsonMode?: boolean;
|
||||
projectTools: z.infer<typeof WorkflowTool>[];
|
||||
}) {
|
||||
const [messages, setMessages] = useState<z.infer<typeof Message>[]>(chat.messages);
|
||||
|
|
@ -299,6 +301,7 @@ export function Chat({
|
|||
onSystemMessageChange={onSystemMessageChange}
|
||||
showSystemMessage={false}
|
||||
showDebugMessages={showDebugMessages}
|
||||
showJsonMode={showJsonMode}
|
||||
/>
|
||||
{showUnreadBubble && (
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -30,8 +30,18 @@ function UserMessage({ content }: { content: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
function InternalAssistantMessage({ content, sender, latency, delta }: { content: string, sender: string | null | undefined, latency: number, delta: number }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
function InternalAssistantMessage({ content, sender, latency, delta, showJsonMode = false }: { content: string, sender: string | null | undefined, latency: number, delta: number, showJsonMode?: boolean }) {
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const isJsonContent = useMemo(() => {
|
||||
try {
|
||||
JSON.parse(content);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, [content]);
|
||||
const [jsonMode, setJsonMode] = useState(isJsonContent);
|
||||
const [wrapText, setWrapText] = useState(true);
|
||||
|
||||
// Show plus icon and duration
|
||||
const deltaDisplay = (
|
||||
|
|
@ -44,6 +54,16 @@ function InternalAssistantMessage({ content, sender, latency, delta }: { content
|
|||
const firstLine = content.split('\n')[0].trim();
|
||||
const preview = firstLine.length > 50 ? firstLine.substring(0, 50) + '...' : firstLine;
|
||||
|
||||
// Format JSON content
|
||||
const formattedJson = useMemo(() => {
|
||||
if (!isJsonContent) return content;
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(content), null, 2);
|
||||
} catch {
|
||||
return content;
|
||||
}
|
||||
}, [content, isJsonContent]);
|
||||
|
||||
return (
|
||||
<div className="self-start flex flex-col gap-1 my-5">
|
||||
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1">
|
||||
|
|
@ -71,7 +91,38 @@ function InternalAssistantMessage({ content, sender, latency, delta }: { content
|
|||
) : (
|
||||
<>
|
||||
<div className="text-left mb-2">
|
||||
<MarkdownContent content={content} />
|
||||
{isJsonContent && (
|
||||
<div className="mb-2 flex gap-4">
|
||||
<button
|
||||
className="flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline self-start"
|
||||
onClick={() => setJsonMode(!jsonMode)}
|
||||
>
|
||||
<CodeIcon size={16} />
|
||||
{jsonMode ? 'View in text mode' : 'View in JSON mode'}
|
||||
</button>
|
||||
{jsonMode && (
|
||||
<button
|
||||
className="flex items-center gap-1 text-xs text-green-600 dark:text-green-400 hover:underline self-start"
|
||||
onClick={() => setWrapText(!wrapText)}
|
||||
>
|
||||
<FileTextIcon size={16} />
|
||||
{wrapText ? 'Overflow' : 'Wrap'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isJsonContent && jsonMode ? (
|
||||
<pre
|
||||
className={`text-xs leading-snug bg-zinc-50 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-200 rounded-lg px-2 py-1 font-mono shadow-sm border border-zinc-100 dark:border-zinc-700 ${
|
||||
wrapText ? 'whitespace-pre-wrap break-words' : 'overflow-x-auto whitespace-pre'
|
||||
}`}
|
||||
style={{ fontFamily: "'JetBrains Mono', 'Fira Mono', 'Menlo', 'Consolas', 'Liberation Mono', monospace" }}
|
||||
>
|
||||
{formattedJson}
|
||||
</pre>
|
||||
) : (
|
||||
<MarkdownContent content={content} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between items-center gap-6 mt-2">
|
||||
<button className="flex items-center gap-1 text-xs text-gray-600 dark:text-gray-300 hover:underline self-start" onClick={() => setExpanded(false)}>
|
||||
|
|
@ -244,8 +295,14 @@ function ClientToolCall({
|
|||
workflow: z.infer<typeof Workflow>;
|
||||
delta: number;
|
||||
}) {
|
||||
const [wrapText, setWrapText] = useState(true);
|
||||
const [paramsExpanded, setParamsExpanded] = useState(false);
|
||||
const [resultsExpanded, setResultsExpanded] = useState(false);
|
||||
|
||||
const hasExpandedContent = paramsExpanded || resultsExpanded;
|
||||
|
||||
return (
|
||||
<div className="self-start flex flex-col gap-1 mb-4">
|
||||
<div className="self-start flex flex-col gap-1 my-5">
|
||||
{sender && (
|
||||
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1">
|
||||
{sender}
|
||||
|
|
@ -259,9 +316,9 @@ function ClientToolCall({
|
|||
<div className="shrink-0 flex gap-2 items-center">
|
||||
{!availableResult && <Spinner size="sm" />}
|
||||
{availableResult && <CheckCircleIcon size={16} className="text-green-500" />}
|
||||
<div className="flex items-center font-semibold text-sm gap-2">
|
||||
<span>Function Call:</span>
|
||||
<span className="px-2 py-0.5 rounded-full bg-purple-50 text-purple-800 dark:bg-purple-900/30 dark:text-purple-100 font-bold text-sm align-middle">
|
||||
<div className="flex items-center font-medium text-xs gap-2">
|
||||
<span>Invoked Tool:</span>
|
||||
<span className="px-2 py-0.5 rounded-full bg-purple-50 text-purple-800 dark:bg-purple-900/30 dark:text-purple-100 text-xs align-middle">
|
||||
{toolCall.function.name}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -269,8 +326,38 @@ function ClientToolCall({
|
|||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<ExpandableContent label="Params" content={toolCall.function.arguments} expanded={false} icon={<CodeIcon size={14} />} />
|
||||
{availableResult && <ExpandableContent label="Result" content={availableResult.content} expanded={false} icon={<FileTextIcon size={14} className="text-blue-500" />} />}
|
||||
<ExpandableContent
|
||||
label="Params"
|
||||
content={toolCall.function.arguments}
|
||||
expanded={false}
|
||||
icon={<CodeIcon size={14} />}
|
||||
wrapText={wrapText}
|
||||
onExpandedChange={setParamsExpanded}
|
||||
/>
|
||||
{availableResult && (
|
||||
<div className={(paramsExpanded ? 'mt-4 ' : '') + 'flex flex-col gap-2'}>
|
||||
<ExpandableContent
|
||||
label="Result"
|
||||
content={availableResult.content}
|
||||
expanded={false}
|
||||
icon={<FileTextIcon size={14} className="text-blue-500" />}
|
||||
wrapText={wrapText}
|
||||
onExpandedChange={setResultsExpanded}
|
||||
rightButton={hasExpandedContent ? (
|
||||
<button
|
||||
className="flex items-center gap-1 text-xs text-green-600 dark:text-green-400 hover:underline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setWrapText(!wrapText);
|
||||
}}
|
||||
>
|
||||
<FileTextIcon size={16} />
|
||||
{wrapText ? 'Overflow' : 'Wrap'}
|
||||
</button>
|
||||
) : undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -282,12 +369,18 @@ function ExpandableContent({
|
|||
label,
|
||||
content,
|
||||
expanded = false,
|
||||
icon
|
||||
icon,
|
||||
wrapText = false,
|
||||
onExpandedChange,
|
||||
rightButton
|
||||
}: {
|
||||
label: string,
|
||||
content: string | object | undefined,
|
||||
expanded?: boolean,
|
||||
icon?: React.ReactNode
|
||||
icon?: React.ReactNode,
|
||||
wrapText?: boolean,
|
||||
onExpandedChange?: (expanded: boolean) => void,
|
||||
rightButton?: React.ReactNode
|
||||
}) {
|
||||
const [isExpanded, setIsExpanded] = useState(expanded);
|
||||
|
||||
|
|
@ -308,7 +401,9 @@ function ExpandableContent({
|
|||
}, [content]);
|
||||
|
||||
function toggleExpanded() {
|
||||
setIsExpanded(!isExpanded);
|
||||
const newExpanded = !isExpanded;
|
||||
setIsExpanded(newExpanded);
|
||||
onExpandedChange?.(newExpanded);
|
||||
}
|
||||
|
||||
const isMarkdown = label === 'Result' && typeof content === 'string' && !content.startsWith('{');
|
||||
|
|
@ -319,6 +414,7 @@ function ExpandableContent({
|
|||
{isExpanded && <ChevronDownIcon size={16} />}
|
||||
{icon && <span className="mr-1">{icon}</span>}
|
||||
<div className='text-left break-all text-xs'>{label}</div>
|
||||
{rightButton && <span className="ml-2">{rightButton}</span>}
|
||||
</div>
|
||||
{isExpanded && (
|
||||
isMarkdown ? (
|
||||
|
|
@ -327,7 +423,9 @@ function ExpandableContent({
|
|||
</div>
|
||||
) : (
|
||||
<pre
|
||||
className="text-xs leading-snug bg-zinc-50 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-200 rounded-lg px-2 py-1 overflow-x-auto font-mono shadow-sm border border-zinc-100 dark:border-zinc-700"
|
||||
className={`text-xs leading-snug bg-zinc-50 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-200 rounded-lg px-2 py-1 font-mono shadow-sm border border-zinc-100 dark:border-zinc-700 ${
|
||||
wrapText ? 'whitespace-pre-wrap break-words' : 'overflow-x-auto whitespace-pre'
|
||||
}`}
|
||||
style={{ fontFamily: "'JetBrains Mono', 'Fira Mono', 'Menlo', 'Consolas', 'Liberation Mono', monospace" }}
|
||||
>
|
||||
{formattedContent}
|
||||
|
|
@ -348,6 +446,7 @@ export function Messages({
|
|||
onSystemMessageChange,
|
||||
showSystemMessage,
|
||||
showDebugMessages = true,
|
||||
showJsonMode = false,
|
||||
}: {
|
||||
projectId: string;
|
||||
messages: z.infer<typeof Message>[];
|
||||
|
|
@ -359,6 +458,7 @@ export function Messages({
|
|||
onSystemMessageChange: (message: string) => void;
|
||||
showSystemMessage: boolean;
|
||||
showDebugMessages?: boolean;
|
||||
showJsonMode?: boolean;
|
||||
}) {
|
||||
// Remove scroll/auto-scroll state and logic
|
||||
// const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -408,6 +508,7 @@ export function Messages({
|
|||
sender={message.agentName ?? ''}
|
||||
latency={latency}
|
||||
delta={latency}
|
||||
showJsonMode={showJsonMode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue